Multiprocessing with python objects - python

I've been doing a Genetic Algorithm to solve simple 2d platformer game levels. Now I want to add multiprocessing to execute the "update" of my population in parallel, yet I haven't been able to make it work as all working examples I've found are with simple arrays, even those with shared memory, and I need to work with nested objects and shared memory.
Right now I have been trying the following code:
# divide players for processes
splits = np.array_split(groups.players_group, settings.PROCESSES)
# create processes
processes = []
for i in range(settings.PROCESSES):
p = mp.Process(target=update_players, args=(splits[i],))
processes.append(p)
processes[i].start()
# join processes (wait for 'em to finish)
for p in processes:
p.join()
Now, errors have not been clear, as now they just seem to have issues with finding objects (logs say that there are empty objects which don't have some required property for the update process, when they in fact should have it. So I am starting to believe the issue is with shared memory, which I don't know how to apply to python objects).
[EDIT]
I've tried wrapping my object lists with the multiprocessing.Array(), yet it gives the following error:
TypeError: unsupported operand type(s) for *: 'type' and 'int'
Also... here is the "update players" function:
def update_players(players, ret_queue):
for p in players:
p.update()
ret_queue.put(players)
and as you can see, it calls Player.Update(), which is insanely long but here it is in case you want to check it out:
def update(self):
if not self.is_dead and not self.reached_goal and not self.finished:
# act according to brain if necessary
if self.is_ai:
# reset movements
self.releaseLeft()
self.releaseRight()
if self.brain_step >= len(self.brain.instructions):
if not self.reached_goal and not self.isMoving():
self.finished = True
else:
self.executeNextBrainStep()
self.rect.y += self.y_spd
self.y_spd += self.gravity
# check if player is colliding with floor
for tile in groups.floor_tiles:
if helpers.rectsColliding(self.rect, tile.rect):
# check vertical collision
if self.rect.getCenterY() < tile.rect.y: # from top
self.y_spd = 0
self.rect.y = tile.rect.y - self.rect.height + 1
self.is_jumping = False
# from bottom
elif self.rect.getCenterY() > (tile.rect.y + tile.rect.height):
self.y_spd = 0
self.rect.y = tile.rect.y + tile.rect.height
# check horizontal collision
# should be within same vertical space
if self.rect.getCenterY() >= (tile.rect.getCenterY() - tile.rect.height/3):
if self.rect.getCenterY() <= (tile.rect.getCenterY() + tile.rect.height/3):
# now we can check the horizontal collision
if self.rect.getCenterX() < tile.rect.x: # from left
self.right = 0
self.rect.x = tile.rect.x - self.rect.width
# from right
elif self.rect.getCenterX() > (tile.rect.x + tile.rect.width):
self.left = 0
self.rect.x = tile.rect.x + tile.rect.width
self.dir = (self.right - self.left)
self.rect.x += self.dir * self.walk_spd
# check if dead
if self.rect.x > settings.SCR_W or (self.rect.x + self.rect.width) < 0:
self.is_dead = True
self.finished = True
if self.rect.y > settings.SCR_H or (self.rect.y + self.rect.height) < 0:
self.is_dead = True
self.finished = True
# if optimizing, check if should stop
if settings.OPTIMIZATION_FITNESS:
self_dist = helpers.dist_modular(self.rect.x, settings.goal.rect.x, self.rect.y, settings.goal.rect.y)
best_dist = helpers.dist_modular(settings.BEST_X, settings.goal.rect.x, settings.BEST_Y, settings.goal.rect.y)
if self_dist < best_dist:
self.finished = True
self.reached_goal = True
# check if reached goal
if helpers.objectsColliding(self, settings.goal):
self.reached_goal = True
self.finished = True
And in case any of you wants to check out the repo, here it is: https://github.com/santyarellano/GeneticLevelTester

Related

(Pygame) What is wrong with this function?

