Amogh1221 commited on
Commit
462adda
Β·
verified Β·
1 Parent(s): 918a0da

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +46 -20
main.py CHANGED
@@ -9,8 +9,18 @@ import chess.engine
9
  import asyncio
10
  import json
11
 
12
- app = FastAPI(title="Deepcastle Engine API")
 
 
 
 
 
 
 
 
13
  from contextlib import asynccontextmanager
 
 
14
  class ConnectionManager:
15
  def __init__(self):
16
  # match_id -> list of websockets
@@ -133,6 +143,8 @@ class EnginePool:
133
  await self.engines.put(engine)
134
  self.all_engines.append(engine)
135
  print(f" [+] Engine {i+1}/{self.size} ready.")
 
 
136
  except Exception as e:
137
  print(f" [!] Failed to start engine {i+1}: {e}")
138
 
@@ -169,15 +181,7 @@ class EnginePool:
169
  except:
170
  pass
171
 
172
- pool = EnginePool(size=8)
173
-
174
- @app.on_event("startup")
175
- async def startup_event():
176
- await pool.start()
177
-
178
- @app.on_event("shutdown")
179
- async def shutdown_event():
180
- await pool.stop()
181
 
182
  def get_normalized_score(info) -> tuple[float, Optional[int]]:
183
  """Returns the score from White's perspective in centipawns."""
@@ -257,6 +261,16 @@ def get_win_percentage_from_cp(cp: int) -> float:
257
  win_chances = 2.0 / (1.0 + math.exp(MULTIPLIER * cp_ceiled)) - 1.0
258
  return 50.0 + 50.0 * win_chances
259
 
 
 
 
 
 
 
 
 
 
 
260
  def get_win_percentage(info: dict) -> float:
261
  score = info.get("score")
262
  if not score:
@@ -398,8 +412,8 @@ async def analyze_game(request: AnalyzeRequest):
398
  player_is_white = (request.player_color.lower() == "white")
399
  fen_history = [board.fen()]
400
  move_history = []
401
- total_cpl = 0.0
402
- player_moves_count = 0
403
  current_score, _ = get_normalized_score(infos_before[0])
404
 
405
  for i, san_move in enumerate(request.moves):
@@ -467,12 +481,14 @@ async def analyze_game(request: AnalyzeRequest):
467
  )
468
 
469
  move_gain = score_after - score_before if is_white_turn else score_before - score_after
470
- cpl = max(0, -move_gain)
471
- cpl = min(cpl, 1000.0)
472
-
 
 
473
  if is_player_turn:
474
- total_cpl += cpl
475
- player_moves_count += 1
476
  counts[cls] = counts.get(cls, 0) + 1
477
 
478
  analysis_results.append(MoveAnalysis(
@@ -488,9 +504,18 @@ async def analyze_game(request: AnalyzeRequest):
488
  ))
489
  infos_before = infos_after
490
 
491
- avg_cpl = total_cpl / max(1, player_moves_count)
492
- accuracy = max(10.0, min(100.0, 100.0 * math.exp(-0.005 * avg_cpl)))
493
- estimated_elo = int(max(400, min(3600, 3600 - (avg_cpl * 20))))
 
 
 
 
 
 
 
 
 
494
 
