Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,58 @@ struct Response {
- macOS 13.0+
- watchOS 9.0+
- tvOS 16.0+


## 直接執行(Windows / Python)
如果你是 Windows 使用者,現在可以直接用 Python 版本執行:

1. 安裝 Python 3.10+
2. 打開命令提示字元(CMD)並切到專案資料夾
3. 執行:`py python_chesscli\chesscli.py`

範例:
```bash
py python_chesscli\chesscli.py
> move e2e4 3
> move e7e5 3
> best 3
> state
> quit
```

也可以直接雙擊 `python_chesscli\run_chesscli.bat` 啟動。

可用指令:
- `move <uci> [depth]`:輸入一步棋並立即分析
- `best [depth]`:分析目前最佳下一步
- `state`:顯示目前對局狀態
- `help`:顯示說明
- `quit`:離開程式

## Chess 分析器(新功能)
你可以用內建的 `ChessGameAnalyzer` 來記錄每一步棋,並在每一步後拿到建議下法。

```swift
import ChatGPTKit

let analyzer = ChessGameAnalyzer()

// 輸入你和朋友每一步(UCI 格式)
try analyzer.recordMove("e2e4", depth: 3)
try analyzer.recordMove("e7e5", depth: 3)

// 取得目前局面的最佳建議
let analysis = analyzer.suggestBestMove(depth: 3)
print("Best move:", analysis.bestMove?.uci ?? "(none)")
print("Eval:", analysis.evaluation)
print("PV:", analysis.principalVariation.map(\.uci))
```

### 目前支援
- 合法走法驗證
- 對局狀態判斷(進行中、將死、和局)
- Minimax + Alpha-Beta 剪枝的最佳下法搜尋
- UCI 走法輸入(例如 `e2e4`、`e7e8q`)

### 備註
目前為輕量版引擎,尚未包含王車易位(castling)與吃過路兵(en passant)。
1 change: 1 addition & 0 deletions Sources/ChatGPTKit/ChatGPTKit.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import FoundationNetworking

public struct ChatGPTKit {
// Initializer
Expand Down
109 changes: 109 additions & 0 deletions Sources/ChatGPTKit/Chess/ChessAnalyzer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import Foundation

public struct ChessAnalysis {
public let bestMove: ChessMove?
public let evaluation: Int
public let principalVariation: [ChessMove]

public init(bestMove: ChessMove?, evaluation: Int, principalVariation: [ChessMove]) {
self.bestMove = bestMove
self.evaluation = evaluation
self.principalVariation = principalVariation
}
}

public struct MoveReview {
public let playedMove: ChessMove
public let evaluationAfterMove: Int
public let suggestedReply: ChessMove?

public init(playedMove: ChessMove, evaluationAfterMove: Int, suggestedReply: ChessMove?) {
self.playedMove = playedMove
self.evaluationAfterMove = evaluationAfterMove
self.suggestedReply = suggestedReply
}
}

public final class ChessGameAnalyzer {
public private(set) var board: ChessBoard

public init(board: ChessBoard = ChessBoard()) {
self.board = board
}

public func suggestBestMove(depth: Int = 3) -> ChessAnalysis {
let depth = max(1, depth)
let side = board.sideToMove
let result = negamax(board: board, depth: depth, alpha: -1_000_000, beta: 1_000_000, perspective: side)
return ChessAnalysis(bestMove: result.bestMove, evaluation: result.score, principalVariation: result.pv)
}

@discardableResult
public func recordMove(_ uciMove: String, depth: Int = 3) throws -> MoveReview {
guard let move = ChessMove(uci: uciMove) else {
throw ChessEngineError.illegalMove("Invalid move format: \(uciMove)")
}
try board.apply(move: move)

let postMoveEvaluation = board.evaluateMaterialAndMobility(for: board.sideToMove)
let suggestion = suggestBestMove(depth: depth)
return MoveReview(
playedMove: move,
evaluationAfterMove: postMoveEvaluation,
suggestedReply: suggestion.bestMove
)
}

private func negamax(
board: ChessBoard,
depth: Int,
alpha: Int,
beta: Int,
perspective: ChessColor
) -> (score: Int, bestMove: ChessMove?, pv: [ChessMove]) {
var alpha = alpha
let moves = board.legalMoves()

if depth == 0 || moves.isEmpty {
switch board.gameState() {
case .checkmate(let winner):
return (winner == perspective ? 900_000 : -900_000, nil, [])
case .stalemate:
return (0, nil, [])
case .inProgress:
return (board.evaluateMaterialAndMobility(for: perspective), nil, [])
}
}

var bestScore = -1_000_000
var bestMove: ChessMove?
var bestPV = [ChessMove]()

for move in moves {
var next = board
next.makeUncheckedMove(move)

let child = negamax(
board: next,
depth: depth - 1,
alpha: -beta,
beta: -alpha,
perspective: perspective
)
let score = -child.score

if score > bestScore {
bestScore = score
bestMove = move
bestPV = [move] + child.pv
}

alpha = max(alpha, score)
if alpha >= beta {
break
}
}

return (bestScore, bestMove, bestPV)
}
}
Loading