mirror of
https://github.com/libretro/scummvm.git
synced 2025-03-06 10:17:14 +00:00
VCRUISE: Partially implement circuit puzzles
This commit is contained in:
parent
c79fbebc93
commit
53551733c6
717
engines/vcruise/circuitpuzzle.cpp
Normal file
717
engines/vcruise/circuitpuzzle.cpp
Normal file
@ -0,0 +1,717 @@
|
||||
/* 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/algorithm.h"
|
||||
|
||||
#include "vcruise/circuitpuzzle.h"
|
||||
|
||||
namespace VCruise {
|
||||
|
||||
namespace CircuitPuzzleTables {
|
||||
|
||||
// These are hard-coded into the Schizm executable (they're 32-bit in it), figured these out by hand
|
||||
// from tracing screen captures and feeding through a boundary finder script.
|
||||
|
||||
static const int16 g_barriersHorizontal1[100] = {
|
||||
166, 6, 191, 62,
|
||||
244, 7, 267, 62,
|
||||
320, 7, 341, 62,
|
||||
394, 8, 416, 63,
|
||||
469, 8, 492, 62,
|
||||
164, 80, 191, 134,
|
||||
244, 80, 265, 130,
|
||||
320, 80, 336, 131,
|
||||
394, 80, 415, 130,
|
||||
468, 80, 492, 130,
|
||||
163, 152, 191, 199,
|
||||
245, 152, 263, 199,
|
||||
320, 152, 340, 199,
|
||||
395, 152, 415, 199,
|
||||
469, 152, 493, 199,
|
||||
166, 221, 187, 270,
|
||||
245, 221, 266, 270,
|
||||
320, 220, 341, 271,
|
||||
394, 221, 414, 271,
|
||||
469, 221, 492, 271,
|
||||
166, 290, 187, 343,
|
||||
244, 290, 263, 343,
|
||||
320, 290, 341, 343,
|
||||
395, 289, 415, 343,
|
||||
469, 291, 494, 346,
|
||||
};
|
||||
|
||||
static const int16 g_barriersVertical1[64] = {
|
||||
187, 64, 240, 84,
|
||||
266, 63, 316, 84,
|
||||
340, 64, 392, 83,
|
||||
416, 64, 470, 84,
|
||||
186, 135, 240, 151,
|
||||
266, 135, 315, 152,
|
||||
341, 134, 393, 151,
|
||||
416, 135, 479, 152,
|
||||
187, 204, 239, 221,
|
||||
266, 203, 314, 221,
|
||||
341, 204, 391, 220,
|
||||
416, 204, 470, 220,
|
||||
187, 273, 239, 293,
|
||||
266, 271, 314, 292,
|
||||
342, 271, 391, 292,
|
||||
416, 272, 471, 292,
|
||||
};
|
||||
|
||||
static const int16 g_barriersHorizontal2[100] = {
|
||||
160, 8, 185, 62,
|
||||
239, 7, 260, 63,
|
||||
309, 8, 332, 63,
|
||||
388, 8, 407, 63,
|
||||
455, 8, 485, 63,
|
||||
162, 79, 184, 130,
|
||||
241, 80, 260, 131,
|
||||
313, 80, 332, 131,
|
||||
387, 82, 408, 131,
|
||||
460, 82, 484, 131,
|
||||
162, 153, 184, 200,
|
||||
238, 153, 258, 200,
|
||||
312, 153, 333, 201,
|
||||
386, 153, 408, 201,
|
||||
459, 153, 485, 200,
|
||||
161, 220, 183, 270,
|
||||
237, 220, 259, 270,
|
||||
313, 221, 332, 270,
|
||||
384, 220, 408, 269,
|
||||
459, 219, 485, 267,
|
||||
161, 289, 183, 343,
|
||||
238, 288, 259, 350,
|
||||
312, 289, 332, 341,
|
||||
383, 291, 408, 342,
|
||||
460, 290, 484, 341,
|
||||
};
|
||||
|
||||
static const int16 g_barriersVertical2[64] = {
|
||||
186, 63, 239, 84,
|
||||
263, 64, 313, 84,
|
||||
337, 64, 389, 84,
|
||||
411, 64, 464, 85,
|
||||
184, 135, 238, 151,
|
||||
259, 136, 311, 151,
|
||||
336, 135, 388, 152,
|
||||
410, 136, 465, 152,
|
||||
184, 203, 238, 220,
|
||||
263, 204, 311, 220,
|
||||
338, 203, 388, 219,
|
||||
408, 203, 464, 219,
|
||||
185, 272, 237, 291,
|
||||
260, 272, 313, 291,
|
||||
337, 271, 388, 290,
|
||||
411, 271, 463, 289,
|
||||
};
|
||||
|
||||
static const int16 g_linksHorizontal1[100] = {
|
||||
136, 24, 206, 38,
|
||||
216, 24, 284, 38,
|
||||
294, 24, 363, 38,
|
||||
374, 24, 442, 38,
|
||||
452, 24, 520, 38,
|
||||
136, 97, 205, 110,
|
||||
215, 97, 284, 110,
|
||||
294, 98, 363, 110,
|
||||
373, 98, 442, 110,
|
||||
451, 98, 520, 110,
|
||||
137, 170, 206, 182,
|
||||
216, 170, 284, 182,
|
||||
294, 170, 363, 182,
|
||||
373, 170, 442, 182,
|
||||
452, 170, 520, 182,
|
||||
137, 242, 204, 255,
|
||||
216, 242, 284, 255,
|
||||
295, 242, 363, 254,
|
||||
374, 242, 441, 254,
|
||||
452, 242, 520, 254,
|
||||
137, 315, 204, 328,
|
||||
216, 315, 284, 328,
|
||||
295, 315, 362, 327,
|
||||
374, 315, 441, 327,
|
||||
452, 315, 520, 327,
|
||||
};
|
||||
|
||||
static const int16 g_linksVertical1[64] = {
|
||||
205, 36, 217, 98,
|
||||
284, 36, 295, 99,
|
||||
362, 36, 374, 99,
|
||||
441, 37, 452, 99,
|
||||
205, 109, 217, 171,
|
||||
284, 109, 295, 171,
|
||||
363, 109, 374, 171,
|
||||
441, 109, 452, 170,
|
||||
205, 181, 217, 244,
|
||||
284, 181, 295, 243,
|
||||
362, 181, 374, 243,
|
||||
441, 181, 452, 243,
|
||||
205, 254, 217, 316,
|
||||
284, 254, 295, 316,
|
||||
362, 254, 374, 316,
|
||||
441, 254, 453, 316,
|
||||
};
|
||||
|
||||
static const int16 g_linksHorizontal2[100] = {
|
||||
135, 26, 206, 39,
|
||||
214, 27, 284, 39,
|
||||
292, 27, 362, 40,
|
||||
370, 28, 439, 41,
|
||||
447, 29, 515, 40,
|
||||
135, 98, 206, 111,
|
||||
214, 99, 284, 111,
|
||||
292, 99, 362, 111,
|
||||
370, 99, 439, 112,
|
||||
447, 100, 515, 112,
|
||||
135, 170, 206, 182,
|
||||
214, 170, 284, 182,
|
||||
293, 170, 362, 182,
|
||||
370, 170, 438, 182,
|
||||
447, 170, 514, 182,
|
||||
135, 241, 206, 255,
|
||||
214, 241, 285, 254,
|
||||
293, 241, 362, 254,
|
||||
370, 241, 438, 253,
|
||||
447, 241, 515, 253,
|
||||
135, 314, 206, 327,
|
||||
214, 314, 284, 326,
|
||||
292, 313, 362, 325,
|
||||
370, 313, 438, 325,
|
||||
447, 312, 515, 324,
|
||||
};
|
||||
|
||||
static const int16 g_linksVertical2[64] = {
|
||||
204, 37, 216, 100,
|
||||
283, 38, 294, 100,
|
||||
360, 39, 372, 100,
|
||||
437, 39, 449, 102,
|
||||
204, 109, 216, 171,
|
||||
283, 110, 294, 171,
|
||||
360, 110, 371, 171,
|
||||
437, 111, 448, 171,
|
||||
204, 181, 216, 242,
|
||||
283, 181, 294, 242,
|
||||
360, 181, 371, 242,
|
||||
436, 181, 448, 242,
|
||||
204, 253, 216, 315,
|
||||
283, 252, 294, 315,
|
||||
360, 252, 372, 314,
|
||||
436, 251, 448, 314,
|
||||
};
|
||||
|
||||
} // End of namespace CircuitPuzzleTables
|
||||
|
||||
struct CircuitPuzzleAIEvaluator {
|
||||
CircuitPuzzleAIEvaluator();
|
||||
|
||||
static const uint kMaxMovesToReach = CircuitPuzzle::kBoardWidth * CircuitPuzzle::kBoardHeight * 2;
|
||||
|
||||
uint stepsToReach[CircuitPuzzle::kBoardWidth][CircuitPuzzle::kBoardHeight];
|
||||
};
|
||||
|
||||
class CircuitPuzzleVisitedSet {
|
||||
public:
|
||||
CircuitPuzzleVisitedSet();
|
||||
|
||||
void set(const Common::Point &coord);
|
||||
bool get(const Common::Point &coord) const;
|
||||
void clear();
|
||||
|
||||
private:
|
||||
uint32 _bits;
|
||||
};
|
||||
|
||||
CircuitPuzzleVisitedSet::CircuitPuzzleVisitedSet() : _bits(0) {
|
||||
}
|
||||
|
||||
void CircuitPuzzleVisitedSet::set(const Common::Point &coord) {
|
||||
int bit = coord.y * static_cast<int>(CircuitPuzzle::kBoardWidth) + coord.x;
|
||||
_bits |= (1u << bit);
|
||||
}
|
||||
|
||||
bool CircuitPuzzleVisitedSet::get(const Common::Point &coord) const {
|
||||
int bit = coord.y * static_cast<int>(CircuitPuzzle::kBoardWidth) + coord.x;
|
||||
return (_bits & (1u << bit)) != 0;
|
||||
}
|
||||
|
||||
void CircuitPuzzleVisitedSet::clear() {
|
||||
_bits = 0;
|
||||
}
|
||||
|
||||
CircuitPuzzleAIEvaluator::CircuitPuzzleAIEvaluator() {
|
||||
for (int x = 0; x < CircuitPuzzle::kBoardWidth; x++)
|
||||
for (int y = 0; y < CircuitPuzzle::kBoardHeight; y++)
|
||||
stepsToReach[x][y] = kMaxMovesToReach;
|
||||
}
|
||||
|
||||
CircuitPuzzle::Action::Action() : _direction(kCellDirectionDown) {
|
||||
}
|
||||
|
||||
CircuitPuzzle::CircuitPuzzle(int layout) : _havePreviousAction(false) {
|
||||
_startPoint = Common::Point(0, 0);
|
||||
_goalPoint = Common::Point(kBoardWidth - 1, 0);
|
||||
|
||||
const int16 *linksHoriz = nullptr;
|
||||
const int16 *linksVert = nullptr;
|
||||
const int16 *barriersHoriz = nullptr;
|
||||
const int16 *barriersVert = nullptr;
|
||||
|
||||
if (layout == 1) {
|
||||
linksHoriz = CircuitPuzzleTables::g_linksHorizontal1;
|
||||
linksVert = CircuitPuzzleTables::g_linksVertical1;
|
||||
barriersHoriz = CircuitPuzzleTables::g_barriersHorizontal1;
|
||||
barriersVert = CircuitPuzzleTables::g_barriersVertical1;
|
||||
} else if (layout == 2) {
|
||||
linksHoriz = CircuitPuzzleTables::g_linksHorizontal2;
|
||||
linksVert = CircuitPuzzleTables::g_linksVertical2;
|
||||
barriersHoriz = CircuitPuzzleTables::g_barriersHorizontal2;
|
||||
barriersVert = CircuitPuzzleTables::g_barriersVertical2;
|
||||
} else
|
||||
error("Unknown circuit screen layout");
|
||||
|
||||
// Pre-connect the side rails
|
||||
for (uint i = 0; i < (kBoardHeight - 1u); i++) {
|
||||
*getConnectionState(Common::Point(0, i), KDirectionDown) = kLinkStateConnected;
|
||||
*getConnectionState(Common::Point(kBoardWidth - 1, i), KDirectionDown) = kLinkStateConnected;
|
||||
}
|
||||
|
||||
// Block edge points
|
||||
for (uint i = 0; i < kBoardWidth; i++)
|
||||
_cells[i][kBoardHeight - 1]._downLink = kLinkStateBlocked;
|
||||
for (uint i = 0; i < kBoardHeight; i++)
|
||||
_cells[kBoardWidth - 1][i]._rightLink = kLinkStateBlocked;
|
||||
|
||||
// Barriers are traced from pixel matches, but links are traced from the highlight boxes.
|
||||
// Since the highlight boxes are (1,1) larger than the clipping box of the animation, and because
|
||||
// the coordinates are inclusive, we need to add (1,1) to barrier sizes, but not link sizes, since the
|
||||
// inclusive (+1,+1) cancels out from the oversize (-1,-1) adjustment.
|
||||
|
||||
// Resolve horizontal links and barriers
|
||||
for (uint y = 0; y < kBoardHeight; y++) {
|
||||
for (uint x = 0; x < (kBoardWidth - 1u); x++) {
|
||||
uint rectDataOffset = (x + y * (kBoardWidth - 1u)) * 4u;
|
||||
|
||||
CellRectSpec &rectSpec = _cellRectSpecs[x][y];
|
||||
rectSpec._rightBarrierRect = Common::Rect(barriersHoriz[rectDataOffset + 0], barriersHoriz[rectDataOffset + 1], barriersHoriz[rectDataOffset + 2] + 1, barriersHoriz[rectDataOffset + 3] + 1);
|
||||
rectSpec._rightLinkRect = Common::Rect(linksHoriz[rectDataOffset + 0], linksHoriz[rectDataOffset + 1], linksHoriz[rectDataOffset + 2], linksHoriz[rectDataOffset + 3]);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve vertical links and barriers. Skip the first and last column.
|
||||
for (uint y = 0; y < (kBoardHeight - 1u); y++) {
|
||||
for (uint x = 1; x < (kBoardWidth - 1u); x++) {
|
||||
uint rectDataOffset = ((x - 1) + y * (kBoardWidth - 2u)) * 4u;
|
||||
|
||||
CellRectSpec &rectSpec = _cellRectSpecs[x][y];
|
||||
rectSpec._downBarrierRect = Common::Rect(barriersVert[rectDataOffset + 0], barriersVert[rectDataOffset + 1], barriersVert[rectDataOffset + 2] + 1, barriersVert[rectDataOffset + 3] + 1);
|
||||
rectSpec._downLinkRect = Common::Rect(linksVert[rectDataOffset + 0], linksVert[rectDataOffset + 1], linksVert[rectDataOffset + 2], linksVert[rectDataOffset + 3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CircuitPuzzle::executeAIAction(Common::RandomSource &randomSource, Common::Point &outCoord, CellDirection &outBlockDirection) {
|
||||
// I've attempted to figure out what Schizm's AI algorithm does to no avail.
|
||||
//
|
||||
// What we do, which is approximately what Schizm's AI does, is find all paths tied for the shortest path to the goal
|
||||
// and randomly block a point on that path. Sometimes Schizm's AI will fail to a 1-distance spot, and sometimes it will fail
|
||||
// to opportunistically block a spot that would cause the AI to immediatley win, so this isn't a perfect reproduction of it,
|
||||
// but it is very close.
|
||||
//
|
||||
// We solve this as a series of 2 flood fills:
|
||||
// - First, each point is assigned a maximum distance score
|
||||
// - Next, all points connected to the start point (0,0) are assigned a score of 0.
|
||||
// - Next, all points are progressively expanded to determine the minimum number of
|
||||
// connections needed to reach the point.
|
||||
// - If the end point is assigned a score, then flood fill stops and the backtrace phase
|
||||
// begins. The backtrace phase collects all links starting from the end that link to
|
||||
// another point with 1 less move required to reach it.
|
||||
// - A random link is chosen as the AI move.
|
||||
|
||||
CircuitPuzzleAIEvaluator evaluator;
|
||||
|
||||
computeStepsToReach(evaluator);
|
||||
|
||||
uint stepsToReachGoal = evaluator.stepsToReach[_goalPoint.x][_goalPoint.y];
|
||||
|
||||
if (stepsToReachGoal == 0 || stepsToReachGoal == CircuitPuzzleAIEvaluator::kMaxMovesToReach)
|
||||
return false;
|
||||
|
||||
const uint kMaxLinks = kBoardWidth * kBoardHeight * 2;
|
||||
|
||||
Action potentialBlocks[kMaxLinks];
|
||||
uint numPotentialBlocks = 0;
|
||||
|
||||
Common::Point pointsList1[kMaxLinks];
|
||||
Common::Point pointsList2[kMaxLinks];
|
||||
|
||||
Common::Point *pointsToFloodFill = pointsList1;
|
||||
Common::Point *pointsToProspect = pointsList2;
|
||||
|
||||
uint numPointsToFloodFill = 1;
|
||||
uint numPointsToProspect = 0;
|
||||
|
||||
pointsToFloodFill[0] = _goalPoint;
|
||||
|
||||
CircuitPuzzleVisitedSet visitedSet;
|
||||
|
||||
uint prospectLevel = stepsToReachGoal;
|
||||
|
||||
while (prospectLevel > 0) {
|
||||
floodFillLinks(pointsToFloodFill, numPointsToFloodFill, visitedSet);
|
||||
|
||||
for (uint i = 0; i < numPointsToFloodFill; i++) {
|
||||
const Common::Point &pt = pointsToFloodFill[i];
|
||||
|
||||
for (uint dir = 0; dir < kDirectionCount; dir++) {
|
||||
const LinkState *linkState = getConnectionState(pt, static_cast<Direction>(dir));
|
||||
if (linkState && (*linkState) == kLinkStateOpen) {
|
||||
Common::Point connectedPoint = getConnectedPoint(pt, static_cast<Direction>(dir));
|
||||
|
||||
if (!visitedSet.get(connectedPoint)) {
|
||||
visitedSet.set(connectedPoint);
|
||||
|
||||
if (evaluator.stepsToReach[connectedPoint.x][connectedPoint.y] + 1u == prospectLevel) {
|
||||
// This point is on the shortest path
|
||||
Action action;
|
||||
|
||||
switch (dir) {
|
||||
case kDirectionUp:
|
||||
action._point = connectedPoint;
|
||||
action._direction = kCellDirectionDown;
|
||||
break;
|
||||
case KDirectionDown:
|
||||
action._point = pt;
|
||||
action._direction = kCellDirectionDown;
|
||||
break;
|
||||
case kDirectionLeft:
|
||||
action._point = connectedPoint;
|
||||
action._direction = kCellDirectionRight;
|
||||
break;
|
||||
case kDirectionRight:
|
||||
action._point = pt;
|
||||
action._direction = kCellDirectionRight;
|
||||
break;
|
||||
default:
|
||||
error("Internal error: Bad direction");
|
||||
return false;
|
||||
}
|
||||
|
||||
potentialBlocks[numPotentialBlocks] = action;
|
||||
numPotentialBlocks++;
|
||||
|
||||
pointsToProspect[numPointsToProspect] = connectedPoint;
|
||||
numPointsToProspect++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::Point *tempList = pointsToFloodFill;
|
||||
pointsToFloodFill = pointsToProspect;
|
||||
pointsToProspect = tempList;
|
||||
|
||||
numPointsToFloodFill = numPointsToProspect;
|
||||
numPointsToProspect = 0;
|
||||
|
||||
prospectLevel--;
|
||||
}
|
||||
|
||||
if (numPotentialBlocks == 0)
|
||||
return false;
|
||||
|
||||
// All potential blocks are now on the shortest path.
|
||||
// Try to mimic some of the AI behavior of Schizm to form wall advances.
|
||||
// The highest-priority move is one that runs parallel to the previous move.
|
||||
// If no such move exists, then a move that shares a corner is priority.
|
||||
uint selectedBlock = 0;
|
||||
if (numPotentialBlocks > 1) {
|
||||
uint blockQualities[kMaxLinks];
|
||||
for (uint i = 0; i < numPotentialBlocks; i++)
|
||||
blockQualities[i] = 0;
|
||||
|
||||
uint highestQuality = 0;
|
||||
if (_havePreviousAction) {
|
||||
for (uint i = 0; i < numPotentialBlocks; i++) {
|
||||
uint quality = 0;
|
||||
|
||||
const Action &pblock = potentialBlocks[i];
|
||||
|
||||
// We don't want to favor horizontal walls because otherwise that triggers are degenerate behavior where the player can run a wall
|
||||
// directly across and the AI will keeps inserting horizontal walls parallel to the player action.
|
||||
bool isWallBlock = false;
|
||||
if (_previousAction._direction == kCellDirectionRight && pblock._direction == kCellDirectionRight && _previousAction._point.x == pblock._point.x)
|
||||
isWallBlock = true;
|
||||
//else if (_previousAction._direction == kCellDirectionDown && pblock._direction == kCellDirectionDown && _previousAction._point.y == pblock._point.y)
|
||||
// isWallBlock = true;
|
||||
|
||||
// If this forms a vertical wall, it's quality 2
|
||||
if (isWallBlock)
|
||||
quality = 2;
|
||||
else {
|
||||
// If this forms a corner, it's quality 1
|
||||
if (_previousAction._direction != pblock._direction) {
|
||||
Common::Point prevAdjacent = _previousAction._point;
|
||||
if (_previousAction._direction == kCellDirectionRight)
|
||||
prevAdjacent.x++;
|
||||
else if (_previousAction._direction == kCellDirectionDown)
|
||||
prevAdjacent.y++;
|
||||
|
||||
Common::Point pblockAdjacent = pblock._point;
|
||||
if (pblock._direction == kCellDirectionRight)
|
||||
pblockAdjacent.x++;
|
||||
else if (pblock._direction == kCellDirectionDown)
|
||||
pblockAdjacent.y++;
|
||||
|
||||
if (prevAdjacent == pblock._point || prevAdjacent == pblockAdjacent || _previousAction._point == pblock._point || _previousAction._point == pblockAdjacent)
|
||||
quality = 1;
|
||||
}
|
||||
}
|
||||
|
||||
blockQualities[i] = quality;
|
||||
if (quality > highestQuality)
|
||||
highestQuality = quality;
|
||||
}
|
||||
}
|
||||
|
||||
uint blocksInHighestQuality[kMaxLinks];
|
||||
uint numBlocksInHighestQuality = 0;
|
||||
|
||||
for (uint i = 0; i < numPotentialBlocks; i++) {
|
||||
if (blockQualities[i] == highestQuality) {
|
||||
blocksInHighestQuality[numBlocksInHighestQuality] = i;
|
||||
numBlocksInHighestQuality++;
|
||||
}
|
||||
}
|
||||
|
||||
if (numBlocksInHighestQuality == 1)
|
||||
selectedBlock = blocksInHighestQuality[0];
|
||||
else {
|
||||
assert(numBlocksInHighestQuality > 1);
|
||||
selectedBlock = blocksInHighestQuality[randomSource.getRandomNumber(numBlocksInHighestQuality - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
const Action &pblock = potentialBlocks[selectedBlock];
|
||||
|
||||
outCoord = pblock._point;
|
||||
outBlockDirection = pblock._direction;
|
||||
|
||||
if (pblock._direction == kCellDirectionDown)
|
||||
_cells[pblock._point.x][pblock._point.y]._downLink = kLinkStateBlocked;
|
||||
if (pblock._direction == kCellDirectionRight)
|
||||
_cells[pblock._point.x][pblock._point.y]._rightLink = kLinkStateBlocked;
|
||||
|
||||
_havePreviousAction = true;
|
||||
_previousAction = pblock;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CircuitPuzzle::addLink(const Common::Point &coord, CellDirection direction) {
|
||||
validateCoord(coord);
|
||||
|
||||
CellState &cell = _cells[coord.x][coord.y];
|
||||
|
||||
LinkState *linkState = nullptr;
|
||||
if (direction == kCellDirectionDown)
|
||||
linkState = &cell._downLink;
|
||||
else if (direction == kCellDirectionRight)
|
||||
linkState = &cell._rightLink;
|
||||
|
||||
if (linkState == nullptr || (*linkState) != kLinkStateOpen)
|
||||
error("Internal error: Circuit link state was invalid");
|
||||
|
||||
*linkState = kLinkStateConnected;
|
||||
}
|
||||
|
||||
CircuitPuzzle::Conclusion CircuitPuzzle::checkConclusion() const {
|
||||
CircuitPuzzleAIEvaluator evaluator;
|
||||
|
||||
computeStepsToReach(evaluator);
|
||||
|
||||
uint stepsToReachGoal = evaluator.stepsToReach[_goalPoint.x][_goalPoint.y];
|
||||
|
||||
if (stepsToReachGoal == 0)
|
||||
return kConclusionPlayerWon;
|
||||
|
||||
if (stepsToReachGoal == CircuitPuzzleAIEvaluator::kMaxMovesToReach)
|
||||
return kConclusionPlayerLost;
|
||||
|
||||
return kConclusionNone;
|
||||
}
|
||||
|
||||
const CircuitPuzzle::CellRectSpec *CircuitPuzzle::getCellRectSpec(const Common::Point &coord) const {
|
||||
validateCoord(coord);
|
||||
|
||||
return &_cellRectSpecs[coord.x][coord.y];
|
||||
}
|
||||
|
||||
bool CircuitPuzzle::isCellDownLinkOpen(const Common::Point &coord) const {
|
||||
validateCoord(coord);
|
||||
|
||||
return _cells[coord.x][coord.y]._downLink == kLinkStateOpen;
|
||||
}
|
||||
|
||||
bool CircuitPuzzle::isCellRightLinkOpen(const Common::Point &coord) const {
|
||||
validateCoord(coord);
|
||||
|
||||
return _cells[coord.x][coord.y]._rightLink == kLinkStateOpen;
|
||||
}
|
||||
|
||||
CircuitPuzzle::CellState::CellState() : _downLink(kLinkStateOpen), _rightLink(kLinkStateOpen) {
|
||||
}
|
||||
|
||||
Common::Point CircuitPuzzle::getConnectedPoint(const Common::Point &coord, Direction direction) {
|
||||
switch (direction) {
|
||||
case kDirectionUp:
|
||||
return Common::Point(coord.x, coord.y - 1);
|
||||
case KDirectionDown:
|
||||
return Common::Point(coord.x, coord.y + 1);
|
||||
case kDirectionLeft:
|
||||
return Common::Point(coord.x - 1, coord.y);
|
||||
case kDirectionRight:
|
||||
return Common::Point(coord.x + 1, coord.y);
|
||||
default:
|
||||
return coord;
|
||||
};
|
||||
}
|
||||
|
||||
CircuitPuzzle::LinkState *CircuitPuzzle::getConnectionState(const Common::Point &coord, Direction direction) {
|
||||
if (!isPositionValid(coord))
|
||||
return nullptr;
|
||||
|
||||
switch (direction) {
|
||||
case kDirectionUp:
|
||||
if (coord.y == 0)
|
||||
return nullptr;
|
||||
return &_cells[coord.x][coord.y - 1]._downLink;
|
||||
case KDirectionDown:
|
||||
if (coord.y == static_cast<int>(kBoardHeight - 1))
|
||||
return nullptr;
|
||||
|
||||
return &_cells[coord.x][coord.y]._downLink;
|
||||
case kDirectionLeft:
|
||||
if (coord.x <= 0)
|
||||
return nullptr;
|
||||
return &_cells[coord.x - 1][coord.y]._rightLink;
|
||||
case kDirectionRight:
|
||||
if (coord.x == static_cast<int>(kBoardWidth - 1))
|
||||
return nullptr;
|
||||
|
||||
return &_cells[coord.x][coord.y]._rightLink;
|
||||
default:
|
||||
return nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
const CircuitPuzzle::LinkState *CircuitPuzzle::getConnectionState(const Common::Point &coord, Direction direction) const {
|
||||
return const_cast<CircuitPuzzle *>(this)->getConnectionState(coord, direction);
|
||||
}
|
||||
|
||||
bool CircuitPuzzle::isPositionValid(const Common::Point &coord) {
|
||||
if (coord.x < 0 || coord.y < 0 || coord.x >= static_cast<int>(kBoardWidth) || coord.y >= static_cast<int>(kBoardHeight))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CircuitPuzzle::computeStepsToReach(CircuitPuzzleAIEvaluator &evaluator) const {
|
||||
const uint kMaxLinks = kBoardWidth * kBoardHeight * 2;
|
||||
|
||||
Common::Point pointsList1[kMaxLinks];
|
||||
Common::Point pointsList2[kMaxLinks];
|
||||
|
||||
Common::Point *pointsToFloodFill = pointsList1;
|
||||
Common::Point *pointsToProspect = pointsList2;
|
||||
|
||||
uint numPointsToFloodFill = 1;
|
||||
uint numPointsToProspect = 0;
|
||||
|
||||
uint floodFillValue = 0;
|
||||
pointsToFloodFill[0] = _startPoint;
|
||||
|
||||
for (uint x = 0; x < kBoardWidth; x++)
|
||||
for (uint y = 0; y < kBoardHeight; y++)
|
||||
evaluator.stepsToReach[x][y] = CircuitPuzzleAIEvaluator::kMaxMovesToReach;
|
||||
|
||||
CircuitPuzzleVisitedSet visitedSet;
|
||||
while (numPointsToFloodFill > 0) {
|
||||
floodFillLinks(pointsToFloodFill, numPointsToFloodFill, visitedSet);
|
||||
|
||||
for (uint i = 0; i < numPointsToFloodFill; i++) {
|
||||
const Common::Point &pt = pointsToFloodFill[i];
|
||||
|
||||
evaluator.stepsToReach[pt.x][pt.y] = floodFillValue;
|
||||
|
||||
for (uint dir = 0; dir < kDirectionCount; dir++) {
|
||||
const LinkState *linkState = getConnectionState(pt, static_cast<Direction>(dir));
|
||||
if (linkState && (*linkState) == kLinkStateOpen) {
|
||||
Common::Point connectedPoint = getConnectedPoint(pt, static_cast<Direction>(dir));
|
||||
|
||||
if (!visitedSet.get(connectedPoint)) {
|
||||
visitedSet.set(connectedPoint);
|
||||
|
||||
pointsToProspect[numPointsToProspect] = connectedPoint;
|
||||
numPointsToProspect++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::Point *tempList = pointsToFloodFill;
|
||||
pointsToFloodFill = pointsToProspect;
|
||||
pointsToProspect = tempList;
|
||||
|
||||
numPointsToFloodFill = numPointsToProspect;
|
||||
numPointsToProspect = 0;
|
||||
|
||||
floodFillValue++;
|
||||
}
|
||||
}
|
||||
|
||||
void CircuitPuzzle::floodFillLinks(Common::Point *pointsList, uint &listSize, CircuitPuzzleVisitedSet &visitedSet) const {
|
||||
for (uint i = 0; i < listSize; i++) {
|
||||
const Common::Point &pt = pointsList[i];
|
||||
|
||||
visitedSet.set(pt);
|
||||
for (uint dir = 0; dir < kDirectionCount; dir++) {
|
||||
const LinkState *linkState = getConnectionState(pt, static_cast<Direction>(dir));
|
||||
if (linkState && (*linkState) == kLinkStateConnected) {
|
||||
Common::Point connectedPoint = getConnectedPoint(pt, static_cast<Direction>(dir));
|
||||
|
||||
if (!visitedSet.get(connectedPoint)) {
|
||||
pointsList[listSize] = connectedPoint;
|
||||
listSize++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CircuitPuzzle::validateCoord(const Common::Point &coord) {
|
||||
assert(coord.x >= 0 && coord.y >= 0 && coord.x < static_cast<int>(kBoardWidth) && coord.y < static_cast<int>(kBoardHeight));
|
||||
}
|
||||
|
||||
|
||||
} // End of namespace VCruise
|
127
engines/vcruise/circuitpuzzle.h
Normal file
127
engines/vcruise/circuitpuzzle.h
Normal file
@ -0,0 +1,127 @@
|
||||
/* 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 VCRUISE_CIRCUITPUZZLE_H
|
||||
#define VCRUISE_CIRCUITPUZZLE_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/random.h"
|
||||
#include "common/rect.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
class RandomSource;
|
||||
|
||||
} // End of namespace Common
|
||||
|
||||
namespace VCruise {
|
||||
|
||||
struct CircuitPuzzleAIEvaluator;
|
||||
class CircuitPuzzleVisitedSet;
|
||||
|
||||
class CircuitPuzzle {
|
||||
public:
|
||||
explicit CircuitPuzzle(int layout);
|
||||
|
||||
static const uint kBoardWidth = 6;
|
||||
static const uint kBoardHeight = 5;
|
||||
|
||||
enum CellDirection {
|
||||
kCellDirectionRight,
|
||||
kCellDirectionDown,
|
||||
};
|
||||
|
||||
enum Conclusion {
|
||||
kConclusionNone,
|
||||
kConclusionPlayerWon,
|
||||
kConclusionPlayerLost,
|
||||
};
|
||||
|
||||
struct CellRectSpec {
|
||||
Common::Rect _rightLinkRect;
|
||||
Common::Rect _downLinkRect;
|
||||
Common::Rect _rightBarrierRect;
|
||||
Common::Rect _downBarrierRect;
|
||||
};
|
||||
|
||||
// Returns true if the AI can act, if it can then the actions are produced
|
||||
bool executeAIAction(Common::RandomSource &randomSource, Common::Point &outCoord, CellDirection &outBlockDirection);
|
||||
|
||||
void addLink(const Common::Point &coord, CellDirection direction);
|
||||
|
||||
Conclusion checkConclusion() const;
|
||||
|
||||
const CellRectSpec *getCellRectSpec(const Common::Point &coord) const;
|
||||
bool isCellDownLinkOpen(const Common::Point &coord) const;
|
||||
bool isCellRightLinkOpen(const Common::Point &coord) const;
|
||||
|
||||
private:
|
||||
enum LinkState {
|
||||
kLinkStateOpen,
|
||||
kLinkStateConnected,
|
||||
kLinkStateBlocked,
|
||||
};
|
||||
|
||||
enum Direction {
|
||||
kDirectionUp,
|
||||
KDirectionDown,
|
||||
kDirectionLeft,
|
||||
kDirectionRight,
|
||||
|
||||
kDirectionCount,
|
||||
};
|
||||
|
||||
struct CellState {
|
||||
CellState();
|
||||
|
||||
LinkState _downLink;
|
||||
LinkState _rightLink;
|
||||
};
|
||||
|
||||
struct Action {
|
||||
Action();
|
||||
|
||||
Common::Point _point;
|
||||
CellDirection _direction;
|
||||
};
|
||||
|
||||
static Common::Point getConnectedPoint(const Common::Point &coord, Direction direction);
|
||||
LinkState *getConnectionState(const Common::Point &coord, Direction direction);
|
||||
const LinkState *getConnectionState(const Common::Point &coord, Direction direction) const;
|
||||
static bool isPositionValid(const Common::Point &coord);
|
||||
|
||||
void computeStepsToReach(CircuitPuzzleAIEvaluator &evaluator) const;
|
||||
void floodFillLinks(Common::Point *pointsList, uint &listSize, CircuitPuzzleVisitedSet &visitedSet) const;
|
||||
|
||||
static void validateCoord(const Common::Point &coord);
|
||||
|
||||
CellState _cells[kBoardWidth][kBoardHeight];
|
||||
CellRectSpec _cellRectSpecs[kBoardWidth][kBoardHeight];
|
||||
Common::Point _startPoint;
|
||||
Common::Point _goalPoint;
|
||||
|
||||
bool _havePreviousAction;
|
||||
Action _previousAction;
|
||||
};
|
||||
|
||||
} // End of namespace VCruise
|
||||
|
||||
#endif
|
@ -2,10 +2,11 @@ MODULE := engines/vcruise
|
||||
|
||||
MODULE_OBJS = \
|
||||
audio_player.o \
|
||||
sampleloop.o \
|
||||
circuitpuzzle.o \
|
||||
metaengine.o \
|
||||
menu.o \
|
||||
runtime.o \
|
||||
sampleloop.o \
|
||||
script.o \
|
||||
textparser.o \
|
||||
vcruise.o
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include "gui/message.h"
|
||||
|
||||
#include "vcruise/audio_player.h"
|
||||
#include "vcruise/circuitpuzzle.h"
|
||||
#include "vcruise/sampleloop.h"
|
||||
#include "vcruise/menu.h"
|
||||
#include "vcruise/runtime.h"
|
||||
@ -1012,7 +1013,8 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
|
||||
_panoramaDirectionFlags(0),
|
||||
_loadedAnimation(0), _loadedAnimationHasSound(false), _animTerminateAtStartOfFrame(true), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0), _animVolume(getDefaultSoundVolume()),
|
||||
_animStartTime(0), _animFramesDecoded(0), _animDecoderState(kAnimDecoderStateStopped),
|
||||
_animPlayWhileIdle(false), _idleLockInteractions(false), _idleIsOnInteraction(false), _idleHaveClickInteraction(false), _idleHaveDragInteraction(false), _idleInteractionID(0), _haveIdleStaticAnimation(false),
|
||||
_animPlayWhileIdle(false), _idleLockInteractions(false), _idleIsOnInteraction(false), _idleIsOnOpenCircuitPuzzleLink(false), _idleIsCircuitPuzzleLinkDown(false),
|
||||
_idleHaveClickInteraction(false), _idleHaveDragInteraction(false), _idleInteractionID(0), _haveIdleStaticAnimation(false),
|
||||
_inGameMenuState(kInGameMenuStateInvisible), _inGameMenuActiveElement(0), _inGameMenuButtonActive {false, false, false, false, false},
|
||||
_lmbDown(false), _lmbDragging(false), _lmbReleaseWasClick(false), _lmbDownTime(0), _lmbDragTolerance(0),
|
||||
_delayCompletionTime(0),
|
||||
@ -3140,7 +3142,33 @@ bool Runtime::dischargeIdleMouseMove() {
|
||||
resetInventoryHighlights();
|
||||
}
|
||||
|
||||
if (isOnInteraction && _idleIsOnInteraction == false) {
|
||||
bool changedCircuitState = false;
|
||||
bool isOnCircuitLink = false;
|
||||
bool isCircuitLinkDown = false;
|
||||
Common::Point circuitCoord;
|
||||
if (_circuitPuzzle) {
|
||||
isOnCircuitLink = resolveCircuitPuzzleInteraction(relMouse, circuitCoord, isCircuitLinkDown);
|
||||
|
||||
if (isOnCircuitLink != _idleIsOnOpenCircuitPuzzleLink)
|
||||
changedCircuitState = true;
|
||||
else {
|
||||
if (isOnCircuitLink && (circuitCoord != _idleCircuitPuzzleCoord || isCircuitLinkDown != _idleIsCircuitPuzzleLinkDown))
|
||||
changedCircuitState = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changedCircuitState) {
|
||||
_idleIsOnOpenCircuitPuzzleLink = isOnCircuitLink;
|
||||
if (isOnCircuitLink) {
|
||||
_idleCircuitPuzzleCoord = circuitCoord;
|
||||
_idleIsCircuitPuzzleLinkDown = isCircuitLinkDown;
|
||||
} else {
|
||||
_idleCircuitPuzzleCoord = Common::Point(0, 0);
|
||||
_idleIsCircuitPuzzleLinkDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isOnInteraction && (_idleIsOnInteraction == false || changedCircuitState)) {
|
||||
_idleIsOnInteraction = true;
|
||||
_idleInteractionID = interactionID;
|
||||
|
||||
@ -5006,6 +5034,45 @@ const Graphics::Font *Runtime::resolveFont(const Common::String &textStyle, uint
|
||||
return fcItem->font;
|
||||
}
|
||||
|
||||
bool Runtime::resolveCircuitPuzzleInteraction(const Common::Point &relMouse, Common::Point &outCoord, bool &outIsDown) const {
|
||||
if (!_circuitPuzzle)
|
||||
return false;
|
||||
|
||||
for (uint cy = 0; cy < CircuitPuzzle::kBoardHeight; cy++) {
|
||||
for (uint cx = 0; cx < CircuitPuzzle::kBoardWidth; cx++) {
|
||||
Common::Point cellCoord(cx, cy);
|
||||
|
||||
const CircuitPuzzle::CellRectSpec *rectSpec = _circuitPuzzle->getCellRectSpec(cellCoord);
|
||||
if (_circuitPuzzle->isCellDownLinkOpen(cellCoord)) {
|
||||
if (padCircuitInteractionRect(rectSpec->_downLinkRect).contains(relMouse)) {
|
||||
outCoord = cellCoord;
|
||||
outIsDown = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (_circuitPuzzle->isCellRightLinkOpen(cellCoord)) {
|
||||
if (padCircuitInteractionRect(rectSpec->_rightLinkRect).contains(relMouse)) {
|
||||
outCoord = cellCoord;
|
||||
outIsDown = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::Rect Runtime::padCircuitInteractionRect(const Common::Rect &rect) {
|
||||
Common::Rect result = rect;
|
||||
result.right += 4;
|
||||
result.bottom += 4;
|
||||
result.top -= 3;
|
||||
result.left -= 3;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Runtime::onLButtonDown(int16 x, int16 y) {
|
||||
onMouseMove(x, y);
|
||||
|
||||
@ -7212,12 +7279,112 @@ void Runtime::scriptOpGetDigit(ScriptArg_t arg) {
|
||||
_scriptStack.push_back(StackValue(digit));
|
||||
}
|
||||
|
||||
OPCODE_STUB(PuzzleInit)
|
||||
OPCODE_STUB(PuzzleCanPress)
|
||||
OPCODE_STUB(PuzzleDoMove1)
|
||||
OPCODE_STUB(PuzzleDoMove2)
|
||||
void Runtime::scriptOpPuzzleInit(ScriptArg_t arg) {
|
||||
TAKE_STACK_INT(kAnimDefStackArgs * 2 + 3);
|
||||
|
||||
AnimationDef animDef1 = stackArgsToAnimDef(stackArgs + 0);
|
||||
AnimationDef animDef2 = stackArgsToAnimDef(stackArgs + kAnimDefStackArgs);
|
||||
|
||||
int firstMover = stackArgs[kAnimDefStackArgs * 2 + 0];
|
||||
int firstMover2 = stackArgs[kAnimDefStackArgs * 2 + 1];
|
||||
int unknownParam = stackArgs[kAnimDefStackArgs * 2 + 2];
|
||||
|
||||
if (firstMover != firstMover2 || unknownParam != 0)
|
||||
error("PuzzleInit had a weird parameter");
|
||||
|
||||
if (firstMover == 2)
|
||||
error("AI moving first not implemented yet");
|
||||
|
||||
_circuitPuzzle.reset(new CircuitPuzzle(firstMover));
|
||||
_circuitPuzzleConnectAnimation = animDef1;
|
||||
_circuitPuzzleBlockAnimation = animDef2;
|
||||
}
|
||||
|
||||
void Runtime::scriptOpPuzzleWhoWon(ScriptArg_t arg) {
|
||||
StackInt_t winner = 0;
|
||||
if (_circuitPuzzle) {
|
||||
switch (_circuitPuzzle->checkConclusion()) {
|
||||
case CircuitPuzzle::kConclusionNone:
|
||||
winner = 0;
|
||||
break;
|
||||
case CircuitPuzzle::kConclusionPlayerWon:
|
||||
winner = 1;
|
||||
break;
|
||||
case CircuitPuzzle::kConclusionPlayerLost:
|
||||
winner = 2;
|
||||
break;
|
||||
default:
|
||||
error("Unhandled puzzle conclusion");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_scriptStack.push_back(StackValue(winner));
|
||||
}
|
||||
|
||||
void Runtime::scriptOpPuzzleCanPress(ScriptArg_t arg) {
|
||||
_scriptStack.push_back(StackValue(_idleIsOnOpenCircuitPuzzleLink ? 1 : 0));
|
||||
}
|
||||
|
||||
void Runtime::scriptOpPuzzleDoMove1(ScriptArg_t arg) {
|
||||
if (!_idleIsOnOpenCircuitPuzzleLink)
|
||||
error("Attempted puzzleDoMove1 but don't have a circuit point");
|
||||
|
||||
if (!_circuitPuzzle)
|
||||
error("Attempted puzzleDoMove1 but the circuit puzzle is gone");
|
||||
|
||||
_circuitPuzzle->addLink(_idleCircuitPuzzleCoord, _idleIsCircuitPuzzleLinkDown ? CircuitPuzzle::kCellDirectionDown : CircuitPuzzle::kCellDirectionRight);
|
||||
|
||||
SoundInstance *snd = nullptr;
|
||||
StackInt_t soundID = 0;
|
||||
resolveSoundByName("85_connect", true, soundID, snd);
|
||||
|
||||
if (snd)
|
||||
triggerSound(kSoundLoopBehaviorNo, *snd, 0, 0, false, false);
|
||||
|
||||
const CircuitPuzzle::CellRectSpec *rectSpec = _circuitPuzzle->getCellRectSpec(_idleCircuitPuzzleCoord);
|
||||
|
||||
if (rectSpec) {
|
||||
AnimationDef animDef = _circuitPuzzleConnectAnimation;
|
||||
animDef.constraintRect = _idleIsCircuitPuzzleLinkDown ? rectSpec->_downLinkRect : rectSpec->_rightLinkRect;
|
||||
|
||||
changeAnimation(animDef, false);
|
||||
|
||||
_gameState = kGameStateWaitingForAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
void Runtime::scriptOpPuzzleDoMove2(ScriptArg_t arg) {
|
||||
if (!_circuitPuzzle)
|
||||
error("Attempted puzzleDoMove2 but the circuit puzzle is gone");
|
||||
|
||||
CircuitPuzzle::CellDirection actionDirection = CircuitPuzzle::kCellDirectionDown;
|
||||
Common::Point actionCoord;
|
||||
|
||||
if (_circuitPuzzle->executeAIAction(*_rng, actionCoord, actionDirection)) {
|
||||
SoundInstance *snd = nullptr;
|
||||
StackInt_t soundID = 0;
|
||||
resolveSoundByName("85_block", true, soundID, snd);
|
||||
|
||||
if (snd)
|
||||
triggerSound(kSoundLoopBehaviorNo, *snd, 0, 0, false, false);
|
||||
|
||||
const CircuitPuzzle::CellRectSpec *rectSpec = _circuitPuzzle->getCellRectSpec(actionCoord);
|
||||
|
||||
if (rectSpec) {
|
||||
AnimationDef animDef = _circuitPuzzleBlockAnimation;
|
||||
animDef.constraintRect = (actionDirection == CircuitPuzzle::kCellDirectionDown) ? rectSpec->_downBarrierRect : rectSpec->_rightBarrierRect;
|
||||
|
||||
changeAnimation(animDef, false);
|
||||
|
||||
_gameState = kGameStateWaitingForAnimation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OPCODE_STUB(PuzzleDone)
|
||||
OPCODE_STUB(PuzzleWhoWon)
|
||||
|
||||
|
||||
OPCODE_STUB(Fn)
|
||||
|
||||
#undef TAKE_STACK_STR
|
||||
|
@ -76,6 +76,7 @@ enum StartConfig {
|
||||
};
|
||||
|
||||
class AudioPlayer;
|
||||
class CircuitPuzzle;
|
||||
class MenuInterface;
|
||||
class MenuPage;
|
||||
class RuntimeMenuInterface;
|
||||
@ -903,6 +904,9 @@ private:
|
||||
|
||||
const Graphics::Font *resolveFont(const Common::String &textStyle, uint size);
|
||||
|
||||
bool resolveCircuitPuzzleInteraction(const Common::Point &relMouse, Common::Point &outCoord, bool &outIsDown) const;
|
||||
static Common::Rect padCircuitInteractionRect(const Common::Rect &rect);
|
||||
|
||||
// Script things
|
||||
void scriptOpNumber(ScriptArg_t arg);
|
||||
void scriptOpRotate(ScriptArg_t arg);
|
||||
@ -1131,6 +1135,10 @@ private:
|
||||
|
||||
AnimationDef _postFacingAnimDef;
|
||||
|
||||
Common::SharedPtr<CircuitPuzzle> _circuitPuzzle;
|
||||
AnimationDef _circuitPuzzleBlockAnimation;
|
||||
AnimationDef _circuitPuzzleConnectAnimation;
|
||||
|
||||
Common::HashMap<uint32, int32> _variables;
|
||||
Common::HashMap<uint, uint32> _timers;
|
||||
|
||||
@ -1228,6 +1236,10 @@ private:
|
||||
bool _idleHaveDragInteraction;
|
||||
uint _idleInteractionID;
|
||||
|
||||
bool _idleIsOnOpenCircuitPuzzleLink;
|
||||
bool _idleIsCircuitPuzzleLinkDown;
|
||||
Common::Point _idleCircuitPuzzleCoord;
|
||||
|
||||
InGameMenuState _inGameMenuState;
|
||||
uint _inGameMenuActiveElement;
|
||||
bool _inGameMenuButtonActive[5];
|
||||
|
Loading…
x
Reference in New Issue
Block a user