-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapp.py
More file actions
140 lines (105 loc) · 3.81 KB
/
Copy pathapp.py
File metadata and controls
140 lines (105 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
"""
Flask server for checkers game.
Human plays black (moves first), AI plays white.
"""
import os
from flask import Flask, jsonify, request, render_template
from checkers import CheckersGame, SQ_TO_RC
from ai import Evaluator, choose_move, train_td, FEATURE_NAMES
app = Flask(__name__)
WEIGHTS_PATH = os.path.join(os.path.dirname(__file__), "weights.json")
# Global game state and AI
game = CheckersGame()
evaluator = Evaluator.load(WEIGHTS_PATH)
def reset_game():
global game
game = CheckersGame()
@app.route("/")
def index():
return render_template("index.html")
@app.route("/api/state")
def api_state():
return jsonify(game.to_dict())
@app.route("/api/new_game", methods=["POST"])
def api_new_game():
reset_game()
return jsonify(game.to_dict())
@app.route("/api/legal_moves", methods=["GET"])
def api_legal_moves():
"""Return legal moves for the current player.
Each move is a list of square indices [from, ..., to]."""
moves = game.get_legal_moves()
# Group by source square for the UI
by_source = {}
for move in moves:
src = move[0]
if src not in by_source:
by_source[src] = []
by_source[src].append(move)
return jsonify({"moves": moves, "by_source": by_source})
@app.route("/api/make_move", methods=["POST"])
def api_make_move():
"""Human makes a move. Returns updated state (does NOT trigger AI)."""
data = request.json
move = data.get("move")
if not move:
return jsonify({"error": "No move provided"}), 400
if game.turn != 1:
return jsonify({"error": "Not your turn"}), 400
legal = game.get_legal_moves()
if move not in legal:
return jsonify({"error": "Illegal move"}), 400
game.make_move(move)
return jsonify(game.to_dict())
@app.route("/api/ai_move", methods=["POST"])
def api_ai_move():
"""AI makes a move. Returns its move and updated state."""
if game.turn != -1:
return jsonify({"error": "Not AI's turn"}), 400
over, _ = game.is_game_over()
if over:
return jsonify({"state": game.to_dict(), "ai_move": None})
ai_move = choose_move(game, evaluator, depth=5)
if ai_move:
game.make_move(ai_move)
return jsonify({"state": game.to_dict(), "ai_move": ai_move})
@app.route("/api/train", methods=["POST"])
def api_train():
"""Run self-play training."""
global evaluator
data = request.json or {}
num_games = min(data.get("num_games", 500), 2000)
depth = data.get("depth", 3)
evaluator, stats = train_td(num_games=num_games, depth=depth, verbose=False)
evaluator.save(WEIGHTS_PATH)
return jsonify({
"stats": stats,
"weights": dict(zip(
[f.replace("_", " ") for f in ["piece_count", "king_count", "back_row",
"center_men", "center_kings", "advancement", "mobility",
"opp_mobility", "vulnerable", "protected"]],
[round(w, 4) for w in evaluator.weights]
))
})
@app.route("/api/arena/results")
def api_arena_results():
"""Return saved arena tournament results."""
results_path = os.path.join(os.path.dirname(__file__), "arena_results.json")
try:
with open(results_path) as f:
import json
return jsonify(json.load(f))
except FileNotFoundError:
return jsonify({"error": "No arena results yet. Run: python arena.py"}), 404
@app.route("/api/arena/curve")
def api_arena_curve():
"""Return saved training curve data."""
curve_path = os.path.join(os.path.dirname(__file__), "training_curve.json")
try:
with open(curve_path) as f:
import json
return jsonify(json.load(f))
except FileNotFoundError:
return jsonify({"error": "No training curve yet. Run: python arena.py --curve"}), 404
if __name__ == "__main__":
app.run(port=5050, debug=True)