diff options
Diffstat (limited to 'engines/avalanche/nim.cpp')
-rw-r--r-- | engines/avalanche/nim.cpp | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/engines/avalanche/nim.cpp b/engines/avalanche/nim.cpp new file mode 100644 index 0000000000..e4897a6d49 --- /dev/null +++ b/engines/avalanche/nim.cpp @@ -0,0 +1,575 @@ +/* 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->drawFilledRectangle(Common::Rect(0, 0, 640, 200), kColorBlack); + // 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 = 54 + _stones[_row] * 64; + int y2 = 63 + 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 = 54 + _stones[_row] * 64; + y2 = 63 + 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] = { { 2, 3 }, { 1, 3 }, { 1, 2 } }; + + 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 |