Strona główna » Po Godzinach » Gry » 4 wygrywa - kod źródłowy
 

4 wygrywa - kod źródłowy

· Gra · Kod źródłowy ·

Wstęp

Aplikacja 4 wygrywa została napisana przy pomocy JavaScript, które zarządza wyświetlaną treścią zmieniając kod HTML oraz CSS. Użyte funkcje są zgodne z najnowszym standardem HTML5.

Umieszczenie na stronie

W celu umieszczenia na stronie gry wystarczy umieścić obiekt typu DIV, który będzie miał identyfikator _game.

  1. <div id="_game"></div>

Skrypt

Zmienne globalne

W celu poprawnego działania aplikacji potrzebnych jest kilka zmiennych globalnych, które mają za zadanie przechowywać informacje pomiędzy różnymi funkcjami. Ich znaczenie jest następujące:

  1. var data = [];
  2. var player = 0;
  3. var players = [];
  4. var checkPos = [[0, 1], [1, 0], [-1, 0], [1, 1], [-1, 1]];

(1.) Zmienna data będzie przechowywać ile jest wolnych pól w każdej kolumnie. Przyśpieszy to sprawdzenie czy w kolumnie jest wolne pole - odczytywanie tego z planszy zajęłoby bardzo dużo czasu. (2.) Zmienna player przechowuje, który gracz się rusza (0 to pierwszy, a 1 to drugi). (3.) Z kolei w zmiennej players będzie przechowywane kto porusza się w danej turze. Człowiek jest reprezentowany przez literę p, a AI przez ai0. (4.) Ostatnia zmienna checkPos przechowuje w którym kierunku trzeba przechodzić, aby sprawdzić czy są 4 żetony ustawione w jednej linii.

Rozpoczęcie gry

Zarówno uruchomienie nowej gry czy zrestartowanie obecnej polega na wywołaniu funkcji prepareGame(). Funkcja ta nie przyjmuje żadnych argumentów.

  1. function prepareGame() {
  2.  var board = document.getElementById("_game");
  3.  board.innerHTML = "";
  4.  players = [getSelectedOption("player1"), getSelectedOption("player2")];
  5.  data = [];
  6.  player = 0;
  7.  for(var x = 0; x < 7; x++){
  8.   for(var y = 0; y < 6; y++){
  9.    board.innerHTML += getField("empty",x,y);
  10.   }
  11.   data.push(5);
  12.  }
  13.  board.innerHTML += '<div id="_msg"></div>';
  14.  reportPlayerState("Oczekiwanie na ruch");
  15.  moveIfAI();
  16. }

(2.) Znajdź odpowiedni obiekt na stronie i (3.) wyczyść wszystko co się w nim znajduje (przydatne podczas resetowania rozgrywki). (4.) Odczytaj z ustawień kto kim gra. (5.) Wyczyść dane o wolnych polach w każdej kolumnie i (6.) ustal, że pierwszy rusza się gracz 1. (7.) Utwórz 7 kolumn: (8.) po 6 pól i (9.) każde ma być puste. (11.) Po utworzeniu kolumny dopisz ile jest w niej wolnych pól. (13.) Dołącz pole wiadomości i ustal nową zawartość ekranu gry. (14.) Pokaż komunikat, że gra została przygotowana. (15.) Spróbuj wykonać ruch przy pomocy AI.

Tworzenie pola odbywa się przy pomocy funkcji getField(). Polega ona na przekazaniu trzech argumentów: jakiego rodzaju ma być pole oraz pozycję X i Y na planszy. Ze względu na sposób tworzenia pole (0, 0) znajduje się w lewym, górnym rogu, a (6, 5) w prawym, dolny.

  1. function getField(obj_class,posx,posy) {
  2.  var rposx = posx * 100;
  3.  var rposy = posy * 100 + 30;
  4.  return '<div id="'+getFieldName(posx, posy)+'" class="field ' + obj_class + '" style="left:' + rposx + 'px;top:' + rposy + 'px">' + '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45" onclick="elClicked(this)"/></svg></div>';
  5. }

(2. - 3.) Na podstawie pozycji wyliczona zostaje pozycja na ekranie gry. (4.) Pole jest reprezentowane przez obiekt div w którym znajduje się obiekt svg tylko z jednym kołem. Do koła jest przypisane zdarzenie onclick - dzięki temu po wciśnięciu pola żeton zostaje dołożony na planszę (o ile ruch jest prawidłowy).

