From f1c31316a12a76c9b9af4b40e605eb040d7dbd43 Mon Sep 17 00:00:00 2001 From: Maksymilian Jopek Date: Tue, 28 Feb 2023 15:44:44 +0100 Subject: v1.0.0 --- battleship.html | 40 ++++ battleshipDebug.html | 45 ++++ battleshipFiles/Battleship.js | 36 +++ battleshipFiles/battleship.css | 15 ++ battleshipFiles/battleshipBotFunctions.js | 279 ++++++++++++++++++++++ battleshipFiles/battleshipFunctions.js | 211 +++++++++++++++++ battleshipFiles/battleshipGameFunctions.js | 362 +++++++++++++++++++++++++++++ battleshipFiles/battleshipUIFunctions.js | 173 ++++++++++++++ battleshipFiles/battleshipUserFunctions.js | 97 ++++++++ battleshipFiles/constants.js | 27 +++ 10 files changed, 1285 insertions(+) create mode 100644 battleship.html create mode 100644 battleshipDebug.html create mode 100644 battleshipFiles/Battleship.js create mode 100644 battleshipFiles/battleship.css create mode 100644 battleshipFiles/battleshipBotFunctions.js create mode 100644 battleshipFiles/battleshipFunctions.js create mode 100644 battleshipFiles/battleshipGameFunctions.js create mode 100644 battleshipFiles/battleshipUIFunctions.js create mode 100644 battleshipFiles/battleshipUserFunctions.js create mode 100644 battleshipFiles/constants.js diff --git a/battleship.html b/battleship.html new file mode 100644 index 0000000..c23bfbf --- /dev/null +++ b/battleship.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + Statki + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/battleshipDebug.html b/battleshipDebug.html new file mode 100644 index 0000000..dc86c3d --- /dev/null +++ b/battleshipDebug.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + Statki + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/battleshipFiles/Battleship.js b/battleshipFiles/Battleship.js new file mode 100644 index 0000000..9b2a62a --- /dev/null +++ b/battleshipFiles/Battleship.js @@ -0,0 +1,36 @@ +class Battleship +{ + constructor(name) + { + // Set name + this.name = name; + // Initializing game map + this.gameMap = createGameMap(); + // Declaring how many of which type ships there would be + this.shipsMap = new Map(); + this.shipsMap.set(4, NUM_OF_4); + this.shipsMap.set(3, NUM_OF_3); + this.shipsMap.set(2, NUM_OF_2); + this.shipsMap.set(1, NUM_OF_1); + this.shipsArray = []; + for(let [key, value] of this.shipsMap) + for(let i = 0; i < value; i++) + this.shipsArray.push(key); + + this.howManyShips = eval(this.shipsArray.join('+')); + // creating vars, used later + this.destroyedShips = 0; + } +} +function createGameMap() +{ + let gameMap = new Array(MAP_SIZE + 2); + for(let i = 0; i < MAP_SIZE + 2; i++) + gameMap[i] = new Array(MAP_SIZE + 2); + + for(let i = 0; i < MAP_SIZE + 2; i++) + for(let j = 0; j < MAP_SIZE + 2; j++) + gameMap[i][j] = 0; + + return gameMap; +} \ No newline at end of file diff --git a/battleshipFiles/battleship.css b/battleshipFiles/battleship.css new file mode 100644 index 0000000..89ec109 --- /dev/null +++ b/battleshipFiles/battleship.css @@ -0,0 +1,15 @@ +.ul +{ + list-style-type: none; +} + +#whosTurn { + position: fixed; + bottom: 10rem; + left: 50%; + translate: -50%; +} + +body { + height: 100vh; +} diff --git a/battleshipFiles/battleshipBotFunctions.js b/battleshipFiles/battleshipBotFunctions.js new file mode 100644 index 0000000..669a881 --- /dev/null +++ b/battleshipFiles/battleshipBotFunctions.js @@ -0,0 +1,279 @@ +function populateMapRandomly(player) +{ + for(let ship of player.shipsArray) + { + let isSet = false; + while(!isSet) + { + let direction = getRandomInt(0, 2) === 0 ? "horizontally" : "vertically"; + let x, y; + // if(direction === "horizontally") + if(direction === "vertically") + { + x = getRandomInt(1, MAP_SIZE - ship + 2); + y = getRandomInt(1, MAP_SIZE + 1); + } + // else if(direction === "vertically") + else if(direction === "horizontally") + { + x = getRandomInt(1, MAP_SIZE + 1); + y = getRandomInt(1, MAP_SIZE - ship + 2); + } + if(checkPositionForShip(x, y, direction, ship, player) === true) + { + isSet = true; + putShipOnGameMap(player, x, y, direction, ship, false); + } + } + } +} + +function botShot(bot, user, ai) +{ + // console.log("Shot number: " + ai.numberOfShots++ + (ai.shipWasShot.length !== 0 ? " (ship was shot)" : " (ship wasnt shot)")); + // console.log("Start: ", ai); + + let superPositions = calculateSuperpositionsArray(bot, ai); + let userTable = document.getElementById("userTable"); + let shotIndex = {x: 'x', y: 'y'}; + + if(ai.shipWasShot.length !== 0) + { + let x = ai.shipWasShot[0]; + let y = ai.shipWasShot[1]; + + if(!ai.shotShipDirection) + { + let possibleShots = new Map(); + if(x !== 1) + possibleShots.set("up", [superPositions[x - 1][y], x - 1, y]); + if(x !== 10) + possibleShots.set("down", [superPositions[x + 1][y], x + 1, y]); + if(y !== 1) + possibleShots.set("left", [superPositions[x][y - 1], x, y - 1]); + if(y !== 10) + possibleShots.set("right", [superPositions[x][y + 1], x, y + 1]); + + possibleShots = new Map([...possibleShots.entries()].sort((a, b) => b[1][0] - a[1][0])); + + if(possibleShots.entries().next().value[0] === "up" || possibleShots.entries().next().value[0] === "down") + ai.shotShipDirection = "vertically"; + else + ai.shotShipDirection = "horizontally"; + + shotIndex.x = possibleShots.entries().next().value[1][1]; + shotIndex.y = possibleShots.entries().next().value[1][2]; + + // if(possibleShots.entries().next().value[1][0] < 1) + // ai.shipWasShot = []; + } + else + { + let beforeFirst = {x: 'x', y: 'y'}, afterLast = {x: 'x', y: 'y'}; + if(ai.shotShipDirection === "vertically") + { + // console.log("unsorted:", ai.shotShips); + ai.shotShips.sort((a, b) => a.x - b.x); + // console.log("sorted:", ai.shotShips); + beforeFirst.x = ai.shotShips.first().x - 1; + beforeFirst.y = ai.shotShips.first().y; + afterLast.x = ai.shotShips.last().x + 1; + afterLast.y = ai.shotShips.last().y; + } + else if(ai.shotShipDirection === "horizontally") + { + // console.log("unsorted:", ai.shotShips); + ai.shotShips.sort((a, b) => a.y - b.y); + // console.log("sorted:", ai.shotShips); + beforeFirst.x = ai.shotShips.first().x; + beforeFirst.y = ai.shotShips.first().y - 1; + afterLast.x = ai.shotShips.last().x; + afterLast.y = ai.shotShips.last().y + 1; + } + // if(superPositions[beforeFirst.x][beforeFirst.y] > 0 || superPositions[afterLast.x][afterLast.y] > 0) + // { + if(superPositions[beforeFirst.x][beforeFirst.y] > superPositions[afterLast.x][afterLast.y]) + { + shotIndex.x = beforeFirst.x; + shotIndex.y = beforeFirst.y; + } + else + { + + shotIndex.x = afterLast.x; + shotIndex.y = afterLast.y; + } + // } + // else + // ai.shipWasShot = []; + } + + // if(!ai.shotShips.includes({x: shotIndex.x, y: shotIndex.y})) + ai.shotShips.push({x: shotIndex.x, y: shotIndex.y}); + } + if(ai.shipWasShot.length === 0 || superPositions[shotIndex.x][shotIndex.y] === 0) + { + let indexes = getAllIndexes2D(superPositions, Math.max.apply(null, + superPositions.map(row => Math.max.apply(null, row)))); + let t = indexes[getRandomInt(0, indexes.length)]; + shotIndex.x = t[0]; + shotIndex.y = t[1]; + } + + if(user.gameMap[shotIndex.x][shotIndex.y] !== 0) + { + ai.shipWasShot = [shotIndex.x, shotIndex.y]; + user.gameMap[shotIndex.x][shotIndex.y] = 2; + ai.gameMap[shotIndex.x][shotIndex.y] = -3; + userTable.children[shotIndex.x - 1].children[shotIndex.y - 1].innerHTML = SHIP_HITTED; + + if(ai.shotShips.length === 0) + ai.shotShips.push({x: shotIndex.x, y: shotIndex.y}); + } + else + { + ai.gameMap[shotIndex.x][shotIndex.y] = -2; + userTable.children[shotIndex.x - 1].children[shotIndex.y - 1].innerHTML = SHIP_MISSED; + user.gameMap[shotIndex.x][shotIndex.y] = 3; + + if(ai.shotShips.length > 1) + ai.shotShips.pop(); + + if(ai.shotShips.length === 1) + ai.shotShipDirection = ""; + + turnChange(bot.name); + } + if(ai.shotShips.length !== 0) + checkShotShips(bot, ai, superPositions); + + // if(ai.shotShips.length !== 0) + // console.log("End: ", ai.gameMap, ai.shipWasShot, ai.shotShipDirection, ai.shotShips); + + // if([0, 3].includes(bot.gameMap[shotIndex.x][shotIndex.y]) )//user.gameMap[shotIndex.x][shotIndex.y] !== 0) + if(user.gameMap[shotIndex.x][shotIndex.y] === 2)//user.gameMap[shotIndex.x][shotIndex.y] !== 0) + { + user.destroyedShips += 1; + if(user.destroyedShips === user.howManyShips) + ifWonEndGame(user, bot); + else if(IM_SPEED || REMOVE_WAITING) + setTimeout(() => botShot(bot, user, ai), 200); + else + setTimeout(() => botShot(bot, user, ai), BOT_DELAY); + } +} + +function checkShotShips(bot, ai, superPositions) +{ + let longestShip = Math.max(...ai.shipsArray); + if(ai.shotShips.length > longestShip) + console.log("%cImpossible stanelo sie", "color: red;"); + else if(ai.shotShips.length < longestShip) + { + let xFirst = ai.shotShips.first().x, + xLast = ai.shotShips.last().x, + yFirst = ai.shotShips.first().y, + yLast = ai.shotShips.last().y; + if(ai.shotShips.length === 1) + { + for(let i = -1; i < 3; i += 2) + { + if(!checkPositionForShotShip(xFirst + i, yFirst, ai.gameMap, superPositions) + || !checkPositionForShotShip(xFirst, yFirst + i, ai.gameMap, superPositions)) + return; + } + // if(!checkPositionForShotShip(x - 1, y, ai.gameMap, superPositions) + // || !checkPositionForShotShip(x + 1, y, ai.gameMap, superPositions) + // || !checkPositionForShotShip(x, y - 1, ai.gameMap, superPositions) + // || !checkPositionForShotShip(x, y + 1, ai.gameMap, superPositions)) + // return; + } + else if(ai.shotShips.length < longestShip) + { + // console.log("%cship.length > 1", "color: lightgreen;"); + if(ai.shotShipDirection === "vertically") + { + ai.shotShips.sort((a, b) => a.x - b.x); + if(!checkPositionForShotShip(xFirst - 1, yFirst, ai.gameMap, superPositions) + || !checkPositionForShotShip(xLast + 1, yLast, ai.gameMap, superPositions)) + return; + } + else if(ai.shotShipDirection === "horizontally") + { + ai.shotShips.sort((a, b) => a.y - b.y); + if(!checkPositionForShotShip(xFirst, yFirst - 1, ai.gameMap, superPositions) + || !checkPositionForShotShip(xLast, yLast + 1, ai.gameMap, superPositions)) + return; + } + } + } + + // console.log("%cimma destroy shotShips", "color: lightblue;"); + ai.shotShips.forEach(shotShip => { + ai.gameMap[shotShip.x][shotShip.y] = -1; + }); + + removeElementFromArray(ai.shotShips.length, ai.shipsArray); + ai.shipWasShot = []; + ai.shotShips = []; + ai.shotShipDirection = ""; +} + +function checkPositionForShotShip(x, y, gameMap, superPositions) +{ + if(superPositions[x][y] === 0 || gameMap[x][y] === -2) + return true; +} + +function calculateSuperpositionsArray(bot, ai) +{ + let gameMap = createGameMap(); + + for(let ship of ai.shipsArray) + { + for(let i = 1; i <= MAP_SIZE; i++) + { + for(let j = 1; j <= MAP_SIZE; j++) + { + if(checkPositionForSuperShip(i, j, "horizontally", ship, ai)) + for(let k = j; k < j + ship; k++) + if(gameMap[i][k] !== -3) + gameMap[i][k] += 1; + if(ship !== 1 && checkPositionForSuperShip(i, j, "vertically", ship, ai)) + for(let k = i; k < i + ship; k++) + if(gameMap[k][j] !== -3) + gameMap[k][j] += 1; + } + } + } + // console.table(gameMap) + return gameMap; +} + +function checkPositionForSuperShip(x, y, direction, ship, player) +{ + if(direction === "horizontally") + { + if(y + ship <= MAP_SIZE + 1 && checkPositionForShip(x, y, "horizontally", ship, player)) + { + for(let k = y; k < y + ship; k++) + if(player.gameMap[x][k] === -2) + return false; + } + else + return false; + } + else if(direction === "vertically") + { + if(x + ship <= MAP_SIZE + 1 && checkPositionForShip(x, y, "vertically", ship, player)) + { + for(let k = x; k < x + ship; k++) + if(player.gameMap[k][y] === -2) + return false; + } + else + return false; + } + + return true; +} diff --git a/battleshipFiles/battleshipFunctions.js b/battleshipFiles/battleshipFunctions.js new file mode 100644 index 0000000..ce4efff --- /dev/null +++ b/battleshipFiles/battleshipFunctions.js @@ -0,0 +1,211 @@ +const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min) + min); +Array.prototype.last = function() {return this[this.length - 1];}; +Array.prototype.first = function() {return this[0];}; +const removeElementFromArray = (value, array) => { + let index = array.indexOf(value); + if(index > -1) + array.splice(index, 1); +}; + +function startNewGame() +{ + let user = new Battleship("user"); + let bot = new Battleship("bot"); + let ai = new Battleship("ai"); + delete ai.howManyShips; + delete ai.name; + delete ai.shipsMap; + ai.numberOfShots = 1; + // ai.gameMap = createGameMap(); + ai.shotShips = []; + ai.shipWasShot = []; + ai.shotShipDirection = ""; + //bot.ai = {}; + window.vars = {}; + + + createUI(user, bot, ai); + + populateMapRandomly(bot); + if(IM_SPEED) + populateMapRandomly(user); + generateGameMap(bot); + + generateGameMap(user); + if(!IM_SPEED) + generateShipsUl(user); + if(IM_SPEED) + document.getElementById("button").click(); + if(!IM_SPEED) + positionShips(user); + //calculateSuperpositionsArray(bot, ai); + + //botShot(bot, user); + let doTest = false; + if(doTest) + { + setTimeout(() => { + document.body.innerHTML = ""; + window.vars = undefined; + startNewGame(); + }, 200); + } +} + +function positionShips(player) +{ + let table = document.getElementById("userTable"); + + window.vars.shipDirection = "horizontally"; + + window.vars.player = player; + table.addEventListener('contextmenu', onRightClick); + + // table.addEventListener("mouseleave", clearUserShipPosition); + table.onmouseleave = clearUserShipPosition; + + [...table.children].forEach((tr, trNumber) => { + [...tr.children].forEach((td, tdNumber) => { + td.onmouseenter = () => showUserShipPosition(tr, trNumber, td, tdNumber, player); + td.onclick = () => putShipOnGameMap(player, trNumber + 1, tdNumber + 1, undefined, undefined, true); + }); + }); +} + +function onRightClick() +{ + window.vars.shipDirection = window.vars.shipDirection === "horizontally" ? "vertically" : "horizontally"; + showUserShipPosition(window.vars.givenTr, window.vars.givenTrNumber, window.vars.givenTd, window.vars.givenTdNumber, window.vars.player); +} + +function putShipOnGameMap(player, x, y, direction, ship, update) +{ + if(player.gameMap[x][y] === 1) + { + clearUserShipPosition(); + + let gettedShip = getShip(x, y, player); + player.shipsArray.push(gettedShip.array.length); + player.shipsArray.sort((a, b) => b - a); + + for(let coordinates of gettedShip.array) + player.gameMap[coordinates.x][coordinates.y] = 0; + + for(let td of gettedShip.table) + td.className = TABLE_BG_COLOR; + + generateShipsUl(player); + window.vars.selectedShip = document.getElementById("ul").children[0].children[0].children[0]; + + for(let i = 0; i < MAP_SIZE; i++) + window.vars.selectedTd[i] = undefined; + + for(let td of window.vars.selectedShip.children) + td.className = SHIP_BG_COLOR; + + window.vars.isPlaceable = false; + return; + } + // Checking if there are all arguments and sense for this function + if(player.shipsArray.length === 0 || /*window.vars.wasShipPlaced ||*/ (update && !window.vars.isPlaceable)) + return false; + if(direction === undefined) + direction = window.vars.shipDirection; + if(ship === undefined) + ship = window.vars.selectedShip.children.length; + + // Preventing ability to place all ships on one td + // TODO: usunac 2 linie + // if(window.vars.wasShipPlaced !== undefined) + // window.vars.wasShipPlaced = true; + + // Correct x and y, because mouse position !== ship start + [x, y] = correctXY(x, y, ship, direction); + + // Updating gameMap + if(direction === "vertically") + for(let i = 0; i < ship; i++) + player.gameMap[x + i][y] = 1; + else if(direction === "horizontally") + for(let i = 0; i < ship; i++) + player.gameMap[x][y + i] = 1; + + // Preventing newly colored td's on table form being turn back to TABLE_BG_COLOR + if(window.vars.selectedTd !== undefined) + for(let i = 0; i < MAP_SIZE; i++) + window.vars.selectedTd[i] = undefined; + + if(update) + { + // Update GUI efficiently + let table = document.getElementById(player.name + "Table"); + if(direction === "vertically") + for(let i = 0; i < ship; i++) + table.children[x + i - 1].children[y - 1].className = SHIP_BG_COLOR; + else if(direction === "horizontally") + for(let i = 0; i < ship; i++) + table.children[x - 1].children[y + i - 1].className = SHIP_BG_COLOR; + + // Removing ship from shipsArray + removeElementFromArray(ship, player.shipsArray); + + // Removing ship from ul of ships, if there's no other ships exiting function and removing onmouseenter + window.vars.selectedShip.parentNode.parentNode.remove(); + if(player.shipsArray.length === 0) + { + // Remove all vars and listeners + [...document.getElementById("userTable").children].forEach(tr => { + [...tr.children].forEach(td => { + td.onmouseenter = undefined;// () => {return;}; + }); + }); + document.getElementById("userTable").onmouseleave = undefined; + document.getElementById("userTable").removeEventListener('contextmenu', onRightClick); + window.vars = {}; + + document.getElementById("whosTurn").style.display = "none"; + document.getElementById("button").style.display = "inline-block"; + return; + } + + window.vars.selectedShip = document.getElementById("ul").children[0].children[0].children[0]; + + // Setting color of newly selected ship + for(let td of window.vars.selectedShip.children) + td.className = SHIP_BG_COLOR; + } +} + +function correctXY(x, y, ship, direction) +{ + if(direction === "vertically" && x + ship > MAP_SIZE + 1) + x -= (x - 1 + ship) - MAP_SIZE; + else if(direction === "horizontally" && y + ship > MAP_SIZE + 1) + y -= (y - 1 + ship) - MAP_SIZE; + + return [x, y]; +} + +function getAllIndexes2D(array, value) +{ + let indexes = []; + array.forEach((row, i) => { + row.forEach((val, j) => { + if(val === value) + indexes.push([i, j]); + }); + }); + + return indexes; +} + +function updateGameMap(player) +{ + let table = document.getElementById(player.name + "Table"); + [...table.children].forEach((tr, i) => { + [...tr.children].forEach((td, j) => { + if([1, 2].includes(player.gameMap[i + 1][j + 1])) + td.className = SHIP_BG_COLOR; + }); + }); +} \ No newline at end of file diff --git a/battleshipFiles/battleshipGameFunctions.js b/battleshipFiles/battleshipGameFunctions.js new file mode 100644 index 0000000..0a29bff --- /dev/null +++ b/battleshipFiles/battleshipGameFunctions.js @@ -0,0 +1,362 @@ +function startGame(player, bot, ai) +{ + window.vars.whosTurn = "user"; + + document.getElementById("button").style.display = "none"; + document.getElementById("whosTurn").style.display = "inline"; + document.getElementById("whosTurn").innerText = "Twoja tura"; + + let userTable = document.getElementById("userTable"); + let botTable = document.getElementById("botTable"); + userTable.onclick = () => window.alert("To Twoja tabela -_-