The first time that I saw the game, was on my brother's computer. He had created the computer himself, based on a Zilog Z80A CPU with 4MHz and 4kbyte RAM. Everything was home made - graphics electronics design, etching the boards, a hex keyboard for his machine code boot software, and a high level language and all the software on top of it. However, the computer had the boot software and the high level language in an EPROM (requires UV light to be erased), and there was no other persistent storage, so all programs that you wanted to use, had to be programmed before using it.
It was back in 1981, and I was allowed to use his computer for gaming, and I knew how to use the hex keyboard to instruct the boot software to start up the high level language, so that was great. I just had to program the snake game every time I wanted to play it, so I got quite good at it. And each time I programmed it, I made it a bit different, of course.
Enough about the background, here is the data structure of a snake game:
type TItem=(spEmpty, spFood, spPoison, spWall, spSnakeHead, spUp, spDown, spLeft, spRight); var arr:array[0..79,0..24] of TItem; TailX,TailY,HeadX,HeadY:integer; SnakeLength,LengthToAdd:integer;
This is how you add food or poison, avoiding snake positions, wall etc.:
procedure AddItem (item:TItem); var x,y:integer; begin // It can be argued that this loop does not // always end within finite time, but who cares? repeat x:=random(80); y:=random(25); until arr[x,y]=spEmpty; arr[x,y]:=item; end;
Then you need this one:
procedure MoveCoor (var x,y:integer;dir:TItem); begin case dir of spUp:dec (y); spDown:inc (y); spLeft:dec (x); spRight:inc (x); end; end;
So when you want to move the snake's head, you do this:
arr[HeadX,HeadY]:=MoveDirection; MoveCoor (HeadX,HeadY,MoveDirection); found:=arr[HeadX,HeadY]; arr[HeadX,HeadY]:=spSnakeHead; if found<>spEmpty then begin if found=spFood then LengthToAdd:=SnakeLength*0.5 // Alternatively just set LengthToAdd:=1 else (* End game, we ran into something that is not healthy *); end;
Increasing the length is simple, just keep a counter of how much longer the snake needs to become, and every time you are about to move the tail, either decrement that counter, or when it is zero, actually move the tail. This is how to move the tail:
if LengthToAdd=0 then begin dir:=arr[TailX,TailY]; arr[TailX,TailY]:=spEmpty; MoveCoor (TailX,TailY,dir); end else begin dec (LengthToAdd); inc (SnakeLength); end;
As you can see, a snake game is seriously simple, uses almost no CPU, is very short in source code and uses very little RAM. There is not a single object in all this, because OOP wasn't invented back then.
Could this be done more easily today using modern programming? I haven't found a better method, yet. If we should describe the old method with modern terminology, what would that description look like? The snake must be some kind of object, but it's data is stored in an array that represents the screen. However, the screen array doesn't just show the presence of the snake, but also the direction to go in order to find the next element of the snake. So, basically, according to modern criteria, this code is unstructured, messy, non-scalable, non-maintainable etc. However, in my opinion, as long as the code is easy to read, it is maintainable, and if it scales within required boundaries, it's scalable. It would be easy to encapsulate the "Snake game engine" into an API, hiding the implementation details, so I definitely still consider this to be great code.