Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions project/classic-games/20260709-1506-twelve-mens-morris.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!doctype html>
<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Mini Twelve Men's Morris</title><script src="https://cdn.tailwindcss.com"></script></head>
<body class="min-h-screen bg-indigo-950 text-indigo-50"><main class="mx-auto flex min-h-screen max-w-4xl flex-col gap-4 px-4 py-5"><header class="flex flex-wrap items-center justify-between gap-3"><div><h1 class="text-2xl font-bold">Mini Twelve Men's Morris</h1><p class="text-sm text-indigo-200">Place twelve pieces each, make mills, remove rivals, then slide along the board.</p></div><button id="newGameBtn" class="rounded bg-fuchsia-300 px-4 py-2 font-semibold text-indigo-950 hover:bg-fuchsia-200">New Game</button></header><section class="grid gap-3 rounded border border-indigo-700 bg-indigo-900 p-3 sm:grid-cols-3"><div>Turn: <b id="turnLabel">White</b></div><div>Phase: <b id="phaseLabel">Place</b></div><div id="message">Choose an empty point.</div></section><section class="grid gap-4 lg:grid-cols-[1fr_220px]"><div id="board" class="mx-auto grid aspect-square w-full max-w-xl grid-cols-7 grid-rows-7 rounded border border-indigo-700 bg-indigo-800 p-3"></div><aside class="rounded border border-indigo-700 bg-indigo-900 p-3 text-sm"><div>White placed: <b id="wPlaced">0</b>/12</div><div>Black placed: <b id="bPlaced">0</b>/12</div><div>White alive: <b id="wAlive">0</b></div><div>Black alive: <b id="bAlive">0</b></div></aside></section></main><script>
const pts=[[0,0],[3,0],[6,0],[1,1],[3,1],[5,1],[2,2],[3,2],[4,2],[0,3],[1,3],[2,3],[4,3],[5,3],[6,3],[2,4],[3,4],[4,4],[1,5],[3,5],[5,5],[0,6],[3,6],[6,6]],mills=[[0,1,2],[3,4,5],[6,7,8],[9,10,11],[12,13,14],[15,16,17],[18,19,20],[21,22,23],[0,9,21],[3,10,18],[6,11,15],[1,4,7],[16,19,22],[8,12,17],[5,13,20],[2,14,23],[0,3,6],[2,5,8],[21,18,15],[23,20,17]],adj=[[1,3,9],[0,2,4],[1,5,14],[0,4,6,10],[1,3,5,7],[2,4,8,13],[3,7,11],[4,6,8],[5,7,12],[0,10,21],[3,9,11,18],[6,10,15],[8,13,17],[5,12,14,20],[2,13,23],[11,16,18],[15,17,19],[12,16,20],[10,15,19,21],[16,18,20,22],[13,17,19,23],[9,18,22],[19,21,23],[14,20,22]];let board,turn,placed,phase,removing,selected,gameOver;const el=id=>document.getElementById(id),boardEl=el('board'),message=el('message'),turnLabel=el('turnLabel'),phaseLabel=el('phaseLabel'),newGameBtn=el('newGameBtn');function enemy(p){return p==='W'?'B':'W'}function name(p){return p==='W'?'White':'Black'}function canFormMill(i,p){return mills.some(m=>m.includes(i)&&m.every(x=>board[x]===p))}function alive(p){return board.filter(x=>x===p).length}function setTurn(p){turn=p;turnLabel.textContent=name(turn)}function next(){selected=null;setTurn(enemy(turn));message.textContent=phase==='place'?'Choose an empty point.':'Select one of your pieces.'}function removable(i){const e=enemy(turn);return board[i]===e&&(!canFormMill(i,e)||board.every((v,j)=>v!==e||canFormMill(j,e)))}function handlePoint(i){if(gameOver)return;if(removing){if(!removable(i)){message.textContent='Pick an opponent piece outside a mill if possible.';return}board[i]='';removing=false;if(phase==='move'&&alive(enemy(turn))<3){message.textContent=`${name(turn)} wins.`;gameOver=true;render();return}next();render();return}if(phase==='place'){if(board[i])return;board[i]=turn;placed[turn]++;const allPlaced=placed.W===12&&placed.B===12,formed=canFormMill(i,turn);if(formed){if(allPlaced)phase='move';removing=true;message.textContent='Mill formed. Remove one opponent piece.'}else if(allPlaced){message.textContent='Filled board. Draw.';gameOver=true}else next();render();return}if(selected===null){if(board[i]===turn){selected=i;message.textContent='Choose an adjacent empty point.';render()}return}if(i===selected){selected=null;message.textContent='Selection cleared.';render();return}if(!board[i]&&adj[selected].includes(i)){board[i]=turn;board[selected]='';if(canFormMill(i,turn)){removing=true;message.textContent='Mill formed. Remove one opponent piece.'}else next();render()}}function render(){boardEl.innerHTML='';pts.forEach(([c,r],i)=>{const b=document.createElement('button');b.className='m-auto flex h-11 w-11 items-center justify-center rounded-full border-2 '+(selected===i?'border-fuchsia-300 ':'border-indigo-500 ')+(board[i]==='W'?'bg-white text-indigo-950':board[i]==='B'?'bg-indigo-950 text-white':'bg-indigo-700 hover:bg-indigo-600');b.style.gridColumn=c+1;b.style.gridRow=r+1;b.textContent=board[i]||'';b.onclick=()=>handlePoint(i);boardEl.appendChild(b)});phaseLabel.textContent=gameOver?'Game Over':removing?'Remove':phase==='place'?'Place':'Move';el('wPlaced').textContent=placed.W;el('bPlaced').textContent=placed.B;el('wAlive').textContent=alive('W');el('bAlive').textContent=alive('B')}function newGame(){board=Array(24).fill('');placed={W:0,B:0};phase='place';removing=false;selected=null;gameOver=false;setTurn('W');message.textContent='Choose an empty point.';render()}newGameBtn.addEventListener('click',newGame);newGame();
</script></body></html>