Witam,
Potrzebuję pomocy w modyfikacji kodu. Główną zasadą gry w statki jest, brak możliwości stykania się ścianami oraz rogami. Niestety nie mogę wprowadzić tej zmiany w zamieszczonym kodzie. Czy mógłby mi ktoś pomóc
(function() {
// Global Constants
var CONST = {};
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
// You are player 0 and the computer is player 1
// The virtual player is used for generating temporary ships
// for calculating the probability heatmap
CONST.HUMAN_PLAYER = 0;
CONST.COMPUTER_PLAYER = 1;
CONST.VIRTUAL_PLAYER = 2;
// Possible values for the parameter `type` (string)
CONST.CSS_TYPE_EMPTY = 'empty';
CONST.CSS_TYPE_SHIP = 'ship';
CONST.CSS_TYPE_MISS = 'miss';
CONST.CSS_TYPE_HIT = 'hit';
CONST.CSS_TYPE_SUNK = 'sunk';
// Grid code:
CONST.TYPE_EMPTY = 0; // 0 = water (empty)
CONST.TYPE_SHIP = 1; // 1 = undamaged ship
CONST.TYPE_MISS = 2; // 2 = water with a cannonball in it (missed shot)
CONST.TYPE_HIT = 3; // 3 = damaged ship (hit shot)
CONST.TYPE_SUNK = 4; // 4 = sunk ship
// TODO: Utwórz ten lepszy kod OO. CONST.AVAILABLE_SHIPS powinna być tablicą
// obiektów, a nie dwóch równoległych tablic. Albo lepiej
// rozwiązaniem byłoby zapisanie "USED" i "UNUSED" jako właściwości
// pojedynczy obiekt statku.
// Te liczby odpowiadają CONST.AVAILABLE_SHIPS
//1) "pancernik" 2) "niszczyciel" 3) "okręt podwodny" 4) "patrolboat"
// Ta zmienna jest używana tylko wtedy, gdy DEBUG_MODE === true.
Game.usedShips = [CONST.UNUSED, CONST.UNUSED, CONST.UNUSED, CONST.UNUSED, CONST.UNUSED];
CONST.USED = 1;
CONST.UNUSED = 0;
// Game Statistics
function Stats(){
this.shotsTaken = 0;
this.shotsHit = 0;
this.totalShots = parseInt(localStorage.getItem('totalShots'), 10) || 0;
this.totalHits = parseInt(localStorage.getItem('totalHits'), 10) || 0;
this.gamesPlayed = parseInt(localStorage.getItem('gamesPlayed'), 10) || 0;
this.gamesWon = parseInt(localStorage.getItem('gamesWon'), 10) || 0;
this.uuid = localStorage.getItem('uuid') || this.createUUID();
if (DEBUG_MODE) {
this.skipCurrentGame = true;
}
}
Stats.prototype.incrementShots = function() {
this.shotsTaken++;
};
Stats.prototype.hitShot = function() {
this.shotsHit++;
};
Stats.prototype.wonGame = function() {
this.gamesPlayed++;
this.gamesWon++;
if (!DEBUG_MODE) {
ga('send', 'event', 'gameOver', 'win', this.uuid);
}
};
Stats.prototype.lostGame = function() {
this.gamesPlayed++;
if (!DEBUG_MODE) {
ga('send', 'event', 'gameOver', 'lose', this.uuid);
}
};
// Zapisuje statystyki gry do lokalnego magazynu, a także przesyła je, gdzie użytkownik umieścił
// ich statki do Google Analytics, aby w przyszłości mogłem zobaczyć
// które komórki ludzkie są nieproporcjonalnie nastawione do umieszczania statków.
Stats.prototype.syncStats = function() {
if(!this.skipCurrentGame) {
var totalShots = parseInt(localStorage.getItem('totalShots'), 10) || 0;
totalShots += this.shotsTaken;
var totalHits = parseInt(localStorage.getItem('totalHits'), 10) || 0;
totalHits += this.shotsHit;
localStorage.setItem('totalShots', totalShots);
localStorage.setItem('totalHits', totalHits);
localStorage.setItem('gamesPlayed', this.gamesPlayed);
localStorage.setItem('gamesWon', this.gamesWon);
localStorage.setItem('uuid', this.uuid);
} else {
this.skipCurrentGame = false;
}
var stringifiedGrid = '';
for (var x = 0; x < Game.size; x++) {
for (var y = 0; y < Game.size; y++) {
stringifiedGrid += '(' + x + ',' + y + '):' + mainGame.humanGrid.cells[x][y] + ';\n';
}
}
if (!DEBUG_MODE) {
ga('send', 'event', 'humanGrid', stringifiedGrid, this.uuid);
}
};
// Aktualizuje pasek boczny z aktualnymi statystykami
Stats.prototype.updateStatsSidebar = function() {
var elWinPercent = document.getElementById('stats-wins');
var elAccuracy = document.getElementById('stats-accuracy');
elWinPercent.innerHTML = this.gamesWon + " of " + this.gamesPlayed;
elAccuracy.innerHTML = Math.round((100 * this.totalHits / this.totalShots) || 0) + "%";
};
// Zresetuj wszystkie statystyczne statystyki gry do zera. Nie zresetuj uuid.
Stats.prototype.resetStats = function(e) {
// Pomiń statystyki śledzenia do końca bieżącej gry lub innego
// odsetek dokładności będzie niewłaściwy (ponieważ śledzisz
// trafienia, które nie zaczęły się od początku gry)
Game.stats.skipCurrentGame = true;
localStorage.setItem('totalShots', 0);
localStorage.setItem('totalHits', 0);
localStorage.setItem('gamesPlayed', 0);
localStorage.setItem('gamesWon', 0);
localStorage.setItem('showTutorial', true);
Game.stats.shotsTaken = 0;
Game.stats.shotsHit = 0;
Game.stats.totalShots = 0;
Game.stats.totalHits = 0;
Game.stats.gamesPlayed = 0;
Game.stats.gamesWon = 0;
Game.stats.updateStatsSidebar();
};
Stats.prototype.createUUID = function(len, radix) {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''),
uuid = [], i;
radix = radix || chars.length;
if (len) {
// Compact form
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
} else {
// rfc4122, version 4 form
var r;
// rfc4122 requires these characters
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random()*16;
uuid[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
};
// Obiekt menedżera gier
// Konstruktor
function Game(size) {
Game.size = size;
this.shotsTaken = 0;
this.createGrid();
this.init();
}
Game.size = 10; // Domyślny rozmiar siatki to 10x10
Game.gameOver = false;
// Sprawdza, czy gra jest wygrywana, a jeśli tak, ponownie zainicjuje grę
Game.prototype.checkIfWon = function() {
if (this.computerFleet.allShipsSunk()) {
alert('brawo!');
Game.gameOver = true;
Game.stats.wonGame();
Game.stats.syncStats();
Game.stats.updateStatsSidebar();
this.showRestartSidebar();
} else if (this.humanFleet.allShipsSunk()) {
alert('srobuj ponownie');
Game.gameOver = true;
Game.stats.lostGame();
Game.stats.syncStats();
Game.stats.updateStatsSidebar();
this.showRestartSidebar();
}
};
// strzela do docelowego gracza w siatce.
// Zwraca {int} Constants.TYPE: Co odkryto
Game.prototype.shoot = function(x, y, targetPlayer) {
var targetGrid;
var targetFleet;
if (targetPlayer === CONST.HUMAN_PLAYER) {
targetGrid = this.humanGrid;
targetFleet = this.humanFleet;
} else if (targetPlayer === CONST.COMPUTER_PLAYER) {
targetGrid = this.computerGrid;
targetFleet = this.computerFleet;
} else {
// Should never be called
console.log(".................");
}
if (targetGrid.isDamagedShip(x, y)) {
return null;
} else if (targetGrid.isMiss(x, y)) {
return null;
} else if (targetGrid.isUndamagedShip(x, y)) {
// zaktualizować planszę / siatkę
targetGrid.updateCell(x, y, 'hit', targetPlayer);
// WAŻNE: Ta funkcja musi być nazwana _after_ aktualizacją komórki do "trafienia"
// ponieważ zastąpi klasę CSS "zatopioną", jeśli okaże się, że statek został zatopiony
targetFleet.findShipByCoords(x, y).incrementDamage(); // increase the damage
this.checkIfWon();
return CONST.TYPE_HIT;
} else {
targetGrid.updateCell(x, y, 'miss', targetPlayer);
this.checkIfWon();
return CONST.TYPE_MISS;
}
};
// Tworzy detektory zdarzeń kliknięcia na każdej z 100 komórek siatki
Game.prototype.shootListener = function(e) {
var self = e.target.self;
// Extract coordinates from event listener
var x = parseInt(e.target.getAttribute('data-x'), 10);
var y = parseInt(e.target.getAttribute('data-y'), 10);
var result = null;
if (self.readyToPlay) {
result = self.shoot(x, y, CONST.COMPUTER_PLAYER);
// Remove the tutorial arrow
if (gameTutorial.showTutorial) {
gameTutorial.nextStep();
}
}
if (result !== null && !Game.gameOver) {
Game.stats.incrementShots();
if (result === CONST.TYPE_HIT) {
Game.stats.hitShot();
}
// Kula AI wystrzeliwa, gdy gracz kliknie komórkę, której jeszcze nie kliknąłeś
self.robot.shoot();
} else {
Game.gameOver = false;
}
};
// Tworzy detektory zdarzeń kliknięcia na każdym ze statków w rosterze
Game.prototype.rosterListener = function(e) {
var self = e.target.self;
// Usuń wszystkie klasy "umieszczania" z listy flot
var roster = document.querySelectorAll('.fleet-roster li');
for (var i = 0; i < roster.length; i++) {
var classes = roster[i].getAttribute('class') || '';
classes = classes.replace('placing', '');
roster[i].setAttribute('class', classes);
}
// Move the highlight to the next step
if (gameTutorial.currentStep === 1) {
gameTutorial.nextStep();
}
// Set the class of the target ship to 'placing'
Game.placeShipType = e.target.getAttribute('id');
document.getElementById(Game.placeShipType).setAttribute('class', 'placing');
Game.placeShipDirection = parseInt(document.getElementById('rotate-button').getAttribute('data-direction'), 10);
self.placingOnGrid = true;
};
// Tworzy detektory zdarzeń kliknięcia na siatce ludzkiego gracza, aby obsługiwać
// umieszczenie statku po wybraniu przez użytkownika nazwy statku
Game.prototype.placementListener = function(e) {
var self = e.target.self;
if (self.placingOnGrid) {
// Extract coordinates from event listener
var x = parseInt(e.target.getAttribute('data-x'), 10);
var y = parseInt(e.target.getAttribute('data-y'), 10);
// Nie wkręcaj kierunku, jeśli użytkownik próbuje ponownie umieścić.
var successful = self.humanFleet.placeShip(x, y, Game.placeShipDirection, Game.placeShipType);
if (successful) {
// Sporządzono umieszczenie tego statku
self.endPlacing(Game.placeShipType);
// Usuń strzałkę pomocniczą
if (gameTutorial.currentStep === 2) {
gameTutorial.nextStep();
}
self.placingOnGrid = false;
if (self.areAllShipsPlaced()) {
var el = document.getElementById('rotate-button');
el.addEventListener(transitionEndEventName(),(function(){
el.setAttribute('class', 'hidden');
if (gameTutorial.showTutorial) {
document.getElementById('start-game').setAttribute('class', 'highlight');
} else {
document.getElementById('start-game').removeAttribute('class');
}
}),false);
el.setAttribute('class', 'invisible');
}
}
}
};
// Tworzy zdarzenia obsługi zdarzeń mouseover, które obsługują mouseover na
// siatka ludzkiego gracza narysuje statek fantomowy, co oznacza, że użytkownik
// można tam umieścić statek
Game.prototype.placementMouseover = function(e) {
var self = e.target.self;
if (self.placingOnGrid) {
var x = parseInt(e.target.getAttribute('data-x'), 10);
var y = parseInt(e.target.getAttribute('data-y'), 10);
var classes;
var fleetRoster = self.humanFleet.fleetRoster;
for (var i = 0; i < fleetRoster.length; i++) {
var shipType = fleetRoster[i].type;
if (Game.placeShipType === shipType &&
fleetRoster[i].isLegal(x, y, Game.placeShipDirection)) {
// Virtual ship
fleetRoster[i].create(x, y, Game.placeShipDirection, true);
Game.placeShipCoords = fleetRoster[i].getAllShipCells();
for (var j = 0; j < Game.placeShipCoords.length; j++) {
var el = document.querySelector('.grid-cell-' + Game.placeShipCoords[j].x + '-' + Game.placeShipCoords[j].y);
classes = el.getAttribute('class');
// Check if the substring ' grid-ship' already exists to avoid adding it twice
if (classes.indexOf(' grid-ship') < 0) {
classes += ' grid-ship';
el.setAttribute('class', classes);
}
}
}
}
}
};
// Tworzy zdarzenia detekcji zdarzeń myszy, które nie rysują statku fantomowego
// na siatce ludzkiego gracza, gdy użytkownik przechodzi przez inną komórkę
Game.prototype.placementMouseout = function(e) {
var self = e.target.self;
if (self.placingOnGrid) {
for (var j = 0; j < Game.placeShipCoords.length; j++) {
var el = document.querySelector('.grid-cell-' + Game.placeShipCoords[j].x + '-' + Game.placeShipCoords[j].y);
classes = el.getAttribute('class');
// Sprawdź, czy substrat 'grid-ship' już istnieje, aby uniknąć dodawania go dwukrotnie
if (classes.indexOf(' grid-ship') > -1) {
classes = classes.replace(' grid-ship', '');
el.setAttribute('class', classes);
}
}
}
};
// Click handler for the Rotate Ship button
Game.prototype.toggleRotation = function(e) {
// Toggle rotation direction
var direction = parseInt(e.target.getAttribute('data-direction'), 10);
if (direction === Ship.DIRECTION_VERTICAL) {
e.target.setAttribute('data-direction', '1');
Game.placeShipDirection = Ship.DIRECTION_HORIZONTAL;
} else if (direction === Ship.DIRECTION_HORIZONTAL) {
e.target.setAttribute('data-direction', '0');
Game.placeShipDirection = Ship.DIRECTION_VERTICAL;
}
};
// Click handler for the Start Game button
Game.prototype.startGame = function(e) {
var self = e.target.self;
var el = document.getElementById('roster-sidebar');
var fn = function() {el.setAttribute('class', 'hidden');};
el.addEventListener(transitionEndEventName(),fn,false);
el.setAttribute('class', 'invisible');
self.readyToPlay = true;
// Rozwiń krok samouczek
if (gameTutorial.currentStep === 3) {
gameTutorial.nextStep();
}
el.removeEventListener(transitionEndEventName(),fn,false);
};
// Kliknij przycisk obsługi programu Restart Game
Game.prototype.restartGame = function(e) {
e.target.removeEventListener(e.type, arguments.callee);
var self = e.target.self;
document.getElementById('restart-sidebar').setAttribute('class', 'hidden');
self.resetFogOfWar();
self.init();
};
// Funkcja debugowania służąca do umieszczania wszystkich statków i dopiero początek
Game.prototype.placeRandomly = function(e){
e.target.removeEventListener(e.type, arguments.callee);
e.target.self.humanFleet.placeShipsRandomly();
e.target.self.readyToPlay = true;
document.getElementById('roster-sidebar').setAttribute('class', 'hidden');
this.setAttribute('class', 'hidden');
};
// Zakończenie umieszczania obecnego statku
Game.prototype.endPlacing = function(shipType) {
document.getElementById(shipType).setAttribute('class', 'placed');
// Mark the ship as 'used'
Game.usedShips[CONST.AVAILABLE_SHIPS.indexOf(shipType)] = CONST.USED;
// Wipe out the variable when you're done with it
Game.placeShipDirection = null;
Game.placeShipType = '';
Game.placeShipCoords = [];
};
// Sprawdza, czy wszystkie statki są umieszczone
// Zwraca boolean
Game.prototype.areAllShipsPlaced = function() {
var playerRoster = document.querySelectorAll('.fleet-roster li');
for (var i = 0; i < playerRoster.length; i++) {
if (playerRoster[i].getAttribute('class') === 'placed') {
continue;
} else {
return false;
}
}
// Reset temporary variables
Game.placeShipDirection = 0;
Game.placeShipType = '';
Game.placeShipCoords = [];
return true;
};
// Zresetuje mgłę wojny
Game.prototype.resetFogOfWar = function() {
for (var i = 0; i < Game.size; i++) {
for (var j = 0; j < Game.size; j++) {
this.humanGrid.updateCell(i, j, 'empty', CONST.HUMAN_PLAYER);
this.computerGrid.updateCell(i, j, 'empty', CONST.COMPUTER_PLAYER);
}
}
// Reset all values to indicate the ships are ready to be placed again
Game.usedShips = Game.usedShips.map(function(){return CONST.UNUSED;});
};
// Zresetowanie stylu CSS na pasku bocznym
Game.prototype.resetRosterSidebar = function() {
var els = document.querySelector('.fleet-roster').querySelectorAll('li');
for (var i = 0; i < els.length; i++) {
els[i].removeAttribute('class');
}
if (gameTutorial.showTutorial) {
gameTutorial.nextStep();
} else {
document.getElementById('roster-sidebar').removeAttribute('class');
}
document.getElementById('rotate-button').removeAttribute('class');
document.getElementById('start-game').setAttribute('class', 'hidden');
if (DEBUG_MODE) {
document.getElementById('place-randomly').removeAttribute('class');
}
};
Game.prototype.showRestartSidebar = function() {
var sidebar = document.getElementById('restart-sidebar');
sidebar.setAttribute('class', 'highlight');
// Deregister listeners
var computerCells = document.querySelector('.computer-player').childNodes;
for (var j = 0; j < computerCells.length; j++) {
computerCells[j].removeEventListener('click', this.shootListener, false);
}
var playerRoster = document.querySelector('.fleet-roster').querySelectorAll('li');
for (var i = 0; i < playerRoster.length; i++) {
playerRoster[i].removeEventListener('click', this.rosterListener, false);
}
var restartButton = document.getElementById('restart-game');
restartButton.addEventListener('click', this.restartGame, false);
restartButton.self = this;
};
// Tworzy divy HTML dla siatki dla obu graczy
Game.prototype.createGrid = function() {
var gridDiv = document.querySelectorAll('.grid');
for (var grid = 0; grid < gridDiv.length; grid++) {
gridDiv[grid].removeChild(gridDiv[grid].querySelector('.no-js')); // Removes the no-js warning
for (var i = 0; i < Game.size; i++) {
for (var j = 0; j < Game.size; j++) {
var el = document.createElement('div');
el.setAttribute('data-x', i);
el.setAttribute('data-y', j);
el.setAttribute('class', 'grid-cell grid-cell-' + i + '-' + j);
gridDiv[grid].appendChild(el);
}
}
}
};
// inicjuje grę. Również resetuje grę, jeśli została wcześniej zainicjowana
Game.prototype.init = function() {
this.humanGrid = new Grid(Game.size);
this.computerGrid = new Grid(Game.size);
this.humanFleet = new Fleet(this.humanGrid, CONST.HUMAN_PLAYER);
this.computerFleet = new Fleet(this.computerGrid, CONST.COMPUTER_PLAYER);
this.robot = new AI(this);
Game.stats = new Stats();
Game.stats.updateStatsSidebar();
// Reset game variables
this.shotsTaken = 0;
this.readyToPlay = false;
this.placingOnGrid = false;
Game.placeShipDirection = 0;
Game.placeShipType = '';
Game.placeShipCoords = [];
this.resetRosterSidebar();
// Dodaj metodę kliknięć dla metody Grid.shoot () dla wszystkich komórek
// Tylko dodaj tego słuchacza do sieci komputerowej
var computerCells = document.querySelector('.computer-player').childNodes;
for (var j = 0; j < computerCells.length; j++) {
computerCells[j].self = this;
computerCells[j].addEventListener('click', this.shootListener, false);
}
// Dodaj listener do listy
var playerRoster = document.querySelector('.fleet-roster').querySelectorAll('li');
for (var i = 0; i < playerRoster.length; i++) {
playerRoster[i].self = this;
playerRoster[i].addEventListener('click', this.rosterListener, false);
}
// Dodawanie detektora kliknięć do siatki ludzkiego gracza podczas wprowadzania
var humanCells = document.querySelector('.human-player').childNodes;
for (var k = 0; k < humanCells.length; k++) {
humanCells[k].self = this;
humanCells[k].addEventListener('click', this.placementListener, false);
humanCells[k].addEventListener('mouseover', this.placementMouseover, false);
humanCells[k].addEventListener('mouseout', this.placementMouseout, false);
}
var rotateButton = document.getElementById('rotate-button');
rotateButton.addEventListener('click', this.toggleRotation, false);
var startButton = document.getElementById('start-game');
startButton.self = this;
startButton.addEventListener('click', this.startGame, false);
var resetButton = document.getElementById('reset-stats');
resetButton.addEventListener('click', Game.stats.resetStats, false);
var randomButton = document.getElementById('place-randomly');
randomButton.self = this;
randomButton.addEventListener('click', this.placeRandomly, false);
this.computerFleet.placeShipsRandomly();
};
// Grid object
// Constructor
function Grid(size) {
this.size = size;
this.cells = [];
this.init();
}
// Inicjowanie i wypełnienie siatki
Grid.prototype.init = function() {
for (var x = 0; x < this.size; x++) {
var row = [];
this.cells[x] = row;
for (var y = 0; y < this.size; y++) {
row.push(CONST.TYPE_EMPTY);
}
}
};
// Aktualizuje klasę CSS komórki w oparciu o typ przekazany
Grid.prototype.updateCell = function(x, y, type, targetPlayer) {
var player;
if (targetPlayer === CONST.HUMAN_PLAYER) {
player = 'human-player';
} else if (targetPlayer === CONST.COMPUTER_PLAYER) {
player = 'computer-player';
} else {
// Should never be called
console.log("There was an error trying to find the correct player's grid");
}
switch (type) {
case CONST.CSS_TYPE_EMPTY:
this.cells[x][y] = CONST.TYPE_EMPTY;
break;
case CONST.CSS_TYPE_SHIP:
this.cells[x+1][y+1] = CONST.TYPE_SHIP;
break;
case CONST.CSS_TYPE_MISS:
this.cells[x][y] = CONST.TYPE_MISS;
break;
case CONST.CSS_TYPE_HIT:
this.cells[x][y] = CONST.TYPE_HIT;
break;
case CONST.CSS_TYPE_SUNK:
this.cells[x][y] = CONST.TYPE_SUNK;
break;
default:
this.cells[x][y] = CONST.TYPE_EMPTY;
break;
}
var classes = ['grid-cell', 'grid-cell-' + x + '-' + y, 'grid-' + type];
document.querySelector('.' + player + ' .grid-cell-' + x + '-' + y).setAttribute('class', classes.join(' '));
};
// Sprawdza, czy komórka zawiera nieuszkodzony statek
// Zwraca boolean
Grid.prototype.isUndamagedShip = function(x, y) {
return this.cells[x][y] === CONST.TYPE_SHIP;
};
// Sprawdza, czy strzał został pominięty. To jest równoważne
// sprawdzenie, czy komórka zawiera kulę armatnią
// Zwraca boolean
Grid.prototype.isMiss = function(x, y) {
return this.cells[x][y] === CONST.TYPE_MISS;
};
// Sprawdza, czy komórka zawiera uszkodzony statek,
// uderzysz lub zatapia.
// Zwraca boolean
Grid.prototype.isDamagedShip = function(x, y) {
return this.cells[x][y] === CONST.TYPE_HIT || this.cells[x][y] === CONST.TYPE_SUNK;
};
// obiekt Floty
// Ten obiekt służy do śledzenia portfela statków gracza
// Konstruktor
function Fleet(playerGrid, player) {
this.numShips = CONST.AVAILABLE_SHIPS.length;
this.playerGrid = playerGrid;
this.player = player;
this.fleetRoster = [];
this.populate();
}
// Posiada flotę
Fleet.prototype.populate = function() {
for (var i = 0; i < this.numShips; i++) {
// loop over the ship types when numShips > Constants.AVAILABLE_SHIPS.length
var j = i % CONST.AVAILABLE_SHIPS.length;
this.fleetRoster.push(new Ship(CONST.AVAILABLE_SHIPS[j], this.playerGrid, this.player));
}
};
// umieszcza statek i zwraca, czy miejsce docelowe jest skuteczne
// Zwraca boolean
Fleet.prototype.placeShip = function(x, y, direction, shipType) {
var shipCoords;
for (var i = 0; i < this.fleetRoster.length; i++) {
var shipTypes = this.fleetRoster[i].type;
if (shipType === shipTypes &&
this.fleetRoster[i].isLegal(x, y, direction)) {
this.fleetRoster[i].create(x, y, direction, false);
shipCoords = this.fleetRoster[i].getAllShipCells();
for (var j = 0; j < shipCoords.length; j++) {
this.playerGrid.updateCell(shipCoords[j].x, shipCoords[j].y, 'ship', this.player);
}
return true;
}
}
return false;
};
// Miejsca statków są losowo umieszczane na pokładzie
// TODO: Unikaj umieszczania statków zbyt blisko siebie
Fleet.prototype.placeShipsRandomly = function() {
var shipCoords;
for (var i = 0; i < this.fleetRoster.length; i++) {
var illegalPlacement = true;
// Prevents the random placement of already placed ships
if(this.player === CONST.HUMAN_PLAYER && Game.usedShips[i] === CONST.USED) {
continue;
}
while (illegalPlacement) {
var randomX = Math.floor(10*Math.random());
var randomY = Math.floor(10*Math.random());
var randomDirection = Math.floor(2*Math.random());
if (this.fleetRoster[i].isLegal(randomX, randomY, randomDirection)) {
this.fleetRoster[i].create(randomX, randomY, randomDirection, false);
shipCoords = this.fleetRoster[i].getAllShipCells();
illegalPlacement = false;
} else {
continue;
}
}
if (this.player === CONST.HUMAN_PLAYER && Game.usedShips[i] !== CONST.USED) {
for (var j = 0; j < shipCoords.length; j++) {
this.playerGrid.updateCell(shipCoords[j].x, shipCoords[j].y, 'ship', this.player);
Game.usedShips[i] = CONST.USED;
}
}
}
};
// Znajduje statek według lokalizacji
// Zwraca obiekt statku znajdujący się pod adresem (x, y)
// Jeśli żaden statek nie istnieje w (x, y), zamiast tego zwraca null
Fleet.prototype.findShipByCoords = function(x, y) {
for (var i = 0; i < this.fleetRoster.length; i++) {
var currentShip = this.fleetRoster[i];
if (currentShip.direction === Ship.DIRECTION_VERTICAL) {
if (y === currentShip.yPosition &&
x >= currentShip.xPosition &&
x < currentShip.xPosition + currentShip.shipLength) {
return currentShip;
} else {
continue;
}
} else {
if (x === currentShip.xPosition &&
y >= currentShip.yPosition &&
y < currentShip.yPosition + currentShip.shipLength) {
return currentShip;
} else {
continue;
}
}
}
return null;
};
// Znajduje statek według jego typu
// Param shipType to ciąg znaków
// Zwraca obiekt statku, który jest typu typu shipType
// Jeśli żaden statek nie istnieje, zwraca null.
Fleet.prototype.findShipByType = function(shipType) {
for (var i = 0; i < this.fleetRoster.length; i++) {
if (this.fleetRoster[i].type === shipType) {
return this.fleetRoster[i];
}
}
return null;
};
// Checks to see if all ships have been sunk
// Returns boolean
Fleet.prototype.allShipsSunk = function() {
for (var i = 0; i < this.fleetRoster.length; i++) {
// If one or more ships are not sunk, then the sentence "all ships are sunk" is false.
if (this.fleetRoster[i].sunk === false) {
return false;
}
}
return true;
};
// Ship object
// Constructor
function Ship(type, playerGrid, player) {
this.damage = 0;
this.type = type;
this.playerGrid = playerGrid;
this.player = player;
switch (this.type) {
case CONST.AVAILABLE_SHIPS[0]:
this.shipLength = 5;
break;
case CONST.AVAILABLE_SHIPS[1]:
this.shipLength = 4;
break;
case CONST.AVAILABLE_SHIPS[2]:
this.shipLength = 3;
break;
case CONST.AVAILABLE_SHIPS[3]:
this.shipLength = 3;
break;
case CONST.AVAILABLE_SHIPS[4]:
this.shipLength = 2;
break;
default:
this.shipLength = 3;
break;
}
this.maxDamage = this.shipLength;
this.sunk = false;
}
// Sprawdza, czy umieszczenie statku jest legalne
// Zwraca boolean
Ship.prototype.isLegal = function(x, y, direction) {
// najpierw sprawdź czy statek znajduje się w sieci ...
if (this.withinBounds(x, y, direction))
{
// ... sprawdź, czy nie koliduje z innym statkiem
for (var i = 0; i < this.shipLength; i++)
{
if (direction === Ship.DIRECTION_VERTICAL)
{
if (this.playerGrid.cells[x + i][y] === CONST.TYPE_SHIP ||
this.playerGrid.cells[x - i][y] === CONST.TYPE_SHIP ||
this.playerGrid.cells[x + i][y] === CONST.TYPE_MISS ||
this.playerGrid.cells[x + i][y] === CONST.TYPE_SUNK)
{
return false;
}
}
else
{
if (this.playerGrid.cells[x][y + i] === CONST.TYPE_SHIP ||
this.playerGrid.cells[x][y + i] === CONST.TYPE_MISS ||
this.playerGrid.cells[x][y + i] === CONST.TYPE_SUNK)
{
return false;}
}
}
return true;
}
else
return false;
};
// Sprawdza, czy statek znajduje się w granicach sieci
// Zwraca boolean
//statek mieści się w granicach gry
Ship.prototype.withinBounds = function(x, y, direction) {
if (direction === Ship.DIRECTION_VERTICAL) {
return x + this.shipLength <= Game.size;
} else {
return y + this.shipLength <= Game.size;
}
};
// zwiększa licznik uszkodzeń statku
// Zwraca statek
Ship.prototype.incrementDamage = function() {
this.damage++;
if (this.isSunk()) {
this.sinkShip(false); // Sinks the ship
}
};
// Sprawdza, czy statek jest zatopiony
// Zwraca boolean
Ship.prototype.isSunk = function() {
return this.damage >= this.maxDamage;
};
// Zanurzysz statek
Ship.prototype.sinkShip = function(virtual) {
this.damage = this.maxDamage; // Force the damage to exceed max damage
this.sunk = true;
// Uczynienie klasy CSS zatopionej, ale tylko wtedy, gdy statek nie jest wirtualny
if (!virtual) {
var allCells = this.getAllShipCells();
for (var i = 0; i < this.shipLength; i++) {
this.playerGrid.updateCell(allCells[i].x, allCells[i].y, 'sunk', this.player);
}
}
};
Ship.prototype.getAllShipCells = function() {
var resultObject = [];
for (var i = 0; i < this.shipLength; i++) {
if (this.direction === Ship.DIRECTION_VERTICAL) {
resultObject[i] = {'x': this.xPosition + i, 'y': this.yPosition};
} else {
resultObject[i] = {'x': this.xPosition, 'y': this.yPosition + i};
}
}
return resultObject;
};
// Inicjuje statek o podanym współrzędnym i kierunku (łożyska).
// Jeśli statek zostanie uznany za "wirtualny", wtedy statek zostanie zainicjowany
// jego współrzędne, ale NIE umieszcza się na siatce.
Ship.prototype.create = function(x, y, direction, virtual) {
// This function assumes that you've already checked that the placement is legal
this.xPosition = x;
this.yPosition = y;
this.direction = direction;
// Jeśli statek jest wirtualny, nie dodaj go do sieci.
if (!virtual) {
for (var i = 0; i < this.shipLength; i++) {
if (this.direction === Ship.DIRECTION_VERTICAL) {
this.playerGrid.cells[x + i][y] = CONST.TYPE_SHIP;
} else {
this.playerGrid.cells[x][y + i] = CONST.TYPE_SHIP;
}
}
}
};
// direction === 0 when the ship is facing north/south
// direction === 1 when the ship is facing east/west
Ship.DIRECTION_VERTICAL = 0;
Ship.DIRECTION_HORIZONTAL = 1;
// Tutorial Object
// Constructor
function Tutorial() {
this.currentStep = 0;
// Check if 'showTutorial' is initialized, if it's uninitialized, set it to true.
this.showTutorial = localStorage.getItem('showTutorial') !== 'false';
}
// Advances the tutorial to the next step
Tutorial.prototype.nextStep = function() {
var humanGrid = document.querySelector('.human-player');
var computerGrid = document.querySelector('.computer-player');
switch (this.currentStep) {
case 0:
document.getElementById('roster-sidebar').setAttribute('class', 'highlight');
document.getElementById('step1').setAttribute('class', 'current-step');
this.currentStep++;
break;
case 1:
document.getElementById('roster-sidebar').removeAttribute('class');
document.getElementById('step1').removeAttribute('class');
humanGrid.setAttribute('class', humanGrid.getAttribute('class') + ' highlight');
document.getElementById('step2').setAttribute('class', 'current-step');
this.currentStep++;
break;
case 2:
document.getElementById('step2').removeAttribute('class');
var humanClasses = humanGrid.getAttribute('class');
humanClasses = humanClasses.replace(' highlight', '');
humanGrid.setAttribute('class', humanClasses);
this.currentStep++;
break;
case 3:
computerGrid.setAttribute('class', computerGrid.getAttribute('class') + ' highlight');
document.getElementById('step3').setAttribute('class', 'current-step');
this.currentStep++;
break;
case 4:
var computerClasses = computerGrid.getAttribute('class');
document.getElementById('step3').removeAttribute('class');
computerClasses = computerClasses.replace(' highlight', '');
computerGrid.setAttribute('class', computerClasses);
document.getElementById('step4').setAttribute('class', 'current-step');
this.currentStep++;
break;
case 5:
document.getElementById('step4').removeAttribute('class');
this.currentStep = 6;
this.showTutorial = false;
localStorage.setItem('showTutorial', false);
break;
default:
break;
}
};
// AI Object
// Optimal battleship-playing AI
// Constructor
function AI(gameObject) {
this.gameObject = gameObject;
this.virtualGrid = new Grid(Game.size);
this.virtualFleet = new Fleet(this.virtualGrid, CONST.VIRTUAL_PLAYER);
this.probGrid = []; // Probability Grid
this.initProbs();
this.updateProbs();
}
AI.PROB_WEIGHT = 5000; // arbitrarily big number
// jaka masa ma przynieść komórki wysokiego prawdopodobieństwa książki otwartej
AI.OPEN_HIGH_MIN = 20;
AI.OPEN_HIGH_MAX = 30;
// ile wagi daje się średnim prawdopodobieństwom książki otwarcia
AI.OPEN_MED_MIN = 15;
AI.OPEN_MED_MAX = 25;
// how much weight to give to the opening book's low probability cells
AI.OPEN_LOW_MIN = 10;
AI.OPEN_LOW_MAX = 20;
// Amount of randomness when selecting between cells of equal probability
AI.RANDOMNESS = 0.1;
// AI's opening book.
// This is the pattern of the first cells for the AI to target
AI.OPENINGS = [
{'x': 7, 'y': 3, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
{'x': 6, 'y': 2, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
{'x': 3, 'y': 7, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
{'x': 2, 'y': 6, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
{'x': 6, 'y': 6, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
{'x': 3, 'y': 3, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
{'x': 5, 'y': 5, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
{'x': 4, 'y': 4, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
// {'x': 9, 'y': 5, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
// {'x': 0, 'y': 4, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
// {'x': 5, 'y': 9, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
// {'x': 4, 'y': 0, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
{'x': 0, 'y': 8, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
{'x': 1, 'y': 9, 'weight': getRandom(AI.OPEN_HIGH_MIN, AI.OPEN_HIGH_MAX)},
{'x': 8, 'y': 0, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
{'x': 9, 'y': 1, 'weight': getRandom(AI.OPEN_HIGH_MIN, AI.OPEN_HIGH_MAX)},
{'x': 9, 'y': 9, 'weight': getRandom(AI.OPEN_HIGH_MIN, AI.OPEN_HIGH_MAX)},
{'x': 0, 'y': 0, 'weight': getRandom(AI.OPEN_HIGH_MIN, AI.OPEN_HIGH_MAX)}
];
// Zrzuca siatkę w oparciu o maksymalne prawdopodobieństwo i strzela do komórki
// ma największe prawdopodobieństwo zawarcia statku
AI.prototype.shoot = function() {
var maxProbability = 0;
var maxProbCoords;
var maxProbs = [];
// Add the AI's opening book to the probability grid
for (var i = 0; i < AI.OPENINGS.length; i++) {
var cell = AI.OPENINGS[i];
if (this.probGrid[cell.x][cell.y] !== 0) {
this.probGrid[cell.x][cell.y] += cell.weight;
}
}
for (var x = 0; x < Game.size; x++) {
for (var y = 0; y < Game.size; y++) {
if (this.probGrid[x][y] > maxProbability) {
maxProbability = this.probGrid[x][y];
maxProbs = [{'x': x, 'y': y}]; // Replace the array
} else if (this.probGrid[x][y] === maxProbability) {
maxProbs.push({'x': x, 'y': y});
}
}
}
maxProbCoords = Math.random() < AI.RANDOMNESS ?
maxProbs[Math.floor(Math.random() * maxProbs.length)] :
maxProbs[0];
var result = this.gameObject.shoot(maxProbCoords.x, maxProbCoords.y, CONST.HUMAN_PLAYER);
// If the game ends, the next lines need to be skipped.
if (Game.gameOver) {
Game.gameOver = false;
return;
}
this.virtualGrid.cells[maxProbCoords.x][maxProbCoords.y] = result;
// If you hit a ship, check to make sure if you've sunk it.
if (result === CONST.TYPE_HIT) {
var humanShip = this.findHumanShip(maxProbCoords.x, maxProbCoords.y);
if (humanShip.isSunk()) {
// Remove any ships from the roster that have been sunk
var shipTypes = [];
for (var k = 0; k < this.virtualFleet.fleetRoster.length; k++) {
shipTypes.push(this.virtualFleet.fleetRoster[k].type);
}
var index = shipTypes.indexOf(humanShip.type);
this.virtualFleet.fleetRoster.splice(index, 1);
// Update the virtual grid with the sunk ship's cells
var shipCells = humanShip.getAllShipCells();
for (var _i = 0; _i < shipCells.length; _i++) {
this.virtualGrid.cells[shipCells[_i].x][shipCells[_i].y] = CONST.TYPE_SUNK;
}
}
}
// Update probability grid after each shot
this.updateProbs();
};
// Update the probability grid
AI.prototype.updateProbs = function() {
var roster = this.virtualFleet.fleetRoster;
var coords;
this.resetProbs();
// Prawdopodobieństwa nie są znormalizowane, aby pasowały do przedziału [0, 1]
// dlatego, że jesteśmy zainteresowani maksymalną wartością.
// Działa to poprzez dopasowanie każdego statku do każdej komórki w każdej orientacji
// W każdej komórce, tym bardziej legalnym sposobem, w jaki statek może przechodzić przez niego, tym bardziej
// prawdopodobnie komórka ma zawierać statek.
// Komórki otaczające znane "trafienia" otrzymują arbitralnie duże prawdopodobieństwo
// tak, że AI próbuje całkowicie zatopić statek przed przejściem dalej.
// TODO: Pomyśl o bardziej efektywnej implementacji
for (var k = 0; k < roster.length; k++) {
for (var x = 0; x < Game.size; x++) {
for (var y = 0; y < Game.size; y++) {
if (roster[k].isLegal(x, y, Ship.DIRECTION_VERTICAL)) {
roster[k].create(x, y, Ship.DIRECTION_VERTICAL, true);
coords = roster[k].getAllShipCells();
if (this.passesThroughHitCell(coords)) {
for (var i = 0; i < coords.length; i++) {
this.probGrid[coords[i].x][coords[i].y] += AI.PROB_WEIGHT * this.numHitCellsCovered(coords);
}
} else {
for (var _i = 0; _i < coords.length; _i++) {
this.probGrid[coords[_i].x][coords[_i].y]++;
}
}
}
if (roster[k].isLegal(x, y, Ship.DIRECTION_HORIZONTAL)) {
roster[k].create(x, y, Ship.DIRECTION_HORIZONTAL, true);
coords = roster[k].getAllShipCells();
if (this.passesThroughHitCell(coords)) {
for (var j = 0; j < coords.length; j++) {
this.probGrid[coords[j].x][coords[j].y] += AI.PROB_WEIGHT * this.numHitCellsCovered(coords);
}
} else {
for (var _j = 0; _j < coords.length; _j++) {
this.probGrid[coords[_j].x][coords[_j].y]++;
}
}
}
// Set hit cells to probability zero so the AI doesn't
// target cells that are already hit
if (this.virtualGrid.cells[x][y] === CONST.TYPE_HIT) {
this.probGrid[x][y] = 0;
}
}
}
}
};
// Initializes the probability grid for targeting
AI.prototype.initProbs = function() {
for (var x = 0; x < Game.size; x++) {
var row = [];
this.probGrid[x] = row;
for (var y = 0; y < Game.size; y++) {
row.push(0);
}
}
};
// Resets the probability grid to all 0.
AI.prototype.resetProbs = function() {
for (var x = 0; x < Game.size; x++) {
for (var y = 0; y < Game.size; y++) {
this.probGrid[x][y] = 0;
}
}
};
AI.prototype.metagame = function() {
// Inputs:
// Proximity of hit cells to edge
// Proximity of hit cells to each other
// Edit the probability grid by multiplying each cell with a new probability weight (e.g. 0.4, or 3). Set this as a CONST and make 1-CONST the inverse for decreasing, or 2*CONST for increasing
};
// Finds a human ship by coordinates
// Returns Ship
AI.prototype.findHumanShip = function(x, y) {
return this.gameObject.humanFleet.findShipByCoords(x, y);
};
// Checks whether or not a given ship's cells passes through
// any cell that is hit.
// Returns boolean
AI.prototype.passesThroughHitCell = function(shipCells) {
for (var i = 0; i < shipCells.length; i++) {
if (this.virtualGrid.cells[shipCells[i].x][shipCells[i].y] === CONST.TYPE_HIT) {
return true;
}
}
return false;
};
// Gives the number of hit cells the ships passes through. The more
// cells this is, the more probable the ship exists in those coordinates
// Returns int
AI.prototype.numHitCellsCovered = function(shipCells) {
var cells = 0;
for (var i = 0; i < shipCells.length; i++) {
if (this.virtualGrid.cells[shipCells[i].x][shipCells[i].y] === CONST.TYPE_HIT) {
cells++;
}
}
return cells;
};
// Global constant only initialized once
var gameTutorial = new Tutorial();
// Start the game
var mainGame = new Game(10);
})();
// Array.prototype.indexOf workaround for IE browsers that don't support it
// From MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement, fromIndex) {
var k;
// 1. Let O be the result of calling ToObject passing
// the this value as the argument.
if (this === null || this === undefined) {
throw new TypeError('"this" is null or not defined');
}
var O = Object(this);
// 2. Let lenValue be the result of calling the Get
// internal method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If len is 0, return -1.
if (len === 0) {
return -1;
}
// 5. If argument fromIndex was passed let n be
// ToInteger(fromIndex); else let n be 0.
var n = +fromIndex || 0;
if (Math.abs(n) === Infinity) {
n = 0;
}
// 6. If n >= len, return -1.
if (n >= len) {
return -1;
}
// 7. If n >= 0, then Let k be n.
// 8. Else, n<0, Let k be len - abs(n).
// If k is less than 0, then let k be 0.
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
// 9. Repeat, while k < len
while (k < len) {
var kValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the
// HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
// i. Let elementK be the result of calling the Get
// internal method of O with the argument ToString(k).
// ii. Let same be the result of applying the
// Strict Equality Comparison Algorithm to
// searchElement and elementK.
// iii. If same is true, return k.
if (k in O && O[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
}
// Array.prototype.map workaround for IE browsers that don't support it
// From MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.io/#x15.4.4.19
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
// 1. Let O be the result of calling ToObject passing the |this|
// value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get internal
// method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If IsCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let A be a new array created as if by the expression new Array(len)
// where Array is the standard built-in constructor with that name and
// len is the value of len.
A = new Array(len);
// 7. Let k be 0
k = 0;
// 8. Repeat, while k < len
while (k < len) {
var kValue, mappedValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal
// method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
// ii. Let mappedValue be the result of calling the Call internal
// method of callback with T as the this value and argument
// list containing kValue, k, and O.
mappedValue = callback.call(T, kValue, k, O);
// iii. Call the DefineOwnProperty internal method of A with arguments
// Pk, Property Descriptor
// { Value: mappedValue,
// Writable: true,
// Enumerable: true,
// Configurable: true },
// and false.
// In browsers that support Object.defineProperty, use the following:
// Object.defineProperty(A, k, {
// value: mappedValue,
// writable: true,
// enumerable: true,
// configurable: true
// });
// For best browser support, use the following:
A[k] = mappedValue;
}
// d. Increase k by 1.
k++;
}
// 9. return A
return A;
};
}
// Browser compatability workaround for transition end event names.
// From modernizr: http://stackoverflow.com/a/9090128
function transitionEndEventName() {
var i,
undefined,
el = document.createElement('div'),
transitions = {
'transition':'transitionend',
'OTransition':'otransitionend', // oTransitionEnd in very old Opera
'MozTransition':'transitionend',
'WebkitTransition':'webkitTransitionEnd'
};
for (i in transitions) {
if (transitions.hasOwnProperty(i) && el.style[i] !== undefined) {
return transitions[i];
}
}
}
// Returns a random number between min (inclusive) and max (exclusive)
function getRandom(min, max) {
return Math.random() * (max - min) + min;
}
// Toggles on or off DEBUG_MODE
function setDebug(val) {
DEBUG_MODE = val;
localStorage.setItem('DEBUG_MODE', val);
localStorage.setItem('showTutorial', 'false');
window.location.reload();
}