This commit is contained in:
Evert Prants 2017-04-03 23:44:36 +03:00
commit 73893b6cb6
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
6 changed files with 1455 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/node_modules/

155
client/index.css Normal file
View File

@ -0,0 +1,155 @@
body {
font-family: "Open Sans";
margin: 0;
color: black;
}
.wrapper {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
background: -moz-radial-gradient(center, ellipse cover, #ffffff 0%, #e5e5e5 100%);
background: -webkit-radial-gradient(center, ellipse cover, #ffffff 0%,#e5e5e5 100%);
background: radial-gradient(ellipse at center, #ffffff 0%,#e5e5e5 100%);
background-repeat: no-repeat;
}
.screen {
position: absolute;
width: 100%;
height: 100%;
}
.dialog {
position: absolute;
width: 260px;
padding: 20px;
left: 0;
right: 0;
margin: auto;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 10px;
top: 20%;
}
h1, h2, h3 {
margin-top: 0;
}
input[type="text"] {
background-color: #f9f9f9;
border: 2px solid #ddd;
border-radius: 2px;
font-size: 125%;
padding: 2px;
box-shadow: inset 1px 1px 10px #ddd;
color: black;
}
.dialog input {
margin-bottom: 10px;
width: 95%;
}
button {
background: #ffffff;
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff 0%,#e5e5e5 100%);
border: 1px solid #ddd;
padding: 5px 15px;
border-radius: 5px;
cursor: pointer;
color: black;
}
button:hover {
background: -moz-linear-gradient(top, #ffffff 0%, #e8e8e8 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e8e8e8 100%);
background: linear-gradient(to bottom, #ffffff 0%,#e8e8e8 100%);
}
canvas {
width: 512px;
height: 512px;
background-color: #03A9F4;
margin: 10px;
/*border: 5px solid #2196F3;*/
}
.boxlayout {
text-align: center;
}
.box {
text-align: left;
width: 25%;
display: inline-block;
height: 400px;
margin: 20px;
background-color: #fff;
padding: 20px;
min-width: 280px;
overflow-y: auto;
border: 1px solid #ddd;
box-shadow: 4px 4px 10px #ddd;
vertical-align: top;
}
.stat {
display: block;
}
#waitlist {
margin-bottom: 5px;
}
#waitlist .red, #waitlist .green {
padding: 10px;
}
#waitlist .red {
background-color: #ffd5d5;
border: 1px solid #ff5c5c;
}
#waitlist .green {
background-color: #daffda;
border: 1px solid #00f700;
}
.waitlistInstance {
background-color: #f5f5f5;
padding: 5px;
max-height: 240px;
overflow-y: auto;
}
.waitlistInstance:nth-child(odd) {
background-color: #f9f9f9;
}
.joinBtn {
float: right;
color: #03A9F4;
text-decoration: none;
font-weight: bold;
padding: 0px 10px;
}
.joinBtn:hover {
text-decoration: underline;
}
.header {
height: 40px;
line-height: 2.5;
padding: 5px 20px;
background-color: #fff;
border-bottom: 1px solid #ddd;
}
button#leave {
float: right;
line-height: 2;
}
.sidebar {
float: left;
background-color: #fff;
padding: 10px;
position: absolute;
top: 51px;
bottom: 0;
}
#g_s_stat {
margin-top: 20px;
border-top: 1px solid #ddd;
color: #119286;
font-weight: bold;
padding-top: 5px;
width: 200px;
}
#game_canvas {
margin-left: 265px;
}

73
client/index.html Normal file
View File

