aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWohlstand <admin@wohlnet.ru>2017-11-09 22:50:32 +0300
committerWohlstand <admin@wohlnet.ru>2017-11-09 22:50:32 +0300
commit0be25a41ca793c3c03814f529890492adfac8ad5 (patch)
treea099d0025109b689bb0d6847e83b9890e34d5d35
parent265ae7a37afb087b9e16f4ddd0eaef28a21826de (diff)
downloadlibADLMIDI-0be25a41ca793c3c03814f529890492adfac8ad5.tar.gz
libADLMIDI-0be25a41ca793c3c03814f529890492adfac8ad5.tar.bz2
libADLMIDI-0be25a41ca793c3c03814f529890492adfac8ad5.zip
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
-rw-r--r--CMakeLists.txt24
-rw-r--r--include/adlmidi.h2
-rw-r--r--src/adlmidi_midiplay.cpp60
-rw-r--r--src/adlmidi_private.hpp4
-rw-r--r--utils/adlmidi-2/adlmidi-2.pro4
-rw-r--r--utils/adlmidi-2/input.cc56
-rw-r--r--utils/adlmidi-2/input.hpp46
-rw-r--r--utils/adlmidi-2/midiplay.cc131
-rw-r--r--utils/adlmidi-2/puzzlegame.cc842
-rwxr-xr-xutils/adlmidi-2/puzzlegame.hpp745
-rw-r--r--utils/midiplay/adlmidiplay.cpp2
11 files changed, 1096 insertions, 820 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3a7da2f..d1ae802 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -202,9 +202,29 @@ if(WITH_ADLMIDI2)
message("Found ${SDL2_LIBRARY}")
endif()
- add_executable(adlmidi2
+ if(NOT WIN32)
+ set(ADLMIDI2_HAS_PUZZLE_GAME "TRUE")
+ set(ADLMIDI2_VIDEO_OUT_SUPPORT "TRUE")
+ endif()
+
+ set(adlmidi2_src)
+ list(APPEND adlmidi2_src
${libADLMIDI_SOURCE_DIR}/utils/adlmidi-2/midiplay.cc
- )
+ ${libADLMIDI_SOURCE_DIR}/utils/adlmidi-2/input.cc)
+
+ if(ADLMIDI2_HAS_PUZZLE_GAME)
+ list(APPEND adlmidi2_src ${libADLMIDI_SOURCE_DIR}/utils/adlmidi-2/puzzlegame.cc)
+ endif()
+
+ add_executable(adlmidi2 ${adlmidi2_src})
+
+ if(ADLMIDI2_HAS_PUZZLE_GAME)
+ target_compile_options(adlmidi2 PUBLIC "-DSUPPORT_PUZZLE_GAME")
+ endif()
+
+ if(ADLMIDI2_VIDEO_OUT_SUPPORT)
+ target_compile_options(adlmidi2 PUBLIC "-DSUPPORT_VIDEO_OUTPUT")
+ endif()
if(WIN32)
target_link_libraries(adlmidi2 ADLMIDI winmm)
diff --git a/include/adlmidi.h b/include/adlmidi.h
index 1a613cf..6a5a21a 100644
--- a/include/adlmidi.h
+++ b/include/adlmidi.h
@@ -214,7 +214,7 @@ extern double adl_tickEvents(ADL_MIDIPlayer *device, double seconds, double gran
/**Hooks**/
-typedef void (*ADL_RawEventHook)(void *userdata, ADL_UInt8 type, ADL_UInt8 subtype, ADL_UInt8 channel, ADL_UInt8 *data, size_t len);
+typedef void (*ADL_RawEventHook)(void *userdata, ADL_UInt8 type, ADL_UInt8 subtype, ADL_UInt8 channel, const ADL_UInt8 *data, size_t len);
typedef void (*ADL_NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend);
typedef void (*ADL_DebugMessageHook)(void *userdata, const char *fmt, ...);
diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp
index 220b18c..83517db 100644
--- a/src/adlmidi_midiplay.cpp
+++ b/src/adlmidi_midiplay.cpp
@@ -193,38 +193,59 @@ void MIDIplay::MidiTrackRow::sortEvents(bool *noteStates)
}
/*
- * If Note-Off and it's Note-On is on the same row - move this danmed note off down!
+ * If Note-Off and it's Note-On is on the same row - move this damned note off down!
*/
if(noteStates)
{
+ std::set<size_t> markAsOn;
for(size_t i = 0; i < anyOther.size(); i++)
{
- MidiEvent &e = anyOther[i];
+ const MidiEvent e = anyOther[i];
if(e.type == MidiEvent::T_NOTEON)
{
- size_t note_i = (e.channel * 255) + e.data[0];
+ const size_t note_i = (e.channel * 255) + (e.data[0] & 0x7F);
//Check, was previously note is on or off
bool wasOn = noteStates[note_i];
- bool keepOn = true;
+ markAsOn.insert(note_i);
+ // Detect zero-length notes are following previously pressed note
+ int noteOffsOnSameNote = 0;
for(EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end();)
{
//If note was off, and note-off on same row with note-on - move it down!
if(
((*j).channel == e.channel) &&
- ((*j).data[0] == e.data[0]) &&
- !wasOn // Also check, is this note already OFF!!!
+ ((*j).data[0] == e.data[0])
)
{
- anyOther.push_back(*j);
- j = noteOffs.erase(j);
- keepOn = false;
- continue;
+ //If note is already off OR more than one note-off on same row and same note
+ if(!wasOn || (noteOffsOnSameNote != 0))
+ {
+ anyOther.push_back(*j);
+ j = noteOffs.erase(j);
+ markAsOn.erase(note_i);
+ continue;
+ } else {
+ //When same row has many note-offs on same row
+ //that means a zero-length note follows previous note
+ //it must be shuted down
+ noteOffsOnSameNote++;
+ }
}
j++;
}
- if(keepOn) noteStates[note_i] = true;
}
}
+
+ //Mark other notes as released
+ for(EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end(); j++)
+ {
+ size_t note_i = (j->channel * 255) + (j->data[0] & 0x7F);
+ noteStates[note_i] = false;
+ }
+
+ for(std::set<size_t>::iterator j = markAsOn.begin(); j != markAsOn.end(); j++)
+ noteStates[*j] = true;
+
}
/***********************************************************************************/
@@ -1543,10 +1564,17 @@ bool MIDIplay::ProcessEventsNew(bool isSeek)
PositionNew::TrackInfo &track = CurrentPositionNew.track[tk];
if((track.status >= 0) && (track.delay <= 0))
{
+ //Check is an end of track has been reached
+ if(track.pos == trackDataNew[tk].end())
+ {
+ track.status = -1;
+ break;
+ }
+
// Handle event
for(size_t i = 0; i < track.pos->events.size(); i++)
{
- MidiEvent &evt = track.pos->events[i];
+ const MidiEvent &evt = track.pos->events[i];
#ifdef ENABLE_BEGIN_SILENCE_SKIPPING
if(!CurrentPositionNew.began && (evt.type == MidiEvent::T_NOTEON))
CurrentPositionNew.began = true;
@@ -1562,11 +1590,7 @@ bool MIDIplay::ProcessEventsNew(bool isSeek)
if(maxTime < track.pos->time)
maxTime = track.pos->time;
#endif
-
// Read next event time (unless the track just ended)
- if(track.pos == trackDataNew[tk].end())
- track.status = -1;
-
if(track.status >= 0)
{
track.delay += track.pos->delay;
@@ -1847,7 +1871,7 @@ void MIDIplay::setErrorString(const std::string &err)
}
-void MIDIplay::HandleEvent(size_t tk, MIDIplay::MidiEvent &evt, int &status)
+void MIDIplay::HandleEvent(size_t tk, const MIDIplay::MidiEvent &evt, int &status)
{
if(hooks.onEvent)
{
@@ -2575,7 +2599,7 @@ ADLMIDI_EXPORT bool AdlInstrumentTester::HandleInputChar(char ch)
default:
const char *p = strchr(notes, ch);
if(p && *p)
- DoNote((p - notes) - 12);
+ DoNote((int)(p - notes) - 12);
}
return true;
}
diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp
index 37e0d25..58b4e10 100644
--- a/src/adlmidi_private.hpp
+++ b/src/adlmidi_private.hpp
@@ -257,7 +257,7 @@ struct MIDIEventHooks
onDebugMessage_userData(NULL)
{}
//! Raw MIDI event hook
- typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, uint8_t *data, size_t len);
+ typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, size_t len);
RawEventHook onEvent;
void *onEvent_userData;
@@ -886,7 +886,7 @@ private:
bool ProcessEvents();
bool ProcessEventsNew(bool isSeek = false);
void HandleEvent(size_t tk);
- void HandleEvent(size_t tk, MidiEvent &evt, int &status);
+ void HandleEvent(size_t tk, const MidiEvent &evt, int &status);
// Determine how good a candidate this adlchannel
// would be for playing a note from this instrument.
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);