2114 lines
77 KiB
Markdown
2114 lines
77 KiB
Markdown
+++
|
||
date = '2025-12-16T16:46:30+08:00'
|
||
draft = false
|
||
title = '计算概论A 2025年秋 大作业(更新)'
|
||
tags = ['计算概论']
|
||
license = 'MIT Licence'
|
||
description = '供计算概论A助教验收'
|
||
+++
|
||
## 文件下载
|
||
已编译的二进制文件:[oasa25](oasa25)(需要先`chmod +x oasa25`才能运行)
|
||
Swift源代码:[oasa25.swift](oasa25.swift)
|
||
Botzone简单交互:[oasa25.cpp](oasa25.cpp)
|
||
> 注意:请使用macOS 14.0及以上版本运行上面的二进制文件。
|
||
## 基本信息
|
||
- 程序名称:oasa25;
|
||
- 编写语言:Swift 5;
|
||
- 编译环境:
|
||
- IDE:Xcode Version 26.0.1 (17A400);
|
||
- macOS:Tahoe 26.0.1 (25A362);
|
||
- 设备:MacBook Air (13英寸, M3, 2024年);
|
||
- 借助AI:DeepSeek;
|
||
- 主要用途:
|
||
- 在原来代码的基础上进行优化;
|
||
- 讲解Swift语法;
|
||
- 修复语法错误;
|
||
- 协助给程序取名;
|
||
- "oasa"(“おアサ”)是“大きいアサインメント”(“大作业”)的缩略;
|
||
- "25"是我的数学分析I期中考试成绩。
|
||
- 程序特色:
|
||
- 使用上:
|
||
- 友好的GUI界面;
|
||
- 包含分栏显示;
|
||
- 很多图标;
|
||
- 二次确认,防止误操作;
|
||
- 完善的存档保存/另存为/读取功能;
|
||
- 清晰的状态显示;
|
||
- 支持历史记录查看;
|
||
- 合法路径绿色高亮显示,被阻挡的路径红色高亮显示;
|
||
- 支持自由调节蒙特卡洛算法评分参数;
|
||
- 终端输出蒙特卡洛方法日志;
|
||
- 技术上:
|
||
- 使用SwiftUI的现成控件实现GUI界面,减少所需代码;
|
||
- 使用多个状态变量,状态划分清晰;
|
||
- 代码分成不同`struct`,分工实现不同功能;
|
||
- 变量名、函数名等清晰易读(感谢DeepSeek);
|
||
- 并行处理,提高运行效率;
|
||
- 实现蒙特卡洛算法;
|
||
- 缺点:不管怎么调,算法都有点傻傻的,只比随机算法好一点点,在Botzone上被打得毫无还手之力(期末了😭难debug)。
|
||
## 编译方法
|
||
```bash
|
||
swiftc -parse-as-library <swift源代码路径> -o oasa25
|
||
```
|
||
由于使用SwiftUI,需要使用macOS编译,如果助教老师使用的不是macOS,要不线下使用我的电脑验收吧。
|
||
## 程序结构(详见源代码)
|
||
- `import`部分:
|
||
- `Combine`:用于实现`ObservableObject`类型的`GameState`;
|
||
- `SwiftUI`:提供程序的UI控件;
|
||
- `UniformTypeIdentifiers`:用于存取文件时对文件类型的规定;
|
||
- `enum GamePhase:`:设定三个游戏阶段的值;
|
||
- 选择棋子阶段`selectPiece`;
|
||
- 移动棋子阶段`movePiece`;
|
||
- 放置障碍物阶段`placeArrow`;
|
||
- `struct HistoryEntry`:历史记录数据结构;
|
||
- id+字符串;
|
||
- `struct gameData`:规定游戏存档的结构;
|
||
- 棋盘数据`chessBoard`(二维数组);
|
||
- 回合数据`blackRound`(Bool值);
|
||
- 历史记录数据`history`(HistoryEntry数组);
|
||
- 选中棋子位置`selectedPieceRow`和`selectedPieceCol`(整数);
|
||
- 阶段数据`gamePhase`;
|
||
- 上次计算的可用路径`availableMoves`(二维数组);
|
||
- 读档后无需再次计算;
|
||
- 游戏结束状态`isGameOver`(Bool值);
|
||
- 自动下棋选项`blackStrategy`和`whiteStrategy`(字符串);
|
||
- 当前回合数`roundNum`(整数);
|
||
- 蒙特卡洛方法计算结果`mcList`(数组);
|
||
- 控制面板参数值`blackControlFactor`、`blackSafetyFactor`、`blackSurroundFactor`、`blackCenterFactor`、`whiteControlFactor`、`whiteSafetyFactor`、`whiteSurroundFactor`和`whiteCenterFactor`(均为双精度浮点类型);
|
||
- `class GameState`:游戏状态环境对象
|
||
- 声明并初始化各个状态变量(详细列表如上);
|
||
- `struct GameManager`:游戏逻辑管理器;
|
||
- `func saveGame`:借助Swift的`NSSavePanel()`功能进行存档的保存(同时会调用自定义的`saveChessBoardToJSON()`);
|
||
- `func loadGame`:借助Swift的`NSOpenPanel()`功能进行存档的读取(同时会调用自定义的`loadChessBoardFromJSON()`);
|
||
- `func saveChessBoardToJSON`:借助Swift的`JSONEncoder()`,保存当前棋局为`.json`存档;
|
||
- `func loadChessBoardFromJSON`:借助Swift的`JSONDecoder()`,从`.json`存档中读取各个变量,还原棋局;
|
||
- `func initializeChessBoard`:新建游戏时的棋盘初始化;
|
||
- `func addHistory`:将最近一步操作插入到`history`字符串组的索引`0`位置的一个简单函数;
|
||
- `func calculateAvailableMoves`:计算可移动的位置,用于给设定按钮状态和染色提供数据;
|
||
- `func getAllAvailablePieces`:获取所有可用的棋子位置,用于设定按钮状态;
|
||
- `func getAllCounterPieces`:除了参数相反,和上面函数功能相同;
|
||
- `func getAllAvailableMoves`:获取所有可移动/可放置障碍物的位置,染色;
|
||
- `func checkWinCondition`和`func checkAndHandleWinCondition`:检查胜利;
|
||
- `func handleSquareTap`:处理格子点击;
|
||
- 蒙特卡洛算法实现部分
|
||
- `private func generateMcListForCurrentTurn`:更新`mcList`状态变量
|
||
- `private func findBestMoveWithMonteCarlo`:通过给定第一行动回合+往下随机8个回合(并重走30次)计算平均得分,为上述函数提供计算结果;
|
||
- `private func getAllPossibleMoves`:模拟完整的一个回合的可行操作方案;
|
||
- `private func simulateRandomGame`:模拟完整一个回合的随机下棋过程;
|
||
- `private func getRandomMove`:随机选取一个回合的下棋方案(返回值:`piece: (Int, Int), target: (Int, Int), arrow: (Int, Int)`)
|
||
- `private func isGameOver`:检查游戏是否结束(模拟下棋版);
|
||
- `private func calculateFinalScore`:
|
||
- 若中途获胜:返回`1.0`或`-1.0`;
|
||
- 否则使用下面函数的返回值;
|
||
- `private func generateRandomMcList`:生成随机走法列表(备用);
|
||
- 因为Swift要防止使用空值,所以需要一个这样的函数来保证每种情况都有值,实际上不会被调用;
|
||
- `private func evaluateBoard`:3项评估分数求和+归一化到[-1,1];
|
||
- `private func calculateSimuMoves`:计算可以移动的方案(不修改GameState版);
|
||
- `private func mcSelect`:基于已生成的`mcList`执行选择;
|
||
- `private func mcMove`:基于已生成的`mcList`执行移动;
|
||
- `private func mcPlace`:基于已生成的`mcList`放置障碍物;
|
||
- 综合的自动下棋:`func autoOperate`
|
||
- 随机下棋:
|
||
- `private func randomSelect`;
|
||
- `private func randomMove`;
|
||
- `private func randomPlace`;
|
||
- 局势评估算法:
|
||
- `private func controlScore`:控制分=倍率*所有棋子可移动格子数目之和;
|
||
- `private func safetyScore`:安全分=每个棋子求和:
|
||
- +倍率*距离边界的最小距离;
|
||
- 若被完全包围:-包围惩罚;
|
||
- 否则:+倍率*所有棋子可移动格子数目之和;(怎么感觉和上面控制分有点重复)
|
||
- `private func centerScore`:中心分=每个棋子求和:
|
||
- 若在中心:+倍率*5;
|
||
- +(10-离中心点距离);
|
||
- `struct ChessSquareView`:棋盘格子视图;
|
||
- 实现单个棋盘格子的显示;
|
||
- `func getSquareColor`:染色;
|
||
- `func isInteractive`:设定按钮状态;
|
||
- `struct ChessBoardRowView`:棋盘行视图;
|
||
- 实现棋盘单行的显示;
|
||
- 调用`struct ChessSquareView`;
|
||
- `struct ChessBoardView`:棋盘视图;
|
||
- 实现棋盘的完整显示;
|
||
- 调用`struct ChessBoardRowView`;
|
||
- `struct MenuButtonView`:菜单按钮视图;
|
||
- 作为定义主菜单按钮的模板,避免代码的大量重复;
|
||
- `struct SidebarView`:左侧栏视图;
|
||
- `var gameInfoView`:显示当前存档路径、当前回合、当前阶段、历史操作;
|
||
- `var phaseDescription`:把阶段的代号转换成文字描述;
|
||
- `var welcomeView`:显示第一局游戏开始前的引导信息;
|
||
- `struct ParameterSliderView`:负责单一滑块组件;
|
||
- `struct ParameterSettingsView`:生成完整滑块界面;
|
||
- `struct RightView`:右侧栏视图;
|
||
- 自动下棋控制面板,支持调节参数;
|
||
- `struct MainMenuView`:主菜单视图;
|
||
- 实现主菜单的显示;
|
||
- 多次调用`struct MenuButtonView`,实现各个菜单按钮;
|
||
- 依据状态变量的不同,显示不同的按钮;
|
||
- `struct ContentView`:主视图。
|
||
- 划分窗口结构:
|
||
- 调用`SidebarView`,实现左侧栏的状态显示;
|
||
- 调用`MainMenuView`,实现左下角的菜单控制;
|
||
- 调用`ChessBoardView`,实现右侧的棋盘显示。
|
||
- 调用`RightView`;
|
||
- 定义函数`private func setWindowTitle`
|
||
- 使用`.onAppear`在程序启动时设置窗口标题。
|
||
|
||
## 源代码展示
|
||
下载[oasa25.swift](oasa25.swift)。
|
||
```swift
|
||
import Combine
|
||
import SwiftUI
|
||
import UniformTypeIdentifiers
|
||
// MARK: - 游戏阶段枚举
|
||
enum GamePhase: Int, Codable {
|
||
case selectPiece = 0
|
||
case movePiece = 1
|
||
case placeArrow = 2
|
||
}
|
||
|
||
// MARK: - 历史记录条目(修复版)
|
||
struct HistoryEntry: Identifiable, Codable, Hashable {
|
||
let id: UUID
|
||
let action: String
|
||
|
||
init(action: String) {
|
||
self.id = UUID()
|
||
self.action = action
|
||
}
|
||
|
||
// 为了兼容现有代码的描述
|
||
var description: String { action }
|
||
}
|
||
|
||
// MARK: - 存档文件结构(修复版)
|
||
struct GameData: Codable {
|
||
let chessBoard: [[Int]]
|
||
let blackRound: Bool
|
||
let history: [HistoryEntry] // 修改为HistoryEntry数组
|
||
let selectedPieceRow: Int?
|
||
let selectedPieceCol: Int?
|
||
let gamePhase: GamePhase
|
||
let availableMoves: [[Int]]
|
||
let isGameOver: Bool // 新增:游戏结束状态
|
||
let blackStrategy: String
|
||
let whiteStrategy: String
|
||
let roundNum: Int
|
||
let mcList: [Int]
|
||
let blackControlFactor: Double
|
||
let blackSafetyFactor: Double
|
||
let blackSurroundFactor: Double
|
||
let blackCenterFactor: Double
|
||
let whiteControlFactor: Double
|
||
let whiteSafetyFactor: Double
|
||
let whiteSurroundFactor: Double
|
||
let whiteCenterFactor: Double
|
||
}
|
||
|
||
// MARK: - 游戏状态环境对象
|
||
class GameState: ObservableObject {
|
||
@Published var withFile = false
|
||
@Published var chessBoard = Array(
|
||
repeating: Array(repeating: 0, count: 8),
|
||
count: 8
|
||
)
|
||
@Published var currentUrl: String = ""
|
||
@Published var unsavable = true
|
||
@Published var isPlaying = false
|
||
@Published var blackRound = true
|
||
@Published var history: [HistoryEntry] = [] // 修改为HistoryEntry数组
|
||
@Published var availableMoves: [[Int]] = Array(
|
||
repeating: Array(repeating: 0, count: 8),
|
||
count: 8
|
||
)
|
||
@Published var selectedPiece: (Int, Int)? = nil
|
||
@Published var gamePhase: GamePhase = .selectPiece
|
||
@Published var whiteAutoOperate = false
|
||
@Published var blackAutoOperate = false
|
||
@Published var isGameOver: Bool = false // 新增:游戏结束状态
|
||
@Published var blackStrategy: String = "纯随机下棋"
|
||
@Published var whiteStrategy: String = "纯随机下棋"
|
||
@Published var roundNum: Int = 0
|
||
@Published var mcList: [Int] = [] // 结构:[当前回合数,是否为黑方行动,选择棋子行坐标,选择棋子列坐标,移动棋子行坐标,移动棋子列坐标,放置障碍物行坐标,放置障碍物列坐标]
|
||
@Published var blackControlFactor: Double = 1
|
||
@Published var blackSafetyFactor: Double = 1
|
||
@Published var blackSurroundFactor: Double = 10
|
||
@Published var blackCenterFactor: Double = 1
|
||
@Published var whiteControlFactor: Double = 1
|
||
@Published var whiteSafetyFactor: Double = 1
|
||
@Published var whiteSurroundFactor: Double = 10
|
||
@Published var whiteCenterFactor: Double = 1
|
||
}
|
||
|
||
// MARK: - 游戏逻辑管理器
|
||
struct GameManager {
|
||
let gameState: GameState
|
||
|
||
// MARK: - 文件操作与游戏初始化
|
||
func saveGame() {
|
||
let savePanel = NSSavePanel()
|
||
savePanel.title = "保存游戏"
|
||
savePanel.message = "选择保存游戏的目录和文件名"
|
||
savePanel.allowedContentTypes = [UTType.json]
|
||
savePanel.nameFieldStringValue = "新游戏.json"
|
||
savePanel.allowsOtherFileTypes = false
|
||
|
||
savePanel.begin { response in
|
||
if response == .OK, let url = savePanel.url {
|
||
self.gameState.currentUrl = url.path
|
||
if self.saveChessBoardToJSON(url: url) {
|
||
self.gameState.unsavable = true
|
||
self.gameState.withFile = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func loadGame() {
|
||
let openPanel = NSOpenPanel()
|
||
openPanel.title = "读取游戏"
|
||
openPanel.message = "选择游戏存档文件"
|
||
openPanel.allowedContentTypes = [UTType.json]
|
||
openPanel.allowsMultipleSelection = false
|
||
|
||
openPanel.begin { response in
|
||
if response == .OK, let url = openPanel.url {
|
||
if self.loadChessBoardFromJSON(url: url) {
|
||
self.gameState.currentUrl = url.path
|
||
self.gameState.withFile = true
|
||
self.gameState.isPlaying = true
|
||
self.gameState.unsavable = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func saveChessBoardToJSON(url: URL) -> Bool {
|
||
do {
|
||
let encoder = JSONEncoder()
|
||
// 使用新的GameData结构
|
||
let gameData = GameData(
|
||
chessBoard: gameState.chessBoard,
|
||
blackRound: gameState.blackRound,
|
||
history: gameState.history, // 现在类型匹配了
|
||
selectedPieceRow: gameState.selectedPiece?.0,
|
||
selectedPieceCol: gameState.selectedPiece?.1,
|
||
gamePhase: gameState.gamePhase,
|
||
availableMoves: gameState.availableMoves,
|
||
isGameOver: gameState.isGameOver, // 新增:保存游戏结束状态
|
||
blackStrategy: gameState.blackStrategy,
|
||
whiteStrategy: gameState.whiteStrategy,
|
||
roundNum: gameState.roundNum,
|
||
mcList: gameState.mcList,
|
||
blackControlFactor: gameState.blackControlFactor,
|
||
blackSafetyFactor: gameState.blackSafetyFactor,
|
||
blackSurroundFactor: gameState.blackSurroundFactor,
|
||
blackCenterFactor: gameState.blackSafetyFactor,
|
||
whiteControlFactor: gameState.whiteControlFactor,
|
||
whiteSafetyFactor: gameState.whiteSafetyFactor,
|
||
whiteSurroundFactor: gameState.whiteSurroundFactor,
|
||
whiteCenterFactor: gameState.whiteCenterFactor
|
||
)
|
||
let data = try encoder.encode(gameData)
|
||
try data.write(to: url)
|
||
return true
|
||
} catch {
|
||
print("保存JSON失败: \(error)")
|
||
return false
|
||
}
|
||
}
|
||
|
||
func loadChessBoardFromJSON(url: URL) -> Bool {
|
||
do {
|
||
let data = try Data(contentsOf: url)
|
||
let decoder = JSONDecoder()
|
||
let loadedData = try decoder.decode(GameData.self, from: data)
|
||
|
||
gameState.chessBoard = loadedData.chessBoard
|
||
gameState.blackRound = loadedData.blackRound
|
||
gameState.history = loadedData.history // 现在类型匹配了
|
||
|
||
if let row = loadedData.selectedPieceRow,
|
||
let col = loadedData.selectedPieceCol
|
||
{
|
||
gameState.selectedPiece = (row, col)
|
||
} else {
|
||
gameState.selectedPiece = nil
|
||
}
|
||
|
||
gameState.gamePhase = loadedData.gamePhase
|
||
gameState.availableMoves = loadedData.availableMoves
|
||
gameState.isGameOver = loadedData.isGameOver // 新增:加载游戏结束状态
|
||
|
||
return true
|
||
} catch {
|
||
print("读取JSON失败: \(error)")
|
||
return false
|
||
}
|
||
}
|
||
func initializeChessBoard() {
|
||
gameState.history = []
|
||
gameState.selectedPiece = nil
|
||
gameState.gamePhase = .selectPiece
|
||
gameState.availableMoves = Array(
|
||
repeating: Array(repeating: 0, count: 8),
|
||
count: 8
|
||
)
|
||
gameState.chessBoard = Array(
|
||
repeating: Array(repeating: 0, count: 8),
|
||
count: 8
|
||
)
|
||
gameState.isGameOver = false // 新增:重置游戏结束状态
|
||
gameState.blackAutoOperate = false // 重置自动下棋
|
||
gameState.whiteAutoOperate = false // 重置自动下棋
|
||
|
||
// 设置初始棋子位置(根据Amazon棋规则)
|
||
// 1黑方Amazon 2白方Amazon 3黑方障碍物 4白方障碍物
|
||
gameState.chessBoard[0][2] = 1
|
||
gameState.chessBoard[0][5] = 1
|
||
gameState.chessBoard[2][0] = 1
|
||
gameState.chessBoard[2][7] = 1
|
||
gameState.chessBoard[7][2] = 2
|
||
gameState.chessBoard[7][5] = 2
|
||
gameState.chessBoard[5][0] = 2
|
||
gameState.chessBoard[5][7] = 2
|
||
}
|
||
|
||
func addHistory(_ action: String) {
|
||
let player = gameState.blackRound ? "黑方" : "白方"
|
||
let historyEntry = HistoryEntry(
|
||
action: "\(player) 第\(gameState.roundNum)回合 \(action)"
|
||
)
|
||
gameState.history.insert(historyEntry, at: 0)
|
||
}
|
||
// MARK: - 算法部分
|
||
func calculateAvailableMoves(from position: (Int, Int)) {
|
||
gameState.availableMoves = Array(
|
||
repeating: Array(repeating: 0, count: 8),
|
||
count: 8
|
||
)
|
||
let (row, col) = position
|
||
let directions = [
|
||
(-1, -1), (-1, 0), (-1, 1),
|
||
(0, -1), (0, 1),
|
||
(1, -1), (1, 0), (1, 1),
|
||
]
|
||
|
||
for direction in directions {
|
||
var currentRow = row + direction.0
|
||
var currentCol = col + direction.1
|
||
var foundObstacle = false
|
||
|
||
while currentRow >= 0 && currentRow < 8 && currentCol >= 0
|
||
&& currentCol < 8
|
||
{
|
||
if !foundObstacle {
|
||
if gameState.chessBoard[currentRow][currentCol] != 0 {
|
||
foundObstacle = true
|
||
gameState.availableMoves[currentRow][currentCol] = 2
|
||
} else {
|
||
gameState.availableMoves[currentRow][currentCol] = 1
|
||
}
|
||
} else {
|
||
gameState.availableMoves[currentRow][currentCol] = 2
|
||
}
|
||
currentRow += direction.0
|
||
currentCol += direction.1
|
||
}
|
||
}
|
||
}
|
||
|
||
func getAllAvailablePieces() -> [(Int, Int)] {
|
||
let currentPlayer = gameState.blackRound ? 1 : 2
|
||
var availablePieces: [(Int, Int)] = []
|
||
|
||
for row in 0..<8 {
|
||
for col in 0..<8 {
|
||
if gameState.chessBoard[row][col] == currentPlayer {
|
||
availablePieces.append((row, col))
|
||
}
|
||
}
|
||
}
|
||
return availablePieces
|
||
}
|
||
|
||
func getAllCounterPieces() -> [(Int, Int)] {
|
||
let currentPlayer = gameState.blackRound ? 2 : 1
|
||
var availablePieces: [(Int, Int)] = []
|
||
|
||
for row in 0..<8 {
|
||
for col in 0..<8 {
|
||
if gameState.chessBoard[row][col] == currentPlayer {
|
||
availablePieces.append((row, col))
|
||
}
|
||
}
|
||
}
|
||
return availablePieces
|
||
}
|
||
|
||
func getAllAvailableMoves() -> [(Int, Int)] {
|
||
var availableMovesList: [(Int, Int)] = []
|
||
|
||
for row in 0..<8 {
|
||
for col in 0..<8 {
|
||
if gameState.availableMoves[row][col] == 1 {
|
||
availableMovesList.append((row, col))
|
||
}
|
||
}
|
||
}
|
||
return availableMovesList
|
||
}
|
||
|
||
func checkWinCondition() -> (isGameOver: Bool, winner: String?) {
|
||
// 如果游戏已经结束,直接返回结果
|
||
if gameState.isGameOver {
|
||
let winner = gameState.blackRound ? "黑方" : "白方"
|
||
return (true, winner)
|
||
}
|
||
|
||
// 获取对方所有棋子
|
||
let opponentPieces = getAllCounterPieces()
|
||
|
||
// 检查对方是否有任何一个棋子能移动
|
||
for piece in opponentPieces {
|
||
// 计算该棋子的可移动位置
|
||
calculateAvailableMoves(from: piece)
|
||
let availableMoves = getAllAvailableMoves()
|
||
|
||
// 如果有任何可移动位置,游戏继续
|
||
if !availableMoves.isEmpty {
|
||
return (false, nil)
|
||
}
|
||
}
|
||
|
||
// 对方所有棋子都无法移动,当前玩家获胜!
|
||
let winner = gameState.blackRound ? "黑方" : "白方"
|
||
return (true, winner)
|
||
}
|
||
func checkAndHandleWinCondition() -> Bool {
|
||
let winResult = checkWinCondition()
|
||
|
||
if winResult.isGameOver && !gameState.isGameOver {
|
||
// 设置游戏结束状态
|
||
gameState.isGameOver = true
|
||
|
||
// 显示胜利消息
|
||
addHistory("获胜")
|
||
// 关闭双方的自动下棋
|
||
gameState.blackAutoOperate = false
|
||
gameState.whiteAutoOperate = false
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
func handleSquareTap(row: Int, column: Int) {// 处理点击事件
|
||
// 如果游戏已结束,禁止操作
|
||
if gameState.isGameOver {
|
||
return
|
||
}
|
||
|
||
let currentPlayer = gameState.blackRound ? 1 : 2
|
||
if (gameState.blackRound && gameState.blackAutoOperate)
|
||
|| (!gameState.blackRound && gameState.whiteAutoOperate)
|
||
{
|
||
return
|
||
}
|
||
|
||
switch gameState.gamePhase {
|
||
case .selectPiece:
|
||
if gameState.blackRound {
|
||
gameState.roundNum += 1
|
||
}
|
||
if gameState.chessBoard[row][column] == currentPlayer {
|
||
gameState.selectedPiece = (row, column)
|
||
calculateAvailableMoves(from: (row, column))
|
||
gameState.gamePhase = .movePiece
|
||
addHistory("选择了棋子 (\(row),\(column))")
|
||
}
|
||
|
||
case .movePiece:
|
||
guard let selected = gameState.selectedPiece else { return }
|
||
if gameState.availableMoves[row][column] == 1 {
|
||
gameState.chessBoard[row][column] =
|
||
gameState.chessBoard[selected.0][selected.1]
|
||
gameState.chessBoard[selected.0][selected.1] = 0
|
||
gameState.selectedPiece = (row, column)
|
||
|
||
calculateAvailableMoves(from: (row, column))
|
||
gameState.gamePhase = .placeArrow
|
||
addHistory("移动棋子到 (\(row),\(column))")
|
||
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||
if self.gameState.blackRound
|
||
&& self.gameState.blackAutoOperate
|
||
{
|
||
self.autoOperate(player: true)
|
||
} else if !self.gameState.blackRound
|
||
&& self.gameState.whiteAutoOperate
|
||
{
|
||
self.autoOperate(player: false)
|
||
}
|
||
}
|
||
} else if gameState.chessBoard[row][column] == currentPlayer {
|
||
gameState.selectedPiece = (row, column)
|
||
calculateAvailableMoves(from: (row, column))
|
||
addHistory("重新选择了棋子 (\(row),\(column))")
|
||
}
|
||
|
||
case .placeArrow:
|
||
if gameState.availableMoves[row][column] == 1 {
|
||
gameState.chessBoard[row][column] = gameState.blackRound ? 3 : 4
|
||
|
||
addHistory("放置障碍物在 (\(row),\(column))")
|
||
|
||
// 🎯 在放置障碍物后立即检查胜利条件
|
||
let isGameOver = checkAndHandleWinCondition()
|
||
|
||
if isGameOver {
|
||
// 游戏结束,清理状态但不切换回合
|
||
gameState.selectedPiece = nil
|
||
gameState.availableMoves = Array(
|
||
repeating: Array(repeating: 0, count: 8),
|
||
count: 8
|
||
)
|
||
gameState.gamePhase = .selectPiece
|
||
gameState.unsavable = false
|
||
// 注意:这里不切换 blackRound,因为游戏已结束
|
||
} else {
|
||
// 游戏继续,正常切换回合
|
||
gameState.selectedPiece = nil
|
||
gameState.availableMoves = Array(
|
||
repeating: Array(repeating: 0, count: 8),
|
||
count: 8
|
||
)
|
||
gameState.gamePhase = .selectPiece
|
||
gameState.blackRound.toggle()
|
||
gameState.unsavable = false
|
||
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||
if self.gameState.blackRound
|
||
&& self.gameState.blackAutoOperate
|
||
{
|
||
self.autoOperate(player: true)
|
||
} else if !self.gameState.blackRound
|
||
&& self.gameState.whiteAutoOperate
|
||
{
|
||
self.autoOperate(player: false)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// MARK: - 蒙特卡洛算法实现
|
||
private func generateMcListForCurrentTurn() {
|
||
let currentBoard = gameState.chessBoard
|
||
let isBlack = gameState.blackRound
|
||
|
||
// 清空之前的列表
|
||
gameState.mcList = []
|
||
|
||
// 添加回合数和玩家信息
|
||
gameState.mcList.append(gameState.roundNum)
|
||
gameState.mcList.append(isBlack ? 1 : 0) // 1:黑方, 0:白方
|
||
|
||
// 生成最佳走法
|
||
if let bestMove = findBestMoveWithMonteCarlo(
|
||
board: currentBoard,
|
||
isBlack: isBlack
|
||
) {
|
||
// 添加走法到列表
|
||
gameState.mcList.append(bestMove.piece.0)
|
||
gameState.mcList.append(bestMove.piece.1)
|
||
gameState.mcList.append(bestMove.target.0)
|
||
gameState.mcList.append(bestMove.target.1)
|
||
gameState.mcList.append(bestMove.arrow.0)
|
||
gameState.mcList.append(bestMove.arrow.1)
|
||
|
||
print(
|
||
"\(isBlack ? "黑" : "白")方蒙特卡洛生成走法: 棋子(\(bestMove.piece)) -> 移动(\(bestMove.target)) -> 箭(\(bestMove.arrow))"
|
||
)
|
||
} else {
|
||
// 如果没有找到走法,使用随机走法
|
||
generateRandomMcList()
|
||
}
|
||
}
|
||
private func findBestMoveWithMonteCarlo(board: [[Int]], isBlack: Bool) -> (
|
||
piece: (Int, Int), target: (Int, Int), arrow: (Int, Int)
|
||
)? {
|
||
let allMoves = getAllPossibleMoves(board: board, isBlack: isBlack)
|
||
|
||
if allMoves.isEmpty {
|
||
return nil
|
||
}
|
||
|
||
var bestMove:
|
||
(piece: (Int, Int), target: (Int, Int), arrow: (Int, Int))?
|
||
var bestScore: Double = -Double.infinity
|
||
let simulationsPerMove = 30 // 每个走法的模拟次数
|
||
|
||
// 并行处理以提高速度
|
||
DispatchQueue.concurrentPerform(iterations: min(10, allMoves.count)) {
|
||
index in
|
||
guard index < allMoves.count else { return }
|
||
let move = allMoves[index]
|
||
|
||
var totalScore: Double = 0.0
|
||
for _ in 0..<simulationsPerMove {
|
||
let score = simulateRandomGame(
|
||
from: move,
|
||
board: board,
|
||
isBlack: isBlack
|
||
)
|
||
totalScore += score
|
||
}
|
||
|
||
let averageScore = totalScore / Double(simulationsPerMove)
|
||
// 使用锁保护共享资源
|
||
if averageScore > bestScore {
|
||
bestScore = averageScore
|
||
bestMove = move
|
||
}
|
||
|
||
}
|
||
print("\(isBlack ? "黑" : "白")方分数:\(bestScore)")
|
||
return bestMove
|
||
}
|
||
private func getAllPossibleMoves(board: [[Int]], isBlack: Bool) -> [(
|
||
piece: (Int, Int), target: (Int, Int), arrow: (Int, Int)
|
||
)] {
|
||
var allMoves:
|
||
[(piece: (Int, Int), target: (Int, Int), arrow: (Int, Int))] = []
|
||
let playerPiece = isBlack ? 1 : 2
|
||
|
||
// 获取所有己方棋子
|
||
for row in 0..<8 {
|
||
for col in 0..<8 {
|
||
if board[row][col] == playerPiece {
|
||
// 获取该棋子的所有移动位置
|
||
let movePositions = calculateSimuMoves(
|
||
from: (row, col),
|
||
board: board
|
||
)
|
||
|
||
for movePos in movePositions {
|
||
// 模拟移动后的棋盘
|
||
var tempBoard = board
|
||
tempBoard[movePos.0][movePos.1] = playerPiece
|
||
tempBoard[row][col] = 0
|
||
|
||
// 获取所有可能的箭位置
|
||
let arrowPositions = calculateSimuMoves(
|
||
from: movePos,
|
||
board: tempBoard
|
||
)
|
||
|
||
for arrowPos in arrowPositions {
|
||
allMoves.append(
|
||
(
|
||
piece: (row, col), target: movePos,
|
||
arrow: arrowPos
|
||
)
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return allMoves
|
||
}
|
||
private func simulateRandomGame(
|
||
from move: (piece: (Int, Int), target: (Int, Int), arrow: (Int, Int)),
|
||
board: [[Int]],
|
||
isBlack: Bool
|
||
) -> Double {
|
||
// 1. 应用第一步走法
|
||
var simBoard = board
|
||
let playerPiece = isBlack ? 1 : 2
|
||
let arrowType = isBlack ? 3 : 4
|
||
|
||
// 移动棋子
|
||
simBoard[move.target.0][move.target.1] = playerPiece
|
||
simBoard[move.piece.0][move.piece.1] = 0
|
||
|
||
// 放置箭
|
||
simBoard[move.arrow.0][move.arrow.1] = arrowType
|
||
|
||
// 2. 继续随机模拟若干步
|
||
var currentIsBlack = !isBlack // 切换到对方回合
|
||
let maxSimulationSteps = 8 // 模拟8步
|
||
|
||
for _ in 0..<maxSimulationSteps {
|
||
// 检查游戏是否结束
|
||
if isGameOver(board: simBoard, isBlackTurn: currentIsBlack) {
|
||
// 游戏结束,返回分数
|
||
return calculateFinalScore(
|
||
board: simBoard,
|
||
originalIsBlack: isBlack
|
||
)
|
||
}
|
||
|
||
// 随机选择一步走法
|
||
guard
|
||
let randomMove = getRandomMove(
|
||
board: simBoard,
|
||
isBlack: currentIsBlack
|
||
)
|
||
else {
|
||
break
|
||
}
|
||
|
||
// 执行随机走法
|
||
let currentPiece = currentIsBlack ? 1 : 2
|
||
let currentArrow = currentIsBlack ? 3 : 4
|
||
|
||
simBoard[randomMove.target.0][randomMove.target.1] = currentPiece
|
||
simBoard[randomMove.piece.0][randomMove.piece.1] = 0
|
||
simBoard[randomMove.arrow.0][randomMove.arrow.1] = currentArrow
|
||
|
||
// 切换玩家
|
||
currentIsBlack.toggle()
|
||
}
|
||
|
||
// 3. 模拟结束,评估局面
|
||
return evaluateBoard(board: simBoard, originalIsBlack: isBlack)
|
||
}
|
||
private func getRandomMove(board: [[Int]], isBlack: Bool) -> (
|
||
piece: (Int, Int), target: (Int, Int), arrow: (Int, Int)
|
||
)? {
|
||
let allMoves = getAllPossibleMoves(board: board, isBlack: isBlack)
|
||
return allMoves.randomElement()
|
||
}
|
||
|
||
// 检查游戏是否结束(模拟途中版)
|
||
private func isGameOver(board: [[Int]], isBlackTurn: Bool) -> Bool {
|
||
let opponentIsBlack = !isBlackTurn
|
||
let opponentPiece = opponentIsBlack ? 1 : 2
|
||
|
||
// 检查对方是否有棋子可以移动
|
||
for row in 0..<8 {
|
||
for col in 0..<8 {
|
||
if board[row][col] == opponentPiece {
|
||
let moves = calculateSimuMoves(
|
||
from: (row, col),
|
||
board: board
|
||
)
|
||
if !moves.isEmpty {
|
||
return false // 对方有棋子可以移动
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return true // 对方无法移动
|
||
}
|
||
private func calculateFinalScore(board: [[Int]], originalIsBlack: Bool)
|
||
-> Double
|
||
{
|
||
// 如果对方无法移动,我方获胜
|
||
let currentPlayerCanMove =
|
||
getRandomMove(board: board, isBlack: originalIsBlack) != nil
|
||
let opponentCanMove =
|
||
getRandomMove(board: board, isBlack: !originalIsBlack) != nil
|
||
|
||
if !opponentCanMove && currentPlayerCanMove {
|
||
return 1.0 // 我方获胜
|
||
} else if opponentCanMove && !currentPlayerCanMove {
|
||
return -1.0 // 对方获胜
|
||
}
|
||
|
||
// 否则返回评估分数
|
||
return evaluateBoard(board: board, originalIsBlack: originalIsBlack)
|
||
}
|
||
// 生成随机走法列表(备用)
|
||
private func generateRandomMcList() {
|
||
let availablePieces = getAllAvailablePieces()
|
||
guard let randomPiece = availablePieces.randomElement() else { return }
|
||
|
||
calculateAvailableMoves(from: randomPiece)
|
||
let availableMovesList = getAllAvailableMoves()
|
||
guard let randomMove = availableMovesList.randomElement() else {
|
||
return
|
||
}
|
||
|
||
// 模拟移动以获取箭的位置
|
||
var tempBoard = gameState.chessBoard
|
||
let playerPiece = gameState.blackRound ? 1 : 2
|
||
|
||
tempBoard[randomMove.0][randomMove.1] = playerPiece
|
||
tempBoard[randomPiece.0][randomPiece.1] = 0
|
||
|
||
let arrowMoves = calculateSimuMoves(from: randomMove, board: tempBoard)
|
||
guard let randomArrow = arrowMoves.randomElement() else { return }
|
||
|
||
// 构建mcList
|
||
gameState.mcList = [
|
||
gameState.roundNum,
|
||
gameState.blackRound ? 1 : 0,
|
||
randomPiece.0, randomPiece.1,
|
||
randomMove.0, randomMove.1,
|
||
randomArrow.0, randomArrow.1,
|
||
]
|
||
}
|
||
private func evaluateBoard(board: [[Int]], originalIsBlack: Bool) -> Double
|
||
{
|
||
// 使用你的评估函数
|
||
let blackScore =
|
||
controlScore(
|
||
forBlack: true,
|
||
board: board,
|
||
factor: gameState.blackControlFactor
|
||
)
|
||
+ safetyScore(
|
||
forBlack: true,
|
||
board: board,
|
||
factor: gameState.blackSafetyFactor,
|
||
factor_surround: gameState.blackSurroundFactor
|
||
)
|
||
+ centerScore(
|
||
forBlack: true,
|
||
board: board,
|
||
factor: gameState.blackCenterFactor
|
||
)
|
||
|
||
let whiteScore =
|
||
controlScore(
|
||
forBlack: false,
|
||
board: board,
|
||
factor: gameState.whiteControlFactor
|
||
)
|
||
+ safetyScore(
|
||
forBlack: false,
|
||
board: board,
|
||
factor: gameState.whiteSafetyFactor,
|
||
factor_surround: gameState.whiteSurroundFactor
|
||
)
|
||
+ centerScore(
|
||
forBlack: false,
|
||
board: board,
|
||
factor: gameState.whiteCenterFactor
|
||
)
|
||
|
||
let diff = Double(blackScore - whiteScore)
|
||
let normalized = tanh(diff / 100.0) // 归一化到[-1, 1]
|
||
|
||
return originalIsBlack ? normalized : -normalized
|
||
}
|
||
// 模拟移动(无GameState版)
|
||
private func calculateSimuMoves(from: (Int, Int), board: [[Int]]) -> [(
|
||
Int, Int
|
||
)] {
|
||
var moves: [(Int, Int)] = []
|
||
let (row, col) = from
|
||
let directions = [
|
||
(-1, -1), (-1, 0), (-1, 1),
|
||
(0, -1), (0, 1),
|
||
(1, -1), (1, 0), (1, 1),
|
||
]
|
||
|
||
for direction in directions {
|
||
var currentRow = row + direction.0
|
||
var currentCol = col + direction.1
|
||
|
||
while currentRow >= 0 && currentRow < 8 && currentCol >= 0
|
||
&& currentCol < 8
|
||
{
|
||
if board[currentRow][currentCol] != 0 {
|
||
break // 遇到障碍或棋子
|
||
}
|
||
moves.append((currentRow, currentCol))
|
||
currentRow += direction.0
|
||
currentCol += direction.1
|
||
}
|
||
}
|
||
|
||
return moves
|
||
}
|
||
private func mcSelect() {
|
||
// 如果mcList为空或者回合不匹配,重新生成
|
||
if gameState.mcList.isEmpty || gameState.mcList[0] != gameState.roundNum
|
||
{
|
||
generateMcListForCurrentTurn()
|
||
}
|
||
|
||
// 检查mcList是否有效
|
||
guard gameState.mcList.count >= 8,
|
||
gameState.mcList[1] == (gameState.blackRound ? 1 : 0)
|
||
else {
|
||
// 如果无效,使用随机选择
|
||
randomSelect()
|
||
return
|
||
}
|
||
|
||
// 从mcList中读取棋子位置
|
||
let pieceRow = gameState.mcList[2]
|
||
let pieceCol = gameState.mcList[3]
|
||
|
||
// 选择棋子
|
||
gameState.selectedPiece = (pieceRow, pieceCol)
|
||
calculateAvailableMoves(from: (pieceRow, pieceCol))
|
||
gameState.gamePhase = .movePiece
|
||
|
||
addHistory("蒙特卡洛方法 选择了棋子 (\(pieceRow),\(pieceCol))")
|
||
}
|
||
private func mcMove(availableMovesList: [(Int, Int)], selected: (Int, Int))
|
||
{
|
||
// 检查mcList是否有效
|
||
guard gameState.mcList.count >= 8,
|
||
gameState.mcList[0] == gameState.roundNum,
|
||
gameState.mcList[2] == selected.0,
|
||
gameState.mcList[3] == selected.1
|
||
else {
|
||
// 如果无效,使用随机移动
|
||
randomMove(
|
||
availableMovesList: availableMovesList,
|
||
selected: selected
|
||
)
|
||
return
|
||
}
|
||
|
||
// 从mcList中读取目标位置
|
||
let targetRow = gameState.mcList[4]
|
||
let targetCol = gameState.mcList[5]
|
||
|
||
// 检查目标位置是否合法
|
||
if availableMovesList.contains(where: { $0 == (targetRow, targetCol) })
|
||
{
|
||
// 执行移动
|
||
gameState.chessBoard[targetRow][targetCol] =
|
||
gameState.chessBoard[selected.0][selected.1]
|
||
gameState.chessBoard[selected.0][selected.1] = 0
|
||
gameState.selectedPiece = (targetRow, targetCol)
|
||
|
||
calculateAvailableMoves(from: (targetRow, targetCol))
|
||
gameState.gamePhase = .placeArrow
|
||
|
||
addHistory("蒙特卡洛方法 移动棋子到 (\(targetRow),\(targetCol))")
|
||
} else {
|
||
// 如果不合法,使用随机移动
|
||
randomMove(
|
||
availableMovesList: availableMovesList,
|
||
selected: selected
|
||
)
|
||
}
|
||
}
|
||
private func mcPlace(obstacleType: Int) {
|
||
// 检查mcList是否有效
|
||
guard gameState.mcList.count >= 8,
|
||
gameState.mcList[0] == gameState.roundNum
|
||
else {
|
||
// 如果无效,使用随机放置
|
||
randomPlace(obstacleType: obstacleType)
|
||
return
|
||
}
|
||
|
||
// 从mcList中读取箭的位置
|
||
let arrowRow = gameState.mcList[6]
|
||
let arrowCol = gameState.mcList[7]
|
||
|
||
// 检查箭位置是否合法
|
||
let availableArrows = getAllAvailableMoves()
|
||
if availableArrows.contains(where: { $0 == (arrowRow, arrowCol) }) {
|
||
// 放置障碍物
|
||
gameState.chessBoard[arrowRow][arrowCol] = obstacleType
|
||
addHistory("蒙特卡洛方法 放置障碍物在 (\(arrowRow),\(arrowCol))")
|
||
} else {
|
||
// 如果不合法,使用随机放置
|
||
randomPlace(obstacleType: obstacleType)
|
||
}
|
||
|
||
}
|
||
// MARK: - 自动下棋(第三版)(已修复)
|
||
func autoOperate(player isBlack: Bool) {
|
||
if gameState.isGameOver || isBlack != gameState.blackRound {
|
||
return
|
||
}
|
||
guard
|
||
gameState.blackRound == isBlack
|
||
&& (isBlack
|
||
? gameState.blackAutoOperate : gameState.whiteAutoOperate)
|
||
else { return }
|
||
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||
let obstacleType = isBlack ? 3 : 4
|
||
|
||
switch self.gameState.gamePhase {
|
||
case .selectPiece:
|
||
// 根据策略执行自动下棋
|
||
if gameState.blackRound {
|
||
gameState.roundNum += 1
|
||
}
|
||
switch isBlack
|
||
? self.gameState.blackStrategy
|
||
: self.gameState.whiteStrategy
|
||
{
|
||
case "纯随机下棋":
|
||
randomSelect()
|
||
case "蒙特卡洛方法":
|
||
mcSelect()
|
||
default:
|
||
print("未选中策略,棋局冻结")
|
||
return
|
||
}
|
||
// 延迟。。
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||
// 在继续前检查是否游戏结束
|
||
if self.gameState.isGameOver {
|
||
return
|
||
}
|
||
self.autoOperate(
|
||
player: self.gameState.blackRound
|
||
)
|
||
}
|
||
|
||
//冗余 if !foundValidPiece {
|
||
// // 这里实际上就是胜利条件,直接处理胜利
|
||
// let winner = isBlack ? "白方" : "黑方"
|
||
// self.addHistory("自动随机下棋 无有效棋子可移动,\(winner)胜利")// 最老的判断胜利条件
|
||
// _ = self.checkAndHandleWinCondition()
|
||
// }
|
||
|
||
case .movePiece:
|
||
guard let selected = self.gameState.selectedPiece else {
|
||
return
|
||
}
|
||
|
||
let availableMovesList = self.getAllAvailableMoves()
|
||
switch isBlack
|
||
? self.gameState.blackStrategy
|
||
: self.gameState.whiteStrategy
|
||
{
|
||
case "纯随机下棋":
|
||
randomMove(
|
||
availableMovesList: availableMovesList,
|
||
selected: selected
|
||
)
|
||
case "蒙特卡洛方法":
|
||
mcMove(
|
||
availableMovesList: availableMovesList,
|
||
selected: selected
|
||
)
|
||
default:
|
||
print("未选中策略,棋局冻结")
|
||
return
|
||
}
|
||
// 延迟。。。
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||
// 在继续前检查是否游戏结束
|
||
if self.gameState.isGameOver {
|
||
return
|
||
}
|
||
self.autoOperate(player: isBlack)
|
||
}
|
||
case .placeArrow:
|
||
switch isBlack
|
||
? self.gameState.blackStrategy
|
||
: self.gameState.whiteStrategy
|
||
{
|
||
case "纯随机下棋":
|
||
randomPlace(obstacleType: obstacleType)
|
||
case "蒙特卡洛方法":
|
||
mcPlace(obstacleType: obstacleType)
|
||
default:
|
||
print("未选中策略,棋局冻结")
|
||
return
|
||
}
|
||
// 🎯 在放置障碍物后立即检查胜利条件
|
||
let isGameOver = self.checkAndHandleWinCondition()
|
||
|
||
if isGameOver {
|
||
// 游戏结束,清理状态但不切换回合
|
||
self.gameState.selectedPiece = nil
|
||
self.gameState.availableMoves = Array(
|
||
repeating: Array(repeating: 0, count: 8),
|
||
count: 8
|
||
)
|
||
self.gameState.gamePhase = .selectPiece
|
||
self.gameState.unsavable = false
|
||
// 注意:这里不切换 blackRound,因为游戏已结束
|
||
} else {
|
||
// 游戏继续,正常切换回合
|
||
self.gameState.selectedPiece = nil
|
||
self.gameState.availableMoves = Array(
|
||
repeating: Array(repeating: 0, count: 8),
|
||
count: 8
|
||
)
|
||
self.gameState.gamePhase = .selectPiece
|
||
self.gameState.blackRound.toggle()
|
||
self.gameState.unsavable = false
|
||
// 又是延迟。。
|
||
DispatchQueue.main.asyncAfter(
|
||
deadline: .now() + 0.3
|
||
) {
|
||
// 在继续前检查是否游戏结束
|
||
if self.gameState.isGameOver {
|
||
return
|
||
}
|
||
if self.gameState.blackRound
|
||
&& self.gameState.blackAutoOperate
|
||
{
|
||
self.autoOperate(player: true)
|
||
} else if !self.gameState.blackRound
|
||
&& self.gameState.whiteAutoOperate
|
||
{
|
||
self.autoOperate(player: false)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// MARK: 随机3步骤(第四版)(已修复)(稳定版)
|
||
private func randomSelect() {
|
||
// 注意:下方代码可替换
|
||
let availablePieces = self.getAllAvailablePieces()
|
||
let shuffledPieces = availablePieces.shuffled()
|
||
for piece in shuffledPieces {
|
||
self.calculateAvailableMoves(from: piece)
|
||
let availableMovesList = self.getAllAvailableMoves()
|
||
|
||
if !availableMovesList.isEmpty {
|
||
self.gameState.selectedPiece = piece // 必须要有!!!!!
|
||
self.gameState.gamePhase = .movePiece // 必须要有!!!!!
|
||
self.addHistory(
|
||
"自动随机下棋 选择了棋子 (\(piece.0),\(piece.1))"
|
||
)
|
||
break
|
||
}
|
||
}
|
||
// 注意:上方代码可替换
|
||
}
|
||
private func randomMove(
|
||
availableMovesList: [(Int, Int)],
|
||
selected: (Int, Int)
|
||
) {
|
||
// 注意:以下代码可替换
|
||
if let randomMove = availableMovesList.randomElement() {
|
||
self.gameState.chessBoard[randomMove.0][randomMove.1] =
|
||
self.gameState.chessBoard[selected.0][selected.1] // 必须要有!!!!!复制棋子到目标位置
|
||
self.gameState.chessBoard[selected.0][selected.1] = 0 // 必须要有!!!!!移除原位的棋子
|
||
self.gameState.selectedPiece = (
|
||
randomMove.0, randomMove.1
|
||
) // 必须要有!!!!!选中移动后到棋子
|
||
|
||
self.calculateAvailableMoves(
|
||
from: (randomMove.0, randomMove.1)
|
||
)
|
||
self.gameState.gamePhase = .placeArrow
|
||
self.addHistory(
|
||
"自动随机下棋 移动棋子到 (\(randomMove.0),\(randomMove.1))"
|
||
)
|
||
} else {
|
||
self.addHistory("自动随机下棋 当前棋子无有效移动,重新选择")
|
||
self.gameState.gamePhase = .selectPiece
|
||
self.gameState.selectedPiece = nil
|
||
self.gameState.availableMoves = Array(
|
||
repeating: Array(repeating: 0, count: 8),
|
||
count: 8
|
||
)
|
||
|
||
}
|
||
// 注意:以上代码可替换
|
||
}
|
||
private func randomPlace(obstacleType: Int) {
|
||
// 注意:以下代码可替换
|
||
let availableArrows = self.getAllAvailableMoves()
|
||
|
||
if let randomArrow = availableArrows.randomElement() {
|
||
self.gameState.chessBoard[randomArrow.0][
|
||
randomArrow.1
|
||
] =
|
||
obstacleType
|
||
|
||
self.addHistory(
|
||
"自动随机下棋 放置障碍物在 (\(randomArrow.0),\(randomArrow.1))"
|
||
)
|
||
|
||
}
|
||
// 注意:以上代码可替换
|
||
}
|
||
// MARK: 局势评估(关键)
|
||
private func controlScore(forBlack: Bool, board: [[Int]], factor: Double)
|
||
-> Double
|
||
{ // 进程管理(并发运行)、设备管理、存储管理、文件管理、用户界面
|
||
let targetPiece = forBlack ? 1 : 2
|
||
var score = 0.0
|
||
// 计算每个棋子的控制区域
|
||
for row in 0..<8 {
|
||
for col in 0..<8 {
|
||
if board[row][col] == targetPiece {
|
||
score +=
|
||
factor
|
||
* Double(
|
||
calculateSimuMoves(from: (row, col), board: board)
|
||
.count
|
||
)
|
||
}
|
||
}
|
||
}
|
||
return score
|
||
}
|
||
private func safetyScore(
|
||
forBlack: Bool,
|
||
board: [[Int]],
|
||
factor: Double,
|
||
factor_surround: Double
|
||
) -> Double {
|
||
let targetPiece = forBlack ? 1 : 2
|
||
var safety = 0.0
|
||
|
||
for row in 0..<8 {
|
||
for col in 0..<8 {
|
||
if board[row][col] == targetPiece {
|
||
// 检查棋子周围是否有出路
|
||
let moves = calculateSimuMoves(
|
||
from: (row, col),
|
||
board: board
|
||
)
|
||
if moves.isEmpty {
|
||
safety -= factor_surround // 棋子被困住
|
||
} else {
|
||
safety += factor * Double(moves.count)
|
||
}
|
||
|
||
// 检查是否靠近棋盘边缘(不利位置)
|
||
let edgeDistance = min(row, 7 - row, col, 7 - col)
|
||
safety += factor * Double(edgeDistance) // 距离边缘越远越安全
|
||
}
|
||
}
|
||
}
|
||
return safety
|
||
}
|
||
private func centerScore(forBlack: Bool, board: [[Int]], factor: Double)
|
||
-> Double
|
||
{
|
||
let targetPiece = forBlack ? 1 : 2
|
||
var centerScore = 0.0
|
||
let centerCells = [(3, 3), (3, 4), (4, 3), (4, 4)]
|
||
|
||
// 检查是否控制中心
|
||
for center in centerCells {
|
||
if board[center.0][center.1] == targetPiece {
|
||
centerScore += factor * 5
|
||
}
|
||
}
|
||
|
||
// 计算棋子距离中心的远近
|
||
for row in 0..<8 {
|
||
for col in 0..<8 {
|
||
if board[row][col] == targetPiece {
|
||
let dx = abs(Double(row) - 3.5)
|
||
let dy = abs(Double(col) - 3.5)
|
||
let distance = sqrt(dx * dx + dy * dy)
|
||
centerScore += factor * (10 - distance) // 越靠近中心得分越高
|
||
}
|
||
}
|
||
}
|
||
|
||
return centerScore
|
||
}
|
||
}
|
||
|
||
// MARK: - 棋盘格子视图
|
||
struct ChessSquareView: View {
|
||
let row: Int
|
||
let column: Int
|
||
let value: Int
|
||
let action: () -> Void
|
||
let blackRound: Bool
|
||
let gamePhase: GamePhase
|
||
let availableMoves: [[Int]]
|
||
|
||
@State private var showContent = true
|
||
|
||
var body: some View {
|
||
ZStack {
|
||
Rectangle()
|
||
.fill(getSquareColor())
|
||
.frame(width: 80, height: 80)
|
||
.overlay(
|
||
Rectangle().stroke(Color.black.opacity(0.2), lineWidth: 1)
|
||
)
|
||
|
||
Button(action: action) {
|
||
ZStack {
|
||
Rectangle()
|
||
.fill(Color.blue.opacity(0.01))
|
||
.frame(width: 75, height: 75)
|
||
|
||
if value == 1 {
|
||
Image(systemName: "person.fill")
|
||
.font(.system(size: 30))
|
||
.opacity(showContent ? 1 : 0)
|
||
} else if value == 2 {
|
||
Image(systemName: "person")
|
||
.font(.system(size: 30))
|
||
.opacity(showContent ? 1 : 0)
|
||
} else if value == 3 {
|
||
Image(systemName: "nosign.app.fill")
|
||
.font(.system(size: 30))
|
||
.opacity(showContent ? 1 : 0)
|
||
} else if value == 4 {
|
||
Image(systemName: "nosign.app")
|
||
.font(.system(size: 30))
|
||
.opacity(showContent ? 1 : 0)
|
||
}
|
||
}
|
||
}
|
||
.disabled(!isInteractive())
|
||
.buttonStyle(PlainButtonStyle())
|
||
}
|
||
.onChange(of: value) { _, _ in
|
||
withAnimation(.easeInOut(duration: 0.3)) {
|
||
showContent = false
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||
withAnimation(.easeInOut(duration: 0.3)) {
|
||
showContent = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.onAppear {
|
||
withAnimation(.easeInOut(duration: 0.3)) {
|
||
showContent = true
|
||
}
|
||
}
|
||
}
|
||
|
||
private func getSquareColor() -> Color {
|
||
if availableMoves[row][column] == 1 {
|
||
return Color.green.opacity(0.5)
|
||
} else if availableMoves[row][column] == 2 {
|
||
return Color.red.opacity(0.5)
|
||
} else {
|
||
return (row + column) % 2 == 0
|
||
? Color.white : Color.gray.opacity(0.3)
|
||
}
|
||
}
|
||
|
||
private func isInteractive() -> Bool {
|
||
let currentPlayer = blackRound ? 1 : 2
|
||
|
||
switch gamePhase {
|
||
case .selectPiece:
|
||
return value == currentPlayer
|
||
case .movePiece:
|
||
return availableMoves[row][column] == 1 || value == currentPlayer
|
||
case .placeArrow:
|
||
return availableMoves[row][column] == 1
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 棋盘行视图
|
||
struct ChessBoardRowView: View {
|
||
let row: Int
|
||
let chessBoard: [[Int]]
|
||
let onSquareTap: (Int, Int) -> Void
|
||
let blackRound: Bool
|
||
let gamePhase: GamePhase
|
||
let availableMoves: [[Int]]
|
||
|
||
var body: some View {
|
||
HStack(spacing: 0) {
|
||
ForEach(0..<8, id: \.self) { column in
|
||
ChessSquareView(
|
||
row: row,
|
||
column: column,
|
||
value: chessBoard[row][column],
|
||
action: { onSquareTap(row, column) },
|
||
blackRound: blackRound,
|
||
gamePhase: gamePhase,
|
||
availableMoves: availableMoves
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 棋盘视图
|
||
struct ChessBoardView: View {
|
||
let chessBoard: [[Int]]
|
||
let onSquareTap: (Int, Int) -> Void
|
||
let blackRound: Bool
|
||
let gamePhase: GamePhase
|
||
let availableMoves: [[Int]]
|
||
|
||
var body: some View {
|
||
VStack(spacing: 0) {
|
||
HStack(spacing: 0) {
|
||
Rectangle()
|
||
.fill(Color.clear)
|
||
.frame(width: 30, height: 30)
|
||
|
||
ForEach(0..<8, id: \.self) { column in
|
||
Text(String(column))
|
||
.font(.system(size: 16, weight: .medium))
|
||
.frame(width: 80, height: 30)
|
||
.foregroundColor(.primary)
|
||
}
|
||
}
|
||
|
||
HStack(spacing: 0) {
|
||
VStack(spacing: 0) {
|
||
ForEach(0..<8, id: \.self) { row in
|
||
Text(String(row))
|
||
.font(.system(size: 16, weight: .medium))
|
||
.frame(width: 30, height: 80)
|
||
.foregroundColor(.primary)
|
||
}
|
||
}
|
||
|
||
VStack(spacing: 0) {
|
||
ForEach(0..<8, id: \.self) { row in
|
||
ChessBoardRowView(
|
||
row: row,
|
||
chessBoard: chessBoard,
|
||
onSquareTap: onSquareTap,
|
||
blackRound: blackRound,
|
||
gamePhase: gamePhase,
|
||
availableMoves: availableMoves
|
||
)
|
||
}
|
||
}
|
||
.overlay(Rectangle().stroke(Color.black, lineWidth: 2))
|
||
.padding()
|
||
}
|
||
}
|
||
.frame(width: 800)
|
||
}
|
||
}
|
||
|
||
// MARK: - 菜单按钮视图
|
||
struct MenuButtonView: View {
|
||
let title: String
|
||
let icon: String
|
||
let action: () -> Void
|
||
let disabled: Bool
|
||
|
||
var body: some View {
|
||
Button(action: action) {
|
||
Label(title, systemImage: icon)
|
||
.font(.system(size: 15))
|
||
.frame(maxWidth: 150)
|
||
}
|
||
.disabled(disabled)
|
||
.buttonStyle(.bordered)
|
||
}
|
||
}
|
||
|
||
// MARK: - 左侧栏视图
|
||
struct SidebarView: View {
|
||
@EnvironmentObject var gameState: GameState
|
||
|
||
var body: some View {
|
||
VStack {
|
||
if gameState.isPlaying {
|
||
gameInfoView
|
||
} else {
|
||
Text("请先打开一个存档,或开启新游戏……")
|
||
.font(.system(size: 25, weight: .medium))
|
||
Spacer()
|
||
}
|
||
}
|
||
.frame(width: 320)
|
||
}
|
||
|
||
private var gameInfoView: some View {
|
||
VStack {
|
||
Spacer()
|
||
.frame(height: 5)
|
||
HStack {
|
||
Image(systemName: "folder")
|
||
.font(.system(size: 10))
|
||
.foregroundStyle(.secondary)
|
||
if gameState.withFile {
|
||
Text("当前存档文件:\(gameState.currentUrl)")
|
||
.font(.system(size: 10))
|
||
.foregroundStyle(.secondary)
|
||
} else {
|
||
Text("新游戏:尚未存档")
|
||
.font(.system(size: 10))
|
||
.foregroundStyle(.secondary)
|
||
}
|
||
}
|
||
HStack {
|
||
Image(systemName: "flag.filled.and.flag.crossed")
|
||
.font(.system(size: 25))
|
||
.foregroundStyle(.primary)
|
||
Text("当前行动方:")
|
||
.font(.system(size: 25, weight: .medium))
|
||
if gameState.blackRound {
|
||
HStack {
|
||
Image(systemName: "person.fill")
|
||
.font(.system(size: 25))
|
||
.foregroundStyle(.primary)
|
||
Text("黑方")
|
||
.font(.system(size: 25, weight: .medium))
|
||
}
|
||
} else {
|
||
HStack {
|
||
Image(systemName: "person")
|
||
.font(.system(size: 25))
|
||
.foregroundStyle(.primary)
|
||
Text("白方")
|
||
.font(.system(size: 25, weight: .medium))
|
||
}
|
||
}
|
||
}
|
||
HStack {
|
||
Image(systemName: "number")
|
||
.font(.system(size: 20))
|
||
.foregroundStyle(.primary)
|
||
Text("当前回合数:\(gameState.roundNum)")
|
||
.font(.system(size: 20, weight: .medium))
|
||
}
|
||
HStack {
|
||
Image(systemName: "flag.circle")
|
||
.font(.system(size: 15))
|
||
Text("当前行动阶段:")
|
||
.font(.system(size: 15))
|
||
|
||
switch gameState.gamePhase {
|
||
case .selectPiece:
|
||
HStack {
|
||
if gameState.blackRound {
|
||
Image(systemName: "person.fill")
|
||
.font(.system(size: 15))
|
||
} else {
|
||
Image(systemName: "person")
|
||
.font(.system(size: 15))
|
||
}
|
||
Text("选择棋子")
|
||
.font(.system(size: 15))
|
||
}
|
||
case .movePiece:
|
||
HStack {
|
||
if gameState.blackRound {
|
||
Image(systemName: "arrow.right.circle.fill")
|
||
.font(.system(size: 15))
|
||
} else {
|
||
Image(systemName: "arrow.right.circle")
|
||
.font(.system(size: 15))
|
||
}
|
||
Text("移动棋子")
|
||
.font(.system(size: 15))
|
||
}
|
||
case .placeArrow:
|
||
HStack {
|
||
if gameState.blackRound {
|
||
Image(systemName: "nosign.app.fill")
|
||
.font(.system(size: 15))
|
||
} else {
|
||
Image(systemName: "nosign.app")
|
||
.font(.system(size: 15))
|
||
}
|
||
Text("放置障碍物")
|
||
.font(.system(size: 15))
|
||
}
|
||
}
|
||
}
|
||
HStack {
|
||
Image(systemName: "clock")
|
||
.font(.system(size: 15))
|
||
Text("历史操作:")
|
||
.font(.system(size: 15))
|
||
}
|
||
|
||
// 修复List使用方式
|
||
List(gameState.history) { entry in
|
||
Text(entry.description)
|
||
.font(.system(size: 12))
|
||
}
|
||
.frame(height: 240)
|
||
}
|
||
}
|
||
}
|
||
// 独立的滑块组件,可复用
|
||
struct ParameterSliderView: View {
|
||
@Binding var value: Double
|
||
let title: String
|
||
let description: String
|
||
let range: ClosedRange<Double>
|
||
let step: Double
|
||
|
||
var body: some View {
|
||
VStack(alignment: .leading, spacing: 8) {
|
||
// 标题和当前值
|
||
HStack {
|
||
Text(title)
|
||
.font(.headline)
|
||
Spacer()
|
||
Text("\(value, specifier: "%.1f")")
|
||
.font(.headline)
|
||
.foregroundColor(.blue)
|
||
}
|
||
|
||
// 滑块
|
||
Slider(value: $value, in: range, step: step)
|
||
.accentColor(.blue)
|
||
|
||
// 说明文字
|
||
Text(description)
|
||
.font(.caption)
|
||
.foregroundColor(.secondary)
|
||
.fixedSize(horizontal: false, vertical: true)
|
||
}
|
||
.padding(.vertical, 8)
|
||
}
|
||
}
|
||
|
||
// 游戏中的参数调节面板优化
|
||
struct ParameterSettingsView: View {
|
||
@Binding var controlFactor: Double
|
||
@Binding var safetyFactor: Double
|
||
@Binding var surroundFactor: Double
|
||
@Binding var centerFactor: Double
|
||
let isBlack: Bool
|
||
|
||
var body: some View {
|
||
VStack(alignment: .leading, spacing: 16) {
|
||
// 标题
|
||
HStack {
|
||
Image(
|
||
systemName: isBlack
|
||
? "arrowshape.left.arrowshape.right.fill"
|
||
: "arrowshape.left.arrowshape.right"
|
||
)
|
||
.font(.system(size: 16))
|
||
Text("\(isBlack ? "黑方" : "白方")蒙特卡洛参数")
|
||
.font(.headline)
|
||
}
|
||
.padding(.bottom, 8)
|
||
|
||
// 滑块组
|
||
ParameterSliderView(
|
||
value: $controlFactor,
|
||
title: "控制分数倍率",
|
||
description: "调高使算法更重视可用移动数目",
|
||
range: 1...10,
|
||
step: 0.1
|
||
)
|
||
|
||
ParameterSliderView(
|
||
value: $safetyFactor,
|
||
title: "安全分数倍率",
|
||
description: "调高使算法更重视不被困住",
|
||
range: 1...10,
|
||
step: 0.1
|
||
)
|
||
|
||
ParameterSliderView(
|
||
value: $surroundFactor,
|
||
title: "包围惩罚系数",
|
||
description: "棋子被完全包围时的惩罚值",
|
||
range: 5...100,
|
||
step: 0.5
|
||
)
|
||
|
||
ParameterSliderView(
|
||
value: $centerFactor,
|
||
title: "中心分数倍率",
|
||
description: "调高使算法更重视占领棋盘中心",
|
||
range: 1...10,
|
||
step: 0.1
|
||
)
|
||
|
||
// 快速设置按钮
|
||
HStack(spacing: 12) {
|
||
Button("均衡型") {
|
||
controlFactor = 1.0
|
||
safetyFactor = 1.0
|
||
surroundFactor = 10.0
|
||
centerFactor = 1.0
|
||
}
|
||
.buttonStyle(.bordered)
|
||
.font(.caption)
|
||
|
||
Button("进攻型") {
|
||
controlFactor = 1.6
|
||
safetyFactor = 0.6
|
||
surroundFactor = 6.0
|
||
centerFactor = 1.6
|
||
}
|
||
.buttonStyle(.bordered)
|
||
.font(.caption)
|
||
|
||
Button("防守型") {
|
||
controlFactor = 0.6
|
||
safetyFactor = 1.6
|
||
surroundFactor = 16.0
|
||
centerFactor = 0.6
|
||
}
|
||
.buttonStyle(.bordered)
|
||
.font(.caption)
|
||
}
|
||
.padding(.top, 8)
|
||
}
|
||
.padding()
|
||
.background(Color.gray.opacity(0.05))
|
||
.cornerRadius(12)
|
||
}
|
||
}
|
||
// MARK: - 右侧栏视图
|
||
struct RightView: View {
|
||
@EnvironmentObject var gameState: GameState
|
||
let gameManager: GameManager
|
||
let options = ["纯随机下棋", "蒙特卡洛方法"]
|
||
|
||
var body: some View {
|
||
VStack {
|
||
Spacer()
|
||
.frame(height: 5)
|
||
|
||
if gameState.isPlaying {
|
||
// 标题栏(固定)
|
||
VStack(spacing: 8) {
|
||
HStack {
|
||
Image(systemName: "command")
|
||
.font(.system(size: 25))
|
||
.foregroundStyle(.primary)
|
||
Text("自动下棋控制面板")
|
||
.font(.system(size: 25))
|
||
.foregroundStyle(.primary)
|
||
}
|
||
.padding(.top, 12)
|
||
|
||
Divider()
|
||
.padding(.horizontal)
|
||
}
|
||
.background(Color(nsColor: .windowBackgroundColor))
|
||
ScrollView {
|
||
// 黑方设置
|
||
HStack {
|
||
Image(systemName: "command.circle.fill")
|
||
.font(.system(size: 15))
|
||
Toggle("黑方自动下棋", isOn: $gameState.blackAutoOperate)
|
||
.disabled(gameState.isGameOver)
|
||
.onChange(of: gameState.blackAutoOperate) {
|
||
_,
|
||
newValue in
|
||
if newValue && gameState.blackRound {
|
||
gameManager.autoOperate(player: true)
|
||
}
|
||
}
|
||
}
|
||
.padding(.horizontal)
|
||
|
||
HStack {
|
||
Image(systemName: "gear.circle.fill")
|
||
.font(.system(size: 15))
|
||
Picker("自动下棋策略", selection: $gameState.blackStrategy) {
|
||
ForEach(options, id: \.self) { option in
|
||
Text(option).tag(option)
|
||
}
|
||
}
|
||
.disabled(gameState.isGameOver)
|
||
.frame(maxWidth: 250)
|
||
}
|
||
.padding(.horizontal)
|
||
|
||
// 只有当选择蒙特卡洛时才显示参数
|
||
if gameState.blackStrategy == "蒙特卡洛方法"
|
||
&& !gameState.isGameOver
|
||
{
|
||
ParameterSettingsView(
|
||
controlFactor: $gameState.blackControlFactor,
|
||
safetyFactor: $gameState.blackSafetyFactor,
|
||
surroundFactor: $gameState.blackSurroundFactor,
|
||
centerFactor: $gameState.blackCenterFactor,
|
||
isBlack: true
|
||
)
|
||
.padding(.horizontal)
|
||
}
|
||
|
||
Divider()
|
||
.padding(.horizontal)
|
||
|
||
// 白方设置
|
||
HStack {
|
||
Image(systemName: "command.circle")
|
||
.font(.system(size: 15))
|
||
Toggle("白方自动下棋", isOn: $gameState.whiteAutoOperate)
|
||
.disabled(gameState.isGameOver)
|
||
.onChange(of: gameState.whiteAutoOperate) {
|
||
_,
|
||
newValue in
|
||
if newValue && !gameState.blackRound {
|
||
gameManager.autoOperate(player: false)
|
||
}
|
||
}
|
||
}
|
||
.padding(.horizontal)
|
||
|
||
HStack {
|
||
Image(systemName: "gear.circle")
|
||
Picker("自动下棋策略", selection: $gameState.whiteStrategy) {
|
||
ForEach(options, id: \.self) { option in
|
||
Text(option).tag(option)
|
||
}
|
||
}
|
||
.disabled(gameState.isGameOver)
|
||
.frame(maxWidth: 250)
|
||
}
|
||
.padding(.horizontal)
|
||
|
||
// 只有当选择蒙特卡洛时才显示参数
|
||
if gameState.whiteStrategy == "蒙特卡洛方法"
|
||
&& !gameState.isGameOver
|
||
{
|
||
ParameterSettingsView(
|
||
controlFactor: $gameState.whiteControlFactor,
|
||
safetyFactor: $gameState.whiteSafetyFactor,
|
||
surroundFactor: $gameState.whiteSurroundFactor,
|
||
centerFactor: $gameState.whiteCenterFactor,
|
||
isBlack: false
|
||
)
|
||
.padding(.horizontal)
|
||
}
|
||
|
||
Spacer()
|
||
}
|
||
} else {
|
||
Text("开始游戏以展示自动操作选项……")
|
||
.font(.system(size: 25, weight: .medium))
|
||
.foregroundColor(.secondary)
|
||
.multilineTextAlignment(.center)
|
||
.padding()
|
||
Spacer()
|
||
}
|
||
}
|
||
|
||
.frame(width: 280)
|
||
}
|
||
}
|
||
|
||
// MARK: - 主菜单视图
|
||
struct MainMenuView: View {
|
||
@EnvironmentObject var gameState: GameState
|
||
let gameManager: GameManager
|
||
|
||
@State private var showNewGameDoubleCheck = false
|
||
@State private var showLoadDoubleCheck = false
|
||
@State private var showQuitDoubleCheck = false
|
||
|
||
var body: some View {
|
||
VStack {
|
||
Spacer()
|
||
.frame(height: 50)
|
||
HStack {
|
||
Image(systemName: "list.bullet")
|
||
.font(.system(size: 20))
|
||
.foregroundStyle(.primary)
|
||
|
||
Text("Amazon棋 菜单")
|
||
.font(.system(size: 20))
|
||
.foregroundStyle(.primary)
|
||
}
|
||
HStack {
|
||
Image(systemName: "info")
|
||
.font(.system(size: 20))
|
||
.foregroundStyle(.secondary)
|
||
VStack {
|
||
Text("2025秋 计算概论A 大作业")
|
||
.font(.system(size: 10))
|
||
.foregroundStyle(.secondary)
|
||
Text("陈奕辰 数学科学学院 2500010834")
|
||
.font(.system(size: 10))
|
||
.foregroundStyle(.secondary)
|
||
}
|
||
}
|
||
Spacer()
|
||
.frame(height: 15)
|
||
|
||
if gameState.unsavable {
|
||
MenuButtonView(
|
||
title: "新游戏",
|
||
icon: "plus",
|
||
action: newGame,
|
||
disabled: false
|
||
)
|
||
} else {
|
||
MenuButtonView(
|
||
title: "新游戏",
|
||
icon: "plus",
|
||
action: { showNewGameDoubleCheck = true },
|
||
disabled: false
|
||
)
|
||
}
|
||
|
||
Spacer().frame(height: 15)
|
||
|
||
if gameState.isPlaying {
|
||
if gameState.unsavable {
|
||
MenuButtonView(
|
||
title: "已保存",
|
||
icon: "square.and.arrow.down",
|
||
action: quickSave,
|
||
disabled: true
|
||
)
|
||
} else {
|
||
MenuButtonView(
|
||
title: "快速保存",
|
||
icon: "square.and.arrow.down",
|
||
action: quickSave,
|
||
disabled: !gameState.withFile
|
||
)
|
||
Spacer().frame(height: 15)
|
||
MenuButtonView(
|
||
title: "另存为",
|
||
icon: "document.badge.plus",
|
||
action: saveGame,
|
||
disabled: false
|
||
)
|
||
}
|
||
Spacer().frame(height: 15)
|
||
}
|
||
|
||
if gameState.unsavable {
|
||
MenuButtonView(
|
||
title: "读取",
|
||
icon: "folder",
|
||
action: loadGame,
|
||
disabled: false
|
||
)
|
||
Spacer().frame(height: 15)
|
||
MenuButtonView(
|
||
title: "退出全部",
|
||
icon: "xmark",
|
||
action: quitGame,
|
||
disabled: false
|
||
)
|
||
} else {
|
||
MenuButtonView(
|
||
title: "读取",
|
||
icon: "folder",
|
||
action: { showLoadDoubleCheck = true },
|
||
disabled: false
|
||
)
|
||
Spacer().frame(height: 15)
|
||
MenuButtonView(
|
||
title: "退出全部",
|
||
icon: "xmark",
|
||
action: { showQuitDoubleCheck = true },
|
||
disabled: false
|
||
)
|
||
}
|
||
}
|
||
.alert("舍弃当前棋局并创建新棋局?", isPresented: $showNewGameDoubleCheck) {
|
||
Button("返回", role: .cancel) {}
|
||
Button("继续", role: .destructive) { newGame() }
|
||
} message: {
|
||
Text("当前棋局尚未保存,是否继续创建新游戏?")
|
||
}
|
||
.alert("舍弃当前棋局并读取?", isPresented: $showLoadDoubleCheck) {
|
||
Button("返回", role: .cancel) {}
|
||
Button("继续", role: .destructive) { loadGame() }
|
||
} message: {
|
||
Text("当前棋局尚未保存,是否继续读取其他棋局?")
|
||
}
|
||
.alert("舍弃当前棋局并退出?", isPresented: $showQuitDoubleCheck) {
|
||
Button("返回", role: .cancel) {}
|
||
Button("继续", role: .destructive) { quitGame() }
|
||
} message: {
|
||
Text("当前棋局尚未保存,是否继续退出?")
|
||
}
|
||
Spacer().frame(height: 20)
|
||
}
|
||
|
||
private func newGame() {
|
||
gameManager.initializeChessBoard()
|
||
gameState.isPlaying = true
|
||
gameState.unsavable = false
|
||
gameState.withFile = false
|
||
gameState.blackRound = true
|
||
}
|
||
|
||
private func saveGame() {
|
||
gameManager.saveGame()
|
||
}
|
||
|
||
private func loadGame() {
|
||
gameManager.loadGame()
|
||
}
|
||
|
||
private func quickSave() {
|
||
let url = URL(fileURLWithPath: gameState.currentUrl)
|
||
_ = gameManager.saveChessBoardToJSON(url: url)
|
||
gameState.unsavable = true
|
||
}
|
||
|
||
private func quitGame() {
|
||
NSApplication.shared.terminate(nil)
|
||
}
|
||
}
|
||
|
||
// MARK: - 主视图
|
||
struct ContentView: View {
|
||
@StateObject private var gameState = GameState()
|
||
private var gameManager: GameManager {
|
||
GameManager(gameState: gameState)
|
||
}
|
||
|
||
var body: some View {
|
||
HStack(spacing: 0) {
|
||
VStack {
|
||
SidebarView()
|
||
.environmentObject(gameState)
|
||
|
||
Rectangle()
|
||
.fill(Color.gray.opacity(0.3))
|
||
.frame(height: 1)
|
||
|
||
MainMenuView(gameManager: gameManager)
|
||
.environmentObject(gameState)
|
||
}
|
||
|
||
Rectangle()
|
||
.fill(Color.gray.opacity(0.3))
|
||
.frame(width: 1)
|
||
.padding()
|
||
|
||
ChessBoardView(
|
||
chessBoard: gameState.chessBoard,
|
||
onSquareTap: { row, column in
|
||
gameManager.handleSquareTap(row: row, column: column)
|
||
},
|
||
blackRound: gameState.blackRound,
|
||
gamePhase: gameState.gamePhase,
|
||
availableMoves: gameState.availableMoves
|
||
)
|
||
|
||
Rectangle()
|
||
.fill(Color.gray.opacity(0.3))
|
||
.frame(width: 1)
|
||
.padding()
|
||
RightView(gameManager: gameManager)
|
||
.environmentObject(gameState)
|
||
}
|
||
.onAppear {
|
||
setWindowTitle()
|
||
}
|
||
}
|
||
|
||
private func setWindowTitle() {
|
||
if let window = NSApplication.shared.windows.first {
|
||
window.title = "这个大アサ的作者数分I期中考了25分高分"
|
||
}
|
||
}
|
||
}
|
||
@main
|
||
struct oasa25App:App
|
||
{
|
||
var body: some Scene
|
||
{
|
||
WindowGroup
|
||
{
|
||
ContentView()
|
||
}
|
||
}
|
||
}
|
||
```
|
||
## 注
|
||
和我之前发的[计算概论A 2025年秋 大作业](http://cirrus.org.cn/p/计算概论a-2025年秋-大作业/)差不多,只是把.app文件编译成了一个单独的二进制文件。 |