PongC: Clon del clásico Pong en modo 2 (C y ASM con SDCC) En este tutorial vamos a intentar hacer un clon del clásico juego Pong en nuestro Amstrad y en modo 2 (640x200 a 2 colores). Como ya vimos en el tutorial Pintando pixeles: Introducción a la memoria de video, en modo 2 cada byte de memoria de video almacena 8 pixeles en un orden muy concreto, por lo tanto para poner a 0 o a 1 un pixel en concreto tenemos que hacerlo a nivel de bit. Vamos a empezar preparando una función para pintar un pixel en modo 2, que sería la siguiente: //////////////////////////////////////////////////////////////////////// //PutPixelMode2 //////////////////////////////////////////////////////////////////////// void PutPixelMode2(unsigned int nX, unsigned int nY, unsigned char nColor) { unsigned char *pAddress = (unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8); unsigned char nBit = 7 - nX % 8; unsigned char nAux = 0; if(nColor == 0) { nAux = ~(1 << nBit); *pAddress = *pAddress & nAux; } else { nAux = (1 << nBit); *pAddress = *pAddress | nAux; } } //////////////////////////////////////////////////////////////////////// Vamos a ver como funciona haciendo nuestro primer programa, que simplemente pintará la pelota del juego. El programa completo quedaría así: //////////////////////////////////////////////////////////////////////// // PongC01.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> //////////////////////////////////////////////////////////////////////// //------------------------ Generic functions -------------------------// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetBorder //////////////////////////////////////////////////////////////////////// void SetBorder(unsigned char nColor) { __asm ld b, 4 (ix) ld c, b call #0xBC38 ;SCR_SET_BORDER __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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //PutPixelMode2 //////////////////////////////////////////////////////////////////////// void PutPixelMode2(unsigned int nX, unsigned int nY, unsigned char nColor) { unsigned char *pAddress = (unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8); unsigned char nBit = 7 - nX % 8; unsigned char nAux = 0; if(nColor == 0) { nAux = ~(1 << nBit); *pAddress = *pAddress & nAux; } else { nAux = (1 << nBit); *pAddress = *pAddress | nAux; } } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //---------------- Specific variables and functions-------------------// //////////////////////////////////////////////////////////////////////// #define BALL_WIDTH 20 #define BALL_HEIGHT 10 //////////////////////////////////////////////////////////////////////// //main //////////////////////////////////////////////////////////////////////// void main() { unsigned int nFPS = 0; unsigned int nTimeLast = 0; unsigned int nX = 0; unsigned int nY = 0; SetMode(2); SetBorder(0); SetColor(0, 0); SetColor(1, 26); nTimeLast = GetTime(); while(1) { nFPS++; if(GetTime() - nTimeLast >= 300) { SetCursor(1, 1); printf("%u ", nFPS); nTimeLast = GetTime(); nFPS = 0; } for(nX = 0; nX < BALL_WIDTH; nX++) for(nY = 0; nY < BALL_HEIGHT; nY++) PutPixelMode2(100 + nX, 100 + nY, 1); } } //////////////////////////////////////////////////////////////////////// Si compilamos y ejecutamos en el emulador obtenemos lo siguiente:
Como puede verse, pintar pixel a pixel es muy lento y apenas alcanzamos los 17 frames por segundo, con lo que no nos vale para hacer el juego... Como la bola al fin y al cabo es cuadrada, vamos mejor a pintarla mediante líneas horizontales para ver si ganamos velocidad. Para ello programamos una nueva función que pinte líneas horizontales en modo 2, que sería la siguiente: //////////////////////////////////////////////////////////////////////// //LineHMode2 //////////////////////////////////////////////////////////////////////// void LineHMode2(unsigned int nX, unsigned int nY, unsigned char nColor, unsigned int nWidth) { unsigned char *pAddress = (unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8); if(nX % 8 == 0 && nWidth % 8 == 0) { memset(pAddress, nColor ? 0xFF : 0x00, nWidth / 8); } else { unsigned int nPixels = 0; unsigned int nX2 = 0; //first byte for(nX2 = nX; (nX2 % 8) && (nX2 < nX + nWidth); nX2++) { unsigned char nBit = 7 - nX2 % 8; unsigned char nAux = 0; if(nColor == 0) { nAux = ~(1 << nBit); *pAddress = *pAddress & nAux; } else { nAux = (1 << nBit); *pAddress = *pAddress | nAux; } nPixels++; } if(nPixels > 0) pAddress++; //intermediate bytes nX2 = (nWidth - nPixels) / 8; memset(pAddress, nColor ? 0xFF : 0x00, nX2); nPixels += nX2 * 8; pAddress += nX2; //last byte for(nX2 = nX + nPixels; (nX2 < nX + nWidth); nX2++) { unsigned char nBit = 7 - nX2 % 8; unsigned char nAux = 0; if(nColor == 0) { nAux = ~(1 << nBit); *pAddress = *pAddress & nAux; } else { nAux = (1 << nBit); *pAddress = *pAddress | nAux; } nPixels++; } } } //////////////////////////////////////////////////////////////////////// Como vemos si la posición en x es múltiplo de 8 y los pixeles que queremos pintar también es tan sencillo como hacer un memset, en caso contrarío la función pinta bit a bit en los bytes de los extremos de la línea y usa memset en los bytes centrales para ahorrar tiempo. Modificamos el programa anterior, para pintar nuestra bola con esta nueva función, quedando de la siguiente forma: //////////////////////////////////////////////////////////////////////// // PongC02.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> //////////////////////////////////////////////////////////////////////// //------------------------ Generic functions -------------------------// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetBorder //////////////////////////////////////////////////////////////////////// void SetBorder(unsigned char nColor) { __asm ld b, 4 (ix) ld c, b call #0xBC38 ;SCR_SET_BORDER __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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //LineHMode2 //////////////////////////////////////////////////////////////////////// void LineHMode2(unsigned int nX, unsigned int nY, unsigned char nColor, unsigned int nWidth) { unsigned char *pAddress = (unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8); if(nX % 8 == 0 && nWidth % 8 == 0) { memset(pAddress, nColor ? 0xFF : 0x00, nWidth / 8); } else { unsigned int nPixels = 0; unsigned int nX2 = 0; //first byte for(nX2 = nX; (nX2 % 8) && (nX2 < nX + nWidth); nX2++) { unsigned char nBit = 7 - nX2 % 8; unsigned char nAux = 0; if(nColor == 0) { nAux = ~(1 << nBit); *pAddress = *pAddress & nAux; } else { nAux = (1 << nBit); *pAddress = *pAddress | nAux; } nPixels++; } if(nPixels > 0) pAddress++; //intermediate bytes nX2 = (nWidth - nPixels) / 8; memset(pAddress, nColor ? 0xFF : 0x00, nX2); nPixels += nX2 * 8; pAddress += nX2; //last byte for(nX2 = nX + nPixels; (nX2 < nX + nWidth); nX2++) { unsigned char nBit = 7 - nX2 % 8; unsigned char nAux = 0; if(nColor == 0) { nAux = ~(1 << nBit); *pAddress = *pAddress & nAux; } else { nAux = (1 << nBit); *pAddress = *pAddress | nAux; } nPixels++; } } } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //---------------- Specific variables and functions-------------------// //////////////////////////////////////////////////////////////////////// #define BALL_WIDTH 20 #define BALL_HEIGHT 10 //////////////////////////////////////////////////////////////////////// //DrawBall //////////////////////////////////////////////////////////////////////// void DrawBall(unsigned int nX, unsigned int nY) { unsigned int nAux = 0; for(nAux = 0; nAux < BALL_HEIGHT; nAux++) LineHMode2(nX, nY + nAux, 1, BALL_WIDTH); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //main //////////////////////////////////////////////////////////////////////// void main() { unsigned int nFPS = 0; unsigned int nTimeLast = 0; unsigned int nX = 0; unsigned int nY = 0; SetMode(2); SetBorder(0); SetColor(0, 0); SetColor(1, 26); nTimeLast = GetTime(); while(1) { nFPS++; if(GetTime() - nTimeLast >= 300) { SetCursor(1, 1); printf("%u ", nFPS); nTimeLast = GetTime(); nFPS = 0; } nX = (nX + 1) % (640 - BALL_WIDTH); nY = (nY + 1) % (200 - BALL_HEIGHT); DrawBall(nX, nY); } } //////////////////////////////////////////////////////////////////////// Si compilamos y ejecutamos en el emulador obtenemos lo siguiente:
Hemos mejorado mucho, ha pasado de 17 a 56 frames por segundo, pero sigue siendo bastante insuficiente ya que falta todo el resto del juego... Vamos a hacer una optimización a la función de pintado de línea, de tal forma que en vez de hacer operaciones a nivel de bit ya tenemos precalculados los 8 posibles bytes del extremo izquierdo y derecho de nuestra línea el código fuente del programa sería el siguiente: //////////////////////////////////////////////////////////////////////// // PongC03.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> //////////////////////////////////////////////////////////////////////// //------------------------ Generic functions -------------------------// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetBorder //////////////////////////////////////////////////////////////////////// void SetBorder(unsigned char nColor) { __asm ld b, 4 (ix) ld c, b call #0xBC38 ;SCR_SET_BORDER __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; } //////////////////////////////////////////////////////////////////////// const unsigned char aMode2Left[8] = { 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01}; const unsigned char aMode2Right[8] = { 0xFF, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE}; //////////////////////////////////////////////////////////////////////// //LineHMode2 //////////////////////////////////////////////////////////////////////// void LineHMode2(unsigned int nX, unsigned int nY, unsigned char nColor, unsigned int nWidth) { unsigned char *pAddress = (unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8); if(nColor == 0 || (nX % 8 == 0 && nWidth % 8 == 0)) { memset(pAddress, nColor ? 0xFF : 0x00, nWidth / 8 + (nWidth % 8 ? 1 : 0)); } else { unsigned int nPixels = 0; unsigned int nAux = 0; //first byte nAux = nX % 8; if(nAux) { *pAddress++ = *(aMode2Left + nAux); nPixels += 8 - nAux; } //intermediate bytes nAux = (nWidth - nPixels) / 8; memset(pAddress, 0xFF, nAux); nPixels += nAux * 8; pAddress += nAux; //last byte nAux = nWidth - nPixels; if(nAux) *pAddress++ = *(aMode2Right + nAux); } } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //---------------- Specific variables and functions-------------------// //////////////////////////////////////////////////////////////////////// #define BALL_WIDTH 20 #define BALL_HEIGHT 10 //////////////////////////////////////////////////////////////////////// //DrawBall //////////////////////////////////////////////////////////////////////// void DrawBall(unsigned int nX, unsigned int nY) { unsigned int nAux = 0; for(nAux = 0; nAux < BALL_HEIGHT; nAux++) LineHMode2(nX, nY + nAux, 1, BALL_WIDTH); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //main //////////////////////////////////////////////////////////////////////// void main() { unsigned int nFPS = 0; unsigned int nTimeLast = 0; unsigned int nX = 0; unsigned int nY = 0; SetMode(2); SetBorder(0); SetColor(0, 0); SetColor(1, 26); nTimeLast = GetTime(); while(1) { nFPS++; if(GetTime() - nTimeLast >= 300) { SetCursor(1, 1); printf("%u ", nFPS); nTimeLast = GetTime(); nFPS = 0; } nX = (nX + 1) % (640 - BALL_WIDTH); nY = (nY + 1) % (200 - BALL_HEIGHT); DrawBall(nX, nY); } } //////////////////////////////////////////////////////////////////////// Si compilamos y ejecutamos en el emulador obtenemos lo siguiente:
Ha mejorado mucho, pasando de 56 a 119 frames por segundo. ¿Podríamos mejorar más? Pues podíamos usar sprites y pintarlos en pantalla con ensamblador para ir más rápido, pero claro como estamos moviendo la bola pixel a pixel y en cada byte hay 8 pixels no podemos tener un sprite para la bola... Para solucionarlo usaremos 8 sprites para la bola, uno por cada posible combinación de la bola en su primer y ultimo byte. Así quedaría nuestro programa si aplicamos estos cambios: //////////////////////////////////////////////////////////////////////// // PongC04.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> //////////////////////////////////////////////////////////////////////// //------------------------ Generic functions -------------------------// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetBorder //////////////////////////////////////////////////////////////////////// void SetBorder(unsigned char nColor) { __asm ld b, 4 (ix) ld c, b call #0xBC38 ;SCR_SET_BORDER __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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //PutSprite() //////////////////////////////////////////////////////////////////////// void PutSprite(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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //---------------- Specific variables and functions-------------------// //////////////////////////////////////////////////////////////////////// #define BALL_WIDTH 20 #define BALL_HEIGHT 10 #define BALL_WIDTH_BYTES 4 const unsigned char aBallSprite[9][BALL_WIDTH_BYTES * BALL_HEIGHT] = { { 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00 }, { 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00 }, { 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00 }, { 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00 }, { 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00 }, { 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80 }, { 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0 }, { 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0 }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, }; //////////////////////////////////////////////////////////////////////// //main //////////////////////////////////////////////////////////////////////// void main() { unsigned int nFPS = 0; unsigned int nTimeLast = 0; unsigned int nX = 0; unsigned int nY = 0; SetMode(2); SetBorder(0); SetColor(0, 0); SetColor(1, 26); nTimeLast = GetTime(); while(1) { nFPS++; if(GetTime() - nTimeLast >= 300) { SetCursor(1, 1); printf("%u ", nFPS); nTimeLast = GetTime(); nFPS = 0; } nX = (nX + 1) % (640 - BALL_WIDTH); nY = (nY + 1) % (200 - BALL_HEIGHT); PutSprite((unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8), BALL_WIDTH_BYTES, BALL_HEIGHT, aBallSprite[nX % 8]); } } //////////////////////////////////////////////////////////////////////// Si compilamos y ejecutamos en el emulador obtenemos lo siguiente:
Eso si que es una mejora, hemos pasado de 119 a 525 frames por segundo... Ahora que ya tenemos una buena velocidad de pintado de la bola vamos a ver como moverla y pintarla bien. Para que la bola no deje estela lo que vamos a hacer es que cada vez que la bola se mueva primero la borramos de su posición anterior y luego la pintamos en la nueva posición. Como estamos pintando directamente en la memoria de video, esto provocaría un parpadeo terrible de la bola, así que para evitar el parpadeo vamos a sincronizarnos con el refresco de pantalla y para ello vamos a usar una función de CPCWiki y la vamos a adaptar al ensamblador de SDCC. Con todos estos cambios, el programa quedaría así: //////////////////////////////////////////////////////////////////////// // PongC05.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> //////////////////////////////////////////////////////////////////////// //------------------------ Generic functions -------------------------// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetBorder //////////////////////////////////////////////////////////////////////// void SetBorder(unsigned char nColor) { __asm ld b, 4 (ix) ld c, b call #0xBC38 ;SCR_SET_BORDER __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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //PutSprite() //////////////////////////////////////////////////////////////////////// void PutSprite(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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //WaitVsync //////////////////////////////////////////////////////////////////////// void WaitVsync() { __asm ld b,#0xf5 ;; PPI port B input _wait_vsync: in a,(c) ;; [4] read PPI port B input ;; (bit 0 = "1" if vsync is active, ;; or bit 0 = "0" if vsync is in-active) rra ;; [1] put bit 0 into carry flag jp nc,_wait_vsync ;; [3] if carry not set, loop, otherwise continue __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //---------------- Specific variables and functions-------------------// //////////////////////////////////////////////////////////////////////// #define BALL_WIDTH 20 #define BALL_HEIGHT 10 #define BALL_WIDTH_BYTES 4 const unsigned char aBallSprite[9][BALL_WIDTH_BYTES * BALL_HEIGHT] = { { 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00 }, { 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00 }, { 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00 }, { 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00 }, { 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00 }, { 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80 }, { 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0 }, { 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0 }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, }; //////////////////////////////////////////////////////////////////////// //main //////////////////////////////////////////////////////////////////////// void main() { unsigned int nFPS = 0; unsigned int nTimeLast = 0; unsigned int nX = 0; unsigned int nY = 0; SetMode(2); SetBorder(0); SetColor(0, 0); SetColor(1, 26); nTimeLast = GetTime(); while(1) { if(GetTime() - nTimeLast >= 300) { SetCursor(1, 1); printf("%u ", nFPS); nTimeLast += 300; nFPS = 0; } WaitVsync(); PutSprite((unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8), BALL_WIDTH_BYTES, BALL_HEIGHT, aBallSprite[8]); nX = (nX + 1) % (640 - BALL_WIDTH); nY = (nY + 1) % (200 - BALL_HEIGHT); PutSprite((unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8), BALL_WIDTH_BYTES, BALL_HEIGHT, aBallSprite[nX % 8]); nFPS++; } } //////////////////////////////////////////////////////////////////////// Si compilamos y ejecutamos en el emulador obtenemos lo siguiente:
Usando la sincronización evitamos el parpadeo, pero nos limitamos a 50fps, así que finalmente la bola habrá que moverla varios pixeles cada vez, con lo que aunque estemos sincronizados con el repintado de pantalla dará cierta sensación de parpadeo... Vamos ahora a añadir el pintado de los jugadores y para ello no nos vamos a complicar, usamos la función de pintar lineas horizontales, pero siempre ajustado a 8 pixeles para que sea rápida. Una vez pintados los jugadores, lo que haremos cuando se muevan arriba o abajo es pintar las nuevas lineas que se ha desplazado y borraremos las viejas para que funcione rápido. El programa quedaría así: //////////////////////////////////////////////////////////////////////// // PongC06.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> //////////////////////////////////////////////////////////////////////// //------------------------ Generic functions -------------------------// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetBorder //////////////////////////////////////////////////////////////////////// void SetBorder(unsigned char nColor) { __asm ld b, 4 (ix) ld c, b call #0xBC38 ;SCR_SET_BORDER __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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //PutSprite() //////////////////////////////////////////////////////////////////////// void PutSprite(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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //WaitVsync //////////////////////////////////////////////////////////////////////// void WaitVsync() { __asm ld b,#0xf5 ;; PPI port B input _wait_vsync: in a,(c) ;; [4] read PPI port B input ;; (bit 0 = "1" if vsync is active, ;; or bit 0 = "0" if vsync is in-active) rra ;; [1] put bit 0 into carry flag jp nc,_wait_vsync ;; [3] if carry not set, loop, otherwise continue __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //LineHMode2Byte //////////////////////////////////////////////////////////////////////// void LineHMode2Byte(unsigned int nX, unsigned int nY, unsigned char nColor, unsigned char nBytes) { unsigned char *pAddress = (unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8); if(nX % 8 == 0) { memset(pAddress, nColor ? 0xFF : 0x00, nBytes); } } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //---------------- Specific variables and functions-------------------// //////////////////////////////////////////////////////////////////////// #define BALL_WIDTH 20 #define BALL_HEIGHT 10 #define BALL_WIDTH_BYTES 4 const unsigned char aBallSprite[9][BALL_WIDTH_BYTES * BALL_HEIGHT] = { { 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00 }, { 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00 }, { 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00 }, { 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00 }, { 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00 }, { 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80 }, { 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0 }, { 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0 }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, }; #define NUM_PLAYERS 2 #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 200 #define PLAYER_WIDTH 16 #define PLAYER_HEIGHT 30 #define PLAYER_WIDTH_BYTES 2 struct _tPlayer { unsigned char nY; unsigned int nX; char nYDir; }_tPlayer; struct _tPlayer aPlayer[NUM_PLAYERS]; //////////////////////////////////////////////////////////////////////// //DrawPlayer //////////////////////////////////////////////////////////////////////// void DrawPlayer(unsigned char nPlayer) { unsigned char nY = 0; for(nY = 0; nY < PLAYER_HEIGHT; nY++) LineHMode2Byte(aPlayer[nPlayer].nX, aPlayer[nPlayer].nY+ nY, 1, PLAYER_WIDTH_BYTES); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //MovePlayer //////////////////////////////////////////////////////////////////////// void MovePlayer(unsigned char nPlayer) { unsigned char nY = 0; unsigned char nYToDelete = 0; unsigned char nYToDraw = 0; int nYNew = aPlayer[nPlayer].nY + aPlayer[nPlayer].nYDir; if(aPlayer[nPlayer].nYDir > 0) { if(nYNew + PLAYER_HEIGHT >= SCREEN_HEIGHT) { nYNew = SCREEN_HEIGHT - PLAYER_HEIGHT - 1; aPlayer[nPlayer].nYDir = -4; } aPlayer[nPlayer].nY = nYNew; nYToDelete = aPlayer[nPlayer].nY - 4; nYToDraw = aPlayer[nPlayer].nY + PLAYER_HEIGHT - 4; } else { if(nYNew <= 0) { nYNew = 0; aPlayer[nPlayer].nYDir = 4; } aPlayer[nPlayer].nY = nYNew; nYToDelete = aPlayer[nPlayer].nY + PLAYER_HEIGHT; nYToDraw = aPlayer[nPlayer].nY; } for(nY = 0; nY < 4; nY++) LineHMode2Byte(aPlayer[nPlayer].nX, nYToDelete + nY, 0, PLAYER_WIDTH_BYTES); for(nY = 0; nY < 4; nY++) LineHMode2Byte(aPlayer[nPlayer].nX, nYToDraw + nY, 1, PLAYER_WIDTH_BYTES); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //main //////////////////////////////////////////////////////////////////////// void main() { unsigned int nFPS = 0; unsigned int nTimeLast = 0; unsigned int nX = 0; unsigned int nY = 0; memset(aPlayer, 0, sizeof(aPlayer)); aPlayer[0].nY = 100 - PLAYER_HEIGHT / 2; aPlayer[1].nY = 100 - PLAYER_HEIGHT / 2; aPlayer[0].nX = 8; aPlayer[1].nX = (SCREEN_WIDTH - 8) - PLAYER_WIDTH; aPlayer[0].nYDir = 4; aPlayer[1].nYDir = -4; SetMode(2); SetBorder(26); SetColor(0, 0); SetColor(1, 26); DrawPlayer(0); DrawPlayer(1); nTimeLast = GetTime(); while(1) { if(GetTime() - nTimeLast >= 300) { SetCursor(5, 1); printf("%u ", nFPS); nTimeLast += 300; nFPS = 0; } WaitVsync(); PutSprite((unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8), BALL_WIDTH_BYTES, BALL_HEIGHT, aBallSprite[8]); nX = (nX + 8) % (640 - BALL_WIDTH); nY = (nY + 4) % (200 - BALL_HEIGHT); PutSprite((unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8), BALL_WIDTH_BYTES, BALL_HEIGHT, aBallSprite[nX % 8]); MovePlayer(0); MovePlayer(1); nFPS++; } } //////////////////////////////////////////////////////////////////////// Si compilamos y ejecutamos en el emulador obtenemos lo siguiente:
Nos faltan los marcadores, como son sólo números del 0 al 9 he hecho los sprites a mano en el propio editor de texto. Como finalmente la bola se mueve de 8 en 8 pixeles, es decir, alineada a byte, ya no nos hacen falta los 8 posibles sprites de la bola, con uno es suficiente. He movido los sprites de la bola y los marcadores a un fichero .h por comodidad: //////////////////////////////////////////////////////////////////////// // Sprites.h // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #define BALL_WIDTH 16 #define BALL_HEIGHT 8 #define BALL_WIDTH_BYTES 2 const unsigned char aBallSprite[2][BALL_WIDTH_BYTES * BALL_HEIGHT] = { { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,}, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, }; #define NUMBER_HEIGHT 20 #define NUMBER_WIDTH_BYTES 4 #define NUMBER_WIDTH 32 const unsigned char aNumberSprite[10][NUMBER_HEIGHT * NUMBER_WIDTH_BYTES] = { { //0 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }, { //1 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, }, { //2 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }, { //3 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }, { //4 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, }, { //5 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }, { //6 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }, { //7 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, }, { //8 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }, { //9 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }, }; //////////////////////////////////////////////////////////////////////// El programa modificado usando ya los marcadores y mostrando lo que sería todo el motor grafico del juego en movimiento sería el siguiente: //////////////////////////////////////////////////////////////////////// // PongC07.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> #include "Sprites.h" //////////////////////////////////////////////////////////////////////// //------------------------ Generic functions -------------------------// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetBorder //////////////////////////////////////////////////////////////////////// void SetBorder(unsigned char nColor) { __asm ld b, 4 (ix) ld c, b call #0xBC38 ;SCR_SET_BORDER __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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //PutSprite() //////////////////////////////////////////////////////////////////////// void PutSprite(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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //WaitVsync //////////////////////////////////////////////////////////////////////// void WaitVsync() { __asm ld b,#0xf5 ;; PPI port B input _wait_vsync: in a,(c) ;; [4] read PPI port B input ;; (bit 0 = "1" if vsync is active, ;; or bit 0 = "0" if vsync is in-active) rra ;; [1] put bit 0 into carry flag jp nc,_wait_vsync ;; [3] if carry not set, loop, otherwise continue __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //LineHMode2Byte //////////////////////////////////////////////////////////////////////// void LineHMode2Byte(unsigned int nX, unsigned int nY, unsigned char nColor, unsigned char nBytes) { unsigned char *pAddress = (unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8); if(nX % 8 == 0) { memset(pAddress, nColor ? 0xFF : 0x00, nBytes); } } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //---------------- Specific variables and functions-------------------// //////////////////////////////////////////////////////////////////////// #define NUM_PLAYERS 2 #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 200 #define SCORE_Y 20 #define SCORE_X_1 ((SCREEN_WIDTH / 2) - 50 - NUMBER_WIDTH / 2) #define SCORE_X_2 ((SCREEN_WIDTH / 2) + 50 - NUMBER_WIDTH / 2) #define PLAYER_WIDTH 16 #define PLAYER_HEIGHT 30 #define PLAYER_WIDTH_BYTES 2 struct _tPlayer { unsigned char nY; unsigned int nX; char nYDir; }_tPlayer; struct _tPlayer aPlayer[NUM_PLAYERS]; //////////////////////////////////////////////////////////////////////// //DrawPlayer //////////////////////////////////////////////////////////////////////// void DrawPlayer(unsigned char nPlayer) { unsigned char nY = 0; for(nY = 0; nY < PLAYER_HEIGHT; nY++) LineHMode2Byte(aPlayer[nPlayer].nX, aPlayer[nPlayer].nY+ nY, 1, PLAYER_WIDTH_BYTES); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //MovePlayer //////////////////////////////////////////////////////////////////////// void MovePlayer(unsigned char nPlayer) { unsigned char nY = 0; unsigned char nYToDelete = 0; unsigned char nYToDraw = 0; int nYNew = aPlayer[nPlayer].nY + aPlayer[nPlayer].nYDir; if(aPlayer[nPlayer].nYDir > 0) { if(nYNew + PLAYER_HEIGHT >= SCREEN_HEIGHT) { nYNew = SCREEN_HEIGHT - PLAYER_HEIGHT - 1; aPlayer[nPlayer].nYDir = -4; } aPlayer[nPlayer].nY = nYNew; nYToDelete = aPlayer[nPlayer].nY - 4; nYToDraw = aPlayer[nPlayer].nY + PLAYER_HEIGHT - 4; } else { if(nYNew <= 0) { nYNew = 0; aPlayer[nPlayer].nYDir = 4; } aPlayer[nPlayer].nY = nYNew; nYToDelete = aPlayer[nPlayer].nY + PLAYER_HEIGHT; nYToDraw = aPlayer[nPlayer].nY; } for(nY = 0; nY < 4; nY++) LineHMode2Byte(aPlayer[nPlayer].nX, nYToDelete + nY, 0, PLAYER_WIDTH_BYTES); for(nY = 0; nY < 4; nY++) LineHMode2Byte(aPlayer[nPlayer].nX, nYToDraw + nY, 1, PLAYER_WIDTH_BYTES); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //main //////////////////////////////////////////////////////////////////////// void main() { unsigned int nFPS = 0; unsigned int nTimeLast = 0; unsigned int nX = 0; unsigned int nY = 0; unsigned char *pScore[NUM_PLAYERS]; unsigned int nCounter = 0; pScore[0] = (unsigned char *)0xC000 + ((SCORE_Y / 8) * 80) + ((SCORE_Y % 8) * 2048) + (SCORE_X_1 / 8); pScore[1] = (unsigned char *)0xC000 + ((SCORE_Y / 8) * 80) + ((SCORE_Y % 8) * 2048) + (SCORE_X_2 / 8); memset(aPlayer, 0, sizeof(aPlayer)); aPlayer[0].nY = 100 - PLAYER_HEIGHT / 2; aPlayer[1].nY = 100 - PLAYER_HEIGHT / 2; aPlayer[0].nX = 8; aPlayer[1].nX = (SCREEN_WIDTH - 8) - PLAYER_WIDTH; aPlayer[0].nYDir = 4; aPlayer[1].nYDir = -4; SetMode(2); SetBorder(26); SetColor(0, 0); SetColor(1, 26); DrawPlayer(0); DrawPlayer(1); nTimeLast = GetTime(); while(1) { if(GetTime() - nTimeLast >= 300) { SetCursor(5, 1); printf("%u ", nFPS); nTimeLast += 300; nFPS = 0; } WaitVsync(); PutSprite((unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8), BALL_WIDTH_BYTES, BALL_HEIGHT, aBallSprite[1]); nX = (nX + 8) % (640 - BALL_WIDTH); nY = (nY + 4) % (200 - BALL_HEIGHT); PutSprite((unsigned char *)0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8), BALL_WIDTH_BYTES, BALL_HEIGHT, aBallSprite[0]); MovePlayer(0); MovePlayer(1); nCounter++; if(nCounter >= 1000) nCounter = 0; PutSprite(pScore[0], NUMBER_WIDTH_BYTES, NUMBER_HEIGHT, aNumberSprite[nCounter / 100]); PutSprite(pScore[1], NUMBER_WIDTH_BYTES, NUMBER_HEIGHT, aNumberSprite[nCounter / 100]); nFPS++; } } //////////////////////////////////////////////////////////////////////// Si compilamos y ejecutamos en el emulador obtenemos lo siguiente:
Ya sólo falta por implementar la lógica del juego y el menú, que debido a su sencillez ni voy a comentar, podéis leer el código directamente. Este es el programa final terminado: //////////////////////////////////////////////////////////////////////// // PongC08.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> #include "Sprites.h" //////////////////////////////////////////////////////////////////////// //------------------------ Generic functions -------------------------// //////////////////////////////////////////////////////////////////////// #define MIN(a,b) (((a)<(b))?(a):(b)) #define MAX(a,b) (((a)>(b))?(a):(b)) //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //SetBorder //////////////////////////////////////////////////////////////////////// void SetBorder(unsigned char nColor) { __asm ld b, 4 (ix) ld c, b call #0xBC38 ;SCR_SET_BORDER __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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //PutSprite() //////////////////////////////////////////////////////////////////////// void PutSprite(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; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //WaitVsync //////////////////////////////////////////////////////////////////////// void WaitVsync() { __asm ld b,#0xf5 ;; PPI port B input _wait_vsync: in a,(c) ;; [4] read PPI port B input ;; (bit 0 = "1" if vsync is active, ;; or bit 0 = "0" if vsync is in-active) rra ;; [1] put bit 0 into carry flag jp nc,_wait_vsync ;; [3] if carry not set, loop, otherwise continue __endasm; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //LineHMode2Byte //////////////////////////////////////////////////////////////////////// void LineHMode2Byte(unsigned int nX, unsigned int nY, unsigned char nColor, unsigned char nBytes) { memset((unsigned char *)(0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 8)), nColor ? 0xFF : 0x00, nBytes); } //////////////////////////////////////////////////////////////////////// //Enumeration to identify each physical key typedef enum _eKey { Key_CursorUp = 0, Key_CursorRight, Key_CursorDown, Key_F9, Key_F6, Key_F3, Key_Enter, Key_FDot, Key_CursorLeft, //8 Key_Copy, Key_F7, Key_F8, Key_F5, Key_F1, Key_F2, Key_F0, Key_Clr, //16 Key_BraceOpen, Key_Return, Key_BraceClose, Key_F4, Key_Shift, Key_BackSlash, Key_Control, Key_Caret, //24 Key_Hyphen, Key_At, Key_P, Key_SemiColon, Key_Colon, Key_Slash, Key_Dot, Key_0, //32 Key_9, Key_O, Key_I, Key_L, Key_K, Key_M, Key_Comma, Key_8, //40 Key_7, Key_U, Key_Y, Key_H, Key_J, Key_N, Key_Space, Key_6_Joy2Up, //48 Key_5_Joy2Down, Key_R_Joy2Left, Key_T_Joy2Right, Key_G_Joy2Fire, Key_F, Key_B, Key_V, Key_4, //56 Key_3, Key_E, Key_W, Key_S, Key_D, Key_C, Key_X, Key_1, //64 Key_2, Key_Esc, Key_Q, Key_Tab, Key_A, Key_CapsLock, Key_Z, Key_Joy1Up, //72 Key_Joy1Down, Key_Joy1Left, Key_Joy1Right, Key_Joy1Fire1, Key_Joy1Fire2, Key_Joy1Fire3, Key_Del, Key_Max //80 }_ekey; //////////////////////////////////////////////////////////////////////// //IsKeyPressedFW //////////////////////////////////////////////////////////////////////// char nKeyPressed; unsigned char IsKeyPressedFW(unsigned char eKey) { __asm LD HL, #_nKeyPressed LD (HL), #0 LD A, 4 (IX) CALL #0xBB1E ;KM TEST KEY JP Z, _end_IsKeyPressed LD HL, #_nKeyPressed LD (HL), #1 _end_IsKeyPressed: __endasm; return nKeyPressed; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //---------------- Specific variables and functions-------------------// //////////////////////////////////////////////////////////////////////// #define NUM_PLAYERS 2 #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 200 #define SCORE_Y 20 #define SCORE_X_1 ((SCREEN_WIDTH / 2) - 50 - NUMBER_WIDTH / 2) #define SCORE_X_2 ((SCREEN_WIDTH / 2) + 50 - NUMBER_WIDTH / 2) #define PLAYER_WIDTH 16 #define PLAYER_HEIGHT 30 #define PLAYER_WIDTH_BYTES 2 #define PLAYER_Y_INC 4 struct _tPlayer { unsigned char nY; unsigned int nX; _ekey ekeyUp; _ekey ekeyDown; unsigned char nScore; char nLastYMove; }_tPlayer; struct _tPlayer aPlayer[NUM_PLAYERS]; struct _tBall { int nY; int nX; char nYDir; char nXDir; }_tBall; struct _tBall tBall; unsigned char *pScoreScreen[NUM_PLAYERS]; //////////////////////////////////////////////////////////////////////// //DrawPlayer //////////////////////////////////////////////////////////////////////// void DrawPlayer(unsigned char nPlayer) { unsigned char nY = 0; for(nY = 0; nY < PLAYER_HEIGHT; nY++) LineHMode2Byte(aPlayer[nPlayer].nX, aPlayer[nPlayer].nY+ nY, 1, PLAYER_WIDTH_BYTES); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //MovePlayer //////////////////////////////////////////////////////////////////////// void MovePlayer(unsigned char nPlayer) { unsigned char nY = 0; unsigned char nYToDelete = 0; unsigned char nYToDraw = 0; int nYNew = 0; unsigned char bKeyUp = IsKeyPressedFW(aPlayer[nPlayer].ekeyUp); unsigned char bKeyDown = IsKeyPressedFW(aPlayer[nPlayer].ekeyDown); if(bKeyUp == bKeyDown) { aPlayer[nPlayer].nLastYMove = 0; return; } nYNew = bKeyUp ? aPlayer[nPlayer].nY - PLAYER_Y_INC : aPlayer[nPlayer].nY + PLAYER_Y_INC; if(bKeyDown) { if(nYNew + PLAYER_HEIGHT > SCREEN_HEIGHT) nYNew = SCREEN_HEIGHT - PLAYER_HEIGHT; aPlayer[nPlayer].nY = nYNew; nYToDelete = aPlayer[nPlayer].nY - PLAYER_Y_INC; nYToDraw = aPlayer[nPlayer].nY + PLAYER_HEIGHT - PLAYER_Y_INC; aPlayer[nPlayer].nLastYMove = 1; } else { if(nYNew <= 0) nYNew = 0; aPlayer[nPlayer].nY = nYNew; nYToDelete = aPlayer[nPlayer].nY + PLAYER_HEIGHT; nYToDraw = aPlayer[nPlayer].nY; aPlayer[nPlayer].nLastYMove = -1; } for(nY = 0; nY < PLAYER_Y_INC; nY++) LineHMode2Byte(aPlayer[nPlayer].nX, nYToDelete + nY, 0, PLAYER_WIDTH_BYTES); for(nY = 0; nY < PLAYER_Y_INC; nY++) LineHMode2Byte(aPlayer[nPlayer].nX, nYToDraw + nY, 1, PLAYER_WIDTH_BYTES); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //MoveBall //////////////////////////////////////////////////////////////////////// char MoveBall() { char nReturn = -1; char bCollision = -1; //Delete ball PutSprite((unsigned char *)0xC000 + ((tBall.nY / 8) * 80) + ((tBall.nY % 8) * 2048) + (tBall.nX / 8), BALL_WIDTH_BYTES, BALL_HEIGHT, aBallSprite[1]); tBall.nY = tBall.nY + tBall.nYDir; if(tBall.nYDir > 0 && tBall.nY >= (SCREEN_HEIGHT - BALL_HEIGHT)) { tBall.nYDir *= -1; tBall.nY = (SCREEN_HEIGHT - BALL_HEIGHT); } if(tBall.nYDir < 0 && tBall.nY <= 0) { tBall.nYDir *= -1; tBall.nY = 0; } tBall.nX = tBall.nX + tBall.nXDir; if(tBall.nXDir < 0) //ball moving to left { if(tBall.nX == aPlayer[0].nX + PLAYER_WIDTH) { if((tBall.nY > aPlayer[0].nY && tBall.nY < aPlayer[0].nY + PLAYER_HEIGHT) || (tBall.nY + BALL_HEIGHT > aPlayer[0].nY && tBall.nY + BALL_HEIGHT < aPlayer[0].nY + PLAYER_HEIGHT)) { bCollision = 0; } } if(tBall.nX < 0) { tBall.nX = 0; nReturn = 1; } } else //ball moving to right { if(tBall.nX + BALL_WIDTH == aPlayer[1].nX) { if((tBall.nY > aPlayer[1].nY && tBall.nY < aPlayer[1].nY + PLAYER_HEIGHT) || (tBall.nY + BALL_HEIGHT > aPlayer[1].nY && tBall.nY + BALL_HEIGHT < aPlayer[1].nY + PLAYER_HEIGHT)) { bCollision = 1; } } if(tBall.nX >= SCREEN_WIDTH - BALL_WIDTH) { tBall.nX = SCREEN_WIDTH - BALL_WIDTH; nReturn = 0; } } if(bCollision != -1) { tBall.nXDir *= -1; if(aPlayer[bCollision].nLastYMove != 0) { tBall.nYDir = aPlayer[bCollision].nLastYMove > 0 ? tBall.nYDir - 1 : tBall.nYDir + 1; if(tBall.nYDir > 0) { tBall.nYDir = MIN(tBall.nYDir , 4); tBall.nYDir = MAX(tBall.nYDir , 1); } else { tBall.nYDir = MIN(tBall.nYDir , -1); tBall.nYDir = MAX(tBall.nYDir , -4); } } } //Draw ball on new position PutSprite((unsigned char *)0xC000 + ((tBall.nY / 8) * 80) + ((tBall.nY % 8) * 2048) + (tBall.nX / 8), BALL_WIDTH_BYTES, BALL_HEIGHT, aBallSprite[0]); return nReturn; } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //ShowMenu //////////////////////////////////////////////////////////////////////// void ShowMenu() { SetMode(1); SetCursor(17, 5); printf("PongC"); SetCursor(1, 8); printf("Use 'qa' and 'pl' to move"); SetCursor(8, 16); printf("Press Space to start game"); SetCursor(3, 24); printf("Mochilote - www.cpcmania.com - 2013"); while(!IsKeyPressedFW(Key_Space)) {} } //////////////////////////////////////////////////////////////////////// //ShowMenuWinner //////////////////////////////////////////////////////////////////////// void ShowMenuWinner(unsigned char nPlayer) { SetMode(1); SetCursor(17, 5); printf("PongC"); SetCursor(10, 8); printf("Player %d (%s) won", nPlayer + 1, nPlayer == 0 ? "left" : "right"); SetCursor(8, 16); printf("Press Space to Exit"); while(!IsKeyPressedFW(Key_Space)) {} } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //InitGame //////////////////////////////////////////////////////////////////////// void InitGame(char nLastWinner) { unsigned char bLeft = 0; if(nLastWinner == -1) memset(aPlayer, 0, sizeof(aPlayer)); aPlayer[0].nY = 100 - PLAYER_HEIGHT / 2; aPlayer[1].nY = 100 - PLAYER_HEIGHT / 2; aPlayer[0].nX = 8; aPlayer[1].nX = (SCREEN_WIDTH - 8) - PLAYER_WIDTH; aPlayer[0].ekeyUp = Key_Q; aPlayer[1].ekeyUp = Key_P; aPlayer[0].ekeyDown = Key_A; aPlayer[1].ekeyDown = Key_L; srand(GetTime()); bLeft = (nLastWinner == -1) ? (rand() % 2) : (nLastWinner == 0); memset(&tBall, 0, sizeof(tBall)); tBall.nX = bLeft ? aPlayer[1].nX - 24 : aPlayer[0].nX + 24; tBall.nY = SCREEN_HEIGHT / 2; tBall.nYDir = 2; tBall.nXDir = bLeft ? -8 : 8; SetMode(2); SetBorder(26); SetColor(0, 0); SetColor(1, 26); DrawPlayer(0); DrawPlayer(1); pScoreScreen[0] = (unsigned char *)0xC000 + ((SCORE_Y / 8) * 80) + ((SCORE_Y % 8) * 2048) + (SCORE_X_1 / 8); pScoreScreen[1] = (unsigned char *)0xC000 + ((SCORE_Y / 8) * 80) + ((SCORE_Y % 8) * 2048) + (SCORE_X_2 / 8); } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //Game //////////////////////////////////////////////////////////////////////// void Game() { char nWinner = -1; InitGame(-1); while(1) { WaitVsync(); nWinner = MoveBall(); MovePlayer(0); MovePlayer(1); PutSprite(pScoreScreen[0], NUMBER_WIDTH_BYTES, NUMBER_HEIGHT, aNumberSprite[aPlayer[0].nScore]); PutSprite(pScoreScreen[1], NUMBER_WIDTH_BYTES, NUMBER_HEIGHT, aNumberSprite[aPlayer[1].nScore]); if(nWinner != -1) { InitGame(nWinner); aPlayer[nWinner].nScore++; if(aPlayer[nWinner].nScore >= 10) { ShowMenuWinner(nWinner); return; } nWinner = -1; } } } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //main //////////////////////////////////////////////////////////////////////// void main() { while(1) { ShowMenu(); Game(); } } //////////////////////////////////////////////////////////////////////// Si compilamos y ejecutamos en el emulador obtenemos lo siguiente, yo he jugado conmigo mismo a dos manos (modo forever alone) para grabar este video:
Pues al final ha quedado un clon del Pong bastante apañao y jugable, pero sobretodo didáctico, que es de lo que se trata. Podéis bajar un zip con todos ficheros (código fuente, bat's para compilar, binarios y dsk's) aquí: PongC.zip |
www.CPCMania.com 2013 |