@ -0,0 +1,73 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript" src="./index.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Open+Sans">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="./index.css">
<title>HTML5 Battleship (simplified version)</title>
</head>
<body>
<div class="wrapper">
<div class="screen" id="start">
<div class="dialog">
<h1>Enter your name</h1>
<p id="warning_message"></p>
<input type="text" id="player_name">
<button id="sock_player_init">Join server</button>
</div>
</div>
<div class="screen boxlayout" style="display: none;" id="selection">
<div class="box">
<h1>Statistics</h1>
<span class="stat">Players in game: <span id="stats_players"></span></span>
<span class="stat">Total games since server started: <span id="stats_games"></span></span>
<span class="stat">You've played in <span id="stats_clientgames"></span> matches this session.</span>
</div>
<div class="box">
<h1>Players waiting</h1>
<div id="waitlist">
<p><i class="fa fa-spinner fa-spin fa-fw"></i>&nbsp;Loading..</p>
</div>
<button id="waitlist_quit" style="display: none;">Cancel</button>
<div class="idbuttons">
<button id="waitlist_join">Join Wait List</button>
<button id="waitlist_join_random">Join Random Game</button>
<button id="waitlist_join_refresh">Refresh</button>
</div>
</div>
<div class="box">
<h1>How to play</h1>
<p>This is a puzzle game inspired by <a href="https://en.wikipedia.org/wiki/Battleship_(puzzle)" target="_blank">Battleship</a>, however this one works quite differently.</p>
<ul>
<li>You start off by placing your ships onto the board.</li>
<li>Once you and your opponent finish placing the ships, the game begins.</li>
<li>When it's your turn, you click on a square to attempt to bomb an opposing ship.</li>
<li>If you miss, it's your opponents turn. If you strike, you can bomb more until you miss again.</li>
<li>You must destroy most of the opponents's ship to sink it.</li>
<li>You win the game by sinking all of the opponent's ships!</li>
</ul>
</div>
</div>
<div class="screen game gamelayout" style="display: none;" id="game">
<div class="header">You're playing against <span id="opponent_name">null</span> <button id="leave">Leave game</button></div>
<canvas id="game_canvas" width="513" height="513"></canvas>
<div class="sidebar">
<span class="stat">You have <span id="g_s_num">0</span> ships left.</span>
<span class="stat">Your opponent has <span id="o_s_num">0</span> ships left.</span>
<span class="stat" id="g_s_stat">Your turn.</span>
</div>
</div>
</div>
<script type="text/mustache" id="waitlistInstance">
<div class="waitlistInstance">
<span class="name">{{name}}</span>
<a href="#" class="joinBtn" onclick="joinWaiting('{{gameId}}')">Join</a>
</div>
</script>
</body>
</html>

689
client/index.js Normal file
View File

