Godzina 1: zdarzenie, addEventListener, obiekt zdarzenia
Godzina 2: nie tylko klik - inne zdarzenia
Godzina 3: budujemy planszę 3×3
Godzina 4 (dziś): kliknięcie pola zapisuje ruch
Godzina 5: kto wygrał - sprawdzamy planszę
Rozgrzewka · 3 minuty
Rozgrzewka: plansza z poprzedniej lekcji
Ile elementów ma tablica utworzona zapisem Array(9).fill("")?
Co jest zapisane w każdym elemencie tej tablicy na starcie gry, zanim ktokolwiek kliknie?
Do czego służy atrybut data-index na przycisku planszy?
Część 1 · Nasłuch na wszystkich 9 polach naraz
Pętla for: jeden nasłuch, dziewięć przycisków
Na godzinie 1 podpięliście addEventListener do jednego przycisku. Plansza ma dziewięć przycisków - zamiast pisać dziewięć razy ten sam kod, document.getElementsByClassName("cell")(lekcja 21) zwraca kolekcję wszystkich przycisków naraz, a pętla for przechodzi po niej i do każdego podpina tę samą funkcję.
Ciekawostka z Wikipedii: kółko i krzyżyk ma dużo dłuższą historię, niż widać po tej planszy. Podobne gry z trzema polami w rzędzie grano już w starożytnym Egipcie ponad 3000 lat temu, a w Rzymie pod nazwą terni lapilli. W 1952 roku brytyjski informatyk Sandy Douglas zaprogramował ją na komputerze EDSAC jako grę OXO - jedną z pierwszych gier komputerowych w historii.
MDN: Learn_web_development/Core/Scripting/Events, sekcja „Using addEventListener()” (mechanizm z lekcji 66); kolekcja getElementsByClassName: MDN, Web/API/Document/getElementsByClassName, jak w lekcji 51 (deck 51_js_dom_2_slajdy.html)
Część 2 · Które pole kliknięto
event.target.dataset: numer pola z atrybutu HTML
Wszystkie przyciski wywołują tę samą funkcję handleMove - ale każdy ma inny numer pola zapisany w atrybucie data-index. Właściwość dataset elementu (event.targetz godziny 1 tego tematu) czyta te atrybuty jako zwykły tekst.
MDN, Web/API/HTMLElement/dataset: „The dataset read-only property of the HTMLElement interface provides read/write access to custom data attributes (data-*) on elements. It exposes a map of strings (DOMStringMap) with an entry for each data-* attribute.” - „Właściwość dataset (tylko do odczytu) interfejsu HTMLElement zapewnia dostęp do niestandardowych atrybutów danych (data-*) na elementach. Udostępnia mapę ciągów znaków (DOMStringMap) z jednym wpisem na każdy atrybut data-*.” (tłum. własne); „For example, a data-abc-def attribute corresponds to dataset.abcDef.” - „Na przykład atrybut data-abc-def odpowiada dataset.abcDef.” (tłum. własne)
Część 3 · Sprawdzamy i zapisujemy ruch
handleMove: czy pole jest puste?
functionhandleMove(event) {
const index = event.target.dataset.index;
if (board[index] !== "") {
return; // pole zajęte, nic nie rób
}
board[index] = currentPlayer;
event.target.textContent = currentPlayer;
}
Kolejność ma znaczenie: najpierw sprawdzacie, czy board[index] jest puste (""). Dopiero gdy jest puste, wpisujecie znak gracza do tablicy board (dane) i do textContent przycisku (to, co widać na ekranie) - dwa różne miejsca, ten sam znak.
Część 4 · Zmieniamy gracza po ruchu
currentPlayer: operator warunkowy przełącza X na O
currentPlayer = currentPlayer === "X" ? "O" : "X";
Ten sam efekt da zapis z if: if (currentPlayer === "X") { currentPlayer = "O"; } else { currentPlayer = "X"; } - operator warunkowy (?:) to krótszy zapis tego samego „jeśli-inaczej” (dział Warunki, lekcje 7-8).
Operator warunkowy (ang. conditional (ternary) operator): dział Warunki, lekcja 37 (37_js_warunki_1_slajdy.html)
Egzamin E.14
Tak pytają na egzaminie
1. Funkcja JavaScript powinna być wywołana za każdym razem, gdy użytkownik wpisze dowolny znak do pola edycji. Którego zdarzenia należy użyć?
onload
onclick
onkeydown
onmouseout
CKE, E.14, czerwiec 2017, zadanie 32
Ćwiczenie przy komputerze · 20 minut
Ćwiczenie: kliknięcie pola zapisuje ruch
Otwórztic-tac-toe.html i tic-tac-toe.js z poprzedniej lekcji (plansza z dziewięcioma przyciskami klasy cell, każdy z data-index od 0 do 8, tablica let board = Array(9).fill("")).
Dodaj zmienną let currentPlayer = "X"; - kto zaczyna.
Pętlą for po document.getElementsByClassName("cell") podepnij addEventListener("click", handleMove) do każdego z dziewięciu przycisków.
Napisz funkcję handleMove(event): odczytaj event.target.dataset.index, sprawdź, czy board[index] === "".
Jeśli pole jest puste: wpisz currentPlayer do board[index] i do event.target.textContent, potem przełącz gracza operatorem warunkowym.
Sprawdź w przeglądarce: kliknij kilka pól po kolei - znaki mają się zmieniać X, O, X, O; kliknięcie zajętego pola ma nic nie robić.
Dla chętnych: zapisz zmiany w swoim repozytorium - commit „Dodaję obsługę kliknięcia pola”.
Cały plik
// tic-tac-toe.js (dopisane do pliku z poprzedniej lekcji)let board = Array(9).fill("");
let currentPlayer = "X";
const cells = document.getElementsByClassName("cell");
for (let i = 0; i < cells.length; i++) {
cells[i].addEventListener("click", handleMove);
}
functionhandleMove(event) {
const index = event.target.dataset.index;
if (board[index] !== "") {
return;
}
board[index] = currentPlayer;
event.target.textContent = currentPlayer;
currentPlayer = currentPlayer === "X" ? "O" : "X";
}
Kod sprawdzony node --check. Logika bez DOM sprawdzona w Node.js: ruch na pole 4 zapisuje X, kolejny ruch na to samo pole jest odrzucany, gracz przełącza się X → O → X; board["2"] działa tak samo jak board[2] - dataset.index zwraca tekst.
Na koniec
Sprawdź się
Dlaczego pętla for jest wygodniejsza niż podpinanie nasłuchu do każdego z dziewięciu przycisków osobno?
Skąd funkcja handleMove wie, które dokładnie pole zostało kliknięte?
Co sprawdza if (board[index] !== "") i co się dzieje, gdy warunek jest prawdziwy?
To tyle na dziś - kolejny temat: kto wygrał, sprawdzamy planszę.
Poza szkołą
A jak to wygląda w produkcji?
Na lekcji
W produkcji
Status
getElementsByClassName + pętla for
document.querySelectorAll(".cell") zwraca NodeList z gotową metodą forEach() - krótszy zapis tej samej pętli, częstszy w nowym kodzie.
rozszerzane
Stan gry w zwykłej tablicy board
W większych aplikacjach stan trzyma framework (np. React, Vue) i sam przerysowuje widok po każdej zmianie stanu.
rozszerzane
Sprawdzenie board[index] !== "" wprost w handleMove
Reguły gry (czy ruch dozwolony, kto wygrał) wydziela się do osobnego pliku - dzięki temu da się je testować bez przeglądarki.
rozszerzane
Znak gracza wpisywany przez textContent przycisku
Bardziej złożone gry rysują planszę na <canvas> albo w WebGL zamiast osobnych elementów HTML na każde pole.
zastępowane (w dużych grach)
MDN, Web/API/Document/querySelectorAll (metoda i NodeList.forEach)