PongC: Clone of the classic Pong in mode 2 (C & ASM with SDCC) Pincha aquí para verlo en español In this tutorial we will try to make a clone of the classic game Pong in our Amstrad in Mode 2 (2-color 640x200). As we saw in the tutorial Painting pixels: Introduction to video memory, in mode 2 each byte of video memory stores 8 pixels in a very specific order. To set to 0 or 1 a pixel in concrete we have to do at the bit level. Let's start preparing a function for paint a pixel in mode 2, which looks like this: //////////////////////////////////////////////////////////////////////// //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; } } //////////////////////////////////////////////////////////////////////// Let's see how it works doing our first program, that simply paint the game ball. The entire program would look like this: //////////////////////////////////////////////////////////////////////// // 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); } } //////////////////////////////////////////////////////////////////////// If you compile and run on the emulator get the following:
As can be seen, paint pixel by pixel is very slow and we barely reached 17 frames per second, not enough to make the game ... As the ball is square, we better paint it by horizontal lines to see if we improve the speed. For it we programmed a new function to paint horizontal lines in mode 2, which looks like this: //////////////////////////////////////////////////////////////////////// //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++; } } } //////////////////////////////////////////////////////////////////////// As we see if the position x is a multiple of 8 and the number of pixels you want to paint are also multiple of 8 is as simple as doing a memset, otherwise the function paint the line bit by bit in first and last bytes and use memset in central bytes to save time. Modify the previous program to paint our ball with this new function, being as follows: //////////////////////////////////////////////////////////////////////// // 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); } } //////////////////////////////////////////////////////////////////////// If you compile and run on the emulator get the following:
We have improved a lot, has increased from 17 to 56 frames per second, but still quite insufficient since we lack all the rest of the game... Let's do an optimization to the function of paint lines, so that instead of doing at the bit level operations we already have precalculated the 8 possible bytes of left and right end of our line, the source code of the program is the following: //////////////////////////////////////////////////////////////////////// // 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); } } //////////////////////////////////////////////////////////////////////// If you compile and run on the emulator get the following:
Greatly improved speed, from 56 to 119 frames per second. Could we improve more? Well, we could use sprites and paint on screen with assembler to go faster, but clear, as we are moving the ball pixel by pixel and each byte's 8 pixels we can not have sprite for the ball... To solve it we will use 8 sprites for the ball, one for each possible combination of the ball on its first and last byte. This is the program if we apply these changes: //////////////////////////////////////////////////////////////////////// // 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]); } } //////////////////////////////////////////////////////////////////////// If you compile and run on the emulator get the following:
Now that's a big improvement, we have gone from 119 to 525 frames per second. Now that we have a good speed, we will see how to move it and paint well. To not let the ball trail what we do is that each time the ball moves first delete its previous position and then we paint at the new position. As we're painting directly in video memory, this will cause terrible flashing ball, so to avoid flicker we will synchronize with the screen refreshing and for this we will use a function of CPCWiki and we will adapt to the assembler of SDCC. With all these changes, the program would look like: //////////////////////////////////////////////////////////////////////// // 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++; } } //////////////////////////////////////////////////////////////////////// If you compile and run on the emulator get the following:
Using synchronization we avoid flicker, but we stuck to 50fps, so finally the ball will have to move several pixels at a time, so even though we are in sync with the screen repainting the ball will give a sense of flickering. Now let's add the players and for that we will not complicate, we use the function of painting horizontal lines, but always adjusted to 8 pixels to be quick. Once painted the players, what we will do when moving up or down is to paint the new lines that has moved and will delete the old to work fast. The program would look like this: //////////////////////////////////////////////////////////////////////// // 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++; } } //////////////////////////////////////////////////////////////////////// If you compile and run on the emulator get the following:
We lack scoring markers, as are just numbers from 0 to 9 I made the sprites by hand in text editor. As the ball moves finally 8 pixels each time, ie byte aligned, we do not need the 8 possible sprites of the ball, one is enough. I moved the sprites of the ball and scoring markers to a file .h for convenience: //////////////////////////////////////////////////////////////////////// // 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, }, }; //////////////////////////////////////////////////////////////////////// The modified program using scoring markers and showing what would be the entire game engine in motion is as follows: //////////////////////////////////////////////////////////////////////// // 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++; } } //////////////////////////////////////////////////////////////////////// If you compile and run on the emulator get the following:
Now just missing to implement the game logic and the menu, which because of its simplicity I will not comment, you can read the code directly. This is the final program completed: //////////////////////////////////////////////////////////////////////// // 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(); } } //////////////////////////////////////////////////////////////////////// If you compile and run on the emulator get the following, I've played with myself with both hands (mode forever alone) to record this video:
Finally we have achieved a clone of Pong quite nice and playable, but especially educational, that's what were trying to get in this tutorials. You could download a zip with all files (source code, bat to compile, binary and dsk's) here: PongC.zip |
www.CPCMania.com 2013 |