//Tetris for P2 using 1080p 2bpp Tile Driver and FlexSpin C //Raymond Allen 2020-2021 MIT license //Ver1c: RJA added preview window and game over message //Adapted from MIT Licensed Tetris game code here: https://github.com/Gregwar/ASCII-Tetris //Copyright(c) < 2010 - 2015 > Grégoire Passault //Tetris graphic adapted from this: https://www.pngkey.com/maxpic/u2t4t4i1u2a9i1t4/ //Converted to 2bpp data file with Prop2BitBitmap tool from here: https://forums.parallax.com/discussion/171223/1080p-tiled-gui/p3 #include "Platform.h" //Most of the P2 board settings here #define P2_TARGET_MHZ 297 #include "sys/p2es_clock.h" // the 2bpp tile driver with VGA output //New, double buffered version... Uses swap() to update struct __using("1080p_TileDriver_8b_double.spin2") vga; //RJA: Visual Studio will show an error here, but it's OK // access to the cogless graphics driver inside the vga driver struct __using("Graphics3g.spin2") gr; //RJA: Visual Studio will show an error here, but it's OK //GarryJ's one cog USB Keyboard/Mouse driver struct __using("1CogKbM_rja3b.spin2") kbm; //RJA: Visual Studio will show an error here, but it's OK //Sound by Johannes Ahlebrand struct __using("simplesound.spin2") sound; //RJA: Visual Studio will show an error here, but it's OK #include "stdbool.h" #include #include #include #include typedef struct { int x; int y; int MaxX; int MaxY; int buttons; } MouseInfo; MouseInfo MouseStatus = { 0,0,0,0 }; MouseInfo OldMouseStatus = { 0,0,0,0 }; int DebugLong; //1080p Screen parameters for tile driver #define Cols (120) //characters are 16 pixels wide #define Rows (68) //16 pixels tall (two 16-pixel rows per character) (only 1/2 of last row visible) //Graphics buffer size #define xtiles (64) #define ytiles (6) //Game data #define Blocks_x 10 //playing screen width in blocks #define Blocks_y 18 //playing screen height in blocks #define Blocks_xNext 6 //window size for next block #define Blocks_yNext 5 //window size for next block //Game position data #define Blocks_Top 10 //position of game on screen #define Blocks_Left ((Cols-Blocks_x*3)/2) //positioned to center on screen #define Blocks_TopNext 12 //position of next window #define Blocks_LeftNext 90 //position of next window struct tetris_level { int score; int nsec; }; struct tetris { char** game; int w; int h; int level; int gameover; int score; struct tetris_block { char data[5][5]; int w; int h; } current; int x; int y; }; struct tetris_block blocks[] = { {{"##", "##"}, 2, 2 }, {{" X ", "XXX"}, 3, 2 }, {{"@@@@"}, 4, 1}, {{"OO", "O ", "O "}, 2, 3}, {{"&&", " &", " &"}, 2, 3}, {{"ZZ ", " ZZ"}, 3, 2} }; struct tetris_level levels[] = { {0, 1200000}, {1500, 900000}, {8000, 700000}, {20000, 500000}, {40000, 400000}, {75000, 300000}, {100000, 200000} }; #define TETRIS_PIECES (sizeof(blocks)/sizeof(struct tetris_block)) #define TETRIS_LEVELS (sizeof(levels)/sizeof(struct tetris_level)) void tetris_init(struct tetris* t, int w, int h) { int x, y; t->level = 1; t->score = 0; t->gameover = 0; t->w = w; t->h = h; t->game = malloc(sizeof(char*) * w); for (x = 0; x < w; x++) { t->game[x] = malloc(sizeof(char) * h); for (y = 0; y < h; y++) t->game[x][y] = ' '; } } void tetris_clean(struct tetris* t) { int x; for (x = 0; x < t->w; x++) { free(t->game[x]); } free(t->game); } //RJA: Terminal escape sequenses #define ESC "\x1b" #define CSI "\x1b[" void tetris_print(struct tetris* t) { //rja clearing screen with escape codes printf(CSI "2J"); // Clear screen printf(CSI "1;1H"); // Home ClearPlayArea(); //draw the next block coming DrawNextBlock(); //draw next block int x, y; char buf[200]; sprintf(buf,"[LEVEL: %d | SCORE: %d] \n", t->level, t->score); printf("%s", buf); vga.row = Rows - 3; vga.col = Cols / 2 - 14; vga.setbackcolor(vga.Malibu2); vga.setforecolor(vga.midnightblue); vga.print_string(buf); for (x = 0; x < 2 * t->w + 2; x++) printf("~"); printf("\n"); for (y = 0; y < t->h; y++) { printf("!"); for (x = 0; x < t->w; x++) { if (x >= t->x && y >= t->y && x < (t->x + t->current.w) && y < (t->y + t->current.h) && t->current.data[y - t->y][x - t->x] != ' ') {//RJA: Print the currently falling block printf("%c ", t->current.data[y - t->y][x - t->x]); char c = t->current.data[y - t->y][x - t->x]; if (c != ' ') DrawBlock(x, y, c); //draw this block } else {//RJA: Print everthing except the falling block printf("%c ", t->game[x][y]); char c = t->game[x][y]; if (c != ' ') DrawBlock(x, y, c); //draw this block } } printf("!\n"); } for (x = 0; x < 2 * t->w + 2; x++) printf("~"); printf("\n"); //VSYNC & Swap display buffer _waitatn(); //wait for video cog to give us attention, signaling VSYNC vga.swap(); } void ClearPlayArea() { //clear play area vga.boxfill(Blocks_Top, Blocks_Left, Blocks_y * 3, Blocks_x * 3, 0); //clear next block area vga.boxfill(Blocks_TopNext, Blocks_LeftNext, Blocks_yNext * 3, Blocks_xNext * 3, 0); } int DrawBlock(int x, int y, char c) {//Draw block with color depending on c int clr; switch (c) { case '#': clr = vga.Yellow; break; case 'X': clr = vga.BlueBell; break; case '@': clr = vga.RazzleDazzleRose; break; case 'O': clr = vga.ScreaminGreen4; break; case '&': clr = vga.Supernova; break; case 'Z': clr = vga.AtomicTangerine; break; default: clr = vga.White; break; } int clrset= vga.MakeColorSet(vga.black, clr, clr, clr); DrawBox(Blocks_Top+y*3, Blocks_Left+x*3, clrset); } int DrawNextBlock() {//Draw next block in next block window for (int y=0;y<= NextBlock.h;y++) for (int x = 0; x <= NextBlock.w; x++) { char c = NextBlock.data[y][x]; if (c != ' ') DrawBlockNext(x, y,c); } } int DrawBlockNext(int x, int y, char c) {//Draw next block with color depending on c int clr; switch (c) { case '#': clr = vga.Yellow; break; case 'X': clr = vga.BlueBell; break; case '@': clr = vga.RazzleDazzleRose; break; case 'O': clr = vga.ScreaminGreen4; break; case '&': clr = vga.Supernova; break; case 'Z': clr = vga.AtomicTangerine; break; default: clr = vga.Black; break; } if (clr != vga.Black) { int clrset = vga.MakeColorSet(vga.black, clr, clr, clr); DrawBox(Blocks_TopNext +3+ y * 3, Blocks_LeftNext +3 + x * 3, clrset); } } void DrawBox(int top, int left, int clr) {//Draw a custom box border using custom characters tacked on to ROM FONT //With tile data right after ROM font and then treat as extra characters int right = left + 3 - 1; int bottom = top + 3 + 1; //top row vga.setboxptr(top * vga.cols + left); vga.boxchr2(268, clr); vga.boxchr2(269, clr); vga.boxchr2(270, clr); //center rows int p = (top + 1) * vga.cols + left; vga.setboxptr(p); vga.boxchr2(271, clr); vga.boxchr2(272, clr); vga.boxchr2(273, clr); //bottom row vga.setboxptr((top + 3 - 1) * vga.cols + left); vga.boxchr2(274, clr); vga.boxchr2(275, clr); vga.boxchr2(276, clr); } int tetris_hittest(struct tetris* t) { int x, y, X, Y; struct tetris_block b = t->current; for (x = 0; x < b.w; x++) for (y = 0; y < b.h; y++) { X = t->x + x; Y = t->y + y; if (X < 0 || X >= t->w) return 1; if (b.data[y][x] != ' ') { if ((Y >= t->h) || (X >= 0 && X < t->w && Y >= 0 && t->game[X][Y] != ' ')) { return 1; } } } return 0; } struct tetris_block NextBlock; void tetris_new_block(struct tetris* t) { t->current = NextBlock; //RJA random to rand(), then to getrnd() t->x = (t->w / 2) - (t->current.w / 2); t->y = 0; if (tetris_hittest(t)) { t->gameover = 1; } //RJA andding NextBlock and moving this to end so can show next block uint32_t r = _rnd(); NextBlock = blocks[r % TETRIS_PIECES]; } void tetris_print_block(struct tetris* t) { int x, y, X, Y; struct tetris_block b = t->current; for (x = 0; x < b.w; x++) for (y = 0; y < b.h; y++) { if (b.data[y][x] != ' ') t->game[t->x + x][t->y + y] = b.data[y][x]; } } void tetris_rotate(struct tetris* t) { struct tetris_block b = t->current; struct tetris_block s = b; int x, y; b.w = s.h; b.h = s.w; for (x = 0; x < s.w; x++) for (y = 0; y < s.h; y++) { b.data[x][y] = s.data[s.h - y - 1][x]; } x = t->x; y = t->y; t->x -= (b.w - s.w) / 2; t->y -= (b.h - s.h) / 2; t->current = b; if (tetris_hittest(t)) { t->current = s; t->x = x; t->y = y; } } void tetris_gravity(struct tetris* t) { int x, y; t->y++; if (tetris_hittest(t)) { t->y--; tetris_print_block(t); tetris_new_block(t); } } void tetris_fall(struct tetris* t, int l) { int x, y; for (y = l; y > 0; y--) { for (x = 0; x < t->w; x++) t->game[x][y] = t->game[x][y - 1]; } for (x = 0; x < t->w; x++) t->game[x][0] = ' '; } void tetris_check_lines(struct tetris* t) { int x, y, l; int p = 100; for (y = t->h - 1; y >= 0; y--) { l = 1; for (x = 0; x < t->w && l; x++) { if (t->game[x][y] == ' ') { l = 0; } } if (l) { t->score += p; p *= 2; tetris_fall(t, y); sound.playSound(2); y++; } } } int tetris_level(struct tetris* t) { //RJA this appears to adjust level by current score and set the speed of the game via the return value int i; for (i = 0; i < (int)TETRIS_LEVELS; i++) { if (t->score >= levels[i].score) { t->level = i + 1; } else break; } return levels[t->level - 1].nsec; } //RJA Original used timespec and nanosleep to set game speed, but we don't have nanosleep, so adjusting a bit uint32_t ns; //nanoseconds to sleep void tetris_run(int w, int h) { //RJA: check clock freq int cf = _clockfreq(); printf("clock freq= %d\n", cf); struct tetris t; char cmd; int count = 0; tetris_init(&t, w, h); srand(time(NULL)); ns = 1000000; //title int clrset = vga.MakeColorSet(vga.white, vga.Killarney, vga.black, vga.Malibu2); vga.DrawTitle(Cols/2-12,1,clrset); //RJA adding NextBlock, so can show what's coming next uint32_t r = _rnd(); NextBlock = blocks[r % TETRIS_PIECES]; vga.row = Blocks_TopNext - 2; vga.col = Blocks_LeftNext; vga.setforecolor(vga.midnightblue); vga.setbackcolor(vga.Malibu2); vga.Print_String("Up Next:"); tetris_new_block(&t); while (!t.gameover) { usleep(ns / 1000);//RJA nanosleep(&tm, NULL); sound.pollTick(); count++; if (count % 50 == 0) { tetris_print(&t); } if (count % 350 == 0) { tetris_gravity(&t); tetris_check_lines(&t); } int cmd2;//RJA need signed cmd to handle -1 return value cmd2 = _rxraw(1);//rja using this to avoid blocking with getchar() while (cmd2 > 0) { switch (cmd2) {//RJA changing to WASD case 'a': sound.playSound(4); t.x--; if (tetris_hittest(&t)) t.x++; break; case 'd': sound.playSound(4); t.x++; if (tetris_hittest(&t)) t.x--; break; case 's': sound.playSound(3); tetris_gravity(&t); break; case 'w': tetris_rotate(&t); sound.playSound(1); break; } cmd2 = _rxraw(1); } ns = tetris_level(&t); //RJA: Adjust game speed } tetris_print(&t); printf("*** GAME OVER ***\n"); vga.row = 4; vga.col = 4; vga.setforecolor(vga.Red); vga.setbackcolor(vga.Malibu2); vga.Print_String("*** GAME OVER ***"); vga.swap(); } int main() { //set clock freq _clkset(_SETFREQ, _CLOCKFREQ); //check clock freq int cf = _clockfreq(); printf("clock freq= %d\n", cf); printf("Starting Graphics...\n"); int pCommand = gr.start(); int pFont = gr.GetFont(); printf("Starting Mouse...\n"); MouseStatus.MaxX = 1920-1; MouseStatus.MaxY = 1080-1; MouseStatus.x = 100; MouseStatus.y = 100; kbm.Start(USB_BASE_PIN, USB_ENABLE_PIN, USB_DM_PIN, USB_ERR_LED_PIN, &MouseStatus); printf("Starting VGA...\n"); vga.start(&MouseStatus, pCommand, pFont, & DebugLong); vga.cls(vga.Malibu2); printf("Starting music...\n"); sound.start(AV_LeftPin, AV_RightPin); sound.Play(); tetris_run(Blocks_x, Blocks_y); //Play Tetris while (true) {//just keep playing sound when game over sound.pollTick(); } }