eaglelandsonce's picture
Update index.html
21d358d verified
Raw
History Blame Contribute Delete
30.4 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Evaluating Agentic Workflows</title>
<style>
:root {
--navy: #102a43;
--blue: #ea4b71;
--blue-soft: #ffe4ec;
--cyan: #ff6d5a;
--teal: #b9385d;
--red: #dc2626;
--ink: #243b53;
--muted: #627d98;
--line: #bcccdc;
--panel: #ffffff;
--soft: #f0f7ff;
--success: #166534;
--success-bg: #dcfce7;
--warning: #92400e;
--warning-bg: #fef3c7;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
padding: 24px;
display: flex;
align-items: center;
justify-content: center;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: var(--ink);
background:
radial-gradient(circle at 15% 15%, rgba(37, 99, 235, 0.16), transparent 32%),
radial-gradient(circle at 85% 85%, rgba(15, 118, 110, 0.15), transparent 34%),
linear-gradient(135deg, #fff3f6 0%, #ecfeff 52%, #f0fdfa 100%);
}
.game-shell {
width: min(1120px, 100%);
padding: 28px;
border: 1px solid rgba(188, 204, 220, 0.8);
border-radius: 24px;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 24px 70px rgba(16, 42, 67, 0.18);
}
h1 {
margin: 0;
text-align: center;
color: var(--navy);
font-size: clamp(2rem, 4vw, 3.25rem);
line-height: 1.05;
letter-spacing: -0.04em;
}
.subtitle {
max-width: 760px;
margin: 12px auto 5px;
text-align: center;
color: var(--muted);
font-size: 1rem;
}
.topics {
margin: 0 auto 22px;
text-align: center;
color: var(--teal);
font-size: 0.88rem;
font-weight: 750;
letter-spacing: 0.03em;
}
.banner {
display: none;
margin-bottom: 18px;
padding: 13px 16px;
border: 1px solid #86efac;
border-radius: 12px;
color: var(--success);
background: var(--success-bg);
text-align: center;
font-weight: 800;
}
.banner.show { display: block; }
.game-layout {
display: grid;
grid-template-columns: minmax(315px, 0.88fr) minmax(390px, 1.12fr);
gap: 24px;
align-items: start;
}
.board-wrap,
.question-panel {
border: 1px solid var(--line);
border-radius: 18px;
background: var(--panel);
box-shadow: 0 10px 30px rgba(16, 42, 67, 0.08);
}
.board-wrap { padding: 18px; }
.turn-pill {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
padding: 10px 12px;
border-radius: 12px;
background: var(--soft);
color: var(--navy);
font-weight: 750;
}
.turn-mark {
display: inline-grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: 10px;
color: #fff;
background: var(--blue);
font-size: 1.2rem;
font-weight: 900;
}
.turn-mark.o { background: var(--red); }
.board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
padding: 12px;
border: 3px solid #f7a8ba;
border-radius: 16px;
background: #fff3f6;
}
.cell {
aspect-ratio: 1;
min-width: 0;
border: 1px solid #f8c4d0;
border-radius: 14px;
background: #fff;
color: var(--blue);
box-shadow: 0 5px 14px rgba(37, 99, 235, 0.09);
cursor: pointer;
font-size: clamp(2.5rem, 7vw, 4rem);
font-weight: 900;
transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.cell:hover:not(:disabled),
.cell:focus-visible:not(:disabled) {
transform: translateY(-2px);
background: #ffe4ec;
box-shadow: 0 8px 18px rgba(37, 99, 235, 0.18);
outline: 3px solid rgba(37, 99, 235, 0.25);
outline-offset: 2px;
}
.cell.o { color: var(--red); }
.cell:disabled { cursor: default; }
.question-panel {
min-height: 455px;
padding: 24px;
display: flex;
flex-direction: column;
}
.question-label {
margin: 0 0 8px;
color: var(--cyan);
font-size: 0.8rem;
font-weight: 850;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.question {
margin: 0;
color: var(--navy);
font-size: clamp(1.25rem, 2.6vw, 1.75rem);
line-height: 1.27;
}
.choices {
display: grid;
gap: 10px;
margin: 22px 0 0;
padding: 0;
list-style: none;
}
.choice-button {
width: 100%;
padding: 13px 14px;
border: 1px solid #f8c4d0;
border-radius: 12px;
background: #f8fbff;
color: var(--ink);
cursor: pointer;
text-align: left;
font-size: 0.98rem;
line-height: 1.35;
transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.choice-button:hover,
.choice-button:focus-visible {
transform: translateX(2px);
border-color: #ea4b71;
background: #fff0f4;
outline: none;
}
.choice-letter {
display: inline-grid;
place-items: center;
width: 27px;
height: 27px;
margin-right: 8px;
border-radius: 8px;
background: var(--blue-soft);
color: var(--blue);
font-weight: 850;
}
.hint {
display: none;
margin: 16px 0 0;
padding: 12px 14px;
border-left: 4px solid #f59e0b;
border-radius: 8px;
color: var(--warning);
background: var(--warning-bg);
font-size: 0.92rem;
line-height: 1.45;
}
.hint.show { display: block; }
.panel-actions {
margin-top: auto;
padding-top: 18px;
display: flex;
justify-content: flex-end;
}
button.secondary,
button.primary {
border: 0;
border-radius: 11px;
cursor: pointer;
font-weight: 800;
transition: transform 0.15s ease, filter 0.15s ease;
}
button.secondary {
padding: 10px 13px;
color: var(--navy);
background: #e6eef7;
}
button.primary {
padding: 11px 17px;
color: #fff;
background: linear-gradient(135deg, var(--blue), var(--cyan));
box-shadow: 0 8px 18px rgba(37, 99, 235, 0.22);
}
button.secondary:hover,
button.primary:hover { transform: translateY(-1px); filter: brightness(0.98); }
.footer-bar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 15px;
margin-top: 20px;
}
.status {
margin: 0;
color: var(--muted);
font-size: 0.96rem;
font-weight: 700;
}
@media (max-width: 840px) {
body { padding: 12px; }
.game-shell { padding: 18px; border-radius: 18px; }
.game-layout { grid-template-columns: 1fr; }
.board-wrap { max-width: 500px; width: 100%; margin: 0 auto; }
.question-panel { min-height: 410px; }
}
@media (max-width: 480px) {
.footer-bar { align-items: stretch; flex-direction: column; }
button.primary { width: 100%; }
.question-panel { padding: 18px; }
.topics { font-size: 0.77rem; }
}
</style>
</head>
<body>
<main class="game-shell">
<h1>Evaluating Agentic Workflows</h1>
<p class="subtitle">Answer correctly to claim a square. An incorrect response passes the turn and leaves the square open.</p>
<p class="topics">REASONING β€’ ACTION β€’ EXECUTION β€’ RELIABILITY β€’ VERIFICATION β€’ PRODUCTION READINESS</p>
<div id="banner" class="banner" role="status" aria-live="polite"></div>
<section class="game-layout" aria-label="Evaluating Agentic Workflows game">
<div class="board-wrap">
<div class="turn-pill">
<span id="turnText">Player X's turn</span>
<span id="turnMark" class="turn-mark" aria-hidden="true">X</span>
</div>
<div id="board" class="board" role="grid" aria-label="Tic tac toe board"></div>
</div>
<div class="question-panel">
<p class="question-label">Knowledge Challenge</p>
<h2 id="panelQuestion" class="question">Select an open square to view a question.</h2>
<ul id="panelChoices" class="choices"></ul>
<p id="panelHint" class="hint"></p>
<div class="panel-actions">
<button id="hintBtn" class="secondary" type="button">Show Hint</button>
</div>
</div>
</section>
<div class="footer-bar">
<p id="status" class="status">A fresh set of nine questions is selected for every game.</p>
<button id="restartBtn" class="primary" type="button">Restart Game</button>
</div>
</main>
<script>
const questions = [
{
question: "Why do standard metrics such as ROUGE and BLEU often fail for autonomous agents?",
choices: ["They focus on single-turn string similarity rather than stateful trajectories", "They require too much human review", "They can evaluate only tool arguments", "They always favor the first response"],
answer: 1,
hint: "Agents make sequences of decisions whose errors can cascade."
},
{
question: "Which evaluation layer asks whether the agent's strategic plan was sound?",
choices: ["Reasoning", "Action", "Execution", "Infrastructure"],
answer: 1,
hint: "This layer evaluates what the agent thinks."
},
{
question: "Which evaluation layer measures tool selection and argument correctness?",
choices: ["Action", "Reasoning", "Execution", "Presentation"],
answer: 1,
hint: "This layer evaluates what the agent uses."
},
{
question: "Which evaluation layer determines whether the user's goal was actually completed?",
choices: ["Execution", "Reasoning", "Action", "Extraction"],
answer: 1,
hint: "This layer evaluates what the workflow achieves."
},
{
question: "An agent begins with a good plan but gradually departs from it. What should evaluators detect?",
choices: ["Trajectory drift", "Position bias", "Chunk overlap", "String mismatch"],
answer: 1,
hint: "Compare planned and actual steps across the trajectory."
},
{
question: "Why can a single early agent error be especially damaging?",
choices: ["It can corrupt downstream actions in a stateful loop", "It only changes the final wording", "It automatically stops all tool calls", "It affects no later decisions"],
answer: 1,
hint: "Agent steps are dependent rather than isolated samples."
},
{
question: "Which metric checks whether the agent chose the correct tool and supplied valid parameters?",
choices: ["Tool and argument correctness", "Task completion", "Semantic similarity", "Response length"],
answer: 1,
hint: "Evaluate both the selected tool and its arguments."
},
{
question: "Which metric measures unnecessary loops, redundant calls, and excess latency?",
choices: ["Step efficiency", "Plan adherence", "Recall", "Judge agreement"],
answer: 1,
hint: "Compare the path taken with a shorter valid path."
},
{
question: "What does end-to-end task completion measure?",
choices: ["Whether the agent successfully achieved the user's goal", "Whether every response used the same words", "Whether the first tool call was fast", "Whether the plan contained many steps"],
answer: 1,
hint: "Focus on the final outcome."
},
{
question: "Where should a failing retriever or tool integration be evaluated to isolate the root cause?",
choices: ["Component level", "End-to-end level only", "User-interface level", "Model-training level"],
answer: 1,
hint: "Test individual units such as retrievers, sub-agents, and tools."
},
{
question: "What does trajectory-level evaluation inspect?",
choices: ["Planning, reasoning steps, tool calls, retries, and execution paths", "Only the final answer", "Only token count", "Only the user prompt"],
answer: 1,
hint: "Inspect the path, not merely the destination."
},
{
question: "Which evaluation method is best suited to exact tool correctness?",
choices: ["Deterministic checks", "ROUGE alone", "Unstructured human intuition only", "Response-length scoring"],
answer: 1,
hint: "Tool names and arguments can be checked programmatically."
},
{
question: "Which method is often appropriate for judging semantic task completion?",
choices: ["An LLM-as-a-judge with a clear rubric", "A character-count comparison", "A latency threshold alone", "Random sampling without criteria"],
answer: 1,
hint: "Meaning and intent require more than word matching."
},
{
question: "What is a major advantage of LLM-as-a-judge over BLEU or ROUGE?",
choices: ["It can evaluate meaning and intent", "It is perfectly deterministic", "It eliminates all bias", "It needs no rubric"],
answer: 1,
hint: "Semantic evaluation can recognize valid answers with different wording."
},
{
question: "What is the circularity trap in pure LLM-as-a-judge evaluation?",
choices: ["The judge may share the same probabilistic failure modes as the agent", "The judge cannot read natural language", "The judge always rejects long responses", "The judge can evaluate only arithmetic"],
answer: 1,
hint: "A probabilistic model is judging another probabilistic model."
},
{
question: "How does a neuro-symbolic evaluation approach reduce judge circularity?",
choices: ["It extracts structured artifacts and verifies them with deterministic code", "It asks the same model twice", "It removes ground truth", "It scores only response style"],
answer: 1,
hint: "Combine neural extraction with symbolic verification."
},
{
question: "What is the purpose of neural extraction in a neuro-symbolic evaluator?",
choices: ["Convert unstructured trajectories into machine-checkable structured data", "Replace all evaluation scripts", "Generate random ground truth", "Shorten every agent response"],
answer: 1,
hint: "The output may be a JSON artifact that code can inspect."
},
{
question: "What makes symbolic verification auditable and repeatable?",
choices: ["Programmatic checks against immutable ground truth", "A judge's confidence score", "A longer natural-language explanation", "A different prompt on every run"],
answer: 1,
hint: "The same inputs should produce the same verdict."
},
{
question: "A judge consistently favors whichever answer appears first. Which mitigation directly tests this bias?",
choices: ["Swap response order and average the results", "Increase response length", "Remove the rubric", "Use only one test case"],
answer: 1,
hint: "Position-consistency checking changes answer order."
},
{
question: "Why use cross-family model judging?",
choices: ["A different model family can reduce self-preference bias", "It guarantees perfect factual accuracy", "It removes the need for testing", "It makes all verdicts deterministic"],
answer: 1,
hint: "For example, one model family can judge another."
},
{
question: "How can evaluators avoid rewarding unnecessary verbosity?",
choices: ["Use a binary pass/fail rubric for the required outcome", "Award points for every extra sentence", "Ignore task requirements", "Use word count as the only metric"],
answer: 1,
hint: "Score whether requirements were met, not how long the answer was."
},
{
question: "A judge and human experts agree on 80% of cases. What does that percentage describe?",
choices: ["Agreement with human judgment", "Tool-call precision", "Task completion rate", "Agent recall"],
answer: 1,
hint: "It compares automated verdicts with human ratings."
},
{
question: "An agent correctly maps user intent to the right function. Which outcome is this?",
choices: ["True positive", "False positive", "False negative", "Trajectory drift"],
answer: 1,
hint: "The correct action was selected when it was needed."
},
{
question: "An agent makes an unnecessary tool call that increases cost and latency. Which outcome is this?",
choices: ["False positive", "True positive", "False negative", "True negative"],
answer: 1,
hint: "The workflow acted when it should not have."
},
{
question: "An overconfident agent skips a needed tool and guesses from memory. Which outcome is this?",
choices: ["False negative", "False positive", "True positive", "Position bias"],
answer: 1,
hint: "A required action was missed."
},
{
question: "What does tool-selection precision emphasize?",
choices: ["Avoiding unnecessary or incorrect tool calls", "Catching every possible needed action regardless of cost", "Matching the final answer word for word", "Making the plan longer"],
answer: 1,
hint: "Higher precision reduces false positives, latency, and API cost."
},
{
question: "What does tool-selection recall emphasize?",
choices: ["Executing needed tool calls so critical actions are not missed", "Eliminating every redundant action", "Reducing answer length", "Matching a reference string"],
answer: 1,
hint: "Higher recall reduces false negatives."
},
{
question: "Why create a gold-standard trajectory dataset?",
choices: ["To provide human-mapped ideal tool selections and paths as an evaluation baseline", "To train every model from scratch", "To replace all production traces", "To guarantee zero failures"],
answer: 1,
hint: "Ideal logged trajectories anchor automated evaluation."
},
{
question: "A production agent has 60% tool-selection precision. What does this most strongly suggest?",
choices: ["Too many selected actions are unnecessary or wrong", "The agent misses exactly 40% of required actions", "The final answers are 60% correct", "The judge agrees with humans 60% of the time"],
answer: 1,
hint: "Precision evaluates the quality of actions the agent chose."
},
{
question: "Which reasoning-layer metric compares the initial plan with the actual execution?",
choices: ["Plan adherence", "Argument correctness", "End-to-end efficiency", "Task completion"],
answer: 1,
hint: "Look for consistency between intended and executed steps."
},
{
question: "Which safety metric tests resistance to malicious instructions embedded in documents or messages?",
choices: ["Injection resilience", "Plan adherence", "Step efficiency", "Position consistency"],
answer: 1,
hint: "Adversarial input should not redirect the agent improperly."
},
{
question: "What does argument correctness evaluate?",
choices: ["Whether parameter values extracted from context are accurate", "Whether the plan is creative", "Whether the response is concise", "Whether the model is from another family"],
answer: 1,
hint: "Check the values passed into the tool call."
},
{
question: "What should execution-path evaluation verify?",
choices: ["Correct arguments and valid, non-looping paths", "Only polished prose", "Only the number of tools available", "Only model temperature"],
answer: 1,
hint: "Execution should be correct and should not get stuck."
},
{
question: "What is the first step in building a production evaluation harness?",
choices: ["Define success criteria and representative test cases", "Automate deployment immediately", "Remove all traces", "Choose the longest benchmark"],
answer: 1,
hint: "Start with ground truth, happy paths, and adversarial scenarios."
},
{
question: "Why instrument an agentic workflow with tracing?",
choices: ["To capture intermediate decisions and locate retrieval or execution failures", "To hide tool calls from evaluators", "To eliminate test cases", "To make outputs deterministic by itself"],
answer: 1,
hint: "Tracing reveals where in the trajectory a failure occurred."
},
{
question: "What is the purpose of automated regression gates in CI/CD?",
choices: ["Block deployment when quality falls below defined thresholds", "Guarantee that no future bug is possible", "Replace production monitoring", "Increase the number of agent steps"],
answer: 1,
hint: "New changes should not silently degrade established quality."
},
{
question: "Manual 'vibe checks' become unreliable at scale primarily because they are:",
choices: ["Slow, inconsistent, and high-latency", "Fully deterministic", "Limited to arithmetic", "Unable to display text"],
answer: 1,
hint: "Human inspection does not scale well across many trajectories."
},
{
question: "Which evaluation design best supports root-cause analysis?",
choices: ["Combine component, trajectory, and end-to-end measurements", "Use only a final satisfaction score", "Inspect one successful run", "Measure latency alone"],
answer: 1,
hint: "Different levels reveal whether the failure began in a unit, path, or outcome."
},
{
question: "Why should agent evaluation include both quality and efficiency?",
choices: ["A correct outcome may still use costly loops or redundant calls", "Efficiency automatically proves correctness", "Quality matters only during development", "Fast agents cannot hallucinate"],
answer: 1,
hint: "Production readiness includes outcome quality, latency, and cost."
},
{
question: "Which evaluation program is strongest for production readiness?",
choices: ["Ground truth, tracing, layered metrics, adversarial tests, and regression gates", "One demo reviewed informally", "BLEU scores without trajectory data", "A judge with no rubric or bias checks"],
answer: 1,
hint: "Reliable evaluation combines evidence across the full workflow lifecycle."
}
];
let currentPlayer;
let boardState;
let cellQuestions;
let selectedCell;
let gameOver;
const boardEl = document.getElementById("board");
const statusEl = document.getElementById("status");
const bannerEl = document.getElementById("banner");
const hintBtn = document.getElementById("hintBtn");
const panelQuestion = document.getElementById("panelQuestion");
const panelChoices = document.getElementById("panelChoices");
const panelHint = document.getElementById("panelHint");
const restartBtn = document.getElementById("restartBtn");
const turnText = document.getElementById("turnText");
const turnMark = document.getElementById("turnMark");
function shuffle(array) {
for (let i = array.length - 1; i > 0; i -= 1) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
function buildBalancedPositions(questionCount, choiceCount) {
const base = Math.floor(questionCount / choiceCount);
const remainder = questionCount % choiceCount;
const positions = [];
for (let position = 1; position <= choiceCount; position += 1) {
for (let count = 0; count < base; count += 1) positions.push(position);
}
const extras = shuffle(
Array.from({ length: choiceCount }, (_, index) => index + 1)
).slice(0, remainder);
return shuffle(positions.concat(extras));
}
function prepareQuestion(question, targetCorrectPosition) {
const originalCorrectIndex = question.answer - 1;
const correctChoice = question.choices[originalCorrectIndex];
const incorrectChoices = shuffle(
question.choices.filter((_, index) => index !== originalCorrectIndex)
);
const newChoices = Array(question.choices.length).fill(null);
newChoices[targetCorrectPosition - 1] = correctChoice;
let wrongIndex = 0;
for (let index = 0; index < newChoices.length; index += 1) {
if (newChoices[index] === null) {
newChoices[index] = incorrectChoices[wrongIndex];
wrongIndex += 1;
}
}
return {
question: question.question,
choices: newChoices,
answer: targetCorrectPosition,
hint: question.hint
};
}
function updateTurnDisplay() {
turnText.textContent = `Player ${currentPlayer}'s turn`;
turnMark.textContent = currentPlayer;
turnMark.classList.toggle("o", currentPlayer === "O");
}
function initGame() {
currentPlayer = "X";
boardState = Array(9).fill("");
selectedCell = null;
gameOver = false;
const selectedQuestions = shuffle([...questions]).slice(0, 9);
const correctPositions = buildBalancedPositions(9, 4);
cellQuestions = selectedQuestions.map((question, index) =>
prepareQuestion(question, correctPositions[index])
);
bannerEl.classList.remove("show");
bannerEl.textContent = "";
statusEl.textContent = "A fresh set of nine questions is selected for every game.";
panelQuestion.textContent = "Select an open square to view a question.";
panelChoices.innerHTML = "";
panelHint.classList.remove("show");
panelHint.textContent = "";
hintBtn.textContent = "Show Hint";
updateTurnDisplay();
renderBoard();
}
function renderBoard() {
boardEl.innerHTML = "";
boardState.forEach((mark, index) => {
const button = document.createElement("button");
button.type = "button";
button.className = `cell${mark === "O" ? " o" : ""}`;
button.textContent = mark;
button.disabled = gameOver || mark !== "";
button.setAttribute("role", "gridcell");
button.setAttribute(
"aria-label",
mark ? `Square ${index + 1}, claimed by ${mark}` : `Square ${index + 1}, open`
);
button.addEventListener("click", () => openQuestion(index));
boardEl.appendChild(button);
});
}
function openQuestion(index) {
if (gameOver || boardState[index] !== "") return;
selectedCell = index;
const question = cellQuestions[index];
panelQuestion.textContent = question.question;
panelChoices.innerHTML = "";
panelHint.textContent = question.hint;
panelHint.classList.remove("show");
hintBtn.textContent = "Show Hint";
question.choices.forEach((choice, choiceIndex) => {
const listItem = document.createElement("li");
const button = document.createElement("button");
const letter = String.fromCharCode(65 + choiceIndex);
button.type = "button";
button.className = "choice-button";
button.innerHTML = `<span class="choice-letter">${letter}</span>${choice}`;
button.addEventListener("click", () => handleAnswer(choiceIndex + 1));
listItem.appendChild(button);
panelChoices.appendChild(listItem);
});
}
function handleAnswer(choice) {
if (selectedCell === null || gameOver) return;
const question = cellQuestions[selectedCell];
const answeringPlayer = currentPlayer;
if (choice === question.answer) {
boardState[selectedCell] = currentPlayer;
selectedCell = null;
renderBoard();
if (checkWin(answeringPlayer)) {
endGame(`Player ${answeringPlayer} wins!`);
return;
}
if (boardState.every(Boolean)) {
endGame("Stalemate β€” strong match!");
return;
}
currentPlayer = currentPlayer === "X" ? "O" : "X";
updateTurnDisplay();
panelQuestion.textContent = "Correct β€” square claimed. Select another open square.";
panelChoices.innerHTML = "";
panelHint.classList.remove("show");
panelHint.textContent = "";
statusEl.textContent = `Player ${answeringPlayer} claimed the square. Player ${currentPlayer} is next.`;
} else {
currentPlayer = currentPlayer === "X" ? "O" : "X";
selectedCell = null;
updateTurnDisplay();
panelQuestion.textContent = "Incorrect β€” the square remains open. The turn passes.";
panelChoices.innerHTML = "";
panelHint.classList.remove("show");
panelHint.textContent = "";
statusEl.textContent = `Player ${answeringPlayer} missed. Player ${currentPlayer} may select any open square.`;
}
}
function checkWin(player) {
const winningLines = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]
];
return winningLines.some(line => line.every(index => boardState[index] === player));
}
function endGame(message) {
gameOver = true;
selectedCell = null;
bannerEl.textContent = `πŸŽ‰ ${message}`;
bannerEl.classList.add("show");
panelQuestion.textContent = message;
panelChoices.innerHTML = "";
panelHint.classList.remove("show");
panelHint.textContent = "";
statusEl.textContent = "Restart to draw a new set of questions.";
renderBoard();
}
hintBtn.addEventListener("click", () => {
if (selectedCell === null || gameOver) return;
panelHint.classList.toggle("show");
hintBtn.textContent = panelHint.classList.contains("show") ? "Hide Hint" : "Show Hint";
});
restartBtn.addEventListener("click", initGame);
initGame();
</script>
</body>
</html>