So, i have been working on a snake game made in Pygame. So far, everything is fine, except for one problem: When the snake eats the fruit, the fruit (that randomly spawns) sometimes appears inside the snake's body. So, to avoid this, i made this function:
def random_fruit(body_pos):
global general_fruit_x, general_fruit_y # Fruit rectangle coordinates
while True:
general_fruit_x = randrange(window[0] // snake.w) * snake.w # (Snake is a pygame.Rect)
general_fruit_y = randrange(window[1] // snake.h) * snake.h
if len(list(filter(lambda z: body_pos == (general_fruit_x, general_fruit_y), body_pos))) > 0:
continue # If the spawning position of the fruit is the same as the snake's body, we continue the loop
else:
break # If not, we are done
set_obj_coordinates(general_fruit, general_fruit_x, general_fruit_y) # set fruit random position
And implemented it in the main game loop:
if fruit_eated:
random_ind1 = random_ind2
snake_len += 1
apple_sound.play()
random_fruit(snake_pos) # snake_pos is a list of tuples with all snake's body coordinates
for m in range(3):
snake_imgs[random_ind1][m] = img("snake_" + snake_colors[random_ind1] + str(m + 1)) # Reset snake image
random_ind2 = randint(0, 3)
if x_move < 0:
rotate_imgs(90, random_ind1)
if x_move > 0:
rotate_imgs(-90, random_ind1)
if y_move > 0:
rotate_imgs(180, random_ind1)
if y_move < 0:
pass
But it seems that the random_fruit function ignores the condition of the snake's body.
Here is the complete code: https://github.com/n4tm/PySnake/tree/main/snake
You have to check if any position of the body is equal to the new random position of the fruit:
if len(list(filter(lambda z: body_pos == (general_fruit_x, general_fruit_y), body_pos))) > 0:`
if any(pos == (general_fruit_x, general_fruit_y) for pos in body_pos):
random_fruit function:
def random_fruit(body_pos):
global general_fruit_x, general_fruit_y
while True:
general_fruit_x = randrange(window[0] // snake.w) * snake.w
general_fruit_y = randrange(window[1] // snake.h) * snake.h
if not any(pos == (general_fruit_x, general_fruit_y) for pos in body_pos):
break
set_obj_coordinates(general_fruit, general_fruit_x, general_fruit_y)

How do I make two characters not run through each other when colliding in PYGAME?

I'm creating my first game and I am having some trouble handling collisions. What I have is a 2 player game played on the same keyboard, awsd and updownleftright controls. When the two players collide, I want them to not be able to move through each other. I'm having trouble figuring that out.
player_one_pos = [300,310]
player_two_pos = [600,310]
def detect_collision(player_one_pos, player_two_pos):
p1_x = player_one_pos[0]
p1_y = player_one_pos[1]
p2_x = player_two_pos[0]
p2_y = player_two_pos[1]
if (p1_x + player_width/2) == (p2_x - player_width/2):
return True
return False
if detect_collision(player_one_pos, player_two_pos):
## players collide, can't go through each other
I was able to get it done by adding an and statement. This fix should suffice for now.
if key_pressed[pygame.K_RIGHT] and x < (goal_right[0] - player_width) and (x != a - player_width):
x += speed_of_travel
if key_pressed[pygame.K_a] and a > (goal_left[0] + goal_width) and (a != x + player_width):
a -= speed_of_travel

How do I call out a class object?

I just currently finished making the game 'snake' as a practice to learn how to program, as I am new to programming for about 3 months.
Although the game is completed and runs the way I intended, I want to try to simplify my code and reduce the amount of lines as much as possible, and possibly make the script tidier as the current majority of my codes are cluster in the while loop.
Until now I haven't touched upon class objects, and I want everything in the while loop to go into individual classes that get called out from the while loop to reduce the amount of lines in it.
off-topic: by reading through the script, how else can I improve it to be run more efficiently, including simplifying some code as I may have over-complicated it?
I looked up how class object is used from w3school and other programming tutorials, but I still don't fully understand it as it only shows examples in using print. I did play around and experimented with class object examples and attempted to call them without using print, but I lack the knowledge of how to use them properly.
from graphics import *
from threading import Timer
import keyboard, random, time
# configurations
width = 400
gridHeight = width
height = 470
timer = False
game = True
score = 0
bonus = 0
x = 70
y = 30
radius = 10
length = radius * 2
playerLength = 3
poisonLength = playerLength
i = 0
k = 0
pointRadius = 5
points = False
cherryPoints = False
key = "Right"
countDown = 0
# set coordinations
cX = 90
cY = 30
coordX = [10]
coordY = [10]
while coordX[len(coordX)-1] != width-10:
cX+=20
coordX.append(cX)
while coordY[len(coordY)-1] != 390:
cY+=20
coordY.append(cY)
randomX = random.choice(coordX)
randomY = random.choice(coordY)
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
# window set up
win = GraphWin("SNAKE", width, height, autoflush = False)
win.setBackground(color_rgb(15,15,15))
# grid
lineX = 20
while lineX < width:
gridX = Line(Point(lineX,0),Point(lineX,gridHeight))
gridX.setOutline(color_rgb(25,25,25))
gridX.draw(win)
lineX += 20
lineY = 20
while lineY <= gridHeight:
gridX = Line(Point(0,lineY),Point(width,lineY))
gridX.setOutline(color_rgb(25,25,25))
gridX.draw(win)
lineY += 20
# snake banner
UI = Rectangle(Point(0,400),Point(width,height))
UI.setFill(color_rgb(102,51,0))
UI.setOutline(color_rgb(102,51,0))
UI.draw(win)
snakeTitle = Text(Point(width/2,420),"SNAKE")
snakeTitle.setTextColor("green")
snakeTitle.setSize(20)
snakeTitle.draw(win)
scoreTitle = Text(Point(320,424),"SCORE")
scoreTitle.setTextColor("white")
scoreTitle.setSize(10)
scoreTitle.draw(win)
scoreUI = Text(Point(320,435),score)
scoreUI.setTextColor("white")
scoreUI.setSize(10)
scoreUI.draw(win)
# make player
player = {}
player[0] = Rectangle(Point(x-20-radius,y-radius), Point(x-20+radius, y+radius))
player[1] = Rectangle(Point(x-40-radius,y-radius), Point(x-40+radius, y+radius))
player[2] = Rectangle(Point(x-60-radius,y-radius), Point(x-60+radius, y+radius))
# make poison
poison = {}
def main():
global timer, scoreUI, score, bonus, playerLength, poisonLength, x, y, points, cherryPoints, randomX, randomY, cherryRandomX, cherryRandomY, poisonRandomX, poisonRandomY, key, countDown, k, game
while(game==True):
# score update
scoreUI.undraw()
scoreUI = Text(Point(320,435),score)
scoreUI.setTextColor("white")
scoreUI.setSize(10)
scoreUI.draw(win)
# generating new body blocks
if len(player) < playerLength:
i+=1
player[i] = player[i-1].clone()
# body following player
player[0].undraw()
for i in range(1,len(player)):
player[len(player)-i].undraw()
player[len(player)-i] = player[len(player)-i-1].clone()
player[len(player)-i].draw(win)
# update player's head coordinate
player[0] = Rectangle(Point(x-radius,y-radius), Point(x+radius,y+radius))
player[0].setFill("green")
player[0].setWidth(2)
player[0].draw(win)
# player movement
if keyboard.is_pressed("Up") and key != "Down":
key = "Up"
elif keyboard.is_pressed("Left") and key != "Right":
key = "Left"
elif keyboard.is_pressed("Down") and key != "Up":
key = "Down"
elif keyboard.is_pressed("Right") and key != "Left":
key = "Right"
if key == "Up":
y -= length
elif key == "Left":
x -= length
elif key == "Down":
y += length
elif key == "Right":
x += length
# point
if points == False: # generates new point when eaten
point = Rectangle(Point(randomX-pointRadius,randomY-pointRadius),Point(randomX+pointRadius,randomY+pointRadius))
point.setFill("white")
point.setWidth(2)
point.draw(win)
points = True
if player[0].getCenter().getX() == point.getCenter().getX() and player[0].getCenter().getY() == point.getCenter().getY(): # when player eats the point
point.undraw()
playerLength += 1
poisonLength += 1
score += 200+bonus
randomX = random.choice(coordX)
randomY = random.choice(coordY)
for i in range(len(player)):
if (point.getCenter().getX() == player[i].getCenter().getX() and point.getCenter().getY() == player[i].getCenter().getY()) or (cherryPoints == True and cherryPoint.getCenter().getX() == point.getCenter().getX() and cherryPoint.getCenter().getY() == point.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and cherry
randomX = random.choice(coordX)
randomY = random.choice(coordY)
for i in range(len(poison)): # regenerate x and y coordinate if point shares the same coordinate to other array of poisons
if point.getCenter().getX() == poison[i].getCenter().getX() and point.getCenter().getY() == poison[i].getCenter().getY():
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
points = False
# cherry
if countDown == 150:
countDown = 0
if cherryPoints == False: # generates new cherry from countdown
cherryPoint = Rectangle(Point(cherryRandomX-pointRadius,cherryRandomY-pointRadius),Point(cherryRandomX+pointRadius,cherryRandomY+pointRadius))
cherryPoint.setFill(color_rgb(213,0,50))
cherryPoint.setWidth(2)
cherryPoint.draw(win)
cherryPoints = True
if cherryPoints == True:
for i in range(2, 6): # cherry blinks between countdown 40 to 100
if countDown == 20*i:
cherryPoint.undraw()
elif countDown == 10+(20*i):
cherryPoint.draw(win)
if countDown >= 100: # when countdown becomes 100, remove cherry and reset count down
cherryPoints = False
countDown = 0
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
if cherryPoints==True and player[0].getCenter().getX() == cherryPoint.getCenter().getX() and player[0].getCenter().getY() == cherryPoint.getCenter().getY(): # when player eats the cherry
cherryPoint.undraw()
score += 500
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
for i in range(len(player)):
if (cherryPoint.getCenter().getX() == player[i].getCenter().getX() and cherryPoint.getCenter().getY() == player[i].getCenter().getY()) or (cherryPoint.getCenter().getX() == point.getCenter().getX() and cherryPoint.getCenter().getY() == point.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and point
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
for i in range(len(poison)): # regenerate x and y coordinate if cherry shares the same coordinate to other array of poisons
if cherryPoint.getCenter().getX() == poison[i].getCenter().getX() and cherryPoint.getCenter().getY() == poison[i].getCenter().getY():
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
cherryPoints = False
# poison
if poisonLength % 5 == 0: # generates a poison block each time the player size reaches the multiple of 5
poison[k] = Rectangle(Point(poisonRandomX-pointRadius,poisonRandomY-pointRadius),Point(poisonRandomX+pointRadius,poisonRandomY+pointRadius))
poison[k].setFill("green")
poison[k].setWidth(2)
poison[k].draw(win)
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
for i in range(len(player)):
if (poison[k].getCenter().getX() == player[i].getCenter().getX() and poison[k].getCenter().getY() == player[i].getCenter().getY()) or (poison[k].getCenter().getX() == point.getCenter().getX() and poison[k].getCenter().getY() == point.getCenter().getY()) or (cherryPoints==True and poison[k].getCenter().getX() == cherryPoint.getCenter().getX() and poison[k].getCenter().getY() == cherryPoint.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and point and cherry
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
for i in range(len(poison)):
if poison[k].getCenter().getX() == poison[i].getCenter().getX() and poison[k].getCenter().getY() == poison[i].getCenter().getY(): # regenerate x and y coordinate if new poison shares the same coordinate to other array of poisons
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
bonus+=50
k+=1
poisonLength+=1
# game over requirements
for i in range(len(poison)): # if player touches poison
if player[0].getCenter().getX() == poison[i].getCenter().getX() and player[0].getCenter().getY() == poison[i].getCenter().getY():
game = False
for i in range(2, len(player)): # if player touches its own body or reach out of window
if (player[0].getCenter().getX() == player[i].getCenter().getX() and player[0].getCenter().getY() == player[i].getCenter().getY()) or x < 0 or x > width or y < 0 or y > gridHeight:
game = False
# FPS
update(10)
countDown += 1
# GAME OVER
gameOver = Text(Point(width/2,200), "GAME OVER")
gameOver.setTextColor("red")
gameOver.setSize(30)
gameOver.draw(win)
update()
time.sleep(2)
win.close()
main()
Ideally the result should replace each code in the while loop with individual classes outside of the function to reduce the amount of lines in the main() function and make the script easier to read.
Classes are essentially just bundles of code that contain various attributes and methods.
A Snake class might have a list of coordinates for each section of the body (the first is the head).
class Snake:
def __init__(self, x, y):
self.positions = [(x, y)]
def get_head(self):
return self.positions[0]
def move_forward(self):
self.positions.pop()
self.positions.insert(0, self.get_head()[1] + 1)
def move_backward(self):
self.positions.pop()
self.positions.insert(0, self.get_head()[1] - 1)
...
And so on. Classes, at this level, let you think of objects as concrete entities, distinct from each other but easily manipulated.

How can I change this to use a q table for reinforcement learning

I am working on learning q-tables and ran through a simple version which only used a 1-dimensional array to move forward and backward. now I am trying 4 direction movement and got stuck on controlling the person.
I got the random movement down now and it will eventually find the goal. but I want it to learn how to get to the goal instead of randomly stumbling on it. So I would appreciate any advice on adding a qlearning into this code. Thank you.
Here is my full code as it stupid simple right now.
import numpy as np
import random
import math
world = np.zeros((5,5))
print(world)
# Make sure that it can never be 0 i.e the start point
goal_x = random.randint(1,4)
goal_y = random.randint(1,4)
goal = (goal_x, goal_y)
print(goal)
world[goal] = 1
print(world)
LEFT = 0
RIGHT = 1
UP = 2
DOWN = 3
map_range_min = 0
map_range_max = 5
class Agent:
def __init__(self, current_position, my_goal, world):
self.current_position = current_position
self.last_postion = current_position
self.visited_positions = []
self.goal = my_goal
self.last_reward = 0
self.totalReward = 0
self.q_table = world
# Update the totoal reward by the reward
def updateReward(self, extra_reward):
# This will either increase or decrese the total reward for the episode
x = (self.goal[0] - self.current_position[0]) **2
y = (self.goal[1] - self.current_position[1]) **2
dist = math.sqrt(x + y)
complet_reward = dist + extra_reward
self.totalReward += complet_reward
def validate_move(self):
valid_move_set = []
# Check for x ranges
if map_range_min < self.current_position[0] < map_range_max:
valid_move_set.append(LEFT)
valid_move_set.append(RIGHT)
elif map_range_min == self.current_position[0]:
valid_move_set.append(RIGHT)
else:
valid_move_set.append(LEFT)
# Check for Y ranges
if map_range_min < self.current_position[1] < map_range_max:
valid_move_set.append(UP)
valid_move_set.append(DOWN)
elif map_range_min == self.current_position[1]:
valid_move_set.append(DOWN)
else:
valid_move_set.append(UP)
return valid_move_set
# Make the agent move
def move_right(self):
self.last_postion = self.current_position
x = self.current_position[0]
x += 1
y = self.current_position[1]
return (x, y)
def move_left(self):
self.last_postion = self.current_position
x = self.current_position[0]
x -= 1
y = self.current_position[1]
return (x, y)
def move_down(self):
self.last_postion = self.current_position
x = self.current_position[0]
y = self.current_position[1]
y += 1
return (x, y)
def move_up(self):
self.last_postion = self.current_position
x = self.current_position[0]
y = self.current_position[1]
y -= 1
return (x, y)
def move_agent(self):
move_set = self.validate_move()
randChoice = random.randint(0, len(move_set)-1)
move = move_set[randChoice]
if move == UP:
return self.move_up()
elif move == DOWN:
return self.move_down()
elif move == RIGHT:
return self.move_right()
else:
return self.move_left()
# Update the rewards
# Return True to kill the episode
def checkPosition(self):
if self.current_position == self.goal:
print("Found Goal")
self.updateReward(10)
return False
else:
#Chose new direction
self.current_position = self.move_agent()
self.visited_positions.append(self.current_position)
# Currently get nothing for not reaching the goal
self.updateReward(0)
return True
gus = Agent((0, 0) , goal)
play = gus.checkPosition()
while play:
play = gus.checkPosition()
print(gus.totalReward)
I have a few suggestions based on your code example:
separate the environment from the agent. The environment needs to have a method of the form new_state, reward = env.step(old_state, action). This method is saying how an action transforms your old state into a new state. It's a good idea to encode your states and actions as simple integers. I strongly recommend setting up unit tests for this method.
the agent then needs to have an equivalent method action = agent.policy(state, reward). As a first pass, you should manually code an agent that does what you think is right. e.g., it might just try to head towards the goal location.
consider the issue of whether the state representation is Markovian. If you could do better at the problem if you had a memory of all the past states you visited, then the state doesn't have the Markov property. Preferably, the state representation should be compact (the smallest set that is still Markovian).
once this structure is set-up, you can then think about actually learning a Q table. One possible method (that is easy to understand but not necessarily that efficient) is Monte Carlo with either exploring starts or epsilon-soft greedy. A good RL book should give pseudocode for either variant.
When you are feeling confident, head to openai gym https://www.gymlibrary.dev/ for some more detailed class structures. There are some hints about creating your own environments here: https://www.gymlibrary.dev/content/environment_creation/

pygame - Enemy flies off screen when collision with ground?

first time poster here.
Ill try to keep this as simple as possible. I am trying to make a game in pygame but it seems my collision detection is acting up. I have a player class that detects whether or not the player is colliding with the ground or any other objects in the environment list, and I have an enemy class that collides with anything in the same list. The enemies work out what direction they need to travel in in order to try and hit the player. There is gravity which is very likely to play a major part in the problem.
The problem is that when the 'flies' are placed in and fall to the ground, they immediately jump off screen, even though their X and Y values seem (according to logs of items on the screen) to not move at all?
As clarification, the two 'flies' are placed into a secondary list to allow for collision detection. Also, as a side note, the 'glitch' doesn't occur if there is no left and right collide detection... Thanks to anyone who can provide help :)
def collisions():
#Detection Working as intended
for fly in Fly.List:
fly_proj = pygame.sprite.spritecollide(fly, Projectile.List, True)
if len(fly_proj) > 0:
for hit in fly_proj:
fly.health -= 100
X = pygame.sprite.spritecollide(fly, current_level.object_list, False)
if len(X) == 0: #Gravity here:
if (fly.vspeed == 0):
fly.vspeed = 1
print("Gravity")
else:
fly.vspeed += 0.35
if len(X) > 1:
for hit in X:
if fly.vspeed > 0:
fly.rect.bottom = hit.rect.top +1
fly.vspeed = 0
elif fly.vspeed < 0:
fly.rect.top = hit.rect.bottom -1
elif fly.hspeed > 0:
fly.rect.right = hit.rect.left
fly.hspeed = 0
elif fly.hspeed < 0:
fly.rect.left = hit.rect.right
fly.hspeed = 0
print(len(X),framecounter, fly.vspeed, fly.hspeed)
#Kill fly if health reaches 0
if fly.health <= 0:
fly.destroy(Fly)
#Detect where the player is
if window_rect.contains(fly.rect):
fly.playersnapshot = player.rect.x
if fly.rect.x - player.rect.x >= -10:
#print("LEFTING")
fly.hspeed = -2
if fly.rect.x - player.rect.x <= 10:
fly.hspeed = 2
fly.truefalse = True
event = None
fly.rect.y += fly.vspeed
fly.rect.x += fly.hspeed
I think your if/else is incorrect.
Probably when fly touch ground you set vspeed to zero and then if/else checks hspeed and it use ground left/right to change fly left/right.
I know one method.
move fly vertically
check collision and use if/else with vspeed
move fly horizontally
check collision and use if/else with hspeed
--
EDIT:
# move horizontally only
fly.rect.x += fly.hspeed
X = pygame.sprite.spritecollide( ... )
for hit in X:
if fly.hspeed > 0:
fly.rect.right = hit.rect.left
else:
fly.rect.left = hit.rect.right
# move vertically only
fly.rect.y += fly.vspeed
X = pygame.sprite.spritecollide( ... )
for hit in X:
if fly.vspeed > 0:
fly.rect.bottom = hit.rect.top
else:
fly.rect.top = hit.rect.bottom
# on_ground = True
I found this method in source code of "Platform Jumper" on ProgramArcadeGames.com
see page with: platform_jumper.py

Categories