Related
Hello i am trying to learn some basics by making some mistakes.
I am making a basic python based snake game, using as little of non built in modules as i can.
at the moment the main one is tkinter.
Some of my logic is changing a member of a list, when i do not expect it do so and i can't see why.
The change is happening at line 60 (21) where index 1 is changed to equal index 0
when only index 0 should change only when "middle" occurs on line 71 (31)
the error is within this function
self.player[1] changes when i did not expect
def move(self):
print("old snake pos {}".format(self.player))
for i in range(len(self.player)-1, -1, -1):
# runs through loop backwards as the positions of the next piece needs to be known
print(i)
if i == len(self.player)-1:
print("last piece")
if self.eat():
print("eat = True")
self.player.append(self.player[-1])
print("{} changed from {} to {}".format(i, self.player[i], self.player[i-1]))
self.player[i] = self.player[i-1]
else:
if i == 0:
print("Head")
print(self.vel)
print(self.player[0])
print(self.player[1])
self.player[0][0] = self.player[0][0] + self.vel[0]
print("why has it changed????????")
print(self.player[0])
print(self.player[1])
self.player[0][1] = self.player[0][1] + self.vel[1]
print(self.player[1])
print(self.player)
continue
print("middle piece")
print("{} changed from {} to {}".format(i, self.player[i], self.player[i - 1]))
self.player[i] = self.player[i - 1]
print(self.player[i])
print(self.player[i])
print(self.player)
print("new snake pos {}".format(self.player))
i have tried different checks, but it doesnt seem to be doing any line that i dont expect at the right time.
and the full code is this:
# programme for learning more about creating graphical displays in Python
# will start with some investigation and use of tkinter resource
# then move to creating a snake style game
import tkinter as tk
import random as r
import time
class SnakeApp(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.setup()
self.gameloop()
def mat2str(self, matrix):
string = ""
for y in matrix:
for x in y:
string += x
string += "\n"
return string
# random an int co-ordinate
def newpos(self):
return [r.randint(0, self.wx), r.randint(0, self.wy)]
# check if the fruit has been eaten
def eat(self):
if any([s == self.fruit for s in self.player]):
print("fruit col detected {}".format(self.fruit))
# new position
self.fruit = self.newpos()
# increase the game speed by 2.5%
self.dt = self.dt * 0.975
# need to add a segment to the player
return True
# move the game 1 turn and compute all the logic before the next frame is drawn
def move(self):
print("old snake pos {}".format(self.player))
for i in range(len(self.player)-1, -1, -1):
# runs through loop backwards as the positions of the next piece needs to be known
print(i)
if i == len(self.player)-1:
print("last piece")
if self.eat():
print("eat = True")
self.player.append(self.player[-1])
print("{} changed from {} to {}".format(i, self.player[i], self.player[i-1]))
self.player[i] = self.player[i-1]
else:
if i == 0:
print("Head")
print(self.vel)
print(self.player[0])
print(self.player[1])
self.player[0][0] = self.player[0][0] + self.vel[0]
print("why has it changed????????")
print(self.player[0])
print(self.player[1])
self.player[0][1] = self.player[0][1] + self.vel[1]
print(self.player[1])
print(self.player)
continue
print("middle piece")
print("{} changed from {} to {}".format(i, self.player[i], self.player[i - 1]))
self.player[i] = self.player[i - 1]
print(self.player[i])
print(self.player[i])
print(self.player)
print("new snake pos {}".format(self.player))
def up(self, event):
print("up")
if self.vel != [0, 1]:
self.vel = [0, -1]
def down(self, event):
print("down")
if self.vel != [0, -1]:
self.vel = [0, 1]
def left(self, event):
print("left")
if self.vel != [1, 0]:
self.vel = [-1, 0]
def right(self, event):
print("right")
if self.vel != [-1, 0]:
self.vel = [1, 0]
def drawempty(self, wx, wy):
frame = []
for y in range(wy):
xlayer = []
for x in range(wx):
xlayer.append("-")
frame.append(xlayer)
return frame
def redraw(self, player, object):
# self.drawempty(self.wx, self.wy)
# replaced redraw each frame with a static empty frame build
print(self.gameframe)
self.gameframe = self.drawempty(self.wx, self.wy)
print(self.gameframe)
# set the string in the co-ord of the object to A
# check for collision needs to occur before this - all game logic before
# set the string in the co-ords of all players to *
print(object)
self.gameframe[object[1]][object[0]] = "A"
for b in player:
self.gameframe[b[1]][b[0]] = "*"
def setup(self):
# set game size
self.wx = 20
self.wy = 20
self.master.geometry("300x300")
self.vel = [-1, 0]
self.dt = 1
# create text matrices of spaces of size wx * wy
#self.gameframe = tk.Variable()
self.gameframe = []
#self.emptyframe = tk.Variable()
self.emptyframe = self.drawempty(self.wx, self.wy)
self.gameframe = self.emptyframe
# create a player and fruit object in the space
self.player = [[round(self.wx / 2), round(self.wy / 2)],[round(self.wx / 2) + 1, round(self.wy / 2)],[round(self.wx / 2) + 2, round(self.wy / 2)]]
self.fruit = self.newpos()
self.redraw(self.player, self.fruit)
self.game = tk.Text(self, height=self.wy, width=self.wx)
self.game.pack()
self.game.insert(tk.END, self.mat2str(self.gameframe))
self.master.bind("<Up>", self.up)
self.master.bind("<Down>", self.down)
self.master.bind("<Left>", self.left)
self.master.bind("<Right>", self.right)
def gameloop(self):
while True:
self.redraw(self.player, self.fruit)
self.game.delete('1.0', tk.END)
self.game.insert(tk.END, self.mat2str(self.gameframe))
self.move()
self.master.update()
time.sleep(self.dt)
Snake = tk.Tk()
app = SnakeApp(master=Snake)
The problem can be found at your move function as this point (Line 122)
self.player[i] = self.player[i - 1] # i == 1, self.player[1] = self.player[0]
At the point when i is 1 this code block is reached and it sets self.player[1] to self.player[0]
The problem is that self.player[0] after that is equal by reference to self.player[1], which means that when one changes, the other does as well (which you said was your problem).
What you need to do to prevent that from happening is to create a copy of self.player[i-1] and set that equal to self.player[i]. This can be achieved through multiple ways.
In your case because you have a list of lists the following should be fine
self.player[i] = [item for item in self.player[i-1]] # Create a new list
if you want use a built-in functions for this you can use deepcopy
from copy import deepcopy
# ...
self.player[i] = deepcopy(self.player[i-1])
Note that this behavior can also be found on lines 25,43, 104 and 122. So you'll have to change all of them to create a copy as shown above.
The code that should be modified is:
self.player[i] = self.player[i - 1]
When you change element 1 to be element 0, you are not making a copy but making them pointing to the same element, which is 0. So when you change element 0, element 1 change as well.
Example why this leads to what you see in code:
player = [[1,2], [3,4]]
i = 1
player[i] = player[i - 1]
player[0][0] = 100
print(player)
[[100, 2], [100, 2]]
One quick solution to create a copy:
self.player[i] = [x for x in self.player[i - 1]]
Good Day. I'm a beginner in coding, and basically I made a simple maze game using python.
it's 3x3 so I made 9 dictionary variables that detailed the possible movement for each tile (North, south, east, west.)
my code consists of nested if-elif as well as a main while loop.
if level == 1: #level of difficulty.
i = 0
maplist1 = {'directions1':'south'}
maplist2 = {'directions1':'south','directions2':'east'}
maplist3 = {'directions1':'west'}
maplist4 = {'directions1':'north','directions2':'east','directions3':'south'}
....
....
current_position = 1 #local variable within the first if statement
Here's a snippet of the code block that would be repeated, with little to no variations (literally repeating) to it.
while i == 0: #to continuously loop the program
if current_position == 1:
directions = maplist1['directions1']
direction = input(f"What direction do you want to go: {directions} ").title() #choose the possible directions to go to.
if direction == "S" or direction == "South":
current_position = 4
print(f"You are in cell {current_position}.")
else:
print("Cannot go in that direction.")
if current_position == 2:
directions = maplist2['directions1']
directions2 = maplist2['directions2']
direction = input(f"What direction do you want to go: {directions} {directions2} ").title()
if direction == "S" or direction == "South":
current_position = 5
print(f"You are in cell {current_position}.")
elif direction == "E" or direction == "East":
current_position = 3
print(f"You are in cell {current_position}.")
else:
print("Cannot go in that direction.")
if current_position == 3:
directions = maplist3['directions1']
direction = input(f"What direction do you want to go: {directions} ").title()
if direction == "W" or direction == "West":
current_position = 2
print(f"You are in cell {current_position}.")
else:
print("Cannot go in that direction.")
if current_position == 4:
directions = maplist4['directions1']
directions2 = maplist4['directions2']
directions3 = maplist4['directions3']
direction = input(f"What direction do you want to go: {directions} {directions2} {directions3} ").title()
if direction == "N" or direction == "North":
current_position = 1
print(f"You are in cell {current_position}.")
elif direction == "S" or direction == "South":
current_position = 7
print(f"You are in cell {current_position}.")
elif direction == "E" or direction == "East":
current_position = 5
print(f"You are in cell {current_position}.")
else:
print("Cannot go in that direction.")
.......
......
.......
if current_position == 9:
print("Congratulations, you finished the game.")
i += 1 #to stop the loop
my question is how to make this simpler, and more compact?
Current location must be remembered every movement
I know how to use def but quite unsure on how to implement it in my maze game.
There are 3 levels to this game, basically 3x3, 4x4, and 5x5. that's the reason why I am asking for any ideas on how to shorten/compact the code.
I don't need you to give me your code, but some guidance would be great on how to proceed because i am feeling lost right now.
Try creating a maze data structure. One typical idea is to create a 2D array of cells, each with a north/west/south/east wall.
Let's create a class, just for better understanding. I want you to ignore everything except the can_move() method.
class Cell:
def __init__(self, walls):
self.walls = walls
def __str__(self):
"""Allows us to call print() on a Cell!"""
return '\n'.join(self.draw())
def _draw_wall(self, wall, s):
return s if wall in self.walls else ' ' * len(s)
def draw(self, inner=' '):
"""Draw top, mid, and bottom parts of the cell."""
n = self._draw_wall('N', '___')
w = self._draw_wall('W', '|')
e = self._draw_wall('E', '|')
s = self._draw_wall('S', '___')
top = ' ' + n + ' '
mid = w + inner + e
bot = w + s + e
return top, mid, bot
def can_move(self, direction):
return direction not in self.walls
Let's make some cells and see what they look like:
>>> Cell('NWSE')
___
| |
|___|
>>> Cell('NWS')
___
|
|___
Now, a maze is made out of a 2D list of cells. Here's a small maze:
maze = Maze(width=3, height=3, rows=[
[Cell('NWE'), Cell('NW'), Cell('NE')],
[Cell('WE'), Cell('WE'), Cell('WE')],
[Cell('WS'), Cell('SE'), Cell('WSE')]
])
Which looks like this:
>>> maze
___ ___ ___
| x || |
| || |
| || || |
| || || |
| || |
|___ ___||___|
But wait! We haven't defined what Maze is. Once again, ignore all the drawing related stuff and focus on move_direction().
class Maze:
def __init__(self, width, height, rows):
self.width = width
self.height = height
self.rows = rows
self.position = (0, 0)
def __str__(self):
return '\n'.join(self.draw_row(i) for i, _ in enumerate(self.rows))
def _inner(self, i, j):
return ' x ' if (i, j) == self.position else ' '
def draw_row(self, i):
triples = [
cell.draw(self._inner(i, j)) for j, cell in enumerate(self.rows[i])
]
return '\n'.join([
''.join(t for t, _, _ in triples),
''.join(m for _, m, _ in triples),
''.join(b for _, _, b in triples)])
def cell_at_position(self, i, j):
return self.rows[i][j]
def move_direction(self, direction):
curr_cell = self.cell_at_position(*self.position)
if not curr_cell.can_move(direction):
print("Can't go in that direction!")
return
deltas = {'N': (-1, 0), 'W': (0, -1), 'S': (1, 0), 'E': (0, 1)}
y, x = self.position
dy, dx = deltas[direction]
self.position = y + dy, x + dx
To use this, just create a simple small loop:
while True:
print(maze)
direction = input('What direction? ')
maze.move_direction(direction)
And watch the magic happen!
You can try it here.
Instead of thinking the maze in 1D i.e the current_position(1,2,3..,9), you should think and code it like a 2D matrix space((0,0),(0,1)).
And Instead of hard coding the base maze to 3, we should be thinking in terms of n x n maze, as now that you have implemented the logic for a 3 x 3, now you should be able to expand it to n x n.
And you handled the possible directions using your maps instead, we should write the logic for boundary conditions, like a method to check the directions possible on each step,
def possible_path(current_x, current_y, matrix_size=3):
# this returns like directions possible like current_x + 1 < matrix_size
# then the east direction is possible.
pass
Direction handling should be handled somewhat like this,
def position_handler(row, col, direction):
# can write the logic for mapping the direction to the movement in X and Y,
# if direction == 'NORTH'
# or can get the config from a map like,
# move = movement.get(direction)
# say NORTH config is =>movement= {'NORTH': {'x': 0, 'y':1}}
# and now move accordingly i.e
# row += move.get('x')
# col += move.get('y')
pass
Exploit rules instead of hardcoding anything.
If you have a 3x3 maze, your (x,y) indexes are somthing like
0,0 1,0 2,0
0,1 1,1 2,1
0,2 1,2 2,2
You can store this as list of lists:
field = [ ["one","two","three"],["four","five","six"],["seven","eight","nine"] ]
pos = (0,0)
p = field[pos[0]][pos[1]] # would be "one"
# rules for movement based on actual position:
move_right = pos[0] < (3-1)
move_left = pos[0] > 0
move_up = pos[1] > 0
move_down = pos[1] < (3-1)
You can use functions to code this:
# p is a coordinate (0,0) etc tuple of x and y
# as functions
def can_move_right(p, dim=3):
return p[0] < dim-1
def can_move_left(p,dim=3):
return p[0] > 0
def can_move_up(p, dim=3):
return p[1] > 0
def can_move_down(p,dim=3):
return p[1] < dim-1
def move_up(p):
if can_move_up(p):
return (p[0],p[1]-1)
# implicitly returns None if not possible
def move_down(p):
if can_move_down(p):
return (p[0],p[1]+1)
# etc
new_pos = move_up( (1,1) ) # => (1,0)
new_pos = move_down( (1,1) ) # => (1,2)
new_pos = move_up( (0,0) ) # None - need to handle it
if not new_pos:
print("Wrong move")
To adjust for n x n you just need to give other dim - the rules are the same.
I am making a puzzle game with Python, it's supposed to be a 3x3 slide puzzle. I've made a class that stores name/id and x,y coordinates.
In the function up() I want to move the asterisk (*) up one row and in theory it should work but apparently not. I have a function that finds the class instance associated with two coordinates (find()) and it returns an instance of the class called symbol.
Layout of the slide puzzle:
1 2 3
4 5 6
7 8 *
When I try to assign the new values to the class instance above me (represented by the 6) nothing gets assigned. I would like to use the global keyword but that seems like such a hassle because then I would have to check every coordinate and then say what variable to change.
This is a puzzle game so it's really important that the player can move around without issues. I haven't tried anything else since I don't know how to proceed from this point.
class symbol:
def __init__(self, x, y, val):
self.x = x
self.y = y
self.val = val
s1 = symbol(1, 1, "1")
s2 = symbol(2, 1, "2")
s3 = symbol(3, 1, "3")
s4 = symbol(1, 2, "4")
s5 = symbol(2, 2, "5")
s6 = symbol(3, 2, "6")
s7 = symbol(1, 3, "7")
s8 = symbol(2, 3, "8")
s9 = symbol(3, 3, "*")
def getPos():
xPos = 0
yPos = 0
for element in symbols:
if element.val == "*":
xPos = element.x
yPos = element.y
else:
pass
return xPos, yPos
def find(x, y):
xPos = 0
yPos = 0
found = symbol(100, 100, "FIND")
for element in symbols:
if element.x == x and element.y == y:
found.x = x
found.y = y
found.val = element.val
else:
pass
return found
def up():
x, y = getPos()
if y > 1:
newBlock = find(x, y-1)
myBlock = find(x, y)
myBlock.val = newBlock.val
newBlock.val = "*"
clear()
draw()
print(f"myBlock: {myBlock.val} ({myBlock.x}, {myBlock.y}), newBlock: {newBlock.val} ({newBlock.x}, {newBlock.y})")
else:
pass
#cannot go higher
The problem is that your find function is making a new symbol and returning that. When up() modifies the result of find(), it's just changing some copy that lives outside of (what I assume is) your list of symbols.
One solution would be to just return the element from the list in find. i.e.
for element in symbols:
if element.x == x and element.y == y:
return element
return None
So I am programming a checkers game, and the problem I am having is with creating several pieces in a loop. I have the class creation part of the code, which I won't post here, and the rest, which I'm posting, but I don't know how to change the variable the loop is about to use. If you can lend me a hand and clear this out, I would be thankful.
Sorry for posting my code as image, I'm new to this website ( and programming) and couldn't format so that the website would accept my post. I really hope it's ok for you guys to help me!
Thanks for the help!
Further clarification: I need to use a different "piece" creation everytime the loop runs. That means the first loop has to create piece1, then piece2, then piece3... and so forward
EDIT: Posting whole code. I know format is wrong, can't help it. So, hope somebody can fix it.
class Piece:
def __init__(self, kind, yposition, xposition):
self.color = kind
self.ypos = xposition
self.xpos = yposition
def getColor(self):
return self.getColor
def adjustY(self, change):
self.ypos = self.ypos + change
def adjustX(self, change):
self.xpos = self.xpos + change
def getY(self):
return self.ypos
def getX(self):
return self.xpos
def mover(self, direction):
self.direc = direction
if self.direc == "right" and self.color == "white":
for n in alist:
if n.getY == (self.getY - 1) and n.getX == (self.getX + 1):
pass
# NOT YET IMPLEMENTED
else:
self.adjustY(-1)
self.adjustX(+1)
elif self.direc == "left" and self.color == "white":
for n in alist:
if n.getY == (self.getY - 1) and n.getX == (self.getX - 1):
pass
# NOT YET IMPLEMENTED
else:
self.adjustY(-1)
self.adjustX(-1)
elif self.direc == "right" and self.color == "black":
for n in alist:
if n.getY == (self.getY + 1) and n.getX == (self.getX + 1):
pass
# NOT YET IMPLEMENTED
else:
self.adjustY(+1)
self.adjustX(+1)
else:
for n in alist:
if n.getY == (self.getY + 1) and n.getX == (self.getX - 1):
pass
# NOT YET IMPLEMENTED
else:
self.adjustY(+1)
self.adjustX(-1)
piece1 = 0
piece2 = 0
piece3 = 0
piece4 = 0
piece5 = 0
piece6 = 0
piece7 = 0
piece8 = 0
piece9 = 0
piece10 = 0
piece11 = 0
piece12 = 0
alistb1 = [piece1,piece2,piece3,piece4,piece5,piece6,piece7,piece8,piece9,piece10,piece11,piece12]
k = 2
for i in range(0,11):
if i >= 0 and i <5:
j = 8
m = 0
elif i >= 5 and i < 9:
j = 7
m = 1
else:
j = 6
m = 0
alistb1[i] = Piece("white",j,(m + 1 + i * k))
print(alistb1[i].getY())
# print(piece7.getY()) test reasons
PS: def mover is not ready yet.
You do not need to assign a variable for each piece. You are already using a list for your pieces. Instead of writing piece1, you can just write pieces[0]. (You do need to note that lists start with index 0.)
range has an exclusive right bound. This means that it is not included, your range ends with one less than that value. You want to use range(0,12).
In python, you can add to lists dynamically. You do not need to allocate enough spaces to fit your pieces. You can use the .append() method of lists.
One way to write your code now is this:
pieces = []
for i in range(0, 12): # 0-11
if i < 5:
pieces.append(Piece("white", 8, 1 + i*2))
elif i < 9:
pieces.append(Piece("white", 7, 2 + i*2))
else:
pieces.append(Piece("white", 6, 1 + i*2))
I took the liberty of simplifying your conditional statements (i will always be >= 0 and if i < 5 is false, then the inverse, i >= 5, is true, so you don't need to restate it in your elif) and getting rid of j, k, and m which are unnecessary variables and can be replaced with literals to save memory.
One more thing: your implementation of getColor will return the function object itself. I think you wanted to do:
def getColor():
return self.color
Use a dictionary and a for loop:
pieces = {}
# I'm assuming you want 12 pieces since your list has 12 pieces
for i in range(1,13): # range starts at m so, range(m,n) iterates from m up to n-1
# I would suggest using more descriptive variable names if you can, row or column for example
if i >= 0 and i <5:
j = 8
m = 0
elif i >= 5 and i < 9:
j = 7
m = 1
else:
j = 6
m = 0
pieces['piece{}'.format(i)] = Piece("white",j,(m + 1 + i * k))
This should do what you want unless I am misunderstanding you. Also this isn't C++ you don't need those get methods you can simply Piece.color to get the color attribute of a piece.
Use the dictionary to access the pieces, pieces['piece1'].whatever(). However for brevity's sake you don't need to pieces['piece{}.format(i)] you can just pieces[i] and the piece would be accessed pieces[1].whatever().
More info on dictionaries http://docs.python.org/3.3/tutorial/datastructures.html#dictionaries
What I have and it works with no errors:
class Piece:
def __init__(self, kind, yposition, xposition):
self.color = kind
self.ypos = xposition
self.xpos = yposition
def getColor(self):
return self.getColor
def adjustY(self, change):
self.ypos = self.ypos + change
def adjustX(self, change):
self.xpos = self.xpos + change
def getY(self):
return self.ypos
def getX(self):
return self.xpos
def mover(self, direction):
self.direc = direction
if self.direc == "right" and self.color == "white":
for n in alist:
if n.getY == (self.getY - 1) and n.getX == (self.getX + 1):
pass
# NOT YET IMPLEMENTED
else:
self.adjustY(-1)
self.adjustX(+1)
elif self.direc == "left" and self.color == "white":
for n in alist:
if n.getY == (self.getY - 1) and n.getX == (self.getX - 1):
pass
# NOT YET IMPLEMENTED
else:
self.adjustY(-1)
self.adjustX(-1)
elif self.direc == "right" and self.color == "black":
for n in alist:
if n.getY == (self.getY + 1) and n.getX == (self.getX + 1):
pass
# NOT YET IMPLEMENTED
else:
self.adjustY(+1)
self.adjustX(+1)
else:
for n in alist:
if n.getY == (self.getY + 1) and n.getX == (self.getX - 1):
pass
# NOT YET IMPLEMENTED
else:
self.adjustY(+1)
self.adjustX(-1)
k=2
pieces = {}
# I'm assuming you want 12 pieces since your list has 12 pieces
for i in range(1,13): # range starts at m so, range(m,n) iterates from m up to n-1
# I would suggest using more descriptive variable names if you can, row or column for example
if i >= 0 and i <5:
j = 8
m = 0
elif i >= 5 and i < 9:
j = 7
m = 1
else:
j = 6
m = 0
pieces['piece{}'.format(i)] = Piece("white",j,(m + 1 + i * k))
Output:
>>> pieces['piece1'].color
'white'
>>> pieces['piece3'].color
'white'
>>> pieces['piece3'].xpos
8
>>> for key in pieces:
print(key, pieces[key])
piece8 <__main__.Piece object at 0x000000000329A4E0>
piece9 <__main__.Piece object at 0x000000000329A550>
piece6 <__main__.Piece object at 0x000000000329A400>
piece7 <__main__.Piece object at 0x000000000329A470>
piece4 <__main__.Piece object at 0x000000000329A320>
piece5 <__main__.Piece object at 0x000000000329A390>
piece2 <__main__.Piece object at 0x0000000003287DA0>
piece3 <__main__.Piece object at 0x000000000329A2B0>
piece1 <__main__.Piece object at 0x00000000031D9CF8>
piece10 <__main__.Piece object at 0x000000000329A5C0>
piece11 <__main__.Piece object at 0x000000000329A630>
piece12 <__main__.Piece object at 0x000000000329A6A0>
>>> pieces['piece3'].mover('right')
Traceback (most recent call last):
File "<pyshell#13>", line 1, in <module>
pieces['piece3'].mover('right')
File "C:/Users/Hannah/Documents/thing.py", line 25, in mover
for n in alist:
NameError: global name 'alist' is not defined
>>> pieces['piece3'].xpos
8
>>> pieces['piece3'].adjustX(1)
>>> pieces['piece3'].xpos
9
Keep in mind dictionaries are unordered so the order they print in is arbitrary.
The traceback on mover is expected since I don't have alist in my version of the code. You will need to modify mover() to work with the dictionary. Some helpful ways to work with dicts:
>>> for n in pieces.values(): # iterates over the values in a dict
n.color
'white'
'white'
'white'
'white'
'white'
'white'
'white'
'white'
'white'
'white'
'white'
'white'
>>> for n in pieces.keys(): # iterates over the keys
print(n)
piece8
piece9
piece6
piece7
piece4
piece5
piece2
piece3
piece1
piece10
piece11
piece12
I am currently working on my Python game, in ika, which uses python 2.5
I decided to use A* pathfinding for the AI. However, I find it too slow for my needs (3-4 enemies can lag the game, but I would like to supply up to 4-5 without problems). I know, that such complex search like A* is not mean to be scripted in python, but I am pretty sure, that my pathfinder is also implemented in the wrong way.
My question is: How can I speed up this algorithm?
I wrote my own binary heap, and there are some try: except: lines inside some functions. Those lines can create large overhead? Are there better methods maintaining the open list?
I supplied the algorithm with graphics interface, for testing purposes (when the pathfinder finishes searching, it will write the number of iterations and seconds it takes to find the path, inside the ika.txt file. Also, Pressing A will do a complete search, and S does that step by step.)
Graphical version:
http://data.hu/get/6084681/A_star.rar
Also, here is a pastebin version:
http://pastebin.com/9N8ybX5F
Here is the main code I use for pathfinding:
import ika
import time
class Node:
def __init__(self,x,y,parent=None,g=0,h=0):
self.x = x
self.y = y
self.parent = parent
self.g = g
self.h = h
def cost(self):
return self.g + self.h
def equal(self,node):
if self.x == node.x and self.y == node.y:
return True
else:
return False
class Emerald_Pathfinder:
def __init__(self):
pass
def setup(self,start,goal):
self.start = start
self.goal = goal
self.openlist = [None,start] # Implemented as binary heap
self.closedlist = {} # Implemented as hash
self.onopenlist = {} # Hash, for searching the openlist
self.found = False
self.current = None
self.iterations = 0
def lowest_cost(self):
pass
def add_nodes(self,current):
nodes = []
x = current.x
y = current.y
self.add_node(x+1,y,current,10,nodes)
self.add_node(x-1,y,current,10,nodes)
self.add_node(x,y+1,current,10,nodes)
self.add_node(x,y-1,current,10,nodes)
# Dont cut across corners
up = map.is_obstacle((x,y-1),x,y-1)
down = map.is_obstacle((x,y+1),x,y+1)
left = map.is_obstacle((x-1,y),x-1,y)
right = map.is_obstacle((x+1,y),x+1,y)
if right == False and down == False:
self.add_node(x+1,y+1,current,14,nodes)
if left == False and up == False:
self.add_node(x-1,y-1,current,14,nodes)
if right == False and up == False:
self.add_node(x+1,y-1,current,14,nodes)
if left == False and down == False:
self.add_node(x-1,y+1,current,14,nodes)
return nodes
def heuristic(self,x1,y1,x2,y2):
return (abs(x1-x2)+abs(y1-y2))*10
def add_node(self,x,y,parent,cost,list):
# If not obstructed
if map.is_obstacle((x,y),x,y) == False:
g = parent.g + cost
h = self.heuristic(x,y,self.goal.x,self.goal.y)
node = Node(x,y,parent,g,h)
list.append(node)
def ignore(self,node,current):
# If its on the closed list, or open list, ignore
try:
if self.closedlist[(node.x,node.y)] == True:
return True
except:
pass
# If the node is on the openlist, do the following
try:
# If its on the open list
if self.onopenlist[(node.x,node.y)] != None:
# Get the id number of the item on the real open list
index = self.openlist.index(self.onopenlist[(node.x,node.y)])
# If one of the coordinates equal, its not diagonal.
if node.x == current.x or node.y == current.y:
cost = 10
else:
cost = 14
# Check, is this items G cost is higher, than the current G + cost
if self.openlist[index].g > (current.g + cost):
# If so, then, make the list items parent, the current node.
self.openlist[index].g = current.g + cost
self.openlist[index].parent = current
# Now resort the binary heap, in the right order.
self.resort_binary_heap(index)
# And ignore the node
return True
except:
pass
return False
def resort_binary_heap(self,index):
m = index
while m > 1:
if self.openlist[m/2].cost() > self.openlist[m].cost():
temp = self.openlist[m/2]
self.openlist[m/2] = self.openlist[m]
self.openlist[m] = temp
m = m / 2
else:
break
def heap_add(self,node):
self.openlist.append(node)
# Add item to the onopenlist.
self.onopenlist[(node.x,node.y)] = node
m = len(self.openlist)-1
while m > 1:
if self.openlist[m/2].cost() > self.openlist[m].cost():
temp = self.openlist[m/2]
self.openlist[m/2] = self.openlist[m]
self.openlist[m] = temp
m = m / 2
else:
break
def heap_remove(self):
if len(self.openlist) == 1:
return
first = self.openlist[1]
# Remove the first item from the onopenlist
self.onopenlist[(self.openlist[1].x,self.openlist[1].y)] = None
last = self.openlist.pop(len(self.openlist)-1)
if len(self.openlist) == 1:
return last
else:
self.openlist[1] = last
v = 1
while True:
u = v
# If there is two children
if (2*u)+1 < len(self.openlist):
if self.openlist[2*u].cost() <= self.openlist[u].cost():
v = 2*u
if self.openlist[(2*u)+1].cost() <= self.openlist[v].cost():
v = (2*u)+1
# If there is only one children
elif 2*u < len(self.openlist):
if self.openlist[2*u].cost() <= self.openlist[u].cost():
v = 2*u
# If at least one child is smaller, than parent, swap them
if u != v:
temp = self.openlist[u]
self.openlist[u] = self.openlist[v]
self.openlist[v] = temp
else:
break
return first
def iterate(self):
# If the open list is empty, exit the game
if len(self.openlist) == 1:
ika.Exit("no path found")
# Expand iteration by one
self.iterations += 1
# Make the current node the lowest cost
self.current = self.heap_remove()
# Add it to the closed list
self.closedlist[(self.current.x,self.current.y)] = True
# Are we there yet?
if self.current.equal(self.goal) == True:
# Target reached
self.goal = self.current
self.found = True
print self.iterations
else:
# Add the adjacent nodes, and check them
nodes_around = self.add_nodes(self.current)
for na in nodes_around:
if self.ignore(na,self.current) == False:
self.heap_add(na)
def iterateloop(self):
time1 = time.clock()
while 1:
# If the open list is empty, exit the game
if len(self.openlist) == 1:
ika.Exit("no path found")
# Expand iteration by one
self.iterations += 1
# Make the current node the lowest cost
self.current = self.heap_remove()
# Add it to the closed list
self.closedlist[(self.current.x,self.current.y)] = True
# Are we there yet?
if self.current.equal(self.goal) == True:
# Target reached
self.goal = self.current
self.found = True
print "Number of iterations"
print self.iterations
break
else:
# Add the adjacent nodes, and check them
nodes_around = self.add_nodes(self.current)
for na in nodes_around:
if self.ignore(na,self.current) == False:
self.heap_add(na)
time2 = time.clock()
time3 = time2-time1
print "Seconds to find path:"
print time3
class Map:
def __init__(self):
self.map_size_x = 20
self.map_size_y = 15
self.obstructed = {} # Library, containing x,y couples
self.start = [2*40,3*40]
self.unit = [16*40,8*40]
def is_obstacle(self,couple,x,y):
if (x >= self.map_size_x or x < 0) or (y >= self.map_size_y or y < 0):
return True
try:
if self.obstructed[(couple)] != None:
return True
except:
return False
def render_screen():
# Draw the Character
ika.Video.DrawRect(map.start[0],map.start[1],map.start[0]+40,map.start[1]+40,ika.RGB(40,200,10),1)
# Draw walls
for x in range(0,map.map_size_x):
for y in range(0,map.map_size_y):
if map.is_obstacle((x,y),x,y) == True:
ika.Video.DrawRect(x*40,y*40,(x*40)+40,(y*40)+40,ika.RGB(168,44,0),1)
# Draw openlist items
for node in path.openlist:
if node == None:
continue
x = node.x
y = node.y
ika.Video.DrawRect(x*40,y*40,(x*40)+40,(y*40)+40,ika.RGB(100,100,100,50),1)
# Draw closedlist items
for x in range(0,map.map_size_x):
for y in range(0,map.map_size_y):
try:
if path.closedlist[(x,y)] == True:
ika.Video.DrawRect(x*40,y*40,(x*40)+20,(y*40)+20,ika.RGB(0,0,255))
except:
pass
# Draw the current square
try:
ika.Video.DrawRect(path.current.x*40,path.current.y*40,(path.current.x*40)+40,(path.current.y*40)+40,ika.RGB(128,128,128), 1)
except:
pass
ika.Video.DrawRect(mouse_x.Position(),mouse_y.Position(),mouse_x.Position()+8,mouse_y.Position()+8,ika.RGB(128,128,128), 1)
# Draw the path, if reached
if path.found == True:
node = path.goal
while node.parent:
ika.Video.DrawRect(node.x*40,node.y*40,(node.x*40)+40,(node.y*40)+40,ika.RGB(40,200,200),1)
node = node.parent
# Draw the Target
ika.Video.DrawRect(map.unit[0],map.unit[1],map.unit[0]+40,map.unit[1]+40,ika.RGB(128,40,200),1)
def mainloop():
while 1:
render_screen()
if mouse_middle.Pressed():
# Iterate pathfinder
if path.found == False:
path.iterateloop()
elif mouse_right.Pressed():
# Iterate pathfinder by one
if path.found == False:
path.iterate()
elif ika.Input.keyboard["A"].Pressed():
# Iterate pathfinder
if path.found == False:
path.iterateloop()
elif ika.Input.keyboard["S"].Pressed():
# Iterate pathfinder by one
if path.found == False:
path.iterate()
elif mouse_left.Position():
# Add a square to the map, to be obstructed
if path.iterations == 0:
x = mouse_x.Position()
y = mouse_y.Position()
map.obstructed[(int(x/40),int(y/40))] = True
# Mouse preview
x = mouse_x.Position()
y = mouse_y.Position()
mx = int(x/40)*40
my = int(y/40)*40
ika.Video.DrawRect(mx,my,mx+40,my+40,ika.RGB(150,150,150,70),1)
ika.Video.ShowPage()
ika.Input.Update()
map = Map()
path = Emerald_Pathfinder()
path.setup(Node(map.start[0]/40,map.start[1]/40),Node(map.unit[0]/40,map.unit[1]/40))
mouse_middle = ika.Input.mouse.middle
mouse_right = ika.Input.mouse.right
mouse_left = ika.Input.mouse.left
mouse_x = ika.Input.mouse.x
mouse_y = ika.Input.mouse.y
# Initialize loop
mainloop()
I appreciate any help!
(sorry for any spelling mistakes, English is not my native language)
I think a proper implementation in python will be fast enough for your purposes. But the boost library has an astar implementation and python bindings. https://github.com/erwinvaneijk/bgl-python