Egy klasszikus egyszerű amőba játék, 3×3-as játékmezőn.
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;
}
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 = '');
}