Snake Game - a game written in Forth

General games-related topics
Post Reply
User avatar
kmurta
Posts: 322
Joined: Tue Sep 01, 2009 5:04 am
Location: Belo Horizonte - BR
Contact:

Snake Game - a game written in Forth

Post by kmurta »

There is this good tutorial on the Forth language which presents at the end a small game written in Forth, Snake!. We will see that the task of transcribing it for the Forth-79 is very simple, even though the game was written to run on modern PCs.

Before proceeding, I suggest you read the original article to have a better understanding of the program logic, as below I will only explain the parts that require changes to work in Toddy Forth-79. And if you have little or no familiarity with Forth, then it's best to read the tutorial from the beginning.

The game code

The original code is written in lowercase, but will be transcribed here using uppercase, as required by TF79.

Variables and contants

The code starts with the creation of two variables that will store the snake's coordinates, SNAKE-X-HEAD and SNAKE-Y-HEAD. These are two one-dimensional arrays, each with 500 memory cells. These variables were created with the word VARIABLE, but the TF79 (as well as most Forths) has another word more suitable for this: CREATE . CREATE creates an empty definition, that is, without anything in your CFA/PFA (if you don't understand what this is, read section 11.2 of the manual). For example, CREATE FOO adds the word FOO to the vocabulary and when we invoke FOO all it does is leave the address of its CFA/PFA on the stack. The word CELLS is not part of the TF79 vocabulary, but it can be easily defined. In Forths that run on 8-bit computers, each cell is equivalent to 2 bytes, so our code would look like this:

Code: Select all

: CELLS  ( n -- ) 2* ;
CREATE SNAKE-X-HEAD  500 CELLS ALLOT
CREATE SNAKE-Y-HEAD  500 CELLS ALLOT
The sentence 500 CELLS ALLOT reserves 500 memory cells, or 1000 bytes. However, the game field has dimensions of 24x24, requiring no more than one byte to store each coordinate. Furthermore, manipulating one byte is faster than manipulating two. So the previous definitions are just

Code: Select all

