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 me.socket.emit('game_error', {message: 'That game has ended!'}) } if (games[gameId].player2 != null) { return me.socket.emit('game_error', {message: 'That game has already started!'}) } if (!clients[games[gameId].player1.uid]) { return me.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 me.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 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 checkForExistingStrike (myStrikes, x, y) { let found = false for (let i in myStrikes) { let cell = myStrikes[i] if (cell.x === x && cell.y === y) { found = true } } return found } 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) { if (checkForExistingStrike(me.strikes, x, y)) { return {event: 5, ship: null} } me.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) { 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: clients[me.uid].socket.emit('infmessage', 'You\'ve already bombed that tile!') break 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)