diff options
Diffstat (limited to 'utils')
-rw-r--r-- | utils/adlmidi-2/adlmidi-2.pro | 4 | ||||
-rw-r--r-- | utils/adlmidi-2/input.cc | 56 | ||||
-rw-r--r-- | utils/adlmidi-2/input.hpp | 46 | ||||
-rw-r--r-- | utils/adlmidi-2/midiplay.cc | 131 | ||||
-rw-r--r-- | utils/adlmidi-2/puzzlegame.cc | 842 | ||||
-rwxr-xr-x | utils/adlmidi-2/puzzlegame.hpp | 745 | ||||
-rw-r--r-- | utils/midiplay/adlmidiplay.cpp | 2 |
7 files changed, 1029 insertions, 797 deletions
diff --git a/utils/adlmidi-2/adlmidi-2.pro b/utils/adlmidi-2/adlmidi-2.pro index f4214c1..8808504 100644 --- a/utils/adlmidi-2/adlmidi-2.pro +++ b/utils/adlmidi-2/adlmidi-2.pro @@ -57,6 +57,6 @@ SOURCES += \ $$PWD/../../src/nukedopl3.c \ $$PWD/../../src/dbopl.cpp \ \ - midiplay.cc - + midiplay.cc \ + puzzlegame.cc diff --git a/utils/adlmidi-2/input.cc b/utils/adlmidi-2/input.cc new file mode 100644 index 0000000..9d69469 --- /dev/null +++ b/utils/adlmidi-2/input.cc @@ -0,0 +1,56 @@ +#include "input.hpp" + +xInput::xInput() +{ +#ifdef _WIN32 + inhandle = GetStdHandle(STD_INPUT_HANDLE); +#endif +#if (!defined(_WIN32) || defined(__CYGWIN__)) && !defined(__DJGPP__) + ioctl(0, TCGETA, &back); + struct termio term = back; + term.c_lflag &= ~(ICANON | ECHO); + term.c_cc[VMIN] = 0; // 0=no block, 1=do block + if(ioctl(0, TCSETA, &term) < 0) + fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK); +#endif +} + +xInput::~xInput() +{ +#if (!defined(_WIN32) || defined(__CYGWIN__)) && !defined(__DJGPP__) + if(ioctl(0, TCSETA, &back) < 0) + fcntl(0, F_SETFL, fcntl(0, F_GETFL) & ~ O_NONBLOCK); +#endif +} + +char xInput::PeekInput() +{ +#ifdef __DJGPP__ + if(kbhit()) + { + int c = getch(); + return c ? c : getch(); + } +#endif +#ifdef _WIN32 + DWORD nread = 0; + INPUT_RECORD inbuf[1]; + while(PeekConsoleInput(inhandle, inbuf, sizeof(inbuf) / sizeof(*inbuf), &nread) && nread) + { + ReadConsoleInput(inhandle, inbuf, sizeof(inbuf) / sizeof(*inbuf), &nread); + if(inbuf[0].EventType == KEY_EVENT + && inbuf[0].Event.KeyEvent.bKeyDown) + { + char c = inbuf[0].Event.KeyEvent.uChar.AsciiChar; + unsigned s = inbuf[0].Event.KeyEvent.wVirtualScanCode; + if(c == 0) c = s; + return c; + } + } +#endif +#if (!defined(_WIN32) || defined(__CYGWIN__)) && !defined(__DJGPP__) + char c = 0; + if(read(0, &c, 1) == 1) return c; +#endif + return '\0'; +} diff --git a/utils/adlmidi-2/input.hpp b/utils/adlmidi-2/input.hpp new file mode 100644 index 0000000..dba4229 --- /dev/null +++ b/utils/adlmidi-2/input.hpp @@ -0,0 +1,46 @@ +#ifndef _WIN32 +#include <unistd.h> +#endif + +#ifdef _WIN32 +# include <cctype> +# define WIN32_LEAN_AND_MEAN +# ifndef NOMINMAX +# define NOMINMAX //To don't damage std::min and std::max +# endif +# include <windows.h> +#endif + +#ifdef __DJGPP__ +#include <conio.h> +#include <pc.h> +#include <dpmi.h> +#include <go32.h> +#include <sys/farptr.h> +#include <dos.h> +#include <stdlib.h> +#define BIOStimer _farpeekl(_dos_ds, 0x46C) +static const unsigned NewTimerFreq = 209; +#elif !defined(_WIN32) || defined(__CYGWIN__) +# include <termios.h> +# include <fcntl.h> +# include <sys/ioctl.h> +# include <csignal> +#endif + +class xInput +{ + #ifdef _WIN32 + void *inhandle; + #endif + #if (!defined(_WIN32) || defined(__CYGWIN__)) && !defined(__DJGPP__) + struct termio back; + #endif +public: + xInput(); + ~xInput(); + + char PeekInput(); +}; + +extern xInput Input; diff --git a/utils/adlmidi-2/midiplay.cc b/utils/adlmidi-2/midiplay.cc index bd0d586..f3b4cde 100644 --- a/utils/adlmidi-2/midiplay.cc +++ b/utils/adlmidi-2/midiplay.cc @@ -22,10 +22,12 @@ #include <assert.h> -#ifndef _WIN32 -#define SUPPORT_VIDEO_OUTPUT -#define SUPPORT_PUZZLE_GAME -#endif +#include "input.hpp" + +//#ifndef _WIN32 +//#define SUPPORT_VIDEO_OUTPUT// MOVED TO CMake build script +//#define SUPPORT_PUZZLE_GAME// MOVED TO CMake build script +//#endif #if !defined(_WIN32) || !defined(_MSC_VER) #define ATTRIBUTE_FORMAT_PRINTF(x, y) __attribute__((format(printf, x, y))) @@ -71,22 +73,7 @@ public: }; #endif -#ifdef __DJGPP__ -#include <conio.h> -#include <pc.h> -#include <dpmi.h> -#include <go32.h> -#include <sys/farptr.h> -#include <dos.h> -#include <stdlib.h> -#define BIOStimer _farpeekl(_dos_ds, 0x46C) -static const unsigned NewTimerFreq = 209; -#elif !defined(_WIN32) || defined(__CYGWIN__) -# include <termios.h> -# include <fcntl.h> -# include <sys/ioctl.h> -# include <csignal> -#endif + #include <deque> #include <algorithm> @@ -151,69 +138,9 @@ static void GuessInitialWindowHeight() SigWinchHandler(0); } -static class Input -{ - #ifdef _WIN32 - void *inhandle; - #endif - #if (!defined(_WIN32) || defined(__CYGWIN__)) && !defined(__DJGPP__) - struct termio back; - #endif -public: - Input() - { - #ifdef _WIN32 - inhandle = GetStdHandle(STD_INPUT_HANDLE); - #endif - #if (!defined(_WIN32) || defined(__CYGWIN__)) && !defined(__DJGPP__) - ioctl(0, TCGETA, &back); - struct termio term = back; - term.c_lflag &= ~(ICANON | ECHO); - term.c_cc[VMIN] = 0; // 0=no block, 1=do block - if(ioctl(0, TCSETA, &term) < 0) - fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK); - #endif - } - ~Input() - { - #if (!defined(_WIN32) || defined(__CYGWIN__)) && !defined(__DJGPP__) - if(ioctl(0, TCSETA, &back) < 0) - fcntl(0, F_SETFL, fcntl(0, F_GETFL) & ~ O_NONBLOCK); - #endif - } - char PeekInput() - { - #ifdef __DJGPP__ - if(kbhit()) - { - int c = getch(); - return c ? c : getch(); - } - #endif - #ifdef _WIN32 - DWORD nread = 0; - INPUT_RECORD inbuf[1]; - while(PeekConsoleInput(inhandle, inbuf, sizeof(inbuf) / sizeof(*inbuf), &nread) && nread) - { - ReadConsoleInput(inhandle, inbuf, sizeof(inbuf) / sizeof(*inbuf), &nread); - if(inbuf[0].EventType == KEY_EVENT - && inbuf[0].Event.KeyEvent.bKeyDown) - { - char c = inbuf[0].Event.KeyEvent.uChar.AsciiChar; - unsigned s = inbuf[0].Event.KeyEvent.wVirtualScanCode; - if(c == 0) c = s; - return c; - } - } - #endif - #if (!defined(_WIN32) || defined(__CYGWIN__)) && !defined(__DJGPP__) - char c = 0; - if(read(0, &c, 1) == 1) return c; - #endif - return '\0'; - } -} Input; + +xInput Input; #ifdef SUPPORT_PUZZLE_GAME #include "puzzlegame.hpp" @@ -727,9 +654,6 @@ public: bool DoCheckTetris() { #ifdef SUPPORT_PUZZLE_GAME - static ADLMIDI_PuzzleGame::TetrisAI player(2); - static ADLMIDI_PuzzleGame::TetrisAI computer(31); - int a = player.GameLoop(); int b = computer.GameLoop(); @@ -754,26 +678,23 @@ public: } UI; #ifdef SUPPORT_PUZZLE_GAME -namespace ADLMIDI_PuzzleGame +void ADLMIDI_PuzzleGame::PutCell(int x, int y, unsigned cell) { - static void PutCell(int x, int y, unsigned cell) - { - static const unsigned char valid_attrs[] = {8, 6, 5, 3}; - unsigned char ch = (unsigned char)cell, attr = (unsigned char)(cell >> 8); - int height = (int)WinHeight();//std::min(NumCards*18, 50u); - y = std::max(0, int(std::min(height, 40) - 25 + y)); - if(ch == 0xDB) ch = '#'; - if(ch == 0xB0) ch = '*'; - if(attr != 1) attr = valid_attrs[attr % sizeof(valid_attrs)]; - - //bool diff = UI.background[x][y] != UI.slots[x][y]; - UI.backgroundcolor[x][y] = attr; - UI.background[x][y] = (char)ch; - UI.GotoXY(x, y); - UI.Color(attr); - UI.PutC((char)ch); - //UI.Draw(x,y, attr, ch); - } + static const unsigned char valid_attrs[] = {8, 6, 5, 3}; + unsigned char ch = (unsigned char)cell, attr = (unsigned char)(cell >> 8); + int height = (int)WinHeight();//std::min(NumCards*18, 50u); + y = std::max(0, int(std::min(height, 40) - 25 + y)); + if(ch == 0xDB) ch = '#'; + if(ch == 0xB0) ch = '*'; + if(attr != 1) attr = valid_attrs[attr % sizeof(valid_attrs)]; + + //bool diff = UI.background[x][y] != UI.slots[x][y]; + UI.backgroundcolor[x][y] = attr; + UI.background[x][y] = (char)ch; + UI.GotoXY(x, y); + UI.Color(attr); + UI.PutC((char)ch); + //UI.Draw(x,y, attr, ch); } #endif @@ -1577,7 +1498,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) } #endif -static void adlEventHook(void *ui, ADL_UInt8 type, ADL_UInt8 subtype, ADL_UInt8 /*channel*/, ADL_UInt8 *data, size_t len) +static void adlEventHook(void *ui, ADL_UInt8 type, ADL_UInt8 subtype, ADL_UInt8 /*channel*/, const ADL_UInt8 *data, size_t len) { UserInterface *mUI = (UserInterface *)ui; diff --git a/utils/adlmidi-2/puzzlegame.cc b/utils/adlmidi-2/puzzlegame.cc new file mode 100644 index 0000000..64c032a --- /dev/null +++ b/utils/adlmidi-2/puzzlegame.cc @@ -0,0 +1,842 @@ + +#define Timer TimerRead() + +#include "input.hpp" +#include "puzzlegame.hpp" + +unsigned long ADLMIDI_PuzzleGame::TimerRead() +{ + static std::chrono::time_point<std::chrono::system_clock> begin = std::chrono::system_clock::now(); + return (unsigned long)(519 * std::chrono::duration<double>(std::chrono::system_clock::now() - begin).count()); +} + +void ADLMIDI_PuzzleGame::Sound(unsigned, unsigned) +{ +} + +void ADLMIDI_PuzzleGame::ScreenPutString(const char *str, unsigned attr, unsigned column, unsigned row) +{ + for(; *str; ++column, ++str) + PutCell(column, row, ((unsigned char)(*str & 0xFF)) | (attr << 8)); +} + +bool ADLMIDI_PuzzleGame::kbhit() +{ + if(peeked_input) return true; + peeked_input = Input.PeekInput(); + return peeked_input != 0; +} + +char ADLMIDI_PuzzleGame::getch() +{ + char r = peeked_input; + peeked_input = 0; + return r; +} + +ADLMIDI_PuzzleGame::TetrisArea::TetrisArea(bool doDraw, unsigned x) : DoDraw(doDraw), RenderX(x) { } + +ADLMIDI_PuzzleGame::TetrisArea::~TetrisArea() +{} + +bool ADLMIDI_PuzzleGame::TetrisArea::Occupied(int x, int y) const +{ + return x < 1 || (x > Width - 2) || (y >= 0 && (Area[y][x] & Occ)); +} + +void ADLMIDI_PuzzleGame::TetrisArea::DrawBlock(unsigned x, unsigned y, int color) +{ + if(x < (unsigned)Width && y < (unsigned)Height) Area[y][x] = color; + if(DoDraw) PutCell(x + RenderX, y, color); +} + +void ADLMIDI_PuzzleGame::TetrisArea::DrawPiece(const ADLMIDI_PuzzleGame::Piece &piece, int color) +{ + piece > [&](int x, int y)->bool { this->DrawBlock(x, y, color); return false; }; +} + +bool ADLMIDI_PuzzleGame::TetrisArea::CollidePiece(const ADLMIDI_PuzzleGame::Piece &piece) const +{ + return piece > [&](int x, int y) + { + return this->Occupied(x, y); + }; +} + +bool ADLMIDI_PuzzleGame::TetrisArea::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 < timer + 6;) ccrReturn(true); + auto label = + " SINGLE " + " DOUBLE " + " TRIPLE " + " TETRIS " + "QUADRUPLE " + "QUINTUPLE " + " SEXTUPLE " + " SEPTUPLE " + " OCTUPLE " + (n_full - 1) * 10; + for(int y = FirstY; y < Height - 1; ++y) + if(list_full & (1u << y)) + DrawBlock(animx, y, label[(animx % 10)] + 0x100); + if(DoDraw) Sound(10 + animx * n_full * 40, 2); + } + if(DoDraw) Sound(50, 15); + // Cascade non-empty lines + int target = Height - 2, y = Height - 2; + for(; y >= 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; + } +} + + +ADLMIDI_PuzzleGame::TetrisAIengine::TetrisAIengine(bool doReturns) : + TetrisArea(false), + DoReturns(doReturns) +{} + +ADLMIDI_PuzzleGame::TetrisAIengine::~TetrisAIengine() +{} + +void ADLMIDI_PuzzleGame::TetrisAIengine::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 ADLMIDI_PuzzleGame::TetrisAIengine::AI_Run(const decltype(Area)&in_area, const ADLMIDI_PuzzleGame::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 < Width; ++x) + for(unsigned rot = 0; rot < 4; ++rot) + for(unsigned rot2 = 0; rot2 < 4; ++rot2) + for(int x2 = -1; x2 <= 1; ++x2) + { + positions1.push_back(std::move<position> ({int(rot), x, int(rot2), x2})); + if(rot2 == rot && x2 == 0) + positions2.push_back(std::move<position> ({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 < Height - 1; ++y) + for(int q = 1, r, x = 1; x < Width; ++x, q = r) + if(q != (r = Occupied(x, y))) + penalties += rowTransitionPenalty; + for(int x = 1; x < Width - 1; ++x) + for(int ceil = 0/*,heap=0*/, q = 0, r, y = 0; y < Height; ++y, q = r) + { + if(q != (r = Occupied(x, y))) + { + penalties += columnTransitionPenalty; + /*if(!r) { penalties += heap * occlusionPenalty; heap = 0; }*/ + } + if(r) + { + ceil = 1; /*++heap;*/ continue; + } + if(ceil) penalties += buriedHolePenalty; + if(Occupied(x - 1, y) && Occupied(x + 1, y)) + for(int y2 = y; y2 < Height - 1 && !Occupied(x, y2); ++y2) + penalties += wellPenalty; + } + + data[ply].base_score = + { + // score + (erodedPieceCellMetricFactor * int(n_full) * num_eroded + + penalties + + landingHeightPenalty * ((Height - 1) * 2 - (miny + maxy))) * 16, + // tie-breaker + 50 * std::abs(Width - 2 - minx - maxx) + + (minx + maxx < Width - 2 ? 10 : 0) - n.r + - 400 * (goal.rot != goal.rot2) + - 800 * (goal.x2 != 0) + }; + } + if(ply + 1 < ply_limit) + { + ++ply; + goto Recursion; +Unrecursion: + --ply; + } + + /*fprintf(stdout, "ply %u: [%u]%u,%u gets %d,%d\n", + ply, + data[ply].pos_no, + positions[data[ply].pos_no].x, + positions[data[ply].pos_no].rot, + data[ply].base_score.first, + data[ply].base_score.second);*/ + + if(data[ply].best_score < data[ply].base_score) + { + data[ply].best_score = data[ply].base_score; + data[ply].best_pos = (/*ply==0&&!doubting ? positions1 :*/ positions2) [ data[ply].pos_no ]; + } +next_move: + ; + } + while(++data[ply].pos_no < (/*ply==0&&!doubting ? positions1 :*/ positions2).size()); + + if(ply > 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); +} + +ADLMIDI_PuzzleGame::Tetris::Tetris(unsigned rx) : TetrisArea(true, rx), seq(), hiscore(0), hudtimer(0) {} + +ADLMIDI_PuzzleGame::Tetris::~Tetris() { } + +int ADLMIDI_PuzzleGame::Tetris::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 < Width - 1 && y < Height - 1) ? Empty : Border); + + score = lines = combo = incoming = pieces = 0; + MakeNext(); + MakeNext(); + first = true; + + for(escaped = false; !escaped;) // Main loop + { + // Generate new piece + MakeNext(); + /*if(first) // Use if making 4 pieces + { + first=false; + ccrReturn(0); + MakeNext(); + }*/ + + dropping = false; + atground = false; +re_collide: + timer = Timer; + AI_Signal(0); // signal changed board configuration + + // Gameover if cannot spawn piece + if(CollidePiece(cur)) + break; + + ccrReturn(0); + + while(!escaped) + { + atground = CollidePiece(cur * [](Piece & p) + { + ++p.y; + }); + if(atground) dropping = false; + // If we're about to hit the floor, give the AI a chance for sliding. + AI_Signal(atground ? 2 : 1); + + DrawPiece(cur, cur.color); + + // Wait for input + for(ticked = false; ;) + { + AI_Run(); + HUD_Run(); + ccrReturn(0); + + if(incoming) + { + // Receive some lines of garbage from opponent + DrawPiece(cur, Empty); + for(int threshold = Height - 1 - incoming, y = 0; y < Height - 1; ++y) + { + unsigned mask = 0x1EF7BDEF >> (rand() % 10); + DrawRow(y, [&](unsigned x) + { + return + y < threshold + ? Area[y + incoming][x] + : (mask & (1 << x)) ? Garbage : Empty; + }); + } + // The new rows may push the piece up a bit. Allow that. + for(; incoming-- > 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 ADLMIDI_PuzzleGame::Tetris::Level() const +{ + return 1 + lines / 10; +} + +void ADLMIDI_PuzzleGame::Tetris::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<npieces; ++test) + { + unsigned choice = (test + rnd) % npieces; + if(choice == 0) continue; // Ignore the duplicate I (saves time) + if(cheat_evil && choice == 7) continue; // Don't give +, it's too evil + + seq[which] = b[ choice ]; + seq[which].r = 0; + + TetrisAIengine<false> 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 ADLMIDI_PuzzleGame::Tetris::HudPrint(int c, int y, const char *a, const char *b, int v) 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 ADLMIDI_PuzzleGame::Tetris::HUD_Run() +{ + if(!hudtimer || Timer < hudtimer) return; + HUD_Add(0, 0, 0); + hudtimer = 0; +} + +void ADLMIDI_PuzzleGame::Tetris::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); +} + +void ADLMIDI_PuzzleGame::Tetris::AI_Signal(int) { } + +int ADLMIDI_PuzzleGame::Tetris::AI_Run() +{ + return 0; +} + +char ADLMIDI_PuzzleGame::Tetris::DelayOpinion() const +{ + return 0; +} + + + + +ADLMIDI_PuzzleGame::TetrisHuman::TetrisHuman(unsigned rx) : Tetris(rx) { } + +int ADLMIDI_PuzzleGame::TetrisHuman::MyKbHit() +{ + return kbhit(); +} + +int ADLMIDI_PuzzleGame::TetrisHuman::MyGetCh() +{ + int c; + return (c = getch()) ? c : (getch() << 8); +} + + + +void ADLMIDI_PuzzleGame::TetrisAI::AI_Signal(int s) +{ + TetrisAIengine::AI_Signal(s); +} + +int ADLMIDI_PuzzleGame::TetrisAI::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); +} + +int ADLMIDI_PuzzleGame::TetrisAI::MyKbHit() +{ + return PendingAIinput ? 1 : (PendingAIinput = GenerateAIinput()) != 0; +} + +int ADLMIDI_PuzzleGame::TetrisAI::MyGetCh() +{ + int r = PendingAIinput; + return r ? static_cast<void>((PendingAIinput = 0)), r : GenerateAIinput(); +} + +char ADLMIDI_PuzzleGame::TetrisAI::d(char c, int maxdelay) +{ + if(Timer >= intimer+maxdelay) { intimer=Timer; return c; } + return 0; +} + +int ADLMIDI_PuzzleGame::TetrisAI::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); +} + +char ADLMIDI_PuzzleGame::TetrisAI::DelayOpinion() const +{ if(doubting || restart || !confident) return -1; + return Tetris::DelayOpinion(); } + +ADLMIDI_PuzzleGame::TetrisAI::TetrisAI(unsigned rx) : Tetris(rx), TetrisAIengine(true), PendingAIinput(0), delay(0), intimer(0) {} + +ADLMIDI_PuzzleGame::TetrisAI::~TetrisAI() +{} + 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 <cstdio> // for std::puts #include <cstdlib> // 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<std::chrono::system_clock> begin = std::chrono::system_clock::now(); - return (unsigned long)(519 * std::chrono::duration<double>( 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<bool DoDraw> + //template<bool DoDraw> 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<typename T> inline void DrawRow(unsigned y, T get) { for(int x=1; x<Width-1; ++x) DrawBlock(x,y, get(x)); } - inline bool TestFully(unsigned y, bool state) const { for(int x=1; x<Width-1; ++x) if(state != !!(Area[y][x]&Occ)) return false; return true; } - - void DrawBlock(unsigned x,unsigned y, int color) - { - if(x < (unsigned)Width && y < (unsigned)Height) Area[y][x] = color; - if(DoDraw) PutCell(x+RenderX, y, color); - } - - void DrawPiece(const Piece& piece, int color) - { - piece>[&](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<timer+6; ) ccrReturn(true); - auto label = - " SINGLE " - " DOUBLE " - " TRIPLE " - " TETRIS " - "QUADRUPLE " - "QUINTUPLE " - " SEXTUPLE " - " SEPTUPLE " - " OCTUPLE "+(n_full-1)*10; - for(int y = FirstY; y < Height-1; ++y) - if(list_full & (1u << y)) - DrawBlock(animx,y, label[(animx%10)] + 0x100); - if(DoDraw) Sound(10 + animx*n_full*40, 2); - } - if(DoDraw) Sound(50, 15); - // Cascade non-empty lines - int target = Height-2, y = Height-2; - for(; y >= 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<bool DoReturns = true> - class TetrisAIengine: TetrisArea<false> + //template<bool DoReturns = true> + class TetrisAIengine: TetrisArea /*<false>*/ { + bool DoReturns; public://protected: + TetrisAIengine(bool doReturns = true); + virtual ~TetrisAIengine(); + typedef std::pair<int/*score*/, int/*prio*/> 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<Width; ++x) - for(unsigned rot=0; rot<4; ++rot) - for(unsigned rot2=0; rot2<4; ++rot2) - for(int x2=-1; x2<=1; ++x2) - { - positions1.push_back( std::move<position> ( {int(rot),x,int(rot2),x2} ) ); - if(rot2 == rot && x2 == 0) - positions2.push_back( std::move<position> ( {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<Height-1; ++y) - for(int q=1,r, x=1; x<Width; ++x, q=r) - if(q != (r = Occupied(x,y))) - penalties += rowTransitionPenalty; - for(int x=1; x<Width-1; ++x) - for(int ceil=0/*,heap=0*/,q=0,r, y=0; y<Height; ++y,q=r) - { - if(q != (r = Occupied(x,y))) - { - penalties += columnTransitionPenalty; - /*if(!r) { penalties += heap * occlusionPenalty; heap = 0; }*/ - } - if(r) { ceil=1; /*++heap;*/ continue; } - if(ceil) penalties += buriedHolePenalty; - if(Occupied(x-1,y) && Occupied(x+1,y)) - for(int y2=y; y2<Height-1 && !Occupied(x,y2); ++y2) - penalties += wellPenalty; - } - - data[ply].base_score = { - // score - (erodedPieceCellMetricFactor * int(n_full) * num_eroded + - penalties + - landingHeightPenalty * ((Height-1) * 2 - (miny+maxy))) * 16, - // tie-breaker - 50 * std::abs(Width-2-minx-maxx) + - (minx+maxx < Width-2 ? 10 : 0) - n.r - - 400*(goal.rot != goal.rot2) - - 800*(goal.x2 != 0) - }; - } - if(ply+1 < ply_limit) - { ++ply; goto Recursion; - Unrecursion: --ply; } - - /*fprintf(stdout, "ply %u: [%u]%u,%u gets %d,%d\n", - ply, - data[ply].pos_no, - positions[data[ply].pos_no].x, - positions[data[ply].pos_no].rot, - data[ply].base_score.first, - data[ply].base_score.second);*/ - - if(data[ply].best_score < data[ply].base_score) - { - data[ply].best_score = data[ply].base_score; - data[ply].best_pos = (/*ply==0&&!doubting ? positions1 :*/ positions2) [ data[ply].pos_no ]; - } - next_move:; - } while(++data[ply].pos_no < (/*ply==0&&!doubting ? positions1 :*/ positions2).size()); - - if(ply > 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<true> + class Tetris: protected TetrisArea /*<true>*/ { 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<Width-1&&y<Height-1) ? Empty : Border); - - score = lines = combo = incoming = pieces = 0; - MakeNext(); - MakeNext(); - first=true; - - for(escaped = false; !escaped; ) // Main loop - { - // Generate new piece - MakeNext(); - /*if(first) // Use if making 4 pieces - { - first=false; - ccrReturn(0); - MakeNext(); - }*/ - - dropping = false; - atground = false; - re_collide: - timer = Timer; - AI_Signal(0); // signal changed board configuration - - // Gameover if cannot spawn piece - if(CollidePiece(cur)) - { - break; - } - - ccrReturn(0); - - while(!escaped) - { - atground = CollidePiece(cur * [](Piece&p){++p.y;}); - if(atground) dropping = false; - // If we're about to hit the floor, give the AI a chance for sliding. - AI_Signal(atground ? 2 : 1); - - DrawPiece(cur, cur.color); - - // Wait for input - for(ticked=false; ; ) - { - AI_Run(); - HUD_Run(); - ccrReturn(0); - - if(incoming) - { - // Receive some lines of garbage from opponent - DrawPiece(cur, Empty); - for(int threshold=Height-1-incoming, y=0; y<Height-1; ++y) - { - unsigned mask = 0x1EF7BDEF >> (rand()%10); - DrawRow(y, [&](unsigned x) { return - y < threshold - ? Area[y+incoming][x] - : (mask & (1<<x)) ? Garbage : Empty; }); - } - // The new rows may push the piece up a bit. Allow that. - for(; incoming-- > 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<npieces; ++test) - { - unsigned choice = (test + rnd) % npieces; - if(choice == 0) continue; // Ignore the duplicate I (saves time) - if(cheat_evil && choice == 7) continue; // Don't give +, it's too evil + int Level() const; - seq[which] = b[ choice ]; - seq[which].r = 0; + void MakeNext(); - TetrisAIengine<false> 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<true> + class TetrisAI: public Tetris, TetrisAIengine /*<true>*/ { 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<void>((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 diff --git a/utils/midiplay/adlmidiplay.cpp b/utils/midiplay/adlmidiplay.cpp index 5ab9d68..a5ced08 100644 --- a/utils/midiplay/adlmidiplay.cpp +++ b/utils/midiplay/adlmidiplay.cpp @@ -100,7 +100,7 @@ static void debugPrint(void * /*userdata*/, const char *fmt, ...) } #ifdef DEBUG_TRACE_ALL_EVENTS -static void debugPrintEvent(void * /*userdata*/, ADL_UInt8 type, ADL_UInt8 subtype, ADL_UInt8 channel, ADL_UInt8 * /*data*/, size_t len) +static void debugPrintEvent(void * /*userdata*/, ADL_UInt8 type, ADL_UInt8 subtype, ADL_UInt8 channel, const ADL_UInt8 * /*data*/, size_t len) { std::fprintf(stdout, " - E: 0x%02X 0x%02X %02d (%d)\n", type, subtype, channel, (int)len); std::fflush(stdout); |