Kubernetes_TicTacToe / index.html
eaglelandsonce's picture
Update index.html
14cc665 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Kubernetes TicTicToe</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gradient-to-br from-emerald-50 to-sky-100 min-h-screen flex items-center justify-center p-6">
<div class="w-full max-w-5xl bg-white rounded-2xl shadow-xl p-6">
<h1 class="text-3xl md:text-4xl font-extrabold text-center text-emerald-700">Kubernetes TicTicToe</h1>
<p class="text-center text-sm text-gray-600 mt-2">
Source:
<a class="text-blue-600 underline" target="_blank" rel="noopener"
href="https://www.linkedin.com/pulse/kubernetes-fundamentals-orchestrating-containers-scale-michael-lively-kaxae/">
Kubernetes Fundamentals: Orchestrating Containers at Scale β€” Michael Lively
</a>
</p>
<div id="banner" class="hidden bg-emerald-100 border border-emerald-300 text-emerald-900 font-semibold text-center py-2 rounded mt-4"></div>
<div class="mt-6 flex flex-col md:flex-row md:space-x-6 space-y-4 md:space-y-0">
<!-- Board -->
<div id="board" class="grid grid-cols-3 gap-2 p-2 bg-gray-50 rounded-xl border-4 border-gray-200 mx-auto"></div>
<!-- Question Panel -->
<div id="questionPanel" class="md:w-1/2 w-full bg-gray-50 rounded-xl shadow-inner p-4">
<h2 id="panelQuestion" class="text-lg md:text-xl font-semibold text-gray-800">
Select a square to view the question
</h2>
<ul id="panelChoices" class="mt-4 space-y-2"></ul>
<p id="panelHint" class="mt-3 text-sm text-gray-600 hidden"></p>
<div class="mt-4 flex items-center space-x-2">
<button id="hintBtn" class="px-3 py-1.5 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">Show Hint</button>
<button id="clearBtn" class="px-3 py-1.5 bg-white border rounded hover:bg-gray-100">Clear Panel</button>
</div>
</div>
</div>
<div class="flex flex-col md:flex-row md:items-center md:justify-between mt-6 space-y-3 md:space-y-0">
<p id="status" class="text-lg font-medium text-gray-800"></p>
<div class="flex items-center space-x-2">
<label class="text-sm text-gray-600">Mode:</label>
<select id="mode" class="border rounded px-2 py-1 text-sm">
<option value="two">Two Players (X vs O)</option>
<option value="solo">Solo (You are X)</option>
</select>
<button id="restartBtn" class="px-4 py-2 bg-emerald-700 text-white rounded hover:bg-emerald-800">Restart</button>
</div>
</div>
</div>
<script>
// --- Utilities ---
const randShuffle = (arr) => arr
.map(x => ({ x, r: Math.random() }))
.sort((a,b) => a.r - b.r)
.map(o => o.x);
// --- Question bank (answers identified by 'answer' index BEFORE shuffle) ---
// Based on your article content (control plane, pods, services, workloads, scaling, CI/CD with K8s, etc.)
const QUESTION_BANK = [
{
question: 'Why do teams use Kubernetes instead of managing containers manually?',
choices: [
'To run monoliths only',
'To automate scaling, healing, and rollouts',
'To replace Linux entirely',
'To remove the need for Dockerfiles'
],
answer: 2,
hint: 'Think automation: scale, balance, recover.'
},
{
question: 'In Kubernetes, what is the smallest deployable unit?',
choices: ['Container', 'Image', 'Pod', 'Node'],
answer: 3,
hint: 'It can hold one or more containers.'
},
{
question: 'Which component stores cluster state?',
choices: ['kubelet', 'kube-proxy', 'etcd', 'scheduler'],
answer: 3,
hint: 'Key–value store for desired/actual state.'
},
{
question: 'What does the scheduler do?',
choices: [
'Stores secrets',
'Assigns Pods to nodes based on resources and constraints',
'Exposes services to the internet',
'Builds images'
],
answer: 2,
hint: 'Placement decisions.'
},
{
question: 'What replaced the old Dockershim integration in Kubernetes v1.24?',
choices: ['containerd/CRI-O via CRI', 'LXC directly', 'Hyper-V only', 'Bash scripts'],
answer: 1,
hint: 'Common modern runtimes are containerd and CRI-O.'
},
{
question: 'What is a Deployment primarily used for?',
choices: [
'One Pod per node',
'Long-running stateless workloads with rolling updates',
'Cron-style scheduled jobs',
'Stable network IDs and storage for stateful apps'
],
answer: 2,
hint: 'Think rolling updates + replicas.'
},
{
question: 'Which workload ensures one Pod runs on every node?',
choices: ['StatefulSet', 'DaemonSet', 'Job', 'ReplicaSet'],
answer: 2,
hint: 'Used for log/metrics agents.'
},
{
question: 'Why do we need Services in Kubernetes?',
choices: [
'Pods are ephemeral; Services provide stable DNS and load balancing',
'To store images',
'To run kernels',
'To create CRDs automatically'
],
answer: 1,
hint: 'Stable name in front of changing Pods.'
},
{
question: 'Which command scales a Deployment to 5 replicas?',
choices: [
'kubectl scale deployment myapp --replicas=5',
'kubectl run myapp --replicas=5',
'kubectl upsize myapp 5',
'kubectl add pods myapp 5'
],
answer: 1,
hint: 'Use the scale subcommand.'
},
{
question: 'What best describes Kubernetes’ model?',
choices: [
'Imperative only',
'Declarative: you state desired end, K8s converges',
'Interactive shell only',
'All manual edits on live Pods'
],
answer: 2,
hint: 'You describe the target state; controllers do the rest.'
},
{
question: 'Which component enforces that containers are healthy on a node?',
choices: ['kubelet', 'kubeadm', 'kubectl', 'kustomize'],
answer: 1,
hint: 'Node agent.'
},
{
question: 'What does kube-proxy handle?',
choices: [
'Pod storage provisioning',
'Scheduling decisions',
'Cluster networking for Services/virtual IPs',
'RBAC policy management'
],
answer: 3,
hint: 'Traffic routing to Service backends.'
},
{
question: 'What is the main reason to use a StatefulSet?',
choices: [
'GPU scheduling',
'Stable identities and storage for stateful Pods',
'One Pod per node',
'Short-lived batch tasks'
],
answer: 2,
hint: 'Think stable network IDs and persistent volumes.'
},
{
question: 'Which command updates the image in a Deployment?',
choices: [
'kubectl set image deployment web web=nginx:1.27',
'kubectl image switch web=nginx:1.27',
'kubectl rollout change web=nginx:1.27',
'kubectl deploy image web=nginx:1.27'
],
answer: 1,
hint: 'Uses β€œset image … deployment …”.'
},
{
question: 'How do you undo a bad rollout?',
choices: [
'kubectl rollback last',
'kubectl rollout undo deployment web',
'kubectl reset deployment web',
'kubectl undo web now'
],
answer: 2,
hint: 'Subcommand is β€œrollout undo”.'
},
{
question: 'How does Kubernetes achieve self-healing?',
choices: [
'By pausing the scheduler',
'Controllers reschedule/restart Pods to match desired state',
'By disabling probes',
'By editing live containers manually'
],
answer: 2,
hint: 'Controllers reconcile actual vs. desired state.'
},
{
question: 'Why is DNS/service discovery important in K8s?',
choices: [
'Because Pod IPs change; DNS gives a stable name',
'It enables image scanning',
'It configures RBAC',
'It stores logs'
],
answer: 1,
hint: 'Pods come and go, names should not.'
},
{
question: 'How do CI/CD and Kubernetes usually integrate?',
choices: [
'CI builds/pushes image; CD applies Deployment for rolling update',
'Kubernetes builds the code directly',
'Docker Hub deploys to Pods automatically',
'kubectl compiles the source'
],
answer: 1,
hint: 'Pipeline builds→pushes, cluster rolls out.'
},
{
question: 'Which principle improves security and isolation of workloads?',
choices: [
'Running all as root',
'Namespaces, RBAC, and admission controls',
'Disabling probes',
'Editing containers in production'
],
answer: 2,
hint: 'Least privilege and boundaries.'
},
{
question: 'Why are Pods considered ephemeral?',
choices: [
'They never restart',
'They can be replaced/rescheduled anytime; controllers keep counts',
'They persist data by default',
'They run only on master nodes'
],
answer: 2,
hint: 'Treat Pods as cattle, not pets.'
}
];
// --- Game State ---
let currentPlayer, boardState, selectedCell, cellQuestions, mode;
const boardEl = document.getElementById('board');
const statusEl = document.getElementById('status');
const bannerEl = document.getElementById('banner');
const hintBtn = document.getElementById('hintBtn');
const clearBtn = document.getElementById('clearBtn');
const panelQuestion = document.getElementById('panelQuestion');
const panelChoices = document.getElementById('panelChoices');
const panelHint = document.getElementById('panelHint');
const restartBtn = document.getElementById('restartBtn');
const modeSel = document.getElementById('mode');
function initGame() {
mode = modeSel.value;
currentPlayer = 'X';
boardState = Array(9).fill('');
bannerEl.classList.add('hidden');
statusEl.innerText = '';
selectedCell = null;
// Pick 9 questions at random, then for each, shuffle choices and remap the correct answer index
const picked = randShuffle(QUESTION_BANK).slice(0, 9).map(q => {
const original = q.choices.map((c, i) => ({ text: c, idx: i + 1 }));
const shuffled = randShuffle(original);
const newChoices = shuffled.map(o => o.text);
const newAnswerIndex = shuffled.findIndex(o => o.idx === q.answer) + 1;
return {
question: q.question,
choices: newChoices,
answer: newAnswerIndex,
hint: q.hint || ''
};
});
cellQuestions = picked;
panelQuestion.innerText = 'Select a square to view the question';
panelChoices.innerHTML = '';
panelHint.classList.add('hidden');
panelHint.innerText = '';
renderBoard();
}
function renderBoard() {
boardEl.innerHTML = '';
boardState.forEach((mark, idx) => {
const btn = document.createElement('button');
let cls = 'bg-white h-24 w-24 md:h-28 md:w-28 flex items-center justify-center text-3xl font-extrabold rounded-xl shadow hover:bg-gray-100 transition';
if (mark === 'X') cls += ' text-emerald-700';
if (mark === 'O') cls += ' text-sky-700';
btn.className = cls;
btn.innerText = mark;
btn.disabled = mark !== '';
btn.addEventListener('click', () => openQuestion(idx));
boardEl.appendChild(btn);
});
statusEl.innerText = `Current: ${currentPlayer}`;
}
function openQuestion(idx) {
selectedCell = idx;
const q = cellQuestions[idx];
panelQuestion.innerText = q.question;
panelChoices.innerHTML = '';
panelHint.classList.add('hidden');
panelHint.innerText = q.hint || '';
q.choices.forEach((c, i) => {
const li = document.createElement('li');
const btn = document.createElement('button');
btn.innerText = c;
btn.className = 'w-full text-left px-4 py-2 bg-gray-100 rounded hover:bg-gray-200';
btn.addEventListener('click', () => handleAnswer(i + 1));
li.appendChild(btn);
panelChoices.appendChild(li);
});
}
function handleAnswer(choice) {
const q = cellQuestions[selectedCell];
if (choice === q.answer) {
boardState[selectedCell] = currentPlayer;
renderBoard();
if (checkWin(currentPlayer)) return endGame(`${currentPlayer} wins!`);
if (boardState.every(c => c)) return endGame('Stalemate!');
// Switch to next player (or AI turn)
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
statusEl.innerText = `Current: ${currentPlayer}`;
if (mode === 'solo' && currentPlayer === 'O') {
setTimeout(aiMove, 450);
}
} else {
alert('Incorrect! Turn missed.');
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
statusEl.innerText = `Current: ${currentPlayer}`;
if (mode === 'solo' && currentPlayer === 'O') {
setTimeout(aiMove, 450);
}
}
}
function checkWin(player) {
const wins = [
[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 wins.some(combo => combo.every(i => boardState[i] === player));
}
function endGame(msg) {
bannerEl.innerText = `πŸŽ‰ ${msg}`;
bannerEl.classList.remove('hidden');
document.querySelectorAll('#board button').forEach(b => b.disabled = true);
}
// Simple AI: pick the first available square and ask/answer its question randomly (50/50 correct).
// Keeps focus on learning while enabling solo play.
function aiMove() {
const open = boardState.map((v,i) => v === '' ? i : null).filter(v => v !== null);
if (open.length === 0) return;
const pick = open[Math.floor(Math.random() * open.length)];
// AI "answers" with a random chance to be correct; you can adjust difficulty here.
const q = cellQuestions[pick];
const aiCorrect = Math.random() < 0.55; // slightly better than random
if (aiCorrect) {
boardState[pick] = 'O';
renderBoard();
if (checkWin('O')) return endGame('O wins!');
if (boardState.every(c => c)) return endGame('Stalemate!');
currentPlayer = 'X';
statusEl.innerText = `Current: ${currentPlayer}`;
} else {
// AI misses turn
currentPlayer = 'X';
statusEl.innerText = `Current: ${currentPlayer}`;
}
}
hintBtn.addEventListener('click', () => panelHint.classList.toggle('hidden'));
clearBtn.addEventListener('click', () => {
panelQuestion.innerText = 'Select a square to view the question';
panelChoices.innerHTML = '';
panelHint.classList.add('hidden');
panelHint.innerText = '';
});
restartBtn.addEventListener('click', initGame);
modeSel.addEventListener('change', initGame);
// Start
initGame();
</script>
</body>
</html>