<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Kevin's Battleship</title>
<style>
#battleship{
position:relative;
background-color:lightgrey;
font-size:1em;
width:650px;
height:420px;
border:1px solid black;
text-align:left;
user-select:none;
font-family:sans-serif;
overflow:hidden;
}
#bs_header{
position:absolute;
width:100%;
height:30px;
left:0;
top:0;
overflow:hidden;
border:1px solid black;
background-color: grey;
}
#bs_title{
position:absolute;
left:0;
top:0;
width:250px;
height:100%;
font-weight:bold;
font-style:italic;
font-size:1.5em;
padding-left:5px;
color:darkgrey;
text-shadow:0 0 2px black;
}
#bs_newGameButton{
position:absolute;
right:40px;
top:5%;
width:100px;
height:70%;
font-size:1em;
text-align:center;
background-color:lightgrey;
border:2px outset grey;
border-radius:8px;
cursor:pointer;
}
#bs_help{
position:absolute;
right:0;
top:0;
width:30px;
height:100%;
font-weight:bold;
font-size:1.5em;
text-align:center;
background-color:dodgerblue;
color:white;
border-radius:999px;
cursor:pointer;
}
#bs_message{
position:absolute;
left:150px;
top:40px;
width:calc(100% - 300px - 10px - 20px);
height:40px;
padding:0 10px;
font-size:1em;
font-weight:bold;
text-align:center;
background-color:rgb(22, 48, 22);
color:rgb(48, 202, 48);
border:5px inset grey;
border-radius:999px;
animation: bs_messageFlash 0.5s linear 0s 1 normal;
}@keyframes bs_messageFlash{
from{background-color:rgb(123, 255, 123);}
to{background-color:rgb(22, 48, 22);}
}
#bs_fastDiv{
position:absolute;
right:10px;
top:50px;
}
#bs_leftBoard,#bs_rightBoard{
position:absolute;
width:calc(30px * 10);
height:calc(30px * 10);
top:100px;
background-color: midnightblue;
border:5px inset grey;
cursor:pointer;
}#bs_leftBoard{
left:10px;
}#bs_rightBoard{
right:10px;
}
.bs_board_block{
cursor:not-allowed !important;
}.bs_board_border{
animation: bs_board_border 0.5s ease-out 0s infinite alternate;
background-color: rgb(36, 36, 146) !important;
}@keyframes bs_board_border{
from{outline:0.1px solid black;}
to{outline:8px solid black;}
}
.bs_bgCell{
position:absolute;
width:28px;
height:28px;
border:1px solid black;
/*animation: bs_waves 3s ease-in 0s infinite alternate;*/
}@keyframes bs_waves{
from{background-color:rgba(255,255,255,0);}
to{background-color:rgba(255,255,255,0.1);}
}
.bs_leftCell,.bs_rightCell{
position:absolute;
width:30px;
height:30px;
}
.bs_has_white{
background-image:radial-gradient(white 30%,black 35%,rgba(0,0,0,0) 36%);
}.bs_has_red{
background-image:radial-gradient(red 30%,black 35%,rgba(0,0,0,0) 36%);
}.bs_has_land{
background-image:radial-gradient(forestgreen 30%,darkgreen 79%,rgba(0,0,0,0) 80%);
}
#bs_leftExplosionHolder,#bs_rightExplosionHolder{
position:absolute;
transform:scale(60%,60%);
pointer-events:none;
}
.bs_explosion{
position:absolute;
width:10px;
height:10px;
border-radius:999px;
animation: bs_explosion 0.5s ease-out 0s infinite alternate;
border:2px solid red;
border-width:0 0 2px 0;
}@keyframes bs_explosion{
0%{background-color:yellow;}
100%{background-color:orangered;}
}
.bs_waterExplosion{
position:absolute;
width:10px;
height:10px;
border-radius:999px;
animation: bs_waterExplosion 0.5s ease-out 0s infinite alternate;
border:2px solid blue;
border-width:0 0 2px 0;
}@keyframes bs_waterExplosion{
0%{background-color:white;}
100%{background-color:dodgerblue;}
}
#bs_placingMenu{
position:absolute;
background-color:darkslategrey;
left:30px;
top:120px;
width:270px;
height:270px;
border-radius:20px;
outline:5px solid dimgrey;
box-shadow: 0px 0px 10px 0px black,
0px 0px 10px 0px black;/* X, Y, outer extent, inner extent, color */
}
.bs_placingMenu_ship{
position:absolute;
left:60px;
cursor:pointer;
}.bs_placingMenu_ship:hover{
outline:5px double dodgerblue;
}.bs_placingMenu_placedShip{
opacity:20%;
}
#bs_placingMenu_ship1{top:calc(55px + 0px + 0px);}
#bs_placingMenu_ship2{top:calc(55px + 30px + 5px);}
#bs_placingMenu_ship3{top:calc(55px + 60px + 10px);}
#bs_placingMenu_ship4{top:calc(55px + 90px + 15px);}
#bs_placingMenu_ship5{top:calc(55px + 120px + 20px);}
#bs_placingMenu_rotate{
position:absolute;
bottom:10px;
right:10px;
overflow:hidden;
text-align:center;
cursor:pointer;
}#bs_placingMenu_rotateSymbol{
font-size:xx-large;
}
.bs_you_ship,.bs_cpu_ship{
pointer-events:none;
}
.bs_placedShip{}
.bs_unplacedShip{
opacity:0%;
}.bs_heldShip{
animation: bs_blinking 0.4s ease-out 0s infinite alternate;
}@keyframes bs_blinking{
0%{opacity:20%;}
100%{opacity:100%;}
}.bs_shipRotated{
transform-origin:15px 15px;
transform:rotate(-90deg);
}
.bs_ship1,.bs_ship2,.bs_ship3,.bs_ship4,.bs_ship5{
position:absolute;
width:150px;
height:30px;
overflow:hidden;
background-size:cover;
background-repeat:no-repeat;
}.bs_ship1{background-image:url("battleships/ship_carrier.png");}
.bs_ship2{background-image:url("battleships/ship_battleship.png");}
.bs_ship3{background-image:url("battleships/ship_destroyer.png");}
.bs_ship4{background-image:url("battleships/ship_submarine.png");}
.bs_ship5{background-image:url("battleships/ship_patrolboat.png");}
#bs_modeMenu{
position:absolute;
background-color:lightgrey;
left:30px;
top:120px;
width:590px;
height:270px;
border-radius:20px;
outline:1px solid black;
box-shadow: 0px 0px 10px 0px black,
0px 0px 10px 0px black;/* X, Y, outer extent, inner extent, color */
}
.bs_modeMenu_option{
position:absolute;
background-color:grey;
color:black;
border-radius:10px;
border:1px solid black;
width:250px;
height:25px;
left:150px;
cursor:pointer;
font-size:large;
padding:10px;
}#bs_modeMenu_option1{
top:30px;
}#bs_modeMenu_option2{
top:80px;
}#bs_modeMenu_option3{
top:130px;
}#bs_modeMenu_option4{
top:180px;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
"use strict";
$(document).ready(function(){
Startup();
StartNewGame(1);
});
function Startup(){
let hhh = "<div id='bs_header'>";
hhh += "<div id='bs_title'>Kevin's Battleship</div>";
hhh += "<div id='bs_newGameButton'>New Game</div>";
hhh += "<div id='bs_help'>?</div>";
hhh += "</div>";
hhh += "<div id='bs_message'>Place your ships</div>";
hhh += "<div id='bs_fastDiv'><span>Faster</span><input type='checkbox' id='bs_fastCheckbox' checked='false'/></div>"
hhh += "<div id='bs_leftBoard'>";
let style;
for(let yyy=0;yyy<10;yyy++){/* left background cells */
for(let xxx=0;xxx<10;xxx++){
style = "";
style += "left:"+(30*xxx)+"px; top:"+(30*yyy)+"px;";
style += "animation-delay:"+(-100+(xxx*0.33+yyy)*1.5)+"s;";
hhh += "<div class='bs_bgCell' style='"+style+"'' id='bs_bgCell"+xxx+"_"+yyy+"'></div>";
}
}hhh += "<div id='bs_cpu_ship1' class='bs_cpu_ship bs_ship1'></div>";
hhh += "<div id='bs_cpu_ship2' class='bs_cpu_ship bs_ship2'></div>";
hhh += "<div id='bs_cpu_ship3' class='bs_cpu_ship bs_ship3'></div>";
hhh += "<div id='bs_cpu_ship4' class='bs_cpu_ship bs_ship4'></div>";
hhh += "<div id='bs_cpu_ship5' class='bs_cpu_ship bs_ship5'></div>";
for(let yyy=0;yyy<10;yyy++){/* left foreground cells */
for(let xxx=0;xxx<10;xxx++){
style = "";
style += "left:"+(30*xxx)+"px; top:"+(30*yyy)+"px;";
hhh += "<div class='bs_leftCell' style='"+style+"'' id='bs_leftCell"+xxx+"_"+yyy+"'></div>";
}
}
hhh += "<div id='bs_leftExplosionHolder'></div>";
hhh += "</div>";
hhh += "<div id='bs_rightBoard'>";
for(let yyy=0;yyy<10;yyy++){/* right background cells */
for(let xxx=0;xxx<10;xxx++){
style = "";
style += "left:"+(30*xxx)+"px; top:"+(30*yyy)+"px;";
style += "animation-delay:"+(-100+(xxx*0.33+yyy)*1.5)+"s;";
hhh += "<div class='bs_bgCell' style='"+style+"'' id='bs_bgCell"+xxx+"_"+yyy+"'></div>";
}
}hhh += "<div id='bs_you_ship1' class='bs_you_ship bs_ship1 bs_unplacedShip'></div>";
hhh += "<div id='bs_you_ship2' class='bs_you_ship bs_ship2 bs_unplacedShip'></div>";
hhh += "<div id='bs_you_ship3' class='bs_you_ship bs_ship3 bs_unplacedShip'></div>";
hhh += "<div id='bs_you_ship4' class='bs_you_ship bs_ship4 bs_unplacedShip'></div>";
hhh += "<div id='bs_you_ship5' class='bs_you_ship bs_ship5 bs_unplacedShip'></div>";
for(let yyy=0;yyy<10;yyy++){/* right foreground cells */
for(let xxx=0;xxx<10;xxx++){
style = "";
style += "left:"+(30*xxx)+"px; top:"+(30*yyy)+"px;";
hhh += "<div class='bs_rightCell' style='"+style+"'' id='bs_rightCell"+xxx+"_"+yyy+"'></div>";
}
}
hhh += "<div id='bs_rightExplosionHolder'></div>";
hhh += "</div>";
hhh += "<div id='bs_placingMenu'>";
hhh += "<div id='bs_placingMenu_ship1' class='bs_placingMenu_ship bs_ship1'></div>";
hhh += "<div id='bs_placingMenu_ship2' class='bs_placingMenu_ship bs_ship2'></div>";
hhh += "<div id='bs_placingMenu_ship3' class='bs_placingMenu_ship bs_ship3'></div>";
hhh += "<div id='bs_placingMenu_ship4' class='bs_placingMenu_ship bs_ship4'></div>";
hhh += "<div id='bs_placingMenu_ship5' class='bs_placingMenu_ship bs_ship5'></div>";
hhh += "<div id='bs_placingMenu_rotate'>Rotate<br><span id='bs_placingMenu_rotateSymbol'>🔄</span></div>";
hhh += "</div>";
hhh += "<div id='bs_modeMenu'>";
hhh += "<div id='bs_modeMenu_option1' class='bs_modeMenu_option'>Normal</div>";
hhh += "<div id='bs_modeMenu_option2' class='bs_modeMenu_option'>☠️Salvo (up to 5 shots)</div>";
hhh += "<div id='bs_modeMenu_option3' class='bs_modeMenu_option'>🏝️Land</div>";
hhh += "<div id='bs_modeMenu_option4' class='bs_modeMenu_option'>🏝️Land and ☠️Salvo</div>";
hhh += "</div>";
$("#battleship").html(hhh);
$("#battleship").attr("oncontextmenu","return false;");/* so right-click does not open a menu */
$("#bs_newGameButton").click(NewGameButton);
$("#bs_help").click(Help);
$(".bs_placingMenu_ship").attr("onmousedown","ClickPlaceMenuShip(this)");
$("#bs_placingMenu_rotate").click(RotateShip);
$(".bs_rightCell").hover(HoverRightCell);
$(".bs_rightCell").attr("onmousedown","ClickRightCell(event,this)");
$(".bs_leftCell").attr("onmousedown","ClickLeftCell(this)");
$("#bs_modeMenu").hide();
$("#bs_modeMenu_option1").attr("onmousedown","StartNewGame(1)");
$("#bs_modeMenu_option2").attr("onmousedown","StartNewGame(2)");
$("#bs_modeMenu_option3").attr("onmousedown","StartNewGame(3)");
$("#bs_modeMenu_option4").attr("onmousedown","StartNewGame(4)");
document.getElementById("bs_fastCheckbox").checked = false;
MarkDiagonalCells();
}
function Help(){
let message="===========================\n"+
"How to play\n\n"+
"Try to find and destroy the enemy battleships before yours are destroyed.\n\n"+
"Right-click to rotate your ships during placement.\n\n"+
"==========================="
;
alert(message);
}
function MarkDiagonalCells(){
let pos={x:0,y:0};
GetRightCell(pos.x,pos.y).addClass("bs_isDiagonal");
while(true){
pos.x+=2;
if(pos.x>9){pos.x-=9+2;pos.y++;}
if(pos.y>9)break;
GetRightCell(pos.x,pos.y).addClass("bs_isDiagonal");
}
}
const shipNames = [
"ERROR",
"Carrier",
"Battleship",
"Destroyer",
"Submarine",
"Patrol Boat"
];
const shipLengths = [99,5,4,3,3,2];
const xLetters = ["A","B","C","D","E","F","G","H","I","J"];
let gameMode = 1;
let gameStep = 1;
let shipSelected = 1;
let youSalvo = 5;
let cpuSalvo = 5;
let ammo = 1;
function NewGameButton(){
clearTimeout(timer_turnDelay);
$("#bs_modeMenu").show();
$("#bs_placingMenu").hide();
BoardBlocks(true,true);
BoardBorders(false,false);
HideExplosions();
ShowShips(false,false);
Message("Choose a game");
gameStep=0;
}
function StartNewGame(mode){
$("#bs_modeMenu").hide();
/* reset everything */
$(".bs_rightCell").add(".bs_leftCell")
.removeClass("bs_has_ship")
.removeClass("bs_has_ship1")
.removeClass("bs_has_ship2")
.removeClass("bs_has_ship3")
.removeClass("bs_has_ship4")
.removeClass("bs_has_ship5")
.removeClass("bs_has_peg")
.removeClass("bs_has_red")
.removeClass("bs_has_white")
.removeClass("bs_has_land");
$(".bs_placingMenu_ship").removeClass("bs_placingMenu_placedShip");
$(".bs_you_ship").removeClass("bs_placedShip").removeClass("bs_heldShip").addClass("bs_unplacedShip");
HoldShip(1);
$("#bs_placingMenu").show();
BoardBlocks(true,false);
BoardBorders(false,true);
ShowShips(false,true);
gameStep=1;
gameMode=mode;
youSalvo=5;
cpuSalvo=5;
/* 1 normal */
/* 2 salvo */
/* 3 islands normal */
/* 4 islands salvo */
if(gameMode==3 || gameMode==4){/* islands */
MakeLand();
}
}
function MakeLand(){
for(let i=0;i<30;i++){
while(true){
let pos;
let cell;
if(Math.random()<0.2){/* random */
pos = {
x: Math.floor(Math.random()*10),
y: Math.floor(Math.random()*10)
};
cell = GetLeftCell(pos.x,pos.y);
if(cell.hasClass("bs_has_land"))continue;
}else{/* append to current land */
let lands = $("#bs_leftBoard").children(".bs_has_land");
if(lands.length==0)continue;
let randomLand = lands.eq(Math.floor(Math.random()*lands.length));
let neighs = GetLeftNeighbors_Perpendicular(randomLand);
neighs = neighs.not(".bs_has_land");
if(neighs.length==0)continue;
cell = neighs.eq(Math.floor(Math.random()*neighs.length));
}
/* apply one land */
cell.addClass("bs_has_land");
pos = GetCoords(cell);GetRightCell(pos.x,pos.y).addClass("bs_has_land");
break;
}
}
}
function GetLeftNeighbors_Perpendicular(cell){
let neighs = $();
let c = GetCoords(cell);
if(c.x-1 < 0){}else neighs = neighs.add(GetLeftCell(c.x-1,c.y));
if(c.x+1 > 9){}else neighs = neighs.add(GetLeftCell(c.x+1,c.y));
if(c.y-1 < 0){}else neighs = neighs.add(GetLeftCell(c.x,c.y-1));
if(c.y+1 > 9){}else neighs = neighs.add(GetLeftCell(c.x,c.y+1));
return neighs;
}
function HoverRightCell(){
if(gameStep == 1){/* placing your ships */
let xs = $(this).css("left");
let ys = $(this).css("top");
$(".bs_heldShip").css("left",xs);
$(".bs_heldShip").css("top",ys);
}
}
function ClickPlaceMenuShip(element){
/* initiate switch ship */
let id = $(element).attr("id");
let shipDigit = id[id.length-1];
/* unselect current held ship */
$(".bs_heldShip").removeClass("bs_heldShip");
HoldShip(shipDigit);
}
function HoldShip(id){
shipSelected = id;
$("#bs_you_ship"+shipSelected)
.removeClass("bs_placedShip")
.addClass("bs_unplacedShip")
.addClass("bs_heldShip");
/* un-dim one on menu */
$("#bs_placingMenu_ship"+shipSelected).removeClass("bs_placingMenu_placedShip");
/* remove from board */
$(".bs_has_ship"+shipSelected)
.removeClass("bs_has_ship")
.removeClass("bs_has_ship"+shipSelected);
/* message */
Message("Place your "+shipNames[id]);
}
const messageAnim="bs_messageFlash 0.5s linear 0s 1 normal";
function Message(m){
$("#bs_message").html(m);
/* reset animation */
let e=document.getElementById("bs_message");
e.style.animation="none";
e.offsetHeight;/* trigger a "reflow" */
e.style.animation=messageAnim;
}
function BoardBlocks(left,right){
left ? $("#bs_leftBoard").addClass("bs_board_block") : $("#bs_leftBoard").removeClass("bs_board_block");
right ? $("#bs_rightBoard").addClass("bs_board_block") : $("#bs_rightBoard").removeClass("bs_board_block");
}
function BoardBorders(left,right){
left ? $("#bs_leftBoard").addClass("bs_board_border") : $("#bs_leftBoard").removeClass("bs_board_border");
right ? $("#bs_rightBoard").addClass("bs_board_border") : $("#bs_rightBoard").removeClass("bs_board_border");
}
function HideExplosions(){
$("#bs_rightExplosionHolder").hide();
$("#bs_leftExplosionHolder").hide();
}
function ShowShips(left,right){
left ? $(".bs_cpu_ship").show() : $(".bs_cpu_ship").hide();
right ? $(".bs_you_ship").css("opacity","") : $(".bs_you_ship").css("opacity",0.5);
}
function PlaceShip(){
$(".bs_heldShip")
.removeClass("bs_heldShip")
.removeClass("bs_unplacedShip")
.addClass("bs_placedShip");
/* dim one on menu */
$("#bs_placingMenu_ship"+shipSelected).addClass("bs_placingMenu_placedShip");
/* cycle to next ship */
if($("#bs_you_ship1").hasClass("bs_placedShip")==false)HoldShip(1);
else if($("#bs_you_ship2").hasClass("bs_placedShip")==false)HoldShip(2);
else if($("#bs_you_ship3").hasClass("bs_placedShip")==false)HoldShip(3);
else if($("#bs_you_ship4").hasClass("bs_placedShip")==false)HoldShip(4);
else if($("#bs_you_ship5").hasClass("bs_placedShip")==false)HoldShip(5);
else{
/* finished placing ships */
$("#bs_placingMenu").hide();
CpuPlaceShips();
YourTurn();
}
}
function ClickRightCell(event,cell){
if(gameStep == 1){ /* placing ships */
if(event.button == 2){
RotateShip();
}else if(event.button == 0){
TryPlaceShip(cell);
}
}
}
function RotateShip(){
$(".bs_heldShip").toggleClass("bs_shipRotated");
}
function TryPlaceShip(cell){
let pos = GetCoords(cell);
let shipLength = shipLengths[parseInt(shipSelected)];
let xmax = 1;
let ymax = 1;
if($(".bs_heldShip").hasClass("bs_shipRotated")){
ymax=shipLength;
}else{
xmax=shipLength;
}
/* check if able to place */
for(let x=0;x<xmax;x++){
for(let y=0;y<ymax;y++){
if(pos.x+x > 9)return false;
if(pos.y-y < 0)return false;
let otherCell = GetRightCell(pos.x+x,pos.y-y);
if(otherCell.hasClass("bs_has_ship"))return false;
if(otherCell.hasClass("bs_has_land"))return false;
}
}
/* able to place, go ahead */
for(let x=0;x<xmax;x++){
for(let y=0;y<ymax;y++){
GetRightCell(pos.x+x,pos.y-y)
.addClass("bs_has_ship")
.addClass("bs_has_ship"+shipSelected);
}
}
PlaceShip();
return true;
}
function GetRightCell(x,y){
return $("#bs_rightCell"+x+"_"+y);
}
function GetLeftCell(x,y){
return $("#bs_leftCell"+x+"_"+y);
}
function GetCoords(element){
let id = $(element).attr("id");
let coordString = id.split("Cell")[1];
let bits = coordString.split("_");
return {x:parseInt(bits[0]), y:parseInt(bits[1])};
}
let timer_turnDelay;
function ClickLeftCell(cell){
if(gameStep==2){/* shooting enemy */
if($(cell).hasClass("bs_has_peg")==false && $(cell).hasClass("bs_has_land")==false){
if($(cell).hasClass("bs_has_ship")==true){
$(cell).addClass("bs_has_peg").addClass("bs_has_red");
Message(GetHitMessage("You",cell));
Explosion(cell);
if(IsAllShipsDestroyed(true)==true){
/* you win */
gameStep=4;
timer_turnDelay=setTimeout(YouWin,Speed(3000,2000));
return;
}
}else{
$(cell).addClass("bs_has_peg").addClass("bs_has_white");
Message(GetMissMessage("You",cell));
WaterExplosion(cell);
/*$("#bs_leftExplosionHolder").hide(); */
}
ammo--;
if(gameMode==2 || gameMode==4){
Message($("#bs_message").text()+".<br>"+ammo+" shots left.");
}
if(ammo<=0){
gameStep=3;
timer_turnDelay=setTimeout(CpuTurn,Speed(3000,2000));
BoardBlocks(true,true);
BoardBorders(false,false);
}
}
}
}
function YouWin(){
gameStep=4;
let yourShots = $("#bs_leftBoard").children(".bs_has_peg").length;
let cpuShots = $("#bs_rightBoard").children(".bs_has_peg").length;
Message("You win!<br>Shots fired (You "+yourShots+") (CPU "+cpuShots+")");
BoardBlocks(true,true);
BoardBorders(false,false);
ShowShips(true,true);
HideExplosions();
}
function YouLose(){
gameStep=4;
let yourShots = $("#bs_leftBoard").children(".bs_has_peg").length;
let cpuShots = $("#bs_rightBoard").children(".bs_has_peg").length;
Message("You lose!<br>Shots fired (You "+yourShots+") (CPU "+cpuShots+")");
BoardBlocks(true,true);
BoardBorders(false,false);
ShowShips(true,true);
HideExplosions();
}
function GetHitMessage(playerName,cell){
let shipId = 0;
if($(cell).hasClass("bs_has_ship1")==true)shipId=1;
else if($(cell).hasClass("bs_has_ship2")==true)shipId=2;
else if($(cell).hasClass("bs_has_ship3")==true)shipId=3;
else if($(cell).hasClass("bs_has_ship4")==true)shipId=4;
else if($(cell).hasClass("bs_has_ship5")==true)shipId=5;
let hitOrSunk = IsShipAlive(playerName=="You",shipId) ? "HIT" : "SUNK";
if(hitOrSunk == "SUNK"){
playerName=="You" ? cpuSalvo-- : youSalvo--;/* reduce salvo */
}
let shipName = shipNames[shipId];
let pos = GetCoords(cell);
let xName = xLetters[pos.x];
let yName = pos.y+1;
return playerName+" "+hitOrSunk+" "+shipName+" at "+xName+yName;
}
function GetMissMessage(playerName,cell){
let pos = GetCoords(cell);
let xName = xLetters[pos.x];
let yName = pos.y+1;
return playerName+" Miss at "+xName+yName;
}
function IsShipAlive(checkLeft,shipId){
let board = checkLeft ? $("#bs_leftBoard") : $("#bs_rightBoard");
let remains = board.children(".bs_has_ship"+shipId).not(".bs_has_peg").length;
return remains>0;
}
function IsAllShipsDestroyed(checkLeft){
let board = checkLeft ? $("#bs_leftBoard") : $("#bs_rightBoard");
let remains = board.children(".bs_has_ship").not(".bs_has_peg").length;
return remains==0;
}
function YourTurn(){
gameStep=2;
HideExplosions();
BoardBlocks(false,true);
BoardBorders(true,false);
ShowShips(false,true);
if(gameMode==2 || gameMode==4){/*salvo */
ammo=youSalvo;
Message("Shoot the enemy.<br>"+ammo+" shots left.");
}else{
ammo=1;
Message("Shoot the enemy.");
}
}
function WaterExplosion(cell){
Explosion2(cell,true);
}
function Explosion(cell){
Explosion2(cell,false);
}
function Explosion2(cell,isWater){
let html="";
let style="";
let size=300.0;
let dist=0.0;
let angle=Math.random()*360.0;
let rrr=0.0;
let xxx=0.0;
let yyy=0.0;
for(let i=0;i<30;i++){
angle+=72.0;
angle+=(Math.random()*15.0)-7.5;
rrr=angle*Math.PI/180.0;
xxx=Math.sin(rrr)*dist;
yyy=Math.cos(rrr)*dist;
/*xxx=shipX+xxx; */
/*yyy=shipY+yyy; */
xxx-=5;/* for the initial explosion size */
yyy-=5;
style="left:"+xxx+"px;bottom:"+yyy+"px;";
style+="transform:scale("+size+"%,"+size+"%);";
style+="animation-delay:"+((i/30)*0.5)+"s;";
if(isWater){
html+="<div class='bs_waterExplosion' style='"+style+"'></div>";
}else{
html+="<div class='bs_explosion' style='"+style+"'></div>";
}
size-=(300-40)/30;
dist+=( 70/30);
}
let holder;
if($(cell).hasClass("bs_leftCell")){
holder = $("#bs_leftExplosionHolder");
}else holder = $("#bs_rightExplosionHolder");
let pos = GetCoords(cell);
holder.css("left",(pos.x*30+15)+"px").css("top",(pos.y*30+15)+"px").show();
holder.html(html);
}
/* ################################################# */
function CpuPlaceShips(){
while(true){if(Cpu_TryPlaceShip(1)==true)break;}
while(true){if(Cpu_TryPlaceShip(2)==true)break;}
while(true){if(Cpu_TryPlaceShip(3)==true)break;}
while(true){if(Cpu_TryPlaceShip(4)==true)break;}
while(true){if(Cpu_TryPlaceShip(5)==true)break;}
}
function Cpu_TryPlaceShip(id){
let pos = {
x: Math.floor(Math.random()*10),
y: Math.floor(Math.random()*10)
};
$("#bs_cpu_ship"+id)
.css("left",(pos.x*30)+"px")
.css("top",(pos.y*30)+"px");
let shipLength = shipLengths[id];
let xmax = 1;
let ymax = 1;
if(Math.random()>0.5){/* decide rotate */
ymax=shipLength;
$("#bs_cpu_ship"+id).addClass("bs_shipRotated");
}else{
xmax=shipLength;
$("#bs_cpu_ship"+id).removeClass("bs_shipRotated");
}
/* check if able to place */
for(let x=0;x<xmax;x++){
for(let y=0;y<ymax;y++){
if(pos.x+x > 9)return false;
if(pos.y-y < 0)return false;
let otherCell = GetLeftCell(pos.x+x,pos.y-y);
if(otherCell.hasClass("bs_has_ship"))return false;
if(otherCell.hasClass("bs_has_land"))return false;
}
}
/* able to place, go ahead */
for(let x=0;x<xmax;x++){
for(let y=0;y<ymax;y++){
GetLeftCell(pos.x+x,pos.y-y)
.addClass("bs_has_ship")
.addClass("bs_has_ship"+id);
}
}return true;
}
function CpuTurn(){
gameStep=3;
$("#bs_leftExplosionHolder").hide();
BoardBlocks(true,true);
BoardBorders(false,true);
ShowShips(false,false);
Message("Please wait...");
if(gameMode==2 || gameMode==4){/*salvo */
ammo=cpuSalvo;
}else{
ammo=1;
}
timer_turnDelay=setTimeout(Cpu_ShootSmart,Speed(2000,100));
}
function Cpu_ShootRandom(){
let pos;
let cell;
while(true){
pos = {
x: Math.floor(Math.random()*10),
y: Math.floor(Math.random()*10)
};
cell = $("#bs_rightCell"+pos.x+"_"+pos.y);
if(cell.hasClass("bs_has_peg")==false && $(cell).hasClass("bs_has_land")==false)break;
}
Cpu_ActuallyShoot(cell);
}
function Cpu_ActuallyShoot(cell){
if($(cell).hasClass("bs_has_peg")){
console.log("ERROR cpu trying to shoot pegged cell");Message("ERROR");
return;
}
if($(cell).hasClass("bs_has_ship")==true){
$(cell).addClass("bs_has_peg").addClass("bs_has_red");
Message(GetHitMessage("CPU",cell));
Explosion(cell);
if(IsAllShipsDestroyed(false)==true){
/* you lose */
gameStep=4;
timer_turnDelay=setTimeout(YouLose,Speed(3000,2000));
return;
}
}else{
$(cell).addClass("bs_has_peg").addClass("bs_has_white");
Message(GetMissMessage("CPU",cell));
WaterExplosion(cell);
}
ammo--;
if(gameMode==2 || gameMode==4){
Message($("#bs_message").text()+".<br>"+ammo+" shots left.");
}
if(ammo<=0){
gameStep=0;
timer_turnDelay=setTimeout(YourTurn,Speed(2500,1000));
}else{
timer_turnDelay=setTimeout(Cpu_ShootSmart,Speed(2500,900));
}
}
function Speed(normal,fast){
return document.getElementById("bs_fastCheckbox").checked ? fast : normal;
}
/*---------------------------------------- */
function Cpu_ShootSmart(){
/* unfinished ships? followup on them */
for(let ship=1;ship<shipLengths.length;ship++){
let redPegs = $("#bs_rightBoard").children(".bs_has_ship"+ship).filter(".bs_has_red");
if(redPegs.length>0 && redPegs.length<shipLengths[ship]){
/*Cpu_ShootRandom(); */
Cpu_Followup(ship,redPegs);
return;
}
}
/* hunt remaining ships */
for(let ship=1;ship<shipLengths.length;ship++){
let redPegs = $("#bs_rightBoard").children(".bs_has_ship"+ship).filter(".bs_has_red");
if(redPegs.length==0){
/*Cpu_ShootRandom(); */
Cpu_Hunt(ship);
return;
}
}
console.log("ERROR cpu cannot shoot smart");Message("ERROR");
}
function Cpu_Followup(ship,redPegs){
let cell;
let cells;
if(redPegs.length==1){
cells = GetRightNeighbors_Perpendicular(redPegs);
cells = cells.not(".bs_has_land").not(".bs_has_peg");
if(cells.length==0){console.log("ERROR no neighs");Message("ERROR");return;}
cell = cells.eq(Math.floor(Math.random()*cells.length));
Cpu_ActuallyShoot(cell);
return;
/* dont guess direction where boat cannot fit? */
}else{
if(GetCoords(redPegs.eq(0)).x == GetCoords(redPegs.eq(1)).x){
/* is vertical */
/* shoot from top to bottom */
cell = TopMostCell(redPegs);
while(true){
cell = GetNeighbor(cell,0,1);
if(cell == null || cell.hasClass("bs_has_land") || cell.hasClass("bs_has_white")){
/* other direction */
cell = TopMostCell(redPegs);
cell = GetNeighbor(cell,0,-1);
Cpu_ActuallyShoot(cell);
return;
}else if(cell.hasClass("bs_has_red")){
continue;
}else{
Cpu_ActuallyShoot(cell);
return;
}
}
}else{
/* is horizontal */
/* shoot from left to right */
cell = LeftMostCell(redPegs);
while(true){
cell = GetNeighbor(cell,1,0);
if(cell == null || cell.hasClass("bs_has_land") || cell.hasClass("bs_has_white")){
/* other direction */
cell = LeftMostCell(redPegs);
cell = GetNeighbor(cell,-1,0);
Cpu_ActuallyShoot(cell);
return;
}else if(cell.hasClass("bs_has_red")){
continue;
}else{
Cpu_ActuallyShoot(cell);
return;
}
}
}
}
}
function Cpu_Hunt(ship){
/* look for all possible horizontal positions */
/* add +1 score to all cells involved */
/* then look for all possible vertical positions */
/* add +1 score to all cells involved */
/* higher score cells are better guesses */
let cellScores = NewCellScoresArray(10*10);
/* score each cell */
let x;
let y;
for(x=0;x<10;x++){
for(y=0;y<10;y++){
ScoreAreaIfShipFits_Horizontal(shipLengths[ship],x,y);
ScoreAreaIfShipFits_Vertical(shipLengths[ship],x,y);
}
}
/* find best scores */
let maxScore = GetMaxScore(cellScores);
let cell;
let cells = $();
for(x=0;x<cellScores.length;x++){
if(cellScores[x]==maxScore){
let pos = ScoreIndexToCoords(x);
cells = cells.add(GetRightCell(pos.x,pos.y));
}
}
/* prioritize diagonals */
let diagCells = cells.filter(".bs_isDiagonal");
if(diagCells.length>0){
cell = diagCells.eq(Math.floor(Math.random()*diagCells.length));
Cpu_ActuallyShoot(cell);
return;
}else{
cell = cells.eq(Math.floor(Math.random()*cells.length));
Cpu_ActuallyShoot(cell);
return;
}
console.log("ERROR cpu cannot hunt");Message("ERROR");
function NewCellScoresArray(size){
let a = new Array(size);
for(let i=0;i<a.length;i++){
a[i]=0;
}return a;
}
function GetMaxScore(a){
let max = -999;
for(let i=0;i<a.length;i++){
if(a[i]>max)max=a[i];
}return max;
}
function CoordsToScoreIndex(x,y){
return x + y*10;
}
function ScoreIndexToCoords(index){
let y = Math.floor(index/10);
let x = index - (y*10);
return {x:x,y:y};
}
function ScoreAreaIfShipFits_Horizontal(shipLength,ox,oy){
let x;
for(x=0;x<shipLength;x++){
if(ox + x > 9)return;
let cell = GetRightCell(ox + x, oy);
if(cell.hasClass("bs_has_land") || cell.hasClass("bs_has_peg")){
return;
}
}
for(x=0;x<shipLength;x++){
cellScores[CoordsToScoreIndex(ox+x,oy)]++;
}
}
function ScoreAreaIfShipFits_Vertical(shipLength,ox,oy){
let y;
for(y=0;y<shipLength;y++){
if(oy + y > 9)return;
let cell = GetRightCell(ox, oy + y);
if(cell.hasClass("bs_has_land") || cell.hasClass("bs_has_peg")){
return;
}
}
for(y=0;y<shipLength;y++){
cellScores[CoordsToScoreIndex(ox,oy+y)]++;
}
}
}
function GetRightNeighbors_Perpendicular(cell){
let neighs = $();
let c = GetCoords(cell);
if(c.x-1 < 0){}else neighs = neighs.add(GetRightCell(c.x-1,c.y));
if(c.x+1 > 9){}else neighs = neighs.add(GetRightCell(c.x+1,c.y));
if(c.y-1 < 0){}else neighs = neighs.add(GetRightCell(c.x,c.y-1));
if(c.y+1 > 9){}else neighs = neighs.add(GetRightCell(c.x,c.y+1));
return neighs;
}
function GetNeighbor(cell,xoffset,yoffset){
let pos = GetCoords(cell);
if(pos.x+xoffset <0 || pos.x+xoffset >9)return null;
if(pos.y+yoffset <0 || pos.y+yoffset >9)return null;
return GetRightCell(pos.x+xoffset,pos.y+yoffset);
}
function TopMostCell(cells){
let minId = -1;
let minValue = 999;
for(let i=0;i<cells.length;i++){
let pos = GetCoords(cells.eq(i));
if(pos.y < minValue){
minValue = pos.y;
minId = i;
}
}return cells.eq(minId);
}
function LeftMostCell(cells){
let minId = -1;
let minValue = 999;
for(let i=0;i<cells.length;i++){
let pos = GetCoords(cells.eq(i));
if(pos.x < minValue){
minValue = pos.x;
minId = i;
}
}return cells.eq(minId);
}
</script>
<style>
body{
background-color:rgb(78, 56, 87);
}
</style>
</head>
<body>
<center>
<div id="battleship"></div>
</center>
</body>
</html>