W celu ujednolicenia nazewnictwa pól oraz, aby umożliwić znalezienie pola na podstawie jego pozycji została napisana funkcja getFieldName():

  1. function getFieldName(posx, posy){
  2.  return "field_" + posx + "_" + posy;
  3. }

Rozgrywka

Po kliknięciu koła zostaje wywołana funkcja elClicked(), która przyjmuje argument el - jest to obiekt, który wywołał funkcję. Na podstawie rodzica DIV tego obiektu można określić współrzędne pola.

  1. function elClicked(el) {
  2.  if(players[player] != "p"){
  3.   reportState("Komputer wykonuje ruch");
  4.   return false;
  5.  }
  6.  elClickedForced(el);
  7. }

(2.) Sprawdź czy ruch aktualnie wykonuje gracz. Jeśli nie to (3.) pokaż odpowiedni komunikat i (4.) przerwij dostawienie żetonu. Jeśli jednak ruch wykonuje gracz Człowiek to (6.) przekaż obiekt el funkcji elClickedForced(). W przypadku, gdy ruch wykonuje AI to funkcja elClicked() jest pomijana.

  1. function elClickedForced(el) {
  2.  var pos = el.parentNode.parentNode.id.split("_");
  3.  if(data[pos[1]] < 0){
  4.   reportPlayerState("Nieprawidłowy ruch");
  5.  } else {
  6.   replaceClass(document.getElementById(getFieldName(pos[1], data[pos[1]])),"empty","p"+player);
  7.   if(checkWin(parseInt(pos[1]), parseInt(data[pos[1]]))){
  8.    showWin(parseInt(pos[1]), parseInt(data[pos[1]]));
  9.    reportPlayerState('Zwycięstwo, <a href="javascript:void(0)" onclick="prepareGame();">restartuj grę</a>');
  10.   } else {
  11.    data[pos[1]]--;
  12.    var allZeros = true;
  13.    for(var i = 0; i < 7; i++){
  14.     if(data[i] != -1)
  15.      allZeros = false;
  16.    }
  17.    if(allZeros) {
  18.     showWin(-10, -10);
  19.     reportState('Remis, <a href="javascript:void(0)" onclick="prepareGame();">restartuj grę</a>');
  20.    } else {
  21.     player = (++player)%2;
  22.     reportPlayerState("Oczekiwanie na ruch");
  23.     moveIfAI();
  24.    }
  25.   }
  26.  }
  27. }

(2.) Pobierz identyfikator obiektu DIV wciśniętego pola i odczytaj z niego odpowiednie wartości. (3.) Jeśli wybrana kolumna nie ma wolnego pola to (4.) pokaż komunikat o niepoprawnym ruchu. (5.) Jednak jeśli dana kolumna ma choć jedno wolne pole to (6.) w najniżej położonym wolnym polu w wybranej kolumnie zmień styl obiektu z pustego na kolor gracza. (7.) Następnie sprawdź czy dzięki temu aktualny gracz wygrał. (8.) Jeśli tak to zakończ rozgrywkę i (9.) pokaż komunikat. (10.) Jednak jeśli rozgrywka nie została zakończona to (11.) zmniejsz ilość wolnych pól w kolumnie i (12. - 16.) sprawdź czy istnieją jeszcze wolne pola. (17.) Jeśli nie istnieje żadne wolne pole to (18.) zakończ rozgrywkę i (19.) pokaż komunikat o remisie. (20.) Jeśli jednak są jeszcze pola to (21.) przekaż kolejkę następnemu graczowi, (22.) pokaż o tym komunikat i (23.) sprawdź czy ruch wykonuje AI.

Lista graczy, a konkretniej informacji kto steruje, którego gracza służy funkcja getSelectedOption(), która z wybranego pola wyboru pobiera informację na temat wybranej opcji przez użytkownika.

  1. function getSelectedOption(name){
  2.  var e = document.getElementById(name);
  3.  return e.options[e.selectedIndex].value;
  4. }

