#include <stdlib.h> /* random() */ #include <stdio.h> /* printf() */ #include <conio.h> /* KEY_nnn, getch() */ #include <os/keyboard.h> #include <os/os.h> #include <os/device.h> /*! * \ingroup programs * \defgroup tetris Tetris * @{ */ /* dimensions of playing area */ #define SCN_WID 15 #define SCN_HT 20 /* direction vectors */ #define DIR_UP { 0, -1 } #define DIR_DN { 0, +1 } #define DIR_LT { -1, 0 } #define DIR_RT { +1, 0 } #define DIR_UP2 { 0, -2 } #define DIR_DN2 { 0, +2 } #define DIR_LT2 { -2, 0 } #define DIR_RT2 { +2, 0 } /* ANSI colors */ #define COLOR_BLACK 0 #define COLOR_RED 1 #define COLOR_GREEN 2 #define COLOR_YELLOW 3 #define COLOR_BLUE 4 #define COLOR_MAGENTA 5 #define COLOR_CYAN 6 #define COLOR_WHITE 7 typedef struct { short int DeltaX, DeltaY; } vector; typedef struct { char Plus90, Minus90; /* pointer to shape rotated +/- 90 degrees */ char Color; /* shape color */ vector Dir[4]; } shape; /* drawing instructions for this shape */ shape Shapes[]= /* shape #0: cube */ { { 0, 0, COLOR_BLUE, { DIR_UP, DIR_RT, DIR_DN, DIR_LT }}, /* shapes #1 & #2: bar */ { 2, 2, COLOR_GREEN, { DIR_LT, DIR_RT, DIR_RT, DIR_RT }}, { 1, 1, COLOR_GREEN, { DIR_UP, DIR_DN, DIR_DN, DIR_DN }}, /* shapes #3 & #4: 'Z' shape */ { 4, 4, COLOR_CYAN, { DIR_LT, DIR_RT, DIR_DN, DIR_RT }}, { 3, 3, COLOR_CYAN, { DIR_UP, DIR_DN, DIR_LT, DIR_DN }}, /* shapes #5 & #6: 'S' shape */ { 6, 6, COLOR_RED, { DIR_RT, DIR_LT, DIR_DN, DIR_LT }}, { 5, 5, COLOR_RED, { DIR_UP, DIR_DN, DIR_RT, DIR_DN }}, /* shapes #7, #8, #9, #10: 'J' shape */ { 8, 10, COLOR_MAGENTA, { DIR_RT, DIR_LT, DIR_LT, DIR_UP }}, { 9, 7, COLOR_MAGENTA, { DIR_UP, DIR_DN, DIR_DN, DIR_LT }}, { 10, 8, COLOR_MAGENTA, { DIR_LT, DIR_RT, DIR_RT, DIR_DN }}, { 7, 9, COLOR_MAGENTA, { DIR_DN, DIR_UP, DIR_UP, DIR_RT }}, /* shapes #11, #12, #13, #14: 'L' shape */ { 12, 14, COLOR_YELLOW, { DIR_RT, DIR_LT, DIR_LT, DIR_DN }}, { 13, 11, COLOR_YELLOW, { DIR_UP, DIR_DN, DIR_DN, DIR_RT }}, { 14, 12, COLOR_YELLOW, { DIR_LT, DIR_RT, DIR_RT, DIR_UP }}, { 11, 13, COLOR_YELLOW, { DIR_DN, DIR_UP, DIR_UP, DIR_LT }}, /* shapes #15, #16, #17, #18: 'T' shape */ { 16, 18, COLOR_WHITE, { DIR_UP, DIR_DN, DIR_LT, DIR_RT2 }}, { 17, 15, COLOR_WHITE, { DIR_LT, DIR_RT, DIR_UP, DIR_DN2 }}, { 18, 16, COLOR_WHITE, { DIR_DN, DIR_UP, DIR_RT, DIR_LT2 }}, { 15, 17, COLOR_WHITE, { DIR_RT, DIR_LT, DIR_DN, DIR_UP2 }}}; char Dirty[SCN_HT], Screen[SCN_WID][SCN_HT]; ////////////////////////////////////////////////////////////////////////////// // ANSI GRAPHIC OUTPUT ////////////////////////////////////////////////////////////////////////////// /***************************************************************************** name: refresh action: updates display device based on contents of global char array Screen[][]. Updates only those rows marked Dirty[] *****************************************************************************/ void refresh(void) { unsigned XPos, YPos; qword fpos; word ch[2]; wchar_t s[8] = L"\x1B[%dm "; s[5] = s[6] = 0x2588; for(YPos=0; YPos < SCN_HT; YPos++) { if(!Dirty[YPos]) continue; /* gotoxy(0, YPos) */ wprintf(L"\x1B[%d;1H", YPos + 1); fpos = YPos * 80; for(XPos=0; XPos < SCN_WID; XPos++) { /* 0xDB is a solid rectangular block in the PC character set */ //wprintf(L"\x1B[%dm\xDB\xDB", 30 + Screen[XPos][YPos]); /* U+2588 is the Unicode Full Block character */ // xxx - gcc seems to break this wprintf(L"\x1B[%dm\x2588\x2588", 30 + Screen[XPos][YPos]); //wprintf(s, 30 + Screen[XPos][YPos]); //ch[0] = ch[1] = (Screen[XPos][YPos] << 8) | 0xDB; //devWriteSync(vga, (fpos + XPos * 2) * 2, ch, sizeof(ch)); } Dirty[YPos]=0; } /* reset foreground color to gray */ wprintf(L"\x1B[37m"); //fflush(stdout); } ////////////////////////////////////////////////////////////////////////////// // GRAPHIC CHUNK DRAW & HIT DETECT ////////////////////////////////////////////////////////////////////////////// /***************************************************************************** name: blockDraw action: draws one graphic block in display buffer at position (XPos, YPos) *****************************************************************************/ void blockDraw(unsigned XPos, unsigned YPos, char Color) { if(XPos >= SCN_WID) XPos=SCN_WID - 1; if(YPos >= SCN_HT) YPos=SCN_HT - 1; Color &= 7; Screen[XPos][YPos]=Color; Dirty[YPos]=1; } /* this row has been modified */ /***************************************************************************** name: blockHit action: determines if coordinates (XPos, YPos) are already occupied by a graphic block returns:color of graphic block at (XPos, YPos) (zero if black/ empty) *****************************************************************************/ char blockHit(unsigned XPos, unsigned YPos) { return(Screen[XPos][YPos]); } ////////////////////////////////////////////////////////////////////////////// // SHAPE DRAW & HIT DETECT ////////////////////////////////////////////////////////////////////////////// /***************************************************************************** name: shapeDraw action: draws shape WhichShape in display buffer at position (XPos, YPos) *****************************************************************************/ void shapeDraw(unsigned XPos, unsigned YPos, unsigned WhichShape) { unsigned Index; for(Index=0; Index < 4; Index++) { blockDraw(XPos, YPos, Shapes[WhichShape].Color); XPos += Shapes[WhichShape].Dir[Index].DeltaX; YPos += Shapes[WhichShape].Dir[Index].DeltaY; } blockDraw(XPos, YPos, Shapes[WhichShape].Color); } /***************************************************************************** name: shapeErase action: erases shape WhichShape in display buffer at position (XPos, YPos) by setting its color to zero *****************************************************************************/ void shapeErase(unsigned XPos, unsigned YPos, unsigned WhichShape) { unsigned Index; for(Index=0; Index < 4; Index++) { blockDraw(XPos, YPos, COLOR_BLACK); XPos += Shapes[WhichShape].Dir[Index].DeltaX; YPos += Shapes[WhichShape].Dir[Index].DeltaY; } blockDraw(XPos, YPos, COLOR_BLACK); } /***************************************************************************** name: shapeHit action: determines if shape WhichShape would collide with something already drawn in display buffer if it were drawn at position (XPos, YPos) returns:nonzero if hit, zero if nothing there *****************************************************************************/ char shapeHit(unsigned XPos, unsigned YPos, unsigned WhichShape) { unsigned Index; for(Index=0; Index < 4; Index++) { if(blockHit(XPos, YPos)) return(1); XPos += Shapes[WhichShape].Dir[Index].DeltaX; YPos += Shapes[WhichShape].Dir[Index].DeltaY; } if(blockHit(XPos, YPos)) return(1); return(0); } ////////////////////////////////////////////////////////////////////////////// // MAIN ROUTINES ////////////////////////////////////////////////////////////////////////////// /***************************************************************************** name: screenInit action: clears display buffer, marks all rows dirty, set raw keyboard mode *****************************************************************************/ void screenInit(void) { unsigned XPos, YPos; for(YPos=0; YPos < SCN_HT; YPos++) { Dirty[YPos]=1; /* force entire screen to be redrawn */ for(XPos=1; XPos < (SCN_WID - 1); XPos++) Screen[XPos][YPos]=0; Screen[0][YPos]=Screen[SCN_WID - 1][YPos]=COLOR_BLUE; } for(XPos=0; XPos < SCN_WID; XPos++) Screen[XPos][0]=Screen[XPos][SCN_HT - 1]=COLOR_BLUE; } /***************************************************************************** *****************************************************************************/ void collapse(void) { char SolidRow[SCN_HT], SolidRows; int Row, Col, Temp; /* determine which rows are solidly filled */ SolidRows=0; for(Row=1; Row < SCN_HT - 1; Row++) { Temp=0; for(Col=1; Col < SCN_WID - 1; Col++) if(Screen[Col][Row]) Temp++; if(Temp == SCN_WID - 2) { SolidRow[Row]=1; SolidRows++; } else SolidRow[Row]=0; } if(SolidRows == 0) return; /* collapse them */ for(Temp=Row=SCN_HT - 2; Row > 0; Row--, Temp--) { while(SolidRow[Temp]) Temp--; if(Temp < 1) { for(Col=1; Col < SCN_WID - 1; Col++) Screen[Col][Row]=COLOR_BLACK; } else { for(Col=1; Col < SCN_WID - 1; Col++) Screen[Col][Row]=Screen[Col][Temp]; } Dirty[Row]=1; } refresh(); } /***************************************************************************** *****************************************************************************/ wint_t getKey(void) { dword end = sysUpTime() + 200; while (!_kbhit()) if (sysUpTime() >= end) return 0; return _wgetch(); } /***************************************************************************** name: main *****************************************************************************/ int main(void) { char Fell, NewShape, NewX, NewY; unsigned Shape, X, Y, i; wint_t Key; /* re-seed the random number generator */ srand(sysUpTime()); /* turn off stdio.h buffering */ //setbuf(stdout, NULL); /* banner screen */ wprintf(L"\x1B[2J"L"\x1B[1;%dH"L"TETRIS by Alexei Pazhitnov", SCN_WID * 2 + 2); wprintf(L"\x1B[2;%dH"L"Software by Chris Giese", SCN_WID * 2 + 2); wprintf(L"\x1B[4;%dH"L"'1' and '2' rotate shape", SCN_WID * 2 + 2); wprintf(L"\x1B[5;%dH"L"Arrow keys move shape", SCN_WID * 2 + 2); wprintf(L"\x1B[6;%dH"L"Esc or Q quits", SCN_WID * 2 + 2); /*wprintf(L"\x1B[2J"L"\x1B[1;%dH"L"TETRIS by Alexei Pazhitnov", 2); wprintf(L"\x1B[2;%dH"L"Software by Chris Giese", 2); wprintf(L"\x1B[4;%dH"L"'1' and '2' rotate shape", 2); wprintf(L"\x1B[5;%dH"L"Arrow keys move shape", 2); wprintf(L"\x1B[6;%dH"L"Esc or Q quits", 2);*/ NEW: wprintf(L"\x1B[9;%dH"L"Press any key to begin", SCN_WID * 2 + 2); //wprintf(L"\x1B[9;%dH"L"Press any key to begin", 2); //fflush(stdout); _wgetch(); wprintf(L"\x1B[8;%dH"L" ", SCN_WID * 2 + 2); wprintf(L"\x1B[9;%dH"L" ", SCN_WID * 2 + 2); /*wprintf(L"\x1B[8;%dH"L" ", 2); wprintf(L"\x1B[9;%dH"L" ", 2);*/ screenInit(); goto FOO; while(1) { Fell=0; NewShape=Shape; NewX=X; NewY=Y; Key=getKey(); if(Key == 0) { NewY++; Fell=1; } else { if(Key == 'q' || Key == 'Q' || Key == 27) break; //goto FIN; if (Key == '1' || Key == KEY_UP) NewShape=Shapes[Shape].Plus90; else if(Key == '2') NewShape=Shapes[Shape].Minus90; else if(Key == KEY_LEFT) { if(X > 0) NewX=X - 1; } else if(Key == KEY_RIGHT) { if(X < SCN_WID - 1) NewX=X + 1; } else if(Key == KEY_DOWN) { if(Y < SCN_HT - 1) NewY=Y + 1; } Fell=0; } /* if nothing has changed, skip the bottom half of this loop */ if(NewX == X && NewY == Y && NewShape == Shape) continue; /* otherwise, erase old shape from the old pos'n */ shapeErase(X, Y, Shape); /* hit anything? */ if(shapeHit(NewX, NewY, NewShape) == 0) /* no, update pos'n */ { X=NewX; Y=NewY; Shape=NewShape; } /* yes -- did the piece hit something while falling on its own? */ else if(Fell) /* yes, draw it at the old pos'n... */ { shapeDraw(X, Y, Shape); /* ... and spawn new shape */ FOO: Y=3; X=SCN_WID / 2; Shape=rand() % 19; collapse(); /* if newly spawned shape hits something, game over */ if(shapeHit(X, Y, Shape)) { /*wprintf(L"\x1B[8;%dH"L"\x1B[37;40;1m" L" GAME OVER"L"\x1B[0m", SCN_WID * 2 + 2);*/ wprintf(L"\x1B[8;%dH"L"\x1B[37;40;1m" L" GAME OVER"L"\x1B[0m", 2); goto NEW; } } /* hit something because of user movement/rotate OR no hit: just redraw it */ shapeDraw(X, Y, Shape); refresh(); } wprintf(L"\x1B[2J"); return 0; } //@}