put it up on github

This commit is contained in:
Evert Prants 2017-04-05 23:14:57 +03:00
commit 8160fed639
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
6 changed files with 1362 additions and 0 deletions

1
.gitignore vendored Normal file
View File

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

194
client/index.css Normal file
View File

@ -0,0 +1,194 @@
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 {
background-color: #e0e0e0;
margin: 10px;
}
.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 {
color: #119286;
font-weight: bold;
padding-top: 5px;
width: 200px;
height: 50px;
}
#game_canvas {
margin-left: 300px;
}
.endresult {
text-align: center;
}
.result {
width: 530px;
display: inline-block;
}
button#lobby {
display: inline-block;
vertical-align: middle;
margin-left: 15px;
}
.bigstat {
font-size: 200%;
font-weight: bold;
margin-bottom: 50px;
margin-top: 10px;
}
.message.t_event {
color: #b5b5b5;
}
.message .sender {
color: red;
}
.message.me .sender {
color: blue !important;
}
.chatbox {
margin-top: 50px;
padding: 5px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 5px;
}
#messages {
height: 250px;
background-color: #fff;
border-radius: 5px;
overflow-y: scroll;
word-wrap: break-word;
white-space: pre-line;
max-width: 254px;
}

72
client/index.html Normal file
View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="about" content="A Diamond* WebGame game">
<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 Connect Four</title>
</head>
<body>
<div class="wrapper">
<div class="screen" id="start">
<div class="dialog">
<h1>Connect Four</h1>
<p id="warning_message"></p>
<input type="text" id="player_name" placeholder="Your name here">
<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 HTML5 remake of <a href="https://en.wikipedia.org/wiki/Connect_Four" target="_blank">Connect Four</a></p>
<ul>
<li>Connect 4 tiles of your color to win.</li>
<li>Matches are detected horizontally, vertically and diagonally.</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="640" height="640"></canvas>
<div class="sidebar">
<span class="stat" id="g_s_stat">Your turn.</span>
<div class="chatbox">
<div class="letterbox" id="messages"></div>
<input type="text" id="message_send" placeholder="Send message..">
</div>
</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>

621
client/index.js Normal file
View File

