Flexbox

Używany do rozmieszczania elementów w wierszu lub kolumnie

Snake

Jako obiekt JavaScript

Snake RL

Dodatkowe metody umożliwiające tworzenie tabeli akcji

Gridbox

-

Sequence

-

+

Deklaracja klasy

Po utworzeniu klasy Snake class Snake {} do konstruktora klasy (jest to funkcja wykonywana po zainicjalizowaniu obiektu) przekazujemy parametr display, aby później uzyskać dostęp do określonego elementu HTML.
W konstruktorze pojawi się kilka zmiennych, w tym zmiana przypisania display do osobnej zmiennej, z której będzie mogła korzystać cała klasa. constructor(dispay) {this.display = display; ... }
Konstrutor będzie zawierał także EventListener, który zmieni kierunek poruszania się postaci na podstawie naciśniętych klawiszy. window.addEventListener("keydown", (e) => { ... });

Metody klasy

draw() { ... } Istnieje kilka sposobów rysowania i w tym przypadku zamiast metody Canvas zostanie użyta metoda Grid.
display.innerHTML = ""; aby wyczyścić poprzednio narysowaną klatkę. this.snake.forEach(segment => { ... }) metoda tablicy wykona podaną funkcję dla każdego segmentu tablicy, funkcja będzie działać następująco: const cell = document.createElement("div"); ... display.appendChild(cell); funkcja tworzy element HTML, ustawia atrybuty CSS dotyczące pozycjonowania na siatce, dodaje klasę active i na koniec dołącza komórkę z postacią do elementu display HTML.
checkCollision(obj, head=false, wall=false) Metoda pomocnicza sprawdza kolizję na podstawie pozycji x i y podanego obiektu i postaci. Posiada również dwa parametry dla dodatkowych celów.
randomItemPos() { ... } Metoda pomocnicza, która będzie używana za każdym razem, gdy trzeba będzie określić losową pozycję elementu na podstawie rozmiaru siatki i pozycji postaci.
update() { ... } Metoda aktualizuje położenie wszystkich elementów i sprawdza stan niepowodzenia gry.
for(let i=0; i < this.add; i++) { ... } Utworzona wcześniej zmienna this.add zostanie użyta do dodania komórki do postaci na podstawie ustawionej w niej wartości. Dla this.add za każdym razem, gdy do listy komórek snake'a dodawana jest nowa pozycja, operator rozprzestrzeniania "..." odtwarza zawartość ostatniej komórki snake'a i przywraca zmiennej add wartość zero, aby zatrzymać dalsze dodawanie this.add = 0
Przesunięcie tabeli postaci powodujące przemieszczanie się for(let i = this.snake.length -2; i >= 0; i--) {} pętla for ma długość snake'a -2, ponieważ indeks zaczyna się od 0, oraz -1, ponieważ trzeba wykluczyć jedną komórkę, pętla jest wykonywana do momentu, gdy i jest równe 0.
this.snake[0].x += this.direction.x i to samo dla osi y, przesuwa głowę postaci o wektor this.direction.
Kolejne dwie linie sprawdzają, kolejno, kolizję postaci z przedmiotem, aby dodać komórkę, oraz kolizję ze ścianą lub kolizję własną, aby stwierdzić, że gra nie powiodła się.
reset() { ... } metoda, która przywraca wartości do pozycji początkowych w celu ponownego uruchomienia gry.
Aby postać poruszała się samodzielnie, klasa Snake wymaga kilku istotnych zmian. Do konstruktora zostanie dodanych pięć dodatkowych zmiennych this.Qtable = {} ... Qtable służy do zapisywania stanu i akcji do wykorzystania, this.alpha to współczynnik tempa nauki, this.gamma to współczynnik limitujący nagrodę, this.loopPrevention, ponieważ snake ma tendencję do utknięcia w pętli, oraz this.itemCtrl do kontroli tej pętli.
states() { ... } Zostanie dodana metoda pozwalająca uzyskać bieżący stan postaci, czyli możliwe kolizje z głową o 1 komórkę w przyszłości oraz bezwzględny kierunek itemu (lewo, prosto, prawo). Liczba stanów musi być ograniczona, ponieważ ilość możliwości staje się zbyt duża.
response(state) { ... } ta metoda po utworzeniu dwóch zmiennych dotyczących qtable uzyskuje lokalną tablicę trzech akcji dla danego stanu. Następnie sortuje tablicę za pomocą funkcji strzałki porównania (zwracając 1 sortuje x przed y, -1 sortuje y przed x, a 0 zachowuje oryginał). Następnie wrzuca do qf pierwszą posortowaną wartość i stosuje zapobieganie pętli. Na końcu zwraca akcję o największej wartości lub wybiera losowo akcję, jeśli więcej akcji ma taką samą maksymalną wartość. Przy aktywacji zapobiegania pętli zostanie wybrana całkowicie losowa akcja, która została podana z tablicy qtable.
action(a) { ... } Metoda akcji działa podobnie jak sterowanie za pomocą klawiatury, pobiera aktualny kierunek i ustawia możliwy ruch na podstawie podanej akcji.
reward(s, a) { ... } metoda ta jako całość wykorzystuje podstawowe równanie Bellmana do ustalania i aktualizowania wartości tablicy q. Instrukcja if wyznacza nagrodę za stan preferowany, czyli gdy zbliża się kolizja, to -1, a gdy item jest widoczny, to +1, z uwzględnieniem podjętych działań.
Flexbox jest używany do rozmieszczania elementów w wierszu lub kolumnie, aby nadać kontenerowi typ flex, div musi posiadać atrybut stylu display: flex;
domyślnie kierunek jest ustawiony na wiersz flex-direction: row;
flexbox jako pojemnik posiada oś główną i oś poprzeczną, które zmieniają się wraz z kierunkiem flex-direction flex-direction: column;
element w pojemniku flexbox ma wartość grow, jeśli element nie ma stałej szerokości, wartość grow jest wartością bezjednostkową, która działa proporcjami. flex-grow: 4;
na osi głównej elementy są rozmieszczone z justify-content
na osi poprzecznej elementy są rozmieszczone z align-item
class Snake {
 constructor(display) {
  this.display = display;
  this.failed = false;
  this.direction = {x: 0, y: 0};
  this.gridSize = 8;
  this.snake = [{x: parseInt(this.gridSize/2), y: parseInt(this.gridSize/2)}];
  this.item = this.randomItemPos();
  this.FPS = 5;
  this.add = 0;

  window.addEventListener("keydown", (e) => {
   switch(e.key) {
    case "ArrowUp":
     if(this.direction.y !== 0) break
     this.direction = {x: 0, y: -1}
     break
    case "ArrowDown":
     if(this.direction.y !== 0) break
     this.direction = {x: 0, y: 1}
     break
    case "ArrowLeft":
     if(this.direction.x !== 0) break
     this.direction = {x: -1, y: 0}
     break
    case "ArrowRight":
     if(this.direction.x !== 0) break
     this.direction = {x: 1, y: 0}
     break
   }
  });
 }
 update() {
  for (let i = 0; i < this.add; i++) {
   this.snake.push({...this.snake[this.snake.length - 1]})
  }
  this.add = 0;

  for(let i = this.snake.length - 2; i >= 0; i--) {
   this.snake[i + 1] = {...this.snake[i]}
  }
  this.snake[0].x += this.direction.x;
  this.snake[0].y += this.direction.y;

  if(this.checkCollision(this.item)) {
   this.add += 1;
   this.item = this.randomItemPos();
  }

  if(this.checkCollision(this.snake[0], true, true)) {
   this.failed = true;
  }
 }
 draw() {
  display.innerHTML = "";
  this.snake.forEach(segment => {
   const elementSnake = document.createElement("div");
   elementSnake.style.gridRowStart = segment.y;
   elementSnake.style.gridColumnStart = segment.x;
   elementSnake.classList.add("active");
   display.appendChild(elementSnake);
  });
  const elementItem = document.createElement("div");
  elementItem.style.gridRowStart = this.item.y;
  elementItem.style.gridColumnStart = this.item.x;
  elementItem.classList.add("error");
  display.appendChild(elementItem);
 }
 checkCollision(obj, head=false, wall=false) {
  let result = this.snake.some((segment, index) => {
   if(index === 0 && head) return false;
   return segment.x === obj.x && segment.y === obj.y;
  });
  if(wall) {
   result = result || (obj.x < 1 || obj.x > this.gridSize || obj.y < 1 || obj.y > this.gridSize)
  } return result;
 }
 randomItemPos() {
  let rand = {
    x: Math.floor(Math.random() * this.gridSize) + 1,
    y: Math.floor(Math.random() * this.gridSize) + 1
   }
  while(this.checkCollision(rand)) {
   rand = {
    x: Math.floor(Math.random() * this.gridSize) + 1,
    y: Math.floor(Math.random() * this.gridSize) + 1
   }
  } return rand;
 }
 reset() {
  this.failed = false;
  this.direction = {x: 0, y: 0}
  this.snake = [{x: parseInt(this.gridSize/2), y: parseInt(this.gridSize/2)}];
  this.item = this.randomItemPos();
  this.add = 0;
 }
}