Commit f7e91492 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: weak bots now actually play weak — MultiPV suboptimal move selection

Before: even amina (skill 1) played near-optimal moves because Stockfish
at depth 3 still finds good moves. Blunders just reduced depth to 1 which
barely changes anything in simple positions.

Now:
- Blunder rolls: analyze 5 lines and pick from the worst (3rd-5th best)
- Weak bots (skill <= 5): use MultiPV and randomly pick suboptimal moves
  - Skill 1-2 (amina): NEVER plays the best move, picks from 2nd-5th line
  - Skill 3-5 (tarek): 40% best move, 60% picks from worse options
- Strong bots (skill > 5): unchanged, use standard depth/skill search
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 37a67945
......@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"log"
"math/rand"
"net/http"
"time"
......@@ -49,32 +50,101 @@ func HandleGetMove(w http.ResponseWriter, r *http.Request) {
return
}
start := time.Now()
isBlunder := bot.ShouldBlunder()
depth := bot.Depth
skillLevel := bot.SkillLevel
contempt := bot.Contempt
// For blunder-prone bots, reduce depth dramatically on blunder rolls
if bot.ShouldBlunder() {
depth = 1
skillLevel = 0
}
timeLimitMs := body.TimeLimitMs
if timeLimitMs <= 0 {
timeLimitMs = 0 // use depth-based search
timeLimitMs = 0
}
req := engine.MoveRequest{
FEN: body.FEN,
Depth: depth,
SkillLevel: skillLevel,
Contempt: contempt,
TimeLimitMs: timeLimitMs,
MultiPV: 1,
var resp *engine.MoveResponse
var err error
if isBlunder {
// On blunder: get multiple lines and pick a bad one
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
lines, analyzeErr := engine.Analyze(ctx, body.FEN, 6, 5)
if analyzeErr == nil && len(lines) > 1 {
// Pick from the worst options (index 2-4 if available)
pickIdx := len(lines) - 1
if pickIdx > 1 {
pickIdx = 2 + rand.Intn(len(lines)-2)
if pickIdx >= len(lines) {
pickIdx = len(lines) - 1
}
}
resp = &engine.MoveResponse{
BestMove: lines[pickIdx].Move,
Evaluation: lines[pickIdx].Evaluation,
Depth: lines[pickIdx].Depth,
}
log.Printf("BLUNDER bot=%s picked line %d/%d move=%s eval=%.2f (best was %s eval=%.2f)",
body.BotID, pickIdx+1, len(lines), resp.BestMove, resp.Evaluation, lines[0].Move, lines[0].Evaluation)
} else {
// Fallback: depth 1 search
req := engine.MoveRequest{FEN: body.FEN, Depth: 1, SkillLevel: 0, MultiPV: 1}
resp, err = engine.GetMove(r.Context(), req)
}
} else if bot.SkillLevel <= 5 {
// Weak bots: use MultiPV and randomly pick a suboptimal move
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
numLines := 4
if bot.SkillLevel <= 2 {
numLines = 5
}
lines, analyzeErr := engine.Analyze(ctx, body.FEN, depth, numLines)
if analyzeErr == nil && len(lines) > 1 {
// Weight toward worse moves for weaker bots
// skill 1: pick from index 1-4, skill 5: pick from index 0-2
maxPick := len(lines) - 1
minPick := 0
if bot.SkillLevel <= 2 {
minPick = 1 // never play the best move
if maxPick < 1 {
maxPick = 1
}
} else if bot.SkillLevel <= 5 {
// 40% chance of best, 60% suboptimal
if rand.Float64() > 0.4 && len(lines) > 1 {
minPick = 1
}
}
pickIdx := minPick
if maxPick > minPick {
pickIdx = minPick + rand.Intn(maxPick-minPick+1)
}
resp = &engine.MoveResponse{
BestMove: lines[pickIdx].Move,
Evaluation: lines[pickIdx].Evaluation,
Depth: lines[pickIdx].Depth,
}
log.Printf("WEAK bot=%s skill=%d picked line %d/%d move=%s",
body.BotID, bot.SkillLevel, pickIdx+1, len(lines), resp.BestMove)
} else {
// Fallback to normal search
req := engine.MoveRequest{FEN: body.FEN, Depth: depth, SkillLevel: skillLevel, Contempt: contempt, MultiPV: 1}
resp, err = engine.GetMove(r.Context(), req)
}
} else {
// Normal strength bots: use standard search
req := engine.MoveRequest{
FEN: body.FEN,
Depth: depth,
SkillLevel: skillLevel,
Contempt: contempt,
TimeLimitMs: timeLimitMs,
MultiPV: 1,
}
resp, err = engine.GetMove(r.Context(), req)
}
start := time.Now()
resp, err := engine.GetMove(r.Context(), req)
if err != nil {
log.Printf("ERROR get_move bot=%s fen=%s err=%v", body.BotID, body.FEN, err)
writeJSON(w, http.StatusInternalServerError, ErrorResponse{Error: "engine error: " + err.Error()})
......@@ -95,8 +165,8 @@ func HandleGetMove(w http.ResponseWriter, r *http.Request) {
resp.ThinkTimeMs = time.Since(start).Milliseconds()
log.Printf("MOVE bot=%s depth=%d skill=%d fen=%.40s move=%s eval=%.2f time=%dms",
body.BotID, depth, skillLevel, body.FEN, resp.BestMove, resp.Evaluation, resp.ThinkTimeMs)
log.Printf("MOVE bot=%s depth=%d skill=%d blunder=%v fen=%.40s move=%s eval=%.2f time=%dms",
body.BotID, depth, skillLevel, isBlunder, body.FEN, resp.BestMove, resp.Evaluation, resp.ThinkTimeMs)
writeJSON(w, http.StatusOK, resp)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment