March 17, 2026

Automatic coding with Google Gemini

 

Since the advent of GPT-3 in 2020 lots of improvement are visible, especially the ability of large language models to code software programs is impressive. To check out the current capabilities a demanding example should be presented. Its a Tetris game written python with a built in feature vector. The feature vector is useful for creating an AI for Tetris.

The prompt consists of a multi-step pipeline. At first, the LLM was asked to create an elaborated prompt which gets executed in the second step.

Create a "Prompt Engineering" cheat sheet designed specifically for architecting software without writing manual code. Domain is: Tetris game including semantic annotation. The annotation is based on a numerical feature vector which determines important properties of the game so that an AI player can use the feature vector to automatically playing the game. The AI doesn't need to be implemented, its enough if the feature vector is shown on the screen and commented in the code.
Execute the Tetris prompt, so that the game is shown as python code.


The resulting game works fine. It can be executed without an error. There are falling tetris pieces which can be moved with cursor keys and there is also a feature vector visible on the screen at top right. The sourcecode for the entire app is 170 lines of code and is using numpy, pygame and random as libraries.

The most complex element of the software is "the get_feature_vector(self)" routine. Most Tetris implementation found at github have no such a routine because its not needed for a human player. But the feature vector is highly important for automating the game so that the computer decides at which position each pieces should be location. It seems, that current generation of LLMs are understanding what the purpose is and how to implement it in software.

It should be mentioned that some smaller bugs are available. For example the feature "height" is working wrong. Of course, the LLM can fix the issue but this would require a rewrite of the software. Another problem is, that the layout of the screen looks a bit messy. So the game needs to be interpreted as a prototype but not as a final version.

import pygame
import random
import numpy as np

# --- Konfiguration ---
SCREEN_WIDTH = 500
SCREEN_HEIGHT = 600
BLOCK_SIZE = 30
GRID_WIDTH = 10
GRID_HEIGHT = 20
SIDEBAR_X = 320

COLORS = [
    (0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255), 
    (255, 255, 0), (255, 165, 0), (0, 255, 255), (128, 0, 128)
]

SHAPES = [
    [[1, 1, 1, 1]],
    [[0, 2, 2], [2, 2, 0]],
    [[3, 3, 0], [0, 3, 3]],
    [[4, 4], [4, 4]],
    [[5, 5, 5], [5, 0, 0]],
    [[6, 6, 6], [0, 0, 6]],
    [[7, 7, 7], [0, 7, 0]]
]

class TetrisEngine:
    def __init__(self):
        self.grid = np.zeros((GRID_HEIGHT, GRID_WIDTH), dtype=int)
        self.lines_cleared_total = 0
        self.game_over = False
        self.new_piece()

    def new_piece(self):
        self.current_shape = random.choice(SHAPES)
        self.piece_color = SHAPES.index(self.current_shape) + 1
        self.piece_x = GRID_WIDTH // 2 - len(self.current_shape[0]) // 2
        self.piece_y = 0
        if self.check_collision(self.piece_x, self.piece_y):
            self.game_over = True

    def rotate_shape(self):
        """
        Matrix-Rotation: Transponieren + Zeilen umkehren.
        Semantischer Hinweis: Ändert die Form-Struktur für den Feature-Vektor.
        """
        # List comprehension für 90-Grad Rotation
        new_shape = [list(row) for row in zip(*self.current_shape[::-1])]
        
        # Kollisionsprüfung: Nur rotieren, wenn Platz da ist
        if not self.check_collision(self.piece_x, self.piece_y, new_shape):
            self.current_shape = new_shape

    def check_collision(self, x, y, shape=None):
        if shape is None: shape = self.current_shape
        for r, row in enumerate(shape):
            for c, val in enumerate(row):
                if val:
                    if (x + c < 0 or x + c >= GRID_WIDTH or 
                        y + r >= GRID_HEIGHT or 
                        (y + r >= 0 and self.grid[y + r][x + c])):
                        return True
        return False

    def freeze_piece(self):
        for r, row in enumerate(self.current_shape):
            for c, val in enumerate(row):
                if val:
                    self.grid[self.piece_y + r][self.piece_x + c] = self.piece_color
        self.clear_lines()
        self.new_piece()

    def clear_lines(self):
        full_rows = [i for i, row in enumerate(self.grid) if all(row)]
        for i in full_rows:
            self.grid = np.delete(self.grid, i, axis=0)
            self.grid = np.insert(self.grid, 0, np.zeros(GRID_WIDTH), axis=0)
        self.lines_cleared_total += len(full_rows)

    def get_feature_vector(self):
        col_heights = []
        for c in range(GRID_WIDTH):
            h = 0
            for r in range(GRID_HEIGHT):
                if self.grid[r][c] != 0:
                    h = GRID_HEIGHT - r
                    break
            col_heights.append(h)
        
        agg_height = sum(col_heights)
        holes = 0
        for c in range(GRID_WIDTH):
            found_block = False
            for r in range(GRID_HEIGHT):
                if self.grid[r][c] != 0:
                    found_block = True
                elif found_block and self.grid[r][c] == 0:
                    holes += 1
        
        bumpiness = 0
        for i in range(GRID_WIDTH - 1):
            bumpiness += abs(col_heights[i] - col_heights[i+1])
            
        return [agg_height, holes, bumpiness, self.lines_cleared_total]

def main():
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("monospace", 16)
    engine = TetrisEngine()

    drop_time = 0
    while not engine.game_over:
        screen.fill((240, 240, 240))
        dt = clock.get_rawtime()
        drop_time += dt
        clock.tick()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT and not engine.check_collision(engine.piece_x - 1, engine.piece_y):
                    engine.piece_x -= 1
                if event.key == pygame.K_RIGHT and not engine.check_collision(engine.piece_x + 1, engine.piece_y):
                    engine.piece_x += 1
                if event.key == pygame.K_DOWN and not engine.check_collision(engine.piece_x, engine.piece_y + 1):
                    engine.piece_y += 1
                if event.key == pygame.K_UP:
                    engine.rotate_shape() # ROTATION TRIGGER

        if drop_time > 400:
            if not engine.check_collision(engine.piece_x, engine.piece_y + 1):
                engine.piece_y += 1
            else:
                engine.freeze_piece()
            drop_time = 0

        # Draw Grid & Piece
        for r in range(GRID_HEIGHT):
            for c in range(GRID_WIDTH):
                if engine.grid[r][c]:
                    pygame.draw.rect(screen, COLORS[engine.grid[r][c]], (c*BLOCK_SIZE, r*BLOCK_SIZE, BLOCK_SIZE-1, BLOCK_SIZE-1))
        
        for r, row in enumerate(engine.current_shape):
            for c, val in enumerate(row):
                if val:
                    pygame.draw.rect(screen, COLORS[engine.piece_color], 
                                     ((engine.piece_x + c)*BLOCK_SIZE, (engine.piece_y + r)*BLOCK_SIZE, BLOCK_SIZE-1, BLOCK_SIZE-1))

        # AI Interface Update
        features = engine.get_feature_vector()
        labels = ["Height", "Holes", "Bumpy", "Lines"]
        title = font.render("AI INPUT VECTOR:", True, (0, 0, 0))
        screen.blit(title, (SIDEBAR_X, 20))
        for i, val in enumerate(features):
            txt = font.render(f"{labels[i]}: {val}", True, (0, 0, 0))
            screen.blit(txt, (SIDEBAR_X, 50 + i*30))

        pygame.display.flip()
    pygame.quit()

if __name__ == "__main__":
    main()
 

No comments:

Post a Comment