Kos Kos - 3 months ago 5
Python Question

Not all objects transferring from one list to another in beginner grid-based game

This has been driving me nuts.

I'm developing a grid-based movement engine for a game. Character instances move around using the "move" function, which reduces their internal moves_left variable by 1 every time.

def move(self, direction): #how characters move around
if self.collision_check(direction) == True:
print("Collision")
return
if self.moves_left == 0:
print("No more moves left")
Map.update()
return

elif direction == "UP":
self.internal_row -= 1
elif direction == "LEFT":
self.internal_column -= 1
elif direction == "RIGHT":
self.internal_column += 1
elif direction == "DOWN":
self.internal_row += 1

self.moves_left = self.moves_left -1
Map.update()


When this variable reaches 0, they are supposed to stop moving and be transferred from the "can move" list of characters to the "no moves" list of characters. This check is in the Map.update() function.

for characterobject in range(0, len(Map.no_moves)-1): #This moves any characters with moves to the can move list
if len(Map.no_moves) > 0:
if Map.no_moves[characterobject].moves_left > 0:
print("character moved from no moves to moves")
Map.can_move.append(Map.no_moves[characterobject])
Map.no_moves.remove(Map.no_moves[characterobject])

for characterobject in range(0, len(Map.can_move)-1):
if len(Map.can_move) == 0:
break
elif Map.can_move[characterobject].moves_left == 0: #This moves any characters with 0 moves from the can't move list to the can move list
print("character moved from moves to no moves")
Map.no_moves.append(Map.can_move[characterobject])
Map.can_move.remove(Map.can_move[characterobject])


The problem that I'm having is that the check is not being made. When a moving character reaches moves_left = 0, the move function prints "no moves left" and Map.update() is called, but the character object stays in the list and is not transferred to the no_moves list.

Here is the full code:

import random
import pygame
import math

pygame.init()
Clock = pygame.time.Clock()
Screen = pygame.display.set_mode([650, 650])
DONE = False
MAPSIZE = 50 #how many tiles


TILEWIDTH = 10 #pixel size of tile
TILEHEIGHT = 10
TILEMARGIN = 2

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
BROWN = (123, 123, 0)
MOVECOLOR = (150, 250, 150)

ITEMS = ["Sword", "Helmet", "Shield", "Coin"] #just a test

KeyLookup = {
pygame.K_LEFT: "LEFT",
pygame.K_RIGHT: "RIGHT",
pygame.K_DOWN: "DOWN",
pygame.K_UP: "UP"
}


class MapTile(object): #The main class for stationary things that inhabit the grid ... grass, trees, rocks and stuff.
def __init__(self, name, internal_column, internal_row):
self.name = name
self.internal_column = internal_column
self.internal_row = internal_row


class Item(object):
def __init__(self, name, weight):
self.name = name
self.weight = weight


class Character(object): #can_move can move around and do cool stuff
def __init__(self, name, HP, internal_column, internal_row):
self.name = name
self.HP = HP
self.internal_column = internal_column
self.internal_row = internal_row

inventory = []
moves_left = 25

def move(self, direction): #how characters move around
if self.collision_check(direction) == True:
print("Collision")
return
if self.moves_left == 0:
print("No more moves left")
Map.update()
return

elif direction == "UP":
self.internal_row -= 1
elif direction == "LEFT":
self.internal_column -= 1
elif direction == "RIGHT":
self.internal_column += 1
elif direction == "DOWN":
self.internal_row += 1

self.moves_left = self.moves_left - 1
Map.update()

def collision_check(self, direction):
if direction == "UP":
if self.internal_row == 0:
return True
if len(Map.Grid[self.internal_column][(self.internal_row)-1]) > 1:
return True
elif direction == "LEFT":
if self.internal_column == 0:
return True
if len(Map.Grid[self.internal_column-1][(self.internal_row)]) > 1:
return True
elif direction == "RIGHT":
if self.internal_column == MAPSIZE-1:
return True
if len(Map.Grid[self.internal_column+1][(self.internal_row)]) > 1:
return True
elif direction == "DOWN":
if self.internal_row == MAPSIZE-1:
return True
if len(Map.Grid[self.internal_column][(self.internal_row)+1]) > 1:
return True

return False

def location(self):
print("Coordinates:" + str(self.internal_column) + ", " + str(self.internal_row))

def check_inventory(self):
weight = 0
for item in self.inventory:
print(item)
weight = weight + item.weight
print(weight)



class Map(object): #The main class; where the action happens
global MAPSIZE
can_move = []
no_moves = []
Grid = []

for row in range(MAPSIZE): # Creating grid
Grid.append([])
for column in range(MAPSIZE):
Grid[row].append([])

for row in range(MAPSIZE): #Filling grid with grass
for column in range(MAPSIZE):
TempTile = MapTile("Grass", column, row)
Grid[column][row].append(TempTile)

for row in range(MAPSIZE): #Putting some rocks near the top
for column in range(MAPSIZE):
TempTile = MapTile("Rock", column, row)
if row == 1:
Grid[column][row].append(TempTile)

