diff --git a/.github/workflows/debug.yml.disabled b/.github/workflows/debug.yml.disabled new file mode 100644 index 00000000..4db1947b --- /dev/null +++ b/.github/workflows/debug.yml.disabled @@ -0,0 +1,13 @@ +name: CI +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..7c978b31 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,59 @@ +name: deploy +on: + push: + branches: [dev, main, master] + pull_request: + branches: [dev, main, master] + +env: + version: 1.1.0.0 + +jobs: + deploy-to-tencent-cos: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Setup dotnet-script + run: dotnet tool install --global dotnet-script + - name: Pre-Process + run: dotnet script .github/preProcess/MauiEnvConfig.csx + - name: Install Workloads + run: dotnet workload install maui-windows + - name: Create Folders need + run: | + mkdir D:\a\installer + mkdir D:\a\publish + - name: Copy THUAI7 + run: Copy-Item -recurse D:\a\THUAI7\THUAI7\ D:\a\mirror\ + - name: Test + run: tree D:\a\mirror + - name: Remove directories not needed + run: | + Remove-Item -recurse -force D:\a\mirror\.git + Remove-Item -recurse D:\a\mirror\.github + Remove-Item -recurse D:\a\mirror\installer + Remove-Item -recurse D:\a\mirror\interface + Remove-Item -recurse D:\a\mirror\logic + - name: Build Server + run: | + mkdir D:\a\mirror\logic + dotnet build "./logic/Server/Server.csproj" -o "D:\a\mirror\logic\Server" -p:WindowsAppSDKSelfContained=true -c Release + - name: Build Client + run: dotnet publish "./logic/Client/Client.csproj" -o "D:\a\mirror\logic\Client" -f net8.0-windows10.0.19041.0 -c Release -p:RuntimeIdentifierOverride=win10-x64 -p:WindowsPackageType=None -p:WindowsAppSDKSelfContained=true + - name: Deploy to bucket + run: dotnet run --project "./dependency/deploy/deploy.csproj" ${{ secrets.INSTALLER_COS_SECRET_ID }} ${{ secrets.INSTALLER_COS_SECRET_KEY }} + - name: Get installer package(No Key contained for safety) + run: | + $version=Get-ChildItem -Path D:\a\publish | ForEach-Object { $_.name } + [Environment]::SetEnvironmentVariable("version", $version, "Machine") + dotnet publish "./installer/installer.csproj" -o "D:\a\installer" -f net8.0-windows10.0.19041.0 -c Release -p:RuntimeIdentifierOverride=win10-x64 -p:WindowsPackageType=None -p:WindowsAppSDKSelfContained=true + ./dependency/7z/7za.exe a -r D:\a\publish\Installer_v${version}.zip D:\a\installer\* + - name: Upload installer package + uses: actions/upload-artifact@v4 + with: + name: Installer_v${{ env.version }}.zip + path: D:\a\publish\Installer_v${{ env.version }}.zip diff --git a/CAPI/cpp/API/src/API.cpp b/CAPI/cpp/API/src/API.cpp index 69bf5e3b..0059658d 100755 --- a/CAPI/cpp/API/src/API.cpp +++ b/CAPI/cpp/API/src/API.cpp @@ -290,7 +290,7 @@ std::future ShipAPI::Construct(THUAI7::ConstructionType constructionType) bool ShipAPI::HaveView(int32_t targetX, int32_t targetY) const { auto selfInfo = GetSelfInfo(); - return logic.HaveView(targetX, targetY, selfInfo->x, selfInfo->y, selfInfo->viewRange); + return logic.HaveView(selfInfo->x, selfInfo->y, targetX, targetY, selfInfo->viewRange); } // Team独有 diff --git a/CAPI/cpp/API/src/DebugAPI.cpp b/CAPI/cpp/API/src/DebugAPI.cpp index 189d7b02..c2641e32 100755 --- a/CAPI/cpp/API/src/DebugAPI.cpp +++ b/CAPI/cpp/API/src/DebugAPI.cpp @@ -239,7 +239,7 @@ std::shared_ptr ShipDebugAPI::GetSelfInfo() const bool ShipDebugAPI::HaveView(int32_t targetX, int32_t targetY) const { auto selfInfo = GetSelfInfo(); - return logic.HaveView(targetX, targetY, selfInfo->x, selfInfo->y, selfInfo->viewRange); + return logic.HaveView(selfInfo->x, selfInfo->y, targetX, targetY, selfInfo->viewRange); } int32_t ShipDebugAPI::GetEnergy() const diff --git a/CAPI/python/PyAPI/API.py b/CAPI/python/PyAPI/API.py index 3b336b58..2a2a2375 100644 --- a/CAPI/python/PyAPI/API.py +++ b/CAPI/python/PyAPI/API.py @@ -10,8 +10,8 @@ def __init__(self, logic: ILogic) -> None: self.__logic = logic self.__pool = ThreadPoolExecutor(20) - def Move(self, timeInMilliseconds: int, angle: float) -> Future[bool]: - return self.__pool.submit(self.__logic.Move, timeInMilliseconds, angle) + def Move(self, timeInMilliseconds: int, angleInRadian: float) -> Future[bool]: + return self.__pool.submit(self.__logic.Move, timeInMilliseconds, angleInRadian) def MoveRight(self, timeInMilliseconds: int) -> Future[bool]: return self.Move(timeInMilliseconds, pi * 0.5) @@ -25,8 +25,8 @@ def MoveUp(self, timeInMilliseconds: int) -> Future[bool]: def MoveDown(self, timeInMilliseconds: int) -> Future[bool]: return self.Move(timeInMilliseconds, 0) - def Attack(self, angle: float) -> Future[bool]: - return self.__pool.submit(self.__logic.Attack, angle) + def Attack(self, angleInRadian: float) -> Future[bool]: + return self.__pool.submit(self.__logic.Attack, angleInRadian) def Recover(self, recover: int) -> Future[bool]: return self.__pool.submit(self.__logic.Recover, recover) @@ -76,7 +76,7 @@ def GetFullMap(self) -> List[List[THUAI7.PlaceType]]: def GetPlaceType(self, cellX: int, cellY: int) -> THUAI7.PlaceType: return self.__logic.GetPlaceType(cellX, cellY) - def GetConstructionState(self, cellX: int, cellY: int) -> tuple: + def GetConstructionState(self, cellX: int, cellY: int) -> Tuple[int, int]: return self.__logic.GetConstructionState(cellX, cellY) def GetWormholeHp(self, cellX: int, cellY: int) -> int: @@ -112,7 +112,7 @@ def HaveView(self, gridX: int, gridY: int) -> bool: self.GetSelfInfo().viewRange, ) - def Print(self, cont: str) -> None: + def Print(self, string: str) -> None: pass def PrintShip(self) -> None: @@ -184,7 +184,7 @@ def GetFullMap(self) -> List[List[THUAI7.PlaceType]]: def GetPlaceType(self, cellX: int, cellY: int) -> THUAI7.PlaceType: return self.__logic.GetPlaceType(cellX, cellY) - def GetConstructionState(self, cellX: int, cellY: int) -> tuple: + def GetConstructionState(self, cellX: int, cellY: int) -> Tuple[int, int]: return self.__logic.GetConstructionState(cellX, cellY) def GetWormholeHp(self, cellX: int, cellY: int) -> int: diff --git a/CAPI/python/PyAPI/DebugAPI.py b/CAPI/python/PyAPI/DebugAPI.py index 0cade2ff..820f0daf 100644 --- a/CAPI/python/PyAPI/DebugAPI.py +++ b/CAPI/python/PyAPI/DebugAPI.py @@ -199,7 +199,7 @@ def GetFullMap(self) -> List[List[THUAI7.PlaceType]]: def GetPlaceType(self, cellX: int, cellY: int) -> THUAI7.PlaceType: return self.__logic.GetPlaceType(cellX, cellY) - def GetConstructionState(self, cellX: int, cellY: int) -> tuple: + def GetConstructionState(self, cellX: int, cellY: int) -> Tuple[int, int]: return self.__logic.GetConstructionState(cellX, cellY) def GetWormholeHp(self, cellX: int, cellY: int) -> int: @@ -235,8 +235,8 @@ def HaveView(self, gridX: int, gridY: int) -> bool: self.GetSelfInfo().viewRange, ) - def Print(self, cont: str) -> None: - self.__logger.info(cont) + def Print(self, string: str) -> None: + self.__logger.info(string) def PrintShip(self) -> None: for ship in self.__logic.GetShips(): @@ -440,7 +440,7 @@ def GetFullMap(self) -> List[List[THUAI7.PlaceType]]: def GetPlaceType(self, cellX: int, cellY: int) -> THUAI7.PlaceType: return self.__logic.GetPlaceType(cellX, cellY) - def GetConstructionState(self, cellX: int, cellY: int) -> tuple: + def GetConstructionState(self, cellX: int, cellY: int) -> Tuple[int, int]: return self.__logic.GetConstructionState(cellX, cellY) def GetWormholeHp(self, cellX: int, cellY: int) -> int: diff --git a/CAPI/python/PyAPI/Interface.py b/CAPI/python/PyAPI/Interface.py index 884fef3e..ca381a70 100644 --- a/CAPI/python/PyAPI/Interface.py +++ b/CAPI/python/PyAPI/Interface.py @@ -75,7 +75,7 @@ def HaveMessage(self) -> bool: pass @abstractmethod - def GetMessage(self) -> Tuple[int, str]: + def GetMessage(self) -> Tuple[int, Union[str, bytes]]: pass @abstractmethod @@ -134,181 +134,353 @@ def BuildShip(self, shipType: THUAI7.ShipType, birthIndex: int) -> bool: class IAPI(metaclass=ABCMeta): - """ - 选手可执行的操作,应当保证所有函数的返回值都应当为 `asyncio.Future`,例如下面的移动函数:\n - 指挥本角色进行移动: - - `timeInMilliseconds` 为移动时间,单位为毫秒 - - `angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴\n - 发送信息、接受信息,注意收消息时无消息则返回 `nullopt` - """ - @abstractmethod def SendMessage(self, toPlayerID: int, message: Union[str, bytes]) -> Future[bool]: + """发送消息 + + :param toPlayerID: 接收方队内编号, 舰船为1~4, 基地为0 + :param message: 待发送消息, 分为 `str` 型和 `bytes` 型 + :return: 发送是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def HaveMessage(self) -> bool: + """检查是否拥有待接收消息 + + :return: 是否拥有待接收消息 + """ pass @abstractmethod - def GetMessage(self) -> Tuple[int, str]: + def GetMessage(self) -> Tuple[int, Union[str, bytes]]: + """接收消息队列的第一个消息 + + :return: 消息发送方的队内编号与消息, 如无消息发送方编号为-1 + """ pass - # 获取游戏目前所进行的帧数 @abstractmethod def GetFrameCount(self) -> int: - "获取游戏目前所进行的帧数" + """获取当前帧率 + + :raise: `NotImplementedError` + """ pass @abstractmethod def Wait(self) -> Future[bool]: - "等待下一帧" + """等待一帧 + - 在 `Setting.Asynchronous() == True` 下 + + :return: 等待是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def EndAllAction(self) -> Future[bool]: + """发出停止一切行动指令 + + :return: 是否进入无行动状态, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def GetShips(self) -> List[THUAI7.Ship]: + """获取本队所有舰船 + + :return: 本队所有舰船, 详见 `THUAI7.Ship` 定义 + """ pass @abstractmethod def GetEnemyShips(self) -> List[THUAI7.Ship]: + """获取敌方舰船 + - 需要敌方舰船处于视野内, 对于 `TeamAPI` 指所有舰船视野 + + :return: 视野内敌方所有舰船, 详见 `THUAI7.Ship` 定义 + """ pass @abstractmethod def GetBullets(self) -> List[THUAI7.Bullet]: + """获取敌方子弹 + - 需要子弹处于视野内 + + :return: 视野内敌方所有子弹, 详见 `THUAI7.Bullet` 定义 + :raise: `NotImplementedError` 对于 `TeamAPI` + """ pass @abstractmethod def GetFullMap(self) -> List[List[THUAI7.PlaceType]]: + """获取地图 + + :return: `THUAI7.PlaceType` 的二维数组 + """ pass @abstractmethod def GetGameInfo(self) -> THUAI7.GameInfo: + """获取当前游戏信息 + + :return: 当前游戏信息, 详见 `THUAI7.GameInfo` 定义 + """ pass @abstractmethod def GetPlaceType(self, cellX: int, cellY: int) -> THUAI7.PlaceType: + """获取区域类型 + + :param cellX: X坐标, 单位Cell + :param cellY: Y坐标, 单位Cell + :return: 该坐标的区域类型 + """ pass @abstractmethod - def GetConstructionState(self, cellX: int, cellY: int) -> tuple: + def GetConstructionState(self, cellX: int, cellY: int) -> Tuple[int, int]: + """获取当前建筑状态 + + :param cellX: X坐标, 单位Cell + :param cellY: Y坐标, 单位Cell + :return: 该建筑当前的所属队伍编号与血量 + """ pass @abstractmethod def GetWormholeHp(self, cellX: int, cellY: int) -> int: + """获取当前虫洞状态 + + :param cellX: X坐标, 单位Cell + :param cellY: Y坐标, 单位Cell + :return: 当前虫洞血量 + """ pass @abstractmethod def GetResourceState(self, cellX: int, cellY: int) -> int: + """获取当前资源状态 + + :param cellX: X坐标, 单位Cell + :param cellY: Y坐标, 单位Cell + :return: 当前资源剩余可开采量 + """ pass @abstractmethod def GetHomeHp(self) -> int: + """获取当前大本营血量 + + :return: 当前大本营血量 + """ pass @abstractmethod def GetEnergy(self) -> int: + """获取当前经济 + + :return: 当前经济 + """ pass @abstractmethod def GetScore(self) -> int: + """获取当前得分 + + :return: 当前得分 + """ pass @abstractmethod def GetPlayerGUIDs(self) -> List[int]: + """获取本队所有舰船GUID + + :return: 本队所有舰船GUID + """ pass @abstractmethod def Print(self, string: str) -> None: + """ + (DEBUG)打印字符串 + + :param string: 待打印字符串 + """ pass @abstractmethod def PrintShip(self) -> None: + """ + (DEBUG)打印所有舰船 + """ pass @abstractmethod def PrintTeam(self) -> None: + """ + (DEBUG)打印队伍信息 + """ pass @abstractmethod def PrintSelfInfo(self) -> None: - pass - - @abstractmethod - def GetSelfInfo(self) -> Union[THUAI7.Ship, THUAI7.Team]: + """ + (DEBUG)打印自身信息, `ShipDebugAPI` 打印舰船信息, `TeamDebugAPI` 打印队伍信息 + """ pass class IShipAPI(IAPI, metaclass=ABCMeta): @abstractmethod def Move(self, timeInMilliseconds: int, angleInRadian: float) -> Future[bool]: + """发出移动指令 + + :param timeInMilliseconds: 期望移动的毫秒数 + :param angleInRadian: 期望移动的弧度数, 向下为x轴正方向, 向右为y轴正方向 + :return: 移动是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def MoveRight(self, timeInMilliseconds: int) -> Future[bool]: + """发出向右移动指令 + + :param timeInMilliseconds: 期望移动的毫秒数 + :return: 移动是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def MoveUp(self, timeInMilliseconds: int) -> Future[bool]: + """发出向上移动指令 + + :param timeInMilliseconds: 期望移动的毫秒数 + :return: 移动是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def MoveLeft(self, timeInMilliseconds: int) -> Future[bool]: + """发出向左移动指令 + + :param timeInMilliseconds: 期望移动的毫秒数 + :return: 移动是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def MoveDown(self, timeInMilliseconds: int) -> Future[bool]: + """发出向下移动指令 + + :param timeInMilliseconds: 期望移动的毫秒数 + :return: 移动是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def Attack(self, angleInRadian: float) -> Future[bool]: + """发出攻击指令 + + :param angleInRadian: 期望攻击的弧度值, 向下为x轴正方向, 向右为y轴正方向 + :return: 攻击是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def Recover(self, recover: int) -> Future[bool]: + """发出回复指令 + - 需要接近可用的 `Home` 或 `Community`(`Construction`) + + :param recover: 期望回复生命值 + :return: 回复是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def Produce(self) -> Future[bool]: + """发出生产指令 + - 需要接近未采集完的 `Resource` + + :return: 进入生产状态是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def Rebuild(self, constructionType: THUAI7.ConstructionType) -> Future[bool]: + """发出重建指令 + - 需要接近待重建 `Construction` + + :param constructionType: 建筑类型 + :return: 进入建造状态是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def Construct(self, constructionType: THUAI7.ConstructionType) -> Future[bool]: + """发出建造指令 + - 需要接近待建 `Construction` + + :param constructionType: 建筑类型 + :return: 进入建造状态是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def GetSelfInfo(self) -> THUAI7.Ship: + """获取本舰船信息 + + :return: 舰船信息, 详见 `THUAI7.Ship` 定义 + """ pass @abstractmethod def HaveView(self, gridX: int, gridY: int) -> bool: + """检测是否拥有视野 + + :param gridX: 待检测X坐标, 单位Grid + :param gridY: 待检测Y坐标, 单位Grid + :return: 是否拥有视野 + """ pass class ITeamAPI(IAPI, metaclass=ABCMeta): @abstractmethod def GetSelfInfo(self) -> THUAI7.Team: + """获取本队伍信息 + + :return: 队伍信息, 详见 `THUAI7.Team` 定义 + """ pass @abstractmethod - def InstallModule( - self, playerID: int, moduleType: THUAI7.ModuleType - ) -> Future[bool]: + def InstallModule(self, playerID: int, moduleType: THUAI7.ModuleType) -> Future[bool]: + """安装模块 + + :param playerID: 待安装模块的舰船编号 + :param moduleType: 模块类型 + :return: 安装是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def Recycle(self, playerID: int) -> Future[bool]: + """回收舰船 + + :param playerID: 待回收舰船编号 + :return: 回收是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass @abstractmethod def BuildShip(self, shipType: THUAI7.ShipType, birthIndex: int) -> Future[bool]: + """建造舰船 + + :param shipType: 舰船类型 + :param birthIndex: 出生点 (可用的 `Home` 与 `Community`(`Construction`)) 编号 + :return: 建造是否成功, 通过 `.result()` 方法等待获取 `bool` + """ pass diff --git a/CAPI/python/PyAPI/logic.py b/CAPI/python/PyAPI/logic.py index 707bbfd1..4fbb56ad 100644 --- a/CAPI/python/PyAPI/logic.py +++ b/CAPI/python/PyAPI/logic.py @@ -155,7 +155,7 @@ def GetConstructionState(self, cellX: int, cellY: int) -> tuple: ) else: self.__logger.warning("GetConstructionState: Out of range") - return (-1,-1) + return (-1, -1) def GetWormholeHp(self, cellX: int, cellY: int) -> int: with self.__mtxState: diff --git a/CAPI/python/PyAPI/structures.py b/CAPI/python/PyAPI/structures.py index f65e9512..7e261c05 100644 --- a/CAPI/python/PyAPI/structures.py +++ b/CAPI/python/PyAPI/structures.py @@ -162,6 +162,27 @@ class NewsType(Enum): class Ship: + """ + :attr x: X坐标, 单位Grid + :attr y: Y坐标, 单位Grid + :attr speed: 速度, 单位Grid/s + :attr hp: 生命值 + :attr armor: 装甲值 + :attr shield: 护盾值 + :attr playerID: 舰船编号, 1~4 + :attr teamID: 所属队伍编号 + :attr guid: GUID + :attr shipState: 行为状态 + :attr shipType: 舰船类型 + :attr viewRange: 视距 + :attr producerType: 采集器类型 + :attr constructorType: 建造器类型 + :attr armorType: 装甲类型 + :attr shieldType: 护盾类型 + :attr weaponType: 武器类型 + :attr facingDirection: 面朝方向的弧度数, 向下为x轴正方向, 向右为y轴正方向 + """ + def __init__(self): self.x: int = 0 self.y: int = 0 @@ -184,6 +205,13 @@ def __init__(self): class Team: + """ + :attr playerID: 队内编号, 应当为0 + :attr teamID: 所属队伍编号 + :attr score: 得分 + :attr energy: 经济 + """ + def __init__(self): self.playerID: int = 0 self.teamID: int = 0 @@ -201,6 +229,19 @@ def __init__(self): class Bullet: + """ + :attr x: X坐标, 单位Grid + :attr y: Y坐标, 单位Grid + :attr facingDirection: 运动方向的弧度数, 向下为x轴正方向, 向右为y轴正方向 + :attr guid: GUID + :attr teamID: 所属队伍编号 + :attr bulletType: 子弹类型 + :attr damage: 伤害 + :attr attackRange: 射程 + :attr bombRange: 爆炸半径 + :attr speed: 运动速度, 单位Grid/s + """ + def __init__(self): self.x: int = 0 self.y: int = 0 @@ -211,7 +252,6 @@ def __init__(self): self.damage: int = 0 self.attackRange: int = 0 self.bombRange: int = 0 - self.explodeRange: float = 0.0 self.speed: int = 0 @@ -226,6 +266,16 @@ def __init__(self): class GameInfo: + """ + :attr gameTime: 当前游戏时间 + :attr redScore: 红队当前分数 + :attr redEnergy: 红队当前经济 + :attr redHomeHp: 红队当前基地血量 + :attr blueScore: 蓝队当前分数 + :attr blueEnergy: 蓝队当前经济 + :attr blueHomeHp: 蓝队当前基地血量 + """ + def __init__(self): self.gameTime: int = 0 self.redScore: int = 0 diff --git a/dependency/7z/7za.dll b/dependency/7z/7za.dll new file mode 100644 index 00000000..b19a1304 Binary files /dev/null and b/dependency/7z/7za.dll differ diff --git a/dependency/7z/7za.exe b/dependency/7z/7za.exe new file mode 100644 index 00000000..383d8e30 Binary files /dev/null and b/dependency/7z/7za.exe differ diff --git a/dependency/deploy/BaseViewModel.cs b/dependency/deploy/BaseViewModel.cs new file mode 100644 index 00000000..d8d0256f --- /dev/null +++ b/dependency/deploy/BaseViewModel.cs @@ -0,0 +1,117 @@ +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Globalization; +using System.Windows.Input; +using System.Runtime.CompilerServices; + +namespace installer.ViewModel +{ + public abstract class NotificationObject : INotifyPropertyChanged + { + public event PropertyChangedEventHandler? PropertyChanged; + /// + ///announce notification + /// + ///property name + public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + /// + ///BaseCommand + /// + public class BaseCommand : ICommand + { + private Func? _canExecute; + private Action _execute; + + public BaseCommand(Func? canExecute, Action execute) + { + _canExecute = canExecute; + _execute = execute; + } + + public BaseCommand(Action execute) : + this(null, execute) + { + } + + + public event EventHandler? CanExecuteChanged + { + add + { + if (_canExecute != null) + { + //CommandManager.RequerySuggested += value; + } + } + remove + { + if (_canExecute != null) + { + //CommandManager.RequerySuggested -= value; + } + } + } + + public bool CanExecute(object? parameter) + { + return _canExecute == null ? true : _canExecute(parameter); + } + + public void Execute(object? parameter) + { + if (_execute != null && CanExecute(parameter)) + { + _execute(parameter); + } + } + } + + public class RadioConverter + { + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value == null || parameter == null) + { + return false; + } + string checkvalue = value.ToString() ?? ""; + string targetvalue = parameter.ToString() ?? ""; + bool r = checkvalue.Equals(targetvalue); + return r; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is null || parameter is null) + { + return null; + } + + if ((bool)value) + { + return parameter.ToString(); + } + return null; + } + } + + public abstract class BaseViewModel : NotificationObject + { + private const string constBackgroundColor = "White"; + public string ConstBackgroundColor { get => constBackgroundColor; } + + private const string constFontSize = "18"; + public string ConstFontSize { get => constFontSize; } + + private const string constTextColor = "Blue"; + public string ConstTextColor { get => constTextColor; } + } +} diff --git a/dependency/deploy/Data/FakeData.cs b/dependency/deploy/Data/FakeData.cs new file mode 100644 index 00000000..e9070a6f --- /dev/null +++ b/dependency/deploy/Data/FakeData.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace installer.Data +{ + public enum DevicePlatform + { + WinUI + } + public static class DeviceInfo + { + public static DevicePlatform Platform { get; set; } = DevicePlatform.WinUI; + } +} diff --git a/dependency/deploy/MauiProgram.cs b/dependency/deploy/MauiProgram.cs new file mode 100644 index 00000000..ba2399d2 --- /dev/null +++ b/dependency/deploy/MauiProgram.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace installer +{ + public static class MauiProgram + { + public static bool RefreshLogs_WhileDebug = false; + public static bool ErrorTrigger_WhileDebug = true; + public static string SecretID = "***"; + public static string SecretKey = "***"; + } +} diff --git a/dependency/deploy/Program.cs b/dependency/deploy/Program.cs new file mode 100644 index 00000000..7531360e --- /dev/null +++ b/dependency/deploy/Program.cs @@ -0,0 +1,72 @@ +using installer.Data; +using installer.Model; +using installer.Services; +using System.Collections.Concurrent; +using System.Diagnostics; + +Logger Log = LoggerProvider.FromConsole(); + +// 全权读写 +Tencent_Cos Cloud = new Tencent_Cos("1319625962", "ap-beijing", "thuai7", Log); +Cloud.UpdateSecret(args[0], args[1]); + +Downloader d = new Downloader(); +d.Cloud.UpdateSecret(args[0], args[1]); +d.Data.Config.InstallPath = @"D:\a\mirror\"; + +d.Log.Partner.Add(Log); +// 每次更新需要更新默认值 +d.CurrentVersion = new TVersion(); +File.Create(Path.Combine("D:\\a\\publish", d.CurrentVersion.InstallerVersion.ToString())); + +if (d.CheckUpdate()) +{ + foreach (var r in d.Data.MD5Update) + { + Log.LogInfo($"{r.state}, {r.name}"); + } + + d.Data.SaveMD5Data(); + List l = new List(); + foreach (var r in d.Data.MD5Update) + { + var n = r.name.Replace('\\', '/'); + n = n.TrimStart('.').TrimStart('/'); + if (r.state == System.Data.DataRowState.Added || r.state == System.Data.DataRowState.Modified) + { + l.Add(Cloud.UploadFileAsync(Path.Combine(d.Data.Config.InstallPath, r.name), n)); + } + else if (r.state == System.Data.DataRowState.Deleted) + { + l.Add(Cloud.DeleteFileAsync(n)); + } + } + Task.WaitAll(l.ToArray()); +} +else +{ + Log.LogInfo("Nothing to update"); +} + +d.Data.SaveMD5Data(); +Cloud.UploadFile(d.Data.MD5DataPath, "hash.json"); + +Cloud.UploadFile(Path.Combine(d.Data.Config.InstallPath, "CAPI", "cpp", "API", "src", "AI.cpp"), + $"Templates/t.{d.CurrentVersion.TemplateVersion}.cpp"); +Cloud.UploadFile(Path.Combine(d.Data.Config.InstallPath, "CAPI", "python", "PyAPI", "AI.py"), + $"Templates/t.{d.CurrentVersion.TemplateVersion}.py"); +Log.LogInfo("User code uploaded."); + +var list = (from i in d.Data.MD5Data + select i.Key.Replace(Path.DirectorySeparatorChar, '/').TrimStart('.').TrimStart('/')).ToArray(); +Log.LogInfo(list[0]); +using (FileStream s = new FileStream(Path.Combine(d.Data.Config.InstallPath, "compress.csv"), FileMode.Create, FileAccess.Write)) +using (StreamWriter w = new StreamWriter(s)) +{ + foreach (var item in list) + { + w.WriteLine("https://thuai7-1319625962.cos.ap-beijing.myqcloud.com/" + item); + } +} +Cloud.UploadFile(Path.Combine(d.Data.Config.InstallPath, "compress.csv"), "compress.csv"); +Log.LogInfo("Compress csv generated."); diff --git a/dependency/deploy/Services/OtherService.cs b/dependency/deploy/Services/OtherService.cs new file mode 100644 index 00000000..94e080d5 --- /dev/null +++ b/dependency/deploy/Services/OtherService.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace installer.Services +{ + public static class Application + { + public static App? Current; + } + + public class App + { + public void Quit() + { + + } + } +} diff --git a/dependency/deploy/deploy.csproj b/dependency/deploy/deploy.csproj new file mode 100644 index 00000000..6dbb2996 --- /dev/null +++ b/dependency/deploy/deploy.csproj @@ -0,0 +1,31 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dependency/deploy/deploy.sln b/dependency/deploy/deploy.sln new file mode 100644 index 00000000..d40fc769 --- /dev/null +++ b/dependency/deploy/deploy.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34309.116 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deploy", "Deploy.csproj", "{1E0F079D-EECF-4DC8-A783-A86F25A2397E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1E0F079D-EECF-4DC8-A783-A86F25A2397E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E0F079D-EECF-4DC8-A783-A86F25A2397E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E0F079D-EECF-4DC8-A783-A86F25A2397E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E0F079D-EECF-4DC8-A783-A86F25A2397E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7B9F1497-D14B-47E1-AEEF-A4254398185F} + EndGlobalSection +EndGlobal diff --git a/installer/Data/MD5FileData.cs b/installer/Data/MD5FileData.cs index ef3ba870..65a63cda 100644 --- a/installer/Data/MD5FileData.cs +++ b/installer/Data/MD5FileData.cs @@ -26,13 +26,13 @@ public class TVersion { // 代码库版本 [JsonInclude] - public Version LibVersion = new Version(1, 0, 2, 2); + public Version LibVersion = new Version(1, 0, 2, 3); // 选手代码模板版本 [JsonInclude] public Version TemplateVersion = new Version(1, 0, 0, 3); // 本体版本 [JsonInclude] - public Version InstallerVersion = new Version(1, 0, 2, 0); + public Version InstallerVersion = new Version(1, 1, 0, 0); public static bool operator <(TVersion l, TVersion r) { return l.LibVersion < r.LibVersion || l.TemplateVersion < r.TemplateVersion || l.InstallerVersion < r.InstallerVersion; diff --git a/installer/MauiProgram.cs b/installer/MauiProgram.cs index 3040057e..2bf8f9ef 100755 --- a/installer/MauiProgram.cs +++ b/installer/MauiProgram.cs @@ -67,7 +67,7 @@ public static MauiApp CreateMauiApp() var c = builder.Services.AddSingleton().First(); builder.Services.AddSingleton(FolderPicker.Default); - + builder.Services.AddSingleton(FilePicker.Default); AddViewModelService(builder); AddPageService(builder); diff --git a/installer/Model/Downloader.cs b/installer/Model/Downloader.cs index 6b1981a6..e444d33e 100755 --- a/installer/Model/Downloader.cs +++ b/installer/Model/Downloader.cs @@ -190,28 +190,42 @@ public void Install(string? path = null) if (Log is FileLogger) ((FileLogger)Log).Path = Path.Combine(Data.Config.InstallPath, "Logs", "Installer.log"); Data.ResetInstallPath(Data.Config.InstallPath); - string zp = Path.Combine(Data.Config.InstallPath, "THUAI7.tar.gz"); Status = UpdateStatus.downloading; (CloudReport.ComCount, CloudReport.Count) = (0, 1); - Cloud.Log.LogInfo($"正在下载安装包……"); + Cloud.Log.LogInfo($"正在下载installer安装包……"); Cloud.DownloadFileAsync(zp, "THUAI7.tar.gz").Wait(); CloudReport.ComCount = 1; Status = UpdateStatus.unarchieving; - Cloud.Log.LogInfo($"安装包下载完毕,正在解压……"); + Cloud.Log.LogInfo($"installer安装包下载完毕,正在解压……"); Cloud.ArchieveUnzip(zp, Data.Config.InstallPath); - Cloud.Log.LogInfo($"解压完成"); + Cloud.Log.LogInfo($"installer解压完成"); File.Delete(zp); CurrentVersion = Data.FileHashData.TVersion; Cloud.Log.LogInfo("正在下载选手代码……"); Status = UpdateStatus.downloading; - CloudReport.Count = 3; + CloudReport.Count += 2; var tocpp = Cloud.DownloadFileAsync(Path.Combine(Data.Config.InstallPath, "CAPI", "cpp", "API", "src", "AI.cpp"), $"./Templates/t.{CurrentVersion.TemplateVersion}.cpp").ContinueWith(_ => CloudReport.ComCount++); var topy = Cloud.DownloadFileAsync(Path.Combine(Data.Config.InstallPath, "CAPI", "python", "PyAPI", "AI.py"), $"./Templates/t.{CurrentVersion.TemplateVersion}.py").ContinueWith(_ => CloudReport.ComCount++); Task.WaitAll(tocpp, topy); + + Cloud.Report.Count += 1; + zp = Path.Combine(Data.Config.InstallPath, "protoCpp.tar.gz"); + Cloud.Log.LogInfo("正在下载proto cpp库……"); + Cloud.DownloadFileAsync(zp, "Setup/proto/protoCpp.tar.gz").Wait(); + CloudReport.ComCount += 1; + Status = UpdateStatus.unarchieving; + Cloud.Log.LogInfo($"proto cpp库下载完毕,正在解压……"); + var protoCppLibPath = Path.Combine(Data.Config.InstallPath, "CAPI", "cpp", "lib"); + if (!Directory.Exists(protoCppLibPath)) + Directory.CreateDirectory(protoCppLibPath); + Cloud.ArchieveUnzip(zp, protoCppLibPath); + Cloud.Log.LogInfo($"proto cpp库解压完成"); + File.Delete(zp); + if (CloudReport.ComCount == CloudReport.Count) { Cloud.Log.LogInfo("选手代码下载成功!"); @@ -296,7 +310,7 @@ public bool CheckUpdate(bool writeMD5 = true) Status = UpdateStatus.success; if (Data.MD5Update.Count != 0 || CurrentVersion < Data.FileHashData.TVersion) { - Data.Log.LogInfo("需要更新,请点击更新按钮以更新。"); + Data.Log.LogInfo("代码库需要更新,请点击更新按钮以更新。"); if (writeMD5) { Data.SaveMD5Data(); @@ -312,6 +326,15 @@ public bool CheckUpdate(bool writeMD5 = true) } return true; } + else if (!Directory.Exists(Path.Combine(Data.Config.InstallPath, "CAPI", "cpp", "lib"))) + { + Data.Log.LogInfo("未检测到proto cpp库,请点击更新按钮以修复。"); + if (writeMD5) + { + Data.SaveMD5Data(); + } + return true; + } else { Data.Log.LogInfo("您的版本已经是最新版本!"); @@ -370,7 +393,23 @@ public int Update() return -1; } } - + // 如果缺少proto cpp库,应当立刻下载 + if (!Directory.Exists(Path.Combine(Data.Config.InstallPath, "CAPI", "cpp", "lib"))) + { + Cloud.Report.Count += 1; + string zp = Path.Combine(Data.Config.InstallPath, "protoCpp.tar.gz"); + Cloud.Log.LogInfo("正在下载proto cpp库……"); + Cloud.DownloadFileAsync(zp, "Setup/proto/protoCpp.tar.gz").Wait(); + CloudReport.ComCount += 1; + Status = UpdateStatus.unarchieving; + Cloud.Log.LogInfo($"proto cpp库下载完毕,正在解压……"); + var protoCppLibPath = Path.Combine(Data.Config.InstallPath, "CAPI", "cpp", "lib"); + if (!Directory.Exists(protoCppLibPath)) + Directory.CreateDirectory(protoCppLibPath); + Cloud.ArchieveUnzip(zp, protoCppLibPath); + Cloud.Log.LogInfo($"proto cpp库解压完成"); + File.Delete(zp); + } // 启动器本身需要更新,返回结果为16 if (CurrentVersion.InstallerVersion < Data.FileHashData.TVersion.InstallerVersion) { diff --git a/installer/Model/Local_Data.cs b/installer/Model/Local_Data.cs index 27cbed03..c2cb39ae 100755 --- a/installer/Model/Local_Data.cs +++ b/installer/Model/Local_Data.cs @@ -216,6 +216,7 @@ public void SaveMD5Data(bool VersionRefresh = true) })); sw.Flush(); } + // Log.LogInfo("MD5Data saved."); } catch (Exception e) { @@ -288,8 +289,8 @@ public void ScanDir(bool VersionRefresh = true) public static bool IsUserFile(string filename) { filename = filename.Replace(Path.DirectorySeparatorChar, '/'); - if (filename.Contains("/git/") || filename.Contains("bin/") || filename.Contains("/obj/") || filename.Contains("/x64/") - || filename.Contains("__pycache__")) + if (filename.Contains("/git/") || filename.Contains("/bin/") || filename.Contains("/obj/") || filename.Contains("/x64/") + || filename.Contains("__pycache__") || filename.Contains("/CAPI/cpp/lib/")) return true; if (filename.Contains("/vs/") || filename.Contains("/.vs/") || filename.Contains("/.vscode/")) return true; diff --git a/installer/Model/Tencent_Cos.cs b/installer/Model/Tencent_Cos.cs index 0bc0998f..b717b704 100755 --- a/installer/Model/Tencent_Cos.cs +++ b/installer/Model/Tencent_Cos.cs @@ -10,6 +10,8 @@ using System; using installer.Data; using System.Threading.Tasks; +using COSXML.Model.Bucket; +using COSXML.Model.Tag; // 禁用对没有调用异步API的异步函数的警告 #pragma warning disable CS1998 @@ -153,12 +155,14 @@ public int DownloadQueue(string basePath, IEnumerable queue) int thID = Log.StartNew(); Log.LogDebug(thID, "Batch download task started."); var array = queue.ToArray(); - Report.Count = array.Count(); - Report.ComCount = 0; - if (Report.Count == 0) + var count = array.Length; + if (count == 0) return 0; - var partitionar = Partitioner.Create(0, Report.Count, Report.Count / 4 > 0 ? Report.Count / 4 : Report.Count); - var c = 0; + var comCount = 0; + var comCountOld = Report.ComCount; + Report.Count += count; + + var partitionar = Partitioner.Create(0, count, count / 4 > 0 ? count / 4 : count); Parallel.ForEach(partitionar, (range, loopState) => { for (long i = range.Item1; i < range.Item2; i++) @@ -177,14 +181,14 @@ public int DownloadQueue(string basePath, IEnumerable queue) } finally { - Interlocked.Increment(ref c); - Report.ComCount = c; + Interlocked.Increment(ref comCount); + Report.ComCount = comCount + comCountOld; Log.LogInfo(thID, $"Child process: {subID} finished."); } } }); Log.LogInfo(thID, "Batch download task finished."); - Report.ComCount = Report.Count; + Report.ComCount = comCount + comCountOld; return thID; } @@ -300,6 +304,37 @@ public void DeleteFile(string remotePath) } } + public List EnumerateDir(string remotePath) + { + int thID = Log.StartNew(); + var result = new List(); + string bucket = $"{BucketName}-{Appid}"; + remotePath = remotePath.TrimStart('.').TrimStart('/'); + Log.LogInfo(thID, $"Enumerate files in {remotePath}"); + + bool truncated = false; + string marker = string.Empty; + do + { + GetBucketRequest request = new GetBucketRequest(bucket); + request.SetPrefix(remotePath); + request.SetDelimiter("/"); + if (!string.IsNullOrEmpty(marker)) + request.SetMarker(marker); + //执行请求 + GetBucketResult res = cosXml.GetBucket(request); + ListBucket info = res.listBucket; + result.AddRange(info.contentsList.Select(i => i.key).Where(i => i != remotePath)); + foreach (var dir in info.commonPrefixesList) + { + result.AddRange(EnumerateDir(dir.prefix)); + } + truncated = info.isTruncated; + marker = info.nextMarker; + } while (truncated); + + return result; + } #region 异步方法包装 public Task DownloadFileAsync(string savePath, string? remotePath = null) { diff --git a/installer/Page/DebugPage.xaml b/installer/Page/DebugPage.xaml index 760eb20d..da6acc56 100644 --- a/installer/Page/DebugPage.xaml +++ b/installer/Page/DebugPage.xaml @@ -3,207 +3,208 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="installer.Page.DebugPage" Title="Launcher"> - - + - - - - - Client + Server + + + + + + - - - -