Saper w JavaScript

Aplikacje Internetowe: Kurs › Saper w JavaScript

W poniższym kursie będę prezentował tematy, które przerabiam w ramach Specjalizacji z uczniami 3 klasy Technikum Elektrycznego w Słupsku. Kurs ma służyć zarówno uczniom w powtórce materiału, jak i wszytkim zainteresowanym programowaniem Aplikacji Internetowych.

Na poprzedniej lekcji uczyliśmy się jak w JavaScripcie posługiwać się tablicami. Czas na praktyczne zastosowanie. Gra Saper znana z Windows doskonale się do tego nadaje. Na tej lekcji napiszemy grę w Sapera, ale ostateczne szlify pozostawię uczniom/czytelnikom.

saper-w-js-1.jpg
Gra Saper w Windows 7

Saper - reguły gry

Gra w Sapera, na najniższym poziomie trudności, odbywa się na planszy 9x9, na której w losowych miejscach są postawione bomby, wszystkie pola na początku gry są zakryte. Gracz odkrywając kolejne pola może trafić na bombę lub na pole bez bomby. Pole bez bomby zawiera informacje ile bomb znajduje się w pobliżu tego pola (uzględniając kierunki góra, dół, prawo, lewo, oraz skosy). Zadaniem gracza jest odsłonięcie wszystkich pól na których nie ma bomb.

Tablica w tablicy

Jak pamiętamy tablice mogą zwierać dane różnego typu, mogą to być liczby całkowite, liczby zmiennoprzecinkowe, teksty itp. Elementami tablicy mogą być rownież inne tablice. Przykład definicji tablicy zawierającej inne tablice wygląda tak:

var tablice = [[80, 56], ["poniedziałek", "wtorek"]]

Pierwszym elementem tablicy tablice jest tablica zawierająca dwie liczby całkowite, drugim elementem tablicy tablice jest natomiast tablica zawierająca dwa teksty.
Do naszej gry w Sapera wykorzystamy właśnie tą właściwość tablic, główna tablica będzie zawierała wiersze, każdy wiersz będzie również tablicą komórek w tym wierszu.

var t=[]; //definicja tablicy zawierającej wiersze
t[0]; //pierwszy wiersz
t[0] = []; //pierwszy wiersz zawierający tablicę komórek
t[0][0]; //pierwsza komórka w pierwszym wierszu;
t[3][4]; //piąta komórka w czwartym wierszu;

Tworzymy tablicę do gry w Sapera

Wytłumaczyłem jak będą zapisane poszczegółne wiersze i kolumny, teraz czas na trochę praktyki. Na początek stwórzmy strone HTML na której odbędzie się gra:

<html>
<head>
<title>Gra: Saper</title>
</head>
<body>
<form>
<button onclick="start();return false;">Start</button>
<div id="plansza"></div>
</form>
</body>
</html>

Jak widać strona składa się z przycisku, do którego podpięta jest funkcja start() oraz div o id=plansza w którym będziemy wyświetlać naszą planszę do gry. Celowo nie tworzę jej statycznie w HTML, gdyż w rozbudowanej wersji naszego Sapera to gracz będzie decydował na jak dużej planszy chce grać.

W funkcji start() napiszemy:

  1. tworzenie pustej planszy do gry
  2. postawienie w losowych miejscach bomb
  3. oznaczenie pół sąsiadujących z bombami
  4. wyświetlenie planszy

Tworzenie pustej planszy 9x9 do gry

Ustalamy sobie, że wpisanie do tablicy -1 będzie oznaczało bombę, a każda inna liczba, ilość bomb sąsiadujących z tym polem 0..8
Na naszej stronie w sekcji <head> tworzymy skrypt:

<head>
<script>
var t = []; //ustawienie naszej tablicy jako zmienna globalna
function start() {
t=[]; //czyszczenie tablicy
for (var y=0;y<=8;y++) { //wiersze od 0 do 8
t[y]=[];
for (var x=0;x<=8;x++) { //kolumny od 0 do 8
t[y][x]=0; //zerujemy każde pole planszy
}
}
}
</script>
</head>
...

Mamy pustą planszę do gry, jednak niestety nie widzimy tego na ekranie czas więc na funkcję pokaz() pokazującą naszą planszę, umiescimy ją skrypcie przed funkcją start():

<script>
var t=[];
function pokaz() {
var text = "";
for (y=0; y<=8; y++) {
for (x=0; x<=8; x++) {
text += t[y][x]+" ";
}
text += "<br/>"; //po każdym wierszu, do następnej lini
}
//pobranie div id=plansza i ustawienie zawartości
var elem = document.getElementById("plansza");
elem.innerHTML=text;
}
function start() {
...
pokaz();
}
</script>

Jak widać funkcję pokaz() wywołaliśmy na końcu fukcji start. Uruchamiamy naszą stronę, wciskamy Start, powinna wyglądać następująco

