Initial commit
This commit is contained in:
4
engines/groovie/POTFILES
Normal file
4
engines/groovie/POTFILES
Normal file
@@ -0,0 +1,4 @@
|
||||
engines/groovie/detection.cpp
|
||||
engines/groovie/metaengine.cpp
|
||||
engines/groovie/saveload.cpp
|
||||
engines/groovie/script.cpp
|
||||
4
engines/groovie/configure.engine
Normal file
4
engines/groovie/configure.engine
Normal file
@@ -0,0 +1,4 @@
|
||||
# This file is included from the main "configure" script
|
||||
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
|
||||
add_engine groovie "Groovie" yes "groovie2" "7th Guest" "highres"
|
||||
add_engine groovie2 "Groovie 2 games" yes "" "" "jpeg 16bit" "midi mpeg2"
|
||||
6
engines/groovie/credits.pl
Normal file
6
engines/groovie/credits.pl
Normal file
@@ -0,0 +1,6 @@
|
||||
begin_section("Groovie");
|
||||
add_person("Henry Bush", "spookypeanut", "");
|
||||
add_person("Ray Carro", "Die4Ever", "");
|
||||
add_person("Scott Thomas", "ST", "");
|
||||
add_person("Jordi Vilalta Prat", "jvprat", "");
|
||||
end_section();
|
||||
524
engines/groovie/cursor.cpp
Normal file
524
engines/groovie/cursor.cpp
Normal file
@@ -0,0 +1,524 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/cursor.h"
|
||||
#include "groovie/groovie.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/archive.h"
|
||||
#include "common/file.h"
|
||||
#include "common/macresman.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "graphics/cursorman.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
// Cursor Manager
|
||||
|
||||
GrvCursorMan::GrvCursorMan(OSystem *system) :
|
||||
_syst(system), _lastTime(0), _current(255), _cursor(nullptr), _lastFrame(0) {
|
||||
}
|
||||
|
||||
GrvCursorMan::~GrvCursorMan() {
|
||||
// Delete the cursors
|
||||
for (uint cursor = 0; cursor < _cursors.size(); cursor++) {
|
||||
delete _cursors[cursor];
|
||||
}
|
||||
|
||||
CursorMan.popAllCursors();
|
||||
}
|
||||
|
||||
void GrvCursorMan::show(bool visible) {
|
||||
CursorMan.showMouse(visible);
|
||||
}
|
||||
|
||||
uint16 GrvCursorMan::getStyle() {
|
||||
return _current;
|
||||
}
|
||||
|
||||
void GrvCursorMan::setStyle(uint16 newStyle) {
|
||||
// Reset the animation
|
||||
_lastFrame = 254;
|
||||
_lastTime = 1;
|
||||
|
||||
// Save the new cursor
|
||||
_current = newStyle;
|
||||
_cursor = _cursors[newStyle & 0xFF];
|
||||
|
||||
// Show the first frame
|
||||
_cursor->enable();
|
||||
animate();
|
||||
}
|
||||
|
||||
void GrvCursorMan::animate() {
|
||||
if (_lastTime) {
|
||||
int newTime = _syst->getMillis();
|
||||
if (newTime - _lastTime >= 66) {
|
||||
_lastFrame++;
|
||||
_lastFrame %= _cursor->getFrames();
|
||||
_cursor->showFrame(_lastFrame);
|
||||
_lastTime = _syst->getMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// t7g Cursor
|
||||
|
||||
class Cursor_t7g : public Cursor {
|
||||
public:
|
||||
Cursor_t7g(uint8 *img, uint8 *pal);
|
||||
|
||||
void enable() override;
|
||||
void showFrame(uint16 frame) override;
|
||||
|
||||
private:
|
||||
byte *_img;
|
||||
byte *_pal;
|
||||
};
|
||||
|
||||
Cursor_t7g::Cursor_t7g(uint8 *img, uint8 *pal) :
|
||||
_pal(pal) {
|
||||
|
||||
_width = img[0];
|
||||
_height = img[1];
|
||||
_numFrames = img[2];
|
||||
uint8 elinor1 = img[3];
|
||||
uint8 elinor2 = img[4];
|
||||
|
||||
_img = img + 5;
|
||||
|
||||
debugC(1, kDebugCursor, "Groovie::Cursor: width: %d, height: %d, frames:%d", _width, _height, _numFrames);
|
||||
debugC(1, kDebugCursor, "Groovie::Cursor: elinor: 0x%02X (%d), 0x%02X (%d)", elinor1, elinor1, elinor2, elinor2);
|
||||
}
|
||||
|
||||
void Cursor_t7g::enable() {
|
||||
// Apply the palette
|
||||
CursorMan.replaceCursorPalette(_pal, 0, 32);
|
||||
}
|
||||
|
||||
void Cursor_t7g::showFrame(uint16 frame) {
|
||||
// Set the mouse cursor
|
||||
int offset = _width * _height * frame;
|
||||
CursorMan.replaceCursor((const byte *)_img + offset, _width, _height, _width >> 1, _height >> 1, 0);
|
||||
}
|
||||
|
||||
|
||||
// t7g Cursor Manager
|
||||
|
||||
#define NUM_IMGS 9
|
||||
static const uint16 cursorDataOffsets[NUM_IMGS] = {
|
||||
0x0000, 0x182f, 0x3b6d, 0x50cc, 0x6e79, 0x825d, 0x96d7, 0xa455, 0xa776
|
||||
};
|
||||
|
||||
#define NUM_PALS 7
|
||||
//Pals: 0xb794, 0xb7f4, 0xb854, 0xb8b4, 0xb914, 0xb974, 0xb9d4
|
||||
|
||||
#define NUM_STYLES 11
|
||||
// pyramid is cursor 8, eyes are 9 & 10
|
||||
const uint GrvCursorMan_t7g::_cursorImg[NUM_STYLES] = {3, 5, 4, 3, 1, 0, 2, 6, 7, 8, 8};
|
||||
const uint GrvCursorMan_t7g::_cursorPal[NUM_STYLES] = {0, 0, 0, 0, 2, 0, 1, 3, 5, 4, 6};
|
||||
|
||||
GrvCursorMan_t7g::GrvCursorMan_t7g(OSystem *system, Common::MacResManager *macResFork) :
|
||||
GrvCursorMan(system) {
|
||||
|
||||
Common::SeekableReadStream *robgjd = nullptr;
|
||||
|
||||
if (macResFork) {
|
||||
// Open the cursors file from the resource fork
|
||||
robgjd = macResFork->getResource("rob.gjd");
|
||||
} else {
|
||||
// Open the cursors file
|
||||
robgjd = SearchMan.createReadStreamForMember("rob.gjd");
|
||||
}
|
||||
|
||||
if (!robgjd)
|
||||
error("Groovie::Cursor: Couldn't open rob.gjd");
|
||||
|
||||
// Load the images
|
||||
for (uint imgnum = 0; imgnum < NUM_IMGS; imgnum++) {
|
||||
robgjd->seek(cursorDataOffsets[imgnum]);
|
||||
_images.push_back(loadImage(*robgjd));
|
||||
}
|
||||
|
||||
// Load the palettes
|
||||
robgjd->seek(-0x60 * NUM_PALS, SEEK_END);
|
||||
for (uint palnum = 0; palnum < NUM_PALS; palnum++) {
|
||||
_palettes.push_back(loadPalette(*robgjd));
|
||||
}
|
||||
|
||||
// Build the cursors
|
||||
for (uint cursor = 0; cursor < NUM_STYLES; cursor++) {
|
||||
Cursor *s = new Cursor_t7g(_images[_cursorImg[cursor]], _palettes[_cursorPal[cursor]]);
|
||||
_cursors.push_back(s);
|
||||
}
|
||||
|
||||
delete robgjd;
|
||||
}
|
||||
|
||||
GrvCursorMan_t7g::~GrvCursorMan_t7g() {
|
||||
// Delete the images
|
||||
for (uint img = 0; img < _images.size(); img++) {
|
||||
delete[] _images[img];
|
||||
}
|
||||
|
||||
// Delete the palettes
|
||||
for (uint pal = 0; pal < _palettes.size(); pal++) {
|
||||
delete[] _palettes[pal];
|
||||
}
|
||||
}
|
||||
|
||||
byte *GrvCursorMan_t7g::loadImage(Common::SeekableReadStream &file) {
|
||||
uint16 decompbytes = 0, offset, i, length;
|
||||
uint8 flagbyte, lengthmask = 0x0F, offsetlen, var_8;
|
||||
byte *cursorStorage = new byte[65536];
|
||||
uint8 *runningcursor = cursorStorage;
|
||||
|
||||
bool finished = false;
|
||||
while (!(finished || file.eos())) {
|
||||
flagbyte = file.readByte();
|
||||
for (i = 1; i <= 8; i++) {
|
||||
if (!file.eos()) {
|
||||
if (flagbyte & 1) {
|
||||
*(runningcursor++) = file.readByte();
|
||||
decompbytes++;
|
||||
} else {
|
||||
var_8 = file.readByte();
|
||||
offsetlen = file.readByte();
|
||||
if (var_8 == 0 && offsetlen == 0) {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
length = (offsetlen & lengthmask) + 3;
|
||||
offsetlen >>= 4;
|
||||
offset = (offsetlen << 8) + var_8;
|
||||
decompbytes += length;
|
||||
|
||||
for (; length > 0; length--, runningcursor++) {
|
||||
*(runningcursor) = *(runningcursor - offset);
|
||||
}
|
||||
}
|
||||
flagbyte = flagbyte >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug(9, "GrvCursorMan_t7g::loadImage(): decompressed %d bytes", decompbytes);
|
||||
|
||||
return cursorStorage;
|
||||
}
|
||||
|
||||
byte *GrvCursorMan_t7g::loadPalette(Common::SeekableReadStream &file) {
|
||||
byte *palette = new byte[3 * 32];
|
||||
file.read(palette, 3 * 32);
|
||||
return palette;
|
||||
}
|
||||
|
||||
|
||||
// v2 Cursor
|
||||
|
||||
class Cursor_v2 : public Cursor {
|
||||
public:
|
||||
Cursor_v2(Common::File &file);
|
||||
~Cursor_v2() override;
|
||||
|
||||
void enable() override;
|
||||
void showFrame(uint16 frame) override;
|
||||
void blendCursor(uint32 *dst, int frame, int w, int h);
|
||||
static void show2Cursors(Cursor_v2 *c1, uint16 frame1, Cursor_v2 *c2, uint16 frame2);
|
||||
|
||||
private:
|
||||
// Currently locked to 16bit
|
||||
byte *_img;
|
||||
|
||||
Graphics::PixelFormat _format;
|
||||
|
||||
void decodeFrame(byte *pal, byte *data, byte *dest, uint32 size);
|
||||
};
|
||||
|
||||
Cursor_v2::Cursor_v2(Common::File &file) {
|
||||
byte *pal = new byte[0x20 * 3];
|
||||
|
||||
_format = g_system->getScreenFormat();
|
||||
|
||||
_numFrames = file.readUint16LE();
|
||||
_width = file.readUint16LE();
|
||||
_height = file.readUint16LE();
|
||||
_hotspotX = file.readUint16LE();
|
||||
_hotspotY = file.readUint16LE();
|
||||
|
||||
_img = new byte[_width * _height * _numFrames * 4];
|
||||
|
||||
debugC(1, kDebugCursor, "Groovie::Cursor: width: %d, height: %d, frames:%d, hotspot: %d, %d", _width, _height, _numFrames, _hotspotX, _hotspotY);
|
||||
|
||||
uint16 tmp16;
|
||||
int loop2count = file.readUint16LE();
|
||||
debugC(5, kDebugCursor, "loop2count?: %d", loop2count);
|
||||
for (int l = 0; l < loop2count; l++) {
|
||||
tmp16 = file.readUint16LE();
|
||||
debugC(5, kDebugCursor, "loop2a: %d", tmp16); // Index frame can merge to/from?
|
||||
tmp16 = file.readUint16LE();
|
||||
debugC(5, kDebugCursor, "loop2b: %d", tmp16); // Number of frames?
|
||||
}
|
||||
|
||||
file.read(pal, 0x20 * 3);
|
||||
|
||||
for (int f = 0; f < _numFrames; f++) {
|
||||
uint32 tmp32 = file.readUint32LE();
|
||||
debugC(5, kDebugCursor, "loop3: %d", tmp32);
|
||||
|
||||
byte *data = new byte[tmp32];
|
||||
file.read(data, tmp32);
|
||||
decodeFrame(pal, data, _img + (f * _width * _height * 4), tmp32);
|
||||
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
delete[] pal;
|
||||
}
|
||||
|
||||
Cursor_v2::~Cursor_v2() {
|
||||
delete[] _img;
|
||||
}
|
||||
|
||||
void Cursor_v2::decodeFrame(byte *pal, byte *data, byte *dest, uint32 size) {
|
||||
// Scratch memory
|
||||
byte *tmp = new byte[_width * _height * 4]();
|
||||
byte *ptr = tmp;
|
||||
|
||||
byte ctrA = 0, ctrB = 0;
|
||||
|
||||
byte alpha = 0, palIdx = 0;
|
||||
|
||||
byte r, g, b;
|
||||
|
||||
const byte alphaDecoded[8] = {0, 36, 73, 109, 146, 182, 219, 255}; // Calculated by: alpha = ((int)(*data & 0xE0) * 255) / 224;
|
||||
|
||||
// Start frame decoding
|
||||
for (int y = 0; y < _height; y++) {
|
||||
for (int x = 0; x < _width; x++) {
|
||||
if (!size) {
|
||||
debugC(1, kDebugCursor, "Cursor_v2::decodeFrame(): Frame underflow");
|
||||
break;
|
||||
}
|
||||
|
||||
// If both counters are empty
|
||||
if (ctrA == 0 && ctrB == 0) {
|
||||
if (*data & 0x80) {
|
||||
ctrA = (*data++ & 0x7F) + 1;
|
||||
size--;
|
||||
} else {
|
||||
ctrB = *data++ + 1;
|
||||
alpha = alphaDecoded[(*data & 0xE0) >> 5];
|
||||
palIdx = *data++ & 0x1F;
|
||||
|
||||
size -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctrA) {
|
||||
// Block type A - chunk of non-continuous pixels
|
||||
palIdx = *data & 0x1F;
|
||||
alpha = alphaDecoded[(*data++ & 0xE0) >> 5];
|
||||
size--;
|
||||
|
||||
r = *(pal + palIdx);
|
||||
g = *(pal + palIdx + 0x20);
|
||||
b = *(pal + palIdx + 0x40);
|
||||
|
||||
ctrA--;
|
||||
} else {
|
||||
// Block type B - chunk of continuous pixels
|
||||
r = *(pal + palIdx);
|
||||
g = *(pal + palIdx + 0x20);
|
||||
b = *(pal + palIdx + 0x40);
|
||||
|
||||
ctrB--;
|
||||
}
|
||||
|
||||
// Decode pixel
|
||||
if (alpha) {
|
||||
ptr[0] = alpha;
|
||||
ptr[1] = r;
|
||||
ptr[2] = g;
|
||||
ptr[3] = b;
|
||||
}
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
if (!size)
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert to screen format
|
||||
// NOTE: Currently locked to 32bpp
|
||||
ptr = tmp;
|
||||
for (int y = 0; y < _height; y++) {
|
||||
for (int x = 0; x < _width; x++) {
|
||||
*(uint32 *)dest = _format.ARGBToColor(ptr[0], ptr[1], ptr[2], ptr[3]);
|
||||
dest += 4;
|
||||
ptr += 4;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] tmp;
|
||||
}
|
||||
|
||||
void Cursor_v2::enable() {
|
||||
}
|
||||
|
||||
void Cursor_v2::showFrame(uint16 frame) {
|
||||
int offset = _width * _height * frame * 4;
|
||||
// SDL uses keycolor even though we're using ABGR8888, so just set it to a pink color that isn't used
|
||||
uint32 keycolor = _format.ARGBToColor(0, 255, 128, 255);
|
||||
CursorMan.replaceCursor((const byte *)(_img + offset), _width, _height, _hotspotX, _hotspotY, keycolor, false, &_format);
|
||||
}
|
||||
|
||||
void blendCursorPixel(uint32 &d, uint32 &s) {
|
||||
#ifdef SCUMM_LITTLE_ENDIAN
|
||||
static const int kAIndex = 0;
|
||||
static const int kBIndex = 1;
|
||||
static const int kGIndex = 2;
|
||||
static const int kRIndex = 3;
|
||||
|
||||
#else
|
||||
static const int kAIndex = 3;
|
||||
static const int kBIndex = 2;
|
||||
static const int kGIndex = 1;
|
||||
static const int kRIndex = 0;
|
||||
#endif
|
||||
|
||||
byte *dst = (byte *)&d;
|
||||
byte *src = (byte *)&s;
|
||||
|
||||
if (src[kAIndex] == 255) {
|
||||
d = s;
|
||||
} else if (src[kAIndex] > 0) {
|
||||
dst[kAIndex] = MAX(src[kAIndex], dst[kAIndex]);
|
||||
dst[kRIndex] = ((src[kRIndex] * src[kAIndex]) + dst[kRIndex] * (255 - src[kAIndex])) >> 8;
|
||||
dst[kGIndex] = ((src[kGIndex] * src[kAIndex]) + dst[kGIndex] * (255 - src[kAIndex])) >> 8;
|
||||
dst[kBIndex] = ((src[kBIndex] * src[kAIndex]) + dst[kBIndex] * (255 - src[kAIndex])) >> 8;
|
||||
}
|
||||
// In case of alpha == 0 just do nothing
|
||||
}
|
||||
|
||||
void Cursor_v2::blendCursor(uint32 *dst, int frame, int w, int h) {
|
||||
uint32 *src = (uint32 *)_img;
|
||||
src += _width * _height * frame;
|
||||
|
||||
int offX = (w - _width) / 2;
|
||||
int offY = (h - _height) / 2;
|
||||
|
||||
for (int y = 0; y < _height; y++) {
|
||||
for (int x = 0; x < _width; x++) {
|
||||
blendCursorPixel(dst[x + offX + (y + offY) * w], src[x + y * _width]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Cursor_v2::show2Cursors(Cursor_v2 *c1, uint16 frame1, Cursor_v2 *c2, uint16 frame2) {
|
||||
int width = MAX(c1->_width, c2->_width);
|
||||
int height = MAX(c1->_height, c2->_height);
|
||||
uint32 *img = new uint32[width * height]();
|
||||
|
||||
c2->blendCursor(img, frame2, width, height);
|
||||
c1->blendCursor(img, frame1, width, height);
|
||||
|
||||
// SDL uses keycolor even though we're using ABGR8888, so just set it to a pink color that isn't used
|
||||
Graphics::PixelFormat format = g_system->getScreenFormat();
|
||||
uint32 keycolor = format.ARGBToColor(0, 255, 128, 255);
|
||||
|
||||
// replaceCursor copies the buffer, so we're ok to delete it
|
||||
CursorMan.replaceCursor((const byte *)img, width, height, c1->_hotspotX, c1->_hotspotY, keycolor, false, &c1->_format);
|
||||
delete[] img;
|
||||
}
|
||||
|
||||
|
||||
// v2 Cursor Manager
|
||||
|
||||
GrvCursorMan_v2::GrvCursorMan_v2(OSystem *system) :
|
||||
GrvCursorMan(system), _cursor2(nullptr), _lastFrame2(0) {
|
||||
|
||||
// Open the icons file
|
||||
Common::File iconsFile;
|
||||
if (!iconsFile.open("icons.ph") && !iconsFile.open("icons.bin"))
|
||||
error("Groovie::Cursor: Couldn't open icons.ph or icons.bin");
|
||||
|
||||
// Verify the signature
|
||||
uint32 tmp32 = iconsFile.readUint32BE();
|
||||
uint16 tmp16 = iconsFile.readUint16LE();
|
||||
if (tmp32 != MKTAG('i','c','o','n') || tmp16 != 1)
|
||||
error("Groovie::Cursor: %s signature failed: %s %d", iconsFile.getName(), tag2str(tmp32), tmp16);
|
||||
|
||||
|
||||
// Read the number of icons
|
||||
uint16 nicons = iconsFile.readUint16LE();
|
||||
|
||||
// Read the icons
|
||||
for (int i = 0; i < nicons; i++) {
|
||||
Cursor *s = new Cursor_v2(iconsFile);
|
||||
_cursors.push_back(s);
|
||||
}
|
||||
|
||||
iconsFile.close();
|
||||
}
|
||||
|
||||
GrvCursorMan_v2::~GrvCursorMan_v2() {
|
||||
}
|
||||
|
||||
|
||||
void GrvCursorMan_v2::animate() {
|
||||
if (_lastTime) {
|
||||
int newTime = _syst->getMillis();
|
||||
if (newTime - _lastTime >= 66) {
|
||||
_lastFrame++;
|
||||
_lastFrame %= _cursor->getFrames();
|
||||
if (_cursor2) {
|
||||
_lastFrame2++;
|
||||
_lastFrame2 %= _cursor2->getFrames();
|
||||
Cursor_v2::show2Cursors((Cursor_v2 *)_cursor, _lastFrame, (Cursor_v2 *)_cursor2, _lastFrame2);
|
||||
} else {
|
||||
_cursor->showFrame(_lastFrame);
|
||||
}
|
||||
_lastTime = _syst->getMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GrvCursorMan_v2::setStyle(uint16 newStyle) {
|
||||
// HACK: Cursor 4 is actually cursor 3, but with some changes to alpha blending
|
||||
// (which is currently not handled)
|
||||
uint8 newStyleLow = newStyle & 0xFF;
|
||||
GrvCursorMan::setStyle(newStyleLow == 4 ? 3 : newStyle);
|
||||
|
||||
if (newStyle & 0x8000) {
|
||||
_cursor2 = _cursors.back();
|
||||
_lastFrame2 = 254;
|
||||
} else {
|
||||
_cursor2 = nullptr;
|
||||
}
|
||||
|
||||
// fix _current back to cursor 4 so that getStyle returns the proper number
|
||||
if (newStyleLow == 4)
|
||||
_current++;
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
106
engines/groovie/cursor.h
Normal file
106
engines/groovie/cursor.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_CURSOR_H
|
||||
#define GROOVIE_CURSOR_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Common {
|
||||
class MacResManager;
|
||||
}
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class Cursor {
|
||||
public:
|
||||
virtual ~Cursor() {}
|
||||
uint16 getFrames() { return _numFrames; }
|
||||
virtual void enable() = 0;
|
||||
virtual void showFrame(uint16 frame) = 0;
|
||||
|
||||
protected:
|
||||
uint16 _width;
|
||||
uint16 _height;
|
||||
uint16 _hotspotX;
|
||||
uint16 _hotspotY;
|
||||
uint16 _numFrames;
|
||||
};
|
||||
|
||||
class GrvCursorMan {
|
||||
public:
|
||||
GrvCursorMan(OSystem *system);
|
||||
virtual ~GrvCursorMan();
|
||||
|
||||
virtual void show(bool visible);
|
||||
virtual void animate();
|
||||
virtual void setStyle(uint16 newStyle);
|
||||
virtual uint16 getStyle();
|
||||
|
||||
protected:
|
||||
OSystem *_syst;
|
||||
|
||||
// Animation variables
|
||||
uint8 _lastFrame;
|
||||
uint32 _lastTime;
|
||||
|
||||
// Styles
|
||||
Common::Array<Cursor *> _cursors;
|
||||
uint16 _current;
|
||||
Cursor *_cursor;
|
||||
};
|
||||
|
||||
class GrvCursorMan_t7g : public GrvCursorMan {
|
||||
public:
|
||||
GrvCursorMan_t7g(OSystem *system, Common::MacResManager *macResFork = 0);
|
||||
~GrvCursorMan_t7g() override;
|
||||
|
||||
private:
|
||||
// Styles data
|
||||
static const uint _cursorImg[];
|
||||
static const uint _cursorPal[];
|
||||
|
||||
// Cursors data
|
||||
Common::Array<byte *> _images;
|
||||
Common::Array<byte *> _palettes;
|
||||
|
||||
// Loading functions
|
||||
byte *loadImage(Common::SeekableReadStream &file);
|
||||
byte *loadPalette(Common::SeekableReadStream &file);
|
||||
};
|
||||
|
||||
class GrvCursorMan_v2 : public GrvCursorMan {
|
||||
public:
|
||||
GrvCursorMan_v2(OSystem *system);
|
||||
~GrvCursorMan_v2() override;
|
||||
|
||||
void animate() override;
|
||||
void setStyle(uint16 newStyle) override;
|
||||
|
||||
private:
|
||||
Cursor *_cursor2;
|
||||
uint8 _lastFrame2;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_CURSOR_H
|
||||
160
engines/groovie/debug.cpp
Normal file
160
engines/groovie/debug.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/debug.h"
|
||||
#include "groovie/graphics.h"
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/resource.h"
|
||||
#include "groovie/script.h"
|
||||
|
||||
#include "common/debug-channels.h"
|
||||
#include "common/system.h"
|
||||
|
||||
#include "graphics/paletteman.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
Debugger::Debugger(GroovieEngine *vm) :
|
||||
_vm(vm), _script(_vm->_script) {
|
||||
|
||||
// Register the debugger comands
|
||||
registerCmd("step", WRAP_METHOD(Debugger, cmd_step));
|
||||
registerCmd("go", WRAP_METHOD(Debugger, cmd_go));
|
||||
registerCmd("pc", WRAP_METHOD(Debugger, cmd_pc));
|
||||
registerCmd("fg", WRAP_METHOD(Debugger, cmd_fg));
|
||||
registerCmd("bg", WRAP_METHOD(Debugger, cmd_bg));
|
||||
registerCmd("mem", WRAP_METHOD(Debugger, cmd_mem));
|
||||
registerCmd("var", WRAP_METHOD(Debugger, cmd_mem)); // alias
|
||||
registerCmd("load", WRAP_METHOD(Debugger, cmd_loadgame));
|
||||
registerCmd("save", WRAP_METHOD(Debugger, cmd_savegame));
|
||||
registerCmd("playref", WRAP_METHOD(Debugger, cmd_playref));
|
||||
registerCmd("dumppal", WRAP_METHOD(Debugger, cmd_dumppal));
|
||||
registerCmd("dumpfile", WRAP_METHOD(Debugger, cmd_dumpfile));
|
||||
}
|
||||
|
||||
Debugger::~Debugger() {
|
||||
}
|
||||
|
||||
int Debugger::getNumber(const char *arg) {
|
||||
return strtol(arg, (char **)nullptr, 0);
|
||||
}
|
||||
|
||||
bool Debugger::cmd_step(int argc, const char **argv) {
|
||||
_script->step();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Debugger::cmd_go(int argc, const char **argv) {
|
||||
_script->step();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Debugger::cmd_fg(int argc, const char **argv) {
|
||||
_vm->_graphicsMan->updateScreen(&_vm->_graphicsMan->_foreground);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Debugger::cmd_bg(int argc, const char **argv) {
|
||||
_vm->_graphicsMan->updateScreen(&_vm->_graphicsMan->_background);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Debugger::cmd_pc(int argc, const char **argv) {
|
||||
if (argc == 2) {
|
||||
int val = getNumber(argv[1]);
|
||||
_script->_currentInstruction = val;
|
||||
}
|
||||
debugPrintf("pc = 0x%04X (%d)\n", _script->_currentInstruction, _script->_currentInstruction);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Debugger::cmd_mem(int argc, const char **argv) {
|
||||
if (argc >= 2) {
|
||||
int pos = getNumber(argv[1]);
|
||||
uint8 val;
|
||||
if (argc >= 3) {
|
||||
// Set
|
||||
val = getNumber(argv[2]);
|
||||
_script->_variables[pos] = val;
|
||||
} else {
|
||||
// Get
|
||||
val = _script->_variables[pos];
|
||||
}
|
||||
debugPrintf("%s[0x%04X] = 0x%02X\n", argv[0], pos, val);
|
||||
} else {
|
||||
debugPrintf("Syntax: %s <addr> [<val>]\n", argv[0]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Debugger::cmd_loadgame(int argc, const char **argv) {
|
||||
if (argc == 2) {
|
||||
int slot = getNumber(argv[1]);
|
||||
_script->loadgame(slot);
|
||||
} else {
|
||||
debugPrintf("Syntax: load <slot>\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Debugger::cmd_savegame(int argc, const char **argv) {
|
||||
if (argc == 2) {
|
||||
int slot = getNumber(argv[1]);
|
||||
_script->directGameSave(slot, "debug save");
|
||||
} else {
|
||||
debugPrintf("Syntax: save <slot>\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Debugger::cmd_playref(int argc, const char **argv) {
|
||||
if (argc == 2) {
|
||||
int ref = getNumber(argv[1]);
|
||||
_script->playvideofromref(ref);
|
||||
} else {
|
||||
debugPrintf("Syntax: playref <videorefnum>\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Debugger::cmd_dumppal(int argc, const char **argv) {
|
||||
uint16 i;
|
||||
byte palettedump[256 * 3];
|
||||
_vm->_system->getPaletteManager()->grabPalette(palettedump, 0, 256);
|
||||
|
||||
for (i = 0; i < 256; i++) {
|
||||
debugPrintf("%3d: %3d,%3d,%3d\n", i, palettedump[(i * 3)], palettedump[(i * 3) + 1], palettedump[(i * 3) + 2]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Debugger::cmd_dumpfile(int argc, const char **argv) {
|
||||
if (argc == 2) {
|
||||
Common::String fileName = argv[1];
|
||||
debugPrintf("Dumping %s...\n", argv[1]);
|
||||
_vm->_resMan->dumpResource(fileName);
|
||||
} else {
|
||||
debugPrintf("Syntax: %s <filename>\n", argv[0]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
58
engines/groovie/debug.h
Normal file
58
engines/groovie/debug.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_DEBUG_H
|
||||
#define GROOVIE_DEBUG_H
|
||||
|
||||
#include "gui/debugger.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class GroovieEngine;
|
||||
class Script;
|
||||
|
||||
class Debugger : public GUI::Debugger {
|
||||
public:
|
||||
Debugger(GroovieEngine *vm);
|
||||
~Debugger() override;
|
||||
|
||||
private:
|
||||
GroovieEngine *_vm;
|
||||
Script *_script;
|
||||
|
||||
int getNumber(const char *arg);
|
||||
|
||||
bool cmd_step(int argc, const char **argv);
|
||||
bool cmd_go(int argc, const char **argv);
|
||||
bool cmd_pc(int argc, const char **argv);
|
||||
bool cmd_bg(int argc, const char **argv);
|
||||
bool cmd_fg(int argc, const char **argv);
|
||||
bool cmd_mem(int argc, const char **argv);
|
||||
bool cmd_loadgame(int argc, const char **argv);
|
||||
bool cmd_savegame(int argc, const char **argv);
|
||||
bool cmd_playref(int argc, const char **argv);
|
||||
bool cmd_dumppal(int argc, const char **argv);
|
||||
bool cmd_dumpfile(int argc, const char **argv);
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_DEBUG_H
|
||||
316
engines/groovie/detection.cpp
Normal file
316
engines/groovie/detection.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/system.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
#include "engines/advancedDetector.h"
|
||||
#include "groovie/detection.h"
|
||||
#include "groovie/groovie.h"
|
||||
|
||||
using namespace Common;
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
static const DebugChannelDef debugFlagList[] = {
|
||||
{Groovie::kDebugVideo, "Video", "Debug video and audio playback"},
|
||||
{Groovie::kDebugResource, "Resource", "Debug resource management"},
|
||||
{Groovie::kDebugScript, "Script", "Debug the scripts"},
|
||||
{Groovie::kDebugUnknown, "Unknown", "Report values of unknown data in files"},
|
||||
{Groovie::kDebugHotspots, "Hotspots", "Show the hotspots"},
|
||||
{Groovie::kDebugCursor, "Cursor", "Debug cursor decompression / switching"},
|
||||
{Groovie::kDebugMIDI, "MIDI", "Debug MIDI / XMIDI files"},
|
||||
{Groovie::kDebugScriptvars, "Scriptvars", "Print out any change to script variables"},
|
||||
{Groovie::kDebugLogic, "Logic", "Debug the AI puzzles in the logic folder and TLC questionnaires"},
|
||||
{Groovie::kDebugFast, "Fast", "Play videos quickly, with no sound (unstable)"},
|
||||
DEBUG_CHANNEL_END};
|
||||
|
||||
static const PlainGameDescriptor groovieGames[] = {
|
||||
// Games
|
||||
{"t7g", "The 7th Guest"},
|
||||
{"11h", "The 11th Hour: The Sequel to The 7th Guest"},
|
||||
{"11hsu", "The 11th Hour: Souped Up"},
|
||||
{"making11h", "The Making of The 11th Hour"},
|
||||
{"clandestiny", "Clandestiny"},
|
||||
{"unclehenry", "Uncle Henry's Playhouse"},
|
||||
{"tlc", "Tender Loving Care"},
|
||||
|
||||
{nullptr, nullptr}};
|
||||
|
||||
const int BASE_FLAGS = ADGF_NO_FLAGS;
|
||||
|
||||
#define GROOVIEGAME(id, extra, f1, x1, s1, f2, x2, s2, language, platform, flags, guiOptions, version) \
|
||||
{ \
|
||||
{ \
|
||||
id, extra, \
|
||||
AD_ENTRY2s(f1, x1, s1, f2, x2, s2), \
|
||||
language, platform, (flags), \
|
||||
(guiOptions) \
|
||||
}, \
|
||||
version \
|
||||
}
|
||||
|
||||
|
||||
#define T7GENTRY(extra, f1, x1, s1, f2, x2, s2, language, platform, flags) \
|
||||
GROOVIEGAME("t7g", extra, f1, x1, s1, f2, x2, s2, language, platform, flags, GUIO10(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT, GUIO_NOSFX, GAMEOPTION_T7G_FAST_MOVIE_SPEED, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_EASIER_AI, GAMEOPTION_SLIMHOTSPOTS, GAMEOPTION_SPEEDRUN), kGroovieT7G)
|
||||
|
||||
#define T7GNOMIDIENTRY(extra, f1, x1, s1, f2, x2, s2, language, platform, flags, guiOptions) \
|
||||
GROOVIEGAME("t7g", extra, f1, x1, s1, f2, x2, s2, language, platform, flags, (GUIO_NOMIDI GUIO_NOASPECT GUIO_NOSFX GAMEOPTION_T7G_FAST_MOVIE_SPEED GAMEOPTION_SLIMHOTSPOTS GAMEOPTION_SPEEDRUN guiOptions), kGroovieT7G)
|
||||
|
||||
#define T11HENTRY(extra, f1, x1, s1, f2, x2, s2, language, platform, flags) \
|
||||
GROOVIEGAME("11h", extra, f1, x1, s1, f2, x2, s2, language, platform, flags, GUIO8(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM GUIO_NOASPECT, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_EASIER_AI, GAMEOPTION_FINAL_HOUR, GAMEOPTION_SLIMHOTSPOTS, GAMEOPTION_SPEEDRUN), kGroovieT11H)
|
||||
|
||||
#define T11HDEMOENTRY(extra, f1, x1, s1, f2, x2, s2, language, platform, flags) \
|
||||
GROOVIEGAME("11h", extra, f1, x1, s1, f2, x2, s2, language, platform, flags | ADGF_DEMO, GUIO7(GUIO_NOLAUNCHLOAD, GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT, GAMEOPTION_SLIMHOTSPOTS, GAMEOPTION_SPEEDRUN), kGroovieT11H)
|
||||
|
||||
#define T11HMAKINGOFENTRY(f1, x1, s1, f2, x2, s2, language, platform) \
|
||||
GROOVIEGAME("making11h", "", f1, x1, s1, f2, x2, s2, language, platform, ADGF_NO_FLAGS, GUIO6(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT, GAMEOPTION_SLIMHOTSPOTS, GAMEOPTION_SPEEDRUN), kGroovieT11H)
|
||||
|
||||
#define CLANENTRY(extra, f1, x1, s1, f2, x2, s2, language, platform, flags) \
|
||||
GROOVIEGAME("clandestiny", extra, f1, x1, s1, f2, x2, s2, language, platform, flags, GUIO6(GUIO_NOMIDI, GUIO_NOASPECT, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_EASIER_AI, GAMEOPTION_SLIMHOTSPOTS, GAMEOPTION_SPEEDRUN), kGroovieCDY)
|
||||
|
||||
#define CLANDEMOENTRY(extra, f1, x1, s1, f2, x2, s2, language, platform, flags) \
|
||||
GROOVIEGAME("clandestiny", extra, f1, x1, s1, f2, x2, s2, language, platform, flags | ADGF_DEMO, GUIO5(GUIO_NOMIDI, GUIO_NOLAUNCHLOAD, GUIO_NOASPECT, GAMEOPTION_SLIMHOTSPOTS, GAMEOPTION_SPEEDRUN), kGroovieCDY)
|
||||
|
||||
#define UHPENTRY(extra, f1, x1, s1, f2, x2, s2, language, platform, flags) \
|
||||
GROOVIEGAME("unclehenry", extra, f1, x1, s1, f2, x2, s2, language, platform, flags, GUIO5(GUIO_NOMIDI, GUIO_NOASPECT, GAMEOPTION_EASIER_AI, GAMEOPTION_SLIMHOTSPOTS, GAMEOPTION_SPEEDRUN), kGroovieUHP)
|
||||
|
||||
#define TLCENTRY(extra, f1, x1, s1, f2, x2, s2, language, platform, flags) \
|
||||
GROOVIEGAME("tlc", extra, f1, x1, s1, f2, x2, s2, language, platform, flags | ADGF_CD, GUIO4(GUIO_NOMIDI, GUIO_NOASPECT, GAMEOPTION_SLIMHOTSPOTS, GAMEOPTION_SPEEDRUN), kGroovieTLC)
|
||||
|
||||
#define TLCDVDENTRY(f1, x1, s1, f2, x2, s2, language, platform) \
|
||||
GROOVIEGAME("tlc", MetaEngineDetection::GAME_NOT_IMPLEMENTED, f1, x1, s1, f2, x2, s2, language, platform, ADGF_DVD, GUIO4(GUIO_NOMIDI, GUIO_NOASPECT, GAMEOPTION_SLIMHOTSPOTS, GAMEOPTION_SPEEDRUN), kGroovieTLC)
|
||||
|
||||
#define TLCDEMOENTRY(f1, x1, s1, f2, x2, s2, language, platform, flags) \
|
||||
GROOVIEGAME("tlc", "Demo", f1, x1, s1, f2, x2, s2, language, platform, flags | ADGF_DEMO, GUIO3(GUIO_NOMIDI, GUIO_NOASPECT, GUIO_NOLAUNCHLOAD), kGroovieTLC)
|
||||
|
||||
#define TLCTRAILERENTRY(f1, x1, s1, f2, x2, s2, language, platform, flags) \
|
||||
GROOVIEGAME("tlc", "Trailer", f1, x1, s1, f2, x2, s2, language, platform, flags | ADGF_DEMO, GUIO3(GUIO_NOMIDI, GUIO_NOASPECT, GUIO_NOLAUNCHLOAD), kGroovieTLC)
|
||||
|
||||
// clang-format off
|
||||
static const GroovieGameDescription gameDescriptions[] = {
|
||||
// groovie.cpp requires the first file to be the main .grv file for v2 games, might as well stick to that convention for v1 games from now on too
|
||||
|
||||
/*==== The 7th Guest ====*/
|
||||
// The 7th Guest 25th Anniversary
|
||||
T7GENTRY("25th Anniversary Edition", "script.grv", "d1b8033b40aa67c076039881eccce90d", 16659,
|
||||
"AT.gjd", "bbaa95ce6e600a8ba5b2902326cd11f8", 28827446, EN_ANY, kPlatformWindows, ADGF_UNSTABLE | ADGF_REMASTERED),
|
||||
|
||||
// The 7th Guest DOS English
|
||||
T7GENTRY("", "script.grv", "d1b8033b40aa67c076039881eccce90d", 16659,
|
||||
"AT.gjd", "bbaa95ce6e600a8ba5b2902326cd11f8", 45171574, EN_ANY, kPlatformDOS, BASE_FLAGS),
|
||||
|
||||
// The 7th Guest Mac English
|
||||
T7GENTRY("", "script.grv", nullptr, AD_NO_SIZE,// FIXMEMD5
|
||||
"T7GMac", "acdc4a58dd3f007f65e99b99d78e0bce", 1814029, EN_ANY, kPlatformMacintosh, ADGF_MACRESFORK),
|
||||
|
||||
// The 7th Guest DOS Russian (Akella)
|
||||
T7GENTRY("", "script.grv", "d1b8033b40aa67c076039881eccce90d", 16659,
|
||||
"intro.gjd", nullptr, 31711554, RU_RUS, kPlatformDOS, BASE_FLAGS),
|
||||
|
||||
// The 7th Guest iOS English
|
||||
T7GNOMIDIENTRY("", "script.grv", "d1b8033b40aa67c076039881eccce90d", 16659,
|
||||
"SeventhGuest", nullptr, AD_NO_SIZE, EN_ANY, kPlatformIOS, BASE_FLAGS, GAMEOPTION_ORIGINAL_SAVELOAD GAMEOPTION_EASIER_AI),
|
||||
|
||||
// "Guest" early DOS demo
|
||||
T7GNOMIDIENTRY("The early \"Guest\" demo is not supported", "playtlc.exe", "9cff0e9649ddf49e9fe5168730aa7201", 254768,
|
||||
"FACE.VDX", "614f820265274dc99d8869de67df1718", 1382814,
|
||||
EN_ANY, kPlatformDOS, ADGF_DEMO | ADGF_UNSUPPORTED, GUIO_NONE),
|
||||
|
||||
/*==== The 11th Hour ====*/
|
||||
// The 11th Hour DOS/Windows English (Available on Steam)
|
||||
T11HENTRY("", "script.grv", "bdb9a783d4debe477ac3856adc454c17", 62447,
|
||||
"introd1.gjd", "9ec3e727182fbe40ee23e786721180eb", 6437077, EN_ANY, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// The 11th Hour DOS/Windows German
|
||||
T11HENTRY("", "script.grv", "560e90b47054639668e44a8b365fbe26", 62447, "introd1.gjd", nullptr, AD_NO_SIZE, DE_DEU, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// The 11th Hour DOS/Windows French
|
||||
T11HENTRY("", "script.grv", "752c0a8ea62a1207c8583f3dbc16e6ef", 62447, "introd1.gjd", nullptr, AD_NO_SIZE, FR_FRA, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// The 11th Hour DOS/Windows Russian (Akella)
|
||||
T11HENTRY("", "script.grv", "bdb9a783d4debe477ac3856adc454c17", 62447,
|
||||
"introd1.gjd", "b80c6d88ac576cdd6f98d1e467629108", 1516, RU_RUS, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// The 11th Hour Mac English
|
||||
T11HENTRY("", "script.grv", "bdb9a783d4debe477ac3856adc454c17", 62447,
|
||||
"The 11th Hour Installer", "bcdb4040b27f15b18f39fb9e496d384a", 1002987, EN_ANY, kPlatformMacintosh, BASE_FLAGS),
|
||||
|
||||
// The 11th Hour Mac English (Installed)
|
||||
T11HENTRY("Installed", "script.grv", "bdb9a783d4debe477ac3856adc454c17", 62447,
|
||||
"el01.mov", "70f42dfc25b1488a08011dc45bb5145d", 6039, EN_ANY, kPlatformMacintosh, BASE_FLAGS),
|
||||
|
||||
// The 11th Hour: Souped Up
|
||||
GROOVIEGAME("11hsu", "", "suscript.grv", NULL, AD_NO_SIZE,
|
||||
"introd1.gjd", "9ec3e727182fbe40ee23e786721180eb", 6437077, EN_ANY, kPlatformWindows, BASE_FLAGS,
|
||||
GUIO7(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM GUIO_NOASPECT, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_EASIER_AI_DEFAULT, GAMEOPTION_SLIMHOTSPOTS, GAMEOPTION_SPEEDRUN), kGroovieT11H),
|
||||
|
||||
/*==== The 11th Hour Demos ====*/
|
||||
// The 11th Hour DOS Interactive Demo English https://archive.org/details/11th_Hour_demo
|
||||
T11HDEMOENTRY("Interactive Demo", "demo.grv", "5faec559b9abf18cf143751b420208dc", 15991,
|
||||
"dvmod1a.gjd", "e304fe68f95c54fc82d785768e372892", 8068568, EN_ANY, kPlatformDOS, BASE_FLAGS),
|
||||
|
||||
// The 11th Hour DOS Interactive Demo English (packaged with the Non-Interactive Demo) https://archive.org/details/11HDEMO
|
||||
T11HDEMOENTRY("Interactive Demo", "demo.grv", "824b1a051f841a50ab7a6b4c10180bbc", 15898,
|
||||
"dvmod1a.gjd", "e304fe68f95c54fc82d785768e372892", 8068568, EN_ANY, kPlatformDOS, BASE_FLAGS),
|
||||
|
||||
// The 11th Hour DOS Non-Interactive Demo English https://archive.org/details/11HDEMO
|
||||
T11HDEMOENTRY("Non-Interactive Demo", "niloop.grv", "b4c35a2a6ebaf72fbd830b590d48f8ea", 456,
|
||||
"dvmod1b.gjd", "43eb268ef6d64a75b9846df5be453d30", 11264100, EN_ANY, kPlatformDOS, BASE_FLAGS),
|
||||
|
||||
/*==== The Making of The 11th Hour ====*/
|
||||
// all are in english even if they came packaged with alternate language versions of the game
|
||||
// I removed the hash check for now so they all match with a single entry since the language field is useless here
|
||||
|
||||
// The Making of The 11th Hour DOS/Windows
|
||||
T11HMAKINGOFENTRY("makingof.grv", nullptr, 994, "zmakd2a.gjd", nullptr, AD_NO_SIZE, EN_ANY, kPlatformWindows),
|
||||
|
||||
/*// The Making of The 11th Hour DOS/Windows English
|
||||
T11HMAKINGOFENTRY(GROOVIEFILES("makingof.grv", "12e1e5eef2c7a9536cd12ac800b31408", 994, "zmakd2a.gjd"), EN_ANY, kPlatformWindows),
|
||||
|
||||
// The Making of The 11th Hour DOS/Windows German
|
||||
T11HMAKINGOFENTRY(GROOVIEFILES("makingof.grv", "03492c6ad3088b3f9f51a3eaba6b8c8e", 994, "zmakd2a.gjd"), EN_ANY, kPlatformWindows),
|
||||
|
||||
// The Making of The 11th Hour DOS/Windows French
|
||||
T11HMAKINGOFENTRY(GROOVIEFILES("makingof.grv", "77c4ae4deb0e323ccd7dcca0f99de2b9", 994, "zmakd2a.gjd"), EN_ANY, kPlatformWindows),
|
||||
|
||||
// The Making of The 11th Hour Macintosh English
|
||||
T11HMAKINGOFENTRY(AD_ENTRY2s("makingof.grv", "12e1e5eef2c7a9536cd12ac800b31408", 994,
|
||||
"The 11th Hour Installer", "bcdb4040b27f15b18f39fb9e496d384a", 1002987), EN_ANY, kPlatformMacintosh),*/
|
||||
|
||||
// The Making of The 11th Hour Macintosh
|
||||
T11HMAKINGOFENTRY("makingof.grv", nullptr, 994,
|
||||
"The 11th Hour Installer", nullptr, 1002987, EN_ANY, kPlatformMacintosh),
|
||||
|
||||
/*==== Clandestiny Demos ====*/
|
||||
// Clandestiny Trailer Macintosh English
|
||||
CLANDEMOENTRY("Trailer", "trailer.grv", "a7c8bdc4e8bff621f4f50928a95eaaba", 6,
|
||||
"The 11th Hour Installer", "bcdb4040b27f15b18f39fb9e496d384a", 1002987, EN_ANY, kPlatformMacintosh, BASE_FLAGS),
|
||||
|
||||
// Clandestiny PC Demo English https://archive.org/details/Clandestiny_demo
|
||||
CLANDEMOENTRY("Demo", "clandemo.grv", "faa863738da1c93673ed58a4b9597a63", 6744, "cddemo.gjd", nullptr, AD_NO_SIZE, EN_ANY, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// Clandestiny PC Trailer English https://downloads.scummvm.org/frs/demos/groovie/clandestiny-dos-ni-demo-en.zip
|
||||
CLANDEMOENTRY("Trailer", "trailer.grv", "a7c8bdc4e8bff621f4f50928a95eaaba", 6,
|
||||
"zclan.gjd", "4a7258166916fcc0d217c8f21fa3cc79", 20454932, EN_ANY, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
/*==== Clandestiny ====*/
|
||||
// Clandestiny PC English
|
||||
CLANENTRY("", "clanmain.grv", "dd424120fa1daa9d6b576d0ba22a4936", 54253, "ACT01MUS.MPG", nullptr, AD_NO_SIZE, EN_ANY, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// Clandestiny Mac/iOS App Store
|
||||
CLANENTRY("Mac/iOS", "CLANMAIN.GRV", "dd424120fa1daa9d6b576d0ba22a4936", 54253, "ACT01MUS.m4a", nullptr, AD_NO_SIZE, EN_ANY, kPlatformUnknown, BASE_FLAGS),
|
||||
|
||||
/*==== Uncle Henry's Playhouse ====*/
|
||||
// Uncle Henry's Playhouse PC English (1996-09-13)
|
||||
UHPENTRY("", "tpot.grv", "849dc7e5309e1b9acf72d8abc9e145df", 11693, "trt7g.gjd", nullptr, AD_NO_SIZE, EN_ANY, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// Uncle Henry's Playhouse PC German
|
||||
// Funsoft (1997-02-14)
|
||||
UHPENTRY("", "tpot.grv", "30d06af7669004f1ea7a99a5ebdb6935", 10469, "trt7g.gjd", nullptr, AD_NO_SIZE, DE_DEU, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// Uncle Henry's Playhouse Beta Version PC English (1996-09-05)
|
||||
UHPENTRY("", "tpot.grv", "123113a26d4bdad6d1f88a53ec6b28a3", 11686, "tpt.gjd", nullptr, AD_NO_SIZE, EN_ANY, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
/*==== Tender Loving Care ====*/
|
||||
// Tender Loving Care PC English (CD-ROM 1998-05-01)
|
||||
TLCENTRY("CD", "tlcmain.grv", "47c235155de5103e72675fe7294720b8", 17479, "tlcnav.gjd", nullptr, AD_NO_SIZE, EN_ANY, kPlatformWindows, ADGF_CD),
|
||||
|
||||
// Tender Loving Care PC English (DVD-ROM 1998-06-12)
|
||||
TLCDVDENTRY("tlcmain.grv", "8a591c47d24dde38615e6ea2e79b51a5", 17375, "tlcnav.gjd", nullptr, AD_NO_SIZE, EN_ANY, kPlatformWindows),
|
||||
|
||||
// Tender Loving Care PC English (DVD-ROM 1998-08-26)
|
||||
TLCDVDENTRY("tlcmain.grv", "151af191015beb6f662919153e6c28d8", 17379, "tlcnav.gjd", nullptr, AD_NO_SIZE, EN_ANY, kPlatformWindows),
|
||||
|
||||
// Tender Loving Care PC German (CD-ROM 1998-04-08)
|
||||
// "Die Versuchung", Funsoft
|
||||
TLCENTRY("CD", "tlcmain.grv", "3459a25a5f31b430d320cba2e47d3c98", 17353, "tlcnav.gjd", nullptr, AD_NO_SIZE, DE_DEU, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// Tender Loving Care PC German (DVD-ROM 1998-08-23)
|
||||
// "Die Versuchung", Conspiracy Entertainment Europe
|
||||
TLCDVDENTRY("tlcmain.grv", "50e62d41ad2cddd0f31ea0a542338387", 17344, "tlcnav.gjd", nullptr, AD_NO_SIZE, DE_DEU, kPlatformWindows),
|
||||
|
||||
// Tender Loving Care PC Demo German (CD-ROM 1998-03-23)
|
||||
// https://archive.org/details/Tender_Loving_Care_demo
|
||||
TLCDEMOENTRY("tlcmain.grv", "6ec818f595eedca6570280af0c681642", 17361, "tlcnav.gjd", nullptr, AD_NO_SIZE, DE_DEU, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// Tender Loving Care PC Trailer (CD-ROM 1998-03-23)
|
||||
// On the same disc with the above German demo
|
||||
TLCTRAILERENTRY("preview.grv", "d95401509a0ef251e8c340737edf728c", 19, "drama1.gjd", "2a4ca274d832675248e51baf7e537bb3", 390727225, UNK_LANG, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
// Tender Loving Care PC Trailer (CD-ROM 1998-05-14)
|
||||
// Included on the English version of the full game. The preview.grv
|
||||
// file is also on the Englissh DVD (1998-09-10), but I do not know if
|
||||
// it actually has the trailer or not. Since the trailer does not
|
||||
// contain any spoken language, it's possible that this entry can be
|
||||
// merged with the German one above.
|
||||
TLCTRAILERENTRY("preview.grv", "d95401509a0ef251e8c340737edf728c", 19, "drama1.gjd", "0ac95ecdd0c8388199bf453de9f7b527", 396742303, UNK_LANG, kPlatformWindows, BASE_FLAGS),
|
||||
|
||||
{AD_TABLE_END_MARKER, kGroovieT7G}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static const char *const directoryGlobs[] = {
|
||||
"MIDI",
|
||||
"GROOVIE",
|
||||
"MEDIA",
|
||||
nullptr
|
||||
};
|
||||
|
||||
class GroovieMetaEngineDetection : public AdvancedMetaEngineDetection<GroovieGameDescription> {
|
||||
public:
|
||||
GroovieMetaEngineDetection() : AdvancedMetaEngineDetection(gameDescriptions, groovieGames) {
|
||||
// Use kADFlagUseExtraAsHint in order to distinguish the 11th hour from
|
||||
// its "Making of" as well as the Clandestiny Trailer; they all share
|
||||
// the same MD5.
|
||||
// TODO: Is this the only reason, or are there others (like the three
|
||||
// potentially sharing a single directory) ? In the former case, then
|
||||
// perhaps a better solution would be to add additional files
|
||||
// to the detection entries. In the latter case, this TODO should be
|
||||
// replaced with an according explanation.
|
||||
_flags = kADFlagUseExtraAsHint;
|
||||
_guiOptions = GUIO2(GUIO_NOSUBTITLES, GUIO_NOASPECT);
|
||||
|
||||
// Need MIDI directory to detect 11H Mac Installed
|
||||
_maxScanDepth = 2;
|
||||
_directoryGlobs = directoryGlobs;
|
||||
}
|
||||
|
||||
const char *getName() const override {
|
||||
return "groovie";
|
||||
}
|
||||
|
||||
const char *getEngineName() const override {
|
||||
return "Groovie";
|
||||
}
|
||||
|
||||
const char *getOriginalCopyright() const override {
|
||||
return "Groovie Engine (C) 1990-1996 Trilobyte";
|
||||
}
|
||||
|
||||
const DebugChannelDef *getDebugChannels() const override {
|
||||
return debugFlagList;
|
||||
}
|
||||
};
|
||||
|
||||
} // End of namespace Groovie
|
||||
|
||||
REGISTER_PLUGIN_STATIC(GROOVIE_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, Groovie::GroovieMetaEngineDetection);
|
||||
55
engines/groovie/detection.h
Normal file
55
engines/groovie/detection.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_DETECTION_H
|
||||
#define GROOVIE_DETECTION_H
|
||||
|
||||
#include "engines/advancedDetector.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
enum EngineVersion {
|
||||
kGroovieT7G,
|
||||
kGroovieT11H,
|
||||
kGroovieCDY,
|
||||
kGroovieUHP,
|
||||
kGroovieTLC
|
||||
};
|
||||
|
||||
struct GroovieGameDescription {
|
||||
AD_GAME_DESCRIPTION_HELPERS(desc);
|
||||
|
||||
ADGameDescription desc;
|
||||
|
||||
EngineVersion version; // Version of the engine
|
||||
};
|
||||
|
||||
#define GAMEOPTION_T7G_FAST_MOVIE_SPEED GUIO_GAMEOPTIONS1
|
||||
#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS2
|
||||
#define GAMEOPTION_EASIER_AI GUIO_GAMEOPTIONS3
|
||||
#define GAMEOPTION_FINAL_HOUR GUIO_GAMEOPTIONS4
|
||||
#define GAMEOPTION_SPEEDRUN GUIO_GAMEOPTIONS5
|
||||
#define GAMEOPTION_EASIER_AI_DEFAULT GUIO_GAMEOPTIONS6
|
||||
#define GAMEOPTION_SLIMHOTSPOTS GUIO_GAMEOPTIONS7
|
||||
|
||||
} // End of namespace Groovie
|
||||
|
||||
#endif
|
||||
134
engines/groovie/font.cpp
Normal file
134
engines/groovie/font.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "graphics/surface.h"
|
||||
|
||||
#include "groovie/font.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
T7GFont::T7GFont() : _maxHeight(0), _maxWidth(0), _glyphs(nullptr) {
|
||||
}
|
||||
|
||||
T7GFont::~T7GFont() {
|
||||
delete[] _glyphs;
|
||||
}
|
||||
|
||||
bool T7GFont::load(Common::SeekableReadStream &stream) {
|
||||
// Read the mapping of characters to glyphs
|
||||
if (stream.read(_mapChar2Glyph, 128) < 128) {
|
||||
error("Groovie::T7GFont: Couldn't read the character to glyph map");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate the number of glyphs
|
||||
byte numGlyphs = 0;
|
||||
for (int i = 0; i < 128; i++)
|
||||
if (_mapChar2Glyph[i] >= numGlyphs)
|
||||
numGlyphs = _mapChar2Glyph[i] + 1;
|
||||
|
||||
// Read the glyph offsets
|
||||
uint16 *glyphOffsets = new uint16[numGlyphs];
|
||||
for (int i = 0; i < numGlyphs; i++)
|
||||
glyphOffsets[i] = stream.readUint16LE();
|
||||
|
||||
if (stream.eos()) {
|
||||
error("Groovie::T7GFont: Couldn't read the glyph offsets");
|
||||
delete[] glyphOffsets;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate the glyph data
|
||||
delete[] _glyphs;
|
||||
_glyphs = new Glyph[numGlyphs];
|
||||
|
||||
// Ensure we're ready to read the first glyph. (Most versions don't
|
||||
// need it, but the russian one does. This fixes bug #5481.)
|
||||
stream.seek(glyphOffsets[0]);
|
||||
|
||||
// Read the glyphs
|
||||
_maxHeight = _maxWidth = 0;
|
||||
for (int i = 0; (i < numGlyphs) && !stream.eos(); i++) {
|
||||
// Verify we're at the expected stream position
|
||||
if (stream.pos() != glyphOffsets[i]) {
|
||||
uint16 offset = glyphOffsets[i];
|
||||
delete[] glyphOffsets;
|
||||
error("Groovie::T7GFont: Glyph %d starts at %d but the current "
|
||||
"offset is %d", i, offset, (int)stream.pos());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the glyph information
|
||||
Glyph *g = &_glyphs[i];
|
||||
g->width = stream.readByte();
|
||||
g->julia = stream.readByte();
|
||||
|
||||
// Read the pixels data into a dynamic array (we don't know its length)
|
||||
Common::Array<byte> data;
|
||||
data.reserve(300);
|
||||
byte b = stream.readByte();
|
||||
while (!stream.eos() && (b != 0xFF)) {
|
||||
data.push_back(b);
|
||||
b = stream.readByte();
|
||||
}
|
||||
|
||||
// Verify the pixel data size
|
||||
assert (data.size() % g->width == 0);
|
||||
g->height = data.size() / g->width;
|
||||
|
||||
// Copy the pixel data into the definitive static array
|
||||
g->pixels = new byte[data.size()];
|
||||
memcpy(g->pixels, data.begin(), data.size());
|
||||
|
||||
// Update the max values
|
||||
if (g->width > _maxWidth)
|
||||
_maxWidth = g->width;
|
||||
if (g->height > _maxHeight)
|
||||
_maxHeight = g->height;
|
||||
}
|
||||
|
||||
delete[] glyphOffsets;
|
||||
return true;
|
||||
}
|
||||
|
||||
void T7GFont::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const {
|
||||
// We ignore the color, as the font is already colored
|
||||
const Glyph *glyph = getGlyph(chr);
|
||||
const byte *src = glyph->pixels;
|
||||
byte *target = (byte *)dst->getBasePtr(x, y);
|
||||
|
||||
for (int i = 0; i < glyph->height; i++) {
|
||||
memcpy(target, src, glyph->width);
|
||||
src += glyph->width;
|
||||
target += dst->pitch;
|
||||
}
|
||||
}
|
||||
|
||||
const T7GFont::Glyph *T7GFont::getGlyph(uint32 chr) const {
|
||||
assert (chr < 128);
|
||||
|
||||
byte numGlyph = _mapChar2Glyph[chr];
|
||||
return &_glyphs[numGlyph];
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
62
engines/groovie/font.h
Normal file
62
engines/groovie/font.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_FONT_H
|
||||
#define GROOVIE_FONT_H
|
||||
|
||||
#include "common/stream.h"
|
||||
#include "graphics/font.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class T7GFont : public Graphics::Font {
|
||||
public:
|
||||
T7GFont();
|
||||
~T7GFont() override;
|
||||
|
||||
bool load(Common::SeekableReadStream &stream);
|
||||
|
||||
int getFontHeight() const override { return _maxHeight; }
|
||||
int getMaxCharWidth() const override { return _maxWidth; }
|
||||
int getCharWidth(uint32 chr) const override { return getGlyph(chr)->width; }
|
||||
void drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const override;
|
||||
|
||||
private:
|
||||
int _maxHeight, _maxWidth;
|
||||
|
||||
struct Glyph {
|
||||
Glyph() : pixels(0), width(0), height(0), julia(0) {}
|
||||
~Glyph() { delete[] pixels; }
|
||||
|
||||
byte width;
|
||||
byte height;
|
||||
byte julia;
|
||||
byte *pixels;
|
||||
};
|
||||
|
||||
byte _mapChar2Glyph[128];
|
||||
Glyph *_glyphs;
|
||||
const Glyph *getGlyph(uint32 chr) const;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_FONT_H
|
||||
200
engines/groovie/graphics.cpp
Normal file
200
engines/groovie/graphics.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/graphics.h"
|
||||
#include "groovie/groovie.h"
|
||||
|
||||
#include "common/rect.h"
|
||||
#include "common/system.h"
|
||||
|
||||
#include "graphics/paletteman.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
GraphicsMan::GraphicsMan(GroovieEngine *vm) :
|
||||
_vm(vm), _changed(false), _fading(0), _fadeStartTime(0) {
|
||||
// Create the game surfaces
|
||||
_foreground.create(640, 320, _vm->_pixelFormat);
|
||||
_background.create(640, 320, _vm->_pixelFormat);
|
||||
_savedground.create(640, 480, _vm->_pixelFormat);
|
||||
}
|
||||
|
||||
GraphicsMan::~GraphicsMan() {
|
||||
// Free the game surfaces
|
||||
_foreground.free();
|
||||
_background.free();
|
||||
_savedground.free();
|
||||
}
|
||||
|
||||
void GraphicsMan::update() {
|
||||
if (_fading) {
|
||||
// Set the start time
|
||||
uint32 time = _vm->_system->getMillis() - _fadeStartTime;
|
||||
|
||||
// Scale the time
|
||||
int step = (time * 20 << 3) / 1000;
|
||||
if (step > 256) {
|
||||
step = 256;
|
||||
}
|
||||
|
||||
// Apply the current fading
|
||||
applyFading(step);
|
||||
|
||||
// Check for the end
|
||||
if (step == 256) {
|
||||
_fading = 0;
|
||||
|
||||
// Clear the buffer when ending the fade out
|
||||
if (_fading == 2)
|
||||
_foreground.fillRect(Common::Rect(640, _foreground.h), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the screen if needed and reset the status
|
||||
if (_changed) {
|
||||
_vm->_system->updateScreen();
|
||||
_changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsMan::switchToFullScreen(bool fullScreen) {
|
||||
// retain the image we currently have, Samantha's moves depend on this
|
||||
_background.copyFrom(_foreground);
|
||||
_foreground.free();
|
||||
|
||||
if (fullScreen) {
|
||||
_foreground.create(640, 480, _vm->_pixelFormat);
|
||||
_foreground.copyRectToSurface(_background, 0, 80, Common::Rect(0, 0, 640, 320));
|
||||
_background.free();
|
||||
_background.create(640, 480, _vm->_pixelFormat);
|
||||
} else {
|
||||
_foreground.create(640, 320, _vm->_pixelFormat);
|
||||
_foreground.copyRectToSurface(_background, 0, 0, Common::Rect(0, 80, 640, 400));
|
||||
_background.free();
|
||||
_background.create(640, 320, _vm->_pixelFormat);
|
||||
}
|
||||
|
||||
_changed = true;
|
||||
}
|
||||
|
||||
void GraphicsMan::change() {
|
||||
_changed = true;
|
||||
}
|
||||
|
||||
void GraphicsMan::mergeFgAndBg() {
|
||||
uint32 i;
|
||||
byte *countf, *countb;
|
||||
|
||||
countf = (byte *)_foreground.getPixels();
|
||||
countb = (byte *)_background.getPixels();
|
||||
for (i = 640 * _foreground.h; i; i--) {
|
||||
if (255 == *(countf)) {
|
||||
*(countf) = *(countb);
|
||||
}
|
||||
countf++;
|
||||
countb++;
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsMan::updateScreen(Graphics::Surface *source) {
|
||||
if (!isFullScreen())
|
||||
_vm->_system->copyRectToScreen(source->getPixels(), source->pitch, 0, 80, 640, 320);
|
||||
else
|
||||
_vm->_system->copyRectToScreen(source->getPixels(), source->pitch, 0, 0, 640, 480);
|
||||
change();
|
||||
}
|
||||
|
||||
void GraphicsMan::saveScreen() {
|
||||
Graphics::Surface *screen = _vm->_system->lockScreen();
|
||||
_vm->_graphicsMan->_savedground.copyFrom(screen->getSubArea(Common::Rect(0, 0, 640, 480)));
|
||||
_vm->_system->unlockScreen();
|
||||
}
|
||||
|
||||
void GraphicsMan::restoreScreen() {
|
||||
_vm->_system->copyRectToScreen(_savedground.getPixels(), _savedground.pitch, 0, 0, 640, 480);
|
||||
change();
|
||||
}
|
||||
|
||||
bool GraphicsMan::isFading() {
|
||||
return _fading;
|
||||
}
|
||||
|
||||
void GraphicsMan::fadeIn(byte *pal) {
|
||||
// Set the start time
|
||||
_fadeStartTime = _vm->_system->getMillis();
|
||||
|
||||
// Copy the target palette
|
||||
memcpy(_paletteFull, pal, 3 * 256);
|
||||
|
||||
// Set the current fading
|
||||
_fading = 1;
|
||||
|
||||
// Apply a black palette right now
|
||||
applyFading(0);
|
||||
}
|
||||
|
||||
void GraphicsMan::fadeOut() {
|
||||
// Set the start time
|
||||
_fadeStartTime = _vm->_system->getMillis();
|
||||
|
||||
// Get the current palette
|
||||
_vm->_system->getPaletteManager()->grabPalette(_paletteFull, 0, 256);
|
||||
|
||||
// Set the current fading
|
||||
_fading = 2;
|
||||
}
|
||||
|
||||
void GraphicsMan::applyFading(int step) {
|
||||
// Calculate the fade factor for the given step
|
||||
int factorR = 0, factorG = 0, factorB = 0;
|
||||
if (_fading == 1) {
|
||||
// Fading in
|
||||
factorR = (step << 2);
|
||||
factorG = (step << 1);
|
||||
factorB = step;
|
||||
if (factorR > 256) factorR = 256;
|
||||
if (factorG > 256) factorG = 256;
|
||||
if (factorB > 256) factorB = 256;
|
||||
} else if (_fading == 2) {
|
||||
// Fading out
|
||||
factorR = 256 - step;
|
||||
factorG = 256 - (step << 1);
|
||||
if (factorR < 0) factorR = 0;
|
||||
if (factorG < 0) factorG = 0;
|
||||
factorB = factorG;
|
||||
}
|
||||
|
||||
// Calculate the new palette
|
||||
byte newpal[256 * 3];
|
||||
for (int i = 0; i < 256; i++) {
|
||||
newpal[(i * 3) + 0] = (_paletteFull[(i * 3) + 0] * factorR) / 256;
|
||||
newpal[(i * 3) + 1] = (_paletteFull[(i * 3) + 1] * factorG) / 256;
|
||||
newpal[(i * 3) + 2] = (_paletteFull[(i * 3) + 2] * factorB) / 256;
|
||||
}
|
||||
|
||||
// Set the screen palette
|
||||
_vm->_system->getPaletteManager()->setPalette(newpal, 0, 256);
|
||||
|
||||
// Request a screen update
|
||||
change();
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
68
engines/groovie/graphics.h
Normal file
68
engines/groovie/graphics.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_GRAPHICS_H
|
||||
#define GROOVIE_GRAPHICS_H
|
||||
|
||||
#include "graphics/surface.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class GroovieEngine;
|
||||
|
||||
class GraphicsMan {
|
||||
public:
|
||||
GraphicsMan(GroovieEngine *vm);
|
||||
~GraphicsMan();
|
||||
|
||||
// Buffers
|
||||
void update();
|
||||
void change();
|
||||
void mergeFgAndBg();
|
||||
void switchToFullScreen(bool fullScreen);
|
||||
bool isFullScreen() { return (_foreground.h == 480); }
|
||||
void updateScreen(Graphics::Surface *source);
|
||||
void saveScreen();
|
||||
void restoreScreen();
|
||||
Graphics::Surface _foreground; // The main surface that most things are drawn to
|
||||
Graphics::Surface _background; // Used occasionally, mostly (only?) in puzzles
|
||||
Graphics::Surface _savedground; // Buffer to save and restore the current screen. Used when opening the gamebook in 11H
|
||||
|
||||
// Palette fading
|
||||
bool isFading();
|
||||
void fadeIn(byte *pal);
|
||||
void fadeOut();
|
||||
|
||||
private:
|
||||
GroovieEngine *_vm;
|
||||
|
||||
bool _changed;
|
||||
|
||||
// Palette fading
|
||||
void applyFading(int step);
|
||||
int _fading;
|
||||
byte _paletteFull[256 * 3];
|
||||
uint32 _fadeStartTime;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_GRAPHICS_H
|
||||
515
engines/groovie/groovie.cpp
Normal file
515
engines/groovie/groovie.cpp
Normal file
@@ -0,0 +1,515 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/cursor.h"
|
||||
#include "groovie/detection.h"
|
||||
#include "groovie/graphics.h"
|
||||
#include "groovie/script.h"
|
||||
#include "groovie/music.h"
|
||||
#include "groovie/resource.h"
|
||||
#include "groovie/video/vdx.h"
|
||||
|
||||
#ifdef ENABLE_GROOVIE2
|
||||
#include "groovie/video/roq.h"
|
||||
#endif
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/debug-channels.h"
|
||||
#include "common/events.h"
|
||||
#include "common/file.h"
|
||||
#include "common/macresman.h"
|
||||
#include "common/compression/stuffit.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "backends/audiocd/audiocd.h"
|
||||
#include "engines/util.h"
|
||||
#include "graphics/fontman.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
const int GroovieEngine::AUTOSAVE_SLOT = MAX_SAVES - 1;
|
||||
|
||||
GroovieEngine::GroovieEngine(OSystem *syst, const GroovieGameDescription *gd) :
|
||||
Engine(syst), _gameDescription(gd), _script(nullptr),
|
||||
_resMan(nullptr), _grvCursorMan(nullptr), _videoPlayer(nullptr), _musicPlayer(nullptr),
|
||||
_graphicsMan(nullptr), _macResFork(nullptr), _waitingForInput(false), _font(nullptr),
|
||||
_spookyMode(false) {
|
||||
|
||||
// Adding the default directories
|
||||
const Common::FSNode gameDataDir(ConfMan.getPath("path"));
|
||||
SearchMan.addSubDirectoryMatching(gameDataDir, "groovie");
|
||||
SearchMan.addSubDirectoryMatching(gameDataDir, "media");
|
||||
SearchMan.addSubDirectoryMatching(gameDataDir, "system");
|
||||
SearchMan.addSubDirectoryMatching(gameDataDir, "MIDI");
|
||||
|
||||
_modeSpeed = kGroovieSpeedNormal;
|
||||
if (ConfMan.hasKey("fast_movie_speed") && ConfMan.getBool("fast_movie_speed"))
|
||||
_modeSpeed = kGroovieSpeedFast;
|
||||
}
|
||||
|
||||
GroovieEngine::~GroovieEngine() {
|
||||
// Delete the remaining objects
|
||||
delete _resMan;
|
||||
delete _grvCursorMan;
|
||||
delete _videoPlayer;
|
||||
delete _musicPlayer;
|
||||
delete _graphicsMan;
|
||||
delete _script;
|
||||
delete _macResFork;
|
||||
}
|
||||
|
||||
Common::Error GroovieEngine::run() {
|
||||
if (_gameDescription->version == kGroovieT11H && getPlatform() == Common::kPlatformMacintosh) {
|
||||
// Load the Mac installer with the lowest priority (in case the user has installed
|
||||
// the game and has the MIDI folder present; faster to just load them)
|
||||
Common::Archive *archive = Common::createStuffItArchive("The 11th Hour Installer", true);
|
||||
|
||||
if (archive)
|
||||
SearchMan.add("The 11th Hour Installer", archive);
|
||||
}
|
||||
|
||||
// TODO: remove this default logging when we're done testing?
|
||||
DebugMan.enableDebugChannel(kDebugScript);
|
||||
DebugMan.enableDebugChannel(kDebugScriptvars);
|
||||
DebugMan.enableDebugChannel(kDebugLogic);
|
||||
DebugMan.enableDebugChannel(kDebugVideo);
|
||||
if (gDebugLevel < 0)
|
||||
gDebugLevel = 0;
|
||||
|
||||
_script = new Script(this, _gameDescription->version);
|
||||
|
||||
// Initialize the graphics
|
||||
switch (_gameDescription->version) {
|
||||
case kGroovieT11H:
|
||||
case kGroovieCDY:
|
||||
case kGroovieUHP:
|
||||
case kGroovieTLC: {
|
||||
// Request the mode with the highest precision available
|
||||
Graphics::PixelFormat format(4, 8, 8, 8, 8, 24, 16, 8, 0);
|
||||
initGraphics(640, 480, &format);
|
||||
|
||||
if (_system->getScreenFormat() != format)
|
||||
return Common::kUnsupportedColorMode;
|
||||
|
||||
// Save the enabled mode
|
||||
_pixelFormat = format;
|
||||
break;
|
||||
}
|
||||
case kGroovieT7G:
|
||||
initGraphics(640, 480);
|
||||
_pixelFormat = Graphics::PixelFormat::createFormatCLUT8();
|
||||
break;
|
||||
|
||||
default:
|
||||
error("GROOVIE: Unknown Game version. groovie.cpp:run()");
|
||||
}
|
||||
|
||||
// Create debugger. It requires GFX to be initialized
|
||||
Debugger *debugger = new Debugger(this);
|
||||
setDebugger(debugger);
|
||||
_script->setDebugger(debugger);
|
||||
|
||||
// Create the graphics manager
|
||||
_graphicsMan = new GraphicsMan(this);
|
||||
|
||||
// Create the resource and cursor managers and the video player
|
||||
// Prepare the font too
|
||||
switch (_gameDescription->version) {
|
||||
case kGroovieT7G:
|
||||
if (getPlatform() == Common::kPlatformMacintosh) {
|
||||
_macResFork = new Common::MacResManager();
|
||||
if (!_macResFork->open(_gameDescription->desc.filesDescriptions[0].fileName))
|
||||
error("Could not open %s as a resource fork", _gameDescription->desc.filesDescriptions[0].fileName);
|
||||
// The Macintosh release used system fonts. We use GUI fonts.
|
||||
_font = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
|
||||
} else {
|
||||
Common::File fontfile;
|
||||
if (!fontfile.open("sphinx.fnt")) {
|
||||
error("Couldn't open sphinx.fnt");
|
||||
return Common::kNoGameDataFoundError;
|
||||
} else if (!_sphinxFont.load(fontfile)) {
|
||||
error("Error loading sphinx.fnt");
|
||||
return Common::kUnknownError;
|
||||
}
|
||||
fontfile.close();
|
||||
_font = &_sphinxFont;
|
||||
}
|
||||
|
||||
_resMan = new ResMan_t7g(_macResFork);
|
||||
_grvCursorMan = new GrvCursorMan_t7g(_system, _macResFork);
|
||||
_videoPlayer = new VDXPlayer(this);
|
||||
break;
|
||||
|
||||
case kGroovieT11H:
|
||||
case kGroovieCDY:
|
||||
case kGroovieUHP:
|
||||
case kGroovieTLC:
|
||||
_font = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
|
||||
_resMan = new ResMan_v2();
|
||||
_grvCursorMan = new GrvCursorMan_v2(_system);
|
||||
#ifdef ENABLE_GROOVIE2
|
||||
_videoPlayer = new ROQPlayer(this);
|
||||
_soundQueue.setVM(this);
|
||||
#endif
|
||||
break;
|
||||
|
||||
default:
|
||||
error("GROOVIE: Unknown Game version. groovie.cpp:run()");
|
||||
}
|
||||
|
||||
|
||||
switch (_gameDescription->version) {
|
||||
case kGroovieT7G:
|
||||
// Detect ScummVM Music Enhancement Project presence (T7G only)
|
||||
if (Common::File::exists("gu16.ogg")) {
|
||||
// Load player for external files
|
||||
_musicPlayer = new MusicPlayerIOS(this);
|
||||
break;
|
||||
}
|
||||
// else, fall through
|
||||
case kGroovieT11H:
|
||||
// Create the music player
|
||||
switch (getPlatform()) {
|
||||
case Common::kPlatformMacintosh:
|
||||
if (_gameDescription->version == kGroovieT7G)
|
||||
_musicPlayer = new MusicPlayerMac_t7g(this);
|
||||
else
|
||||
_musicPlayer = new MusicPlayerMac_v2(this);
|
||||
break;
|
||||
case Common::kPlatformIOS:
|
||||
_musicPlayer = new MusicPlayerIOS(this);
|
||||
break;
|
||||
default:
|
||||
_musicPlayer = new MusicPlayerXMI(this, _gameDescription->version == kGroovieT7G ? "fat" : "sample");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case kGroovieCDY:
|
||||
case kGroovieUHP:
|
||||
_musicPlayer = new MusicPlayerClan(this);
|
||||
break;
|
||||
|
||||
case kGroovieTLC:
|
||||
_musicPlayer = new MusicPlayerTlc(this);
|
||||
break;
|
||||
}
|
||||
|
||||
// Load volume levels
|
||||
syncSoundSettings();
|
||||
|
||||
// Get the name of the main script
|
||||
Common::String filename;
|
||||
if (_gameDescription->version == kGroovieT7G) {
|
||||
filename = "script.grv";
|
||||
// Run The 7th Guest's demo if requested
|
||||
if (ConfMan.hasKey("demo_mode") && ConfMan.getBool("demo_mode"))
|
||||
filename = "demo.grv";
|
||||
} else {
|
||||
filename = _gameDescription->desc.filesDescriptions[0].fileName;
|
||||
}
|
||||
|
||||
// Check the script file extension
|
||||
if (!filename.hasSuffixIgnoreCase(".grv")) {
|
||||
error("%s isn't a valid script filename", filename.c_str());
|
||||
return Common::kUnknownError;
|
||||
}
|
||||
|
||||
// Load the script
|
||||
if (!_script->loadScript(filename)) {
|
||||
error("Couldn't load the script file %s", filename.c_str());
|
||||
return Common::kUnknownError;
|
||||
}
|
||||
|
||||
// Should I load a saved game?
|
||||
if (ConfMan.hasKey("save_slot")) {
|
||||
// Get the requested slot
|
||||
int slot = ConfMan.getInt("save_slot");
|
||||
_script->directGameLoad(slot);
|
||||
}
|
||||
|
||||
// Game timer counter
|
||||
int tmr = 0;
|
||||
|
||||
// Check that the game files and the audio tracks aren't together run from
|
||||
// the same cd
|
||||
if (getPlatform() != Common::kPlatformIOS && _gameDescription->version == kGroovieT7G) {
|
||||
if (!existExtractedCDAudioFiles()
|
||||
&& !isDataAndCDAudioReadFromSameCD()) {
|
||||
warnMissingExtractedCDAudio();
|
||||
}
|
||||
_system->getAudioCDManager()->open();
|
||||
}
|
||||
|
||||
while (!shouldQuit()) {
|
||||
// Handle input
|
||||
Common::Event ev;
|
||||
while (_eventMan->pollEvent(ev)) {
|
||||
switch (ev.type) {
|
||||
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
|
||||
_script->setAction(ev.customType);
|
||||
break;
|
||||
case Common::EVENT_KEYDOWN:
|
||||
// Send the event to the scripts
|
||||
_script->setKbdChar(ev.kbd.ascii);
|
||||
|
||||
// Continue the script execution to handle the key
|
||||
_waitingForInput = false;
|
||||
break;
|
||||
|
||||
case Common::EVENT_MAINMENU:
|
||||
// Closing the GMM
|
||||
case Common::EVENT_MOUSEMOVE:
|
||||
// Continue the script execution, the mouse pointer
|
||||
// may fall inside a different hotspot now
|
||||
_waitingForInput = false;
|
||||
break;
|
||||
|
||||
case Common::EVENT_LBUTTONDOWN:
|
||||
// Send the event to the scripts
|
||||
_script->setMouseClick(1);
|
||||
|
||||
// Continue the script execution to handle
|
||||
// the click
|
||||
_waitingForInput = false;
|
||||
break;
|
||||
|
||||
case Common::EVENT_RBUTTONDOWN:
|
||||
// Send the event to the scripts (to skip the video)
|
||||
_script->setMouseClick(2);
|
||||
break;
|
||||
|
||||
case Common::EVENT_QUIT:
|
||||
quitGame();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The event loop may have triggered the quit status. In this case,
|
||||
// stop the execution.
|
||||
if (shouldQuit()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_graphicsMan->isFading()) {
|
||||
// We're waiting for a fading to end, let the CPU rest
|
||||
// for a while and continue
|
||||
_system->delayMillis(10);
|
||||
} else {
|
||||
if (_waitingForInput) {
|
||||
// Still waiting for input, just update the mouse, game timer and then wait a bit more
|
||||
_grvCursorMan->animate();
|
||||
_system->updateScreen();
|
||||
|
||||
// Wait a little bit between increments. While mouse is moving, this triggers
|
||||
// only negligably slower.
|
||||
if (tmr >= 500) {
|
||||
_script->timerTick();
|
||||
tmr = 0;
|
||||
}
|
||||
|
||||
_system->delayMillis(10);
|
||||
tmr += 10;
|
||||
|
||||
// the script doesn't unset _waitingForInput
|
||||
// so we unset it here in order to let the script run as many steps as it needs to
|
||||
// this makes the game more responsive
|
||||
_waitingForInput = false;
|
||||
}
|
||||
|
||||
// Everything's fine, execute another script step
|
||||
_script->step();
|
||||
}
|
||||
|
||||
// Update the screen if required
|
||||
_graphicsMan->update();
|
||||
_soundQueue.tick();
|
||||
}
|
||||
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
void GroovieEngine::pauseEngineIntern(bool pause) {
|
||||
Engine::pauseEngineIntern(pause);
|
||||
if (_musicPlayer)
|
||||
_musicPlayer->pause(pause);
|
||||
}
|
||||
|
||||
Common::Platform GroovieEngine::getPlatform() const {
|
||||
return _gameDescription->desc.platform;
|
||||
}
|
||||
|
||||
EngineVersion GroovieEngine::getEngineVersion() const {
|
||||
return _gameDescription->version;
|
||||
}
|
||||
|
||||
bool GroovieEngine::hasFeature(EngineFeature f) const {
|
||||
return
|
||||
(f == kSupportsReturnToLauncher) ||
|
||||
(f == kSupportsSavingDuringRuntime) ||
|
||||
(f == kSupportsLoadingDuringRuntime);
|
||||
}
|
||||
|
||||
bool GroovieEngine::canLaunchLoad() const {
|
||||
if (_gameDescription->desc.guiOptions == nullptr)
|
||||
return false;
|
||||
return strstr(_gameDescription->desc.guiOptions, GUIO_NOLAUNCHLOAD) != nullptr;
|
||||
}
|
||||
|
||||
bool GroovieEngine::isDemo() const {
|
||||
return _gameDescription->desc.flags & ADGF_DEMO;
|
||||
}
|
||||
|
||||
void GroovieEngine::syncSoundSettings() {
|
||||
Engine::syncSoundSettings();
|
||||
|
||||
bool mute = ConfMan.getBool("mute");
|
||||
|
||||
// Set the music volume
|
||||
_musicPlayer->setUserVolume(mute ? 0 : ConfMan.getInt("music_volume"));
|
||||
|
||||
// Videos just contain one digital audio track, which can be used for
|
||||
// both SFX or Speech, but the engine doesn't know what they contain, so
|
||||
// we have to use just one volume setting for videos.
|
||||
// We use "speech" because most users will want to change the videos
|
||||
// volume when they can't hear the speech because of the music.
|
||||
_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType,
|
||||
mute ? 0 : ConfMan.getInt("speech_volume"));
|
||||
}
|
||||
|
||||
bool GroovieEngine::canLoadGameStateCurrently(Common::U32String *msg) {
|
||||
// TODO: verify the engine has been initialized
|
||||
if (isDemo())
|
||||
return false;
|
||||
if (_script)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GroovieEngine::canSaveGameStateCurrently(Common::U32String *msg) {
|
||||
// TODO: verify the engine has been initialized
|
||||
if (isDemo())
|
||||
return false;
|
||||
if (_script)
|
||||
return _script->canDirectSave();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::Error GroovieEngine::loadGameState(int slot) {
|
||||
_script->directGameLoad(slot);
|
||||
|
||||
// TODO: Use specific error codes
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
Common::Error GroovieEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
|
||||
_script->directGameSave(slot,desc);
|
||||
|
||||
// TODO: Use specific error codes
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
int GroovieEngine::getAutosaveSlot() const {
|
||||
return AUTOSAVE_SLOT;
|
||||
}
|
||||
|
||||
void GroovieEngine::waitForInput() {
|
||||
_waitingForInput = true;
|
||||
}
|
||||
|
||||
SoundEffectQueue::SoundEffectQueue() {
|
||||
_vm = nullptr;
|
||||
_player = nullptr;
|
||||
_file = nullptr;
|
||||
}
|
||||
|
||||
void SoundEffectQueue::setVM(GroovieEngine *vm) {
|
||||
_vm = vm;
|
||||
#ifdef ENABLE_GROOVIE2
|
||||
_player = new ROQSoundPlayer(vm);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SoundEffectQueue::queue(Common::SeekableReadStream *soundfile, uint32 loops) {
|
||||
if (_queue.size() > 20) {
|
||||
stopAll();
|
||||
}
|
||||
_queue.push({soundfile, loops});
|
||||
for (uint32 i = 1; i < loops; i++) {
|
||||
_queue.push({soundfile, loops});
|
||||
}
|
||||
tick();
|
||||
}
|
||||
|
||||
void SoundEffectQueue::tick() {
|
||||
#ifdef ENABLE_GROOVIE2
|
||||
if (_file && !_player->playFrame()) {
|
||||
_vm->_script->setBitFlag(0, true);
|
||||
return;
|
||||
}
|
||||
if (_queue.size() == 0) {
|
||||
deleteFile();
|
||||
return;
|
||||
}
|
||||
|
||||
SoundQueueEntry entry = _queue.front();
|
||||
if (entry._loops != 0 || _queue.size() > 1) {
|
||||
_queue.pop();
|
||||
}
|
||||
if (_file != entry._file) {
|
||||
deleteFile();
|
||||
}
|
||||
_file = entry._file;
|
||||
|
||||
_vm->_script->setBitFlag(0, true);
|
||||
_file->seek(0);
|
||||
_player->load(_file, 0);
|
||||
_player->playFrame();
|
||||
if (_player->isFastForwarding()) {
|
||||
stopAll();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SoundEffectQueue::deleteFile() {
|
||||
if (_file) {
|
||||
delete _file;
|
||||
_file = nullptr;
|
||||
_vm->_script->setBitFlag(0, false);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEffectQueue::stopAll() {
|
||||
if (_file && _player) {
|
||||
_player->stopAudioStream();
|
||||
}
|
||||
_queue.clear();
|
||||
deleteFile();
|
||||
}
|
||||
|
||||
} // End of namespace Groovie
|
||||
171
engines/groovie/groovie.h
Normal file
171
engines/groovie/groovie.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_GROOVIE_H
|
||||
#define GROOVIE_GROOVIE_H
|
||||
|
||||
#include "groovie/debug.h"
|
||||
#include "groovie/font.h"
|
||||
|
||||
#include "engines/engine.h"
|
||||
#include "graphics/pixelformat.h"
|
||||
#include "groovie/detection.h"
|
||||
|
||||
namespace Common {
|
||||
class MacResManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the namespace of the Groovie engine.
|
||||
*
|
||||
* Status of this engine: This engine supports both versions of the Groovie
|
||||
* game engine. The 7th Guest uses the first revision of Groovie, and is
|
||||
* now fully completable. All remaining Groovie games use V2 of the engine,
|
||||
* which is under slow development.
|
||||
*
|
||||
* Games using this engine:
|
||||
* - The 7th Guest (completable)
|
||||
* - The 11th Hour
|
||||
* - Clandestiny
|
||||
* - Uncle Henry's Playhouse
|
||||
* - Tender Loving Care
|
||||
*/
|
||||
namespace Groovie {
|
||||
|
||||
class GraphicsMan;
|
||||
class GrvCursorMan;
|
||||
class MusicPlayer;
|
||||
class ResMan;
|
||||
class Script;
|
||||
class VideoPlayer;
|
||||
|
||||
enum DebugLevels {
|
||||
kDebugVideo = 1,
|
||||
kDebugResource,
|
||||
kDebugScript,
|
||||
kDebugUnknown,
|
||||
kDebugHotspots,
|
||||
kDebugCursor,
|
||||
kDebugMIDI,
|
||||
kDebugScriptvars,
|
||||
kDebugLogic,
|
||||
kDebugFast,
|
||||
//kDebugTlcGame,
|
||||
};
|
||||
|
||||
/**
|
||||
* This enum reflects the available movie speed settings:
|
||||
* - Normal: play videos at a normal speed
|
||||
* - Fast: play videos with audio at a fast speed. Videos without audio,
|
||||
* like teeth animations, are played at their regular speed to avoid
|
||||
* audio sync issues
|
||||
*/
|
||||
enum GameSpeed {
|
||||
kGroovieSpeedNormal,
|
||||
kGroovieSpeedFast
|
||||
};
|
||||
|
||||
enum GROOVIEAction {
|
||||
kActionNone,
|
||||
kActionSkip
|
||||
};
|
||||
|
||||
#define MAX_SAVES 25
|
||||
|
||||
struct GroovieGameDescription;
|
||||
|
||||
struct SoundQueueEntry {
|
||||
Common::SeekableReadStream *_file;
|
||||
uint32 _loops;
|
||||
};
|
||||
|
||||
class SoundEffectQueue {
|
||||
public:
|
||||
SoundEffectQueue();
|
||||
void setVM(GroovieEngine *vm);
|
||||
void queue(Common::SeekableReadStream *soundfile, uint32 loops);
|
||||
void tick();
|
||||
void stopAll();
|
||||
|
||||
protected:
|
||||
void deleteFile();
|
||||
VideoPlayer *_player;
|
||||
GroovieEngine *_vm;
|
||||
Common::Queue<SoundQueueEntry> _queue;
|
||||
Common::SeekableReadStream *_file;
|
||||
};
|
||||
|
||||
class GroovieEngine : public Engine {
|
||||
public:
|
||||
static const int AUTOSAVE_SLOT;
|
||||
|
||||
GroovieEngine(OSystem *syst, const GroovieGameDescription *gd);
|
||||
~GroovieEngine() override;
|
||||
|
||||
Common::Platform getPlatform() const;
|
||||
EngineVersion getEngineVersion() const;
|
||||
|
||||
int getAutosaveSlot() const override;
|
||||
bool canLaunchLoad() const;
|
||||
bool isDemo() const;
|
||||
|
||||
protected:
|
||||
|
||||
// Engine APIs
|
||||
Common::Error run() override;
|
||||
void pauseEngineIntern(bool pause) override;
|
||||
|
||||
bool hasFeature(EngineFeature f) const override;
|
||||
|
||||
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
|
||||
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
|
||||
Common::Error loadGameState(int slot) override;
|
||||
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
|
||||
void syncSoundSettings() override;
|
||||
|
||||
public:
|
||||
void waitForInput();
|
||||
bool isWaitingForInput() { return _waitingForInput; }
|
||||
|
||||
Graphics::PixelFormat _pixelFormat;
|
||||
bool _spookyMode;
|
||||
Script *_script;
|
||||
ResMan *_resMan;
|
||||
GrvCursorMan *_grvCursorMan;
|
||||
VideoPlayer *_videoPlayer;
|
||||
SoundEffectQueue _soundQueue;
|
||||
MusicPlayer *_musicPlayer;
|
||||
GraphicsMan *_graphicsMan;
|
||||
const Graphics::Font *_font;
|
||||
|
||||
Common::MacResManager *_macResFork;
|
||||
|
||||
GameSpeed _modeSpeed;
|
||||
|
||||
private:
|
||||
const GroovieGameDescription *_gameDescription;
|
||||
bool _waitingForInput;
|
||||
T7GFont _sphinxFont;
|
||||
};
|
||||
|
||||
} // End of namespace Groovie
|
||||
|
||||
#endif // GROOVIE_GROOVIE_H
|
||||
840
engines/groovie/logic/beehive.cpp
Normal file
840
engines/groovie/logic/beehive.cpp
Normal file
@@ -0,0 +1,840 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/logic/beehive.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
namespace {
|
||||
extern const int8 beehiveLogicTable1[368];
|
||||
extern const int8 beehiveLogicTable2[800];
|
||||
}
|
||||
|
||||
void BeehiveGame::overrideClick(byte *vars) {
|
||||
if (overrideIndex >= overrideMoves.size())
|
||||
return;
|
||||
|
||||
int move = overrideMoves[overrideIndex];
|
||||
vars[0] = move / 10;
|
||||
vars[1] = move % 10;
|
||||
}
|
||||
|
||||
void BeehiveGame::overrideMove(byte *vars) {
|
||||
if (overrideIndex >= overrideMoves.size())
|
||||
return;
|
||||
|
||||
int from = overrideMoves[overrideIndex++];
|
||||
int to = overrideMoves[overrideIndex++];
|
||||
vars[0] = from / 10;
|
||||
vars[1] = from % 10;
|
||||
|
||||
vars[2] = to / 10;
|
||||
vars[3] = to % 10;
|
||||
}
|
||||
|
||||
void BeehiveGame::run(byte *scriptVariables) {
|
||||
int8 *hexagons = (int8 *)scriptVariables + 25;
|
||||
int8 *hexDifference = (int8 *)scriptVariables + 13;
|
||||
byte op = scriptVariables[14] - 1;
|
||||
|
||||
enum kBeehiveColor {
|
||||
kBeehiveColorYellow = -1,
|
||||
kBeehiveColorRed = 1
|
||||
};
|
||||
|
||||
debugC(1, kDebugLogic, "Beehive subop %d", op);
|
||||
|
||||
int8 v21, v22, v24;
|
||||
int8 tempState[64];
|
||||
|
||||
// init hexDifference on every iteration
|
||||
*hexDifference = 4;
|
||||
|
||||
switch (op) {
|
||||
case 0: // Init board's hexagons
|
||||
_maxDepth = 4;
|
||||
memset(_beehiveState, 0, HEXCOUNT);
|
||||
_beehiveState[0] = kBeehiveColorYellow;
|
||||
_beehiveState[4] = kBeehiveColorRed;
|
||||
_beehiveState[34] = kBeehiveColorYellow;
|
||||
_beehiveState[60] = kBeehiveColorRed;
|
||||
_beehiveState[56] = kBeehiveColorYellow;
|
||||
_beehiveState[26] = kBeehiveColorRed;
|
||||
return;
|
||||
|
||||
case 1:
|
||||
memset(hexagons, 0, HEXCOUNT);
|
||||
scriptVariables[85] = 0;
|
||||
sub02(&v22, tempState);
|
||||
if (v22) {
|
||||
for (int i = 0; i < v22; i++)
|
||||
scriptVariables[tempState[i] + 25] = kBeehiveColorRed;
|
||||
} else {
|
||||
*hexDifference = getHexDifference();
|
||||
}
|
||||
return;
|
||||
|
||||
case 2: // Player clicks on a honey-filled (source) hexagon
|
||||
memset(hexagons, 0, HEXCOUNT);
|
||||
scriptVariables[85] = 0;
|
||||
//overrideClick(scriptVariables);
|
||||
v24 = 10 * scriptVariables[0] + scriptVariables[1];
|
||||
debugC(2, kDebugLogic, "Beehive player clicked %d", (int)v24);
|
||||
selectSourceHexagon(v24, &v22, tempState);
|
||||
for (int j = 0; j < v22; j++)
|
||||
scriptVariables[tempState[j] + 25] = kBeehiveColorRed;
|
||||
scriptVariables[v24 + 25] = kBeehiveColorRed;
|
||||
return;
|
||||
|
||||
case 3: // Player moves into an empty (destination) hexagon
|
||||
scriptVariables[24] = 1;
|
||||
scriptVariables[4] = 2;
|
||||
overrideMove(scriptVariables);
|
||||
v24 = 10 * scriptVariables[0] + scriptVariables[1];
|
||||
v22 = 10 * scriptVariables[2] + scriptVariables[3];
|
||||
debugC(1, kDebugLogic, "Beehive player moved from %d, to %d", (int)v24, (int)v22);
|
||||
sub16(v24, v22, hexDifference, (int8 *)scriptVariables + 16, (int8 *)scriptVariables + 17);
|
||||
scriptVariables[15] = scriptVariables[16];
|
||||
sub04(v24, v22, (int8 *)scriptVariables);
|
||||
return;
|
||||
|
||||
case 4: // Stauf plays
|
||||
scriptVariables[24] = 1;
|
||||
scriptVariables[4] = 1;
|
||||
calcStaufMove(&v24, &v22, hexDifference, &v21, (int8 *)scriptVariables + 16, (int8 *)scriptVariables + 17);
|
||||
// Execute method tail
|
||||
break;
|
||||
|
||||
case 5: // Calculate board state after every move
|
||||
if (scriptVariables[24] == 1) {
|
||||
scriptVariables[0] = scriptVariables[2];
|
||||
scriptVariables[1] = scriptVariables[3];
|
||||
scriptVariables[24] = 0;
|
||||
}
|
||||
|
||||
if (scriptVariables[16]) {
|
||||
int8 v16 = scriptVariables[16] - 1;
|
||||
*hexDifference = 1;
|
||||
scriptVariables[16] = v16;
|
||||
v24 = 10 * scriptVariables[0] + scriptVariables[1];
|
||||
int8 v23 = scriptVariables[v16 + 17];
|
||||
scriptVariables[2] = v23 / 10;
|
||||
scriptVariables[3] = v23 % 10;
|
||||
sub04(v24, v23, (int8 *)scriptVariables);
|
||||
} else {
|
||||
*hexDifference = 4 - (scriptVariables[4] == 2 ? 1 : 0);
|
||||
}
|
||||
return;
|
||||
|
||||
case 6:
|
||||
scriptVariables[24] = 1;
|
||||
scriptVariables[4] = 2;
|
||||
calcSamanthaMove(&v24, &v22, hexDifference, &v21, (int8 *)scriptVariables + 16, (int8 *)scriptVariables + 17);
|
||||
// Execute method tail
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (v24 == -1) {
|
||||
*hexDifference = getHexDifference();
|
||||
} else {
|
||||
scriptVariables[0] = v24 / 10;
|
||||
scriptVariables[1] = v24 % 10;
|
||||
scriptVariables[2] = v22 / 10;
|
||||
scriptVariables[3] = v22 % 10;
|
||||
sub04(v24, v22, (int8 *)scriptVariables);
|
||||
}
|
||||
}
|
||||
|
||||
void BeehiveGame::sub02(int8 *a1, int8 *a2) {
|
||||
int8 v9 = -1;
|
||||
|
||||
*a1 = 0;
|
||||
|
||||
while (findCell(_beehiveState, &v9, -1)) {
|
||||
bool v3 = false;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (v3)
|
||||
break;
|
||||
|
||||
int8 move = beehiveLogicTable1[6 * v9 + i];
|
||||
if (move != -1 && !_beehiveState[move]) {
|
||||
a2[*a1] = v9;
|
||||
v3 = true;
|
||||
++*a1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (v3)
|
||||
break;
|
||||
|
||||
int8 move = beehiveLogicTable2[12 * v9 + i];
|
||||
if (move != -1 && !_beehiveState[move]) {
|
||||
a2[*a1] = v9;
|
||||
v3 = true;
|
||||
++*a1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!*a1) {
|
||||
for (int i = 0; i < HEXCOUNT; ++i)
|
||||
if (!_beehiveState[i])
|
||||
_beehiveState[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void BeehiveGame::sub04(int8 a1, int8 a2, int8 *scriptVariables) {
|
||||
int v3 = 0;
|
||||
if (scriptVariables[13] == 1) {
|
||||
if (beehiveLogicTable1[6 * a1] != a2) {
|
||||
for (; v3 < 5; v3++) {
|
||||
if (beehiveLogicTable1[6 * a1 + v3] == a2)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int v7 = v3 + 12;
|
||||
scriptVariables[5] = v7 / 10;
|
||||
scriptVariables[6] = v7 % 10;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
scriptVariables[10] = 0;
|
||||
scriptVariables[7] = 0;
|
||||
if (beehiveLogicTable2[12 * a1] != a2) {
|
||||
for (; v3 < 11; v3++) {
|
||||
if (beehiveLogicTable2[12 * a1 + v3] == a2)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
scriptVariables[5] = v3 / 10;
|
||||
int8 v5 = -1;
|
||||
int8 v6 = -1;
|
||||
scriptVariables[6] = v3 % 10;
|
||||
|
||||
switch (v3) {
|
||||
case 0:
|
||||
v6 = beehiveLogicTable1[6 * a1];
|
||||
break;
|
||||
case 1:
|
||||
v5 = beehiveLogicTable1[6 * a1];
|
||||
// fall through
|
||||
case 2:
|
||||
v6 = beehiveLogicTable1[6 * a1 + 1];
|
||||
break;
|
||||
case 3:
|
||||
v5 = beehiveLogicTable1[6 * a1 + 1];
|
||||
// fall through
|
||||
case 4:
|
||||
v6 = beehiveLogicTable1[6 * a1 + 2];
|
||||
break;
|
||||
case 5:
|
||||
v5 = beehiveLogicTable1[6 * a1 + 2];
|
||||
// fall through
|
||||
case 6:
|
||||
v6 = beehiveLogicTable1[6 * a1 + 3];
|
||||
break;
|
||||
case 7:
|
||||
v5 = beehiveLogicTable1[6 * a1 + 3];
|
||||
// fall through
|
||||
case 8:
|
||||
v6 = beehiveLogicTable1[6 * a1 + 4];
|
||||
break;
|
||||
case 9:
|
||||
v5 = beehiveLogicTable1[6 * a1 + 4];
|
||||
// fall through
|
||||
case 10:
|
||||
v6 = beehiveLogicTable1[6 * a1 + 5];
|
||||
break;
|
||||
case 11:
|
||||
v6 = beehiveLogicTable1[6 * a1 + 5];
|
||||
v5 = beehiveLogicTable1[6 * a1];
|
||||
break;
|
||||
default:
|
||||
v6 = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
int8 v4 = 0;
|
||||
|
||||
if (v5 != -1)
|
||||
v4 = _beehiveState[v5];
|
||||
|
||||
if (_beehiveState[v6]) {
|
||||
scriptVariables[8] = v6 / 10;
|
||||
scriptVariables[9] = v6 % 10;
|
||||
scriptVariables[7] = 2 - (_beehiveState[v6] == 1 ? 1 : 0);
|
||||
}
|
||||
|
||||
if (v4) {
|
||||
scriptVariables[11] = v5 / 10;
|
||||
scriptVariables[12] = v5 % 10;
|
||||
scriptVariables[10] = 2 - (v4 == 1 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void BeehiveGame::calcSamanthaMove(int8 *a1, int8 *a2, int8 *a3, int8 *a4, int8 *a5, int8 *a6) {
|
||||
int8 params[4];
|
||||
|
||||
*a4 = 0;
|
||||
_maxDepth = 5;// in the original game Samantha did 4 like Stauf
|
||||
|
||||
if (calcMove(_beehiveState, -125, -1, _maxDepth, 0, params) == 125
|
||||
&& (*a4 = 1, calcMove(_beehiveState, -125, -1, _maxDepth, 1, params) == 125)) {
|
||||
*a1 = -1;
|
||||
*a2 = -1;
|
||||
for (int i = 0; i < HEXCOUNT; ++i) {
|
||||
if (!_beehiveState[i])
|
||||
_beehiveState[i] = 1;
|
||||
}
|
||||
} else {
|
||||
*a1 = params[1];
|
||||
*a2 = params[2];
|
||||
*a3 = params[0];
|
||||
sub17(_beehiveState, -1, params, a5, a6);
|
||||
}
|
||||
}
|
||||
|
||||
void BeehiveGame::calcStaufMove(int8 *a1, int8 *a2, int8 *a3, int8 *a4, int8 *a5, int8 *a6) {
|
||||
int8 params[4];
|
||||
|
||||
*a4 = 0;
|
||||
|
||||
_maxDepth = 4;
|
||||
if (_easierAi) {
|
||||
int numPieces = 0;
|
||||
for (int i = 0; i < HEXCOUNT; ++i)
|
||||
numPieces += _beehiveState[i] != 0;
|
||||
|
||||
if (numPieces < 16)
|
||||
_maxDepth = 3;
|
||||
else
|
||||
_maxDepth = 1;
|
||||
}
|
||||
|
||||
if (calcMove(_beehiveState, 125, 1, _maxDepth, 0, params) == -125
|
||||
&& (*a4 = 1, calcMove(_beehiveState, 125, 1, _maxDepth, 1, params) == -125)) {
|
||||
*a1 = -1;
|
||||
*a2 = -1;
|
||||
for (int i = 0; i < HEXCOUNT; ++i) {
|
||||
if (!_beehiveState[i])
|
||||
_beehiveState[i] = -1;
|
||||
}
|
||||
} else {
|
||||
*a1 = params[1];
|
||||
*a2 = params[2];
|
||||
*a3 = params[0];
|
||||
sub17(_beehiveState, 1, params, a5, a6);
|
||||
}
|
||||
}
|
||||
|
||||
int8 BeehiveGame::sub11(int8 *beehiveState, int8 *a2, int8 *a3, int8 *a4, int8 a5, int8 a6, int8 *a7) {
|
||||
if (*a2 == -1) {
|
||||
if (!findCell(beehiveState, a2, a5))
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8 v16 = 0;
|
||||
|
||||
while (1) {
|
||||
while (1) {
|
||||
if (v16)
|
||||
return 1;
|
||||
|
||||
for (; *a3 < 6; (*a3)++) {
|
||||
if (v16)
|
||||
break;
|
||||
|
||||
int8 v9 = beehiveLogicTable1[6 * *a2 + *a3];
|
||||
|
||||
if (v9 != -1 && !beehiveState[v9] && *a2 < sub12(beehiveState, a5, v9, *a2)) {
|
||||
v16 = 1;
|
||||
*a7 = 1;
|
||||
a7[1] = *a2;
|
||||
a7[2] = beehiveLogicTable1[6 * *a2 + *a3];
|
||||
}
|
||||
}
|
||||
|
||||
if (*a4 >= 12)
|
||||
break;
|
||||
|
||||
while (!v16) {
|
||||
int8 v11 = beehiveLogicTable2[12 * *a2 + *a4];
|
||||
|
||||
if (v11 != -1
|
||||
&& !beehiveState[v11]
|
||||
&& !sub13(beehiveState, v11, a5)
|
||||
&& sub13(beehiveState, beehiveLogicTable2[12 * *a2 + *a4], -a5)) {
|
||||
int8 v12 = sub13(beehiveState, *a2, -a5);
|
||||
int8 v13 = *a4 >> 1;
|
||||
int8 v14 = ~(1 << v13) & v12;
|
||||
|
||||
if ((*a4 & 1) != 0) {
|
||||
if (v13 == 5)
|
||||
v14 &= ~1u;
|
||||
else
|
||||
v14 &= ~(1 << (v13 + 1));
|
||||
}
|
||||
|
||||
if (!v14 || !sub13(beehiveState, *a2, a5) || a6) {
|
||||
v16 = 1;
|
||||
*a7 = 2;
|
||||
a7[1] = *a2;
|
||||
a7[2] = beehiveLogicTable2[12 * *a2 + *a4];
|
||||
}
|
||||
}
|
||||
|
||||
(*a4)++;
|
||||
if (*a4 >= 12)
|
||||
break;
|
||||
}
|
||||
|
||||
if (*a4 >= 12)
|
||||
break;
|
||||
}
|
||||
|
||||
if (v16)
|
||||
return 1;
|
||||
|
||||
if (!findCell(beehiveState, a2, a5))
|
||||
return 0;
|
||||
|
||||
*a3 = 0;
|
||||
*a4 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int8 BeehiveGame::sub12(int8 *beehiveState, int8 a2, int8 a3, int8 a4) {
|
||||
int8 result = 125;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int8 v7 = beehiveLogicTable1[i + 6 * a3];
|
||||
|
||||
if (v7 != -1 && beehiveState[v7] == a2 && a4 != v7 && result > v7)
|
||||
result = beehiveLogicTable1[i + 6 * a3];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int8 BeehiveGame::sub13(int8 *beehiveState, int8 a2, int8 a3) {
|
||||
int result = 0;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int8 v5 = beehiveLogicTable1[6 * a2 + i];
|
||||
|
||||
if (v5 != -1 && beehiveState[v5] == a3)
|
||||
result |= 1 << i;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void BeehiveGame::sub15(int8 *beehiveState, int8 a2, int8 *a3) {
|
||||
beehiveState[a3[2]] = a2;
|
||||
|
||||
if (*a3 == 2)
|
||||
beehiveState[a3[1]] = 0;
|
||||
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
int8 v4 = beehiveLogicTable1[6 * a3[2] + i];
|
||||
if (v4 != -1) {
|
||||
if (!(a2 + beehiveState[v4]))
|
||||
beehiveState[v4] = a2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BeehiveGame::sub16(int8 a1, int8 a2, int8 *a3, int8 *a4, int8 *a5) {
|
||||
int8 params[4];
|
||||
|
||||
params[0] = sub19(a1, a2);
|
||||
params[1] = a1;
|
||||
params[2] = a2;
|
||||
*a3 = params[0];
|
||||
sub17(_beehiveState, -1, params, a4, a5);
|
||||
}
|
||||
|
||||
void BeehiveGame::sub17(int8 *beehiveState, int8 a2, int8 *a3, int8 *a4, int8 *a5) {
|
||||
beehiveState[a3[2]] = a2;
|
||||
|
||||
if (*a3 == 2)
|
||||
beehiveState[a3[1]] = 0;
|
||||
|
||||
*a4 = 0;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int8 v6 = beehiveLogicTable1[6 * a3[2] + i];
|
||||
if (v6 != -1) {
|
||||
if (!(a2 + beehiveState[v6])) {
|
||||
beehiveState[v6] = a2;
|
||||
a5[(*a4)++] = beehiveLogicTable1[6 * a3[2] + i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BeehiveGame::selectSourceHexagon(int8 a1, int8 *a2, int8 *a3) {
|
||||
*a2 = 0;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int8 val = beehiveLogicTable1[6 * a1 + i];
|
||||
if (val != -1 && !_beehiveState[val])
|
||||
a3[(*a2)++] = val;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
int val = beehiveLogicTable2[12 * a1 + i];
|
||||
if (val != -1 && !_beehiveState[val])
|
||||
a3[(*a2)++] = val;
|
||||
}
|
||||
}
|
||||
|
||||
int8 BeehiveGame::sub19(int8 a1, int8 a2) {
|
||||
for (int i = 0; i < 6; i++)
|
||||
if (beehiveLogicTable1[6 * a1 + i] == a2)
|
||||
return 1;
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int8 BeehiveGame::calcMove(int8 *beehiveState, int8 a2, int8 a3, int8 depth, int a5, int8 *params) {
|
||||
int8 paramsloc[4];
|
||||
int8 params2[3];
|
||||
int8 state[64];
|
||||
|
||||
if (!depth)
|
||||
return getTotal(beehiveState);
|
||||
|
||||
int8 v7 = -125 * a3;
|
||||
int8 v14 = 0;
|
||||
int8 v13 = 0;
|
||||
int8 v15 = -1;
|
||||
|
||||
if (sub11(beehiveState, &v15, &v14, &v13, a3, a5, params2)) {
|
||||
do {
|
||||
for (int i = 0; i < HEXCOUNT; i++)
|
||||
state[i] = beehiveState[i];
|
||||
|
||||
sub15(state, a3, params2);
|
||||
int8 v8 = calcMove(state, v7, -a3, depth - 1, a5, paramsloc);
|
||||
|
||||
if (a3 <= 0) {
|
||||
if (v8 < v7) {
|
||||
params[0] = params2[0];
|
||||
params[1] = params2[1];
|
||||
params[2] = params2[2];
|
||||
v7 = v8;
|
||||
}
|
||||
if (a2 >= v7)
|
||||
return v7;
|
||||
} else {
|
||||
if (v8 > v7) {
|
||||
params[0] = params2[0];
|
||||
params[1] = params2[1];
|
||||
params[2] = params2[2];
|
||||
v7 = v8;
|
||||
}
|
||||
if (a2 <= v7)
|
||||
return v7;
|
||||
}
|
||||
} while (sub11(beehiveState, &v15, &v14, &v13, a3, a5, params2));
|
||||
}
|
||||
|
||||
if (depth < _maxDepth && -125 * a3 == v7)
|
||||
return getTotal(beehiveState);
|
||||
else
|
||||
return v7;
|
||||
}
|
||||
|
||||
int8 BeehiveGame::getHexDifference() {
|
||||
return (getTotal(_beehiveState) >= 0 ? 1 : 0) + 5;
|
||||
}
|
||||
|
||||
int8 BeehiveGame::getTotal(int8 *hexagons) {
|
||||
int8 result = 0;
|
||||
|
||||
for (int i = 0; i < HEXCOUNT; i++)
|
||||
result += hexagons[i];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int8 BeehiveGame::findCell(int8 *beehiveState, int8 *pos, int8 key) {
|
||||
for (int i = *pos + 1; i < HEXCOUNT; i++) {
|
||||
if (beehiveState[i] == key) {
|
||||
*pos = i;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const int8 beehiveLogicTable1[368] = {
|
||||
-1, 5, 6, 1, -1, -1,
|
||||
0, 6, 7, 2, -1, -1,
|
||||
1, 7, 8, 3, -1, -1,
|
||||
2, 8, 9, 4, -1, -1,
|
||||
3, 9, 10, -1, -1, -1,
|
||||
-1, 11, 12, 6, 0, -1,
|
||||
5, 12, 13, 7, 1, 0,
|
||||
6, 13, 14, 8, 2, 1,
|
||||
7, 14, 15, 9, 3, 2,
|
||||
8, 15, 16, 10, 4, 3,
|
||||
9, 16, 17, -1, -1, 4,
|
||||
-1, 18, 19, 12, 5, -1,
|
||||
11, 19, 20, 13, 6, 5,
|
||||
12, 20, 21, 14, 7, 6,
|
||||
13, 21, 22, 15, 8, 7,
|
||||
14, 22, 23, 16, 9, 8,
|
||||
15, 23, 24, 17, 10, 9,
|
||||
16, 24, 25, -1, -1, 10,
|
||||
-1, 26, 27, 19, 11, -1,
|
||||
18, 27, 28, 20, 12, 11,
|
||||
19, 28, 29, 21, 13, 12,
|
||||
20, 29, 30, 22, 14, 13,
|
||||
21, 30, 31, 23, 15, 14,
|
||||
22, 31, 32, 24, 16, 15,
|
||||
23, 32, 33, 25, 17, 16,
|
||||
24, 33, 34, -1, -1, 17,
|
||||
-1, -1, 35, 27, 18, -1,
|
||||
26, 35, 36, 28, 19, 18,
|
||||
27, 36, 37, 29, 20, 19,
|
||||
28, 37, 38, 30, 21, 20,
|
||||
29, 38, 39, 31, 22, 21,
|
||||
30, 39, 40, 32, 23, 22,
|
||||
31, 40, 41, 33, 24, 23,
|
||||
32, 41, 42, 34, 25, 24,
|
||||
33, 42, -1, -1, -1, 25,
|
||||
-1, -1, 43, 36, 27, 26,
|
||||
35, 43, 44, 37, 28, 27,
|
||||
36, 44, 45, 38, 29, 28,
|
||||
37, 45, 46, 39, 30, 29,
|
||||
38, 46, 47, 40, 31, 30,
|
||||
39, 47, 48, 41, 32, 31,
|
||||
40, 48, 49, 42, 33, 32,
|
||||
41, 49, -1, -1, 34, 33,
|
||||
-1, -1, 50, 44, 36, 35,
|
||||
43, 50, 51, 45, 37, 36,
|
||||
44, 51, 52, 46, 38, 37,
|
||||
45, 52, 53, 47, 39, 38,
|
||||
46, 53, 54, 48, 40, 39,
|
||||
47, 54, 55, 49, 41, 40,
|
||||
48, 55, -1, -1, 42, 41,
|
||||
-1, -1, 56, 51, 44, 43,
|
||||
50, 56, 57, 52, 45, 44,
|
||||
51, 57, 58, 53, 46, 45,
|
||||
52, 58, 59, 54, 47, 46,
|
||||
53, 59, 60, 55, 48, 47,
|
||||
54, 60, -1, -1, 49, 48,
|
||||
-1, -1, -1, 57, 51, 50,
|
||||
56, -1, -1, 58, 52, 51,
|
||||
57, -1, -1, 59, 53, 52,
|
||||
58, -1, -1, 60, 54, 53,
|
||||
59, -1, -1, -1, 55, 54,
|
||||
0, 0
|
||||
};
|
||||
|
||||
const int8 beehiveLogicTable2[800] = {
|
||||
-1, -1, 11, 12, 13, 7, 2, -1, -1, -1, -1, -1,
|
||||
-1, 5, 12, 13, 14, 8, 3, -1, -1, -1, -1, -1,
|
||||
0, 6, 13, 14, 15, 9, 4, -1, -1, -1, -1, -1,
|
||||
1, 7, 14, 15, 16, 10, -1, -1, -1, -1, -1, -1,
|
||||
2, 8, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, 18, 19, 20, 13, 7, 1, -1, -1, -1, -1,
|
||||
-1, 11, 19, 20, 21, 14, 8, 2, -1, -1, -1, -1,
|
||||
5, 12, 20, 21, 22, 15, 9, 3, -1, -1, -1, 0,
|
||||
6, 13, 21, 22, 23, 16, 10, 4, -1, -1, -1, 1,
|
||||
7, 14, 22, 23, 24, 17, -1, -1, -1, -1, -1, 2,
|
||||
8, 15, 23, 24, 25, -1, -1, -1, -1, -1, -1, 3,
|
||||
-1, -1, 26, 27, 28, 20, 13, 6, 0, -1, -1, -1,
|
||||
-1, 18, 27, 28, 29, 21, 14, 7, 1, 0, -1, -1,
|
||||
11, 19, 28, 29, 30, 22, 15, 8, 2, 1, 0, 5,
|
||||
12, 20, 29, 30, 31, 23, 16, 9, 3, 2, 1, 6,
|
||||
13, 21, 30, 31, 32, 24, 17, 10, 4, 3, 2, 7,
|
||||
14, 22, 31, 32, 33, 25, -1, -1, -1, 4, 3, 8,
|
||||
15, 23, 32, 33, 34, -1, -1, -1, -1, -1, 4, 9,
|
||||
-1, -1, -1, 35, 36, 28, 20, 12, 5, -1, -1, -1,
|
||||
-1, 26, 35, 36, 37, 29, 21, 13, 6, 5, -1, -1,
|
||||
18, 27, 36, 37, 38, 30, 22, 14, 7, 6, 5, 11,
|
||||
19, 28, 37, 38, 39, 31, 23, 15, 8, 7, 6, 12,
|
||||
20, 29, 38, 39, 40, 32, 24, 16, 9, 8, 7, 13,
|
||||
21, 30, 39, 40, 41, 33, 25, 17, 10, 9, 8, 14,
|
||||
22, 31, 40, 41, 42, 34, -1, -1, -1, 10, 9, 15,
|
||||
23, 32, 41, 42, -1, -1, -1, -1, -1, -1, 10, 16,
|
||||
-1, -1, -1, -1, 43, 36, 28, 19, 11, -1, -1, -1,
|
||||
-1, -1, -1, 43, 44, 37, 29, 20, 12, 11, -1, -1,
|
||||
26, 35, 43, 44, 45, 38, 30, 21, 13, 12, 11, 18,
|
||||
27, 36, 44, 45, 46, 39, 31, 22, 14, 13, 12, 19,
|
||||
28, 37, 45, 46, 47, 40, 32, 23, 15, 14, 13, 20,
|
||||
29, 38, 46, 47, 48, 41, 33, 24, 16, 15, 14, 21,
|
||||
30, 39, 47, 48, 49, 42, 34, 25, 17, 16, 15, 22,
|
||||
31, 40, 48, 49, -1, -1, -1, -1, -1, 17, 16, 23,
|
||||
32, 41, 49, -1, -1, -1, -1, -1, -1, -1, 17, 24,
|
||||
-1, -1, -1, -1, 50, 44, 37, 28, 19, 18, -1, -1,
|
||||
-1, -1, -1, 50, 51, 45, 38, 29, 20, 19, 18, 26,
|
||||
35, 43, 50, 51, 52, 46, 39, 30, 21, 20, 19, 27,
|
||||
36, 44, 51, 52, 53, 47, 40, 31, 22, 21, 20, 28,
|
||||
37, 45, 52, 53, 54, 48, 41, 32, 23, 22, 21, 29,
|
||||
38, 46, 53, 54, 55, 49, 42, 33, 24, 23, 22, 30,
|
||||
39, 47, 54, 55, -1, -1, -1, 34, 25, 24, 23, 31,
|
||||
40, 48, 55, -1, -1, -1, -1, -1, -1, 25, 24, 32,
|
||||
-1, -1, -1, -1, 56, 51, 45, 37, 28, 27, 26, -1,
|
||||
-1, -1, -1, 56, 57, 52, 46, 38, 29, 28, 27, 35,
|
||||
43, 50, 56, 57, 58, 53, 47, 39, 30, 29, 28, 36,
|
||||
44, 51, 57, 58, 59, 54, 48, 40, 31, 30, 29, 37,
|
||||
45, 52, 58, 59, 60, 55, 49, 41, 32, 31, 30, 38,
|
||||
46, 53, 59, 60, -1, -1, -1, 42, 33, 32, 31, 39,
|
||||
47, 54, 60, -1, -1, -1, -1, -1, 34, 33, 32, 40,
|
||||
-1, -1, -1, -1, -1, 57, 52, 45, 37, 36, 35, -1,
|
||||
-1, -1, -1, -1, -1, 58, 53, 46, 38, 37, 36, 43,
|
||||
50, 56, -1, -1, -1, 59, 54, 47, 39, 38, 37, 44,
|
||||
51, 57, -1, -1, -1, 60, 55, 48, 40, 39, 38, 45,
|
||||
52, 58, -1, -1, -1, -1, -1, 49, 41, 40, 39, 46,
|
||||
53, 59, -1, -1, -1, -1, -1, -1, 42, 41, 40, 47,
|
||||
-1, -1, -1, -1, -1, -1, 58, 52, 45, 44, 43, -1,
|
||||
-1, -1, -1, -1, -1, -1, 59, 53, 46, 45, 44, 50,
|
||||
56, -1, -1, -1, -1, -1, 60, 54, 47, 46, 45, 51,
|
||||
57, -1, -1, -1, -1, -1, -1, 55, 48, 47, 46, 52,
|
||||
58, -1, -1, -1, -1, -1, -1, -1, 49, 48, 47, 53,
|
||||
0, 0, 0, 0, 26, 18, 35, 11, 27, 43, 5, 19,
|
||||
36, 50, 0, 12, 28, 44, 56, 6, 20, 37, 51, 1,
|
||||
13, 29, 45, 57, 7, 21, 38, 52, 2, 14, 30, 46,
|
||||
58, 8, 22, 39, 53, 3, 15, 31, 47, 59, 9, 23,
|
||||
40, 54, 4, 16, 32, 48, 60, 10, 24, 41, 55, 17,
|
||||
33, 49, 25, 42, 34, 0, 0, 0
|
||||
};
|
||||
|
||||
} // End of anonymous namespace
|
||||
|
||||
void BeehiveGame::testGame(Common::Array<int> moves, bool playerWin) {
|
||||
byte vars[1024];
|
||||
memset(vars, 0, sizeof(vars));
|
||||
|
||||
int8 &hexDifference = ((int8 *)vars)[13];
|
||||
byte &op = vars[14];// can't do the -1 with a reference
|
||||
byte &counter = vars[16];
|
||||
|
||||
op = 1;
|
||||
run(vars);
|
||||
op = 2;
|
||||
run(vars);
|
||||
|
||||
for (uint i = 0; i < moves.size(); i += 2) {
|
||||
int from = moves[i];
|
||||
int to = moves[i + 1];
|
||||
|
||||
op = 3;
|
||||
vars[0] = from / 10;
|
||||
vars[1] = from % 10;
|
||||
run(vars);
|
||||
|
||||
op = 4;
|
||||
vars[0] = from / 10;
|
||||
vars[1] = from % 10;
|
||||
vars[2] = to / 10;
|
||||
vars[3] = to % 10;
|
||||
run(vars);
|
||||
|
||||
while (counter) {
|
||||
op = 6;
|
||||
run(vars);
|
||||
}
|
||||
op = 6;
|
||||
run(vars);
|
||||
|
||||
if (i + 2 < moves.size() && hexDifference == 6) {
|
||||
error("early Stauf win");
|
||||
} else if (i + 2 < moves.size() && hexDifference == 5) {
|
||||
error("early player win");
|
||||
}
|
||||
|
||||
op = 5;
|
||||
run(vars);
|
||||
|
||||
while (counter) {
|
||||
op = 6;
|
||||
run(vars);
|
||||
}
|
||||
op = 6;
|
||||
run(vars);
|
||||
|
||||
op = 2;
|
||||
run(vars);
|
||||
|
||||
if (i + 2 < moves.size() && hexDifference == 6) {
|
||||
error("early Stauf win");
|
||||
} else if (i + 2 < moves.size() && hexDifference == 5) {
|
||||
error("early player win");
|
||||
}
|
||||
}
|
||||
|
||||
if (playerWin && hexDifference != 5)
|
||||
error("player didn't win");
|
||||
if (playerWin == false && hexDifference != 6)
|
||||
error("Stauf didn't win");
|
||||
}
|
||||
|
||||
void BeehiveGame::tests() {
|
||||
warning("starting BeehiveGame::tests()");
|
||||
// 8 moves per line, in from and to pairs
|
||||
|
||||
// speedrun strat
|
||||
testGame({
|
||||
/**/ 34, 42, /**/ 56, 50, /**/ 50, 35, /**/ 42, 55, /**/ 34, 42, /**/ 42, 49, /**/ 35, 43, /**/ 43, 50,
|
||||
/**/ 50, 51, /**/ 51, 52, /**/ 52, 53, /**/ 53, 54, /**/ 52, 57, /**/ 52, 46, /**/ 34, 25, /**/ 34, 24,
|
||||
/**/ 25, 23, /**/ 46, 31, /**/ 31, 30, /**/ 52, 38, /**/ 29, 12, /**/ 31, 39, /**/ 35, 28, /**/ 49, 32,
|
||||
/**/ 31, 40, /**/ 39, 47, /**/ 20, 19, /**/ 29, 37, /**/ 57, 58, /**/ 53, 46, /**/ 53, 52
|
||||
}, true);
|
||||
|
||||
// losing game
|
||||
testGame({
|
||||
/**/ 34, 25, /**/ 25, 10, /**/ 34, 17, /**/ 0, 2, /**/ 56, 57, /**/ 57, 51, /**/ 51, 50, /**/ 51, 52,
|
||||
/**/ 51, 44, /**/ 50, 43, /**/ 50, 35, /**/ 36, 38, /**/ 35, 37, /**/ 38, 39, /**/ 38, 29, /**/ 45, 58,
|
||||
/**/ 58, 59, /**/ 57, 45, /**/ 44, 35, /**/ 35, 26, /**/ 46, 54, /**/ 59, 60, /**/ 59, 55, /**/ 55, 40,
|
||||
/**/ 39, 23
|
||||
}, false);
|
||||
|
||||
// copy the moveset from one of the tests to play it out yourself
|
||||
overrideMoves = {};
|
||||
overrideIndex = 0;
|
||||
|
||||
warning("finished BeehiveGame::tests()");
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
90
engines/groovie/logic/beehive.h
Normal file
90
engines/groovie/logic/beehive.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_LOGIC_BEEHIVE_H
|
||||
#define GROOVIE_LOGIC_BEEHIVE_H
|
||||
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
/*
|
||||
* Beehive (Blood and Honey) puzzle (hs.grv)
|
||||
*
|
||||
* An infection-style game in which the player must cover more
|
||||
* territory than the computer. It's similar to the microscope puzzle
|
||||
* in the 7th Guest. The playfield is a honeycomb made of 61
|
||||
* hexagons. The hexagons are numbered starting from the top-left
|
||||
* corner, with a direction from bottom left to top right.
|
||||
*/
|
||||
class BeehiveGame {
|
||||
public:
|
||||
BeehiveGame(bool easierAi) {
|
||||
#if 0
|
||||
_easierAi = false;
|
||||
tests();
|
||||
#endif
|
||||
_easierAi = easierAi;
|
||||
}
|
||||
~BeehiveGame() {}
|
||||
|
||||
void run(byte *scriptVariables);
|
||||
|
||||
private:
|
||||
void sub02(int8 *a1, int8 *a2);
|
||||
void sub04(int8 a1, int8 a2, int8 *scriptVariables);
|
||||
void calcSamanthaMove(int8 *a1, int8 *a2, int8 *a3, int8 *a4, int8 *a5, int8 *a6);
|
||||
void calcStaufMove(int8 *a1, int8 *a2, int8 *a3, int8 *a4, int8 *a5, int8 *a6);
|
||||
int8 sub11(int8 *beehiveState, int8 *a2, int8 *a3, int8 *a4, int8 a5, int8 a6, int8 *a7);
|
||||
int8 sub12(int8 *beehiveState, int8 a2, int8 a3, int8 a4);
|
||||
int8 sub13(int8 *beehiveState, int8 a2, int8 a3);
|
||||
void sub15(int8 *beehiveState, int8 a2, int8 *a3);
|
||||
void sub16(int8 a1, int8 a2, int8 *a3, int8 *a4, int8 *a5);
|
||||
void sub17(int8 *beehiveState, int8 a2, int8 *a3, int8 *a4, int8 *a5);
|
||||
void selectSourceHexagon(int8 a1, int8 *a2, int8 *a3);
|
||||
int8 sub19(int8 a1, int8 a2);
|
||||
int8 getHexDifference();
|
||||
int8 getTotal(int8 *hexagons);
|
||||
int8 calcMove(int8 *beehiveState, int8 a2, int8 a3, int8 depth, int a5, int8 *a6);
|
||||
int8 findCell(int8 *beehiveState, int8 *pos, int8 key);
|
||||
void testGame(Common::Array<int> moves, bool playerWin);
|
||||
void tests();
|
||||
void overrideClick(byte *vars);
|
||||
void overrideMove(byte *vars);
|
||||
|
||||
#define HEXCOUNT 61
|
||||
int8 _beehiveState[HEXCOUNT];
|
||||
|
||||
Common::Array<int> overrideMoves;
|
||||
uint overrideIndex;
|
||||
bool _easierAi;
|
||||
int8 _maxDepth;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_LOGIC_BEEHIVE_H
|
||||
427
engines/groovie/logic/cake.cpp
Normal file
427
engines/groovie/logic/cake.cpp
Normal file
@@ -0,0 +1,427 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/logic/cake.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
/*
|
||||
* T11hCake() constructor
|
||||
* - Each spot on the board is part of multiple potential victory lines
|
||||
* - The first x and y dimensions of the loops select the origin point of the line
|
||||
* - The z is for the distance along that line
|
||||
* - Then we push_back the id number of the line into the array at _map.indecies[x][y]
|
||||
* - This is used in UpdateScores()
|
||||
* .
|
||||
* @see UpdateScores()
|
||||
*/
|
||||
CakeGame::CakeGame(bool easierAi) : _random("CakeGame") {
|
||||
restart();
|
||||
|
||||
_map = {};
|
||||
int numLines = 0;
|
||||
|
||||
// map all the lines with slope of (1, 0)
|
||||
for (int y = 0; y < HEIGHT; y++) {
|
||||
for (int x = 0; x <= WIDTH - GOAL_LEN; x++) {
|
||||
for (int z = 0; z < GOAL_LEN; z++) {
|
||||
setLineNum(x + z, y, numLines);
|
||||
}
|
||||
numLines++;
|
||||
}
|
||||
}
|
||||
|
||||
// map all the lines with slope of (0, 1)
|
||||
for (int x = 0; x < WIDTH; x++) {
|
||||
for (int y = 0; y <= HEIGHT - GOAL_LEN; y++) {
|
||||
for (int z = 0; z < GOAL_LEN; z++) {
|
||||
setLineNum(x, y + z, numLines);
|
||||
}
|
||||
numLines++;
|
||||
}
|
||||
}
|
||||
|
||||
// map all the lines with slope of (1,1)
|
||||
for (int y = 0; y <= HEIGHT - GOAL_LEN; y++) {
|
||||
for (int x = 0; x <= WIDTH - GOAL_LEN; x++) {
|
||||
for (int z = 0; z < GOAL_LEN; z++) {
|
||||
setLineNum(x + z, y + z, numLines);
|
||||
}
|
||||
numLines++;
|
||||
}
|
||||
}
|
||||
|
||||
// map all the lines with slope of (1,-1)
|
||||
for (int y = GOAL_LEN - 1; y < HEIGHT; y++) {
|
||||
for (int x = 0; x <= WIDTH - GOAL_LEN; x++) {
|
||||
for (int z = 0; z < GOAL_LEN; z++) {
|
||||
setLineNum(x + z, y - z, numLines);
|
||||
}
|
||||
numLines++;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
_easierAi = false;
|
||||
testCake();
|
||||
#endif
|
||||
|
||||
_easierAi = easierAi;
|
||||
}
|
||||
|
||||
void CakeGame::run(byte *scriptVariables) {
|
||||
byte &lastMove = scriptVariables[1];
|
||||
byte &winner = scriptVariables[3];
|
||||
winner = 0;
|
||||
|
||||
if (lastMove == 8) {
|
||||
restart();
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastMove == 9) {
|
||||
// samantha makes a move
|
||||
lastMove = aiGetBestMove(6);
|
||||
_hasCheated = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isColumnFull(lastMove)) {
|
||||
warning("player tried to place a bon bon in a full column, last_move: %d", (int)lastMove);
|
||||
lastMove = 10;
|
||||
return;
|
||||
}
|
||||
|
||||
placeBonBon(lastMove);
|
||||
winner = getWinner();
|
||||
if (winner) {
|
||||
return;
|
||||
}
|
||||
|
||||
int depth = 4 + (_hasCheated == false);
|
||||
if (_easierAi && _moveCount > 8)
|
||||
depth = 3;
|
||||
else if (_easierAi)
|
||||
depth = 2;
|
||||
|
||||
lastMove = aiGetBestMove(depth);
|
||||
placeBonBon(lastMove);
|
||||
if (gameEnded()) {
|
||||
winner = STAUF;
|
||||
}
|
||||
}
|
||||
|
||||
void CakeGame::restart() {
|
||||
_playerProgress = {};
|
||||
_staufProgress = {};
|
||||
memset(_boardState, 0, sizeof(_boardState));
|
||||
memset(_columnHeights, 0, sizeof(_columnHeights));
|
||||
_moveCount = 0;
|
||||
_hasCheated = false;
|
||||
|
||||
_playerProgress._score = NUM_LINES;
|
||||
_staufProgress._score = NUM_LINES;
|
||||
}
|
||||
|
||||
void CakeGame::setLineNum(uint x, uint y, uint index) {
|
||||
assert(x < WIDTH);
|
||||
assert(y < HEIGHT);
|
||||
byte slot = _map.lengths[x][y]++;
|
||||
assert(slot < GOAL_LEN * GOAL_LEN);
|
||||
assert(index < NUM_LINES);
|
||||
_map.indecies[x][y][slot] = index;
|
||||
}
|
||||
|
||||
bool CakeGame::isColumnFull(byte column) {
|
||||
return _columnHeights[column] >= HEIGHT;
|
||||
}
|
||||
|
||||
CakeGame::PlayerProgress &CakeGame::getPlayerProgress(bool stauf) {
|
||||
if (stauf)
|
||||
return _staufProgress;
|
||||
else
|
||||
return _playerProgress;
|
||||
}
|
||||
|
||||
/*
|
||||
* UpdateScores()
|
||||
* - Each PlayerProgress has an array of ints, _linesCounters[], where each entry maps to the ID of a line
|
||||
* - When a bon bon is added to the board, we look up _map.lengths[x][y] and then loop through all the indecies for that point
|
||||
* - Increment the PlayerProgress._linesCounters[id]
|
||||
* - Calculate the scores proportional to the PlayerProgress._linesCounters[id]
|
||||
* .
|
||||
* .
|
||||
*/
|
||||
void CakeGame::updateScores(byte x, bool revert) {
|
||||
bool stauf = _moveCount % 2;
|
||||
PlayerProgress &pp = getPlayerProgress(stauf);
|
||||
|
||||
byte y = _columnHeights[x] - 1;
|
||||
|
||||
// get the number of potential victory lines that this spot exists in
|
||||
int num_lines = _map.lengths[x][y];
|
||||
|
||||
for (int line = 0; line < num_lines; line++) {
|
||||
// get the ID for this potential victory line
|
||||
int index = _map.indecies[x][y][line];
|
||||
int len = pp._linesCounters[index];
|
||||
|
||||
// add this new bon bon to the progress of this potential victory line, or remove in the case of revert
|
||||
int mult = 1;// mult is used for multiplying the score gains, depends on revert
|
||||
if (!revert)
|
||||
pp._linesCounters[index]++;
|
||||
else {
|
||||
len = --pp._linesCounters[index];
|
||||
mult = -1;
|
||||
}
|
||||
|
||||
if (GOAL_LEN == len + 1) {
|
||||
// that's a bingo
|
||||
pp._score += WIN_SCORE * mult;
|
||||
}
|
||||
else {
|
||||
PlayerProgress &pp2 = getPlayerProgress(!stauf);
|
||||
int len2 = pp2._linesCounters[index];
|
||||
if (len == 0) {
|
||||
// we started a new line, take away the points the opponent had from this line since we ruined it for them
|
||||
pp2._score -= (1 << (len2 & 31)) * mult;
|
||||
}
|
||||
if (len2 == 0) {
|
||||
// the opponent doesn't have any spots in this line, so we get points for it
|
||||
pp._score += (1 << (len & 31)) * mult;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CakeGame::placeBonBon(byte x) {
|
||||
byte y = _columnHeights[x]++;
|
||||
if (_moveCount % 2)
|
||||
_boardState[x][y] = STAUF;
|
||||
else
|
||||
_boardState[x][y] = PLAYER;
|
||||
|
||||
updateScores(x);
|
||||
|
||||
_moveCount++;
|
||||
}
|
||||
|
||||
void CakeGame::revertMove(byte x) {
|
||||
// PlaceBonBon in reverse, this is used for the AI's recursion rollback
|
||||
_moveCount--;
|
||||
|
||||
updateScores(x, true);
|
||||
|
||||
byte y = --_columnHeights[x];
|
||||
_boardState[x][y] = 0;
|
||||
}
|
||||
|
||||
byte CakeGame::getWinner() {
|
||||
if (_playerProgress._score >= WIN_SCORE)
|
||||
return PLAYER;
|
||||
|
||||
if (_staufProgress._score >= WIN_SCORE)
|
||||
return STAUF;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CakeGame::gameEnded() {
|
||||
if (getWinner())
|
||||
return true;
|
||||
|
||||
if (_moveCount >= WIDTH * HEIGHT)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int CakeGame::getScoreDiff() {
|
||||
if (_moveCount % 2)
|
||||
return _staufProgress._score - _playerProgress._score;
|
||||
else
|
||||
return _playerProgress._score - _staufProgress._score;
|
||||
}
|
||||
|
||||
int CakeGame::aiRecurse(int search_depth, int parent_score) {
|
||||
int best_score = 0x7fffffff;
|
||||
|
||||
for (byte move = 0; move < WIDTH; move++) {
|
||||
if (isColumnFull(move))
|
||||
continue;
|
||||
|
||||
placeBonBon(move);
|
||||
int score = getScoreDiff();
|
||||
if (search_depth > 1 && !gameEnded())
|
||||
score = aiRecurse(search_depth - 1, best_score);
|
||||
revertMove(move);
|
||||
|
||||
if (score < best_score)
|
||||
best_score = score;
|
||||
|
||||
if (-parent_score != best_score && parent_score <= -best_score)
|
||||
break;
|
||||
}
|
||||
|
||||
// we negate the score because from the perspective of our parent caller, this is his opponent's score
|
||||
return -best_score;
|
||||
}
|
||||
|
||||
byte CakeGame::aiGetBestMove(int search_depth) {
|
||||
int best_move = 0xffff;
|
||||
uint counter = 1;
|
||||
|
||||
for (int best_score = 0x7fffffff; best_score > 999999 && search_depth > 1; search_depth--) {
|
||||
for (byte move = 0; move < WIDTH; move++) {
|
||||
if (isColumnFull(move))
|
||||
continue;
|
||||
|
||||
placeBonBon(move);
|
||||
if (getWinner()) {
|
||||
revertMove(move);
|
||||
return move;
|
||||
}
|
||||
|
||||
int score = aiRecurse(search_depth - 1, best_score);
|
||||
revertMove(move);
|
||||
if (score < best_score) {
|
||||
counter = 1;
|
||||
best_move = move;
|
||||
best_score = score;
|
||||
} else if (best_score == score) {
|
||||
// rng is only used on moves with equal scores
|
||||
counter++;
|
||||
uint r = _random.getRandomNumber(1000000 - 1);
|
||||
if (r * counter < 1000000) {
|
||||
best_move = move;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best_move;
|
||||
}
|
||||
|
||||
void CakeGame::testCake() {
|
||||
warning("starting CakeGame::testCake()");
|
||||
uint32 oldSeed = _random.getSeed();
|
||||
// test the draw condition, grouped by column
|
||||
runCakeTestNoAi(/*move 1*/ "7777777" /*8*/ "6666666" /*15*/ "5555555" /*22*/ "34444444" /*30*/ "333333" /*36*/ "2222222" /*43*/ "01111111" /*51*/ "000000", false, true);
|
||||
|
||||
runCakeTest(9, "24223233041", true);
|
||||
runCakeTest(1, "232232432445", false);
|
||||
runCakeTest(123, "4453766355133466", false);
|
||||
|
||||
_random.setSeed(oldSeed);
|
||||
warning("finished CakeGame::testCake()");
|
||||
}
|
||||
|
||||
void CakeGame::runCakeTestNoAi(const char *moves, bool playerWin, bool draw = false) {
|
||||
warning("starting runCakeTestNoAi(%s, %d)", moves, (int)playerWin);
|
||||
|
||||
restart();
|
||||
|
||||
for (int i = 0; moves[i]; i++) {
|
||||
byte win = getWinner();
|
||||
if (win) {
|
||||
error("early win at %d, winner: %d", i, (int)win);
|
||||
}
|
||||
if (gameEnded()) {
|
||||
error("early draw at %d", i);
|
||||
}
|
||||
byte move = moves[i] - '0';
|
||||
placeBonBon(move);
|
||||
}
|
||||
|
||||
byte winner = getWinner();
|
||||
if (draw) {
|
||||
if (winner != 0 || !gameEnded())
|
||||
error("wasn't a draw! winner: %d, gameover: %d", (int)winner, (int)gameEnded());
|
||||
} else if (playerWin && winner != PLAYER) {
|
||||
error("player didn't win! winner: %d", (int)winner);
|
||||
} else if (playerWin == false && winner != STAUF) {
|
||||
error("Stauf didn't win! winner: %d", (int)winner);
|
||||
}
|
||||
|
||||
warning("finished runCakeTestNoAi(%s, %d), winner: %d", moves, (int)playerWin, (int)winner);
|
||||
}
|
||||
|
||||
void CakeGame::runCakeTest(uint seed, const char *moves, bool playerWin) {
|
||||
warning("starting runCakeTest(%u, %s, %d)", seed, moves, (int)playerWin);
|
||||
|
||||
// first fill the board with the expected moves and test the win-detection function by itself without AI
|
||||
runCakeTestNoAi(moves, playerWin);
|
||||
|
||||
restart();
|
||||
|
||||
byte vars[1024];
|
||||
memset(vars, 0, sizeof(vars));
|
||||
byte &lastMove = vars[1];
|
||||
byte &winner = vars[3];
|
||||
winner = 0;
|
||||
lastMove = 8;
|
||||
run(vars);
|
||||
|
||||
uint old_seed = _random.getSeed();
|
||||
_random.setSeed(seed);
|
||||
|
||||
for (int i = 0; moves[i]; i += 2) {
|
||||
if (winner != 0) {
|
||||
error("early win at %d, winner: %d", i, (int)winner);
|
||||
}
|
||||
lastMove = moves[i] - '0';
|
||||
byte stauf_move = moves[i + 1] - '0';
|
||||
|
||||
run(vars);
|
||||
|
||||
if (stauf_move < 8) {
|
||||
if (winner == 2) {
|
||||
error("early player win at %d", i);
|
||||
}
|
||||
|
||||
if (stauf_move != lastMove) {
|
||||
error("incorrect Stauf move, expected: %d, got: %d", (int)stauf_move, (int)lastMove);
|
||||
}
|
||||
} else if (winner != 2) {
|
||||
error("missing Stauf move, last_move: %d", (int)lastMove);
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
if (playerWin && winner != 2) {
|
||||
error("player didn't win! winner: %d", (int)winner);
|
||||
} else if (playerWin == false && winner != 1) {
|
||||
error("Stauf didn't win! winner: %d", (int)winner);
|
||||
}
|
||||
|
||||
_random.setSeed(old_seed);
|
||||
|
||||
warning("finished runCakeTest(%u, %s, %d)", seed, moves, (int)playerWin);
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
98
engines/groovie/logic/cake.h
Normal file
98
engines/groovie/logic/cake.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_LOGIC_CAKE_H
|
||||
#define GROOVIE_LOGIC_CAKE_H
|
||||
|
||||
#include "common/random.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
/*
|
||||
* Cake (Connect Four) puzzle in the dining room (tb.grv)
|
||||
*/
|
||||
class CakeGame {
|
||||
public:
|
||||
CakeGame(bool easierAi);
|
||||
void run(byte *scriptVariables);
|
||||
|
||||
private:
|
||||
static const int WIDTH = 8;
|
||||
static const int HEIGHT = 7;
|
||||
static const int GOAL_LEN = 4;
|
||||
static const int WIN_SCORE = 1000000;//!< the number of points added for a connect four
|
||||
static const byte STAUF = 1;
|
||||
static const byte PLAYER = 2;
|
||||
static const int NUM_LINES = 107;//!< how many potential victory lines there are
|
||||
|
||||
Common::RandomSource _random;
|
||||
|
||||
//! ID numbers for all of the potential victory lines for each spot on the board
|
||||
struct LinesMappings {
|
||||
byte lengths[WIDTH][HEIGHT];
|
||||
byte indecies[WIDTH][HEIGHT][GOAL_LEN * GOAL_LEN];
|
||||
};
|
||||
|
||||
//! how many points a player has, and their progress on potential victory lines
|
||||
struct PlayerProgress {
|
||||
int _score;
|
||||
int _linesCounters[NUM_LINES];//!< how many pieces are claimed in each potential victory, links to LineMappings, an entry of 4 means that's a victory
|
||||
};
|
||||
|
||||
PlayerProgress _playerProgress;
|
||||
PlayerProgress _staufProgress;
|
||||
|
||||
byte _boardState[WIDTH][HEIGHT];//!< (0, 0) is the bottom left of the board
|
||||
byte _columnHeights[WIDTH];
|
||||
|
||||
int _moveCount;
|
||||
bool _hasCheated;
|
||||
|
||||
LinesMappings _map;//!< ID numbers for all of the potential victory lines for each spot on the board
|
||||
bool _easierAi;
|
||||
|
||||
void restart();
|
||||
void setLineNum(uint x, uint y, uint index);
|
||||
bool isColumnFull(byte column);
|
||||
PlayerProgress &getPlayerProgress(bool stauf);
|
||||
void updateScores(byte x, bool revert = false);
|
||||
void placeBonBon(byte x);
|
||||
void revertMove(byte x);
|
||||
byte getWinner();
|
||||
bool gameEnded();
|
||||
int getScoreDiff();
|
||||
int aiRecurse(int search_depth, int parent_score);
|
||||
byte aiGetBestMove(int search_depth);
|
||||
void testCake();
|
||||
void runCakeTest(uint seed, const char *moves, bool player_win);
|
||||
void runCakeTestNoAi(const char *moves, bool player_win, bool draw);
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_LOGIC_CAKE_H
|
||||
800
engines/groovie/logic/cell.cpp
Normal file
800
engines/groovie/logic/cell.cpp
Normal file
@@ -0,0 +1,800 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/logic/cell.h"
|
||||
#include "common/config-manager.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
CellGame::CellGame(bool easierAi) {
|
||||
_startX = _startY = _endX = _endY = 255;
|
||||
|
||||
_stack_index = _boardStackPtr = 0;
|
||||
_flag4 = false;
|
||||
_flag2 = false;
|
||||
_flag1 = false;
|
||||
_coeff3 = 0;
|
||||
|
||||
_moveCount = 0;
|
||||
_easierAi = easierAi;
|
||||
}
|
||||
|
||||
byte CellGame::getStartX() {
|
||||
if (_startX > BOARDSIZE) {
|
||||
warning ("CellGame::getStartX: not calculated yet (%d)!", _startX);
|
||||
return 0;
|
||||
} else {
|
||||
return _startX;
|
||||
}
|
||||
}
|
||||
|
||||
byte CellGame::getStartY() {
|
||||
if (_startY > BOARDSIZE) {
|
||||
warning ("CellGame::getStartY: not calculated yet (%d)!", _startY);
|
||||
return 6;
|
||||
} else {
|
||||
return _startY;
|
||||
}
|
||||
}
|
||||
|
||||
byte CellGame::getEndX() {
|
||||
if (_endX > BOARDSIZE) {
|
||||
warning ("CellGame::getEndX: not calculated yet (%d)!", _endX);
|
||||
return 1;
|
||||
} else {
|
||||
return _endX;
|
||||
}
|
||||
}
|
||||
|
||||
byte CellGame::getEndY() {
|
||||
if (_endY > BOARDSIZE) {
|
||||
warning ("CellGame::getEndY: not calculated yet (%d)!", _endY);
|
||||
return 6;
|
||||
} else {
|
||||
return _endY;
|
||||
}
|
||||
}
|
||||
|
||||
CellGame::~CellGame() {
|
||||
}
|
||||
|
||||
const int8 possibleMoves[][9] = {
|
||||
{ 1, 7, 8, -1 },
|
||||
{ 0, 2, 7, 8, 9, -1 },
|
||||
{ 1, 3, 8, 9, 10, -1 },
|
||||
{ 2, 4, 9, 10, 11, -1 },
|
||||
{ 3, 5, 10, 11, 12, -1 },
|
||||
{ 4, 6, 11, 12, 13, -1 }, // 5
|
||||
{ 5, 12, 13, -1 },
|
||||
{ 0, 1, 8, 14, 15, -1 },
|
||||
{ 0, 1, 2, 7, 9, 14, 15, 16, -1 },
|
||||
{ 1, 2, 3, 8, 10, 15, 16, 17, -1 },
|
||||
{ 2, 3, 4, 9, 11, 16, 17, 18, -1 }, // 10
|
||||
{ 3, 4, 5, 10, 12, 17, 18, 19, -1 },
|
||||
{ 4, 5, 6, 11, 13, 18, 19, 20, -1 },
|
||||
{ 5, 6, 12, 19, 20, -1 },
|
||||
{ 7, 8, 15, 21, 22, -1 },
|
||||
{ 7, 8, 9, 14, 16, 21, 22, 23, -1 }, // 15
|
||||
{ 8, 9, 10, 15, 17, 22, 23, 24, -1 },
|
||||
{ 9, 10, 11, 16, 18, 23, 24, 25, -1 },
|
||||
{ 10, 11, 12, 17, 19, 24, 25, 26, -1 },
|
||||
{ 11, 12, 13, 18, 20, 25, 26, 27, -1 },
|
||||
{ 12, 13, 19, 26, 27, -1 }, // 20
|
||||
{ 14, 15, 22, 28, 29, -1 },
|
||||
{ 14, 15, 16, 21, 23, 28, 29, 30, -1 },
|
||||
{ 15, 16, 17, 22, 24, 29, 30, 31, -1 },
|
||||
{ 16, 17, 18, 23, 25, 30, 31, 32, -1 },
|
||||
{ 17, 18, 19, 24, 26, 31, 32, 33, -1 }, // 25
|
||||
{ 18, 19, 20, 25, 27, 32, 33, 34, -1 },
|
||||
{ 19, 20, 26, 33, 34, -1 },
|
||||
{ 21, 22, 29, 35, 36, -1 },
|
||||
{ 21, 22, 23, 28, 30, 35, 36, 37, -1 },
|
||||
{ 22, 23, 24, 29, 31, 36, 37, 38, -1 }, // 30
|
||||
{ 23, 24, 25, 30, 32, 37, 38, 39, -1 },
|
||||
{ 24, 25, 26, 31, 33, 38, 39, 40, -1 },
|
||||
{ 25, 26, 27, 32, 34, 39, 40, 41, -1 },
|
||||
{ 26, 27, 33, 40, 41, -1 },
|
||||
{ 28, 29, 36, 42, 43, -1 }, // 35
|
||||
{ 28, 29, 30, 35, 37, 42, 43, 44, -1 },
|
||||
{ 29, 30, 31, 36, 38, 43, 44, 45, -1 },
|
||||
{ 30, 31, 32, 37, 39, 44, 45, 46, -1 },
|
||||
{ 31, 32, 33, 38, 40, 45, 46, 47, -1 },
|
||||
{ 32, 33, 34, 39, 41, 46, 47, 48, -1 }, // 40
|
||||
{ 33, 34, 40, 47, 48, -1 },
|
||||
{ 35, 36, 43, -1 },
|
||||
{ 35, 36, 37, 42, 44, -1 },
|
||||
{ 36, 37, 38, 43, 45, -1 },
|
||||
{ 37, 38, 39, 44, 46, -1 }, // 45
|
||||
{ 38, 39, 40, 45, 47, -1 },
|
||||
{ 39, 40, 41, 46, 48, -1 },
|
||||
{ 40, 41, 47, -1 }
|
||||
};
|
||||
|
||||
|
||||
const int8 strategy2[][17] = {
|
||||
{ 2, 9, 14, 15, 16, -1 },
|
||||
{ 3, 10, 14, 15, 16, 17, -1 },
|
||||
{ 0, 4, 7, 11, 14, 15, 16, 17, 18, -1 },
|
||||
{ 1, 5, 8, 12, 15, 16, 17, 18, 19, -1 },
|
||||
{ 2, 6, 9, 13, 16, 17, 18, 19, 20, -1 },
|
||||
{ 3, 10, 17, 18, 19, 20, -1 }, // 5
|
||||
{ 4, 11, 18, 19, 20, -1 },
|
||||
{ 2, 9, 16, 21, 22, 23, -1 },
|
||||
{ 3, 10, 17, 21, 22, 23, 24, -1 },
|
||||
{ 0, 4, 7, 11, 14, 18, 21, 22, 23, 24, 25, -1 },
|
||||
{ 1, 5, 8, 12, 15, 19, 22, 23, 24, 25, 26, -1 }, // 10
|
||||
{ 2, 6, 9, 13, 16, 20, 23, 24, 25, 26, 27, -1 },
|
||||
{ 3, 10, 17, 24, 25, 26, 27, -1 },
|
||||
{ 4, 11, 18, 25, 26, 27, -1 },
|
||||
{ 0, 1, 2, 9, 16, 23, 28, 29, 30, -1 },
|
||||
{ 0, 1, 2, 3, 10, 17, 24, 28, 29, 30, 31, -1 }, // 15
|
||||
{ 0, 1, 2, 3, 4, 7, 11, 14, 18, 21, 25, 28, 29, 30, 31, 32, -1 },
|
||||
{ 1, 2, 3, 4, 5, 8, 12, 15, 19, 22, 26, 29, 30, 31, 32, 33, -1 },
|
||||
{ 2, 3, 4, 5, 6, 9, 13, 16, 20, 23, 27, 30, 31, 32, 33, 34, -1 },
|
||||
{ 3, 4, 5, 6, 10, 17, 24, 31, 32, 33, 34, -1 },
|
||||
{ 4, 5, 6, 11, 18, 25, 32, 33, 34, -1 }, // 20
|
||||
{ 7, 8, 9, 16, 23, 30, 35, 36, 37, -1 },
|
||||
{ 7, 8, 9, 10, 17, 24, 31, 35, 36, 37, 38, -1 },
|
||||
{ 7, 8, 9, 10, 11, 14, 18, 21, 25, 28, 32, 35, 36, 37, 38, 39, -1 },
|
||||
{ 8, 9, 10, 11, 12, 15, 19, 22, 26, 29, 33, 36, 37, 38, 39, 40, -1 },
|
||||
{ 9, 10, 11, 12, 13, 16, 20, 23, 27, 30, 34, 37, 38, 39, 40, 41, -1 }, // 25
|
||||
{ 10, 11, 12, 13, 17, 24, 31, 38, 39, 40, 41, -1 },
|
||||
{ 11, 12, 13, 18, 25, 32, 39, 40, 41, -1 },
|
||||
{ 14, 15, 16, 23, 30, 37, 42, 43, 44, -1 },
|
||||
{ 14, 15, 16, 17, 24, 31, 38, 42, 43, 44, 45, -1 },
|
||||
{ 14, 15, 16, 17, 18, 21, 25, 28, 32, 35, 39, 42, 43, 44, 45, 46, -1 }, // 30
|
||||
{ 15, 16, 17, 18, 19, 22, 26, 29, 33, 36, 40, 43, 44, 45, 46, 47, -1 },
|
||||
{ 16, 17, 18, 19, 20, 23, 27, 30, 34, 37, 41, 44, 45, 46, 47, 48, -1 },
|
||||
{ 17, 18, 19, 20, 24, 31, 38, 45, 46, 47, 48, -1 },
|
||||
{ 18, 19, 20, 25, 32, 39, 46, 47, 48, -1 },
|
||||
{ 21, 22, 23, 30, 37, 44, -1 }, // 35
|
||||
{ 21, 22, 23, 24, 31, 38, 45, -1 },
|
||||
{ 21, 22, 23, 24, 25, 28, 32, 35, 39, 42, 46, -1 },
|
||||
{ 22, 23, 24, 25, 26, 29, 33, 36, 40, 43, 47, -1 },
|
||||
{ 23, 24, 25, 26, 27, 30, 34, 37, 41, 44, 48, -1 },
|
||||
{ 24, 25, 26, 27, 31, 38, 45, -1 }, // 40
|
||||
{ 25, 26, 27, 32, 39, 46, -1 },
|
||||
{ 28, 29, 30, 37, 44, -1 },
|
||||
{ 28, 29, 30, 31, 38, 45, -1 },
|
||||
{ 28, 29, 30, 31, 32, 35, 39, 42, 46, -1 },
|
||||
{ 29, 30, 31, 32, 33, 36, 40, 43, 47, -1 }, // 45
|
||||
{ 30, 31, 32, 33, 34, 37, 41, 44, 48, -1 },
|
||||
{ 31, 32, 33, 34, 38, 45, -1 },
|
||||
{ 32, 33, 34, 39, 46, -1 }
|
||||
};
|
||||
|
||||
void CellGame::copyToTempBoard() {
|
||||
for (int i = 0; i < 53; ++i) {
|
||||
_tempBoard[i] = _board[i];
|
||||
}
|
||||
}
|
||||
|
||||
void CellGame::copyFromTempBoard() {
|
||||
for (int i = 0; i < 53; ++i) {
|
||||
_board[i] = _tempBoard[i];
|
||||
}
|
||||
}
|
||||
|
||||
void CellGame::copyToShadowBoard() {
|
||||
_board[53] = 0;
|
||||
_board[55] = 1;
|
||||
_board[56] = 0;
|
||||
|
||||
for (int i = 0; i < 49; ++i) {
|
||||
_shadowBoard[i] = _board[i];
|
||||
}
|
||||
}
|
||||
|
||||
void CellGame::pushBoard() {
|
||||
assert(_boardStackPtr < 57 * 9);
|
||||
|
||||
for (int i = 0; i < 57; ++i)
|
||||
_boardStack[_boardStackPtr + i] = _board[i];
|
||||
_boardStackPtr += 57;
|
||||
}
|
||||
|
||||
void CellGame::popBoard() {
|
||||
assert(_boardStackPtr > 0);
|
||||
|
||||
_boardStackPtr -= 57;
|
||||
for (int i = 0; i < 57; ++i) {
|
||||
_board[i] = _boardStack[_boardStackPtr + i];
|
||||
}
|
||||
}
|
||||
|
||||
void CellGame::pushShadowBoard() {
|
||||
assert(_boardStackPtr < 57 * 9);
|
||||
|
||||
for (int i = 0; i < 57; ++i)
|
||||
_boardStack[_boardStackPtr + i] = _shadowBoard[i];
|
||||
|
||||
_boardStackPtr += 57;
|
||||
}
|
||||
|
||||
void CellGame::popShadowBoard() {
|
||||
assert(_boardStackPtr > 0);
|
||||
|
||||
_boardStackPtr -= 57;
|
||||
|
||||
for (int i = 0; i < 57; ++i) {
|
||||
_shadowBoard[i] = _boardStack[_boardStackPtr + i];
|
||||
}
|
||||
}
|
||||
|
||||
void CellGame::clearMoves() {
|
||||
_stack_startXY[0] = _board[53];
|
||||
_stack_endXY[0] = _board[54];
|
||||
_stack_pass[0] = _board[55];
|
||||
|
||||
_stack_index = 1;
|
||||
}
|
||||
|
||||
void CellGame::pushMove() {
|
||||
_stack_startXY[_stack_index] = _board[53];
|
||||
_stack_endXY[_stack_index] = _board[54];
|
||||
_stack_pass[_stack_index] = _board[55];
|
||||
|
||||
_stack_index++;
|
||||
}
|
||||
|
||||
void CellGame::resetMove() {
|
||||
_board[53] = 0;
|
||||
_board[54] = 0;
|
||||
_board[55] = 0;
|
||||
}
|
||||
|
||||
void CellGame::takeCells(uint16 whereTo, int8 color) {
|
||||
int cellN;
|
||||
const int8 *str;
|
||||
|
||||
str = possibleMoves[whereTo];
|
||||
while (1) {
|
||||
cellN = *str++;
|
||||
if (cellN < 0)
|
||||
break;
|
||||
if (_tempBoard[cellN] > 0) {
|
||||
--_tempBoard[_tempBoard[cellN] + 48];
|
||||
_tempBoard[cellN] = color;
|
||||
++_tempBoard[color + 48];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CellGame::countAllCells() {
|
||||
_board[49] = 0;
|
||||
_board[50] = 0;
|
||||
_board[51] = 0;
|
||||
_board[52] = 0;
|
||||
|
||||
for (int i = 0; i < 49; i++) {
|
||||
switch (_board[i]) {
|
||||
case 1: // CELL_BLUE
|
||||
_board[49]++;
|
||||
break;
|
||||
case 2: // CELL_GREEN
|
||||
_board[50]++;
|
||||
break;
|
||||
case 3:
|
||||
_board[51]++;
|
||||
break;
|
||||
case 4:
|
||||
_board[52]++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int CellGame::countCellsOnTempBoard(int8 color) {
|
||||
const int8 *str;
|
||||
int res = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 49; i++)
|
||||
_boardSum[i] = 0;
|
||||
|
||||
for (i = 0; i < 49; i++) {
|
||||
if (_tempBoard[i] == color) {
|
||||
for (str = possibleMoves[i]; *str > 0; str++) {
|
||||
if (!_tempBoard[*str])
|
||||
++_boardSum[*str];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 49; i++)
|
||||
res += _boardSum[i];
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool CellGame::canMoveFunc1(int8 color) {
|
||||
const int8 *str;
|
||||
|
||||
if (_board[55] == 1) {
|
||||
for (; _board[53] < 49; _board[53]++) {
|
||||
if (_shadowBoard[_board[53]] == color) {
|
||||
str = &possibleMoves[_board[53]][_board[56]];
|
||||
for (;_board[56] < 8; _board[56]++) {
|
||||
_board[54] = *str++;
|
||||
if (_board[54] < 0)
|
||||
break;
|
||||
if (!_shadowBoard[_board[54]]) {
|
||||
_shadowBoard[_board[54]] = -1;
|
||||
++_board[56];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_board[56] = 0;
|
||||
}
|
||||
}
|
||||
_board[53] = 0;
|
||||
_board[55] = 2;
|
||||
_board[56] = 0;
|
||||
}
|
||||
if (_board[55] == 2) {
|
||||
for (; _board[53] < 49; _board[53]++) {
|
||||
if (_shadowBoard[_board[53]] == color) {
|
||||
str = &strategy2[_board[53]][_board[56]];
|
||||
for (;_board[56] < 16; _board[56]++) {
|
||||
_board[54] = *str++;
|
||||
if (_board[54] < 0)
|
||||
break;
|
||||
if (!_board[_board[54]]) {
|
||||
++_board[56];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_board[56] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CellGame::canMoveFunc3(int8 color) {
|
||||
const int8 *str;
|
||||
|
||||
if (_board[55] == 1) {
|
||||
for (; _board[53] < 49; _board[53]++) {
|
||||
if (_shadowBoard[_board[53]] == color) {
|
||||
str = &possibleMoves[_board[53]][_board[56]];
|
||||
for (;_board[56] < 8; _board[56]++) {
|
||||
_board[54] = *str++;
|
||||
if (_board[54] < 0)
|
||||
break;
|
||||
if (!_shadowBoard[_board[54]]) {
|
||||
_shadowBoard[_board[54]] = -1;
|
||||
++_board[56];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_board[56] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_board[53] = 0;
|
||||
_board[55] = 2;
|
||||
_board[56] = 0;
|
||||
for (int i = 0; i < 49; ++i)
|
||||
_shadowBoard[i] = _board[i];
|
||||
}
|
||||
if (_board[55] == 2) {
|
||||
for (; _board[53] < 49; _board[53]++) {
|
||||
if (_shadowBoard[_board[53]] == color) {
|
||||
str = &strategy2[_board[53]][_board[56]];
|
||||
for (;_board[56] < 16; _board[56]++) {
|
||||
_board[54] = *str++;
|
||||
if (_board[54] < 0)
|
||||
break;
|
||||
if (!_shadowBoard[_board[54]]) {
|
||||
_shadowBoard[_board[54]] = -1;
|
||||
++_board[56];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_board[56] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CellGame::canMoveFunc2(int8 color) {
|
||||
const int8 *str;
|
||||
|
||||
while (1) {
|
||||
while (_board[_board[54]]) {
|
||||
++_board[54];
|
||||
if (_board[54] >= 49)
|
||||
return false;
|
||||
}
|
||||
if (!_board[55]) {
|
||||
str = possibleMoves[_board[54]];
|
||||
while (1) {
|
||||
_board[53] = *str++;
|
||||
if (_board[53] < 0)
|
||||
break;
|
||||
if (_board[_board[53]] == color) {
|
||||
_board[55] = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_board[55] = 1;
|
||||
}
|
||||
if (_board[55] == 1) {
|
||||
_board[55] = 2;
|
||||
_board[56] = 0;
|
||||
}
|
||||
if (_board[55] == 2) {
|
||||
str = &strategy2[_board[54]][_board[56]];
|
||||
for (; _board[56] < 16; _board[56]++) {
|
||||
_board[53] = *str++;
|
||||
if (_board[53] < 0)
|
||||
break;
|
||||
if (_board[_board[53]] == color) {
|
||||
++_board[56];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
++_board[54];
|
||||
_board[55] = 0;
|
||||
if (_board[54] >= 49)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CellGame::makeMove(int8 color) {
|
||||
copyToTempBoard();
|
||||
_tempBoard[_board[54]] = color;
|
||||
++_tempBoard[color + 48];
|
||||
if (_board[55] == 2) {
|
||||
_tempBoard[_board[53]] = 0;
|
||||
--_tempBoard[color + 48];
|
||||
}
|
||||
takeCells(_board[54], color);
|
||||
}
|
||||
|
||||
int CellGame::getBoardWeight(int8 color1, int8 color2) {
|
||||
int8 celln;
|
||||
const int8 *str;
|
||||
byte cellCnt[8];
|
||||
|
||||
str = possibleMoves[_board[54]];
|
||||
cellCnt[1] = _board[49];
|
||||
cellCnt[2] = _board[50];
|
||||
cellCnt[3] = _board[51];
|
||||
cellCnt[4] = _board[52];
|
||||
if (_board[55] != 2)
|
||||
++cellCnt[color2];
|
||||
celln = *str++;
|
||||
|
||||
celln = _board[celln];
|
||||
if (celln > 0) {
|
||||
--cellCnt[celln];
|
||||
++cellCnt[color2];
|
||||
}
|
||||
celln = *str++;
|
||||
|
||||
celln = _board[celln];
|
||||
if (celln > 0) {
|
||||
--cellCnt[celln];
|
||||
++cellCnt[color2];
|
||||
}
|
||||
celln = *str++;
|
||||
|
||||
celln = _board[celln];
|
||||
if (celln > 0) {
|
||||
--cellCnt[celln];
|
||||
++cellCnt[color2];
|
||||
}
|
||||
while (1) {
|
||||
celln = *str++;
|
||||
if (celln < 0)
|
||||
break;
|
||||
celln = _board[celln];
|
||||
if (celln > 0) {
|
||||
--cellCnt[celln];
|
||||
++cellCnt[color2];
|
||||
}
|
||||
}
|
||||
return _coeff3 + 2 * (2 * cellCnt[color1] - cellCnt[1] - cellCnt[2] - cellCnt[3] - cellCnt[4]);
|
||||
}
|
||||
|
||||
void CellGame::chooseBestMove(int8 color) {
|
||||
int moveIndex = 0;
|
||||
|
||||
if (_flag2) {
|
||||
int bestWeight = 32767;
|
||||
for (int i = 0; i < _stack_index; ++i) {
|
||||
_board[53] = _stack_startXY[i];
|
||||
_board[54] = _stack_endXY[i];
|
||||
_board[55] = _stack_pass[i];
|
||||
makeMove(color);
|
||||
int curWeight = countCellsOnTempBoard(color);
|
||||
if (curWeight <= bestWeight) {
|
||||
if (curWeight < bestWeight)
|
||||
moveIndex = 0;
|
||||
bestWeight = curWeight;
|
||||
_stack_startXY[moveIndex] = _board[53];
|
||||
_stack_endXY[moveIndex] = _board[54];
|
||||
_stack_pass[moveIndex++] = _board[55];
|
||||
}
|
||||
}
|
||||
_stack_index = moveIndex;
|
||||
}
|
||||
|
||||
_startX = _stack_startXY[0] % 7;
|
||||
_startY = _stack_startXY[0] / 7;
|
||||
_endX = _stack_endXY[0] % 7;
|
||||
_endY = _stack_endXY[0] / 7;
|
||||
}
|
||||
|
||||
int8 CellGame::calcBestWeight(int8 color1, int8 color2, uint16 depth, int bestWeight) {
|
||||
int8 res;
|
||||
int8 curColor;
|
||||
bool canMove;
|
||||
int type;
|
||||
uint16 i;
|
||||
int8 currBoardWeight;
|
||||
int8 weight;
|
||||
|
||||
pushBoard();
|
||||
copyFromTempBoard();
|
||||
curColor = color2;
|
||||
for (i = 0;; ++i) {
|
||||
if (i >= 4) {
|
||||
res = _coeff3 + 2 * (2 * _board[color1 + 48] - _board[49] - _board[50] - _board[51] - _board[52]);
|
||||
popBoard();
|
||||
return res;
|
||||
}
|
||||
++curColor;
|
||||
if (curColor > 4)
|
||||
curColor = 1;
|
||||
|
||||
if (_board[curColor + 48]) {
|
||||
if (_board[curColor + 48] >= 49 - _board[49] - _board[50] - _board[51] - _board[52]) {
|
||||
resetMove();
|
||||
canMove = canMoveFunc2(curColor);
|
||||
type = 1;
|
||||
} else {
|
||||
copyToShadowBoard();
|
||||
if (depth == 1) {
|
||||
canMove = canMoveFunc3(curColor);
|
||||
type = 3;
|
||||
} else {
|
||||
canMove = canMoveFunc1(curColor);
|
||||
type = 2;
|
||||
}
|
||||
}
|
||||
if (canMove)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_flag1) {
|
||||
popBoard();
|
||||
return bestWeight + 1;
|
||||
}
|
||||
|
||||
depth -= 1;
|
||||
if (depth) {
|
||||
makeMove(curColor);
|
||||
if (type == 1) {
|
||||
res = calcBestWeight(color1, curColor, depth, bestWeight);
|
||||
} else {
|
||||
pushShadowBoard();
|
||||
res = calcBestWeight(color1, curColor, depth, bestWeight);
|
||||
popShadowBoard();
|
||||
}
|
||||
} else {
|
||||
res = getBoardWeight(color1, curColor);
|
||||
}
|
||||
|
||||
if ((res < bestWeight && color1 != curColor) || _flag4) {
|
||||
popBoard();
|
||||
return res;
|
||||
}
|
||||
|
||||
currBoardWeight = _coeff3 + 2 * (2 * _board[color1 + 48] - _board[49] - _board[50] - _board[51] - _board[52]);
|
||||
while (1) {
|
||||
if (type == 1) {
|
||||
canMove = canMoveFunc2(curColor);
|
||||
} else if (type == 2) {
|
||||
canMove = canMoveFunc1(curColor);
|
||||
} else {
|
||||
canMove = canMoveFunc3(curColor);
|
||||
}
|
||||
|
||||
if (!canMove)
|
||||
break;
|
||||
if (_flag1) {
|
||||
popBoard();
|
||||
return bestWeight + 1;
|
||||
}
|
||||
if (_board[55] == 2) {
|
||||
if (getBoardWeight(color1, curColor) == currBoardWeight)
|
||||
continue;
|
||||
}
|
||||
if (!depth) {
|
||||
weight = getBoardWeight(color1, curColor);
|
||||
if (type == 1) {
|
||||
if (_board[55] == 2)
|
||||
_board[56] = 16;
|
||||
}
|
||||
} else {
|
||||
makeMove(curColor);
|
||||
if (type != 1) {
|
||||
pushShadowBoard();
|
||||
weight = calcBestWeight(color1, curColor, depth, bestWeight);
|
||||
popShadowBoard();
|
||||
} else {
|
||||
weight = calcBestWeight(color1, curColor, depth, bestWeight);
|
||||
}
|
||||
}
|
||||
if ((weight < res && color1 != curColor) || (weight > res && color1 == curColor))
|
||||
res = weight;
|
||||
|
||||
if ((res < bestWeight && color1 != curColor) || _flag4)
|
||||
break;
|
||||
}
|
||||
popBoard();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void CellGame::doGame(int8 color, int depth) {
|
||||
bool canMove;
|
||||
int type;
|
||||
|
||||
countAllCells();
|
||||
if (_board[color + 48] >= 49 - _board[49] - _board[50] - _board[51] - _board[52]) {
|
||||
resetMove();
|
||||
canMove = canMoveFunc2(color);
|
||||
type = true;
|
||||
} else {
|
||||
copyToShadowBoard();
|
||||
canMove = canMoveFunc1(color);
|
||||
type = false;
|
||||
}
|
||||
|
||||
if (canMove) {
|
||||
int8 w1, w2;
|
||||
if (_board[color + 48] - _board[49] - _board[50] - _board[51] - _board[52] == 0)
|
||||
depth = 0;
|
||||
_coeff3 = 0;
|
||||
if (_board[55] == 1)
|
||||
_coeff3 = 1;
|
||||
clearMoves();
|
||||
if (depth) {
|
||||
makeMove(color);
|
||||
_flag4 = false;
|
||||
if (type) {
|
||||
w2 = calcBestWeight(color, color, depth, -127);
|
||||
} else {
|
||||
pushShadowBoard();
|
||||
w2 = calcBestWeight(color, color, depth, -127);
|
||||
popShadowBoard();
|
||||
}
|
||||
} else {
|
||||
w2 = getBoardWeight(color, color);
|
||||
}
|
||||
int8 currBoardWeight = 2 * (2 * _board[color + 48] - _board[49] - _board[50] - _board[51] - _board[52]);
|
||||
while (1) {
|
||||
if (type)
|
||||
canMove = canMoveFunc2(color);
|
||||
else
|
||||
canMove = canMoveFunc1(color);
|
||||
|
||||
if (!canMove)
|
||||
break;
|
||||
if (_flag1)
|
||||
break;
|
||||
_coeff3 = 0;
|
||||
if (_board[55] == 2) {
|
||||
if (getBoardWeight(color, color) == currBoardWeight)
|
||||
continue;
|
||||
}
|
||||
if (_board[55] == 1)
|
||||
_coeff3 = 1;
|
||||
if (depth) {
|
||||
makeMove(color);
|
||||
_flag4 = false;
|
||||
if (type) {
|
||||
w1 = calcBestWeight(color, color, depth, w2);
|
||||
} else {
|
||||
pushShadowBoard();
|
||||
w1 = calcBestWeight(color, color, depth, w2);
|
||||
popShadowBoard();
|
||||
}
|
||||
} else {
|
||||
w1 = getBoardWeight(color, color);
|
||||
}
|
||||
if (w1 == w2)
|
||||
pushMove();
|
||||
|
||||
if (w1 > w2) {
|
||||
clearMoves();
|
||||
w2 = w1;
|
||||
}
|
||||
}
|
||||
chooseBestMove(color);
|
||||
}
|
||||
}
|
||||
|
||||
const int8 depths[] = { 1, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2, 2, 3, 2, 2, 3, 3, 2, 3, 3, 3 };
|
||||
|
||||
void CellGame::calcMove(int8 color, uint16 depth) {
|
||||
_flag1 = false;
|
||||
++_moveCount;
|
||||
if (depth) {
|
||||
if (depth == 1) {
|
||||
_flag2 = true;
|
||||
doGame(color, 0);
|
||||
} else {
|
||||
int newDepth;
|
||||
|
||||
newDepth = depths[3 * (depth - 2) + _moveCount % 3];
|
||||
if (_easierAi && _moveCount > 7)
|
||||
newDepth = 1;
|
||||
|
||||
_flag2 = true;
|
||||
if (newDepth >= 20) {
|
||||
assert(0); // This branch is not implemented
|
||||
} else {
|
||||
doGame(color, newDepth);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_flag2 = false;
|
||||
doGame(color, depth);
|
||||
}
|
||||
}
|
||||
|
||||
void CellGame::run(uint16 depth, byte *scriptBoard) {
|
||||
const byte color = 2;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 49; i++, scriptBoard++) {
|
||||
_board[i] = 0;
|
||||
if (*scriptBoard == 50)
|
||||
_board[i] = 1;
|
||||
if (*scriptBoard == 66)
|
||||
_board[i] = 2;
|
||||
}
|
||||
for (i = 49; i < 57; i++)
|
||||
_board[i] = 0;
|
||||
|
||||
calcMove(color, depth);
|
||||
}
|
||||
|
||||
|
||||
} // End of Groovie namespace
|
||||
101
engines/groovie/logic/cell.h
Normal file
101
engines/groovie/logic/cell.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_LOGIC_CELL_H
|
||||
#define GROOVIE_LOGIC_CELL_H
|
||||
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#define BOARDSIZE 7
|
||||
#define CELL_CLEAR 0
|
||||
#define CELL_BLUE 1
|
||||
#define CELL_GREEN 2
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class CellGame {
|
||||
public:
|
||||
CellGame(bool easierAi);
|
||||
~CellGame();
|
||||
void run(uint16 depth, byte *scriptBoard);
|
||||
|
||||
byte getStartX();
|
||||
byte getStartY();
|
||||
byte getEndX();
|
||||
byte getEndY();
|
||||
|
||||
private:
|
||||
void copyToTempBoard();
|
||||
void copyFromTempBoard();
|
||||
void copyToShadowBoard();
|
||||
void pushBoard();
|
||||
void popBoard();
|
||||
void pushShadowBoard();
|
||||
void popShadowBoard();
|
||||
void clearMoves();
|
||||
void pushMove();
|
||||
void resetMove();
|
||||
bool canMoveFunc1(int8 color);
|
||||
bool canMoveFunc2(int8 color);
|
||||
bool canMoveFunc3(int8 color);
|
||||
void takeCells(uint16 whereTo, int8 color);
|
||||
void countAllCells();
|
||||
int countCellsOnTempBoard(int8 color);
|
||||
void makeMove(int8 color);
|
||||
int getBoardWeight(int8 color1, int8 color2);
|
||||
void chooseBestMove(int8 color);
|
||||
int8 calcBestWeight(int8 color1, int8 color2, uint16 depth, int bestWeight);
|
||||
void doGame(int8 color, int depth);
|
||||
void calcMove(int8 color, uint16 depth);
|
||||
|
||||
byte _startX;
|
||||
byte _startY;
|
||||
byte _endX;
|
||||
byte _endY;
|
||||
|
||||
int8 _board[57];
|
||||
int8 _tempBoard[58];
|
||||
int8 _shadowBoard[64];
|
||||
int8 _boardStack[570];
|
||||
int _boardStackPtr;
|
||||
|
||||
int8 _boardSum[58];
|
||||
|
||||
int8 _stack_startXY[128];
|
||||
int8 _stack_endXY[128];
|
||||
int8 _stack_pass[128];
|
||||
int _stack_index;
|
||||
|
||||
int _coeff3;
|
||||
bool _flag1, _flag2, _flag4;
|
||||
int _moveCount;
|
||||
bool _easierAi;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_LOGIC_CELL_H
|
||||
250
engines/groovie/logic/gallery.cpp
Normal file
250
engines/groovie/logic/gallery.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/logic/gallery.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
// Links between the pieces in the Gallery challenge
|
||||
// For example, the first row signifies that piece 1
|
||||
// is connected to pieces 2, 4 and 5
|
||||
const byte GalleryGame::kGalleryLinks[21][10] = {
|
||||
{ 2, 4, 5, 0, 0, 0, 0, 0, 0, 0 }, // 1
|
||||
{ 1, 5, 3, 0, 0, 0, 0, 0, 0, 0 }, // 2
|
||||
{ 2, 5, 9, 12, 0, 0, 0, 0, 0, 0 }, // 3
|
||||
{ 1, 5, 6, 7, 8, 0, 0, 0, 0, 0 }, // 4
|
||||
{ 1, 2, 3, 4, 7, 8, 9, 0, 0, 0 }, // 5
|
||||
{ 4, 7, 10, 11, 13, 14, 15, 16, 18, 0 }, // 6
|
||||
{ 4, 5, 6, 8, 9, 10, 0, 0, 0, 0 }, // 7
|
||||
{ 4, 5, 7, 0, 0, 0, 0, 0, 0, 0 }, // 8
|
||||
{ 3, 5, 7, 10, 11, 12, 18, 0, 0, 0 }, // 9
|
||||
{ 6, 7, 9, 11, 0, 0, 0, 0, 0, 0 }, // 10
|
||||
{ 6, 9, 10, 18, 0, 0, 0, 0, 0, 0 }, // 11
|
||||
{ 3, 9, 18, 21, 0, 0, 0, 0, 0, 0 }, // 12
|
||||
{ 6, 14, 17, 19, 0, 0, 0, 0, 0, 0 }, // 13
|
||||
{ 6, 13, 15, 17, 19, 20, 21, 0, 0, 0 }, // 14
|
||||
{ 6, 14, 16, 18, 21, 0, 0, 0, 0, 0 }, // 15
|
||||
{ 6, 15, 0, 0, 0, 0, 0, 0, 0, 0 }, // 16
|
||||
{13, 14, 19, 0, 0, 0, 0, 0, 0, 0 }, // 17
|
||||
{ 6, 9, 11, 12, 15, 21, 0, 0, 0, 0 }, // 18
|
||||
{13, 14, 17, 20, 0, 0, 0, 0, 0, 0 }, // 19
|
||||
{14, 19, 21, 0, 0, 0, 0, 0, 0, 0 }, // 20
|
||||
{12, 14, 15, 18, 20, 0, 0, 0, 0, 0 } // 21
|
||||
};
|
||||
|
||||
enum kGalleryPieceStatus {
|
||||
kPieceUnselected = 0,
|
||||
kPieceSelected = 1
|
||||
};
|
||||
|
||||
void GalleryGame::run(byte *scriptVariables) {
|
||||
byte pieceStatus[kPieceCount];
|
||||
byte status1[kPieceCount];
|
||||
|
||||
memcpy(pieceStatus, scriptVariables + 26, kPieceCount);
|
||||
|
||||
int selectedPieces = 0;
|
||||
for (int i = 0; i < kPieceCount; i++) {
|
||||
status1[i] = 0;
|
||||
// in this context it seems like kPieceSelected means it's available for selection
|
||||
if (pieceStatus[i] == kPieceSelected) {
|
||||
byte status2[kPieceCount];
|
||||
for (int j = 0; j < kPieceCount; j++)
|
||||
status2[j] = pieceStatus[j];
|
||||
|
||||
status2[i] = 0;
|
||||
|
||||
byte curLink = kGalleryLinks[i][0];
|
||||
int linkedPiece = 1;
|
||||
while (curLink != 0) {
|
||||
linkedPiece++;
|
||||
status2[curLink - 1] = kPieceUnselected;
|
||||
curLink = kGalleryLinks[i][linkedPiece - 1];
|
||||
}
|
||||
status1[i] = galleryAI(status2, 1);
|
||||
// in this context, kPieceSelected means we think it's an optimal move
|
||||
if (status1[i] == kPieceSelected) {
|
||||
selectedPieces++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedPieces == 0) {
|
||||
// optimal move not found, choose a move with a high score?
|
||||
int highestScore = 0;
|
||||
for (int i = 0; i < kPieceCount; i++) {
|
||||
if (highestScore < status1[i]) {
|
||||
highestScore = status1[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (highestScore == 2) {
|
||||
highestScore = 1;
|
||||
} else {
|
||||
if (highestScore < kPieceCount) {
|
||||
highestScore = 2;
|
||||
} else {
|
||||
highestScore -= 12;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < kPieceCount; i++) {
|
||||
if (highestScore < status1[i]) {
|
||||
status1[i] = kPieceSelected;
|
||||
selectedPieces++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int selectedPiece = 0;
|
||||
|
||||
// var 49 is set by the script calling o_random
|
||||
// choose one of our good moves
|
||||
byte v12 = scriptVariables[49] % selectedPieces;
|
||||
for (int i = 0; i < kPieceCount; i++) {
|
||||
if (status1[selectedPiece] == 1 && !v12--)
|
||||
break;
|
||||
|
||||
selectedPiece++;
|
||||
}
|
||||
|
||||
scriptVariables[47] = (selectedPiece + 1) / 10;
|
||||
scriptVariables[48] = (selectedPiece + 1) % 10;
|
||||
}
|
||||
|
||||
byte GalleryGame::galleryAI(byte *pieceStatus, int depth) {
|
||||
byte status1[kPieceCount];
|
||||
byte status2[kPieceCount];
|
||||
|
||||
int selectedPieces = 0;
|
||||
|
||||
for (int i = 0; i < kPieceCount; i++) {
|
||||
status1[i] = 0;
|
||||
if (pieceStatus[i] == kPieceSelected) {
|
||||
for (int j = 0; j < kPieceCount; j++)
|
||||
status2[j] = pieceStatus[j];
|
||||
|
||||
status2[i] = 0;
|
||||
selectedPieces = 1;
|
||||
|
||||
byte curLink = kGalleryLinks[i][0];
|
||||
int linkedPiece = 1;
|
||||
while (curLink != 0) {
|
||||
linkedPiece++;
|
||||
status2[curLink - 1] = kPieceUnselected;
|
||||
curLink = kGalleryLinks[i][linkedPiece - 1];
|
||||
}
|
||||
status1[i] = galleryAI(status2, depth == 0 ? 1 : 0);
|
||||
if (!depth && status1[i] == kPieceSelected) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedPieces) {
|
||||
byte v8 = 0;
|
||||
byte v9 = 0;
|
||||
byte v10 = 0;
|
||||
for (int j = 0; j < 21; ++j) {
|
||||
byte v12 = status1[j];
|
||||
if (v12) {
|
||||
++v10;
|
||||
if (v12 == 1)
|
||||
++v9;
|
||||
else
|
||||
v8 += v12;
|
||||
}
|
||||
}
|
||||
if (v9 == v10)
|
||||
return 1; // I believe 1 means this is an optimal move
|
||||
else if (_easierAi && (v9 + 1 == v10 || v9 - 1 == v10))
|
||||
return 1; // close enough to an optimal move?
|
||||
else
|
||||
return (v8 + 102 * v9) / v10;// otherwise, higher numbers are better
|
||||
}
|
||||
|
||||
return depth == 0 ? 2 : 1;
|
||||
}
|
||||
|
||||
|
||||
void GalleryGame::testsWriteMove(int move, byte pieceStatus[kPieceCount]) {
|
||||
if (pieceStatus[move] != kPieceSelected)
|
||||
error("illegal move to %d", move + 1);
|
||||
|
||||
pieceStatus[move] = kPieceUnselected;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
byte curLink = kGalleryLinks[move][i];
|
||||
if (!curLink)
|
||||
break;
|
||||
pieceStatus[curLink - 1] = kPieceUnselected;
|
||||
}
|
||||
}
|
||||
|
||||
void GalleryGame::ensureSamanthaWins(int seed) {
|
||||
byte scriptVariables[1024];
|
||||
byte goalPieceStatus[kPieceCount];
|
||||
memset(goalPieceStatus, 0, sizeof(goalPieceStatus));
|
||||
Common::RandomSource rng("ensureSamanthaWins");
|
||||
|
||||
rng.setSeed(seed);
|
||||
warning("starting ensureSamanthaWins with seed %u", seed);
|
||||
|
||||
memset(scriptVariables, 1, sizeof(scriptVariables));
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
bool isStauf = i % 2;
|
||||
scriptVariables[49] = rng.getRandomNumber(14);
|
||||
|
||||
run(scriptVariables);
|
||||
|
||||
int selectedMove = scriptVariables[47] * 10 + scriptVariables[48] - 1;
|
||||
warning("Move %d: %s moved to %d", i, (isStauf ? "Stauf" : "Samantha"), selectedMove + 1);
|
||||
|
||||
testsWriteMove(selectedMove, scriptVariables + 26);
|
||||
|
||||
if (memcmp(scriptVariables + 26, goalPieceStatus, sizeof(goalPieceStatus)) == 0) {
|
||||
if (isStauf)
|
||||
error("Stauf won");
|
||||
else
|
||||
warning("Samantha won");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
error("game took too long");
|
||||
}
|
||||
|
||||
void GalleryGame::test() {
|
||||
warning("running gallery tests");
|
||||
for (int i = 0; i < 20; i++) {
|
||||
ensureSamanthaWins(i);
|
||||
}
|
||||
warning("finished running gallery tests");
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
105
engines/groovie/logic/gallery.h
Normal file
105
engines/groovie/logic/gallery.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_LOGIC_GALLERY_H
|
||||
#define GROOVIE_LOGIC_GALLERY_H
|
||||
|
||||
#include "common/random.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
/*
|
||||
* Puzzle in the Gallery (bs.grv).
|
||||
* The aim is to select the last piece of the image.
|
||||
* There are 18 pieces in total.
|
||||
* When selecting a piece, all surrounding pieces are also selected
|
||||
*
|
||||
* +--------------------+--------------------------------+--------+
|
||||
* | 1/1A | 2/1B | |
|
||||
* | +--------------+--+--------------------------+-----+ |
|
||||
* | | | | |
|
||||
* +--+ 4/1D | 5/1E | 3/1C |
|
||||
* | | | |
|
||||
* +-----+--------+--+--------+-----------------+--+--------+ |
|
||||
* | | | | | | |
|
||||
* | | | | | | |
|
||||
* | | | 8/21 | | | |
|
||||
* | | | | +-----------+ | |
|
||||
* | | | | | | | |
|
||||
* | | +-----------+ | 10/23 | 9/22 | |
|
||||
* | | | | | |
|
||||
* | | 7/20 +-----+-----+ +-----+
|
||||
* | | | | | | |
|
||||
* | +--------------------------+ | | | |
|
||||
* | 6/1F | | | |
|
||||
* +-----------+-----------+-----+--+ | 11 | | 12 |
|
||||
* | 13/26 | | | | | / | | / |
|
||||
* | +-----+-----+ | | | | 24 +-----------+ 25 |
|
||||
* | | | | | | | | | |
|
||||
* +-----+ 17/2A | | |16| | | | |
|
||||
* | | | | |/ | | | | |
|
||||
* | +-----+-----+ | |29| | | +-----+
|
||||
* | | | | | | | | |
|
||||
* | | | | | +-----+ 18/2B | |
|
||||
* | 19/2C | 14/27 | | | | | |
|
||||
* | | | | +-----------+ | |
|
||||
* | | | | | | | |
|
||||
* | | | +--+ 15/28 | | |
|
||||
* | | | | | |
|
||||
* | +--------+--+--------------------+-----------+ |
|
||||
* | | 20/2D | 21/2E |
|
||||
* +-----------+--------+-----------------------------------------+
|
||||
*/
|
||||
|
||||
const int kPieceCount = 21;
|
||||
|
||||
class GalleryGame {
|
||||
public:
|
||||
GalleryGame(bool easierAi) {
|
||||
#if 0
|
||||
_easierAi = false;
|
||||
test();
|
||||
#endif
|
||||
_easierAi = easierAi;
|
||||
}
|
||||
void run(byte *scriptVariables);
|
||||
|
||||
private:
|
||||
byte galleryAI(byte *pieceStatus, int depth);
|
||||
|
||||
static const byte kGalleryLinks[21][10];
|
||||
bool _easierAi;
|
||||
|
||||
void test();
|
||||
void ensureSamanthaWins(int seed);
|
||||
void testsWriteMove(int move, byte pieceStatus[kPieceCount]);
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_LOGIC_GALLERY_H
|
||||
677
engines/groovie/logic/mousetrap.cpp
Normal file
677
engines/groovie/logic/mousetrap.cpp
Normal file
@@ -0,0 +1,677 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/logic/mousetrap.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
MouseTrapGame::MouseTrapGame(bool easierAi) : _random("MouseTrapGame") {
|
||||
_mouseTrapCounter = _mouseTrapCounter1 = 0;
|
||||
_mouseTrapX = _mouseTrapY = 0;
|
||||
memset(_mouseTrapRoute, 0, 75);
|
||||
memset(_mouseTrapRouteCopy, 0, 76);
|
||||
_mouseTrapPosX = _mouseTrapPosY = 0;
|
||||
memset(_mouseTrapCells, 0, 31);
|
||||
_mouseTrapNumSteps = 0;
|
||||
_easierAi = easierAi;
|
||||
}
|
||||
|
||||
void MouseTrapGame::run(byte *scriptVariables) {
|
||||
byte op = scriptVariables[2];
|
||||
|
||||
// variable 24 is the mouse?
|
||||
//scriptVariables[24] = 2;
|
||||
|
||||
// player wins: scriptVariables[22] = 1;
|
||||
// stauf wins: scriptVariables[22] = 2;
|
||||
// allows the player to click to place the mouse somewhere? scriptVariables[5] = 0;
|
||||
|
||||
switch (op) {
|
||||
case 0:
|
||||
sub01(scriptVariables);
|
||||
break;
|
||||
case 1: // init board
|
||||
// value of 0 is V, 1 is <, 2 is ^, 3 is >
|
||||
// variable 23 is the outside piece
|
||||
//scriptVariables[23] = _random.getRandomNumber(3);
|
||||
// variable slot is the space number + 25, the left corner
|
||||
// (Stauf's goal) is space 1, above that is space 2, the
|
||||
// center is 13, and the right corner (goal) is space 25
|
||||
init();
|
||||
sub03(scriptVariables);
|
||||
break;
|
||||
case 2: // before player chooses the floor to move, set the banned move
|
||||
{
|
||||
int clicked = xyToPos(_mouseTrapX, _mouseTrapY);
|
||||
scriptVariables[clicked + 50] = 0;
|
||||
break;
|
||||
}
|
||||
case 3: // after player moving floor
|
||||
// a bunch of hardcoded conditionals to copy variables and
|
||||
// set the banned move
|
||||
// this probably also sets a variable to allow the player to
|
||||
// move the mouse, and checks for win/lose
|
||||
sub05(scriptVariables);
|
||||
break;
|
||||
case 5: // maybe player moving mouse
|
||||
sub06(scriptVariables);
|
||||
break;
|
||||
case 6: // Stauf moving floor?
|
||||
sub07(scriptVariables);
|
||||
break;
|
||||
case 7: // maybe Stauf moving mouse
|
||||
sub08(scriptVariables);
|
||||
break;
|
||||
case 8: // Samantha making a move
|
||||
sub09(scriptVariables);
|
||||
break;
|
||||
|
||||
default:
|
||||
warning("Unknown mousetrap op %d", op);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const int8 mouseTrapStates[] = {
|
||||
6, 12, 9, 3
|
||||
};
|
||||
|
||||
static const int8 mouseTrapLookup[] = {
|
||||
1, 0, 3, 0, 0, 1, 0, 3, 1, 4, 3, 4, 4, 1, 4, 3
|
||||
};
|
||||
|
||||
void MouseTrapGame::init() {
|
||||
int8 initState[8], initX[8], initY[8];
|
||||
|
||||
initState[0] = 0;
|
||||
initState[1] = 1;
|
||||
initState[3] = 3;
|
||||
initState[2] = 2;
|
||||
initState[4] = 4;
|
||||
initState[5] = 5;
|
||||
initState[6] = 6;
|
||||
initState[7] = 7;
|
||||
initY[0] = 1;
|
||||
initY[1] = 3;
|
||||
initY[2] = 0;
|
||||
initY[3] = 4;
|
||||
initY[4] = 0;
|
||||
initY[5] = 4;
|
||||
initY[6] = 1;
|
||||
initY[7] = 3;
|
||||
initX[0] = 0;
|
||||
initX[1] = 0;
|
||||
initX[2] = 1;
|
||||
initX[3] = 1;
|
||||
initX[4] = 3;
|
||||
initX[5] = 3;
|
||||
initX[6] = 4;
|
||||
initX[7] = 4;
|
||||
|
||||
// easier AI gives a fixed board state, because the random configurations can give you some bad ones
|
||||
if(_easierAi)
|
||||
_random.setSeed(711);
|
||||
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
int8 j = _random.getRandomNumber(i);
|
||||
|
||||
_mouseTrapCells[5 * initY[i] + 5 + initX[i]] = mouseTrapStates[initState[j] >> 1];
|
||||
|
||||
for (; j < i; j++) {
|
||||
initState[j] = initState[j + 1];
|
||||
}
|
||||
}
|
||||
|
||||
_mouseTrapCells[11] = mouseTrapStates[3];
|
||||
_mouseTrapCells[16] = mouseTrapStates[0];
|
||||
_mouseTrapCells[5] = 12;
|
||||
_mouseTrapCells[21] = mouseTrapStates[0];
|
||||
_mouseTrapCells[12] = mouseTrapStates[3];
|
||||
_mouseTrapCells[15] = 13;
|
||||
_mouseTrapCells[25] = 9;
|
||||
_mouseTrapCells[22] = mouseTrapStates[1];
|
||||
_mouseTrapCells[13] = mouseTrapStates[2];
|
||||
_mouseTrapCells[18] = mouseTrapStates[2];
|
||||
_mouseTrapCells[23] = mouseTrapStates[1];
|
||||
_mouseTrapCells[7] = 14;
|
||||
_mouseTrapCells[17] = 15;
|
||||
_mouseTrapCells[27] = 11;
|
||||
_mouseTrapCells[9] = 6;
|
||||
_mouseTrapCells[19] = 7;
|
||||
_mouseTrapCells[29] = 3;
|
||||
_mouseTrapCells[30] = mouseTrapStates[_random.getRandomNumber(3)];
|
||||
|
||||
_mouseTrapPosY = 2;
|
||||
_mouseTrapPosX = 2;
|
||||
_mouseTrapY = 0;
|
||||
_mouseTrapX = 0;
|
||||
_mouseTrapNumSteps = 0;
|
||||
_mouseTrapCounter = 0;
|
||||
}
|
||||
|
||||
void MouseTrapGame::sub01(byte *scriptVariables) {
|
||||
int8 x, y;
|
||||
|
||||
findMaxPointInRoute(&x, &y);
|
||||
scriptVariables[5] = (_mouseTrapPosX == x && _mouseTrapPosY == y) ? 1 : 0;
|
||||
if (havePosInRoute(4, 4)) {
|
||||
copyRoute(4, 4);
|
||||
scriptVariables[22] = 1;
|
||||
} else if (havePosInRoute(0, 0)) {
|
||||
copyRoute(0, 0);
|
||||
scriptVariables[22] = 2;
|
||||
} else {
|
||||
scriptVariables[22] = 0;
|
||||
if (!scriptVariables[5])
|
||||
copyRoute(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void MouseTrapGame::sub03(byte *scriptVariables) {
|
||||
int cnt = 1;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 5; j++) {
|
||||
scriptVariables[cnt + 25] = findState(_mouseTrapCells[5 * j + 5 + i]);
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
scriptVariables[23] = findState(_mouseTrapCells[30]);
|
||||
}
|
||||
|
||||
void MouseTrapGame::sub05(byte *scriptVariables) {
|
||||
int8 x, y;
|
||||
|
||||
posToXY(scriptVariables[1] + 10 * scriptVariables[0], &x, &y);
|
||||
flipField(x, y);
|
||||
|
||||
if (calcSolution()) {
|
||||
scriptVariables[5] = 0;
|
||||
updateRoute();
|
||||
|
||||
if (havePosInRoute(4, 4)) {
|
||||
copyRoute(4, 4);
|
||||
scriptVariables[22] = 1;
|
||||
} else if (havePosInRoute(0, 0)) {
|
||||
copyRoute(0, 0);
|
||||
scriptVariables[22] = 2;
|
||||
} else {
|
||||
copyStateToVars(scriptVariables);
|
||||
scriptVariables[22] = 0;
|
||||
}
|
||||
} else {
|
||||
scriptVariables[5] = 1;
|
||||
scriptVariables[22] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MouseTrapGame::sub06(byte *scriptVariables) {
|
||||
int8 x, y;
|
||||
|
||||
posToXY(10 * scriptVariables[0] + scriptVariables[1], &x, &y);
|
||||
copyRoute(x, y);
|
||||
}
|
||||
|
||||
void MouseTrapGame::sub07(byte *scriptVariables) {
|
||||
int8 x1, y1, x2, y2;
|
||||
|
||||
goFarthest(&x1, &y1);
|
||||
flipField(x1, y1);
|
||||
|
||||
if (!calcSolution()) {
|
||||
scriptVariables[5] = 1;
|
||||
scriptVariables[22] = 0;
|
||||
} else {
|
||||
scriptVariables[5] = 0;
|
||||
updateRoute();
|
||||
|
||||
if (!havePosInRoute(0, 0)) {
|
||||
if (havePosInRoute(4, 4)) {
|
||||
copyRoute(4, 4);
|
||||
scriptVariables[22] = 1;
|
||||
} else {
|
||||
findMinPointInRoute(&x2, &y2);
|
||||
|
||||
if (_mouseTrapPosX != x2 || _mouseTrapPosY != y2) {
|
||||
copyRoute(x2, y2);
|
||||
scriptVariables[22] = 0;
|
||||
} else {
|
||||
scriptVariables[5] = 1;
|
||||
scriptVariables[22] = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
copyRoute(0, 0);
|
||||
scriptVariables[22] = 2;
|
||||
}
|
||||
}
|
||||
|
||||
int8 pos = xyToPos(x1, y1);
|
||||
scriptVariables[0] = pos / 10;
|
||||
scriptVariables[1] = pos % 10;
|
||||
}
|
||||
|
||||
void MouseTrapGame::sub08(byte *scriptVariables) {
|
||||
int8 x1, y1, x, y;
|
||||
|
||||
popLastStep(&x1, &y1);
|
||||
int8 pos = xyToPos(x1, y1);
|
||||
|
||||
_mouseTrapPosX = x1;
|
||||
_mouseTrapPosY = y1;
|
||||
|
||||
scriptVariables[0] = scriptVariables[11];
|
||||
scriptVariables[1] = scriptVariables[12];
|
||||
scriptVariables[11] = pos / 10;
|
||||
scriptVariables[12] = pos % 10;
|
||||
posToXY(scriptVariables[1] + 10 * scriptVariables[0], &x, &y);
|
||||
|
||||
if (y > y1) {
|
||||
scriptVariables[15] = 0;
|
||||
} else if (y < y1) {
|
||||
scriptVariables[15] = 2;
|
||||
} else if (x > x1) {
|
||||
scriptVariables[15] = 3;
|
||||
} else if (x < x1) {
|
||||
scriptVariables[15] = 1;
|
||||
}
|
||||
|
||||
if (!_mouseTrapCounter1)
|
||||
scriptVariables[2] = 0;
|
||||
}
|
||||
|
||||
void MouseTrapGame::sub09(byte *scriptVariables) {
|
||||
int8 x1, y1, x2, y2;
|
||||
|
||||
getBestDirection(&x1, &y1);
|
||||
flipField(x1, y1);
|
||||
|
||||
if (!calcSolution()) {
|
||||
scriptVariables[5] = 1;
|
||||
scriptVariables[22] = 0;
|
||||
} else {
|
||||
scriptVariables[5] = 0;
|
||||
updateRoute();
|
||||
|
||||
if (!havePosInRoute(4, 4)) {
|
||||
if (havePosInRoute(0, 0)) {
|
||||
copyRoute(0, 0);
|
||||
scriptVariables[22] = 2;
|
||||
} else {
|
||||
findMaxPointInRoute(&x2, &y2);
|
||||
copyRoute(x2, y2);
|
||||
scriptVariables[22] = 0;
|
||||
}
|
||||
} else {
|
||||
copyRoute(4, 4);
|
||||
scriptVariables[22] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int8 pos = xyToPos(x1, y1);
|
||||
scriptVariables[0] = pos / 10;
|
||||
scriptVariables[1] = pos % 10;
|
||||
}
|
||||
|
||||
void MouseTrapGame::copyRoute(int8 x, int8 y) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < _mouseTrapCounter; i++) {
|
||||
if (_mouseTrapRoute[3 * i] == x && _mouseTrapRoute[3 * i + 1] == y)
|
||||
break;
|
||||
}
|
||||
|
||||
_mouseTrapCounter1 = 0;
|
||||
|
||||
do {
|
||||
_mouseTrapRouteCopy[3 * _mouseTrapCounter1 + 0] = _mouseTrapRoute[3 * i + 0];
|
||||
_mouseTrapRouteCopy[3 * _mouseTrapCounter1 + 1] = _mouseTrapRoute[3 * i + 1];
|
||||
_mouseTrapRouteCopy[3 * _mouseTrapCounter1 + 2] = _mouseTrapRoute[3 * i + 2];
|
||||
|
||||
_mouseTrapCounter1++;
|
||||
|
||||
i = _mouseTrapRoute[3 * i + 2];
|
||||
} while (i);
|
||||
}
|
||||
|
||||
int8 MouseTrapGame::xyToPos(int8 x, int8 y) {
|
||||
return 5 * y + x + 1;
|
||||
}
|
||||
|
||||
void MouseTrapGame::posToXY(int8 pos, int8 *x, int8 *y) {
|
||||
*y = (pos - 1) / 5;
|
||||
*x = (pos - 1) % 5;
|
||||
}
|
||||
|
||||
void MouseTrapGame::copyStateToVars(byte *scriptVariables) {
|
||||
memset(scriptVariables + 51, 0, 24);
|
||||
scriptVariables[75] = 0;
|
||||
|
||||
for (int i = 0; i < _mouseTrapCounter; i++)
|
||||
scriptVariables[xyToPos(_mouseTrapRoute[3 * i], _mouseTrapRoute[3 * i + 1]) + 50] = 1;
|
||||
}
|
||||
|
||||
int8 MouseTrapGame::findState(int8 val) {
|
||||
int8 result = 0;
|
||||
|
||||
while (mouseTrapStates[result] != val) {
|
||||
if (++result >= 4)
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void MouseTrapGame::flipField(int8 x, int8 y) {
|
||||
int8 tmp;
|
||||
|
||||
if (y) {
|
||||
if (y == 4) {
|
||||
if (x == 1) {
|
||||
tmp = _mouseTrapCells[10];
|
||||
_mouseTrapCells[10] = _mouseTrapCells[11];
|
||||
_mouseTrapCells[11] = _mouseTrapCells[12];
|
||||
_mouseTrapCells[12] = _mouseTrapCells[13];
|
||||
_mouseTrapCells[13] = _mouseTrapCells[14];
|
||||
_mouseTrapCells[14] = _mouseTrapCells[30];
|
||||
_mouseTrapCells[30] = tmp;
|
||||
_mouseTrapX = 1;
|
||||
_mouseTrapY = 0;
|
||||
} else if (x == 3) {
|
||||
tmp = _mouseTrapCells[20];
|
||||
_mouseTrapCells[20] = _mouseTrapCells[21];
|
||||
_mouseTrapCells[21] = _mouseTrapCells[22];
|
||||
_mouseTrapCells[22] = _mouseTrapCells[23];
|
||||
_mouseTrapCells[23] = _mouseTrapCells[24];
|
||||
_mouseTrapCells[24] = _mouseTrapCells[30];
|
||||
_mouseTrapCells[30] = tmp;
|
||||
_mouseTrapX = 3;
|
||||
_mouseTrapY = 0;
|
||||
}
|
||||
} else if (x) {
|
||||
if (x == 4) {
|
||||
if (y == 1) {
|
||||
tmp = _mouseTrapCells[6];
|
||||
_mouseTrapCells[6] = _mouseTrapCells[11];
|
||||
_mouseTrapCells[11] = _mouseTrapCells[16];
|
||||
_mouseTrapCells[16] = _mouseTrapCells[21];
|
||||
_mouseTrapCells[21] = _mouseTrapCells[26];
|
||||
_mouseTrapCells[26] = _mouseTrapCells[30];
|
||||
_mouseTrapCells[30] = tmp;
|
||||
_mouseTrapX = 0;
|
||||
_mouseTrapY = 1;
|
||||
} else if (y == 3) {
|
||||
tmp = _mouseTrapCells[8];
|
||||
_mouseTrapCells[8] = _mouseTrapCells[13];
|
||||
_mouseTrapCells[13] = _mouseTrapCells[18];
|
||||
_mouseTrapCells[18] = _mouseTrapCells[23];
|
||||
_mouseTrapCells[23] = _mouseTrapCells[28];
|
||||
_mouseTrapCells[28] = _mouseTrapCells[30];
|
||||
_mouseTrapCells[30] = tmp;
|
||||
_mouseTrapX = 0;
|
||||
_mouseTrapY = 3;
|
||||
}
|
||||
}
|
||||
} else if (y == 1) {
|
||||
tmp = _mouseTrapCells[26];
|
||||
_mouseTrapCells[26] = _mouseTrapCells[21];
|
||||
_mouseTrapCells[21] = _mouseTrapCells[16];
|
||||
_mouseTrapCells[16] = _mouseTrapCells[11];
|
||||
_mouseTrapCells[11] = _mouseTrapCells[6];
|
||||
_mouseTrapCells[6] = _mouseTrapCells[30];
|
||||
_mouseTrapCells[30] = tmp;
|
||||
_mouseTrapX = 4;
|
||||
_mouseTrapY = 1;
|
||||
} else if (y == 3) {
|
||||
tmp = _mouseTrapCells[28];
|
||||
_mouseTrapCells[28] = _mouseTrapCells[23];
|
||||
_mouseTrapCells[23] = _mouseTrapCells[18];
|
||||
_mouseTrapCells[18] = _mouseTrapCells[13];
|
||||
_mouseTrapCells[13] = _mouseTrapCells[8];
|
||||
_mouseTrapCells[8] = _mouseTrapCells[30];
|
||||
_mouseTrapCells[30] = tmp;
|
||||
_mouseTrapX = 4;
|
||||
_mouseTrapY = 3;
|
||||
}
|
||||
} else if (x == 1) {
|
||||
tmp = _mouseTrapCells[14];
|
||||
_mouseTrapCells[14] = _mouseTrapCells[13];
|
||||
_mouseTrapCells[13] = _mouseTrapCells[12];
|
||||
_mouseTrapCells[12] = _mouseTrapCells[11];
|
||||
_mouseTrapCells[11] = _mouseTrapCells[10];
|
||||
_mouseTrapCells[10] = _mouseTrapCells[30];
|
||||
_mouseTrapCells[30] = tmp;
|
||||
_mouseTrapX = 1;
|
||||
_mouseTrapY = 4;
|
||||
} else if (x == 3) {
|
||||
tmp = _mouseTrapCells[24];
|
||||
_mouseTrapCells[24] = _mouseTrapCells[23];
|
||||
_mouseTrapCells[23] = _mouseTrapCells[22];
|
||||
_mouseTrapCells[22] = _mouseTrapCells[21];
|
||||
_mouseTrapCells[21] = _mouseTrapCells[20];
|
||||
_mouseTrapCells[20] = _mouseTrapCells[30];
|
||||
_mouseTrapCells[30] = tmp;
|
||||
_mouseTrapX = 3;
|
||||
_mouseTrapY = 4;
|
||||
}
|
||||
}
|
||||
|
||||
bool MouseTrapGame::calcSolution() {
|
||||
int8 pos = _mouseTrapPosY + 5 * _mouseTrapPosX; // coordinates swapped?
|
||||
int8 val = _mouseTrapCells[pos + 5];
|
||||
|
||||
return ((val & 1) != 0 && _mouseTrapPosX && (_mouseTrapCells[pos] & 4) != 0)
|
||||
|| ((val & 4) != 0 && _mouseTrapPosX < 4 && (_mouseTrapCells[pos + 10] & 1) != 0)
|
||||
|| ((val & 8) != 0 && _mouseTrapPosY < 4 && (_mouseTrapCells[pos + 6] & 2) != 0)
|
||||
|| ((val & 2) != 0 && _mouseTrapPosY && (_mouseTrapCells[pos + 4] & 8) != 0);
|
||||
}
|
||||
|
||||
bool MouseTrapGame::havePosInRoute(int8 x, int8 y) {
|
||||
for (int i = 0; i < _mouseTrapCounter; i++) {
|
||||
if (_mouseTrapRoute[3 * i] == x && _mouseTrapRoute[3 * i + 1] == y)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MouseTrapGame::addToRoute(int8 x, int8 y, int8 num) {
|
||||
if (!havePosInRoute(x, y)) {
|
||||
_mouseTrapRoute[3 * _mouseTrapCounter] = x;
|
||||
_mouseTrapRoute[3 * _mouseTrapCounter + 1] = y;
|
||||
_mouseTrapRoute[3 * _mouseTrapCounter + 2] = num;
|
||||
|
||||
_mouseTrapCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
void MouseTrapGame::updateRoute() {
|
||||
_mouseTrapCounter = 0;
|
||||
|
||||
addToRoute(_mouseTrapPosX, _mouseTrapPosY, 0);
|
||||
|
||||
int prevCounter = 0;
|
||||
|
||||
do {
|
||||
for (int i = prevCounter; i < _mouseTrapCounter; i++) {
|
||||
int8 y1 = _mouseTrapRoute[3 * i + 1];
|
||||
int8 x1 = _mouseTrapRoute[3 * i];
|
||||
int8 pos = 5 * x1 + y1;
|
||||
int8 mask = _mouseTrapCells[pos + 5];
|
||||
|
||||
if ((mask & 1) != 0 && x1 && (_mouseTrapCells[pos] & 4) != 0)
|
||||
addToRoute(x1 - 1, y1, i);
|
||||
if ((mask & 4) != 0 && x1 < 4 && (_mouseTrapCells[pos + 10] & 1) != 0)
|
||||
addToRoute(x1 + 1, y1, i);
|
||||
if ((mask & 8) != 0 && y1 < 4 && (_mouseTrapCells[pos + 6] & 2) != 0)
|
||||
addToRoute(x1, y1 + 1, i);
|
||||
if ((mask & 2) != 0 && y1 && (_mouseTrapCells[pos + 4] & 8) != 0)
|
||||
addToRoute(x1, y1 - 1, i);
|
||||
}
|
||||
|
||||
prevCounter = _mouseTrapCounter;
|
||||
} while (_mouseTrapCounter != prevCounter);
|
||||
}
|
||||
|
||||
void MouseTrapGame::popLastStep(int8 *x, int8 *y) {
|
||||
_mouseTrapCounter1--;
|
||||
|
||||
*x = _mouseTrapRouteCopy[3 * _mouseTrapCounter1];
|
||||
*y = _mouseTrapRouteCopy[3 * _mouseTrapCounter1 + 1];
|
||||
}
|
||||
|
||||
void MouseTrapGame::goFarthest(int8 *x, int8 *y) {
|
||||
int8 origX = _mouseTrapX;
|
||||
int8 origY = _mouseTrapY;
|
||||
int8 maxVal = 0;
|
||||
int8 maxX = 0, maxY = 0;
|
||||
|
||||
if (_mouseTrapNumSteps)
|
||||
--_mouseTrapNumSteps;
|
||||
|
||||
for (int8 i = 0; i < 8; i++) {
|
||||
int8 x1 = mouseTrapLookup[2 * i];
|
||||
int8 y1 = mouseTrapLookup[2 * i + 1];
|
||||
if (x1 != origX || y1 != origY) {
|
||||
flipField(x1, y1);
|
||||
|
||||
int8 dist = calcDistanceToExit();
|
||||
|
||||
if (_easierAi)
|
||||
dist += _random.getRandomNumber(2);
|
||||
|
||||
if (_mouseTrapNumSteps && _random.getRandomNumber(1) != 0)
|
||||
dist += 3;
|
||||
|
||||
if (dist >= maxVal) {
|
||||
maxVal = dist;
|
||||
maxX = x1;
|
||||
maxY = y1;
|
||||
}
|
||||
|
||||
flipField(mouseTrapLookup[2 * ((i + 4) & 7)], mouseTrapLookup[2 * ((i + 4) & 7) + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
*x = maxX;
|
||||
*y = maxY;
|
||||
}
|
||||
|
||||
void MouseTrapGame::findMinPointInRoute(int8 *x, int8 *y) {
|
||||
int8 maxVal = 0;
|
||||
int8 x1 = _mouseTrapPosX;
|
||||
int8 y1 = _mouseTrapPosY;
|
||||
for (int i = 0; i < _mouseTrapCounter; i++) {
|
||||
if (8 - _mouseTrapRoute[3 * i + 1] - _mouseTrapRoute[3 * i] > maxVal) {
|
||||
maxVal = 8 - _mouseTrapRoute[3 * i + 1] - _mouseTrapRoute[3 * i];
|
||||
x1 = _mouseTrapRoute[3 * i];
|
||||
y1 = _mouseTrapRoute[3 * i + 1];
|
||||
}
|
||||
}
|
||||
*x = x1;
|
||||
*y = y1;
|
||||
}
|
||||
|
||||
int8 MouseTrapGame::calcDistanceToExit() {
|
||||
int8 maxDist = 0;
|
||||
|
||||
updateRoute();
|
||||
if (havePosInRoute(4, 4))
|
||||
return 0;
|
||||
|
||||
for (int i = 0; i < _mouseTrapCounter; i++) {
|
||||
if (8 - _mouseTrapRoute[3 * i + 1] - _mouseTrapRoute[3 * i] > maxDist)
|
||||
maxDist = 8 - _mouseTrapRoute[3 * i + 1] - _mouseTrapRoute[3 * i];
|
||||
}
|
||||
|
||||
return maxDist;
|
||||
}
|
||||
|
||||
void MouseTrapGame::getBestDirection(int8 *x, int8 *y) {
|
||||
int8 maxVal = 0;
|
||||
int8 origX = _mouseTrapX;
|
||||
int8 origY = _mouseTrapY;
|
||||
_mouseTrapNumSteps = 8;
|
||||
int8 maxX = 0, maxY = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int x1 = mouseTrapLookup[2 * i];
|
||||
int y1 = mouseTrapLookup[2 * i + 1];
|
||||
|
||||
if (origX != x1 || origY != y1) {
|
||||
flipField(x1, y1);
|
||||
|
||||
int8 maxInRoute = findMaxInRoute();
|
||||
if (maxInRoute >= maxVal) {
|
||||
maxVal = maxInRoute;
|
||||
maxX = x1;
|
||||
maxY = y1;
|
||||
}
|
||||
|
||||
flipField(mouseTrapLookup[2 * ((i + 4) & 7)], mouseTrapLookup[2 * ((i + 4) & 7) + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
*x = maxX;
|
||||
*y = maxY;
|
||||
}
|
||||
|
||||
void MouseTrapGame::findMaxPointInRoute(int8 *x, int8 *y) {
|
||||
int8 maxVal = 0;
|
||||
int8 x1 = _mouseTrapPosX;
|
||||
int8 y1 = _mouseTrapPosY;
|
||||
|
||||
updateRoute();
|
||||
|
||||
for (int i = 0; i < _mouseTrapCounter; i++) {
|
||||
if (_mouseTrapRoute[3 * i] + _mouseTrapRoute[3 * i + 1] > maxVal) {
|
||||
maxVal = _mouseTrapRoute[3 * i] + _mouseTrapRoute[3 * i + 1];
|
||||
x1 = _mouseTrapRoute[3 * i];
|
||||
y1 = _mouseTrapRoute[3 * i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
*x = x1;
|
||||
*y = y1;
|
||||
}
|
||||
|
||||
int8 MouseTrapGame::findMaxInRoute() {
|
||||
updateRoute();
|
||||
|
||||
if (havePosInRoute(0, 0))
|
||||
return 0;
|
||||
|
||||
int8 maxCoords = 0;
|
||||
|
||||
for (int i = 0; i < _mouseTrapCounter; i++) {
|
||||
if (_mouseTrapRoute[3 * i] + _mouseTrapRoute[3 * i + 1] > maxCoords)
|
||||
maxCoords = _mouseTrapRoute[3 * i] + _mouseTrapRoute[3 * i + 1];
|
||||
}
|
||||
|
||||
return maxCoords;
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
101
engines/groovie/logic/mousetrap.h
Normal file
101
engines/groovie/logic/mousetrap.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_LOGIC_MOUSETRAP_H
|
||||
#define GROOVIE_LOGIC_MOUSETRAP_H
|
||||
|
||||
#include "common/random.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
/*
|
||||
* Mouse Trap puzzle in the Lab.
|
||||
*
|
||||
* Stauf's Goal is space 1, counting up as you go north east
|
||||
* towards the north corner which is space 5 and the moveable
|
||||
* space to the left of that is space 4.
|
||||
* South east from Stauf's goal is the next line starting with
|
||||
* space 6, counting up as you go north east where the moveable
|
||||
* space to the right of the north corner is space 10
|
||||
*
|
||||
* Next line is 11 (unmovable) to 15 (unmoveable), this line
|
||||
* contains the center space which is space 13
|
||||
* Next line is 16 (moveable) to 20 (moveable)
|
||||
* Next line is 21 (unmovable) to 25 (unmovable), with 25 being
|
||||
* the player's goal door
|
||||
*
|
||||
* Space -2 is the next piece, outside of the box
|
||||
*/
|
||||
class MouseTrapGame {
|
||||
public:
|
||||
MouseTrapGame(bool easierAi);
|
||||
void run(byte *scriptVariables);
|
||||
|
||||
private:
|
||||
void init();
|
||||
void sub01(byte *scriptVariables);
|
||||
void sub03(byte *scriptVariables);
|
||||
void sub05(byte *scriptVariables);
|
||||
void sub06(byte *scriptVariables);
|
||||
void sub07(byte *scriptVariables);
|
||||
void sub08(byte *scriptVariables);
|
||||
void sub09(byte *scriptVariables);
|
||||
void copyRoute(int8 x, int8 y);
|
||||
int8 xyToPos(int8 x, int8 y);
|
||||
void posToXY(int8 pos, int8 *x, int8 *y);
|
||||
void copyStateToVars(byte *scriptVariables);
|
||||
int8 findState(int8 val);
|
||||
void flipField(int8 x, int8 y);
|
||||
bool calcSolution();
|
||||
bool havePosInRoute(int8 y, int8 x);
|
||||
void addToRoute(int8 y, int8 x, int8 num);
|
||||
void updateRoute();
|
||||
void popLastStep(int8 *x, int8 *y);
|
||||
void goFarthest(int8 *x, int8 *y);
|
||||
void findMinPointInRoute(int8 *y, int8 *x);
|
||||
int8 calcDistanceToExit();
|
||||
void getBestDirection(int8 *x, int8 *y);
|
||||
void findMaxPointInRoute(int8 *x, int8 *y);
|
||||
int8 findMaxInRoute();
|
||||
|
||||
|
||||
private:
|
||||
Common::RandomSource _random;
|
||||
int8 _mouseTrapX, _mouseTrapY;
|
||||
int8 _mouseTrapCounter, _mouseTrapCounter1;
|
||||
int8 _mouseTrapRoute[75];
|
||||
int8 _mouseTrapRouteCopy[76];
|
||||
int8 _mouseTrapCells[31];
|
||||
int8 _mouseTrapPosX, _mouseTrapPosY;
|
||||
int8 _mouseTrapNumSteps;
|
||||
bool _easierAi;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_LOGIC_MOUSETRAP_H
|
||||
721
engines/groovie/logic/othello.cpp
Normal file
721
engines/groovie/logic/othello.cpp
Normal file
@@ -0,0 +1,721 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/logic/othello.h"
|
||||
#include "groovie/groovie.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
const int EMPTY_PIECE = 0;
|
||||
const int AI_PIECE = 1;
|
||||
const int PLAYER_PIECE = 2;
|
||||
|
||||
int xyToVar(int x, int y) {
|
||||
return x * 10 + y + 25;
|
||||
}
|
||||
|
||||
void sortPossibleMoves(Freeboard (&boards)[30], int numPossibleMoves) {
|
||||
if (numPossibleMoves < 2)
|
||||
return;
|
||||
|
||||
Common::sort(&boards[0], &boards[numPossibleMoves]);
|
||||
}
|
||||
|
||||
int OthelloGame::scoreEdge(byte (&board)[8][8], int x, int y, int slopeX, int slopeY) {
|
||||
const int8 *scores = &_edgesScores[0];
|
||||
const int8 *ptr = &scores[board[x][y]];
|
||||
|
||||
// we don't score either corner in this function
|
||||
x += slopeX;
|
||||
y += slopeY;
|
||||
int endX = x + slopeX * 5;
|
||||
int endY = y + slopeY * 5;
|
||||
|
||||
while (x <= endX && y <= endY) {
|
||||
ptr = &scores[*ptr + board[x][y]];
|
||||
x += slopeX;
|
||||
y += slopeY;
|
||||
}
|
||||
return _cornersScores[*ptr];
|
||||
}
|
||||
|
||||
int OthelloGame::scoreEarlyGame(Freeboard *freeboard) {
|
||||
// in the early game the AI's search depth can't see far enough
|
||||
// so instead of the score simply counting the pieces, we use some heuristics
|
||||
int scores[3];
|
||||
scores[0] = 0;
|
||||
scores[1] = 0;
|
||||
scores[2] = 0;
|
||||
|
||||
byte(&b)[8][8] = freeboard->_boardstate;
|
||||
|
||||
int scoreRightEdge = scoreEdge(b, 7, 0, 0, 1);
|
||||
int scoreBottomEdge = scoreEdge(b, 0, 7, 1, 0);
|
||||
int scoreTopEdge = scoreEdge(b, 0, 0, 1, 0);
|
||||
int scoreLeftEdge = scoreEdge(b, 0, 0, 0, 1);
|
||||
scores[AI_PIECE] = scoreRightEdge + scoreBottomEdge + scoreTopEdge + scoreLeftEdge;
|
||||
|
||||
int topLeft = b[0][0];
|
||||
int bottomLeft = b[0][7];
|
||||
int topRight = b[7][0];
|
||||
int bottomRight = b[7][7];
|
||||
|
||||
//subtract points for bad spots relative to the opponent
|
||||
//diagonal from the corners
|
||||
const int8 *diagFromCorners = &_scores[0][0];
|
||||
scores[b[1][1]] -= diagFromCorners[topLeft];
|
||||
scores[b[1][6]] -= diagFromCorners[bottomLeft];
|
||||
scores[b[6][1]] -= diagFromCorners[topRight];
|
||||
scores[b[6][6]] -= diagFromCorners[bottomRight];
|
||||
|
||||
// 2 away from the edge
|
||||
const int8 *twoAwayFromEdge = &_scores[1][0];
|
||||
scores[b[1][2]] -= twoAwayFromEdge[b[0][2]];
|
||||
scores[b[1][5]] -= twoAwayFromEdge[b[0][5]];
|
||||
scores[b[2][1]] -= twoAwayFromEdge[b[2][0]];
|
||||
scores[b[2][6]] -= twoAwayFromEdge[b[2][7]];
|
||||
scores[b[5][1]] -= twoAwayFromEdge[b[5][0]];
|
||||
scores[b[5][6]] -= twoAwayFromEdge[b[5][7]];
|
||||
scores[b[6][2]] -= twoAwayFromEdge[b[7][2]];
|
||||
scores[b[6][5]] -= twoAwayFromEdge[b[7][5]];
|
||||
|
||||
// 3 away from the edge
|
||||
const int8 *threeAwayFromEdge = &_scores[2][0];
|
||||
scores[b[1][3]] -= threeAwayFromEdge[b[0][3]];
|
||||
scores[b[1][4]] -= threeAwayFromEdge[b[0][4]];
|
||||
scores[b[3][1]] -= threeAwayFromEdge[b[3][0]];
|
||||
scores[b[3][6]] -= threeAwayFromEdge[b[3][7]];
|
||||
scores[b[4][1]] -= threeAwayFromEdge[b[4][0]];
|
||||
scores[b[4][6]] -= threeAwayFromEdge[b[4][7]];
|
||||
scores[b[6][3]] -= threeAwayFromEdge[b[7][3]];
|
||||
scores[b[6][4]] -= threeAwayFromEdge[b[7][4]];
|
||||
|
||||
// corners
|
||||
scores[topLeft] += 0x32;
|
||||
scores[bottomLeft] += 0x32;
|
||||
scores[topRight] += 0x32;
|
||||
scores[bottomRight] += 0x32;
|
||||
|
||||
// left column
|
||||
scores[b[0][1]] += 4;
|
||||
scores[b[0][2]] += 0x10;
|
||||
scores[b[0][3]] += 0xc;
|
||||
scores[b[0][4]] += 0xc;
|
||||
scores[b[0][5]] += 0x10;
|
||||
scores[b[0][6]] += 4;
|
||||
|
||||
// top row
|
||||
scores[b[1][0]] += 4;
|
||||
scores[b[2][0]] += 0x10;
|
||||
scores[b[3][0]] += 0xc;
|
||||
scores[b[4][0]] += 0xc;
|
||||
scores[b[5][0]] += 0x10;
|
||||
scores[b[6][0]] += 4;
|
||||
|
||||
// bottom row
|
||||
scores[b[1][7]] += 4;
|
||||
scores[b[2][7]] += 0x10;
|
||||
scores[b[3][7]] += 0xc;
|
||||
scores[b[4][7]] += 0xc;
|
||||
scores[b[5][7]] += 0x10;
|
||||
scores[b[6][7]] += 4;
|
||||
|
||||
// away from the edges (interesting we don't score the center/starting spots?)
|
||||
scores[b[2][2]] += 1;
|
||||
scores[b[2][5]] += 1;
|
||||
scores[b[5][2]] += 1;
|
||||
scores[b[5][5]] += 1;
|
||||
|
||||
// right column
|
||||
scores[b[7][1]] += 4;
|
||||
scores[b[7][2]] += 0x10;
|
||||
scores[b[7][3]] += 0xc;
|
||||
scores[b[7][4]] += 0xc;
|
||||
scores[b[7][5]] += 0x10;
|
||||
scores[b[7][6]] += 4;
|
||||
|
||||
return scores[AI_PIECE] - scores[PLAYER_PIECE];
|
||||
}
|
||||
|
||||
int OthelloGame::scoreLateGame(Freeboard *freeboard) {
|
||||
byte *board = &freeboard->_boardstate[0][0];
|
||||
// in the late game, we simply score the same way we determine the winner, because the AI's search depth can see to the end of the game
|
||||
int scores[3];
|
||||
scores[0] = 0;
|
||||
scores[1] = 0;
|
||||
scores[2] = 0;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
scores[board[i]]++;
|
||||
}
|
||||
return (scores[AI_PIECE] - scores[PLAYER_PIECE]) * 4;
|
||||
}
|
||||
|
||||
int OthelloGame::scoreBoard(Freeboard *board) {
|
||||
if (_isLateGame || _easierAi)
|
||||
return scoreLateGame(board);
|
||||
else
|
||||
return scoreEarlyGame(board);
|
||||
}
|
||||
|
||||
void OthelloGame::restart(void) {
|
||||
_counter = 0;
|
||||
_isLateGame = false;
|
||||
_board._score = 0;
|
||||
|
||||
// clear the board
|
||||
memset(_board._boardstate, EMPTY_PIECE, sizeof(_board._boardstate));
|
||||
// set the starting pieces
|
||||
_board._boardstate[4][4] = AI_PIECE;
|
||||
_board._boardstate[3][3] = _board._boardstate[4][4];
|
||||
_board._boardstate[4][3] = PLAYER_PIECE;
|
||||
_board._boardstate[3][4] = _board._boardstate[4][3];
|
||||
}
|
||||
|
||||
void OthelloGame::writeBoardToVars(Freeboard *board, byte *vars) {
|
||||
for (int x = 0; x < 8; x++) {
|
||||
for (int y = 0; y < 8; y++) {
|
||||
byte b = _lookupPlayer[board->_boardstate[x][y]];
|
||||
vars[xyToVar(x, y)] = b;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void OthelloGame::readBoardStateFromVars(byte *vars) {
|
||||
for (int x = 0; x < 8; x++) {
|
||||
for (int y = 0; y < 8; y++) {
|
||||
byte b = vars[xyToVar(x, y)];
|
||||
if (b == _lookupPlayer[0]) {
|
||||
_board._boardstate[x][y] = EMPTY_PIECE;
|
||||
}
|
||||
if (b == _lookupPlayer[1]) {
|
||||
_board._boardstate[x][y] = AI_PIECE;
|
||||
}
|
||||
if (b == _lookupPlayer[2]) {
|
||||
_board._boardstate[x][y] = PLAYER_PIECE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Freeboard OthelloGame::getPossibleMove(Freeboard *freeboard, int moveSpot) {
|
||||
// we make a new board with the piece placed and captures completed
|
||||
int player = _isAiTurn ? AI_PIECE : PLAYER_PIECE;
|
||||
int opponent = _isAiTurn ? PLAYER_PIECE : AI_PIECE;
|
||||
|
||||
// copy the board
|
||||
Freeboard newboard;
|
||||
memcpy(newboard._boardstate, freeboard->_boardstate, sizeof(newboard._boardstate));
|
||||
|
||||
byte *board = &newboard._boardstate[0][0];
|
||||
int8 **line = _lines[moveSpot];
|
||||
|
||||
// check every line until we hit the null-terminating pointer
|
||||
for (line = _lines[moveSpot]; *line != NULL; line++) {
|
||||
int8 *lineSpot = *line;
|
||||
int piece = board[*lineSpot];
|
||||
int8 *_lineSpot;
|
||||
// we already know the current moveSpot is the player's piece
|
||||
// if these 2 loops were a regex replacement, they would be something like s/(O+)P/(P+)P/
|
||||
for (_lineSpot = lineSpot; piece == opponent; _lineSpot++) {
|
||||
piece = board[*_lineSpot];
|
||||
}
|
||||
// if _lineSpot was advanced (meaning at least 1 opponent piece), and now we're at a player piece
|
||||
if (_lineSpot != lineSpot && piece == player) {
|
||||
// apply the captures
|
||||
piece = board[*lineSpot];
|
||||
while (piece == opponent) {
|
||||
board[*lineSpot] = player;
|
||||
lineSpot++;
|
||||
piece = board[*lineSpot];
|
||||
}
|
||||
}
|
||||
}
|
||||
// add the new piece
|
||||
board[moveSpot] = player;
|
||||
return newboard;
|
||||
}
|
||||
|
||||
void OthelloGame::checkPossibleMove(Freeboard *board, Freeboard (&boards)[30], int8 **lineSpot, int &numPossibleMoves, int moveSpot, byte player, byte opponent) {
|
||||
int8 *testSpot;
|
||||
// loop through a list of slots in line with piece moveSpot, looping away from moveSpot
|
||||
do {
|
||||
do {
|
||||
// skip all spots that aren't the opponent
|
||||
testSpot = *lineSpot;
|
||||
lineSpot++;
|
||||
if (testSpot == NULL) // end of the null terminated line?
|
||||
return;
|
||||
} while (board->_boardstate[*testSpot / 8][*testSpot % 8] != opponent);
|
||||
|
||||
// we found the opponent, skip to the first piece that doesn't belong to the opponent
|
||||
for (; board->_boardstate[*testSpot / 8][*testSpot % 8] == opponent; testSpot++) {
|
||||
}
|
||||
|
||||
// start over again if didn't find a piece of our own on the other side
|
||||
} while (board->_boardstate[*testSpot / 8][*testSpot % 8] != player);
|
||||
// so we found (empty space)(opponent+)(our own piece)
|
||||
// add this to the list of possible moves
|
||||
boards[numPossibleMoves] = getPossibleMove(board, moveSpot);
|
||||
boards[numPossibleMoves]._score = scoreBoard(&boards[numPossibleMoves]);
|
||||
numPossibleMoves++;
|
||||
}
|
||||
|
||||
int OthelloGame::getAllPossibleMoves(Freeboard *board, Freeboard (&boards)[30]) {
|
||||
int moveSpot = 0;
|
||||
byte player = _isAiTurn ? AI_PIECE : PLAYER_PIECE;
|
||||
byte opponent = _isAiTurn ? PLAYER_PIECE : AI_PIECE;
|
||||
int numPossibleMoves = 0;
|
||||
int8 ***line = &_lines[0];
|
||||
do {
|
||||
if (board->_boardstate[moveSpot / 8][moveSpot % 8] == 0) {
|
||||
checkPossibleMove(board, boards, *line, numPossibleMoves, moveSpot, player, opponent);
|
||||
}
|
||||
|
||||
line++;
|
||||
moveSpot++;
|
||||
if (moveSpot > 63) {
|
||||
sortPossibleMoves(boards, numPossibleMoves);
|
||||
return numPossibleMoves;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
int OthelloGame::aiRecurse(Freeboard *board, int depth, int parentScore, int opponentBestScore) {
|
||||
Freeboard possibleMoves[30];
|
||||
int numPossibleMoves = getAllPossibleMoves(board, possibleMoves);
|
||||
if (numPossibleMoves == 0) {
|
||||
_isAiTurn = !_isAiTurn;
|
||||
numPossibleMoves = getAllPossibleMoves(board, possibleMoves);
|
||||
if (numPossibleMoves == 0) {
|
||||
return scoreLateGame(board);
|
||||
}
|
||||
}
|
||||
|
||||
int _depth = depth - 1;
|
||||
bool isPlayerTurn = !_isAiTurn;
|
||||
int bestScore = isPlayerTurn ? 100 : -100;
|
||||
Freeboard *boardsIter = &possibleMoves[0];
|
||||
for (int i = 0; i < numPossibleMoves; i++, boardsIter++) {
|
||||
Freeboard *tBoard = boardsIter;
|
||||
_isAiTurn = isPlayerTurn; // reset and flip the global for whose turn it is before recursing
|
||||
int score;
|
||||
if (_depth == 0) {
|
||||
score = (int)tBoard->_score;
|
||||
} else {
|
||||
if (isPlayerTurn) {
|
||||
score = aiRecurse(tBoard, _depth, parentScore, bestScore);
|
||||
} else {
|
||||
score = aiRecurse(tBoard, _depth, bestScore, opponentBestScore);
|
||||
}
|
||||
}
|
||||
if ((bestScore < score) != isPlayerTurn) {
|
||||
bool done = true;
|
||||
if (isPlayerTurn) {
|
||||
if (parentScore < score)
|
||||
done = false;
|
||||
} else {
|
||||
if (score < opponentBestScore)
|
||||
done = false;
|
||||
}
|
||||
bestScore = score;
|
||||
if (done) {
|
||||
return score;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestScore;
|
||||
}
|
||||
|
||||
byte OthelloGame::aiDoBestMove(Freeboard *pBoard) {
|
||||
Freeboard possibleMoves[30];
|
||||
int bestScore = -101;
|
||||
int bestMove = 0;
|
||||
int parentScore = -100;
|
||||
if (_flag1 == 0) {
|
||||
_isAiTurn = 1;
|
||||
}
|
||||
|
||||
Freeboard *board = pBoard;
|
||||
int numPossibleMoves = getAllPossibleMoves(board, possibleMoves);
|
||||
if (numPossibleMoves == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int move = 0; move < numPossibleMoves; move++) {
|
||||
_isAiTurn = !_isAiTurn; // flip before recursing
|
||||
int depth = _depths[_counter];
|
||||
if (_easierAi)
|
||||
depth = 1;
|
||||
int score = aiRecurse(&possibleMoves[move], depth, parentScore, 100);
|
||||
if (bestScore < score) {
|
||||
parentScore = score;
|
||||
bestMove = move;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
*pBoard = possibleMoves[bestMove];
|
||||
if (_flag1 == 0) {
|
||||
_counter += 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void OthelloGame::initLines(void) {
|
||||
// allocate an array of strings, the lines are null-terminated
|
||||
int8 **lines = &_linesStorage[0];
|
||||
int8 *line = &_lineStorage[0];
|
||||
|
||||
for (int baseX = 0; baseX < 8; baseX++) {
|
||||
for (int baseY = 0; baseY < 8; baseY++) {
|
||||
// assign the array of strings to the current spot
|
||||
_lines[(baseX * 8 + baseY)] = lines;
|
||||
for (int slopeX = -1; slopeX < 2; slopeX++) {
|
||||
for (int slopeY = -1; slopeY < 2; slopeY++) {
|
||||
// don't include current spot in its own line
|
||||
if (slopeX == 0 && slopeY == 0)
|
||||
continue;
|
||||
|
||||
// assign the current line to the current spot in the lines array, uint saves us from bounds checking for below 0
|
||||
*lines = line;
|
||||
uint x = baseX + slopeX;
|
||||
uint y;
|
||||
for (y = baseY + slopeY; x < 8 && y < 8; y += slopeY) {
|
||||
*line = x * 8 + y;
|
||||
line++;
|
||||
x += slopeX;
|
||||
}
|
||||
if (baseX + slopeX != (int)x || baseY + slopeY != (int)y) {
|
||||
*line = baseX * 8 + baseY;
|
||||
line++;
|
||||
lines++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// append a 0 to the lines array to terminate that set of lines
|
||||
*lines = NULL;
|
||||
lines++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint OthelloGame::makeMove(Freeboard *freeboard, uint8 x, uint8 y) {
|
||||
Freeboard possibleMoves[30];
|
||||
Freeboard *board = freeboard;
|
||||
_isAiTurn = 0;
|
||||
uint numPossibleMoves = getAllPossibleMoves(board, possibleMoves);
|
||||
if (numPossibleMoves == 0)
|
||||
return 0;
|
||||
|
||||
if (x == '*') {
|
||||
_flag1 = 1;
|
||||
aiDoBestMove(freeboard);
|
||||
_flag1 = 0;
|
||||
_counter += 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// uint saves us from bounds checking below 0, not yet sure why this function uses y, x instead of x, y but it works
|
||||
if (y < 8 && x < 8 && board->_boardstate[y][x] == 0) {
|
||||
// find the pre-made board the represents this move
|
||||
uint newBoardSlot = 0;
|
||||
for (; newBoardSlot < numPossibleMoves && possibleMoves[newBoardSlot]._boardstate[y][x] == 0; newBoardSlot++) {
|
||||
}
|
||||
if (newBoardSlot == numPossibleMoves)
|
||||
return 0;
|
||||
|
||||
*freeboard = possibleMoves[newBoardSlot];
|
||||
_counter += 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte OthelloGame::getLeader(Freeboard *f) {
|
||||
byte counters[3] = {};
|
||||
|
||||
for (int x = 0; x < 8; x++) {
|
||||
for (int y = 0; y < 8; y++) {
|
||||
byte t = f->_boardstate[x][y];
|
||||
counters[t]++;
|
||||
}
|
||||
}
|
||||
|
||||
if (counters[2] < counters[1])
|
||||
return 1;
|
||||
if (counters[2] > counters[1])
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
void OthelloGame::opInit(byte *vars) {
|
||||
vars[0] = 0;
|
||||
restart();
|
||||
|
||||
for (int x = 0; x < 8; x++) {
|
||||
for (int y = 0; y < 8; y++) {
|
||||
vars[xyToVar(x, y)] = _lookupPlayer[_board._boardstate[x][y]];
|
||||
}
|
||||
}
|
||||
|
||||
vars[4] = 1;
|
||||
}
|
||||
|
||||
void OthelloGame::tickBoard() {
|
||||
if (_counter < 60) {
|
||||
if (_movesLateGame < _counter) {
|
||||
_isLateGame = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OthelloGame::opPlayerMove(byte *vars) {
|
||||
tickBoard();
|
||||
|
||||
if (_counter < 60) {
|
||||
_flag2 = 0;
|
||||
byte x = vars[3];
|
||||
byte y = vars[2];
|
||||
// top left spot is 0, 0
|
||||
debugC(1, kDebugLogic, "OthelloGame player moved to %d, %d", (int)x, (int)y);
|
||||
vars[4] = makeMove(&_board, x, y);
|
||||
} else {
|
||||
vars[0] = getLeader(&_board);
|
||||
vars[4] = 1;
|
||||
}
|
||||
writeBoardToVars(&_board, vars);
|
||||
}
|
||||
|
||||
// this might be for a hint move? maybe on easy mode?
|
||||
void OthelloGame::op3(byte *vars) {
|
||||
tickBoard();
|
||||
|
||||
if (_counter < 60) {
|
||||
vars[3] = '*';
|
||||
uint move = makeMove(&_board, '*', vars[2]);
|
||||
vars[4] = move;
|
||||
if (move == 0) {
|
||||
_flag2 = 1;
|
||||
} else {
|
||||
_flag2 = 0;
|
||||
}
|
||||
} else {
|
||||
vars[0] = getLeader(&_board);
|
||||
vars[4] = 1;
|
||||
}
|
||||
writeBoardToVars(&_board, vars);
|
||||
}
|
||||
|
||||
void OthelloGame::opAiMove(byte *vars) {
|
||||
tickBoard();
|
||||
|
||||
if (_counter < 60) {
|
||||
uint move = aiDoBestMove(&_board);
|
||||
vars[4] = move;
|
||||
if (move == 0 && _flag2 != 0) {
|
||||
vars[0] = getLeader(&_board);
|
||||
}
|
||||
} else {
|
||||
vars[0] = getLeader(&_board);
|
||||
vars[4] = 0;
|
||||
}
|
||||
writeBoardToVars(&_board, vars);
|
||||
}
|
||||
|
||||
void OthelloGame::op5(byte *vars) {
|
||||
_counter = vars[2];
|
||||
readBoardStateFromVars(vars);
|
||||
initLines();
|
||||
vars[4] = 1;
|
||||
}
|
||||
|
||||
OthelloGame::OthelloGame(bool easierAi)
|
||||
: _random("OthelloGame"),
|
||||
_depths {
|
||||
1, 4, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 4, 7, 6, 5, 4, 3, 2, 1, 1
|
||||
},
|
||||
_lookupPlayer { 21, 40, 31 },
|
||||
_scores {
|
||||
{ 30, 0, 0, 0 },
|
||||
{ 4, 0, 0, 0 },
|
||||
{ 5, 0, 0, 0 }
|
||||
},
|
||||
_edgesScores{
|
||||
0, 3, 6, 9, 3, 15, 12, 18,
|
||||
6, 0, 45, 6, 0, 3, 27, 12,
|
||||
60, 15, 9, 18, 36, 21, 24, 27,
|
||||
30, 24, 36, 33, 39, 27, 21, 3,
|
||||
27, 21, 24, 69, 33, 18, 36, 30,
|
||||
39, 78, 42, 45, 48, 51, 45, 57,
|
||||
54, 60, 48, 42, 87, 48, 42, 45,
|
||||
6, 54, 102, 57, 51, 60, 15, 63,
|
||||
66, 69, 72, 66, 78, 75, 81, 69,
|
||||
63, 24, 69, 63, 66, 69, 75, 39,
|
||||
78, 72, 81, 78, 84, 87, 90, 93,
|
||||
87, 99, 96, 102, 90, 84, 87, 90,
|
||||
84, 87, 48, 96, 102, 99, 93, 102,
|
||||
57, 0, 0, 0, 0, 0, 0, 0
|
||||
},
|
||||
_cornersScores{
|
||||
0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0,
|
||||
-20, 0, 0, 0, 20,
|
||||
0, -20, 0, 0, 0,
|
||||
20, 20, 20, 20, 20,
|
||||
20, 20, 20, 20, 20,
|
||||
20, 0, 20, 20, 20,
|
||||
40, 20, 0, 20, 20,
|
||||
20, 40, -20, -20, -20,
|
||||
-20, -20, -20, -20, -20,
|
||||
-20, -20, -40, -20, -20,
|
||||
-20, 0, -20, -40, -20,
|
||||
-20, -20, 0, 40, 40,
|
||||
40, 40, 40, 40, 40,
|
||||
40, 40, 40, 20, 40,
|
||||
40, 40, 40, 40, 20,
|
||||
40, 40, 40, 40, -40,
|
||||
-40, -40, -40, -40, -40,
|
||||
-40, -40, -40, -40, -40,
|
||||
-40, -40, -40, -20, -40,
|
||||
-40, -40, -40, -40, -20
|
||||
},
|
||||
_movesLateGame(52)
|
||||
{
|
||||
_isLateGame = false;
|
||||
_counter = 0;
|
||||
_isAiTurn = 0;
|
||||
_flag1 = 0;
|
||||
_flag2 = 0;
|
||||
initLines();
|
||||
|
||||
#if 0
|
||||
_easierAi = false;
|
||||
test();
|
||||
#endif
|
||||
_easierAi = easierAi;
|
||||
}
|
||||
|
||||
void OthelloGame::run(byte *vars) {
|
||||
byte op = vars[1];
|
||||
debugC(1, kDebugLogic, "OthelloGame op %d", (int)op);
|
||||
|
||||
switch (op) {
|
||||
case 0: // init/restart
|
||||
opInit(vars);
|
||||
break;
|
||||
case 1: // win/lose?
|
||||
_flag2 = 1;
|
||||
break;
|
||||
case 2: // player move
|
||||
opPlayerMove(vars);
|
||||
break;
|
||||
case 3: // ???
|
||||
op3(vars);
|
||||
break;
|
||||
case 4: // ai move
|
||||
opAiMove(vars);
|
||||
break;
|
||||
case 5: // ???
|
||||
op5(vars);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OthelloGame::test() {
|
||||
warning("OthelloGame::test() starting");
|
||||
// pairs of x, y, 3 moves per line
|
||||
testMatch({
|
||||
// x1,y1,x2,y2,x3,y3
|
||||
5, 4, 5, 2, 3, 2,
|
||||
6, 6, 1, 2, 1, 0
|
||||
}, true);
|
||||
|
||||
testMatch({
|
||||
// x1,y1,x2,y2,x3,y3
|
||||
5, 4, 6, 2, 4, 2,
|
||||
5, 1, 5, 5, 3, 5,
|
||||
1, 5, 2, 4, 6, 1,
|
||||
6, 4, 6, 3, 7, 4,
|
||||
7, 1, 6, 0, 1, 4,
|
||||
2, 2, 1, 3, 6, 6,
|
||||
6, 7, 0, 6, 2, 6,
|
||||
4, 6, 3, 6, 5, 6,
|
||||
1, 6, 1, 1, 2, 1,
|
||||
3, 1, 3, 0, 0, 2,
|
||||
2, 7
|
||||
// x1,y1,x2,y2,x3,y3
|
||||
}, false);
|
||||
|
||||
warning("OthelloGame::test() finished");
|
||||
}
|
||||
|
||||
void OthelloGame::testMatch(Common::Array<int> moves, bool playerWin) {
|
||||
byte vars[1024];
|
||||
memset(vars, 0, sizeof(vars));
|
||||
byte &op = vars[1];
|
||||
byte &x = vars[3];
|
||||
byte &y = vars[2];
|
||||
byte &winner = vars[4];
|
||||
byte &winner2 = vars[0];
|
||||
|
||||
warning("OthelloGame::testMatch(%u, %d) starting", moves.size(), (int)playerWin);
|
||||
op = 0;
|
||||
run(vars);
|
||||
|
||||
for (uint i = 0; i < moves.size(); i += 2) {
|
||||
if (winner2 != 0)
|
||||
error("early winner? %d, %d", (int)winner, (int)winner2);
|
||||
|
||||
x = moves[i];
|
||||
y = moves[i + 1];
|
||||
op = 2;
|
||||
run(vars);
|
||||
|
||||
if (winner != 1)
|
||||
error("early winner? %d, %d", (int)winner, (int)winner2);
|
||||
|
||||
op = 4;
|
||||
run(vars);
|
||||
}
|
||||
|
||||
if (playerWin && winner2 != 0)
|
||||
error("player didn't win, %d", (int)winner2);
|
||||
else if (playerWin == false && winner2 != 1)
|
||||
error("ai didn't win? %d", (int)winner2);
|
||||
|
||||
warning("OthelloGame::testMatch(%u, %d) finished", moves.size(), (int)playerWin);
|
||||
}
|
||||
|
||||
} // namespace Groovie
|
||||
101
engines/groovie/logic/othello.h
Normal file
101
engines/groovie/logic/othello.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_LOGIC_OTHELLO_H
|
||||
#define GROOVIE_LOGIC_OTHELLO_H
|
||||
|
||||
#include "common/random.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
/*
|
||||
* Othello/Reversi Cursed Coins puzzle in Clandestiny and UHP.
|
||||
*/
|
||||
struct Freeboard {
|
||||
int _score;
|
||||
byte _boardstate[8][8]; // 0 is empty, 1 is player, 2 is AI
|
||||
|
||||
// for sorting an array of pointers
|
||||
friend bool operator<(const Freeboard &a, const Freeboard &b) {
|
||||
return a._score > b._score;
|
||||
}
|
||||
};
|
||||
|
||||
class OthelloGame {
|
||||
public:
|
||||
OthelloGame(bool easierAi);
|
||||
void run(byte *scriptVariables);
|
||||
|
||||
private:
|
||||
int scoreEdge(byte (&board)[8][8], int x, int y, int slopeX, int slopeY);
|
||||
int scoreEarlyGame(Freeboard *freeboard);
|
||||
int scoreLateGame(Freeboard *freeboard);
|
||||
int scoreBoard(Freeboard *board);
|
||||
void restart(void);
|
||||
void writeBoardToVars(Freeboard *board, byte *vars);
|
||||
void readBoardStateFromVars(byte *vars);
|
||||
Freeboard getPossibleMove(Freeboard *freeboard, int moveSpot);
|
||||
void checkPossibleMove(Freeboard *board, Freeboard (&boards)[30], int8 **lineSpot, int &numPossibleMoves, int moveSpot, byte player, byte opponent);
|
||||
int getAllPossibleMoves(Freeboard *board, Freeboard (&boards)[30]);
|
||||
int aiRecurse(Freeboard *board, int depth, int parentScore, int opponentBestScore);
|
||||
byte aiDoBestMove(Freeboard *pBoard);
|
||||
void initLines(void);
|
||||
uint makeMove(Freeboard *freeboard, uint8 x, uint8 y);
|
||||
byte getLeader(Freeboard *f);
|
||||
void opInit(byte *vars);
|
||||
void tickBoard();
|
||||
void opPlayerMove(byte *vars);
|
||||
void op3(byte *vars);
|
||||
void opAiMove(byte *vars);
|
||||
void op5(byte *vars);
|
||||
|
||||
void test();
|
||||
void testMatch(Common::Array<int> moves, bool playerWin);
|
||||
|
||||
Common::RandomSource _random;
|
||||
byte _flag1;
|
||||
int8 _flag2;
|
||||
const int _depths[60];
|
||||
int _counter;
|
||||
const int _movesLateGame; // this is 52, seems to be a marker of when to change the function pointer to an aleternate scoring algorithm for the late game
|
||||
bool _isLateGame; // used to choose the scoring function, true means scoreLateGame
|
||||
const int8 _lookupPlayer[3]; // used to convert from internal values that represent piece colors to what the script uses in vars, {21, 40, 31}
|
||||
const int8 _scores[3][4];
|
||||
const int8 _edgesScores[112];
|
||||
const int _cornersScores[105];
|
||||
int _isAiTurn;
|
||||
int8 **_lines[64];
|
||||
int8 *_linesStorage[484];
|
||||
int8 _lineStorage[2016];
|
||||
Freeboard _board;
|
||||
bool _easierAi;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_LOGIC_OTHELLO_H
|
||||
891
engines/groovie/logic/pente.cpp
Normal file
891
engines/groovie/logic/pente.cpp
Normal file
@@ -0,0 +1,891 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/logic/pente.h"
|
||||
#include "common/stack.h"
|
||||
#include "common/algorithm.h"
|
||||
#include "groovie/groovie.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
#ifdef UINT_MAX
|
||||
#undef UINT_MAX
|
||||
#endif
|
||||
const uint UINT_MAX = (uint)-1;
|
||||
const int WIN_SCORE = 100000000;
|
||||
const int CAPTURE_SCORE = 1000000;
|
||||
const uint PLAYER = 79;
|
||||
const uint STAUF = 88;
|
||||
|
||||
struct pentePlayerTable {
|
||||
Common::FixedStack<int, 813> lines;
|
||||
};
|
||||
|
||||
struct penteTable {
|
||||
pentePlayerTable player;
|
||||
pentePlayerTable stauf;
|
||||
int playerScore;
|
||||
int staufScore;
|
||||
byte playerLines;
|
||||
byte staufLines;
|
||||
byte width;
|
||||
byte height;
|
||||
uint16 boardSize;
|
||||
byte lineLength;
|
||||
uint16 moveCounter;
|
||||
byte boardState[20][15];
|
||||
uint16 linesCounter;
|
||||
uint16 linesTable[20][15][21];
|
||||
byte numAdjacentPieces[20][15];
|
||||
byte calcTouchingPieces; // the deepest level of AI recursion sets this to 0, and then sets it back to 1 when returning
|
||||
};
|
||||
|
||||
void PenteGame::addLine(int x, int y, int linesCounter) {
|
||||
int i = ++_table->linesTable[x][y][0];
|
||||
_table->linesTable[x][y][i] = linesCounter;
|
||||
}
|
||||
|
||||
void PenteGame::buildLookupTable() {
|
||||
int width = _table->width;
|
||||
int height = _table->height;
|
||||
uint16 linesCounter = 0;
|
||||
int lineLength = _table->lineLength;
|
||||
|
||||
// slope of (1,0)
|
||||
for (int x = 0; x <= width - lineLength; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int z = 0; z < lineLength; z++) {
|
||||
addLine(x + z, y, linesCounter);
|
||||
}
|
||||
linesCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
// slope of (0,1)
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y <= height - lineLength; y++) {
|
||||
for (int z = 0; z < lineLength; z++) {
|
||||
addLine(x, y + z, linesCounter);
|
||||
}
|
||||
linesCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
// slope of (1,1)
|
||||
for (int x = 0; x <= width - lineLength; x++) {
|
||||
for (int y = 0; y <= height - lineLength; y++) {
|
||||
for (int z = 0; z < lineLength; z++) {
|
||||
addLine(x + z, y + z, linesCounter);
|
||||
}
|
||||
linesCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
// slope of (1,-1)
|
||||
for (int x = 0; x <= width - lineLength; x++) {
|
||||
for (int y = lineLength - 1; y < height; y++) {
|
||||
for (int z = 0; z < lineLength; z++) {
|
||||
addLine(x + z, y - z, linesCounter);
|
||||
}
|
||||
linesCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
_table->linesCounter = linesCounter;
|
||||
}
|
||||
|
||||
void PenteGame::penteDeInit() {
|
||||
delete _table;
|
||||
_table = nullptr;
|
||||
}
|
||||
|
||||
void PenteGame::penteInit(uint width, uint height, uint length) {
|
||||
_table = new penteTable();
|
||||
_table->width = width;
|
||||
_table->height = height;
|
||||
_table->boardSize = height * width;
|
||||
_table->lineLength = length;
|
||||
memset(_table->boardState, 0, sizeof(_table->boardState));
|
||||
|
||||
buildLookupTable();
|
||||
assert(_table->linesCounter == 812);
|
||||
|
||||
_table->staufScore = (uint)_table->linesCounter;
|
||||
_table->playerScore = (uint)_table->linesCounter;
|
||||
memset(_table->numAdjacentPieces, 0, sizeof(_table->numAdjacentPieces));
|
||||
|
||||
_table->calcTouchingPieces = 1;
|
||||
|
||||
_nextCapturedSpot = -1;
|
||||
_animateCapturesBitMask = 0;
|
||||
_previousMove = 0;
|
||||
}
|
||||
|
||||
int &PenteGame::getPlayerTable(bool staufTurn, pentePlayerTable *&pt) {
|
||||
pt = staufTurn ? &_table->stauf : &_table->player;
|
||||
return staufTurn ? _table->staufScore : _table->playerScore;
|
||||
}
|
||||
|
||||
void PenteGame::scoreLine(uint16 lineIndex, bool isStaufTurn, bool revert) {
|
||||
pentePlayerTable *playerTable;
|
||||
int &score = getPlayerTable(isStaufTurn, playerTable);
|
||||
|
||||
int lineLength, mult;
|
||||
if (revert) {
|
||||
lineLength = --playerTable->lines[lineIndex];
|
||||
mult = -1;
|
||||
} else {
|
||||
lineLength = playerTable->lines[lineIndex]++;
|
||||
mult = 1;
|
||||
}
|
||||
|
||||
if (_table->lineLength - lineLength == 1) {
|
||||
score = (int)score + (int)WIN_SCORE * mult;
|
||||
} else {
|
||||
pentePlayerTable *opponentTable;
|
||||
int &opponentScore = getPlayerTable(!isStaufTurn, opponentTable);
|
||||
int opponentLineLength = opponentTable->lines[lineIndex];
|
||||
if (lineLength == 0) {
|
||||
opponentScore += (-(1 << ((byte)opponentLineLength & 0x1f))) * mult;
|
||||
if (_table->lineLength - opponentLineLength == 1) {
|
||||
if (isStaufTurn)
|
||||
_table->playerLines -= mult;
|
||||
else
|
||||
_table->staufLines -= mult;
|
||||
}
|
||||
}
|
||||
if (opponentLineLength == 0) {
|
||||
score += (1 << ((byte)lineLength & 0x1f)) * mult;
|
||||
if (_table->lineLength - lineLength == 2) {
|
||||
byte b;
|
||||
if (isStaufTurn)
|
||||
b = (_table->staufLines += mult);
|
||||
else
|
||||
b = (_table->playerLines += mult);
|
||||
|
||||
if (revert)
|
||||
b -= mult;
|
||||
|
||||
if (1 < b) {
|
||||
score = (int)score + (int)CAPTURE_SCORE * mult;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PenteGame::calcTouchingPieces(byte moveX, byte moveY, bool revert) {
|
||||
byte endX, endY;
|
||||
|
||||
if (moveX + 1 < _table->width) {
|
||||
endX = moveX + 1;
|
||||
} else {
|
||||
endX = _table->width - 1;
|
||||
}
|
||||
|
||||
if (moveY + 1 < _table->height) {
|
||||
endY = moveY + 1;
|
||||
} else {
|
||||
endY = _table->height - 1;
|
||||
}
|
||||
|
||||
byte x = 0;
|
||||
if (1 < moveX) {
|
||||
x = moveX - 1;
|
||||
}
|
||||
|
||||
for (; x <= endX; x++) {
|
||||
byte y = 0;
|
||||
if (1 < moveY) {
|
||||
y = moveY - 1;
|
||||
}
|
||||
|
||||
for (; y <= endY; y++) {
|
||||
if (revert)
|
||||
_table->numAdjacentPieces[x][y]--;
|
||||
else
|
||||
_table->numAdjacentPieces[x][y]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PenteGame::updateScore(byte x, byte y, bool isStauf) {
|
||||
assert(_table->boardState[x][y] == 0);
|
||||
_table->boardState[x][y] = isStauf ? STAUF : PLAYER;
|
||||
uint16 lines = _table->linesTable[x][y][0];
|
||||
|
||||
for (int i = 1; i <= lines; i++) {
|
||||
uint16 lineIndex = _table->linesTable[x][y][i];
|
||||
scoreLine(lineIndex, isStauf, false);
|
||||
}
|
||||
|
||||
if (_table->calcTouchingPieces != 0) {
|
||||
calcTouchingPieces(x, y, false);
|
||||
}
|
||||
|
||||
_table->moveCounter++;
|
||||
}
|
||||
|
||||
void PenteGame::revertScore(byte x, byte y) {
|
||||
assert(_table->boardState[x][y] != 0);
|
||||
bool stauf_turn = _table->boardState[x][y] == STAUF;
|
||||
_table->boardState[x][y] = 0;
|
||||
_table->moveCounter--;
|
||||
uint lines = _table->linesTable[x][y][0];
|
||||
|
||||
for (uint i = 1; i <= lines; i++) {
|
||||
uint16 lineIndex = _table->linesTable[x][y][i];
|
||||
scoreLine(lineIndex, stauf_turn, true);
|
||||
}
|
||||
|
||||
if (_table->calcTouchingPieces != 0) {
|
||||
calcTouchingPieces(x, y, true);
|
||||
}
|
||||
}
|
||||
|
||||
byte PenteGame::scoreCaptureSingle(byte x, byte y, int slopeX, int slopeY) {
|
||||
byte x1 = x + slopeX;
|
||||
byte y1 = y + slopeY;
|
||||
byte x2 = x + slopeX * 2;
|
||||
byte y2 = y + slopeY * 2;
|
||||
byte endX = x + slopeX * 3;
|
||||
byte endY = y + slopeY * 3;
|
||||
|
||||
// we don't need to check for below 0 when we have unsigned types
|
||||
if (x >= _table->width || y >= _table->height)
|
||||
return 0;
|
||||
if (endX >= _table->width || endY >= _table->height)
|
||||
return 0;
|
||||
|
||||
auto &boardState = _table->boardState;
|
||||
byte captor = boardState[x][y];
|
||||
byte captive = captor == STAUF ? PLAYER : STAUF;
|
||||
|
||||
// make sure the captor is at the start and end of the line
|
||||
if (boardState[endX][endY] != captor)
|
||||
return 0;
|
||||
|
||||
// check that the captive is both of the middle pieces
|
||||
if (boardState[x1][y1] != captive || boardState[x2][y2] != captive)
|
||||
return 0;
|
||||
|
||||
// now we take away the points the captor had for these pieces
|
||||
revertScore(x1, y1);
|
||||
revertScore(x2, y2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct Slope {
|
||||
int x, y;
|
||||
};
|
||||
// the order of these is important because we return the bitMask
|
||||
Slope slopes[] = {{1, 0},
|
||||
{1, 1},
|
||||
{0, 1},
|
||||
{-1, 1},
|
||||
{-1, 0},
|
||||
{-1, -1},
|
||||
{0, -1},
|
||||
{1, -1}};
|
||||
|
||||
uint PenteGame::scoreCapture(byte x, byte y) {
|
||||
byte bitMask = 0;
|
||||
bool isStauf = _table->boardState[x][y] == STAUF;
|
||||
|
||||
for (const Slope &slope : slopes) {
|
||||
bitMask <<= 1;
|
||||
bitMask |= scoreCaptureSingle(x, y, slope.x, slope.y);
|
||||
}
|
||||
|
||||
for (int i = bitMask; i; i >>= 1) {
|
||||
if ((i & 1) == 0)
|
||||
continue;
|
||||
pentePlayerTable *playerTable;
|
||||
int &score = getPlayerTable(isStauf, playerTable);
|
||||
|
||||
int lineLength = ++playerTable->lines[_table->linesCounter];
|
||||
if (_table->lineLength == lineLength) {
|
||||
score += WIN_SCORE;
|
||||
} else {
|
||||
score += 1 << ((lineLength - 1U) & 0x1f);
|
||||
}
|
||||
}
|
||||
return bitMask;
|
||||
}
|
||||
|
||||
void PenteGame::animateCapture(int16 move, byte *bitMaskG, int16 *outCapture1, int16 *outCapture2) {
|
||||
int x = move / 15;
|
||||
int y = 0xe - (move % 15);
|
||||
|
||||
byte &bitMask = *bitMaskG;
|
||||
byte bit = 0;
|
||||
for (bit = 0; bit < 8; bit++) {
|
||||
if (bitMask >> bit & 1) {
|
||||
bitMask = 1 << bit ^ bitMask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int16 baseSpot;
|
||||
switch (bit) {
|
||||
case 0:
|
||||
*outCapture1 = (x + 2) * 15 - y;
|
||||
*outCapture2 = x * 15 - y + 46;
|
||||
return;
|
||||
case 1:
|
||||
*outCapture1 = (x + 1) * 15 - y;
|
||||
*outCapture2 = x * 15 - y + 16;
|
||||
return;
|
||||
case 2:
|
||||
baseSpot = x * 15 - y;
|
||||
*outCapture1 = baseSpot;
|
||||
*outCapture2 = baseSpot - 14;
|
||||
return;
|
||||
case 3:
|
||||
baseSpot = x * 15 - y;
|
||||
*outCapture1 = baseSpot - 1;
|
||||
*outCapture2 = baseSpot - 16;
|
||||
return;
|
||||
case 4:
|
||||
baseSpot = x * 15 - y;
|
||||
*outCapture1 = baseSpot - 2;
|
||||
*outCapture2 = baseSpot - 18;
|
||||
return;
|
||||
case 5:
|
||||
baseSpot = x * 15 - y;
|
||||
*outCapture1 = baseSpot + 13;
|
||||
*outCapture2 = baseSpot + 12;
|
||||
return;
|
||||
case 6:
|
||||
baseSpot = x * 15 - y;
|
||||
*outCapture1 = baseSpot + 28;
|
||||
*outCapture2 = baseSpot + 42;
|
||||
return;
|
||||
case 7:
|
||||
baseSpot = x * 15 - y;
|
||||
*outCapture1 = baseSpot + 29;
|
||||
*outCapture2 = baseSpot + 44;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void PenteGame::revertCapture(byte x, byte y, byte bitMask) {
|
||||
bool isPlayer = _table->boardState[x][y] == PLAYER;
|
||||
for (int i = bitMask; i; i >>= 1) {
|
||||
if ((i & 1) == 0)
|
||||
continue;
|
||||
|
||||
pentePlayerTable *playerTable;
|
||||
int &score = getPlayerTable(!isPlayer, playerTable);
|
||||
|
||||
int linesCounter = --playerTable->lines[_table->linesCounter];
|
||||
|
||||
if (_table->lineLength - linesCounter == 1) {
|
||||
score -= WIN_SCORE;
|
||||
} else {
|
||||
score -= 1 << linesCounter;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((bitMask >> i & 1) == 0)
|
||||
continue;
|
||||
|
||||
Slope &slope = slopes[7 - i];
|
||||
updateScore(x + slope.x * 2, y + slope.y * 2, isPlayer);
|
||||
updateScore(x + slope.x, y + slope.y, isPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
int PenteGame::scoreMoveAndRevert(byte x, byte y, char depth, int parentScore, bool &gameOver) {
|
||||
updateScore(x, y, _table->moveCounter % 2);
|
||||
uint capturesMask = scoreCapture(x, y);
|
||||
|
||||
if (_table->playerScore >= WIN_SCORE || _table->staufScore >= WIN_SCORE)
|
||||
gameOver = true;
|
||||
else
|
||||
gameOver = false;
|
||||
|
||||
int scoreDiff;
|
||||
if (depth > 0 && gameOver==false && _table->boardSize != _table->moveCounter) {
|
||||
scoreDiff = aiRecurse(depth, parentScore);
|
||||
} else {
|
||||
if (_table->moveCounter % 2 == 0) {
|
||||
scoreDiff = _table->playerScore - _table->staufScore;
|
||||
} else {
|
||||
scoreDiff = _table->staufScore - _table->playerScore;
|
||||
}
|
||||
}
|
||||
if (capturesMask != 0) {
|
||||
revertCapture(x, y, capturesMask);
|
||||
}
|
||||
revertScore(x, y);
|
||||
return scoreDiff;
|
||||
}
|
||||
|
||||
int PenteGame::scoreMoveAndRevert(byte x, byte y, char depth, int parentScore) {
|
||||
// same thing, but don't need to provide the reference to the gameOverBool
|
||||
bool gameOver;
|
||||
return scoreMoveAndRevert(x, y, depth, parentScore, gameOver);
|
||||
}
|
||||
|
||||
int PenteGame::aiRecurseTail(int parentScore) {
|
||||
int bestScore = 0x7fffffff;
|
||||
|
||||
_table->calcTouchingPieces = 0;
|
||||
for (byte x = 0; x < _table->width; x++) {
|
||||
for (byte y = 0; y < _table->height; y++) {
|
||||
if ((_table->boardState[x][y] != 0) ||
|
||||
(_table->numAdjacentPieces[x][y] == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int scoreDiff = scoreMoveAndRevert(x, y, 0, 0);
|
||||
if (scoreDiff < bestScore) {
|
||||
bestScore = scoreDiff;
|
||||
}
|
||||
if (-parentScore != bestScore && parentScore <= -bestScore) {
|
||||
_table->calcTouchingPieces = 1;
|
||||
return -bestScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
_table->calcTouchingPieces = 1;
|
||||
|
||||
return -bestScore;
|
||||
}
|
||||
|
||||
int PenteGame::aiRecurse(char depth, int parentScore) {
|
||||
if (depth == 1) {
|
||||
// don't do more recursion
|
||||
return aiRecurseTail(parentScore);
|
||||
}
|
||||
|
||||
// do more recursion after finding some good moves
|
||||
struct GoodMove {
|
||||
int scoreDiff;
|
||||
byte x, y;
|
||||
|
||||
bool operator()(GoodMove a, GoodMove b) {
|
||||
return a.scoreDiff < b.scoreDiff;
|
||||
}
|
||||
};
|
||||
Common::FixedStack<GoodMove, 300> goodMoves; // 300 slots because the board is 20x15, but we rarely need many since the search excludes spots with no adjacenet pieces
|
||||
int bestScore = 0x7fffffff;
|
||||
|
||||
for (byte x = 0; x < _table->width; x++) {
|
||||
for (byte y = 0; y < _table->height; y++) {
|
||||
if ((_table->boardState[x][y] != 0) ||
|
||||
(_table->numAdjacentPieces[x][y] == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int scoreDiff = scoreMoveAndRevert(x, y, 0, 0);
|
||||
goodMoves.push({scoreDiff, x, y});
|
||||
}
|
||||
}
|
||||
|
||||
// sort ascending by scoreDiff, most of the time you'll see scores like -40 at the top and -34 at the end
|
||||
Common::sort(&goodMoves[0], &goodMoves.top(), goodMoves[0]);
|
||||
|
||||
for (uint i = 0; i < goodMoves.size(); i++) {
|
||||
byte x = goodMoves[i].x;
|
||||
byte y = goodMoves[i].y;
|
||||
|
||||
int scoreDiff = scoreMoveAndRevert(x, y, depth - 1, bestScore);
|
||||
if (scoreDiff < bestScore) {
|
||||
bestScore = scoreDiff;
|
||||
}
|
||||
if (-parentScore != bestScore && parentScore <= -bestScore)
|
||||
break;
|
||||
}
|
||||
return -bestScore;
|
||||
}
|
||||
|
||||
uint16 PenteGame::aiGetBestMove(byte depth) {
|
||||
for (int x = 0; x < _table->width; x++) {
|
||||
for (int y = 0; y < _table->height; y++) {
|
||||
if (_table->boardState[x][y] != 0 || _table->numAdjacentPieces[x][y] == 0)
|
||||
continue;
|
||||
|
||||
bool gameOver;
|
||||
scoreMoveAndRevert(x, y, 0, 0, gameOver);
|
||||
if (gameOver) {
|
||||
return y + x * 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte counter = 1;
|
||||
int bestScore = 0x7fffffff;
|
||||
uint16 bestMove = 0xffff;
|
||||
|
||||
for (; bestScore > 99999999 && depth > 1; depth--) {
|
||||
for (int x = 0; x < _table->width; x++) {
|
||||
for (int y = 0; y < _table->height; y++) {
|
||||
if (_table->boardState[x][y] != 0 || _table->numAdjacentPieces[x][y] == 0)
|
||||
continue;
|
||||
|
||||
int scoreRecurse = scoreMoveAndRevert(x, y, depth - 1, bestScore);
|
||||
|
||||
if (scoreRecurse < bestScore) {
|
||||
counter = 1;
|
||||
bestMove = x * 100 + y;
|
||||
bestScore = scoreRecurse;
|
||||
} else {
|
||||
if (scoreRecurse == bestScore) {
|
||||
counter += 1;
|
||||
uint rng = _random.getRandomNumber(UINT_MAX);
|
||||
if ((rng % CAPTURE_SCORE) * counter < CAPTURE_SCORE) {
|
||||
bestMove = x * 100 + y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestMove;
|
||||
}
|
||||
|
||||
int varsMoveToXY(byte hundreds, byte tens, byte ones, byte &x, byte &y) {
|
||||
int move = hundreds * 100 + tens * 10 + ones;
|
||||
x = move / 15;
|
||||
y = 14 - move % 15;
|
||||
return move;
|
||||
}
|
||||
|
||||
void aiMoveToXY(int move, byte &x, byte &y) {
|
||||
x = move / 100;
|
||||
y = move % 100;
|
||||
}
|
||||
|
||||
void moveToVars(int move, byte &hundreds, byte &tens, byte &ones) {
|
||||
hundreds = move / 100;
|
||||
tens = move % 100 / 10;
|
||||
ones = move % 10;
|
||||
}
|
||||
|
||||
int xyToMove(uint x, uint y) {
|
||||
return x * 15 - y + 14;
|
||||
}
|
||||
|
||||
void moveXYToVars(uint x, uint y, byte &hundreds, byte &tens, byte &ones) {
|
||||
int move = xyToMove(x, y);
|
||||
moveToVars(move, hundreds, tens, ones);
|
||||
}
|
||||
|
||||
void PenteGame::animateCapturesCheckWinner(byte *vars) {
|
||||
if (_animateCapturesBitMask != 0 && _nextCapturedSpot < 0) {
|
||||
int16 capturedSpot;
|
||||
animateCapture(_previousMove, &_animateCapturesBitMask, &capturedSpot, &_nextCapturedSpot);
|
||||
vars[5] = 1;
|
||||
moveToVars(capturedSpot, vars[0], vars[1], vars[2]);
|
||||
return;
|
||||
}
|
||||
if (_animateCapturesBitMask != 0 || _nextCapturedSpot > -1) {
|
||||
moveToVars(_nextCapturedSpot, vars[0], vars[1], vars[2]);
|
||||
_nextCapturedSpot = -1;
|
||||
vars[5] = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_table->playerScore >= WIN_SCORE || _table->moveCounter >= _table->boardSize)
|
||||
vars[5] = 3; // player wins
|
||||
else if (_table->staufScore >= WIN_SCORE)
|
||||
vars[5] = 2; // Stauf wins
|
||||
else {
|
||||
// the match continues
|
||||
vars[5] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
penteDeInit();
|
||||
}
|
||||
|
||||
void PenteGame::opQueryPiece(byte *vars) {
|
||||
// this runs multiple times to check if pieces belong to stauf or the player?
|
||||
// this happens when you close the gamebook
|
||||
byte x, y;
|
||||
varsMoveToXY(vars[0], vars[1], vars[2], x, y);
|
||||
byte piece = _table->boardState[x][y];
|
||||
if (piece == 0) {
|
||||
vars[3] = 0;
|
||||
return;
|
||||
}
|
||||
if (piece == 0x4f) {
|
||||
vars[3] = 2;
|
||||
return;
|
||||
}
|
||||
if (piece != 0x58) {
|
||||
return;
|
||||
}
|
||||
vars[3] = 1;
|
||||
}
|
||||
|
||||
void PenteGame::run(byte *vars) {
|
||||
byte op = vars[4];
|
||||
if (_table == nullptr && op != 0) {
|
||||
debugC(kDebugLogic, "pente Init, seed %u", _random.getSeed());
|
||||
penteInit(20, 15, 5);
|
||||
}
|
||||
debugC(kDebugLogic, "penteOp vars[4]: %d", (int)op);
|
||||
|
||||
switch (op) {
|
||||
case 0:
|
||||
penteDeInit();
|
||||
return;
|
||||
case 1:
|
||||
byte x, y;
|
||||
_previousMove = varsMoveToXY(vars[0], vars[1], vars[2], x, y);
|
||||
debugC(kDebugLogic, "player moved to %d, %d", (int)x, (int)y);
|
||||
updateScore(x, y, _table->moveCounter % 2);
|
||||
_animateCapturesBitMask = scoreCapture(x, y);
|
||||
return;
|
||||
case 2:
|
||||
case 4:
|
||||
animateCapturesCheckWinner(vars);
|
||||
return;
|
||||
case 3:
|
||||
break;
|
||||
case 5:
|
||||
opQueryPiece(vars);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
byte aiDepth = vars[6];
|
||||
if (aiDepth == 0) {
|
||||
aiDepth = 3;
|
||||
} else if (aiDepth == 1) {
|
||||
aiDepth = 4;
|
||||
} else {
|
||||
aiDepth = 5;
|
||||
}
|
||||
|
||||
if (aiDepth != 2) {
|
||||
if (_easierAi && aiDepth > 2)
|
||||
aiDepth = 2;
|
||||
_previousMove = aiGetBestMove(aiDepth);
|
||||
}
|
||||
else
|
||||
warning("pente unknown else");
|
||||
|
||||
byte x, y;
|
||||
aiMoveToXY(_previousMove, x, y);
|
||||
debugC(kDebugLogic, "Stauf moved to %d, %d", (int)x, (int)y);
|
||||
updateScore(x, y, _table->moveCounter % 2);
|
||||
_animateCapturesBitMask = scoreCapture(x, y);
|
||||
_previousMove = xyToMove(x, y);
|
||||
moveXYToVars(x, y, vars[0], vars[1], vars[2]);
|
||||
}
|
||||
|
||||
PenteGame::PenteGame(bool easierAi) : _random("PenteGame") {
|
||||
_table = nullptr;
|
||||
_nextCapturedSpot = -1;
|
||||
_animateCapturesBitMask = 0;
|
||||
_previousMove = 0;
|
||||
#if 0
|
||||
_easierAi = false;
|
||||
test();
|
||||
#endif
|
||||
_easierAi = easierAi;
|
||||
}
|
||||
|
||||
void PenteGame::test() {
|
||||
warning("starting PenteGame::test()");
|
||||
uint32 oldSeed = _random.getSeed();
|
||||
|
||||
// 6 moves per line
|
||||
testGame(3,
|
||||
{
|
||||
/*x=*/10, /*y=*/6, /*x=*/9, /*y=*/6, /*x=*/10, /*y=*/7, /*x=*/10, /*y=*/5, /*x=*/10, /*y=*/8, /*x=*/9, /*y=*/9,
|
||||
/*x=*/10, /*y=*/9, /*x=*/10, /*y=*/10, /*x=*/9, /*y=*/8, /*x=*/8, /*y=*/7, /*x=*/8, /*y=*/8, /*x=*/7, /*y=*/8,
|
||||
/*x=*/6, /*y=*/9, /*x=*/11, /*y=*/4,
|
||||
}, false);
|
||||
|
||||
testGame(10,
|
||||
{
|
||||
/*x=*/10, /*y=*/6, /*x=*/11, /*y=*/7, /*x=*/10, /*y=*/5, /*x=*/10, /*y=*/7, /*x=*/9, /*y=*/7, /*x=*/12, /*y=*/7,
|
||||
/*x=*/10, /*y=*/4, /*x=*/8, /*y=*/8, /*x=*/10, /*y=*/3, /*x=*/11, /*y=*/5, /*x=*/10, /*y=*/2, /*x=*/9, /*y=*/7,
|
||||
/*x=*/10, /*y=*/6,
|
||||
}, true);
|
||||
|
||||
// test bottom left corner
|
||||
testGame(1993,
|
||||
{
|
||||
/*x=*/0, /*y=*/0, /*x=*/1, /*y=*/1, /*x=*/1, /*y=*/0, /*x=*/2, /*y=*/0, /*x=*/0, /*y=*/1, /*x=*/0, /*y=*/2,
|
||||
/*x=*/2, /*y=*/1, /*x=*/3, /*y=*/2, /*x=*/1, /*y=*/2, /*x=*/2, /*y=*/3, /*x=*/4, /*y=*/1, /*x=*/1, /*y=*/4,
|
||||
/*x=*/5, /*y=*/1, /*x=*/6, /*y=*/1, /*x=*/3, /*y=*/0, /*x=*/5, /*y=*/2, /*x=*/4, /*y=*/3, /*x=*/3, /*y=*/1,
|
||||
/*x=*/3, /*y=*/3, /*x=*/5, /*y=*/3, /*x=*/4, /*y=*/1, /*x=*/4, /*y=*/3, /*x=*/3, /*y=*/3, /*x=*/3, /*y=*/4,
|
||||
/*x=*/2, /*y=*/5, /*x=*/7, /*y=*/0
|
||||
}, false);
|
||||
|
||||
// test bottom right corner
|
||||
testGame(1995,
|
||||
{
|
||||
/*x=*/19, /*y=*/0, /*x=*/18, /*y=*/1, /*x=*/19, /*y=*/1, /*x=*/18, /*y=*/2, /*x=*/18, /*y=*/0, /*x=*/18, /*y=*/3,
|
||||
/*x=*/18, /*y=*/4, /*x=*/17, /*y=*/5, /*x=*/17, /*y=*/0, /*x=*/16, /*y=*/5, /*x=*/17, /*y=*/4, /*x=*/16, /*y=*/4,
|
||||
/*x=*/18, /*y=*/5, /*x=*/18, /*y=*/6, /*x=*/18, /*y=*/5, /*x=*/15, /*y=*/3, /*x=*/18, /*y=*/4, /*x=*/14, /*y=*/2,
|
||||
}, false);
|
||||
|
||||
// test top left corner
|
||||
testGame(1996,
|
||||
{
|
||||
/*x=*/0, /*y=*/14, /*x=*/1, /*y=*/13, /*x=*/1, /*y=*/14, /*x=*/2, /*y=*/14, /*x=*/0, /*y=*/13, /*x=*/0, /*y=*/12,
|
||||
/*x=*/1, /*y=*/12, /*x=*/2, /*y=*/11, /*x=*/2, /*y=*/12, /*x=*/3, /*y=*/12, /*x=*/4, /*y=*/13, /*x=*/1, /*y=*/10,
|
||||
/*x=*/0, /*y=*/9, /*x=*/3, /*y=*/10, /*x=*/1, /*y=*/12, /*x=*/4, /*y=*/9, /*x=*/5, /*y=*/8, /*x=*/6, /*y=*/9,
|
||||
/*x=*/3, /*y=*/11, /*x=*/6, /*y=*/10, /*x=*/6, /*y=*/11, /*x=*/4, /*y=*/8, /*x=*/3, /*y=*/9, /*x=*/4, /*y=*/10,
|
||||
/*x=*/4, /*y=*/11, /*x=*/2, /*y=*/10, /*x=*/0, /*y=*/10, /*x=*/5, /*y=*/10
|
||||
}, false);
|
||||
|
||||
// test top right corner
|
||||
testGame(2019,
|
||||
{
|
||||
/*x=*/19, /*y=*/14, /*x=*/18, /*y=*/13, /*x=*/19, /*y=*/12, /*x=*/18, /*y=*/12, /*x=*/18, /*y=*/11, /*x=*/17, /*y=*/10,
|
||||
/*x=*/18, /*y=*/14, /*x=*/16, /*y=*/11, /*x=*/18, /*y=*/9, /*x=*/15, /*y=*/12, /*x=*/14, /*y=*/13, /*x=*/15, /*y=*/10,
|
||||
/*x=*/15, /*y=*/11, /*x=*/14, /*y=*/10, /*x=*/17, /*y=*/12, /*x=*/16, /*y=*/10, /*x=*/13, /*y=*/10, /*x=*/18, /*y=*/10
|
||||
}, false);
|
||||
|
||||
for (uint32 i = 0; i < 10; i++)
|
||||
testRandomGame(i);
|
||||
|
||||
_easierAi = true;
|
||||
for (uint32 i = 10; i < 20; i++)
|
||||
testRandomGame(i);
|
||||
|
||||
_random.setSeed(oldSeed);
|
||||
warning("finished PenteGame::test()");
|
||||
}
|
||||
|
||||
bool PenteGame::testGame(uint32 seed, Common::Array<int> moves, bool playerWin) {
|
||||
byte vars[1024];
|
||||
byte &winner = vars[5];
|
||||
byte &op = vars[4];
|
||||
|
||||
warning("starting PenteGame::testGame(%u, %u, %d)", seed, moves.size(), (int)playerWin);
|
||||
memset(vars, 0, sizeof(vars));
|
||||
_random.setSeed(seed);
|
||||
|
||||
op = 0;
|
||||
run(vars);
|
||||
|
||||
for (uint i = 0; i < moves.size(); i += 2) {
|
||||
if (winner)
|
||||
error("%u: early winner: %d", i, (int)winner);
|
||||
|
||||
int x = moves[i];
|
||||
int y = moves[i + 1];
|
||||
|
||||
if (i % 4) {
|
||||
// check Stauf's move
|
||||
op = 3;
|
||||
run(vars);
|
||||
|
||||
byte sX, sY;
|
||||
varsMoveToXY(vars[0], vars[1], vars[2], sX, sY);
|
||||
|
||||
if (sX != x || sY != y)
|
||||
error("%u: Stauf, expected (%d, %d), got (%d, %d)", i, (int)x, (int)y, (int)sX, (int)sY);
|
||||
|
||||
do {
|
||||
op = 4;
|
||||
run(vars);
|
||||
} while (winner == 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
moveXYToVars(x, y, vars[0], vars[1], vars[2]);
|
||||
op = 1;
|
||||
run(vars);
|
||||
|
||||
do {
|
||||
op = 2;
|
||||
run(vars);
|
||||
} while (winner == 1);
|
||||
}
|
||||
|
||||
if (playerWin && winner != 3)
|
||||
error("player didn't win, winner: %d", (int)winner);
|
||||
else if (playerWin == false && winner != 2)
|
||||
error("Stauf didn't win, winner: %d", (int)winner);
|
||||
|
||||
warning("finished PenteGame::testGame(%u, %u, %d)", seed, moves.size(), (int)playerWin);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PenteGame::testRandomGame(uint32 seed) {
|
||||
byte vars[1024];
|
||||
byte &winner = vars[5];
|
||||
byte &op = vars[4];
|
||||
|
||||
warning("starting PenteGame::testRandomGame(%u)", seed);
|
||||
memset(vars, 0, sizeof(vars));
|
||||
_random.setSeed(seed);
|
||||
|
||||
op = 0;
|
||||
run(vars);
|
||||
|
||||
while (1) {
|
||||
// Player makes a random move
|
||||
int x, y;
|
||||
do {
|
||||
x = _random.getRandomNumber(19);
|
||||
y = _random.getRandomNumber(14);
|
||||
} while (_table != nullptr && _table->boardState[x][y]);
|
||||
|
||||
moveXYToVars(x, y, vars[0], vars[1], vars[2]);
|
||||
op = 1;
|
||||
run(vars);
|
||||
|
||||
do {
|
||||
op = 2;
|
||||
run(vars);
|
||||
} while (winner == 1);
|
||||
|
||||
if (winner)
|
||||
break;
|
||||
|
||||
// Stauf's move
|
||||
op = 3;
|
||||
run(vars);
|
||||
|
||||
do {
|
||||
op = 4;
|
||||
run(vars);
|
||||
} while (winner == 1);
|
||||
|
||||
if (winner)
|
||||
break;
|
||||
}
|
||||
|
||||
if (winner != 2)
|
||||
error("Stauf didn't win, winner: %d", (int)winner);
|
||||
|
||||
warning("finished PenteGame::testRandomGame(%u)", seed);
|
||||
}
|
||||
|
||||
} // namespace Groovie
|
||||
85
engines/groovie/logic/pente.h
Normal file
85
engines/groovie/logic/pente.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_LOGIC_PENTE_H
|
||||
#define GROOVIE_LOGIC_PENTE_H
|
||||
|
||||
#include "common/random.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
/*
|
||||
* Pente puzzle at the end of the game.
|
||||
*/
|
||||
|
||||
struct pentePlayerTable;
|
||||
struct penteTable;
|
||||
|
||||
class PenteGame {
|
||||
public:
|
||||
PenteGame(bool easierAi);
|
||||
void run(byte *vars);
|
||||
|
||||
private:
|
||||
void animateCapturesCheckWinner(byte *vars);
|
||||
void opQueryPiece(byte *vars);
|
||||
|
||||
void addLine(int x, int y, int linesCounter);
|
||||
void buildLookupTable();
|
||||
void penteDeInit();
|
||||
void penteInit(uint width, uint height, uint length);
|
||||
int &getPlayerTable(bool staufTurn, pentePlayerTable *&pt);
|
||||
void scoreLine(uint16 lineIndex, bool isStaufTurn, bool revert);
|
||||
void calcTouchingPieces(byte moveX, byte moveY, bool revert);
|
||||
void updateScore(byte x, byte y, bool whose_turn);
|
||||
void revertScore(byte x, byte y);
|
||||
byte scoreCaptureSingle(byte x, byte y, int slopeX, int slopeY);
|
||||
uint scoreCapture(byte x, byte y);
|
||||
void animateCapture(short move, byte *bitMaskG, short *outCapture1, short *outCapture2);
|
||||
void revertCapture(byte x, byte y, byte y2);
|
||||
int scoreMoveAndRevert(byte x, byte y, char depth, int parentScore, bool &gameOver);
|
||||
int scoreMoveAndRevert(byte x, byte y, char depth, int parentScore);
|
||||
int aiRecurseTail(int parentScore);
|
||||
int aiRecurse(char depth, int parentScore);
|
||||
uint16 aiGetBestMove(byte depth);
|
||||
void test();
|
||||
bool testGame(uint32 seed, Common::Array<int> moves, bool playerWin);
|
||||
void testRandomGame(uint32 seed);
|
||||
|
||||
Common::RandomSource _random;
|
||||
|
||||
byte _animateCapturesBitMask;
|
||||
short _previousMove;
|
||||
short _nextCapturedSpot;
|
||||
penteTable *_table;
|
||||
bool _easierAi;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_LOGIC_PENTE_H
|
||||
1273
engines/groovie/logic/tlcgame.cpp
Normal file
1273
engines/groovie/logic/tlcgame.cpp
Normal file
File diff suppressed because it is too large
Load Diff
199
engines/groovie/logic/tlcgame.h
Normal file
199
engines/groovie/logic/tlcgame.h
Normal file
@@ -0,0 +1,199 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_LOGIC_TLCGAME_H
|
||||
#define GROOVIE_LOGIC_TLCGAME_H
|
||||
|
||||
#include "common/textconsole.h"
|
||||
#include "common/random.h"
|
||||
|
||||
#define GROOVIE_TLC_MAX_EPSIODES (15)
|
||||
#define GROOVIE_TLC_MAX_QUEST_EP (50)
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class GroovieEngine;
|
||||
|
||||
// The regions.rle contains 898 entries. Round about 18 kByte in memory.
|
||||
struct TlcRegionsHeader {
|
||||
char name[12];
|
||||
int numAnswers;
|
||||
uint32 offset;
|
||||
};
|
||||
|
||||
struct TlcRegion {
|
||||
uint16 left;
|
||||
uint16 top;
|
||||
uint16 right;
|
||||
uint16 bottom;
|
||||
};
|
||||
|
||||
struct TlcEpQuestionData {
|
||||
bool questionUsed;
|
||||
uint32 questionScore;
|
||||
};
|
||||
|
||||
struct TlcTatHeader {
|
||||
uint32 questionsNum;
|
||||
uint32 questionsOffset;
|
||||
uint8 binDividends[16];
|
||||
};
|
||||
|
||||
struct TlcTatAnswer {
|
||||
uint8 binScore[16];
|
||||
};
|
||||
|
||||
struct TlcTatQuestions {
|
||||
char name[6];
|
||||
int answerCount;
|
||||
TlcTatAnswer answerData[8];
|
||||
};
|
||||
|
||||
class TlcGame
|
||||
{
|
||||
public:
|
||||
#ifdef ENABLE_GROOVIE2
|
||||
TlcGame(byte *scriptVariables);
|
||||
~TlcGame();
|
||||
|
||||
static const char *getTlcMusicFilename(int musicId);
|
||||
|
||||
void handleOp(uint8 op);
|
||||
|
||||
/**
|
||||
* Handle region commands. A region describes the coordinates of
|
||||
* a rectangle as clickable area in the question dialogs. These regions
|
||||
* are provided by a the extra file.
|
||||
* screen coordinates.
|
||||
*/
|
||||
void opRegions();
|
||||
|
||||
/**
|
||||
* Get the coordiantes of the region for the next answer. There are
|
||||
* up to 8 answers possible for each question. In the script the
|
||||
* coordinates are (0,0,0,0) which will be replace by the new ones.
|
||||
* @param left Left value of the rectangle
|
||||
* @param top Top value of the rectangle
|
||||
* @param right Right value of the rectangle
|
||||
* @param bottom Bottom value of the rectangle
|
||||
* @return 0 if anwer was found. -1 in case no more answer
|
||||
* available for this question
|
||||
*/
|
||||
int getRegionNext(uint16 &left, uint16 &top, uint16 &right, uint16 &bottom);
|
||||
|
||||
/**
|
||||
* Rewinds the internal answer counter for the function
|
||||
* getRegionNext()
|
||||
*/
|
||||
void getRegionRewind();
|
||||
|
||||
|
||||
/**
|
||||
* Handles some flags which are used during a TAT. The game seems to
|
||||
* use this flags to skip some questions during a TAT.
|
||||
* OpCode_0x42(2)
|
||||
*/
|
||||
void opFlags();
|
||||
|
||||
|
||||
/**
|
||||
* Handles all Exit Poll commands. The exit poll (EP) questions are
|
||||
* described in detail in the file EPAIDB.RLE.
|
||||
* OpCode_0x42(1)
|
||||
*/
|
||||
void opExitPoll();
|
||||
|
||||
/**
|
||||
* Handles all TAT commands. The TAT questions are described in detail
|
||||
* in the file TATAIDB.RLE
|
||||
*/
|
||||
void opTat();
|
||||
|
||||
private:
|
||||
Common::RandomSource _random;
|
||||
|
||||
void inline setScriptVar(uint16 var, byte value);
|
||||
void inline setScriptVar16(uint16 var, uint16 value);
|
||||
uint16 inline getScriptVar16(uint16 var);
|
||||
byte *_scriptVariables;
|
||||
|
||||
/**
|
||||
* Loads the description part of the regions.rle file into memory
|
||||
* This makes it faster to search for the correct quesion.
|
||||
*/
|
||||
void regionsInit();
|
||||
void regionsLoad();
|
||||
|
||||
// Variables for region handling
|
||||
int _numRegionHeaders;
|
||||
int _curAnswerIndex;
|
||||
int _curQuestNumAnswers;
|
||||
TlcRegion _curQuestRegions[8];
|
||||
TlcRegionsHeader *_regionHeader;
|
||||
|
||||
/**
|
||||
* Functions for Exit Poll Commands
|
||||
*/
|
||||
void epInit();
|
||||
void epSelectNextQuestion();
|
||||
void epResultQuestion();
|
||||
void epResultEpisode();
|
||||
|
||||
// Variables for Exit Poll handling
|
||||
int16 _epScoreBin[6];
|
||||
int _epEpisodeIdx; // 15 Episodes: 0..14
|
||||
int _epQuestionIdx; // 1..X (questions in current episode. counted up for every question)
|
||||
int _epQuestionNumOfPool; // 1..X (question number in the data base. The questions are played in random order)
|
||||
int _epQuestionsInEpisode;
|
||||
TlcEpQuestionData *_epQuestionsData;
|
||||
|
||||
// Variables for flag handling
|
||||
byte _tatFlags[0x0E][0x09];
|
||||
|
||||
/**
|
||||
* Functions for TAT Commands
|
||||
*/
|
||||
void tatInitRegs();
|
||||
void tatLoadDB();
|
||||
void tatLoadDBHeaders();
|
||||
void tatResultQuest();
|
||||
void tatResultEpisode();
|
||||
void tatGetProfile();
|
||||
void debugTatFlags(int y, int y2);
|
||||
|
||||
// Variables for TAT handling
|
||||
int _tatEpisodes;
|
||||
int _tatQuestCount;
|
||||
TlcTatHeader *_tatHeaders;
|
||||
TlcTatQuestions *_tatQuestions;
|
||||
// uint8 _tatCoeffs[15][16];
|
||||
#endif
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_LOGIC_TLCGAME_H
|
||||
987
engines/groovie/logic/triangle.cpp
Normal file
987
engines/groovie/logic/triangle.cpp
Normal file
@@ -0,0 +1,987 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/logic/triangle.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
namespace {
|
||||
extern const int8 triangleLookup1[12];
|
||||
extern const int8 triangleLookup2[12];
|
||||
extern const int8 triangleLookup3[12];
|
||||
extern const int8 triangleLogicTable[924];
|
||||
}
|
||||
|
||||
TriangleGame::TriangleGame() : _random("TriangleGame") {
|
||||
init();
|
||||
#if 0
|
||||
test();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TriangleGame::run(byte *scriptVariables) {
|
||||
byte op = scriptVariables[3];
|
||||
uint8 move;
|
||||
|
||||
switch (op) {
|
||||
case 3:
|
||||
init();
|
||||
scriptVariables[3] = 0;
|
||||
return;
|
||||
|
||||
case 4:
|
||||
// Samantha AI
|
||||
move = sub03(2);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
// Stauf AI (only called after Samantha)
|
||||
move = sub03(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Player and then Stauf
|
||||
debugC(kDebugLogic, "player chose spot %d", (int)(scriptVariables[1]) + (10 * (int)scriptVariables[0]));
|
||||
setCell(scriptVariables[1] + 10 * scriptVariables[0], 2);
|
||||
scriptVariables[3] = sub02();
|
||||
|
||||
if (scriptVariables[3] == 0) {
|
||||
move = sub03(1);
|
||||
} else {
|
||||
debugC(kDebugLogic, "winner: %d", (int)scriptVariables[3]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
scriptVariables[0] = move / 10;
|
||||
scriptVariables[1] = move % 10;
|
||||
scriptVariables[3] = sub02();
|
||||
debugC(kDebugLogic, "stauf chose spot %d, winner: %d", (int)move, (int)scriptVariables[3]);
|
||||
}
|
||||
|
||||
void TriangleGame::init() {
|
||||
debugC(kDebugLogic, "TriangleGame::init(), seed: %u", _random.getSeed());
|
||||
_triangleCellCount = 0;
|
||||
memset(_triangleCells, 0, 66);
|
||||
}
|
||||
|
||||
int8 TriangleGame::sub02() {
|
||||
int8 v6[132];
|
||||
int8 v7[68];
|
||||
|
||||
sub05(_triangleCells, v6, v7);
|
||||
|
||||
for (int i = 0; v6[i] != 66; i++) {
|
||||
bool v1 = false;
|
||||
bool v2 = false;
|
||||
bool pl = false;
|
||||
|
||||
// There could be several sections, each one
|
||||
// ends with 66. And the overall list ends with 66 too
|
||||
// Hence, two loops
|
||||
for (; v6[i] != 66; i++) {
|
||||
if (!triangleLogicTable[14 * v6[i] + 6])
|
||||
pl = true;
|
||||
if (!triangleLogicTable[14 * v6[i] + 7])
|
||||
v2 = true;
|
||||
if (!triangleLogicTable[14 * v6[i] + 8])
|
||||
v1 = true;
|
||||
}
|
||||
|
||||
if (pl && v2 && v1)
|
||||
return _triangleCells[v6[i - 1]];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8 TriangleGame::sub03(int8 player) {
|
||||
int8 pickedMoves[4];
|
||||
int8 tempTriangle1[68];
|
||||
int8 tempTriangle2[68];
|
||||
int8 a6a[132];
|
||||
int8 tempTriangle3[68];
|
||||
int8 tempMoves[132];
|
||||
int8 pos;
|
||||
|
||||
if (_triangleCellCount >= 2) {
|
||||
sub05(_triangleCells, tempMoves, tempTriangle3);
|
||||
sub07(tempMoves, _triangleCells, tempTriangle3, tempTriangle2, tempTriangle1, a6a);
|
||||
|
||||
// Find move until valid one
|
||||
(pos = sub09(player, tempTriangle2, tempTriangle1, a6a, _triangleCells)) != 66 ||
|
||||
(pos = sub10(player, tempTriangle1, _triangleCells)) != 66 ||
|
||||
(pos = sub12(player, a6a, _triangleCells, tempTriangle1)) != 66 ||
|
||||
(pos = sub09(3 - player, tempTriangle2, tempTriangle1, a6a, _triangleCells));
|
||||
|
||||
if (pos == 66) {
|
||||
pos = _random.getRandomNumber(65);
|
||||
|
||||
int8 oldPos = pos;
|
||||
while (_triangleCells[pos]) {
|
||||
if (++pos > 65)
|
||||
pos = 0;
|
||||
if (oldPos == pos) {
|
||||
pos = 66;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int8 max = 0;
|
||||
if (!_triangleCells[24]) {
|
||||
pickedMoves[0] = 24;
|
||||
max = 1;
|
||||
}
|
||||
|
||||
if (!_triangleCells[31])
|
||||
pickedMoves[max++] = 31;
|
||||
if (!_triangleCells[32])
|
||||
pickedMoves[max++] = 32;
|
||||
if (max)
|
||||
pos = pickedMoves[_random.getRandomNumber(max - 1)];
|
||||
else {
|
||||
warning("TriangleGame: Undefined behaviour");
|
||||
pos = 0; // tempMoves is uninitalized in this branch, so just return 0
|
||||
}
|
||||
}
|
||||
|
||||
if (pos != 66)
|
||||
setCell(pos, player);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void TriangleGame::sub05(int8 *triangleCells, int8 *tempMoves, int8 *tempTriangle) {
|
||||
int8 dest[8];
|
||||
|
||||
for (int i = 0; i < 66; i++)
|
||||
tempTriangle[i] = triangleCells[i];
|
||||
|
||||
int v16 = 3;
|
||||
|
||||
for (int j = 0; j < 66; ++j) {
|
||||
if (triangleCells[j]) {
|
||||
bool flag = false;
|
||||
|
||||
copyLogicRow(j, triangleCells[j], dest);
|
||||
for (int8 *k = dest; *k != 66; k++) {
|
||||
if (j > *k) {
|
||||
if (flag) {
|
||||
if (tempTriangle[j] != tempTriangle[*k])
|
||||
replaceCells(tempTriangle, j, tempTriangle[j], tempTriangle[*k]);
|
||||
} else {
|
||||
flag = true;
|
||||
tempTriangle[j] = tempTriangle[*k];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!flag)
|
||||
tempTriangle[j] = v16++;
|
||||
}
|
||||
}
|
||||
|
||||
int v11 = 0;
|
||||
|
||||
if (v16 > 3) {
|
||||
for (int v12 = 3; v12 < v16; v12++) {
|
||||
int v14 = v11;
|
||||
|
||||
for (int m = 0; m < 66; m++) {
|
||||
if (tempTriangle[m] == v12)
|
||||
tempMoves[v11++] = m;
|
||||
}
|
||||
|
||||
if (v11 != v14)
|
||||
tempMoves[v11++] = 66;
|
||||
}
|
||||
}
|
||||
|
||||
tempMoves[v11] = 66;
|
||||
}
|
||||
|
||||
void TriangleGame::sub07(int8 *tempMoves, int8 *triangleCells, int8 *tempTriangle3, int8 *tempTriangle2, int8 *tempTriangle1, int8 *tempMoves2) {
|
||||
int8 singleRow[8];
|
||||
int8 tempTriangle[68];
|
||||
int8 routes[4356];
|
||||
|
||||
for (int i = 0; i < 66; i++) {
|
||||
tempTriangle2[i] = tempTriangle3[i];
|
||||
tempTriangle1[i] = 0;
|
||||
}
|
||||
|
||||
if (*tempMoves != 66) {
|
||||
for (int8 *ptr1 = tempMoves; *ptr1 != 66; ptr1++) {
|
||||
int8 *v45 = &routes[66 * tempTriangle3[*ptr1]];
|
||||
*v45 = 66;
|
||||
|
||||
while (*ptr1 != 66) {
|
||||
copyLogicRow(*ptr1++, 0, singleRow);
|
||||
collapseLoops(v45, singleRow);
|
||||
}
|
||||
}
|
||||
|
||||
for (int8 *ptr2 = tempMoves; *ptr2 != 66; ptr2++) {
|
||||
int8 *j;
|
||||
|
||||
for (j = ptr2; *j != 66; j++)
|
||||
;
|
||||
for (int8 *k = j + 1; *k != 66; k++) {
|
||||
if (triangleCells[*k] == triangleCells[*ptr2]) {
|
||||
int8 val1 = tempTriangle2[*k];
|
||||
int8 val2 = tempTriangle2[*ptr2];
|
||||
|
||||
if (val1 != val2) {
|
||||
int8 lookupLen = copyLookup(&routes[66 * val2], &routes[66 * val1], tempTriangle);
|
||||
if (lookupLen == 1) {
|
||||
if (triangleCells[*ptr2] == 1) {
|
||||
tempTriangle1[tempTriangle[0]] |= 1u;
|
||||
} else if (triangleCells[*ptr2] == 2) {
|
||||
tempTriangle1[tempTriangle[0]] |= 0x40u;
|
||||
}
|
||||
} else if (lookupLen > 1) {
|
||||
int8 v16 = lookupLen - 1;
|
||||
do {
|
||||
if (triangleCells[*ptr2] == 1) {
|
||||
tempTriangle1[tempTriangle[v16]] |= 0x10u;
|
||||
} else if (triangleCells[*ptr2] == 2) {
|
||||
tempTriangle1[tempTriangle[v16]] |= 0x20u;
|
||||
}
|
||||
} while (v16--);
|
||||
|
||||
collapseLoops(&routes[66 * tempTriangle2[*k]], &routes[66 * tempTriangle2[*ptr2]]);
|
||||
int8 from = tempTriangle2[*ptr2];
|
||||
int8 to = tempTriangle2[*k];
|
||||
for (int m = 0; m < 66; m++) {
|
||||
if (tempTriangle2[m] == from)
|
||||
tempTriangle2[m] = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; *k != 66; k++)
|
||||
;
|
||||
}
|
||||
for (; *ptr2 != 66; ptr2++)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
int8 maxVal = 0;
|
||||
for (int i = 0; i < 66; i++) {
|
||||
if (tempTriangle2[i] > maxVal)
|
||||
maxVal = tempTriangle2[i];
|
||||
}
|
||||
|
||||
int8 len2 = 0;
|
||||
|
||||
for (int i = 3; i <= maxVal; i++) {
|
||||
int8 prevLen2 = len2;
|
||||
for (int j = 0; j < 66; j++) {
|
||||
if (tempTriangle2[j] == i)
|
||||
tempMoves2[len2++] = j;
|
||||
}
|
||||
|
||||
if (prevLen2 != len2)
|
||||
tempMoves2[len2++] = 66;
|
||||
}
|
||||
|
||||
tempMoves2[len2] = 66;
|
||||
|
||||
for (int8 *ptr3 = tempMoves2; *ptr3 != 66; ptr3++) {
|
||||
bool flag1 = false, flag2 = false, flag3 = false;
|
||||
int8 row = tempTriangle2[*ptr3];
|
||||
byte mask1 = 0, mask2 = 0;
|
||||
|
||||
for (int i = 0; i < 66; i++) {
|
||||
if (tempTriangle2[i] == row && triangleCells[i]) {
|
||||
if (triangleCells[i] == 1) {
|
||||
mask1 = 1;
|
||||
mask2 = 16;
|
||||
} else {
|
||||
mask1 = 64;
|
||||
mask2 = 32;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (*ptr3 != 66) {
|
||||
if (!triangleLogicTable[14 * *ptr3 + 6])
|
||||
flag1 = 1;
|
||||
if (!triangleLogicTable[14 * *ptr3 + 7])
|
||||
flag2 = 1;
|
||||
if (!triangleLogicTable[14 * *ptr3 + 8])
|
||||
flag3 = 1;
|
||||
++ptr3;
|
||||
}
|
||||
|
||||
if (!flag1) {
|
||||
int8 lookup1 = copyLookup(triangleLookup1, &routes[66 * row], tempTriangle);
|
||||
if (lookup1 == 1) {
|
||||
tempTriangle1[tempTriangle[0]] |= mask1;
|
||||
} else if (lookup1 > 1) {
|
||||
int k = lookup1 - 1;
|
||||
do
|
||||
tempTriangle1[tempTriangle[k]] |= mask2;
|
||||
while (k--);
|
||||
flag1 = 1;
|
||||
}
|
||||
}
|
||||
if (!flag2) {
|
||||
int8 lookup2 = copyLookup(triangleLookup2, &routes[66 * row], tempTriangle);
|
||||
if (lookup2 == 1) {
|
||||
tempTriangle1[tempTriangle[0]] |= mask1;
|
||||
} else if (lookup2 > 1) {
|
||||
int k = lookup2 - 1;
|
||||
do
|
||||
tempTriangle1[tempTriangle[k]] |= mask2;
|
||||
while (k--);
|
||||
flag2 = 1;
|
||||
}
|
||||
}
|
||||
if (!flag3) {
|
||||
int8 lookup3 = copyLookup(triangleLookup3, &routes[66 * row], tempTriangle);
|
||||
if (lookup3 == 1) {
|
||||
tempTriangle1[tempTriangle[0]] |= mask1;
|
||||
} else if (lookup3 > 1) {
|
||||
int k = lookup3 - 1;
|
||||
do
|
||||
tempTriangle1[tempTriangle[k]] |= mask2;
|
||||
while (k--);
|
||||
flag3 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
byte mask3 = 0;
|
||||
|
||||
if (flag1)
|
||||
mask3 = 4;
|
||||
if (flag2)
|
||||
mask3 |= 8u;
|
||||
if (flag3)
|
||||
mask3 |= 2u;
|
||||
|
||||
for (int i = 0; i < 66; i++) {
|
||||
if (tempTriangle2[i] == row)
|
||||
tempTriangle1[i] |= mask3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int8 TriangleGame::sub09(int8 player, int8 *tempTriangle2, int8 *tempTriangle1, int8 *a4, int8 *triangleCells) {
|
||||
int8 movesTable[280];
|
||||
|
||||
int numDir1 = 0;
|
||||
int numDir2 = 0;
|
||||
int numDir3 = 0;
|
||||
int numDir4 = 0;
|
||||
int row = 0;
|
||||
|
||||
for (const int8 *tPtr = &triangleLogicTable[6]; tPtr < &triangleLogicTable[924]; tPtr += 14, row++) {
|
||||
if (!triangleCells[row] && (tempTriangle1[row] & (player == 1 ? 1 : 64)) != 0) {
|
||||
int c1 = 0, c2 = 0, c3 = 0;
|
||||
int mask = 0;
|
||||
|
||||
copyLogicRow(row, player, movesTable);
|
||||
|
||||
for (int8 *movPtr = movesTable; *movPtr != 66; ++movPtr) {
|
||||
int row2 = 0;
|
||||
|
||||
mask |= tempTriangle1[*movPtr];
|
||||
|
||||
for (const int8 *tPtr2 = &triangleLogicTable[6]; tPtr2 < &triangleLogicTable[924]; tPtr2 += 14, row2++) {
|
||||
if (tempTriangle2[row2] == tempTriangle2[*movPtr]) {
|
||||
c1 += (tPtr2[0] == 0) ? 1 : 0;
|
||||
c2 += (tPtr2[1] == 0) ? 1 : 0;
|
||||
c3 += (tPtr2[2] == 0) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c1)
|
||||
mask &= ~4u;
|
||||
if (c2)
|
||||
mask &= ~8u;
|
||||
if (c3)
|
||||
mask &= ~2u;
|
||||
|
||||
if (tPtr[0] || c1) {
|
||||
if (tPtr[1] || c2) {
|
||||
if (tPtr[2] || c3) {
|
||||
if (mask) {
|
||||
if (mask == 0xe) {
|
||||
movesTable[numDir2 + 76] = row;
|
||||
numDir2++;
|
||||
} else if (mask == 2 || mask == 8 || mask == 4) {
|
||||
movesTable[numDir4 + 212] = row;
|
||||
numDir4++;
|
||||
} else {
|
||||
movesTable[numDir3 + 144] = row;
|
||||
numDir3++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
movesTable[numDir1 + 8] = row;
|
||||
numDir1++;
|
||||
}
|
||||
} else {
|
||||
movesTable[numDir1 + 8] = row;
|
||||
numDir1++;
|
||||
}
|
||||
} else {
|
||||
movesTable[numDir1 + 8] = row;
|
||||
numDir1++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numDir1)
|
||||
return movesTable[_random.getRandomNumber(numDir1 - 1) + 8];
|
||||
if (numDir2)
|
||||
return movesTable[_random.getRandomNumber(numDir2 - 1) + 76];
|
||||
if (numDir3)
|
||||
return movesTable[_random.getRandomNumber(numDir3 - 1) + 144];
|
||||
if (numDir4)
|
||||
return movesTable[_random.getRandomNumber(numDir4 - 1) + 212];
|
||||
|
||||
return 66;
|
||||
}
|
||||
|
||||
int8 TriangleGame::sub10(int8 player, int8 *a2, int8 *triangleCells) {
|
||||
int8 *destPtr;
|
||||
byte mask;
|
||||
int counter;
|
||||
int8 dest1[8];
|
||||
int8 dest2[68];
|
||||
|
||||
mask = 0;
|
||||
counter = 0;
|
||||
|
||||
if (player == 1)
|
||||
mask = 16;
|
||||
else if (player == 2)
|
||||
mask = 32;
|
||||
|
||||
for (int i = 0; i < 66; ++i) {
|
||||
if (!triangleCells[i] && (mask & (byte)a2[i]) != 0) {
|
||||
copyLogicRow(i, player, dest1);
|
||||
|
||||
destPtr = dest1;
|
||||
|
||||
while (*destPtr != 66) {
|
||||
if ((a2[*destPtr] & 0xE) == 0xE) {
|
||||
dest2[counter] = i;
|
||||
counter++;
|
||||
break;
|
||||
}
|
||||
|
||||
destPtr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (counter)
|
||||
return dest2[_random.getRandomNumber(counter - 1)];
|
||||
|
||||
return 66;
|
||||
}
|
||||
|
||||
int8 TriangleGame::sub12(int8 player, int8 *tempMoves, int8 *triangleCells, int8 *tempTriangle) {
|
||||
int8 moves[8];
|
||||
int8 tempTriangle1[68];
|
||||
int8 tempTriangle2[68];
|
||||
int8 tempTriangle3[68];
|
||||
int8 tempTriangle4[68];
|
||||
|
||||
int8 result = 66;
|
||||
int maxlen = -1;
|
||||
int8 *startPtr = triangleCells;
|
||||
|
||||
for (int8 *ptr = tempMoves; *ptr != 66; ptr++) {
|
||||
int len = 0;
|
||||
int8 *beg = ptr;
|
||||
int8 p0 = *ptr;
|
||||
|
||||
for (; *ptr != 66; ++ptr)
|
||||
++len;
|
||||
|
||||
if (len > maxlen && triangleCells[p0] == player) {
|
||||
maxlen = len;
|
||||
startPtr = beg;
|
||||
}
|
||||
}
|
||||
|
||||
tempTriangle4[0] = 66;
|
||||
|
||||
for (int8 *ptr = startPtr; *ptr != 66; ++ptr) {
|
||||
if (sub13(*ptr, triangleCells, moves))
|
||||
collapseLoops(tempTriangle4, moves);
|
||||
}
|
||||
|
||||
int len1 = 0, len2 = 0, len3 = 0;
|
||||
|
||||
tempTriangle1[0] = 66;
|
||||
tempTriangle2[0] = 66;
|
||||
tempTriangle3[0] = 66;
|
||||
|
||||
for (int8 *ptr = tempTriangle4; *ptr != 66; ++ptr) {
|
||||
int8 v13 = 100;
|
||||
|
||||
int8 v15 = triangleLogicTable[14 * *ptr + 11];
|
||||
if (v15 < 100)
|
||||
v13 = triangleLogicTable[14 * *ptr + 11];
|
||||
|
||||
int8 v16 = triangleLogicTable[14 * *ptr + 13];
|
||||
if (v13 > v16)
|
||||
v13 = triangleLogicTable[14 * *ptr + 13];
|
||||
|
||||
int8 v17 = triangleLogicTable[14 * *ptr + 12];
|
||||
if (v13 > v17)
|
||||
v13 = triangleLogicTable[14 * *ptr + 12];
|
||||
|
||||
if (v13 == v15) {
|
||||
tempTriangle1[len1++] = *ptr;
|
||||
} else if (v13 == v17) {
|
||||
tempTriangle2[len2++] = *ptr;
|
||||
} else if (v13 == v16) {
|
||||
tempTriangle3[len3++] = *ptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool flag1 = false, flag2 = false, flag3 = false;
|
||||
tempTriangle3[len3] = 66;
|
||||
tempTriangle2[len2] = 66;
|
||||
tempTriangle1[len1] = 66;
|
||||
|
||||
int8 startVal = tempTriangle[*startPtr];
|
||||
|
||||
switch (startVal) {
|
||||
case 8:
|
||||
flag3 = true;
|
||||
break;
|
||||
case 4:
|
||||
flag2 = true;
|
||||
break;
|
||||
case 2:
|
||||
flag1 = true;
|
||||
break;
|
||||
case 12:
|
||||
flag2 = true;
|
||||
flag3 = flag2;
|
||||
break;
|
||||
case 6:
|
||||
flag1 = true;
|
||||
flag2 = true;
|
||||
break;
|
||||
case 10:
|
||||
flag1 = true;
|
||||
flag3 = true;
|
||||
break;
|
||||
case 14:
|
||||
flag2 = false;
|
||||
flag1 = false;
|
||||
flag3 = flag2;
|
||||
break;
|
||||
default:
|
||||
flag2 = true;
|
||||
flag1 = true;
|
||||
flag3 = flag2;
|
||||
break;
|
||||
}
|
||||
|
||||
int minLen = 101;
|
||||
if (flag1) {
|
||||
for (int8 *ptr = tempTriangle1; *ptr != 66; ++ptr) {
|
||||
int8 part1 = 0;
|
||||
if ((startVal & 8) == 0)
|
||||
part1 = triangleLogicTable[14 * *ptr + 7];
|
||||
int8 part2 = 0;
|
||||
if ((startVal & 4) == 0)
|
||||
part2 = triangleLogicTable[14 * *ptr + 6];
|
||||
if (minLen > part1 + part2) {
|
||||
minLen = part1 + part2;
|
||||
result = *ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flag2) {
|
||||
for (int8 *ptr = tempTriangle2; *ptr != 66; ++ptr) {
|
||||
int8 part1 = 0;
|
||||
if ((startVal & 8) == 0)
|
||||
part1 = triangleLogicTable[14 * *ptr + 7];
|
||||
int8 part2 = 0;
|
||||
if ((startVal & 2) == 0)
|
||||
part2 = triangleLogicTable[14 * *ptr + 8];
|
||||
if (minLen > part1 + part2) {
|
||||
minLen = part1 + part2;
|
||||
result = *ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flag3) {
|
||||
for (int8 *ptr = tempTriangle3; *ptr != 66; ++ptr) {
|
||||
int8 part1 = 0;
|
||||
if ((startVal & 2) == 0)
|
||||
part1 = triangleLogicTable[14 * *ptr + 8];
|
||||
int8 part2 = 0;
|
||||
if ((startVal & 4) == 0)
|
||||
part2 = triangleLogicTable[14 * *ptr + 6];
|
||||
if (minLen > part1 + part2) {
|
||||
minLen = part1 + part2;
|
||||
result = *ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int TriangleGame::sub13(int8 row, int8 *triangleCells, int8 *moves) {
|
||||
int pos = 0;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int8 v6 = triangleLogicTable[14 * row + i];
|
||||
|
||||
if (v6 != -1 && !triangleCells[v6]) {
|
||||
int v7 = (i + 1) % 6;
|
||||
int8 v8 = triangleLogicTable[14 * row + v7];
|
||||
|
||||
if (v8 != -1 && !triangleCells[v8]) {
|
||||
int8 v9 = triangleLogicTable[14 * v6 + v7];
|
||||
|
||||
if (v9 != -1 && !triangleCells[v9])
|
||||
moves[pos++] = v9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moves[pos] = 66;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void TriangleGame::setCell(int8 cellnum, int8 val) {
|
||||
assert(cellnum >= 0);
|
||||
assert(cellnum < 66);
|
||||
if (cellnum >= 0 && cellnum < 66) {
|
||||
++_triangleCellCount;
|
||||
assert(_triangleCells[cellnum] == 0);
|
||||
_triangleCells[cellnum] = val;
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleGame::copyLogicRow(int row, int8 key, int8 *dest) {
|
||||
int pos = 0;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int8 val = triangleLogicTable[14 * row + i];
|
||||
if (val != -1 && _triangleCells[val] == key)
|
||||
dest[pos++] = val;
|
||||
}
|
||||
|
||||
dest[pos] = 66;
|
||||
}
|
||||
|
||||
void TriangleGame::replaceCells(int8 *tempTriangle, int limit, int8 from, int8 to) {
|
||||
for (int i = 0; i <= limit; ++i) {
|
||||
if (tempTriangle[i] == from)
|
||||
tempTriangle[i] = to;
|
||||
}
|
||||
}
|
||||
|
||||
int TriangleGame::copyLookup(const int8 *lookup, int8 *start, int8 *dest){
|
||||
int counter = 0;
|
||||
|
||||
if (*lookup == 66) {
|
||||
*dest = 66;
|
||||
return counter;
|
||||
}
|
||||
|
||||
for (; *lookup != 66; lookup++) {
|
||||
for (int8 *ptr = start; *ptr != 66; ptr++) {
|
||||
if (*ptr == *lookup)
|
||||
dest[counter++] = *lookup;
|
||||
}
|
||||
}
|
||||
|
||||
dest[counter] = 66;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
void TriangleGame::collapseLoops(int8 *route, int8 *singleRow) {
|
||||
int len = 0;
|
||||
for (int8 *i = route; *i != 66; i++)
|
||||
len++;
|
||||
|
||||
int origlen = len;
|
||||
|
||||
|
||||
for (int8 *i = singleRow; *i != 66; i++) {
|
||||
int j;
|
||||
for (j = 0; j < len; j++) {
|
||||
if (route[j] == *i)
|
||||
break;
|
||||
}
|
||||
if (j == len)
|
||||
route[len++] = *i;
|
||||
}
|
||||
|
||||
if (len != origlen)
|
||||
route[len] = 66;
|
||||
}
|
||||
|
||||
bool TriangleGame::testGame(uint32 seed, const Common::Array<uint8> moves, bool playerWin) {
|
||||
byte vars[1024];
|
||||
byte &op = vars[3];
|
||||
byte &move10s = vars[0];
|
||||
byte &move1s = vars[1];
|
||||
byte &winner = vars[3];
|
||||
|
||||
memset(vars, 0, sizeof(vars));
|
||||
|
||||
op = 3;
|
||||
run(vars);
|
||||
|
||||
warning("starting TriangleGame::testGame(%u, %u, %d)", seed, moves.size(), (int)playerWin);
|
||||
_random.setSeed(seed);
|
||||
|
||||
for (uint i = 0; i < moves.size(); i++) {
|
||||
if (i % 2) {
|
||||
// check Stauf's move
|
||||
uint8 move = ((uint)move10s * 10) + (uint)move1s;
|
||||
if (move != moves[i]) {
|
||||
error("%u: bad Stauf move: %d", (int)i, (int)move);
|
||||
// return false here is useful for finding the right seed to test
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// else, input player's move
|
||||
if (winner != 0)
|
||||
error("%u: early winner: %d", (int)i, (int)winner);
|
||||
|
||||
uint8 move = moves[i];
|
||||
move10s = move / 10;
|
||||
move1s = move % 10;
|
||||
op = 0;
|
||||
run(vars);
|
||||
}
|
||||
|
||||
if (playerWin && winner != 2)
|
||||
error("player didn't win, winner: %d", (int)winner);
|
||||
if (playerWin == false && winner != 1)
|
||||
error("Stauf didn't win, winner: %d", (int)winner);
|
||||
|
||||
warning("finished TriangleGame::testGame(%u, %u, %d)", seed, moves.size(), (int)playerWin);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TriangleGame::ensureSamanthaWin(uint32 seed) {
|
||||
byte vars[1024];
|
||||
byte &op = vars[3];
|
||||
byte &winner = vars[3];
|
||||
|
||||
op = 3;
|
||||
run(vars);
|
||||
|
||||
warning("starting TriangleGame::ensureSamanthaWin(%u)", seed);
|
||||
_random.setSeed(seed);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
// Samantha
|
||||
op = 4;
|
||||
run(vars);
|
||||
if (winner)
|
||||
break;
|
||||
|
||||
// Stauf
|
||||
op = 5;
|
||||
run(vars);
|
||||
if (winner)
|
||||
break;
|
||||
}
|
||||
|
||||
if (winner != 2)
|
||||
error("Samantha didn't win, winner: %d", (int)winner);
|
||||
|
||||
warning("finished TriangleGame::ensureSamanthaWin(%u)", seed);
|
||||
}
|
||||
|
||||
void TriangleGame::testPlayRandomly(uint32 seed) {
|
||||
byte vars[1024];
|
||||
byte &op = vars[3];
|
||||
byte &move10s = vars[0];
|
||||
byte &move1s = vars[1];
|
||||
byte &winner = vars[3];
|
||||
|
||||
op = 3;
|
||||
run(vars);
|
||||
|
||||
warning("starting TriangleGame::testPlayRandomly(%u)", seed);
|
||||
_random.setSeed(seed);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
// Player make random move
|
||||
uint8 move = 0;
|
||||
do {
|
||||
move = _random.getRandomNumber(65);
|
||||
} while (_triangleCells[move]);
|
||||
|
||||
move10s = move / 10;
|
||||
move1s = move % 10;
|
||||
op = 0;
|
||||
run(vars);
|
||||
if (winner)
|
||||
break;
|
||||
|
||||
// Stauf
|
||||
op = 5;
|
||||
run(vars);
|
||||
if (winner)
|
||||
break;
|
||||
}
|
||||
|
||||
if (winner != 1)
|
||||
error("Stauf didn't win, winner: %d", (int)winner);
|
||||
|
||||
warning("finished TriangleGame::testPlayRandomly(%u)", seed);
|
||||
}
|
||||
|
||||
void TriangleGame::test() {
|
||||
warning("starting TriangleGame::test");
|
||||
uint32 oldSeed = _random.getSeed();
|
||||
|
||||
// Samantha appears to not always win, but she usually does, and she wins these seeds
|
||||
// haven't verified if she always wins in the original game
|
||||
for (uint32 i = 100; i < 105; i++)
|
||||
ensureSamanthaWin(i);
|
||||
|
||||
// Similar thing here, technically there might be a seed where the player wins, but Stauf should win the vast majority of them
|
||||
for (uint32 i = 200; i < 205; i++)
|
||||
testPlayRandomly(i);
|
||||
|
||||
testGame(1, {24, 32, 30, 42, 37, 53, 45, 39, 19, 47, 20, 56, 55, 59, 36, 49, 29, 46, 23, 38, 18}, true);
|
||||
testGame(1, {24, 32, 30, 42, 37, 53, 19, 39, 45, 47, 46, 59, 56, 49, 38, 48, 31, 40, 25, 50, 20}, true);
|
||||
testGame(2, {24, 31, 33, 38, 43, 46, 16, 41, 54, 52, 64, 61, 53, 37, 42, 51, 32, 40, 23, 60, 15}, true);
|
||||
testGame(2, {24, 31, 33, 38, 43, 46, 16, 41, 53, 52, 64, 61, 54, 37, 34, 50, 25, 36, 17, 0, 10}, true);
|
||||
testGame(40680, {0, 24, 1, 12, 2, 4, 3, 5, 6, 30, 7, 9, 8, 29, 10, 13, 11, 47, 14, 18, 20, 37, 19, 36, 27, 57, 26, 31}, false);
|
||||
|
||||
_random.setSeed(oldSeed);
|
||||
warning("finished TriangleGame::test");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const int8 triangleLookup1[12] = {
|
||||
0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66
|
||||
};
|
||||
|
||||
const int8 triangleLookup2[12] = {
|
||||
0, 2, 5, 9, 14, 20, 27, 35, 44, 54, 65, 66
|
||||
};
|
||||
|
||||
const int8 triangleLookup3[12] = {
|
||||
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66
|
||||
};
|
||||
|
||||
const int8 triangleLogicTable[924] = {
|
||||
-1, -1, 2, 1, -1, -1, 0, 0, 10, 10, 6, 0, 10, 10,
|
||||
0, 2, 4, 3, -1, -1, 0, 1, 9, 9, 5, 1, 9, 9,
|
||||
-1, -1, 5, 4, 1, 0, 1, 0, 9, 11, 5, 1, 9, 9,
|
||||
1, 4, 7, 6, -1, -1, 0, 2, 8, 8, 4, 2, 8, 8,
|
||||
2, 5, 8, 7, 3, 1, 1, 1, 8, 10, 4, 1, 8, 8,
|
||||
-1, -1, 9, 8, 4, 2, 2, 0, 8, 12, 4, 2, 8, 8,
|
||||
3, 7, 11, 10, -1, -1, 0, 3, 7, 7, 3, 3, 7, 7,
|
||||
4, 8, 12, 11, 6, 3, 1, 2, 7, 9, 3, 2, 7, 7,
|
||||
5, 9, 13, 12, 7, 4, 2, 1, 7, 11, 3, 2, 7, 7,
|
||||
-1, -1, 14, 13, 8, 5, 3, 0, 7, 13, 3, 3, 7, 7,
|
||||
6, 11, 16, 15, -1, -1, 0, 4, 6, 6, 3, 4, 6, 6,
|
||||
7, 12, 17, 16, 10, 6, 1, 3, 6, 8, 2, 3, 6, 6,
|
||||
8, 13, 18, 17, 11, 7, 2, 2, 6, 10, 2, 2, 6, 6,
|
||||
9, 14, 19, 18, 12, 8, 3, 1, 6, 12, 2, 3, 6, 6,
|
||||
-1, -1, 20, 19, 13, 9, 4, 0, 6, 14, 3, 4, 6, 6,
|
||||
10, 16, 22, 21, -1, -1, 0, 5, 5, 5, 3, 5, 5, 5,
|
||||
11, 17, 23, 22, 15, 10, 1, 4, 5, 7, 2, 4, 5, 5,
|
||||
12, 18, 24, 23, 16, 11, 2, 3, 5, 9, 1, 3, 5, 5,
|
||||
13, 19, 25, 24, 17, 12, 3, 2, 5, 11, 1, 3, 5, 5,
|
||||
14, 20, 26, 25, 18, 13, 4, 1, 5, 13, 2, 4, 5, 5,
|
||||
-1, -1, 27, 26, 19, 14, 5, 0, 5, 15, 3, 5, 5, 5,
|
||||
15, 22, 29, 28, -1, -1, 0, 6, 4, 4, 3, 6, 6, 4,
|
||||
16, 23, 30, 29, 21, 15, 1, 5, 4, 6, 2, 5, 5, 4,
|
||||
17, 24, 31, 30, 22, 16, 2, 4, 4, 8, 1, 4, 4, 4,
|
||||
18, 25, 32, 31, 23, 17, 3, 3, 4, 10, 0, 3, 4, 4,
|
||||
19, 26, 33, 32, 24, 18, 4, 2, 4, 12, 1, 4, 4, 4,
|
||||
20, 27, 34, 33, 25, 19, 5, 1, 4, 14, 2, 5, 4, 5,
|
||||
-1, -1, 35, 34, 26, 20, 6, 0, 4, 16, 3, 6, 4, 6,
|
||||
21, 29, 37, 36, -1, -1, 0, 7, 3, 3, 3, 7, 7, 3,
|
||||
22, 30, 38, 37, 28, 21, 1, 6, 3, 5, 2, 6, 6, 3,
|
||||
23, 31, 39, 38, 29, 22, 2, 5, 3, 7, 1, 5, 5, 3,
|
||||
24, 32, 40, 39, 30, 23, 3, 4, 3, 9, 0, 4, 4, 3,
|
||||
25, 33, 41, 40, 31, 24, 4, 3, 3, 11, 0, 4, 3, 4,
|
||||
26, 34, 42, 41, 32, 25, 5, 2, 3, 13, 1, 5, 3, 5,
|
||||
27, 35, 43, 42, 33, 26, 6, 1, 3, 15, 2, 6, 3, 6,
|
||||
-1, -1, 44, 43, 34, 27, 7, 0, 3, 17, 3, 7, 3, 7,
|
||||
28, 37, 46, 45, -1, -1, 0, 8, 2, 2, 4, 8, 8, 2,
|
||||
29, 38, 47, 46, 36, 28, 1, 7, 2, 4, 3, 7, 7, 2,
|
||||
30, 39, 48, 47, 37, 29, 2, 6, 2, 6, 2, 6, 6, 2,
|
||||
31, 40, 49, 48, 38, 30, 3, 5, 2, 8, 1, 5, 5, 3,
|
||||
32, 41, 50, 49, 39, 31, 4, 4, 2, 10, 1, 4, 4, 4,
|
||||
33, 42, 51, 50, 40, 32, 5, 3, 2, 12, 1, 5, 3, 5,
|
||||
34, 43, 52, 51, 41, 33, 6, 2, 2, 14, 2, 6, 2, 6,
|
||||
35, 44, 53, 52, 42, 34, 7, 1, 2, 16, 3, 7, 2, 7,
|
||||
-1, -1, 54, 53, 43, 35, 8, 0, 2, 18, 4, 8, 2, 8,
|
||||
36, 46, 56, 55, -1, -1, 0, 9, 1, 1, 5, 9, 9, 1,
|
||||
37, 47, 57, 56, 45, 36, 1, 8, 1, 3, 4, 8, 8, 1,
|
||||
38, 48, 58, 57, 46, 37, 2, 7, 1, 5, 3, 7, 7, 2,
|
||||
39, 49, 59, 58, 47, 38, 3, 6, 1, 7, 2, 6, 6, 3,
|
||||
40, 50, 60, 59, 48, 39, 4, 5, 1, 9, 2, 5, 5, 4,
|
||||
41, 51, 61, 60, 49, 40, 5, 4, 1, 11, 2, 5, 4, 5,
|
||||
42, 52, 62, 61, 50, 41, 6, 3, 1, 13, 2, 6, 3, 6,
|
||||
43, 53, 63, 62, 51, 42, 7, 2, 1, 15, 3, 7, 2, 7,
|
||||
44, 54, 64, 63, 52, 43, 8, 1, 1, 17, 4, 8, 1, 8,
|
||||
-1, -1, 65, 64, 53, 44, 9, 0, 1, 19, 5, 9, 1, 9,
|
||||
45, 56, -1, -1, -1, -1, 0, 10, 0, 0, 6, 10, 10, 0,
|
||||
46, 57, -1, -1, 55, 45, 1, 9, 0, 2, 5, 9, 9, 1,
|
||||
47, 58, -1, -1, 56, 46, 2, 8, 0, 4, 4, 8, 8, 2,
|
||||
48, 59, -1, -1, 57, 47, 3, 7, 0, 6, 3, 7, 7, 3,
|
||||
49, 60, -1, -1, 58, 48, 4, 6, 0, 8, 3, 6, 6, 4,
|
||||
50, 61, -1, -1, 59, 49, 5, 5, 0, 10, 3, 5, 5, 5,
|
||||
51, 62, -1, -1, 60, 50, 6, 4, 0, 12, 3, 6, 4, 6,
|
||||
52, 63, -1, -1, 61, 51, 7, 3, 0, 14, 3, 7, 3, 7,
|
||||
53, 64, -1, -1, 62, 52, 8, 2, 0, 16, 4, 8, 2, 8,
|
||||
54, 65, -1, -1, 63, 53, 9, 1, 0, 18, 5, 9, 1, 9,
|
||||
-1, -1, -1, -1, 64, 54, 10, 0, 0, 20, 6, 10, 0, 10
|
||||
};
|
||||
|
||||
} // End of anonymous namespace
|
||||
|
||||
|
||||
} // End of Groovie namespace
|
||||
71
engines/groovie/logic/triangle.h
Normal file
71
engines/groovie/logic/triangle.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_LOGIC_TRIANGLE_H
|
||||
#define GROOVIE_LOGIC_TRIANGLE_H
|
||||
|
||||
#include "common/random.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class TriangleGame {
|
||||
public:
|
||||
TriangleGame();
|
||||
void run(byte *scriptVariables);
|
||||
|
||||
private:
|
||||
void init();
|
||||
int8 sub02();
|
||||
int8 sub03(int8 player);
|
||||
void sub05(int8 *triangleCells, int8 *a2, int8 *a3);
|
||||
void sub07(int8 *tempMoves, int8 *triangleCells, int8 *tempTriangle3, int8 *tempTriangle2, int8 *tempTriangle1, int8 *tempMoves2);
|
||||
int8 sub09(int8 key, int8 *a2, int8 *a3, int8 *a4, int8 *triangleCells);
|
||||
int8 sub10(int8 key, int8 *a2, int8 *triangleCells);
|
||||
int8 sub12(int8 a1, int8 *a2, int8 *triangleCells, int8 *a4);
|
||||
int sub13(int8 row, int8 *triangleCells, int8 *moves);
|
||||
void setCell(int8 cellnum, int8 val);
|
||||
void copyLogicRow(int row, int8 key, int8 *dest);
|
||||
void replaceCells(int8 *tempTriangle, int limit, int8 from, int8 to);
|
||||
int copyLookup(const int8 *lookup, int8 *start, int8 *dest);
|
||||
void collapseLoops(int8 *route, int8 *singleRow);
|
||||
|
||||
bool testGame(uint32 seed, Common::Array<uint8> moves, bool player_win);
|
||||
void ensureSamanthaWin(uint32 seed);
|
||||
void testPlayRandomly(uint32 seed);
|
||||
void test();
|
||||
|
||||
private:
|
||||
int _triangleCellCount;
|
||||
int8 _triangleCells[66];
|
||||
|
||||
Common::RandomSource _random;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_LOGIC_TRIANGLE_H
|
||||
642
engines/groovie/logic/winerack.cpp
Normal file
642
engines/groovie/logic/winerack.cpp
Normal file
@@ -0,0 +1,642 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/logic/winerack.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
enum WineBottle {
|
||||
kWineBottleOpponent = 1,
|
||||
kWineBottlePlayer = 2
|
||||
};
|
||||
|
||||
namespace {
|
||||
extern const int8 wineRackLogicTable[1200];
|
||||
}
|
||||
|
||||
WineRackGame::WineRackGame() : _random("WineRackGame"), _totalBottles(0) {
|
||||
memset(_wineRackGrid, 0, 100);
|
||||
|
||||
#if 0
|
||||
runTests();
|
||||
#endif
|
||||
}
|
||||
|
||||
void WineRackGame::run(byte *scriptVariables) {
|
||||
char op = scriptVariables[3];
|
||||
byte pos = 0;
|
||||
|
||||
/* positions on the board
|
||||
* north = 9 (0, 9)
|
||||
south = 90 (9, 0)
|
||||
east = 99 (9, 9)
|
||||
west = 0 (0, 0)
|
||||
*/
|
||||
|
||||
switch (op) {
|
||||
case 3:
|
||||
debugC(kDebugLogic, "WineRackGame initGrid, seed: %u", _random.getSeed());
|
||||
initGrid(scriptVariables[4]);
|
||||
break;
|
||||
case 4: // Player's move
|
||||
pos = calculateNextMove(kWineBottlePlayer);
|
||||
placeBottle(pos, kWineBottlePlayer);
|
||||
scriptVariables[0] = pos / 10;
|
||||
scriptVariables[1] = pos % 10;
|
||||
scriptVariables[3] = didPlayerWin();
|
||||
break;
|
||||
case 5: // Opponent's move
|
||||
scriptVariables[3] = 0;
|
||||
pos = calculateNextMove(kWineBottleOpponent);
|
||||
placeBottle(pos, kWineBottleOpponent);
|
||||
scriptVariables[0] = pos / 10;
|
||||
scriptVariables[1] = pos % 10;
|
||||
scriptVariables[3] = didAiWin() != 0 ? 1 : 0;
|
||||
break;
|
||||
default:
|
||||
scriptVariables[3] = 0;
|
||||
placeBottle(scriptVariables[0] * 10 + scriptVariables[1], 2);
|
||||
|
||||
if (didPlayerWin()) {
|
||||
scriptVariables[3] = 2;
|
||||
} else {
|
||||
pos = calculateNextMove(kWineBottleOpponent);
|
||||
placeBottle(pos, kWineBottleOpponent);
|
||||
scriptVariables[0] = pos / 10;
|
||||
scriptVariables[1] = pos % 10;
|
||||
scriptVariables[3] = didAiWin() != 0 ? 1 : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WineRackGame::initGrid(byte difficulty) {
|
||||
memset(_wineRackGrid, 0, 100);
|
||||
|
||||
switch (difficulty) {
|
||||
case 0:
|
||||
_totalBottles = 24;
|
||||
|
||||
_wineRackGrid[15] = kWineBottlePlayer;
|
||||
_wineRackGrid[18] = kWineBottleOpponent;
|
||||
_wineRackGrid[19] = kWineBottleOpponent;
|
||||
_wineRackGrid[20] = kWineBottleOpponent;
|
||||
_wineRackGrid[21] = kWineBottleOpponent;
|
||||
_wineRackGrid[22] = kWineBottleOpponent;
|
||||
_wineRackGrid[23] = kWineBottleOpponent;
|
||||
_wineRackGrid[25] = kWineBottlePlayer;
|
||||
_wineRackGrid[26] = kWineBottleOpponent;
|
||||
_wineRackGrid[27] = kWineBottleOpponent;
|
||||
_wineRackGrid[28] = kWineBottleOpponent;
|
||||
_wineRackGrid[33] = kWineBottleOpponent;
|
||||
_wineRackGrid[34] = kWineBottleOpponent;
|
||||
_wineRackGrid[35] = kWineBottlePlayer;
|
||||
_wineRackGrid[36] = kWineBottleOpponent;
|
||||
_wineRackGrid[44] = kWineBottlePlayer;
|
||||
_wineRackGrid[45] = kWineBottlePlayer;
|
||||
_wineRackGrid[54] = kWineBottlePlayer;
|
||||
_wineRackGrid[62] = kWineBottlePlayer;
|
||||
_wineRackGrid[63] = kWineBottlePlayer;
|
||||
_wineRackGrid[64] = kWineBottlePlayer;
|
||||
_wineRackGrid[72] = kWineBottlePlayer;
|
||||
_wineRackGrid[82] = kWineBottlePlayer;
|
||||
_wineRackGrid[91] = kWineBottlePlayer;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
_totalBottles = 12;
|
||||
|
||||
_wineRackGrid[75] = kWineBottlePlayer;
|
||||
_wineRackGrid[56] = kWineBottlePlayer;
|
||||
_wineRackGrid[45] = kWineBottlePlayer;
|
||||
_wineRackGrid[27] = kWineBottlePlayer;
|
||||
_wineRackGrid[24] = kWineBottlePlayer;
|
||||
_wineRackGrid[15] = kWineBottlePlayer;
|
||||
_wineRackGrid[64] = kWineBottleOpponent;
|
||||
_wineRackGrid[34] = kWineBottleOpponent;
|
||||
_wineRackGrid[33] = kWineBottleOpponent;
|
||||
_wineRackGrid[18] = kWineBottleOpponent;
|
||||
_wineRackGrid[16] = kWineBottleOpponent;
|
||||
_wineRackGrid[14] = kWineBottleOpponent;
|
||||
break;
|
||||
|
||||
default:
|
||||
_totalBottles = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WineRackGame::placeBottle(byte pos, byte val) {
|
||||
debugC(kDebugLogic, "placeBottle(%d, %d)", (int)pos, (int)val);
|
||||
_totalBottles++;
|
||||
assert(_wineRackGrid[pos] == 0);
|
||||
_wineRackGrid[pos] = val;
|
||||
}
|
||||
|
||||
int8 WineRackGame::calculateNextMove(byte player) {
|
||||
int8 moves1[24];
|
||||
int8 moves2[24];
|
||||
byte playerIndex = (player == 1) + 1;
|
||||
|
||||
if (!_totalBottles)
|
||||
return randomMoveStart();
|
||||
if (_totalBottles == 1)
|
||||
return randomMoveStart2();
|
||||
|
||||
sub05(player, moves1);
|
||||
sub05(playerIndex, moves2);
|
||||
|
||||
int8 result = sub06(moves1, moves2);
|
||||
|
||||
if (result == -1)
|
||||
return findEmptySpot();
|
||||
|
||||
assert(_wineRackGrid[result] == 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int8 WineRackGame::findEmptySpot() {
|
||||
int8 result = 0;
|
||||
|
||||
while (_wineRackGrid[result]) {
|
||||
if (result == 99)
|
||||
return 100;
|
||||
else
|
||||
++result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void WineRackGame::sub05(int8 player, int8 *moves) {
|
||||
int8 moves1[23];
|
||||
int8 moves2[23];
|
||||
int8 to, delta, playerIndex;
|
||||
|
||||
if (player == 1) {
|
||||
to = 90;
|
||||
delta = 10;
|
||||
playerIndex = 2;
|
||||
} else {
|
||||
to = 9;
|
||||
delta = 1;
|
||||
playerIndex = 3;
|
||||
}
|
||||
|
||||
memset(moves, 0, 23);
|
||||
|
||||
for (int i = 0; i < to; i += delta) {
|
||||
if (!_wineRackGrid[i] || _wineRackGrid[i] == player) {
|
||||
memset(moves1, 0, sizeof(moves1));
|
||||
memset(moves2, 0, sizeof(moves2));
|
||||
|
||||
sub13(i, playerIndex, moves1, moves2);
|
||||
|
||||
if (moves[0] < moves1[0]) {
|
||||
memcpy(moves, moves1, 23);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int8 WineRackGame::sub06(int8 *moves1, int8 *moves2) {
|
||||
for (int i = 0; i < moves1[2]; i++) {
|
||||
|
||||
int8 result = moves1[i + 3];
|
||||
|
||||
if (!_wineRackGrid[result]) {
|
||||
for (int j = 0; j < moves2[2]; j++)
|
||||
if (moves2[j + 3] == result)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < moves1[2]; i++) {
|
||||
if (!_wineRackGrid[moves1[i + 3]])
|
||||
return moves1[i + 3];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32 WineRackGame::didPlayerWin() {
|
||||
memset(_wineRackGrid2, 0, 100);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (_wineRackGrid[i] == kWineBottlePlayer) {
|
||||
int var = 0;
|
||||
sub10(100, i, 2, 3, &var);
|
||||
if (var == 1)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WineRackGame::sub10(int8 endPos, int8 pos, int unused, int player, int *val) {
|
||||
int8 candidates[8];
|
||||
|
||||
if (*val)
|
||||
return;
|
||||
|
||||
if (wineRackLogicTable[12 * pos + player] == -1) {
|
||||
*val = 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub11(pos, candidates);
|
||||
|
||||
for (int i = 0; candidates[i] != 100; i++) {
|
||||
int8 nextPos = candidates[i];
|
||||
|
||||
if (endPos != nextPos)
|
||||
sub10(pos, nextPos, unused, player, val);
|
||||
}
|
||||
}
|
||||
|
||||
void WineRackGame::sub11(int8 pos, int8 *candidates) {
|
||||
int cnt = 0;
|
||||
|
||||
_wineRackGrid2[pos] = 1;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int8 val = wineRackLogicTable[12 * pos + i];
|
||||
|
||||
if (!_wineRackGrid2[val] && _wineRackGrid[pos] == _wineRackGrid[val])
|
||||
candidates[cnt++] = val;
|
||||
}
|
||||
|
||||
candidates[cnt] = 100;
|
||||
}
|
||||
|
||||
uint32 WineRackGame::didAiWin() {
|
||||
memset(_wineRackGrid2, 0, 100);
|
||||
|
||||
for (int i = 0; i < 100; i += 10) {
|
||||
if (_wineRackGrid[i] == kWineBottleOpponent) {
|
||||
int var = 0;
|
||||
sub10(100, i, 1, 2, &var);
|
||||
if (var == 1)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WineRackGame::sub13(int8 cell, int8 player, int8 *moves1, int8 *moves2) {
|
||||
int8 candidates[4];
|
||||
|
||||
if (cell == -1)
|
||||
return;
|
||||
|
||||
moves2[moves2[2] + 3] = cell;
|
||||
|
||||
if (wineRackLogicTable[12 * cell + player] < 0) {
|
||||
++moves2[2];
|
||||
|
||||
moves2[0] = countEmtpy(moves2);
|
||||
|
||||
if (moves2[0] > moves1[0])
|
||||
memcpy(moves1, moves2, 23);
|
||||
|
||||
--moves2[2];
|
||||
} else {
|
||||
++moves2[2];
|
||||
|
||||
if (player == 2)
|
||||
sub15(cell, candidates);
|
||||
else
|
||||
sub16(cell, candidates);
|
||||
|
||||
for (int i = 0; candidates[i] != -1; i++)
|
||||
sub13(candidates[i], player, moves1, moves2);
|
||||
|
||||
--moves2[2];
|
||||
}
|
||||
}
|
||||
|
||||
void WineRackGame::sub15(int8 cell, int8 *candidates) {
|
||||
int8 depth = 0;
|
||||
int8 pos2 = wineRackLogicTable[12 * cell + 2];
|
||||
int8 pos1 = wineRackLogicTable[12 * cell + 1];
|
||||
|
||||
if (_wineRackGrid[pos2] == 2) {
|
||||
if (pos1 < 0 || _wineRackGrid[pos1] == 2) {
|
||||
if (cell >= 20) {
|
||||
int8 val1 = _wineRackGrid[cell - 10];
|
||||
|
||||
if (val1 != 2) {
|
||||
int8 val2 = _wineRackGrid[cell - 10];
|
||||
|
||||
if (val2 != 2 && (val1 == 1 || val2 == 1)) {
|
||||
depth = 1;
|
||||
candidates[0] = cell - 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cell < 80) {
|
||||
int8 val1 = _wineRackGrid[cell + 10];
|
||||
|
||||
if (val1 != 2) {
|
||||
int8 val2 = _wineRackGrid[cell + 11];
|
||||
|
||||
if (val2 != 2 && (val1 == 1 || val2 == 1)) {
|
||||
candidates[depth] = cell + 10;
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (_wineRackGrid[cell] == 1 || _wineRackGrid[pos1] == 1) {
|
||||
depth = 1;
|
||||
candidates[0] = pos1;
|
||||
}
|
||||
} else if (pos1 < 0 || _wineRackGrid[pos1] == 2) {
|
||||
if (_wineRackGrid[cell] == 1 || _wineRackGrid[pos2] == 1) {
|
||||
depth = 1;
|
||||
candidates[0] = pos2;
|
||||
}
|
||||
} else {
|
||||
depth = 2;
|
||||
candidates[0] = pos2;
|
||||
candidates[1] = pos1;
|
||||
}
|
||||
|
||||
candidates[depth] = -1;
|
||||
}
|
||||
|
||||
void WineRackGame::sub16(int8 cell, int8 *candidates) {
|
||||
int8 depth = 0;
|
||||
int8 pos3 = wineRackLogicTable[12 * cell + 3];
|
||||
int8 pos4 = wineRackLogicTable[12 * cell + 4];
|
||||
|
||||
if (_wineRackGrid[pos3] == 1) {
|
||||
if (pos4 < 0 || _wineRackGrid[pos4] == 1) {
|
||||
if (cell % 10 >= 2) {
|
||||
int8 val1 = _wineRackGrid[cell - 1];
|
||||
|
||||
if (val1 != 1) {
|
||||
int8 val2 = _wineRackGrid[cell + 8];
|
||||
|
||||
if (val2 != 1 && (val1 == 2 || val2 == 2)) {
|
||||
depth = 1;
|
||||
candidates[0] = cell - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cell < 80 && _wineRackGrid[cell + 1] != 1) {
|
||||
int8 val1 = _wineRackGrid[cell + 11];
|
||||
|
||||
if (val1 != 1 && (_wineRackGrid[cell + 1] == 2 || val1 == 2)) {
|
||||
candidates[depth] = cell + 1;
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
} else if (_wineRackGrid[cell] == 2 || _wineRackGrid[pos4] == 2) {
|
||||
depth = 1;
|
||||
candidates[0] = pos4;
|
||||
}
|
||||
} else if (pos4 < 0 || _wineRackGrid[pos4] == 1) {
|
||||
if (_wineRackGrid[cell] == 2 || _wineRackGrid[pos3] == 2) {
|
||||
depth = 1;
|
||||
candidates[0] = pos3;
|
||||
}
|
||||
} else {
|
||||
depth = 2;
|
||||
candidates[0] = pos3;
|
||||
candidates[1] = pos4;
|
||||
}
|
||||
|
||||
candidates[depth] = -1;
|
||||
}
|
||||
|
||||
int8 WineRackGame::countEmtpy(int8 *moves) {
|
||||
int8 cnt = 0;
|
||||
|
||||
for (int i = 0; i < moves[2]; i++) {
|
||||
if (!_wineRackGrid[moves[i + 3]])
|
||||
++cnt;
|
||||
}
|
||||
|
||||
return 20 - cnt;
|
||||
}
|
||||
|
||||
int8 WineRackGame::randomMoveStart() {
|
||||
const int8 moves[] = { 44, 45, 54, 55 };
|
||||
|
||||
return moves[_random.getRandomNumber(3)];
|
||||
}
|
||||
|
||||
int8 WineRackGame::randomMoveStart2() {
|
||||
const int8 moves[] = { 25, 26, 63, 64 };
|
||||
// the original game doesn't ensure the spot isn't taken
|
||||
int8 res = 0;
|
||||
do {
|
||||
res = moves[_random.getRandomNumber(3)];
|
||||
} while (_wineRackGrid[res] != 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
void WineRackGame::testWinCondition(byte player, int baseX, int baseY) {
|
||||
initGrid(2);
|
||||
|
||||
int basePos = baseX * 10 + baseY;
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (player == kWineBottlePlayer)
|
||||
placeBottle(i * 10 + basePos, player);
|
||||
else
|
||||
placeBottle(i + basePos, player);
|
||||
}
|
||||
|
||||
if (player == kWineBottlePlayer && !didPlayerWin()) {
|
||||
error("WineRackGame::testWinCondition(%d, %d, %d) failed", (int)player, baseX, baseY);
|
||||
} else if (player == kWineBottleOpponent && !didAiWin()) {
|
||||
error("WineRackGame::testWinCondition(%d, %d, %d) failed", (int)player, baseX, baseY);
|
||||
}
|
||||
}
|
||||
|
||||
void WineRackGame::testGame(uint32 seed, Common::Array<int> moves, bool playerWin) {
|
||||
byte vars[1024] = {};
|
||||
byte &x = vars[0];
|
||||
byte &y = vars[1];
|
||||
byte &op = vars[3];
|
||||
byte &winner = vars[3];
|
||||
byte &difficulty = vars[4];
|
||||
_random.setSeed(seed);
|
||||
|
||||
difficulty = 2;
|
||||
op = 3;
|
||||
run(vars);
|
||||
winner = 0;
|
||||
|
||||
for (uint i = 0; i < moves.size(); i += 2) {
|
||||
if (winner)
|
||||
error("early winner");
|
||||
x = moves[i];
|
||||
y = moves[i + 1];
|
||||
op = 1;
|
||||
run(vars);
|
||||
}
|
||||
|
||||
if (playerWin && winner != 2)
|
||||
error("WineRackGame::testGame(%u, %u, %d) player didn't win", seed, moves.size(), (int)playerWin);
|
||||
else if (playerWin == false && winner != 1)
|
||||
error("WineRackGame::testGame(%u, %u, %d) ai didn't win", seed, moves.size(), (int)playerWin);
|
||||
}
|
||||
|
||||
void WineRackGame::runTests() {
|
||||
warning("WineRackGame::runTests() starting");
|
||||
uint32 oldSeed = _random.getSeed();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
testWinCondition(kWineBottlePlayer, 0, i);
|
||||
testWinCondition(kWineBottleOpponent, i, 0);
|
||||
}
|
||||
|
||||
// pairs of x,y for the player's moves
|
||||
testGame(1, {9,0, 9,1, 9,2, 9,3, 9,4, 9,5, 9,6, 9,7, 9,8, 9,9}, false);
|
||||
testGame(2, {5,5, 3,5, 7,4, 1,6, 9,3, 0,7, 2,6, 4,5, 6,5, 8,4}, true);
|
||||
// in the original game, the AI had a 25% chance to move to 2,6 as its first move, even if your first move was to 2,6 already
|
||||
testGame(147160395, {2,6, 3,6, 4,6, 5,6, 7,6, 8,6, 9,6, 2,7, 3,7, 4,7}, false);
|
||||
|
||||
_random.setSeed(oldSeed);
|
||||
warning("WineRackGame::runTests() finished");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const int8 wineRackLogicTable[1200] = {
|
||||
-1, -1, 1, 10, -1, -1, -1, -1, 11, -1, -1, -1,
|
||||
-1, -1, 2, 11, 10, 0, -1, -1, 12, 20, -1, -1,
|
||||
-1, -1, 3, 12, 11, 1, -1, -1, 13, 21, 10, -1,
|
||||
-1, -1, 4, 13, 12, 2, -1, -1, 14, 22, 11, -1,
|
||||
-1, -1, 5, 14, 13, 3, -1, -1, 15, 23, 12, -1,
|
||||
-1, -1, 6, 15, 14, 4, -1, -1, 16, 24, 13, -1,
|
||||
-1, -1, 7, 16, 15, 5, -1, -1, 17, 25, 14, -1,
|
||||
-1, -1, 8, 17, 16, 6, -1, -1, 18, 26, 15, -1,
|
||||
-1, -1, 9, 18, 17, 7, -1, -1, 19, 27, 16, -1,
|
||||
-1, -1, -1, 19, 18, 8, -1, -1, -1, 28, 17, -1,
|
||||
0, 1, 11, 20, -1, -1, -1, 2, 21, -1, -1, -1,
|
||||
1, 2, 12, 21, 20, 10, -1, 3, 22, 30, -1, 0,
|
||||
2, 3, 13, 22, 21, 11, -1, 4, 23, 31, 20, 1,
|
||||
3, 4, 14, 23, 22, 12, -1, 5, 24, 32, 21, 2,
|
||||
4, 5, 15, 24, 23, 13, -1, 6, 25, 33, 22, 3,
|
||||
5, 6, 16, 25, 24, 14, -1, 7, 26, 34, 23, 4,
|
||||
6, 7, 17, 26, 25, 15, -1, 8, 27, 35, 24, 5,
|
||||
7, 8, 18, 27, 26, 16, -1, 9, 28, 36, 25, 6,
|
||||
8, 9, 19, 28, 27, 17, -1, -1, 29, 37, 26, 7,
|
||||
9, -1, -1, 29, 28, 18, -1, -1, -1, 38, 27, 8,
|
||||
10, 11, 21, 30, -1, -1, 1, 12, 31, -1, -1, -1,
|
||||
11, 12, 22, 31, 30, 20, 2, 13, 32, 40, -1, 10,
|
||||
12, 13, 23, 32, 31, 21, 3, 14, 33, 41, 30, 11,
|
||||
13, 14, 24, 33, 32, 22, 4, 15, 34, 42, 31, 12,
|
||||
14, 15, 25, 34, 33, 23, 5, 16, 35, 43, 32, 13,
|
||||
15, 16, 26, 35, 34, 24, 6, 17, 36, 44, 33, 14,
|
||||
16, 17, 27, 36, 35, 25, 7, 18, 37, 45, 34, 15,
|
||||
17, 18, 28, 37, 36, 26, 8, 19, 38, 46, 35, 16,
|
||||
18, 19, 29, 38, 37, 27, 9, -1, 39, 47, 36, 17,
|
||||
19, -1, -1, 39, 38, 28, -1, -1, -1, 48, 37, 18,
|
||||
20, 21, 31, 40, -1, -1, 11, 22, 41, -1, -1, -1,
|
||||
21, 22, 32, 41, 40, 30, 12, 23, 42, 50, -1, 20,
|
||||
22, 23, 33, 42, 41, 31, 13, 24, 43, 51, 40, 21,
|
||||
23, 24, 34, 43, 42, 32, 14, 25, 44, 52, 41, 22,
|
||||
24, 25, 35, 44, 43, 33, 15, 26, 45, 53, 42, 23,
|
||||
25, 26, 36, 45, 44, 34, 16, 27, 46, 54, 43, 24,
|
||||
26, 27, 37, 46, 45, 35, 17, 28, 47, 55, 44, 25,
|
||||
27, 28, 38, 47, 46, 36, 18, 29, 48, 56, 45, 26,
|
||||
28, 29, 39, 48, 47, 37, 19, -1, 49, 57, 46, 27,
|
||||
29, -1, -1, 49, 48, 38, -1, -1, -1, 58, 47, 28,
|
||||
30, 31, 41, 50, -1, -1, 21, 32, 51, -1, -1, -1,
|
||||
31, 32, 42, 51, 50, 40, 22, 33, 52, 60, -1, 30,
|
||||
32, 33, 43, 52, 51, 41, 23, 34, 53, 61, 50, 31,
|
||||
33, 34, 44, 53, 52, 42, 24, 35, 54, 62, 51, 32,
|
||||
34, 35, 45, 54, 53, 43, 25, 36, 55, 63, 52, 33,
|
||||
35, 36, 46, 55, 54, 44, 26, 37, 56, 64, 53, 34,
|
||||
36, 37, 47, 56, 55, 45, 27, 38, 57, 65, 54, 35,
|
||||
37, 38, 48, 57, 56, 46, 28, 39, 58, 66, 55, 36,
|
||||
38, 39, 49, 58, 57, 47, 29, -1, 59, 67, 56, 37,
|
||||
39, -1, -1, 59, 58, 48, -1, -1, -1, 68, 57, 38,
|
||||
40, 41, 51, 60, -1, -1, 31, 42, 61, -1, -1, -1,
|
||||
41, 42, 52, 61, 60, 50, 32, 43, 62, 70, -1, 40,
|
||||
42, 43, 53, 62, 61, 51, 33, 44, 63, 71, 60, 41,
|
||||
43, 44, 54, 63, 62, 52, 34, 45, 64, 72, 61, 42,
|
||||
44, 45, 55, 64, 63, 53, 35, 46, 65, 73, 62, 43,
|
||||
45, 46, 56, 65, 64, 54, 36, 47, 66, 74, 63, 44,
|
||||
46, 47, 57, 66, 65, 55, 37, 48, 67, 75, 64, 45,
|
||||
47, 48, 58, 67, 66, 56, 38, 49, 68, 76, 65, 46,
|
||||
48, 49, 59, 68, 67, 57, 39, -1, 69, 77, 66, 47,
|
||||
49, -1, -1, 69, 68, 58, -1, -1, -1, 78, 67, 48,
|
||||
50, 51, 61, 70, -1, -1, 41, 52, 71, -1, -1, -1,
|
||||
51, 52, 62, 71, 70, 60, 42, 53, 72, 80, -1, 50,
|
||||
52, 53, 63, 72, 71, 61, 43, 54, 73, 81, 70, 51,
|
||||
53, 54, 64, 73, 72, 62, 44, 55, 74, 82, 71, 52,
|
||||
54, 55, 65, 74, 73, 63, 45, 56, 75, 83, 72, 53,
|
||||
55, 56, 66, 75, 74, 64, 46, 57, 76, 84, 73, 54,
|
||||
56, 57, 67, 76, 75, 65, 47, 58, 77, 85, 74, 55,
|
||||
57, 58, 68, 77, 76, 66, 48, 59, 78, 86, 75, 56,
|
||||
58, 59, 69, 78, 77, 67, 49, -1, 79, 87, 76, 57,
|
||||
59, -1, -1, 79, 78, 68, -1, -1, -1, 88, 77, 58,
|
||||
60, 61, 71, 80, -1, -1, 51, 62, 81, -1, -1, -1,
|
||||
61, 62, 72, 81, 80, 70, 52, 63, 82, 90, -1, 60,
|
||||
62, 63, 73, 82, 81, 71, 53, 64, 83, 91, 80, 61,
|
||||
63, 64, 74, 83, 82, 72, 54, 65, 84, 92, 81, 62,
|
||||
64, 65, 75, 84, 83, 73, 55, 66, 85, 93, 82, 63,
|
||||
65, 66, 76, 85, 84, 74, 56, 67, 86, 94, 83, 64,
|
||||
66, 67, 77, 86, 85, 75, 57, 68, 87, 95, 84, 65,
|
||||
67, 68, 78, 87, 86, 76, 58, 69, 88, 96, 85, 66,
|
||||
68, 69, 79, 88, 87, 77, 59, -1, 89, 97, 86, 67,
|
||||
69, -1, -1, 89, 88, 78, -1, -1, -1, 98, 87, 68,
|
||||
70, 71, 81, 90, -1, -1, 61, 72, 91, -1, -1, -1,
|
||||
71, 72, 82, 91, 90, 80, 62, 73, 92, -1, -1, 70,
|
||||
72, 73, 83, 92, 91, 81, 63, 74, 93, -1, 90, 71,
|
||||
73, 74, 84, 93, 92, 82, 64, 75, 94, -1, 91, 72,
|
||||
74, 75, 85, 94, 93, 83, 65, 76, 95, -1, 92, 73,
|
||||
75, 76, 86, 95, 94, 84, 66, 77, 96, -1, 93, 74,
|
||||
76, 77, 87, 96, 95, 85, 67, 78, 97, -1, 94, 75,
|
||||
77, 78, 88, 97, 96, 86, 68, 79, 98, -1, 95, 76,
|
||||
78, 79, 89, 98, 97, 87, 69, -1, 99, -1, 96, 77,
|
||||
79, -1, -1, 99, 98, 88, -1, -1, -1, -1, 97, 78,
|
||||
80, 81, 91, -1, -1, -1, 71, 82, -1, -1, -1, -1,
|
||||
81, 82, 92, -1, -1, 90, 72, 83, -1, -1, -1, 80,
|
||||
82, 83, 93, -1, -1, 91, 73, 84, -1, -1, -1, 81,
|
||||
83, 84, 94, -1, -1, 92, 74, 85, -1, -1, -1, 82,
|
||||
84, 85, 95, -1, -1, 93, 75, 86, -1, -1, -1, 83,
|
||||
85, 86, 96, -1, -1, 94, 76, 87, -1, -1, -1, 84,
|
||||
86, 87, 97, -1, -1, 95, 77, 88, -1, -1, -1, 85,
|
||||
87, 88, 98, -1, -1, 96, 78, 89, -1, -1, -1, 86,
|
||||
88, 89, 99, -1, -1, 97, 79, -1, -1, -1, -1, 87,
|
||||
89, -1, -1, -1, -1, 98, -1, -1, -1, -1, -1, 88
|
||||
};
|
||||
|
||||
} // End of anonymous namespace
|
||||
|
||||
} // End of Groovie namespace
|
||||
76
engines/groovie/logic/winerack.h
Normal file
76
engines/groovie/logic/winerack.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* This file is dual-licensed.
|
||||
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
||||
* this code on November 10th, 2021, to be use in closed-source products.
|
||||
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_LOGIC_WINERACK_H
|
||||
#define GROOVIE_LOGIC_WINERACK_H
|
||||
|
||||
#include "common/random.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
/*
|
||||
* Wine rack puzzle in Clandestiny.
|
||||
* The player needs to create a path of bottles from one side to the other
|
||||
* before the ghost of Auld Sot does.
|
||||
*/
|
||||
class WineRackGame {
|
||||
public:
|
||||
WineRackGame();
|
||||
void run(byte *scriptVariables);
|
||||
|
||||
private:
|
||||
void initGrid(byte difficulty);
|
||||
void placeBottle(byte pos, byte val);
|
||||
int8 calculateNextMove(byte op);
|
||||
int8 findEmptySpot();
|
||||
void sub05(int8 player, int8 *moves);
|
||||
int8 sub06(int8 *moves1, int8 *moves2);
|
||||
uint32 didPlayerWin();
|
||||
void sub10(int8 endPos, int8 pos, int unused, int player, int *val);
|
||||
void sub11(int8 pos, int8 *candidates);
|
||||
uint32 didAiWin();
|
||||
void sub13(int8 cell, int8 player, int8 *moves1, int8 *moves2);
|
||||
void sub15(int8 cell, int8 *candidates);
|
||||
void sub16(int8 cell, int8 *candidates);
|
||||
int8 countEmtpy(int8 *moves);
|
||||
int8 randomMoveStart();
|
||||
int8 randomMoveStart2();
|
||||
|
||||
void testWinCondition(byte player, int baseX, int baseY);
|
||||
void testGame(uint32 seed, Common::Array<int> moves, bool playerWin);
|
||||
void runTests();
|
||||
|
||||
int _totalBottles;
|
||||
byte _wineRackGrid[100];
|
||||
byte _wineRackGrid2[100];
|
||||
Common::RandomSource _random;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_LOGIC_WINERACK_H
|
||||
98
engines/groovie/lzss.cpp
Normal file
98
engines/groovie/lzss.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/lzss.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
#define OUT_BUFF_SIZE 131072
|
||||
#define COMP_THRESH 3 // Compression not attempted if string to be compressed is less than 3 long
|
||||
|
||||
LzssReadStream::LzssReadStream(Common::ReadStream *indata, uint8 lengthmask, uint8 lengthbits) {
|
||||
/*
|
||||
TODO: Nasty hack. Make a buffer bigger than I'll ever need... probably.
|
||||
What should *really* happen is I should define a whole new type of stream
|
||||
that gets lzss decompressed on the fly
|
||||
*/
|
||||
_outLzssBufData = (uint8 *)malloc(OUT_BUFF_SIZE);
|
||||
_size = decodeLZSS(indata, lengthmask, lengthbits);
|
||||
_pos = 0;
|
||||
}
|
||||
|
||||
LzssReadStream::~LzssReadStream() {
|
||||
free(_outLzssBufData);
|
||||
}
|
||||
|
||||
uint32 LzssReadStream::decodeLZSS(Common::ReadStream *in, uint8 lengthmask, uint8 lengthbits) {
|
||||
uint32 N = 1 << (16 - lengthbits); /* History buffer size */
|
||||
byte *histbuff = new byte[N](); /* History buffer */
|
||||
uint32 outstreampos = 0;
|
||||
uint32 bufpos = 0;
|
||||
|
||||
while (!in->eos()) {
|
||||
byte flagbyte = in->readByte();
|
||||
for (uint32 i = 1; i <= 8; i++) {
|
||||
if (!in->eos()) {
|
||||
if ((flagbyte & 1) == 0) {
|
||||
uint32 offsetlen = in->readUint16LE();
|
||||
if (offsetlen == 0) {
|
||||
break;
|
||||
}
|
||||
uint32 length = (offsetlen & lengthmask) + COMP_THRESH;
|
||||
uint32 offset = (bufpos - (offsetlen >> lengthbits)) & (N - 1);
|
||||
for (uint32 j = 0; j < length; j++) {
|
||||
byte tempa = histbuff[(offset + j) & (N - 1)];
|
||||
_outLzssBufData[outstreampos++] = tempa;
|
||||
histbuff[bufpos] = tempa;
|
||||
bufpos = (bufpos + 1) & (N - 1);
|
||||
}
|
||||
} else {
|
||||
byte tempa = in->readByte();
|
||||
if (in->eos()) {
|
||||
break;
|
||||
}
|
||||
_outLzssBufData[outstreampos++] = tempa;
|
||||
histbuff[bufpos] = tempa;
|
||||
bufpos = (bufpos + 1) & (N - 1);
|
||||
}
|
||||
flagbyte = flagbyte >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete[] histbuff;
|
||||
return outstreampos;
|
||||
}
|
||||
|
||||
bool LzssReadStream::eos() const {
|
||||
return _pos >= _size;
|
||||
}
|
||||
|
||||
uint32 LzssReadStream::read(void *buf, uint32 size) {
|
||||
if (size > _size - _pos)
|
||||
size = _size - _pos;
|
||||
|
||||
memcpy(buf, &_outLzssBufData[_pos], size);
|
||||
_pos += size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
42
engines/groovie/lzss.h
Normal file
42
engines/groovie/lzss.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class LzssReadStream : public Common::ReadStream {
|
||||
private:
|
||||
uint8 *_outLzssBufData;
|
||||
uint32 _size;
|
||||
uint32 _pos;
|
||||
|
||||
uint32 decodeLZSS(Common::ReadStream *in, uint8 lengthmask, uint8 lengthbits);
|
||||
|
||||
public:
|
||||
LzssReadStream(Common::ReadStream *indata, uint8 lengthmask, uint8 lengthbits);
|
||||
~LzssReadStream() override;
|
||||
|
||||
bool eos() const override;
|
||||
uint32 read(void *buf, uint32 size) override;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
233
engines/groovie/metaengine.cpp
Normal file
233
engines/groovie/metaengine.cpp
Normal file
@@ -0,0 +1,233 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/saveload.h"
|
||||
|
||||
#include "common/system.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
#include "backends/keymapper/action.h"
|
||||
#include "backends/keymapper/keymapper.h"
|
||||
#include "backends/keymapper/standard-actions.h"
|
||||
|
||||
#include "engines/advancedDetector.h"
|
||||
#include "groovie/detection.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
static const ADExtraGuiOptionsMap optionsList[] = {
|
||||
{
|
||||
GAMEOPTION_T7G_FAST_MOVIE_SPEED,
|
||||
{
|
||||
_s("Fast movie speed"),
|
||||
_s("Play movies at an increased speed"),
|
||||
"fast_movie_speed",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_ORIGINAL_SAVELOAD,
|
||||
{
|
||||
_s("Use original save/load screens"),
|
||||
_s("Use the original save/load screens instead of the ScummVM ones"),
|
||||
"originalsaveload",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_EASIER_AI,
|
||||
{
|
||||
_s("Easier AI"),
|
||||
_s("Decrease the difficulty of AI puzzles"),
|
||||
"easier_ai",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_EASIER_AI_DEFAULT,
|
||||
{
|
||||
_s("Easier AI"),
|
||||
_s("Decrease the difficulty of AI puzzles"),
|
||||
"easier_ai",
|
||||
true,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_FINAL_HOUR,
|
||||
{
|
||||
_s("Updated Credits Music"),
|
||||
_s("Play the song The Final Hour during the credits instead of reusing MIDI songs"),
|
||||
"credits_music",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_SLIMHOTSPOTS,
|
||||
{
|
||||
_s("Slim Left/Right Hotspots"),
|
||||
_s("Shrinks the hotspots on the left and right sides for exiting puzzles"),
|
||||
"slim_hotspots",
|
||||
true,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_SPEEDRUN,
|
||||
{
|
||||
_s("Speedrun Mode"),
|
||||
_s("Affects the controls for fast forwarding the game"),
|
||||
"speedrun_mode",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
AD_EXTRA_GUI_OPTIONS_TERMINATOR
|
||||
};
|
||||
|
||||
class GroovieMetaEngine : public AdvancedMetaEngine<GroovieGameDescription> {
|
||||
public:
|
||||
const char *getName() const override {
|
||||
return "groovie";
|
||||
}
|
||||
|
||||
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
|
||||
return optionsList;
|
||||
}
|
||||
|
||||
Common::Error createInstance(OSystem *syst, Engine **engine, const GroovieGameDescription *gd) const override;
|
||||
bool hasFeature(MetaEngineFeature f) const override;
|
||||
|
||||
SaveStateList listSaves(const char *target) const override;
|
||||
int getMaximumSaveSlot() const override;
|
||||
bool removeSaveState(const char *target, int slot) const override;
|
||||
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
|
||||
int getAutosaveSlot() const override;
|
||||
|
||||
Common::KeymapArray initKeymaps(const char *target) const override;
|
||||
};
|
||||
|
||||
Common::Error GroovieMetaEngine::createInstance(OSystem *syst, Engine **engine, const GroovieGameDescription *gd) const {
|
||||
#ifndef ENABLE_GROOVIE2
|
||||
if (gd->version != kGroovieT7G)
|
||||
return Common::Error(Common::kUnsupportedGameidError, _s("GroovieV2 support is not compiled in"));
|
||||
#endif
|
||||
|
||||
*engine = new GroovieEngine(syst,gd);
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
bool GroovieMetaEngine::hasFeature(MetaEngineFeature f) const {
|
||||
return
|
||||
(f == kSupportsListSaves) ||
|
||||
(f == kSupportsLoadingDuringStartup) ||
|
||||
(f == kSupportsDeleteSave) ||
|
||||
(f == kSimpleSavesNames) ||
|
||||
(f == kSavesSupportMetaInfo);
|
||||
}
|
||||
|
||||
SaveStateList GroovieMetaEngine::listSaves(const char *target) const {
|
||||
return SaveLoad::listValidSaves(target);
|
||||
}
|
||||
|
||||
int GroovieMetaEngine::getMaximumSaveSlot() const {
|
||||
return SaveLoad::getMaximumSlot();
|
||||
}
|
||||
|
||||
bool GroovieMetaEngine::removeSaveState(const char *target, int slot) const {
|
||||
if (!SaveLoad::isSlotValid(slot)) {
|
||||
// Invalid slot, do nothing
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::String filename = SaveLoad::getSlotSaveName(target, slot);
|
||||
return g_system->getSavefileManager()->removeSavefile(filename);
|
||||
}
|
||||
|
||||
SaveStateDescriptor GroovieMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
|
||||
SaveStateDescriptor desc;
|
||||
|
||||
Common::InSaveFile *savefile = SaveLoad::openForLoading(target, slot, &desc);
|
||||
delete savefile;
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
int GroovieMetaEngine::getAutosaveSlot() const {
|
||||
return GroovieEngine::AUTOSAVE_SLOT;
|
||||
}
|
||||
|
||||
Common::KeymapArray GroovieMetaEngine::initKeymaps(const char *target) const {
|
||||
using namespace Common;
|
||||
using namespace Groovie;
|
||||
|
||||
Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "groovie-engine", _("Groovie engine"));
|
||||
|
||||
Action *act;
|
||||
|
||||
act = new Action(kStandardActionLeftClick, _("Left click"));
|
||||
act->setLeftClickEvent();
|
||||
act->addDefaultInputMapping("MOUSE_LEFT");
|
||||
act->addDefaultInputMapping("JOY_A");
|
||||
engineKeyMap->addAction(act);
|
||||
|
||||
act = new Action(kStandardActionRightClick, _("Right click"));
|
||||
act->setRightClickEvent();
|
||||
act->addDefaultInputMapping("MOUSE_RIGHT");
|
||||
act->addDefaultInputMapping("JOY_B");
|
||||
engineKeyMap->addAction(act);
|
||||
|
||||
act = new Action("SKIPORFAST", _("Skip or fast forward scene"));
|
||||
act->setCustomEngineActionEvent(kActionSkip);
|
||||
act->addDefaultInputMapping("ESCAPE");
|
||||
act->addDefaultInputMapping("SPACE");
|
||||
act->addDefaultInputMapping("JOY_X");
|
||||
engineKeyMap->addAction(act);
|
||||
|
||||
return Keymap::arrayOf(engineKeyMap);
|
||||
}
|
||||
|
||||
} // End of namespace Groovie
|
||||
|
||||
#if PLUGIN_ENABLED_DYNAMIC(GROOVIE)
|
||||
REGISTER_PLUGIN_DYNAMIC(GROOVIE, PLUGIN_TYPE_ENGINE, Groovie::GroovieMetaEngine);
|
||||
#else
|
||||
REGISTER_PLUGIN_STATIC(GROOVIE, PLUGIN_TYPE_ENGINE, Groovie::GroovieMetaEngine);
|
||||
#endif
|
||||
42
engines/groovie/module.mk
Normal file
42
engines/groovie/module.mk
Normal file
@@ -0,0 +1,42 @@
|
||||
MODULE := engines/groovie
|
||||
|
||||
MODULE_OBJS := \
|
||||
logic/cell.o \
|
||||
video/player.o \
|
||||
video/vdx.o \
|
||||
cursor.o \
|
||||
debug.o \
|
||||
font.o \
|
||||
graphics.o \
|
||||
groovie.o \
|
||||
lzss.o \
|
||||
metaengine.o \
|
||||
music.o \
|
||||
resource.o \
|
||||
saveload.o \
|
||||
script.o
|
||||
|
||||
ifdef ENABLE_GROOVIE2
|
||||
MODULE_OBJS += \
|
||||
logic/beehive.o \
|
||||
logic/cake.o \
|
||||
logic/gallery.o \
|
||||
logic/mousetrap.o \
|
||||
logic/othello.o \
|
||||
logic/pente.o \
|
||||
logic/tlcgame.o \
|
||||
logic/triangle.o \
|
||||
logic/winerack.o \
|
||||
video/roq.o
|
||||
endif
|
||||
|
||||
# This module can be built as a plugin
|
||||
ifeq ($(ENABLE_GROOVIE), DYNAMIC_PLUGIN)
|
||||
PLUGIN := 1
|
||||
endif
|
||||
|
||||
# Include common rules
|
||||
include $(srcdir)/rules.mk
|
||||
|
||||
# Detection objects
|
||||
DETECT_OBJS += $(MODULE)/detection.o
|
||||
876
engines/groovie/music.cpp
Normal file
876
engines/groovie/music.cpp
Normal file
@@ -0,0 +1,876 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
#include "groovie/music.h"
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/resource.h"
|
||||
#include "groovie/logic/tlcgame.h"
|
||||
|
||||
#include "backends/audiocd/audiocd.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/file.h"
|
||||
#include "common/macresman.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/midiparser.h"
|
||||
#include "audio/miles.h"
|
||||
#include "audio/decoders/mp3.h"
|
||||
#include "audio/decoders/quicktime.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
// MusicPlayer
|
||||
|
||||
MusicPlayer::MusicPlayer(GroovieEngine *vm) :
|
||||
_vm(vm), _isPlaying(false), _backgroundFileRef(0), _gameVolume(100),
|
||||
_prevCDtrack(0), _backgroundDelay(0), _fadingStartTime(0), _fadingStartVolume(0),
|
||||
_fadingEndVolume(0), _fadingDuration(0), _midiInit(false), _userVolume(0) {
|
||||
}
|
||||
|
||||
MusicPlayer::~MusicPlayer() {
|
||||
g_system->getAudioCDManager()->stop();
|
||||
}
|
||||
|
||||
void MusicPlayer::playSong(uint32 fileref) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (_isPlaying)
|
||||
unload();
|
||||
|
||||
// Set the volumes
|
||||
_fadingEndVolume = 100;
|
||||
_gameVolume = 100;
|
||||
updateVolume();
|
||||
|
||||
// Play the referenced file once
|
||||
play(fileref, false);
|
||||
}
|
||||
|
||||
void MusicPlayer::setBackgroundSong(uint32 fileref) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
debugC(1, kDebugMIDI, "Groovie::Music: Changing the background song: %04X", fileref);
|
||||
_backgroundFileRef = fileref;
|
||||
}
|
||||
|
||||
void MusicPlayer::frameTick() {
|
||||
if (_backgroundDelay > 0) {
|
||||
_backgroundDelay--;
|
||||
if (_backgroundDelay == 0)
|
||||
playSong(_backgroundFileRef);
|
||||
}
|
||||
}
|
||||
|
||||
void MusicPlayer::setBackgroundDelay(uint16 delay) {
|
||||
_backgroundDelay = delay;
|
||||
}
|
||||
|
||||
void MusicPlayer::playCD(uint8 track) {
|
||||
int startms = 0;
|
||||
|
||||
// Stop the MIDI playback
|
||||
unload();
|
||||
|
||||
debugC(1, kDebugMIDI, "Groovie::Music: Playing CD track %d", track);
|
||||
|
||||
if (track == 3) {
|
||||
// This is the credits song, start at 23:20
|
||||
startms = 1400000;
|
||||
// TODO: If we want to play it directly from the CD, we should decrement
|
||||
// the song number (it's track 2 on the 2nd CD)
|
||||
} else if ((track == 98) && (_prevCDtrack == 3)) {
|
||||
// Track 98 is used as a hack to stop the credits song
|
||||
g_system->getAudioCDManager()->stop();
|
||||
stopCreditsIOS();
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the playing track in order to be able to stop the credits song
|
||||
_prevCDtrack = track;
|
||||
|
||||
// Wait until the CD stops playing the current song
|
||||
// It was in the original interpreter, but it introduces a big delay
|
||||
// in the middle of the introduction, so it's disabled right now
|
||||
/*
|
||||
g_system->getAudioCDManager()->updateCD();
|
||||
while (g_system->getAudioCDManager()->isPlaying()) {
|
||||
// Wait a bit and try again
|
||||
_vm->_system->delayMillis(100);
|
||||
g_system->getAudioCDManager()->updateCD();
|
||||
}
|
||||
*/
|
||||
|
||||
// Play the track starting at the requested offset (1000ms = 75 frames)
|
||||
g_system->getAudioCDManager()->play(track - 1, 1, startms * 75 / 1000, 0);
|
||||
|
||||
// If the audio is not playing from the CD, play the "fallback" MIDI.
|
||||
// The Mac version has no CD tracks, so it will always use the MIDI.
|
||||
if (!g_system->getAudioCDManager()->isPlaying()) {
|
||||
if (track == 2) {
|
||||
// Intro MIDI fallback
|
||||
if (_vm->getPlatform() == Common::kPlatformMacintosh)
|
||||
playSong(70);
|
||||
else
|
||||
playSong((19 << 10) | 36); // XMI.GJD, file 36
|
||||
} else if (track == 3) {
|
||||
// TODO: Credits MIDI fallback
|
||||
if (_vm->getPlatform() == Common::kPlatformIOS)
|
||||
playCreditsIOS();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MusicPlayer::startBackground() {
|
||||
debugC(3, kDebugMIDI, "Groovie::Music: startBackground()");
|
||||
if (!_isPlaying && _backgroundFileRef) {
|
||||
debugC(3, kDebugMIDI, "Groovie::Music: Starting the background song (0x%4X)", _backgroundFileRef);
|
||||
play(_backgroundFileRef, true);
|
||||
}
|
||||
}
|
||||
|
||||
void MusicPlayer::setUserVolume(uint16 volume) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
// Save the new user volume
|
||||
_userVolume = volume;
|
||||
if (_userVolume > 0x100)
|
||||
_userVolume = 0x100;
|
||||
|
||||
// Apply it
|
||||
updateVolume();
|
||||
}
|
||||
|
||||
void MusicPlayer::setGameVolume(uint16 volume, uint16 time) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
debugC(1, kDebugMIDI, "Groovie::Music: Setting game volume from %d to %d in %dms", _gameVolume, volume, time);
|
||||
|
||||
// Save the start parameters of the fade
|
||||
_fadingStartTime = _vm->_system->getMillis();
|
||||
_fadingStartVolume = _gameVolume;
|
||||
_fadingDuration = time;
|
||||
|
||||
// Save the new game volume
|
||||
_fadingEndVolume = volume;
|
||||
if (_fadingEndVolume > 100)
|
||||
_fadingEndVolume = 100;
|
||||
}
|
||||
|
||||
bool MusicPlayer::play(uint32 fileref, bool loop) {
|
||||
// Unload the previous song
|
||||
unload();
|
||||
|
||||
// Set the new state
|
||||
_isPlaying = true;
|
||||
|
||||
// Load the new file
|
||||
return load(fileref, loop);
|
||||
}
|
||||
|
||||
void MusicPlayer::stop() {
|
||||
_backgroundFileRef = 0;
|
||||
setBackgroundDelay(0);
|
||||
unload();
|
||||
}
|
||||
|
||||
void MusicPlayer::applyFading() {
|
||||
debugC(6, kDebugMIDI, "Groovie::Music: applyFading() _fadingStartTime = %d, _fadingDuration = %d, _fadingStartVolume = %d, _fadingEndVolume = %d", _fadingStartTime, _fadingDuration, _fadingStartVolume, _fadingEndVolume);
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
// Calculate the passed time
|
||||
uint32 time = _vm->_system->getMillis() - _fadingStartTime;
|
||||
debugC(6, kDebugMIDI, "Groovie::Music: time = %d, _gameVolume = %d", time, _gameVolume);
|
||||
if (time >= _fadingDuration) {
|
||||
// Set the end volume
|
||||
_gameVolume = _fadingEndVolume;
|
||||
} else {
|
||||
// Calculate the interpolated volume for the current time
|
||||
_gameVolume = (_fadingStartVolume * (_fadingDuration - time) +
|
||||
_fadingEndVolume * time) / _fadingDuration;
|
||||
}
|
||||
if (_gameVolume == _fadingEndVolume) {
|
||||
// If we were fading to 0, stop the playback and restore the volume
|
||||
if (_fadingEndVolume == 0) {
|
||||
debugC(1, kDebugMIDI, "Groovie::Music: Faded to zero: end of song. _fadingEndVolume set to 100");
|
||||
// WORKAROUND The original interpreter would keep playing a track
|
||||
// at volume 0 after it has faded out. When a new track was
|
||||
// started, it would restore the volume first and a short part of
|
||||
// the old track would be heard before the new track would start.
|
||||
// To prevent this, playback is actually stopped after fading out,
|
||||
// but _isPlaying remains true. This keeps the original
|
||||
// interpreter behavior of not starting the background music, but
|
||||
// it prevents the issue when starting playback of a new track.
|
||||
unload(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply it
|
||||
updateVolume();
|
||||
}
|
||||
|
||||
void MusicPlayer::onTimer(void *refCon) {
|
||||
debugC(9, kDebugMIDI, "Groovie::Music: onTimer()");
|
||||
MusicPlayer *music = (MusicPlayer *)refCon;
|
||||
Common::StackLock lock(music->_mutex);
|
||||
|
||||
// Apply the game volume fading
|
||||
if (music->_gameVolume != music->_fadingEndVolume) {
|
||||
// Apply the next step of the fading
|
||||
music->applyFading();
|
||||
}
|
||||
|
||||
// If the game is accepting user input, start the background music if necessary
|
||||
if (music->_vm->isWaitingForInput())
|
||||
music->startBackground();
|
||||
|
||||
// Handle internal timed events
|
||||
music->onTimerInternal();
|
||||
}
|
||||
|
||||
void MusicPlayer::unload(bool updateState) {
|
||||
debugC(1, kDebugMIDI, "Groovie::Music: Stopping the playback");
|
||||
|
||||
if (updateState)
|
||||
// Set the new state
|
||||
_isPlaying = false;
|
||||
}
|
||||
|
||||
void MusicPlayer::playCreditsIOS() {
|
||||
Audio::AudioStream *stream = Audio::SeekableAudioStream::openStreamFile("7th_Guest_Dolls_from_Hell_OC_ReMix");
|
||||
|
||||
if (!stream) {
|
||||
warning("Could not find '7th_Guest_Dolls_from_Hell_OC_ReMix' audio file");
|
||||
return;
|
||||
}
|
||||
|
||||
_vm->_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_handleCreditsIOS, stream);
|
||||
}
|
||||
|
||||
void MusicPlayer::stopCreditsIOS() {
|
||||
_vm->_system->getMixer()->stopHandle(_handleCreditsIOS);
|
||||
}
|
||||
|
||||
// MusicPlayerMidi
|
||||
|
||||
MusicPlayerMidi::MusicPlayerMidi(GroovieEngine *vm) :
|
||||
MusicPlayer(vm), _midiParser(nullptr), _data(nullptr), _driver(nullptr) {
|
||||
// Initialize the channel volumes
|
||||
for (int i = 0; i < 0x10; i++) {
|
||||
_chanVolumes[i] = 0x7F;
|
||||
}
|
||||
}
|
||||
|
||||
MusicPlayerMidi::~MusicPlayerMidi() {
|
||||
// Stop the callback
|
||||
if (_driver)
|
||||
_driver->setTimerCallback(nullptr, nullptr);
|
||||
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
// Unload the parser
|
||||
unload();
|
||||
delete _midiParser;
|
||||
|
||||
// Unload the MIDI Driver
|
||||
if (_driver) {
|
||||
_driver->close();
|
||||
delete _driver;
|
||||
}
|
||||
}
|
||||
|
||||
void MusicPlayerMidi::send(uint32 b) {
|
||||
if ((b & 0xFFF0) == 0x07B0) { // Volume change
|
||||
// Save the specific channel volume
|
||||
byte chan = b & 0xF;
|
||||
_chanVolumes[chan] = (b >> 16) & 0x7F;
|
||||
|
||||
// Send the updated value
|
||||
updateChanVolume(chan);
|
||||
|
||||
return;
|
||||
}
|
||||
if (_driver)
|
||||
_driver->send(b);
|
||||
}
|
||||
|
||||
void MusicPlayerMidi::sysEx(const byte *msg, uint16 length) {
|
||||
if (_driver)
|
||||
_driver->sysEx(msg, length);
|
||||
}
|
||||
|
||||
uint16 MusicPlayerMidi::sysExNoDelay(const byte *msg, uint16 length) {
|
||||
return _driver ? _driver->sysExNoDelay(msg, length) : 0;
|
||||
}
|
||||
|
||||
void MusicPlayerMidi::metaEvent(byte type, const byte *data, uint16 length) {
|
||||
switch (type) {
|
||||
case 0x2F:
|
||||
// End of Track, play the background song
|
||||
endTrack();
|
||||
break;
|
||||
default:
|
||||
if (_driver)
|
||||
_driver->metaEvent(type, data, length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MusicPlayerMidi::pause(bool pause) {
|
||||
if (_midiParser) {
|
||||
if (pause) {
|
||||
_midiParser->pausePlaying();
|
||||
} else {
|
||||
_midiParser->resumePlaying();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MusicPlayerMidi::updateChanVolume(byte channel) {
|
||||
// Generate a MIDI Control change message for the volume
|
||||
uint32 b = 0x7B0;
|
||||
|
||||
// Specify the channel
|
||||
b |= (channel & 0xF);
|
||||
|
||||
// Scale by the user and game volumes
|
||||
uint32 val = (_chanVolumes[channel] * _userVolume * _gameVolume) / 0x100 / 100;
|
||||
val &= 0x7F;
|
||||
|
||||
// Send it to the driver
|
||||
if (_driver)
|
||||
_driver->send(b | (val << 16));
|
||||
}
|
||||
|
||||
void MusicPlayerMidi::endTrack() {
|
||||
debugC(3, kDebugMIDI, "Groovie::Music: endTrack()");
|
||||
unload();
|
||||
}
|
||||
|
||||
void MusicPlayerMidi::onTimerInternal() {
|
||||
// TODO: We really only need to call this while music is playing.
|
||||
if (_midiParser)
|
||||
_midiParser->onTimer();
|
||||
}
|
||||
|
||||
void MusicPlayerMidi::updateVolume() {
|
||||
// Apply it to all the channels
|
||||
for (int i = 0; i < 0x10; i++) {
|
||||
updateChanVolume(i);
|
||||
}
|
||||
}
|
||||
|
||||
void MusicPlayerMidi::unload(bool updateState) {
|
||||
MusicPlayer::unload(updateState);
|
||||
|
||||
// Unload the parser data
|
||||
if (_midiParser)
|
||||
_midiParser->unloadMusic();
|
||||
|
||||
// Unload the data
|
||||
delete[] _data;
|
||||
_data = nullptr;
|
||||
}
|
||||
|
||||
bool MusicPlayerMidi::loadParser(Common::SeekableReadStream *stream, bool loop) {
|
||||
if (!_midiParser)
|
||||
return false;
|
||||
|
||||
// Read the whole file to memory
|
||||
int length = stream->size();
|
||||
_data = new byte[length];
|
||||
stream->read(_data, length);
|
||||
delete stream;
|
||||
|
||||
// Set the looping option
|
||||
_midiParser->property(MidiParser::mpAutoLoop, loop);
|
||||
|
||||
// Start parsing the data
|
||||
if (!_midiParser->loadMusic(_data, length)) {
|
||||
error("Groovie::Music: Couldn't parse the data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Activate the timer source
|
||||
if (_driver)
|
||||
_driver->setTimerCallback(this, &onTimer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// MusicPlayerXMI
|
||||
|
||||
MusicPlayerXMI::MusicPlayerXMI(GroovieEngine *vm, const Common::String >lName) :
|
||||
MusicPlayerMidi(vm), _multisourceDriver(nullptr), _milesXmidiTimbres(nullptr) {
|
||||
|
||||
// Create the driver
|
||||
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
|
||||
MusicType musicType = MidiDriver::getMusicType(dev);
|
||||
if (musicType == MT_GM && ConfMan.getBool("native_mt32"))
|
||||
musicType = MT_MT32;
|
||||
_driver = nullptr;
|
||||
|
||||
_musicType = 0;
|
||||
|
||||
// 7th Guest uses FAT.AD/FAT.OPL/FAT.MT
|
||||
// 11th Hour uses SAMPLE.AD/SAMPLE.OPL/SAMPLE.MT
|
||||
switch (musicType) {
|
||||
case MT_ADLIB:
|
||||
_driver = _multisourceDriver = Audio::MidiDriver_Miles_AdLib_create(Common::Path(gtlName + ".AD"), Common::Path(gtlName + ".OPL"));
|
||||
break;
|
||||
case MT_MT32:
|
||||
Audio::MidiDriver_Miles_Midi *milesDriver;
|
||||
milesDriver = Audio::MidiDriver_Miles_MIDI_create(musicType, Common::Path(gtlName + ".MT"));
|
||||
_milesXmidiTimbres = milesDriver;
|
||||
_driver = _multisourceDriver = milesDriver;
|
||||
break;
|
||||
case MT_GM:
|
||||
_driver = _multisourceDriver = Audio::MidiDriver_Miles_MIDI_create(musicType, "");
|
||||
break;
|
||||
case MT_NULL:
|
||||
_driver = _multisourceDriver = new MidiDriver_NULL_Multisource();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_musicType = musicType;
|
||||
|
||||
assert(_driver);
|
||||
|
||||
// Create the parser
|
||||
_midiParser = MidiParser::createParser_XMIDI(nullptr, nullptr, 0);
|
||||
|
||||
_multisourceDriver->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
|
||||
_multisourceDriver->property(MidiDriver::PROP_MILES_VERSION,
|
||||
_vm->getEngineVersion() == kGroovieT7G ? Audio::MILES_VERSION_2 : Audio::MILES_VERSION_3);
|
||||
if (_vm->getEngineVersion() == kGroovieT7G && musicType == MT_GM)
|
||||
// The 7th Guest GM init sets drumkit to 0x30 (Orchestra) and relies on
|
||||
// this remaining set; tracks don't set this at start. Some tracks
|
||||
// temporarily change the drumkit; if playback is stopped at the wrong
|
||||
// time this will cause the changed drumkit to remain in effect.
|
||||
// Set a default drumkit value to make sure it is set correctly at the
|
||||
// start of each track.
|
||||
_multisourceDriver->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_DRUMKIT, 0x30);
|
||||
if (_vm->getEngineVersion() == kGroovieT11H)
|
||||
// Some The 11th Hour tracks use modulation, but not all tracks reset
|
||||
// it at start. Set a default value to make sure it is reset at the
|
||||
// start of each track.
|
||||
_multisourceDriver->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_MODULATION, 0);
|
||||
|
||||
int result = _driver->open();
|
||||
if (result > 0 && result != MidiDriver::MERR_ALREADY_OPEN)
|
||||
error("Opening MidiDriver failed with error code %i", result);
|
||||
|
||||
_multisourceDriver->setSourceNeutralVolume(0, 100);
|
||||
|
||||
// Set the parser's driver
|
||||
_midiParser->setMidiDriver(this);
|
||||
|
||||
// Set the timer rate
|
||||
_midiParser->setTimerRate(_driver->getBaseTempo());
|
||||
}
|
||||
|
||||
MusicPlayerXMI::~MusicPlayerXMI() {
|
||||
_midiParser->stopPlaying();
|
||||
}
|
||||
|
||||
void MusicPlayerXMI::send(int8 source, uint32 b) {
|
||||
_multisourceDriver->send(source, b);
|
||||
}
|
||||
|
||||
void MusicPlayerXMI::metaEvent(int8 source, byte type, const byte *data, uint16 length) {
|
||||
if (type == 0x2F) // End Of Track
|
||||
MusicPlayerMidi::endTrack();
|
||||
_multisourceDriver->metaEvent(source, type, data, length);
|
||||
}
|
||||
|
||||
void MusicPlayerXMI::stopAllNotes(bool stopSustainedNotes) {
|
||||
if (_driver)
|
||||
_driver->stopAllNotes(stopSustainedNotes);
|
||||
}
|
||||
|
||||
bool MusicPlayerXMI::isReady(int8 source) {
|
||||
return _driver ? _driver->isReady(source) : false;
|
||||
}
|
||||
|
||||
void MusicPlayerXMI::updateVolume() {
|
||||
_multisourceDriver->setSourceVolume(0, _gameVolume);
|
||||
}
|
||||
|
||||
void MusicPlayerXMI::setUserVolume(uint16 volume) {
|
||||
_multisourceDriver->syncSoundSettings();
|
||||
}
|
||||
|
||||
bool MusicPlayerXMI::load(uint32 fileref, bool loop) {
|
||||
debugC(1, kDebugMIDI, "Groovie::Music: Starting the playback of song: %04X", fileref);
|
||||
|
||||
// Open the song resource
|
||||
Common::SeekableReadStream *file = _vm->_resMan->open(fileref);
|
||||
if (!file) {
|
||||
error("Groovie::Music: Couldn't find resource 0x%04X", fileref);
|
||||
return false;
|
||||
}
|
||||
|
||||
return loadParser(file, loop);
|
||||
}
|
||||
|
||||
void MusicPlayerXMI::unload(bool updateState) {
|
||||
MusicPlayerMidi::unload(updateState);
|
||||
_multisourceDriver->deinitSource(0);
|
||||
}
|
||||
|
||||
// MusicPlayerMac_t7g
|
||||
|
||||
MusicPlayerMac_t7g::MusicPlayerMac_t7g(GroovieEngine *vm) : MusicPlayerMidi(vm) {
|
||||
// Create the parser
|
||||
_midiParser = MidiParser::createParser_SMF();
|
||||
|
||||
// Create the driver
|
||||
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
|
||||
_driver = MidiDriver::createMidi(dev);
|
||||
assert(_driver);
|
||||
|
||||
_driver->open(); // TODO: Handle return value != 0 (indicating an error)
|
||||
|
||||
// Set the parser's driver
|
||||
_midiParser->setMidiDriver(this);
|
||||
|
||||
// Set the timer rate
|
||||
_midiParser->setTimerRate(_driver->getBaseTempo());
|
||||
|
||||
// Sanity check
|
||||
assert(_vm->_macResFork);
|
||||
}
|
||||
|
||||
bool MusicPlayerMac_t7g::load(uint32 fileref, bool loop) {
|
||||
debugC(1, kDebugMIDI, "Groovie::Music: Starting the playback of song: %04X", fileref);
|
||||
|
||||
// First try for compressed MIDI
|
||||
Common::SeekableReadStream *file = _vm->_macResFork->getResource(MKTAG('c','m','i','d'), fileref & 0x3FF);
|
||||
|
||||
if (file) {
|
||||
// Found the resource, decompress it
|
||||
Common::SeekableReadStream *tmp = decompressMidi(file);
|
||||
delete file;
|
||||
file = tmp;
|
||||
} else {
|
||||
// Otherwise, it's uncompressed
|
||||
file = _vm->_macResFork->getResource(MKTAG('M','i','d','i'), fileref & 0x3FF);
|
||||
if (!file)
|
||||
error("Groovie::Music: Couldn't find resource 0x%04X", fileref);
|
||||
}
|
||||
|
||||
return loadParser(file, loop);
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *MusicPlayerMac_t7g::decompressMidi(Common::SeekableReadStream *stream) {
|
||||
// Initialize an output buffer of the given size
|
||||
uint32 size = stream->readUint32BE();
|
||||
byte *output = (byte *)malloc(size);
|
||||
|
||||
byte *current = output;
|
||||
uint32 decompBytes = 0;
|
||||
while ((decompBytes < size) && !stream->eos()) {
|
||||
// 8 flags
|
||||
byte flags = stream->readByte();
|
||||
|
||||
for (byte i = 0; (i < 8) && !stream->eos(); i++) {
|
||||
if (flags & 1) {
|
||||
// 1: Next byte is a literal
|
||||
*(current++) = stream->readByte();
|
||||
if (stream->eos())
|
||||
continue;
|
||||
decompBytes++;
|
||||
} else {
|
||||
// 0: It's a reference to part of the history
|
||||
uint16 args = stream->readUint16BE();
|
||||
if (stream->eos())
|
||||
continue;
|
||||
|
||||
// Length = 4bit unsigned (3 minimal)
|
||||
uint8 length = (args >> 12) + 3;
|
||||
|
||||
// Offset = 12bit signed (all values are negative)
|
||||
int16 offset = (args & 0xFFF) | 0xF000;
|
||||
|
||||
// Copy from the past decompressed bytes
|
||||
decompBytes += length;
|
||||
while (length > 0) {
|
||||
*(current) = *(current + offset);
|
||||
current++;
|
||||
length--;
|
||||
}
|
||||
}
|
||||
flags = flags >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the output buffer wrapped in a MemoryReadStream
|
||||
return new Common::MemoryReadStream(output, size, DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
// MusicPlayerMac_v2
|
||||
|
||||
MusicPlayerMac_v2::MusicPlayerMac_v2(GroovieEngine *vm) : MusicPlayerMidi(vm) {
|
||||
// Create the parser
|
||||
_midiParser = MidiParser::createParser_QT();
|
||||
|
||||
// Create the driver
|
||||
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
|
||||
_driver = MidiDriver::createMidi(dev);
|
||||
assert(_driver);
|
||||
|
||||
_driver->open(); // TODO: Handle return value != 0 (indicating an error)
|
||||
|
||||
// Set the parser's driver
|
||||
_midiParser->setMidiDriver(this);
|
||||
|
||||
// Set the timer rate
|
||||
_midiParser->setTimerRate(_driver->getBaseTempo());
|
||||
}
|
||||
|
||||
bool MusicPlayerMac_v2::load(uint32 fileref, bool loop) {
|
||||
debugC(1, kDebugMIDI, "Groovie::Music: Starting the playback of song: %04X", fileref);
|
||||
|
||||
// Find correct filename
|
||||
ResInfo info;
|
||||
_vm->_resMan->getResInfo(fileref, info);
|
||||
uint len = info.filename.size();
|
||||
if (len < 4)
|
||||
return false; // This shouldn't actually occur
|
||||
|
||||
// Remove the extension and add ".mov"
|
||||
info.filename.deleteLastChar();
|
||||
info.filename.deleteLastChar();
|
||||
info.filename.deleteLastChar();
|
||||
info.filename += "mov";
|
||||
|
||||
Common::SeekableReadStream *file = SearchMan.createReadStreamForMember(Common::Path(info.filename));
|
||||
|
||||
if (!file) {
|
||||
warning("Could not find file '%s'", info.filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return loadParser(file, loop);
|
||||
}
|
||||
|
||||
MusicPlayerIOS::MusicPlayerIOS(GroovieEngine *vm) : MusicPlayer(vm) {
|
||||
vm->getTimerManager()->installTimerProc(&onTimer, 50 * 1000, this, "groovieMusic");
|
||||
}
|
||||
|
||||
MusicPlayerIOS::~MusicPlayerIOS() {
|
||||
_vm->getTimerManager()->removeTimerProc(&onTimer);
|
||||
}
|
||||
|
||||
void MusicPlayerIOS::updateVolume() {
|
||||
// Just set the mixer volume for the music sound type
|
||||
_vm->_system->getMixer()->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, _userVolume * _gameVolume / 100);
|
||||
}
|
||||
|
||||
void MusicPlayerIOS::unload(bool updateState) {
|
||||
MusicPlayer::unload(updateState);
|
||||
|
||||
_vm->_system->getMixer()->stopHandle(_handle);
|
||||
}
|
||||
|
||||
bool MusicPlayerIOS::load(uint32 fileref, bool loop) {
|
||||
// Find correct filename
|
||||
ResInfo info;
|
||||
_vm->_resMan->getResInfo(fileref, info);
|
||||
uint len = info.filename.size();
|
||||
if (len < 4)
|
||||
return false; // This shouldn't actually occur
|
||||
/*
|
||||
19462 door
|
||||
19463 ??
|
||||
19464 ??
|
||||
19465 puzzle?
|
||||
19466 cake
|
||||
19467 maze
|
||||
19468 ambient (but not 69, amb b. odd)
|
||||
19470 puzzle
|
||||
19471
|
||||
19473
|
||||
19475 coffins or blood pump
|
||||
19476 blood pump or coffins
|
||||
19493
|
||||
19499 chapel
|
||||
19509 downstair ambient
|
||||
19510 bedroom 'skip 3 and 5' puzzle (should loop from partway?)
|
||||
19514
|
||||
19515 bathroom drain teeth
|
||||
*/
|
||||
if ((fileref >= 19462 && fileref <= 19468) ||
|
||||
fileref == 19470 || fileref == 19471 ||
|
||||
fileref == 19473 || fileref == 19475 ||
|
||||
fileref == 19476 || fileref == 19493 ||
|
||||
fileref == 19499 || fileref == 19509 ||
|
||||
fileref == 19510 || fileref == 19514 ||
|
||||
fileref == 19515)
|
||||
loop = true; // XMIs for these refs self-loop
|
||||
|
||||
// iOS port provides alternative intro sequence music
|
||||
if (info.filename == "gu39.xmi") {
|
||||
info.filename = "intro";
|
||||
} else if (info.filename == "gu32.xmi") {
|
||||
info.filename = "foyer";
|
||||
} else {
|
||||
// Remove the extension
|
||||
info.filename.deleteLastChar();
|
||||
info.filename.deleteLastChar();
|
||||
info.filename.deleteLastChar();
|
||||
info.filename.deleteLastChar();
|
||||
}
|
||||
|
||||
if (info.filename == "ini_sc") {
|
||||
// This is an initialization MIDI file, which is not
|
||||
// needed for digital tracks
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the audio stream
|
||||
Audio::SeekableAudioStream *seekStream = Audio::SeekableAudioStream::openStreamFile(Common::Path(info.filename));
|
||||
|
||||
if (!seekStream) {
|
||||
warning("Could not play audio file '%s'", info.filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
Audio::AudioStream *audStream = seekStream;
|
||||
|
||||
// Loop if requested
|
||||
if (loop)
|
||||
audStream = Audio::makeLoopingAudioStream(seekStream, 0);
|
||||
|
||||
// MIDI player handles volume reset on load, IOS player doesn't - force update here
|
||||
updateVolume();
|
||||
|
||||
// Play!
|
||||
_vm->_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_handle, audStream);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
MusicPlayerTlc::MusicPlayerTlc(GroovieEngine *vm) : MusicPlayer(vm) {
|
||||
_file = nullptr;
|
||||
vm->getTimerManager()->installTimerProc(&onTimer, 50 * 1000, this, "groovieMusic");
|
||||
}
|
||||
|
||||
MusicPlayerTlc::~MusicPlayerTlc() {
|
||||
_vm->getTimerManager()->removeTimerProc(&onTimer);
|
||||
}
|
||||
|
||||
void MusicPlayerTlc::updateVolume() {
|
||||
// Just set the mixer volume for the music sound type
|
||||
_vm->_system->getMixer()->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, _userVolume * _gameVolume / 100);
|
||||
}
|
||||
|
||||
void MusicPlayerTlc::unload(bool updateState) {
|
||||
MusicPlayer::unload(updateState);
|
||||
|
||||
_vm->_system->getMixer()->stopHandle(_handle);
|
||||
if (_file) {
|
||||
delete _file;
|
||||
}
|
||||
_file = nullptr;
|
||||
}
|
||||
|
||||
Common::String MusicPlayerTlc::getFilename(uint32 fileref) {
|
||||
#ifdef ENABLE_GROOVIE2
|
||||
return TlcGame::getTlcMusicFilename(fileref);
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MusicPlayerTlc::load(uint32 fileref, bool loop) {
|
||||
unload();
|
||||
_file = new Common::File();
|
||||
|
||||
Common::String filename = getFilename(fileref);
|
||||
|
||||
// Apple platforms use m4a files instead of mpg
|
||||
if (_vm->getPlatform() == Common::kPlatformUnknown)
|
||||
filename += ".m4a";
|
||||
else
|
||||
filename += ".mpg";
|
||||
|
||||
// Create the audio stream from fileref
|
||||
_file->open(Common::Path(filename));
|
||||
Audio::SeekableAudioStream *seekStream = nullptr;
|
||||
if (_file->isOpen()) {
|
||||
if (filename.hasSuffix(".m4a"))
|
||||
seekStream = Audio::makeQuickTimeStream(_file, DisposeAfterUse::NO);
|
||||
#ifdef USE_MAD
|
||||
else
|
||||
seekStream = Audio::makeMP3Stream(_file, DisposeAfterUse::NO);
|
||||
#endif
|
||||
} else {
|
||||
delete _file;
|
||||
_file = nullptr;
|
||||
}
|
||||
|
||||
if (!seekStream) {
|
||||
warning("Could not play audio file '%s'", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
Audio::AudioStream *audStream = seekStream;
|
||||
|
||||
// TODO: Loop if requested
|
||||
if (!loop)
|
||||
warning("TODO: MusicPlayerTlc::load with loop == false");
|
||||
|
||||
if (loop || 1)
|
||||
audStream = Audio::makeLoopingAudioStream(seekStream, 0);
|
||||
|
||||
// MIDI player handles volume reset on load, IOS player doesn't - force update here
|
||||
updateVolume();
|
||||
|
||||
// Play!
|
||||
_vm->_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_handle, audStream);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This a list of files for background music. This list is hardcoded in the Clandestiny player.
|
||||
const char *kClanMusicFiles[] = {
|
||||
"mbf_arb1", "mbf_arm1", "mbf_bal1", "mbf_c2p2", "act18mus", "act15mus", "act21mus",
|
||||
"act05mus", "act04mus", "act23mus", "act17mus", "act03mus", "act06mus", "act19mus",
|
||||
"act07mus", "mbf_mne1", "act24mus", "act24mus", "act14mus", "act20mus", "act15mus",
|
||||
"act13mus", "act08mus", "mbf_uph1", "mbf_uph1", "act19mus", "mbf_bol1", "mbf_cbk1",
|
||||
"mbf_glf1", "mbf_bro1", "mbf_c1r1", "mbf_c1r1", "mbf_c1r1", "mbf_c1r1", "mbf_c2r1",
|
||||
"mbf_c2r1", "mbf_c2r1", "mbf_c2r1", "mbf_c3r1", "mbf_c3r1", "mbf_c3r1", "mbf_c4r1",
|
||||
"mbf_c4r1", "mbf_c1p2", "mbf_c3p3", "mbf_c1p3", "mbf_bro1", "mbf_c1p1", "act17mus",
|
||||
"mbf_c2p2", "mbf_c2p1", "act10mus", "mbf_c1p1", "mbf_mne1", "mbf_c3p3", "act17mus",
|
||||
"mbf_c3p2", "mbf_c3p1", "act25mus", "mbf_c4p2", "mbf_c4p1"
|
||||
};
|
||||
|
||||
Common::String MusicPlayerClan::getFilename(uint32 fileref) {
|
||||
return kClanMusicFiles[fileref];
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
234
engines/groovie/music.h
Normal file
234
engines/groovie/music.h
Normal file
@@ -0,0 +1,234 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_MUSIC_H
|
||||
#define GROOVIE_MUSIC_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/file.h"
|
||||
#include "audio/mididrv.h"
|
||||
#include "audio/mididrv_ms.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/miles.h"
|
||||
|
||||
class MidiParser;
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class GroovieEngine;
|
||||
class TlcGame;
|
||||
|
||||
class MusicPlayer {
|
||||
public:
|
||||
MusicPlayer(GroovieEngine *vm);
|
||||
virtual ~MusicPlayer();
|
||||
|
||||
void playSong(uint32 fileref);
|
||||
// Stops all music playback. Clears the current
|
||||
// background song.
|
||||
void stop();
|
||||
void setBackgroundSong(uint32 fileref);
|
||||
void playCD(uint8 track);
|
||||
void startBackground();
|
||||
bool isPlaying() { return _isPlaying; }
|
||||
// Pause or resume the music. Note that digital music
|
||||
// already pauses when the ScummVM menu is open, so
|
||||
// it does not seem to need an implementation.
|
||||
virtual void pause(bool pause) { }
|
||||
|
||||
// Set the MIDI initialization state
|
||||
void setMidiInit(bool midiInit) { _midiInit = midiInit; }
|
||||
// Returns true if MIDI has been fully initialized
|
||||
bool isMidiInit() { return _midiInit; }
|
||||
|
||||
void frameTick();
|
||||
void setBackgroundDelay(uint16 delay);
|
||||
|
||||
// Volume
|
||||
virtual void setUserVolume(uint16 volume);
|
||||
void setGameVolume(uint16 volume, uint16 time);
|
||||
|
||||
private:
|
||||
// Song playback
|
||||
bool play(uint32 fileref, bool loop);
|
||||
bool _isPlaying;
|
||||
uint32 _backgroundFileRef;
|
||||
uint8 _prevCDtrack;
|
||||
|
||||
uint16 _backgroundDelay;
|
||||
|
||||
// T7G iOS credits mp3 stream
|
||||
void playCreditsIOS();
|
||||
void stopCreditsIOS();
|
||||
Audio::SoundHandle _handleCreditsIOS;
|
||||
|
||||
// Volume fading
|
||||
uint32 _fadingStartTime;
|
||||
uint16 _fadingStartVolume;
|
||||
uint16 _fadingEndVolume;
|
||||
uint16 _fadingDuration;
|
||||
void applyFading();
|
||||
|
||||
protected:
|
||||
GroovieEngine *_vm;
|
||||
|
||||
// True if the MIDI initialization has completed
|
||||
bool _midiInit;
|
||||
|
||||
// Callback
|
||||
static void onTimer(void *data);
|
||||
virtual void onTimerInternal() {}
|
||||
Common::Mutex _mutex;
|
||||
|
||||
// User volume
|
||||
uint16 _userVolume;
|
||||
// Game volume
|
||||
uint16 _gameVolume;
|
||||
|
||||
// These are specific for each type of music
|
||||
virtual void updateVolume() = 0;
|
||||
virtual bool load(uint32 fileref, bool loop) = 0;
|
||||
virtual void unload(bool updateState = true);
|
||||
};
|
||||
|
||||
class MusicPlayerMidi : public MusicPlayer, public MidiDriver_BASE {
|
||||
public:
|
||||
MusicPlayerMidi(GroovieEngine *vm);
|
||||
~MusicPlayerMidi() override;
|
||||
|
||||
// MidiDriver_BASE interface
|
||||
void send(uint32 b) override;
|
||||
void sysEx(const byte* msg, uint16 length) override;
|
||||
uint16 sysExNoDelay(const byte *msg, uint16 length) override;
|
||||
void metaEvent(byte type, const byte *data, uint16 length) override;
|
||||
|
||||
void pause(bool pause) override;
|
||||
|
||||
private:
|
||||
// Channel volumes
|
||||
byte _chanVolumes[0x10];
|
||||
void updateChanVolume(byte channel);
|
||||
|
||||
protected:
|
||||
byte *_data;
|
||||
MidiParser *_midiParser;
|
||||
MidiDriver *_driver;
|
||||
|
||||
void onTimerInternal() override;
|
||||
void updateVolume() override;
|
||||
void unload(bool updateState = true) override;
|
||||
void endTrack();
|
||||
|
||||
bool loadParser(Common::SeekableReadStream *stream, bool loop);
|
||||
};
|
||||
|
||||
class MusicPlayerXMI : public MusicPlayerMidi, public Audio::MidiDriver_Miles_Xmidi_Timbres {
|
||||
public:
|
||||
MusicPlayerXMI(GroovieEngine *vm, const Common::String >lName);
|
||||
~MusicPlayerXMI();
|
||||
|
||||
using MusicPlayerMidi::send;
|
||||
void send(int8 source, uint32 b) override;
|
||||
using MusicPlayerMidi::metaEvent;
|
||||
void metaEvent(int8 source, byte type, const byte *data, uint16 length) override;
|
||||
void stopAllNotes(bool stopSustainedNotes) override;
|
||||
void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) override {
|
||||
if (_milesXmidiTimbres)
|
||||
_milesXmidiTimbres->processXMIDITimbreChunk(timbreListPtr, timbreListSize);
|
||||
};
|
||||
bool isReady(int8 source = -1) override;
|
||||
|
||||
void setUserVolume(uint16 volume) override;
|
||||
|
||||
protected:
|
||||
void updateVolume() override;
|
||||
bool load(uint32 fileref, bool loop) override;
|
||||
void unload(bool updateState = true) override;
|
||||
|
||||
private:
|
||||
// Output music type
|
||||
uint8 _musicType;
|
||||
|
||||
MidiDriver_Multisource *_multisourceDriver;
|
||||
MidiDriver_Miles_Xmidi_Timbres *_milesXmidiTimbres;
|
||||
};
|
||||
|
||||
class MusicPlayerMac_t7g : public MusicPlayerMidi {
|
||||
public:
|
||||
MusicPlayerMac_t7g(GroovieEngine *vm);
|
||||
|
||||
protected:
|
||||
bool load(uint32 fileref, bool loop) override;
|
||||
|
||||
private:
|
||||
Common::SeekableReadStream *decompressMidi(Common::SeekableReadStream *stream);
|
||||
};
|
||||
|
||||
class MusicPlayerMac_v2 : public MusicPlayerMidi {
|
||||
public:
|
||||
MusicPlayerMac_v2(GroovieEngine *vm);
|
||||
|
||||
protected:
|
||||
bool load(uint32 fileref, bool loop) override;
|
||||
};
|
||||
|
||||
class MusicPlayerIOS : public MusicPlayer {
|
||||
public:
|
||||
MusicPlayerIOS(GroovieEngine *vm);
|
||||
~MusicPlayerIOS() override;
|
||||
|
||||
protected:
|
||||
void updateVolume() override;
|
||||
bool load(uint32 fileref, bool loop) override;
|
||||
void unload(bool updateState = true) override;
|
||||
|
||||
private:
|
||||
Audio::SoundHandle _handle;
|
||||
};
|
||||
|
||||
class MusicPlayerTlc : public MusicPlayer {
|
||||
public:
|
||||
MusicPlayerTlc(GroovieEngine *vm);
|
||||
~MusicPlayerTlc();
|
||||
|
||||
protected:
|
||||
virtual Common::String getFilename(uint32 fileref);
|
||||
void updateVolume() override;
|
||||
bool load(uint32 fileref, bool loop) override;
|
||||
void unload(bool updateState = true) override;
|
||||
|
||||
private:
|
||||
Audio::SoundHandle _handle;
|
||||
Common::File *_file;
|
||||
};
|
||||
|
||||
class MusicPlayerClan : public MusicPlayerTlc {
|
||||
public:
|
||||
MusicPlayerClan(GroovieEngine *vm) : MusicPlayerTlc(vm) {}
|
||||
|
||||
protected:
|
||||
Common::String getFilename(uint32 fileref) override;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_MUSIC_H
|
||||
362
engines/groovie/resource.cpp
Normal file
362
engines/groovie/resource.cpp
Normal file
@@ -0,0 +1,362 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/file.h"
|
||||
#include "common/macresman.h"
|
||||
#include "common/substream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/config-manager.h"
|
||||
|
||||
#include "groovie/resource.h"
|
||||
#include "groovie/groovie.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
// ResMan
|
||||
|
||||
Common::SeekableReadStream *ResMan::open(uint32 fileRef) {
|
||||
// Get the information about the resource
|
||||
ResInfo resInfo;
|
||||
if (!getResInfo(fileRef, resInfo)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
debugC(1, kDebugResource, "Groovie::Resource: Opening resource %d", fileRef);
|
||||
return open(resInfo);
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *ResMan::open(const ResInfo &resInfo) {
|
||||
// Do we know the name of the required GJD?
|
||||
if (resInfo.gjd >= _gjds.size()) {
|
||||
// Is this a raw vob file? (TLC DVD)
|
||||
if (resInfo.gjd >= 1000) {
|
||||
Common::Path filename = Common::Path(Common::String::format("VOB%u.VOB", resInfo.offset));
|
||||
if (!Common::File::exists(filename)) {
|
||||
return nullptr;
|
||||
}
|
||||
Common::File *vobFile = new Common::File();
|
||||
vobFile->open(filename);
|
||||
return vobFile;
|
||||
}
|
||||
|
||||
error("Groovie::Resource: Unknown GJD %d", resInfo.gjd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
debugC(1, kDebugResource, "Groovie::Resource: Opening resource (%s, %d, %d, %d)", _gjds[resInfo.gjd].toString().c_str(), resInfo.offset, resInfo.size, resInfo.disks);
|
||||
|
||||
// Does it exist?
|
||||
if (!Common::File::exists(_gjds[resInfo.gjd])) {
|
||||
error("Groovie::Resource: %s not found (resInfo.disks: %d)", _gjds[resInfo.gjd].toString().c_str(), resInfo.disks);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Open the pack file
|
||||
Common::File *gjdFile = new Common::File();
|
||||
if (!gjdFile->open(_gjds[resInfo.gjd])) {
|
||||
delete gjdFile;
|
||||
error("Groovie::Resource: Couldn't open %s", _gjds[resInfo.gjd].toString().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Save the used gjd file (except xmi and gamwav)
|
||||
if (resInfo.gjd < 19) {
|
||||
_lastGjd = resInfo.gjd;
|
||||
}
|
||||
|
||||
// Returning the resource substream
|
||||
Common::SeekableSubReadStream *file = new Common::SeekableSubReadStream(gjdFile, resInfo.offset, resInfo.offset + resInfo.size, DisposeAfterUse::YES);
|
||||
if (ConfMan.getBool("dump_resources")) {
|
||||
dumpResource(file, Common::Path(resInfo.filename), false);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
Common::String ResMan::getGjdName(const ResInfo &resInfo) {
|
||||
if (resInfo.gjd >= _gjds.size()) {
|
||||
if (resInfo.gjd >= 1000) {
|
||||
return Common::String::format("VOB%u.VOB", resInfo.offset);
|
||||
}
|
||||
|
||||
error("Groovie::Resource: Unknown GJD %d", resInfo.gjd);
|
||||
}
|
||||
|
||||
return _gjds[resInfo.gjd].baseName();
|
||||
}
|
||||
|
||||
void ResMan::dumpResource(const Common::String &fileName) {
|
||||
uint32 fileRef = getRef(fileName);
|
||||
dumpResource(fileRef, Common::Path(fileName));
|
||||
}
|
||||
|
||||
void ResMan::dumpResource(uint32 fileRef, const Common::Path &fileName) {
|
||||
Common::SeekableReadStream *inFile = open(fileRef);
|
||||
dumpResource(inFile, fileName);
|
||||
}
|
||||
|
||||
void ResMan::dumpResource(Common::SeekableReadStream *inFile, const Common::Path &fileName, bool dispose) {
|
||||
Common::DumpFile outFile;
|
||||
outFile.open(fileName);
|
||||
|
||||
int64 totalSize = inFile->size();
|
||||
byte *data = new byte[totalSize];
|
||||
inFile->read(data, totalSize);
|
||||
|
||||
outFile.write(data, totalSize);
|
||||
outFile.flush();
|
||||
|
||||
delete[] data;
|
||||
|
||||
if (dispose)
|
||||
delete inFile;
|
||||
else
|
||||
inFile->seek(0);
|
||||
|
||||
outFile.close();
|
||||
}
|
||||
|
||||
// ResMan_t7g
|
||||
|
||||
static const char t7g_gjds[][0x15] = {"at", "b", "ch", "d", "dr", "fh", "ga", "hdisk", "htbd", "intro", "jhek", "k", "la", "li", "mb", "mc", "mu", "n", "p", "xmi", "gamwav"};
|
||||
|
||||
ResMan_t7g::ResMan_t7g(Common::MacResManager *macResFork) : _macResFork(macResFork) {
|
||||
for (int i = 0; i < 0x15; i++) {
|
||||
// Prepare the filename
|
||||
Common::String filename = t7g_gjds[i];
|
||||
filename += ".gjd";
|
||||
|
||||
// Handle the special case of Mac's hdisk.gjd
|
||||
if (_macResFork && i == 7)
|
||||
filename = "T7GData";
|
||||
|
||||
// Append it to the list of GJD files
|
||||
_gjds.push_back(Common::Path(filename));
|
||||
}
|
||||
}
|
||||
|
||||
uint32 ResMan_t7g::getRef(Common::String name) {
|
||||
// Get the name of the RL file
|
||||
Common::String rlFileName(t7g_gjds[_lastGjd]);
|
||||
rlFileName += ".rl";
|
||||
|
||||
Common::SeekableReadStream *rlFile = nullptr;
|
||||
|
||||
if (_macResFork) {
|
||||
// Open the RL file from the resource fork
|
||||
rlFile = _macResFork->getResource(rlFileName);
|
||||
} else {
|
||||
// Open the RL file
|
||||
rlFile = SearchMan.createReadStreamForMember(Common::Path(rlFileName));
|
||||
}
|
||||
|
||||
if (!rlFile)
|
||||
error("Groovie::Resource: Couldn't open %s", rlFileName.c_str());
|
||||
|
||||
uint32 resNum;
|
||||
bool found = false;
|
||||
for (resNum = 0; !found && !rlFile->err() && !rlFile->eos(); resNum++) {
|
||||
// Read the resource name
|
||||
char readname[12];
|
||||
rlFile->read(readname, 12);
|
||||
|
||||
// Test whether it's the resource we're searching
|
||||
Common::String resname(readname, 12);
|
||||
if (resname.hasPrefix(name.c_str())) {
|
||||
debugC(2, kDebugResource, "Groovie::Resource: Resource %12s matches %s", readname, name.c_str());
|
||||
found = true;
|
||||
}
|
||||
|
||||
// Skip the rest of resource information
|
||||
rlFile->read(readname, 8);
|
||||
}
|
||||
|
||||
// Close the RL file
|
||||
delete rlFile;
|
||||
|
||||
// Verify we really found the resource
|
||||
if (!found) {
|
||||
error("Groovie::Resource: Couldn't find resource %s in %s", name.c_str(), rlFileName.c_str());
|
||||
return (uint32)-1;
|
||||
}
|
||||
|
||||
return (_lastGjd << 10) | (resNum - 1);
|
||||
}
|
||||
|
||||
bool ResMan_t7g::getResInfo(uint32 fileRef, ResInfo &resInfo) {
|
||||
// Calculate the GJD and the resource number
|
||||
resInfo.gjd = fileRef >> 10;
|
||||
uint16 resNum = fileRef & 0x3FF;
|
||||
|
||||
// Get the name of the RL file
|
||||
Common::String rlFileName(t7g_gjds[resInfo.gjd]);
|
||||
rlFileName += ".rl";
|
||||
|
||||
Common::SeekableReadStream *rlFile = nullptr;
|
||||
|
||||
if (_macResFork) {
|
||||
// Open the RL file from the resource fork
|
||||
rlFile = _macResFork->getResource(rlFileName);
|
||||
} else {
|
||||
// Open the RL file
|
||||
rlFile = SearchMan.createReadStreamForMember(Common::Path(rlFileName));
|
||||
}
|
||||
|
||||
if (!rlFile)
|
||||
error("Groovie::Resource: Couldn't open %s", rlFileName.c_str());
|
||||
|
||||
// Seek to the position of the desired resource
|
||||
rlFile->seek(resNum * 20);
|
||||
if (rlFile->eos()) {
|
||||
delete rlFile;
|
||||
error("Groovie::Resource: Invalid resource number: 0x%04X (%s)", resNum, rlFileName.c_str());
|
||||
}
|
||||
|
||||
// Read the resource name
|
||||
char resname[13];
|
||||
rlFile->read(resname, 12);
|
||||
resname[12] = 0;
|
||||
debugC(2, kDebugResource, "Groovie::Resource: Resource name: %12s", resname);
|
||||
resInfo.filename = resname;
|
||||
|
||||
// Read the resource information
|
||||
resInfo.offset = rlFile->readUint32LE();
|
||||
resInfo.size = rlFile->readUint32LE();
|
||||
|
||||
// Close the resource RL file
|
||||
delete rlFile;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ResMan_v2
|
||||
|
||||
ResMan_v2::ResMan_v2() {
|
||||
Common::File indexfile;
|
||||
|
||||
// Open the GJD index file
|
||||
if (!indexfile.open("gjd.gjd")) {
|
||||
error("Groovie::Resource: Couldn't open gjd.gjd");
|
||||
return;
|
||||
}
|
||||
|
||||
Common::String line = indexfile.readLine();
|
||||
while (!indexfile.eos() && !line.empty()) {
|
||||
// Get the name before the space
|
||||
Common::String filename;
|
||||
for (const char *cur = line.c_str(); *cur != ' '; cur++) {
|
||||
filename += *cur;
|
||||
}
|
||||
|
||||
// Append it to the list of GJD files
|
||||
if (!filename.empty()) {
|
||||
_gjds.push_back(Common::Path(filename));
|
||||
}
|
||||
|
||||
// Read the next line
|
||||
line = indexfile.readLine();
|
||||
}
|
||||
|
||||
// Close the GJD index file
|
||||
indexfile.close();
|
||||
}
|
||||
|
||||
uint32 ResMan_v2::getRef(Common::String name) {
|
||||
// Open the RL file
|
||||
Common::File rlFile;
|
||||
if (!rlFile.open("dir.rl")) {
|
||||
error("Groovie::Resource: Couldn't open dir.rl");
|
||||
return (uint32)-1;
|
||||
}
|
||||
|
||||
// resources are always in lowercase
|
||||
name.toLowercase();
|
||||
uint32 resNum;
|
||||
bool found = false;
|
||||
for (resNum = 0; !found && !rlFile.err() && !rlFile.eos(); resNum++) {
|
||||
// Seek past metadata
|
||||
rlFile.seek(14, SEEK_CUR);
|
||||
|
||||
// Read the resource name
|
||||
char readname[18];
|
||||
rlFile.read(readname, 18);
|
||||
|
||||
// Test whether it's the resource we're searching
|
||||
Common::String resname(readname, 18);
|
||||
if (resname.hasPrefixIgnoreCase(name.c_str())) {
|
||||
debugC(2, kDebugResource, "Groovie::Resource: Resource %18s matches %s", readname, name.c_str());
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Close the RL file
|
||||
rlFile.close();
|
||||
|
||||
// Verify we really found the resource
|
||||
if (!found) {
|
||||
warning("Groovie::Resource: Couldn't find resource %s", name.c_str());
|
||||
return (uint32)-1;
|
||||
}
|
||||
|
||||
return resNum;
|
||||
}
|
||||
|
||||
bool ResMan_v2::getResInfo(uint32 fileRef, ResInfo &resInfo) {
|
||||
// Open the RL file
|
||||
Common::File rlFile;
|
||||
if (!rlFile.open("dir.rl")) {
|
||||
error("Groovie::Resource: Couldn't open dir.rl");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seek to the position of the desired resource
|
||||
rlFile.seek(fileRef * 32);
|
||||
if (rlFile.eos()) {
|
||||
rlFile.close();
|
||||
error("Groovie::Resource: Invalid resource number: 0x%04X", fileRef);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the resource information
|
||||
resInfo.disks = rlFile.readUint32LE(); // Seems to be a bitfield indicating on which disk(s) the file can be found
|
||||
resInfo.offset = rlFile.readUint32LE();
|
||||
resInfo.size = rlFile.readUint32LE();
|
||||
resInfo.gjd = rlFile.readUint16LE();
|
||||
|
||||
// Read the resource name
|
||||
char resname[19];
|
||||
resname[18] = 0;
|
||||
rlFile.read(resname, 18);
|
||||
debugC(2, kDebugResource, "Groovie::Resource: Resource name: %18s", resname);
|
||||
resInfo.filename = resname;
|
||||
|
||||
// 6 padding bytes? (it looks like they're always 0)
|
||||
|
||||
// Close the resource RL file
|
||||
rlFile.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
83
engines/groovie/resource.h
Normal file
83
engines/groovie/resource.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_RESOURCE_H
|
||||
#define GROOVIE_RESOURCE_H
|
||||
|
||||
namespace Common {
|
||||
class MacResManager;
|
||||
}
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
struct ResInfo {
|
||||
uint32 disks; // This seems to be a bitfield indicating on which disk(s) the file is located.
|
||||
uint16 gjd;
|
||||
uint32 offset;
|
||||
uint32 size;
|
||||
Common::String filename;
|
||||
};
|
||||
|
||||
class ResMan {
|
||||
public:
|
||||
virtual ~ResMan() {}
|
||||
|
||||
Common::SeekableReadStream *open(uint32 fileRef);
|
||||
Common::SeekableReadStream *open(const ResInfo &resInfo);
|
||||
|
||||
void dumpResource(const Common::String &fileName);
|
||||
void dumpResource(uint32 fileRef, const Common::Path &fileName);
|
||||
void dumpResource(Common::SeekableReadStream *inFile, const Common::Path &fileName, bool dispose = true);
|
||||
|
||||
Common::String getGjdName(const ResInfo &resInfo);
|
||||
virtual uint32 getRef(Common::String name) = 0;
|
||||
virtual bool getResInfo(uint32 fileRef, ResInfo &resInfo) = 0;
|
||||
|
||||
protected:
|
||||
Common::Array<Common::Path> _gjds;
|
||||
|
||||
uint16 _lastGjd;
|
||||
};
|
||||
|
||||
class ResMan_t7g : public ResMan {
|
||||
public:
|
||||
ResMan_t7g(Common::MacResManager *macResFork = 0);
|
||||
~ResMan_t7g() override {}
|
||||
|
||||
uint32 getRef(Common::String name) override;
|
||||
bool getResInfo(uint32 fileRef, ResInfo &resInfo) override;
|
||||
|
||||
private:
|
||||
Common::MacResManager *_macResFork;
|
||||
};
|
||||
|
||||
class ResMan_v2 : public ResMan {
|
||||
public:
|
||||
ResMan_v2();
|
||||
~ResMan_v2() override {}
|
||||
|
||||
uint32 getRef(Common::String name) override;
|
||||
bool getResInfo(uint32 fileRef, ResInfo &resInfo) override;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_RESOURCE_H
|
||||
189
engines/groovie/saveload.cpp
Normal file
189
engines/groovie/saveload.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/saveload.h"
|
||||
#include "groovie/groovie.h"
|
||||
|
||||
#include "common/system.h"
|
||||
#include "common/substream.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
#define SUPPORTED_SAVEFILE_VERSION 1
|
||||
// 0 - Just script variables, compatible with the original
|
||||
// 1 - Added one byte with version number at the beginning
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
int SaveLoad::getMaximumSlot() {
|
||||
return MAX_SAVES - 1;
|
||||
}
|
||||
|
||||
bool SaveLoad::isSlotValid(int slot) {
|
||||
return slot >= 0 && slot <= getMaximumSlot();
|
||||
}
|
||||
|
||||
Common::String SaveLoad::getSlotSaveName(const Common::String &target, int slot) {
|
||||
Common::String fileName = Common::String::format("%s.%03d", target.c_str(), slot);
|
||||
return fileName;
|
||||
}
|
||||
|
||||
SaveStateList SaveLoad::listValidSaves(const Common::String &target) {
|
||||
SaveStateList list;
|
||||
|
||||
// some Groovie 2 games use save 0 with a garbage name for internal tracking, other games use slot 0 for Open House mode
|
||||
const Common::U32String reservedName = _("Reserved"); // I18N: Savegame default name in Groovie engine
|
||||
bool hasReserved = false;
|
||||
|
||||
// Get the list of savefiles
|
||||
Common::String pattern = Common::String::format("%s.0##", target.c_str());
|
||||
Common::StringArray savefiles = g_system->getSavefileManager()->listSavefiles(pattern);
|
||||
|
||||
// Sort the list of filenames
|
||||
sort(savefiles.begin(), savefiles.end());
|
||||
|
||||
// Fill the information for the existing savegames
|
||||
for (auto &savefile : savefiles) {
|
||||
const char *ext = strrchr(savefile.c_str(), '.');
|
||||
if (!ext)
|
||||
continue;
|
||||
|
||||
int slot = atoi(ext + 1);
|
||||
|
||||
if (!isSlotValid(slot))
|
||||
continue;
|
||||
|
||||
SaveStateDescriptor descriptor;
|
||||
Common::InSaveFile *file = SaveLoad::openForLoading(target, slot, &descriptor);
|
||||
if (file) {
|
||||
// It's a valid savefile, save the descriptor
|
||||
delete file;
|
||||
if (slot == 0) {
|
||||
hasReserved = true;
|
||||
if (descriptor.getDescription() != "OPEN HOUSE" && descriptor.getDescription() != "Open House")
|
||||
descriptor.setDescription(reservedName);
|
||||
}
|
||||
list.push_back(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasReserved) {
|
||||
SaveStateDescriptor desc;
|
||||
desc.setDescription(reservedName);
|
||||
list.push_back(desc);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
Common::InSaveFile *SaveLoad::openForLoading(const Common::String &target, int slot, SaveStateDescriptor *descriptor) {
|
||||
// Validate the slot number
|
||||
if (!isSlotValid(slot)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Open the savefile
|
||||
Common::String savename = getSlotSaveName(target, slot);
|
||||
Common::InSaveFile *savefile = g_system->getSavefileManager()->openForLoading(savename);
|
||||
if (!savefile) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Read the savefile version
|
||||
uint8 version;
|
||||
if (savefile->size() == 1024) {
|
||||
version = 0;
|
||||
} else {
|
||||
version = savefile->readByte();
|
||||
}
|
||||
|
||||
// Verify we can read this version
|
||||
if (version > SUPPORTED_SAVEFILE_VERSION) {
|
||||
//TODO: show the error about unsupported savefile version
|
||||
}
|
||||
|
||||
// Save the current position as the start for the engine data
|
||||
int metaDataSize = savefile->pos();
|
||||
|
||||
// Fill the SaveStateDescriptor if it was provided
|
||||
if (descriptor) {
|
||||
// Initialize the SaveStateDescriptor
|
||||
descriptor->setSaveSlot(slot);
|
||||
|
||||
// TODO: Add extra information
|
||||
//setSaveDate(int year, int month, int day)
|
||||
//setSaveTime(int hour, int min)
|
||||
//setPlayTime(int hours, int minutes)
|
||||
|
||||
// Read the savegame description
|
||||
Common::String description;
|
||||
unsigned char c = 1;
|
||||
for (int i = 0; (c != 0) && (i < 15); i++) {
|
||||
c = savefile->readByte();
|
||||
switch (c) {
|
||||
case 0:
|
||||
break;
|
||||
case 16: // @
|
||||
// fall through intended
|
||||
case 254: // . (generated when pressing space)
|
||||
c = ' ';
|
||||
break;
|
||||
case 244: // $
|
||||
c = 0;
|
||||
break;
|
||||
default:
|
||||
c += 0x30;
|
||||
}
|
||||
if (c != 0) {
|
||||
description += c;
|
||||
}
|
||||
}
|
||||
descriptor->setDescription(description);
|
||||
}
|
||||
|
||||
// Return a substream, skipping the metadata
|
||||
Common::SeekableSubReadStream *sub = new Common::SeekableSubReadStream(savefile, metaDataSize, savefile->size(), DisposeAfterUse::YES);
|
||||
|
||||
// Move to the beginning of the substream
|
||||
sub->seek(0, SEEK_SET);
|
||||
|
||||
return sub;
|
||||
}
|
||||
|
||||
Common::OutSaveFile *SaveLoad::openForSaving(const Common::String &target, int slot) {
|
||||
// Validate the slot number
|
||||
if (!isSlotValid(slot)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Open the savefile
|
||||
Common::String savename = getSlotSaveName(target, slot);
|
||||
Common::OutSaveFile *savefile = g_system->getSavefileManager()->openForSaving(savename);
|
||||
if (!savefile) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Write the savefile version
|
||||
savefile->writeByte(SUPPORTED_SAVEFILE_VERSION);
|
||||
|
||||
return savefile;
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
48
engines/groovie/saveload.h
Normal file
48
engines/groovie/saveload.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_SAVELOAD_H
|
||||
#define GROOVIE_SAVELOAD_H
|
||||
|
||||
#include "common/savefile.h"
|
||||
#include "engines/game.h"
|
||||
#include "engines/savestate.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class SaveLoad {
|
||||
public:
|
||||
// Validating slot numbers
|
||||
static int getMaximumSlot();
|
||||
static bool isSlotValid(int slot);
|
||||
|
||||
// Getting information
|
||||
static Common::String getSlotSaveName(const Common::String &target, int slot);
|
||||
static SaveStateList listValidSaves(const Common::String &target);
|
||||
|
||||
// Opening savefiles
|
||||
static Common::InSaveFile *openForLoading(const Common::String &target, int slot, SaveStateDescriptor *descriptor = nullptr);
|
||||
static Common::OutSaveFile *openForSaving(const Common::String &target, int slot);
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_SAVELOAD_H
|
||||
2738
engines/groovie/script.cpp
Normal file
2738
engines/groovie/script.cpp
Normal file
File diff suppressed because it is too large
Load Diff
288
engines/groovie/script.h
Normal file
288
engines/groovie/script.h
Normal file
@@ -0,0 +1,288 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_SCRIPT_H
|
||||
#define GROOVIE_SCRIPT_H
|
||||
|
||||
#include "groovie/groovie.h"
|
||||
#ifdef ENABLE_GROOVIE2
|
||||
#include "groovie/logic/beehive.h"
|
||||
#include "groovie/logic/cake.h"
|
||||
#include "groovie/logic/gallery.h"
|
||||
#include "groovie/logic/mousetrap.h"
|
||||
#include "groovie/logic/othello.h"
|
||||
#include "groovie/logic/pente.h"
|
||||
#include "groovie/logic/triangle.h"
|
||||
#include "groovie/logic/winerack.h"
|
||||
#endif
|
||||
|
||||
#include "common/random.h"
|
||||
#include "common/rect.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Graphics {
|
||||
struct Surface;
|
||||
}
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class CellGame;
|
||||
class Debugger;
|
||||
class GroovieEngine;
|
||||
class TlcGame;
|
||||
|
||||
class Script {
|
||||
friend class Debugger;
|
||||
|
||||
public:
|
||||
Script(GroovieEngine *vm, EngineVersion version);
|
||||
~Script();
|
||||
|
||||
void setDebugger(Debugger *debugger);
|
||||
void setVariable(uint16 varnum, byte value);
|
||||
|
||||
void timerTick();
|
||||
|
||||
bool loadScript(Common::String scriptfile);
|
||||
void directGameLoad(int slot);
|
||||
void directGameSave(int slot, const Common::String &desc);
|
||||
bool canDirectSave() const;
|
||||
void step();
|
||||
|
||||
void setMouseClick(uint8 button);
|
||||
void setKbdChar(uint8 c);
|
||||
void setAction(uint8 a);
|
||||
|
||||
void setBitFlag(int bitnum, bool value);
|
||||
bool getBitFlag(int bitnum);
|
||||
|
||||
Common::String &getContext();
|
||||
|
||||
private:
|
||||
GroovieEngine *_vm;
|
||||
|
||||
Common::RandomSource _random;
|
||||
|
||||
bool _firstbit;
|
||||
uint8 _lastCursor;
|
||||
|
||||
EngineVersion _version;
|
||||
|
||||
// Script filename (for debugging purposes)
|
||||
Common::String _scriptFile;
|
||||
Common::String _savedScriptFile;
|
||||
|
||||
// Save names
|
||||
Common::String _saveNames[MAX_SAVES];
|
||||
bool _wantAutosave;
|
||||
|
||||
// Code
|
||||
byte *_code;
|
||||
uint16 _codeSize;
|
||||
uint16 _currentInstruction;
|
||||
byte *_savedCode;
|
||||
uint16 _savedCodeSize;
|
||||
uint16 _savedInstruction;
|
||||
|
||||
// Variables
|
||||
byte _variables[0x400];
|
||||
byte _savedVariables[0x180];
|
||||
|
||||
// Stack
|
||||
uint16 _stack[0x20];
|
||||
uint8 _stacktop;
|
||||
uint8 _savedStacktop;
|
||||
|
||||
// Input
|
||||
bool _mouseClicked;
|
||||
uint8 _eventMouseClicked;
|
||||
uint8 _kbdChar;
|
||||
uint8 _eventKbdChar;
|
||||
uint8 _eventAction;
|
||||
uint16 _inputLoopAddress;
|
||||
uint8 _newCursorStyle;
|
||||
uint16 _hotspotTopAction;
|
||||
uint16 _hotspotTopCursor;
|
||||
uint16 _hotspotBottomAction;
|
||||
uint16 _hotspotBottomCursor;
|
||||
uint16 _hotspotRightAction;
|
||||
uint16 _hotspotLeftAction;
|
||||
uint16 _hotspotSlot;
|
||||
bool _fastForwarding;
|
||||
void resetFastForward();
|
||||
|
||||
// Video
|
||||
Common::SeekableReadStream *_videoFile;
|
||||
uint32 _videoRef;
|
||||
uint16 _bitflags;
|
||||
uint16 _videoSkipAddress;
|
||||
|
||||
// Debugging
|
||||
Debugger *_debugger;
|
||||
Common::String _debugString;
|
||||
uint16 _oldInstruction;
|
||||
|
||||
// Special classes depending on played game
|
||||
CellGame *_cellGame;
|
||||
TlcGame *_tlcGame;
|
||||
|
||||
// Helper functions
|
||||
uint8 getCodeByte(uint16 address);
|
||||
uint8 readScript8bits();
|
||||
uint16 readScript16bits();
|
||||
uint32 readScript32bits();
|
||||
uint16 readScript8or16bits();
|
||||
uint8 readScriptChar(bool allow7C, bool limitVal, bool limitVar);
|
||||
void readScriptString(Common::String &str);
|
||||
uint8 readScriptVar();
|
||||
uint32 getVideoRefString(Common::String &resName);
|
||||
|
||||
void executeInputAction(uint16 address);
|
||||
bool hotspot(Common::Rect rect, uint16 addr, uint8 cursor);
|
||||
|
||||
void loadgame(uint slot);
|
||||
bool preview_loadgame(uint slot);
|
||||
void savegame(uint slot, const Common::String &name);
|
||||
bool playvideofromref(uint32 fileref, bool loopUntilAudioDone = false);
|
||||
bool playBackgroundSound(uint32 fileref, uint32 loops);
|
||||
void printString(Graphics::Surface *surface, const char *str);
|
||||
|
||||
// Opcodes
|
||||
typedef void (Script::*OpcodeFunc)();
|
||||
OpcodeFunc *_opcodes;
|
||||
static OpcodeFunc _opcodesT7G[];
|
||||
static OpcodeFunc _opcodesV2[];
|
||||
|
||||
void o_invalid();
|
||||
|
||||
void o_nop();
|
||||
void o_nop8();
|
||||
void o_nop16();
|
||||
void o_nop32();
|
||||
void o_nop8or16();
|
||||
|
||||
void o_playsong();
|
||||
void o_bf9on();
|
||||
void o_palfadeout();
|
||||
void o_bf8on();
|
||||
void o_bf6on();
|
||||
void o_bf7on();
|
||||
void o_setbackgroundsong();
|
||||
void o_videofromref();
|
||||
void o_bf5on();
|
||||
void o_inputloopstart();
|
||||
void o_keyboardaction();
|
||||
void o_hotspot_rect();
|
||||
void o_hotspot_left();
|
||||
void o_hotspot_right();
|
||||
void o_hotspot_center();
|
||||
void o_hotspot_current();
|
||||
void o_inputloopend();
|
||||
void o_random();
|
||||
void o_jmp();
|
||||
void o_loadstring();
|
||||
void o_ret();
|
||||
void o_call();
|
||||
void o_sleep();
|
||||
void o_strcmpnejmp_var();
|
||||
void o_copybgtofg();
|
||||
void o_strcmpnejmp();
|
||||
void o_xor_obfuscate();
|
||||
void o_vdxtransition();
|
||||
void o_swap();
|
||||
void o_inc();
|
||||
void o_dec();
|
||||
void o_strcmpeqjmp();
|
||||
void o_mov();
|
||||
void o_add();
|
||||
void o_videofromstring1();
|
||||
void o_videofromstring2();
|
||||
void o_stopmidi();
|
||||
void o_endscript();
|
||||
void o_sethotspottop();
|
||||
void o_sethotspotbottom();
|
||||
void o_loadgame();
|
||||
void o_savegame();
|
||||
void o_hotspotbottom_4();
|
||||
void o_midivolume();
|
||||
void o_jne();
|
||||
void o_loadstringvar();
|
||||
void o_chargreatjmp();
|
||||
void o_bf7off();
|
||||
void o_charlessjmp();
|
||||
void o_copyrecttobg();
|
||||
void o_restorestkpnt();
|
||||
void o_obscureswap();
|
||||
void o_printstring();
|
||||
void o_hotspot_slot();
|
||||
void o_checkvalidsaves();
|
||||
void o_resetvars();
|
||||
void o_mod();
|
||||
void o_loadscript();
|
||||
void o_setvideoorigin();
|
||||
void o_sub();
|
||||
void o_returnscript();
|
||||
void o_sethotspotright();
|
||||
void o_sethotspotleft();
|
||||
void o_getcd();
|
||||
void o_playcd();
|
||||
void o_musicdelay();
|
||||
void o_hotspot_outrect();
|
||||
void o_stub56();
|
||||
void o_wipemaskfromstring58();
|
||||
void o_stub59();
|
||||
|
||||
void o2_bf0on();
|
||||
void o2_copybgtofg();
|
||||
void o2_printstring();
|
||||
void o2_playsong();
|
||||
void o2_midicontrol();
|
||||
void o2_setbackgroundsong();
|
||||
void o2_videofromref();
|
||||
void o2_vdxtransition();
|
||||
void o2_setvideoskip();
|
||||
void o2_savescreen();
|
||||
void o2_restorescreen();
|
||||
void o_gamelogic();
|
||||
void o2_copyfgtobg();
|
||||
void o2_setscriptend();
|
||||
void o2_playsound();
|
||||
void o2_check_sounds_overlays();
|
||||
void o2_preview_loadgame();
|
||||
|
||||
#ifdef ENABLE_GROOVIE2
|
||||
BeehiveGame _beehive;
|
||||
CakeGame _cake;
|
||||
GalleryGame _gallery;
|
||||
MouseTrapGame _mouseTrap;
|
||||
OthelloGame _othello;
|
||||
PenteGame _pente;
|
||||
TriangleGame _triangle;
|
||||
WineRackGame _wineRack;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_SCRIPT_H
|
||||
160
engines/groovie/video/player.cpp
Normal file
160
engines/groovie/video/player.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
#include "groovie/video/player.h"
|
||||
#include "groovie/groovie.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "common/debug-channels.h"
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
VideoPlayer::VideoPlayer(GroovieEngine *vm) :
|
||||
_vm(vm), _syst(vm->_system), _file(nullptr), _audioStream(nullptr), _fps(0), _overrideSpeed(false), _flags(0),
|
||||
_begunPlaying(false), _millisBetweenFrames(0), _lastFrameTime(0), _frameTimeDrift(0) {
|
||||
|
||||
_startTime = _syst->getMillis();
|
||||
|
||||
int16 h = g_system->getOverlayHeight();
|
||||
|
||||
_subtitles.setBBox(Common::Rect(20, h - 120, g_system->getOverlayWidth() - 20, h - 20));
|
||||
_subtitles.setColor(0xff, 0xff, 0xff);
|
||||
_subtitles.setFont("LiberationSans-Regular.ttf");
|
||||
}
|
||||
|
||||
bool VideoPlayer::load(Common::SeekableReadStream *file, uint16 flags) {
|
||||
_file = file;
|
||||
_flags = flags;
|
||||
_overrideSpeed = false;
|
||||
_startTime = _syst->getMillis();
|
||||
|
||||
stopAudioStream();
|
||||
_fps = loadInternal();
|
||||
|
||||
if (_fps != 0) {
|
||||
setOverrideSpeed(_overrideSpeed);
|
||||
_begunPlaying = false;
|
||||
return true;
|
||||
} else {
|
||||
_file = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayer::setOverrideSpeed(bool isOverride) {
|
||||
_overrideSpeed = isOverride;
|
||||
if (_fps != 0) {
|
||||
if (isOverride)
|
||||
_millisBetweenFrames = 1000.0f / 26.0f;
|
||||
else
|
||||
_millisBetweenFrames = 1000.0f / float(_fps);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayer::fastForward() {
|
||||
_millisBetweenFrames = 0;
|
||||
_frameTimeDrift = 0;
|
||||
stopAudioStream();
|
||||
}
|
||||
|
||||
bool VideoPlayer::isFastForwarding() {
|
||||
return DebugMan.isDebugChannelEnabled(kDebugFast) || _millisBetweenFrames <= 0;
|
||||
}
|
||||
|
||||
bool VideoPlayer::playFrame() {
|
||||
bool end = true;
|
||||
|
||||
// Process the next frame while the file is open
|
||||
if (_file) {
|
||||
end = playFrameInternal();
|
||||
|
||||
_subtitles.drawSubtitle(_lastFrameTime - _startTime);
|
||||
}
|
||||
|
||||
// The file has been completely processed
|
||||
if (end) {
|
||||
_file = nullptr;
|
||||
|
||||
// Wait for pending audio
|
||||
if (_audioStream) {
|
||||
if (_audioStream->endOfData() || isFastForwarding()) {
|
||||
// Mark the audio stream as finished (no more data will be appended)
|
||||
_audioStream->finish();
|
||||
_audioStream = nullptr;
|
||||
} else {
|
||||
// Don't end if there's still audio playing
|
||||
end = false;
|
||||
}
|
||||
}
|
||||
|
||||
unloadSubtitles();
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
void VideoPlayer::unloadSubtitles() {
|
||||
if (_subtitles.isLoaded()) {
|
||||
_subtitles.close();
|
||||
g_system->hideOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayer::waitFrame() {
|
||||
if (isFastForwarding()) {
|
||||
return;
|
||||
}
|
||||
uint32 currTime = _syst->getMillis();
|
||||
if (!_begunPlaying) {
|
||||
_begunPlaying = true;
|
||||
_lastFrameTime = currTime;
|
||||
_frameTimeDrift = 0.0f;
|
||||
|
||||
if (_subtitles.isLoaded()) {
|
||||
g_system->showOverlay(false);
|
||||
g_system->clearOverlay();
|
||||
}
|
||||
} else {
|
||||
uint32 millisDiff = currTime - _lastFrameTime;
|
||||
float fMillis = _millisBetweenFrames + _frameTimeDrift;
|
||||
// use floorf instead of roundf, because delayMillis often slightly over-sleeps
|
||||
uint32 millisSleep = MAX(0.0f, floorf(fMillis) - float(millisDiff));
|
||||
|
||||
if (millisSleep > 0) {
|
||||
debugC(7, kDebugVideo, "Groovie::Player: Delaying %d (currTime=%d, _lastFrameTime=%d, millisDiff=%d, _millisBetweenFrame=%.2f, _frameTimeDrift=%.2f)",
|
||||
millisSleep, currTime, _lastFrameTime, millisDiff, _millisBetweenFrames, _frameTimeDrift);
|
||||
_syst->delayMillis(millisSleep);
|
||||
currTime = _syst->getMillis();
|
||||
debugC(7, kDebugVideo, "Groovie::Player: Finished delay at %d", currTime);
|
||||
}
|
||||
|
||||
_frameTimeDrift = fMillis - float(currTime - _lastFrameTime);
|
||||
if (abs(_frameTimeDrift) >= _millisBetweenFrames) {
|
||||
_frameTimeDrift = 0;
|
||||
}
|
||||
debugC(6, kDebugVideo, "Groovie::Player: Frame displayed at %d (%f FPS), _frameTimeDrift=%.2f", currTime, 1000.0 / (currTime - _lastFrameTime), _frameTimeDrift);
|
||||
_lastFrameTime = currTime;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
89
engines/groovie/video/player.h
Normal file
89
engines/groovie/video/player.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_VIDEO_PLAYER_H
|
||||
#define GROOVIE_VIDEO_PLAYER_H
|
||||
|
||||
#include "common/system.h"
|
||||
#include "video/subtitles.h"
|
||||
|
||||
namespace Audio {
|
||||
class QueuingAudioStream;
|
||||
}
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class GroovieEngine;
|
||||
|
||||
class VideoPlayer {
|
||||
public:
|
||||
VideoPlayer(GroovieEngine *vm);
|
||||
virtual ~VideoPlayer() {}
|
||||
|
||||
bool load(Common::SeekableReadStream *file, uint16 flags);
|
||||
bool playFrame();
|
||||
virtual void resetFlags() {}
|
||||
virtual void setOrigin(int16 x, int16 y) {}
|
||||
virtual void stopAudioStream() = 0;
|
||||
void fastForward();
|
||||
bool isFastForwarding();
|
||||
virtual void drawString(Graphics::Surface *surface, const Common::String &text, int posx, int posy, uint32 color, bool blackBackground) {}
|
||||
virtual void copyfgtobg(uint8 arg) {}
|
||||
void setOverrideSpeed(bool isOverride);
|
||||
|
||||
void loadSubtitles(const char *fname) { _subtitles.loadSRTFile(fname); }
|
||||
void unloadSubtitles();
|
||||
|
||||
virtual bool isFileHandled() { return false; }
|
||||
|
||||
protected:
|
||||
// To be implemented by subclasses
|
||||
virtual uint16 loadInternal() = 0;
|
||||
virtual bool playFrameInternal() = 0;
|
||||
|
||||
bool getOverrideSpeed() const { return _overrideSpeed; }
|
||||
|
||||
GroovieEngine *_vm;
|
||||
OSystem *_syst;
|
||||
Common::SeekableReadStream *_file;
|
||||
uint16 _flags;
|
||||
Audio::QueuingAudioStream *_audioStream;
|
||||
|
||||
|
||||
private:
|
||||
// Synchronization stuff
|
||||
bool _begunPlaying;
|
||||
bool _overrideSpeed;
|
||||
uint16 _fps;
|
||||
float _millisBetweenFrames;
|
||||
uint32 _lastFrameTime;
|
||||
float _frameTimeDrift;
|
||||
uint32 _startTime;
|
||||
|
||||
Video::Subtitles _subtitles;
|
||||
|
||||
protected:
|
||||
virtual void waitFrame();
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_VIDEO_PLAYER_H
|
||||
1137
engines/groovie/video/roq.cpp
Normal file
1137
engines/groovie/video/roq.cpp
Normal file
File diff suppressed because it is too large
Load Diff
138
engines/groovie/video/roq.h
Normal file
138
engines/groovie/video/roq.h
Normal file
@@ -0,0 +1,138 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_VIDEO_ROQ_H
|
||||
#define GROOVIE_VIDEO_ROQ_H
|
||||
|
||||
#include "groovie/video/player.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
namespace Video {
|
||||
class VideoDecoder;
|
||||
}
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class GroovieEngine;
|
||||
|
||||
struct ROQBlockHeader {
|
||||
uint16 type;
|
||||
uint32 size;
|
||||
uint16 param;
|
||||
};
|
||||
|
||||
class ROQPlayer : public VideoPlayer {
|
||||
public:
|
||||
ROQPlayer(GroovieEngine *vm);
|
||||
~ROQPlayer();
|
||||
void setOrigin(int16 x, int16 y) override;
|
||||
|
||||
Audio::SoundHandle getSoundHandle() {
|
||||
return _soundHandle;
|
||||
}
|
||||
|
||||
void drawString(Graphics::Surface *surface, const Common::String &text, int posx, int posy, uint32 color, bool blackBackground) override;
|
||||
void copyfgtobg(uint8 arg) override;
|
||||
|
||||
bool isFileHandled() override { return _isFileHandled; }
|
||||
|
||||
protected:
|
||||
void waitFrame() override;
|
||||
uint16 loadInternal() override;
|
||||
bool playFrameInternal() override;
|
||||
void stopAudioStream() override;
|
||||
virtual void createAudioStream(bool stereo);
|
||||
|
||||
Audio::SoundHandle _soundHandle;
|
||||
Graphics::Surface *_bg, *_screen, *_overBuf;
|
||||
Graphics::Surface *_currBuf, *_prevBuf;
|
||||
|
||||
private:
|
||||
bool readBlockHeader(ROQBlockHeader &blockHeader);
|
||||
|
||||
bool isValidBlockHeaderType(uint16 blockHeaderType);
|
||||
bool processBlock();
|
||||
bool processBlockInfo(ROQBlockHeader &blockHeader);
|
||||
bool processBlockQuadCodebook(ROQBlockHeader &blockHeader);
|
||||
bool processBlockQuadVector(ROQBlockHeader &blockHeader);
|
||||
void processBlockQuadVectorBlock(int baseX, int baseY);
|
||||
void processBlockQuadVectorBlockSub(int baseX, int baseY);
|
||||
bool processBlockStill(ROQBlockHeader &blockHeader);
|
||||
bool processBlockSoundMono(ROQBlockHeader &blockHeader);
|
||||
bool processBlockSoundStereo(ROQBlockHeader &blockHeader);
|
||||
bool processBlockAudioContainer(ROQBlockHeader &blockHeader);
|
||||
bool playFirstFrame() { return _flagNoPlay; }; // _alpha && !_flagOverlay; }
|
||||
void clearOverlay();
|
||||
void dumpAllSurfaces(const Common::String &funcname);
|
||||
|
||||
void paint2(byte i, int destx, int desty);
|
||||
void paint4(byte i, int destx, int desty);
|
||||
void paint8(byte i, int destx, int desty);
|
||||
void copy(byte size, int destx, int desty, int dx, int dy);
|
||||
|
||||
// Origin
|
||||
int16 _origX, _origY;
|
||||
//int16 _screenOffset;
|
||||
void calcStartStop(int &start, int &stop, int origin, int length);
|
||||
|
||||
// Block coding type
|
||||
byte getCodingType();
|
||||
uint16 _codingType;
|
||||
byte _codingTypeCount;
|
||||
|
||||
// Codebooks
|
||||
uint16 _num2blocks;
|
||||
uint16 _num4blocks;
|
||||
uint32 _codebook2[256 * 4];
|
||||
byte _codebook4[256 * 4];
|
||||
|
||||
// Flags
|
||||
bool _flagNoPlay; //!< Play only first frame and do not print the image to the screen
|
||||
bool _flagOverlay; //!< If _flagNoPlay is set. Copy frame to the foreground otherwise to the background
|
||||
bool _altMotionDecoder; // Some ROQ vids use a variation on the copy codeblock
|
||||
bool _flagMasked; //!< Clear the video instead of play it, used in pente
|
||||
|
||||
// Buffers
|
||||
void redrawRestoreArea(int screenOffset, bool force);
|
||||
void buildShowBuf();
|
||||
byte _scaleX, _scaleY;
|
||||
byte _offScale;
|
||||
int8 _motionOffX, _motionOffY;
|
||||
bool _interlacedVideo;
|
||||
bool _dirty;
|
||||
byte _alpha;
|
||||
bool _firstFrame;
|
||||
Common::Rect *_restoreArea; // Area to be repainted by foreground
|
||||
|
||||
Video::VideoDecoder *_videoDecoder;
|
||||
bool _isFileHandled;
|
||||
};
|
||||
|
||||
class ROQSoundPlayer : public ROQPlayer {
|
||||
public:
|
||||
ROQSoundPlayer(GroovieEngine *vm);
|
||||
~ROQSoundPlayer();
|
||||
void createAudioStream(bool stereo) override;
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_VIDEO_ROQ_H
|
||||
584
engines/groovie/video/vdx.cpp
Normal file
584
engines/groovie/video/vdx.cpp
Normal file
@@ -0,0 +1,584 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "groovie/video/vdx.h"
|
||||
#include "groovie/graphics.h"
|
||||
#include "groovie/groovie.h"
|
||||
#include "groovie/lzss.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/debug-channels.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "graphics/paletteman.h"
|
||||
|
||||
#define TILE_SIZE 4 // Size of each tile on the image: only ever seen 4 so far
|
||||
#define VDX_IDENT 0x9267 // 37479
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
VDXPlayer::VDXPlayer(GroovieEngine *vm) :
|
||||
VideoPlayer(vm), _origX(0), _origY(0), _flagOnePrev(false),
|
||||
_fg(&_vm->_graphicsMan->_foreground), _bg(&_vm->_graphicsMan->_background) {
|
||||
}
|
||||
|
||||
VDXPlayer::~VDXPlayer() {
|
||||
//delete _audioStream;
|
||||
}
|
||||
|
||||
void VDXPlayer::resetFlags() {
|
||||
_flagOnePrev = false;
|
||||
}
|
||||
|
||||
void VDXPlayer::setOrigin(int16 x, int16 y) {
|
||||
_origX = x;
|
||||
_origY = y;
|
||||
}
|
||||
|
||||
void VDXPlayer::stopAudioStream() {
|
||||
if (_audioStream) {
|
||||
g_system->getMixer()->stopHandle(_soundHandle);
|
||||
}
|
||||
_audioStream = nullptr;
|
||||
}
|
||||
|
||||
uint16 VDXPlayer::loadInternal() {
|
||||
if (DebugMan.isDebugChannelEnabled(kDebugVideo)) {
|
||||
int8 i;
|
||||
debugN(1, "Groovie::VDX: New VDX: bitflags are ");
|
||||
for (i = 15; i >= 0; i--) {
|
||||
debugN(1, "%d", _flags & (1 << i)? 1 : 0);
|
||||
if (i % 4 == 0) {
|
||||
debugN(1, " ");
|
||||
}
|
||||
}
|
||||
debug(1, " <- 0 ");
|
||||
}
|
||||
// Flags:
|
||||
// - 1 Puzzle piece? Skip palette, don't redraw full screen, draw still to b/ack buffer
|
||||
// - 2 Transparent color is 0xFF
|
||||
// - 5 Skip still chunks
|
||||
// - 7
|
||||
// - 8 Just show the first frame
|
||||
// - 9 Start a palette fade in
|
||||
_flagZero = ((_flags & (1 << 0)) != 0);
|
||||
_flagOne = ((_flags & (1 << 1)) != 0);
|
||||
_flag2Byte = (_flags & (1 << 2)) ? 0xFF : 0x00;
|
||||
_flagThree = ((_flags & (1 << 3)) != 0);
|
||||
_flagFour = ((_flags & (1 << 4)) != 0);
|
||||
_flagFive = ((_flags & (1 << 5)) != 0);
|
||||
_flagSix = ((_flags & (1 << 6)) != 0);
|
||||
_flagSeven = ((_flags & (1 << 7)) != 0);
|
||||
_flagEight = ((_flags & (1 << 8)) != 0);
|
||||
_flagNine = ((_flags & (1 << 9)) != 0);
|
||||
|
||||
// Enable highspeed if we're not obeying fps, and not marked as special
|
||||
// This will be disabled in chunk audio if we're actually an audio vdx
|
||||
if (_vm->_modeSpeed == kGroovieSpeedFast && ((_flags & (1 << 15)) == 0))
|
||||
setOverrideSpeed(true);
|
||||
|
||||
if (_flagOnePrev && !_flagOne && !_flagEight) {
|
||||
_flagSeven = true;
|
||||
}
|
||||
|
||||
// Save _flagOne for the next video
|
||||
_flagOnePrev = _flagOne;
|
||||
|
||||
//_flagTransparent = _flagOne;
|
||||
_flagFirstFrame = _flagEight;
|
||||
//_flagSkipPalette = _flagSeven;
|
||||
_flagSkipPalette = false;
|
||||
//_flagSkipStill = _flagFive || _flagSeven;
|
||||
//_flagUpdateStill = _flagNine || _flagSix;
|
||||
|
||||
// Begin reading the file
|
||||
debugC(1, kDebugVideo, "Groovie::VDX: Playing video");
|
||||
|
||||
if (_file->readUint16LE() != VDX_IDENT) {
|
||||
error("Groovie::VDX: This does not appear to be a 7th guest VDX file");
|
||||
return 0;
|
||||
} else {
|
||||
debugC(5, kDebugVideo, "Groovie::VDX: VDX file identified correctly");
|
||||
}
|
||||
|
||||
uint16 tmp;
|
||||
|
||||
// Skip unknown data: 6 bytes, ref Martine
|
||||
tmp = _file->readUint16LE();
|
||||
debugC(2, kDebugVideo, "Groovie::VDX: Martine1 = 0x%04X", tmp);
|
||||
tmp = _file->readUint16LE();
|
||||
debugC(2, kDebugVideo, "Groovie::VDX: Martine2 = 0x%04X", tmp);
|
||||
tmp = _file->readUint16LE();
|
||||
debugC(2, kDebugVideo, "Groovie::VDX: Martine3 (FPS?) = %d", tmp);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool VDXPlayer::playFrameInternal() {
|
||||
byte currRes = 0x80;
|
||||
Common::ReadStream *vdxData = nullptr;
|
||||
while (currRes == 0x80) {
|
||||
currRes = _file->readByte();
|
||||
|
||||
// Skip unknown data: 1 byte, ref Edward
|
||||
byte tmp = _file->readByte();
|
||||
|
||||
uint32 compSize = _file->readUint32LE();
|
||||
uint8 lengthmask = _file->readByte();
|
||||
uint8 lengthbits = _file->readByte();
|
||||
|
||||
if (_file->eos())
|
||||
break;
|
||||
|
||||
debugC(5, kDebugVideo, "Groovie::VDX: Edward = 0x%04X", tmp);
|
||||
|
||||
// Read the chunk data and decompress if needed
|
||||
if (compSize)
|
||||
vdxData = _file->readStream(compSize);
|
||||
|
||||
if (lengthmask && lengthbits) {
|
||||
Common::ReadStream *decompData = new LzssReadStream(vdxData, lengthmask, lengthbits);
|
||||
delete vdxData;
|
||||
vdxData = decompData;
|
||||
}
|
||||
|
||||
// Use the current chunk
|
||||
switch (currRes) {
|
||||
case 0x00:
|
||||
debugC(6, kDebugVideo, "Groovie::VDX: Replay frame");
|
||||
break;
|
||||
case 0x20:
|
||||
debugC(5, kDebugVideo, "Groovie::VDX: Still frame");
|
||||
getStill(vdxData);
|
||||
break;
|
||||
case 0x25:
|
||||
debugC(5, kDebugVideo, "Groovie::VDX: Animation frame");
|
||||
getDelta(vdxData);
|
||||
break;
|
||||
case 0x80:
|
||||
debugC(5, kDebugVideo, "Groovie::VDX: Sound resource");
|
||||
chunkSound(vdxData);
|
||||
break;
|
||||
default:
|
||||
error("Groovie::VDX: Invalid resource type: %d", currRes);
|
||||
}
|
||||
delete vdxData;
|
||||
vdxData = nullptr;
|
||||
}
|
||||
|
||||
// Wait until the current frame can be shown
|
||||
waitFrame();
|
||||
|
||||
// TODO: Move it to a better place
|
||||
// Update the screen
|
||||
if (currRes == 0x25) {
|
||||
//if (_flagSeven) {
|
||||
//_vm->_graphicsMan->mergeFgAndBg();
|
||||
//}
|
||||
_vm->_graphicsMan->updateScreen(_bg);
|
||||
}
|
||||
|
||||
// Report the end of the video if we reached the end of the file or if we
|
||||
// just wanted to play one frame.
|
||||
if (_file->eos() || _flagFirstFrame) {
|
||||
_origX = _origY = 0;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static const uint16 vdxBlockMapLookup[] = {
|
||||
0xc800, 0xec80, 0xfec8, 0xffec, 0xfffe, 0x3100, 0x7310, 0xf731,
|
||||
0xff73, 0xfff7, 0x6c80, 0x36c8, 0x136c, 0x6310, 0xc631, 0x8c63,
|
||||
0xf000, 0xff00, 0xfff0, 0x1111, 0x3333, 0x7777, 0x6666, 0xcccc,
|
||||
0x0ff0, 0x00ff, 0xffcc, 0x0076, 0xff33, 0x0ee6, 0xccff, 0x6770,
|
||||
0x33ff, 0x6ee0, 0x4800, 0x2480, 0x1248, 0x0024, 0x0012, 0x2100,
|
||||
0x4210, 0x8421, 0x0042, 0x0084, 0xf888, 0x0044, 0x0032, 0x111f,
|
||||
0x22e0, 0x4c00, 0x888f, 0x4470, 0x2300, 0xf111, 0x0e22, 0x00c4,
|
||||
0xf33f, 0xfccf, 0xff99, 0x99ff, 0x4444, 0x2222, 0xccee, 0x7733,
|
||||
0x00f8, 0x00f1, 0x00bb, 0x0cdd, 0x0f0f, 0x0f88, 0x13f1, 0x19b3,
|
||||
0x1f80, 0x226f, 0x27ec, 0x3077, 0x3267, 0x37e4, 0x38e3, 0x3f90,
|
||||
0x44cf, 0x4cd9, 0x4c99, 0x5555, 0x603f, 0x6077, 0x6237, 0x64c9,
|
||||
0x64cd, 0x6cd9, 0x70ef, 0x0f00, 0x00f0, 0x0000, 0x4444, 0x2222
|
||||
};
|
||||
|
||||
void VDXPlayer::getDelta(Common::ReadStream *in) {
|
||||
uint16 k, l;
|
||||
|
||||
// Get the size of the local palette
|
||||
uint16 palSize = in->readUint16LE();
|
||||
|
||||
// Load the palette if it isn't empty
|
||||
if (palSize) {
|
||||
uint16 palBitField[16];
|
||||
|
||||
// Load the bit field
|
||||
for (l = 0; l < 16; l++) {
|
||||
palBitField[l] = in->readUint16LE();
|
||||
}
|
||||
|
||||
// Load the actual palette
|
||||
for (l = 0; l < 16; l++) {
|
||||
int flag = 1 << 15;
|
||||
for (uint16 j = 0; j < 16; j++) {
|
||||
int palIndex = (l * 16) + j;
|
||||
|
||||
if (flag & palBitField[l]) {
|
||||
for (k = 0; k < 3; k++) {
|
||||
_palBuf[(palIndex * 3) + k] = in->readByte();
|
||||
}
|
||||
}
|
||||
flag = flag >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the palette
|
||||
if (!_flagSeven) {
|
||||
//if (!_flagSix && !_flagSeven) {
|
||||
setPalette(_palBuf);
|
||||
}
|
||||
}
|
||||
|
||||
uint8 currOpCode = in->readByte();
|
||||
uint8 param1, param2, param3;
|
||||
|
||||
uint16 currentLine = 0;
|
||||
uint32 offset = 0;
|
||||
while (!in->eos()) {
|
||||
byte colors[16];
|
||||
if (currOpCode < 0x60) {
|
||||
param1 = in->readByte();
|
||||
param2 = in->readByte();
|
||||
expandColorMap(colors, vdxBlockMapLookup[currOpCode], param1, param2);
|
||||
decodeBlockDelta(offset, colors, 640);
|
||||
offset += TILE_SIZE;
|
||||
} else if (currOpCode > 0x7f) {
|
||||
param1 = in->readByte();
|
||||
param2 = in->readByte();
|
||||
param3 = in->readByte();
|
||||
expandColorMap(colors, (param1 << 8) + currOpCode, param2, param3);
|
||||
decodeBlockDelta(offset, colors, 640);
|
||||
offset += TILE_SIZE;
|
||||
} else switch (currOpCode) {
|
||||
case 0x60: /* Fill tile with the 16 colors given as parameters */
|
||||
for (l = 0; l < 16; l++) {
|
||||
colors[l] = in->readByte();
|
||||
}
|
||||
decodeBlockDelta(offset, colors, 640);
|
||||
offset += TILE_SIZE;
|
||||
break;
|
||||
case 0x61: /* Skip to the end of this line, next block is start of next */
|
||||
/* Note this is used at the end of EVERY line */
|
||||
currentLine++;
|
||||
offset = currentLine * TILE_SIZE * 640;
|
||||
break;
|
||||
case 0x62:
|
||||
case 0x63:
|
||||
case 0x64:
|
||||
case 0x65:
|
||||
case 0x66:
|
||||
case 0x67:
|
||||
case 0x68:
|
||||
case 0x69:
|
||||
case 0x6a:
|
||||
case 0x6b: /* Skip next param1 blocks (within line) */
|
||||
offset += (currOpCode - 0x62) * TILE_SIZE;
|
||||
break;
|
||||
case 0x6c:
|
||||
case 0x6d:
|
||||
case 0x6e:
|
||||
case 0x6f:
|
||||
case 0x70:
|
||||
case 0x71:
|
||||
case 0x72:
|
||||
case 0x73:
|
||||
case 0x74:
|
||||
case 0x75: /* Next param1 blocks are filled with color param2 */
|
||||
param1 = currOpCode - 0x6b;
|
||||
param2 = in->readByte();
|
||||
for (l = 0; l < 16; l++) {
|
||||
colors[l] = param2;
|
||||
}
|
||||
for (k = 0; k < param1; k++) {
|
||||
decodeBlockDelta(offset, colors, 640);
|
||||
offset += TILE_SIZE;
|
||||
}
|
||||
break;
|
||||
case 0x76:
|
||||
case 0x77:
|
||||
case 0x78:
|
||||
case 0x79:
|
||||
case 0x7a:
|
||||
case 0x7b:
|
||||
case 0x7c:
|
||||
case 0x7d:
|
||||
case 0x7e:
|
||||
case 0x7f: /* Next bytes contain colors to fill the next param1 blocks in the current line*/
|
||||
param1 = currOpCode - 0x75;
|
||||
for (k = 0; k < param1; k++) {
|
||||
param2 = in->readByte();
|
||||
for (l = 0; l < 16; l++) {
|
||||
colors[l] = param2;
|
||||
}
|
||||
decodeBlockDelta(offset, colors, 640);
|
||||
offset += TILE_SIZE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
error("Groovie::VDX: Broken somehow");
|
||||
}
|
||||
currOpCode = in->readByte();
|
||||
}
|
||||
}
|
||||
|
||||
void VDXPlayer::getStill(Common::ReadStream *in) {
|
||||
uint16 numXTiles = in->readUint16LE();
|
||||
debugC(5, kDebugVideo, "Groovie::VDX: numXTiles=%d", numXTiles);
|
||||
uint16 numYTiles = in->readUint16LE();
|
||||
debugC(5, kDebugVideo, "Groovie::VDX: numYTiles=%d", numYTiles);
|
||||
|
||||
// It's skipped in the original:
|
||||
uint16 colorDepth = in->readUint16LE();
|
||||
debugC(5, kDebugVideo, "Groovie::VDX: colorDepth=%d", colorDepth);
|
||||
|
||||
uint16 imageWidth = TILE_SIZE * numXTiles;
|
||||
|
||||
uint8 mask = 0;
|
||||
byte *buf;
|
||||
if (_flagOne) {
|
||||
// Paint to the foreground
|
||||
buf = (byte *)_fg->getPixels();
|
||||
if (_flag2Byte) {
|
||||
mask = 0xff;
|
||||
} else {
|
||||
mask = 0;
|
||||
}
|
||||
|
||||
// TODO: Verify this is the right procedure. Couldn't find it on the
|
||||
// disassembly, but it's required to work properly
|
||||
_flagFirstFrame = true;
|
||||
} else {
|
||||
// Paint to the background
|
||||
buf = (byte *)_bg->getPixels();
|
||||
}
|
||||
|
||||
// Read the palette
|
||||
in->read(_palBuf, 3 * 256);
|
||||
|
||||
if (_flagSeven) {
|
||||
_flagFive = true;
|
||||
}
|
||||
|
||||
// Skip the frame when flag 5 is set, unless flag 1 is set
|
||||
if (!_flagFive || _flagOne) {
|
||||
|
||||
byte colors[16];
|
||||
for (uint16 j = 0; j < numYTiles; j++) {
|
||||
byte *currentTile = buf + j * TILE_SIZE * imageWidth;
|
||||
for (uint16 i = numXTiles; i; i--) {
|
||||
uint8 color1 = in->readByte();
|
||||
uint8 color0 = in->readByte();
|
||||
uint16 colorMap = in->readUint16LE();
|
||||
expandColorMap(colors, colorMap, color1, color0);
|
||||
decodeBlockStill(currentTile, colors, 640, mask);
|
||||
|
||||
currentTile += TILE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the palette
|
||||
if (_flagNine) {
|
||||
// Flag 9 starts a fade in
|
||||
if (!isFastForwarding())
|
||||
fadeIn(_palBuf);
|
||||
else
|
||||
setPalette(_palBuf);
|
||||
} else {
|
||||
if (!_flagOne && !_flagSeven) {
|
||||
// Actually apply the palette
|
||||
setPalette(_palBuf);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_flagOne) {
|
||||
_vm->_graphicsMan->updateScreen(_bg);
|
||||
}
|
||||
/*
|
||||
if (_flagSix) {
|
||||
if (_flagOne) {
|
||||
_vm->_graphicsMan->updateScreen(_fg);
|
||||
} else {
|
||||
_vm->_graphicsMan->updateScreen(_bg);
|
||||
}
|
||||
_flagSix = 0;
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
// Skip the remaining data
|
||||
debugC(10, kDebugVideo, "Groovie::VDX: Skipping still frame");
|
||||
while (!in->eos()) {
|
||||
in->readByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VDXPlayer::expandColorMap(byte *out, uint16 colorMap, uint8 color1, uint8 color0) {
|
||||
// It's a bit faster to start from the end
|
||||
out += 16;
|
||||
for (int i = 16; i; i--) {
|
||||
// Set the corresponding color
|
||||
// The following is an optimized version of:
|
||||
// *--out = (colorMap & 1) ? color1 : color0;
|
||||
uint8 selector = -(colorMap & 1);
|
||||
*--out = (selector & color1) | (~selector & color0);
|
||||
|
||||
// Update the flag map to test the next color
|
||||
colorMap >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void VDXPlayer::decodeBlockStill(byte *buf, byte *colors, uint16 imageWidth, uint8 mask) {
|
||||
assert(TILE_SIZE == 4);
|
||||
|
||||
for (int y = TILE_SIZE; y; y--) {
|
||||
if (_flagOne) {
|
||||
// TODO: optimize with bit logic?
|
||||
for (int x = 0; x < TILE_SIZE; x++) {
|
||||
// 0xff pixels don't modify the buffer
|
||||
if (*colors != 0xff) {
|
||||
// Write the color
|
||||
*buf = *colors | mask;
|
||||
// Note: if the mask is 0, it paints the image
|
||||
// else, it paints the image's mask using 0xff
|
||||
}
|
||||
|
||||
// Point to the next color
|
||||
colors++;
|
||||
|
||||
// Point to the next pixel
|
||||
buf++;
|
||||
}
|
||||
|
||||
// Point to the start of the next line
|
||||
buf += imageWidth - TILE_SIZE;
|
||||
} else {
|
||||
*((uint32 *)buf) = *((uint32 *)colors);
|
||||
colors += 4;
|
||||
|
||||
// Point to the start of the next line
|
||||
buf += imageWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VDXPlayer::decodeBlockDelta(uint32 offset, byte *colors, uint16 imageWidth) {
|
||||
assert(TILE_SIZE == 4);
|
||||
|
||||
byte *dest;
|
||||
// TODO: Verify just the else block is required
|
||||
//if (_flagOne) {
|
||||
// Paint to the foreground
|
||||
//dest = (byte *)_fg->getPixels() + offset;
|
||||
//} else {
|
||||
dest = (byte *)_bg->getPixels() + offset;
|
||||
//}
|
||||
|
||||
// Move the pointers to the beginning of the current block
|
||||
int32 blockOff = _origX + _origY * imageWidth;
|
||||
dest += blockOff;
|
||||
byte *fgBuf = nullptr;
|
||||
if (_flagSeven) {
|
||||
fgBuf = (byte *)_fg->getPixels() + offset + blockOff;
|
||||
//byte *bgBuf = (byte *)_bg->getPixels() + offset + blockOff;
|
||||
}
|
||||
|
||||
for (int y = TILE_SIZE; y; y--) {
|
||||
if (_flagSeven) {
|
||||
// Paint mask
|
||||
for (int x = 0; x < TILE_SIZE; x++) {
|
||||
// TODO: this can probably be optimized with bit logic
|
||||
if (fgBuf[x] != 0xff) {
|
||||
if (*colors == 0xff) {
|
||||
dest[x] = fgBuf[x];
|
||||
} else {
|
||||
dest[x] = *colors;
|
||||
}
|
||||
}
|
||||
colors++;
|
||||
}
|
||||
fgBuf += imageWidth;
|
||||
} else {
|
||||
// Paint directly
|
||||
*((uint32 *)dest) = *((uint32 *)colors);
|
||||
colors += 4;
|
||||
}
|
||||
|
||||
// Move to the next line
|
||||
dest += imageWidth;
|
||||
}
|
||||
}
|
||||
|
||||
void VDXPlayer::chunkSound(Common::ReadStream *in) {
|
||||
if (getOverrideSpeed())
|
||||
setOverrideSpeed(false);
|
||||
|
||||
if (!_audioStream && !isFastForwarding()) {
|
||||
_audioStream = Audio::makeQueuingAudioStream(22050, false);
|
||||
g_system->getMixer()->playStream(Audio::Mixer::kSpeechSoundType, &_soundHandle, _audioStream);
|
||||
}
|
||||
|
||||
byte *data = (byte *)malloc(60000);
|
||||
int chunksize = in->read(data, 60000);
|
||||
if (!isFastForwarding()) {
|
||||
_audioStream->queueBuffer(data, chunksize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
|
||||
} else {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
void VDXPlayer::fadeIn(uint8 *targetpal) {
|
||||
// Don't do anything if we're asked to skip palette changes
|
||||
if (_flagSkipPalette)
|
||||
return;
|
||||
|
||||
// TODO: Is it required? If so, move to an appropriate place
|
||||
// Copy the foreground to the background
|
||||
memcpy((byte *)_vm->_graphicsMan->_foreground.getPixels(), (byte *)_vm->_graphicsMan->_background.getPixels(), 640 * 320);
|
||||
|
||||
// Start a fadein
|
||||
_vm->_graphicsMan->fadeIn(targetpal);
|
||||
|
||||
// Show the background
|
||||
_vm->_graphicsMan->updateScreen(_bg);
|
||||
}
|
||||
|
||||
void VDXPlayer::setPalette(uint8 *palette) {
|
||||
if (_flagSkipPalette)
|
||||
return;
|
||||
|
||||
debugC(7, kDebugVideo, "Groovie::VDX: Setting palette");
|
||||
_syst->getPaletteManager()->setPalette(palette, 0, 256);
|
||||
}
|
||||
|
||||
} // End of Groovie namespace
|
||||
85
engines/groovie/video/vdx.h
Normal file
85
engines/groovie/video/vdx.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GROOVIE_VIDEO_VDX_H
|
||||
#define GROOVIE_VIDEO_VDX_H
|
||||
|
||||
#include "groovie/video/player.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
namespace Common {
|
||||
class ReadStream;
|
||||
}
|
||||
|
||||
namespace Groovie {
|
||||
|
||||
class VDXPlayer : public VideoPlayer {
|
||||
public:
|
||||
VDXPlayer(GroovieEngine *vm);
|
||||
~VDXPlayer() override;
|
||||
void resetFlags() override;
|
||||
void setOrigin(int16 x, int16 y) override;
|
||||
|
||||
protected:
|
||||
uint16 loadInternal() override;
|
||||
bool playFrameInternal() override;
|
||||
void stopAudioStream() override;
|
||||
|
||||
private:
|
||||
Graphics::Surface *_fg, *_bg;
|
||||
uint8 _palBuf[3 * 256];
|
||||
Audio::SoundHandle _soundHandle;
|
||||
|
||||
// Origin
|
||||
int16 _origX, _origY;
|
||||
|
||||
// Video flags
|
||||
bool _flagZero;
|
||||
bool _flagOne;
|
||||
bool _flagOnePrev;
|
||||
byte _flag2Byte;
|
||||
bool _flagThree;
|
||||
bool _flagFour;
|
||||
bool _flagFive;
|
||||
bool _flagSix;
|
||||
bool _flagSeven;
|
||||
bool _flagEight;
|
||||
bool _flagNine;
|
||||
|
||||
//bool _flagSkipStill;
|
||||
bool _flagSkipPalette;
|
||||
bool _flagFirstFrame;
|
||||
//bool _flagTransparent;
|
||||
//bool _flagUpdateStill;
|
||||
|
||||
void getStill(Common::ReadStream *in);
|
||||
void getDelta(Common::ReadStream *in);
|
||||
void expandColorMap(byte *out, uint16 colorMap, uint8 color1, uint8 color0);
|
||||
void decodeBlockStill(byte *buf, byte *colors, uint16 imageWidth, uint8 mask);
|
||||
void decodeBlockDelta(uint32 offset, byte *colors, uint16 imageWidth);
|
||||
void chunkSound(Common::ReadStream *in);
|
||||
void setPalette(uint8 *palette);
|
||||
void fadeIn(uint8 *palette);
|
||||
};
|
||||
|
||||
} // End of Groovie namespace
|
||||
|
||||
#endif // GROOVIE_VIDEO_VDX_H
|
||||
Reference in New Issue
Block a user