Tic Tac Toe / Amőba

Mi a cél?

Egy klasszikus egyszerű amőba játék, 3×3-as játékmezőn.

DEMO (a képre kattintva indul)

Tic Tac Toe / Amőba

A HTML váz

Az egyetlen egyedi a template-ben, hogy valamennyi cellát ellátunk egyedi jelölővel, hogy a játék során tudjuk, hogy melyikre kattintottak, illetve, hogy tudjuk, hogy melyiken végzünk valamilyen műveletet. Ezt megtehetnénk valamelyik dataset objektum elemmel is (data-val kezdődő attributumok a HTML-ben), ezek sokszor hasznosak, de most elég lesz egy egyszerű value attributum is.

<!DOCTYPE html> <html lang="hu"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tic-tac-toe</title> <link rel="stylesheet" href="style.css"> </head> <body> <main class="game"> <h1 class="game__title">Tic Tac Toe / Amőba</h1> <h3 class="game__info"> </h2> <div class="game__container"> <div value="0" class="cell"></div> <div value="1" class="cell"></div> <div value="2" class="cell"></div> <div value="3" class="cell"></div> <div value="4" class="cell"></div> <div value="5" class="cell"></div> <div value="6" class="cell"></div> <div value="7" class="cell"></div> <div value="8" class="cell"></div> </div> <button class="restart">Új játék</button> </main> <script src="main.js"></script> </body> </html>

A CSS stílus

A játék containerre CSS Grid-et alkalmazunk, az elemek középre állítását pedig a line-height használatával valósítjuk meg.

* { margin: 0; padding: 0; } *, *::before, *::after { box-sizing: inherit; } html { box-sizing: border-box; } body { min-height: 100vh; font-family: sans-serif; } .game { text-align: center; } .game__title { margin: 2rem; color: orange; } .winner { transform: scale(1.2); color: red; } .winner-sign { background-color: yellow; } .game__container { display: grid; grid-template-columns: repeat(3, auto); width: 30rem; margin: 2rem auto; } .cell { width: 10rem; height: 10rem; box-shadow: 0 0 0 1px #666666; border: 1px solid #666666; cursor: pointer; font-size: 6rem; line-height: 10rem; } .restart { padding: 0.5rem 1rem; cursor: pointer; background-color: orange; border: none; font-family: inherit; color: #fff; border-radius: 3px; transition: all .3s; } .restart:hover { transform: scale(1.1); background-color: orangered; }

A játék logika

Felírjuk a szükséges DOM elemeket és két változót inicialzálunk az induláshoz, hogy melyik játékos kezd és hogy a játék aktív.

const info = document.querySelector('.game__info'); const cells = document.querySelectorAll('.cell'); const restartBtn = document.querySelector('.restart'); let gameActive = true; let currentPlayer = 'O';

A megjelenő info szövegeket nem sima, hanem függvény változókba tesszük, mert az aktuális játékos személyének változásától függően fognak változni. A ternary expression csak a névelőt változtatja, attól függően, hogy O vagy X a játékos.

let stepMessage = () => `A${currentPlayer === 'X' ? 'z' : ''} ${currentPlayer} játékos következik!`; let drawMessage = `A játszma döntetlennel zárult!`; let theWinnerMessage = () => `A győztes pedig nem más, mint a${currentPlayer === 'X' ? 'z' : ''} ${currentPlayer} játékos!!!`;

A theGame tömbben tároljuk a lépéseket, így az kezdetben üres. A winningIndex tömbben vannak a nyertes kombinációk, tehát ha ezen indexek helyén a theGame tömbben azonos karaterek vannak, akkor nyerés történt.

let theGame = ['', '', '', '', '', '', '', '', '']; const winningIndex = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ];

Ha elindul a játék, kiírjuk, hogy melyik játékos lép.

info.textContent = stepMessage();

A játék cellákra és az újra gombra felírjuk az esemény figyelőket.

cells.forEach(cell => cell.addEventListener('click', handleCellClicked)); restartBtn.addEventListener('click', handleRestart);

A megklikkelt cellánál kinyerjük a value HTML attributumból a cella indexét és amennyiben a cella még üres, akkor megjelenítjük a lépést és beírjuk a theGame tömbbe.

function handleCellClicked(event) { const cellClicked = event.target; const cellClickedIndex = parseInt(cellClicked.getAttribute('value')); if (theGame[cellClickedIndex] !== '' || !gameActive) { return; } theGame[cellClickedIndex] = currentPlayer; cellClicked.textContent = currentPlayer; handleResult(); }

A lépés után megvizsgáljuk, hogy nem történt-e nyerés vagy ha már nincs üres mező, akkor döntetlen. Ehhez végigmegyünk a nyerő indexek tömbjének nyertes hármas csoportjain. Ha nyerés történt, akkor a három cellára teszünk egy egyedi CSS osztályt, kiírjuk az üzenetet és befejezzük a játékot. Ha nem történt nyerés, akkor játékost cserélünk és várjuk a kattintást.

function handleResult() { let roundWon = false; for (let i = 0; i < 8; i++) { let a = theGame[winningIndex[i][0]]; let b = theGame[winningIndex[i][1]]; let c = theGame[winningIndex[i][2]]; if (a === '' || b === '' || c === '') { continue; } if (a === b && b === c) { roundWon = true; for (let j = 0; j < 3; j++) { cells[winningIndex[i][j]].classList.add('winner-sign'); } break; } } if (roundWon) { info.classList.add('winner'); info.textContent = theWinnerMessage(); gameActive = false; return; } let roundDraw = !theGame.includes(''); if (roundDraw) { info.textContent = drawMessage; gameActive = false; return; } currentPlayer = currentPlayer === 'O' ? 'X' : 'O'; info.textContent = stepMessage(); }

A nyerés után lehetőség van egy új játékra a újra gombbal, amikor is minden változót visszaállítunk, mintha a program most indulna.

function handleRestart() { gameActive = true; currentPlayer = 'O'; theGame = ['', '', '', '', '', '', '', '', '']; info.classList.remove('winner'); for (let j = 0; j < 9; j++) { cells[j].classList.remove('winner-sign'); } info.textContent = stepMessage(); cells.forEach(cell => cell.textContent = ''); }

A teljes kód