Plan tematu (5 godzin):
addEventListener, obiekt zdarzeniaboard[index] !== "" w funkcji handleMove i co się dzieje, gdy jest prawdziwy?handleMove wie, które dokładnie pole tablicy board zmienić po kliknięciu?currentPlayer przełącza się między "X" i "O" po udanym ruchu?Kółko i krzyżyk kończy się wygraną, gdy trzy takie same znaki (XXX albo OOO) staną w jednej linii: w rzędzie, w kolumnie albo na przekątnej. Na planszy 3×3 (indeksy 0-8 z lekcji 28) jest dokładnie osiem takich linii. Każdą linię zapisujecie jako trójkę indeksów tablicy board, a wszystkie osiem trójek razem - jako tablicę tablic.
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]
];
for: po kolei, aż do trafieniafor (let i = 0; i < winningLines.length; i++) {
const a = winningLines[i][0];
const b = winningLines[i][1];
const c = winningLines[i][2];
if (board[a] === board[b] && board[b] === board[c] && board[a] !== "") {
return board[a];
}
}
Pętla przechodzi po kolei przez wszystkie osiem trójek z winningLines (i < winningLines.length, tak jak w lekcji 13 - długość tablicy, nie liczba 8 wpisana na sztywno). Dla trójki [a, b, c] sprawdza, czy wszystkie trzy pola mają ten sam znak i czy ten znak nie jest pustym tekstem "" (bo trzy puste pola też są sobie równe). Gdy trafi, return od razu kończy funkcję - kolejne trójki nie są już sprawdzane.
for po tablicy: lekcja 43 (43_js_tablice_3_slajdy.html)includes()Gdy pętla sprawdzi wszystkie osiem trójek i żadna nie pasuje, gra jeszcze się nie skończyła zwycięstwem. Mogła się jednak skończyć remisem: remis jest wtedy, gdy w tablicy board nie ma już ani jednego pustego pola. Metoda includes("") sprawdza, czy w tablicy JEST jeszcze pusty tekst - znak ! przed nią odwraca odpowiedź na „nie ma już pustych pól”.
if (!board.includes("")) {
return "remis";
}
return null; // gra jeszcze trwa
Gdy gra jeszcze trwa (są puste pola i nikt nie wygrał), funkcja zwraca null - handleMove wie wtedy, że ma przełączyć gracza i grać dalej.
resetGame(): plansza wraca do zerafunction resetGame() {
board = Array(9).fill("");
currentPlayer = "X";
for (let i = 0; i < cells.length; i++) {
cells[i].textContent = "";
}
status.textContent = "";
}
Przycisk id="newGame" dostaje addEventListener("click", resetGame). Kliknięcie zamienia board z powrotem na dziewięć pustych pól (ten sam zapis Array(9).fill("") co na starcie gry, lekcja 28), ustawia currentPlayer z powrotem na "X", czyści tekst na każdym przycisku (textContent, lekcja 22) i czyści komunikat w #status.
Array(9).fill(""): lekcja 68; textContent: lekcja 52 (MDN, Web/API/Node/textContent, zacytowane w 54_js_dom_5_slajdy.html)Ten temat to projekt własny - bank CKE nie ma pytania o sprawdzanie planszy do gry. Ale funkcja checkWinner() to dokładnie ten sam wzorzec: pętla for z warunkiem sprawdzanym przy każdym przejściu przez tablicę. Tego wzorca dotyczy pytanie z banku:
var result = 0;
for (i = 0; i < 100; i++)
if (numbers[i] % 2 == 0) result += numbers[i];
Po wykonaniu przedstawionego kodu JavaScript, działającego na wcześniej zainicjalizowanej tablicy numbers, w zmiennej result jest przechowywana suma
tic-tac-toe.html i tic-tac-toe.js z poprzednich dwóch godzin (plansza, tablica board, currentPlayer, funkcja handleMove(event)).tic-tac-toe.html dodaj pod planszą <p id="status"></p> i przycisk <button type="button" id="newGame">Nowa gra</button>.tic-tac-toe.js zapisz const winningLines z ośmioma trójkami indeksów (3 rzędy, 3 kolumny, 2 przekątne).checkWinner(): pętlą for sprawdź każdą trójkę - gdy trzy pola mają ten sam, niepusty znak, zwróć go; gdy żadna nie pasuje i board.includes("") jest fałszywe, zwróć "remis", w przeciwnym razie null.handleMove(event) wywołaj checkWinner() zaraz po wpisaniu znaku: gdy wynik to "remis" albo znak gracza, wpisz komunikat do #status i zakończ funkcję (return) - dopiero gdy nikt nie wygrał, przełącz gracza jak w lekcji 29.resetGame(): wyczyść board, ustaw currentPlayer na "X", wyczyść tekst każdego przycisku i #status; podepnij ją pod addEventListener("click", ...) przycisku #newGame.tic-tac-toe.html<!-- tic-tac-toe.html, caly plik -->
<!DOCTYPE html><html lang="pl"><head>
<meta charset="utf-8"><title>Kółko i krzyżyk</title>
<style>#board{display:grid;grid-template-columns:repeat(3,100px);gap:4px}
#board button{height:100px;font-size:2rem}</style>
</head><body>
<div id="board">
<button type="button" class="cell"></button> <button type="button" class="cell"></button> <button type="button" class="cell"></button>
<button type="button" class="cell"></button> <button type="button" class="cell"></button> <button type="button" class="cell"></button>
<button type="button" class="cell"></button> <button type="button" class="cell"></button> <button type="button" class="cell"></button>
</div>
<p id="status"></p>
<button type="button" id="newGame">Nowa gra</button>
<script src="tic-tac-toe.js"></script>
</body></html>
node --check; siatka i przyciski: lekcja 28; #status i #newGame: nowe w tej lekcjitic-tac-toe.js// tic-tac-toe.js, część 1: z lekcji 28-29 + nowe przygotowanie
let board = Array(9).fill("");
let currentPlayer = "X";
const cells = document.getElementsByClassName("cell");
for (let i = 0; i < cells.length; i++) {
cells[i].setAttribute("data-index", i);
cells[i].addEventListener("click", handleMove);
}
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]
];
const status = document.getElementById("status");
const newGameButton = document.getElementById("newGame");
newGameButton.addEventListener("click", resetGame);
node --check; pętle podpinające: lekcje 28-29 (tu dla zwięzłości połączone w jedną pętlę); dalszy ciąg w dodatku 3/4 i 4/4handleMove: rozwiązanie// tic-tac-toe.js, część 2: handleMove (zmieniona - dopisane sprawdzenie zwycięzcy)
function handleMove(event) {
const index = event.target.dataset.index;
if (board[index] !== "") {
return;
}
board[index] = currentPlayer;
event.target.textContent = currentPlayer;
const winner = checkWinner();
if (winner === "remis") {
status.textContent = "Remis!";
return;
}
if (winner !== null) {
status.textContent = "Wygrywa " + winner + "!";
return;
}
currentPlayer = currentPlayer === "X" ? "O" : "X";
}
node --check; pierwsze pięć linii ciała funkcji: lekcja 29; wywołanie checkWinner() i komunikat w #status: nowe w tej lekcjicheckWinner i resetGame: rozwiązanie// tic-tac-toe.js, część 3: checkWinner i resetGame (nowe)
function checkWinner() {
for (let i = 0; i < winningLines.length; i++) {
const a = winningLines[i][0];
const b = winningLines[i][1];
const c = winningLines[i][2];
if (board[a] === board[b] && board[b] === board[c] && board[a] !== "") {
return board[a];
}
}
if (!board.includes("")) {
return "remis";
}
return null;
}
function resetGame() {
board = Array(9).fill("");
currentPlayer = "X";
for (let i = 0; i < cells.length; i++) {
cells[i].textContent = "";
}
status.textContent = "";
}
node --check; logika uruchomiona w Node.js (bez DOM) na 3 planszach: ["X","X","X","O","O","","","",""] → "X" (wygrana w rzędzie 0-1-2), ["O","X","X","X","O","","","","O"] → "O" (wygrana na przekątnej 0-4-8), ["X","O","X","X","O","O","O","X","X"] → "remis" (pełna plansza, brak trójki)checkWinner()?board oznacza remis?board i na ekranie kliknięcie przycisku „Nowa gra”?To tyle na dziś - gra w kółko i krzyżyk jest gotowym projektem.
| Na lekcji | W produkcji | Status |
|---|---|---|
8 układów zwycięskich wpisanych ręcznie do winningLines | Przy większych planszach (np. Connect Four, Gomoku) liczba układów rośnie bardzo szybko - silniki gier liczą je algorytmicznie zamiast wpisywać ręcznie. | zastępowane |
checkWinner() skanuje od nowa całą tablicę winningLines po każdym ruchu | Sprawdza się tylko linie przechodzące przez pole ostatniego ruchu - mniej porównań, szybsze przy większych planszach. | zastępowane |
| Przeciwnik to drugi uczeń klikający tę samą planszę | Przeciwnika komputerowego liczy algorytm przeglądający możliwe ruchy i wybierający najlepszy (np. minimax) - kółko i krzyżyk to klasyczny przykład uczący tego algorytmu. | inna forma |