Snake: Minijuego con tiles, teclado y sprites (C y ASM con SDCC) En este tutorial vamos a ver como hacer una variante del famoso juego de la serpiente, que en este caso tiene que evitar chocar con las paredes y otros obstáculos, cada poco tiempo la serpiente crece y la velocidad aumenta... Para desarrollarlo vamos a introducir nuevos conceptos, como son la lectura del teclado para poder controlar a la serpiente, así como una introducción a los 'tiles', utilizados en multitud de juegos. Para el teclado vamos a usar el Firmware, ya que en este caso no necesitamos varias pulsaciones simultaneas y es lo más sencillo por ahora. Vamos a usar el comando KM READ CHAR (BB09), a través de la siguiente función: //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// Para el juego vamos a usar las letras 'opqa' y las flechas. Para ver que valor numerico tienen, hacemos un sencillo programa que nos imprima tanto el caracter como el codigo cada vez que se pulse una tecla: //////////////////////////////////////////////////////////////////////// // 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); } } //////////////////////////////////////////////////////////////////////// Si lo cargamos en un emulador y pulsamos varias teclas obtendremos algo similar a esto:
Vamos a ver ahora como vamos a organizar el juego, para ello vamos a utilizar 'tiles'. ¿Que es un 'tile'? Un 'tile' es cada una de las celdas o subdivisiones de la pantalla que vamos a manejar en el juego. En nuestro caso concreto, cada tile podrá ser una parte de la serpiente, un borde, una piedra o bien parte del fondo. Cuando la serpiente se mueva, lo hará de 'tile' en 'tile' y cuando haya que pintar se pintará tambien 'tile' por 'tile' cuando sea necesario. Para este minijuego vamos a dividir la pantalla (modo 0 160x200) en tiles de 8x10 pixels, con lo que nos sale que tenemos 20x20 tiles. Para manejar todo esto fácilmente desde nuestro código fuente, vamos a usar defines, enums y arrays de C, de la siguiente manera: #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 Con lo que accediendo al array aBackgroundTiles[x][y] obtenemos o modificamos fácilmente el tipo de cualquier tile. Para manejar la serpiente vamos usar otro array con cada una de las partes de la serpiente y que ademas irá creciendo así como un enum para su movimiento: 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; Vamos a ver una primera versión del juego, que en vez de pintar sprites, rellena cada tipo de tile con un color. El codigo es completamente funcional, a falta de meter los graficos. Tenemos entre otras las siguientes funciones DrawTile, KeyboardProcess y MoveSnake que son el grueso del juego y tambien tenemos las funciones SetColor, SetMode, SetCursor, InitGame, Game y ShowMenu, muy cortas y sencillas. El código fuente es el siguiente: //////////////////////////////////////////////////////////////////////// // 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(); } } //////////////////////////////////////////////////////////////////////// Como podemos ver en la función Game, que es el bucle principal del juego, lo que se hace es procesar el teclado y cuando toca, mover la serpiente, cada 20 movimientos de la serpiente se aumenta la velocidad y crece la serpiente. En la función MoveSnake vemos como avanza la cabeza de la serpiente, fuerza el pintado del nuevo tile y después quita la cola de la serpiente (a no ser que toque crecer) y repinta el tile para que salga el fondo. Si compilamos y ejecutamos obtendremos los siguiente:
Para que el juego sea más chulo, vamos a meter sprites, en este caso los sprites deben ser del tamaño del tile así que he dibujado los siguientes sprites para usar en el juego:
Una vez convertidos y adaptados (como ya hemos visto en tutoriales anteriores) tenemos lo siguiente: //////////////////////////////////////////////////////////////////////// // 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 }; //////////////////////////////////////////////////////////////////////// Modificamos un poco el programa principal y finalmente este es el código fuente final del juego: //////////////////////////////////////////////////////////////////////// // 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(); } } //////////////////////////////////////////////////////////////////////// Si compilamos y ejecutamos obtenemos lo siguiente:
Pues con apenas 500 lineas de código (incluyendo comentarios y lineas en blanco) ha quedado chulo y sobretodo didáctico :-) Con poco esfuerzo podríamos modificarlo para incluir por ejemplo piedras que se mueven, ratones para comer, etc. Si alguien se anima, que me lo haga saber. Podéis bajar un zip con todos ficheros (código fuente, bat's para compilar, binarios y dsk's) aquí: Snake.zip |
www.CPCMania.com 2012 |