W celu zmiany przynależności pola zmieniany jest styl pola. Wcześniej przypisana klasa empty jest zmieniana na p1 lub p2 w zależności od gracza. Funkcja replaceClass() zamienia w podanym obiekcie obj starą klasę oldclass na nową newclass:

  1. function replaceClass(obj,oldclass,newclass){
  2.  obj.className = obj.className.replace(oldclass, newclass);
  3. }

Sprawdzanie zwycięstwa

W celu sprawdzenia zwycięstwa funkcja checkWin() sprawdza dla każdego przypadku z zmiennej checkPos wszystkie możliwa zakończenia na podstawie klikniętej pozycji.

  1. function checkWin(posx, posy) {
  2.  var object = document.getElementById(getFieldName(posx, posy));
  3.  for(var i = 0; i < checkPos.length; i++){
  4.   if(checkLine(object.className, posx, posy, checkPos[i][0], checkPos[i][1]))
  5.    return true;
  6.  }
  7.  return false;
  8. }

(1.) Na podstawie pozycji posx i posy: (2.) znajdź kliknięte pole i (3. - 6.) sprawdź wszystkie możliwości podane i zwróć prawde jeśli gracz wygrał, a jeśli nie to (8.) zwróć fałsz.

Funkcję checkWin() wspiera funkcja checkLine(), która na podstawie podanego kierunku szukania zwycięstwa sprawdza wszystkie możliwości:

  1. function checkLine(obj_class, nposx, nposy, chngex, chngey) {
  2.  var isOk = false;
  3.  for(var j = 0; j < 3; j++) {
  4.   var posx = nposx - j * chngex, posy = nposy - j * chngey;
  5.   isOk = true;
  6.   for(var i = 0; i < 4; i++) {
  7.    if(!ifExist(posx, posy) || obj_class != document.getElementById(getFieldName(posx, posy)).className)
  8.     isOk = false;
  9.    posx += chngex;
  10.    posy += chngey;
  11.   }
  12.   if(isOk)
  13.    return true;
  14.  }
  15.  return false;
  16. }

(1.) Szukaj zwycięstwa dla gracza zapisanego w zmiennej obj_class. Szukaj zwycięstwa wokół pola (nposx, nposy) przesuwając się o (chngx, chngy) szukając czy następne pole należy do tego gracza. (2.) Załóż, że gracz nie odniósł zwycięstwa. (3.) Uwzględnij przypadki, gdy pole jest dodane w środku 4 elementowej linii. (4.) Wtedy oblicz przypuszczalne pierwsze pole w danej czwórce i (5.) przypuść, że linia istnieje. (6. - 11.) Sprawdź czy przeszukiwana linia należy do gracza. (7.) Jeśli przesszukiwane pole nie istnieje, albo nie należy do gracza do (8.) zapisz, że w tym obszarze nie ma czterech żetonów gracza. (9.) Jednak jeśli są 4 żetony w linii to (10.) zwróć prawdę. (12.) Na koniec, o ile nie została zwrócona prawda to zwróć fałsz.

W trakcie przeszukiwania funkcja na podstawie danych nie sprawdza czy wszystkie pola istnieją. Z tego powodu funkcja ifExist() pozwala sprawdzić czy dane pole istnieje. W celu określenia czy pole istnieje wystarczy jego pozycja (posx, posy).

  1. function ifExist(posx, posy){
  2.  return document.getElementById(getFieldName(posx, posy)) != undefined;
  3. }

Zakończenie gry

W celu zakończenia rozgrywki należy (2.) pobrać wszystkie pola na ekranie rozgrywki, a następnie (3.) każdemu z pól: (4.) dodać efekt rozmycia i (5.) wyłączyć możliwość kliknięcia.

  1. function showWin(nposx, nposy){
  2.  var children = document.getElementsByClassName("field");
  3.  for(var i = 0; i < children.length; i++) {
  4.   replaceClass(children[i],"field","field fieldb");
  5.   children[i].getElementsByTagName("svg")[0].getElementsByTagName("circle")[0].onclick = "";
  6.  }
  7. }

Rozpoczęcie rozgrywki

Na samym początku rozgrywki zostaje przygotowana plansza oraz odczytane kolory ustawione na stronie:

  1. prepareGame();
  2. changeColour("0", document.getElementById("player1_colour").value);
  3. changeColour("1", document.getElementById("player2_colour").value);

Komunikaty