for i in range(10): #Trees in random places
random_row = random.randint(0, MAPSIZE - 1)
random_column = random.randint(0, MAPSIZE - 1)
TempTile = MapTile("Tree", random_column, random_row)
Grid[random_column][random_row].append(TempTile)

def generate_hero(self): #Generate a character and place it randomly
random_row = random.randint(0, MAPSIZE - 1)
random_column = random.randint(0, MAPSIZE - 1)
id_number = len(Map.can_move)
temp_hero = Character(str(id_number), 10, random_column, random_row)
i = random.randint(0, len(ITEMS)-1)
temp_hero.inventory.append(ITEMS[i])

self.Grid[random_column][random_row].append(temp_hero)
self.can_move.append(temp_hero)
Map.update()


def update(self): #Important function
for column in range(MAPSIZE): #These nested loops go through entire grid
for row in range(MAPSIZE): #They check if any objects internal coordinates
for i in range(len(Map.Grid[column][row])): #disagree with its place on the grid and update it accordingly

if Map.Grid[column][row][i].internal_column != column:
TempChar = Map.Grid[column][row][i]
Map.Grid[column][row].remove(Map.Grid[column][row][i])
Map.Grid[int(TempChar.internal_column)][int(TempChar.internal_row)].append(TempChar)

elif Map.Grid[column][row][i].internal_row != row:
TempChar = Map.Grid[column][row][i]
Map.Grid[column][row].remove(Map.Grid[column][row][i])
Map.Grid[int(TempChar.internal_column)][int(TempChar.internal_row)].append(TempChar)

for characterobject in range(0, len(Map.no_moves)-1): #This moves any characters with moves to the can move list
if len(Map.no_moves) > 0:
if Map.no_moves[characterobject].moves_left > 0:
print("character moved from no moves to moves")
Map.can_move.append(Map.no_moves[characterobject])
Map.no_moves.remove(Map.no_moves[characterobject])

for characterobject in range(0, len(Map.can_move)-1):
print(str(characterobject))
if len(Map.can_move) == 0:
break
elif Map.can_move[characterobject].moves_left == 0: #This moves any characters with 0 moves from the can't move list to the can move list
print("character moved from moves to no moves")
Map.no_moves.append(Map.can_move[characterobject])
Map.can_move.remove(Map.can_move[characterobject])



Map = Map()
Map.generate_hero()

while not DONE: #Main pygame loop

for event in pygame.event.get(): #catching events
if event.type == pygame.QUIT:
DONE = True

elif event.type == pygame.MOUSEBUTTONDOWN:
Pos = pygame.mouse.get_pos()
column = Pos[0] // (TILEWIDTH + TILEMARGIN) #Translating the position of the mouse into rows and columns
row = Pos[1] // (TILEHEIGHT + TILEMARGIN)
print(str(row) + ", " + str(column))

for i in range(len(Map.Grid[column][row])):
print(str(Map.Grid[column][row][i].name)) #print stuff that inhabits that square

elif event.type == pygame.KEYDOWN:
if event.key == 97: # Keypress: a
print("new turn")
for characterobject in range(0, len(Map.no_moves)-1):
Map.no_moves[characterobject].moves_left = 25
Map.update()

elif event.key == 115: # Keypress: s
print("boop")
Map.generate_hero()
Map.update()

elif len(Map.can_move) > 0:
Map.can_move[0].move(KeyLookup[event.key])

else:
print("invalid")

Screen.fill(BLACK)



for row in range(MAPSIZE): # Drawing grid
for column in range(MAPSIZE):
for i in range(0, len(Map.Grid[column][row])):
Color = WHITE

if len(Map.can_move) > 0: # Creating colored area around character showing his move range
if (math.sqrt((Map.can_move[0].internal_column - column)**2 + (Map.can_move[0].internal_row - row)**2)) <= Map.can_move[0].moves_left:
Color = MOVECOLOR

if len(Map.Grid[column][row]) > 1:
Color = RED
if Map.Grid[column][row][i].name == "Tree":
Color = GREEN
if str(Map.Grid[column][row][i].__class__.__name__) == "Character":
Color = BROWN



pygame.draw.rect(Screen, Color, [(TILEMARGIN + TILEWIDTH) * column + TILEMARGIN,
(TILEMARGIN + TILEHEIGHT) * row + TILEMARGIN,
TILEWIDTH,
TILEHEIGHT])

Clock.tick(30)
pygame.display.flip()


pygame.quit()


Play around with it and see what I mean. You can press "s" to add a new character. Notice what happens in the shell when a character can no logner move. You're supposed to be able to press "a" to give characters in the no_moves list more moves, but that doesn't work either.

Thanks

J F J F
Answer

You shouldn’t iterate over an array you’re mutating, which is causing this problem. Instead:

arr = Map.no_moves[:] # copy
for item in arr:
    if item.moves_left == 0:
        Map.no_moves.remove(item)
        Map.can_move.append(item)

Note that this concept applies to almost every language, so it’s good to keep this pattern in your toolbox.

Comments