Skip to content

Commit c37b421

Browse files
authored
Adding a file for generating descriptions (v1)
1 parent 942cae8 commit c37b421

1 file changed

Lines changed: 112 additions & 0 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { Chess } from "chess.js";
2+
3+
type StockfishEvals = Record<string, number>; // UCI → eval (white-positive)
4+
type MaiaEvals = Record<string, number[]>; // UCI → 9-length probability array
5+
6+
const A = 1, B = 0.8, EPS = 0.08;
7+
8+
const winRate = (p: number) => 1 / (1 + Math.exp(-(p - A) / B));
9+
const wdl = (p: number) => { const w = winRate(p), l = winRate(-p); return { w, d: 1 - w - l }; };
10+
11+
const legalMovesUci = (fen: string) => {
12+
const c = new Chess(fen);
13+
const s = new Set<string>();
14+
c.moves({ verbose: true }).forEach(m => s.add(m.from + m.to + (m.promotion ?? "")));
15+
return s;
16+
};
17+
18+
export function describePosition(
19+
fen: string,
20+
sf: StockfishEvals,
21+
maia: MaiaEvals,
22+
whiteToMove: boolean,
23+
eps = EPS
24+
): string {
25+
const legal = legalMovesUci(fen);
26+
const moves = Object.keys(sf).filter(m => legal.has(m));
27+
if (!moves.length) return "No legal moves available.";
28+
29+
const seval: Record<string, number> = {};
30+
moves.forEach(m => seval[m] = (whiteToMove ? 1 : -1) * sf[m]);
31+
32+
const opt = moves.reduce((a, b) => seval[a] > seval[b] ? a : b);
33+
const { w: wOpt, d: dOpt } = wdl(seval[opt]);
34+
35+
const good = moves.filter(m => {
36+
const { w, d } = wdl(seval[m]);
37+
return Math.abs(w - wOpt) <= eps && Math.abs(d - dOpt) <= eps;
38+
});
39+
40+
const nGood = good.length;
41+
const abundance =
42+
nGood === 1 ? "only one move"
43+
: nGood === 2 ? "two moves"
44+
: "several moves";
45+
46+
const avgGood = good.reduce((s, m) => s + seval[m], 0) / nGood;
47+
48+
let outcome: string;
49+
if (avgGood > 2.5) outcome = "to cleanly win";
50+
else if (avgGood > 1.0) outcome = "to win";
51+
else if (avgGood > 0.35) outcome = "for an advantage";
52+
else if (avgGood >= -0.35) outcome = "to keep the balance";
53+
else if (avgGood >= -1.0) outcome = "to hold the position";
54+
else outcome = "to stay in the game";
55+
56+
let setLevels = 0, optLevels = 0, temptLevels = 0;
57+
58+
for (let lvl = 0; lvl < 9; lvl++) {
59+
const probs = moves
60+
.map(m => [maia[m]?.[lvl] ?? 0, m] as [number, string])
61+
.sort((a, b) => b[0] - a[0]);
62+
63+
const [p1, m1] = probs[0];
64+
const [p2, m2] = probs[1] ?? [0, ""];
65+
const [p3, m3] = probs[2] ?? [0, ""];
66+
67+
const inGood = good.includes(m1);
68+
if (inGood) setLevels++;
69+
if (m1 === opt) optLevels++;
70+
71+
const nearTop = (prob: number) => (p1 - prob) <= eps;
72+
const tempting =
73+
inGood && (
74+
(m2 && !good.includes(m2) && nearTop(p2)) ||
75+
(m3 && !good.includes(m3) && nearTop(p3))
76+
);
77+
78+
if (tempting) temptLevels++;
79+
}
80+
81+
const tier = (k: number) => k <= 2 ? 0 : k <= 6 ? 1 : 2;
82+
const setTier = tier(setLevels);
83+
const optTier = tier(optLevels);
84+
85+
const phrSet =
86+
setTier === 0 ? "hard for players to find"
87+
: setTier === 1 ? "findable for skilled players"
88+
: "straightforward for players across skill levels to find";
89+
90+
let phrBest =
91+
optTier === 0 ? "hard for players to find"
92+
: optTier === 1 ? "findable for skilled players"
93+
: "straightforward for players across skill levels to find";
94+
95+
if (optTier === 1) phrBest = "only " + phrBest;
96+
97+
const verb = nGood === 1 ? "is" : "are";
98+
const pron = nGood === 1 ? "it is" : "they are";
99+
100+
const hasTempting = setLevels > 0 && temptLevels > setLevels / 2;
101+
const temptText = hasTempting ? " There are also tempting alternatives." : "";
102+
103+
if (nGood === 1) {
104+
return `There ${verb} ${abundance} ${outcome}, and ${pron} ${phrSet}.${temptText}`;
105+
}
106+
107+
if (optTier < setTier) {
108+
return `There ${verb} ${abundance} ${outcome}, and ${pron} ${phrSet}, but the best move is ${phrBest}.${temptText}`;
109+
}
110+
111+
return `There ${verb} ${abundance} ${outcome}, and ${pron} ${phrSet}.${temptText}`;
112+
}

0 commit comments

Comments
 (0)