Istnieją dwie funkcje, które wyświetlają komunikaty dla użytkownika. Pierwsza z nich pokazuje dowolny komunikat przekazany w argumencie msg:

  1. function reportState(msg) {
  2.  document.getElementById("_msg").innerHTML = msg;
  3. }

Z kolei druga funkcja reportPlayerState() korzysta z reportState(). Różnica polega na tym, że do wiadomości jest dopisywane, którego gracza dotyczy komunikat:

  1. function reportPlayerState(msg) {
  2.  reportState("<b>[Gracz " + (player + 1) + "]</b> " + msg);
  3. }

Sztuczna inteligencja

Do wywołania ruchu sztucznej inteligencji służy funkcja moveIfAI(), któa jeśli wykryje, że graczem jest sztuczna inteligencja to po krótkim czasie wykona ruch odpowiednim graczem.

  1. function moveIfAI(){
  2.  if(players[player] == "ai0"){
  3.   reportPlayerState("AI szuka ruchu");
  4.   setTimeout(function(){
  5.    var i = Math.floor(Math.random()*7);
  6.    var k = 0;
  7.    while(data[i] == -1){
  8.     i=(++i)%7;
  9.     k++;
  10.     if(k == 7) {
  11.      return;
  12.     }
  13.    }
  14.    elClickedForced( document.getElementById( getFieldName(i, 0) ).getElementsByTagName( "svg" )[0].getElementsByTagName( "circle" )[0]);
  15.   }, 1000);
  16.  }
  17. }

Przykładowo graczem rusza się AI Losowe ruchy. Wtedy (2.) funkcja to wykrywa, która to sztuczna inteligencja, (3.) zgłasza wykonywanie ruchu i (4. - 15.) ustawia wykonanie ruchu na później.

Kolory drużyn

Zmiana koloru drużyny jest możliwa dzięki obiektu input typu color. Przykładowo w celu zmiany koloru gracza 2 obiekt ma dopisane zdarzenie onchange, które zostaje wywołane po zmianie koloru. Wywołuje ona funkcję changeColour(), która zmiena kolor gracza o numerze podanym jako pierwszy argument na kolor, który jest podany jako drugi argument.

  1. <input id="player2_colour" value="#00b211" onchange="changeColour('1', this.value);" type="color" />

Funkcja changeColour() potrafi znaleźć odpowiedni arkusz stylów oraz styl gracza, a następnie zmienić kolor wypełnienia koła.

  1. function changeColour(player, colour){
  2.  for(var i = 0; i < document.styleSheets.length; i++){
  3.   if(document.styleSheets[i].title == "css_game"){
  4.    for(var j = 0; j < document.styleSheets[i].cssRules.length; j++){
  5.     if(document.styleSheets[i].cssRules[j].selectorText == ".p" + player + " > svg > circle"){
  6.      document.styleSheets[i].cssRules[j].style.fill = colour;
  7.     }
  8.    }
  9.   }
  10.  }
  11. }

Styl rozgrywki

Elementy gry są częściowo ustalana poprzez kod CSS. Styl ekranu gry:

  1. #_game {
  2.  width: 700px;
  3.  height: 630px;
  4.  background: yellow;
  5.  overflow: hidden;
  6.  position: relative;
  7.  margin: 0 auto;
  8. }

Styl pola komunikatów:

  1. #_msg {
  2.  width: 700px;
  3.  top: 0;
  4.  padding: 4px;
  5.  background: #cfd8dc;
  6. }

Ogólny styl pola:

  1. .field {
  2.  width: 100px;
  3.  height: 100px;
  4.  position: absolute
  5. }

Styl pola na koniec rozgrywki:

  1.  .fieldb > svg {
  2.  opacity: 0.5;
  3.  filter: blur(3px);
  4.  -webkit-filter: blur(3px);
  5.  transition:opacity 1s, filter 1s
  6. }

Styl pola:

  1.  .field > svg > circle {
  2.  stroke-width: 2px;
  3.  stroke: black;transition:fill 1s
  4. }

Styl pustego pola (.empty), gracza 1 (.p0) oraz gracza 2 (.p1):

  1. .empty > svg > circle { fill: #ddd }
  2. .p0 > svg > circle { fill: #f00 }
  3. .p1 > svg > circle { fill: #8a0 }