159 lines
5.8 KiB
JavaScript
159 lines
5.8 KiB
JavaScript
const Weights = {
|
|
async init() {
|
|
await this.refresh();
|
|
},
|
|
|
|
async refresh() {
|
|
let data;
|
|
|
|
try {
|
|
const response = await fetch("/api/chess/weights");
|
|
data = await response.json();
|
|
} catch (err) {
|
|
console.error("❌ Could not load weights.", err);
|
|
document.getElementById("weightsStatus").textContent = "Could not load weights.";
|
|
return;
|
|
}
|
|
|
|
this.renderBoardSet(data.mg, "weightsGridMg");
|
|
this.renderBoardSet(data.eg, "weightsGridEg");
|
|
this.renderFeatures(data.features);
|
|
|
|
const trained = [...data.mg, ...data.eg].some(b => b.squares.some(v => v !== 0))
|
|
|| data.features.some(f => f.value !== 0);
|
|
|
|
document.getElementById("weightsStatus").textContent = trained
|
|
? "Where the learned engine thinks each piece belongs. Blue = preferred, red = avoided. Midgame vs endgame tables are blended by how much material is left."
|
|
: "No training yet — everything is neutral. Run some learned games on the Watch page.";
|
|
},
|
|
|
|
// pieces: [{ name, squares[64] }]. Prepends an "Overall" board summing the set.
|
|
renderBoardSet(pieces, containerId) {
|
|
const overall = new Array(64).fill(0);
|
|
for (const piece of pieces)
|
|
for (let sq = 0; sq < 64; sq++)
|
|
overall[sq] += piece.squares[sq];
|
|
|
|
const boards = [{ name: "Overall", squares: overall }, ...pieces];
|
|
const grid = document.getElementById(containerId);
|
|
grid.innerHTML = "";
|
|
boards.forEach(board => grid.appendChild(this.buildBoard(board)));
|
|
},
|
|
|
|
buildBoard(board) {
|
|
const wrapper = document.createElement("div");
|
|
wrapper.className = "weightBoard";
|
|
|
|
const maxAbs = board.squares.reduce((m, v) => Math.max(m, Math.abs(v)), 0);
|
|
|
|
const header = document.createElement("div");
|
|
header.className = "weightBoardHeader";
|
|
if (board.name !== "Overall") {
|
|
const icon = document.createElement("img");
|
|
icon.src = `/images/Chess Images/White${board.name}.svg`;
|
|
icon.alt = board.name;
|
|
icon.className = "weightBoardIcon";
|
|
header.appendChild(icon);
|
|
}
|
|
const title = document.createElement("span");
|
|
title.textContent = board.name;
|
|
header.appendChild(title);
|
|
const range = document.createElement("span");
|
|
range.className = "weightRange";
|
|
range.textContent = maxAbs === 0 ? "neutral" : `±${maxAbs}`;
|
|
header.appendChild(range);
|
|
wrapper.appendChild(header);
|
|
|
|
const heat = document.createElement("div");
|
|
heat.className = "miniHeat";
|
|
|
|
for (let row = 0; row < 8; row++) {
|
|
const rankLabel = document.createElement("div");
|
|
rankLabel.className = "heatLabel";
|
|
rankLabel.textContent = 8 - row; // rank 8 at top, 1 at bottom
|
|
heat.appendChild(rankLabel);
|
|
|
|
for (let col = 0; col < 8; col++) {
|
|
const rank = 7 - row; // rank index, 0 = rank 1
|
|
const sq = rank * 8 + col; // white-relative square (A1 = 0)
|
|
heat.appendChild(this.buildSquare(board.squares[sq], sq, maxAbs));
|
|
}
|
|
}
|
|
|
|
heat.appendChild(this.cornerSpacer());
|
|
for (let col = 0; col < 8; col++) {
|
|
const fileLabel = document.createElement("div");
|
|
fileLabel.className = "heatLabel";
|
|
fileLabel.textContent = String.fromCharCode(97 + col);
|
|
heat.appendChild(fileLabel);
|
|
}
|
|
|
|
wrapper.appendChild(heat);
|
|
return wrapper;
|
|
},
|
|
|
|
buildSquare(value, sq, maxAbs) {
|
|
const cell = document.createElement("div");
|
|
cell.className = "heatSquare";
|
|
|
|
if (value !== 0 && maxAbs > 0) {
|
|
const ratio = Math.abs(value) / maxAbs;
|
|
const alpha = (0.12 + 0.88 * ratio).toFixed(3);
|
|
cell.style.backgroundColor = value > 0
|
|
? `rgba(74, 134, 232, ${alpha})` // high -> blue
|
|
: `rgba(232, 74, 74, ${alpha})`; // low -> red
|
|
cell.textContent = value;
|
|
}
|
|
|
|
const file = String.fromCharCode(97 + (sq & 7));
|
|
const rank = (sq >> 3) + 1;
|
|
cell.title = `${file}${rank}: ${value}`;
|
|
return cell;
|
|
},
|
|
|
|
cornerSpacer() {
|
|
const spacer = document.createElement("div");
|
|
spacer.className = "heatLabel";
|
|
return spacer;
|
|
},
|
|
|
|
// features: [{ name, value }]
|
|
renderFeatures(features) {
|
|
const panel = document.getElementById("featureWeights");
|
|
panel.innerHTML = "";
|
|
|
|
const maxAbs = features.reduce((m, f) => Math.max(m, Math.abs(f.value)), 0);
|
|
|
|
features.forEach(feature => {
|
|
const row = document.createElement("div");
|
|
row.className = "featureRow";
|
|
|
|
const label = document.createElement("span");
|
|
label.className = "featureLabel";
|
|
label.textContent = feature.name;
|
|
|
|
const track = document.createElement("div");
|
|
track.className = "featureTrack";
|
|
const bar = document.createElement("div");
|
|
bar.className = "featureBar";
|
|
const ratio = maxAbs === 0 ? 0 : Math.abs(feature.value) / maxAbs;
|
|
bar.style.width = `${(ratio * 100).toFixed(1)}%`;
|
|
bar.style.backgroundColor = feature.value >= 0
|
|
? "rgba(74, 134, 232, 0.85)"
|
|
: "rgba(232, 74, 74, 0.85)";
|
|
track.appendChild(bar);
|
|
|
|
const value = document.createElement("span");
|
|
value.className = "featureValue";
|
|
value.textContent = feature.value;
|
|
|
|
row.append(label, track, value);
|
|
panel.appendChild(row);
|
|
});
|
|
}
|
|
};
|
|
|
|
window.addEventListener("load", () => Weights.init());
|
|
|
|
console.log("Weights.js loaded");
|