@ -0,0 +1,621 @@
(function ($) {
let io = window.io.connect()
let Connect4 = {
DOM: {},
playerName: '',
playerID: '',
verified: null,
locked: false,
waitlist: [],
played: 0,
renderTick: false,
Game: {
gameId: null,
myTurn: false,
myColor: '',
opponentID: '',
opponentName: '',
places: [[],[],[],[],[],[],[],[],[]]
},
color: {
blue: '#102aed',
red: '#ed1010'
}
}
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 -= Connect4.DOM.canvas.offsetLeft
y -= Connect4.DOM.canvas.offsetTop
return {x: x, y: y}
}
let GameDrawer = {
drawMyBoard: true,
boardStaticState: null,
mX: 0,
mY: 0,
padding: 32,
gridX: 0,
gridY: 0,
gridSize: 64,
mouseOn: false,
bw: 576,
bh: 576,
startGame: () => {
Connect4.Game.places = [[],[],[],[],[],[],[],[],[]]
Connect4.ctx.clearRect(0, 0, Connect4.canvasW, Connect4.canvasH)
Connect4.Game.myTurn = true
let p = GameDrawer.padding
Connect4.ctx.beginPath()
for (let x = 0; x <= GameDrawer.bw; x += GameDrawer.gridSize) {
Connect4.ctx.moveTo(0.5 + x + p, p)
Connect4.ctx.lineTo(0.5 + x + p, GameDrawer.bh + p)
}
for (let x = 0; x <= GameDrawer.bh; x += GameDrawer.gridSize) {
Connect4.ctx.moveTo(p, 0.5 + x + p)
Connect4.ctx.lineTo(GameDrawer.bw + p, 0.5 + x + p)
}
Connect4.ctx.closePath()
Connect4.ctx.lineWidth = 1
Connect4.ctx.strokeStyle = "black"
Connect4.ctx.stroke()
GameDrawer.boardStaticState = new Image()
GameDrawer.boardStaticState.src = Connect4.DOM.canvas.toDataURL()
GameDrawer.boardStaticState.onload = () => {
Connect4.renderTick = true
GameDrawer.gameLoop()
}
},
possible: (column) => {
let inTable = Connect4.Game.places[column]
if (inTable.length === 9) {
return false
}
return true
},
click: () => {
if (!Connect4.Game.gameId) return
if (Connect4.Game.myTurn && GameDrawer.mouseOn) {
let column = GameDrawer.gridX - 1
if (GameDrawer.possible(column)) {
io.emit('place_at', {column: column, gameId: Connect4.Game.gameId})
}
}
},
updater: () => {
for (let i in Connect4.Game.places) {
let col = Connect4.Game.places[i]
for (let p in col) {
let piece = col[p]
if (piece.dy < piece.y) {
piece.dy += 0.5
} else {
piece.dy = piece.y
}
Connect4.ctx.fillStyle = Connect4.color[piece.color]
Connect4.ctx.fillRect((parseInt(i) * GameDrawer.gridSize) + GameDrawer.padding,
(piece.dy * GameDrawer.gridSize) + GameDrawer.padding, GameDrawer.gridSize, GameDrawer.gridSize)
}
}
if (!Connect4.renderTick || !Connect4.Game.gameId) return
if (Connect4.Game.myTurn) {
if (GameDrawer.mouseOn) {
let pos = (GameDrawer.gridX * GameDrawer.gridSize)
Connect4.ctx.beginPath()
Connect4.ctx.moveTo(pos - 8, (GameDrawer.padding / 2) - 4)
Connect4.ctx.lineTo(pos, (GameDrawer.padding / 2) + 4)
Connect4.ctx.lineTo(pos + 8, (GameDrawer.padding / 2) - 4)
Connect4.ctx.closePath()
Connect4.ctx.lineWidth = 10
Connect4.ctx.strokeStyle = Connect4.color[Connect4.Game.myColor]
Connect4.ctx.stroke()
}
}
},
gameLoop: () => {
Connect4.ctx.clearRect(0, 0, Connect4.canvasW, Connect4.canvasH)
if (!Connect4.renderTick) return
GameDrawer.updater()
Connect4.ctx.drawImage(GameDrawer.boardStaticState, 0, 0)
requestAnimFrame(GameDrawer.gameLoop)
},
initialize: () => {
Connect4.DOM.canvas.addEventListener('mousemove', (e) => {
let p = pointerOnCanvas(e)
if (p.x > GameDrawer.padding && p.y > GameDrawer.padding) {
let gridX = Math.floor((p.x + GameDrawer.padding) / GameDrawer.gridSize)
let gridY = Math.floor((p.y + GameDrawer.padding) / GameDrawer.gridSize)
if (gridX <= 9 && gridY <= 9) {
GameDrawer.mouseOn = true
GameDrawer.gridX = gridX
GameDrawer.gridY = gridY
} else {
GameDrawer.mouseOn = false
}
} else {
GameDrawer.mouseOn = false
}
})
Connect4.DOM.canvas.addEventListener('mouseleave', (e) => {
GameDrawer.mouseOn = false
})
Connect4.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) {
Connect4.DOM.joinWarn.innerHTML = msg
}
function logStatus (msg) {
Connect4.DOM.statusCurrent.innerHTML = msg
}
function joinGame (game) {
Connect4.played += 1
alert('Game has started!')
//io.emit('leave_game', {gameId: Connect4.Game.gameId})
Connect4.Game.gameId = game.gameId
Connect4.Game.opponentID = game.opponentId
Connect4.Game.opponentName = game.opponentName
Connect4.Game.myColor = game.color
Connect4.DOM.opponentName.innerHTML = game.opponentName
Connect4.DOM.chatbox.innerHTML = ''
io.emit('game_poll', {gameId: Connect4.Game.gameId})
Connect4.DOM.gameScreen.style.display = 'block'
Connect4.DOM.selectionScreen.style.display = 'none'
Connect4.DOM.waitlistBtns.style.display = 'block'
Connect4.DOM.waitlistQuit.style.display = 'none'
GameDrawer.startGame()
addChatMessage('event', null, 'Game started!')
}
function attemptJoin (name) {
if (Connect4.locked) return
if (!io.connected) {
return logWarning('Disconnected from server socket.')
}
if (playerNameValidation(name) == false) {
return logWarning('Username not allowed.')
}
logWarning('Attempting to join..')
Connect4.locked = true
io.emit('session_create', {name: name})
}
function joinSuccess (data) {
Connect4.playerName = data.name
Connect4.playerID = data.uid
Connect4.DOM.selectionScreen.style.display = 'block'
storeVar('name', data.name)
io.emit('poll_games')
Connect4.locked = false
}
function joinResponse (data) {
if (data.success !== true) {
Connect4.locked = false
return logWarning(data.message)
}
Connect4.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 Connect4.waitlist) {
let game = Connect4.waitlist[i]
finalML += mustacheTempl('waitlistInstance', game)
}
waitlist.innerHTML = finalML
}
window.joinWaiting = (gameId) => {
if (Connect4.Game.gameId) return
io.emit('game_attempt_join', {gameId: gameId})
}
function gameEnds (reason, winner) {
if (reason === 1) {
if (winner === true) {
alert('You won!')
logStatus('You won!')
} else {
alert('You lost.')
logStatus('You lost.')
}
}
if (reason === 0 && winner === true) {
alert('Your opponent left the game.')
Connect4.DOM.gameScreen.style.display = 'none'
Connect4.DOM.selectionScreen.style.display = 'block'
Connect4.renderTick = false
}
if (reason === 2) {
alert('You tied!')
logStatus('It\'s a tie!.')
}
Connect4.locked = false
Connect4.Game.gameId = null
Connect4.Game.myTurn = false
io.emit('poll_games')
//Connect4.DOM.gameScreen.style.display = 'none'
//Connect4.DOM.selectionScreen.style.display = 'block'
Connect4.DOM.waitlistBtns.style.display = 'block'
Connect4.DOM.waitlistQuit.style.display = 'none'
addChatMessage('event', null, 'Disconnected')
}
function forceRelogin () {
logWarning('Please log in again.')
Connect4.DOM.gameScreen.style.display = 'none'
Connect4.DOM.selectionScreen.style.display = 'none'
Connect4.DOM.startScreen.style.display = 'block'
Connect4.DOM.resultScreen.style.display = 'none'
Connect4.locked = false
Connect4.playerName = ''
Connect4.Game.gameId = null
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;")
}
function addChatMessage (type, senderName, message) {
let msgElem = '<div class="message t_' + type + '">'
if (senderName) {
msgElem += '<span class="sender">' + senderName + '</span>&nbsp;'
}
msgElem += '<span class="line">' + escapeHtml(message) + '</span>'
Connect4.DOM.chatbox.innerHTML += msgElem
Connect4.DOM.chatbox.scrollTop = Connect4.DOM.chatbox.scrollHeight
}
window.onload = () => {
const startScreen = Connect4.DOM.startScreen = $.querySelector('#start')
const selectionScreen = Connect4.DOM.selectionScreen = $.querySelector('#selection')
const gameScreen = Connect4.DOM.gameScreen = $.querySelector('#game')
const warning = Connect4.DOM.joinWarn = startScreen.querySelector('#warning_message')
const playerName = startScreen.querySelector('#player_name')
const startButton = startScreen.querySelector('#sock_player_init')
const waitlist = Connect4.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 = Connect4.DOM.waitlistQuit = selectionScreen.querySelector('#waitlist_quit')
const waitlistBtns = Connect4.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 = Connect4.DOM.opponentName = gameScreen.querySelector('#opponent_name')
let canvas = Connect4.DOM.canvas = gameScreen.querySelector('#game_canvas')
let ctx = Connect4.ctx = canvas.getContext('2d')
Connect4.canvasW = canvas.width
Connect4.canvasH = canvas.height
Connect4.DOM.statusCurrent = gameScreen.querySelector('#g_s_stat')
const chatbox = Connect4.DOM.chatbox = gameScreen.querySelector('#messages')
const chatfield = Connect4.DOM.chatfield = gameScreen.querySelector('#message_send')
GameDrawer.initialize()
let uname = getStored('name')
if (uname) {
playerName.value = uname
}
playerName.addEventListener('keydown', (e) => {
if (e.keyCode === 13) {
attemptJoin(playerName.value)
}
}, false)
chatfield.addEventListener('keydown', (e) => {
if (e.keyCode === 13 && Connect4.Game.gameId) {
if (chatfield.value != '') {
io.emit('chat_send', {message: chatfield.value, gameId: Connect4.Game.gameId})
addChatMessage('chat me', Connect4.playerName, chatfield.value)
chatfield.value = ''
}
}
})
startButton.addEventListener('click', (e) => {
attemptJoin(playerName.value)
}, false)
newGame.addEventListener('click', (e) => {
if (Connect4.locked) return
if (Connect4.Game.gameId) return
io.emit('new_game')
Connect4.locked = true
})
refresh.addEventListener('click', (e) => {
if (Connect4.locked) return
io.emit('poll_games')
})
waitlistQuit.addEventListener('click', (e) => {
io.emit('leave_game', {gameId: Connect4.Game.gameId})
})
leaveBtn.addEventListener('click', (e) => {
if (Connect4.Game.gameId) {
io.emit('leave_game', {gameId: Connect4.Game.gameId})
}
Connect4.DOM.gameScreen.style.display = 'none'
Connect4.DOM.selectionScreen.style.display = 'block'
Connect4.renderTick = false
})
random.addEventListener('click', (e) => {
Connect4.joinRandomWhenDone = true
io.emit('poll_games')
})
io.on('chat', (data) => {
addChatMessage('chat', data.name, data.message)
})
io.on('infmessage', (message) => {
logStatus(message)
})
io.on('game_start', (data) => {
leaveBtn.innerHTML = 'Leave game'
joinGame(data)
})
io.on('left_success', () => {
gameEnds(0, null)
})
io.on('turn', (val) => {
if (val === true) {
Connect4.Game.myTurn = true
logStatus('Your turn.')
} else {
Connect4.Game.myTurn = false
logStatus('Your opponent\'s turn.')
}
})
io.on('place', (data) => {
let col = Connect4.Game.places[data.column]
col.push({y: 8 - col.length, color: data.color, dy: 0})
})
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)
leaveBtn.innerHTML = 'Back to lobby'
})
io.on('game_new_done', (data) => {
Connect4.locked = true
Connect4.DOM.waitlist.innerHTML = '<div class="green">Waiting for an opponent..</div>'
Connect4.DOM.waitlistBtns.style.display = 'none'
Connect4.DOM.waitlistQuit.style.display = 'block'
Connect4.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) => {
Connect4.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 = Connect4.played
if (!list.length) {
waitlist.innerHTML = '<div class="red">No people currently waiting, press <b>Join Wait List</b> to enter.</div>'
Connect4.waitlist = []
if(Connect4.joinRandomWhenDone) {
delete Connect4.joinRandomWhenDone
}
return
}
Connect4.waitlist = list
if (Connect4.joinRandomWhenDone && Connect4.waitlist.length) {
delete Connect4.joinRandomWhenDone
let rand = getRandomInt(1, Connect4.waitlist.length)
io.emit('game_attempt_join', {gameId: Connect4.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"
}
}

454
server.js Normal file
View File

@ -0,0 +1,454 @@
const express = require('express')
const socketio = require('socket.io')
const http = require('http')
const path = require('path')
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 determineOpponent (myIndex) {
let opponent = 'red'
if (myIndex === 'red') {
opponent = 'blue'
}
return opponent
}
function killGamesClientIsIn (uid) {
for (let gameId in games) {
let game = games[gameId]
if (game.blue && game.blue === uid) {
if (!game.isWaiting && game.red) {
clients[game.red].socket.emit('game_end', {win: true, result: 0})
}
} else if (game.red && game.red === uid) {
if (clients[game.blue]) {
clients[game.blue].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] = {
blue: uid,
red: null,
isWaiting: true,
turn: 1,
places: [[],[],[],[],[],[],[],[],[]],
created: new Date(),
started: null
}
}
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].red != null) {
return me.socket.emit('game_error', {message: 'That game has already started!'})
}
if (!clients[games[gameId].blue]) {
return me.socket.emit('game_error', {message: 'That game has ended!'})
}
let game = games[gameId]
game.red = uid
game.isWaiting = false
game.started = new Date()
let opponent = clients[game.blue]
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, color: 'blue'})
me.socket.emit('game_start', {gameId: gameId, opponentId: opponent.uid, opponentName: opponent.name, color: 'red'})
game.turn = 'blue'
clients[game.blue].socket.emit('turn', true)
clients[uid].socket.emit('turn', false)
totalGames += 1
}
function endGame (gameId, victoryId, loserId, status) {
if (clients[victoryId]) {
clients[victoryId].socket.emit('game_end', {win: true, result: status})
}
if (clients[loserId]) {
clients[loserId].socket.emit('game_end', {win: false, result: status})
}
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.blue].name
if (uid && game.blue === 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) return null
if (game.blue && game.blue === uid) {
return 'blue'
} else if (game.red && game.red === uid) {
return 'red'
}
return null
}
function getPiece (game, col, index) {
if (col > 8 || index > 8) return
col = game.places[col]
if (!col) return
if (!col.length) {
return
}
let match = null
for (let i in col) {
if (col[i].y === index) {
match = col[i]
break
}
}
return match
}
function detectWin (color, game) {
let win = false
for (let c in game.places) {
let col = game.places[c]
for (let p in col) {
let piece = col[p]
let matches = 0
for (let i = 0; i < 4; i++) {
let pAt = getPiece(game, parseInt(c) + i, piece.y)
if (pAt && pAt.color === color) {
console.log(pAt)
matches += 1
} else {
matches = 0
}
}
if (matches >= 4) {
win = true
console.log('horizontal win')
break
}
matches = 0
for (let i = 0; i < 4; i++) {
let pAt = getPiece(game, parseInt(c), piece.y + i)
if (pAt && pAt.color === color) {
matches += 1
} else {
matches = 0
}
}
if (matches >= 4) {
console.log('vertical win')
win = true
break
}
matches = 0
for (let i = 0; i < 4; i++) {
let pAt = getPiece(game, parseInt(c) + i, piece.y - i)
if (pAt && pAt.color === color) {
matches += 1
} else {
matches = 0
}
}
if (matches >= 4) {
console.log('diagonal right win')
win = true
break
}
matches = 0
for (let i = 0; i < 4; i++) {
let pAt = getPiece(game, parseInt(c) - i, piece.y - i)
if (pAt && pAt.color === color) {
matches += 1
} else {
matches = 0
}
}
if (matches >= 4) {
console.log('diagonal left win')
win = true
break
}
}
}
return win
}
function detectTie (game) {
let tie = true
for (let c in game.places) {
let col = game.places[c]
if (col.length !== 9) {
tie = false
break
}
}
return tie
}
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})
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('chat_send', (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 = determineOpponent(playerInGame)
let opponentObj = game[opponent]
let me = game[playerInGame]
clients[opponentObj].socket.emit('chat', {name: clients[me].name, message: data.message})
})
socket.on('place_at', (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
}
if (data.column == null || data.column > 9) {
socket.emit('game_error', {message: 'Unexpected column'})
return
}
let opponent = determineOpponent(playerInGame)
opponent = game[opponent]
let me = game[playerInGame]
clients[me].socket.emit('place', {column: data.column, color: playerInGame})
clients[opponent].socket.emit('place', {column: data.column, color: playerInGame})
game.places[data.column].push({color: playerInGame, y: 8 - game.places[data.column].length})
console.log(game.places[data.column])
if (detectWin(playerInGame, game)) {
endGame(data.gameId, me, opponent, 1)
return
}
if (detectTie(game)) {
endGame(data.gameId, me, opponent, 2)
return
}
clients[me].socket.emit('turn', false)
clients[opponent].socket.emit('turn', true)
})
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(8245)