From 0be25a41ca793c3c03814f529890492adfac8ad5 Mon Sep 17 00:00:00 2001 From: Wohlstand Date: Thu, 9 Nov 2017 22:50:32 +0300 Subject: Resolve weird crash caused by Tetris inside ADLMIDI2 and other changes - Move most of inline classes methods into own CC file - Move Input into own header with own CC file to share it with the puzzle game - Created virtual destructors and resolved weak vtable trouble between of Tetris's classes - Remove static declarisons of Tetris class. Instead, let it be member of UserInterface - Fixed forgot note-offs while sorting events row with zero length notes - Fixed crash caused by unsafe access by reference to element of array that was modified/reallocated one or multiple times - Stabilize dealing with zero-length notes --- utils/adlmidi-2/puzzlegame.hpp | 745 ++++------------------------------------- 1 file changed, 56 insertions(+), 689 deletions(-) (limited to 'utils/adlmidi-2/puzzlegame.hpp') diff --git a/utils/adlmidi-2/puzzlegame.hpp b/utils/adlmidi-2/puzzlegame.hpp index e8b3899..ece8d16 100755 --- a/utils/adlmidi-2/puzzlegame.hpp +++ b/utils/adlmidi-2/puzzlegame.hpp @@ -4,6 +4,9 @@ * License: MIT */ +#ifndef TETRIS_PUZZLEGAME +#define TETRIS_PUZZLEGAME + // Standard C++ includes: #include // for std::puts #include // for std::rand @@ -29,36 +32,13 @@ namespace ADLMIDI_PuzzleGame const auto Occ = 0x10000u; const auto Empty = 0x12Eu, Border = Occ+0x7DBu, Garbage = Occ+0x6DBu; - static unsigned long TimerRead() - { - static std::chrono::time_point begin = std::chrono::system_clock::now(); - return (unsigned long)(519 * std::chrono::duration( std::chrono::system_clock::now() - begin ).count()); - } - #define Timer TimerRead() - static void Sound(unsigned/*freq*/, unsigned/*duration*/) - { - } - static void PutCell(int x, int y, unsigned cell); - static void ScreenPutString(const char* str, unsigned attr, unsigned column, unsigned row) - { - for(; *str; ++column, ++str) - { - PutCell(column, row, ((unsigned char)(*str & 0xFF)) | (attr << 8)); - } - } + unsigned long TimerRead(); + void Sound(unsigned/*freq*/, unsigned/*duration*/); + void PutCell(int x, int y, unsigned cell); + void ScreenPutString(const char* str, unsigned attr, unsigned column, unsigned row); static char peeked_input = 0; - static bool kbhit() - { - if(peeked_input) return true; - peeked_input = Input.PeekInput(); - return peeked_input != 0; - } - static char getch() - { - char r = peeked_input; - peeked_input = 0; - return r; - } + bool kbhit(); + char getch(); // Game engine using uint64_t = std::uint_fast64_t; @@ -85,123 +65,47 @@ namespace ADLMIDI_PuzzleGame inline Piece operator*(T it) const { Piece res(*this); it(res); return res; } }; - template + //template struct TetrisArea { + bool DoDraw; int Area[Height][Width]; unsigned RenderX; unsigned n_full, list_full, animx; unsigned long timer; struct { ccrVars; } cascadescope; public: - TetrisArea(unsigned x=0) : RenderX(x) { } - - bool Occupied(int x,int y) const - { return x<1 || (x>Width-2) || (y>=0 && (Area[y][x] & Occ)); } + TetrisArea(bool doDraw, unsigned x=0); + virtual ~TetrisArea(); + bool Occupied(int x,int y) const; template inline void DrawRow(unsigned y, T get) { for(int x=1; x[&](int x,int y)->bool { this->DrawBlock(x,y,color); return false; }; - } - - bool CollidePiece(const Piece& piece) const - { - return piece>[&](int x,int y) { return this->Occupied(x,y); }; - } - bool CascadeEmpty(int FirstY) - { - if(DoDraw) - { - ccrBegin(cascadescope); - - // Count full lines - n_full = list_full = 0; - for(int y = std::max(0,FirstY); y < Height-1; ++y) - if(TestFully(y,true)) - { - ++n_full; - list_full |= 1u << y; - } - if(n_full) - { - // Clear all full lines in Tengen Tetris style. - for(animx = 1; animx < Width-1; ++animx) - { - for(timer=Timer; Timer= 0; --y) - if(!(list_full & (1u << y))) - DrawRow(target--, [&](unsigned x) { return this->Area[y][x]; }); - // Clear the top lines - for(auto n=n_full; n-- > 0; ) - DrawRow(target--, [](unsigned) { return Empty; }); - } - ccrFinish(false); - } - else - { - // Cascade non-empty lines - int target = Height-2, y = Height-2; - n_full = 0; - for(int miny = std::max(0,FirstY); y >= miny; --y) - if(TestFully(y, true)) - { - ++n_full; - miny = 0; - } - else - { - if(target != y) - memcpy(&Area[target], &Area[y], sizeof(Area[0][0])*Width); - --target; - } - // Clear the top lines - for(auto n=n_full; n-- > 0; ) - DrawRow(target--, [](unsigned) { return Empty; }); - return false; - } - } + void DrawBlock(unsigned x,unsigned y, int color); + void DrawPiece(const Piece& piece, int color); + bool CollidePiece(const Piece& piece) const; + bool CascadeEmpty(int FirstY); + protected: + virtual int MyKbHit() = 0; + virtual int MyGetCh() = 0; }; - template - class TetrisAIengine: TetrisArea + //template + class TetrisAIengine: TetrisArea /**/ { + bool DoReturns; public://protected: + TetrisAIengine(bool doReturns = true); + virtual ~TetrisAIengine(); + typedef std::pair scoring; struct position { @@ -239,245 +143,15 @@ namespace ADLMIDI_PuzzleGame struct { ccrVars; } aiscope; public: - void AI_Signal(int signal) - { - // any = piece moved; 0 = new piece, 2 = at ground - switch(signal) - { - case 0: // new piece - // do full scan and reset score - confident = false; - restart = true; - resting = false; - doubting = false; - break; - case 1: // piece moved - // keep scanning (low prio), no resets - resting = false; - break; - case 2: // now at ground - // do full scan without resetting score - resting = false; - restart = true; - doubting = true; - break; - } - } - int AI_Run(const decltype(Area)& in_area, const Piece* seq) - { - std::memcpy(Area, in_area, sizeof(Area)); - - // For AI, we use Pierre Dellacherie's algorithm, - // but extended for arbitrary ply lookahead. - enum { - landingHeightPenalty = -1, erodedPieceCellMetricFactor = 2, - rowTransitionPenalty = -2, columnTransitionPenalty = -2, - buriedHolePenalty = -8, wellPenalty = -2/*, - occlusionPenalty = 0*/ }; - - ccrBegin(aiscope); - - positions1.clear(); - positions2.clear(); - for(int x=-1; x ( {int(rot),x,int(rot2),x2} ) ); - if(rot2 == rot && x2 == 0) - positions2.push_back( std::move ( {int(rot),x,int(rot2),x2} ) ); - } - - confident = false; - doubting = false; - Restart: - restart = false; - resting = false; - - /*std::random_shuffle(positions1.begin(), positions1.end());*/ - std::random_shuffle(positions2.begin(), positions2.end()); - - ply = 0; - - Recursion: - // Take current board as testing platform - std::memcpy(data[ply].bkup, Area, sizeof(Area)); - if(!confident || ply > 0) - { - data[ply].best_score = {-999999,0}; - if(ply == 0) - { - //int heap_room = 0; - //while(TestFully(heap_room,false)) ++heap_room; - ply_limit = 2; - } - } - for(;;) - { - data[ply].pos_no = 0; - do { - if(DoReturns) - { - ccrReturn(0); - if(restart) goto Restart; - } - if(ply > 0) - { - // Game might have changed the the board contents. - std::memcpy(data[0].bkup, in_area, sizeof(Area)); - - // Now go on and test with the current testing platform - std::memcpy(Area, data[ply].bkup, sizeof(Area)); - } - - // Fix the piece in place, cascade rows, and analyze the result. - { const position& goal = (/*ply==0&&!doubting ? positions1 :*/ positions2) - [ data[ply].pos_no ]; - Piece n = seq[ply]; - n.x = goal.x; - n.r = goal.rot; - if(ply) n.y = 0; - - // If we are analyzing a mid-fall maneuver, verify whether - // the piece can be actually maneuvered into this position. - //if(ply==0 && n.y >= 0) - for(Piece q,t=*seq; t.x!=n.x && t.r!=n.r; ) - if( (t.r == n.r || (q=t, ++t.r, CollidePiece(t)&&(t=q,true))) - && (t.x <= n.x || (q=t, --t.x, CollidePiece(t)&&(t=q,true))) - && (t.x >= n.x || (q=t, ++t.x, CollidePiece(t)&&(t=q,true)))) - goto next_move; // no method of maneuvering. - - // Land the piece if it's not already landed - do ++n.y; while(!CollidePiece(n)); --n.y; - if(n.y < 0 || CollidePiece(n)) goto next_move; // cannot place piece? - - /* - // Rotate to ground-rotation - if(n.r != goal.rot2 || goal.x2 != 0) - { - while(n.r != goal.rot2) - { - ++n.r; - if(CollidePiece(n)) goto next_move; - } - if(goal.x2 != 0) - { - do n.x += goal.x2; while(!CollidePiece(n)); - n.x -= goal.x2; - } - - do ++n.y; while(!CollidePiece(n)); --n.y; - if(n.y < 0 || CollidePiece(n)) goto next_move; // cannot place piece? - } - */ - - DrawPiece(n, Occ); // place piece - - // Find out the extents of this piece, and how many - // cells of the piece contribute into full (completed) rows. - char full[4]={-1,-1,-1,-1}; - int miny=n.y+9, maxy=n.y-9, minx=n.x+9, maxx=n.x-9, num_eroded=0; - n>[&](int x,int y) -> bool - { if(x < minx) {minx = x;} if(x > maxx) {maxx = x;} - if(y < miny) {miny = y;} if(y > maxy) {maxy = y;} - if(full[y - n.y] < 0) full[y - n.y] = this->TestFully(y,true); - num_eroded += full[y - n.y]; - return false; }; - - CascadeEmpty(n.y); - - // Analyze the board and assign penalties - int penalties = 0; - for(int y=0; y 0) - { - int v = data[ply].best_score.first; - v /= 2; - //if(ply_limit == 4) v /= 2; else v *= 2; - data[ply-1].base_score.first += v;// >> (2-ply); - goto Unrecursion; - /* - parent += child / 2 : - Game 0x7ffff0d94ce0 over with score=91384,lines=92 - Game 0x7ffff0d96a40 over with score=153256,lines=114 - parent += child : - Game 0x7fff4a4eb8a0 over with score=83250,lines=86 - Game 0x7fff4a4ed600 over with score=295362,lines=166 - parent += child * 2 : - Game 0x7fff000a2e00 over with score=182306,lines=131 - Game 0x7fff000a10a0 over with score=383968,lines=193 - parent += child * 4 : - Game 0x7fff267867b0 over with score=62536,lines=75 - Game 0x7fff26788510 over with score=156352,lines=114 - */ - } - // all scanned; unless piece placement changes we're as good as we can be. - confident = true; - resting = true; - doubting = false; - while(resting) ccrReturn(0); - if(restart) goto Restart; - } // infinite refining loop - ccrFinish(0); - } + void AI_Signal(int signal); + int AI_Run(const decltype(Area)& in_area, const Piece* seq); }; - class Tetris: protected TetrisArea + class Tetris: protected TetrisArea /**/ { public: - Tetris(unsigned rx) : TetrisArea(rx), seq(),hiscore(0),hudtimer(0) {} - virtual ~Tetris() { } + Tetris(unsigned rx); + virtual ~Tetris(); protected: // These variables should be local to GameLoop(), @@ -493,361 +167,54 @@ namespace ADLMIDI_PuzzleGame public: unsigned incoming; - int GameLoop() - { - Piece &cur = *seq; - ccrBegin(loopscope); - - // Initialize area - for(auto y=Height; y-- > 0; ) - for(auto x=Width; x-- > 0; ) - DrawBlock(x,y, (x>0&&x> (rand()%10); - DrawRow(y, [&](unsigned x) { return - y < threshold - ? Area[y+incoming][x] - : (mask & (1< 0 && CollidePiece(cur); --cur.y) {} - incoming = 0; - goto re_collide; - } - - ticked = Timer >= timer + std::max(atground?40:10, int( ((17-Level())*8)) ); - if(ticked || MyKbHit() || dropping) break; - } - - Piece n = cur; - if(MyKbHit()) dropping = false; - - switch(ticked ? 's' : (MyKbHit() ? MyGetCh() : (dropping ? 's' : 0))) - { - case 'H'<<8: case 'w': ++n.r; /*Sound(120, 1);*/ break; - case 'P'<<8: case 's': ++n.y; break; - case 'K'<<8: case 'a': --n.x; /*Sound(120, 1);*/ break; - case 'M'<<8: case 'd': ++n.x; /*Sound(120, 1);*/ break; - case 'q': case '\033': /*escaped = true;*/ break; - case ' ': dropping = true; /*fallthrough*/ - default: continue; - } - if(n.x != cur.x) kicked = false; - - if(CollidePiece(n)) - { - if(n.y == cur.y+1) break; // fix piece if collide against ground - // If tried rotating, and was unsuccessful, try wall kicks - if(n.r != cur.r && !CollidePiece(n*[](Piece&p){++p.x;})) { kicked = true; ++n.x; } else - if(n.r != cur.r && !CollidePiece(n*[](Piece&p){--p.x;})) { kicked = true; --n.x; } else - continue; // no move - } - DrawPiece(cur, Empty); - if(n.y > cur.y) timer = Timer; // Reset autodrop timer - cur = n; - } - - // If the piece cannot be moved sideways or up from its final position, - // determine that it must have been spin-fixed into its place. - // It is a bonus-worthy accomplishment if it ends up clearing lines. - spinned = CollidePiece(cur*[](Piece&p){--p.y;}) - && CollidePiece(cur*[](Piece&p){--p.x;}) - && CollidePiece(cur*[](Piece&p){++p.x;}); - DrawPiece(cur, cur.color|Occ); - Sound(50, 30); - - while(CascadeEmpty(cur.y)) ccrReturn(0); - if(n_full > 1) ccrReturn(n_full-1); // Send these rows to opponent - - pieces += 1; - lines += n_full; - static const unsigned clr[] = {0,1,3,5,8, 13,21,34,45}; - int multiplier = Level(), clears = clr[n_full]; - int bonus = (clears * 100 + (cur.y*50/Height)); - int extra = 0; - if(spinned) extra = ((clears+1) * ((kicked ? 3 : 4)/2) - clears) * 100; - combo = n_full ? combo + n_full : 0; - int comboscore = combo > n_full ? combo*50*multiplier : 0; - bonus *= multiplier; - extra *= multiplier; - score += bonus + extra + comboscore; - HUD_Add(bonus, extra, comboscore); - //if(n_full) std::fprintf(stdout, "Game %p += %u lines -> score=%u,lines=%u\n", this, n_full, score,lines); - } - //std::fprintf(stdout, "Game %p over with score=%u,lines=%u\n", this, score,lines); - //over_lines += lines; - - if(score > hiscore) hiscore = score; - HudPrint(7, 4, "", "%-7u", hiscore); - - ccrFinish(-1); - } + int GameLoop(); protected: - int Level() const { return 1 + lines/10; } - - void MakeNext() - { - const int which = 2; // Index within seq[] to populate - static const Piece b[] = - { - { { { 0x04040404,0x00000F00,0x04040404,0x00000F00 } }, 0,0, 0xBDB,0 }, // I - { { { 0x0000080E,0x000C0808,0x00000E02,0x00020206 } }, 0,0, 0x3DB,0 }, // J - { { { 0x0000020E,0x0008080C,0x00000E08,0x00060202 } }, 0,0, 0x6DB,0 }, // L - { { { 0x00000606,0x00000606,0x00000606,0x00000606 } }, 0,0, 0xEDB,0 }, // O - { { { 0x00080C04,0x0000060C,0x00080C04,0x0000060C } }, 0,0, 0xADB,0 }, // S - { { { 0x00000E04,0x00040C04,0x00040E00,0x00040604 } }, 0,0, 0x5DB,0 }, // T - { { { 0x00020604,0x00000C06,0x00020604,0x00000C06 } }, 0,0, 0x4DB,0 }, // Z - // Add some pentaminos to create a challenge worth the AI: - { { { 0x00020702,0x00020702,0x00020702,0x00020702 } }, 0,0, 0x2DB,0 }, // + - { { { 0x000E0404,0x00020E02,0x0004040E,0x00080E08 } }, 0,0, 0x9DB,0 }, // T5 - { { { 0x00000A0E,0x000C080C,0x00000E0A,0x00060206 } }, 0,0, 0x3DB,0 }, // C - { { { 0x00060604,0x000E0600,0x02060600,0x00060700 } }, 0,0, 0x7DB,0 }, // P - { { { 0x00060602,0x00070600,0x04060600,0x00060E00 } }, 0,0, 0xFDB,0 }, // Q - { { { 0x04040404,0x00000F00,0x04040404,0x00000F00 } }, 0,0, 0xBDB,0 }, // I - { { { 0x00040702,0x00030602,0x00020701,0x00020306 } }, 0,0, 0xDDB,0 }, // R - { { { 0x00010702,0x00020603,0x00020704,0x00060302 } }, 0,0, 0x8DB,0 }, // F - // I is included twice, otherwise it's just a bit too unlikely. - }; - int c = Empty; - auto fx = [this]() { seq[0].x=1; seq[0].y=-1; - seq[1].x=Width+1; seq[1].y=SHeight-8; - seq[2].x=Width+5; seq[2].y=SHeight-8; - /*seq[3].x=Width+9; seq[3].y=SHeight-8; - seq[4].x=Width+1; seq[4].y=SHeight-4; - seq[5].x=Width+5; seq[5].y=SHeight-4; - seq[6].x=Width+9; seq[6].y=SHeight-4;*/ - }; - auto db = [&](int x,int y) - { - PutCell(x+RenderX, y, c); - return false; - }; - fx(); - seq[1]>db; seq[0] = seq[1]; - seq[2]>db; seq[1] = seq[2]; - /*seq[3]>db; seq[2] = seq[3]; - seq[4]>db; seq[3] = seq[4]; - seq[5]>db; seq[4] = seq[5]; - seq[6]>db; seq[5] = seq[6];*/ - fx(); - const unsigned npieces = sizeof(b) / sizeof(*b); - unsigned rnd = (unsigned)((std::rand()/double(RAND_MAX)) * (4 * npieces)); - #ifndef EASY - /*if(pieces > 3) - { - int heap_room = 0; - while(TestFully(heap_room,false)) ++heap_room; - bool cheat_good = heap_room <= (7 + lines/20); - bool cheat_evil = heap_room >= 18; - if(heap_room >= 16 && (pieces % 5) == 0) cheat_good = false; - if( (pieces % 11) == 0) cheat_good = true; - if(cheat_good) cheat_evil = false; - if(cheat_good || cheat_evil) - { - // Don't tell anyone, but in desperate situations, - // we let AI judge what's best for upcoming pieces, - // in order to prolong the game! - // So, the AI cheats, but it is an equal benefactor. - // EXCEPTION: if there is an abundance of space, - // bring out the _worst_ possible pieces! - TetrisAIengine<>::scoring best_score; - unsigned best_choice = ~0u; - for(unsigned test=0; test chooser; - chooser.AI_Signal(0); - chooser.AI_Run(Area, seq); - auto& s = chooser.data[0].best_score; - if(best_choice == ~0u - || (cheat_evil ? s < best_score : s > best_score) - ) - { best_score = s; best_choice = choice; } - } - rnd = best_choice * 4 + (rnd % 4); - } - }*/ - #endif - seq[which] = b[rnd / 4]; - seq[which].r = rnd % 4; - fx(); - c=seq[1].color; seq[1]>db; - c=(seq[2].color&0xF00) + 176; seq[2]>db; - /*c=(seq[3].color&0xF00) + 176; seq[3]>db; - c=(seq[4].color&0xF00) + 176; seq[4]>db; - c=(seq[5].color&0xF00) + 176; seq[5]>db; - c=(seq[6].color&0xF00) + 176; seq[6]>db;*/ - HudPrint(0, SHeight-9, "NEXT:","",0); - HudPrint(12, 0, "SCORE:", "%-7u", score); - HudPrint(12, 2, "LINES:", "%-5u", lines); - HudPrint(12, 4, "LEVEL:", "%-3u", Level()); - } + void HudPrint(int c, int y,const char*a,const char*b,int v=0) const; - void HudPrint(int c, int y,const char*a,const char*b,int v=0) const - { - char Buf[64]; - snprintf(Buf, sizeof Buf, b, v); - ScreenPutString(a, 15, RenderX+Width+2, y); - ScreenPutString(Buf, c, RenderX+Width+2, y+1); - } + void HUD_Run(); + void HUD_Add(int bonus, int extra, int combo); - void HUD_Run() - { - if(!hudtimer || Timer < hudtimer) return; - HUD_Add(0,0,0); - hudtimer = 0; - } - void HUD_Add(int bonus, int extra, int combo) - { - hudtimer = Timer + 180; - static const char blank[] = " "; - HudPrint(10, 6,bonus?blank :blank,bonus?"%+-6d":blank, bonus); - HudPrint(10, 8,combo?"Combo":blank,combo?"%+-6d":blank, combo); - HudPrint(13, 10,extra?"Skill":blank,extra?"%+-6d":blank, extra); - } - - virtual void AI_Signal(int) { } - virtual int AI_Run() { return 0; } + virtual void AI_Signal(int); + virtual int AI_Run(); virtual int MyKbHit() = 0; virtual int MyGetCh() = 0; public: // Return -1 = don't want sleep, +1 = want sleep, 0 = don't mind - virtual char DelayOpinion() const { return 0; }//dropping ? -1 : 0; } + virtual char DelayOpinion() const;//dropping ? -1 : 0; } }; class TetrisHuman: public Tetris { public: - TetrisHuman(unsigned rx) : Tetris(rx) { } + TetrisHuman(unsigned rx); protected: - virtual int MyKbHit() { return kbhit(); } - virtual int MyGetCh() { int c; return (c = getch()) ? c : (getch() << 8); } + virtual int MyKbHit(); + virtual int MyGetCh(); }; - class TetrisAI: public Tetris, TetrisAIengine + class TetrisAI: public Tetris, TetrisAIengine /**/ { protected: - virtual void AI_Signal(int s) { TetrisAIengine::AI_Signal(s); } - virtual int AI_Run() - { - #ifdef __DJGPP__ - char buf1[16]/*, buf2[16], buf3[16]*/; - sprintf(buf1, "COM%u%c%c%c%c", ply_limit, - resting ? 'r' : '_', - confident ? 'C' : '_', - doubting ? 'D' : '_', - atground ? 'G' : '_' - ); - ScreenPutString(buf1, 0x70, Tetris::RenderX+Width, 0); - /* - sprintf(buf2, "%-8d", data[0].best_score.first); - sprintf(buf3, "%2d,%d", data[0].best_pos.x, - data[0].best_pos.rot); - ScreenPutString(buf2, 0x08, Tetris::RenderX+Width, 1); - ScreenPutString(buf3, 0x08, Tetris::RenderX+Width, 4); - */ - #endif - return TetrisAIengine::AI_Run(Tetris::Area, seq); - } - virtual int MyKbHit() - { - return PendingAIinput ? 1 : (PendingAIinput = GenerateAIinput()) != 0; - } - virtual int MyGetCh() - { - int r = PendingAIinput; - return r ? static_cast((PendingAIinput=0)), r : GenerateAIinput(); - } - char d(char c, int maxdelay) - { - if(Timer >= intimer+maxdelay) { intimer=Timer; return c; } - return 0; - } - int GenerateAIinput() - { - /*if(TetrisAIengine::atground) - { - if(seq->r != data[0].best_pos.rot2) return d('w',1); - if(data[0].best_pos.x2 < 0) return d('a', 1); - if(data[0].best_pos.x2 > 0) return d('d', 1); - } - else*/ - { - if(seq->r != data[0].best_pos.rot) return d('w',1); - if(seq->y >= 0 && seq->x > data[0].best_pos.x) return d('a', 2); - if(seq->y >= 0 && seq->x < data[0].best_pos.x) return d('d', 2); - } - if(doubting || !confident) return 0; - return d('s',3); - } + virtual void AI_Signal(int s); + virtual int AI_Run(); + virtual int MyKbHit(); + virtual int MyGetCh(); + char d(char c, int maxdelay); + int GenerateAIinput(); int PendingAIinput, delay; unsigned long intimer; public: - virtual char DelayOpinion() const - { if(doubting || restart || !confident) return -1; - return Tetris::DelayOpinion(); } + virtual char DelayOpinion() const; - TetrisAI(unsigned rx) : Tetris(rx), PendingAIinput(0), delay(0), intimer(0) {} + TetrisAI(unsigned rx); + ~TetrisAI(); }; - #undef Timer } + +#endif //TETRIS_PUZZLEGAME -- cgit v1.2.3