Search code examples
pythonpytorchpygameartificial-intelligencegame-physics

AI and Game physics


When I run my test, it works completely fine with one update. However, when I make my AI do the inputs, the cube just drops off the screen, with the player's position rapidly increasing positively, and the velocity and y for the player's position increasing negatively. And I’m stuck because I wouldn’t think it’s the collision with the floor where it falls out cause the test run works. Maybe it’s the rapid input of the AI? Here is the GitHub for the project physics-ai-game.

  • My current game file:
import pygame
from math import dist

pygame.init()
w, h = 1250, 720

class Direction:
    @staticmethod
    def update(left=0, right=0, jump=0):
        return left, right, jump

class Player:
    def __init__(self, x, y):
        self.rect = pygame.Rect(x, y, 10, 10)
        self.vel_x = 0
        self.vel_y = 0
        self.gravity = 0.2
        self.ground = False
        self.collision_tol = 5

    def update_velo(self, left, right, jump):
        self.vel_x = 300 if right == 1 else 0 if left == 1 else 0
        self.vel_y += self.gravity
        self.vel_y = -10 if jump == 1 else self.vel_y
        print("Velocity X:", self.vel_x)
        print("Velocity Y:", self.vel_y)

    def move(self):
        self.rect.x += self.vel_x
        self.rect.y += self.vel_y
        print("Player Position:", self.rect.x, self.rect.y)

    def collisions(self, objects, walls):

        for object in objects:
            if self.rect.colliderect(object):
                print("Collision detected with object:", object)
                if abs(self.rect.right - object.left) <= self.collision_tol:
                    self.rect.right = object.left
                    self.ground = True
                elif abs(self.rect.left - object.right) <= self.collision_tol:
                    self.rect.left = object.right
                    self.ground = True
                elif self.rect.bottom >= object.top:
                    self.rect.bottom = object.top
                    self.ground = True
                    self.vel_y = 0
                elif self.rect.top <= object.bottom:
                    self.rect.top = object.bottom
                    self.ground = False
                else:
                    self.ground = False
        
        for wall in walls:
            if self.rect.colliderect(wall):
                print("Collision detected with wall:", wall)
                if self.vel_x > 0:
                    self.rect.right = wall.left
                elif self.vel_x < 0:
                    self.rect.left = wall.right

                if self.vel_y > 0:
                    self.rect.bottom = wall.top
                    self.vel_y = 0
                    self.ground = True
                    
                elif self.vel_y < 0:
                    self.rect.top = wall.bottom
                    self.vel_y = 0

    def update(self, left, right, jump, objects, walls):
        self.update_velo(left, right, jump)
        self.move()
        self.collisions(objects, walls)
        print("Player position:", self.rect.x, self.rect.y)

class Game:
    def __init__(self):
        self.display = pygame.display.set_mode((w, h))
        pygame.display.set_caption("AI Game")
        self.clock = pygame.time.Clock()
        self.endpt = pygame.Rect(1200, h - 250, 20, 20)
        self.floor = pygame.Rect(0, h - 20, w, 200)
        self.left_wall = pygame.Rect(0, 0, 20, h)
        self.right_wall = pygame.Rect(w - 20, 0, 20, h)
        self.objects_for_lvl = [
            pygame.Rect(100, h - 100, 100, 80),
            pygame.Rect(250, h - 140, 100, 25),
            pygame.Rect(400, h - 170, 100, 25),
            pygame.Rect(535, h - 170, 75, 25),
            pygame.Rect(735, h - 170, 75, 25),
            pygame.Rect(900, h - 200, 150, 25),
            pygame.Rect(1150, h - 230, 200, 25)
        ]

        self.player = Player(30, 600)
        self.running = True
        self.reset()

    def reset(self):
        self.score = 0
        self.frame_iteration = 0

    def step(self, action):
        self.frame_iteration += 1

        for ev in pygame.event.get():
            if ev.type == pygame.QUIT:
                pygame.quit()
                quit()

        left, right, jump = action
        self.player.update(left, right, jump, self.objects_for_lvl, [self.floor, self.left_wall, self.right_wall])

        reward, game_over = self.calculate_score()

        self._update()

        return reward, game_over, self.score

    def calculate_score(self):
        reward = 0
        game_over = False

        if self.player.rect.colliderect(self.endpt):
            reward += 10
        elif self.player.rect.colliderect(self.floor):
            reward -= 5

        self.score += reward
        game_over = self.player.rect.colliderect(self.left_wall) or self.player.rect.colliderect(self.right_wall)

        return reward, game_over

    def _update(self):    
        self.display.fill('white')  

        for obj in self.objects_for_lvl:  
            pygame.draw.rect(self.display, (128, 128, 128), obj)

        pygame.draw.rect(self.display, (128, 128, 128), self.floor)  
        pygame.draw.rect(self.display, (128, 128, 128), self.left_wall)
        pygame.draw.rect(self.display, (128, 128, 128), self.right_wall)
    
        pygame.draw.rect(self.display, (255, 0, 0), self.player.rect)  # Draw player object
        pygame.draw.rect(self.display, (0, 255, 0), self.endpt)  

        pygame.display.update()


if __name__ == "__main__":
    game = Game()
    while game.running:
        action = Direction.update(1, 0, 0) # move left once 
        print("Action:", action)
        reward, game_over, score = game.step(action)
        if game_over:
            print("Game Over")
            game.running = False

