Snake: Minigame with tile's, keyboard and sprites (C & ASM with SDCC) Pincha aquí para verlo en español In this tutorial we will see how to make a variant of the famous snake game, which in this case has to avoid colliding with the walls and other obstacles, every little time the snake grows and the speed increases... To develop it we will introduce new concepts, such as reading the keyboard to control the snake, and an introduction to the 'tiles', used in many games. For the keyboard we will use the firmware, since in this case we do not need multiple simultaneous keypresses and is the simplest for now. Let's use the command KM READ CHAR (BB09), through the following function: //////////////////////////////////////////////////////////////////////// //GetChar() //////////////////////////////////////////////////////////////////////// char nGetChar; char GetChar() { __asm LD HL, #_nGetChar LD (HL), #0 CALL #0xBB09 ;KM READ CHAR JP NC, _end_getchar LD (HL), A _end_getchar: __endasm; return nGetChar; } //////////////////////////////////////////////////////////////////////// For the game we will use the letters 'opqa' and arrows. To find its numerical value, we make a simple program that will print both the character and the code every time you press a key: //////////////////////////////////////////////////////////////////////// // snake01.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> //////////////////////////////////////////////////////////////////////// //GetChar() //////////////////////////////////////////////////////////////////////// char nGetChar; char GetChar() { __asm LD HL, #_nGetChar LD (HL), #0 CALL #0xBB09 ;KM READ CHAR JP NC, _end_getchar LD (HL), A _end_getchar: __endasm; return nGetChar; } //////////////////////////////////////////////////////////////////////// void main() { while(1) { char nChar = GetChar(); if(nChar != 0) printf("%d = %c\n\r", nChar, nChar); } } //////////////////////////////////////////////////////////////////////// If we run it on an emulator and press several keys get something like this:
Let's see now how we will organize the game, for this we will use 'tiles'. What is a 'tile'? A 'tile' is each one of the cells or subdivisions of the screen that we manage in the game. In our case, each tile can be a part of the snake, a border, a stone or part of the background. When the snake moves, will move one by one and also be painted 'tile' for 'tile', when necessary. To this minigame will divide the screen (160x200 mode 0), in tiles of 8x10 pixels, which leaves us we 20x20 tiles.. To handle all this easily from our source code, we will use defines, enums and arrays of C, as follows: #define TILE_HEIGHT 10 //pixel #define TILE_WIDTH 8 //pixel (4 bytes) #define MODE0_HEIGHT 200 #define MODE0_WIDTH 160 #define NUM_TILES_WIDTH MODE0_WIDTH / TILE_WIDTH #define NUM_TILES_HEIGHT MODE0_HEIGHT / TILE_HEIGHT enum _eTileType { TileType_None, TileType_Border, TileType_Rock, TileType_Snake }_eTileType; enum _eTileType aBackgroundTiles[NUM_TILES_WIDTH][NUM_TILES_HEIGHT]; //20x20 By using aBackgroundTiles array we easily obtain or modify the type of a tile. To handle the snake we use another array with each of the parts of the snake (that will grow) and also have an enum for movement: typedef struct _tSnakePiece { unsigned char nX; unsigned char nY; }_tSnakePiece; #define MAX_SNAKE_PIECES 50 _tSnakePiece aSnake[MAX_SNAKE_PIECES]; unsigned int nSnakePieces = 0; enum _eDirection { Direction_Up, Direction_Down, Direction_Left, Direction_Right }_eDirection; enum _eDirection eDirection = Direction_Right; Let's see an early version of the game, instead of painting sprites, each type of tile filled with a color. The code is fully functional, with the absence to put the graphics. We include the following functions DrawTile, KeyboardProcess and MoveSnake that are the bulk of the game and also have the functions SetColor, SetMode, SetCursor, InitGame, Game and ShowMenu, very short and simple. The source code is as follows: //////////////////////////////////////////////////////////////////////// // snake02.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> #define TILE_HEIGHT 10 //pixel #define TILE_WIDTH 8 //pixel (4 bytes) #define MODE0_HEIGHT 200 #define MODE0_WIDTH 160 #define NUM_TILES_WIDTH MODE0_WIDTH / TILE_WIDTH #define NUM_TILES_HEIGHT MODE0_HEIGHT / TILE_HEIGHT enum _eTileType { TileType_None, TileType_Border, TileType_Rock, TileType_Snake }_eTileType; enum _eTileType aBackgroundTiles[NUM_TILES_WIDTH][NUM_TILES_HEIGHT]; //20x20 #define NUM_COLORS 4 const unsigned char Palette[NUM_COLORS] = {0, 26, 12, 6}; typedef struct _tSnakePiece { unsigned char nX; unsigned char nY; }_tSnakePiece; #define MAX_SNAKE_PIECES 50 _tSnakePiece aSnake[MAX_SNAKE_PIECES]; unsigned int nSnakePieces = 0; enum _eDirection { Direction_Up, Direction_Down, Direction_Left, Direction_Right }_eDirection; enum _eDirection eDirection = Direction_Right; //////////////////////////////////////////////////////////////////////// //GetTime() //////////////////////////////////////////////////////////////////////// unsigned char char3,char4; unsigned int GetTime() { unsigned int nTime = 0; __asm CALL #0xBD0D ;KL TIME PLEASE PUSH HL POP DE LD HL, #_char3 LD (HL), D LD HL, #_char4 LD (HL), E __endasm; nTime = (char3 << 8) + char4; return nTime; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //GetChar() //////////////////////////////////////////////////////////////////////// char nGetChar; char GetChar() { __asm LD HL, #_nGetChar LD (HL), #0 CALL #0xBB09 ;KM READ CHAR JP NC, _end_getchar LD (HL), A _end_getchar: __endasm; return nGetChar; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //DrawTile //////////////////////////////////////////////////////////////////////// void DrawTile(unsigned char nTileX, unsigned char nTileY) { enum _eTileType eTileType = aBackgroundTiles[nTileX][nTileY]; unsigned int nRow = 0; for(nRow = 0; nRow < TILE_HEIGHT; nRow++) { unsigned int nY = nTileY * TILE_HEIGHT + nRow; unsigned int nX = nTileX * TILE_WIDTH; unsigned char *pScreen = (unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 2); unsigned char nColor = 0; switch(eTileType) { case TileType_None: nColor = 0; break; case TileType_Border: nColor = 192; break; //11000000 case TileType_Rock: nColor = 12; break; //00001100 case TileType_Snake: nColor = 204; break; //11001100 } memset(pScreen, nColor, TILE_WIDTH / 2); } } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetColor //////////////////////////////////////////////////////////////////////// void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex) { __asm ld a, 4 (ix) ld b, 5 (ix) ld c, b call #0xBC32 ;SCR SET INK __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetMode //////////////////////////////////////////////////////////////////////// void SetMode(unsigned char nMode) { __asm ld a, 4 (ix) call #0xBC0E ;SCR_SET_MODE __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetCursor //////////////////////////////////////////////////////////////////////// void SetCursor(unsigned char nColum, unsigned char nLine) { __asm ld h, 4 (ix) ld l, 5 (ix) call #0xBB75 ;TXT SET CURSOR __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //InitMode0 //////////////////////////////////////////////////////////////////////// void InitMode0() { SetMode(0); //SCR SET BORDER 0 __asm ld b, #0 ;black ld c, b call #0xBC38 __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //InitGame //////////////////////////////////////////////////////////////////////// void InitGame() { unsigned char nColor = 0; unsigned int nX = 0; unsigned int nY = 0; for(nColor = 0; nColor < NUM_COLORS; nColor++) SetColor(nColor, Palette[nColor]); for(nX = 0; nX < NUM_TILES_WIDTH; nX++) for(nY = 0; nY < NUM_TILES_HEIGHT; nY++) aBackgroundTiles[nX][nY] = TileType_None; for(nX = 0; nX < NUM_TILES_WIDTH; nX++) { aBackgroundTiles[nX][0] = TileType_Border; aBackgroundTiles[nX][NUM_TILES_WIDTH - 1] = TileType_Border; } for(nY = 0; nY < NUM_TILES_HEIGHT; nY++) { aBackgroundTiles[0][nY] = TileType_Border; aBackgroundTiles[NUM_TILES_HEIGHT - 1][nY] = TileType_Border; } aBackgroundTiles[4][4] = TileType_Rock; aBackgroundTiles[15][4] = TileType_Rock; aBackgroundTiles[4][15] = TileType_Rock; aBackgroundTiles[15][15] = TileType_Rock; aBackgroundTiles[9][9] = TileType_Rock; aBackgroundTiles[9][10] = TileType_Rock; aBackgroundTiles[10][9] = TileType_Rock; aBackgroundTiles[10][10] = TileType_Rock; nSnakePieces = 0; eDirection = Direction_Right; for(nX = 0; nX < MAX_SNAKE_PIECES; nX++) { aSnake[nX].nX = 0; aSnake[nX].nY = 0; } for(nX = 0; nX < 5; nX++) { aSnake[nX].nX = nX + 5; aSnake[nX].nY = 12; aBackgroundTiles[aSnake[nX].nX][aSnake[nX].nY] = TileType_Snake; nSnakePieces++; } for(nX = 0; nX < NUM_TILES_WIDTH; nX++) for(nY = 0; nY < NUM_TILES_HEIGHT; nY++) if(aBackgroundTiles[nX][nY] != TileType_None) DrawTile(nX, nY); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //KeyboardProcess //////////////////////////////////////////////////////////////////////// void KeyboardProcess() { char nChar = GetChar(); if(nChar == 'Q' || nChar == 'q' || nChar == -16) //Up { if(eDirection == Direction_Left || eDirection == Direction_Right) eDirection = Direction_Up; } else if(nChar == 'A' || nChar == 'a' || nChar == -15) //Down { if(eDirection == Direction_Left || eDirection == Direction_Right) eDirection = Direction_Down; } else if(nChar == 'O' || nChar == 'o' || nChar == -14) //Left { if(eDirection == Direction_Up || eDirection == Direction_Down) eDirection = Direction_Left; } else if(nChar == 'P' || nChar == 'p' || nChar == -13) //Right { if(eDirection == Direction_Up || eDirection == Direction_Down) eDirection = Direction_Right; } } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //MoveSnake //////////////////////////////////////////////////////////////////////// unsigned char MoveSnake(unsigned char bGrow) { _tSnakePiece *pSnakePieceHead = &aSnake[nSnakePieces - 1]; //new piece switch(eDirection) { case Direction_Right: aSnake[nSnakePieces].nX = pSnakePieceHead->nX + 1; aSnake[nSnakePieces].nY = pSnakePieceHead->nY; break; case Direction_Left: aSnake[nSnakePieces].nX = pSnakePieceHead->nX - 1; aSnake[nSnakePieces].nY = pSnakePieceHead->nY; break; case Direction_Up: aSnake[nSnakePieces].nX = pSnakePieceHead->nX; aSnake[nSnakePieces].nY = pSnakePieceHead->nY - 1; break; case Direction_Down: aSnake[nSnakePieces].nX = pSnakePieceHead->nX; aSnake[nSnakePieces].nY = pSnakePieceHead->nY + 1; break; } //has crashed if(aBackgroundTiles[aSnake[nSnakePieces].nX][aSnake[nSnakePieces].nY] != TileType_None) return 0; aBackgroundTiles[aSnake[nSnakePieces].nX][aSnake[nSnakePieces].nY] = TileType_Snake; DrawTile(aSnake[nSnakePieces].nX, aSnake[nSnakePieces].nY); nSnakePieces++; if(bGrow && nSnakePieces < MAX_SNAKE_PIECES) return 1; //delete tail of the snake aBackgroundTiles[aSnake[0].nX][aSnake[0].nY] = TileType_None; DrawTile(aSnake[0].nX, aSnake[0].nY); nSnakePieces--; memcpy(aSnake, &aSnake[1], sizeof(_tSnakePiece) * nSnakePieces); return 1; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //Game //////////////////////////////////////////////////////////////////////// void Game() { unsigned int nLastMoveTime = GetTime(); unsigned int nGameMovements = 0; unsigned int nGameSpeed = 50; unsigned char bGrowSnake = 0; InitMode0(); InitGame(); while(1) { KeyboardProcess(); if(GetTime() - nLastMoveTime < nGameSpeed) continue; nLastMoveTime = GetTime(); nGameMovements++; if(nGameMovements % 20 == 0) { if(nGameSpeed > 15) nGameSpeed -= 2; bGrowSnake = 1; } if(!MoveSnake(bGrowSnake)) break; bGrowSnake = 0; } SetMode(1); SetCursor(6, 7); printf("You have reached %d Movements", nGameMovements); SetCursor(8, 14); printf("Press Enter to play again"); while(GetChar() != 13) {} } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //ShowMenu //////////////////////////////////////////////////////////////////////// void ShowMenu() { SetMode(1); SetCursor(17, 5); printf("SNAKE"); SetCursor(1, 8); printf("Use cursors or 'opqa' to move the snake"); SetCursor(8, 16); printf("Press Enter to start game"); SetCursor(3, 24); printf("Mochilote - www.cpcmania.com - 2012"); while(GetChar() != 13) {} } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //main //////////////////////////////////////////////////////////////////////// void main() { ShowMenu(); while(1) { Game(); } } //////////////////////////////////////////////////////////////////////// As we see in Game function, which is the main loop of the game, what we do is to process the keyboard, and when appropriate the snake moves, every 20 movements of the snake, it increases the speed and the snake grows. In function MoveSnake we see how moves the head of the serpent, the new tile is painted and then remove the tail of the snake (unless the snake grow) and repaints to show the tile background. If you compile and run we get the following:
To make the game more cool, let's get sprites, the sprites in this case should be of the size of the tile so I have drawn these sprites to use in the game:
Once converted and adapted (as we have seen in previous tutorials) we have the following: //////////////////////////////////////////////////////////////////////// // sprites.h // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// const char SpriteBrick[] = { 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02 , 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02 , 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x02, 0x03 , 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03 , 0x02, 0x03, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00 }; const char SpriteRock[] = { 0x0C, 0x03, 0x03, 0x0C, 0x09, 0x30, 0x30, 0x06 , 0x12, 0x30, 0x30, 0x21, 0x12, 0x21, 0x12, 0x21 , 0x12, 0x21, 0x12, 0x21, 0x12, 0x21, 0x12, 0x21 , 0x12, 0x21, 0x12, 0x21, 0x12, 0x30, 0x30, 0x21 , 0x09, 0x30, 0x30, 0x06, 0x0C, 0x03, 0x03, 0x0C }; const char SpriteSand[] = { 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x18, 0x18, 0x18 , 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x18, 0x18, 0x18 , 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x18, 0x18, 0x18 , 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x18, 0x18, 0x18 , 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x18, 0x18, 0x18 }; const char SpriteSnakeBodyHorz[] = { 0x0C, 0xF0, 0xF0, 0x0C, 0x58, 0xCC, 0x44, 0xA4 , 0xA0, 0xCC, 0x88, 0xD8, 0xE4, 0x44, 0xCC, 0x50 , 0xE4, 0x88, 0xCC, 0xD8, 0xE4, 0xCC, 0x44, 0xD8 , 0xA0, 0xCC, 0x88, 0xD8, 0xE4, 0x44, 0xCC, 0x50 , 0x58, 0x88, 0xCC, 0xA4, 0x0C, 0xF0, 0xF0, 0x0C }; const char SpriteSnakeBodyVert[] = { 0x0C, 0xF0, 0xF0, 0x0C, 0x58, 0x88, 0xCC, 0xA4 , 0xE4, 0x44, 0xCC, 0x50, 0xA0, 0xCC, 0x88, 0xD8 , 0xE4, 0xCC, 0x44, 0xD8, 0xE4, 0x88, 0xCC, 0xD8 , 0xE4, 0x44, 0xCC, 0x50, 0xA0, 0xCC, 0x88, 0xD8 , 0x58, 0xCC, 0x44, 0xA4, 0x0C, 0xF0, 0xF0, 0x0C }; const char SpriteSnakeHeadDown[] = { 0x0C, 0xF0, 0xF0, 0x0C, 0x58, 0xCC, 0xCC, 0xA4 , 0xE4, 0xCC, 0xCC, 0xD8, 0xE4, 0xFC, 0xFC, 0xD8 , 0xE4, 0x7C, 0xBC, 0xD8, 0xE4, 0xCC, 0xCC, 0xD8 , 0xE4, 0xCC, 0xCC, 0xD8, 0x58, 0x9C, 0x6C, 0xA4 , 0x0C, 0xB4, 0x78, 0x0C, 0x0C, 0x1C, 0x2C, 0x0C }; const char SpriteSnakeHeadLeft[] = { 0x0C, 0x0C, 0xF0, 0xA4, 0x0C, 0x58, 0xCC, 0xD8 , 0x0C, 0xE4, 0xCC, 0xD8, 0x58, 0xCC, 0x6C, 0xD8 , 0x3C, 0x6C, 0xEC, 0xD8, 0x3C, 0x6C, 0xEC, 0xD8 , 0x58, 0xCC, 0x6C, 0xD8, 0x0C, 0xE4, 0xCC, 0xD8 , 0x0C, 0x58, 0xCC, 0xD8, 0x0C, 0x0C, 0xF0, 0xA4 }; const char SpriteSnakeHeadRight[] = { 0x58, 0xF0, 0x0C, 0x0C, 0xE4, 0xCC, 0xA4, 0x0C , 0xE4, 0xCC, 0xD8, 0x0C, 0xE4, 0x9C, 0xCC, 0xA4 , 0xE4, 0xDC, 0x9C, 0x3C, 0xE4, 0xDC, 0x9C, 0x3C , 0xE4, 0x9C, 0xCC, 0xA4, 0xE4, 0xCC, 0xD8, 0x0C , 0xE4, 0xCC, 0xA4, 0x0C, 0x58, 0xF0, 0x0C, 0x0C }; const char SpriteSnakeHeadUp[] = { 0x0C, 0x1C, 0x2C, 0x0C, 0x0C, 0xB4, 0x78, 0x0C , 0x58, 0x9C, 0x6C, 0xA4, 0xE4, 0xCC, 0xCC, 0xD8 , 0xE4, 0xCC, 0xCC, 0xD8, 0xE4, 0x7C, 0xBC, 0xD8 , 0xE4, 0xFC, 0xFC, 0xD8, 0xE4, 0xCC, 0xCC, 0xD8 , 0x58, 0xCC, 0xCC, 0xA4, 0x0C, 0xF0, 0xF0, 0x0C }; //////////////////////////////////////////////////////////////////////// We modified the program a bit and finally this is the game's source code: //////////////////////////////////////////////////////////////////////// // snake03.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> #include "sprites.h" #define TILE_HEIGHT 10 //pixel #define TILE_WIDTH 8 //pixel (4 bytes) #define MODE0_HEIGHT 200 #define MODE0_WIDTH 160 #define NUM_TILES_WIDTH MODE0_WIDTH / TILE_WIDTH #define NUM_TILES_HEIGHT MODE0_HEIGHT / TILE_HEIGHT enum _eTileType { TileType_None, TileType_Border, TileType_Rock, TileType_SnakeHeadUp, TileType_SnakeHeadDown, TileType_SnakeHeadLeft, TileType_SnakeHeadRight, TileType_SnakeBodyHorz, TileType_SnakeBodyVert }_eTileType; enum _eTileType aBackgroundTiles[NUM_TILES_WIDTH][NUM_TILES_HEIGHT]; //20x20 #define NUM_COLORS 9 const unsigned char Palette[NUM_COLORS] = {0, 26, 25, 18, 15, 9, 6, 22, 3}; typedef struct _tSnakePiece { unsigned char nX; unsigned char nY; }_tSnakePiece; #define MAX_SNAKE_PIECES 50 _tSnakePiece aSnake[MAX_SNAKE_PIECES]; unsigned int nSnakePieces = 0; enum _eDirection { Direction_Up, Direction_Down, Direction_Left, Direction_Right }_eDirection; enum _eDirection eDirection = Direction_Right; //////////////////////////////////////////////////////////////////////// //GetTime() //////////////////////////////////////////////////////////////////////// unsigned char char3,char4; unsigned int GetTime() { unsigned int nTime = 0; __asm CALL #0xBD0D ;KL TIME PLEASE PUSH HL POP DE LD HL, #_char3 LD (HL), D LD HL, #_char4 LD (HL), E __endasm; nTime = (char3 << 8) + char4; return nTime; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //GetChar() //////////////////////////////////////////////////////////////////////// char nGetChar; char GetChar() { __asm LD HL, #_nGetChar LD (HL), #0 CALL #0xBB09 ;KM READ CHAR JP NC, _end_getchar LD (HL), A _end_getchar: __endasm; return nGetChar; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //PutSpriteMode0() //////////////////////////////////////////////////////////////////////// void PutSpriteMode0(unsigned char *pAddress, unsigned char nWidth, unsigned char nHeight, unsigned char *pSprite) { __asm LD L, 4(IX) LD H, 5(IX) LD C, 6(IX) LD B, 7(IX) LD E, 8(IX) LD D, 9(IX) _loop_alto: PUSH BC LD B,C PUSH HL _loop_ancho: LD A,(DE) LD (HL),A INC DE INC HL DJNZ _loop_ancho POP HL LD A,H ADD #0x08 LD H,A SUB #0xC0 JP NC, _sig_linea LD BC, #0xC050 ADD HL,BC _sig_linea: POP BC DJNZ _loop_alto __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //DrawTile //////////////////////////////////////////////////////////////////////// void DrawTile(unsigned char nTileX, unsigned char nTileY) { enum _eTileType eTileType = aBackgroundTiles[nTileX][nTileY]; unsigned int nY = nTileY * TILE_HEIGHT; unsigned int nX = nTileX * TILE_WIDTH; unsigned char *pScreen = (unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 2); unsigned char *pSprite = (unsigned char *)SpriteSand; switch(eTileType) { case TileType_Border: pSprite = (unsigned char *)SpriteBrick; break; case TileType_Rock: pSprite = (unsigned char *)SpriteRock; break; case TileType_SnakeBodyVert: pSprite = (unsigned char *)SpriteSnakeBodyVert; break; case TileType_SnakeBodyHorz: pSprite = (unsigned char *)SpriteSnakeBodyHorz; break; case TileType_SnakeHeadDown: pSprite = (unsigned char *)SpriteSnakeHeadDown; break; case TileType_SnakeHeadUp: pSprite = (unsigned char *)SpriteSnakeHeadUp; break; case TileType_SnakeHeadLeft: pSprite = (unsigned char *)SpriteSnakeHeadLeft; break; case TileType_SnakeHeadRight: pSprite = (unsigned char *)SpriteSnakeHeadRight; break; } PutSpriteMode0(pScreen, TILE_WIDTH / 2, TILE_HEIGHT, pSprite); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetColor //////////////////////////////////////////////////////////////////////// void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex) { __asm ld a, 4 (ix) ld b, 5 (ix) ld c, b call #0xBC32 ;SCR SET INK __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetMode //////////////////////////////////////////////////////////////////////// void SetMode(unsigned char nMode) { __asm ld a, 4 (ix) call #0xBC0E ;SCR_SET_MODE __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetCursor //////////////////////////////////////////////////////////////////////// void SetCursor(unsigned char nColum, unsigned char nLine) { __asm ld h, 4 (ix) ld l, 5 (ix) call #0xBB75 ;TXT SET CURSOR __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //InitMode0 //////////////////////////////////////////////////////////////////////// void InitMode0() { SetMode(0); //SCR SET BORDER 0 __asm ld b, #0 ;black ld c, b call #0xBC38 __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //InitGame //////////////////////////////////////////////////////////////////////// void InitGame() { unsigned char nColor = 0; unsigned int nX = 0; unsigned int nY = 0; for(nColor = 0; nColor < NUM_COLORS; nColor++) SetColor(nColor, Palette[nColor]); for(nX = 0; nX < NUM_TILES_WIDTH; nX++) for(nY = 0; nY < NUM_TILES_HEIGHT; nY++) aBackgroundTiles[nX][nY] = TileType_None; for(nX = 0; nX < NUM_TILES_WIDTH; nX++) { aBackgroundTiles[nX][0] = TileType_Border; aBackgroundTiles[nX][NUM_TILES_WIDTH - 1] = TileType_Border; } for(nY = 0; nY < NUM_TILES_HEIGHT; nY++) { aBackgroundTiles[0][nY] = TileType_Border; aBackgroundTiles[NUM_TILES_HEIGHT - 1][nY] = TileType_Border; } aBackgroundTiles[4][4] = TileType_Rock; aBackgroundTiles[15][4] = TileType_Rock; aBackgroundTiles[4][15] = TileType_Rock; aBackgroundTiles[15][15] = TileType_Rock; aBackgroundTiles[9][9] = TileType_Rock; aBackgroundTiles[9][10] = TileType_Rock; aBackgroundTiles[10][9] = TileType_Rock; aBackgroundTiles[10][10] = TileType_Rock; nSnakePieces = 0; eDirection = Direction_Right; for(nX = 0; nX < MAX_SNAKE_PIECES; nX++) { aSnake[nX].nX = 0; aSnake[nX].nY = 0; } for(nX = 0; nX < 5; nX++) { aSnake[nX].nX = nX + 5; aSnake[nX].nY = 12; aBackgroundTiles[aSnake[nX].nX][aSnake[nX].nY] = nX < 4 ? TileType_SnakeBodyHorz : TileType_SnakeHeadRight; nSnakePieces++; } for(nX = 0; nX < NUM_TILES_WIDTH; nX++) for(nY = 0; nY < NUM_TILES_HEIGHT; nY++) DrawTile(nX, nY); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //KeyboardProcess //////////////////////////////////////////////////////////////////////// void KeyboardProcess() { char nChar = GetChar(); if(nChar == 'Q' || nChar == 'q' || nChar == -16) //Up { if(eDirection == Direction_Left || eDirection == Direction_Right) eDirection = Direction_Up; } else if(nChar == 'A' || nChar == 'a' || nChar == -15) //Down { if(eDirection == Direction_Left || eDirection == Direction_Right) eDirection = Direction_Down; } else if(nChar == 'O' || nChar == 'o' || nChar == -14) //Left { if(eDirection == Direction_Up || eDirection == Direction_Down) eDirection = Direction_Left; } else if(nChar == 'P' || nChar == 'p' || nChar == -13) //Right { if(eDirection == Direction_Up || eDirection == Direction_Down) eDirection = Direction_Right; } } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //MoveSnake //////////////////////////////////////////////////////////////////////// unsigned char MoveSnake(unsigned char bGrow) { _tSnakePiece *pSnakePieceHead = &aSnake[nSnakePieces - 1]; enum _eTileType eTileType = TileType_None; //new piece switch(eDirection) { case Direction_Right: aSnake[nSnakePieces].nX = pSnakePieceHead->nX + 1; aSnake[nSnakePieces].nY = pSnakePieceHead->nY; eTileType = TileType_SnakeHeadRight; break; case Direction_Left: aSnake[nSnakePieces].nX = pSnakePieceHead->nX - 1; aSnake[nSnakePieces].nY = pSnakePieceHead->nY; eTileType = TileType_SnakeHeadLeft; break; case Direction_Up: aSnake[nSnakePieces].nX = pSnakePieceHead->nX; aSnake[nSnakePieces].nY = pSnakePieceHead->nY - 1; eTileType = TileType_SnakeHeadUp; break; case Direction_Down: aSnake[nSnakePieces].nX = pSnakePieceHead->nX; aSnake[nSnakePieces].nY = pSnakePieceHead->nY + 1; eTileType = TileType_SnakeHeadDown; break; } //has crashed if(aBackgroundTiles[aSnake[nSnakePieces].nX][aSnake[nSnakePieces].nY] != TileType_None) return 0; aBackgroundTiles[aSnake[nSnakePieces].nX][aSnake[nSnakePieces].nY] = eTileType; DrawTile(aSnake[nSnakePieces].nX, aSnake[nSnakePieces].nY); switch(aBackgroundTiles[pSnakePieceHead->nX][pSnakePieceHead->nY]) { case TileType_SnakeHeadDown: case TileType_SnakeHeadUp: eTileType = TileType_SnakeBodyVert; break; case TileType_SnakeHeadLeft: case TileType_SnakeHeadRight: eTileType = TileType_SnakeBodyHorz; break; } aBackgroundTiles[pSnakePieceHead->nX][pSnakePieceHead->nY] = eTileType; DrawTile(pSnakePieceHead->nX, pSnakePieceHead->nY); nSnakePieces++; if(bGrow && nSnakePieces < MAX_SNAKE_PIECES) return 1; //delete tail of the snake aBackgroundTiles[aSnake[0].nX][aSnake[0].nY] = TileType_None; DrawTile(aSnake[0].nX, aSnake[0].nY); nSnakePieces--; memcpy(aSnake, &aSnake[1], sizeof(_tSnakePiece) * nSnakePieces); return 1; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //Game //////////////////////////////////////////////////////////////////////// void Game() { unsigned int nLastMoveTime = GetTime(); unsigned int nGameMovements = 0; unsigned int nGameSpeed = 50; unsigned char bGrowSnake = 0; InitMode0(); InitGame(); while(1) { KeyboardProcess(); if(GetTime() - nLastMoveTime < nGameSpeed) continue; nLastMoveTime = GetTime(); nGameMovements++; if(nGameMovements % (nGameSpeed > 25 ? 20 : 40) == 0) { if(nGameSpeed > 15) nGameSpeed -= 2; bGrowSnake = 1; } if(!MoveSnake(bGrowSnake)) break; bGrowSnake = 0; } SetMode(1); SetCursor(6, 7); printf("You have reached %d Movements", nGameMovements); SetCursor(8, 14); printf("Press Enter to play again"); while(GetChar() != 13) {} } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //ShowMenu //////////////////////////////////////////////////////////////////////// void ShowMenu() { SetMode(1); SetCursor(17, 5); printf("SNAKE"); SetCursor(1, 8); printf("Use cursors or 'opqa' to move the snake"); SetCursor(8, 16); printf("Press Enter to start game"); SetCursor(3, 24); printf("Mochilote - www.cpcmania.com - 2012"); while(GetChar() != 13) {} } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //main //////////////////////////////////////////////////////////////////////// void main() { ShowMenu(); while(1) { Game(); } } //////////////////////////////////////////////////////////////////////// If you compile and run we get the following:
With just 500 lines of code (including comments and blank lines) has been a cool game and especially educational :-) With little effort we could modify it to include for example stones that move, mouse to eat etc.. If anyone is encouraged, please let me know. You could download a zip with all files (source code, bat to compile, binary and dsk's) here: Snake.zip |
www.CPCMania.com 2012 |