Browse Source

Merge branch 'dev' of github.com:eesast/THUAI6 into dev

tags/0.1.0
TCL 2 years ago
parent
commit
99dfc5b673
44 changed files with 296 additions and 207 deletions
  1. +4
    -0
      CAPI/cpp/API/include/AI.h
  2. +4
    -0
      CAPI/cpp/API/include/API.h
  3. +4
    -0
      CAPI/cpp/API/include/Communication.h
  4. +4
    -0
      CAPI/cpp/API/include/ConcurrentQueue.hpp
  5. +4
    -0
      CAPI/cpp/API/include/constants.h
  6. +4
    -3
      CAPI/cpp/API/include/logic.h
  7. +4
    -0
      CAPI/cpp/API/include/state.h
  8. +4
    -0
      CAPI/cpp/API/include/structures.h
  9. +4
    -0
      CAPI/cpp/API/include/utils.hpp
  10. +1
    -1
      CAPI/cpp/API/src/AI.cpp
  11. +5
    -0
      CAPI/cpp/API/src/API.cpp
  12. +4
    -0
      CAPI/cpp/API/src/Communication.cpp
  13. +5
    -0
      CAPI/cpp/API/src/DebugAPI.cpp
  14. +12
    -21
      CAPI/cpp/API/src/logic.cpp
  15. +4
    -0
      CAPI/cpp/API/src/main.cpp
  16. +2
    -0
      CAPI/cpp/proto/Services.grpc.pb.h
  17. +1
    -1
      CAPI/python/PyAPI/AI.py
  18. +4
    -4
      CAPI/python/PyAPI/constants.py
  19. +26
    -24
      CAPI/python/PyAPI/logic.py
  20. +3
    -3
      CAPI/python/run.sh
  21. +6
    -6
      dependency/shell/run.sh
  22. +11
    -3
      docs/CAPI接口(cpp).md
  23. +10
    -4
      docs/CAPI接口(python).md
  24. +3
    -1
      docs/GameRules.md
  25. +10
    -6
      docs/QandA.md
  26. +2
    -2
      docs/使用文档.md
  27. +2
    -2
      logic/Client/Client.csproj
  28. +2
    -1
      logic/Client/MainWindow.xaml
  29. +48
    -70
      logic/Client/MainWindow.xaml.cs
  30. BIN
      logic/Client/eesast_software_trans.ico
  31. BIN
      logic/Client/eesast_software_trans_enlarged.ico
  32. +0
    -1
      logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs
  33. +25
    -8
      logic/GameClass/GameObj/Map/Map.cs
  34. +0
    -1
      logic/GameEngine/CollisionChecker.cs
  35. +0
    -1
      logic/Gaming/AttackManager.cs
  36. +13
    -26
      logic/Gaming/SkillManager/SkillManager.cs
  37. +60
    -12
      logic/Server/PlaybackServer.cs
  38. +0
    -1
      logic/Server/RpcServices.cs
  39. +1
    -5
      logic/cmd/PlaybackServer.cmd
  40. BIN
      resource/CompileFaster.png
  41. BIN
      resource/eesast_software.png
  42. BIN
      resource/eesast_software_trans.ico
  43. BIN
      resource/eesast_software_trans.png
  44. BIN
      resource/eesast_software_trans_enlarged.ico

+ 4
- 0
CAPI/cpp/API/include/AI.h View File

@@ -4,6 +4,10 @@


#include "API.h" #include "API.h"


#undef GetMessage
#undef SendMessage
#undef PeekMessage

