[Scummvm-git-logs] scummvm master -> 6acd8cb65c5d56a2fe7674da44df8820f73b72f8

sev- noreply at scummvm.org
Thu Oct 17 19:50:33 UTC 2024


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
6acd8cb65c QDENGINE: Added code for "advanced" minigames


Commit: 6acd8cb65c5d56a2fe7674da44df8820f73b72f8
    https://github.com/scummvm/scummvm/commit/6acd8cb65c5d56a2fe7674da44df8820f73b72f8
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2024-10-17T21:49:50+02:00

Commit Message:
QDENGINE: Added code for "advanced" minigames

The code is added as is, with the following simple transformations:

 o windows-1251 -> utf8
 o astyle
 o Added copyright header
 o Added namespace
 o Fixed include paths
 o Removed references to the system-wide includes
 o Fixed guard defines

Also, couple of obviously redundant files were removed.

The files contain unused code and are not yet plugged into the build
system. This is the beginning for traceability of changes.

Changed paths:
  A engines/qdengine/minigames/adv/EffectManager.cpp
  A engines/qdengine/minigames/adv/EffectManager.h
  A engines/qdengine/minigames/adv/EventManager.cpp
  A engines/qdengine/minigames/adv/EventManager.h
  A engines/qdengine/minigames/adv/ExportInterface.cpp
  A engines/qdengine/minigames/adv/FlyObject.cpp
  A engines/qdengine/minigames/adv/FlyObject.h
  A engines/qdengine/minigames/adv/HoldData.h
  A engines/qdengine/minigames/adv/MinigameInterface.h
  A engines/qdengine/minigames/adv/ObjectContainer.cpp
  A engines/qdengine/minigames/adv/ObjectContainer.h
  A engines/qdengine/minigames/adv/Range.cpp
  A engines/qdengine/minigames/adv/Range.h
  A engines/qdengine/minigames/adv/Rect.h
  A engines/qdengine/minigames/adv/RunTime.cpp
  A engines/qdengine/minigames/adv/RunTime.h
  A engines/qdengine/minigames/adv/TextManager.cpp
  A engines/qdengine/minigames/adv/TextManager.h
  A engines/qdengine/minigames/adv/common.cpp
  A engines/qdengine/minigames/adv/common.h
  A engines/qdengine/minigames/adv/m_karaoke.cpp
  A engines/qdengine/minigames/adv/m_karaoke.h
  A engines/qdengine/minigames/adv/m_puzzle.cpp
  A engines/qdengine/minigames/adv/m_puzzle.h
  A engines/qdengine/minigames/adv/m_scores.cpp
  A engines/qdengine/minigames/adv/m_scores.h
  A engines/qdengine/minigames/adv/m_swap.cpp
  A engines/qdengine/minigames/adv/m_swap.h
  A engines/qdengine/minigames/adv/m_triangles.cpp
  A engines/qdengine/minigames/adv/m_triangles.h
  A engines/qdengine/minigames/adv/qdMath.h