Solution

  • this seems to be pretty difficult task. i think the problem is with the collisions. i made some minor changes to the code, it is still not working, but now you can use SPACE to make a step. like this you can see, that the red rectangle is moving up, then down and as soon it touches the ground, it stopps. it seems to be in an endless loop. might be easier to find the bug now.

    import pygame
    
    pygame.init()
    w, h = 1250, 720
    
    
    class Direction:
        @staticmethod
        def update(left=0, right=0, jump=0):
            return left, right, jump
    
    
    class Player:
        def __init__(self, x, y):
            self.rect = pygame.Rect(x, y + 1, 10, 10)
            self.vel_x = 0
            self.vel_y = 0
            self.gravity = 0.8
            self.ground = False
            self.collision_tol = 10
    
        def __repr__(self):
            return f"Player(x={self.rect.x}, y={self.rect.y}, vel_x={self.vel_x}, vel_y={self.vel_y}, ground={self.ground})"
    
        def update_velo(self, left, right, jump, objects, walls):
            self.vel_x = 10 if right == 1 else 0 if left == 1 else 0
            self.vel_y += self.gravity
            self.ground = self.rect.colliderect(walls[0]) or any(self.rect.colliderect(obj) for obj in objects)
    
            if jump == 1 and self.ground:
                self.vel_y = -10
    
            # print("Velocity X:", self.vel_x)
            # print("Velocity Y:", self.vel_y)
            # print(f"self.ground:{self.ground}")
    
        def move(self):
            self.rect.x += self.vel_x
            self.rect.y += self.vel_y
            # print("Player Position:", self.rect.x, self.rect.y)
    
        def collisions(self, objects, walls):
            for object in objects:
                if self.rect.colliderect(object):
                    print("Collision detected with object:", object)
                    if abs(self.rect.right - object.left) <= self.collision_tol:
                        self.rect.right = object.left
                        self.ground = True
                    elif abs(self.rect.left - object.right) <= self.collision_tol:
                        self.rect.left = object.right
                        self.ground = True
                    elif self.rect.bottom >= object.top:
                        self.rect.bottom = object.top
                        self.ground = True
                        self.vel_y = 0
                    elif self.rect.top <= object.bottom:
                        self.rect.top = object.bottom
                        self.ground = False
                    else:
                        self.ground = False
    
            for wall in walls:
                if self.rect.colliderect(wall):
                    print("Collision detected with wall:", wall)
                    if self.vel_x > 0:
                        self.rect.right = wall.left
                    elif self.vel_x < 0:
                        self.rect.left = wall.right
    
                    if self.vel_y > 0:
                        self.rect.bottom = wall.top
                        self.vel_y = 0
                        self.ground = True
    
                    elif self.vel_y < 0:
                        self.rect.top = wall.bottom
                        self.vel_y = 0
    
        def update(self, left, right, jump, objects, walls):
            self.update_velo(left, right, jump, objects, walls)
            self.move()
            self.collisions(objects, walls)
            # print("Player position:", self.rect.x, self.rect.y)
    
            print(self)
    
    
    class Game:
        def __init__(self, display):
            self.display = display
            self.clock = pygame.time.Clock()
            self.endpt = pygame.Rect(1200, h - 250, 20, 20)
            self.floor = pygame.Rect(0, h - 20, w, 200)
            self.left_wall = pygame.Rect(0, 0, 20, h)
            self.right_wall = pygame.Rect(w - 20, 0, 20, h)
            self.objects_for_lvl = [
                pygame.Rect(100, h - 100, 100, 80),
                pygame.Rect(250, h - 140, 100, 25),
                pygame.Rect(400, h - 170, 100, 25),
                pygame.Rect(535, h - 170, 75, 25),
                pygame.Rect(735, h - 170, 75, 25),
                pygame.Rect(900, h - 200, 150, 25),
                pygame.Rect(1150, h - 230, 200, 25)
                ]
    
            self.player = Player(30, 600)
            self.running = True
            self.reset()
    
        def reset(self):
            self.score = 0
            self.frame_iteration = 0
    
        def step(self, action):
            self.frame_iteration += 1
    
            for ev in pygame.event.get():
                if ev.type == pygame.QUIT:
                    pygame.quit()
                    quit()
    
            left, right, jump = action
            self.player.update(left, right, jump, self.objects_for_lvl, [self.floor, self.left_wall, self.right_wall])
    
            reward, game_over = self.calculate_score()
    
            # self._update()
    
            return reward, game_over, self.score
    
        def calculate_score(self):
            reward = 0
            game_over = False
    
            if self.player.rect.colliderect(self.endpt):
                reward += 10
            elif self.player.rect.colliderect(self.floor) and self.player.rect.x >= 100:
                reward -= 5
    
            self.score += reward
            game_over = self.player.rect.colliderect(self.left_wall) or self.player.rect.colliderect(self.floor) and self.player.rect.x >= 100
    
            return reward, game_over
    
        def draw(self):
            for obj in self.objects_for_lvl:
                pygame.draw.rect(self.display, (128, 128, 128), obj)
    
            pygame.draw.rect(self.display, (128, 128, 128), self.floor)
            pygame.draw.rect(self.display, (128, 128, 128), self.left_wall)
            pygame.draw.rect(self.display, (128, 128, 128), self.right_wall)
            pygame.draw.rect(self.display, (255, 0, 0), self.player.rect)  # Draw player object
            pygame.draw.rect(self.display, (0, 255, 0), self.endpt)
    
    
    if __name__ == "__main__":
        display = pygame.display.set_mode((w, h))
        pygame.display.set_caption("AI Game")
        game = Game(display)
        while game.running:
    
            # reset screen
            display.fill('white')
    
            # use SPACE for every step
            events = pygame.event.get()
            for event in events:
                if event.type == pygame.KEYDOWN:
                    action = Direction.update(0, 0, 1)  # move up once
                    print("Action:", action)
                    reward, game_over, score = game.step(action)
                    if game_over:
                        print("Game Over")
                        game.running = False
    
            # draw the stuff
            game.draw()
            pygame.display.update()