Lets play a game on the PicoCalc. Here is my version of the classic Snake game.
Here is the code if anyone wants to have a look, or use it as a guide to translate it to something else.
/*
* snake:
* My version of this classic game.
* Translated from my RTB Basic version...
* Gordon Henderson, 2026
*********************************************************************************
*/
GET "libhdr"
GET "vdu"
GET "sys"
GET "rubyKeys"
GLOBAL
{
snakeX: ug
snakeY
xSpeed ; ySpeed
foodX ; foodY
snakeDia ; snakeRad ; snakeRad3
numSegs
gWidth ; gHeight
tWidth ; tHeight
maxFood
quickTick ; slowTick
score
done
}
MANIFEST
{
// How many snake segments can fit on the screen?
segsWide = 25
segsHigh = 25
// Ticker times
// The quick timer is for more food, the slow timer makes us shorter...
quickTickTime = 4000 // mS
slowTickTime = 9000
movesPerSecond = 10
snakeSpeed = 1000/movesPerSecond
}
/*
* center:
* Display centered text
*********************************************************************************
*/
LET center (s) BE
{
FOR i = 1 TO tWidth / 2 - s%0 / 2
sawrch (' ')
sawrites (s)
}
/*
* getSpace:
* Wait for space to be typed
*********************************************************************************
*/
AND getSpace () BE
{
vduInk (colWhite)
center ("Press the SPACE bar when ready ")
{} REPEATUNTIL sardch () = ' '
}
AND snakeDie (message) BE
{
vduXY (4,0)
vduInk (colRed)
sawrites (message)
vduXY (0,tHeight-1)
getSpace ()
done := TRUE
}
/*
* updateSnake:
* Move it along to the next location in our playing field
********************************************************************************
*/
AND updateSnake () BE
{
LET x0 = snakeX!0 + xSpeed
LET y0 = snakeY!0 + ySpeed
FOR i = numSegs - 1 TO 1 BY -1 DO
{
snakeX!i := snakeX!(i-1)
snakeY!i := snakeY!(i-1)
}
snakeX!0 := x0
snakeY!0 := y0
}
/*
* longer: shorter:
* Add or remove length to the snake
*********************************************************************************
*/
AND longer (segments) BE
{
LET new = numSegs + segments
FOR i = numSegs TO new DO
{
snakeX!i := snakeX!(numSegs-1)
snakeY!i := snakeY!(numSegs-1)
}
numSegs := new
}
AND shorter (segments) BE
{
numSegs := numSegs - segments
IF numSegs > 1 THEN
RETURN
snakeDie ("Your snake has starved to death!")
}
/*
* checkFood:
* Eat food (if we land on it!)
*********************************************************************************
*/
AND checkFood () BE
{
LET x = snakeX!0
LET y = snakeY!0
FOR i = 1 TO maxFood DO
{
IF (foodX!i = x) & (foodY!i = y) THEN
{
longer (3)
foodX!i := 0 // burp
BREAK
}
}
}
/*
* moreFood:
* See if we can put more food on the table
*********************************************************************************
*/
AND moreFood () BE
{
LET store = 0
LET hit = ?
LET fx, fy = ?, ?
FOR i = 1 TO maxFood DO
IF foodX!i = 0 THEN
{
store := i
BREAK
}
IF store = 0 THEN
RETURN // Too much food on the table
// find a free slot
// ie. don't land food on the snake or a rock
FOR i = 1 TO 5 DO // Only try 5 times, then give up
{
fx := (randno (segsWide - 2) + 1) * snakeDia
fy := (randno (segsHigh - 2) + 1) * snakeDia
hit := FALSE
FOR j = 0 TO numSegs - 1 DO
IF (snakeX!j = fx) & (snakeY!j = fy) THEN hit := TRUE
UNLESS hit THEN BREAK
}
IF hit THEN RETURN // Oops. Can't find space!
// Store location
foodX!store := fx
foodY!store := fy
}
/*
* drawSnake:
* With some imagination...
*********************************************************************************
*/
LET drawSnake () BE
{
// Tongue
// Lime greeen...
vduGCol (colLime)
TEST xSpeed = 0 THEN // Moving Up or Down
vduEllipse (snakeX!0, snakeY!0, snakeRad / 4, snakeRad * 2, TRUE)
ELSE // Moving Left or Right
vduEllipse (snakeX!0, snakeY!0, snakeRad * 2, snakeRad / 3, TRUE)
// Head
// Red head with yellow dot inside
vduGCol (colRed)
vduCircle (snakeX!0, snakeY!0, snakeRad, 1)
vduGCol (colYellow)
vduCircle (snakeX!0, snakeY!0, snakeRad / 2, 1)
// Body
FOR i = 1 TO numSegs - 1 DO
{
vduGCol (colYellow)
vduCircle (snakeX!i, snakeY!i, snakeRad, 1)
vduGCol (colOlive)
vduCircle (snakeX!i, snakeY!i, snakeRad * 3 / 4, 1)
}
}
/*
* drawFood:
* Draw the food on the display
*********************************************************************************
*/
AND drawFood () BE
{
FOR i = 0 TO maxFood DO
{
LET fx = foodX!i
LET fy = foodY!i
UNLESS fx = 0 THEN
{
vduGCol (colRed)
vduCircle (fx, fy, snakeRad * 8 / 10, TRUE)
vduGCol (colLime)
vduCircle (fx - snakeRad3, fy + snakeRad3, snakeRad3, 1)
vduCircle (fx + snakeRad3, fy + snakeRad3, snakeRad3, 1)
}
}
}
/*
* drawBorder: drawScene:
* Draw the details on a blank canvass
*********************************************************************************
*/
AND drawBorder () BE
{
vduGCol (colPink)
vduRectangle (4,4, gWidth-8, gHeight-8, FALSE)
vduRectangle (5,5, gWidth-10, gHeight-10, FALSE)
}
AND drawScene () BE
{
vduCls ()
drawBorder ()
drawSnake ()
drawFood ()
vduUpdate ()
}
/*
* collided:
* Did we hit something? Either ourself, or a wall
*********************************************************************************
*/
AND collided () = VALOF
{
LET x0 = snakeX!0 // Check the head
LET y0 = snakeY!0
// Wall?
IF (x0 = 0) | (y0 = 0) |
((x0 / snakeDia) = segsWide + 1) |
((y0 / snakeDia) = segsHigh + 1) THEN
{
snakeDie ("Your snake hit the wall!")
RESULTIS TRUE
}
// Myself?
FOR i = 1 TO numSegs-1 DO
IF (snakeX!i = x0) & (snakeY!i = y0) THEN
{
snakeDie ("Your snake bit itself!")
RESULTIS TRUE
}
RESULTIS FALSE
}
/*
* mainGameLoop:
* Is where is all happens
*********************************************************************************
*/
AND mainGameLoop () BE
{
LET key = ?
LET now, then = ?,?
then := sys (Sys_cputime)
drawScene ()
key := sapollrdch ()
UNLESS key < 0 THEN
{
SWITCHON key INTO
{
CASE KEY_ARROW_UP:
xSpeed := 0
ySpeed := snakeDia
ENDCASE
CASE KEY_ARROW_DOWN:
xSpeed := 0
ySpeed := -snakeDia
ENDCASE
CASE KEY_ARROW_LEFT:
xSpeed := -snakeDia
ySpeed := 0
ENDCASE
CASE KEY_ARROW_RIGHT:
xSpeed := snakeDia
ySpeed := 0
ENDCASE
CASE 'q': CASE 'Q':
done := TRUE
BREAK
ENDCASE
// Debug/Cheat?
CASE ' ':
longer (1)
ENDCASE
}
}
// Basic checks
checkFood ()
updateSnake ()
// Collided?
IF collided () THEN
BREAK
// Quick ticker?
IF sys (Sys_cputime) > quickTick THEN
{
moreFood (1)
quickTick := quickTick + quickTickTime
}
// Slow ticker?
// Snake can die here, so check at the end, or insert a BREAK...
IF sys (Sys_cputime) > slowTick THEN
{
shorter (1)
slowTick := slowTick + slowTickTime
}
// We really don't want to move the snake more (or less) than 10 times a second, so
// Somewhat crudely ...
now := sys (Sys_cputime)
sys (Sys_delay, snakeSpeed - (now - then))
} REPEATUNTIL done
/*
* reset:
* Reset for a new game
*********************************************************************************
*/
AND reset () BE
{
vduCls ()
// Initialise the snake with 5 segments
numSegs := 5
FOR i = 0 TO numSegs - 1 DO
{
snakeX!i := segsWide / 2 * snakeDia + i * snakeDia
snakeY!i := segsHigh / 2 * snakeDia
}
// Start going Left
xSpeed := -snakeDia ; ySpeed := 0
/***
-- Need to re-position snake if we do this
// Random direction
SWITCHON randno (4) INTO
{
CASE 0: xSpeed := -snakeDia ; ySpeed := 0 ; ENDCASE // Left
CASE 1: xSpeed := snakeDia ; ySpeed := 0 ; ENDCASE // Right
CASE 2: xSpeed := 0 ; ySpeed := -snakeDia ; ENDCASE // Up
CASE 3: xSpeed := 0 ; ySpeed := snakeDia ; ENDCASE // Down
}
****/
// Food
FOR i = 0 TO maxFood DO
{
foodX!i := 0
foodY!i := 0
}
// Reset timers, scores
quickTick := sys (Sys_cputime) + quickTickTime
slowTick := sys (Sys_cputime) + slowTickTime
score := 0
done := FALSE
}
/*
* setup:
* Initial setup, allocate memory, set globals, etc.
*********************************************************************************
*/
AND setup () = VALOF
{
gWidth := vduProps!vduProps_gWidth
gHeight := vduProps!vduProps_gHeight
tWidth := vduProps!vduProps_tWidth
tHeight := vduProps!vduProps_tHeight
// How big to make the sname segments?
snakeDia := gWidth / segsWide
snakeRad := snakeDia / 2
snakeRad3 := snakeRad / 3
// If we need to save RAM we could move to byte arrays rather than words..
snakeX := getvec (segsWide * segsHigh) ; UNLESS snakeX THEN RESULTIS FALSE
snakeY := getvec (segsWide * segsHigh) ; UNLESS snakeX THEN RESULTIS FALSE
maxFood := (segsWide * segsHigh / 200)
foodX := getvec (maxFood) ; UNLESS foodX THEN RESULTIS FALSE
foodY := getvec (maxFood) ; UNLESS foodY THEN RESULTIS FALSE
RESULTIS TRUE
}
/*
* printTitle
* Print the title and intro page
*********************************************************************************
*/
AND printTitle () BE
{
vduCls ()
vduXY (tWidth / 2 - 5, 4)
vduInk (colRed) ; sawrites ("S ")
vduInk (colYellow) ; sawrites ("N ")
vduInk (colRed) ; sawrites ("A ")
vduInk (colYellow) ; sawrites ("K ")
vduInk (colRed) ; sawrites ("E")
vduXY (tWidth / 2 - 5, 5)
vduInk (colYellow)
sawrites ("=========")
}
/*
* welcome
* Splash screen and instructions
*********************************************************************************
*/
AND welcome () BE
{
LET key = ?
vduInk (colWhite)
center ("*n")
center ("Instructions ? ")
key := sardch ()
REPEATUNTIL (key = 'y') | (key = 'n')
TEST key = 'y' THEN
{
sawritef ("Yes*n")
// For 40-column PicoCalc
// 1 2 3
// 0123456789012345678901234567890123456789
center ("*n")
center ("Control the snake with the arrow keys*n")
center ("Use the UP, DOWN, LEFT and RIGHT keys*n")
center ("to change the snakes direction.*n")
center ("*n")
center ("Look for food: Raspberry-like things*n")
center ("but avoid rocks!*n")
center ("*n")
center ("Food will make the snake grow, but if*n")
center ("you don't feed it, then it will shrink*n")
center ("and die.*n")
center ("*n")
center ("The longer the snake...*n")
center ("... the more points you get.*n")
center ("*n")
}
ELSE
sawritef ("No*n*n")
getSpace ()
}
/*
* start:
* Where we begin
*********************************************************************************
*/
AND start () = VALOF
{
LET key = ?
UNLESS setup () THEN
{
writef ("snake: Setup faile - out of memory*n")
RESULTIS 1
}
printTitle ()
welcome ()
// Outer Game loop
{
{
reset ()
drawScene ()
mainGameLoop ()
} REPEATUNTIL done
// Score is the snake length
vduXY (3,tHeight-1)
vduDeleteEOL ()
vduInk (colLime)
sawritef (" Score: %3d. Again? ", numSegs)
vduInk (colWhite)
key := sardch ()
REPEATUNTIL (key = 'y') | (key = 'n')
} REPEATUNTIL key = 'n'
sawrites ("No.*n")
freevec (snakeX)
freevec (snakeY)
freevec (foodX)
freevec (foodY)
RESULTIS 0
}
Cheers,
-G



