/* 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 . * */ #include "common/debug.h" #include "common/memstream.h" #include "qdengine/qdengine.h" #include "qdengine/minigames/adv/m_triangles.h" #include "qdengine/minigames/adv/RunTime.h" #include "qdengine/minigames/adv/EventManager.h" #include "qdengine/minigames/adv/qdMath.h" namespace QDEngine { MinigameInterface *createMinigameTriangle(MinigameManager *runtime) { return new MinigameTriangle(runtime); } enum { EVENT_TURN, EVENT_GET_RIGHT, EVENT_PUT_RIGHT }; MinigameTriangle::Node::Node(int number, int rot) { _number = number; _rotation = rot; _isBack = false; _highlight = false; _animated = false; _flip = 0; } void MinigameTriangle::Node::release(MinigameManager *runtime) { for (auto &it : _face) runtime->release(it); } bool MinigameTriangle::Node::hit(const mgVect2f &pos) const { return obj().hit(pos); } MinigameTriangle::MinigameTriangle(MinigameManager *runtime) { _runtime = runtime; int type = 0; if (!_runtime->getParameter("game_type", type, true)) return; switch (type) { case 1: _gameType = RECTANGLE; break; case 2: _gameType = HEXAGON; break; default: _gameType = TRIANGLE; } _fieldLines = _fieldWidth = 0; if (!_runtime->getParameter("size", _fieldLines, true)) return; if (_fieldLines < 2) return; if (_gameType == RECTANGLE) { if (!_runtime->getParameter("width", _fieldWidth, true)) return; if (_fieldWidth < 2) return; } switch (_gameType) { case TRIANGLE: _fieldSize = sqr(_fieldLines); break; case RECTANGLE: _fieldSize = _fieldLines * _fieldWidth; break; case HEXAGON: assert(_fieldLines % 2 == 0); if (_fieldLines % 2 != 0) return; _fieldSize = 3 * sqr(_fieldLines) / 2; break; } if (!_runtime->getParameter("animation_time", _animationTime, true)) return; _quickReselect = _runtime->getParameter("quick_reselect", false); const char *faceNameBegin = _runtime->parameter("object_name_begin", "obj_"); const char *backNameBegin = _runtime->parameter("backg_name_begin", "element__back"); const char *selectNameBegin = _runtime->parameter("select_name_begin", "element_select_"); char name[64]; name[63] = 0; for (int num = 0; num < _fieldSize; ++num) { _nodes.push_back(Node(num, 0)); Node &node = _nodes.back(); for (int angle = 1; angle <= 3; ++angle) { snprintf(name, 63, "%s%02d_%1d", faceNameBegin, num + 1, angle); debugC(5, kDebugMinigames, "Loading face object: %s", name); QDObject obj = _runtime->getObject(name); node._face.push_back(obj); _positions.push_back(obj->R()); } } Common::MemoryReadWriteStream gameData(DisposeAfterUse::YES); for (auto &it : _positions) it.write(gameData); if (!_runtime->processGameData(gameData)) return; GameInfo *gameInfo = _runtime->getCurrentGameInfo(); if (gameInfo) { Common::MemoryReadStream buf((byte *)gameInfo->_gameData, gameInfo->_dataSize); for (auto &it : _positions) it.read(buf); } else { for (auto &it : _positions) it.read(gameData); } for (int num = 1; num <= 2; ++num) { for (int angle = 1; angle <= 3; ++angle) { snprintf(name, 63, "%s%1d_%1d", backNameBegin, num, angle); debugC(5, kDebugMinigames, "Loading back object: %s", name); if (!_backSides[(num - 1) * 3 + angle - 1].load(name, _runtime)) return; } snprintf(name, 63, "%s%1d", selectNameBegin, num); debugC(5, kDebugMinigames, "Loading select object: %s", name); if (!_selectBorders[num - 1].load(name, _runtime)) return; } _selectDepth = _nodes[0]._face[0].depth(_runtime) - 1000; _selected = -1; _hovered = -1; _animationState = NO_ANIMATION; _animatedNodes[0] = _animatedNodes[1] = -1; _animationTimer = 0.f; if (!_runtime->debugMode()) for (int i = 0; i < 150; ++i) { int pos1 = _runtime->rnd(0, _nodes.size() - 1); for (int j = 0; j < 20; ++j) { int pos2 = _runtime->rnd(pos1 - 10, pos1 + 10); if (compatible(pos1, pos2)) { swapNodes(pos1, pos2, true); break; } } } for (int idx = 0; idx < _fieldSize; ++idx) updateNode(_nodes[idx], idx); setState(RUNNING); } MinigameTriangle::~MinigameTriangle() { for (auto &it : _nodes) it.release(_runtime); for (int idx = 0; idx < 2; ++idx) _selectBorders[idx].release(_runtime); for (int idx = 0; idx < 6; ++idx) _backSides[idx].release(_runtime); } void MinigameTriangle::Node::debugInfo() const { debugC(5, kDebugMinigames, "name:\"%s\" state:\"%s\" number:%d rotation:%d flip:%d isBack:%d highlight:%d animated:%d", obj().getName(), obj()->current_state_name(), _number, _rotation, _flip, _isBack, _highlight, _animated); } const Common::String MinigameTriangle::Node::getFaceStateName(int angle, bool selected, bool animated, bool instantaneous) { assert(!selected || !animated); // анимированные выделенными быть не могут static const char *angleNames[3] = {"0", "120", "240"}; assert(angle >= 0 && angle < ARRAYSIZE(angleNames)); Common::String out; out = Common::String::format("%s%s%s", (animated ? "02_" : "01_"), angleNames[angle], (selected || instantaneous ? "_sel" : "")); return out; } const char *MinigameTriangle::Node::getBackStateName(bool selected, bool animated, bool instantaneous) { assert(!selected || !animated); // анимированные выделенными быть не могут if (animated) return selected || instantaneous ? "02_sel" : "02"; else return selected || instantaneous ? "01_sel" : "01"; } const char *MinigameTriangle::Node::getBorderStateName(bool selected) { return selected ? "01" : "02"; } void MinigameTriangle::releaseNodeBack(Node &node) { if (node._back) { node._back.setState(Node::getBackStateName(false, false, false)); for (int type = 0; type < 6; ++type) _backSides[type].releaseObject(node._back, _runtime); } } void MinigameTriangle::updateNode(Node &node, int position, int flip, bool quick) { for (auto &fit : node._face) _runtime->hide(fit); node._flip = flip; if (node._isBack) { if (!node._back) node._back = _backSides[orientation(position) * 3 + flip].getObject(); node._back->set_R(slotCoord(position, flip)); node._back->update_screen_R(); node._back.setState(Node::getBackStateName(node._highlight, node._animated, quick)); } else { releaseNodeBack(node); QDObject &face = node._face[flip]; face->set_R(slotCoord(position, flip)); face->update_screen_R(); face.setState(Node::getFaceStateName(node._rotation, node._highlight, node._animated, quick).c_str()); } } void MinigameTriangle::highlight(int idx, bool hl) { if (idx >= 0) { assert(idx < (int)_nodes.size()); _nodes[idx]._highlight = hl; updateNode(_nodes[idx], idx); } } void MinigameTriangle::beginSwapNodes(int pos1, int pos2) { assert(compatible(pos1, pos2)); if (pos1 > pos2) SWAP(pos1, pos2); _animationState = FIRST_PHASE; _animationTimer = _animationTime; _animatedNodes[0] = pos1; _animatedNodes[1] = pos2; Node &node1 = _nodes[pos1]; Node &node2 = _nodes[pos2]; node1._animated = true; node2._animated = true; releaseNodeBack(node1); releaseNodeBack(node2); updateNode(node1, pos1, destination(pos1, pos2)); updateNode(node2, pos2, destination(pos1, pos2)); debugC(5, kDebugMinigames, ">>>>>>>>>>>>>>>>>>>>>>>>>>> change %d <> %d, 1st phase <<<<<<<<<<<<<<<<<<<<<<<<<<<<", pos1, pos2); _nodes[pos1].debugInfo(); _nodes[pos2].debugInfo(); } void MinigameTriangle::endSwapNodes(int pos1, int pos2) { Node &node1 = _nodes[pos1]; Node &node2 = _nodes[pos2]; bool counted = false; if (node1._number == pos1) { // поставили на свое место assert(!node1._isBack); counted = true; _runtime->event(EVENT_PUT_RIGHT, node1.obj()->screen_R()); } if (node2._number == pos1) { // сняли со своего места assert(node2._isBack); counted = true; _runtime->event(EVENT_GET_RIGHT, node1.obj()->screen_R()); } if (node2._number == pos2) { // поставили на свое место assert(!node2._isBack); counted = true; _runtime->event(EVENT_PUT_RIGHT, node2.obj()->screen_R()); } if (node1._number == pos2) { // сняли со своего места assert(node1._isBack); counted = true; _runtime->event(EVENT_GET_RIGHT, node2.obj()->screen_R()); } if (!counted) { // просто сделали ход mgVect2i pos = node1.obj()->screen_R(); pos += node2.obj()->screen_R(); pos /= 2; _runtime->event(EVENT_TURN, pos); } bool isWin = true; int position = 0; for (auto &it : _nodes) { if (it._number != position++) { isWin = false; break; } } if (isWin) { setState(GAME_WIN); return; } } bool MinigameTriangle::animate(float dt) { if (_animationState == NO_ANIMATION) return false; _animationTimer -= dt; if (_animationTimer > 0) return true; Node &node1 = _nodes[_animatedNodes[0]]; Node &node2 = _nodes[_animatedNodes[1]]; switch (_animationState) { case FIRST_PHASE: { node1._rotation = getRotate(_animatedNodes[0], _animatedNodes[1]); node2._rotation = getRotate(_animatedNodes[1], _animatedNodes[0]); node1._isBack = !node1._isBack; node2._isBack = !node2._isBack; releaseNodeBack(node1); releaseNodeBack(node2); for (auto &it : node1._face) it.setState(Node::getFaceStateName(0, false, false, false).c_str()); for (auto &it : node2._face) it.setState(Node::getFaceStateName(0, false, false, false).c_str()); updateNode(node1, _animatedNodes[1], destination(_animatedNodes[0], _animatedNodes[1]), true); updateNode(node2, _animatedNodes[0], destination(_animatedNodes[1], _animatedNodes[0]), true); _animationTimer = 0.f; _animationState = SECOND_PHASE; debugC(5, kDebugMinigames, ">>>>>>>>>>>>>>>>>>>>>>>>>>> change %d <> %d, 2nd phase 1 <<<<<<<<<<<<<<<<<<<<<<<<<<<<", _animatedNodes[0], _animatedNodes[1]); node1.debugInfo(); node2.debugInfo(); return true; } case SECOND_PHASE: node1._animated = false; node2._animated = false; updateNode(node1, _animatedNodes[1], destination(_animatedNodes[0], _animatedNodes[1])); updateNode(node2, _animatedNodes[0], destination(_animatedNodes[1], _animatedNodes[0])); SWAP(node1, node2); _animationTimer = _animationTime; _animationState = FIRD_PHASE; debugC(5, kDebugMinigames, ">>>>>>>>>>>>>>>>>>>>>>>>>>> change %d <> %d, 2nd phase 2 <<<<<<<<<<<<<<<<<<<<<<<<<<<<", _animatedNodes[0], _animatedNodes[1]); node2.debugInfo(); node1.debugInfo(); return true; case FIRD_PHASE: _animationTimer = 0.f; _animationState = NO_ANIMATION; releaseNodeBack(node1); releaseNodeBack(node2); updateNode(node1, _animatedNodes[0]); updateNode(node2, _animatedNodes[1]); endSwapNodes(_animatedNodes[0], _animatedNodes[1]); debugC(5, kDebugMinigames, "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ change %d <> %d, finished ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^", _animatedNodes[0], _animatedNodes[1]); _animatedNodes[0] = -1; _animatedNodes[1] = -1; return true; default: break; } return false; } void MinigameTriangle::swapNodes(int pos1, int pos2, bool silentQuick) { if (silentQuick) { Node &node1 = _nodes[pos1]; Node &node2 = _nodes[pos2]; node1._rotation = getRotate(pos1, pos2); node2._rotation = getRotate(pos2, pos1); node1._isBack = !node1._isBack; node2._isBack = !node2._isBack; releaseNodeBack(node1); releaseNodeBack(node2); SWAP(node1, node2); updateNode(node1, pos1, 0, true); updateNode(node2, pos2, 0, true); } else beginSwapNodes(pos1, pos2); } void MinigameTriangle::quant(float dt) { if (_selected >= 0) _runtime->setGameHelpVariant(0); else _runtime->setGameHelpVariant(1); if (animate(dt)) return; int mousePos = -1; for (int idx = 0; idx < _fieldSize; ++idx) if (_nodes[idx].hit(_runtime->mousePosition())) { mousePos = idx; break; } int startAnimation = -1; int lastSelected = _selected; if (_runtime->mouseLeftPressed()) { if (mousePos < 0) // кликнули мимо - снимаем выделение _selected = -1; else if (_selected < 0) // ничего выделено небыло, просто выделяем _selected = mousePos; else if (_selected == mousePos) // кликнули на выделенном - снимаем выделение _selected = -1; else if (compatible(_selected, mousePos)) { // поменять фишки местами startAnimation = _selected; _selected = -1; } else { _selected = -1; if (_quickReselect) _selected = mousePos; } } if (_selected != lastSelected) { for (int idx = 0; idx < _fieldSize; ++idx) { Node &node = _nodes[idx]; if (idx == _selected || compatible(_selected, idx)) { // с этой фишкой можно поменяться if (!node._border) node._border = _selectBorders[orientation(idx)].getObject(); node._border.setState(Node::getBorderStateName(idx == _selected)); node._border->set_R(slotCoord(idx)); node._border->update_screen_R(); _runtime->setDepth(node._border, _selectDepth); } else if (node._border) { _selectBorders[0].releaseObject(node._border, _runtime); _selectBorders[1].releaseObject(node._border, _runtime); } } } if (_hovered != mousePos || _selected != lastSelected) { highlight(_hovered, false); highlight(_selected >= 0 ? _selected : lastSelected, false); _hovered = mousePos; if (_hovered >= 0 && startAnimation < 0) { if (_selected >= 0) { if (compatible(_selected, _hovered)) { highlight(_hovered, true); highlight(_selected, true); } } else highlight(_hovered, true); } } if (startAnimation >= 0) { _hovered = -1; swapNodes(startAnimation, mousePos, false); } if (_runtime->mouseRightPressed() && mousePos >= 0) { debugC(2, kDebugMinigames, "----- DUBUG INFO FOR %d POSITION --------------------", mousePos); debugC(2, kDebugMinigames, "row = %d, begin = %d, orientation = %d", rowByNum(mousePos), rowBegin(rowByNum(mousePos)), orientation(mousePos)); _nodes[mousePos].debugInfo(); } } int MinigameTriangle::rowBegin(int row) const { if (row == _fieldLines) return _fieldSize; switch (_gameType) { case TRIANGLE: return sqr(row); case RECTANGLE: return row * _fieldWidth; default: break; } //case HEXAGON: assert(row >= 0 && row < _fieldLines); if (row >= _fieldLines / 2) { row -= _fieldLines / 2; return _fieldSize / 2 + (2 * _fieldLines - row) * row; } return (_fieldLines + row) * row; } int MinigameTriangle::rowByNum(int num) const { if (num >= _fieldSize) return _fieldLines; switch (_gameType) { case TRIANGLE: return floor(sqrt((float)num)); case RECTANGLE: return num / _fieldWidth; default: break; } //case HEXAGON: int row = num < _fieldSize / 2 ? 0 : _fieldLines / 2; while (row < _fieldLines && num >= rowBegin(row)) ++row; return row > 0 ? row - 1 : 0; } int MinigameTriangle::orientation(int num) const { switch (_gameType) { case TRIANGLE: return (rowByNum(num) + num) % 2; case RECTANGLE: return num % 2; default: break; } //case HEXAGON: return (num + rowByNum(num) + (num >= _fieldSize / 2 ? 1 : 0)) % 2; } bool MinigameTriangle::compatible(int num1, int num2) const { if (num1 > num2) SWAP(num1, num2); if (num1 < 0) return false; int row1 = rowByNum(num1); int row2 = rowByNum(num2); if (row2 >= _fieldLines) return false; if (row1 == row2) // в одном слое return num2 - num1 == 1; // должны быть рядом else if (row2 - row1 != 1) // или на соседних слоях return false; else if (orientation(num1) != 0) // широкими сторонами друг к другу return false; int center1 = (rowBegin(row1) + rowBegin(row1 + 1) - 1) / 2; int center2 = (rowBegin(row2) + rowBegin(row2 + 1) - 1) / 2; return center1 - num1 == center2 - num2; // и точно друг под другом } int MinigameTriangle::getRotate(int num1, int num2) const { static int solves[3][2][3] = { {{0, 2, 1}, {0, 2, 1}}, {{2, 1, 0}, {1, 0, 2}}, {{1, 0, 2}, {2, 1, 0}} }; assert(compatible(num1, num2)); return solves[rowByNum(num1) != rowByNum(num2) ? 0 : (num2 < num1 ? 1 : 2)] [orientation(num1)][_nodes[num1]._rotation]; } int MinigameTriangle::destination(int num1, int num2) const { if (orientation(num1) == 0) return rowByNum(num1) != rowByNum(num2) ? 0 : (num2 < num1 ? 1 : 2); else return rowByNum(num1) != rowByNum(num2) ? 0 : (num2 < num1 ? 2 : 1); } mgVect3f MinigameTriangle::slotCoord(int pos, int angle) const { assert(pos * 3 + angle < (int)_positions.size()); return _positions[pos * 3 + angle]; } } // namespace QDEngine