[CREATE SNAKE-X-HEAD  500 ALLOT
CREATE SNAKE-Y-HEAD  500 ALLOT
and we can discard the definition of CELLS. The following definitions do not require changes, except SNAKE-X and SNAKE-Y where it is enough to eliminate CELLS from their definitions.

After that, we define the words that will draw the snake:

Code: Select all

: DRAW-WHITE ( x y -- )  SWAP AT BL EMIT ;
: DRAW-BLACK ( x y -- )  SWAP AT 128 EMIT ;
The word AT positions the cursor in the coordinates found on the stack, it is equivalent to BASIC's AT. BL leaves 32 on the stack, which is the ASCII code for the space character; and 128 is the code of reverse space.

So the code for this first part looks like this:

Code: Select all

CREATE SNAKE-X-HEAD 500 ALLOT
CREATE SNAKE-Y-HEAD 500 ALLOT

VARIABLE APPLE-X
VARIABLE APPLE-Y

 0 CONSTANT LEFT
 1 CONSTANT UP
 2 CONSTANT RIGHT
 3 CONSTANT DOWN

24 CONSTANT WIDTH
24 CONSTANT HEIGHT

VARIABLE DIRECTION
VARIABLE LENGTH

: SNAKE-X SNAKE-X-HEAD + ;
: SNAKE-Y SNAKE-Y-HEAD + ;

: DRAW-WHITE SWAP AT BL EMIT ;
: DRAW-BLACK SWAP AT 128 EMIT ;
Initialization

Here we have just two small changes, the first is in the definition of INITIALIZE-SNAKE where we replace the ! by C! (we will manipulate only 1 byte of the array); The second change is in the definition of INITIALIZE, where the loops are replaced by the word PAGE, to erase the screen (not necessary, but more efficient).

This part then looks like this:

Code: Select all

: DRAW-WALLS
  WIDTH 0 DO
    I 0 DRAW-BLACK
    I HEIGHT 1 - DRAW-BLACK
  LOOP
  HEIGHT 0 DO
    0 I DRAW-BLACK
    WIDTH 1 - I DRAW-BLACK
  LOOP ;

: INITIALIZE-SNAKE
  4 LENGTH !
  LENGTH @ 1 + 0 DO
    12 I - I SNAKE-X C!
    12 I SNAKE-Y C!
  LOOP
  RIGHT DIRECTION ! ;

: SET-APPLE-POSITION
  APPLE-X !  APPLE-Y ! ;

: INITIALIZE-APPLE
  4 4 SET-APPLE-POSITION ;

: INITIALIZE
  PAGE
  DRAW-WALLS
  INITIALIZE-SNAKE
  INITIALIZE-APPLE ;

Moving the Snake

Here the words MOVE-* and MOVE-SNAKE-TAIL are changed so that they can handle only 1 byte, in accordance with the new definitions of SNAKE-X-HEAD and SNAKE-Y-HEAD. MOVE-SNAKE-HEAD does not change. But another not so obvious adjustment is still needed in MOVE-SNAKE-TAIL.

With the Forth-83 standard, the DO/LOOP structure had its behavior slightly modified in relation to Forth-79. I won't go into detail about these differences, a quick Google search will bring up this information. But one difference in particular applies to the DO/+LOOP present in MOVE-SNAKE-TAIL: when there is a negative step, in the most recent Forths the loop performs once more than in the Forth-79. For example, 0 3 DO I . -1 +LOOP will print the sequence 3 2 1 on Forth-79 and 3 2 1 0 post Forth-83. The solution is simple, subtract 1 from the loop limit, so the equivalent loop in Forth-79 will be -1 3 DO I . -1 +LOOP.

So the code for this part looks like this:

Code: Select all

: MOVE-UP  SNAKE-Y-HEAD C@
  1- SNAKE-Y-HEAD C! ;
: MOVE-LEFT  SNAKE-X-HEAD C@
  1- SNAKE-X-HEAD C! ;
: MOVE-DOWN  SNAKE-Y-HEAD C@
  1+ SNAKE-Y-HEAD C! ;
: MOVE-RIGHT  SNAKE-X-HEAD C@
  1+ SNAKE-X-HEAD C! ;

: MOVE-SNAKE-HEAD  DIRECTION @
  LEFT OVER  = IF MOVE-LEFT ELSE
  UP OVER    = IF MOVE-UP ELSE
  RIGHT OVER = IF MOVE-RIGHT
  ELSE DOWN OVER  = IF MOVE-DOWN
  THEN THEN THEN THEN DROP ;

: MOVE-SNAKE-TAIL
  -1 LENGTH @ DO
    I SNAKE-X C@ I 1+ SNAKE-X C!
    I SNAKE-Y C@ I 1+ SNAKE-Y C!
  -1 +LOOP ;

Keyboard Input

Here just the use of the word INKEY in CHECK-INPUT and the replacement of the key codes with ASCII key in CHANGE-DIRECTION, with control of the snake being assigned to the IJKL keys.

Here is the code for that part:

Code: Select all

: IS-HORIZONTAL  DIRECTION @ DUP
  LEFT = SWAP
  RIGHT = OR ;

: IS-VERTICAL  DIRECTION @ DUP
  UP = SWAP
  DOWN = OR ;

: TURN-UP     IS-HORIZONTAL
  IF UP DIRECTION ! THEN ;
: TURN-LEFT   IS-VERTICAL
  IF LEFT DIRECTION ! THEN ;
: TURN-DOWN   IS-HORIZONTAL
  IF DOWN DIRECTION ! THEN ;
: TURN-RIGHT  IS-VERTICAL
  IF RIGHT DIRECTION ! THEN ;

: CHANGE-DIRECTION
  ASCII J OVER =
  IF TURN-LEFT ELSE
  ASCII I OVER =
  IF TURN-UP ELSE
  ASCII L OVER =
  IF TURN-RIGHT ELSE
  ASCII K OVER =
  IF TURN-DOWN
  THEN THEN THEN THEN DROP ;

: CHECK-INPUT
  INKEY CHANGE-DIRECTION  ;

The Apple

Here only the replacement of RANDOM by RND, the usual exchange of @ for C@ associated with SNAKE-X_HEAD and SNAKE-Y-HEAD and, finally, the inclusion of the word BELL in CHECK-APPLE to produce a sound signal when the snake take an apple:

Code: Select all

: RANDOM-POSITION 
  WIDTH 4 - RND 2 + ;

: MOVE-APPLE
  APPLE-X @ APPLE-Y @ DRAW-WHITE
  RANDOM-POSITION
  RANDOM-POSITION
  SET-APPLE-POSITION ;

: GROW-SNAKE  1 LENGTH +! ;

: CHECK-APPLE
  SNAKE-X-HEAD C@ APPLE-X @ =
  SNAKE-Y-HEAD C@ APPLE-Y @ =
  AND IF  BELL
          MOVE-APPLE
          GROW-SNAKE
      THEN ;

Collision Detection

Coordinates are inverted to comply with AT requirement; address 16398 contains the address within the DFILE of the coordinate defined by the AT.

Code: Select all

: CHECK-COLLISION
  SNAKE-Y-HEAD C@
  SNAKE-X-HEAD C@  AT
  16398 @  C@ 128 = ;

Drawing the Snake and Apple

The usual exchanges of @ for C@ associated with SNAKE-X and SNAKE-Y and the adoption of the asterisk to represent the apple:

Code: Select all

: DRAW-SNAKE
  LENGTH @ 0
  DO
    I SNAKE-X C@ I SNAKE-Y C@
    DRAW-BLACK
  LOOP
  LENGTH @ SNAKE-X C@
  LENGTH @ SNAKE-Y C@
  DRAW-WHITE ;

: DRAW-APPLE
  APPLE-Y @ 
  APPLE-X @
  AT ASCII * EMIT ;

The Game Loop

The "GAME OVER" message was moved to START, a message informing the length of the snake was included and also an end-of-game sound signal was added:

Code: Select all

: GAME-LOOP
  BEGIN
    DRAW-SNAKE
    DRAW-APPLE
    CHECK-INPUT
    MOVE-SNAKE-TAIL
    MOVE-SNAKE-HEAD
    CHECK-APPLE
    CHECK-COLLISION
  UNTIL ;

: START INITIALIZE GAME-LOOP
  7 26 AT ." GAME" 8 26 AT
  ." OVER" 11 25 AT ." SNAKE"
  12 25 AT ." LENGTH:"
  14 27 AT  LENGTH @ 3 .R 
  3 0 DO BELL 2 WAIT LOOP
  BEGIN INKEY UNTIL PAGE ;

The word SLEEP has an equivalent in the TF79, WAIT, which pauses processing for multiples of 20ms. The 100 SLEEP of the original program could then be replaced by 5 WAIT ( 5x20ms ), but this was not necessary here. At start the snake is very fast, but it becomes slower as it grows, making the game boring after a certain size (see the video below).

Click to open
Click to open

Below is the complete list of the game. Copy and paste the text into the SNAKE.FTH file and then convert it to a block file:

blk2fth.pl SNAKE.FTH

Then transfer the created file SNAKE.BLK to the ZXpand SD card and load it into the Forth-79.

Code: Select all

\     -= SNAKE GAME =-     1/6
CREATE SNAKE-X-HEAD 500 ALLOT
CREATE SNAKE-Y-HEAD 500 ALLOT

VARIABLE APPLE-X
VARIABLE APPLE-Y

0 CONSTANT LEFT
1 CONSTANT UP
2 CONSTANT RIGHT
3 CONSTANT DOWN
24 CONSTANT WIDTH
24 CONSTANT HEIGHT

VARIABLE DIRECTION
VARIABLE LENGTH

: SNAKE-X  ( n -- addr)
  SNAKE-X-HEAD + ;

: SNAKE-Y  ( n -- addr)
  SNAKE-Y-HEAD + ;

: DRAW-WHITE SWAP AT BL EMIT ;
: DRAW-BLACK SWAP AT 128 EMIT ;

: DRAW-WALLS
  WIDTH 0 DO
    I 0 DRAW-BLACK
    I HEIGHT 1 - DRAW-BLACK
  LOOP
  HEIGHT 0 DO              -->
\     -= SNAKE GAME =-     2/6
    0 I DRAW-BLACK
    WIDTH 1 - I DRAW-BLACK
  LOOP ;

: INITIALIZE-SNAKE
  4 LENGTH !
  LENGTH @ 1 + 0 DO
    12 I - I SNAKE-X C!
    12 I SNAKE-Y C!
  LOOP
  RIGHT DIRECTION ! ;

: SET-APPLE-POSITION
  APPLE-X !  APPLE-Y ! ;

: INITIALIZE-APPLE
  4 4 SET-APPLE-POSITION ;

: INITIALIZE
  PAGE
  DRAW-WALLS
  INITIALIZE-SNAKE
  INITIALIZE-APPLE ;

: MOVE-UP  SNAKE-Y-HEAD C@
  1- SNAKE-Y-HEAD C! ;
: MOVE-LEFT  SNAKE-X-HEAD C@
  1- SNAKE-X-HEAD C! ;
: MOVE-DOWN  SNAKE-Y-HEAD C@
  1+ SNAKE-Y-HEAD C! ;
  -->
\     -= SNAKE GAME =-     3/6
: MOVE-RIGHT  SNAKE-X-HEAD C@
  1+ SNAKE-X-HEAD C! ;

: MOVE-SNAKE-HEAD  DIRECTION @
  LEFT OVER  = IF MOVE-LEFT ELSE
  UP OVER    = IF MOVE-UP ELSE
  RIGHT OVER = IF MOVE-RIGHT
  ELSE DOWN OVER  = IF MOVE-DOWN
  THEN THEN THEN THEN DROP ;

: MOVE-SNAKE-TAIL
  -1 LENGTH @ DO
    I SNAKE-X C@ I 1+ SNAKE-X C!
    I SNAKE-Y C@ I 1+ SNAKE-Y C!
  -1 +LOOP ;

: IS-HORIZONTAL  DIRECTION @ DUP
  LEFT = SWAP
  RIGHT = OR ;

: IS-VERTICAL  DIRECTION @ DUP
  UP = SWAP
  DOWN = OR ;

: TURN-UP     IS-HORIZONTAL
  IF UP DIRECTION ! THEN ;
: TURN-LEFT   IS-VERTICAL
  IF LEFT DIRECTION ! THEN ;
: TURN-DOWN   IS-HORIZONTAL
  IF DOWN DIRECTION ! THEN ;
-->
\     -= SNAKE GAME =-     4/6

: TURN-RIGHT  IS-VERTICAL
  IF RIGHT DIRECTION ! THEN ;

: CHANGE-DIRECTION
  ASCII J OVER =
  IF TURN-LEFT ELSE
  ASCII I OVER =
  IF TURN-UP ELSE
  ASCII L OVER =
  IF TURN-RIGHT ELSE
  ASCII K OVER =
  IF TURN-DOWN
  THEN THEN THEN THEN DROP ;

: CHECK-INPUT
  INKEY CHANGE-DIRECTION ;

: RANDOM-POSITION 
  WIDTH 4 - RND 2 + ;

: MOVE-APPLE
  APPLE-X @ APPLE-Y @ DRAW-WHITE
  RANDOM-POSITION
  RANDOM-POSITION
  SET-APPLE-POSITION ;

: GROW-SNAKE  1 LENGTH +! ;
  -->


\     -= SNAKE GAME =-     5/6
: CHECK-APPLE
  SNAKE-X-HEAD C@ APPLE-X @ =
  SNAKE-Y-HEAD C@ APPLE-Y @ =
  AND IF  BELL
          MOVE-APPLE
          GROW-SNAKE
      THEN ;

: CHECK-COLLISION
  SNAKE-Y-HEAD C@
  SNAKE-X-HEAD C@ AT
  16398 @  C@ 128 = ;

: DRAW-SNAKE
  LENGTH @ 0
  DO
    I SNAKE-X C@ I SNAKE-Y C@
    DRAW-BLACK
  LOOP
  LENGTH @ SNAKE-X C@
  LENGTH @ SNAKE-Y C@
  DRAW-WHITE ;

: DRAW-APPLE
  APPLE-Y @ 
  APPLE-X @
  AT ASCII * EMIT ;
  -->



\     -= SNAKE GAME =-     6/6

: GAME-LOOP
  BEGIN
    DRAW-SNAKE
    DRAW-APPLE
    CHECK-INPUT
    MOVE-SNAKE-TAIL
    MOVE-SNAKE-HEAD
    CHECK-APPLE
    CHECK-COLLISION
  UNTIL ;

: START INITIALIZE GAME-LOOP
  7 26 AT ." GAME" 8 26 AT
  ." OVER" 11 25 AT ." SNAKE"
  12 25 AT ." LENGTH:"
  14 27 AT  LENGTH @ 3 .R 
  3 0 DO BELL 2 WAIT LOOP
  BEGIN INKEY UNTIL PAGE ;
Note: Listing corrected on 03/17/2024

In the next article we will see how we can optimize the game to maintain a satisfactory speed.
1 x ZX81, 2 x TK85 , 1 TK82C, 1 TK95, 1 x Alphacom 32 printer, 1 x ZXpand
ZeXtender board, Joy81 - Programmable Joystick Controller, Turbo Sound 81
http://zx81.eu5.org
https://toddysoftware.itch.io/
User avatar
kmurta
Posts: 322
Joined: Tue Sep 01, 2009 5:04 am
Location: Belo Horizonte - BR
Contact:

Snake Game - Some optimizations

Post by kmurta »

We saw previously that the snake becomes slower as it grows and the main reason for this is the DRAW-SNAKE routine:

Code: Select all

: DRAW-SNAKE
  LENGTH @ 0
  DO
    I SNAKE-X C@ I SNAKE-Y C@
    DRAW-BLACK
  LOOP
  LENGTH @ SNAKE-X C@
  LENGTH @ SNAKE-Y C@
  DRAW-WHITE ;
Note that the snake is completely redrawed for each cycle, which is a waste of processing as only the positions of the snake's head and tail would need to be updated. On modern PCs this is not critical, but on the little Zeddy it makes a lot of difference.

Let's then redefine DRAW-SNAKE, but first let's change LENGTH to 2 in INITIALIZE-SNAKE:

Code: Select all

: INITIALIZE-SNAKE
  2 LENGTH !
  LENGTH @ 1+ 0 DO
    12 I - I SNAKE-X C!
    12 I SNAKE-Y C!
  LOOP
  RIGHT DIRECTION ! ;
This is necessary to make DRAW-SNAKE as simple as possible:

Code: Select all

: DRAW-SNAKE
  0 SNAKE-X C@
  0 SNAKE-Y C@  DRAW-BLACK       \ draw snake head
  LENGTH @ DUP SNAKE-X C@
  SWAP SNAKE-Y C@ DRAW-WHITE ;   \ erase snake tail
These changes are enough to give to the snake a more uniform speed, regardless of its size.

In fact, the game is so fast that there was a need to insert a delay between cycles. Include the line below in the variable declaration on screen 1:

Code: Select all

VARIABLE SLEEP  5 SLEEP !
and include SLEEP @ WAIT in the GAME-LOOP definition, on screen 6:

Code: Select all

: GAME-LOOP
  BEGIN
    DRAW-SNAKE
    DRAW-APPLE
    SLEEP @ WAIT
    CHECK-INPUT
    MOVE-SNAKE-TAIL
    MOVE-SNAKE-HEAD
    CHECK-APPLE
    CHECK-COLLISION
  UNTIL ;
So if you want to change the speed of the game, just change the SLEEP value. The smaller, the faster.

But there is still another routine that can be optimized, MOVE-SNAKE-TAIL:

Code: Select all

: MOVE-SNAKE-TAIL
  -1 LENGTH @ DO
    I SNAKE-X C@ I 1+ SNAKE-X C!
    I SNAKE-Y C@ I 1+ SNAKE-Y C!
  -1 +LOOP ;
Note that the contents of the SNAKE-X-HEAD and SNAKE-Y-HEAD arrays are moved up one position, making room for the new position of the snake's head in the first element of the array. This displacement is done backwards, from the highest position within the arrays, as explained in the original article. It is easy to see that, as happened with DRAW-SNAKE, as the snake grows, the longer MOVE-SNAKE-TAIL takes to update all positions.

So what to do? Is there a faster way to do this?

Anyone who programs in Z80 assembly will quickly realize that this displacement of array values could be done using the LDDR instruction, but does the Forth-79 have something similar?

Yes, he has! The word <CMOVE is nothing more than the open door to the LDDR instruction, just as CMOVE is to the LDIR instruction!

So MOVE-SNAKE-TAIL is redefined as a function of <CMOVE :

Code: Select all

: MOVE-SNAKE-TAIL
  SNAKE-X-HEAD DUP 1+ LENGTH @
  1+ <CMOVE
  SNAKE-Y-HEAD DUP 1+ LENGTH @
  1+ <CMOVE ;
Below I leave the video demonstrating the program's editing process, its compilation and final result.

click to open
click to open

Code: Select all

\     -= SNAKE GAME =-     1/6
CREATE SNAKE-X-HEAD 500 ALLOT
CREATE SNAKE-Y-HEAD 500 ALLOT

VARIABLE APPLE-X
VARIABLE APPLE-Y

0 CONSTANT LEFT
1 CONSTANT UP
2 CONSTANT RIGHT
3 CONSTANT DOWN
24 CONSTANT WIDTH
24 CONSTANT HEIGHT

VARIABLE DIRECTION
VARIABLE LENGTH
VARIABLE SLEEP  5 SLEEP !

: SNAKE-X  ( n -- addr)
  SNAKE-X-HEAD + ;
: SNAKE-Y  ( n -- addr)
  SNAKE-Y-HEAD + ;

: DRAW-WHITE SWAP AT BL EMIT ;
: DRAW-BLACK SWAP AT 128 EMIT ;

: DRAW-WALLS
  WIDTH 0 DO
    I 0 DRAW-BLACK
    I HEIGHT 1 - DRAW-BLACK
  LOOP
  HEIGHT 0 DO              -->
\     -= SNAKE GAME =-     2/6
    0 I DRAW-BLACK
    WIDTH 1 - I DRAW-BLACK
  LOOP ;

: INITIALIZE-SNAKE
  2 LENGTH !
  LENGTH @ 1 + 0 DO
    12 I - I SNAKE-X C!
    12 I SNAKE-Y C!
  LOOP
  RIGHT DIRECTION ! ;

: SET-APPLE-POSITION
  APPLE-X !  APPLE-Y ! ;

: INITIALIZE-APPLE
  4 4 SET-APPLE-POSITION ;

: INITIALIZE
  PAGE
  DRAW-WALLS
  INITIALIZE-SNAKE
  INITIALIZE-APPLE ;

: MOVE-UP  SNAKE-Y-HEAD C@
  1- SNAKE-Y-HEAD C! ;
: MOVE-LEFT  SNAKE-X-HEAD C@
  1- SNAKE-X-HEAD C! ;
: MOVE-DOWN  SNAKE-Y-HEAD C@
  1+ SNAKE-Y-HEAD C! ;
  -->
\     -= SNAKE GAME =-     3/6
: MOVE-RIGHT  SNAKE-X-HEAD C@
  1+ SNAKE-X-HEAD C! ;

: MOVE-SNAKE-HEAD  DIRECTION @
  LEFT OVER  = IF MOVE-LEFT ELSE
  UP OVER    = IF MOVE-UP ELSE
  RIGHT OVER = IF MOVE-RIGHT
  ELSE DOWN OVER  = IF MOVE-DOWN
  THEN THEN THEN THEN DROP ;

: MOVE-SNAKE-TAIL
  SNAKE-X-HEAD DUP 1+ LENGTH @
  1+ <CMOVE
  SNAKE-Y-HEAD DUP 1+ LENGTH @
  1+ <CMOVE ;

: IS-HORIZONTAL  DIRECTION @ DUP
  LEFT = SWAP
  RIGHT = OR ;

: IS-VERTICAL  DIRECTION @ DUP
  UP = SWAP
  DOWN = OR ;

: TURN-UP     IS-HORIZONTAL
  IF UP DIRECTION ! THEN ;
: TURN-LEFT   IS-VERTICAL
  IF LEFT DIRECTION ! THEN ;
: TURN-DOWN   IS-HORIZONTAL
  IF DOWN DIRECTION ! THEN ;
-->
\     -= SNAKE GAME =-     4/6

: TURN-RIGHT  IS-VERTICAL
  IF RIGHT DIRECTION ! THEN ;

: CHANGE-DIRECTION
  ASCII J OVER =
  IF TURN-LEFT ELSE
  ASCII I OVER =
  IF TURN-UP ELSE
  ASCII L OVER =
  IF TURN-RIGHT ELSE
  ASCII K OVER =
  IF TURN-DOWN
  THEN THEN THEN THEN DROP ;

: CHECK-INPUT
  INKEY CHANGE-DIRECTION ;

: RANDOM-POSITION
  WIDTH 4 - RND 2 + ;

: MOVE-APPLE
  APPLE-X @ APPLE-Y @ DRAW-WHITE
  RANDOM-POSITION
  RANDOM-POSITION
  SET-APPLE-POSITION ;

: GROW-SNAKE  1 LENGTH +! ;
  -->


\     -= SNAKE GAME =-     5/6
: CHECK-APPLE
  SNAKE-X-HEAD C@ APPLE-X @ =
  SNAKE-Y-HEAD C@ APPLE-Y @ =
  AND IF  BELL
          MOVE-APPLE
          GROW-SNAKE
      THEN ;

: CHECK-COLLISION
  SNAKE-Y-HEAD C@
  SNAKE-X-HEAD C@ AT
  16398 @  C@ 128 = ;



: DRAW-SNAKE
  0 SNAKE-X C@
  0 SNAKE-Y C@
  DRAW-BLACK
  LENGTH @ DUP SNAKE-X C@
  SWAP SNAKE-Y C@
  DRAW-WHITE ;

: DRAW-APPLE
  APPLE-Y @
  APPLE-X @
  AT ASCII * EMIT ;
  -->



\     -= SNAKE GAME =-     6/6

: GAME-LOOP
  BEGIN
    DRAW-SNAKE
    DRAW-APPLE
    SLEEP @  WAIT
    CHECK-INPUT
    MOVE-SNAKE-TAIL
    MOVE-SNAKE-HEAD
    CHECK-APPLE
    CHECK-COLLISION
  UNTIL ;

: START INITIALIZE GAME-LOOP
  7 26 AT ." GAME" 8 26 AT
  ." OVER" 11 25 AT ." SNAKE"
  12 25 AT ." LENGTH:"
  14 27 AT  LENGTH @ 3 .R
  3 0 DO BELL 2 WAIT LOOP
  BEGIN INKEY UNTIL PAGE ;


The objective of these articles was to try to demystify a little the idea that Forth is a complicated and inaccessible language for most mortals. In fact, for those who allow themselves to venture into their fields, Forth is a fun and very stimulating language for the brain, as it forces us to think in an unconventional way. I hope I was able to, at least partially, achieve my goal.

See you next!
1 x ZX81, 2 x TK85 , 1 TK82C, 1 TK95, 1 x Alphacom 32 printer, 1 x ZXpand
ZeXtender board, Joy81 - Programmable Joystick Controller, Turbo Sound 81
http://zx81.eu5.org
https://toddysoftware.itch.io/
Moggy
Posts: 3416
Joined: Wed Jun 18, 2008 2:00 pm

Re: Snake Game - a game written in Forth

Post by Moggy »

Excellent work, Kelly :ugeek:


Something that to the eye looks like BASIC with the speed and smoothness of M/C.

I shall be studying this and will try to work out how it works at the nuts and bolts level and hopefully improve my own feeble efforts. :oops: :lol:
???????????????????????????PIINKEY$?????RND????????????????????????????????????????????????????????PI????????
Moggy
Posts: 3416
Joined: Wed Jun 18, 2008 2:00 pm

Re: Snake Game - a game written in Forth

Post by Moggy »

If like me you make a ton of mistakes when typing in listings then here is the finished product for those who wish just to try it out.

I have changed the controls to 5-left 6-down 7-up 8-right as I find them easier than those in the listing.

To use load Toddy F79 as normal then enter..

1 GET SNAKE22 ( ENTER )

FAST 1 LOAD SLOW ( ENTER )

START ( ENTER ) to run the game. Enter START again to re-run when game over.

I called it Snake22 because that was the amount of spelling errors I made before getting it right! :lol:
Attachments
SNAKE22.P
(6 KiB) Downloaded 185 times
???????????????????????????PIINKEY$?????RND????????????????????????????????????????????????????????PI????????
User avatar
siggi
Posts: 1029
Joined: Thu May 08, 2008 9:30 am
Location: Wetterau, Germany
Contact:

Re: Snake Game - Some optimizations

Post by siggi »

kmurta wrote: Wed Mar 20, 2024 1:41 am
The objective of these articles was to try to demystify a little the idea that Forth is a complicated and inaccessible language for most mortals. In fact, for those who allow themselves to venture into their fields, Forth is a fun and very stimulating language for the brain, as it forces us to think in an unconventional way.
Forth is almost self-explanatory compared to this brain challenge: https://www.sinclairzxworld.com/viewtopic.php?p=1613

:mrgreen:
Siggi
My ZX81 web-server: online since 2007, running since dec. 2020 using ZeddyNet hardware
http://zx81.ddns.net/ZxTeaM
Post Reply