0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0

Mamy więc pustą planszę, na której możemy stawiać bomby.

Stawiamy bomby

Bomby musimy postawić w losowych miejscach planszy, wiec wykorzystamy do tego funkcję Math.random() języka JavaScript. Funkcja ta losuje liczbę z zakresu 0..1. W naszym przypadku musimy wylosować zarówno wiersz jak i kolumnę w której postawimy bombę. Wiersze i kolumny mają indeksy w zakresie 0..8, w dodatku są liczbami całkowitymi. Zakres losowania zwiększymy bardzo prosto:

var y = Math.random() * 8; //losujemy z zakresu 0..8

Pozostaje jedynie zakrąglić wylosowaną liczbę do najbliższej liczby całkowitej, wykorzystamy do tego Math.round():

var y = Math.round(Math.random() * 8);

napiszmy więc funkcję stawiającą bomby, ilośc bomb do postawienia bedzie argumentem tej funkcji

function postawbomby(ile) {
while (ile>0) {
var y = Math.round(Math.random() * 8); //losujemy wiersz
var x = Math.round(Math.random() * 8); //losujemy kolumne
t[y][x]=-1; //stawiamy bombe w wylosowanym miejscu
ile--; //zmniejszamy ilość bomb pozostałych do postawienia
}
}

Ok, teraz nieznacznie zmodyfikujemy naszą funkcję start() tuż przed wywołaniem funkcji pokaz(), wywołamy funkcję postawbomby().

function start() {
...
postawbomby(10); //stawiamy 10 bomb
pokaz();
}

 Po uruchomieniu strony i przyciśnięciu Start powinniśmy uzyskać coś takiego:

0 0 0 0 -1 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 -1 0 0 0 0 0 0
0 0 0 0 0 0 -1 0 0
0 0 -1 0 0 -1 0 0 0
0 0 0 0 0 0 0 -1 0
0 0 0 -1 0 0 0 0 0
0 -1 0 0 0 0 0 0 0
0 0 0 0 -1 0 0 0 0

Kolejne przyciskanie Start powinno generować za każdym razem inne ustawienie bomb. Ok, ale czy nie zauważyliście, że czasem bomb nie jest 10 tylko 9 albo nawet 8 ? Dzieje się tak dlatego, iż całkiem możliwe jest takie losowanie, że bombę postawimy w miejscu, w którym już stoi inna bomba. Musimy zabezpieczyć się przed takim scenariuszem. Zmodyfikujemy funkcję postawbomby() aby sprawdzała czy w wylosowanej pozycji nie ma bomby:

function postawbomby(ile) {
while (ile>0) {
var y = Math.round(Math.random() * 8); //losujemy wiersz
var x = Math.round(Math.random() * 8); //losujemy kolumne
if (t[y][x] != -1) { //czy w wylosowanym miejscu nie ma bomby
t[y][x]=-1; //stawiamy bombe w wylosowanym miejscu
ile--; //zmniejszamy ilość bomb pozostałych do postawienia
}
}
}

Ok, bomby mamy, czas poinformować sąsiadów

Sąsiedzie to ja bomba, jestem obok ciebie

Jak pamiętamy z zasad gry w Sapera, w polach, w których nie ma bomby jest informacja, ile bomb znajduje się w sąsiedztwie tego pola. Efekt taki będzie najprościej uzyskać, gdy po postawieniu bomby, zwiększymy zawartość pola każdego sąsiada o 1 (pamietamy że na początku każde pole ma wartość 0). Zanim napiszemy funkcję, powinniśmy zwrócić uwagę na to, że bomba może być postawiona przy którejś z krawędzi, wtedy z tej strony nie ma sąsiada. Na początek w naszej funkcji napiszemy informowanie sąsiada z lewej o obecności bomby. Jeśli pozycja bomby to t[y][x], to pozycja sąsiada z lewej strony będzie t[y][x-1]. Z tą wiedzą przystępujemy do pisania funkcji

function sasiad(y,x) {
//z lewej strony
if (x>0) { //czy bomba nie jest przy lewej krawędzi
t[y][x-1] = -1;
}
}

Nestety i tym razem jest pewien haczyk. Jeśli w sąsiednim polu też jest bomba, to ta informacja zostanie skasowana (zmiana z -1 na 0), co jest działaniem niepożądanym, powinniśmy więc sprawdzać czy w polu sąsiednim nie stoi już bomba, modyfikujemy więc nasz kod:

function sasiad(y,x) {
//z lewej strony
if ((x>0) && (t[y][x-1] != -1)) { //czy tam nie ma bomby
t[y][x-1] = -1;
}
}

Teraz nasza funkcja będzie działać prawidłowo, wstawmy więc jej wywołanie, najlepiej będzie ją wywołać od razu po postawieniu bomby, czyli w funkcji postawbomby():

