|
|
|
|
|
import uvicorn |
|
|
from fastapi import FastAPI, Query, HTTPException |
|
|
from fastapi.responses import PlainTextResponse |
|
|
import numpy as np |
|
|
import logging |
|
|
import time |
|
|
|
|
|
|
|
|
from game_logic import parse_board_hex, is_game_over |
|
|
from ai import find_best_move, get_dynamic_depth, SEARCH_DEPTH |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
log = logging.getLogger(__name__) |
|
|
|
|
|
app = FastAPI() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/move", response_class=PlainTextResponse) |
|
|
async def get_ai_move(board: str = Query(..., min_length=16, max_length=16, regex="^[0-9a-fA-F]{16}$")): |
|
|
""" |
|
|
Receives a 16-char hex board string, returns the best move 'u','d','l','r' or 'g'. |
|
|
""" |
|
|
log.info(f"Received board: {board}") |
|
|
start_time = time.time() |
|
|
try: |
|
|
board_array = parse_board_hex(board.upper()) |
|
|
|
|
|
if is_game_over(board_array): |
|
|
log.info("Game Over detected.") |
|
|
return "g" |
|
|
|
|
|
|
|
|
depth_to_use = get_dynamic_depth(board_array) |
|
|
|
|
|
|
|
|
log.info(f"Calculating move with depth {depth_to_use}...") |
|
|
move = find_best_move(board_array, depth=depth_to_use) |
|
|
|
|
|
duration = time.time() - start_time |
|
|
log.info(f"Replying with move: '{move}' in {duration:.4f}s") |
|
|
return move |
|
|
|
|
|
except ValueError as ve: |
|
|
log.error(f"Invalid board string: {board} - {ve}") |
|
|
raise HTTPException(status_code=400, detail="Invalid board string format.") |
|
|
except Exception as e: |
|
|
log.error(f"Internal error processing board {board}: {e}", exc_info=True) |
|
|
|
|
|
|
|
|
return "g" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
log.info(f"Starting 2048 AI API with BASE_DEPTH={SEARCH_DEPTH}...") |
|
|
|
|
|
|
|
|
uvicorn.run(app, host="0.0.0.0", port=7860) |
|
|
|