#define NUMPIXELS 128 #define NUMROWS 8 #define NUMCOLUMNS 8 #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 // https://github.com/electrified/rc2014-ym2149/tree/4b8af5396633bc87178b81087cec0f71b8307908 #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 #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 snekgame.c -create-app -o snekgame.bin // prototypes #ifdef YM2149 void sound(int dly, int coarse, int fine, int amplitude, int envelopeCoarse, int envelopeFine); void nosound(); unsigned char joystickA(); unsigned char joystickB(); #endif void delay(int); // Used to sound lenght void pause(unsigned int p); // Used to game speed void palette(int n, uint8_t *R, uint8_t *G, uint8_t *B); uint8_t able_to_move_in_direction(uint8_t proposed_dir); void move_snek(); uint8_t turn_snek(); uint8_t turn_snek_cw(); uint8_t turn_snek_acw(); void new_apple(); bool change_dir(); void clear_grid(); void printBits(size_t const size, void const * const ptr); void printgrid(); // debug // globals #define SNEK_ROW 0 #define SNEK_COL 1 // grid is our record of what's where, a 2D copy of the display uint8_t grid[8][16]={0}; struct point { uint8_t row; uint8_t col; }; struct point snek[NUMPIXELS]; // snek is a circular buffer containing the co-ordinatess of the body segments // refer to coordinates like this: snek[pos_tail].row and snek[pos_tail].col // to move the snake, a new point is added at the head, and one removed from the tail // position in the circ buffer of the snake's head and tail. uint8_t pos_tail, pos_head; #define DIR_UP 0 #define DIR_RIGHT 1 #define DIR_DOWN 2 #define DIR_LEFT 3 #define DIR_NONE 4 uint8_t snek_dir; #define UNOCCUPIED 0 #define OCCUPIED_BODY 1 #define OCCUPIED_APPLE 2 #define EDGE_OF_GRID 3 bool quit = false; #define PAUSE_TIME_INIT 16384 // Initial snake speed #define PAUSE_TIME_MIN 2048 // Maximup snake speed #define PAUSE_TIME_SPEED_UP 512 // Speed up step value unsigned int pause_time = PAUSE_TIME_INIT; int main() { uint8_t R,G,B; // use this delay to randomise unsigned int t; // always initialise NUMPIXELS initialise(NUMPIXELS); printf("8x16 Snek v0.1\nS. Dixon 2022\npress the 'any' key to continue.\n\n"); printf("8x16 Snek v0.2\nL. Vanek modification for joystick\n"); printf("Press SET for new apple\n"); printf("Press FIRE to speed up snake to have more adrenaline\n"); printf("Press RST to exit\n"); while (true) { if(kbhit()) { char ch = getch(); // eat it break; } t++; } srand(t); bool lost = false; while (!quit) { lost = false; pause_time = PAUSE_TIME_INIT; printf("\n==========================================\nNew game, pause time: %d\n", pause_time); // set up clearbuffer(NUMPIXELS); clear_grid(); // initial position of snake grid[4][3] = OCCUPIED_BODY; snek[0].row = 4; snek[0].col = 3; pos_tail = 0; palette(RGB_BLUE,&R,&G,&B); setPixelRC(4, 3, R, G, B); grid[4][4] = OCCUPIED_BODY; snek[1].row = 4; snek[1].col = 4; pos_head = 1; setPixelRC(4, 4, R, G, B); snek_dir = DIR_RIGHT; new_apple(); // looks for unoccupied spot show(); // run loop while (lost == false && quit == false) { if(able_to_move_in_direction(snek_dir) == 0) { if(change_dir()) { uint8_t occ = able_to_move_in_direction(snek_dir); if(occ == OCCUPIED_APPLE) { move_snek(true); //printf("new_apple point a\n\r"); #ifdef YM2149 sound(100, 1, 2, 15, 0, 0); #endif new_apple(); } else { move_snek(false); } } else { move_snek(false); } } else if(able_to_move_in_direction(snek_dir) == OCCUPIED_APPLE) { move_snek(true); //printf("new_apple point b\n\r"); #ifdef YM2149 sound(100, 1, 2, 15, 0, 0); #endif new_apple(); } else { uint8_t new_dir = turn_snek(); if(new_dir == DIR_NONE) { // printf("stopped by %d\r\n",able_to_move_in_direction(snek_dir)); lost = true; #ifdef YM2149 sound(400, 1, 255, 16, 0, 10); #endif new_apple(); } else { snek_dir = new_dir; uint8_t occ = able_to_move_in_direction(snek_dir); if(occ == OCCUPIED_APPLE) { move_snek(true); //printf("new_apple point a\n\r"); #ifdef YM2149 sound(200, 1, 2, 15, 0, 0); #endif new_apple(); } else { move_snek(false); } } } //printf("run loop point c, grid is now\n\r"); //printgrid(); show(); if(kbhit()) { // press a key to quit quit = true; char ch = getch(); // eat it //printgrid(); pause(65536); } pause(pause_time); } // end run loop } // end quit loop return 0; } void new_apple() { uint8_t r,c; uint8_t R,G,B; do{ r = rand() % 8; c = rand() % 16; } while (grid[r][c] != UNOCCUPIED); grid[r][c] = OCCUPIED_APPLE; //printf("new apple placed at r:%d c:%d\n\r",r,c); //printgrid(); palette(RGB_RED, &R, &G, &B); setPixelRC(r, c, R, G, B); // Speed up snake after eating apple to have more adrenaline if (pause_time > PAUSE_TIME_MIN) { pause_time -= PAUSE_TIME_SPEED_UP; printf("Pause time: %d\n", pause_time); } } uint8_t turn_snek() { if(rand() % 100 > 50){ // 50% of the time return turn_snek_acw(); } else{ return turn_snek_cw(); } } bool change_dir() { int new_dir = snek_dir; unsigned char btnJoystickA = joystickA(); unsigned char btnJoystickB = joystickB(); if(btnJoystickA & JOYSTICK_LEFT) { new_dir = DIR_LEFT; outp(0, JOYSTICK_LEFT); printf("DIR_LEFT\n"); } else if(btnJoystickA & JOYSTICK_RIGHT) { new_dir = DIR_RIGHT; outp(0, JOYSTICK_RIGHT); printf("DIR_RIGHT\n"); } else if(btnJoystickA & JOYSTICK_UP) { new_dir = DIR_UP; outp(0, JOYSTICK_UP); printf("DIR_UP\n"); } else if(btnJoystickA & JOYSTICK_DOWN) { new_dir = DIR_DOWN; outp(0, JOYSTICK_DOWN); printf("DIR_DOWN\n"); } else if ((btnJoystickA & JOYSTICK_FIRE) || (btnJoystickB & JOYSTICK_FIRE)) { outp(0, JOYSTICK_FIRE); printf("FIRE\n"); if (pause_time > PAUSE_TIME_MIN) { pause_time -= PAUSE_TIME_SPEED_UP; printf("Pause time: %d\n", pause_time); } } else if ((btnJoystickA & JOYSTICK_SET) || (btnJoystickB & JOYSTICK_SET)) { outp(0, JOYSTICK_SET); printf("SET\n"); new_apple(); } else if ((btnJoystickA & JOYSTICK_RST) || (btnJoystickB & JOYSTICK_RST)) { outp(0, JOYSTICK_RST); printf("RST\n"); quit = true; } else { outp(0, 0); // Turn LED;s Off return false; } if(new_dir != snek_dir) { uint8_t occ = able_to_move_in_direction(new_dir); if(occ == 0 || occ == OCCUPIED_APPLE) { snek_dir = new_dir; return true; } } // else return false; } // returns the new direction uint8_t turn_snek_acw() { uint8_t occ; // try UP occ = able_to_move_in_direction(DIR_UP); if(occ==0 || occ==OCCUPIED_APPLE) { return DIR_UP; } // try LEFT occ = able_to_move_in_direction(DIR_LEFT); if(occ==0 || occ == OCCUPIED_APPLE) { return DIR_LEFT; } // try DOWN occ = able_to_move_in_direction(DIR_DOWN); if(occ==0 || occ == OCCUPIED_APPLE) { return DIR_DOWN; } // try RIGHT occ = able_to_move_in_direction(DIR_RIGHT); if(occ==0 || occ == OCCUPIED_APPLE) { return DIR_RIGHT; } // default return DIR_NONE; } // returns the new direction uint8_t turn_snek_cw() { uint8_t occ; // try UP occ = able_to_move_in_direction(DIR_UP); if(occ==0 || occ == OCCUPIED_APPLE) { return DIR_UP; } // try RIGHT occ = able_to_move_in_direction(DIR_RIGHT); if(occ==0 || occ == OCCUPIED_APPLE) { return DIR_RIGHT; } // try DOWN occ = able_to_move_in_direction(DIR_DOWN); if(occ==0 || occ == OCCUPIED_APPLE) { return DIR_DOWN; } // try LEFT occ = able_to_move_in_direction(DIR_LEFT); if(occ==0 || occ == OCCUPIED_APPLE) { return DIR_LEFT; } // default return DIR_NONE; } void move_snek(bool eat){ uint8_t R,G,B; // calculate new head r,c uint8_t new_head_r = snek[pos_head].row; uint8_t new_head_c = snek[pos_head].col; switch(snek_dir){ case DIR_UP: new_head_r--; break; case DIR_RIGHT: new_head_c++; break; case DIR_DOWN: new_head_r++; break; case DIR_LEFT: new_head_c--; break; } if(eat==false) { // clear tail palette(RGB_BLACK,&R,&G,&B); setPixelRC(snek[pos_tail].row, snek[pos_tail].col, R, G, B); grid[snek[pos_tail].row][snek[pos_tail].col] = UNOCCUPIED; // advance tail pointer pos_tail++; pos_tail = pos_tail % NUMPIXELS; // wrap at NUMPIXELS } // print new head palette(RGB_BLUE,&R,&G,&B); setPixelRC(new_head_r, new_head_c, R, G, B); grid[new_head_r][new_head_c] = OCCUPIED_BODY; // advance head pointers pos_head++; pos_head = pos_head % NUMPIXELS; // wrap at NUMPIXELS snek[pos_head].row = new_head_r; snek[pos_head].col = new_head_c; } // returns 0 if free to move uint8_t able_to_move_in_direction(uint8_t proposed_dir) { uint8_t headrow = snek[pos_head].row; uint8_t headcol = snek[pos_head].col; // first check grid switch(proposed_dir) { case DIR_UP: if(headrow==0) return EDGE_OF_GRID; else headrow--; break; case DIR_RIGHT: if(headcol==15) return EDGE_OF_GRID; else headcol++; break; case DIR_DOWN: if(headrow==7) return EDGE_OF_GRID; else headrow++; break; case DIR_LEFT: if(headcol==0) return EDGE_OF_GRID; else headcol--; break; } // now check actual square - headrow,headcol uint8_t occupied = grid[headrow][headcol]; if(occupied==OCCUPIED_BODY || occupied==OCCUPIED_APPLE) return occupied; return 0; // OK } void clear_grid() { for(int r = 0; r < 8; r++) { for(int c = 0; c < 16; c++) { grid[r][c] = UNOCCUPIED; } } } void pause(unsigned int val) { for (int i = 0; i < val; i++) { ; } } void delay(int val) { for (int i = 0; i < val; i++) { for (int j = 0; j < 25; j++) { ; } } } 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 sound(int dly, int coarse, int fine, int amplitude, int envelopeCoarse, int envelopeFine) { outp(register_port, 7); if (amplitude < 16) { outp(data_port, 0b00111100); // Enable chn A+B on mixer, I/O ports set to Input } else { outp(data_port, 0b00111000); // Enable chn A+B+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); //------------------------------------------------------------------------------------ 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); // It's curious - input data from I/O ports are in Register ports. This stolen me 2 days to realize it 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 // 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(""); } // debug tool void printgrid() { for(int r = 0; r < 8; r++) { for(int c = 0; c < 16; c++) { printf("%d", grid[r][c]); } printf("\r\n"); } uint8_t pos = pos_tail; while(pos!=pos_head){ printf("%d,%d ",snek[pos].row,snek[pos].col); pos++; pos = pos % 64; // wrap at 64 } printf("\n\r"); }