class IAI class IAI
{ {
public: public:


+ 4
- 0
CAPI/cpp/API/include/API.h View File

@@ -22,6 +22,10 @@


#include "structures.h" #include "structures.h"


#undef GetMessage
#undef SendMessage
#undef PeekMessage

const constexpr int numOfGridPerCell = 1000; const constexpr int numOfGridPerCell = 1000;


class IAI; class IAI;


+ 4
- 0
CAPI/cpp/API/include/Communication.h View File

@@ -14,6 +14,10 @@
#include <queue> #include <queue>
#include <atomic> #include <atomic>


#undef GetMessage
#undef SendMessage
#undef PeekMessage

class Logic; class Logic;


class Communication class Communication


+ 4
- 0
CAPI/cpp/API/include/ConcurrentQueue.hpp View File

@@ -8,6 +8,10 @@
#include <utility> #include <utility>
#include <optional> #include <optional>


#undef GetMessage
#undef SendMessage
#undef PeekMessage

template<typename Elem> template<typename Elem>
class ConcurrentQueue class ConcurrentQueue
{ {


+ 4
- 0
CAPI/cpp/API/include/constants.h View File

@@ -6,6 +6,10 @@
#define SCCI static const constexpr inline #define SCCI static const constexpr inline
#endif #endif


#undef GetMessage
#undef SendMessage
#undef PeekMessage

namespace Constants namespace Constants
{ {
SCCI int frameDuration = 50; // 每帧毫秒数 SCCI int frameDuration = 50; // 每帧毫秒数


+ 4
- 3
CAPI/cpp/API/include/logic.h View File

@@ -31,6 +31,10 @@
#include "Communication.h" #include "Communication.h"
#include "ConcurrentQueue.hpp" #include "ConcurrentQueue.hpp"


#undef GetMessage
#undef SendMessage
#undef PeekMessage

// 封装了通信组件和对AI对象进行操作 // 封装了通信组件和对AI对象进行操作
class Logic : public ILogic class Logic : public ILogic
{ {
@@ -49,9 +53,6 @@ private:
THUAI6::TrickerType trickerType; THUAI6::TrickerType trickerType;
THUAI6::StudentType studentType; THUAI6::StudentType studentType;


// GUID信息
std::vector<int64_t> playerGUIDs;

std::unique_ptr<IGameTimer> timer; std::unique_ptr<IGameTimer> timer;


std::thread tAI; // 用于运行AI的线程 std::thread tAI; // 用于运行AI的线程


+ 4
- 0
CAPI/cpp/API/include/state.h View File

@@ -9,6 +9,10 @@


#include "structures.h" #include "structures.h"


#undef GetMessage
#undef SendMessage
#undef PeekMessage

// 存储场上的状态 // 存储场上的状态
struct State struct State
{ {


+ 4
- 0
CAPI/cpp/API/include/structures.h View File

@@ -8,6 +8,10 @@
#include <vector> #include <vector>
#include <string> #include <string>


#undef GetMessage
#undef SendMessage
#undef PeekMessage

namespace THUAI6 namespace THUAI6
{ {




+ 4
- 0
CAPI/cpp/API/include/utils.hpp View File

@@ -13,6 +13,10 @@


#include "structures.h" #include "structures.h"


#undef GetMessage
#undef SendMessage
#undef PeekMessage

namespace AssistFunction namespace AssistFunction
{ {




+ 1
- 1
CAPI/cpp/API/src/AI.cpp View File

@@ -5,7 +5,7 @@
#include "constants.h" #include "constants.h"
// 注意不要使用conio.h,Windows.h等非标准库 // 注意不要使用conio.h,Windows.h等非标准库


// 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新
// 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新,大致一帧更新一次
extern const bool asynchronous = false; extern const bool asynchronous = false;


// 选手需要依次将player0到player4的职业在这里定义 // 选手需要依次将player0到player4的职业在这里定义


+ 5
- 0
CAPI/cpp/API/src/API.cpp View File

@@ -1,6 +1,11 @@
#include <optional> #include <optional>
#include "AI.h" #include "AI.h"
#include "API.h" #include "API.h"

#undef GetMessage
#undef SendMessage
#undef PeekMessage

#define PI 3.14159265358979323846 #define PI 3.14159265358979323846


int StudentAPI::GetFrameCount() const int StudentAPI::GetFrameCount() const


+ 4
- 0
CAPI/cpp/API/src/Communication.cpp View File

@@ -5,6 +5,10 @@
#include <mutex> #include <mutex>
#include <condition_variable> #include <condition_variable>


#undef GetMessage
#undef SendMessage
#undef PeekMessage

using grpc::ClientContext; using grpc::ClientContext;


Communication::Communication(std::string sIP, std::string sPort) Communication::Communication(std::string sIP, std::string sPort)


+ 5
- 0
CAPI/cpp/API/src/DebugAPI.cpp View File

@@ -4,6 +4,11 @@
#include "API.h" #include "API.h"
#include "utils.hpp" #include "utils.hpp"
#include "structures.h" #include "structures.h"

#undef GetMessage
#undef SendMessage
#undef PeekMessage

#define PI 3.14159265358979323846 #define PI 3.14159265358979323846


StudentDebugAPI::StudentDebugAPI(ILogic& logic, bool file, bool print, bool warnOnly, int64_t playerID) : StudentDebugAPI::StudentDebugAPI(ILogic& logic, bool file, bool print, bool warnOnly, int64_t playerID) :


+ 12
- 21
CAPI/cpp/API/src/logic.cpp View File

@@ -8,6 +8,10 @@
#include "utils.hpp" #include "utils.hpp"
#include "Communication.h" #include "Communication.h"


#undef GetMessage
#undef SendMessage
#undef PeekMessage

extern const bool asynchronous; extern const bool asynchronous;


Logic::Logic(THUAI6::PlayerType type, int64_t ID, THUAI6::TrickerType tricker, THUAI6::StudentType student) : Logic::Logic(THUAI6::PlayerType type, int64_t ID, THUAI6::TrickerType tricker, THUAI6::StudentType student) :
@@ -323,17 +327,6 @@ void Logic::ProcessMessage()
case THUAI6::GameState::GameStart: case THUAI6::GameState::GameStart:
logger->info("Game Start!"); logger->info("Game Start!");


// 重新读取玩家的guid,保证人类在前屠夫在后
playerGUIDs.clear();
for (const auto& obj : clientMsg.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::StudentMessage)
playerGUIDs.push_back(obj.student_message().guid());
for (const auto& obj : clientMsg.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::TrickerMessage)
playerGUIDs.push_back(obj.tricker_message().guid());
currentState->guids = playerGUIDs;
bufferState->guids = playerGUIDs;

// 读取地图 // 读取地图
for (const auto& item : clientMsg.obj_message()) for (const auto& item : clientMsg.obj_message())
if (Proto2THUAI6::messageOfObjDict[item.message_of_obj_case()] == THUAI6::MessageOfObj::MapMessage) if (Proto2THUAI6::messageOfObjDict[item.message_of_obj_case()] == THUAI6::MessageOfObj::MapMessage)
@@ -368,16 +361,6 @@ void Logic::ProcessMessage()


break; break;
case THUAI6::GameState::GameRunning: case THUAI6::GameState::GameRunning:
// 重新读取玩家的guid,guid确保人类在前屠夫在后
playerGUIDs.clear();
for (const auto& obj : clientMsg.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::StudentMessage)
playerGUIDs.push_back(obj.student_message().guid());
for (const auto& obj : clientMsg.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::TrickerMessage)
playerGUIDs.push_back(obj.tricker_message().guid());
currentState->guids = playerGUIDs;
bufferState->guids = playerGUIDs;


LoadBuffer(clientMsg); LoadBuffer(clientMsg);
break; break;
@@ -605,9 +588,16 @@ void Logic::LoadBuffer(const protobuf::MessageToClient& message)
bufferState->props.clear(); bufferState->props.clear();
bufferState->bullets.clear(); bufferState->bullets.clear();
bufferState->bombedBullets.clear(); bufferState->bombedBullets.clear();
bufferState->guids.clear();


logger->debug("Buffer cleared!"); logger->debug("Buffer cleared!");
// 读取新的信息 // 读取新的信息
for (const auto& obj : message.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::StudentMessage)
bufferState->guids.push_back(obj.student_message().guid());
for (const auto& obj : message.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::TrickerMessage)
bufferState->guids.push_back(obj.tricker_message().guid());
bufferState->gameInfo = Proto2THUAI6::Protobuf2THUAI6GameInfo(message.all_message()); bufferState->gameInfo = Proto2THUAI6::Protobuf2THUAI6GameInfo(message.all_message());
LoadBufferSelf(message); LoadBufferSelf(message);
for (const auto& item : message.obj_message()) for (const auto& item : message.obj_message())
@@ -689,6 +679,7 @@ bool Logic::TryConnection()


bool Logic::HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const bool Logic::HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const
{ {
std::unique_lock<std::mutex> lock(mtxState);
return AssistFunction::HaveView(viewRange, selfX, selfY, gridX, gridY, currentState->gameMap); return AssistFunction::HaveView(viewRange, selfX, selfY, gridX, gridY, currentState->gameMap);
} }




+ 4
- 0
CAPI/cpp/API/src/main.cpp View File

@@ -4,6 +4,10 @@
#include <tclap/CmdLine.h> #include <tclap/CmdLine.h>
#include <array> #include <array>


#undef GetMessage
#undef SendMessage
#undef PeekMessage

#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning(disable : 4996) #pragma warning(disable : 4996)
#endif #endif


+ 2
- 0
CAPI/cpp/proto/Services.grpc.pb.h View File

@@ -25,6 +25,8 @@
#include <grpcpp/impl/codegen/stub_options.h> #include <grpcpp/impl/codegen/stub_options.h>
#include <grpcpp/impl/codegen/sync_stream.h> #include <grpcpp/impl/codegen/sync_stream.h>


#undef SendMessage

namespace protobuf namespace protobuf
{ {




+ 1
- 1
CAPI/python/PyAPI/AI.py View File

@@ -8,7 +8,7 @@ import time




class Setting: class Setting:
# 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新
# 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新,大致一帧更新一次
@staticmethod @staticmethod
def asynchronous() -> bool: def asynchronous() -> bool:
return False return False


+ 4
- 4
CAPI/python/PyAPI/constants.py View File

@@ -301,13 +301,13 @@ class SummonGolem:


class CommonAttackOfTricker: class CommonAttackOfTricker:
BulletBombRange = 0 BulletBombRange = 0
BulletAttackRange = Constants.Constants.basicAttackShortRange
BulletAttackRange = Constants.basicAttackShortRange
ap = Constants.basicApOfTricker ap = Constants.basicApOfTricker
Speed = Constants.basicBulletMoveSpeed Speed = Constants.basicBulletMoveSpeed
IsRemoteAttack = False IsRemoteAttack = False
CastTime = BulletAttackRange * 1000 / Speed CastTime = BulletAttackRange * 1000 / Speed
Backswing =Constants.basicBackswing
RecoveryFromHit =Constants.basicRecoveryFromHit
Backswing = Constants.basicBackswing
RecoveryFromHit = Constants.basicRecoveryFromHit
cd = Constants.basicBackswing cd = Constants.basicBackswing
maxBulletNum = 1 maxBulletNum = 1
@@ -340,4 +340,4 @@ class JumpyDumpty:
BulletAttackRange = Constants.basicRemoteAttackRange * 2 BulletAttackRange = Constants.basicRemoteAttackRange * 2
ap = (int)(Constants.basicApOfTricker* 0.6) ap = (int)(Constants.basicApOfTricker* 0.6)
Speed = Constants.basicBulletMoveSpeed* 43 / 37 Speed = Constants.basicBulletMoveSpeed* 43 / 37
IsRemoteAttack = False
IsRemoteAttack = False

+ 26
- 24
CAPI/python/PyAPI/logic.py View File

@@ -3,6 +3,7 @@ from typing import List, Union, Callable, Tuple
import threading import threading
import logging import logging
import copy import copy
import platform
import proto.MessageType_pb2 as MessageType import proto.MessageType_pb2 as MessageType
import proto.Message2Server_pb2 as Message2Server import proto.Message2Server_pb2 as Message2Server
import proto.Message2Clients_pb2 as Message2Clients import proto.Message2Clients_pb2 as Message2Clients
@@ -22,7 +23,6 @@ class Logic(ILogic):


# ID # ID
self.__playerID: int = playerID self.__playerID: int = playerID
self.__playerGUIDs: List[int] = []


self.__playerType: THUAI6.PlayerType = playerType self.__playerType: THUAI6.PlayerType = playerType


@@ -214,7 +214,7 @@ class Logic(ILogic):


def GetPlayerGUIDs(self) -> List[int]: def GetPlayerGUIDs(self) -> List[int]:
with self.__mtxState: with self.__mtxState:
return copy.deepcopy(self.__playerGUIDs)
return copy.deepcopy(self.__currentState.guids)


# IStudentAPI使用的接口 # IStudentAPI使用的接口


@@ -263,7 +263,8 @@ class Logic(ILogic):
return self.__comm.EndAllAction(self.__playerID) return self.__comm.EndAllAction(self.__playerID)


def HaveView(self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int) -> bool: def HaveView(self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int) -> bool:
return AssistFunction.HaveView(viewRange, selfX, selfY, gridX, gridY, self.__currentState.gameMap)
with self.__mtxState:
return AssistFunction.HaveView(viewRange, selfX, selfY, gridX, gridY, self.__currentState.gameMap)


# Logic内部逻辑 # Logic内部逻辑
def __TryConnection(self) -> bool: def __TryConnection(self) -> bool:
@@ -286,15 +287,6 @@ class Logic(ILogic):
if self.__gameState == THUAI6.GameState.GameStart: if self.__gameState == THUAI6.GameState.GameStart:
# 读取玩家的GUID # 读取玩家的GUID
self.__logger.info("Game start!") self.__logger.info("Game start!")
self.__playerGUIDs.clear()
for obj in clientMsg.obj_message:
if obj.WhichOneof("message_of_obj") == "student_message":
self.__playerGUIDs.append(obj.student_message.guid)
for obj in clientMsg.obj_message:
if obj.WhichOneof("message_of_obj") == "tricker_message":
self.__playerGUIDs.append(obj.tricker_message.guid)
self.__currentState.guids = self.__playerGUIDs
self.__bufferState.guids = self.__playerGUIDs


for obj in clientMsg.obj_message: for obj in clientMsg.obj_message:
if obj.WhichOneof("message_of_obj") == "map_message": if obj.WhichOneof("message_of_obj") == "map_message":
@@ -318,15 +310,6 @@ class Logic(ILogic):


elif self.__gameState == THUAI6.GameState.GameRunning: elif self.__gameState == THUAI6.GameState.GameRunning:
# 读取玩家的GUID # 读取玩家的GUID
self.__playerGUIDs.clear()
for obj in clientMsg.obj_message:
if obj.WhichOneof("message_of_obj") == "student_message":
self.__playerGUIDs.append(obj.student_message.guid)
for obj in clientMsg.obj_message:
if obj.WhichOneof("message_of_obj") == "tricker_message":
self.__playerGUIDs.append(obj.tricker_message.guid)
self.__currentState.guids = self.__playerGUIDs
self.__bufferState.guids = self.__playerGUIDs
self.__LoadBuffer(clientMsg) self.__LoadBuffer(clientMsg)
else: else:
self.__logger.error("Unknown GameState!") self.__logger.error("Unknown GameState!")
@@ -467,9 +450,21 @@ class Logic(ILogic):
self.__bufferState.students.clear() self.__bufferState.students.clear()
self.__bufferState.trickers.clear() self.__bufferState.trickers.clear()
self.__bufferState.props.clear() self.__bufferState.props.clear()
self.__bufferState.bullets.clear()
self.__bufferState.bombedBullets.clear()
self.__bufferState.guids.clear()
self.__logger.debug("Buffer cleared!") self.__logger.debug("Buffer cleared!")

for obj in message.obj_message:
if obj.WhichOneof("message_of_obj") == "student_message":
self.__bufferState.guids.append(obj.student_message.guid)
for obj in message.obj_message:
if obj.WhichOneof("message_of_obj") == "tricker_message":
self.__bufferState.guids.append(obj.tricker_message.guid)

self.__bufferState.gameInfo = Proto2THUAI6.Protobuf2THUAI6GameInfo( self.__bufferState.gameInfo = Proto2THUAI6.Protobuf2THUAI6GameInfo(
message.all_message) message.all_message)

self.__LoadBufferSelf(message) self.__LoadBufferSelf(message)
for item in message.obj_message: for item in message.obj_message:
self.__LoadBufferCase(item) self.__LoadBufferCase(item)
@@ -511,9 +506,16 @@ class Logic(ILogic):
formatter = logging.Formatter( formatter = logging.Formatter(
"[%(name)s] [%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", '%H:%M:%S') "[%(name)s] [%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", '%H:%M:%S')
# 确保文件存在 # 确保文件存在
if not os.path.exists(os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs"):
os.makedirs(os.path.dirname(os.path.dirname(
os.path.realpath(__file__))) + "/logs")
# if not os.path.exists(os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs"):
# os.makedirs(os.path.dirname(os.path.dirname(
# os.path.realpath(__file__))) + "/logs")

if platform.system().lower() == "windows":
os.system(
f"mkdir {os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '/logs'}")
else:
os.system(
f"mkdir -p {os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '/logs'}")


fileHandler = logging.FileHandler(os.path.dirname( fileHandler = logging.FileHandler(os.path.dirname(
os.path.dirname(os.path.realpath(__file__))) + "/logs/logic" + str(self.__playerID) + "-log.txt", "w+", encoding="utf-8") os.path.dirname(os.path.realpath(__file__))) + "/logs/logic" + str(self.__playerID) + "-log.txt", "w+", encoding="utf-8")


+ 3
- 3
CAPI/python/run.sh View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash


python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0 -d -o&
python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -o&
python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0&
# python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -o&
# python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2& # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2&
# python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3& # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3&
# python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4&
python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4 -d&

+ 6
- 6
dependency/shell/run.sh View File

@@ -4,10 +4,10 @@ python_dir=/usr/local/PlayerCode/CAPI/python/PyAPI
playback_dir=/usr/local/playback playback_dir=/usr/local/playback


if [ $EXPOSED -eq 1 ]; then if [ $EXPOSED -eq 1 ]; then
nice -10 ./Server --port 8888 --studentCount 4 --trickerCount 1 --resultFileName $playback_dir/result --gameTimeInSecond $TIME --url $URL --token $TOKEN --fileName $playback_dir/video --startLockFile $playback_dir/start.lock > $playback_dir/server.log &
nice -10 ./Server --port 8888 --studentCount 4 --trickerCount 1 --resultFileName $playback_dir/result --gameTimeInSecond $TIME --url $URL --token $TOKEN --fileName $playback_dir/video --startLockFile $playback_dir/start.lock > $playback_dir/server.log 2>&1 &
server_pid=$! server_pid=$!
else else
nice -10 ./Server --port 8888 --studentCount 4 --trickerCount 1 --resultFileName $playback_dir/result --gameTimeInSecond $TIME --notAllowSpectator --url $URL --token $TOKEN --fileName $playback_dir/video --startLockFile $playback_dir/start.lock > $playback_dir/server.log &
nice -10 ./Server --port 8888 --studentCount 4 --trickerCount 1 --resultFileName $playback_dir/result --gameTimeInSecond $TIME --notAllowSpectator --url $URL --token $TOKEN --fileName $playback_dir/video --startLockFile $playback_dir/start.lock > $playback_dir/server.log 2>&1 &
server_pid=$! server_pid=$!
fi fi
sleep 5 sleep 5
@@ -20,9 +20,9 @@ do
j=$((i - 1)) j=$((i - 1))
if [ -f "./player$i.py" ]; then if [ -f "./player$i.py" ]; then
cp -f ./player$i.py $python_dir/AI.py cp -f ./player$i.py $python_dir/AI.py
nice -0 python3 $python_dir/main.py -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log &
nice -0 python3 $python_dir/main.py -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log 2>&1 &
elif [ -f "./capi$i" ]; then elif [ -f "./capi$i" ]; then
nice -0 ./capi$i -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log &
nice -0 ./capi$i -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log 2>&1 &
else else
echo "ERROR. $i is not found." echo "ERROR. $i is not found."
fi fi
@@ -33,9 +33,9 @@ do
j=$((i - 1)) j=$((i - 1))
if [ -f "./player$i.py" ]; then if [ -f "./player$i.py" ]; then
cp -f ./player$i.py $python_dir/AI.py cp -f ./player$i.py $python_dir/AI.py
nice -0 python3 $python_dir/main.py -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log &
nice -0 python3 $python_dir/main.py -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log 2>&1 &
elif [ -f "./capi$i" ]; then elif [ -f "./capi$i" ]; then
nice -0 ./capi$i -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log &
nice -0 ./capi$i -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log 2>&1 &
else else
echo "ERROR. $i is not found." echo "ERROR. $i is not found."
fi fi


+ 11
- 3
docs/CAPI接口(cpp).md View File

@@ -59,13 +59,13 @@
下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。


- `THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY)` :返回某一位置场地种类信息。场地种类详见 structure.h 。 - `THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY)` :返回某一位置场地种类信息。场地种类详见 structure.h 。
- `bool IsDoorOpen(int32_t cellX, int32_t cellY) const`:查询特定位置门是否开启,没有门也返回false
- 以下指令,若查询物品当前在视野内,则返回最新进度;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。
- 以下指令,若查询物品当前在视野内,则返回最新进度/状态;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度/状态;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。
- `int32_t GetChestProgress(int32_t cellX, int32_t cellY) const`:查询特定位置箱子开启进度 - `int32_t GetChestProgress(int32_t cellX, int32_t cellY) const`:查询特定位置箱子开启进度
- `int32_t GetGateProgress(int32_t cellX, int32_t cellY) const`:查询特定位置校门开启进度 - `int32_t GetGateProgress(int32_t cellX, int32_t cellY) const`:查询特定位置校门开启进度
- `int32_t GetClassroomProgress(int32_t cellX, int32_t cellY) const`:查询特定位置教室作业完成进度 - `int32_t GetClassroomProgress(int32_t cellX, int32_t cellY) const`:查询特定位置教室作业完成进度
- `int32_t GetDoorProgress(int32_t cellX, int32_t cellY) const`:查询特定位置门开启状态 - `int32_t GetDoorProgress(int32_t cellX, int32_t cellY) const`:查询特定位置门开启状态
- `THUAI6::HiddenGateState GetHiddenGateState(int32_t cellX, int32_t cellY) const`::查询特定位置隐藏校门状态,没有隐藏校门返回THUAI6::HiddenGateState::Null
- `bool IsDoorOpen(int32_t cellX, int32_t cellY) const`:查询特定位置门是否开启,没有门/不在视野内也返回false
- `THUAI6::HiddenGateState GetHiddenGateState(int32_t cellX, int32_t cellY) const`::查询特定位置隐藏校门状态,没有隐藏校门/不在视野内返回THUAI6::HiddenGateState::Null


#### 其他 #### 其他
- `std::shared_ptr<const THUAI6::GameInfo> GetGameInfo() const`:查询当前游戏状态 - `std::shared_ptr<const THUAI6::GameInfo> GetGameInfo() const`:查询当前游戏状态
@@ -88,6 +88,14 @@
void PrintSelfInfo() const; void PrintSelfInfo() const;
~~~ ~~~


### 部分属性解释 stuctures.h
~~~c++
struct Player
{
std::vector<PropType> props;//大小固定为3,空的位置为NullPropType
}
~~~

## 接口一览 ## 接口一览
~~~c++ ~~~c++
// 指挥本角色进行移动,`timeInMilliseconds` 为移动时间,单位为毫秒;`angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴 // 指挥本角色进行移动,`timeInMilliseconds` 为移动时间,单位为毫秒;`angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴


+ 10
- 4
docs/CAPI接口(python).md View File

@@ -72,14 +72,13 @@
下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。


- `def GetPlaceType(self, cellX: int, cellY: int) -> THUAI6.PlaceType` :返回某一位置场地种类信息。场地种类详见 structure.h 。 - `def GetPlaceType(self, cellX: int, cellY: int) -> THUAI6.PlaceType` :返回某一位置场地种类信息。场地种类详见 structure.h 。
- `def IsDoorOpen(self, cellX: int, cellY: int) -> bool`:查询特定位置门是否开启,没有门也返回false
- 以下指令,若查询物品当前在视野内,则返回最新进度;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。
- 以下指令,若查询物品当前在视野内,则返回最新进度/状态;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度/状态;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。
- `def GetChestProgress(self, cellX: int, cellY: int) -> int`:查询特定位置箱子开启进度 - `def GetChestProgress(self, cellX: int, cellY: int) -> int`:查询特定位置箱子开启进度
- `def GetGateProgress(self, cellX: int, cellY: int) -> int`:查询特定位置校门开启进度 - `def GetGateProgress(self, cellX: int, cellY: int) -> int`:查询特定位置校门开启进度
- `def GetClassroomProgress(self, cellX: int, cellY: int) -> int`:查询特定位置教室作业完成进度 - `def GetClassroomProgress(self, cellX: int, cellY: int) -> int`:查询特定位置教室作业完成进度
- `def GetDoorProgress(self, cellX: int, cellY: int) -> int`:查询特定位置门开启状态 - `def GetDoorProgress(self, cellX: int, cellY: int) -> int`:查询特定位置门开启状态
- `def GetHiddenGateState(self, cellX: int, cellY: int) -> THUAI6.HiddenGateState`::查询特定位置隐藏校门状态,没有隐藏校门返回THUAI6::HiddenGateState::Null
- `def IsDoorOpen(self, cellX: int, cellY: int) -> bool`:查询特定位置门是否开启,没有门/不在视野内也返回false
- `def GetHiddenGateState(self, cellX: int, cellY: int) -> THUAI6.HiddenGateState`::查询特定位置隐藏校门状态,没有隐藏校门/不在视野内返回THUAI6::HiddenGateState::Null


#### 其他 #### 其他


@@ -105,6 +104,13 @@
def PrintSelfInfo(self) -> None: def PrintSelfInfo(self) -> None:
~~~ ~~~


### 部分属性解释 stuctures.h
~~~python
class Player:
def __init__(self, **kwargs) -> None:
self.prop: List[PropType] = []//大小固定为3,空的位置为NullPropType
~~~

## 接口一览 ## 接口一览


~~~python ~~~python


+ 3
- 1
docs/GameRules.md View File

@@ -64,7 +64,7 @@ CellX=\frac{x}{1000},CellY=\frac{y}{1000}
$$ $$


- 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子 - 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子
- 隐藏校门刷新点的区域类型始终为隐藏校门
- 任何格子的区域类型(PlaceType)始终不变,所有隐藏校门刷新点的区域类型均为隐藏校门


### 人物 ### 人物
- 人物直径为800 - 人物直径为800
@@ -360,10 +360,12 @@ $$


### 信息相关 ### 信息相关
- Bgm在没有符合条件的情况下,值为0。 - Bgm在没有符合条件的情况下,值为0。
- 不能给自己发信息


### 技能 ### 技能
- CD冷却计时是在开始使用技能的瞬间开始的 - CD冷却计时是在开始使用技能的瞬间开始的
- Klee的小炸弹有碰撞体积 - Klee的小炸弹有碰撞体积
- 除了切换攻击类型的技能,在不能执行指令的状态下(包括翻窗)均不能使用技能


### 职业 ### 职业
- 学生职业可以重复选择 - 学生职业可以重复选择

+ 10
- 6
docs/QandA.md View File

@@ -22,11 +22,6 @@ Q:卡死在第一帧不动


A:大概率是你的代码死循环了 A:大概率是你的代码死循环了


Q: 怎么开始游戏?

A:
需要确保学生阵营和捣蛋鬼阵营的人数都达到Server.cmd中设定的值。人数不足也可以打开WPF,参考使用文档,修改RunGUIClient.cmd的参数,然后运行RunGUIClient.cmd,这样可以通过WPF运行部分客户端,来达到人数限制。

## C++ ## C++


Q:显示API项目已卸载 Q:显示API项目已卸载
@@ -54,6 +49,15 @@ Q:CAPI编译不通过(第二种)


A:查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 A:查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴


Q:编译好慢啊

A:
1. 尽量不要改其他文件,甚至连点下保存都别点
2. 不要点重新生成,要点生成
3. 开启下图选项
![CompileFaster](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/CompileFaster.png)

## Python ## Python


### grpc版本更新失败 ### grpc版本更新失败
@@ -76,4 +80,4 @@ A:初赛结束会调数值及机制,增加新角色


Q:初赛后会修改什么呢? Q:初赛后会修改什么呢?


A:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息
A:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息;提供不同格式的信息传递;增加职业;优化游戏结束条件;角色毅力值清空不在使捣蛋鬼产生BGM

+ 2
- 2
docs/使用文档.md View File

@@ -14,7 +14,7 @@
- Windows:先查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 - Windows:先查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴
- Linux:首先自行安装`gRPC`,具体方法可以参考官方教程https://grpc.io/docs/languages/cpp/quickstart/。 - Linux:首先自行安装`gRPC`,具体方法可以参考官方教程https://grpc.io/docs/languages/cpp/quickstart/。
- 然后在`CAPI\cpp\API\src\AI.cpp`中编写代码 - 然后在`CAPI\cpp\API\src\AI.cpp`中编写代码
- 选手不应当修改`AI.cpp`中原来有的代码,除了`void AI::play(IStudentAPI& api)`和`void AI::play(ITrickerAPI& api)`
- 选手不应当修改`AI.cpp`中原来有的代码,除了`void AI::play(IStudentAPI& api)`和`void AI::play(ITrickerAPI& api)`,及修改asynchronous的返回值
- 每帧执行一次`AI::play(IStudentAPI& api)`或`AI::play(ITrickerAPI& api)`(除非执行该函数超过一帧50ms),获取的信息都是这一帧的开始的状态 - 每帧执行一次`AI::play(IStudentAPI& api)`或`AI::play(ITrickerAPI& api)`(除非执行该函数超过一帧50ms),获取的信息都是这一帧的开始的状态
- 选手可以在`AI.cpp`内`void AI::play`外新增函数和变量 - 选手可以在`AI.cpp`内`void AI::play`外新增函数和变量
- Windows:然后用Visual Studio打开`CAPI\cpp\CAPI.sln`编译,注意使用Debug模式 - Windows:然后用Visual Studio打开`CAPI\cpp\CAPI.sln`编译,注意使用Debug模式
@@ -26,7 +26,7 @@


- 首先在Python环境下运行`GeneratePythonProto.cmd`,以安装必要的包、并生成对应的grpc python文件 - 首先在Python环境下运行`GeneratePythonProto.cmd`,以安装必要的包、并生成对应的grpc python文件
- 然后在`CAPI\python\PyAPI\AI.py`中编写代码 - 然后在`CAPI\python\PyAPI\AI.py`中编写代码
- 选手不应当修改`AI.py`中原来有的代码,除了`StudentPlay(self, api: IStudentAPI)`和`TrickerPlay(self, api: ITrickerAPI)`
- 选手不应当修改`AI.py`中原来有的代码,除了`StudentPlay(self, api: IStudentAPI)`和`TrickerPlay(self, api: ITrickerAPI)`,及修改asynchronous的返回值
- 每帧执行一次`AI::play(IStudentAPI& api)`或`AI::play(ITrickerAPI& api)`(除非执行该函数超过一帧50ms),获取的信息都是这一帧的开始的状态 - 每帧执行一次`AI::play(IStudentAPI& api)`或`AI::play(ITrickerAPI& api)`(除非执行该函数超过一帧50ms),获取的信息都是这一帧的开始的状态
- 选手可以在`AI.py`内新增函数和变量 - 选手可以在`AI.py`内新增函数和变量
- Windows:最后通过运行`RunPython.cmd`执行比赛代码 - Windows:最后通过运行`RunPython.cmd`执行比赛代码


+ 2
- 2
logic/Client/Client.csproj View File

@@ -5,7 +5,7 @@
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<ApplicationIcon>EESAST.ico</ApplicationIcon>
<ApplicationIcon>eesast_software_trans_enlarged.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>


<ItemGroup> <ItemGroup>
@@ -13,7 +13,7 @@
</ItemGroup> </ItemGroup>


<ItemGroup> <ItemGroup>
<Content Include="EESAST.ico" />
<Content Include="eesast_software_trans_enlarged.ico" />
</ItemGroup> </ItemGroup>


<ItemGroup> <ItemGroup>


+ 2
- 1
logic/Client/MainWindow.xaml View File

@@ -5,7 +5,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Client" xmlns:local="clr-namespace:Client"
mc:Ignorable="d" mc:Ignorable="d"
Title="ClientVI" Height="738" Width="850" KeyDown="KeyBoardControl" Background="White" ResizeMode="CanResizeWithGrip" WindowStyle="None" MouseLeftButtonDown="DragWindow" MouseDoubleClick="Attack" MinHeight="738" MinWidth="1100">
Title="ClientVI" Height="738" Width="850" KeyDown="KeyBoardControl" Background="White" ResizeMode="CanResizeWithGrip" WindowStyle="None" MouseLeftButtonDown="DragWindow" MouseDoubleClick="Attack" MinHeight="738" MinWidth="1100"
SizeChanged="ZoomMap">


<Window.Resources> <Window.Resources>
<ImageBrush x:Key="Logo" ImageSource="Logo.png"/> <ImageBrush x:Key="Logo" ImageSource="Logo.png"/>


+ 48
- 70
logic/Client/MainWindow.xaml.cs View File

@@ -34,8 +34,6 @@ namespace Client
{ {
public MainWindow() public MainWindow()
{ {
unitHeight = unitWidth = unit = 13;
bonusflag = true;
timer = new DispatcherTimer timer = new DispatcherTimer
{ {
Interval = new TimeSpan(50000) // 每50ms刷新一次 Interval = new TimeSpan(50000) // 每50ms刷新一次
@@ -60,6 +58,10 @@ namespace Client
listOfGate = new List<MessageOfGate>(); listOfGate = new List<MessageOfGate>();
listOfHiddenGate = new List<MessageOfHiddenGate>(); listOfHiddenGate = new List<MessageOfHiddenGate>();
WindowStartupLocation = WindowStartupLocation.CenterScreen; WindowStartupLocation = WindowStartupLocation.CenterScreen;
unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50;
unitFontsize = unit / 13;
unitHeight = UpperLayerOfMap.ActualHeight / 50;
unitWidth = UpperLayerOfMap.ActualWidth / 50;
ReactToCommandline(); ReactToCommandline();
} }


@@ -194,6 +196,7 @@ namespace Client
0 => PlayerType.NullPlayerType, 0 => PlayerType.NullPlayerType,
1 => PlayerType.StudentPlayer, 1 => PlayerType.StudentPlayer,
2 => PlayerType.TrickerPlayer, 2 => PlayerType.TrickerPlayer,
_ => PlayerType.NullPlayerType
}; };
playerMsg.PlayerType = playerType; playerMsg.PlayerType = playerType;
if (Convert.ToInt64(comInfo[3]) == 1) if (Convert.ToInt64(comInfo[3]) == 1)
@@ -268,9 +271,9 @@ namespace Client
{ {
TextBox icon = new() TextBox icon = new()
{ {
FontSize = 10,
Width = 20,
Height = 20,
FontSize = 7 * unitFontsize,
Width = unitWidth,
Height = unitHeight,
Text = text, Text = text,
HorizontalAlignment = HorizontalAlignment.Left, HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top, VerticalAlignment = VerticalAlignment.Top,
@@ -282,37 +285,23 @@ namespace Client
UpperLayerOfMap.Children.Add(icon); UpperLayerOfMap.Children.Add(icon);
} }


private void ZoomMap()
{
for (int i = 0; i < 50; i++)
{
for (int j = 0; j < 50; j++)
{
if (mapPatches[i, j] != null && (mapPatches[i, j].Width != UpperLayerOfMap.ActualWidth / 50 || mapPatches[i, j].Height != UpperLayerOfMap.ActualHeight / 50))
{
mapPatches[i, j].Width = UpperLayerOfMap.ActualWidth / 50;
mapPatches[i, j].Height = UpperLayerOfMap.ActualHeight / 50;
mapPatches[i, j].HorizontalAlignment = HorizontalAlignment.Left;
mapPatches[i, j].VerticalAlignment = VerticalAlignment.Top;
mapPatches[i, j].Margin = new Thickness(UpperLayerOfMap.ActualWidth / 50 * j, UpperLayerOfMap.ActualHeight / 50 * i, 0, 0);
}
}
}
}

private void ZoomMapAtFirst()
private void ZoomMap(object sender, SizeChangedEventArgs e)
{ {
unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50;
unitFontsize = unit / 13;
unitHeight = UpperLayerOfMap.ActualHeight / 50;
unitWidth = UpperLayerOfMap.ActualWidth / 50;
for (int i = 0; i < 50; i++) for (int i = 0; i < 50; i++)
{ {
for (int j = 0; j < 50; j++) for (int j = 0; j < 50; j++)
{ {
if (mapPatches[i, j] != null) if (mapPatches[i, j] != null)
{ {
mapPatches[i, j].Width = UpperLayerOfMap.ActualWidth / 50;
mapPatches[i, j].Height = UpperLayerOfMap.ActualHeight / 50;
mapPatches[i, j].Width = unitWidth;
mapPatches[i, j].Height = unitHeight;
mapPatches[i, j].HorizontalAlignment = HorizontalAlignment.Left; mapPatches[i, j].HorizontalAlignment = HorizontalAlignment.Left;
mapPatches[i, j].VerticalAlignment = VerticalAlignment.Top; mapPatches[i, j].VerticalAlignment = VerticalAlignment.Top;
mapPatches[i, j].Margin = new Thickness(UpperLayerOfMap.ActualWidth / 50 * j, UpperLayerOfMap.ActualHeight / 50 * i, 0, 0);
mapPatches[i, j].Margin = new Thickness(unitWidth * j, unitHeight * i, 0, 0);
} }
} }
} }
@@ -330,7 +319,7 @@ namespace Client
Height = unitHeight, Height = unitHeight,
HorizontalAlignment = HorizontalAlignment.Left, HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top, VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(Width * (j), Height * (i), 0, 0)
Margin = new Thickness(unitWidth * j, unitHeight * i, 0, 0)//unitWidth cannot be replaced by Width
}; };
switch (defaultMap[i, j]) switch (defaultMap[i, j])
{ {
@@ -697,7 +686,7 @@ namespace Client
MaxButton.Content = "🗖"; MaxButton.Content = "🗖";
foreach (var obj in listOfHuman) foreach (var obj in listOfHuman)
{ {
if (obj.PlayerId < GameData.numOfStudent && obj.StudentType != StudentType.Robot)
if (!isDataFixed[obj.PlayerId] && obj.PlayerId < GameData.numOfStudent && obj.StudentType != StudentType.Robot)
{ {
IStudentType occupation = (IStudentType)OccupationFactory.FindIOccupation(Transformation.ToStudentType(obj.StudentType)); IStudentType occupation = (IStudentType)OccupationFactory.FindIOccupation(Transformation.ToStudentType(obj.StudentType));
totalLife[obj.PlayerId] = occupation.MaxHp; totalLife[obj.PlayerId] = occupation.MaxHp;
@@ -709,60 +698,52 @@ namespace Client
coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD; coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD;
++i; ++i;
} }
isDataFixed[obj.PlayerId] = true;
} }
} }
foreach (var obj in listOfButcher) foreach (var obj in listOfButcher)
{ {
IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType));
int j = 0;
foreach (var skill in occupation1.ListOfIActiveSkill)
if (!isDataFixed[obj.PlayerId])
{ {
var iActiveSkill = SkillFactory.FindIActiveSkill(skill);
coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD;
++j;
IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType));
int j = 0;
foreach (var skill in occupation1.ListOfIActiveSkill)
{
var iActiveSkill = SkillFactory.FindIActiveSkill(skill);
coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD;
++j;
}
isDataFixed[obj.PlayerId] = true;
} }
} }
if (StatusBarsOfSurvivor != null) if (StatusBarsOfSurvivor != null)
{ {
for (int i = 0; i < GameData.numOfStudent; i++) for (int i = 0; i < GameData.numOfStudent; i++)
{ {
StatusBarsOfSurvivor[i].SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime); StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime);
} }
} }
if (StatusBarsOfHunter != null) if (StatusBarsOfHunter != null)
{ {
StatusBarsOfHunter.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime); StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime);
} }
if (StatusBarsOfCircumstance != null)
StatusBarsOfCircumstance.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
// 完成窗口信息更新 // 完成窗口信息更新
if (StatusBarsOfSurvivor != null)
{
for (int i = 0; i < GameData.numOfStudent; i++)
{
StatusBarsOfSurvivor[i].SetFontSize(12 * unitFontsize);
}
}
if (StatusBarsOfHunter != null)
StatusBarsOfHunter.SetFontSize(12 * unitFontsize);
if (StatusBarsOfCircumstance != null)
StatusBarsOfCircumstance.SetFontSize(12 * unitFontsize);
if (!isClientStocked) if (!isClientStocked)
{ {
unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50;
unitHeight = UpperLayerOfMap.ActualHeight / 50;
unitWidth = UpperLayerOfMap.ActualWidth / 50;
try try
{ {
// if (log != null)
//{
// string temp = "";
// for (int i = 0; i < dataDict[GameObjType.Character].Count; i++)
// {
// temp += Convert.ToString(dataDict[GameObjType.Character][i].MessageOfCharacter.TeamID) + "\n";
// }
// log.Content = temp;
// }
UpperLayerOfMap.Children.Clear(); UpperLayerOfMap.Children.Clear();
// if ((communicator == null || !communicator.Client.IsConnected) && !isPlaybackMode)
//{
// UnderLayerOfMap.Children.Clear();
// throw new Exception("Client is unconnected.");
// }
// else
//{

foreach (var data in listOfAll) foreach (var data in listOfAll)
{ {
StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode); StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode);
@@ -770,7 +751,6 @@ namespace Client
if (!hasDrawed && mapFlag) if (!hasDrawed && mapFlag)
{ {
DrawMap(); DrawMap();
ZoomMapAtFirst();
} }
foreach (var data in listOfHuman) foreach (var data in listOfHuman)
{ {
@@ -791,7 +771,7 @@ namespace Client
icon.Fill = Brushes.Gray; icon.Fill = Brushes.Gray;
TextBox num = new() TextBox num = new()
{ {
FontSize = 7 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 7 * unitFontsize,
Width = 2 * radiusTimes * unitWidth, Width = 2 * radiusTimes * unitWidth,
Height = 2 * radiusTimes * unitHeight, Height = 2 * radiusTimes * unitHeight,
Text = Convert.ToString(data.PlayerId), Text = Convert.ToString(data.PlayerId),
@@ -943,7 +923,7 @@ namespace Client
int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfFixedGenerator); int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfFixedGenerator);
TextBox icon = new() TextBox icon = new()
{ {
FontSize = 8 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 8 * unitFontsize,
Width = unitWidth, Width = unitWidth,
Height = unitHeight, Height = unitHeight,
Text = Convert.ToString(deg), Text = Convert.ToString(deg),
@@ -965,7 +945,7 @@ namespace Client
int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedChest); int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedChest);
TextBox icon = new() TextBox icon = new()
{ {
FontSize = 8 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 8 * unitFontsize,
Width = unitWidth, Width = unitWidth,
Height = unitHeight, Height = unitHeight,
Text = Convert.ToString(deg), Text = Convert.ToString(deg),
@@ -987,7 +967,7 @@ namespace Client
int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedDoorway); int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedDoorway);
TextBox icon = new() TextBox icon = new()
{ {
FontSize = 8 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 8 * unitFontsize,
Width = unitWidth, Width = unitWidth,
Height = unitHeight, Height = unitHeight,
Text = Convert.ToString(deg), Text = Convert.ToString(deg),
@@ -1009,7 +989,7 @@ namespace Client
{ {
TextBox icon = new() TextBox icon = new()
{ {
FontSize = 9 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 9 * unitFontsize,
Width = unitWidth, Width = unitWidth,
Height = unitHeight, Height = unitHeight,
HorizontalAlignment = HorizontalAlignment.Left, HorizontalAlignment = HorizontalAlignment.Left,
@@ -1042,7 +1022,7 @@ namespace Client
isEmergencyOpened = true; isEmergencyOpened = true;
TextBox icon = new() TextBox icon = new()
{ {
FontSize = 9 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 9 * unitFontsize,
Width = unitWidth, Width = unitWidth,
Height = unitHeight, Height = unitHeight,
Text = Convert.ToString("🔓"), Text = Convert.ToString("🔓"),
@@ -1056,8 +1036,6 @@ namespace Client
UpperLayerOfMap.Children.Add(icon); UpperLayerOfMap.Children.Add(icon);
} }
} }
//}
ZoomMap();
} }
catch (Exception exc) catch (Exception exc)
{ {
@@ -1440,7 +1418,6 @@ namespace Client
private MessageOfTricker? butcher = null; private MessageOfTricker? butcher = null;
private bool humanOrButcher;//true for human private bool humanOrButcher;//true for human


private bool bonusflag;
private bool mapFlag = false; private bool mapFlag = false;
private bool hasDrawed = false; private bool hasDrawed = false;
public int[,] defaultMap = new int[,] { public int[,] defaultMap = new int[,] {
@@ -1502,7 +1479,8 @@ namespace Client
bool isSpectatorMode = false; bool isSpectatorMode = false;
bool isEmergencyOpened = false; bool isEmergencyOpened = false;
bool isEmergencyDrawed = false; bool isEmergencyDrawed = false;
bool isDataFixed = false;
bool[] isDataFixed = new bool[5] { false, false, false, false, false };
double unitFontsize = 10;
const double radiusTimes = 1.0 * Preparation.Utility.GameData.characterRadius / Preparation.Utility.GameData.numOfPosGridPerCell; const double radiusTimes = 1.0 * Preparation.Utility.GameData.characterRadius / Preparation.Utility.GameData.numOfPosGridPerCell;
const double bulletRadiusTimes = 1.0 * GameData.bulletRadius / Preparation.Utility.GameData.numOfPosGridPerCell; const double bulletRadiusTimes = 1.0 * GameData.bulletRadius / Preparation.Utility.GameData.numOfPosGridPerCell;
private int[] totalLife = new int[4] { 100, 100, 100, 100 }, totalDeath = new int[4] { 100, 100, 100, 100 }; private int[] totalLife = new int[4] { 100, 100, 100, 100 }, totalDeath = new int[4] { 100, 100, 100, 100 };


BIN
logic/Client/eesast_software_trans.ico View File

Before After

BIN
logic/Client/eesast_software_trans_enlarged.ico View File

Before After

+ 0
- 1
logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs View File

@@ -1,6 +1,5 @@
using Preparation.Interface; using Preparation.Interface;
using Preparation.Utility; using Preparation.Utility;
using System;


namespace GameClass.GameObj namespace GameClass.GameObj
{ {


+ 25
- 8
logic/GameClass/GameObj/Map/Map.cs View File

@@ -3,7 +3,6 @@ using System.Threading;
using Preparation.Interface; using Preparation.Interface;
using Preparation.Utility; using Preparation.Utility;
using System; using System;
using GameClass.GameObj;


namespace GameClass.GameObj namespace GameClass.GameObj
{ {
@@ -13,29 +12,47 @@ namespace GameClass.GameObj
private readonly Dictionary<uint, XY> birthPointList; // 出生点列表 private readonly Dictionary<uint, XY> birthPointList; // 出生点列表
public Dictionary<uint, XY> BirthPointList => birthPointList; public Dictionary<uint, XY> BirthPointList => birthPointList;


private object lockForNum = new();
private readonly object lockForNum = new();
private void WhenStudentNumChange() private void WhenStudentNumChange()
{ {
if (numOfDeceasedStudent + numOfEscapedStudent == GameData.numOfStudent) if (numOfDeceasedStudent + numOfEscapedStudent == GameData.numOfStudent)
{ {
Timer.IsGaming = false; Timer.IsGaming = false;
return;
} }


if (GameData.numOfStudent - NumOfDeceasedStudent - NumOfEscapedStudent == 1)
if (GameData.numOfStudent - numOfDeceasedStudent - numOfEscapedStudent == 1)
{ {
GameObjLockDict[GameObjType.EmergencyExit].EnterReadLock();
GameObjLockDict[GameObjType.Character].EnterReadLock();
try try
{ {
foreach (EmergencyExit emergencyExit in GameObjDict[GameObjType.EmergencyExit])
if (emergencyExit.CanOpen)
foreach (Character player in GameObjDict[GameObjType.Character])
if (player.PlayerState == PlayerStateType.Addicted)
{ {
emergencyExit.IsOpen = true;
Timer.IsGaming = false;
break; break;
} }
} }
finally finally
{ {
GameObjLockDict[GameObjType.EmergencyExit].ExitReadLock();
GameObjLockDict[GameObjType.Character].ExitReadLock();
}
if (Timer.IsGaming)
{
GameObjLockDict[GameObjType.EmergencyExit].EnterWriteLock();
try
{
foreach (EmergencyExit emergencyExit in GameObjDict[GameObjType.EmergencyExit])
if (emergencyExit.CanOpen)
{
emergencyExit.IsOpen = true;
break;
}
}
finally
{
GameObjLockDict[GameObjType.EmergencyExit].ExitWriteLock();
}
} }
} }
} }


+ 0
- 1
logic/GameEngine/CollisionChecker.cs View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics;
using System.Threading; using System.Threading;
using Preparation.Interface; using Preparation.Interface;
using Preparation.Utility; using Preparation.Utility;


+ 0
- 1
logic/Gaming/AttackManager.cs View File

@@ -6,7 +6,6 @@ using Preparation.Utility;
using GameEngine; using GameEngine;
using Preparation.Interface; using Preparation.Interface;
using Timothy.FrameRateTask; using Timothy.FrameRateTask;
using System.Numerics;


namespace Gaming namespace Gaming
{ {


+ 13
- 26
logic/Gaming/SkillManager/SkillManager.cs View File

@@ -17,44 +17,31 @@ namespace Gaming
switch (activeSkillType) switch (activeSkillType)
{ {
case ActiveSkillType.BecomeInvisible: case ActiveSkillType.BecomeInvisible:
BecomeInvisible(character);
break;
return BecomeInvisible(character);
case ActiveSkillType.UseKnife: case ActiveSkillType.UseKnife:
UseKnife(character);
break;
return UseKnife(character);
case ActiveSkillType.Howl: case ActiveSkillType.Howl:
Howl(character);
break;
return Howl(character);
case ActiveSkillType.CanBeginToCharge: case ActiveSkillType.CanBeginToCharge:
CanBeginToCharge(character);
break;
return CanBeginToCharge(character);
case ActiveSkillType.Inspire: case ActiveSkillType.Inspire:
Inspire(character);
break;
return Inspire(character);
case ActiveSkillType.Encourage: case ActiveSkillType.Encourage:
Encourage(character);
break;
return Encourage(character);
case ActiveSkillType.Punish: case ActiveSkillType.Punish:
Punish(character);
break;
return Punish(character);
case ActiveSkillType.JumpyBomb: case ActiveSkillType.JumpyBomb:
JumpyBomb(character);
break;
return JumpyBomb(character);
case ActiveSkillType.WriteAnswers: case ActiveSkillType.WriteAnswers:
WriteAnswers(character);
break;
return WriteAnswers(character);
case ActiveSkillType.SummonGolem: case ActiveSkillType.SummonGolem:
SummonGolem(character);
break;
return SummonGolem(character);
case ActiveSkillType.UseRobot: case ActiveSkillType.UseRobot:
UseRobot(character);
break;
return UseRobot(character);
case ActiveSkillType.Rouse: case ActiveSkillType.Rouse:
Rouse(character);
break;
return Rouse(character);
case ActiveSkillType.ShowTime: case ActiveSkillType.ShowTime:
ShowTime(character);
break;
return ShowTime(character);
default: default:
return false; return false;
} }


+ 60
- 12
logic/Server/PlaybackServer.cs View File

@@ -5,6 +5,7 @@ using System.Threading;
using Timothy.FrameRateTask; using Timothy.FrameRateTask;
using Gaming; using Gaming;
using Grpc.Core; using Grpc.Core;
using System.Collections.Concurrent;


namespace Server namespace Server
{ {
@@ -12,13 +13,31 @@ namespace Server
{ {
protected readonly ArgumentOptions options; protected readonly ArgumentOptions options;
private int[,] teamScore; private int[,] teamScore;
private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new();
private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new();
private object semaDictLock = new(); private object semaDictLock = new();
private MessageToClient? currentGameInfo = new(); private MessageToClient? currentGameInfo = new();
private MessageOfObj currentMapMsg = new();
private uint spectatorMinPlayerID = 2023; private uint spectatorMinPlayerID = 2023;
private List<uint> spectatorList = new List<uint>(); private List<uint> spectatorList = new List<uint>();
public int TeamCount => options.TeamCount; public int TeamCount => options.TeamCount;
private MessageWriter? mwr = null; private MessageWriter? mwr = null;
private object spetatorJoinLock = new();
protected object spectatorLock = new object();
protected bool isSpectatorJoin = false;
protected bool IsSpectatorJoin
{
get
{
lock (spectatorLock)
return isSpectatorJoin;
}

set
{
lock (spectatorLock)
isSpectatorJoin = value;
}
}
private bool IsGaming { get; set; } private bool IsGaming { get; set; }
private int[] finalScore; private int[] finalScore;
public int[] FinalScore public int[] FinalScore
@@ -38,18 +57,20 @@ namespace Server


public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context) public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context)
{ {
if (request.PlayerId >= spectatorMinPlayerID)
if (request.PlayerId >= spectatorMinPlayerID && options.NotAllowSpectator == false)
{ {
// 观战模式 // 观战模式
uint tp = (uint)request.PlayerId;
if (!spectatorList.Contains(tp))
lock (spetatorJoinLock) // 具体原因见另一个上锁的地方
{ {
spectatorList.Add(tp);
Console.WriteLine("A new spectator comes to watch this game.");
var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1));
lock (semaDictLock)
if (semaDict.TryAdd(request.PlayerId, (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1))))
{
Console.WriteLine("A new spectator comes to watch this game.");
IsSpectatorJoin = true;
}
else
{ {
semaDict.Add(request.PlayerId, temp);
Console.WriteLine($"Duplicated Spectator ID {request.PlayerId}");
return;
} }
} }
do do
@@ -63,15 +84,32 @@ namespace Server
//Console.WriteLine("Send!"); //Console.WriteLine("Send!");
} }
} }
catch (InvalidOperationException)
{
if (semaDict.TryRemove(request.PlayerId, out var semas))
{
try
{
semas.Item1.Release();
semas.Item2.Release();
}
catch { }
Console.WriteLine($"The spectator {request.PlayerId} exited");
}
}
catch (Exception) catch (Exception)
{ {
//Console.WriteLine(ex);
// Console.WriteLine(ex);
} }
finally finally
{ {
semaDict[request.PlayerId].Item2.Release();
try
{
semaDict[request.PlayerId].Item2.Release();
}
catch { }
} }
} while (IsGaming == true);
} while (IsGaming);
return; return;
} }
} }
@@ -79,6 +117,16 @@ namespace Server
public void ReportGame(MessageToClient? msg) public void ReportGame(MessageToClient? msg)
{ {
currentGameInfo = msg; currentGameInfo = msg;
if (currentGameInfo != null && currentGameInfo.GameState == GameState.GameStart)
{
currentMapMsg = currentGameInfo.ObjMessage[0];
}

if (currentGameInfo != null && IsSpectatorJoin)
{
currentGameInfo.ObjMessage.Add(currentMapMsg);
IsSpectatorJoin = false;
}


foreach (var kvp in semaDict) foreach (var kvp in semaDict)
{ {


+ 0
- 1
logic/Server/RpcServices.cs View File

@@ -1,7 +1,6 @@
using Grpc.Core; using Grpc.Core;
using Protobuf; using Protobuf;
using System.Threading; using System.Threading;
using Timothy.FrameRateTask;
using System; using System;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using Gaming; using Gaming;


+ 1
- 5
logic/cmd/PlaybackServer.cmd View File

@@ -1,6 +1,2 @@
@echo off @echo off
::start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName test --playback true

ping -n 2 127.0.0.1 > NUL

start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2030
start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --port 8888 --fileName .\ladder1.thuaipb --playback --playbackSpeed 4.0

BIN
resource/CompileFaster.png View File

Before After
Width: 1182  |  Height: 819  |  Size: 61 kB

BIN
resource/eesast_software.png View File

Before After
Width: 2048  |  Height: 2048  |  Size: 52 kB

BIN
resource/eesast_software_trans.ico View File

Before After

BIN
resource/eesast_software_trans.png View File

Before After
Width: 2048  |  Height: 2048  |  Size: 56 kB

BIN
resource/eesast_software_trans_enlarged.ico View File

Before After

Loading…
Cancel
Save