
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()