@ -0,0 +1,689 @@
(function ($) {
let io = window.io.connect()
let Battleship = {
DOM: {},
playerName: '',
playerID: '',
verified: null,
locked: false,
waitlist: [],
played: 0,
Game: {
gameId: null,
inGame: false,
myTurn: true,
opponentID: '',
opponentName: '',
ships: 0,
oShips: 0,
gridHome: {ships: [], strikes: []},
gridOpponent: []
}
}
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60)
}
})()
function mustacheTempl (tmlTag, data) {
let html = ''
const tag = document.querySelector('#' + tmlTag)
if (!tag) return ''
html = tag.innerHTML
html = window.Mustache.to_html(html, data)
return html
}
function pointerOnCanvas (e) {
let x
let y
if (e.changedTouches) {
let touch = e.changedTouches[0]
if (touch) {
e.pageX = touch.pageX
e.pageY = touch.pageY
}
}
if (e.pageX || e.pageY) {
x = e.pageX
y = e.pageY
} else {
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop
}
x -= Battleship.DOM.canvas.offsetLeft
y -= Battleship.DOM.canvas.offsetTop
return {x: x, y: y}
}
let GameDrawer = {
drawMyBoard: true,
boardStaticState: null,
mX: 0,
mY: 0,
gridX: 0,
gridY: 0,
gridSize: 32,
mouseOn: false,
bw: 512,
bh: 512,
placingShips: true,
canPlace: false,
shipIndex: 0,
shipOrientation: 0,
shipTiles: [],
startGame: () => {
GameDrawer.placingShips = true
GameDrawer.canPlace = false
GameDrawer.shipIndex = 0
GameDrawer.shipOrientation = 0
GameDrawer.shipTiles = []
Battleship.ctx.clearRect(0, 0, Battleship.canvasW, Battleship.canvasH)
Battleship.Game.myTurn = true
let p = 0
let context = Battleship.ctx
for (let x = 0; x <= GameDrawer.bw; x += GameDrawer.gridSize) {
context.moveTo(0.5 + x + p, p)
context.lineTo(0.5 + x + p, GameDrawer.bh + p)
}
for (let x = 0; x <= GameDrawer.bh; x += GameDrawer.gridSize) {
context.moveTo(p, 0.5 + x + p)
context.lineTo(GameDrawer.bw + p, 0.5 + x + p)
}
context.strokeStyle = "black"
context.stroke()
GameDrawer.boardStaticState = new Image()
GameDrawer.boardStaticState.src = Battleship.DOM.canvas.toDataURL()
GameDrawer.boardStaticState.onload = () => {
GameDrawer.gameLoop()
}
},
click: () => {
if (GameDrawer.placingShips && GameDrawer.canPlace && GameDrawer.shipIndex != -1) {
let shipData = Battleship.ships[GameDrawer.shipIndex]
let placed = {
name: shipData.name,
sunken: false,
cells: GameDrawer.shipTiles
}
io.emit('ship_place', {ship: placed, gameId: Battleship.Game.gameId})
if (GameDrawer.shipIndex + 1 < Battleship.ships.length) {
GameDrawer.shipIndex += 1
} else {
GameDrawer.shipIndex = -1
logStatus('Waiting for opponent')
}
} else if (!GameDrawer.placingShips && Battleship.Game.myTurn) {
io.emit('set_bomb', {x: GameDrawer.gridX, y: GameDrawer.gridY, gameId: Battleship.Game.gameId})
}
},
intersectsExisting: (cx, cy) => {
let is = false
for (let i in Battleship.Game.gridHome.ships) {
let ship = Battleship.Game.gridHome.ships[i]
for (let j in ship.cells) {
let cell = ship.cells[j]
if (cell.x === cx && cell.y === cy) {
is = true
}
}
}
return is
},
updater: () => {
if (Battleship.Game.myTurn && !GameDrawer.placingShips) {
if (GameDrawer.mouseOn) {
Battleship.ctx.fillStyle = '#f4b002'
Battleship.ctx.fillRect(GameDrawer.gridX * GameDrawer.gridSize, GameDrawer.gridY * GameDrawer.gridSize, GameDrawer.gridSize, GameDrawer.gridSize)
}
for (let i in Battleship.Game.gridHome.strikes) {
let strike = Battleship.Game.gridHome.strikes[i]
if (strike.destroy) {
Battleship.ctx.fillStyle = '#ff0000'
} else {
Battleship.ctx.fillStyle = '#dddd00'
}
Battleship.ctx.fillRect(strike.x * GameDrawer.gridSize, strike.y * GameDrawer.gridSize, GameDrawer.gridSize, GameDrawer.gridSize)
}
} else if ((!Battleship.Game.myTurn && !GameDrawer.placingShips) || (Battleship.Game.myTurn && GameDrawer.placingShips)) {
for (let i in Battleship.Game.gridOpponent) {
let strike = Battleship.Game.gridOpponent[i]
Battleship.ctx.fillStyle = '#dddd00'
Battleship.ctx.fillRect(strike.x * GameDrawer.gridSize, strike.y * GameDrawer.gridSize, GameDrawer.gridSize, GameDrawer.gridSize)
}
for (let i in Battleship.Game.gridHome.ships) {
let ship = Battleship.Game.gridHome.ships[i]
for (let j in ship.cells) {
let cell = ship.cells[j]
let color = '#dddddd'
if (cell.destroyed || ship.sunken) {
color = '#ff0000'
}
Battleship.ctx.fillStyle = color
Battleship.ctx.fillRect(cell.x * GameDrawer.gridSize, cell.y * GameDrawer.gridSize, GameDrawer.gridSize, GameDrawer.gridSize)
}
}
}
if (Battleship.Game.myTurn && GameDrawer.placingShips) {
let shipData = Battleship.ships[GameDrawer.shipIndex]
if (!shipData) return
let shipCenter = Math.floor(shipData.tiles / 2)
let cellsOff = 0
let positions = []
let color = '#00dd00'
for (let i = 0; i < shipData.tiles; i++) {
let sx = 0
let sy = 0
i = parseInt(i)
let rx = 0
let ry = 0
if (i < shipCenter) {
if (GameDrawer.shipOrientation === 0) {
sx = GameDrawer.gridX - (shipCenter - i)
sy = GameDrawer.gridY
} else {
sx = GameDrawer.gridX
sy = GameDrawer.gridY - (shipCenter - i)
}
} else if (i === shipCenter) {
sx = GameDrawer.gridX
sy = GameDrawer.gridY
} else {
if (GameDrawer.shipOrientation === 0) {
sx = GameDrawer.gridX + (i - shipCenter)
sy = GameDrawer.gridY
} else {
sx = GameDrawer.gridX
sy = GameDrawer.gridY + (i - shipCenter)
}
}
if (sx < 0 || sy < 0 || sx > 15 || sy > 15) {
cellsOff += 1
}
if (GameDrawer.intersectsExisting(sx, sy)) {
cellsOff += 1
}
positions.push({x: sx, y: sy, destroyed: false})
}
for (let i in positions) {
let pos = positions[i]
if (cellsOff > 0) {
color = '#dd0000'
GameDrawer.canPlace = false
} else {
GameDrawer.canPlace = true
}
Battleship.ctx.fillStyle = color
Battleship.ctx.fillRect(pos.x * GameDrawer.gridSize, pos.y * GameDrawer.gridSize, GameDrawer.gridSize, GameDrawer.gridSize)
}
GameDrawer.shipTiles = positions
}
},
clearCanvas: () => {
Battleship.ctx.clearRect(0, 0, Battleship.canvasW, Battleship.canvasH)
Battleship.ctx.drawImage(GameDrawer.boardStaticState, 0, 0)
},
gameLoop: () => {
GameDrawer.clearCanvas()
if (!Battleship.Game.gameId) return
GameDrawer.updater()
requestAnimFrame(GameDrawer.gameLoop)
},
initialize: () => {
Battleship.DOM.canvas.addEventListener('mousemove', (e) => {
let posOnCanvas = pointerOnCanvas(e)
GameDrawer.mX = posOnCanvas.x
GameDrawer.mY = posOnCanvas.y
GameDrawer.mouseOn = true
let rowCount = GameDrawer.gridSize
GameDrawer.gridX = Math.floor(GameDrawer.mX / rowCount)
GameDrawer.gridY = Math.floor(GameDrawer.mY / rowCount)
})
Battleship.DOM.canvas.addEventListener('mouseleave', (e) => {
GameDrawer.mouseOn = false
})
Battleship.DOM.canvas.addEventListener('click', (e) => {
GameDrawer.click()
})
document.addEventListener('keydown', (e) => {
if (GameDrawer.placingShips && e.keyCode === 82) {
if (GameDrawer.shipOrientation === 0) {
GameDrawer.shipOrientation = 1
} else {
GameDrawer.shipOrientation = 0
}
}
})
}
}
function getStored (variable) {
let result = null
if (!window.localStorage) {
return null
}
if (window.localStorage.game_store) {
try {
let obj = JSON.parse(window.localStorage.game_store)
if (obj[variable] != null) {
result = obj[variable]
}
} catch (e) {
result = null
}
}
return result
}
function storeVar (variable, value) {
if (!window.localStorage) {
return null
}
if (window.localStorage.game_store) {
try {
let obj = JSON.parse(window.localStorage.game_store)
obj[variable] = value
window.localStorage.game_store = JSON.stringify(obj)
} catch (e) {
return null
}
} else {
let obj = {}
obj[variable] = value
window.localStorage.game_store = JSON.stringify(obj)
}
}
function playerNameValidation (name) {
if (/^([A-Z0-9_\-@]{3,20})$/i.test(name)) {
return true
}
return false
}
function logWarning (msg) {
Battleship.DOM.joinWarn.innerHTML = msg
}
function logStatus (msg) {
Battleship.DOM.statusCurrent.innerHTML = msg
}
function joinGame (game) {
Battleship.played += 1
alert('Game has started!')
//io.emit('leave_game', {gameId: Battleship.Game.gameId})
Battleship.Game.gameId = game.gameId
Battleship.Game.opponentID = game.opponentId
Battleship.Game.opponentName = game.opponentName
Battleship.DOM.opponentName.innerHTML = game.opponentName
io.emit('game_poll', {gameId: Battleship.Game.gameId})
logStatus('Place your ships onto the board.<br>Press `r` to rotate')
Battleship.DOM.gameScreen.style.display = 'block'
Battleship.DOM.selectionScreen.style.display = 'none'
Battleship.DOM.waitlistBtns.style.display = 'block'
Battleship.DOM.waitlistQuit.style.display = 'none'
GameDrawer.startGame()
}
function attemptJoin (name) {
if (Battleship.locked) return
if (!io.connected) {
return logWarning('Disconnected from server socket.')
}
if (playerNameValidation(name) == false) {
return logWarning('Username not allowed.')
}
logWarning('Attempting to join..')
Battleship.locked = true
io.emit('session_create', {name: name})
}
function joinSuccess (data) {
Battleship.playerName = data.name
Battleship.playerID = data.uid
Battleship.DOM.selectionScreen.style.display = 'block'
storeVar('name', data.name)
io.emit('poll_games')
Battleship.locked = false
}
function joinResponse (data) {
if (data.success !== true) {
Battleship.locked = false
return logWarning(data.message)
}
Battleship.DOM.startScreen.style.display = 'none'
joinSuccess(data)
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
function constructWaitList() {
let finalML = ''
for (let i in Battleship.waitlist) {
let game = Battleship.waitlist[i]
finalML += mustacheTempl('waitlistInstance', game)
}
waitlist.innerHTML = finalML
}
window.joinWaiting = (gameId) => {
if (Battleship.Game.gameId) return
io.emit('game_attempt_join', {gameId: gameId})
}
function gameEnds (reason, winner) {
if (reason === 1) {
if (winner === true) {
alert('You won!')
} else {
alert('You lost.')
}
}
if (reason === 0 && winner === true) {
alert('Your opponent left the game.')
}
Battleship.locked = false
Battleship.Game.gameId = null
io.emit('poll_games')
Battleship.DOM.gameScreen.style.display = 'none'
Battleship.DOM.selectionScreen.style.display = 'block'
Battleship.DOM.waitlistBtns.style.display = 'block'
Battleship.DOM.waitlistQuit.style.display = 'none'
Battleship.Game.gridHome = {ships: [], strikes: []}
Battleship.Game.gridOpponent = []
Battleship.DOM.dataOpponentDestroyed.innerHTML = '0'
Battleship.DOM.dataMineDestroyed.innerHTML = '0'
}
function forceRelogin () {
logWarning('Please log in again.')
Battleship.DOM.gameScreen.style.display = 'none'
Battleship.DOM.selectionScreen.style.display = 'none'
Battleship.DOM.startScreen.style.display = 'block'
Battleship.locked = false
Battleship.playerName = ''
Battleship.Game.gameId = null
}
window.onload = () => {
const startScreen = Battleship.DOM.startScreen = $.querySelector('#start')
const selectionScreen = Battleship.DOM.selectionScreen = $.querySelector('#selection')
const gameScreen = Battleship.DOM.gameScreen = $.querySelector('#game')
const warning = Battleship.DOM.joinWarn = startScreen.querySelector('#warning_message')
const playerName = startScreen.querySelector('#player_name')
const startButton = startScreen.querySelector('#sock_player_init')
const waitlist = Battleship.DOM.waitlist = selectionScreen.querySelector('#waitlist')
const random = selectionScreen.querySelector('#waitlist_join_random')
const newGame = selectionScreen.querySelector('#waitlist_join')
const refresh = selectionScreen.querySelector('#waitlist_join_refresh')
const waitlistQuit = Battleship.DOM.waitlistQuit = selectionScreen.querySelector('#waitlist_quit')
const waitlistBtns = Battleship.DOM.waitlistBtns = selectionScreen.querySelector('.idbuttons')
const stat_ingame = selectionScreen.querySelector('#stats_players')
const stat_total = selectionScreen.querySelector('#stats_games')
const stat_client = selectionScreen.querySelector('#stats_clientgames')
const leaveBtn = gameScreen.querySelector('#leave')
const opponentName = Battleship.DOM.opponentName = gameScreen.querySelector('#opponent_name')
let canvas = Battleship.DOM.canvas = gameScreen.querySelector('#game_canvas')
let ctx = Battleship.ctx = canvas.getContext('2d')
Battleship.canvasW = canvas.width
Battleship.canvasH = canvas.height
const dataOpponentDestroyed = Battleship.DOM.dataOpponentDestroyed = gameScreen.querySelector('#o_s_num')
const dataMineDestroyed = Battleship.DOM.dataMineDestroyed = gameScreen.querySelector('#g_s_num')
Battleship.DOM.statusCurrent = gameScreen.querySelector('#g_s_stat')
GameDrawer.initialize()
let uname = getStored('name')
if (uname) {
playerName.value = uname
}
playerName.addEventListener('keydown', (e) => {
if (e.keyCode === 13) {
attemptJoin(playerName.value)
}
}, false)
startButton.addEventListener('click', (e) => {
attemptJoin(playerName.value)
}, false)
newGame.addEventListener('click', (e) => {
if (Battleship.locked) return
if (Battleship.Game.gameId) return
io.emit('new_game')
Battleship.locked = true
})
refresh.addEventListener('click', (e) => {
if (Battleship.locked) return
io.emit('poll_games')
})
waitlistQuit.addEventListener('click', (e) => {
io.emit('leave_game', {gameId: Battleship.Game.gameId})
})
leaveBtn.addEventListener('click', (e) => {
io.emit('leave_game', {gameId: Battleship.Game.gameId})
})
random.addEventListener('click', (e) => {
Battleship.joinRandomWhenDone = true
io.emit('poll_games')
})
io.on('destroy_turn', (val) => {
GameDrawer.placingShips = false
if (val === true) {
Battleship.Game.myTurn = true
logStatus('It\'s your turn!<br>Click anywhere on the grid to attempt to destroy enemy ships.')
} else {
Battleship.Game.myTurn = false
logStatus('Your opponent\'s turn.')
}
})
io.on('update_hits', (data) => {
if (data.me) {
Battleship.Game.gridHome.strikes = data.strikes
} else {
Battleship.Game.gridOpponent = data.strikes
}
})
io.on('updateShip', (dship) => {
for (let i in Battleship.Game.gridHome.ships) {
let ship = Battleship.Game.gridHome.ships[i]
if (ship.name === dship.name) {
for (let j in ship.cells) {
let cell = ship.cells[j]
cell.destroyed = dship.cells[j].destroyed
}
ship.sunken = dship.sunken
if (ship.sunken) {
logStatus('Opponent sunk one of your ships!')
}
}
}
})
io.on('infmessage', (message) => {
logStatus(message)
})
io.on('game_settings', (data) => {
Battleship.ships = data.ships
})
io.on('verified_place', (ship) => {
Battleship.Game.gridHome.ships.push(ship)
})
io.on('game_start', (data) => {
joinGame(data)
})
io.on('left_success', () => {
gameEnds(0, null)
})
io.on('game_error', (data) => {
alert(data.message)
gameEnds(0, null)
io.emit('poll_games')
})
io.on('force_relog', () => {
forceRelogin()
})
io.on('game_end', (data) => {
gameEnds(data.result, data.win)
})
io.on('game_new_done', (data) => {
Battleship.locked = true
Battleship.DOM.waitlist.innerHTML = '<div class="green">Waiting for an opponent..</div>'
Battleship.DOM.waitlistBtns.style.display = 'none'
Battleship.DOM.waitlistQuit.style.display = 'block'
Battleship.Game.gameId = data.gameId
})
io.on('current_stats', (data) => {
dataOpponentDestroyed.innerHTML = data.opponentShipsLeft
dataMineDestroyed.innerHTML = data.myShipsLeft
})
io.on('login_status', joinResponse)
io.on('poll_games_res', (data) => {
Battleship.DOM.waitlistQuit.style.display = 'none'
let list = data.list
if (data.sessions != null) {
stat_ingame.innerHTML = data.sessions
}
if (data.totalGames != null) {
stat_total.innerHTML = data.totalGames
}
stat_client.innerHTML = Battleship.played
if (!list.length) {
waitlist.innerHTML = '<div class="red">No people currently waiting, press <b>Join Wait List</b> to enter.</div>'
Battleship.waitlist = []
if(Battleship.joinRandomWhenDone) {
delete Battleship.joinRandomWhenDone
}
return
}
Battleship.waitlist = list
if (Battleship.joinRandomWhenDone && Battleship.waitlist.length) {
delete Battleship.joinRandomWhenDone
let rand = getRandomInt(1, Battleship.waitlist.length)
io.emit('game_attempt_join', {gameId: Battleship.waitlist[rand - 1].gameId})
}
constructWaitList()
})
io.on('disconnect', () => {
gameEnds(0, null)
forceRelogin()
logWarning('Server disconnected')
})
}
})(document)

