Talking about Sokoban game, and Game development.
Sometimes the art of coding takes you to unexpected shores. This is why I wrote a Sokoban game for the Commodore 64.
How did it come to my mind? Back in the eighties, there was a magazine called Zzap!64 covering the best games for 8-bit computers like C64, Spectrum ZX and more. There also was an italian version of the magazine, which recently came back to life, and I have the honor of being one of the editors.
The 8-bit scene is incredibly exciting, with a lot of new games being released every week, and there are also many new tools for modern computers to develop 8-bit games.
One of them is ugBasic, a cross-platform language similar to good old BASIC. I tried it, and ended up building a Sokoban game.
I already built a lot of Sokoban games for various platforms in various languages, so I said to myself: why not? Although I can code a Sokoban game with my eyes closed, this time I ran into a series of problems, like low RAM, no tweening, and just a few colors.
But eventually, as usual, I managed to build a Sokoban game with 64 levels. How can you play, if you don’t have an actual C64 hardware? With emulator like VICE.
Look at the source code:
BITMAP ENABLE (2)
COLOR BORDER BLACK
DIM levels AS BYTE (2304) = #{_
0,0,1,1,1,1,0,0,1,1,1,1,0,0,0,0,0,0,1,4,2,1,3,0,0,0,0,1,0,0,0,0,0,1,1,1,_
1,0,0,0,0,1,1,3,0,2,0,1,0,0,4,0,0,1,0,3,1,2,0,1,0,0,1,1,1,1,1,1,1,1,1,1,_
2,0,1,1,1,1,2,0,1,1,1,1,4,3,0,0,1,1,0,3,0,0,1,1,0,0,1,1,1,1,1,1,1,1,1,1,_
2,0,4,0,0,1,0,0,3,3,0,1,1,1,0,2,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,_
1,1,0,0,1,1,0,0,5,4,1,1,0,0,3,2,1,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,1,1,1,1,_
0,0,1,1,1,1,0,4,1,1,1,1,2,3,1,1,1,1,0,0,0,0,1,1,2,3,1,0,1,1,1,0,0,0,1,1,_
1,0,0,0,0,1,0,0,1,1,0,1,0,3,2,3,2,0,1,0,1,0,0,0,1,0,4,0,1,1,1,1,1,1,1,1,_
1,1,0,0,1,1,1,1,3,2,4,0,0,0,0,1,0,0,0,0,3,2,0,1,0,0,1,0,0,1,1,1,1,0,0,1,_
1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,1,2,1,0,0,3,3,2,1,1,0,0,0,4,1,1,1,1,1,1,1,_
1,0,0,0,1,1,1,3,1,2,1,1,0,0,0,0,1,1,0,3,1,2,0,1,1,0,4,0,0,1,1,1,1,0,0,1,_
0,0,0,1,0,0,0,0,2,0,0,0,1,3,1,1,0,0,0,4,2,1,0,0,0,3,0,0,0,0,1,1,1,1,0,0,_
2,2,6,1,1,1,0,3,3,1,1,1,1,3,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,1,1,1,1,1,1,_
1,0,0,0,1,1,1,0,0,0,1,1,1,1,0,0,1,1,0,3,3,3,0,1,0,2,6,2,0,1,1,1,1,1,1,1,_
0,0,1,1,1,1,0,0,0,0,1,1,0,3,2,0,1,1,1,0,5,0,1,1,1,3,2,0,1,1,1,0,4,1,1,1,_
1,0,0,0,0,1,1,0,1,0,0,1,2,5,1,0,0,1,2,0,1,0,1,1,4,3,3,0,0,0,0,0,1,0,0,0,_
1,0,0,0,0,1,1,0,2,2,2,1,0,3,3,3,0,1,0,4,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,_
0,2,2,0,0,1,0,2,4,3,0,1,1,0,3,3,0,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,_
0,4,1,1,1,1,0,0,1,1,1,1,3,3,3,0,0,1,2,2,2,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,_
1,1,0,6,0,1,1,1,0,5,0,1,0,0,3,5,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,_
0,0,1,1,1,1,0,0,1,1,1,1,0,0,3,0,0,1,1,3,5,2,0,1,0,0,2,0,1,1,0,0,4,0,1,1,_
0,0,0,0,0,1,2,1,0,1,0,1,0,3,3,2,0,1,2,0,0,1,1,1,0,1,3,1,1,1,0,0,4,1,1,1,_
1,0,0,0,0,1,1,0,2,2,2,1,0,3,3,0,3,1,4,0,1,0,0,1,0,0,1,0,0,1,1,1,1,1,1,1,_
1,1,0,0,1,1,2,0,3,0,3,0,4,1,0,0,1,0,0,1,3,2,2,0,0,0,0,1,1,1,1,1,1,1,1,1,_
1,1,0,0,1,1,1,2,3,0,1,1,0,3,2,4,1,1,0,2,3,0,1,1,0,0,1,1,1,1,0,0,1,1,1,1,_
0,0,0,1,1,1,0,0,0,1,1,1,1,3,3,0,3,0,1,2,2,0,2,0,1,0,0,1,1,1,1,4,0,1,1,1,_
1,0,0,1,1,1,0,0,0,1,1,1,0,5,2,4,0,1,1,3,5,0,0,1,0,0,0,1,1,1,0,0,0,1,1,1,_
1,1,0,0,1,1,1,1,3,0,1,1,0,6,5,2,0,1,0,0,3,0,0,1,0,0,0,1,1,1,0,0,0,1,1,1,_
1,1,0,0,0,1,1,0,0,1,0,1,1,6,3,5,5,1,0,0,0,1,0,1,0,0,0,0,0,1,1,1,1,1,1,1,_
0,0,1,0,0,0,0,2,0,0,2,2,0,0,0,1,1,1,0,3,3,3,0,1,1,1,4,0,0,1,1,1,1,1,1,1,_
1,1,0,0,1,1,0,0,0,0,1,1,0,3,0,3,0,1,0,0,1,3,4,1,2,2,2,0,1,1,1,1,1,1,1,1,_
1,0,0,1,1,1,1,0,0,0,0,0,1,3,2,2,0,2,0,0,1,1,4,1,0,3,3,0,0,1,0,0,0,0,0,1,_
0,2,1,1,1,1,0,2,1,1,1,1,3,0,0,3,4,0,0,0,0,3,0,0,2,0,1,1,1,1,1,1,1,1,1,1,_
1,1,1,1,4,0,1,1,0,0,0,0,0,3,3,1,2,0,2,0,0,0,0,1,0,3,0,1,0,1,1,1,0,0,2,1,_
0,0,0,1,1,1,0,1,0,1,1,1,0,0,0,1,1,1,5,6,3,5,0,1,0,0,0,0,0,1,1,1,1,0,0,1,_
1,1,1,0,0,1,0,0,2,3,0,1,0,0,2,3,4,1,1,1,2,3,1,1,1,1,0,0,1,1,1,1,0,0,1,1,_
0,0,1,1,1,1,0,3,2,0,1,1,0,0,3,2,1,1,1,3,2,0,1,1,1,4,0,1,1,1,1,0,0,1,1,1,_
1,1,1,0,0,1,0,0,1,0,0,1,0,0,3,3,3,0,4,2,2,0,2,0,1,1,1,0,0,1,1,1,1,1,1,1,_
0,0,1,1,1,1,0,2,2,0,4,0,0,3,0,1,1,0,1,0,0,1,1,0,1,3,2,0,3,0,1,0,0,1,1,1,_
1,1,0,0,1,1,1,1,0,3,1,1,0,0,3,2,3,0,0,0,2,4,2,0,1,0,0,0,1,1,1,1,1,1,1,1,_
0,0,0,0,1,1,0,0,2,0,0,1,1,3,5,3,0,1,1,0,2,4,1,1,1,1,0,0,1,1,1,1,1,1,1,1,_
1,1,0,2,0,1,1,1,3,2,0,1,0,0,0,2,0,1,0,3,3,0,0,1,1,0,4,1,1,1,1,1,1,1,1,1,_
0,0,0,0,1,1,4,3,3,0,1,1,1,3,2,2,1,1,1,0,2,0,1,1,1,1,0,0,1,1,1,1,1,1,1,1,_
0,0,0,0,0,1,0,1,3,2,0,1,0,0,3,2,1,1,1,1,3,2,1,1,1,1,4,0,1,1,1,1,0,0,1,1,_
1,1,0,4,1,1,0,0,5,0,1,1,0,0,5,2,0,1,0,0,5,3,0,1,0,0,0,1,1,1,1,1,1,1,1,1,_
1,1,1,0,0,0,1,1,1,4,1,0,0,0,0,0,3,0,0,1,2,2,2,1,0,3,0,1,3,1,1,1,0,0,0,1,_
1,0,0,1,1,1,0,0,0,1,1,1,0,0,5,3,4,0,0,0,2,5,0,0,1,1,0,0,1,1,1,1,1,1,1,1,_
1,1,0,0,1,1,1,1,4,0,1,1,1,1,3,5,1,1,0,0,5,2,0,0,0,0,0,0,0,0,0,0,1,1,1,1,_
0,0,1,1,1,1,0,2,3,2,1,1,0,0,3,0,0,0,1,1,0,0,4,2,1,1,3,0,1,1,1,1,0,0,1,1,_
1,2,0,0,2,1,0,2,0,1,0,1,0,0,0,3,0,1,0,3,1,3,1,1,1,0,4,0,1,1,1,1,1,1,1,1,_
0,0,2,0,0,1,0,1,5,1,0,1,5,0,3,0,0,1,0,0,4,0,1,1,0,0,1,1,1,1,1,1,1,1,1,1,_
2,0,0,1,1,1,2,1,0,1,1,1,0,0,0,3,0,1,0,3,3,2,0,1,0,0,4,1,1,1,1,1,1,1,1,1,_
0,0,0,1,1,1,0,1,0,1,1,1,0,1,6,0,1,1,0,3,5,3,0,1,0,0,2,0,0,1,1,1,1,0,0,1,_
1,1,0,0,1,1,1,1,0,2,1,1,1,1,3,0,3,0,0,4,0,3,1,0,0,1,2,2,0,0,0,0,0,1,1,1,_
1,1,0,0,1,1,0,0,0,5,1,1,0,1,0,2,1,1,0,0,4,3,1,1,1,1,0,5,0,1,1,1,0,0,0,1,_
1,1,1,0,0,1,0,0,1,3,2,1,0,0,0,3,2,1,0,0,1,3,2,1,1,0,1,0,0,1,1,0,0,4,0,1,_
1,0,0,1,1,1,1,3,0,0,0,1,1,4,3,2,0,1,1,3,1,2,1,1,0,0,0,2,1,1,0,0,0,0,1,1,_
1,1,0,0,1,1,1,0,3,0,1,1,0,2,3,2,1,1,0,0,3,2,0,1,1,1,4,0,0,1,1,1,1,0,0,1,_
1,1,1,1,0,0,1,0,4,0,0,0,0,5,5,5,2,0,0,0,3,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,_
1,1,0,0,1,1,1,1,0,0,0,0,0,3,3,5,0,0,0,2,4,2,0,1,0,0,1,1,1,1,1,1,1,1,1,1,_
1,1,0,0,1,1,1,1,2,4,2,0,0,0,3,2,1,0,0,0,0,3,0,0,1,1,1,0,3,1,1,1,1,0,0,1,_
1,0,2,0,1,1,1,4,0,3,1,1,0,0,5,0,0,0,0,5,0,5,0,0,1,0,0,0,1,1,1,1,1,1,1,1,_
1,0,0,0,1,1,1,3,1,0,1,1,0,4,0,0,1,1,0,1,0,2,1,1,2,3,3,2,0,1,1,1,0,0,0,1,_
0,0,0,0,0,1,0,1,0,1,0,1,0,2,0,3,2,0,1,1,3,5,3,0,1,1,0,2,0,1,1,1,1,4,0,1,_
1,0,0,0,0,1,1,3,1,1,2,1,1,0,1,1,2,1,0,0,0,3,2,0,0,3,0,0,4,0,1,1,0,0,1,1_
}
wallImage := LOAD IMAGE("images/wall.png")
playerImage := LOAD IMAGE("images/player.png")
crateImage := LOAD IMAGE("images/crate.png")
goalImage := LOAD IMAGE("images/goal.png")
floorImage := LOAD IMAGE("images/floor.png")
crateOnGoalImage := LOAD IMAGE("images/crate_in_place.png")
arrowImage := LOAD IMAGE("images/arrow.png")
lockedImage := LOAD IMAGE("images/locked.png")
unlockedImage := LOAD IMAGE("images/unlocked.png")
DIM levelArray AS BYTE (8, 8)
DIM solvedLevels AS BYTE WITH 0 (64)
CONST floor = 0
CONST wall = 1
CONST goal = 2
CONST crate = 3
CONST player = 4
VAR level AS BYTE = 0
VAR posX AS BYTE
VAR posY AS BYTE
VAR number
VAR playerX AS BYTE
VAR playerY AS BYTE
VAR destinationX AS BYTE
VAR destinationY AS BYTE
VAR crateDestinationX AS BYTE
VAR crateDestinationY AS BYTE
VAR start AS BYTE
CLS
LOCATE 17, 6 : PRINT "SOKO64"
LOCATE 15, 10 : PRINT "PRESS FIRE"
LOCATE 9, 23 : PRINT "2022 Emanuele Feronato"
PUT IMAGE playerImage AT 120, 110
PUT IMAGE crateImage AT 152, 110
PUT IMAGE goalImage AT 184, 110
DO
IF JFIRE(1) THEN
EXIT
ENDIF
LOOP
PUT IMAGE floorImage AT 120, 110
PUT IMAGE playerImage AT 136, 110
WAIT 100 MILLISECONDS
PUT IMAGE floorImage AT 136, 110
PUT IMAGE playerImage AT 152, 110
PUT IMAGE crateImage AT 168, 110
WAIT 100 MILLISECONDS
PUT IMAGE floorImage AT 152, 110
PUT IMAGE playerImage AT 168, 110
PUT IMAGE crateOnGoalImage AT 184, 110
WAIT 1000 MILLISECONDS
playLevel :
CLS
IF level < 6 THEN
start = 1
ELSE
start = level - 5
IF start > 52 THEN
start = 52
ENDIF
ENDIF
FOR i = 0 TO 12
IF start + i < 10 THEN
LOCATE i * 3 + 1, 22 : PRINT "0"
LOCATE i * 3 + 2, 22 : PRINT (start + i)
ELSE
LOCATE i * 3 + 1, 22 : PRINT (start + i)
ENDIF
IF solvedLevels(start + i - 1) = 1 THEN
PUT IMAGE unlockedImage AT 8 + 24 * i, 172
ELSE
PUT IMAGE lockedImage AT 8 + 24 * i, 172
ENDIF
NEXT
LOCATE 0, 0 : PRINT "(P)rev"
LOCATE 17, 0 : PRINT "(R)etry"
LOCATE 34, 0 : PRINT "(N)ext"
PUT IMAGE arrowImage AT 8 + 24 * (level - start + 1), 184
posY = 48
number = 36 * level
posX = 96
FOR i = 0 TO 7
levelArray(0, i) = wall
PUT IMAGE wallImage AT posX, 32
levelArray(7, i) = wall
PUT IMAGE wallImage AT posX, 144
ADD posX, 16
NEXT
FOR i = 1 TO 6
levelArray(i, 0) = wall
PUT IMAGE wallImage AT 96, posY
levelArray(i, 7) = wall
PUT IMAGE wallImage AT 208, posY
posX = 112
FOR j = 1 TO 6
value = levels(number)
levelArray(i, j) = floor
IF value = wall THEN
levelArray(i, j) = wall
PUT IMAGE wallImage AT posX, posY
ENDIF
IF value = player THEN
levelArray(i, j) = player
PUT IMAGE playerImage AT posX, posY
playerX = j
playerY = i
ENDIF
IF value = player + goal THEN
levelArray(i, j) = player + goal
PUT IMAGE playerImage AT posX, posY
playerX = j
playerY = i
ENDIF
IF value = crate THEN
levelArray(i, j) = crate
PUT IMAGE crateImage AT posX, posY
ENDIF
IF value = goal THEN
levelArray(i, j) = goal
PUT IMAGE goalImage AT posX, posY
ENDIF
IF value = crate + goal THEN
levelArray(i, j) = crate + goal
PUT IMAGE crateOnGoalImage AT posX, posY
ENDIF
ADD posX, 16
INC number
NEXT
ADD posY, 16
NEXT
playerInput:
DO
IF JLEFT(1) THEN
destinationX = playerX - 1
destinationY = playerY
crateDestinationX = destinationX - 1
crateDestinationY = destinationY
GOTO movePlayer
ENDIF
IF JRIGHT(1) THEN
destinationX = playerX + 1
destinationY = playerY
crateDestinationX = destinationX + 1
crateDestinationY = destinationY
GOTO movePlayer
ENDIF
IF JUP(1) THEN
destinationX = playerX
destinationY = playerY - 1
crateDestinationX = destinationX
crateDestinationY = destinationY - 1
GOTO movePlayer
ENDIF
IF JDOWN(1) THEN
destinationX = playerX
destinationY = playerY + 1
crateDestinationX = destinationX
crateDestinationY = destinationY + 1
GOTO movePlayer
ENDIF
IF KEY STATE(KEY R) THEN
GOTO playLevel
ENDIF
IF (KEY STATE(KEY P)) AND (level > 0) THEN
DEC level
GOTO playLevel
ENDIF
IF (KEY STATE(KEY N)) AND (level < 63) THEN
INC level
GOTO playLevel
ENDIF
LOOP
movePlayer :
IF (levelArray(destinationY, destinationX) = goal) OR (levelArray(destinationY, destinationX) = floor) THEN
IF levelArray(playerY, playerX) = player THEN
PUT IMAGE floorImage AT playerX * 16 + 96 , playerY * 16 + 32
ELSE
PUT IMAGE goalImage AT playerX * 16 + 96 , playerY * 16 + 32
ENDIF
ADD levelArray(playerY, playerX), - player
playerX = destinationX
playerY = destinationY
ADD levelArray(destinationY, destinationX), player
PUT IMAGE playerImage AT playerX * 16 + 96, playerY * 16 + 32
WAIT 100 MILLISECONDS
GOTO playerInput
ENDIF
IF (levelArray(destinationY, destinationX) = crate) OR (levelArray(destinationY, destinationX) = crate + goal) THEN
IF (levelArray(crateDestinationY, crateDestinationX) = goal) OR (levelArray(crateDestinationY, crateDestinationX) = floor) THEN
IF levelArray(playerY, playerX) = player THEN
PUT IMAGE floorImage AT playerX * 16 + 96 , playerY * 16 + 32
ELSE
PUT IMAGE goalImage AT playerX * 16 + 96 , playerY * 16 + 32
ENDIF
ADD levelArray(playerY, playerX), - player
playerX = destinationX
playerY = destinationY
ADD levelArray(destinationY, destinationX), player
ADD levelArray(destinationY, destinationX), -crate
ADD levelArray(crateDestinationY, crateDestinationX), crate
PUT IMAGE playerImage AT playerX * 16 + 96, playerY * 16 + 32
IF levelArray(crateDestinationY, crateDestinationX) = crate + goal THEN
PUT IMAGE crateOnGoalImage AT crateDestinationX * 16 + 96, crateDestinationY * 16 + 32
ELSE
PUT IMAGE crateImage AT crateDestinationX * 16 + 96, crateDestinationY * 16 + 32
ENDIF
FOR i = 0 TO 7
FOR j = 0 TO 7
IF (levelArray(i, j) = goal) OR (levelArray(i, j) = player + goal) THEN
WAIT 100 MILLISECONDS
GOTO playerInput
ENDIF
NEXT
NEXT
GOTO levelCompleted
ENDIF
ENDIF
GOTO playerInput
levelCompleted :
LOCATE 17, 3 : PRINT "SOLVED!"
solvedLevels(level) = 1
DO
IF KEY STATE(KEY R) THEN
GOTO playLevel
ENDIF
IF (KEY STATE(KEY P)) AND (level > 0) THEN
DEC level
GOTO playLevel
ENDIF
IF (KEY STATE(KEY N)) AND (level < 63) THEN
INC level
GOTO playLevel
ENDIF
LOOP
Tomorrow I will comment the source and upload the game to my itch page, meanwhile you can download the source code of the whole project and play with it.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.