diff --git a/Src/2DOBB.hpp b/Src/2DOBB.hpp deleted file mode 100644 index 4606a7b..0000000 --- a/Src/2DOBB.hpp +++ /dev/null @@ -1,161 +0,0 @@ -#pragma once -#include "Vec2.hpp" -/*-------------------------------------------------------------------- -本文件来自 -http://www.flipcode.com/archives/2D_OBB_Intersection.shtml -2D OBB Intersection - -For 2D graphics or movement of 3D objects on a 2D ground plane it is often -useful to discover if the 2D oriented bounding boxes of two objects overlap -(have a non - empty intersection).One motivating example is the placement of -a new building in a Real - Time Strategy game.The UI needs to continuously -check whether the footprint of the new building overlaps the footprint of -any existing building.If there is any overlap, the UI should indicate that -is an illegal placement. - -Stefan Gottschalk's thesis (Collision Queries using Oriented Bounding -Boxes, Ph.D.Thesis, Department of Computer Science, University of North -Carolina at Chapel Hill, 1999) introduces the separating - axis method -for performing the equivalent test on 3D oriented bounding boxes. -This method depends on the observation that -for two boxes to be disjoint(i.e. * not* intersecting), there must be some axis -along which their projections are disjoint.The 3D case considers each of 15 -axes as a potential -separating axis.These axes are the three edge axes of box 1, the three edge -axes of box 2, and the nine cross products formed by taking some edge of box 1 -and some edge of box 2. - -In 2D this simplifies dramatically and only four axes need be considered. -These are -the orthogonal edges of each bounding box.If a few values are precomputed -every time a box moves, we end up performing only 16 dot products and some -comparisons in the worst case for each overlap test.One nice property of the -separating - axis method is that it can be -structured in an early out fashion, so that many fewer operations are needed in -the case where the boxes do not intersect.In general, the first test is -extremely -likely to fail(and return "no overlap") when there is no overlap.If it -passes, -the second test is even more likely to fail if there is no overlap, and so on. -Only when the boxes are in extremely close proximity is there even a 50 % chance -of -executing more than 2 tests. - -The C++ code sample provided efficiently computes this fast 2D oriented -bounding box -overlap.I augmented the OBB2D class with some methods for rendering and -construction to help visualize the result.OBB2D::overlaps1Way performs the -real work.It tests to see whether the box passed as an argument overlaps the -current box along either of the current box's dimensions. Note that this test -must be performed for each box on the other to determine whether there is truly -any overlap.To make the tests extremely efficient, OBB2D::origin stores the -projection of corner number zero onto a box's axes and the axes are stored -explicitly in OBB2D::axis.The magnitude of these stored axes is the inverse -of the corresponding edge length so that all overlap tests can be performed on -the interval[0, 1] without normalization, and square roots are avoided -throughout the entire class. - -Morgan McGuire morgan@cs.brown.edu - - -*/ -class OBB2D { -private: - /** Corners of the box, where 0 is the lower left. */ - Vec2d corner[4]; - - /** Two edges of the box extended away from corner[0]. */ - Vec2d axis[2]; - - /** origin[a] = corner[0].dot(axis[a]); */ - double origin[2]; - - /** Returns true if other overlaps one dimension of this. */ - bool overlaps1Way(const OBB2D& other) const { - for (int a = 0; a < 2; ++a) { - - double t = other.corner[0].dot(axis[a]); - - // Find the extent of box 2 on axis a - double tMin = t; - double tMax = t; - - for (int c = 1; c < 4; ++c) { - t = other.corner[c].dot(axis[a]); - - if (t < tMin) { - tMin = t; - } - else if (t > tMax) { - tMax = t; - } - } - - // We have to subtract off the origin - - // See if [tMin, tMax] intersects [0, 1] - if ((tMin > 1 + origin[a]) || (tMax < origin[a])) { - // There was no intersection along this dimension; - // the boxes cannot possibly overlap. - return false; - } - } - - // There was no dimension along which there is no intersection. - // Therefore the boxes overlap. - return true; - } - - - /** Updates the axes after the corners move. Assumes the - corners actually form a rectangle. */ - void computeAxes() { - axis[0] = corner[1] - corner[0]; - axis[1] = corner[3] - corner[0]; - - // Make the length of each axis 1/edge length so we know any - // dot product must be less than 1 to fall within the edge. - - for (int a = 0; a < 2; ++a) { - axis[a] /= axis[a].lengthSqr(); - origin[a] = corner[0].dot(axis[a]); - } - } - -public: - - OBB2D(const Vec2d& center, const double w, const double h, double angle) - { - Vec2d X(cos(angle), sin(angle)); - Vec2d Y(-sin(angle), cos(angle)); - - X *= w / 2; - Y *= h / 2; - - corner[0] = center - X - Y; - corner[1] = center + X - Y; - corner[2] = center + X + Y; - corner[3] = center - X + Y; - - computeAxes(); - } - - - /** For testing purposes. */ - void moveTo(const Vec2d& center) { - Vec2d centroid = (corner[0] + corner[1] + corner[2] + corner[3]) / 4; - - Vec2d translation = center - centroid; - - for (int c = 0; c < 4; ++c) { - corner[c] += translation; - } - - computeAxes(); - } - - /** Returns true if the intersection of the boxes is non-empty. */ - bool overlaps(const OBB2D& other) const { - return overlaps1Way(other) && other.overlaps1Way(*this); - } -}; \ No newline at end of file diff --git a/Src/GameConnection.hpp b/Src/GameConnection.hpp index 5473a5b..90c7e95 100644 --- a/Src/GameConnection.hpp +++ b/Src/GameConnection.hpp @@ -3,15 +3,23 @@ #include #include #include "Object.hpp" - +enum class PlayerState:int +{ + NOTCREATED=0, + NORMAL=1, + INVINCIBLE =2, + DYING=4 +}; struct GameConnection { virtual ~GameConnection() noexcept = default; - virtual void GetPowers(std::vector &powers) noexcept = 0; - virtual void GetEnemyData(std::vector &enemy) noexcept = 0; - virtual void GetEnemyBulletData(std::vector &bullet, const Player &player, double maxRange) noexcept = 0; - virtual void GetPlayerData(Player &self) noexcept = 0; - virtual void GetEnemyLaserData(std::vector &laser) noexcept = 0; + virtual int getTimeline() noexcept = 0; + virtual void getPowers(std::vector &powers) noexcept = 0; + virtual void getEnemyData(std::vector &enemy) noexcept = 0; + virtual void getEnemyBulletData(std::vector &bullet, const Player &player, double maxRange) noexcept = 0; + virtual void getPlayerData(Player &self) noexcept = 0; + virtual void getEnemyLaserData(std::vector &laser) noexcept = 0; virtual void sendKeyInfo(int dir,bool shift,bool z,bool x) noexcept = 0; + virtual void getMousePosition(Vec2d& pos)noexcept = 0; + virtual PlayerState getPlayerStateInformation()noexcept = 0; }; - std::unique_ptr createGameConnection(); diff --git a/Src/GameConnectionTH10.cpp b/Src/GameConnectionTH10.cpp index 1d79262..455b0c8 100644 --- a/Src/GameConnectionTH10.cpp +++ b/Src/GameConnectionTH10.cpp @@ -2,39 +2,42 @@ #include "KeyboardManager.hpp" #include "Windows.hpp" #include - -class GameConnectionTH10 : public GameConnection { -public: - GameConnectionTH10(); - ~GameConnectionTH10() noexcept override; - void GetPowers(std::vector &powers) noexcept override; - void GetEnemyData(std::vector &enemy) noexcept override; - void GetEnemyBulletData(std::vector &bullet, const Player &player, double maxRange) noexcept override; - void GetPlayerData(Player &self) noexcept override; - void GetEnemyLaserData(std::vector &laser) noexcept override; - void sendKeyInfo(int dir, bool shift, bool z, bool x) noexcept override; -private: - HANDLE mHProcess{nullptr}; - static bool GetProcessIdByName(const char *exeFileName, DWORD &pid) noexcept; - void readProcessRaw(intptr_t offset, size_t length, void *target) const noexcept { - static thread_local SIZE_T nbr; - ReadProcessMemory(mHProcess, reinterpret_cast(offset), target, length, &nbr); - } - template - T readProcess(intptr_t offset) const noexcept { - T result; - readProcessRaw(offset, sizeof(T), &result); - return result; - } -}; +#include namespace { char staticBuffer[0x7F0 * 2001]; template T read(void *buffer) noexcept { return *reinterpret_cast(buffer); } } - -void GameConnectionTH10::GetPowers(std::vector &powers) noexcept { +class GameConnectionTH10 : public GameConnection { +public: + GameConnectionTH10(); + ~GameConnectionTH10() noexcept override; + void getPowers(std::vector& powers) noexcept override; + void getEnemyData(std::vector& enemy) noexcept override; + void getEnemyBulletData(std::vector& bullet, const Player& player, double maxRange) noexcept override; + void getPlayerData(Player& self) noexcept override; + void getEnemyLaserData(std::vector& laser) noexcept override; + void sendKeyInfo(int dir, bool shift, bool z, bool x) noexcept override; + void getMousePosition(Vec2d& pos)noexcept override; + int getTimeline() noexcept override; + PlayerState getPlayerStateInformation()noexcept override; +private: + HWND mWindow{ nullptr }; + HANDLE mHProcess{ nullptr }; + static bool GetProcessIdByName(const char* exeFileName, DWORD& pid) noexcept; + void readProcessRaw(intptr_t offset, size_t length, void* target) const noexcept { + static thread_local SIZE_T nbr; + ReadProcessMemory(mHProcess, reinterpret_cast(offset), target, length, &nbr); + } + template + T readProcess(intptr_t offset) const noexcept { + T result; + readProcessRaw(offset, sizeof(T), &result); + return result; + } +}; +void GameConnectionTH10::getPowers(std::vector &powers) noexcept { powers.clear(); auto ebp = staticBuffer; const auto base = readProcess(0x00477818); @@ -50,7 +53,7 @@ void GameConnectionTH10::GetPowers(std::vector &powers) noexcept { } } -void GameConnectionTH10::GetEnemyData(std::vector &enemy) noexcept { +void GameConnectionTH10::getEnemyData(std::vector &enemy) noexcept { enemy.clear(); const auto base = readProcess(0x00477704); if (!base) return; @@ -70,9 +73,17 @@ void GameConnectionTH10::GetEnemyData(std::vector &enemy) noexcept { } } } - +void GameConnectionTH10::getMousePosition(Vec2d& pos)noexcept{ + const Vec2d orgdelta = Vec2d(28, 45); + POINT m; + GetCursorPos(&m); + RECT rect; + GetWindowRect(mWindow, &rect); + Vec2d org = Vec2d(rect.left + orgdelta.x + 200.0, rect.top + orgdelta.y); + pos = Vec2d(m.x - org.x, m.y - org.y); +} void -GameConnectionTH10::GetEnemyBulletData(std::vector &bullet, const Player &player, double maxRange) noexcept { +GameConnectionTH10::getEnemyBulletData(std::vector &bullet, const Player &player, double maxRange) noexcept { bullet.clear(); auto ebx = staticBuffer; const auto base = readProcess(0x004776F0); @@ -89,22 +100,23 @@ GameConnectionTH10::GetEnemyBulletData(std::vector &bullet, const Player const auto x = read(ebx + 0x3B4), y = read(ebx + 0x3B8), w = read(ebx + 0x3F0), h = read(ebx + 0x3F4), dx = read(ebx + 0x3C0), dy = read(ebx + 0x3C4); - //涓轰簡鏁堢巼锛屽彧鑰冭檻鍙兘浼氱鍒扮殑瀛愬脊 if (distanceSqr(Vec2d(x, y), player.pos) <= maxRange * maxRange) - bullet.emplace_back(x, y, w, h, dx / 2.0f, dy / 2.0f); + bullet.emplace_back(x, y, w, h, dx, dy); } ebx += 0x7F0; } } -void GameConnectionTH10::GetPlayerData(Player &self) noexcept { +void GameConnectionTH10::getPlayerData(Player &self) noexcept { const auto obj_base = readProcess(0x00477834); if (!obj_base) return; self.pos.x = readProcess(obj_base + 0x3C0); self.pos.y = readProcess(obj_base + 0x3C4); + self.size.x = 2.0; + self.size.y = 2.0; } -void GameConnectionTH10::GetEnemyLaserData(std::vector &laser) noexcept { +void GameConnectionTH10::getEnemyLaserData(std::vector &laser) noexcept { laser.clear(); const auto base = readProcess(0x0047781C); if (!base) return; @@ -115,13 +127,22 @@ void GameConnectionTH10::GetEnemyLaserData(std::vector &laser) noexcept { const auto x = readProcess(esi + 0x24), y = readProcess(esi + 0x28), w = readProcess(esi + 0x44), h = readProcess(esi + 0x40), arc = readProcess(esi + 0x3C); - laser.emplace_back(x, y, w / 2.0f, h, arc); + laser.emplace_back(x, y, w, h, arc); if (!ebx) break; esi = ebx; } } } - +PlayerState GameConnectionTH10::getPlayerStateInformation()noexcept { + int base_addr = readProcess(0x00477834); + if (!base_addr){ + return (PlayerState)0; + } + return (PlayerState)readProcess(base_addr + 0x458); +} +int GameConnectionTH10::getTimeline() noexcept { + return readProcess(0x00474C88); +} void GameConnectionTH10::sendKeyInfo(int32_t dir, bool shift, bool z, bool x) noexcept { KeyboardManager::sendKeyInfo(dir, shift, z, x); } @@ -138,6 +159,9 @@ GameConnectionTH10::GameConnectionTH10() { mHProcess = OpenProcess(PROCESS_VM_READ, true, pid); if (!mHProcess) throw std::runtime_error("cannot open th10 process!"); + mWindow = FindWindow(0, "搶曽晽恄榐丂乣 Mountain of Faith. ver 1.00a"); + if (!mWindow) + throw std::runtime_error("cannot open th10 window!"); } bool GameConnectionTH10::GetProcessIdByName(const char *exeFileName, DWORD &pid) noexcept { diff --git a/Src/GameManager.cpp b/Src/GameManager.cpp index d0f688b..f5de797 100644 --- a/Src/GameManager.cpp +++ b/Src/GameManager.cpp @@ -1,50 +1,18 @@ +#include "Windows.hpp" #include "GameManager.hpp" #include "bmpCreater.hpp" +#include "Vec2.hpp" #include - -namespace { - constexpr uint64_t compress(const Node& in) noexcept { - return static_cast(in.time) << 60 | - static_cast(in.pos.x * 1048576) << 30 | static_cast(in.pos.y * 1048576); - } -} - -GameManager::GameManager(): mState(GameState::NORMAL), mConnection(createGameConnection()) { - valueMap.set_empty_key(compress(Node(0, {0.0, 0.0}))); - valueMap.set_deleted_key(compress(Node(-1, {0.0, 0.0}))); -} - -Vec2d pointRotate(const Vec2d target, const Vec2d center, const double arc) noexcept { - const auto x = (target.x - center.x) * cos(arc) - (target.y - center.y) * sin(arc); - const auto y = (target.x - center.x) * sin(arc) + (target.y - center.y) * cos(arc); - return center + Vec2d(x, y); -} - -void GameManager::updateEnemyLaserBoxes() { - //灏嗘縺鍏夌殑鍒ゅ畾璁句负鐢ˋABB鍖呰捣鏉ラ偅涔堝ぇ(gg,鍏堣繖涔堝啓鍐嶆參鎱㈡敼鍚) - for (Laser& laser : mLaser) { - Vec2d ul = Vec2d(laser.pos.x - laser.size.x / 2.0, laser.pos.y); - Vec2d ur = Vec2d(laser.pos.x + laser.size.x / 2.0, laser.pos.y); - Vec2d dl = Vec2d(laser.pos.x - laser.size.x / 2.0, laser.pos.y + laser.size.y); - Vec2d dr = Vec2d(laser.pos.x + laser.size.x / 2.0, laser.pos.y + laser.size.y); - double arc = laser.arc - 3.1415926 * 5.0 / 2.0; - ul = pointRotate(ul, laser.pos, arc); - ur = pointRotate(ur, laser.pos, arc); - dl = pointRotate(dl, laser.pos, arc); - dr = pointRotate(dr, laser.pos, arc); - laser.pos = (ul + ur + dl + dr) / 4.0; - double sizeX = std::max(std::max(ul.x, ur.x), std::max(dl.x, dr.x)) - std::min( - std::min(ul.x, ur.x), std::min(dl.x, dr.x)); - double sizeY = std::max(std::max(ul.y, ur.y), std::max(dl.y, dr.y)) - std::min( - std::min(ul.y, ur.y), std::min(dl.y, dr.y)); - laser.size = Vec2d(sizeX, sizeY); - } +Vec2d pointRotate(Vec2d target, Vec2d center, double arc) { + float _x, _y; + _x = (target.x - center.x) * cos(arc) - (target.y - center.y) * sin(arc); + _y = (target.x - center.x) * sin(arc) + (target.y - center.y) * cos(arc); + return center + Vec2d(_x, _y); } - void GameManager::outputValueMap(const char* path) { - updateBoardInformation(1000.0); + updateBoardInformation(99999.0); static Pixel map[480][400]; - memset(map, 0, sizeof(map)); // 璁剧疆鑳屾櫙涓洪粦鑹 + memset(map, 0, sizeof(map)); // 设置背景为黑色 double total = 400 * 480; double now = 0; for (int i = 0; i < 400; ++i) { @@ -55,72 +23,98 @@ void GameManager::outputValueMap(const char* path) { double value = getValue(state); double k = 255.0 / 600.0, b = 140.0 * k; value = k * value + b; - map[479 - j][i].g = std::min(255, std::max(0, (int)value)); - map[479 - j][i].r = std::min(255, std::max(0, 255 - (int)value)); + map[479 - j][i].g = std::min(255, std::max(0,(int)value)); + map[479 - j][i].r = std::min(255, std::max(0, 255-(int)value)); } } //if (i % 100 == 0)std::cout << now / total * 100.0<<"%" << std::endl; } - generateBmp(map, 400, 480, path); + generateBmp((BYTE*)map, 400, 480, path); } - -static int invincibleTime = 0; - -void GameManager::updateBoardInformation(const double ratio) { - mConnection->GetPlayerData(mPlayer); - mConnection->GetEnemyData(mEnemy); - mConnection->GetEnemyBulletData(mBullet, mPlayer, ratio); - mConnection->GetEnemyLaserData(mLaser); - mConnection->GetPowers(mPowers); - updateEnemyLaserBoxes(); +void GameManager::updateEnemyLaserBoxes(const double ratio) noexcept { + for (auto& laser : mLaser) { + const auto arc = laser.arc - 3.1415926 * 5.0 / 2.0; + const auto ul = pointRotate(Vec2d(laser.pos.x - laser.size.x / 2.0, laser.pos.y), laser.pos, arc), + ur = pointRotate(Vec2d(laser.pos.x + laser.size.x / 2.0, laser.pos.y), laser.pos, arc), + dl = pointRotate(Vec2d(laser.pos.x - laser.size.x / 2.0, laser.pos.y + laser.size.y), laser.pos, + arc), + dr = pointRotate(Vec2d(laser.pos.x + laser.size.x / 2.0, laser.pos.y + laser.size.y), laser.pos, + arc); + const auto s = (ul + ur)*0.5; + const auto t = (dl + dr)*0.5; + const auto delta = (t - s).unit()*5.0; + double w = laser.size.x*0.5 + 2.0; + double add = w / laser.size.y; + for (double i = 0; i <= 1; i += add){ + double x = i * s.x + (1.0 - i)*t.x; + double y = i * s.y + (1.0 - i)*t.y; + if (distanceSqr(Vec2d(x, y), mPlayer.pos) <= ratio * ratio) { + mBullet.emplace_back(x, y, w, w, delta.x, delta.y); + } + } + } } - -void GameManager::update(unsigned long long frameCount) { - const int maxInvincibleTime = 240; - const int maxDepth = 4; - updateBoardInformation(static_cast(maxDepth) * playerSpeed[0] + 15.0); - if (invincibleTime == maxInvincibleTime - 60 && mPowers.empty()) { invincibleTime = 0; } +void GameManager::updateBoardInformation(const double ratio) noexcept { + mConnection->getPlayerData(mPlayer); + mConnection->getEnemyData(mEnemy); + mConnection->getEnemyBulletData(mBullet, mPlayer, ratio); + mConnection->getEnemyLaserData(mLaser); + mConnection->getPowers(mPowers); + updateEnemyLaserBoxes(ratio); +} +int invincibleTime=0; +void GameManager::update(unsigned long long frameCount,bool enabledMouse) { + mouseMode = enabledMouse; + const int maxDepth = 7; + const int maxInvincibleTime=240; + updateBoardInformation((double)maxDepth * playerSpeed[0] + 15.0); + mConnection->getMousePosition(mMousePos); + mMousePos = fixupPos(mMousePos); + if (invincibleTime == maxInvincibleTime-60 && mPowers.size() == 0){ + invincibleTime = 0; + } if (invincibleTime > 0)invincibleTime--; - //纭畾褰撳墠鐘舵 + //确定当前状态 mState = GameState::NORMAL; switch (mState) { case GameState::NORMAL: { - //BFS鎼滅储maxDepth姝ワ紝鎵惧埌maxDepth姝ュ唴浠峰兼渶楂樼殑鍙埌杈句綅缃 - valueMap.clear_no_resize(); + //BFS搜索maxDepth步,找到maxDepth步内价值最高的可到达位置。 + valueMap.erase(valueMap.begin(), valueMap.end()); Node startState = Node(0, fixupPos(mPlayer.pos)); - valueMap[compress(startState)] = NodeSave(0, false, getValue(startState)); + valueMap[startState] = NodeSave(0, false, getValue(startState)); bfsQueue.push(startState); while (!bfsQueue.empty()) { - const auto now = bfsQueue.front(); - const auto nowData = valueMap[compress(now)]; + Node now = bfsQueue.front(); + NodeSave nowData = valueMap[now]; bfsQueue.pop(); if (now.time >= maxDepth)continue; for (int i = 0; i < 9; ++i) { for (int j = 0; j <= 1; ++j) { - const Node nex{now.time + 1, fixupPos(now.pos + Vec2d{dx[i], dy[i]} * playerSpeed[j])}; - if (!legalState(nex))continue; - if (auto&& [node, success] = valueMap.insert({compress(nex), NodeSave()}); success) { + if (now.time > 0&&nowData.shift != j)continue; + Node nex = Node(now.time + 1, fixupPos( + Vec2d(now.pos.x + dx[i] * playerSpeed[j], now.pos.y + dy[i] * playerSpeed[j]))); + if (valueMap.find(nex) == valueMap.end()) { + if (!legalState(nex))continue; if (now.time == 0) - node->second = NodeSave(i, static_cast(j), getValue(nex)); + valueMap[nex] = NodeSave(i, static_cast(j), getValue(nex)); else - node->second = NodeSave(nowData.from, nowData.shift, getValue(nex)); + valueMap[nex] = NodeSave(nowData.from, nowData.shift, getValue(nex)); bfsQueue.push(nex); } } } } - std::cout << "ValueMap:" << valueMap.size() << std::endl; - - //閫夋嫨鏈楂樹及浠 + //选择最高估价 bool haveNoChoice = true; double maxValue = -99999999999.0; int moveKeyChoice = -1; bool useShift = false; bool useBomb = false; NodeSave movement; + //std::cout << valueMap.size() << std::endl; for (auto&& item : valueMap) { - if (item.first >> 60 == 0)continue; - if (item.second.value - maxValue > eps) { + if (item.first.time == 0)continue; + if (item.second.value - maxValue > doubleEqualEps) { haveNoChoice = false; maxValue = item.second.value; useShift = item.second.shift; @@ -128,43 +122,18 @@ void GameManager::update(unsigned long long frameCount) { movement = item.second; } } - //鎵旈浄鍒ゆ柇 + //扔雷判断 useBomb = false; - Object newPlayer = Object(mPlayer.pos.x, mPlayer.pos.y, mPlayer.size.x, mPlayer.size.y); - //1.琚瓙寮规墦涓 - if (invincibleTime > 0)useBomb = false; - else { - for (auto bullet : mBullet) { - if (hitTestBombChoice(bullet, newPlayer)) { - useBomb = true; - invincibleTime = maxInvincibleTime; - break; - } - } - //2.琚綋鏈 - for (auto enemy : mEnemy) { - if (hitTestBombChoice(enemy, newPlayer)) { - useBomb = true; - invincibleTime = maxInvincibleTime; - break; - } - } - //3.琚縺鍏夋墦涓 - for (auto laser : mLaser) { - if (hitTestBombChoice(laser, newPlayer)) { - useBomb = true; - invincibleTime = maxInvincibleTime; - break; - } - } + if (mConnection->getPlayerStateInformation() == PlayerState::DYING){ + invincibleTime = maxInvincibleTime; + useBomb = true; } - //if (!mLaser.empty())useBomb = true; - //鍙戦佸喅绛 + //发送决策 if (mEnemy.size() <= 1 && mBullet.empty()) - //璺宠繃瀵硅瘽锛岄棿闅斿抚鎸塟 + //跳过对话,间隔帧按Z mConnection->sendKeyInfo(moveKeyChoice, useShift, frameCount % 2, useBomb); else - //姝e父杩涜娓告垙 + //正常进行游戏 mConnection->sendKeyInfo(moveKeyChoice, useShift, true, useBomb); break; } @@ -173,7 +142,7 @@ void GameManager::update(unsigned long long frameCount) { } } -//鍒ゆ柇鐘舵佹槸鍚﹀悎娉(鏌愪釜浣嶇疆鑳藉惁鍒拌揪) +//判断状态是否合法(某个位置能否到达) bool GameManager::legalState(Node state) const noexcept { if (invincibleTime > 0)return true; Object newPlayer = Object(state.pos.x, state.pos.y, mPlayer.size.x, mPlayer.size.y); @@ -190,35 +159,44 @@ bool GameManager::legalState(Node state) const noexcept { //} return true; } - -//瀵圭姸鎬佽繘琛屼及浠 +//对状态进行估价 double GameManager::getValue(Node state) const noexcept { double value = 0.0; double minEnemyDis = 400.0; Vec2d newPos = state.pos; Object newPlayer = Object(newPos.x, newPos.y, mPlayer.size.x, mPlayer.size.y); + //鼠标引导模式估价 + if (mouseMode) + { + double dis = distance(mMousePos, newPos); + value += 150.0 * (sqrt(390400.0) - dis) / sqrt(390400.0); + value -= 0.1 * state.time; + return value; + } minEnemyDis = 400.0; - //鏀剁偣浼颁环锛岀鐐硅秺杩戯紝浠峰艰秺楂 + //收点估价,离点越近,价值越高 double minPowerDis = 390400.0; for (auto& power : mPowers) { Vec2d newPowerPos = power.pos + power.delta * state.time; double dis = distanceSqr(newPowerPos, newPos); - if (dis < minPowerDis) { minPowerDis = dis; } + if (dis < minPowerDis) { + minPowerDis = dis; + } } - if (invincibleTime > 0)value += 400 * (390400.0 - minPowerDis) / 390400.0; + if (invincibleTime>0)value += 400 * (390400.0 - minPowerDis) / 390400.0; else value += 180 * (390400.0 - minPowerDis) / 390400.0; - //鍦板浘浣嶇疆浼颁环(绔欏湪鍦板浘鍋忎笅鐨勪綅缃姞鍒) + //地图位置估价(站在地图偏下的位置加分) value += 80.0 * getMapValue(newPos); - //鍑荤牬鏁屾満浼颁环(绔欏湪鏁屾満姝d笅鏂瑰姞鍒) - if (invincibleTime == 0) { + //击破敌机估价(站在敌机正下方加分) + if (invincibleTime == 0){ for (auto& enemy : mEnemy) { double dis = abs(enemy.pos.x + enemy.delta.x * state.time - newPos.x); minEnemyDis = std::min(minEnemyDis, dis); } value += 80.0 * (400 - minEnemyDis) / 400; } - //瀛愬脊浼颁环(鍜屽瓙寮硅繍鍔ㄦ柟鍚戝す瑙掕秺澶у噺鍒嗚秺灏) + //子弹估价(和子弹运动方向夹角越大减分越少) double avgScore = 0; double count = 0; for (auto bullet : mBullet) { @@ -227,7 +205,7 @@ double GameManager::getValue(Node state) const noexcept { count++; Vec2d selfDir = (newPos - bullet.pos).unit(); Vec2d bulletDir = bullet.delta.unit(); - //璇ヤ綅缃殑浠峰间笌璇ヤ綅缃埌瀛愬脊鐨勮繛绾垮拰瀛愬脊杩愬姩鏂瑰悜鐨勫す瑙掓湁鍏筹紝澶硅瓒婂ぇ锛屽噺鍒嗚秺灏 + //该位置的价值与该位置到子弹的连线和子弹运动方向的夹角有关,夹角越大,减分越少 double dirvalue = selfDir.dot(bulletDir); dirvalue += 1; avgScore -= dirvalue; @@ -241,19 +219,19 @@ double GameManager::getValue(Node state) const noexcept { count = 0; double count2 = 0; double avgScore2 = 0; - //鏁屾満浼颁环 + //敌机估价 for (auto& enemy : mEnemy) { double dis = distanceSqr(enemy.pos, newPos); Vec2d selfDir = (newPos - enemy.pos).unit(); Vec2d up(0, -1); double dirvalue = selfDir.dot(up); - //绔欑殑浣嶇疆鍋忛珮锛屽鏄撹寮瑰箷灏佹銆傗滀粠鏁屾満鎸囧悜鑷満鐨勫悜閲忊濆拰鈥滀粠鏁屾満鎸囧悜姝d笂鏂圭殑鍚戦噺鈥濈殑澶硅瓒婂ぇ瓒婂畨鍏紝鍑忓垎瓒婂皯銆 + //站的位置偏高,容易被弹幕封死。“从敌机指向自机的向量”和“从敌机指向正上方的向量”的夹角越大越安全,减分越少。 if (enemy.pos.y <= 240) { dirvalue += 1; avgScore -= dirvalue; count++; } - //绂绘晫鏈鸿繃杩涘鏄撹鍙戝嚭鐨勫脊骞曟墦姝伙紝涔熷彲鑳借浣撴湳銆傚洜姝よ窛绂昏秺杩戝噺鍒嗚秺澶氥 + //离敌机过进容易被发出的弹幕打死,也可能被体术。因此距离越近减分越多。 if (dis <= 20000.0) { avgScore2 -= 1.0 - (dis / 20000.0); count2++; @@ -271,8 +249,7 @@ double GameManager::getValue(Node state) const noexcept { value -= 0.1 * state.time; return value; } - -//淇瓒呭嚭鍦板浘鐨勫潗鏍(涓昏鏄嚜鏈) +//修正超出地图的坐标(主要是自机) Vec2d GameManager::fixupPos(const Vec2d& pos) noexcept { Vec2d res = pos; if (res.x < ulCorner.x)res.x = ulCorner.x; @@ -281,21 +258,13 @@ Vec2d GameManager::fixupPos(const Vec2d& pos) noexcept { if (res.y > drCorner.y)res.y = drCorner.y; return res; } - -//鎵旈浄鏃剁殑纰版挒妫娴嬶紝璇樊涓1.0 -bool GameManager::hitTestBombChoice(const Object& a, const Object& b) noexcept { - return abs(a.pos.x - b.pos.x) - ((a.size.x + b.size.x) / 2.0) <= 1.0 && - abs(a.pos.y - b.pos.y) - ((a.size.y + b.size.y) / 2.0) <= 1.0; -} - -//鍐崇瓥鏃剁殑纰版挒妫娴嬶紝灏嗗垽瀹氳寖鍥村鍔4.5鍚庢娴嬨傝涓嶈闅忔剰淇敼杩欎釜鍊笺 -//鍒ゅ畾杩囧ぇ锛孉I搴曞姏涓嬮檷銆傚垽瀹氳繃灏忥紝AI鏇村鏄撶Щ鍔ㄥ埌鏋佸叾鎺ヨ繎瀛愬脊鐨勫湴鏂癸紝鐢变簬瑙嗛噹鐨勫眬闄愭э紝瀹规槗鎾炲脊銆 +//决策时的碰撞检测 bool GameManager::hitTest(const Object& a, const Object& b) noexcept { - return abs(a.pos.x - b.pos.x) - ((a.size.x + b.size.x) / 2.0) <= 4.5 && - abs(a.pos.y - b.pos.y) - ((a.size.y + b.size.y) / 2.0) <= 4.5; + const double hitTestEps = 0.05;//此值过小可能导致AI经常冲太高死,但从算法上改进比调大此值要好 + return abs(a.pos.x - b.pos.x) - ((a.size.x + b.size.x) / 2.0) <= hitTestEps && + abs(a.pos.y - b.pos.y) - ((a.size.y + b.size.y) / 2.0) <= hitTestEps; } - -//鍦板浘浣嶇疆浼颁环 +//地图位置估价 double GameManager::getMapValue(Vec2d pos) noexcept { if (pos.y <= 100) return pos.y * 0.9 / 100; @@ -303,3 +272,15 @@ double GameManager::getMapValue(Vec2d pos) noexcept { double disx = (200 - abs(0 - pos.x)) / 200.0; return dis * 0.95 + disx * 0.05; } + +bool operator<(const Node& lhs, const Node& rhs) { + if (lhs.time < rhs.time)return true; + if (lhs.time > rhs.time)return false; + if ((rhs.pos.x - lhs.pos.x) > eps)return true; + if ((lhs.pos.x - rhs.pos.x) > eps)return false; + if ((rhs.pos.y - lhs.pos.y) > eps)return true; + return false; +} +int GameManager::getTimeline() noexcept{ + return mConnection->getTimeline(); +} diff --git a/Src/GameManager.hpp b/Src/GameManager.hpp index 4b5deac..dc8bdba 100644 --- a/Src/GameManager.hpp +++ b/Src/GameManager.hpp @@ -1,32 +1,34 @@ #pragma once +#include #include -#include "sparsehash/dense_hash_map" #include "GameConnection.hpp" const double s2d2 = sqrt(2.0) / 2.0; const double dx[9] = {0, 1.0, s2d2, 0, -s2d2, -1.0, -s2d2, 0, s2d2}; const double dy[9] = {0, 0, -s2d2, -1.0, -s2d2, 0, s2d2, 1.0, s2d2}; -const double playerSpeed[2] = {4.5, 2.0}; -const Vec2d ulCorner = Vec2d(-200, 0); -const Vec2d drCorner = Vec2d(200, 480); -const double eps = 1e-7; +const double dx4[5] = { 0, 1.0, 0, -1.0, 0}; +const double dy4[5] = { 0, 0, -1.0, 0, 1.0 }; +const double playerSpeed[2] = {4.5, 2.0};//灵梦4.5/2.0 魔理沙5.0/2.0 +const Vec2d ulCorner = Vec2d(-184, 32); +const Vec2d drCorner = Vec2d(184, 432); +const double eps = 1e-1; +const double doubleEqualEps = 1e-9; //从高位到低位分别为上下左右 const int keyinfo[9] = {0x0, 0x1, 0x9, 0x8, 0xa, 0x2, 0x6, 0x4, 0x5}; - +const int keyinfo4[5] = { 0x0,0x1,0x8,0x2,0x4 }; enum class GameState { NORMAL, COLLECT, MOVE }; - struct Node { int8_t time; Vec2d pos; - Node() = default; constexpr Node(int8_t time_, Vec2d pos_) noexcept : time(time_), pos(pos_) {} }; +bool operator<(const Node& lhs, const Node& rhs); struct NodeSave { bool shift; int from; @@ -34,30 +36,32 @@ struct NodeSave { NodeSave() = default; constexpr NodeSave(int from_, bool shift_, double value_) noexcept : shift(shift_), from(from_), value(value_) {} }; - class GameManager { public: - GameManager(); + GameManager() : mState(GameState::NORMAL), mConnection(createGameConnection()) {} + void update(unsigned long long frameCount,bool enabledMouse); void outputValueMap(const char* path); - void update(unsigned long long frameCount); + int getTimeline() noexcept; private: - google::dense_hash_map valueMap { 5500 }; + std::map valueMap; std::queue bfsQueue; GameState mState; - Player mPlayer{}; + Player mPlayer {}; std::vector mEnemy; std::vector mBullet; std::vector mLaser; std::vector mPowers; + Vec2d mMousePos; + bool mouseMode; std::unique_ptr mConnection; - void updateEnemyLaserBoxes(); - void updateBoardInformation(const double ratio); bool legalState(Node state) const noexcept; //估价效率过低,待修改 double getValue(Node state) const noexcept; static Vec2d fixupPos(const Vec2d& pos) noexcept; static bool hitTest(const Object& a, const Object& b) noexcept; - static bool hitTestBombChoice(const Object& a, const Object& b) noexcept; + // Frame Advancing + void updateEnemyLaserBoxes(const double ratio) noexcept; + void updateBoardInformation(double ratio) noexcept; //地图位置估价 static double getMapValue(Vec2d pos) noexcept; }; diff --git a/Src/Object.hpp b/Src/Object.hpp index 5334c26..d9fa4c2 100644 --- a/Src/Object.hpp +++ b/Src/Object.hpp @@ -19,5 +19,5 @@ struct Laser : Object { struct Player : Object { Player() = default; - constexpr Player(double x, double y, double w, double h) : Object(x, y, 2, 2) {} + constexpr Player(double x, double y, double w, double h) : Object(x, y, 4.0, 4.0) {} }; diff --git a/Src/bmpCreater.hpp b/Src/bmpCreater.hpp index eb680b4..e5904eb 100644 --- a/Src/bmpCreater.hpp +++ b/Src/bmpCreater.hpp @@ -1,11 +1,79 @@ -#pragma once -#include +#include +#include +#include +#include +typedef long LONG; +typedef unsigned char BYTE; +typedef unsigned long DWORD; +typedef unsigned short WORD; +//位图文件头文件定义 +//其中不包括文件类型信息(由于结构体的内存结构决定,要是加了的话将不能正确的读取文件信息) +typedef struct { + WORD bfType;//文件类型,必须是0x424D,即字符“BM” + DWORD bfSize;//文件大小 + WORD bfReserved1;//保留字 + WORD bfReserved2;//保留字 + DWORD bfOffBits;//从文件头到实际位图数据的偏移字节数 +} BMPFILEHEADER_T; -//生成Bmp图片,传递RGB值,传递图片像素大小,传递图片存储路径 -void generateBmp(void* pData, int width, int height, const char* filename); - -struct Pixel { - uint8_t b, g, r; - constexpr Pixel(): b(0), g(0), r(0) {} - constexpr Pixel(uint8_t r_, uint8_t g_, uint8_t b_) : b(b_), g(g_), r(r_) {} +struct BMPFILEHEADER_S { + WORD bfType; + DWORD bfSize; + WORD bfReserved1; + WORD bfReserved2; + DWORD bfOffBits; }; +typedef struct { + DWORD biSize;//信息头大小 + LONG biWidth;//图像宽度 + LONG biHeight;//图像高度 + WORD biPlanes;//位平面数,必须为1 + WORD biBitCount;//每像素位数 + DWORD biCompression;//压缩类型 + DWORD biSizeImage;//压缩图像大小字节数 + LONG biXPelsPerMeter;//水平分辨率 + LONG biYPelsPerMeter;//垂直分辨率 + DWORD biClrUsed;//位图实际用到的色彩数 + DWORD biClrImportant;//本位图中重要的色彩数 +} BMPINFOHEADER_T;//位图信息头定义 + +void generateBmp(BYTE * pData, int width, int height, const char * filename) {//生成Bmp图片,传递RGB值,传递图片像素大小,传递图片存储路径 + int size = width * height * 3; // 每个像素点3个字节 + // 位图第一部分,文件信息 + BMPFILEHEADER_T bfh; + bfh.bfType = 0X4d42; //bm + bfh.bfSize = size // data size + + sizeof(BMPFILEHEADER_T) // first section size + + sizeof(BMPINFOHEADER_T) // second section size + ; + bfh.bfReserved1 = 0; // reserved + bfh.bfReserved2 = 0; // reserved + bfh.bfOffBits = bfh.bfSize - size; + + // 位图第二部分,数据信息 + BMPINFOHEADER_T bih; + bih.biSize = sizeof(BMPINFOHEADER_T); + bih.biWidth = width; + bih.biHeight = height; + bih.biPlanes = 1; + bih.biBitCount = 24; + bih.biCompression = 0; + bih.biSizeImage = size; + bih.biXPelsPerMeter = 0; + bih.biYPelsPerMeter = 0; + bih.biClrUsed = 0; + bih.biClrImportant = 0; + FILE * fp = fopen(filename, "wb"); + if (!fp) return; + fwrite(&bfh, 1, sizeof(BMPFILEHEADER_T), fp); + fwrite(&bih, 1, sizeof(BMPINFOHEADER_T), fp); + fwrite(pData, 1, size, fp); + fclose(fp); +} +struct Pixel{ + BYTE b; + BYTE g; + BYTE r; + Pixel() { r = g = b = 0; } + Pixel(BYTE r_, BYTE g_, BYTE b_) { r = r_; g = g_;b = b_; } +}; \ No newline at end of file diff --git a/Src/main.cpp b/Src/main.cpp index a4f568d..334939f 100644 --- a/Src/main.cpp +++ b/Src/main.cpp @@ -3,10 +3,10 @@ #include #include #include +#include "GameConnection.hpp" #include "KeyboardManager.hpp" #include "GameManager.hpp" -using namespace std; using namespace std::chrono; class StopWatch { @@ -20,8 +20,6 @@ class StopWatch { private: steady_clock::time_point mBeginTime, mEndTime; }; - - void pauseUntilPress(const char* info, char key) { std::cout << info << std::endl; while (true) { @@ -29,38 +27,57 @@ void pauseUntilPress(const char* info, char key) { std::this_thread::sleep_for(10ms); } } - int main() { try { KeyboardManager::init(); auto game = std::make_shared(); bool quit = false; std::cout << "准备完成" << std::endl; - pauseUntilPress("请将焦点放在风神录窗口上,开始游戏,然后按C开启AI", 'C'); - std::cout << "已开始游戏,按P键打印估价图,Q键退出" << std::endl; + pauseUntilPress("请将焦点放在风神录窗口上,开始游戏,按C开启AI", 'C'); + std::cout << "已开始游戏,按C键开启/关闭鼠标引导,Q键退出" << std::endl; unsigned long long frameCount = 0; StopWatch watch; int mapOutputCount = 0; - double cd = 0; - const double maxcd = 10; + const double maxCd = 30; + double cd = 30; + bool mouseMode = false; + std::cout << "鼠标引导当前处于关闭状态" << std::endl; + int gameFrame = game->getTimeline(); + unsigned long long loopCount = 0; while (!quit) { - if (cd > 0)cd -= 1.0; - if (isKeyDown('Q')) - break; - if (isKeyDown('P') && cd <= 0) { - cd = maxcd; - mapOutputCount++; - std::stringstream outputStream; - outputStream << "./value" << mapOutputCount << ".bmp"; - std::cout << outputStream.str() << std::endl; - game->outputValueMap(outputStream.str().c_str()); - continue; - } - watch.start(); - frameCount++; - game->update(frameCount); - watch.stop(); - std::this_thread::sleep_for(milliseconds(std::max(0, 16 - watch.elapsed_ms()))); + if (loopCount % 16 == 0){ + if (cd > 0)cd -= 1.0; + if (isKeyDown('Q')) + break; + if (cd <= 0 && isKeyDown('C')){ + cd = maxCd; + mouseMode ^= 1; + std::cout << (mouseMode ? "鼠标引导已开启" : "鼠标引导已关闭") << std::endl; + } + if (isKeyDown('P') && cd <= 0) { + cd = maxCd; + mapOutputCount++; + std::stringstream outputStream; + outputStream << "./value" << mapOutputCount << ".bmp"; + std::cout << outputStream.str() << std::endl; + game->outputValueMap(outputStream.str().c_str()); + continue; + } + } + int getGameFrame = game->getTimeline(); + if (getGameFrame != gameFrame){ + if (getGameFrame > gameFrame + 1){ + std::cout << "Frame Lost!" << std::endl; + } + gameFrame = getGameFrame; + //watch.start(); + frameCount++; + game->update(frameCount, mouseMode); + //watch.stop(); + //cout << watch.elapsed_ms() / 16.0 * 100.0 << "%" << endl; + } + loopCount++; + std::this_thread::sleep_for(milliseconds(1)); } KeyboardManager::sendKeyInfo(0, false, false, false); } diff --git a/TH10_Collision_Points/TH10_Collision_Points.vcxproj b/TH10_Collision_Points/TH10_Collision_Points.vcxproj index 7f4e4fb..566ad0e 100644 --- a/TH10_Collision_Points/TH10_Collision_Points.vcxproj +++ b/TH10_Collision_Points/TH10_Collision_Points.vcxproj @@ -19,14 +19,12 @@ - - @@ -38,33 +36,33 @@ {CF610FEC-D7F3-4AAF-8661-23758D2EA736} TH10_Collision_Points - 10.0.16299.0 + 10.0 TH10_AI Application true - v141 + v142 MultiByte Application false - v141 + v142 true MultiByte Application true - v141 + v142 MultiByte Application false - v141 + v142 true MultiByte @@ -138,6 +136,7 @@ true true stdcpplatest + %(PreprocessorDefinitions) true diff --git a/TH10_Collision_Points/TH10_Collision_Points.vcxproj.filters b/TH10_Collision_Points/TH10_Collision_Points.vcxproj.filters index 19f1608..7d27810 100644 --- a/TH10_Collision_Points/TH10_Collision_Points.vcxproj.filters +++ b/TH10_Collision_Points/TH10_Collision_Points.vcxproj.filters @@ -23,9 +23,6 @@ 婧愭枃浠 - - 婧愭枃浠 - @@ -49,8 +46,5 @@ 澶存枃浠 - - 澶存枃浠 - \ No newline at end of file