495
  return AnalyzeResponse(
496
  accuracy=round(accuracy, 1),
@@ -505,4 +530,5 @@ async def analyze_game(request: AnalyzeRequest):
505
 
506
  if __name__ == "__main__":
507
  import uvicorn
 
508
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
9
  import asyncio
10
  import json
11
 
12
+ @asynccontextmanager
13
+ async def lifespan(app: FastAPI):
14
+ # Startup: Initialize the engine pool
15
+ await pool.start()
16
+ yield
17
+ # Shutdown: Clean up the engine pool
18
+ await pool.stop()
19
+
20
+ app = FastAPI(title="Deepcastle Engine API", lifespan=lifespan)
21
  from contextlib import asynccontextmanager
22
+
23
+ # ─── Multiplaying / Challenge Manager ──────────────────────────────────────────
24
  class ConnectionManager:
25
  def __init__(self):
26
  # match_id -> list of websockets
 
143
  await self.engines.put(engine)
144
  self.all_engines.append(engine)
145
  print(f" [+] Engine {i+1}/{self.size} ready.")
146
+ # Give the system some room to breathe between processes
147
+ await asyncio.sleep(0.5)
148
  except Exception as e:
149
  print(f" [!] Failed to start engine {i+1}: {e}")
150
 
 
181
  except:
182
  pass
183
 
184
+ pool = EnginePool(size=4)
 
 
 
 
 
 
 
 
185
 
186
  def get_normalized_score(info) -> tuple[float, Optional[int]]:
187
  """Returns the score from White's perspective in centipawns."""
 
261
  win_chances = 2.0 / (1.0 + math.exp(MULTIPLIER * cp_ceiled)) - 1.0
262
  return 50.0 + 50.0 * win_chances
263
 
264
+ def get_move_accuracy(win_pct_before: float, win_pct_after: float, is_white_move: bool) -> float:
265
+ """Lichess-style win%-based per-move accuracy (0–100)."""
266
+ if is_white_move:
267
+ diff = win_pct_before - win_pct_after
268
+ else:
269
+ diff = (100.0 - win_pct_before) - (100.0 - win_pct_after)
270
+
271
+ accuracy = 103.1668 * math.exp(-0.04354 * max(0.0, diff)) - 3.1669
272
+ return max(0.0, min(100.0, accuracy))
273
+
274
  def get_win_percentage(info: dict) -> float:
275
  score = info.get("score")
276
  if not score:
 
412
  player_is_white = (request.player_color.lower() == "white")
413
  fen_history = [board.fen()]
414
  move_history = []
415
+ player_move_accuracies: List[float] = []
416
+ player_cpls: List[float] = [] # keep for estimated_elo
417
  current_score, _ = get_normalized_score(infos_before[0])
418
 
419
  for i, san_move in enumerate(request.moves):
 
481
  )
482
 
483
  move_gain = score_after - score_before if is_white_turn else score_before - score_after
484
+ cpl = max(0.0, min(1000.0, -move_gain))
485
+
486
+ # Lichess-style per-move accuracy using win%
487
+ move_acc = get_move_accuracy(win_pct_before, win_pct_after, is_white_turn)
488
+
489
  if is_player_turn:
490
+ player_move_accuracies.append(move_acc)
491
+ player_cpls.append(cpl)
492
  counts[cls] = counts.get(cls, 0) + 1
493
 
494
  analysis_results.append(MoveAnalysis(
 
504
  ))
505
  infos_before = infos_after
506
 
507
+ # NEW β€” Lichess win%-based accuracy
508
+ if player_move_accuracies:
509
+ # Lichess uses harmonic mean blended with arithmetic mean
510
+ arithmetic_mean = sum(player_move_accuracies) / len(player_move_accuracies)
511
+ harmonic_mean = len(player_move_accuracies) / sum(1.0 / max(a, 0.1) for a in player_move_accuracies)
512
+ accuracy = (arithmetic_mean + harmonic_mean) / 2.0
513
+ else:
514
+ accuracy = 0.0
515
+
516
+ # Elo from avg CPL using exponential decay calibrated to your 3600 engine
517
+ avg_cpl = sum(player_cpls) / max(1, len(player_cpls))
518
+ estimated_elo = int(max(400, min(3600, round(3600 * math.exp(-0.01 * avg_cpl)))))
519
 
520
  return AnalyzeResponse(
521
  accuracy=round(accuracy, 1),
 
530
 
531
  if __name__ == "__main__":
532
  import uvicorn
533
+ # Hugging Face Spaces port is 7860
534
  uvicorn.run(app, host="0.0.0.0", port=7860)