Wstęp
Celem tego artykułu jest opisanie procesu tworzenia opisanej na stronie gry 3 w linii w wersji na przeglądarkę wykorzystując HTML, CSS i JavaScript.
Kod źródłowy
index.html - kod źródłowy
- <html>
- <head>
- <meta charset="UTF-8">
- <script type="text/javascript" src="g00s.js"></script>
- <link rel="stylesheet" href="g00c.css" type="text/css">
- </head>
- <body onload="newGame();">
- <div id="gboard"></div>
- <div id="gstatus"></div>
- </body>
- </html>
Powyższy szablon pozwala wyświetlić grę jako niezależną stronę. W celu zamieszczenia projektu na dowolnej podstronie witryny wystarczy zadeklarować używanie pliku z silnikiem gry ("g00s.js") oraz stylu wyglądu strony ("g00c.css"). Cała gra opiera się na szablonie zawartym w body, który można też umieścić w dowolnym divie. Obiekt "gboard" to plansza, a gstatus to element, który będzie wyświetlał komunikaty dotyczące rozgrywki.
g00s.js - kod źródłowy
- var items, player, sel, selN;
- var userLang=(navigator.language||navigator.userLanguage).substr(0,2);
- var playern=["G1","G2"];
Deklaracja podstawowych zmiennych. Kolejno oznaczają:
- items - przechowuje tablice z informacjami o pionkach
- player - wskazuje, którego gracza jest tura
- sel - przechowuje pozycje wybranego pionka
- selN - przechowuje nazwę zaznaczonego pionka
- userlang - przechowuje dane o domyślnym języku
- playern - przechowuje nicki graczy
- function newGame(){
- items=[[2,1,2],[0,0,0],[1,2,1]];
- player=0; sel=0;
- for(var i=0;i<2;i++)
- playern[i]=getLine(i,[]);
- document.getElementById('gboard').innerHTML='<div class="gboardb"></div>';
- var tempP=1,tempC=0;
- for(var x=0;x<items.length;x++){
- for(var y=0;y<items[x].length;y++){
- if(items[x][y]!=0){
- document.getElementById('gboard').innerHTML+=getPiece(tempP,items[x][y]);
- setPos('gP'+tempP,x,y);
- tempP++;
- }
- document.getElementById('gboard').innerHTML+=getField(x+"_"+y,tempC);
- setPos('gF'+x+"_"+y,x,y);
- tempC=increase(tempC);
- }
- }
- addEl(getField('sel','s'));
- refSel();
- whoMove();
- }
Elementowi items jest przypisywana tablica z rozkładem pionków tj. "0" - puste, "1" - pionek gracza 1, "2" - pionek gracza 2. Następnie ustawiamy gracza, który rozpoczyna i ustawiamy zerowe zaznaczanie. (8.) Pętla przepisuje z tablicy językowej domyślne wartości nazwy graczy. (10.) Element gboardb jest to czarne tło pomiędzy polami i planszą. (11.) Deklaracja zmiennych pomocniczych, które pomagają nazywać pola i pionki. (12. - 23.) Pętla, która tworzy dla każdego elementu listy pole oraz dla wartości tablicy >0 pionek odpowiednio sformatowany. (24.) Wprowadzony element "sel" odpowiada za pokazywanie zaznaczonego elementu i (25.) odświeża jego pozycje. (26.) Pokazuje komunikat czyj ruch.
- function endGame(){
- remSel();
- updateStatus(getLine(5,[]));
- addEl('<div id="gwin" onclick="newGame();"><p style="display:table-cell;vertical-align:middle;">'+getLine(6, [getPlayerName()])+'</p></div>');
- }
Funkcja odpowiedzialna za wyświetlenie wyniku. (29.) Usuwa zaznaczenie. (30.) Wyświetla informacje o zakończeniu rozgrywki. (31.) Pokazuje komunikat o zwycięzcy.
- function addEl(div){document.getElementById('gboard').innerHTML+=div;}
Dodaje element na plansze. Wykorzystywane przy inicjalizacji gry w celu dodania pól oraz pionków.
- function setPos(id,nX,nY){
- document.getElementById(id).style.top=25+nX*72-(getInt(document.getElementById(id).style.width)/2);
- document.getElementById(id).style.left=25+nY*72-(getInt(document.getElementById(id).style.height)/2);
- }
Ustala pozycję obiektu na planszy na podstawie danych.
- function select(el){
- var p=posfromXY(el.style.top,el.style.left);
- if(items[p[0]][p[1]]==player+1){
- setSel(p[0],p[1]);
- selN=el.attributes["id"].value;
- } else {
- updateStatus(getLine(4, [getPlayerName()])+playerSign());
- remSel();
- }
- }
(43.) Oblicza pozycją obiektu na planszy np. (1,1) na podstawie położenia. (44.) Sprawdza czy pionek należy do gracza, który ma turę. Jeśli tak to zapisuje dane pionka: (45.) pozycję i jego (46.) id. Jeśli jednak to nie pionek gracza to (48.)pokazuje stosowny komunikat i (49.) usuwa zaznaczenie.
- function getPlayerName(){return playern[player];}
Zwraca nick n-tego graca.
- function checkField(x,y){
- if(items[x][y]==(player+1))
- return 1;
- return 0;
- }
Jeśli na odpytywanym polu jest pionek gracza to zwraca 1 jeśli nie to 0.
- function checkWin(p){
- if(checkField(p[0],0)+checkField(p[0],1)+checkField(p[0],2)==3) return true; else
- if(checkField(0,p[1])+checkField(1,p[1])+checkField(2,p[1])==3) return true; else
- if(checkField(0,0)+checkField(1,1)+checkField(2,2)==3) return true; else
- if(checkField(0,2)+checkField(1,1)+checkField(2,0)==3) return true; else
- return false;
- }
Na koniec tury odpytuje wszystkie możliwe wygrywające kombinacje na podstawie współrzędnych ostatniego ruchu. Jeśli którakolwiek suma wynosi 3 oznacza to pełną linie. Dzięki skorzystaniu ze współrzędnej ruchu ograniczamy ilość kombinacji do sprawdzenia. Sprawdzamy tylko 4, a nie 8.
- function tryMove(el){
- var p=posfromXY(el.style.top,el.style.left);
- if(Math.round(Math.sqrt(Math.pow(sel[0]-p[0],2)+Math.pow(sel[1]-p[1],2)))==1){
- items[p[0]][p[1]]=items[sel[0]][sel[1]];
- items[sel[0]][sel[1]]=0;
- setPos(selN,p[0],p[1]);
- if(checkWin(p))
- endGame();
- else
- nextPlayer();
- } else {
- updateStatus(getLine(3, [getPlayerName()])+playerSign());
- }
- remSel();
- }
Funkcja jest wykonywana, gdy jest wybrany pionek i klikniemy na dowolne pole. (70.)Obliczamy pozycję zaznaczenia. (71.)Obliczamy odległość zaznaczonego pionka od zaznaczonego pola. Według zasad poprawny ruch to ruch na sąsiednie wolne pole. Daje nam to dwa rozwiązania prawidłowe dla odległości pomiędzy punktami: 1 oraz 1.44..., oba zaokrąglone w dół dają jeden. W przypadku powodzenia (72.-74.)zmieniamy pozycję pionka, (75.)sprawdzamy zwycięstwo i jeśli gracz wygrał to (76.) kończymy grę, jeśli gra toczy się dalej to (78.)przekazujemy ture. Jeśli jednak odległość jest nieprawidłowa to (80.) pokazujemy stosowny komunikat. (82.)Funkcję kończymy usuwając zaznaczenia niezależnie od pierwotnego warunku z odległością.
- function updateStatus(txt){document.getElementById('gstatus').innerHTML=txt;}
Aktualizuje komunikat wyświetlany w obszarze statusu gry.
- function whoMove(){updateStatus(getLine(2, [getPlayerName()])+playerSign());}
Wysyła aktualizację z informacją którego gracza jest ruch.
- function playerSign(){return ' <svg height="16" width="16"><circle cx="9" cy="9" r="6" stroke="black" stroke-width="1" class="gpc'+(player+1)+'" /></svg>';}
Pobiera małe kółko w kolorze gracza. Funkcja wykorzystywana w celu szybkiego powiadomienia, którego gracza dotyczy komunikat.
- function remSel(){selN=="";setSel(-1,-1);}
Usuwa zaznaczenie wybranego pionka.
- function setSel(x,y){
- if(x==-1&&y==-1)
- sel=0;
- else
- sel=[x,y];
- refSel();
- }
Funkcja zapisuje wybraną pozycję i odświeża pozycję obiektu zaznaczenia.
- function refSel(){
- if(sel==0)
- document.getElementById('gFsel').style.display='none';
- else{
- setPos('gFsel',sel[0],sel[1]);
- document.getElementById('gFsel').style.display='block';
- }
- }
(99.) Sprawdza czy istnieje zaznaczenie. Jeśli zaznaczenie wynosi 0 to nic nie jest zaznaczone i (100.) ukrywa obiekt zaznaczenia, w przeciwnym wypadku (102.) ustala pozycję na planszy i (103.) pokazuje obiekt.
- function posfromXY(x,y){return [(getInt(x)-25)/72,(getInt(y)-25)/72];}
Oblicza pozycję na planszy na podstawie współrzędnych obiektu.
- function nextPlayer(){player=increase(player);whoMove();}
Przekazuje turę następnemu graczowi i pokazuje komunikat czyj ruch.
- function increase(i){return ((i+1)%2);}
Funkcja zwiększa wartość podanej zmiennej i trzyma ją w zakresie (0-1).
- function getPiece(id,style){return '<svg onclick="select(this);" id="gP'+id+'" class="gpd"><circle class="gpc'+style+' gp_" cx="32"cy="32"r="26"stroke="black"stroke-width="3"/></svg>';}
Zwraca kod pionka, sformatowany odpowiedniego dla podanego stylu.
- function getField(id,style){return '<svg onclick="tryMove(this);" id="gF'+id+'" class="gfd"><rect class="gfc'+style+' gf_" width="64"height="64"/></svg>';}
Zwraca kod pola, sformatowany odpowiedniego dla podanego stylu.
- function getInt(txt){return txt.replace("px","");}
Funkcja pomocnicza. Usuwa "px" w zwróconej wartości współrzędnych i zwraca liczbę.
- function getLine(i,args){
- var str=window["lng_"+userLang](i);
- if(str=="")
- str=window["lng_en"](i);
- for(var i=0;i<args.length;i++)
- str=str.replace("{"+i+"}",args[i]);
- return str;
- }
Pobiera łańcuch znaków o zadanym numerze ("i") i wstawie argumenty z listy("args") w odpowiednie miejsca. Warto zwrócić uwagę na (115.) gdzie wywoływana jest funkcja na podstawie nazwy, która składa się z prefiksu "lng_" i kodu języka. W przypadku, gdy (116.)pobrany tekst jest pusty, (117.)pobierany jest domyślny w języku angielskim, a następnie (118., 119.)odpowiednie pola zastępowane przez argumenty. Funkcja zwraca odpowiednio sformatowany tekst.
- function lng_pl(i){return ["Gracz 1","Gracz 2","{0} wykonuje ruch","{0} - nieprawidłowy ruch","To nie twój pionek!","Kliknij plansze, aby zagrać ponownie","{0} zwycięża!"][i];}
- function lng_en(i){return ["Player 1","Player 2","{0} move","{0} - illegal move","This is not your pawn!","Click board to play again","{0} is the winner!"][i];}
Funkcje, które zwracają i-ty element z listy, która jest odpowiednio przetłumaczona.
g00c.css - kod źródłowy
- .gfc1{fill:#FFFFFF;}
- .gfc0{fill:#444444;}
- .gfcs{fill:#00AA44;}
- .gpc1{color:#0044AA;fill:#0044AA;}
- .gpc2{color:#A02C2C;fill:#A02C2C;}
- .gpd{z-index:20;position:absolute;}
- .gfd{z-index:0;position:absolute;}
- .gboardb{background:#000000;position:relative;width:224px;height:224px;left:16px;top:16px;}
- #gsel{z-index:10;}
- #gwin{display:table;position:absolute;top:0;left:0;width:256px;height:256px;z-index:30;background:rgba(0,0,0,0.75);text-align:center;font-size:20px;color:#FFFFFF;}
- #gstatus{max-width:248px;min-width:248px;text-align:center;border:4px solid #552200;}
- #gboard{position:relative;background:#552200;width:256px;height:256px;}
Dokument stylizujący obiekty. Wyjaśnienie:
- .gfc(x) to style koloru pola, "s" dla obiektu zaznaczenia
- .gpc(x) to kolor pionka dla x-gracza
- .gpd - styl dla dowolnego obiektu typu pion
- .gfd - styl dla dowolnego obiektu typu pole
- .gboardb - styl czarnego pola między planszą, a polami
- #gsel - styl obiektu pokazującego obecne zaznaczenie
- #gwin - styl pola informującego o zwycięstwie
- #gstatus - styl obiektu pokazującego komunikatu z gry
- #gboard - style planszy