/* * Ping-Pong game for RC2014 with two NeoPixel 8x8 Matrixes * * Based on: * https://github.com/samagragupta/ping-pong-C/blob/master/pingpong.c * * Lumir Vanek vanekl.nospam@valachnet.cz */ #define NUMPIXELS 128 #define NUMROWS 8 #define NUMCOLUMNS 8 #define YM2149 // https://z80kits.com/shop/ym2149-sound-card #ifdef YM2149 // Sound and joystick inputs #define data_port 0xd0 #define register_port 0xd8 #define JOYSTICK_LEFT 0b00000001 #define JOYSTICK_DOWN 0b00000010 #define JOYSTICK_UP 0b00000100 #define JOYSTICK_RIGHT 0b00001000 #define JOYSTICK_FIRE 0b00010000 #define JOYSTICK_RST 0b00100000 #define JOYSTICK_SET 0b01000000 #endif #define RGB_BLACK 0 #define RGB_BLUE 1 #define RGB_GREEN 2 #define RGB_CYAN 3 #define RGB_RED 4 #define RGB_MAGENTA 5 #define RGB_BROWN 6 #define RGB_LIGHTGRAY 7 #define RGB_DARKGRAY 8 #define RGB_YELLOW 14 #define RGB_WHITE 15 #include "./NPLib2x64.c" #include #include #include #include #include // NPLib API // void initialise(); // void show(void); // void setPixel(int pixelNumber, unsigned char R, unsigned char G, unsigned char B); // void setPixelRC(int row, int column, unsigned char R, unsigned char G, unsigned char B); // void clearPixel(int pixelNumber); // void clearbuffer(); // compile for RC2014 (CPM) using Z88dk : // zcc +cpm -vn pingpong.c -create-app -o pingpong.bin unsigned char spriteSmileUp[] = { 0x3c, 0x42, 0xA5, 0x81, 0xA5, 0x99, 0x42, 0x3C }; unsigned char spriteSmileDown[] = { 0x3C, 0x42, 0xA5, 0x81, 0x99, 0xA5, 0x42, 0x3C }; unsigned char sprite0[] = { 0x38, 0x44, 0xD6, 0xD6, 0xD6, 0xD6, 0x44, 0x38 }; unsigned char sprite1[] = { 0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E }; unsigned char sprite2[] = { 0x7C, 0xC6, 0x06, 0x0C, 0x78, 0xC0, 0xC0, 0xFE }; unsigned char sprite3[] = { 0x7C, 0xC6, 0x06, 0x1C, 0x1C, 0x06, 0xC6, 0x7C }; unsigned char sprite4[] = { 0x2C, 0x6C, 0x6C, 0xCC, 0xCC, 0xFE, 0x0C, 0x0C }; unsigned char sprite5[] = { 0x7E, 0x60, 0x60, 0x3C, 0x06, 0x06, 0xC6, 0x7C }; // prototypes void pingpong(); void clear(); void delay(int); void writeSprite1(unsigned char* sprite, unsigned char R, unsigned char G, unsigned char B); void writeSprite2(unsigned char* sprite, unsigned char R, unsigned char G, unsigned char B); void palette(int n, uint8_t *R, uint8_t *G, uint8_t *B); void printBits(size_t const size, void const * const ptr); // Plays sound for specified time (param is delay) void soundA(int dly, int coarse, int fine, int amplitude, int envelopeCoarse, int envelopeFine); void soundB(int dly, int coarse, int fine, int amplitude, int envelopeCoarse, int envelopeFine); #ifdef YM2149 void initSound(); void nosound(); unsigned char joystickA(); unsigned char joystickB(); #endif // Structure of Ball struct ball { int x ; int y ; int speedx; int speedy; } ball; // Structure of Bat(b1, b2) - Table tennis bat struct bat { int x; int y; int w; int l; int s; // Step value } b1, b2; // Score for both players, 0 - 5 struct score { int s; } s1, s2; int speed; // Ball speed used by delay() in main loop int bat_len; // Bal vertical lenght, 2 or 3 px char driver; // 'j' for joystick, 'k' use keyboard int main() { // Initialise NeoPixel board initialise(NUMPIXELS); #ifdef YM2149 initSound(); #endif driver = 'k'; // Keyboard is default printf("NeoPixel Ping-Pong game v0.3\nBy L. Vanek 2023\n\n"); //=============================================================================== // Select keyboard or joystick #ifdef YM2149 printf("Use [K] keyboard or [J] joysticks ?\n"); scanf("%c", &driver); if (driver != 'k' && driver != 'j') { driver = 'k'; } #endif if (driver == 'k') { printf("Player A: use a/z to move Bat Up/Down\n"); printf("Player B: use m/k to move Bat Up/Down\n"); printf("Press ESC to exit game\n"); } else { printf("\nUse joystick Up/Down buttons\n"); printf("Press RST button to exit game\n"); } pingpong(); return 0; } void pingpong() { uint8_t R, G, B; replay: //=============================================================================== // Ball speed: 1 (Fast) - X (Slow) printf("Enter speed: [1] (Fastest) - [X] (Slower)\n"); scanf("%d", &speed); if (speed < 1) { speed = 1; } //=============================================================================== // Bat lenght: 2 (Difficult) or 3 (Easy) printf("\nEnter Bat length: [2] (Difficult) or [3] (Easy)\n"); scanf("%d", &bat_len); if ((bat_len < 2) || (bat_len > 3)) { bat_len = 3; } printf("Press the 'any' key to start\n"); getch(); //=============================================================================== // Init main variables ball.x = 7; ball.y = 3; ball.speedx = 1; ball.speedy = 1; b1.x = 0; b1.y = 2; b1.w = 1; b1.l = bat_len; b1.s = 1; // Step value b2.x = 15; b2.y = 2; b2.w = 1; b2.l = bat_len; b2.s = 1; // Step value s1.s = 0; s2.s = 0; while(true) { printf("A - %d\n", s1.s); printf("B - %d\n\n", s2.s); if(ball.y <= 0 || ball.y >= 7) // Test Y axis range for Ball { ball.speedy = ball.speedy * -1; // Oposite direction on Y axis } //=============================================================================== // Move Ball to new position ball.x = ball.x + ball.speedx; ball.y = ball.y + ball.speedy; //=============================================================================== clear(); // Paint Ball palette(RGB_WHITE, &R, &G, &B); setPixelRC(ball.y, ball.x, R, G, B); // Paint Bats (for simplicity they can't change axis X) palette(RGB_BLUE, &R, &G, &B); setPixelRC(b1.y, b1.x, R, G, B); setPixelRC(b1.y+1, b1.x, R, G, B); if (bat_len == 3) { setPixelRC(b1.y+2, b1.x, R, G, B); } setPixelRC(b2.y, b2.x, R, G, B); setPixelRC(b2.y+1, b2.x, R, G, B); if (bat_len == 3) { setPixelRC(b2.y+2, b2.x, R, G, B); } show(); //=============================================================================== if(ball.x <= b1.x) { ball.x = 7; ball.y = 3; s2.s = s2.s + 1; // Ball is too left, B scores printf("B scores !\n"); printf("A - %d\n", s1.s); printf("B - %d\n\n", s2.s); for (int i = 1; i < 3; i++) { palette(RGB_RED, &R, &G, &B); setPixelRC(b1.y, b1.x, R, G, B); setPixelRC(b1.y+1, b1.x, R, G, B); if (bat_len == 3) { setPixelRC(b1.y+2, b1.x, R, G, B); } show(); #ifdef YM2149 soundB(400, 1, 255, 16, 0, 10); #endif palette(RGB_BLUE, &R, &G, &B); setPixelRC(b1.y, b1.x, R, G, B); setPixelRC(b1.y+1, b1.x, R, G, B); if (bat_len == 3) { setPixelRC(b1.y+2, b1.x, R, G, B); } show(); #ifdef YM2149 soundB(400, 1, 255, 16, 0, 10); #endif } } if(ball.x >= b2.x) { ball.x = 7; ball.y = 3; s1.s = s1.s + 1; // Ball is too right, A scores printf("A scores !\n"); printf("A - %d\n", s1.s); printf("B - %d\n\n", s2.s); for (int i = 1; i < 3; i++) { palette(RGB_RED, &R, &G, &B); setPixelRC(b2.y, b2.x, R, G, B); setPixelRC(b2.y+1, b2.x, R, G, B); if (bat_len == 3) { setPixelRC(b2.y+2, b2.x, R, G, B); } show(); soundA(400, 1, 255, 16, 0, 10); palette(RGB_BLUE, &R, &G, &B); setPixelRC(b2.y, b2.x, R, G, B); setPixelRC(b2.y+1, b2.x, R, G, B); if (bat_len == 3) { setPixelRC(b2.y+2, b2.x, R, G, B); } show(); soundA(400, 1, 255, 16, 0, 10); } } //=============================================================================== if(b1.x + b1.w == ball.x) // Test hit of Ball with A player { bool yHit = false; if (bat_len == 3) { if((ball.y == b1.y) || (ball.y == b1.y + 1) || (ball.y == b1.y + 2)) { yHit = true; } } else { if((ball.y == b1.y) || (ball.y == b1.y + 1)) { yHit = true; } } if (yHit == true) { printf("A pings !\n"); ball.speedx = ball.speedx * -1; // Turn Ball back to player B palette(RGB_MAGENTA, &R, &G, &B); setPixelRC(b1.y, b1.x, R, G, B); setPixelRC(b1.y+1, b1.x, R, G, B); if (bat_len == 3) { setPixelRC(b1.y+2, b1.x, R, G, B); } show(); soundB(100, 1, 2, 15, 0, 0); palette(RGB_BLUE, &R, &G, &B); setPixelRC(b1.y, b1.x, R, G, B); setPixelRC(b1.y+1, b1.x, R, G, B); if (bat_len == 3) { setPixelRC(b1.y+2, b1.x, R, G, B); } show(); } } if(b2.x - b2.w == ball.x) // Test hit of Ball with B player { bool yHit = false; if (bat_len == 3) { if((ball.y == b2.y) || (ball.y == b2.y + 1) || (ball.y == b2.y + 2)) { yHit = true; } } else { if((ball.y == b2.y) || (ball.y == b2.y + 1)) { yHit = true; } } if (yHit == true) { printf("B pings !\n"); ball.speedx = ball.speedx * -1; // Turn Ball back to player A palette(RGB_MAGENTA, &R, &G, &B); setPixelRC(b2.y, b2.x, R, G, B); setPixelRC(b2.y+1, b2.x, R, G, B); if (bat_len == 3) { setPixelRC(b2.y+2, b2.x, R, G, B); } show(); soundA(100, 1, 2, 15, 0, 0); palette(RGB_BLUE, &R, &G, &B); setPixelRC(b2.y, b2.x, R, G, B); setPixelRC(b2.y+1, b2.x, R, G, B); if (bat_len == 3) { setPixelRC(b2.y+2, b2.x, R, G, B); } show; } } //=============================================================================== if (driver == 'k') { if(kbhit()) { int ch = getch(); if(b1.y + b1.l < NUMROWS) { if((ch == 'z') || (ch == 'Z')) { b1.y = b1.y + b1.s; // Move A down } } if(b1.y > 0) { if((ch == 'a') || (ch == 'A')) { b1.y = b1.y - b1.s; // Move A up } } if(b2.y + b2.l < NUMROWS) { if((ch == 'm') || (ch == 'M')) { b2.y = b2.y + b2.s; // Move B down } } if(b2.y > 0) { if((ch == 'k') || (ch == 'K')) { b2.y = b2.y - b2.s; // Move B up } } if(ch == 27) // ESC { clear(); return; } } } else { unsigned char btnJoystickA = joystickA(); unsigned char btnJoystickB = joystickB(); if(b1.y + b1.l < NUMROWS) { if(btnJoystickA & JOYSTICK_DOWN) { b1.y = b1.y + b1.s; // Move A down } } if(b1.y > 0) { if(btnJoystickA & JOYSTICK_UP) { b1.y = b1.y - b1.s; // Move A up } } if(b2.y + b2.l < NUMROWS) { if(btnJoystickB & JOYSTICK_DOWN) { b2.y = b2.y + b2.s; // Move B down } } if(b2.y > 0) { if(btnJoystickB & JOYSTICK_UP) { b2.y = b2.y - b2.s; // Move B up } } if((btnJoystickA & JOYSTICK_RST) || (btnJoystickB & JOYSTICK_RST)) { clear(); return; } } delay(speed); //=============================================================================== // Display score for players if(s1.s >= 5 || s2.s >= 5) { printf("\nA - %d \t B - %d", s1.s, s2.s); clear(); if (s1.s == 5) // Who wins ? { palette(RGB_YELLOW,&R,&G,&B); // Yellow score number for winner unsigned char *spriteA = &sprite0; spriteA += 8 * s1.s; writeSprite1((unsigned char*) spriteA, R, G, B); palette(RGB_GREEN,&R,&G,&B); // Green score number for loser unsigned char *spriteB = &sprite0; spriteB += 8 * s2.s; writeSprite2((unsigned char*) spriteB, R, G, B); show(); } else if (s2.s == 5) { palette(RGB_GREEN,&R,&G,&B); // Green score number for loser unsigned char *spriteA = &sprite0; spriteA += 8 * s1.s; writeSprite1((unsigned char*) spriteA, R, G, B); palette(RGB_YELLOW,&R,&G,&B); // Yellow score number for winner unsigned char *spriteB = &sprite0; spriteB += 8 * s2.s; writeSprite2((unsigned char*) spriteB, R, G, B); show(); } delay(3000); // Get time to loser :-( congrats to winner :-) //=============================================================================== // Display Un/Happy smiles for players if (s1.s >= 5) // Who wins ? { palette(RGB_YELLOW,&R,&G,&B); writeSprite1((unsigned char*) &spriteSmileUp, R, G, B); palette(RGB_GREEN,&R,&G,&B); writeSprite2((unsigned char*) &spriteSmileDown, R, G, B); show(); } else if (s2.s >= 5) { palette(RGB_GREEN, &R, &G, &B); writeSprite1((unsigned char*) &spriteSmileDown, R, G, B); palette(RGB_YELLOW, &R, &G, &B); writeSprite2((unsigned char*) &spriteSmileUp, R, G, B); show(); } delay(3000); printf("\n\nGAME OVER, press 'r' to replay or ESC to exit\n"); char ch = getch(); if((ch == 'r') || (ch == 'R')) { goto replay; } else if(ch == 27) // ESC { clear(); return; } } // if(s1.s >= 5 || s2.s >= 5) //=============================================================================== } // while(true) } void clear() { for(int pixelNumber = 0; pixelNumber < NUMPIXELS; pixelNumber++) { clearPixel(pixelNumber); } show(); } void delay(int val) { for (int i = 0; i < val; i++) { for (int j = 0; j < 25; j++) { ; } } } /* * Write a sprite to 1.st matrix * * Sprite is the pointer to a byte array, encoding the contains of the sprite */ void writeSprite1(unsigned char* sprite, unsigned char R, unsigned char G, unsigned char B) { for (int row = 0; row < 8; row++) { unsigned char value = sprite[row]; unsigned char mask = 128; // bin 10000000 for (int column = 0; column < 8; column++) { if (value & mask) // pixel is not 0 { setPixelRC(row, column, R, G, B); } else { setPixelRC(row, column, 0, 0, 0); } mask >>= 1; } } } /* * Write a sprite to 2.nd matrix * * Sprite is the pointer to a byte array, encoding the contains of the sprite */ void writeSprite2(unsigned char* sprite, unsigned char R, unsigned char G, unsigned char B) { for (int row = 0; row < 8; row++) { unsigned char value = sprite[row]; unsigned char mask = 128; // bin 10000000 for (int column = 8; column < 16; column++) { if (value & mask) // pixel is not 0 { setPixelRC(row, column, R, G, B); } else { setPixelRC(row, column, 0, 0, 0); } mask >>= 1; } } } void palette(int n, uint8_t *R, uint8_t *G, uint8_t *B ) { switch(n) { case RGB_BLUE: *R = 0; *G = 0; *B = 32; break; case RGB_GREEN: *R = 0; *G = 32; *B = 0; break; case RGB_CYAN: *R = 0; *G = 32; *B = 32; break; case RGB_RED: *R = 32; *G = 0; *B = 0; break; case RGB_MAGENTA: *R = 32; *G = 0; *B = 32; break; case RGB_BROWN: *R = 32; *G = 16; *B = 0; break; case RGB_LIGHTGRAY: *R = 16; *G = 16; *B = 16; break; case RGB_DARKGRAY: *R = 8; *G = 8; *B = 8; break; case RGB_YELLOW: *R = 32; *G = 32; *B = 0; break; case RGB_WHITE: *R = 32; *G = 32; *B = 32; break; default: *R = 0; *G = 0; *B = 0; break; } } #ifdef YM2149 void initSound() { //outp(register_port, 7); // Ch. A, B mixer //outp(data_port, 0b00111100); // Enable chn A+B on mixer, I/O ports set to Input } /* * Note: I have reproboxes in opposite sides due to simplest cabling, * so soundB is for player A and vice versa */ void soundA(int dly, int coarse, int fine, int amplitude, int envelopeCoarse, int envelopeFine) { outp(register_port, 7); // Ch. A, C mixer if (amplitude < 16) { outp(data_port, 0b00111110); // Enable chn A on mixer, I/O ports set to Input } else { outp(data_port, 0b00111010); // Enable chn A+C on mixer, I/O ports set to Input } outp(register_port, 1); // Ch. A, coarse outp(data_port, coarse); outp(register_port, 0); // Ch. A, fine outp(data_port, fine); outp(register_port, 8); // Ch. A, volume outp(data_port, amplitude); //------------------------------------------------------------------------------------ if (amplitude >= 16) { outp(register_port, 5); // Ch. C, coarse outp(data_port, coarse); outp(register_port, 4); // Ch. C, fine outp(data_port, fine); outp(register_port, 13); // Envelope SHAPE/CYCLE CONTROL outp(data_port, 0b00001110); outp(register_port, 12); // Envelope coarse outp(data_port, envelopeCoarse); outp(register_port, 11); // Envelope fine outp(data_port, envelopeFine); } delay(dly); nosound(); } void soundB(int dly, int coarse, int fine, int amplitude, int envelopeCoarse, int envelopeFine) { outp(register_port, 7); // Ch. B, C mixer if (amplitude < 16) { outp(data_port, 0b00111101); // Enable chn A on mixer, I/O ports set to Input }else { outp(data_port, 0b00111001); // Enable chn B+C on mixer, I/O ports set to Input } outp(register_port, 3); // Ch. B, coarse outp(data_port, coarse); outp(register_port, 2); // Ch. B, fine outp(data_port, fine); outp(register_port, 9); // Ch. B, volume outp(data_port, amplitude); //------------------------------------------------------------------------------------ if (amplitude >= 16) { outp(register_port, 5); // Ch. C, coarse outp(data_port, coarse); outp(register_port, 4); // Ch. C, fine outp(data_port, fine); outp(register_port, 13); // Envelope SHAPE/CYCLE CONTROL outp(data_port, 0b00001110); outp(register_port, 12); // Envelope coarse outp(data_port, envelopeCoarse); outp(register_port, 11); // Envelope fine outp(data_port, envelopeFine); } delay(dly); nosound(); } void nosound() { outp(register_port, 8); // Ch. A, volume outp(data_port, 0); outp(register_port, 9); // Ch. B, volume outp(data_port, 0); outp(register_port, 10); // Ch. C, volume outp(data_port, 0); outp(register_port, 5); // Ch. C, coarse outp(data_port, 0); outp(register_port, 4); // Ch. C, fine outp(data_port, 0); outp(register_port, 13); // Envelope SHAPE/CYCLE CONTROL outp(data_port, 0b00000000); outp(register_port, 7); outp(data_port, 0b00111111); // Disable all chanels, I/O ports set to Input } unsigned char joystickA() { outp(register_port, 7); outp(data_port, 0); outp(register_port, 14); // I/O port A unsigned char btnJoystickA = (unsigned char) inp(register_port); btnJoystickA = ~btnJoystickA; outp (0, btnJoystickA); //printBits(sizeof(btnJoystickA), &btnJoystickA); //if (btnJoystickA != 0) { printf("Joy A: %d\n", btnJoystickA); } return btnJoystickA; } unsigned char joystickB() { outp(register_port, 7); outp(data_port, 0); outp(register_port, 15); // I/O port B unsigned char btnJoystickB = (unsigned char) inp(register_port); btnJoystickB = ~btnJoystickB; outp (1, btnJoystickB); //printBits(sizeof(btnJoystickB), &btnJoystickB); //if (btnJoystickB != 0) { printf("Joy B: %d\n", btnJoystickB); } return btnJoystickB; } #endif #ifndef YM2149 // If You don't have sound card, there is only delay void soundA1(int param) { delay(param); } void soundA2(int param) { delay(param); } void soundB1(int param) { delay(param); } void soundB2(int param) { delay(param); } #endif // Assumes little endian void printBits(size_t const size, void const * const ptr) { unsigned char *b = (unsigned char*) ptr; unsigned char byte; int i, j; for (i = size-1; i >= 0; i--) { for (j = 7; j >= 0; j--) { byte = (b[i] >> j) & 1; printf("%u", byte); } } puts(""); }