diff --git a/engines/qdengine/minigames/adv/EffectManager.cpp b/engines/qdengine/minigames/adv/EffectManager.cpp
new file mode 100644
index 00000000000..8a8c745bf61
--- /dev/null
+++ b/engines/qdengine/minigames/adv/EffectManager.cpp
@@ -0,0 +1,92 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/EffectManager.h"
+#include "qdengine/minigames/adv/qdMath.h"
+
+namespace QDEngine {
+
+EffectManager::EffectManager(HoldData<EffectManagerData> &data) {
+	const char *effectName = runtime->parameter("effect_name", "effect");
+	if (runtime->testObject(effectName)) {
+		effect_ = runtime->getObject(effectName);
+		data_.crd = effect_->R();
+		effect_->set_screen_scale(mgVect2f(0.01f, 0.01f), mgVect2f(10000.f, 10000.f));
+		runtime->hide(effect_);
+	}
+
+	data.process(data_);
+
+	effectTime_ = clamp(getParameter("effect_time", 3.f), 0.5f, 10.f);
+	phaseTime_ = clamp(getParameter("effect_phase_time", effectTime_ / 20.f), 0.03f, 1.f);
+	phaseSpeed_ = clamp(getParameter("effect_phase_speed", 1.5f), 1.05f, 10.f);
+
+	current_ = EFFECT_COUNT;
+
+}
+
+EffectManager::~EffectManager() {
+	runtime->release(effect_);
+
+}
+
+void EffectManager::quant(float dt) {
+	if (current_ == EFFECT_COUNT)
+		return;
+
+	if (runtime->time() > effectTimer_) {
+		stop(current_);
+		return;
+	}
+
+	if (runtime->time() > phaseTimer_) {
+		phaseTimer_ = runtime->time() + phaseTime_;
+		mgVect2f scale = effect_->screen_scale();
+		mgVect2f speed = scale;
+		scale *= phaseSpeed_;
+		speed = scale - speed;
+		speed /= phaseTime_;
+		effect_->set_screen_scale(scale, speed);
+	}
+
+}
+
+void EffectManager::start(EffectType id) {
+	if (current_ != EFFECT_COUNT || !effect_)
+		return;
+	effectTimer_ = runtime->time() + effectTime_;
+	current_ = id;
+	phaseTimer_ = runtime->time();
+	effect_->set_screen_scale(mgVect2f(0.02f, 0.02f), mgVect2f(10000.f, 10000.f));
+	effect_->set_R(data_.crd);
+
+}
+
+void EffectManager::stop(EffectType id) {
+	if (current_ == EFFECT_COUNT)
+		return;
+	runtime->hide(effect_);
+	effect_->set_screen_scale(mgVect2f(0.01f, 0.01f), mgVect2f(10000.f, 10000.f));
+	current_ = EFFECT_COUNT;
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/EffectManager.h b/engines/qdengine/minigames/adv/EffectManager.h
new file mode 100644
index 00000000000..c1a8bec78a3
--- /dev/null
+++ b/engines/qdengine/minigames/adv/EffectManager.h
@@ -0,0 +1,60 @@
+/* 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 QDENGINE_MINIGAMES_ADV_EFFECT_MANAGER_H
+#define QDENGINE_MINIGAMES_ADV_EFFECT_MANAGER_H
+
+#include "qdengine/minigames/adv/RunTime.h"
+#include "qdengine/minigames/adv/HoldData.h"
+
+namespace QDEngine {
+
+enum EffectType {
+	EFFECT_1,
+	EFFECT_COUNT
+};
+
+class EffectManager {
+public:
+	EffectManager(HoldData<EffectManagerData> &data);
+	~EffectManager();
+
+	void quant(float dt);
+
+	void start(EffectType id);
+	void stop(EffectType id);
+
+private:
+	EffectType current_;
+	EffectManagerData data_;
+	float phaseTime_;
+	float effectTime_;
+	float phaseSpeed_;
+
+	float effectTimer_;
+	float phaseTimer_;
+	QDObject effect_;
+
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_EFFECT_MANAGER_H
diff --git a/engines/qdengine/minigames/adv/EventManager.cpp b/engines/qdengine/minigames/adv/EventManager.cpp
new file mode 100644
index 00000000000..35b68e7208d
--- /dev/null
+++ b/engines/qdengine/minigames/adv/EventManager.cpp
@@ -0,0 +1,137 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/EventManager.h"
+#include "qdengine/minigames/adv/RunTime.h"
+#include "qdengine/minigames/adv/TextManager.h"
+
+namespace QDEngine {
+
+EventManager::EventPreset::EventPreset() {
+	score = 0;
+	fontID = -1;
+	escapeID = -1;
+	triggerEventID = -1;
+}
+
+EventManager::EventManager() {
+	score_ = 0;
+
+	char str_cache[256];
+
+	for (int idx = 0;; ++idx) {
+		_snprintf(str_cache, 127, "register_trigger_%d", idx);
+		if (const char * descr = runtime->parameter(str_cache, false))
+			triggerEvents_.push_back(runtime->getObject(descr));
+		else
+			break;
+	}
+	dprintf("registered %d trigger objects\n", triggerEvents_.size());
+
+	eventPresets_.resize(SYSTEM_EVENTS_SIZE);
+	for (int idx = 0; idx < SYSTEM_EVENTS_SIZE; ++idx) {
+		_snprintf(str_cache, 127, "system_event_%d", idx);
+		if (const char * descr = runtime->parameter(str_cache, false)) {
+			EventPreset preset;
+			int read = sscanf(descr, "%d %d", &preset.score, &preset.triggerEventID);
+			xxassert(read == 2, (XBuffer() < "Неверная строка для описания" < str_cache).c_str());
+			if (read == 2) {
+				xxassert(preset.triggerEventID < (int)triggerEvents_.size(), (XBuffer() < "Ссылка на незарегистрированный триггер в " < str_cache).c_str());
+				if (preset.triggerEventID < (int)triggerEvents_.size())
+					eventPresets_[idx] = preset;
+			}
+		}
+	}
+
+	for (int idx = 0;; ++idx) {
+		_snprintf(str_cache, 127, "register_event_%d", idx);
+		if (const char * descr = runtime->parameter(str_cache, false)) {
+			EventPreset preset;
+			int read = sscanf(descr, "%d %d %d %d", &preset.score, &preset.fontID, &preset.escapeID, &preset.triggerEventID);
+			xxassert(read == 4, (XBuffer() < "Неверная строка для описания события " < idx).c_str());
+			xxassert(preset.triggerEventID < (int)triggerEvents_.size(), (XBuffer() < "Ссылка на незарегистрированный триггер в " < str_cache).c_str());
+			if (read == 4 && preset.triggerEventID < (int)triggerEvents_.size())
+				eventPresets_.push_back(preset);
+			else
+				eventPresets_.push_back(EventPreset());
+		} else
+			break;
+	}
+	dprintf("registered %d events\n", eventPresets_.size());
+
+	if (const char * data = runtime->parameter("allow_negative", false)) {
+		int tmp;
+		sscanf(data, "%d", &tmp);
+		enableNegative_ = tmp;
+	} else
+		enableNegative_ = false;
+}
+
+void EventManager::sysEvent(int eventID) {
+	xassert(eventID >= 0);
+	//dprintf("System event: %d\n", eventID);
+
+	xassert(eventID < SYSTEM_EVENTS_SIZE);
+
+	mgVect2i pos = runtime->screenSize() / 2;
+	event(eventID - SYSTEM_EVENTS_SIZE, mgVect2f(pos.x, pos.y), 1);
+}
+
+void EventManager::event(int eventID, const mgVect2f& pos, int factor) {
+	//dprintf("Event: %d, pos=(%5.1f, %5.1f), fartor=%d\n", eventID, pos.x, pos.y, factor);
+
+	eventID += SYSTEM_EVENTS_SIZE;
+
+	if (eventID >= eventPresets_.size())
+		return;
+
+	const EventPreset& pr = eventPresets_[eventID];
+
+	if (pr.triggerEventID >= 0) {
+		xassert(pr.triggerEventID < triggerEvents_.size());
+		triggerEvents_[pr.triggerEventID]->set_state("on");
+	}
+
+	if (pr.score) {
+		int diff = addScore(pr.score);
+
+		if (pr.fontID >= 0 && pr.escapeID >= 0 && diff != 0)
+			runtime->textManager().showNumber(diff, pos, pr.fontID, pr.escapeID);
+	}
+}
+
+int EventManager::addScore(int sc) {
+	int diff = score_;
+
+	score_ += sc;
+	if (score_ < 0 && !enableNegative_)
+		score_ = 0;
+
+	diff = score_ - diff;
+
+	if (diff)
+		runtime->textManager().updateScore(score_);
+
+	return diff;
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/EventManager.h b/engines/qdengine/minigames/adv/EventManager.h
new file mode 100644
index 00000000000..a42de3d1391
--- /dev/null
+++ b/engines/qdengine/minigames/adv/EventManager.h
@@ -0,0 +1,70 @@
+/* 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 QDENGINE_MINIGAMES_ADV_EVENT_MANAGER_H
+#define QDENGINE_MINIGAMES_ADV_EVENT_MANAGER_H
+
+namespace QDEngine {
+
+enum SystemEvent {
+	EVENT_TIME_1_SECOND_TICK,
+	EVENT_TIME_10_SECOND_TICK,
+	EVENT_TIME_60_SECOND_TICK,
+	EVENT_TIME_10_SECOND_LEFT,
+	EVENT_TIME_LESS_10_SECOND_LEFT_SECOND_TICK,
+	EVENT_TIME_OUT,
+	EVENT_GAME_LOSE,
+	EVENT_GAME_WIN,
+	SYSTEM_EVENTS_SIZE
+};
+
+class EventManager {
+public:
+	EventManager();
+
+	void sysEvent(int eventID);
+	void event(int eventID, const mgVect2f& pos, int factor);
+
+	int score() const {
+		return score_;
+	}
+	int addScore(int sc);
+
+private:
+	int score_;
+	bool enableNegative_;
+
+	struct EventPreset {
+		EventPreset();
+		int score;
+		int fontID;
+		int escapeID;
+		int triggerEventID;
+	};
+	typedef vector<EventPreset> EventPresets;
+	EventPresets eventPresets_;
+
+	QDObjects triggerEvents_;
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_EVENT_MANAGER_H
diff --git a/engines/qdengine/minigames/adv/ExportInterface.cpp b/engines/qdengine/minigames/adv/ExportInterface.cpp
new file mode 100644
index 00000000000..aea96013aaa
--- /dev/null
+++ b/engines/qdengine/minigames/adv/ExportInterface.cpp
@@ -0,0 +1,46 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/RunTime.h"
+
+namespace QDEngine {
+
+qdMiniGameInterface *open_game_interface(const char* name) {
+	dprintf("open_game_interface: %s, runtime%s\n", name, runtime ? "!=0" : "==0");
+
+	if (!runtime)
+		return runtime = new MinigameManager;
+
+	return new MinigameManager;
+}
+
+bool close_game_interface(qdMiniGameInterface* game) {
+	dprintf("close_game_interface, runtime%s%s\n", runtime == game ? "==game" : "!=game", runtime ? "!=0" : "==0");
+
+	delete game;
+	if (game == runtime)
+		runtime = 0;
+
+	return true;
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/FlyObject.cpp b/engines/qdengine/minigames/adv/FlyObject.cpp
new file mode 100644
index 00000000000..e34f5974e7b
--- /dev/null
+++ b/engines/qdengine/minigames/adv/FlyObject.cpp
@@ -0,0 +1,56 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/FlyObject.h"
+#include "qdengine/minigames/adv/qdMath.h"
+#include "qdengine/minigames/adv/RunTime.h"
+
+namespace QDEngine {
+
+FlyObjectBase::FlyObjectBase(const mgVect2f& _c, const mgVect2f& _t, float _s)
+	: current(_c)
+	, target(_t)
+	, speed(_s) {
+}
+
+bool FlyObjectBase::quant(float dt) {
+	mgVect2f dir = target;
+	dir -= current;
+	float step = speed * dt;
+	if (abs(dir) < step) {
+		current = target;
+		return false;
+	}
+	norm(dir);
+	dir *= step;
+	current += dir;
+	return true;
+}
+
+
+bool FlyQDObject::quant(float dt, QDObject& obj) {
+	bool ret = FlyObjectBase::quant(dt);
+	obj->set_R(runtime->game2world(current, depth));
+	return ret;
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/FlyObject.h b/engines/qdengine/minigames/adv/FlyObject.h
new file mode 100644
index 00000000000..f2ab50e0dfb
--- /dev/null
+++ b/engines/qdengine/minigames/adv/FlyObject.h
@@ -0,0 +1,54 @@
+/* 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 QDENGINE_MINIGAMES_ADV_FLYOBJECT_H
+#define QDENGINE_MINIGAMES_ADV_FLYOBJECT_H
+
+namespace QDEngine {
+
+struct FlyObjectBase {
+	FlyObjectBase(const mgVect2f& _c = mgVect2f(), const mgVect2f& _t = mgVect2f(), float _s = 1.f);
+	bool quant(float dt);
+
+	mgVect2f current;
+	mgVect2f target;
+	float speed;
+};
+
+struct FlyQDObject : public FlyObjectBase {
+	FlyQDObject(float dp = 0.f) : depth(dp), data(-1) {}
+	FlyQDObject(const FlyObjectBase& crd, float dp, int dat) : FlyObjectBase(crd), depth(dp), data(dat) {}
+
+	bool operator== (int dat) const {
+		return data == dat;
+	}
+
+	bool quant(float dt, QDObject& obj);
+
+	float depth;
+	int data;
+};
+
+typedef vector<FlyQDObject> FlyQDObjects;
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_FLYOBJECT_H
diff --git a/engines/qdengine/minigames/adv/HoldData.h b/engines/qdengine/minigames/adv/HoldData.h
new file mode 100644
index 00000000000..cf250c9dff9
--- /dev/null
+++ b/engines/qdengine/minigames/adv/HoldData.h
@@ -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 QDENGINE_MINIGAMES_ADV_HOLD_DATA_H
+#define QDENGINE_MINIGAMES_ADV_HOLD_DATA_H
+
+namespace QDEngine {
+
+template <class T>
+class HoldData {
+	T emptyData_;
+	T &data_;
+	bool empty_;
+public:
+	HoldData() : data_(emptyData_), empty_(true) {}
+	HoldData(T* data, bool empty)
+		: data_(data ? * data : emptyData_) {
+		empty_ = data ? empty : true;
+	}
+
+	void process(T& current) {
+		if (empty_) {
+			data_ = current;
+			empty_ = false;
+		} else
+			current = data_;
+	}
+
+	const T &get() const {
+		xassert(!empty_);
+		return data_;
+	}
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_HOLD_DATA_H
diff --git a/engines/qdengine/minigames/adv/MinigameInterface.h b/engines/qdengine/minigames/adv/MinigameInterface.h
new file mode 100644
index 00000000000..f3608f74e09
--- /dev/null
+++ b/engines/qdengine/minigames/adv/MinigameInterface.h
@@ -0,0 +1,54 @@
+/* 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 QDENGINE_MINIGAMES_ADV_MINIGAME_INTERFACE_H
+#define QDENGINE_MINIGAMES_ADV_MINIGAME_INTERFACE_H
+
+namespace QDEngine {
+
+class MinigameInterface {
+public:
+	enum StateType {
+		NOT_INITED,
+		RUNNING,
+		GAME_WIN,
+		GAME_LOST
+	};
+
+	MinigameInterface() : state_(NOT_INITED) {}
+	virtual ~MinigameInterface() {}
+
+	virtual void quant(float dt) = 0;
+
+	void setState(StateType state) {
+		state_ = state;
+	}
+	StateType state() const {
+		return state_;
+	}
+
+private:
+	StateType state_;
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_MINIGAME_INTERFACE_H
diff --git a/engines/qdengine/minigames/adv/ObjectContainer.cpp b/engines/qdengine/minigames/adv/ObjectContainer.cpp
new file mode 100644
index 00000000000..1ca2e90eecb
--- /dev/null
+++ b/engines/qdengine/minigames/adv/ObjectContainer.cpp
@@ -0,0 +1,117 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/ObjectContainer.h"
+#include "qdengine/minigames/adv/RunTime.h"
+
+namespace QDEngine {
+
+ObjectContainer::ObjectContainer() {
+	current_ = 0;
+
+}
+
+void ObjectContainer::release() {
+	QDObjects::iterator it;
+	FOR_EACH(objects_, it)
+	runtime->release(*it);
+
+	objects_.clear();
+	current_ = 0;
+}
+
+void ObjectContainer::pushObject(QDObject& obj) {
+	xassert(find(objects_.begin(), objects_.end(), obj) == objects_.end());
+	objects_.push_back(obj);
+}
+
+const char *ObjectContainer::name() const {
+	#ifdef _DEBUG
+	return name_.c_str();
+	#else
+	return "";
+	#endif
+
+}
+
+bool ObjectContainer::load(const char* base_name, bool hide) {
+	if (!runtime->testObject(base_name)) {
+		xxassert(false, (XBuffer() < "Не найден объект: \"" < base_name < "\"").c_str());
+		return false;
+	}
+
+	#ifdef _DEBUG
+	name_ = base_name;
+	#endif
+
+	QDObject obj = runtime->getObject(base_name);
+	coord_ = runtime->world2game(obj);
+	pushObject(obj);
+	if (hide)
+		runtime->hide(obj);
+
+	char name[128];
+	name[127] = 0;
+	for (int dubl = 0; ; ++dubl) {
+		_snprintf(name, 127, "%s%04d", base_name, dubl);
+		if (runtime->testObject(name)) {
+			obj = runtime->getObject(name);
+			pushObject(obj);
+			if (hide)
+				runtime->hide(obj);
+		} else
+			break;
+	}
+
+	return true;
+}
+
+void ObjectContainer::hideAll() {
+	QDObjects::iterator it;
+	FOR_EACH(objects_, it)
+	runtime->hide(*it);
+}
+
+QDObject ObjectContainer::getObject() {
+	if (current_ < objects_.size())
+		return objects_[current_++];
+	xxassert(0, (XBuffer() < "кончились объекты \"" < name() < "\" в пуле").c_str());
+//#ifdef _DEBUG
+//	return QDObject::ZERO;
+//#else
+	return objects_[0]; // плохо, но альтернатива это вообще упасть
+//#endif
+
+}
+
+void ObjectContainer::releaseObject(QDObject& obj) {
+	QDObjects::iterator it = find(objects_.begin(), objects_.end(), obj);
+	if (it != objects_.end()) {
+		xxassert((int)distance(objects_.begin(), it) < current_, (XBuffer() < "объект в пул \"" < name() < "\" возвращен несколько раз").c_str());
+		runtime->hide(obj);
+		if (current_ > 0)
+			swap(*it, objects_[--current_]);
+		obj = 0;
+	}
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/ObjectContainer.h b/engines/qdengine/minigames/adv/ObjectContainer.h
new file mode 100644
index 00000000000..92194e6ad52
--- /dev/null
+++ b/engines/qdengine/minigames/adv/ObjectContainer.h
@@ -0,0 +1,54 @@
+/* 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 QDENGINE_MINIGAMES_ADV_OBJECT_CONTAINER_H
+#define QDENGINE_MINIGAMES_ADV_OBJECT_CONTAINER_H
+
+namespace QDEngine {
+
+class ObjectContainer {
+	QDObjects objects_;
+	int current_;
+	mgVect3f coord_;
+	#ifdef _DEBUG
+	string name_;
+	#endif
+	const char *name() const;
+	void pushObject(QDObject& obj);
+
+public:
+	ObjectContainer();
+	void release();
+
+	bool load(const char* name, bool hide = true);
+	void hideAll();
+
+	const mgVect3f &coord() const {
+		return coord_;
+	}
+
+	QDObject getObject();
+	void releaseObject(QDObject& obj);
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_OBJECT_CONTAINER_H
diff --git a/engines/qdengine/minigames/adv/Range.cpp b/engines/qdengine/minigames/adv/Range.cpp
new file mode 100644
index 00000000000..744c082b623
--- /dev/null
+++ b/engines/qdengine/minigames/adv/Range.cpp
@@ -0,0 +1,205 @@
+/* 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 "qdengine/minigames/adv/Range.h"
+
+namespace QDEngine {
+
+void Rangef::set(float _min, float _max) {
+	min_ = _min;
+	max_ = _max;
+}
+
+Rangef Rangef::intersection(const Rangef& _range) {
+	float begin;
+	float end;
+	if (maximum() < _range.minimum() || minimum() > _range.maximum())
+		return Rangef(0.f, 0.f);
+
+	if (include(_range.minimum()))
+		begin = _range.minimum();
+	else
+		begin = minimum();
+
+	if (include(_range.maximum()))
+		end = _range.maximum();
+	else
+		end = maximum();
+	return Rangef(begin, end);
+}
+
+
+float Rangef::clip(float &_value) const {
+	if (include(_value))
+		return _value;
+	else {
+		if (_value < minimum())
+			return minimum();
+		else
+			return maximum();
+	}
+}
+
+// --------------------- Rangei
+
+void Rangei::set(int _min, int _max) {
+	min_ = _min;
+	max_ = _max;
+}
+
+Rangei Rangei::intersection(const Rangei& _range) {
+	int begin;
+	int end;
+	if (maximum() < _range.minimum() || minimum() > _range.maximum())
+		return Rangei(0, 0);
+
+	if (include(_range.minimum()))
+		begin = _range.minimum();
+	else
+		begin = minimum();
+
+	if (include(_range.maximum()))
+		end = _range.maximum();
+	else
+		end = maximum();
+	return Rangei(begin, end);
+}
+
+
+int Rangei::clip(int &_value) {
+	if (include(_value))
+		return _value;
+	else {
+		if (_value < minimum())
+			return minimum();
+		else
+			return maximum();
+	}
+}
+
+/*
+/// Абстракция закрытого интервала (отрезка).
+template<typename ScalarType = float>
+class Range
+{
+public:
+    typedef Range<ScalarType> RangeType;
+
+    Range (ScalarType _min = ScalarType(0), ScalarType _max = ScalarType(0)) :
+    min_ (_min),
+        max_ (_max)
+    {}
+
+    inline ScalarType minimum () const
+    {
+        return min_;
+    }
+    inline void minimum (ScalarType _min)
+    {
+        min_ = _min;
+    }
+    inline ScalarType maximum () const
+    {
+        return max_;
+    }
+    inline void maximum (ScalarType _max)
+    {
+        max_ = _max;
+    }
+    inline void set (ScalarType _min, ScalarType _max)
+    {
+        min_ = _min;
+        max_ = _max;
+    }
+
+    inline ScalarType length () const
+    {
+        return (maximum () - minimum ());
+    }
+
+    inline ScalarType center() const
+    {
+        return (maximum() + minimum()) / 2;
+    }
+
+    /// Корректен ли интервал (нет - в случае когда minimum > maximum);
+    inline bool is_valid () const
+    {
+        return (minimum () <= maximum ());
+    }
+
+    /// Включает ли отрезок (закрытый интервал) точку \c _value.
+    inline bool include (ScalarType _value) const
+    {
+        return (minimum () <= _value) && (maximum () >= _value);
+    }
+    /// Включает ли интервал в себя \c _range.
+    inline bool include (const RangeType& _range) const
+    {
+        return (minimum () <= _range.minimum ()) && (maximum () >= _range.maximum ());
+    }
+
+    /// Возвращает пересечение интервала *this и \c _range.
+    inline RangeType intersection (const RangeType& _range)
+    {
+        ScalarType begin;
+        ScalarType end;
+        if (maximum () < _range.minimum () || minimum () > _range.maximum ())
+            return RangeType (0, 0);
+
+        if (include (_range.minimum ()))
+            begin = _range.minimum ();
+        else
+            begin = minimum ();
+
+        if (include (_range.maximum ()))
+            end = _range.maximum ();
+        else
+            end = maximum ();
+        return RangeType (begin, end);
+    }
+
+    /// Возвращает \c _value в пределах интервала [minimum, maximum].
+    inline ScalarType clip (ScalarType& _value)
+    {
+        if (include (_value))
+            return _value;
+        else
+        {
+            if (_value < minimum ())
+                return minimum ();
+            else
+                return maximum ();
+        }
+    }
+
+        void serialize(Archive& ar){
+            ar.serialize(min_, "min_", "Минимум");
+            ar.serialize(max_, "max_", "Максимум");
+        }
+
+private:
+    ScalarType min_;
+    ScalarType max_;
+};
+*/
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/Range.h b/engines/qdengine/minigames/adv/Range.h
new file mode 100644
index 00000000000..19843890ceb
--- /dev/null
+++ b/engines/qdengine/minigames/adv/Range.h
@@ -0,0 +1,141 @@
+/* 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 QDENGINE_MINIGAMES_ADV_RANGE_H
+#define QDENGINE_MINIGAMES_ADV_RANGE_H
+
+namespace QDEngine {
+
+class Rangef {
+public:
+	Rangef(float _min = 0.f, float _max = 0.f)
+		: min_(_min)
+		, max_(_max)
+	{}
+
+	float minimum() const {
+		return min_;
+	}
+	void setMinimum(float _min) {
+		min_ = _min;
+	}
+
+	float maximum() const {
+		return max_;
+	}
+	void setMaximum(float _max) {
+		max_ = _max;
+	}
+
+	void set(float _min, float _max);
+
+	float length() const {
+		return max_ - min_;
+	}
+	float center() const {
+		return (max_ + min_) / 2.f;
+	}
+
+	/// Корректен ли интервал (нет - в случае когда minimum > maximum);
+	bool is_valid() const {
+		return min_ <= max_;
+	}
+
+	/// Включает ли отрезок (закрытый интервал) точку \c _value.
+	bool include(float _value) const {
+		return (min_ <= _value) && (max_ >= _value);
+	}
+	/// Включает ли интервал в себя \c _range.
+	bool include(const Rangef& _range) const {
+		return min_ <= _range.min_ && max_ >= _range.max_;
+	}
+
+	/// Возвращает пересечение интервала *this и \c _range.
+	Rangef intersection(const Rangef& _range);
+
+	/// Возвращает \c _value в пределах интервала [minimum, maximum].
+	float clip(float &_value) const;
+
+private:
+	float min_;
+	float max_;
+};
+
+// --------------------- Rangei
+
+class Rangei {
+public:
+	Rangei(int _min = 0.f, int _max = 0.f)
+		: min_(_min)
+		, max_(_max)
+	{}
+
+	int minimum() const {
+		return min_;
+	}
+	void setMinimum(int _min) {
+		min_ = _min;
+	}
+
+	int maximum() const {
+		return max_;
+	}
+	void setMaximum(int _max) {
+		max_ = _max;
+	}
+
+	void set(int _min, int _max);
+
+	int length() const {
+		return max_ - min_;
+	}
+	int center() const {
+		return (max_ + min_) / 2;
+	}
+
+	/// Корректен ли интервал (нет - в случае когда minimum > maximum);
+	bool is_valid() const {
+		return min_ <= max_;
+	}
+
+	/// Включает ли отрезок (закрытый интервал) точку \c _value.
+	bool include(int _value) const {
+		return (min_ <= _value) && (max_ >= _value);
+	}
+	/// Включает ли интервал в себя \c _range.
+	bool include(const Rangei& _range) const {
+		return min_ <= _range.min_ && max_ >= _range.max_;
+	}
+
+	/// Возвращает пересечение интервала *this и \c _range.
+	Rangei intersection(const Rangei& _range);
+
+	/// Возвращает \c _value в пределах интервала [minimum, maximum].
+	int clip(int &_value);
+
+private:
+	int min_;
+	int max_;
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_RANGE_H
diff --git a/engines/qdengine/minigames/adv/Rect.h b/engines/qdengine/minigames/adv/Rect.h
new file mode 100644
index 00000000000..54f12cd7f64
--- /dev/null
+++ b/engines/qdengine/minigames/adv/Rect.h
@@ -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/>.
+ *
+ */
+
+#ifndef QDENGINE_MINIGAMES_ADV_RECT_H
+#define QDENGINE_MINIGAMES_ADV_RECT_H
+
+#include "qdengine/minigames/adv/Range.h"
+
+namespace QDEngine {
+
+/*
+ * FIXME: Подразумевается, что left < right и top < bottom, добавить
+ * стратегию для кустомизации этого понятия?
+ */
+
+/// Абстрактый прямоугольник.
+/**
+ *  @param ScalarType - скалярный тип
+ *  @param VectType - векторный тип
+ */
+template<typename scalar_type, class vect_type>
+struct Rect {
+	typedef typename vect_type VectType;
+	typedef typename scalar_type ScalarType;
+	typedef Rect<ScalarType, VectType> RectType;
+	typedef Rangef RangeType;
+
+	// конструкторы
+	Rect() :
+		left_(ScalarType(0)),
+		top_(ScalarType(0)),
+		width_(ScalarType(0)),
+		height_(ScalarType(0)) {}
+
+	/// Создаёт Rect размера \a _size, левый-верхний угол остаётся в точке (0, 0).
+	Rect(const VectType& _size) :
+		top_(ScalarType(0)),
+		left_(ScalarType(0)),
+		width_(_size.x),
+		height_(_size.y) {}
+
+	Rect(ScalarType _left, ScalarType _top, ScalarType _width, ScalarType _height) :
+		left_(_left),
+		top_(_top),
+		width_(_width),
+		height_(_height) {}
+
+	Rect(const VectType& _top_left, const VectType& _size) :
+		left_(_top_left.x),
+		top_(_top_left.y),
+		width_(_size.x),
+		height_(_size.y) {}
+
+	void set(ScalarType _left, ScalarType _top, ScalarType _width, ScalarType _height) {
+		left_ = _left;
+		top_ = _top;
+		width_ = _width;
+		height_ = _height;
+	}
+
+	inline ScalarType left() const {
+		return left_;
+	}
+	inline ScalarType top() const {
+		return top_;
+	}
+	inline ScalarType width() const {
+		return width_;
+	}
+	inline ScalarType height() const {
+		return height_;
+	}
+
+	VectType left_top() const {
+		return VectType(left_, top_);
+	}
+	VectType right_top() const {
+		return VectType(left + width_, top_);
+	}
+	VectType left_bottom() const {
+		return VectType(left_, top_ + height_);
+	}
+	VectType right_bottom() const {
+		return VectType(left_ + width_, top_ + height_);
+	}
+
+	// аксессоры (вычисляющие):
+	inline ScalarType right() const {
+		return left_ + width_;
+	}
+	inline ScalarType bottom() const {
+		return top_ + height_;
+	}
+
+	/*
+	* FIXME: для float и double деление на 2 лучше заменить на умножение на 0.5,
+	* для целых типов лучше исползовать сдвиг.
+	*/
+
+	/// Возвращает координаты цетра прямоугольника.
+	inline VectType center() const {
+		return VectType(left_ + width_ / ScalarType(2),
+		                top_ + height_ / ScalarType(2));
+	}
+	/// Возвращает размер прямоугольника.
+	inline VectType size() const {
+		return VectType(width_, height_);
+	}
+
+	// сеттеры:
+	inline void left(ScalarType _left) {
+		left_ = _left;
+	}
+	inline void top(ScalarType _top) {
+		top_ = _top;
+	}
+	inline void width(ScalarType _width) {
+		width_ = _width;
+	}
+	inline void height(ScalarType _height) {
+		height_ = _height;
+	}
+
+	// сеттеры (вычисляющие):
+	inline void right(ScalarType _right) {
+		left_ = _right - width_;
+	}
+	inline void bottom(ScalarType _bottom) {
+		top_ = _bottom - height_;
+	}
+
+	/// Переносит центр прямоугольника в точку \a _center не изменяя его размер.
+	inline void center(const VectType& _center) {
+		left_ = _center.x - width_ / ScalarType(2);
+		top_ = _center.y - height_ / ScalarType(2);
+	}
+	/*
+	* FIXME: размер должен менятся относительно левого-верхнего угла (как у
+	* сеттеров width и height) или относительно центра? Добавить
+	* класс-стратегию для этих целей? Фунцию с другим именем (напр
+	* scale (), которая принимает центр, относительно которого происходит
+	* скэлинг)?
+	*/
+	/// Устанавливает новые размеры, сохраняя левый-верхний угол в преждней точке.
+	inline void size(const VectType& _size) {
+		width_ = _size.x;
+		height_ = _size.y;
+	}
+
+	// утилиты:
+
+	/// Проверяет не находится ли точка \a _point внутри прямоугольника
+	inline bool point_inside(const VectType& _point) const {
+		if (_point.x >= left() && _point.y >= top() &&
+		                                      _point.x <= right() && _point.y <= bottom())
+			return true;
+		else
+			return false;
+	}
+	/// Проверяет не находится ли прямоугольник \a _rect внутри прямоугольника
+	inline bool rect_inside(const RectType& _rect) const {
+		if (_rect.left() >= left() && _rect.top() >= top() &&
+		                    _rect.bottom() <= bottom() && _rect.right() <= right())
+			return true;
+		else
+			return false;
+	}
+
+	inline bool rect_overlap(const RectType& _rect) const {
+		if (left() > _rect.right() || right() < _rect.left()
+		        || top() > _rect.bottom() || bottom() < _rect.top())
+			return false;
+
+		return true;
+	}
+
+	/// Производит скэлинг.
+	/**
+	*  Возвращает копию прямоугольника, над которой произведён скэлинг
+	*  относительно точки \a _origin.
+	*/
+	inline RectType scaled(const VectType& _scale, const VectType& _origin) const {
+		return (*this - _origin) * _scale + _origin;
+	}
+
+	/// Исправляет отрицательную ширину/высоту
+	inline void validate() {
+		if (width() < ScalarType(0)) {
+			left(left() + width());
+			width(-width());
+		}
+		if (height() < ScalarType(0)) {
+			top(top() + height());
+			height(-height());
+		}
+	}
+
+	inline RectType intersection(const RectType& _rect) const {
+		RangeType xRange = RangeType(left(), right()).intersection(RangeType(_rect.left(), _rect.right()));
+		RangeType yRange = RangeType(top(), bottom()).intersection(RangeType(_rect.top(), _rect.bottom()));
+		return RectType(xRange.minimum(), yRange.minimum(), xRange.length(), yRange.length());
+	}
+
+	// Операторы
+	RectType operator+(const VectType& _point) const {
+		return RectType(left() + _point.x, top() + _point.y,
+		                width(), height());
+	}
+
+	RectType operator-(const VectType& _point) const {
+		return RectType(left() - _point.x, top() - _point.y,
+		                width(), height());
+	}
+
+	RectType operator*(const VectType& point) const {
+		return RectType(left() * point.x, top() * point.y,
+		                width() * point.x, height() * point.y);
+	}
+
+	RectType operator*(const RectType& rhs) const {
+		VectType leftTop(left() + width() * rhs.left(), top() + height() * rhs.top());
+		VectType size(this->size() * rhs.size());
+		return RectType(leftTop, size);
+	}
+
+	RectType operator/(const RectType& rhs) const {
+		VectType leftTop((left() - rhs.left()) / rhs.width(), (top() - rhs.top()) / rhs.height());
+		VectType size(width() / rhs.width(), height() / rhs.height());
+		return RectType(leftTop, size);
+	}
+
+	RectType operator/(const VectType& _point) const {
+		return RectType(left() / _point.x, top() / _point.y,
+		                width() / _point.x, height() / _point.y);
+	}
+
+	bool operator==(const RectType& rect) const {
+		return (left_ == rect.left_ && top_ == rect.top_ &&
+		width_ == rect.width_ && height_ == rect.height_);
+	}
+
+	bool eq(const RectType& rect, ScalarType eps = FLT_COMPARE_TOLERANCE) const {
+		return (abs(left_ - rect.left_) < eps && abs(top_ - rect.top_) < eps &&
+		        abs(width_ - rect.width_) < eps && abs(height_ - rect.height_) < eps);
+	}
+
+	bool operator!=(const RectType& rect) const {
+		return (left_ != rect.left_ || top_ != rect.top_ ||
+		                                       width_ != rect.width_ || height_ != rect.height_);
+	}
+
+protected:
+	ScalarType left_;
+	ScalarType top_;
+	ScalarType width_;
+	ScalarType height_;
+
+public:
+	// SideKick на этом обламывается:
+	template<class ST, class VT>
+	operator ::Rect<ST, VT>() const {
+		return ::Rect<ST, VT>(static_cast<ST>(left()),
+		                      static_cast<ST>(top()),
+		                      static_cast<ST>(width()),
+		                      static_cast<ST>(height()));
+	}
+
+
+	bool clipLine(VectType& pos0, VectType& pos1) const;
+};
+
+template<typename ScalarType, class VectType>
+bool Rect<ScalarType, VectType>::clipLine(VectType& pos0, VectType& pos1) const {
+	VectType p0(pos0), p1(pos1);
+
+	bool b0 = point_inside(p0);
+	bool b1 = point_inside(p1);
+
+	if (b0 && b1) // вся линия внутри clip
+		return true;
+	else {
+		float tc;
+		float t[4] = {-1.0f, -1.0f, -1.0f, -1.0f};
+		int find = 0;
+		ScalarType dx = p1.x - p0.x;
+		ScalarType dy = p1.y - p0.y;
+
+		ScalarType crd;
+
+		if (abs(dy) > 0) {
+			tc = (float)(top() - p0.y) / dy;
+			if (tc >= 0.0f && tc <= 1.0f) {
+				crd = p0.x + tc * dx;
+				if (crd >= left() && crd <= right())
+					t[find++] = tc;
+			}
+
+			tc = (float)(bottom() - p0.y) / dy;
+			if (tc >= 0.0f && tc <= 1.0f) {
+				crd = p0.x + tc * dx;
+				if (crd >= left() && crd <= right())
+					t[find++] = tc;
+			}
+		}
+
+		if (abs(dx) > 0) {
+			tc = (float)(left() - p0.x) / dx;
+			if (tc >= 0.0f && tc <= 1.0f) {
+				crd = p0.y + tc * dy;
+				if (crd >= top() && crd <= bottom())
+					t[find++] = tc;
+			}
+
+			tc = (float)(right() - p0.x) / dx;
+			if (tc >= 0.0f && tc <= 1.0f) {
+				crd = p0.y + tc * dy;
+				if (crd >= top() && crd <= bottom())
+					t[find++] = tc;
+			}
+		}
+
+		if (b0) { //внутри только точка p0
+			pos1.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
+			pos0.set(p0.x, p0.y);
+		} else if (b1) { //внутри только точка p1
+			pos0.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
+			pos1.set(p1.x, p1.y);
+		} else if (find) { //обе точки снаружи, но часть отрезка внутри
+			if (t[0] < t[1]) {
+				pos0.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
+				pos1.set(p0.x + t[1]*dx, p0.y + t[1]*dy);
+			} else {
+				pos1.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
+				pos0.set(p0.x + t[1]*dx, p0.y + t[1]*dy);
+			}
+		} else
+			return false;
+	}
+	return true;
+}
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_RECT_H
diff --git a/engines/qdengine/minigames/adv/RunTime.cpp b/engines/qdengine/minigames/adv/RunTime.cpp
new file mode 100644
index 00000000000..3556ce88428
--- /dev/null
+++ b/engines/qdengine/minigames/adv/RunTime.cpp
@@ -0,0 +1,1041 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/qdMath.h"
+#include "qdengine/minigames/adv/RunTime.h"
+#include "qdengine/minigames/adv/HoldData.h"
+#include "qdengine/system/input/keyboard_input.h"
+#include "qdengine/minigames/adv/TextManager.h"
+#include "qdengine/minigames/adv/EventManager.h"
+#include "qdengine/minigames/adv/EffectManager.h"
+#include "qdengine/minigames/adv/MinigameInterface.h"
+
+namespace QDEngine {
+
+MinigameManager *runtime = 0;
+// createGame() должна реализоваться непосредственно в КАЖДОМ проекте игры
+MinigameInterface *createGame();
+
+class TimeManager {
+	enum Direction {
+		UP,
+		LEFT,
+		RIGHT,
+		DOWN
+	};
+public:
+	TimeManager(HoldData<TimeManagerData> &data);
+	~TimeManager();
+
+	bool timeIsOut() const;
+	float leftTime() const;
+	float timeCost() const {
+		return timeCost_;
+	}
+
+	void quant(float dt);
+
+private:
+	float gameTime_;
+	float timeCost_;
+	int lastEventTime_;
+	mgVect3f startPos_;
+	mgVect2f size_;
+	Direction direction_;
+	QDObject timeBar_;
+};
+
+MinigameManager::MinigameManager()
+	: currentGameIndex_(-1, -1) {
+	state_container_name_ = "Saves\\minigames.dat";
+
+	engine_ = 0;
+	scene_ = 0;
+
+	timeManager_ = 0;
+	textManager_ = 0;
+	eventManager_ = 0;
+	effectManager_ = 0;
+	state_flag_ = 0;
+	pause_flag_ = 0;
+	complete_help_ = 0;
+	complete_help_miniature_ = 0;
+	game_help_ = 0;
+	game_help_trigger_ = 0;
+	game_help_enabled_ = true;
+	game_ = 0;
+	gameTime_ = 0;
+
+	currentGameInfo_ = 0;
+
+	invertMouseButtons_ = false;
+	debugMode_ = false;
+	seed_ = 0;
+
+	for (int idx = 0; idx < 256; ++idx)
+		lastKeyChecked_[idx] = false;
+}
+
+MinigameManager::~MinigameManager() {
+	xassert(!engine_ && !scene_);
+
+	GameInfoMap::iterator it;
+	FOR_EACH(gameInfos_, it) {
+		//dprintf("free: (%d,%d)\n", it->first.gameLevel_, it->first.gameNum_);
+		it->second.free();
+	}
+}
+
+bool MinigameManager::init(const qdEngineInterface* engine_interface) {
+	dprintf("init game\n");
+
+	xxassert(runtime == this, "Попытка одновременного запуска дубля миниигры");
+	if (runtime != this)
+		return false;
+	xassert(!engine_ && !scene_);
+
+	xassert(engine_interface);
+	if (!engine_interface)
+		return false;
+
+	engine_ = engine_interface;
+	scene_ = engine_->current_scene_interface();
+
+	xassert(scene_);
+	if (!scene_) {
+		engine_ = 0;
+		return false;
+	}
+
+	if (!createGame()) {
+		xxassert(0, "Игра не смогла проинициализироваться");
+		finit();
+		return false;
+	}
+
+	saveState();
+
+	return true;
+}
+
+bool MinigameManager::createGame() {
+	xassert(engine_ && scene_);
+	xassert(!game_);
+
+	screenSize_ = engine_->screen_size();
+
+	#ifdef _DEBUG
+	debugMode_ = getParameter("debug_mode", false);
+	dprintf("%s", debugMode_ ? "DEBUG MODE\n" : "");
+	#endif
+
+	seed_ = 0;
+
+	if (!loadState())
+		return false;
+
+	if (currentGameInfo_) {
+		dprintf("level: %d, game: %d, index: %d\n", currentGameIndex_.gameLevel_, currentGameIndex_.gameNum_, currentGameInfo_->game_.sequenceIndex_);
+		dprintf("%s\n", currentGameInfo_->game_.sequenceIndex_ == -1 ? "FIRST TIME PLAY" : "RePlay game");
+	}
+
+	int s = getParameter("random_seed", -1);
+	seed_ = debugMode_ ? 0 : (s >= 0 ? s : seed_);
+
+	engine_->rnd_init(seed_);
+	dprintf("seed = %d\n", seed_);
+
+	invertMouseButtons_ = getParameter("invert_mouse_buttons", false);
+	mouseAdjast_ = getParameter("ajast_mouse", mgVect2f());
+
+	HoldData<TimeManagerData> timeData(currentGameInfo_ ? &currentGameInfo_->timeManagerData_ : 0, !currentGameInfo_ || currentGameInfo_->empty_);
+	timeManager_ = new TimeManager(timeData);
+
+	textManager_ = new TextManager();
+
+	eventManager_ = new EventManager();
+
+	HoldData<EffectManagerData> effectData(currentGameInfo_ ? &currentGameInfo_->effectManagerData_ : 0, !currentGameInfo_ || currentGameInfo_->empty_);
+	effectManager_ = new EffectManager(effectData);
+
+	const char *stateFlagName = parameter("state_flag_name", "state_flag");
+
+	if (state_flag_ = scene_->object_interface(stateFlagName)) {
+		if (!state_flag_->has_state("game") || !state_flag_->has_state("win") || !state_flag_->has_state("lose")) {
+			xxassert(false, (XBuffer() < "У объекта \"" < stateFlagName < "\" должны быть состояния: game, win, lose").c_str());
+			return false;
+		}
+	} else {
+		xxassert(false, (XBuffer() < "Отсутствует объект передачи состояния \"" < stateFlagName < "\"").c_str());
+		return false;
+	}
+
+	const char *pauseFlagName = parameter("pause_flag_name", "BackHelp");
+
+	if (pause_flag_ = scene_->object_interface(pauseFlagName)) {
+		if (!pause_flag_->has_state("on")) {
+			xxassert(false, (XBuffer() < "У объекта \"" < pauseFlagName < "\" должно быть состояние: on").c_str());
+			return false;
+		}
+	}
+
+	complete_help_state_name_ = "01";
+
+	if (testObject(parameter("complete_help_miniatute", "miniature"))) {
+		complete_help_miniature_ = getObject(parameter("complete_help_miniatute", "miniature"));
+		if (complete_help_ = getObject(parameter("complete_help", "complete"))) {
+			if (!complete_help_->has_state("off") || !complete_help_->has_state("01")) {
+				xxassert(false, (XBuffer() < "У объекта для отображения собранной игры должны быть состояния: off, 01").c_str());
+				return false;
+			}
+		} else {
+			xxassert(false, (XBuffer() < "Не найден объект для отображения собранной игры").c_str());
+			return false;
+		}
+	}
+
+	game_help_state_name_ = "off";
+
+	if (testObject(parameter("tips_object", "tips"))) {
+		game_help_ = getObject(parameter("tips_object", "tips"));
+		game_help_.setState(game_help_state_name_.c_str());
+	}
+	if (testObject(parameter("tips_switcher", "tips_button"))) {
+		game_help_trigger_ = getObject(parameter("tips_switcher", "tips_button"));
+		game_help_trigger_.setState(game_help_enabled_ ? "01" : "02");
+	}
+
+	game_ = ::createGame();
+
+	if (currentGameInfo_)
+		currentGameInfo_->empty_ = false;
+
+	if (game_ && game_->state() != MinigameInterface::NOT_INITED) {
+		textManager_->updateScore(eventManager_->score());
+		state_flag_->set_state("game");
+		return true;
+	}
+
+	return false;
+}
+
+#define SAFE_RELEASE(name)                      \
+	if(name){                                   \
+		scene_->release_object_interface(name); \
+		name = 0;                               \
+	}
+
+bool MinigameManager::finit() {
+	dprintf("finit game\n");
+	if (!engine_)
+		return false;
+
+	delete game_;
+	game_ = 0;
+
+	delete effectManager_;
+	effectManager_ = 0;
+
+	delete eventManager_;
+	eventManager_ = 0;
+
+	delete textManager_;
+	textManager_ = 0;
+
+	delete timeManager_;
+	timeManager_ = 0;
+
+	SAFE_RELEASE(state_flag_)
+	SAFE_RELEASE(pause_flag_)
+
+	release(complete_help_miniature_);
+	release(complete_help_);
+
+	release(game_help_);
+	release(game_help_trigger_);
+	game_help_enabled_ = true;
+
+	complete_help_state_name_.clear();
+	game_help_state_name_.clear();
+
+	completeCounters_.clear();
+
+	currentGameInfo_ = 0;
+	currentGameIndex_ = GameInfoIndex(-1, -1);
+
+	gameInfos_.clear();
+
+	seed_ = 0;
+	debugMode_ = false;
+	invertMouseButtons_ = false;
+	mouseAdjast_ = mgVect2f();
+
+	if (scene_) {
+		engine_->release_scene_interface(scene_);
+		scene_ = 0;
+	}
+
+	gameTime_ = 0;
+
+	engine_ = 0;
+
+	return true;
+}
+#undef SAFE_RELEASE
+
+
+bool MinigameManager::new_game(const qdEngineInterface* engine_interface) {
+	if (!loadState(false)) {
+		dprintf("new game skiped\n");
+		return false;
+	}
+	dprintf("new game\n");
+
+	GameInfoMap::iterator it;
+	FOR_EACH(gameInfos_, it) {
+		dprintf("clean game data (%d, %d)\n", it->first.gameLevel_, it->first.gameNum_);
+		it->second.game_ = MinigameData();
+	}
+
+	saveState(true);
+	return true;
+
+}
+
+class TempValue {
+	const qdEngineInterface *pre_engine_;
+	qdMinigameSceneInterface *pre_scene_;
+	MinigameManager *pre_runtime_;
+public:
+	TempValue(MinigameManager* new_runtime, const qdEngineInterface* new_engine, qdMinigameSceneInterface* new_scene) {
+		xassert(new_runtime);
+		pre_runtime_ = runtime;
+		runtime = new_runtime;
+
+		xassert(new_engine && new_scene);
+		pre_engine_ = runtime->engine_;
+		pre_scene_ = runtime->scene_;
+
+		runtime->engine_ = new_engine;
+		runtime->scene_ = new_scene;
+	}
+	~TempValue() {
+		runtime->engine_ = pre_engine_;
+		runtime->scene_ = pre_scene_;
+
+		runtime = pre_runtime_;
+	}
+};
+
+#define TEMP_SCENE_ENTER() TempValue tempSceneObject(this, engine, const_cast<qdMinigameSceneInterface*>(scene))
+
+int MinigameManager::save_game(const qdEngineInterface* engine, const qdMinigameSceneInterface* scene, char* buffer, int buffer_size) {
+	dprintf("save game\n");
+	TEMP_SCENE_ENTER();
+	loadState();
+	if (currentGameInfo_ && !currentGameInfo_->empty()) {
+		dprintf("save game (%d, %d)\n", currentGameIndex_.gameLevel_, currentGameIndex_.gameNum_);
+		XBuffer out((void*)buffer, buffer_size);
+		out.write(GameInfo::version());
+		out.write(currentGameInfo_->game_);
+		return out.tell();
+	}
+	return 0;
+
+}
+
+int MinigameManager::load_game(const qdEngineInterface* engine, const qdMinigameSceneInterface* scene, const char* buffer, int buffer_size) {
+	xassert(!game_);
+	if (game_) {
+		dprintf("load game skiped\n");
+		return buffer_size;
+	}
+	dprintf("load game\n");
+	TEMP_SCENE_ENTER();
+	loadState();
+	if (currentGameInfo_) {
+		if (buffer_size > 0) {
+			dprintf("load game (%d, %d)\n", currentGameIndex_.gameLevel_, currentGameIndex_.gameNum_);
+			XBuffer in((void*)buffer, buffer_size);
+			int version;
+			in.read(version);
+			if (version == GameInfo::version()) {
+				in.read(currentGameInfo_->game_);
+				xxassert(!currentGameInfo_->empty_, "Загрузка данных по миниигре без данных о сцене. Рекомендуется удалить все сохранения.");
+				if (in.tell() != buffer_size) {
+					currentGameInfo_->game_ = MinigameData();
+					xxassert(0, "Не совпадает размер данных в сохранении и в миниигре.");
+					return 0;
+				}
+			} else {
+				xxassert(0, "Несовместимая версия сохранения для миниигры.");
+				return 0;
+			}
+		} else {
+			dprintf("clean game (%d, %d)\n", currentGameIndex_.gameLevel_, currentGameIndex_.gameNum_);
+			currentGameInfo_->game_ = MinigameData();
+		}
+		saveState();
+	}
+	return buffer_size;
+
+}
+
+bool MinigameManager::loadState(bool current) {
+	if (game_) {
+		dprintf("load state skiped\n");
+		return false;
+	}
+	dprintf("load state\n");
+	if (current) {
+		int gameNumber = getParameter("game_number", -1);
+		int gameLevel = -1;
+		if (gameNumber >= 0)
+			if (!getParameter("game_level", gameLevel, true))
+				return false;
+		currentGameIndex_ = GameInfoIndex(gameNumber, gameLevel);
+	} else
+		currentGameIndex_ = GameInfoIndex(-1, -1);
+
+	if (!current || currentGameIndex_.gameNum_ >= 0) {
+
+		if (current)
+			dprintf("current game: (%d,%d)\n", currentGameIndex_.gameLevel_, currentGameIndex_.gameNum_);
+
+		XStream file(false);
+		if (file.open(state_container_name_, XS_IN)) {
+			int version;
+			file > version;
+			if (version != GameInfo::version()) {
+				xxassert(0, (XBuffer() < "Не совпадает версия сохранения состояния миниигры. Удалите " < state_container_name_).c_str());
+				return false;
+			}
+			file > seed_;
+			GameInfoIndex index(0, 0);
+			while (!file.eof()) {
+				file.read(index);
+				xassert(gameInfos_.find(index) == gameInfos_.end());
+				if (file.eof())
+					return false;
+				{
+					GameInfo data;
+					file > data;
+					dprintf("read game info: (%d,%d), index: %d, game data:%d\n", index.gameLevel_, index.gameNum_, data.game_.sequenceIndex_, data.empty_ ? 0 : 1);
+					if (data.game_.sequenceIndex_ >= 0)
+						completeCounters_[index.gameLevel_]++;
+					gameInfos_[index] = data;
+				}
+			}
+		}
+
+		currentGameInfo_ = current ? &gameInfos_[currentGameIndex_] : 0;
+	}
+	return true;
+}
+
+extern bool createDirForFile(const char* partialPath);
+void MinigameManager::saveState(bool force) {
+	dprintf("save state\n");
+	if (force || currentGameIndex_.gameNum_ >= 0) {
+		XStream file(false);
+		if (createDirForFile(state_container_name_) && file.open(state_container_name_, XS_OUT)) {
+			file < GameInfo::version();
+			file < (engine_ ? engine_->rnd(999999) : seed_);
+			GameInfoMap::const_iterator it;
+			FOR_EACH(gameInfos_, it)
+			if (!it->second.empty()) {
+				dprintf("write game info: (%d,%d), index: %d, game data:%d\n", it->first.gameLevel_, it->first.gameNum_, it->second.game_.sequenceIndex_, it->second.empty_ ? 0 : 1);
+				file.write(it->first);
+				file < it->second;
+			}
+		} else {
+			xxassert(0, (XBuffer() < "Не удалось сохранить прогресс в файл: \"" < state_container_name_ < "\"").c_str());
+		}
+	}
+}
+
+bool MinigameManager::quant(float dt) {
+	if (!game_)
+		return false;
+
+	if (pause_flag_ && pause_flag_->is_state_active("on"))
+		return true;
+
+	gameTime_ += dt;
+
+	mgVect2i pos = engine_->mouse_cursor_position();
+	mousePos_ = mgVect2f(pos.x, pos.y);
+	mousePos_ += mouseAdjast_;
+
+	if (game_->state() == MinigameInterface::RUNNING) {
+		timeManager_->quant(dt);
+
+		if (complete_help_miniature_) {
+			xassert(complete_help_);
+			if (complete_help_miniature_.hit(mousePos_))
+				complete_help_.setState(complete_help_state_name_.c_str());
+			else
+				complete_help_.setState("off");
+		}
+
+		if (game_help_trigger_) {
+			if (game_help_trigger_.hit(mousePosition())) {
+				game_help_trigger_.setState(game_help_enabled_ ? "01_sel" : "02_sel");
+				if (mouseLeftPressed())
+					game_help_enabled_ = !game_help_enabled_;
+			} else
+				game_help_trigger_.setState(game_help_enabled_ ? "01" : "02");
+		}
+
+		if (timeManager_->timeIsOut()) {
+			signal(EVENT_TIME_OUT);
+			game_->setState(MinigameInterface::GAME_LOST);
+		} else
+			game_->quant(dt);
+
+		if (game_help_)
+			game_help_.setState(game_help_enabled_ ? game_help_state_name_.c_str() : "off");
+
+		#ifdef _DEBUG
+		if (keyPressed(VK_MULTIPLY, true))
+			game_->setState(MinigameInterface::GAME_WIN);
+		#endif
+
+		switch (game_->state()) {
+		case MinigameInterface::GAME_LOST:
+			if (!timeManager_->timeIsOut())
+				signal(EVENT_GAME_LOSE);
+		case MinigameInterface::NOT_INITED:
+			gameLose();
+			break;
+
+		case MinigameInterface::GAME_WIN:
+			signal(EVENT_GAME_WIN);
+			gameWin();
+			break;
+		}
+	}
+
+	for (int vKey = 0; vKey < 256; ++vKey)
+		if (lastKeyChecked_[vKey])
+			lastKeyChecked_[vKey] = engine_->is_key_pressed(vKey);
+
+	if (game_->state() != MinigameInterface::NOT_INITED) {
+		textManager_->quant(dt);
+		effectManager_->quant(dt);
+		return true;
+	}
+
+	return false;
+}
+
+void MinigameManager::setCompleteHelpVariant(int idx) {
+	xassert(idx >= 0);
+	char buf[32];
+	buf[31] = 0;
+	_snprintf(buf, 31, "%02d", idx + 1);
+	complete_help_state_name_ = buf;
+}
+
+void MinigameManager::setGameHelpVariant(int idx) {
+	if (idx >= 0) {
+		char buf[32];
+		buf[31] = 0;
+		_snprintf(buf, 31, "%02d", idx + 1);
+		game_help_state_name_ = buf;
+	} else
+		game_help_state_name_ = "off";
+}
+
+void MinigameManager::event(int eventID, const mgVect2f& pos, int factor) {
+	eventManager_->event(eventID, pos, factor);
+}
+
+void MinigameManager::signal(SystemEvent id) {
+	eventManager_->sysEvent(id);
+}
+
+const MinigameData *MinigameManager::getScore(int level, int game) const {
+	GameInfoMap::const_iterator it = gameInfos_.find(GameInfoIndex(game, level));
+	if (it != gameInfos_.end())
+		return &it->second.game_;
+	return 0;
+}
+
+bool MinigameManager::testAllGamesWin() {
+	XStream file(false);
+	if (!file.open(gameListFileName(), XS_IN))
+		return false;
+
+	char read_buf[512];
+	while (!file.eof()) {
+		file.getline(read_buf, 512);
+		XBuffer xbuf((void*)read_buf, strlen(read_buf));
+		int level;
+		xbuf >= level;
+		unsigned char ch;
+		xbuf > ch;
+		if (ch != ':') {
+			xxassert(ch != ':', (XBuffer() < "Неправильный формат файла \"" < gameListFileName() < "\"").c_str());
+			return false;
+		}
+		while (xbuf.tell() < xbuf.size()) {
+			xbuf > ch;
+			if (isdigit(ch)) {
+				--xbuf;
+				int game;
+				xbuf >= game;
+				const MinigameData* data = getScore(level, game);
+				if (!data || data->sequenceIndex_ == -1)
+					return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+void MinigameManager::gameWin() {
+	dprintf("Game Win\n");
+	state_flag_->set_state("win");
+
+	if (debugMode() || !currentGameInfo_)
+		return;
+
+	xassert(currentGameIndex_.gameNum_ >= 0);
+
+	effectManager_->start(EFFECT_1);
+
+	if (currentGameIndex_.gameNum_ == 0)
+		return;
+
+	int gameTime = round(time());
+	eventManager_->addScore(round(timeManager_->leftTime() * timeManager_->timeCost()));
+
+	currentGameInfo_->game_.lastTime_ = gameTime;
+	currentGameInfo_->game_.lastScore_ = eventManager_->score();
+
+	if (currentGameInfo_->game_.sequenceIndex_ >= 0) { // это переигровка
+		if (eventManager_->score() > currentGameInfo_->game_.bestScore_) {
+			dprintf("установлен новый рекорд очков.\n");
+			currentGameInfo_->game_.bestScore_ = eventManager_->score();
+			currentGameInfo_->game_.bestTime_ = gameTime;
+		}
+	} else {
+		dprintf("добавляем очки к сумме прохождения: %d\n", eventManager_->score());
+		currentGameInfo_->game_.sequenceIndex_ = completeCounters_[currentGameIndex_.gameLevel_];
+		currentGameInfo_->game_.bestScore_ = eventManager_->score();
+		currentGameInfo_->game_.bestTime_ = gameTime;
+		if (QDCounter all_score = getCounter("all_score")) {
+			all_score->add_value(eventManager_->score());
+			if (testAllGamesWin()) {
+				dprintf("Все игры пройдены, добавлена запись в таблицу рекордов: %d\n", all_score->value());
+				engine_->add_hall_of_fame_entry(all_score->value());
+			}
+			release(all_score);
+		}
+		if (QDCounter all_time = getCounter("all_time")) {
+			all_time->add_value(gameTime);
+			release(all_time);
+		}
+	}
+
+	saveState();
+}
+
+void MinigameManager::gameLose() {
+	dprintf("Game Lose\n");
+	state_flag_->set_state("lose");
+}
+
+const char *MinigameManager::parameter(const char* name, bool required) const {
+	xxassert(scene_, "Сцена не определена");
+	const char *txt = scene_->minigame_parameter(name);
+	xxassert(!required || txt, (XBuffer() < "Не задан обязательный параметр [" < name < "] в ini файле").c_str());
+	return txt;
+}
+
+const char *MinigameManager::parameter(const char* name, const char* def) const {
+	xxassert(def, (XBuffer() < "Не задано значение по умолчанию для параметра [" < name < "]").c_str());
+	const char *txt = scene_->minigame_parameter(name);
+	xxassert(def || txt, (XBuffer() < "Не задан обязательный параметр [" < name < "] в ini файле").c_str());
+	return txt ? txt : (def ? def : "");
+}
+
+bool MinigameManager::mouseLeftPressed() const {
+	if (invertMouseButtons_)
+		return engine_->is_mouse_event_active(qdEngineInterface::MOUSE_EV_RIGHT_DOWN);
+	return engine_->is_mouse_event_active(qdEngineInterface::MOUSE_EV_LEFT_DOWN);
+
+}
+
+bool MinigameManager::mouseRightPressed() const {
+	if (invertMouseButtons_)
+		return engine_->is_mouse_event_active(qdEngineInterface::MOUSE_EV_LEFT_DOWN);
+	return engine_->is_mouse_event_active(qdEngineInterface::MOUSE_EV_RIGHT_DOWN);
+
+}
+
+bool MinigameManager::keyPressed(int vKey, bool once) const {
+	xassert(vKey >= 0 && vKey <= 255);
+	if (engine_->is_key_pressed(vKey)) {
+		if (once && lastKeyChecked_[vKey])
+			return false;
+		return lastKeyChecked_[vKey] = true;
+	}
+	return lastKeyChecked_[vKey] = false;
+}
+
+mgVect3f MinigameManager::game2world(const mgVect3i& coord) const {
+	return scene_->screen2world_coords(reinterpret_cast<const mgVect2i &>(coord), coord.z);
+}
+
+mgVect3f MinigameManager::game2world(const mgVect3f& coord) const {
+	return scene_->screen2world_coords(mgVect2i(round(coord.x), round(coord.y)), round(coord.z));
+}
+
+mgVect3f MinigameManager::game2world(const mgVect2i& coord, int depth) const {
+	return scene_->screen2world_coords(coord, depth);
+}
+
+mgVect3f MinigameManager::game2world(const mgVect2f& coord, int depth) const {
+	return scene_->screen2world_coords(mgVect2i(round(coord.x), round(coord.y)), depth);
+}
+
+mgVect2f MinigameManager::world2game(const mgVect3f& pos) const {
+	mgVect2i scr = scene_->world2screen_coords(pos);
+	return mgVect2f(scr.x, scr.y);
+}
+
+mgVect3f MinigameManager::world2game(qdMinigameObjectInterface* obj) const {
+	mgVect2i scr = obj->screen_R();
+	return mgVect3f(scr.x, scr.y, round(getDepth(obj)));
+}
+
+mgVect2f MinigameManager::getSize(qdMinigameObjectInterface* obj) const {
+	if (obj) {
+		mgVect2i size = obj->screen_size();
+		return mgVect2f(size.x, size.y);
+	}
+	return mgVect2f();
+}
+
+void MinigameManager::setDepth(qdMinigameObjectInterface* obj, int depth) const {
+	mgVect2i scr = obj->screen_R();
+	obj->set_R(scene_->screen2world_coords(scr, depth));
+}
+
+float MinigameManager::getDepth(qdMinigameObjectInterface* obj) const {
+	return scene_->screen_depth(obj->R());
+}
+
+float MinigameManager::getDepth(const mgVect3f& pos) const {
+	return scene_->screen_depth(pos);
+}
+
+QDObject MinigameManager::getObject(const char* name) const {
+	xxassert(name && *name, "Нулевое имя для получение объекта");
+	if (!name || !*name)
+		return QDObject::ZERO;
+	qdMinigameObjectInterface* obj = scene_->object_interface(name);
+	xxassert(obj, (XBuffer() < "Не найден объект: \"" < name < "\"").c_str());
+	if (obj)
+		return QDObject(obj, name);
+	return QDObject::ZERO;
+}
+
+bool MinigameManager::testObject(const char* name) const {
+	if (qdMinigameObjectInterface * obj = scene_->object_interface(name)) {
+		scene_->release_object_interface(obj);
+		return true;
+	}
+	return false;
+}
+
+void MinigameManager::release(QDObject& obj) {
+	if (obj) {
+		scene_->release_object_interface(obj);
+		obj = 0;
+	}
+}
+
+QDCounter MinigameManager::getCounter(const char* name) {
+	qdMinigameCounterInterface* counter = engine_->counter_interface(name);
+	xxassert(counter, (XBuffer() < "Не найден счетчик: \"" < name < "\"").c_str());
+	return counter;
+}
+
+void MinigameManager::release(QDCounter& counter) {
+	xxassert(counter, "Передан нулевой счетчик для освобождения");
+	engine_->release_counter_interface(counter);
+	counter = 0;
+}
+
+void MinigameManager::setText(const char* name, const char* text) const {
+	engine_->set_interface_text(0, name, text);
+}
+
+void MinigameManager::setText(const char* name, int toText, const char* format) const {
+	char text[16];
+	text[15] = 0;
+	_snprintf(text, 15, format, toText);
+	setText(name, text);
+}
+
+void MinigameManager::hide(qdMinigameObjectInterface* obj) const {
+	obj->set_R(scene_->screen2world_coords(mgVect2i(-10000, -10000), getDepth(obj)));
+}
+
+float MinigameManager::rnd(float min, float max) const {
+	return min + engine_->fabs_rnd(max - min);
+}
+
+int MinigameManager::rnd(int min, int max) const {
+	return min + round(engine_->fabs_rnd(max - min));
+}
+
+int MinigameManager::rnd(const vector<float> &prob) const {
+	float rnd = runtime->rnd(0.f, .9999f);
+	float accum = 0.f;
+	int idx = 0;
+	int size = prob.size();
+	for (; idx < size; ++idx) {
+		accum += prob[idx];
+		if (rnd <= accum)
+			break;
+	}
+	xassert(idx >= 0 && idx < prob.size());
+	#ifdef _DEBUG
+	float sum = 0.f;
+	vector<float>::const_iterator pit;
+	FOR_EACH(prob, pit)
+	sum += *pit;
+	xassert(abs(sum - 1.f) < 0.0001f);
+	#endif
+	return idx;
+}
+
+
+//========================================================================================================================
+
+
+// если данные еще ни разу не сохранялись - запоминаем
+// если уже есть запомненные, то заменяем на них
+bool MinigameManager::processGameData(XBuffer& data) {
+	if (currentGameInfo_) {
+		if (currentGameInfo_->empty_) {
+			currentGameInfo_->empty_ = false;
+			xassert(data.tell());
+			currentGameInfo_->write(data.buffer(), data.tell());
+		} else {
+			xxassert(data.tell() == currentGameInfo_->dataSize_, (XBuffer() < "Сильно устаревшее сохранение состояния миниигры. Удалите " < state_container_name_).c_str());
+			if (data.tell() == currentGameInfo_->dataSize_) {
+				data.set(0);
+				data.write(currentGameInfo_->gameData_, currentGameInfo_->dataSize_, true);
+			} else {
+				data.set(0);
+				return false;
+			}
+		}
+	}
+	data.set(0);
+	return true;
+}
+
+MinigameData::MinigameData() {
+	sequenceIndex_ = -1;
+	lastScore_ = 0;
+	lastTime_ = 0;
+	bestTime_ = 0;
+	bestScore_ = 0;
+}
+
+GameInfo::GameInfo() {
+	empty_ = true;
+	dataSize_ = 0;
+	gameData_ = 0;
+}
+
+void GameInfo::free() {
+	if (gameData_) {
+		xassert(dataSize_ > 0);
+		//dprintf("memory free: %#x\n", gameData_);
+		::free(gameData_);
+		gameData_ = 0;
+	}
+	dataSize_ = 0;
+}
+
+void GameInfo::write(void* data, unsigned int size) {
+	if (dataSize_ != size) {
+		free();
+		if (size > 0) {
+			gameData_ = malloc(size);
+			dataSize_ = size;
+			//dprintf("memory alloc: %#x, %d bytes\n", gameData_, size);
+		}
+	}
+	if (dataSize_ > 0)
+		memcpy(gameData_, data, dataSize_);
+}
+
+XStream &operator< (XStream& out, const GameInfo& info) {
+	out.write(info.game_);
+	out.write(info.empty_);
+	if (!info.empty_) {
+		out.write(info.timeManagerData_);
+		out.write(info.effectManagerData_);
+		out < info.dataSize_;
+		if (info.dataSize_ > 0)
+			out.write(info.gameData_, info.dataSize_);
+	}
+	return out;
+}
+
+XStream &operator> (XStream& in, GameInfo& info) {
+	in.read(info.game_);
+	in.read(info.empty_);
+	if (!info.empty_) {
+		in.read(info.timeManagerData_);
+		in.read(info.effectManagerData_);
+		unsigned int size;
+		in > size;
+		XBuffer buf(size);
+		in.read(buf.buffer(), size);
+		info.write(buf.buffer(), size);
+	}
+	return in;
+}
+
+
+//========================================================================================================================
+
+
+TimeManager::TimeManager(HoldData<TimeManagerData> &data) {
+	if (const char * data = runtime->parameter("game_time", false)) {
+		if (sscanf(data, "%f", &gameTime_) != 1)
+			gameTime_ = -1.f;
+	} else
+		gameTime_ = -1.f;
+
+	timeCost_ = 0.f;
+
+	if (gameTime_ > 0) {
+		if (const char * data = runtime->parameter("time_bar"))
+			timeBar_ = runtime->getObject(data);
+
+		if (const char * data = runtime->parameter("time_cost"))
+			sscanf(data, "%f", &timeCost_);
+	}
+
+	if (timeBar_) {
+		TimeManagerData myData;
+		myData.crd = runtime->world2game(timeBar_);
+
+		data.process(myData);
+
+		startPos_ = myData.crd;
+		size_ = runtime->getSize(timeBar_);
+
+		if (const char * data = runtime->parameter("time_bar_direction")) {
+			int dir;
+			if (sscanf(data, "%d", &dir) == 1) {
+				xassert(dir >= 0 && dir <= 3);
+				direction_ = Direction(dir);
+			} else
+				direction_ = DOWN;
+		} else
+			direction_ = DOWN;
+	} else
+		size_ = mgVect2f(-1.f, -1.f);
+
+	xassert(runtime->time() == 0.f);
+
+	lastEventTime_ = 0;
+
+}
+
+TimeManager::~TimeManager() {
+	if (timeBar_)
+		runtime->release(timeBar_);
+
+}
+
+bool TimeManager::timeIsOut() const {
+	if (gameTime_ > 0.f)
+		return runtime->time() > gameTime_;
+	return false;
+
+}
+
+float TimeManager::leftTime() const {
+	if (gameTime_ <= 0.f)
+		return 0;
+	return runtime->time() > gameTime_ ? 0 : gameTime_ - runtime->time();
+
+}
+
+void TimeManager::quant(float dt) {
+	int seconds = round(runtime->time());
+	if (seconds != lastEventTime_) {
+		lastEventTime_ = seconds;
+		runtime->textManager().updateTime(seconds);
+		int amountSeconds = round(leftTime());
+		if (gameTime_ < 0.f || amountSeconds > 10)
+			if (seconds % 60 == 0)
+				runtime->signal(EVENT_TIME_60_SECOND_TICK);
+			else if (seconds % 10 == 0)
+				runtime->signal(EVENT_TIME_10_SECOND_TICK);
+			else
+				runtime->signal(EVENT_TIME_1_SECOND_TICK);
+		else if (amountSeconds == 10)
+			runtime->signal(EVENT_TIME_10_SECOND_LEFT);
+		else
+			runtime->signal(EVENT_TIME_LESS_10_SECOND_LEFT_SECOND_TICK);
+	}
+
+	if (gameTime_ <= 0.f || !timeBar_)
+		return;
+
+	float phase = clamp(runtime->time() / gameTime_, 0.f, 1.f);
+	mgVect3f pos;
+	switch (direction_) {
+	case UP:
+		pos.y = -size_.y * phase;
+		break;
+	case DOWN:
+		pos.y = size_.y * phase;
+		break;
+	case LEFT:
+		pos.x = -size_.x * phase;
+		break;
+	case RIGHT:
+		pos.x = size_.x * phase;
+		break;
+	}
+
+	pos += startPos_;
+
+	timeBar_->set_R(runtime->game2world(pos));
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/RunTime.h b/engines/qdengine/minigames/adv/RunTime.h
new file mode 100644
index 00000000000..69ffa02410b
--- /dev/null
+++ b/engines/qdengine/minigames/adv/RunTime.h
@@ -0,0 +1,272 @@
+/* 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 QDENGINE_MINIGAMES_ADV_RUNTIME_H
+#define QDENGINE_MINIGAMES_ADV_RUNTIME_H
+
+namespace QDEngine {
+
+class qdEngineInterface;
+class qdMinigameSceneInterface;
+
+class MinigameInterface;
+class TextManager;
+class TimeManager;
+enum SystemEvent;
+class EventManager;
+class EffectManager;
+
+struct TimeManagerData {
+	mgVect3f crd;
+};
+
+struct EffectManagerData {
+	mgVect3f crd;
+};
+
+struct MinigameData {
+	MinigameData();
+	int sequenceIndex_;
+	int lastScore_;
+	int lastTime_;
+	int bestTime_;
+	int bestScore_;
+};
+
+struct GameInfo {
+	GameInfo();
+	void write(void* data, unsigned int size);
+	void free();
+	static int version() {
+		return 9;
+	}
+	bool empty() const {
+		return empty_ && game_.sequenceIndex_ < 0;
+	}
+
+	MinigameData game_;
+	bool empty_;
+	TimeManagerData timeManagerData_;
+	EffectManagerData effectManagerData_;
+	unsigned int dataSize_;
+	void *gameData_;
+};
+
+class XStream;
+XStream &operator< (XStream& out, const GameInfo& info);
+XStream &operator> (XStream& in, GameInfo& info);
+
+class MinigameManager : public qdMiniGameInterface {
+	friend class TempValue;
+public:
+	MinigameManager();
+	~MinigameManager();
+
+	// begin MiniGame virtual interface
+	bool init(const qdEngineInterface* engine_interface);
+	bool quant(float dt);
+	bool finit();
+
+	bool new_game(const qdEngineInterface* engine);
+	int save_game(const qdEngineInterface* engine, const qdMinigameSceneInterface* scene, char* buffer, int buffer_size);
+	int load_game(const qdEngineInterface* engine, const qdMinigameSceneInterface* scene, const char* buffer, int buffer_size);
+	// finish MiniGame virtual interface
+
+	// при необходимости заменяет на неизмененные предыдущим прохождением данные
+	bool processGameData(XBuffer& data);
+
+	mgVect2f mousePosition() const {
+		return mousePos_;
+	}
+	bool mouseLeftPressed() const;
+	bool mouseRightPressed() const;
+	bool keyPressed(int vKey, bool once = false) const;
+
+	mgVect2i screenSize() const {
+		return screenSize_;
+	}
+	float time() const {
+		return gameTime_;
+	}
+
+	const MinigameData *getScore(int level, int game) const;
+
+	bool debugMode() const {
+		return debugMode_;
+	}
+
+	TextManager &textManager() const {
+		return *textManager_;
+	}
+
+	void signal(SystemEvent id);
+	void event(int eventID, const mgVect2f& pos, int factor = 1);
+	void event(int eventID, const mgVect2i& pos, int factor = 1) {
+		event(eventID, mgVect2f(pos.x, pos.y), factor);
+	}
+
+	// указывает вариант показа информации о победе (поворот собранной картинки и т.д.)
+	void setCompleteHelpVariant(int idx);
+	// указывает номер подсказки для показа, -1 - спрятать подсказку
+	void setGameHelpVariant(int idx);
+
+	// Возвращает параметр из прикрепленного к игре ini файла
+	const char *parameter(const char* name, bool required = true) const;
+	const char *parameter(const char* name, const char* def) const;
+
+	// Пересчитывает из экранных координат UI игры в 3D координаты R() объекта на мире
+	mgVect3f game2world(const mgVect3i& coord) const;
+	mgVect3f game2world(const mgVect3f& coord) const;
+	mgVect3f game2world(const mgVect2i& coord, int depth = 0) const;
+	mgVect3f game2world(const mgVect2f& coord, int depth = 0) const;
+	// Пересчитывает из мировых координат R() в 2D UI координаты и глубину
+	mgVect2f world2game(const mgVect3f& pos) const;
+	mgVect3f world2game(qdMinigameObjectInterface* obj) const;
+	// размер объекта
+	mgVect2f getSize(qdMinigameObjectInterface* obj) const;
+
+	// Меняет глубину объекта, не меняя его 2D положения на экране
+	void setDepth(qdMinigameObjectInterface* obj, int depth) const;
+	// Получает глубину объекта, чем меньше, тем ближе к игроку
+	float getDepth(qdMinigameObjectInterface* obj) const;
+	// Получает глубину точки, чем меньше, тем ближе к игроку
+	float getDepth(const mgVect3f& pos) const;
+
+	// получает интерфейс к динамическому игровому объекту по имени
+	QDObject getObject(const char* name) const;
+	// проверяет существование динамического объекта в сцене
+	bool testObject(const char* name) const;
+	// освобождает интерфейс
+	void release(QDObject& obj);
+
+	// задать текст для контрола
+	void setText(const char* name, const char* text) const;
+	void setText(const char* name, int toText, const char* format = "%d") const;
+
+	// спрятать объект за пределами экрана
+	void hide(qdMinigameObjectInterface* obj) const;
+
+	// случайное значение в диапазоне [min, max]
+	float rnd(float min, float max) const;
+	int rnd(int min, int max) const;
+	// случайный диапазон, из набора вероятностей
+	int rnd(const vector<float> &prob) const;
+
+	// файл со списком игр по уровням
+	const char *gameListFileName() const {
+		return "resource\\minigames.lst";
+	}
+
+private:
+	MinigameInterface *game_;
+
+	// Вывод текста с помощью объектов
+	TextManager *textManager_;
+	// Подсчет и визуализация времени
+	TimeManager *timeManager_;
+	// Обработка событий игры
+	EventManager *eventManager_;
+	// выводимые эффекты
+	EffectManager *effectManager_;
+
+	// Время в секундах с момента стара игры
+	float gameTime_;
+	// кеш проверенных на нажатие клавиш, для отслеживания непосредственно нажатия
+	mutable bool lastKeyChecked_[256];
+	// Размер играна
+	mgVect2i screenSize_;
+	// текущее положение мыши
+	mgVect2f mousePos_;
+	// подстройка мыши
+	mgVect2f mouseAdjast_;
+
+	// объект для передачи сигнала об окончании игры в триггеры
+	qdMinigameObjectInterface *state_flag_;
+	// объект для получения сигнала о постановке на паузу
+	qdMinigameObjectInterface *pause_flag_;
+	// справка по победе
+	QDObject complete_help_;
+	QDObject complete_help_miniature_;
+	// текущее состояние для включения справки
+	string complete_help_state_name_;
+	// справка по игре
+	QDObject game_help_;
+	QDObject game_help_trigger_;
+	bool game_help_enabled_;
+	// текущее состояние для включения справки
+	string game_help_state_name_;
+
+	// интерфейс к движку
+	const qdEngineInterface *engine_;
+	// интерфейс к текущей сцене
+	qdMinigameSceneInterface *scene_;
+
+	// игра запущена для отладки
+	bool debugMode_;
+	// rnd seed
+	int seed_;
+
+	// кнопки мыши инвертированы
+	bool invertMouseButtons_;
+
+	// имя файла и информацией о минииграх
+	const char *state_container_name_;
+	// количество пройденных игр на каждом уровне
+	typedef map<int, int> Counters;
+	Counters completeCounters_;
+
+	struct GameInfoIndex {
+		GameInfoIndex(int idx, int level) : gameNum_(idx), gameLevel_(level) {}
+		int gameNum_;
+		int gameLevel_;
+		bool operator< (const GameInfoIndex& rs) const {
+			return gameLevel_ == rs.gameLevel_ ? gameNum_ < rs.gameNum_ : gameLevel_ < rs.gameLevel_;
+		}
+	};
+	// информация о пройденных играх
+	typedef map<GameInfoIndex, GameInfo> GameInfoMap;
+	GameInfoMap gameInfos_;
+	// Информация о текущей игре, при выходе запишется
+	GameInfoIndex currentGameIndex_;
+	GameInfo *currentGameInfo_;
+
+	// проверить что все необходимые игры пройдены
+	bool testAllGamesWin();
+	// Непосредственно создает и инициализирует игру
+	bool createGame();
+	// обработка победы
+	void gameWin();
+	// обработка поражения
+	void gameLose();
+	// чтение данных об играх
+	bool loadState(bool current = true);
+	// сохранение данных в файл
+	void saveState(bool force = false);
+
+	// Полуить объект-счетчик
+	QDCounter getCounter(const char* name);
+	// Освободить счетчик
+	void release(QDCounter& counter);
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_RUNTIME_H
diff --git a/engines/qdengine/minigames/adv/TextManager.cpp b/engines/qdengine/minigames/adv/TextManager.cpp
new file mode 100644
index 00000000000..1888dcbd378
--- /dev/null
+++ b/engines/qdengine/minigames/adv/TextManager.cpp
@@ -0,0 +1,367 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/TextManager.h"
+#include "qdengine/minigames/adv/RunTime.h"
+#include "qdengine/minigames/adv/qdMath.h"
+
+namespace QDEngine {
+
+TextManager::TextManager() {
+	char str_cache[256];
+
+	for (int idx = 0;; ++idx) {
+		_snprintf(str_cache, 127, "register_font_%d", idx);
+		if (const char * descr = runtime->parameter(str_cache, false)) {
+			sscanf(descr, "%255s", str_cache);
+			Font digit;
+			if (!digit.pool.load(str_cache))
+				break;
+			dprintf("%d character set \"%s\" loaded, ", idx, str_cache);
+			_snprintf(str_cache, 127, "font_size_%d", idx);
+			if (descr = runtime->parameter(str_cache, false)) {
+				int read = sscanf(descr, "%f %f", &digit.size.x, &digit.size.y);
+				xxassert(read == 2, (XBuffer() < "Неверная строка с размерами шрифта в [" < str_cache < "]").c_str());
+				dprintf("re");
+			} else {
+				QDObject& obj = digit.pool.getObject();
+				obj->set_state("0");
+				digit.size = runtime->getSize(obj);
+				digit.pool.releaseObject(obj);
+			}
+			dprintf("set size to (%5.1f, %5.1f)\n", digit.size.x, digit.size.y);
+			fonts_.push_back(digit);
+		} else
+			break;
+	}
+
+	for (int idx = 0;; ++idx) {
+		_snprintf(str_cache, 127, "register_particle_escape_%d", idx);
+		if (const char * descr = runtime->parameter(str_cache, false)) {
+			Escape escape;
+			int read = sscanf(descr, "%d (%f><%f, %f><%f) (%f><%f, %f><%f) %f '%15s",
+			                  &escape.depth,
+			                  &escape.vel_min.x, &escape.vel_max.x, &escape.vel_min.y, &escape.vel_max.y,
+			                  &escape.accel_min.x, &escape.accel_max.x, &escape.accel_min.y, &escape.accel_max.y,
+			                  &escape.aliveTime, escape.format);
+			xxassert(read == 11, (XBuffer() < "Неверная строка для описания полета в [" < str_cache < "]").c_str());
+			if (read != 11)
+				break;
+			escapes_.push_back(escape);
+		} else
+			break;
+	}
+	dprintf("registered %d particle escapes\n", escapes_.size());
+
+	if (getStaticPreset(show_scores_, "show_scores"))
+		show_scores_.textID = createStaticText(show_scores_.pos, show_scores_.font, show_scores_.align);
+	else
+		show_scores_.textID = -1;
+
+	if (getStaticPreset(show_time_, "show_time"))
+		show_time_.textID = createStaticText(show_time_.pos, show_time_.font, show_time_.align);
+	else
+		show_time_.textID = -1;
+
+	targetScore_ = 0;
+	currentScore_ = 0;
+	scoreUpdateTimer_ = 0.f;
+
+	scoreUpdateTime_ = getParameter("score_update_time", 0.1f);
+}
+
+bool TextManager::getStaticPreset(StaticTextPreset& preset, const char* name) const {
+	if (const char * descr = runtime->parameter(name, false)) {
+		int align = 0;
+		char str[64];
+		str[63] = 0;
+		int read = sscanf(descr, "%d %d |%63s", &align, &preset.font, str);
+		xxassert(read == 3, (XBuffer() < "Неверная строка для описания формата текста в [" < name < "]").c_str());
+		if (read != 3)
+			return false;
+
+		char *pos_obj = strchr(str, '|');
+		xxassert(pos_obj, (XBuffer() < "Неверная строка для описания формата текста в [" < name < "]").c_str());
+		if (!pos_obj)
+			return false;
+		*pos_obj = 0;
+		++pos_obj;
+
+		strncpy(preset.format, str, 15);
+
+		switch (align) {
+		case 0:
+			preset.align = ALIGN_RIGHT;
+			break;
+		case 1:
+			preset.align = ALIGN_LEFT;
+			break;
+		default:
+			preset.align = ALIGN_CENTER;
+			break;
+		}
+
+		if (QDObject obj = runtime->getObject(pos_obj)) {
+			preset.pos = runtime->world2game(obj);
+			runtime->release(obj);
+		} else
+			return false;
+	} else
+		return false;
+
+	return true;
+}
+
+TextManager::~TextManager() {
+	Messages::iterator mit;
+	FOR_EACH(flowMsgs_, mit)
+	mit->release();
+
+	StaticMessages::iterator sit;
+	FOR_EACH(staticMsgs_, sit)
+	sit->release();
+
+	Fonts::iterator dit;
+	FOR_EACH(fonts_, dit)
+	dit->pool.release();
+}
+
+int TextManager::createStaticText(const mgVect3f& pos, int fontID, TextAlign align) {
+	xassert(fontID >= 0 && fontID < fonts_.size());
+
+	StaticMessage msg(&fonts_[fontID]);
+
+	msg.align_ = align;
+	msg.depth_ = pos.z;
+	msg.pos_ = mgVect2f(pos.x, pos.y);
+
+	staticMsgs_.push_back(msg);
+	return (int)staticMsgs_.size() - 1;
+}
+
+void TextManager::updateStaticText(int textID, const char* txt) {
+	xassert(textID >= 0 && textID < staticMsgs_.size());
+
+	staticMsgs_[textID].setText(txt);
+}
+
+void TextManager::showText(const char* txt, const mgVect2f& pos, int fontID, int escapeID) {
+	xassert(fontID >= 0 && fontID < fonts_.size());
+	xassert(escapeID >= 0 && escapeID < escapes_.size());
+
+	Escape& es = escapes_[escapeID];
+
+	Message msg(&fonts_[fontID]);
+
+	msg.setText(txt);
+	if (msg.empty())
+		return;
+
+	msg.time_ = es.aliveTime > 0 ? es.aliveTime : 1.e6f;
+
+	msg.depth_ = es.depth;
+	msg.pos_ = pos;
+
+	msg.vel_.x = runtime->rnd(es.vel_min.x, es.vel_max.x);
+	msg.vel_.y = runtime->rnd(es.vel_min.y, es.vel_max.y);
+	msg.accel_.x = runtime->rnd(es.accel_min.x, es.accel_max.x);
+	msg.accel_.y = runtime->rnd(es.accel_min.y, es.accel_max.y);
+
+	flowMsgs_.push_back(msg);
+}
+
+void TextManager::showNumber(int num, const mgVect2f& pos, int fontID, int escapeID) {
+	xassert(fontID >= 0 && fontID < fonts_.size());
+	xassert(escapeID >= 0 && escapeID < escapes_.size());
+
+	char buf[16];
+	buf[15] = 0;
+	_snprintf(buf, 15, escapes_[escapeID].format, num);
+
+	showText(buf, pos, fontID, escapeID);
+}
+
+TextManager::Escape::Escape() {
+	depth = 0;
+	aliveTime = -1;
+	format[15] = 0;
+}
+
+TextManager::StaticTextPreset::StaticTextPreset() {
+	font = -1;
+	align = ALIGN_CENTER;
+	format[15] = 0;
+}
+
+TextManager::StaticMessage::StaticMessage(Font* font, TextAlign align_) {
+	font_ = font;
+	align_ = align_;
+	depth_ = 0.f;
+}
+
+void TextManager::StaticMessage::release() {
+	QDObjects::iterator it;
+	FOR_EACH(objects_, it)
+	font_->pool.releaseObject(*it);
+	objects_.clear();
+}
+
+void TextManager::StaticMessage::setText(const char* str) {
+	xassert(font_);
+
+	if (!str) {
+		release();
+		return;
+	}
+
+	int len = (int)strlen(str);
+
+	if (objects_.size() < len)
+		objects_.resize(len);
+	else
+		while (objects_.size() > len) {
+			if (objects_.back())
+				font_->pool.releaseObject(objects_.back());
+			objects_.pop_back();
+		}
+
+	for (int idx = 0; idx < len; ++idx) {
+		if (validSymbol(str[idx])) {
+			if (!objects_[idx])
+				objects_[idx] = font_->pool.getObject();
+		} else if (objects_[idx])
+			font_->pool.releaseObject(objects_[idx]);
+	}
+
+	char name[2];
+	name[1] = 0;
+	for (int idx = 0; idx < len; ++idx) {
+		if (objects_[idx]) {
+			name[0] = str[idx];
+			objects_[idx].setState(name);
+		}
+	}
+
+	update();
+}
+
+void TextManager::StaticMessage::update() {
+	if (objects_.empty())
+		return;
+
+	float width = font_->size.x * (objects_.size() - 1);
+	float x = pos_.x;
+	float y = pos_.y;
+	switch (align_) {
+	case ALIGN_RIGHT:
+		x -= width;
+		break;
+	case ALIGN_CENTER:
+		x -= width / 2.f;
+		break;
+	}
+	if (y < -font_->size.y || y > runtime->screenSize().y + font_->size.y
+	        || x < -2 * width || x > runtime->screenSize().x + 2 * width) {
+		release();
+		return;
+	}
+
+	QDObjects::iterator it;
+	FOR_EACH(objects_, it) {
+		if (*it)
+			(*it)->set_R(runtime->game2world(mgVect2f(x, y), depth_));
+		x += font_->size.x;
+	}
+}
+
+TextManager::Message::Message(Font* font)
+	: StaticMessage(font) {
+	time_ = 0.f;
+}
+
+void TextManager::Message::release() {
+	StaticMessage::release();
+	time_ = 0.f;
+}
+
+void TextManager::Message::quant(float dt) {
+	if (empty())
+		return;
+
+	time_ -= dt;
+	if (time_ < 0.f) {
+		release();
+		return;
+	}
+
+	vel_ += accel_ * dt;
+	pos_ += vel_ * dt;
+
+	update();
+}
+
+void TextManager::quant(float dt) {
+	Messages::iterator it = flowMsgs_.begin();
+	while (it != flowMsgs_.end()) {
+		it->quant(dt);
+		if (it->empty())
+			it = flowMsgs_.erase(it);
+		else
+			++it;
+	}
+
+	if (show_scores_.textID >= 0) {
+		if (scoreUpdateTimer_ >= 0.f && scoreUpdateTimer_ <= runtime->time()) {
+			int sgn = SIGN(targetScore_ - currentScore_);
+			int mod = abs(currentScore_ - targetScore_);
+			currentScore_ += sgn * (mod / 10 + 1);
+
+			char buf[16];
+			buf[15] = 0;
+			_snprintf(buf, 15, show_scores_.format, currentScore_);
+			updateStaticText(show_scores_.textID, buf);
+
+			scoreUpdateTimer_ = currentScore_ != targetScore_ ? runtime->time() + scoreUpdateTime_ : -1.f;
+		}
+	}
+}
+
+void TextManager::updateScore(int score) {
+	targetScore_ = score;
+	if (scoreUpdateTimer_ < 0.f)
+		scoreUpdateTimer_ = runtime->time();
+}
+
+void TextManager::updateTime(int seconds) {
+	if (show_time_.textID >= 0) {
+		char buf[16];
+		buf[15] = 0;
+		int h = seconds / 3600;
+		seconds -= 3600 * h;
+		int minutes = seconds / 60;
+		seconds -= 60 * minutes;
+		_snprintf(buf, 15, show_time_.format, h, minutes, seconds);
+		updateStaticText(show_time_.textID, buf);
+	}
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/TextManager.h b/engines/qdengine/minigames/adv/TextManager.h
new file mode 100644
index 00000000000..e7b37bd3a64
--- /dev/null
+++ b/engines/qdengine/minigames/adv/TextManager.h
@@ -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/>.
+ *
+ */
+
+#ifndef QDENGINE_MINIGAMES_ADV_TEXT_MANAGER_H
+#define QDENGINE_MINIGAMES_ADV_TEXT_MANAGER_H
+
+#include "qdengine/minigames/adv/ObjectContainer.h"
+
+namespace QDEngine {
+
+enum TextAlign {
+	ALIGN_LEFT,
+	ALIGN_RIGHT,
+	ALIGN_CENTER
+};
+
+class TextManager {
+public:
+	TextManager();
+	~TextManager();
+
+	int createStaticText(const mgVect3f& screen_pos, int fontID, TextAlign align);
+	void updateStaticText(int textID, const char* txt);
+
+	void showText(const char* txt, const mgVect2f& pos, int fontID, int escapeID);
+	void showNumber(int num, const mgVect2f& pos, int fontID, int escapeID);
+
+	void quant(float dt);
+	void updateScore(int score);
+	void updateTime(int seconds);
+
+private:
+	struct Font {
+		mgVect2f size;
+		ObjectContainer pool;
+	};
+	typedef vector<Font> Fonts;
+
+	struct Escape {
+		Escape();
+		int depth;
+		float aliveTime;
+		mgVect2f vel_min;
+		mgVect2f vel_max;
+		mgVect2f accel_min;
+		mgVect2f accel_max;
+		char format[16];
+	};
+	typedef vector<Escape> Escapes;
+
+	struct StaticTextPreset {
+		StaticTextPreset();
+		mgVect3f pos;
+		int font;
+		TextAlign align;
+		char format[16];
+		int textID;
+	};
+	bool getStaticPreset(StaticTextPreset& preset, const char* name) const;
+
+	struct StaticMessage {
+		StaticMessage(Font* font = 0, TextAlign align_ = ALIGN_CENTER);
+		void release();
+
+		bool empty() const {
+			return objects_.empty();
+		}
+
+		void setText(const char* str);
+
+		int depth_;
+		mgVect2f pos_;
+		TextAlign align_;
+
+	protected:
+		void update();
+		bool validSymbol(unsigned char ch) const {
+			return ch > ' ' && ch != '_';
+		}
+
+	private:
+		Font *font_;
+
+		QDObjects objects_;
+	};
+	typedef vector<StaticMessage> StaticMessages;
+
+	struct Message : public StaticMessage {
+		Message(Font* font = 0);
+		void release();
+
+		void quant(float dt);
+
+		float time_;
+		mgVect2f vel_;
+		mgVect2f accel_;
+
+	};
+	typedef vector<Message> Messages;
+
+	Fonts fonts_;
+	Escapes escapes_;
+	StaticTextPreset show_scores_;
+	StaticTextPreset show_time_;
+	StaticMessages staticMsgs_;
+	Messages flowMsgs_;
+
+	int targetScore_;
+	int currentScore_;
+	float scoreUpdateTime_;
+	float scoreUpdateTimer_;
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_TEXT_MANAGER_H
diff --git a/engines/qdengine/minigames/adv/common.cpp b/engines/qdengine/minigames/adv/common.cpp
new file mode 100644
index 00000000000..dc377fd39e2
--- /dev/null
+++ b/engines/qdengine/minigames/adv/common.cpp
@@ -0,0 +1,156 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/RunTime.h"
+
+namespace QDEngine {
+
+QDObject QDObject::ZERO(0, "ZERO OBJECT");
+
+const char *QDObject::getName() const {
+	#ifdef _DEBUG
+	return name_.c_str();
+	#else
+	return "";
+	#endif
+}
+
+bool QDObject::hit(const mgVect2f& point) const {
+	return obj_->hit_test(mgVect2i(round(point.x), round(point.y)));
+}
+
+float QDObject::depth() const {
+	return runtime->getDepth(obj_);
+}
+
+void QDObject::setState(const char* name) {
+	if (!obj_->is_state_active(name))
+		obj_->set_state(name);
+}
+
+template<class T>
+T getParameter(const char* name, const T& defValue) {
+	return round(getParameter<float>(name, (float)defValue));
+}
+
+template<class T>
+bool getParameter(const char* name, T& out, bool obligatory) {
+	float retValue = out;
+	if (getParameter<float>(name, retValue, obligatory)) {
+		out = round(retValue);
+		return true;
+	}
+	return false;
+}
+
+template<>
+float getParameter(const char* name, const float &defValue) {
+	if (const char * data = runtime->parameter(name, false)) {
+		float retValue = defValue;
+		if (_snscanf(data, 8, "%f", &retValue) == 1)
+			return retValue;
+		xxassert(false, (XBuffer() < "В параметре [" < name < "] неверный тип данных. Должно быть число.").c_str());
+	}
+	return defValue;
+
+}
+
+template<>
+bool getParameter(const char* name, float &out, bool obligatory) {
+	if (const char * data = runtime->parameter(name, obligatory)) {
+		float retValue = out;
+		if (_snscanf(data, 8, "%f", &retValue) == 1) {
+			out = retValue;
+			return true;
+		}
+		xxassert(false, (XBuffer() < "В параметре [" < name < "] неверный тип данных. Должно быть число.").c_str());
+	}
+	return false;
+
+}
+
+template<>
+mgVect2f getParameter(const char* name, const mgVect2f& defValue) {
+	if (const char * data = runtime->parameter(name, false)) {
+		mgVect2f retValue = defValue;
+		if (_snscanf(data, 16, "%f %f", &retValue.x, &retValue.y) == 2)
+			return retValue;
+		xxassert(false, (XBuffer() < "В параметре [" < name < "] неверный тип данных. Должна быть пара чисел.").c_str());
+	}
+	return defValue;
+
+}
+
+template<>
+bool getParameter(const char* name, mgVect2f& out, bool obligatory) {
+	if (const char * data = runtime->parameter(name, obligatory)) {
+		mgVect2f retValue = out;
+		if (_snscanf(data, 16, "%f %f", &retValue.x, &retValue.y) == 2) {
+			out = retValue;
+			return true;
+		}
+		xxassert(false, (XBuffer() < "В параметре [" < name < "] неверный тип данных. Должна быть пара чисел.").c_str());
+	}
+	return false;
+
+}
+
+template<>
+mgVect2i getParameter(const char* name, const mgVect2i& defValue) {
+	mgVect2f retValue = getParameter(name, mgVect2f(defValue.x, defValue.y));
+	return mgVect2i(round(retValue.x), round(retValue.y));
+
+}
+
+template<>
+bool getParameter(const char* name, mgVect2i& out, bool obligatory) {
+	mgVect2f retValue = mgVect2f(out.x, out.y);
+	if (getParameter<mgVect2f>(name, retValue, obligatory)) {
+		out = mgVect2i(round(retValue.x), round(retValue.y));
+		return true;
+	}
+	return false;
+}
+
+void dummyInstanceGetParameter() {
+	bool db = false;
+	getParameter("", db);
+	getParameter("", db, false);
+
+	int di = 0;
+	getParameter("", di);
+	getParameter("", di, false);
+
+	float df = 0.f;
+	getParameter("", df);
+	getParameter("", df, false);
+
+	mgVect2i d2i;
+	getParameter("", d2i);
+	getParameter("", d2i, false);
+
+	mgVect2i d2f;
+	getParameter("", d2f);
+	getParameter("", d2f, false);
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/common.h b/engines/qdengine/minigames/adv/common.h
new file mode 100644
index 00000000000..b4b01109dc1
--- /dev/null
+++ b/engines/qdengine/minigames/adv/common.h
@@ -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 QDENGINE_MINIGAMES_ADV_COMMON_H
+#define QDENGINE_MINIGAMES_ADV_COMMON_H
+
+#include "qdengine/qdcore/qd_minigame_interface.h"
+
+namespace QDEngine {
+
+void dprintf(const char *format, ...);
+
+typedef mgVect3<int> mgVect3i;
+
+using namespace std;
+
+class QDObject {
+	qdMinigameObjectInterface *obj_;
+
+	#ifdef _DEBUG
+	string name_;
+	#endif
+
+public:
+	static QDObject ZERO;
+
+	QDObject(qdMinigameObjectInterface* obj = 0, const char* name = "") : obj_(obj) {
+		#ifdef _DEBUG
+		name_ = name;
+		#endif
+	}
+
+	const char *getName() const; // DEBUG ONLY
+	bool hit(const mgVect2f& point) const;
+	float depth() const;
+
+	void setState(const char* name);
+
+	bool operator==(const QDObject& obj) const {
+		return obj_ == obj.obj_;
+	}
+	bool operator==(const qdMinigameObjectInterface* obj) const {
+		return obj_ == obj;
+	}
+
+	operator qdMinigameObjectInterface* () const {
+		return obj_;
+	}
+	qdMinigameObjectInterface* operator->() const {
+		return obj_;
+	}
+};
+
+typedef qdMinigameCounterInterface *QDCounter;
+
+typedef vector<QDObject> QDObjects;
+typedef vector<int> Indexes;
+typedef vector<mgVect3f> Coords;
+
+class MinigameManager;
+extern MinigameManager *runtime;
+
+template<class T>
+T getParameter(const char* name, const T& defValue);
+
+template<class T>
+bool getParameter(const char* name, T& out, bool obligatory);
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_COMMON_H
diff --git a/engines/qdengine/minigames/adv/m_karaoke.cpp b/engines/qdengine/minigames/adv/m_karaoke.cpp
new file mode 100644
index 00000000000..858e1cb6e4e
--- /dev/null
+++ b/engines/qdengine/minigames/adv/m_karaoke.cpp
@@ -0,0 +1,197 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/m_karaoke.h"
+#include "qdengine/minigames/adv/RunTime.h"
+
+namespace QDEngine {
+
+MinigameInterface *createGame() {
+	return new Karaoke;
+}
+
+Karaoke::Node::Node() {
+	type = Karaoke::CLEAR;
+	time = 0.f;
+
+}
+
+
+Karaoke::Karaoke() {
+	controlName_ = runtime->parameter("control_name", true);
+	if (!controlName_ || !*controlName_)
+		return;
+
+	colorReaded_ = runtime->parameter("color_first", true);
+	if (!colorReaded_ || !*colorReaded_)
+		return;
+
+	struct Parse {
+		XStream &file;
+		Nodes &nodes;
+
+		float currentTime;
+
+		char buf[1024];
+		const char *begin;
+		const char *cur;
+
+		void putLine(bool putEmpty = false) {
+			if (cur > begin && *begin) {
+				Node node;
+				node.type = STRING;
+				node.time = currentTime;
+				node.text = string(begin, cur);
+				nodes.push_back(node);
+			} else if (putEmpty) {
+				Node node;
+				node.type = STRING;
+				node.time = currentTime;
+				nodes.push_back(node);
+			}
+			begin = cur = buf;
+		}
+
+		char read() {
+			if (!file.read((void *)cur, 1)) {
+				putLine();
+				return 0;
+			}
+			return *cur++;
+		}
+
+		Parse(XStream&  _file, Nodes& _nodes) : file(_file), nodes(_nodes) {
+			currentTime = 0.f;
+			begin = cur = buf;
+			bool prevNumber = false;
+			while (!file.eof()) {
+				switch (read()) {
+				case 0:
+					return;
+				case '/': {
+					if (read() == '/') {
+						--cur;
+						break;
+					}
+					cur -= 2;
+					putLine(prevNumber);
+					prevNumber = true;
+
+					file.seek(-1, XS_CUR);
+					float tm = 0;
+					file >= tm;
+
+					if (tm <= 0.f) {
+						currentTime = 0.f;
+						nodes.push_back(Node());
+					} else
+						currentTime = tm;
+					file.seek(-1, XS_CUR);
+					continue;
+				}
+				case '>':
+					if (prevNumber)
+						--cur;
+				}
+				prevNumber = false;
+			}
+			putLine();
+
+		}
+	};
+
+	const char *fileName = runtime->parameter("text_file", true);
+	if (!fileName)
+		return;
+
+	XStream file(false);
+	if (!file.open(fileName, XS_IN)) {
+		xxassert(false, (XBuffer() < "Не удалось открыть файл \"" < fileName < "\"").c_str());
+		return;
+	}
+
+	Parse(file, nodes_);
+	dprintf("read %d tags\n", nodes_.size());
+
+	startScreenTag_ = 0;
+	currentTag_ = 0;
+
+	startTime_ = 0.001f * GetTickCount();
+	startTagTime_ = 0.f;
+
+	setState(MinigameInterface::RUNNING);
+
+}
+
+void Karaoke::quant(float dt) {
+	float curTime = 0.001f * GetTickCount() - startTime_;
+	if (curTime < 0.f)
+		curTime = 0.f;
+
+	Node& node = nodes_[currentTag_];
+	if (node.type == CLEAR) {
+		++currentTag_;
+		if (currentTag_ == nodes_.size())
+			setState(MinigameInterface::GAME_WIN);
+		startScreenTag_ = currentTag_;
+		return;
+	}
+
+	XBuffer outText;
+	outText < colorReaded_;
+	int idx = startScreenTag_;
+	while (idx < currentTag_) {
+		xassert(idx < nodes_.size());
+		xassert(nodes_[idx].type == STRING);
+		outText < nodes_[idx].text.c_str();
+		++idx;
+	}
+
+	float phase = (curTime - startTagTime_) / node.time;
+	xassert(phase >= 0.f);
+	if (phase >= 1.f) {
+		outText < node.text.c_str() < "&>";
+		++currentTag_;
+		startTagTime_ += node.time;
+		if (currentTag_ == nodes_.size())
+			setState(MinigameInterface::GAME_WIN);
+	} else {
+		int part = phase * node.text.size();
+		outText < string(node.text.begin(), node.text.begin() + part).c_str() < "&>";
+		outText < string(node.text.begin() + part, node.text.end()).c_str();
+	}
+
+	++idx;
+	while (idx < nodes_.size()) {
+		if (nodes_[idx].type == CLEAR)
+			break;
+		outText < nodes_[idx].text.c_str();
+		++idx;
+	}
+
+	if (runtime->mouseRightPressed())
+		dprintf("%s\n", outText.c_str());
+
+	runtime->setText(controlName_, outText.c_str());
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/m_karaoke.h b/engines/qdengine/minigames/adv/m_karaoke.h
new file mode 100644
index 00000000000..6580074241a
--- /dev/null
+++ b/engines/qdengine/minigames/adv/m_karaoke.h
@@ -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 QDENGINE_MINIGAMES_ADV_M_KARAOKE_H
+#define QDENGINE_MINIGAMES_ADV_M_KARAOKE_H
+
+#include "qdengine/minigames/adv/MinigameInterface.h"
+
+namespace QDEngine {
+
+class Karaoke : public MinigameInterface {
+public:
+	Karaoke();
+	void quant(float dt);
+
+	enum TagType {
+		STRING,
+		CLEAR
+	};
+
+private:
+	const char *controlName_;
+	const char *colorReaded_;
+
+	struct Node {
+		Node();
+		TagType type;
+		float time;
+		string text;
+	};
+
+	typedef vector<Node> Nodes;
+	Nodes nodes_;
+
+	float startTime_;
+	int startScreenTag_;
+	int currentTag_;
+	float startTagTime_;
+
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_M_KARAOKE_H
diff --git a/engines/qdengine/minigames/adv/m_puzzle.cpp b/engines/qdengine/minigames/adv/m_puzzle.cpp
new file mode 100644
index 00000000000..41e818de85d
--- /dev/null
+++ b/engines/qdengine/minigames/adv/m_puzzle.cpp
@@ -0,0 +1,460 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/m_puzzle.h"
+#include "qdengine/minigames/adv/RunTime.h"
+#include "qdengine/minigames/adv/Rect.h"
+#include "qdengine/minigames/adv/qdMath.h"
+#include "qdengine/system/input/keyboard_input.h"
+
+namespace QDEngine {
+
+typedef Rect<float, mgVect2f> Rectf;
+
+MinigameInterface *createGame() {
+	return new Puzzle;
+}
+
+enum {
+	EVENT_GET,
+	EVENT_PUT,
+	EVENT_SWAP,
+	EVENT_ROTATE_IN_FIELD,
+	EVENT_RETURN,
+	EVENT_PUT_RIGHT,
+	EVENT_CLICK_RIGHT,
+	EVENT_CLICK,
+	EVENT_ROTATE_IN_STACK,
+	EVENT_FIELD_ROTATE
+};
+
+const char *Puzzle::getStateName(int angle, bool selected, bool small) const {
+	static const char *small_pref = "inv_";
+	static const char *selected_suf = "_sel";
+
+	static char buf[32];
+	buf[31] = 0;
+
+	xassert(angle >= 0 && angle < angles_);
+	angle = (angle + globalAngle_) % angles_;
+
+	_snprintf(buf, 31, "%s%02d%s", !singleSize_ && small ? small_pref : "", angle + 1, selected ? selected_suf : "");
+	return buf;
+}
+
+Puzzle::Puzzle() {
+	if (!getParameter("game_size", gameSize_, true))
+		return;
+	xassert(gameSize_ > 0 && gameSize_ < 100);
+
+	field_.resize(gameSize_, -1);
+	globalAngle_ = 0;
+
+	singleSize_ = getParameter("small_objects", false);
+
+	angles_ = getParameter("angles", 4);
+	xassert(angles_ > 0 &&  angles_ < 10);
+
+	if (!(stackBottom_ = runtime->getObject(runtime->parameter("inventory_bottom"))))
+		return;
+	if (!getParameter("inventory_size", stackSize_, true))
+		return;
+
+	if (getParameter("rotate_period", rotateTimePeriod_, false)) {
+		xassert(sqr(sqrt((float)gameSize_)) == gameSize_);
+		if (sqr(sqrt((float)gameSize_)) != gameSize_)
+			return;
+	} else
+		rotateTimePeriod_ = 86400; // сутки
+	nextRotateTime_ = runtime->time() + rotateTimePeriod_;
+
+	flySpeed_ = getParameter("inventory_drop_speed", 240.f);
+	xassert(flySpeed_ > 0.f);
+	returnSpeed_ = getParameter("inventory_return_speed", -1.f);
+
+	const char *name_begin = runtime->parameter("obj_name_begin", "obj_");
+
+	char buf[128];
+	buf[127] = 0;
+
+	XBuffer gameData;
+	for (int idx = 0; idx < gameSize_; ++idx) {
+		_snprintf(buf, 127, "%s%02d", name_begin, idx + 1);
+
+		Node node;
+		node.obj = runtime->getObject(buf);
+
+		if (runtime->debugMode()) {
+			node.pos = nodes_.size();
+			node.angle = 0;
+			field_[node.pos] = node.pos;
+		} else
+			node.angle = runtime->rnd(0, angles_ - 1);
+		node.obj.setState(getStateName(node.angle, false, true));
+
+		gameData.write(node.obj->R());
+
+		nodes_.push_back(node);
+	}
+
+	if (!runtime->processGameData(gameData))
+		return;
+
+	for (int idx = 0; idx < gameSize_; ++idx) {
+		mgVect3f crd;
+		gameData.read(crd);
+		nodes_[idx].obj->set_R(crd);
+		positions_.push_back(crd);
+	}
+
+	if (runtime->debugMode())
+		nodes_[0].angle = angles_ - 1;
+
+	size_ = runtime->getSize(nodes_[0].obj);
+	dprintf("size = (%6.2f,%6.2f)\n", size_.x, size_.y);
+
+	depth_ = nodes_[0].obj.depth();
+
+	stackPlaceSize_ = getParameter("inventory_place_size", size_ * 1.2f);
+	xassert(stackPlaceSize_.x > 0.f && stackPlaceSize_.x < 500.f && stackPlaceSize_.y > 0.f && stackPlaceSize_.y < 500.f);
+	dprintf("stackPlaceSize = (%5.1f, %5.1f)\n", stackPlaceSize_.x, stackPlaceSize_.y);
+
+	prevPlace_ = -1;
+	pickedItem_ = -1;
+	mouseObjPose_ = stidx(stackSize_ + 1);
+
+	inField_ = runtime->debugMode() ? nodes_.size() : 0;
+	nextObjTime_ = runtime->time();
+
+	setState(MinigameInterface::RUNNING);
+}
+
+Puzzle::~Puzzle() {
+	Nodes::iterator it;
+	FOR_EACH(nodes_, it)
+	runtime->release(it->obj);
+
+	runtime->release(stackBottom_);
+}
+
+void Puzzle::rotate(int item) {
+	xassert(item >= 0 && item < nodes_.size());
+	nodes_[item].angle = (nodes_[item].angle + 1) % angles_;
+}
+
+int Puzzle::stidx(int idx) const {
+	return -idx - 2;
+}
+
+bool Puzzle::testPlace(int item) const {
+	xassert(item >= 0 && item < nodes_.size());
+	return nodes_[item].pos == item && nodes_[item].angle == 0;
+}
+
+bool Puzzle::isFlying(int idx) const {
+	FlyQDObjects::const_iterator it;
+	FOR_EACH(flyObjs_, it)
+	if (it->data == idx)
+		return true;
+	return false;
+}
+
+bool Puzzle::isOnMouse(const Node& node) const {
+	if (node.pos == mouseObjPose_) {
+		return true;
+	}
+	return false;
+}
+
+void Puzzle::put(int where, int what, float flowSpeed) {
+	xassert(where < (int)field_.size());
+	xassert(what >= 0 && what < nodes_.size());
+
+	Node& node = nodes_[what];
+	int start = node.pos;
+
+	if (flowSpeed > 0.f || isFlying(what)) {
+		FlyQDObject* flyObj = 0;
+
+		FlyQDObjects::iterator fit;
+		FOR_EACH(flyObjs_, fit)
+		if (fit->data == what)
+			break;
+		if (fit != flyObjs_.end()) // Этот фрагмент уже летит, просто поменять точку назначения
+			flyObj = &*fit;
+		else { // Добавляем новый летящий фрагмент
+			flyObjs_.push_back(FlyQDObject());
+			flyObj = &flyObjs_.back();
+
+			flyObj->data = what;
+
+			mgVect3f from = isOnMouse(node) ? node.obj->R() : start < -1 ? stackPosition(stidx(start)) : position(start);
+			flyObj->current = runtime->world2game(from);
+			node.obj->set_R(from);
+
+			flyObj->speed = flowSpeed;
+		}
+
+		mgVect3f to = where < -1 ? stackPosition(stidx(where)) : position(where);
+		flyObj->target = runtime->world2game(to);
+		flyObj->depth = runtime->getDepth(to);
+	}
+
+	if (where >= 0)
+		field_[where] = what;
+
+	node.pos = where;
+}
+
+void Puzzle::putOnStack(int what, float speed) {
+	put(stidx((int)stack_.size()), what, speed);
+	stack_.push_back(what);
+}
+
+void Puzzle::returnToStack() {
+	xassert(pickedItem_ != -1);
+	runtime->event(EVENT_RETURN, runtime->mousePosition());
+	if (prevPlace_ >= 0)
+		put(prevPlace_, pickedItem_);
+	else
+		putOnStack(pickedItem_, returnSpeed_);
+	prevPlace_ = -1;
+	pickedItem_ = -1;
+	runtime->event(EVENT_CLICK, runtime->mousePosition());
+}
+
+void Puzzle::quant(float dt) {
+	if (pickedItem_ == -1)
+		runtime->setGameHelpVariant(0);
+	else
+		runtime->setGameHelpVariant(1);
+
+	if (runtime->time() > nextRotateTime_) {
+		runtime->event(EVENT_FIELD_ROTATE, mgVect2f(400, 300));
+		nextRotateTime_ = runtime->time() + rotateTimePeriod_;
+		globalAngle_ = (globalAngle_ + 1) % angles_;
+		runtime->setCompleteHelpVariant(globalAngle_);
+	}
+
+	FlyQDObjects::iterator fit = flyObjs_.begin();
+	while (fit != flyObjs_.end())
+		if (!isOnMouse(nodes_[fit->data]) && fit->quant(dt, nodes_[fit->data].obj))
+			++fit;
+		else
+			fit = flyObjs_.erase(fit);
+
+	if (inField_ < nodes_.size() && runtime->time() > nextObjTime_ &&
+	(stack_.size() < stackSize_ - 1 || stack_.size() < stackSize_ && pickedItem_ == -1)) { // нужно добавить в инвентори фишку
+		// ищем случайный не выставленный фрагмент
+		int freeIdx = round(runtime->rnd(0.f, nodes_.size() - 1));
+		Nodes::iterator it = nodes_.begin();
+		for (;;) {
+			if (++it == nodes_.end())
+				it = nodes_.begin();
+			if (it->isFree())
+				if (!freeIdx--)
+					break;
+		}
+		int idx = distance(nodes_.begin(), it);
+
+		++inField_;
+		nextObjTime_ = runtime->time() + stackPlaceSize_.y / flySpeed_;
+
+		it->pos = stidx(stackSize_);
+		it->obj.setState(getStateName(it->angle, false, true));
+
+		putOnStack(idx, flySpeed_);
+	}
+
+	mgVect2f mouse = runtime->mousePosition();
+
+	int hovPlace = -1;  // Номер места которое сейчас под мышкой
+	for (int idx = 0; idx < stack_.size(); ++idx)
+		if (nodes_[stack_[idx]].obj.hit(mouse)) {
+			hovPlace = stidx(idx);
+			break;
+		}
+	if (hovPlace == -1) {
+		float radius = 0.5f * size_.x;
+		for (int idx = 0; idx < gameSize_; ++idx)
+			if (dist(runtime->world2game(position(idx)), mouse) < radius) {
+				hovPlace = idx;
+				break;
+			}
+	}
+	if (hovPlace == -1) {
+		mgVect2i st = stackBottom_->screen_R();
+		st.y -= stackPlaceSize_.y * stackSize_ - 0.5f * stackPlaceSize_.x;
+		Rectf stackPos(st.x - 0.5f * stackPlaceSize_.x, st.y, stackPlaceSize_.x, stackPlaceSize_.y * stackSize_);
+		if (stackPos.point_inside(mouse))
+			hovPlace = stidx(stackSize_);
+	}
+
+	if (runtime->mouseLeftPressed()) {
+		if (hovPlace >= 0) { // клик по полю
+			Indexes::value_type& hovItem = field_[hovPlace];
+			if (hovItem == -1) // клик по пустой ячейке
+				if (pickedItem_ == -1) // на мыши ничего нет
+					runtime->event(EVENT_CLICK, mouse);
+				else { // кладем фрагмент с мыши
+					put(hovPlace, pickedItem_);
+					if (testPlace(pickedItem_)) // положили на свое свое место
+						runtime->event(EVENT_PUT_RIGHT, mouse);
+					else // просто положили
+						runtime->event(EVENT_PUT, mouse);
+					pickedItem_ = -1;
+					prevPlace_ = -1;
+				} else { // клик по непустой ячейке
+				if (testPlace(hovPlace)) // клик по правильно уложенной фишке
+					runtime->event(EVENT_CLICK_RIGHT, mouse);
+				else if (pickedItem_ != -1) { // поменять с тем что на мыше
+					bool swap = true;
+					if (prevPlace_ >= 0)
+						put(prevPlace_, hovItem);
+					else
+						putOnStack(hovItem, returnSpeed_);
+					if (testPlace(hovItem)) { // оказалась при обмене на своем месте
+						runtime->event(EVENT_PUT_RIGHT, runtime->world2game(position(prevPlace_)));
+						swap = false;
+					}
+					put(hovPlace, pickedItem_);
+					if (testPlace(pickedItem_)) { // положили на свое свое место
+						runtime->event(EVENT_PUT_RIGHT, mouse);
+						swap = false;
+					}
+					if (swap) // просто обменяли
+						runtime->event(EVENT_SWAP, mouse);
+					pickedItem_ = -1;
+					prevPlace_ = -1;
+				} else { // взять фрагмент на мышь
+					runtime->event(EVENT_GET, mouse);
+					prevPlace_ = hovPlace;
+					pickedItem_ = hovItem;
+					nodes_[pickedItem_].pos = mouseObjPose_;
+					hovItem = -1;
+				}
+			}
+		} else if (hovPlace < -1) { // клик по стеку
+			int hovStack = stidx(hovPlace);
+			if (pickedItem_ == -1) // на мыши ничего нет
+				if (hovStack < stack_.size()) { // взять фрагмент из стека на мышь
+					runtime->event(EVENT_GET, mouse);
+					Indexes::iterator it = stack_.begin() + hovStack;
+					xassert(*it >= 0);
+					prevPlace_ = -1;
+					pickedItem_ = *it;
+					nodes_[pickedItem_].pos = mouseObjPose_;
+					stack_.erase(it);
+					for (int idx = hovStack; idx < stack_.size(); ++idx)
+						put(stidx(idx), stack_[idx], flySpeed_);
+				} else // пустой клик в области стека
+					runtime->event(EVENT_CLICK, mouse);
+			else // вернуть фишку на место
+				returnToStack();
+		} else // пустой клик мимо игрового поля
+			runtime->event(EVENT_CLICK, mouse);
+	} else if (runtime->mouseRightPressed()) {
+		if (pickedItem_ == -1) {
+			if (hovPlace >= 0) { // клик по полю
+				if (testPlace(hovPlace)) // клик по правильно уложенной фишке
+					runtime->event(EVENT_CLICK_RIGHT, mouse);
+				else {
+					Indexes::value_type& hovItem = field_[hovPlace];
+					if (hovItem >= 0) {
+						rotate(hovItem);
+						if (testPlace(hovItem)) // повернули на правильный угол
+							runtime->event(EVENT_PUT_RIGHT, mouse);
+						else // просто положили
+							runtime->event(EVENT_ROTATE_IN_FIELD, mouse);
+					} else // попытка прокрутить пустое место
+						runtime->event(EVENT_CLICK, mouse);
+				}
+			} else  if (hovPlace < -1) { // клик по стеку
+				int hovStack = stidx(hovPlace);
+				if (hovStack < stack_.size()) { // покрутить внутри стека
+					runtime->event(EVENT_ROTATE_IN_STACK, mouse);
+					rotate(stack_[hovStack]);
+				} else // попытка прокрутить пустое место
+					runtime->event(EVENT_CLICK, mouse);
+			} else // пустой клик мимо игрового поля
+				runtime->event(EVENT_CLICK, mouse);
+		} else // вернуть фишку на место
+			returnToStack();
+	}
+
+	bool iWin = true;
+	for (int idx = 0; idx < nodes_.size(); ++idx) {
+		Node& node = nodes_[idx];
+		if (node.pos != -1) {
+			if (node.pos >= 0) {
+				if (isFlying(idx))
+					node.obj.setState(getStateName(node.angle, false, false));
+				else {
+					node.obj.setState(getStateName(node.angle, node.pos == hovPlace && !testPlace(idx), false));
+					node.obj->set_R(position(node.pos));
+				}
+			} else if (idx == pickedItem_) {
+				node.obj.setState(getStateName(node.angle, hovPlace >= 0 && !testPlace(hovPlace), false));
+				node.obj->set_R(runtime->game2world(mouse, stackBottom_.depth() - 200));
+			} else {
+				node.obj.setState(getStateName(node.angle, node.pos == hovPlace && pickedItem_ == -1, true));
+				if (!isFlying(idx))
+					node.obj->set_R(stackPosition(stidx(node.pos)));
+			}
+			iWin = iWin && testPlace(idx);
+		} else {
+			runtime->hide(node.obj);
+			iWin = false;
+		}
+	}
+
+	if (iWin)
+		setState(GAME_WIN);
+
+}
+
+const mgVect3f &Puzzle::position(int num) const {
+	xassert(num >= 0 && num < positions_.size());
+	// Если глобальный поворот ненулевой, пересчитываем индекс
+	if (globalAngle_ > 0) {
+		int size = sqrt((float)gameSize_);
+		int y = num / size;
+		int x = num - y * size;
+		--size;
+		for (int angle = 0; angle < globalAngle_; ++angle) {
+			int tmp = x;
+			x = size - y;
+			y = tmp;
+		}
+		num = y * (size + 1) + x;
+	}
+	xassert(num >= 0 && num < positions_.size());
+	return positions_[num];
+}
+
+mgVect3f Puzzle::stackPosition(int num) const {
+	mgVect3f bottom = runtime->world2game(stackBottom_);
+	bottom.y -= stackPlaceSize_.y * num;
+	return runtime->game2world(bottom);
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/m_puzzle.h b/engines/qdengine/minigames/adv/m_puzzle.h
new file mode 100644
index 00000000000..37d9127add9
--- /dev/null
+++ b/engines/qdengine/minigames/adv/m_puzzle.h
@@ -0,0 +1,117 @@
+/* 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 QDENGINE_MINIGAMES_ADV_M_PUZZLE_H
+#define QDENGINE_MINIGAMES_ADV_M_PUZZLE_H
+
+#include "qdengine/minigames/adv/MinigameInterface.h"
+#include "qdengine/minigames/adv/FlyObject.h"
+
+namespace QDEngine {
+
+class Puzzle : public MinigameInterface {
+	struct Node {
+		QDObject obj;
+		int angle;
+		int pos;
+
+		bool inStack() const {
+			return pos < -1;
+		}
+		bool isFree() const {
+			return pos == -1;
+		}
+
+		Node() : angle(1), pos(-1) {}
+	};
+
+	typedef vector<Node> Nodes;
+
+public:
+	Puzzle();
+	~Puzzle();
+
+	void quant(float dt);
+
+private:
+	int gameSize_;
+	int angles_;
+
+	int globalAngle_;
+	float rotateTimePeriod_;
+	float nextRotateTime_;
+
+	bool singleSize_;
+	mgVect2f size_;
+	float depth_;
+
+	Nodes nodes_;
+	/// Номер места с которого взяли фрагмент
+	int prevPlace_;
+	/// Индекс фрагмента на мыши
+	int pickedItem_;
+
+	int inField_;
+
+	float nextObjTime_;
+	int mouseObjPose_;
+
+	QDObject stackBottom_;
+	int stackSize_;
+	mgVect2f stackPlaceSize_;
+
+	Indexes stack_;
+	Indexes field_;
+
+	FlyQDObjects flyObjs_;
+	/// скорость падения новых в стек
+	float flySpeed_;
+	/// скорость возврата в стек
+	float returnSpeed_;
+
+	Coords positions_;
+
+	const char *getStateName(int angle, bool selected, bool small) const;
+	/// повернуть фишку
+	void rotate(int hovItem);
+	/// проверить нахождение фишки на своем месте
+	bool testPlace(int idx) const;
+	/// фишка на мыши?
+	bool isOnMouse(const Node& node) const;
+	/// проверить фишку на предмет самостоятельного управления позиционированием
+	bool isFlying(int idx) const;
+	/// конверсия между номером в стеке и индексом положения
+	int stidx(int idx) const;
+	/// положить what в ячейку where
+	void put(int where, int what, float flowSpeed = -1.f);
+	/// положить на вершину инвентори
+	void putOnStack(int what, float speed);
+	/// вернуть с мыши в инвентори
+	void returnToStack();
+	/// мировые координаты слота на поле
+	const mgVect3f &position(int num) const;
+	/// положение N-ой фишки в инвентори
+	mgVect3f stackPosition(int N) const;
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_M_PUZZLE_H
diff --git a/engines/qdengine/minigames/adv/m_scores.cpp b/engines/qdengine/minigames/adv/m_scores.cpp
new file mode 100644
index 00000000000..5ed9a912c64
--- /dev/null
+++ b/engines/qdengine/minigames/adv/m_scores.cpp
@@ -0,0 +1,211 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/m_scores.h"
+
+namespace QDEngine {
+
+MinigameInterface *createGame() {
+	return new Scores;
+}
+
+Scores::Scores() {
+	const char *fileName = runtime->parameter("minigame_list");
+	if (!fileName || !*fileName)
+		return;
+
+	if (!_stricmp(fileName, runtime->gameListFileName())) {
+		xxassert(0, (XBuffer() < "[minigame_list] должен ссылаться на \"" < runtime->gameListFileName() < "\"").c_str());
+		return;
+	}
+
+	const char *gameButtonName = runtime->parameter("game_miniature_button");
+	if (!gameButtonName || !*gameButtonName)
+		return;
+
+	XBuffer gameData;
+	char name[128];
+	name[127] = 0;
+	for (int num = 1; ; ++num) {
+		_snprintf(name, 127, "%s%02d", gameButtonName, num);
+		if (runtime->testObject(name)) {
+			QDObject obj = runtime->getObject(name);
+			gameData.write(obj->R());
+			games_.push_back(runtime->getObject(name));
+		} else
+			break;
+	}
+
+	if (games_.empty()) {
+		xxassert(false, (XBuffer() < "Не найдены образы игр \"" < gameButtonName < "\"").c_str());
+		return;
+	}
+
+	if (!runtime->processGameData(gameData))
+		return;
+
+	positions_.resize(games_.size());
+	for (int idx = 0; idx < games_.size(); ++idx)
+		gameData.read(positions_[idx]);
+
+	XStream file(false);
+	if (!file.open(fileName, XS_IN)) {
+		xxassert(false, (XBuffer() < "Не удалось открыть файл со списком игр \"" < fileName < "\"").c_str());
+		return;
+	}
+
+	char read_buf[512];
+	while (!file.eof()) {
+		file.getline(read_buf, 512);
+		XBuffer xbuf((void*)read_buf, strlen(read_buf));
+		int level;
+		xbuf >= level;
+		unsigned char ch;
+		xbuf > ch;
+		if (ch != ':') {
+			xxassert(ch != ':', "Неправильный формат файла.");
+			return;
+		}
+		Level lvl(level);
+		dprintf("%d: ", level);
+		while (xbuf.tell() < xbuf.size()) {
+			xbuf > ch;
+			if (isdigit(ch)) {
+				--xbuf;
+				int game;
+				xbuf >= game;
+				lvl.games.push_back(game);
+				dprintf("%d, ", game);
+				if (const MinigameData * data = runtime->getScore(level, game))
+					lvl.data.push_back(GameData(game, *data));
+			}
+		}
+		if (lvl.games.size() > games_.size()) {
+			xxassert(lvl.games.size() <= games_.size(), "Мало образов игр");
+			return;
+		}
+		sort(lvl.data.begin(), lvl.data.end());
+		levels_.push_back(lvl);
+		dprintf("\n");
+	}
+	if (levels_.empty())
+		return;
+	sort(levels_.begin(), levels_.end());
+	level_ = 0;
+	preLevel_ = -1;
+
+	if (!(bestScore_ = runtime->parameter("best_score")))
+		return;
+	if (!(bestTime_ = runtime->parameter("best_time")))
+		return;
+	if (!(lastScore_ = runtime->parameter("last_score")))
+		return;
+	if (!(lastTime_ = runtime->parameter("last_time")))
+		return;
+	if (!(currentLevel_ = runtime->parameter("current_level")))
+		return;
+
+	if (!(prev_ = runtime->getObject(runtime->parameter("prev_button"))))
+		return;
+	if (!(next_ = runtime->getObject(runtime->parameter("next_button"))))
+		return;
+
+	outMaxLevel_ = runtime->getObject(runtime->parameter("for_game_level"));
+	if (outMaxLevel_) {
+		int level = 0;
+		for (; level < levels_.size(); ++level)
+			if (levels_[level].data.size() < levels_[level].games.size())
+				break;
+		if (level < levels_.size())
+			outMaxLevel_.setState((XBuffer() <= levels_[level].level).c_str());
+		else
+			outMaxLevel_.setState("all");
+	}
+
+	setState(MinigameInterface::RUNNING);
+
+}
+
+Scores::~Scores() {
+	runtime->release(prev_);
+	runtime->release(next_);
+
+	QDObjects::iterator it;
+	FOR_EACH(games_, it)
+	runtime->release(*it);
+
+}
+
+void Scores::quant(float dt) {
+	xassert(level_ >= 0 && level_ < levels_.size());
+	const Level& lvl = levels_[level_];
+
+	if (level_ != preLevel_) {
+		preLevel_ = level_;
+
+
+		runtime->setText(currentLevel_, lvl.level);
+
+		for (int idx = 0; idx < games_.size(); ++idx)
+			runtime->hide(games_[idx]);
+
+		for (int idx = 0; idx < games_.size(); ++idx) {
+			if (idx < lvl.data.size()) {
+				const GameData& data = lvl.data[idx];
+				int gameId = data.num;
+				int gameNum;
+				for (gameNum = 0; gameNum < lvl.games.size(); ++gameNum)
+					if (gameId == lvl.games[gameNum])
+						break;
+				xassert(gameNum < lvl.games.size());
+				xassert(gameNum < games_.size());
+				games_[gameNum].setState((XBuffer() < level_).c_str());
+				games_[gameNum]->set_R(positions_[idx]);
+				runtime->setText(getName(bestScore_, idx), data.info.bestScore_);
+				runtime->setText(getName(bestTime_, idx), data.info.bestTime_);
+				runtime->setText(getName(lastScore_, idx), data.info.lastScore_);
+				runtime->setText(getName(lastTime_, idx), data.info.lastTime_);
+			} else {
+				runtime->setText(getName(bestScore_, idx), "");
+				runtime->setText(getName(bestTime_, idx), "");
+				runtime->setText(getName(lastScore_, idx), "");
+				runtime->setText(getName(lastTime_, idx), "");
+			}
+		}
+	}
+
+	if (runtime->mouseLeftPressed()) {
+		if (level_ < levels_.size() - 1 && lvl.data.size() == lvl.games.size() && next_.hit(runtime->mousePosition()))
+			++level_;
+		else if (level_ > 0 && prev_.hit(runtime->mousePosition()))
+			--level_;
+	}
+}
+
+const char *Scores::getName(const char* begin, int idx) const {
+	static char buf[32];
+	buf[31] = 0;
+	_snprintf(buf, 31, "%s%02d", begin, idx + 1);
+	return buf;
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/m_scores.h b/engines/qdengine/minigames/adv/m_scores.h
new file mode 100644
index 00000000000..6e775c5a835
--- /dev/null
+++ b/engines/qdengine/minigames/adv/m_scores.h
@@ -0,0 +1,81 @@
+/* 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 QDENGINE_MINIGAMES_ADV_M_SCORES_H
+#define QDENGINE_MINIGAMES_ADV_M_SCORES_H
+
+#include "qdengine/minigames/adv/MinigameInterface.h"
+#include "qdengine/minigames/adv/RunTime.h"
+
+namespace QDEngine {
+
+class Scores : public MinigameInterface {
+public:
+	Scores();
+	~Scores();
+
+	void quant(float dt);
+
+private:
+	struct GameData {
+		GameData(int gameNum, const MinigameData& inf) : num(gameNum), info(inf) {}
+		int num;
+		MinigameData info;
+		bool operator< (const GameData& rsh) const {
+			return info.sequenceIndex_ < rsh.info.sequenceIndex_;
+		}
+	};
+	typedef vector<GameData> GameDatas;
+	struct Level {
+		Level(int lvl = 0) : level(lvl) {}
+		int level;
+		Indexes games;
+		GameDatas data;
+		bool operator< (const Level& rsh) const {
+			return level < rsh.level;
+		}
+	};
+	typedef vector<Level> Levels;
+	Levels levels_;
+
+	const char *currentLevel_;
+	const char *bestScore_;
+	const char *bestTime_;
+	const char *lastScore_;
+	const char *lastTime_;
+
+	QDObject prev_;
+	QDObject next_;
+	QDObject outMaxLevel_;
+
+	QDObjects games_;
+
+	int preLevel_;
+	int level_;
+
+	Coords positions_;
+
+	const char *getName(const char* begin, int idx) const;
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_M_SCORES_H
diff --git a/engines/qdengine/minigames/adv/m_swap.cpp b/engines/qdengine/minigames/adv/m_swap.cpp
new file mode 100644
index 00000000000..4f064c7aaa1
--- /dev/null
+++ b/engines/qdengine/minigames/adv/m_swap.cpp
@@ -0,0 +1,294 @@
+/* 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 "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/m_swap.h"
+#include "qdengine/minigames/adv/RunTime.h"
+#include "qdengine/minigames/adv/Rect.h"
+
+namespace QDEngine {
+
+typedef Rect<float, mgVect2f> Rectf;
+
+MinigameInterface *createGame() {
+	return new Swap;
+}
+
+enum {
+	EVENT_GET,
+	EVENT_SWAP,
+	EVENT_ROTATE,
+	EVENT_RETURN,
+	EVENT_PUT_RIGHT,
+	EVENT_GET_RIGHT,
+	EVENT_CLICK,
+	EVENT_AUTO_ROTATE
+};
+
+const char *Swap::getStateName(int angle, bool selected) const {
+	static const char *selected_suf = "_sel";
+
+	static char buf[32];
+	buf[31] = 0;
+
+	xassert(angle >= 0 && angle < angles_);
+
+	_snprintf(buf, 31, "%02d%s", angle + 1, selected ? selected_suf : "");
+	return buf;
+}
+
+Swap::Swap() {
+	if (!getParameter("game_size", gameSize_, true) || gameSize_ < 2)
+		return;
+
+	if ((angles_ = getParameter("angles", 4)) < 1)
+		return;
+
+	if ((rotateTimePeriod_ = getParameter("rotate_period", 86400.f)) < 10.f)
+		return;
+	nextRotateTime_ = runtime->time() + rotateTimePeriod_;
+
+	const char *name_begin = runtime->parameter("obj_name_begin", "obj_");
+
+	char buf[128];
+	buf[127] = 0;
+
+	XBuffer gameData;
+
+	for (int idx = 0; idx < gameSize_; ++idx) {
+		_snprintf(buf, 127, "%s%02d", name_begin, idx + 1);
+
+		Node node(idx);
+		node.obj = runtime->getObject(buf);
+		node.angle = 0;
+		node.obj.setState(getStateName(node.angle, false));
+		nodes_.push_back(node);
+
+		gameData.write(node.obj->R());
+	}
+
+	if (!runtime->processGameData(gameData))
+		return;
+
+	positions_.resize(gameSize_);
+	for (int idx = 0; idx < gameSize_; ++idx)
+		gameData.read(positions_[idx]);
+
+	size_ = getParameter("element_size", runtime->getSize(nodes_[0].obj));
+	xassert(size_.x > 0.f && size_.y > 0.f && size_.x < 500.f && size_.y < 500.f);
+	dprintf("element_size = (%6.2f,%6.2f)\n", size_.x, size_.y);
+
+	pickedItem_ = -1;
+	last1_ = last2_ = -1;
+
+	if (runtime->debugMode()) {
+		last1_ = 0;
+		last2_ = 1;
+		rotate(last1_, last2_, false);
+	} else
+		for (int cnt = 0; cnt < 50; ++cnt) {
+			rotate(runtime->rnd(0, gameSize_ - 1), runtime->rnd(0, gameSize_ - 1), true, true);
+			swap(runtime->rnd(0, gameSize_ - 1), runtime->rnd(0, gameSize_ - 1), true);
+		}
+
+
+	setState(MinigameInterface::RUNNING);
+
+}
+
+Swap::~Swap() {
+	Nodes::iterator it;
+	FOR_EACH(nodes_, it)
+	runtime->release(it->obj);
+
+}
+
+void Swap::quant(float dt) {
+	if (pickedItem_ >= 0)
+		runtime->setGameHelpVariant(1);
+	else if (last1_ >= 0)
+		runtime->setGameHelpVariant(2);
+	else
+		runtime->setGameHelpVariant(0);
+
+	if (runtime->time() > nextRotateTime_) {
+		int item1 = runtime->rnd(0, gameSize_ - 1);
+		int item2 = runtime->rnd(0, gameSize_ - 1);
+		if (item1 != last1_ && item1 != last2_ && item1 != pickedItem_ && item2 != last1_ && item2 != last2_ && item2 != pickedItem_) {
+			nextRotateTime_ = runtime->time() + rotateTimePeriod_;
+			rotate(item1, item2, false, true);
+			runtime->event(EVENT_AUTO_ROTATE, mgVect2f(400, 300));
+			return;
+		}
+	}
+
+	mgVect2f mouse = runtime->mousePosition();
+
+	int hovPlace = -1;  // Номер места которое сейчас под мышкой
+	if (pickedItem_ == -1) {
+		Nodes::iterator it;
+		FOR_EACH(nodes_, it)
+		if (it->obj.hit(mouse)) {
+			hovPlace = distance(nodes_.begin(), it);
+			break;
+		}
+	}
+	if (hovPlace == -1)
+		for (int idx = 0; idx < gameSize_; ++idx) {
+			Rectf rect(size_ * 0.9f);
+			rect.center(runtime->world2game(position(idx)));
+			if (rect.point_inside(mouse)) {
+				hovPlace = idx;
+				break;
+			}
+		}
+
+	//dprintf("%d\n", hovPlace);
+	if (runtime->mouseLeftPressed()) {
+		if (hovPlace >= 0) { // клик по полю
+			if (pickedItem_ == -1) { // мышь пустая, берем
+				deactivate();
+				runtime->event(EVENT_GET, mouse);
+				pickedItem_ = hovPlace;
+			} else if (pickedItem_ == hovPlace) { // вернуть на место
+				runtime->event(EVENT_RETURN, mouse);
+				put(pickedItem_, false);
+				pickedItem_ = -1;
+			} else { // поменять местами
+				last1_ = pickedItem_;
+				last2_ = hovPlace;
+				swap(last1_, last2_, false);
+				pickedItem_ = -1;
+			}
+		} else { // пустой клик мимо игрового поля
+			deactivate();
+			runtime->event(EVENT_CLICK, mouse);
+		}
+	} else if (runtime->mouseRightPressed()) {
+		if (pickedItem_ >= 0) // если на мыши фрагмент ничего не делаем
+			runtime->event(EVENT_CLICK, mouse);
+		else if (hovPlace == last1_ || hovPlace == last2_) // клик по выделенным
+			rotate(last1_, last2_, false);
+		else // пустой клик мимо активного места
+			runtime->event(EVENT_CLICK, mouse);
+	}
+
+	if (pickedItem_ >= 0)
+		nodes_[pickedItem_].obj->set_R(runtime->game2world(mouse, -5000));
+
+	int idx = 0;
+	for (; idx < gameSize_; ++idx)
+		if (!testPlace(idx))
+			break;
+
+	if (idx == nodes_.size()) {
+		deactivate();
+		setState(MinigameInterface::GAME_WIN);
+	}
+}
+
+const mgVect3f &Swap::position(int num) const {
+	xassert(num >= 0 && num < positions_.size());
+	return positions_[num];
+}
+
+void Swap::put(int item, bool hl) {
+	xassert(item >= 0 && item < nodes_.size());
+	nodes_[item].obj->set_R(position(item));
+	nodes_[item].obj.setState(getStateName(nodes_[item].angle, hl));
+
+}
+
+void Swap::deactivate() {
+	if (last1_ >= 0) {
+		xassert(last2_ >= 0);
+		put(last1_, false);
+		put(last2_, false);
+	}
+	last1_ = -1;
+	last2_ = -1;
+}
+
+bool Swap::testPlace(int item) const {
+	xassert(item >= 0 && item < nodes_.size());
+	return nodes_[item].home == item && nodes_[item].angle == 0;
+}
+
+void Swap::swap(int item1, int item2, bool silent) {
+	xassert(item1 >= 0 && item1 < nodes_.size());
+	xassert(item2 >= 0 && item2 < nodes_.size());
+
+	bool res = false;
+	if (!silent) {
+		if (testPlace(item1)) { // сняли со своего места
+			runtime->event(EVENT_GET_RIGHT, runtime->world2game(position(item1)));
+			res = true;
+		}
+		if (testPlace(item2)) { // сняли со своего места
+			runtime->event(EVENT_GET_RIGHT, runtime->world2game(position(item2)));
+			res = true;
+		}
+	}
+
+	::swap(nodes_[item1], nodes_[item2]);
+	put(item1, !silent);
+	put(item2, !silent);
+
+	if (!silent) {
+		if (testPlace(item1)) { // оказалась при обмене на своем месте
+			runtime->event(EVENT_PUT_RIGHT, runtime->world2game(position(item1)));
+			res = true;
+		}
+		if (testPlace(item2)) { // положили на свое свое место
+			runtime->event(EVENT_PUT_RIGHT, runtime->world2game(position(item2)));
+			res = true;
+		}
+		if (!res) // просто обменяли
+			runtime->event(EVENT_SWAP, runtime->mousePosition());
+	}
+}
+
+void Swap::rotate(int item1, int item2, bool silent, bool avto) {
+	xassert(item1 >= 0 && item1 < nodes_.size());
+	xassert(item2 >= 0 && item2 < nodes_.size());
+
+	if (!silent) {
+		if (testPlace(item1)) // сняли со своего места
+			runtime->event(EVENT_GET_RIGHT, runtime->world2game(position(item1)));
+		if (testPlace(item2)) // сняли со своего места
+			runtime->event(EVENT_GET_RIGHT, runtime->world2game(position(item2)));
+	}
+
+	nodes_[item1].angle = (nodes_[item1].angle + 1) % angles_;
+	nodes_[item2].angle = (nodes_[item2].angle + 1) % angles_;
+	put(item1, !avto);
+	put(item2, !avto);
+
+	if (!silent) {
+		if (testPlace(item1)) // оказалась при обмене на своем месте
+			runtime->event(EVENT_PUT_RIGHT, runtime->world2game(position(item1)));
+		if (testPlace(item2)) // положили на свое свое место
+			runtime->event(EVENT_PUT_RIGHT, runtime->world2game(position(item2)));
+		runtime->event(EVENT_ROTATE, runtime->mousePosition());
+	}
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/m_swap.h b/engines/qdengine/minigames/adv/m_swap.h
new file mode 100644
index 00000000000..7c8642ed473
--- /dev/null
+++ b/engines/qdengine/minigames/adv/m_swap.h
@@ -0,0 +1,78 @@
+/* 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 QDENGINE_MINIGAMES_ADV_M_SWAP_H
+#define QDENGINE_MINIGAMES_ADV_M_SWAP_H
+
+#include "qdengine/minigames/adv/MinigameInterface.h"
+
+namespace QDEngine {
+
+class Swap : public MinigameInterface {
+public:
+	Swap();
+	~Swap();
+
+	void quant(float dt);
+private:
+	int gameSize_;
+	int angles_;
+
+	float rotateTimePeriod_;
+	float nextRotateTime_;
+
+	mgVect2f size_;
+
+	struct Node {
+		Node(int idx = -1) : home(idx), angle(0) {}
+		QDObject obj;
+		int angle;
+		int home;
+	};
+	typedef vector<Node> Nodes;
+	Nodes nodes_;
+
+	// Индекс фрагмента на мыши
+	int pickedItem_;
+	// активные фрагменты после обмена
+	int last1_, last2_;
+
+	Coords positions_;
+
+	const char *getStateName(int angle, bool selected) const;
+	// поменять местами, если было снятие или укладка на/с правильного места, то true
+	void swap(int item1, int item2, bool silent);
+	// повернуть фишку
+	void rotate(int item1, int item2, bool silent, bool avto = false);
+	// погасить выделенные
+	void deactivate();
+	// проверить нахождение фишки на своем месте
+	bool testPlace(int idx) const;
+	// поставить объект на свое место и включить нужное состояние
+	void put(int idx, bool hl);
+	// мировые координаты слота на поле
+	const mgVect3f &position(int num) const;
+
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_M_SWAP_H
diff --git a/engines/qdengine/minigames/adv/m_triangles.cpp b/engines/qdengine/minigames/adv/m_triangles.cpp
new file mode 100644
index 00000000000..ce509b1a86a
--- /dev/null
+++ b/engines/qdengine/minigames/adv/m_triangles.cpp
@@ -0,0 +1,610 @@
+/* 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 "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 *createGame() {
+	return new MinigameTriangle;
+}
+
+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() {
+	QDObjects::iterator it;
+	FOR_EACH(face_, it)
+	runtime->release(*it);
+}
+
+bool MinigameTriangle::Node::hit(const mgVect2f& pos) const {
+	return obj().hit(pos);
+}
+
+MinigameTriangle::MinigameTriangle() {
+	int type = 0;
+	if (!getParameter("game_type", type, true))
+		return;
+
+	switch (type) {
+	case 1:
+		gameType_ = RECTANGLE;
+		break;
+	case 2:
+		gameType_ = HEXAGON;
+		break;
+	default:
+		gameType_ = TRIANGLE;
+	}
+
+	if (!getParameter("size", fieldLines_, true))
+		return;
+	if (fieldLines_ < 2)
+		return;
+
+	if (gameType_ == RECTANGLE) {
+		if (!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:
+		xassert(fieldLines_ % 2 == 0);
+		if (fieldLines_ % 2 != 0)
+			return;
+		fieldSize_ = 3 * sqr(fieldLines_) / 2;
+		break;
+	}
+
+	if (!getParameter("animation_time", animationTime_, true))
+		return;
+
+	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) {
+			sprintf(name, "%s%02d_%1d", faceNameBegin, num + 1, angle);
+			QDObject obj = runtime->getObject(name);
+			node.face_.push_back(obj);
+			positions_.push_back(obj->R());
+		}
+	}
+
+	XBuffer gameData;
+
+	Coords::iterator it;
+	FOR_EACH(positions_, it)
+	gameData.write(*it);
+
+	if (!runtime->processGameData(gameData))
+		return;
+
+	FOR_EACH(positions_, it)
+	gameData.read(*it);
+
+	for (int num = 1; num <= 2; ++num) {
+		for (int angle = 1; angle <= 3; ++angle) {
+			sprintf(name, "%s%1d_%1d", backNameBegin, num, angle);
+			if (!backSides_[(num - 1) * 3 + angle - 1].load(name))
+				return;
+		}
+		sprintf(name, "%s%1d", selectNameBegin, num);
+		if (!selectBorders_[num - 1].load(name))
+			return;
+	}
+
+	selectDepth_ = nodes_[0].face_[0].depth() - 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() {
+	Nodes::iterator it;
+	FOR_EACH(nodes_, it)
+	it->release();
+
+	for (int idx = 0; idx < 2; ++idx)
+		selectBorders_[idx].release();
+
+	for (int idx = 0; idx < 6; ++idx)
+		backSides_[idx].release();
+}
+
+void MinigameTriangle::Node::debugInfo() const {
+	dprintf("name:\"%s\" state:\"%s\" number:%d rotation:%d flip:%d isBack:%d highlight:%d animated:%d\n", obj().getName(), obj()->current_state_name(), number_, rotation_, flip, isBack_, highlight_, animated_);
+}
+
+const char *MinigameTriangle::Node::getFaceStateName(int angle, bool selected, bool animated, bool instantaneous) {
+	xassert(!selected || !animated); // анимированные выделенными быть не могут
+
+	static char *angleNames[3] = {"0", "120", "240"};
+	xassert(angle >= 0 && angle < sizeof(angleNames) / sizeof(angleNames[0]));
+
+	static XBuffer out;
+	out.init();
+
+	out < (animated ? "02_" : "01_") < angleNames[angle] < (selected || instantaneous ? "_sel" : "") < '\0';
+	return out.c_str();
+}
+
+const char *MinigameTriangle::Node::getBackStateName(bool selected, bool animated, bool instantaneous) {
+	xassert(!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_);
+	}
+}
+
+void MinigameTriangle::updateNode(Node& node, int position, int flip, bool quick) {
+	QDObjects::iterator fit;
+	FOR_EACH(node.face_, fit)
+	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));
+	}
+}
+
+void MinigameTriangle::highlight(int idx, bool hl) {
+	if (idx >= 0) {
+		xassert(idx < (int)nodes_.size());
+		nodes_[idx].highlight_ = hl;
+		updateNode(nodes_[idx], idx);
+	}
+}
+
+void MinigameTriangle::beginSwapNodes(int pos1, int pos2) {
+	xassert(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));
+
+	//dprintf(">>>>>>>>>>>>>>>>>>>>>>>>>>> change %d <> %d, 1st phase <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n", 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) { // поставили на свое место
+		xassert(!node1.isBack_);
+		counted = true;
+		runtime->event(EVENT_PUT_RIGHT, node1.obj()->screen_R());
+	}
+
+	if (node2.number_ == pos1) { // сняли со своего места
+		xassert(node2.isBack_);
+		counted = true;
+		runtime->event(EVENT_GET_RIGHT, node1.obj()->screen_R());
+	}
+
+	if (node2.number_ == pos2) { // поставили на свое место
+		xassert(!node2.isBack_);
+		counted = true;
+		runtime->event(EVENT_PUT_RIGHT, node2.obj()->screen_R());
+	}
+
+	if (node1.number_ == pos2) { // сняли со своего места
+		xassert(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;
+	Nodes::const_iterator it;
+	FOR_EACH(nodes_, it)
+	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);
+
+		QDObjects::iterator it;
+		FOR_EACH(node1.face_, it)
+		(*it).setState(Node::getFaceStateName(0, false, false, false));
+		FOR_EACH(node2.face_, it)
+		(*it).setState(Node::getFaceStateName(0, false, false, false));
+
+		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;
+
+		//dprintf(">>>>>>>>>>>>>>>>>>>>>>>>>>> change %d <> %d, 2nd phase 1 <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n", 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;
+
+		//dprintf(">>>>>>>>>>>>>>>>>>>>>>>>>>> change %d <> %d, 2nd phase 2 <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n", 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]);
+		//dprintf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ change %d <> %d, finished ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", animatedNodes_[0], animatedNodes_[1]);
+
+		animatedNodes_[0] = -1;
+		animatedNodes_[1] = -1;
+
+		return true;
+	}
+
+	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 (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_);
+				selectBorders_[1].releaseObject(node.border_);
+			}
+		}
+	}
+
+	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) {
+		dprintf("----- DUBUG INFO FOR %d POSITION --------------------\n", mousePos);
+		dprintf("row = %d, begin = %d, orientation = %d\n", 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_;
+	}
+	//case HEXAGON:
+	xassert(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_;
+	}
+	//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;
+	}
+	//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}}
+	};
+	xassert(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 {
+	xassert(pos * 3 + angle < positions_.size());
+	return positions_[pos * 3 + angle];
+}
+
+} // namespace QDEngine
diff --git a/engines/qdengine/minigames/adv/m_triangles.h b/engines/qdengine/minigames/adv/m_triangles.h
new file mode 100644
index 00000000000..2323b714a54
--- /dev/null
+++ b/engines/qdengine/minigames/adv/m_triangles.h
@@ -0,0 +1,131 @@
+/* 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 QDENGINE_MINIGAMES_ADV_M_TRIANGLES_H
+#define QDENGINE_MINIGAMES_ADV_M_TRIANGLES_H
+
+#include "qdengine/minigames/adv/common.h"
+#include "qdengine/minigames/adv/MinigameInterface.h"
+#include "qdengine/minigames/adv/ObjectContainer.h"
+
+namespace QDEngine {
+
+class MinigameTriangle : public MinigameInterface {
+	enum GameType {
+		TRIANGLE,
+		RECTANGLE,
+		HEXAGON
+	};
+
+	enum AnimationState {
+		NO_ANIMATION,
+		FIRST_PHASE,
+		SECOND_PHASE,
+		FIRD_PHASE
+	};
+
+	struct Node {
+		Node(int number = -1, int rot = -1);
+
+		void release();
+		void debugInfo() const;
+
+		const QDObject &obj() const {
+			return isBack_ ? back_ : face_[flip];
+		}
+
+		bool hit(const mgVect2f& pos) const;
+
+		int number_; // правильная позиция (номер слота)
+		int rotation_; // текущий угол поворота (правильный угол = 0)
+		int flip;
+		QDObjects face_; // набор возможных углов переворота для лицевой стороны
+		QDObject back_; // обратная сторона
+		QDObject border_; // рамка
+		bool isBack_; // повернут лицом (true) или рубашкой (false)
+		bool highlight_;
+		bool animated_;
+
+		static const char *getFaceStateName(int angle, bool selected, bool animated, bool instantaneous);
+		static const char *getBackStateName(bool selected, bool animated, bool instantaneous);
+		static const char *getBorderStateName(bool selected);
+	};
+	typedef vector<Node> Nodes;
+
+public:
+	MinigameTriangle();
+	~MinigameTriangle();
+	void quant(float dt);
+
+private:
+	GameType gameType_;
+	Coords positions_;
+	int selectDepth_;
+
+	int fieldLines_;
+	int fieldWidth_;
+	int fieldSize_;
+	Nodes nodes_;
+	ObjectContainer selectBorders_[2];
+	ObjectContainer backSides_[6];
+	int selected_;
+	int hovered_;
+
+	AnimationState animationState_;
+	int animatedNodes_[2];
+	float animationTime_;
+	float animationTimer_;
+
+	/// очистить рубашку фишки
+	void releaseNodeBack(Node& node);
+	/// выставить графические состояния соответствующие текущему логическому
+	void updateNode(Node& node, int position, int flip = 0, bool quick = false);
+	/// подсветить/потушить фрагмент
+	void highlight(int idx, bool hl);
+
+	/// поменять местами фишки
+	void swapNodes(int pos1, int pos2, bool quick);
+	/// начать анимацию обмена
+	void beginSwapNodes(int pos1, int pos2);
+	/// отработка анимации переворота фишек
+	bool animate(float dt);
+	/// вызывается после окончания переворота
+	void endSwapNodes(int pos1, int pos2);
+
+	/// по номеру фишки вычисляет слой
+	int rowByNum(int num) const;
+	/// возвращает с какой фишки начинается слой
+	int rowBegin(int row) const;
+	/// 0 - угол вверх
+	int orientation(int num) const;
+	/// можно поменять местами
+	bool compatible(int num1, int num2) const;
+	/// определить какой будет угол поворота у num1 при переходе в num2
+	int getRotate(int num1, int num2) const;
+	/// направление переворота
+	int destination(int num1, int num2) const;
+	/// по номеру слота и углу переворота (с учетом типа игры) возвращает экранные координаты
+	mgVect3f slotCoord(int pos, int angle = 0) const;
+};
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_M_TRIANGLES_H
diff --git a/engines/qdengine/minigames/adv/qdMath.h b/engines/qdengine/minigames/adv/qdMath.h
new file mode 100644
index 00000000000..8acd888032f
--- /dev/null
+++ b/engines/qdengine/minigames/adv/qdMath.h
@@ -0,0 +1,66 @@
+/* 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 QDENGINE_MINIGAMES_ADV_QDMATH_H
+#define QDENGINE_MINIGAMES_ADV_QDMATH_H
+
+namespace QDEngine {
+
+const float FLT_EPS = 1.192092896e-07f;
+const float FLT_INF = 1.e+30f;
+
+#define SQRT2 1.41421356f
+#define SQRT3 1.73205081f
+
+inline float dist(const mgVect2f& v1, const mgVect2f& v2) {
+	return sqrt(sqr(v1.x - v2.x) + sqr(v1.y - v2.y));
+}
+
+inline float abs(const mgVect2f& v) {
+	return sqrt(sqr(v.x) + sqr(v.y));
+}
+
+inline void norm(mgVect2f& v) {
+	float mod = abs(v);
+	if (mod < FLT_EPS) {
+		v = mgVect2f(0, 1);
+		return;
+	}
+	v.x /= mod;
+	v.y /= mod;
+}
+
+template<class T, class T1, class T2>
+inline T clamp(const T& x, const T1& xmin, const T2& xmax) {
+	if (x < xmin) return xmin;
+	if (x > xmax) return xmax;
+	return x;
+}
+
+template<class T>
+inline T abs(const T& x) {
+	if (x < 0) return -x;
+	return x;
+}
+
+} // namespace QDEngine
+
+#endif // QDENGINE_MINIGAMES_ADV_QDMATH_H




More information about the Scummvm-git-logs mailing list