07-04-2026, 08:39 AM
Gave gemini this training program:
Then I told it to be "creative" and create two games. Here's the first one, where it re-used very much from the training program, still quite fun:
, and here's the second one, after I told gemini it needn't re-use that much of the code:
Code:
' An AI training program in naalaa 7
' ----------------------------------
' Pass this program to Gemini to help it understand and use the language.
' Constants, visible in entire program but can't change values.
constant DIR_NONE = 0, DIR_LEFT = 1, DIR_RIGHT = 2, DIR_UP = 3, DIR_DOWN = 4
' Visible (global) variables, can be accessed in functions.
visible vObjects
' Use 'set window title, w, h, fullscreen, scale' to create a 320x240 pixels large window, visually
' upscaled by 2 for crispy pixels.
set window "Trainer", 320, 240, false, 2
' Turn off automatic redraw, meaning that we will have to call 'redraw' to manually copy the game's
' back buffer to the window.
set redraw off
' Create 100 moving objects and put them in vObjects.
vObjects = []
for i = 1 to 100
if rnd(2) = 0
obj = MovingRectangle(rnd(320), rnd(240), 16 + rnd(16), 16 + rnd(16))
else
obj = MovingEllipse(rnd(320), rnd(240), 16 + rnd(16), 16 + rnd(16))
endif
vObjects[sizeof(vObjects)] = obj
obj.SetDir(rnd(1, 4))
obj.SetSpeed(0.25 + rnd()*4.75)
next
' Control a crosshair.
crossHairX = 160
crossHairY = 120
' Define a polygon in the form [x0, y0, x1, y1 .. xn, yn], with its origin at (0, 0) that we can
' draw anywhere on the screen.
starPolygon = [0, -32, 8, -8, 32, 0, 8, 8, 0, 32, -8, 8, -32, 0, -8, -8]
starAngle = 0 ' The rotation angle of our star polygon.
starAngleSpeed = 4 ' The speed of rotation.
' Loop until user presses the escape key.
while not keydown(KEY_ESCAPE)
' Update objects.
if sizeof(vObjects)
for i = sizeof(vObjects) - 1 to 0
if not vObjects[i].Update()
free key vObjects, i
endif
next
endif
' Move crosshair.
if keydown(KEY_LEFT) crossHairX = max(crossHairX - 2, 0)
if keydown(KEY_RIGHT) crossHairX = min(crossHairX + 2, 319)
if keydown(KEY_UP) crossHairY = max(crossHairY - 2, 0)
if keydown(KEY_DOWN) crossHairY = min(crossHairY + 2, 240)
' Change rotation direction of the star with left click. Setting the second parameter for
' 'mousebutton' to true means that the function won't return true until the the mouse button has
' been released and pressed again.
if mousebutton(0, true) starAngleSpeed = -starAngleSpeed
' Update star angle. The modulo operator (%) performs mathematical modulo, meaning that it
' handles negative numbers correctly.
starAngle = (starAngle + starAngleSpeed)%360
' Clear screen black.
set color 0, 0, 0
cls
' 'foreach' doesn't guarantee any specific order, so the objects will seem to change z order
' when the vObjects shrinks. I'm using 'foreach' just to show how it works :)
foreach o in vObjects
o.Draw()
next
' Draw a transparent blue gradient at the top.
for y = 0 to 63
set color 32, 64, 255, 255*(63 - y)/64
draw line 0, y, 319, y
next
' Draw red crosshair.
set color 255, 0, 0
draw line crossHairX, crossHairY - 8, crossHairX, crossHairY + 8
draw line crossHairX - 8, crossHairY, crossHairX + 8, crossHairY
' Draw transparent star polygon at mouse cursor position using 'draw poly xform points, x, y,
' scaleX, scaleY, angle, pivotX, pivotY, filled'.
set color 255, 255, 255, 128
mx = mousex(); my = mousey()
draw poly xform starPolygon, mx, my, 1, 1, rad(starAngle), 0, 0, true
' 'draw poly points, filled' draws the polygon defined by 'points' without any
' transformations. So only the bottom left quarter of the star will be visible, at the top right
' corner of the window, when we use this command.
draw poly starPolygon, true
' Write some info at the bottom left corner using the default fixed size font (all characters
' are 8x16 pixels large).
set color 255, 255, 255
set caret 0, 208
' 'wln txt' writes a text string and jumps to a new line.
wln "Mouse position: " + mx + ", " + my
' 'write txt' writes a string without jumping to a new line. All numbers are floating points,
' use 'floor' or 'int' to remove the fractional part.
write "Star angle: " + floor(starAngle)
' Copy backbuffer to window and use 'fwait' to cap fps at 60.
redraw
fwait 60
wend
' Create and return a rectangle that can move.
function MovingRectangle(centerX, centerY, w, h)
' Create object and set properties.
mr = [dir: DIR_NONE,
x: centerX - w/2, y: centerY - h/2,
w: w, h: h,
dx: 0, dy: 0,
speed: 1, bouncesLeft: 5,
r: rnd(256), g: rnd(256), b: rnd(255)]
' Add some functions to object.
mr.SetDir = function(dir)
this.dir = dir
select dir
case DIR_LEFT
this.dx = -1; this.dy = 0
case DIR_RIGHT
this.dx = 1; this.dy = 0
case DIR_UP
this.dx = 0; this.dy = -1
case DIR_DOWN
this.dx = 0; this.dy = 1
default
this.dx = 0; this.dy = 0
endsel
endfunc
mr.Stop = function()
this.SetDir(DIR_NONE)
endfunc
mr.GetDir = function()
return this.dir
endfunc
mr.SetSpeed = function(spd)
this.spd = spd
endfunc
mr.GetSpeed = function()
return this.spd
endfunc
mr.Update = function()
' Move.
this.x = this.x + this.dx
this.y = this.y + this.dy
' Turn on screen edges.
if this.dir = DIR_LEFT and this.x < 0
this.SetDir(DIR_RIGHT)
this.bouncesLeft = this.bouncesLeft - 1
elseif this.dir = DIR_RIGHT and this.x > 320
this.SetDir(DIR_LEFT)
this.bouncesLeft = this.bouncesLeft - 1
endif
if this.dir = DIR_UP and this.y < 0
this.SetDir(DIR_DOWN)
this.bouncesLeft = this.bouncesLeft - 1
elseif this.dir = DIR_DOWN and this.y > 240
this.SetDir(DIR_UP)
this.bouncesLeft = this.bouncesLeft - 1
endif
return this.bouncesLeft > 0
endfunc
mr.Draw = function()
set color this.r, this.g, this.b
' Use 'draw rect x, y, w, h, filled' to draw a filled rectangle.
draw rect this.x, this.y, this.w, this.h, true
endfunc
return mr
endfunc
' Create and return a moving ellipse.
function MovingEllipse(x, y, w, h)
' Base on moving rectangle.
me = MovingRectangle(x, y, w, h)
' Replace the Draw function.
me.Draw = function()
set color this.r, this.g, this.b
' Use 'draw ellipse centerX, centerY, radiusX, radiusY, filled' to draw a filled ellipse.
draw ellipse this.x + this.w/2, this.y + this.h/2, this.w/2, this.h/2, true
endfunc
return me
endfunc
' Return true if the bounding rectangle of objects a and b overlaps. Tables/arrays are always passed
' by reference.
function Overlaps(a, b)
return a.x + a.w > b.x and a.x < b.x + b.w and a.y + a.h > b.y and a.y < b.y + b.h
endfuncThen I told it to be "creative" and create two games. Here's the first one, where it re-used very much from the training program, still quite fun:
Code:
' Neon Star Eater - Commented Educational Version
' --------------------------------------------------
' 'constant' defines values that cannot change while the program runs.
' These are visible everywhere automatically.
constant DIR_NONE = 0, DIR_LEFT = 1, DIR_RIGHT = 2, DIR_UP = 3, DIR_DOWN = 4
constant STATE_PLAYING = 0, STATE_GAMEOVER = 1
' 'visible' declares global variables. Without this, variables created in
' the main loop or functions are local to that specific scope.
' Using a 'v' prefix is a great convention to easily identify them!
visible vEnemies
visible vFood
visible vScore
visible vGameState
visible vStarPoly
visible vStarAngle
visible vStarAngleSpeed
' Create a window: title, width, height, fullscreen (false), scale (2x)
set window "Neon Star Eater", 320, 240, false, 2
' Turn off automatic redraw so we can draw everything in the background
' and flip it to the screen all at once (double buffering) to prevent flickering.
set redraw off
' Initialize the player's shape as a list of x, y coordinates forming a polygon.
vStarPoly = [0, -12, 4, -4, 12, 0, 4, 4, 0, 12, -4, 4, -12, 0, -4, -4]
vStarAngle = 0
vStarAngleSpeed = 4
' Call our setup function to set the initial game state
ResetGame()
' --- MAIN GAME LOOP ---
' This loop runs continuously until the user presses the Escape key.
while not keydown(KEY_ESCAPE)
' Clear the screen to black at the start of every frame
set color 0, 0, 0
cls
' Handle logic and drawing based on the current game state
if vGameState = STATE_PLAYING
UpdatePlaying()
DrawPlaying()
elseif vGameState = STATE_GAMEOVER
UpdateGameOver()
DrawGameOver()
endif
' Copy our background drawings to the actual window
redraw
' Cap the frame rate at 60 Frames Per Second
fwait 60
wend
' --- GAME STATE FUNCTIONS ---
function ResetGame()
vScore = 0
vGameState = STATE_PLAYING
' Empty tables (arrays) to hold our game objects
vEnemies = []
vFood = []
' Spawn initial food using a 'for' loop
for i = 1 to 3
SpawnFood()
next
endfunc
function SpawnEnemy()
' Get current mouse position to know where the player is
mx = mousex(); my = mousey()
safe = false
ex = 0
ey = 0
' Keep picking random coordinates until we find one far enough from the player
while not safe
ex = rnd(320)
ey = rnd(240)
' Calculate the squared distance (a^2 + b^2 = c^2).
' We skip the square root to save processing power!
distSq = (ex - mx)*(ex - mx) + (ey - my)*(ey - my)
' 10000 means the enemy must spawn at least 100 pixels away (100^2 = 10000)
if distSq > 10000
safe = true
endif
wend
' Create a new enemy object and set its properties
obj = MovingRectangle(ex, ey, 12 + rnd(12), 12 + rnd(12))
obj.SetDir(rnd(1, 4))
obj.SetSpeed(1.0 + rnd()*2.0)
obj.r = 255; obj.g = 32; obj.b = 32 ' RGB color: Bright Red
' Add the new object to the end of the vEnemies array.
' sizeof() gets the current number of items in the array.
vEnemies[sizeof(vEnemies)] = obj
endfunc
function SpawnFood()
obj = MovingEllipse(rnd(320), rnd(240), 10, 10)
obj.SetDir(rnd(1, 4))
obj.SetSpeed(0.5 + rnd()*1.5)
obj.r = 32; obj.g = 255; obj.b = 32 ' RGB color: Bright Green
vFood[sizeof(vFood)] = obj
endfunc
function UpdatePlaying()
mx = mousex(); my = mousey()
' Rotate the star. The modulo (%) operator keeps the angle between 0 and 359
vStarAngle = (vStarAngle + vStarAngleSpeed)%360
' Create a temporary table representing the player's bounding box for collisions
playerBox = [x: mx - 8, y: my - 8, w: 16, h: 16]
' --- ENEMY UPDATE ---
if sizeof(vEnemies)
' Loop backwards! When removing items from an array inside a loop,
' looping backwards prevents index shifting errors.
for i = sizeof(vEnemies) - 1 to 0
vEnemies[i].Update()
if Overlaps(playerBox, vEnemies[i])
vGameState = STATE_GAMEOVER
endif
next
endif
' --- FOOD UPDATE ---
if sizeof(vFood)
for i = sizeof(vFood) - 1 to 0
vFood[i].Update()
if Overlaps(playerBox, vFood[i])
vScore = vScore + 10
' 'free key' removes an item from an array at the specified index
free key vFood, i
SpawnFood()
' The modulo (%) operator checks if the score is perfectly divisible by 50
if vScore % 50 = 0
SpawnEnemy()
endif
endif
next
endif
endfunc
function DrawPlaying()
' 'foreach' is a clean way to loop through an array when you don't need the index
foreach e in vEnemies
e.Draw()
next
foreach f in vFood
f.Draw()
next
' Set color to Yellow with 200 alpha (slight transparency)
set color 255, 255, 0, 200
mx = mousex(); my = mousey()
' Draw the player polygon.
' xform allows scaling (1, 1) and rotation. 'rad()' converts degrees to radians.
' The last parameter (true) means the polygon will be filled.
draw poly xform vStarPoly, mx, my, 1, 1, rad(vStarAngle), 0, 0, true
' Draw UI Score text at coordinates x:4, y:4
set color 255, 255, 255
set caret 4, 4
write "SCORE: " + vScore
endfunc
function UpdateGameOver()
' Wait for the player to press SPACE to restart
if keydown(KEY_SPACE)
ResetGame()
endif
endfunc
function DrawGameOver()
DrawPlaying() ' Keep the game visible in the background
' Draw a dark, translucent rectangle over the whole screen (0,0 to 320,240)
set color 0, 0, 0, 180
draw rect 0, 0, 320, 240, true
' Draw Game Over text
set color 255, 50, 50
set caret 120, 100
write "GAME OVER"
set color 255, 255, 255
set caret 95, 130
write "Final Score: " + vScore
set color 200, 200, 200
set caret 70, 160
write "Press SPACE to Restart"
endfunc
' --- CORE OBJECT CLASSES ---
' This function acts like a "Class Constructor" in other languages.
' It creates a table with properties and attaches functions to it.
function MovingRectangle(centerX, centerY, w, h)
' Create the base table (object) with its properties
mr = [dir: DIR_NONE,
x: centerX - w/2, y: centerY - h/2,
w: w, h: h, dx: 0, dy: 0,
speed: 1,
r: 255, g: 255, b: 255]
' Attach a method to the object.
' 'this' refers to the specific table that owns the function.
mr.SetDir = function(dir)
this.dir = dir
' 'select/case' is a cleaner way to write multiple 'if/elseif' statements
select dir
case DIR_LEFT
this.dx = -this.speed; this.dy = 0
case DIR_RIGHT
this.dx = this.speed; this.dy = 0
case DIR_UP
this.dx = 0; this.dy = -this.speed
case DIR_DOWN
this.dx = 0; this.dy = this.speed
default
this.dx = 0; this.dy = 0
endsel
endfunc
mr.SetSpeed = function(spd)
this.speed = spd
this.SetDir(this.dir) ' Update dx/dy based on the new speed
endfunc
mr.Update = function()
' Move the object
this.x = this.x + this.dx
this.y = this.y + this.dy
' Bounce off the screen edges
if this.dir = DIR_LEFT and this.x < 0
this.SetDir(DIR_RIGHT)
elseif this.dir = DIR_RIGHT and this.x > 320 - this.w
this.SetDir(DIR_LEFT)
endif
if this.dir = DIR_UP and this.y < 0
this.SetDir(DIR_DOWN)
elseif this.dir = DIR_DOWN and this.y > 240 - this.h
this.SetDir(DIR_UP)
endif
return true
endfunc
mr.Draw = function()
set color this.r, this.g, this.b
draw rect this.x, this.y, this.w, this.h, true
endfunc
' Return the fully constructed object
return mr
endfunc
' Inheritance! This creates a MovingEllipse by first creating a MovingRectangle,
' and then overriding its Draw function.
function MovingEllipse(x, y, w, h)
me = MovingRectangle(x, y, w, h)
' Override the default rectangle drawing method
me.Draw = function()
set color this.r, this.g, this.b
' Ellipses are drawn from their center point, so we add half the width/height
draw ellipse this.x + this.w/2, this.y + this.h/2, this.w/2, this.h/2, true
endfunc
return me
endfunc
' Axis-Aligned Bounding Box (AABB) collision detection.
' Checks if the right side of A is past the left side of B, etc.
function Overlaps(a, b)
return a.x + a.w > b.x and a.x < b.x + b.w and a.y + a.h > b.y and a.y < b.y + b.h
endfunc, and here's the second one, after I told gemini it needn't re-use that much of the code:
Code:
' Neon Herder - An Anomaly Containment Game
' ----------------------------------------------------
' Goal: Herd all the yellow wandering anomalies into the center blue core using the mouse.
' Mechanics: The mouse emits a repulsion field. Anomalies flee when the mouse gets close.
set window "Neon Herder", 320, 240, false, 2
set redraw off
' Global variables
visible vAnomalies
visible anomaliesLeft
vAnomalies = []
anomaliesLeft = 50
' Generate 50 wandering anomalies
for i = 1 to 50
' Create anomaly object
anomaly = [x: rnd(320), y: rnd(240), dx: 0.0, dy: 0.0, captured: false]
' Object method to handle physics and AI
anomaly.Update = function(mx, my)
if this.captured
' Gently settle into the core once captured
this.x = this.x + (160 - this.x) * 0.05
this.y = this.y + (120 - this.y) * 0.05
return
endif
' Brownian motion (random wandering)
this.dx = this.dx + (rnd() * 2 - 1) * 0.5
this.dy = this.dy + (rnd() * 2 - 1) * 0.5
' Apply friction to prevent infinite speed
this.dx = this.dx * 0.9
this.dy = this.dy * 0.9
' Repulsion from the mouse cursor
' We use distance squared to avoid needing a heavy square root calculation
distSq = (this.x - mx)*(this.x - mx) + (this.y - my)*(this.y - my)
if distSq < 2500 ' 50 pixel radius
' Flee from the mouse!
this.dx = this.dx + (this.x - mx) * 0.03
this.dy = this.dy + (this.y - my) * 0.03
endif
' Apply velocity
this.x = this.x + this.dx
this.y = this.y + this.dy
' Wall collisions (bounce)
if this.x < 0
this.x = 0; this.dx = -this.dx
elseif this.x > 319
this.x = 319; this.dx = -this.dx
endif
if this.y < 0
this.y = 0; this.dy = -this.dy
elseif this.y > 239
this.y = 239; this.dy = -this.dy
endif
' Check if anomaly has been corralled into the core (radius 20)
distToCore = (this.x - 160)*(this.x - 160) + (this.y - 120)*(this.y - 120)
if distToCore < 400
this.captured = true
anomaliesLeft = anomaliesLeft - 1
endif
endfunc
' Object method to draw the anomaly
anomaly.Draw = function()
if this.captured
set color 0, 255, 255 ' Cyan when captured
else
set color 255, 255, 0 ' Yellow when wild
endif
' Draw as a crisp 3x3 pixel
draw rect this.x - 1, this.y - 1, 3, 3, true
endfunc
' Append to our global array
vAnomalies[sizeof(vAnomalies)] = anomaly
next
' Main Game Loop
while not keydown(KEY_ESCAPE)
mx = mousex()
my = mousey()
' Clear screen to a dark, moody blue-black
set color 10, 10, 20
cls
' Draw the containment core in the center
set color 0, 100, 255, 100
draw ellipse 160, 120, 20, 20, true
set color 0, 200, 255
draw ellipse 160, 120, 20, 20, false
' Draw the player's repulsion field aura
set color 255, 50, 50, 30
draw ellipse mx, my, 50, 50, true
set color 255, 50, 50, 128
draw ellipse mx, my, 50, 50, false
' Update and draw all anomalies
foreach a in vAnomalies
a.Update(mx, my)
a.Draw()
next
' UI / HUD
set color 255, 255, 255
set caret 4, 4
if anomaliesLeft > 0
write "Uncontained: " + anomaliesLeft
else
set color 0, 255, 0
write "CONTAINMENT COMPLETE!"
endif
' Render and cap at 60 FPS
redraw
fwait 60
wend