20
package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "battleship.js",
"version": "0.0.0",
"description": "Battleship game in the browser",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"battleship",
"game",
"html5"
],
"author": "Evert",
"license": "MIT",
"dependencies": {
"express": "^4.15.2",
"socketio": "^1.0.0"
}
}

517
server.js Normal file
View File

@ -0,0 +1,517 @@
const express = require('express')
const socketio = require('socket.io')
const http = require('http')
const path = require('path')
// TODO LIST:
// * More ships
// * Timer
// * Chat box
// * Side-by-side board display
const ships = [
{name: 'biggest', tiles: 5, destCount: 4},
{name: 'bigger', tiles: 4, destCount: 3},
{name: 'medium', tiles: 3, destCount: 2},
{name: 'smaller', tiles: 3, destCount: 2},
{name: 'smallest', tiles: 2, destCount: 1}
]
let app = express()
let server = http.createServer(app)
let io = socketio(server)
app.enable('trust proxy')
app.disable('x-powered-by')
app.use('/', express.static(path.join(__dirname, '/client/')))
function playerNameValidation (name) {
if (/^([A-Z0-9_\-@]{3,20})$/i.test(name)) {
return true
}
return false
}
let clients = {}
let games = {}
let totalGames = 0
// Generate a random int betweem two ints
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
// Generate random string of characters
function nuid(len) {
let buf = [],
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
charlen = chars.length
for (let i = 0; i < len; ++i) {
buf.push(chars[getRandomInt(0, charlen - 1)])
}
return buf.join('')
}
function clientsBySocketID (id) {
let result = null
for (let uid in clients) {
let client = clients[uid]
if (client.sockID === id) {
result = uid
}
}
return result
}
function killGamesClientIsIn (uid) {
for (let gameId in games) {
let game = games[gameId]
if (game.player1 && game.player1.uid === uid) {
if (!game.isWaiting && game.player2) {
clients[game.player2.uid].socket.emit('game_end', {win: true, result: 0})
}
} else if (game.player2 && game.player2.uid === uid) {
if (clients[game.player1.uid]) {
clients[game.player1.uid].socket.emit('game_end', {win: true, result: 0})
}
} else {
continue
}
delete games[gameId]
console.log(gameId + ' was ended abruptly on ' + uid + '\'s demand.')
}
}
function createNewGame (uid) {
let client = clients[uid]
let gameId = nuid(16)
client.socket.emit('game_new_done', {gameId: gameId})
console.log(client.name + ' has started a new game. ID: ' + gameId)
games[gameId] = {
player1: {
uid: uid,
ships: [],
strikes: [],
placed: false,
destructions: 0
},
player2: null,
isWaiting: true,
turn: 1,
started: new Date()
}
}
function joinGame (uid, gameId) {
let me = clients[uid]
if (!games[gameId]) {
return socket.emit('game_error', {message: 'That game has ended!'})
}
if (games[gameId].player2 != null) {
return socket.emit('game_error', {message: 'That game has already started!'})
}
if (!clients[games[gameId].player1.uid]) {
return socket.emit('game_error', {message: 'That game has ended!'})
}
games[gameId].player2 = {
uid: uid,
ships: [],
strikes: [],
placed: false,
destructions: 0
}
games[gameId].isWaiting = false
let opponent = clients[games[gameId].player1.uid]
if (!opponent) {
return socket.emit('game_error', {message: 'Your opponent abruptly dissappeared, what?'})
}
opponent.socket.emit('game_start', {gameId: gameId, opponentId: uid, opponentName: me.name})
me.socket.emit('game_start', {gameId: gameId, opponentId: opponent.uid, opponentName: opponent.name})
totalGames += 1
}
function endGame (gameId, victoryId, loserId) {
if (clients[victoryId]) {
clients[victoryId].socket.emit('game_end', {win: true, result: 1})
}
if (clients[loserId]) {
clients[loserId].socket.emit('game_end', {win: false, result: 1})
}
delete games[gameId]
console.log(gameId + ' ended with ' + victoryId + '\'s victory.')
}
function waitingGamesList (uid) {
let result = []
let cap = 0
let gamesInSession = 0
for (let i in games) {
let game = games[i]
if (!game.isWaiting) {
gamesInSession += 1
}
}
for (let gameId in games) {
if (cap >= 20) break
let game = games[gameId]
if (game.isWaiting) {
let userName = clients[game.player1.uid].name
if (uid && game.player1.uid === uid) continue
result.push({
gameId: gameId,
name: userName,
started: game.started
})
cap += 1
}
}
return {
sessions: gamesInSession,
totalGames: totalGames,
list: result
}
}
function bombTile (playerIndex, gameId, xTile, yTile) {
}
function determinePlayerById (gameId, uid) {
let game = games[gameId]
if (game.player1 && game.player1.uid === uid) {
return 'player1'
} else if (game.player2 && game.player2.uid === uid) {
return 'player2'
}
return null
}
function getShipByName (name) {
let ship = null
for (let i in ships) {
if (ships[i].name === name) {
ship = ships[i]
}
}
return ship
}
function integrityCheck (shipDataProvided) {
if (!shipDataProvided.name || shipDataProvided.sunken == null || !shipDataProvided.cells) {
return false
}
let shipActual = getShipByName(shipDataProvided.name)
if (shipActual === null) {
return false
}
if (shipDataProvided.cells.length !== shipActual.tiles) {
return false
}
return true
}
function getDestroyedTiles (ship) {
let count = 0
for (let i in ship.cells) {
let cell = ship.cells[i]
if (cell.destroyed === true) {
count += 1
}
}
return count
}
function markAllTilesDestroyed (ship, myStrikes) {
for (let i in ship.cells) {
let cell = ship.cells[i]
cell.destroyed = true
myStrikes.push({x: cell.x, y: cell.y, destroy: true})
}
ship.sunken = true
}
function attemptToBombTile (playerIndex, opponent, game, x, y) {
let me = game[playerIndex]
let opponentObj = game[opponent]
let tileMatch = null
let shipMatch = null
for (let i in opponentObj.ships) {
let ship = opponentObj.ships[i]
for (let j in ship.cells) {
let cell = ship.cells[j]
if (cell.x === x && cell.y === y) {
tileMatch = cell
shipMatch = ship
}
}
}
if (!tileMatch) {
me.strikes.push({x: x, y: y, destroy: false})
//opponentObj.strikes.push({x: x, y: y, destroy: false})
return {event: 2, ship: null}
}
if (tileMatch.destroyed === true) {
return {event: 5, ship: shipMatch}
}
tileMatch.destroyed = true
me.strikes.push({x: x, y: y, destroy: true})
let shipActual = getShipByName(shipMatch.name)
let destroyedTileCount = getDestroyedTiles(shipMatch)
if (destroyedTileCount >= shipActual.destCount) {
markAllTilesDestroyed(shipMatch, me.strikes)
opponentObj.destructions += 1
if (opponentObj.destructions === ships.length) {
console.log('winner: ' + me.uid)
return {event: 6, ship: shipMatch}
}
return {event: 1, ship: shipMatch}
}
return {event: 0, ship: shipMatch}
}
io.on('connection', (socket) => {
socket.on('session_create', (data) => {
if (!data.name) {
return socket.emit('login_status', {success: false, message: 'Invalid name.'})
}
if (!playerNameValidation(data.name)) {
return socket.emit('login_status', {success: false, message: 'Invalid name.'})
}
let playerUid = nuid(32)
socket.emit('login_status', {success: true, uid: playerUid, name: data.name})
socket.emit('game_settings', {ships: ships})
clients[playerUid] = {
socket: socket,
name: data.name,
sockID: socket.conn.id
}
console.log('New player: "' + data.name + '" with uid ' + playerUid)
})
socket.on('poll_games', () => {
let client = clientsBySocketID(socket.conn.id)
socket.emit('poll_games_res', waitingGamesList(client))
})
socket.on('game_attempt_join', (data) => {
let client = clientsBySocketID(socket.conn.id)
if (!client) {
socket.emit('game_error', {message: 'You are not logged in properly!'})
socket.emit('force_relog')
return
}
if (!data.gameId) return
joinGame(client, data.gameId)
})
socket.on('leave_game', (data) => {
let client = clientsBySocketID(socket.conn.id)
if (!client) return
killGamesClientIsIn(client)
socket.emit('left_success')
})
socket.on('new_game', () => {
let client = clientsBySocketID(socket.conn.id)
if (!client) {
socket.emit('game_error', {message: 'You are not logged in properly!'})
socket.emit('force_relog')
return
}
createNewGame(client)
})
socket.on('ship_place', (data) => {
let client = clientsBySocketID(socket.conn.id)
if (!client) {
socket.emit('game_error', {message: 'You are not logged in properly!'})
socket.emit('force_relog')
return
}
if (!data.gameId || !data.ship) return
let game = games[data.gameId]
let playerInGame = determinePlayerById(data.gameId, client)
if (!playerInGame) {
socket.emit('game_error', {message: 'unexpected error. code: 763'})
return
}
let meObj = game[playerInGame]
if (meObj.placed === true) {
socket.emit('game_error', {message: 'Please don\'t cheat.'})
return
}
if (!integrityCheck(data.ship)) {
socket.emit('game_error', {message: 'Something went wrong when you tried placing a ship.'})
return
}
meObj.ships.push({
name: data.ship.name,
sunken: false,
cells: data.ship.cells
})
socket.emit('verified_place', data.ship)
if (meObj.ships.length === ships.length) {
meObj.placed = true
let opponent = 'player2'
if (playerInGame === 'player2') {
opponent = 'player1'
}
if (game[opponent].placed) {
game.turn = 'player1'
clients[game.player1.uid].socket.emit('destroy_turn', true)
clients[game.player2.uid].socket.emit('destroy_turn', false)
}
clients[meObj.uid].socket.emit('current_stats', {
opponentShipsLeft: ships.length - game[opponent].destructions,
myShipsLeft: ships.length - meObj.destructions
})
clients[game[opponent].uid].socket.emit('current_stats', {
opponentShipsLeft: ships.length - meObj.destructions,
myShipsLeft: ships.length - game[opponent].destructions
})
}
})
socket.on('set_bomb', (data) => {
let client = clientsBySocketID(socket.conn.id)
if (!client) {
socket.emit('game_error', {message: 'You are not logged in properly!'})
socket.emit('force_relog')
return
}
let game = games[data.gameId]
let playerInGame = determinePlayerById(data.gameId, client)
if (!playerInGame) {
socket.emit('game_error', {message: 'unexpected error. code: 763'})
return
}
let opponent = 'player2'
if (playerInGame === 'player2') {
opponent = 'player1'
}
let result = attemptToBombTile(playerInGame, opponent, game, data.x, data.y)
let opponentObj = game[opponent]
let me = game[playerInGame]
if (result.ship) {
clients[opponentObj.uid].socket.emit('updateShip', result.ship)
}
clients[me.uid].socket.emit('update_hits', {me: true, strikes: me.strikes})
clients[me.uid].socket.emit('update_hits', {me: false, strikes: opponentObj.strikes})
switch (result.event) {
case 5:
case 1:
clients[me.uid].socket.emit('infmessage', 'You sunk a ship!')
break
case 0:
clients[me.uid].socket.emit('infmessage', 'You destroyed some of the opponents ship!')
break
case 2:
game.turn = opponent
clients[me.uid].socket.emit('destroy_turn', false)
clients[opponentObj.uid].socket.emit('destroy_turn', true)
clients[me.uid].socket.emit('infmessage', 'You missed!')
break
case 6:
endGame(data.gameId, client, opponentObj.uid)
break
}
clients[me.uid].socket.emit('current_stats', {
opponentShipsLeft: ships.length - opponentObj.destructions,
myShipsLeft: ships.length - me.destructions
})
clients[opponentObj.uid].socket.emit('current_stats', {
opponentShipsLeft: ships.length - me.destructions,
myShipsLeft: ships.length - opponentObj.destructions
})
})
socket.on('disconnect', () => {
let client = clientsBySocketID(socket.conn.id)
if (!client) return
killGamesClientIsIn(client)
console.log('Player uid ' + client + ' left.')
delete clients[client]
})
})
server.listen(8000)