Categories
Artificial Intelligence Game Development PC Nerding

Blazing fast game development using LLMs

I keep saying it: AI is advancing too fast, not keeping up will make us humans obsolete. While generative AI will not replace a skilled photographer, writer, or programmer, it’s already good enough to replace the hobbyist to low skilled ones. I used photographer, writer and programmer as example as I feel I’m in the hobby-low skilled range in all three categories and I’m sure AI can already replace people at my level. /Rant end

Today I’m sharing my latest conversation with a LLM, specifically Codestral in this case, about game development. I don’t label myself a game developer, I made a few over the years, I participated in Ludum Dare competitions, but it’s not one of my main hobbies.

My conversation started with a simple prompt like this:

🐮: Don’t waste time talking, only code. Make a pygame pong game.

💻: Sure, here is a simple implementation of the classic Pong game using Pygame:

import pygame
import sys
import random

# Initialize Pygame
pygame.init()

# Set up some constants
WIDTH, HEIGHT = 800, 600
BALL_SPEED = [4, 4]
PADDLE_SPEED = 5
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

# Set up the window
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Pygame Pong')

# Set up the game objects
ball = pygame.Rect(WIDTH // 2 - 15, HEIGHT // 2 - 15, 30, 30)
player = pygame.Rect(WIDTH - 20, HEIGHT // 2 - 70, 10, 140)
opponent = pygame.Rect(10, HEIGHT // 2 - 70, 10, 140)

# Main game loop
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    # Move the ball
    ball.left += BALL_SPEED[0]
    ball.top += BALL_SPEED[1]

    # Collision with walls
    if ball.top <= 0 or ball.bottom >= HEIGHT:
        BALL_SPEED[1] = -BALL_SPEED[1]
    if ball.left <= 0 or ball.right >= WIDTH:
        BALL_SPEED = [4, 4]
        ball.center = (WIDTH // 2, HEIGHT // 2)

    # Collision with paddles
    if ball.colliderect(player) or ball.colliderect(opponent):
        BALL_SPEED[0] = -BALL_SPEED[0]

    # Move the player paddle
    keys = pygame.key.get_pressed()
    if keys[pygame.K_UP]:
        player.top -= PADDLE_SPEED
    if keys[pygame.K_DOWN]:
        player.top += PADDLE_SPEED

    # Move the opponent paddle (AI)
    if opponent.top < ball.y:
        opponent.top += PADDLE_SPEED
    elif opponent.bottom > ball.y:
        opponent.top -= PADDLE_SPEED

    # Draw everything
    screen.fill(BLACK)
    pygame.draw.ellipse(screen, WHITE, ball)
    pygame.draw.rect(screen, WHITE, player)
    pygame.draw.rect(screen, WHITE, opponent)

    # Flip the display
    pygame.display.flip()

This game uses two paddles (one controlled by the user and one by a simple AI). The ball bounces off the walls and paddles. If the ball hits the left or right edge of the screen, it resets to the center.

Now, this isn’t rocket science, Pong is considered the “Hello World!” of coding games, but I never ever programmed something from scratch in Python and I have absolutely zero knowledge of Pygame libraries. Making even a simple Pong game would have required at least a couple hours of reading documentation or a good tutorial. Codestral delivered a neatly commented code in a few seconds. If you run this code through python, the game work, but it has a noticeable problem: it runs at like a million frame per seconds. Let me try telling the LLM to add a frame limiter.

🐮: add a frame limiter

💻: Sure, here is the same Pong game code with a frame limiter added:

import pygame
import sys
import random

# Initialize Pygame
pygame.init()

# Set up some constants
WIDTH, HEIGHT = 800, 600
BALL_SPEED = [4, 4]
PADDLE_SPEED = 5
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
FPS = 60 # Frames per second

# Set up the window
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Pygame Pong')

# Set up the game objects
ball = pygame.Rect(WIDTH // 2 - 15, HEIGHT // 2 - 15, 30, 30)
player = pygame.Rect(WIDTH - 20, HEIGHT // 2 - 70, 10, 140)
opponent = pygame.Rect(10, HEIGHT // 2 - 70, 10, 140)

# Create a clock object to control the frame rate
clock = pygame.time.Clock()

# Main game loop
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    # Move the ball
    ball.left += BALL_SPEED[0]
    ball.top += BALL_SPEED[1]

    # Collision with walls
    if ball.top <= 0 or ball.bottom >= HEIGHT:
        BALL_SPEED[1] = -BALL_SPEED[1]
    if ball.left <= 0 or ball.right >= WIDTH:
        BALL_SPEED = [4, 4]
        ball.center = (WIDTH // 2, HEIGHT // 2)

    # Collision with paddles
    if ball.colliderect(player) or ball.colliderect(opponent):
        BALL_SPEED[0] = -BALL_SPEED[0]

    # Move the player paddle
    keys = pygame.key.get_pressed()
    if keys[pygame.K_UP]:
        player.top -= PADDLE_SPEED
    if keys[pygame.K_DOWN]:
        player.top += PADDLE_SPEED

    # Move the opponent paddle (AI)
    if opponent.top < ball.y:
        opponent.top += PADDLE_SPEED
    elif opponent.bottom > ball.y:
        opponent.top -= PADDLE_SPEED

    # Draw everything
    screen.fill(BLACK)
    pygame.draw.ellipse(screen, WHITE, ball)
    pygame.draw.rect(screen, WHITE, player)
    pygame.draw.rect(screen, WHITE, opponent)

    # Flip the display and limit the frame rate
    pygame.display.flip()
    clock.tick(FPS)

The game will now run at a maximum of 60 frames per second (fps), which can help improve performance and make the game smoother.

Again, I know nothing about python/pygame, the LLM knows better and decides to add a clock.tick(FPS) at the end of the main loop, and sets the FPS constant grouped with the other ones at the top. All I had to do was to ask. Now the game is actually playable, but I don’t want the game to start automatically and I want a score system with a win condition.

🐮: Add a start button. Add a score system. Add a win condition for when the player or cpu reaches 5 goals.

💻: Sure, I’ve added these changes to the code:

import pygame
import sys
import random

# Initialize Pygame
pygame.init()

# Set up some constants
WIDTH, HEIGHT = 800, 600
BALL_SPEED = [4, 4]
PADDLE_SPEED = 5
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
FPS = 60 # Frames per second

# Set up the window
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Pygame Pong')

# Set up game variables
player_score = 0
opponent_score = 0
game_started = False

# Font for the score text
font = pygame.font.Font(None, 36)

def draw_text(text, font, color, x, y):
    img = font.render(text, True, color)
    screen.blit(img, (x, y))

# Set up the game objects
ball = pygame.Rect(WIDTH // 2 - 15, HEIGHT // 2 - 15, 30, 30)
player = pygame.Rect(WIDTH - 20, HEIGHT // 2 - 70, 10, 140)
opponent = pygame.Rect(10, HEIGHT // 2 - 70, 10, 140)
start_button = pygame.Rect(WIDTH // 2 - 50, HEIGHT // 2, 100, 50)

# Create a clock object to control the frame rate
clock = pygame.time.Clock()

# Main game loop
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.MOUSEBUTTONDOWN and not game_started:
            # Start the game when clicking the start button
            if start_button.collidepoint(event.pos):
                game_started = True

    if game_started:
        # Move the ball
        ball.left += BALL_SPEED[0]
        ball.top += BALL_SPEED[1]

        # Collision with walls
        if ball.top <= 0 or ball.bottom >= HEIGHT:
            BALL_SPEED[1] = -BALL_SPEED[1]
        if ball.left <= 0:
            player_score += 1
            ball = pygame.Rect(WIDTH // 2 - 15, HEIGHT // 2 - 15, 30, 30)
            BALL_SPEED = [4, 4]
        elif ball.right >= WIDTH:
            opponent_score += 1
            ball = pygame.Rect(WIDTH // 2 - 15, HEIGHT // 2 - 15, 30, 30)
            BALL_SPEED = [-4, 4]

        # Collision with paddles
        if ball.colliderect(player):
            BALL_SPEED[0] = -BALL_SPEED[0]
        elif ball.collidererect(opponent):
            BALL_SPEED[0] = -BALL_SPEED[0]

        # Move the player paddle with keyboard
        keys = pygame.key.get_pressed()
        if keys[pygame.K_UP]:
            player.top -= PADDLE_SPEED
        if keys[pygame.K_DOWN]:
            player.top += PADDLE_SPEED
    else:
        # Move the opponent paddle towards the ball (AI)
        if opponent.top < ball.y:
            opponent.top += PADDLE_SPEED
        elif opponent.bottom > ball.y:
            opponent.top -= PADDLE_SPEED

    # Draw everything
    screen.fill(BLACK)
    pygame.draw.ellipse(screen, WHITE, ball)
    pygame.draw.rect(screen, WHITE, player)
    pygame.draw.rect(screen, WHITE, opponent)
    draw_text("Player: " + str(player_score), font, WHITE, 10, 10)
    draw_text("CPU: " + str(opponent_score), font, WHITE, WIDTH - 90, 10)

    # Draw start button if the game hasn't started yet
    if not game_started:
        pygame.draw.rect(screen, WHITE, start_button)
        draw_text("START", font, BLACK, WIDTH // 2 - 30, HEIGHT // 2 + 15)

    # Check win condition
    if player_score >= 5:
        print("Player wins!")
        game_started = False
        player_score = 0
        opponent_score = 0
    elif opponent_score >= 5:
        print("CPU wins!")
        game_started = False
        player_score = 0
        opponent_score = 0

    # Flip the display and limit the frame rate
    pygame.display.flip()
    clock.tick(FPS)

Now, there is a start button that appears when the game hasn’t started yet. After clicking this button, the game will begin. A score system has been added, which increases the player’s or CPU’s score by one every time they score a goal. The win condition has also been implemented: If either the player or the CPU reaches a score of 5 goals, the respective win message will be printed and the game will reset for a new round.

This bunch of multiplicated 0s an 1s absolutely delivered what I asked for. This time the code doesn’t run, tho, and required manual intervention:
– There was and odd typo to fix, the LLM calls colliders colliderect, and in the typo collidererect at line #72 there’s two extra characters. Luckily python outputted to my cmd the line, the error and the possible fix.
– The AI paddle didn’t move after I clicked the Start, which was caused by a stray else statement, at line #81. This one required me too actually look where the paddle moving code was in the code.

Now I have a complete game:

A couple things remain to be fixed: the ball and AI paddle move at the same speed making impossible for the player to score a goal and the win condition is outputted into the console instead of ingame. Why do I have to do the heavy lifting?

🐮: There are three things to fix: The AI paddle is to fast compared to the ball; The AI paddle doesn’t move after I press start; Player/CPU wins should be outputted in an ingame box, not into the console.

💻: Sure, let’s make these modifications.
1. Let’s slow down the opponent’s paddle by using a smaller speed factor for it.
2. To make sure the AI paddle moves after we press start, I will move its motion inside of our main game loop and include a condition to check if the game has started.
3. For displaying the winner within the game screen, I’ll add some text that says “Player wins!” or “CPU wins!”. This will be drawn on the screen using pygame’s font rendering functionality. When either player reaches 5 goals, we’ll set a flag to indicate that the game is over and display the win message in each frame until a new game starts

Let me make these changes for you.

import pygame
import sys
import random

# Initialize Pygame
pygame.init()

# Set up some constants
WIDTH, HEIGHT = 800, 600
BALL_SPEED = [4, 4]
PADDLE_SPEED = 5
AI_PADDLE_SPEED = 3 # Slowing down the AI paddle
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
FPS = 60 # Frames per second

# Set up the window
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Pygame Pong')

# Set up game variables
player_score = 0
opponent_score = 0
game_started = False
game_over = False # New flag for when the game is over
winner = None # Variable to store the winner

# Font for the score text
font = pygame.font.Font(None, 36)

def draw_text(text, font, color, x, y):
    img = font.render(text, True, color)
    screen.blit(img, (x, y))

# Set up the game objects
ball = pygame.Rect(WIDTH // 2 - 15, HEIGHT // 2 - 15, 30, 30)
player = pygame.Rect(WIDTH - 20, HEIGHT // 2 - 70, 10, 140)
opponent = pygame.Rect(10, HEIGHT // 2 - 70, 10, 140)
start_button = pygame.Rect(WIDTH // 2 - 50, HEIGHT // 2, 100, 50) # Start button rectangle object

# Create a clock object to control the frame rate
clock = pygame.time.Clock()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.MOUSEBUTTONDOWN and not game_started: # Check if the mouse was clicked and the game hasn't started yet
            if start_button.collidepoint(event.pos): # If the click was on the start button, then start the game
                game_started = True

    keys = pygame.key.get_pressed()
    if keys[pygame.K_UP] and player.top > 0:
        player.move_ip(0, -PADDLE_SPEED)
    if keys[pygame.K_DOWN] and player.bottom < HEIGHT:
        player.move_ip(0, PADDLE_SPEED)

    # Move the AI paddle if the game has started
    if game_started:
        if ball.centery > opponent.centery + AI_PADDLE_SPEED // 2 and opponent.bottom < HEIGHT:
            opponent.move_ip(0, AI_PADDLE_SPEED)
        elif ball.centery < opponent.centery - AI_PADDLE_SPEED // 2 and opponent.top > 0:
            opponent.move_ip(0, -AI_PADDLE_SPEED)

    # Move the ball if the game has started and isn't over
    if game_started and not game_over:
        ball.move_ip(*BALL_SPEED)
        if ball.left <= 0 or ball.right >= WIDTH:
            BALL_SPEED[0] = -BALL_SPEED[0]
            if ball.left <= 0:
                player_score += 1
            else:
                opponent_score += 1
        if ball.top <= 0 or ball.bottom >= HEIGHT:
            BALL_SPEED[1] = -BALL_SPEED[1]
        if ball.colliderect(player) or ball.colliderect(opponent):
            BALL_SPEED[0] = -BALL_SPEED[0]

    # Draw everything on the screen
    screen.fill(BLACK)
    pygame.draw.rect(screen, WHITE, player)
    pygame.draw.rect(screen, WHITE, opponent)
    pygame.draw.ellipse(screen, WHITE, ball)
    draw_text("Player: " + str(player_score), font, WHITE, 10, 10)
    draw_text("CPU: " + str(opponent_score), font, WHITE, WIDTH - 90, 10)

    # Draw the start button if the game hasn't started yet
    if not game_started and not game_over:
        pygame.draw.rect(screen, WHITE, start_button)
        draw_text("START", font, BLACK, WIDTH // 2 - 30, HEIGHT // 2 + 15)
    # If the game is over, display the winner message in the center of the screen
    elif game_over:
        draw_text(winner + " wins!", font, WHITE, WIDTH // 2 - 80, HEIGHT // 2)

    # Check if either player has reached 5 points and end the game if so
    if player_score >= 5:
        winner = "Player"
        game_over = True
        game_started = False
        player_score = 0
        opponent_score = 0
    elif opponent_score >= 5:
        winner = "CPU"
        game_over = True
        game_started = False
        player_score = 0
        opponent_score = 0

    # If the game is over and we press enter, reset the game state
    if keys[pygame.K_RETURN] and game_over:
        winner = None
        game_over = False
        ball.center = (WIDTH // 2, HEIGHT // 2)
        player.center = (50, HEIGHT // 2)
        opponent.center = (WIDTH - 50, HEIGHT // 2)
        BALL_SPEED = [random.choice([-4, 4]), random.choice([-4, 4])]

    pygame.display.update()
    clock.tick(60)

This is a simple implementation of a pong game in Python using the Pygame library. The player can move their paddle up and down to hit the ball back towards their opponent (the computer). When the ball hits one of the sides of the screen, a point is scored for the other player. If either player reaches 5 points, they win and the game ends. Pressing enter will reset the game state so that you can play again.
The AI paddle is controlled by moving it towards the center of the ball’s y-coordinate if it is above or below the center of the paddle. This gives the computer a simple but effective strategy for hitting the ball back towards the player. The speed at which the ball and paddles move can be adjusted to change the difficulty of the game.

The newly updated code is playable without any modification.
There are a few new problems:
– The ball now doesn’t recenter after a goal, the code was actually removed.
– You click Start for the first game, but use Enter to reset after a match.

I got bored of toying around and I will stop here, making this blog post took me over an hour, while making the game probably took the AI five minutes at most. I’m incredibly amused with the AI’s skill.

Categories
Game Development

A Flappy Bird clone

I know I missed the Flappy Jam, I didn’t absolutely care about it. I had some time and I decided to make a parody game about the fucks I gave about it. Actually, I neved player Flappy Bird myself and I made this game by copy pasting some graphics and watching a couple Youtube video about the game.
You can play it here. Hope you enjoy.

Flying Fuck Screen

Categories
Game Development PC Nerding

Raspberry PI and Random values

I’m developing a simple rpg game, testing it with .NET on Win and Mono on Win/Mac/Linux works perfectly. Today I decided to test it on my Arch Raspberry PI to see if it could run well on this device and the result is that the game won’t even start for some “Array out of index” exception that does not appear with other systems.
After some time debugging here and there, I found out that the Random.Next function does not behave correctly on RPi.
Running this test code:
Random r = new Random();
for (int i = 0; i<10; i++) { Console.WriteLine(r.Next(0,7)); }

on the RPi prints ten times the number seven!
This is wrong for two reasons: First obliviously it's not returning random numbers and secondly it is returning the maxValue, which should be "exclusive" and it may never be returned by the function.
So calling something like
array[random.Next(0,array.Length)]
should return a value between 0 and array.Length-1, but it returns array.Length causing my out of index exception.

I tried testing Random.Next(), Random.Next(int) and Random.NextDouble() and they all behave correctly. I dunno if this is a bug in Mono:ARM or something related to the Raspberry PI.

UPDATE: Further investigations lead me to think that the problem was actually Math.Round(double), which is called in Random.Next. I'm not sure I understood it completely but looks like it is a well know problem: Mono:ARM does not support PI's hard float OSs, causing errors while working with floating point numbers. There's a patch for that and even an official RPi Debian distro with soft float support, which I won't bother installing right now. Next time PI, next time.

UPDATE: 3 April
Found an hard float version of Mono here on official Pi forums. I tested it on a clean upgraded dist-upgraded Raspbian "wheezy" from 2013 Feb 09 image and it works, remember to expand the root filesystem through raspi-config or to uninstall unneeded packages as wheezy's root partition is 2gb only and is filled with junk stuff (at least junk for me, as I use my RPI via command line SSH). On my Arch setup for some reason it was not working, but an user reported in that thread that it works on his Arch, so I think was my fault, probably some file from my previous mono test that wasn't deleted or overwritten.