mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-16 06:39:17 +00:00
299a96c049
Thanks to uruk for checking on this.
576 lines
13 KiB
C++
576 lines
13 KiB
C++
/* 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 2
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This code is based on the original source code of Lord Avalot d'Argent version 1.3.
|
|
* Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman.
|
|
*/
|
|
|
|
#include "avalanche/avalanche.h"
|
|
#include "avalanche/nim.h"
|
|
|
|
namespace Avalanche {
|
|
|
|
const char * const Nim::kNames[2] = {"Avalot", "Dogfood"};
|
|
|
|
Nim::Nim(AvalancheEngine *vm) {
|
|
_vm = vm;
|
|
|
|
resetVariables();
|
|
}
|
|
|
|
void Nim::resetVariables() {
|
|
_playedNim = 0;
|
|
_turns = 0;
|
|
_dogfoodsTurn = false;
|
|
_stonesLeft = 0;
|
|
_clicked = false;
|
|
_row = 0;
|
|
_number = 0;
|
|
_squeak = false;
|
|
_lmo = false;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
_old[i] = 0;
|
|
_stones[i] = 0;
|
|
_inAp[i] = 0;
|
|
_r[i] = 0;
|
|
}
|
|
}
|
|
|
|
void Nim::synchronize(Common::Serializer &sz) {
|
|
if (sz.isLoading() && sz.getVersion() < 2)
|
|
return;
|
|
|
|
sz.syncAsByte(_playedNim);
|
|
}
|
|
|
|
void Nim::playNim() {
|
|
if (_vm->_wonNim) { // Already won the game.
|
|
_vm->_dialogs->displayScrollChain('Q', 6);
|
|
return;
|
|
}
|
|
|
|
if (!_vm->_askedDogfoodAboutNim) {
|
|
_vm->_dialogs->displayScrollChain('Q', 84);
|
|
return;
|
|
}
|
|
|
|
_vm->_dialogs->displayScrollChain('Q', 3);
|
|
_playedNim++;
|
|
|
|
_vm->_graphics->saveScreen();
|
|
_vm->fadeOut();
|
|
|
|
CursorMan.showMouse(false);
|
|
setup();
|
|
board();
|
|
//CursorMan.showMouse(true);
|
|
|
|
do {
|
|
|
|
startMove();
|
|
if (_dogfoodsTurn)
|
|
dogFood();
|
|
else {
|
|
CursorMan.showMouse(true);
|
|
takeSome();
|
|
CursorMan.showMouse(false);
|
|
}
|
|
_stones[_row] -= _number;
|
|
showChanges();
|
|
} while (_stonesLeft != 0);
|
|
|
|
endOfGame(); // Winning sequence is A1, B3, B1, C1, C1, btw.
|
|
|
|
_vm->fadeOut();
|
|
_vm->_graphics->restoreScreen();
|
|
_vm->_graphics->removeBackup();
|
|
_vm->fadeIn();
|
|
CursorMan.showMouse(true);
|
|
|
|
if (_dogfoodsTurn) {
|
|
// Dogfood won - as usual.
|
|
if (_playedNim == 1) // Your first game.
|
|
_vm->_dialogs->displayScrollChain('Q', 4); // Goody! Play me again?
|
|
else
|
|
_vm->_dialogs->displayScrollChain('Q', 5); // Oh, look at that! I've won again!
|
|
_vm->decreaseMoney(4); // And you've just lost 4d!
|
|
} else {
|
|
// You won - strange!
|
|
_vm->_dialogs->displayScrollChain('Q', 7);
|
|
_vm->_objects[kObjectLute - 1] = true;
|
|
_vm->refreshObjectList();
|
|
_vm->_wonNim = true;
|
|
_vm->_background->draw(-1, -1, 0); // Show the settle with no lute on it.
|
|
|
|
// 7 points for winning!
|
|
_vm->incScore(7);
|
|
}
|
|
|
|
if (_playedNim == 1) {
|
|
// 3 points for playing your 1st game.
|
|
_vm->incScore(3);
|
|
}
|
|
}
|
|
|
|
void Nim::chalk(int x, int y, Common::String text) {
|
|
const Color greys[] = { kColorBlack, kColorDarkgray, kColorLightgray, kColorWhite };
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
_vm->_graphics->drawNormalText(text, _vm->_font, 8, x - i, y, greys[i]);
|
|
_vm->_graphics->refreshScreen();
|
|
int freq = i * 100 * text.size();
|
|
if (freq == 0)
|
|
_vm->_system->delayMillis(3);
|
|
else
|
|
_vm->_sound->playNote(freq, 3);
|
|
_vm->_system->delayMillis(30);
|
|
}
|
|
}
|
|
|
|
void Nim::setup() {
|
|
_vm->fadeIn();
|
|
_vm->_graphics->nimLoad();
|
|
|
|
_vm->_graphics->blackOutScreen();
|
|
// Upper left rectangle.
|
|
_vm->_graphics->drawRectangle(Common::Rect(10, 5, 381, 71), kColorRed);
|
|
_vm->_graphics->drawFilledRectangle(Common::Rect(11, 6, 380, 70), kColorBrown);
|
|
// Bottom right rectangle.
|
|
_vm->_graphics->drawRectangle(Common::Rect(394, 50, 635, 198), kColorRed);
|
|
_vm->_graphics->drawFilledRectangle(Common::Rect(395, 51, 634, 197), kColorBrown);
|
|
|
|
_vm->_graphics->nimDrawLogo();
|
|
_vm->_graphics->nimDrawInitials();
|
|
|
|
_vm->_graphics->drawNormalText("SCOREBOARD:", _vm->_font, 8, 475, 45, kColorWhite);
|
|
_vm->_graphics->drawNormalText("Turn:", _vm->_font, 8, 420, 55, kColorYellow);
|
|
_vm->_graphics->drawNormalText("Player:", _vm->_font, 8, 490, 55, kColorYellow);
|
|
_vm->_graphics->drawNormalText("Move:", _vm->_font, 8, 570, 55, kColorYellow);
|
|
|
|
chalk(27, 7, "Take pieces away with:");
|
|
chalk(77, 17, "1) the mouse (click leftmost)");
|
|
chalk(53, 27, "or 2) the keyboard:");
|
|
chalk(220, 27, Common::String(24) + '/' + 25 + ": choose row,");
|
|
chalk(164, 37, Common::String("+/- or ") + 27 + '/' + 26 + ": more/fewer,");
|
|
chalk(204, 47, "Enter: take stones.");
|
|
|
|
_vm->_graphics->refreshScreen();
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
_stones[i] = i + 3;
|
|
_stonesLeft = 12;
|
|
|
|
_turns = 0;
|
|
_dogfoodsTurn = true;
|
|
|
|
_row = 0;
|
|
_number = 1;
|
|
for (int i = 0; i < 3; i++)
|
|
_old[i] = 0;
|
|
}
|
|
|
|
void Nim::board() {
|
|
_vm->_graphics->drawFilledRectangle(Common::Rect(57, 72, 393, 200), kColorBlack);
|
|
for (int i = 0; i < 3; i++)
|
|
for (int j = 0; j < _stones[i]; j++)
|
|
_vm->_graphics->nimDrawStone(64 + j * 8 * 8, 75 + i * 35);
|
|
// It's practically the body of the Pascal function "plotstone()", reimplemented.
|
|
// It's the only place where we use it, so there's no reason to keep it separated as a function.
|
|
_vm->_graphics->refreshScreen();
|
|
}
|
|
|
|
void Nim::startMove() {
|
|
_turns++;
|
|
Common::String turnsStr = Common::String::format("%d", _turns);
|
|
int y = 55 + _turns * 10;
|
|
_dogfoodsTurn = !_dogfoodsTurn;
|
|
chalk(433, y, turnsStr);
|
|
chalk(493, y, kNames[_dogfoodsTurn]);
|
|
for (int i = 0; i < 3; i++)
|
|
_old[i] = _stones[i];
|
|
}
|
|
|
|
void Nim::showChanges() {
|
|
chalk(573, 55 + _turns * 10, Common::String('A' + _row) + Common::String('0' + _number));
|
|
board();
|
|
_stonesLeft -= _number;
|
|
}
|
|
|
|
void Nim::blip() {
|
|
_vm->_sound->playNote(1771, 3);
|
|
}
|
|
|
|
void Nim::findNextUp() {
|
|
while (_stones[_row] == 0) {
|
|
_row--;
|
|
if (_row < 0)
|
|
_row = 2;
|
|
}
|
|
|
|
if (_number > _stones[_row])
|
|
_number = _stones[_row];
|
|
}
|
|
|
|
void Nim::findNextDown() {
|
|
while (_stones[_row] == 0) {
|
|
_row++;
|
|
if (_row > 2)
|
|
_row = 0;
|
|
}
|
|
|
|
if (_number > _stones[_row])
|
|
_number = _stones[_row];
|
|
}
|
|
|
|
bool Nim::checkInput() {
|
|
while (!_vm->shouldQuit()) {
|
|
_vm->_graphics->refreshScreen();
|
|
Common::Event event;
|
|
while (_vm->getEvent(event)) {
|
|
if (event.type == Common::EVENT_LBUTTONUP) {
|
|
Common::Point cursorPos = _vm->getMousePos();
|
|
|
|
int8 newRow = (cursorPos.y / 2 - 38) / 35 - 1;
|
|
if ((newRow < 0) || (newRow > 2)) {
|
|
blip();
|
|
return false;
|
|
}
|
|
|
|
int8 newNum = _stones[newRow] - ((cursorPos.x - 64) / 64);
|
|
if ((newNum < 1) || (newNum > _stones[newRow])) {
|
|
blip();
|
|
return false;
|
|
}
|
|
|
|
_number = newNum;
|
|
_row = newRow;
|
|
|
|
return true;
|
|
} else if (event.type == Common::EVENT_KEYDOWN) {
|
|
switch (event.kbd.keycode) {
|
|
case Common::KEYCODE_LEFT:
|
|
case Common::KEYCODE_KP_PLUS:
|
|
if (_stones[_row] > _number)
|
|
_number++;
|
|
return false;
|
|
case Common::KEYCODE_RIGHT:
|
|
case Common::KEYCODE_KP_MINUS:
|
|
if (_number > 1)
|
|
_number--;
|
|
return false;
|
|
case Common::KEYCODE_1:
|
|
_number = 1;
|
|
return false;
|
|
case Common::KEYCODE_2:
|
|
if (_stones[_row] >= 2)
|
|
_number = 2;
|
|
return false;
|
|
case Common::KEYCODE_3:
|
|
if (_stones[_row] >= 3)
|
|
_number = 3;
|
|
else
|
|
_number = _stones[_row];
|
|
return false;
|
|
case Common::KEYCODE_4:
|
|
if (_stones[_row] >= 4)
|
|
_number = 4;
|
|
else
|
|
_number = _stones[_row];
|
|
return false;
|
|
case Common::KEYCODE_5:
|
|
if (_stones[_row] == 5)
|
|
_number = 5;
|
|
else
|
|
_number = _stones[_row];
|
|
return false;
|
|
case Common::KEYCODE_HOME:
|
|
_number = _stones[_row];
|
|
return false;
|
|
case Common::KEYCODE_END:
|
|
_number = 1;
|
|
return false;
|
|
case Common::KEYCODE_UP:
|
|
_row--;
|
|
if (_row < 0)
|
|
_row = 2;
|
|
findNextUp();
|
|
return false;
|
|
case Common::KEYCODE_DOWN:
|
|
_row++;
|
|
if (_row > 2)
|
|
_row = 0;
|
|
findNextDown();
|
|
return false;
|
|
case Common::KEYCODE_a:
|
|
if (_stones[0] != 0) {
|
|
_row = 0;
|
|
if (_number > _stones[_row])
|
|
_number = _stones[_row];
|
|
}
|
|
return false;
|
|
case Common::KEYCODE_b:
|
|
if (_stones[1] != 0) {
|
|
_row = 1;
|
|
if (_number > _stones[_row])
|
|
_number = _stones[_row];
|
|
}
|
|
return false;
|
|
case Common::KEYCODE_c:
|
|
if (_stones[2] != 0) {
|
|
_row = 2;
|
|
if (_number > _stones[_row])
|
|
_number = _stones[_row];
|
|
}
|
|
return false;
|
|
case Common::KEYCODE_PAGEUP:
|
|
_row = 0;
|
|
findNextDown();
|
|
return false;
|
|
case Common::KEYCODE_PAGEDOWN:
|
|
_row = 2;
|
|
findNextUp();
|
|
return false;
|
|
case Common::KEYCODE_RETURN:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Nim::takeSome() {
|
|
_number = 1;
|
|
|
|
do {
|
|
byte sr;
|
|
do {
|
|
sr = _stones[_row];
|
|
if (sr == 0) {
|
|
if (_row == 2)
|
|
_row = 0;
|
|
else
|
|
_row++;
|
|
_number = 1;
|
|
}
|
|
} while (sr == 0);
|
|
|
|
if (_number > sr)
|
|
_number = sr;
|
|
|
|
int x1 = 63 + (_stones[_row] - _number) * 64;
|
|
int y1 = 38 + 35 * (_row + 1);
|
|
int x2 = 55 + _stones[_row] * 64;
|
|
int y2 = 64 + 35 * (_row + 1);
|
|
_vm->_graphics->drawRectangle(Common::Rect(x1, y1, x2, y2), kColorBlue); // Draw the selection rectangle.
|
|
_vm->_graphics->refreshScreen();
|
|
|
|
bool confirm = false;
|
|
do {
|
|
confirm = checkInput();
|
|
|
|
if (!confirm) {
|
|
_vm->_graphics->drawRectangle(Common::Rect(x1, y1, x2, y2), kColorBlack); // Erase the previous selection.
|
|
x1 = 63 + (_stones[_row] - _number) * 64;
|
|
y1 = 38 + 35 * (_row + 1);
|
|
x2 = 55 + _stones[_row] * 64;
|
|
y2 = 64 + 35 * (_row + 1);
|
|
_vm->_graphics->drawRectangle(Common::Rect(x1, y1, x2, y2), kColorBlue); // Draw the new one.
|
|
_vm->_graphics->refreshScreen();
|
|
}
|
|
} while (!confirm);
|
|
|
|
return;
|
|
|
|
} while (true);
|
|
}
|
|
|
|
void Nim::endOfGame() {
|
|
chalk(595, 55 + _turns * 10, "Wins!");
|
|
_vm->_graphics->drawNormalText("- - - Press any key... - - -", _vm->_font, 8, 100, 190, kColorWhite);
|
|
|
|
Common::Event event;
|
|
bool escape = false;
|
|
while (!_vm->shouldQuit() && !escape) {
|
|
_vm->_graphics->refreshScreen();
|
|
while (_vm->getEvent(event)) {
|
|
if ((event.type == Common::EVENT_LBUTTONUP) || (event.type == Common::EVENT_KEYDOWN)) {
|
|
escape = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
_vm->_graphics->nimFree();
|
|
}
|
|
|
|
bool Nim::find(byte x) {
|
|
bool ret = false;
|
|
for (int i = 0; i < 3; i++) {
|
|
if (_stones[i] == x) {
|
|
ret = true;
|
|
_inAp[i] = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void Nim::findAp(byte start, byte stepSize) {
|
|
byte thisOne = 0;
|
|
byte matches = 0;
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
_inAp[i] = 0; // Blank 'em all!
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
if (find(start + i * stepSize))
|
|
matches++;
|
|
else
|
|
thisOne = i;
|
|
}
|
|
|
|
// Now... Matches must be 0, 1, 2, or 3.
|
|
// 0 / 1 mean there are no A.P.s here, so we'll keep looking,
|
|
// 2 means there is a potential A.P.that we can create (ideal!), and
|
|
// 3 means that we're already in an A.P. (Trouble!)
|
|
|
|
byte ooo = 0; // Odd one out.
|
|
|
|
switch (matches) {
|
|
case 2:
|
|
for (int i = 0; i < 3; i++) { // Find which one didn't fit the A.P.
|
|
if (!_inAp[i])
|
|
ooo = i;
|
|
}
|
|
|
|
if (_stones[ooo] > (start + thisOne * stepSize)) { // Check if it's possible!
|
|
// Create an A.P.
|
|
_row = ooo; // Already calculated.
|
|
// Start + thisone * stepsize will give the amount we SHOULD have here.
|
|
_number = _stones[_row] - (start + thisOne * stepSize);
|
|
_lmo = true;
|
|
return;
|
|
}
|
|
break;
|
|
case 3: // We're actually IN an A.P! Trouble! Oooh dear.
|
|
// Take 1 from the largest pile.
|
|
_row = _r[2];
|
|
_number = 1;
|
|
_lmo = true;
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Nim::dogFood() {
|
|
_lmo = false;
|
|
byte live = 0;
|
|
byte sr[3];
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
if (_stones[i] > 0) {
|
|
_r[live] = i;
|
|
sr[live] = _stones[i];
|
|
live++;
|
|
}
|
|
}
|
|
|
|
switch (live) {
|
|
case 1: // Only one is free - so take 'em all!
|
|
_row = _r[0];
|
|
_number = _stones[_r[0]];
|
|
return;
|
|
case 2: // Two are free - make them equal!
|
|
if (sr[0] > sr[1]) { // T > b
|
|
_row = _r[0];
|
|
_number = sr[0] - sr[1];
|
|
}
|
|
else if (sr[0] < sr[1]) { // B > t
|
|
_row = _r[1];
|
|
_number = sr[1] - sr[0];
|
|
}
|
|
else { // B = t... oh no, we've lost!
|
|
_row = _r[0];
|
|
_number = 1;
|
|
}
|
|
return;
|
|
case 3: {
|
|
// Ho hum... this'll be difficult!
|
|
// There are three possible courses of action when we have 3 lines left:
|
|
// 1) Look for 2 equal lines, then take the odd one out.
|
|
// 2) Look for A.P.s, and capitalise on them.
|
|
// 3) Go any old where.
|
|
const byte other[3][2] = { { 1, 2 }, { 0, 2 }, { 0, 1 } };
|
|
|
|
for (int i = 0; i < 3; i++) { // Look for 2 equal lines.
|
|
if (_stones[other[i][0]] == _stones[other[i][1]]) {
|
|
_row = i; // This row.
|
|
_number = _stones[i]; // All of 'em.
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool sorted;
|
|
do {
|
|
sorted = true;
|
|
for (int i = 0; i < 2; i++) {
|
|
if (sr[i] > sr[i + 1]) {
|
|
byte temp = sr[i + 1];
|
|
sr[i + 1] = sr[i];
|
|
sr[i] = temp;
|
|
|
|
temp = _r[i + 1];
|
|
_r[i + 1] = _r[i];
|
|
_r[i] = temp;
|
|
|
|
sorted = false;
|
|
}
|
|
}
|
|
} while (!sorted);
|
|
|
|
// Now we look for A.P.s...
|
|
for (int i = 1; i <= 3; i++) {
|
|
findAp(i, 1); // There are 3 "1"s.
|
|
if (_lmo)
|
|
return; // Cut - out.
|
|
}
|
|
findAp(1, 2); // Only "2" possible.
|
|
if (_lmo)
|
|
return;
|
|
|
|
// A.P.search must have failed - use the default move.
|
|
_row = _r[2];
|
|
_number = 1;
|
|
return;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // End of namespace Avalanche
|