function postawbomby(ile) {
while (ile>0) {
var y = Math.round(Math.random() * 8); //losujemy wiersz
var x = Math.round(Math.random() * 8); //losujemy kolumne
if (t[y][x] != -1) { //czy w wylosowanym miejscu nie ma bomby
t[y][x]=-1; //stawiamy bombe w wylosowanym miejscu
ile--; //zmniejszamy ilość bomb pozostałych do postawienia
sasiad(y,x); //informujemy sąsiadów
}
}
}

Jeśli uruchomimy stronę, to po przyciśnieciu Start pokaże się coś w tym stylu:

0 0 0 1 -1 0 0 0 0
0 0 0 0 0 0 0 0 0
0 1 -1 0 0 0 0 0 0
0 0 0 0 0 1 -1 0 0
0 1 -1 0 1 -1 0 0 0
0 0 0 0 0 0 1 -1 0
0 0 1 -1 0 0 0 0 0
1 -1 0 0 0 0 0 0 0
0 0 0 1 -1 0 0 0 0

Jak widać, z lewej strony każdej bomby pole ma wartość 1, analogicznie poinformujemy sąsiadów z prawej, z góry i z dołu, nasz funkcja sasiad() bedzie wygladala nastepujaco:

function sasiad(y,x) {
//z lewej strony
if ((x>0) && (t[y][x-1] != -1)) { //czy bomba nie jest przy lewej krawędzi
t[y][x-1] = -1;
}
//z prawej strony
if ((x<8) && (t[y][x+1] !=-1)) {
t[y][x+1] = -1;
}
//z góry
if ((y>0) && (t[y-1][x] !=-1)) {
t[y-1][x] = -1;
}
//z dołu
if ((y<8) && (t[y+1][x] != -1)) {
t[y+1][x] = -1;
}
}

Przy oznaczaniu z prawej i zdołu musimy uważać aby nie "wyjść" poza planszę stąd warunek x<8y<8 (nasza planasza posiada pola od 0..8, można zastosować tutaj zmienną jeśli chcemy zrobić Sapera dla innych rozmiarów planszy).

Funkcja sasiad() nie jest kompletna, nie uwzględnia sąsiadów po skosie, to zadanie dla Was

Ale czemu to nie wygląda jak plansza

Faktycznie, na razie mamy tylko jakieś liczby, czasem trudno się połapać, w której kolumnie jest każda z liczb. Musimy więc trochę bardziej się przyłożyć do "rysowania" planszy. Idealnie do tego celu będzie się nadawała HTML-owa tabelka, czyli TABLE, modyfikujemy więc naszą funkcję pokaz() :

  function pokaz() {
var text = "<table>";
for (y=0; y<=8; y++) {
text += "<tr>"; //nowy wiersz tabeli
for (x=0; x<=8; x++) {
text+= "<td><div>" + t[y][x]+ "</div></td>";
}
text+="</tr>"; //koniec wiersza tabeli
}
text += "</table>"; //koniec tabeli
var elem = document.getElementById("plansza");
elem.innerHTML = textl
}

Ok, wygląda nieznacznie lepiej, ale to jeszcze nie to, ale od czego mamy style, uczyliśmy się przecież o nich na poprzednich lekcjach. Ostylujmy więc nasze komórki tabeli dodając arkusz styli do naszej strony:

<head>
<style>
td {
width: 30px;
height: 30px;
background-color: gray;
color: blue;
border: 1px solid black;
}
</style>
...
</head>

Odpalamy stronę, wciskamy Start, i jak ? Podoba się ? Już jest całkiem fajnie, jeszcze tylko dla czytelności planszy zamieńmy reprezentację naszych bomb z -1 na *, co trochę lepiej bedzie udawało bombę. Chętni mogą zamiast * umieścić jakiś obrazek w tagu img, ale to już pozostawiam Wam, na razie po raz kolejny zmodyfikujemy naszą funkcję pokaz() :

  function pokaz() {
var text = "<table>";
for (y=0; y<=8; y++) {
text += "<tr>"; //nowy wiersz tabeli
for (x=0; x<=8; x++) {
text+= "<td><div>";
if (t[y][x] == -1) {
text += "*"; //w przypadku bomby postaw *
} else {
text += t[y][x]; //w przeciwnym wypisz liczbę
}
text += "</div></td>";
}
text+="</tr>"; //koniec wiersza tabeli
}
text += "</table>"; //koniec tabeli
var elem = document.getElementById("plansza");
elem.innerHTML = text;
}

Plansza jest, wyglada już podobnie do tej z Sapera, musimy ją jedynie "zasłonić" przyciskami, które potem gracz będzie odsłaniał, ale to już w następnym odcinku.


cdn...

Komentarze

Komentarze do Saper w JavaScript na it.jursza.com