-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.js
executable file
·273 lines (219 loc) · 6.9 KB
/
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/usr/bin/env node
/* Imports */
const express = require('express')
const app = express()
const connecticut = require('./private/connecticut.js')
const helmet = require('helmet')
const randomWords = require('random-words')
const session = require('express-session')({
secret: 'lkast443jh5345sdkjhsdg83234!@#325',
resave: true,
saveUninitialized: true
})
const sharedsession = require('express-socket.io-session')
/* Set the template engine to EJS */
app.set('view engine', 'ejs')
/* Make the server use helmet for added security */
app.use(helmet())
/* Enable the use of sessions */
app.use(session)
/* Add middleware to parse post requests */
app.use(express.urlencoded({extended: true}))
app.use(express.json())
/* Make sure the app uses the 'public' directory for static content */
app.use(express.static('public'))
/* Everything must pass through an nginx server on localhost */
server = app.listen(3000, 'localhost')
const io = require('socket.io')(server)
/* Allow the linking between sessions and sockets */
io.use(sharedsession(session, {
autoSave:true
}))
/* Redirect homepage to index */
app.get('/', (req, res) => {
res.render('index')
})
/* New game handler */
app.post('/newgame', (req, res) => {
var action = req.body.action
/* Create the game */
game = GameConnection.newGame()
/* Join the game according to the action */
if (action == 'joinBlack') {
game.join(req, connecticut.Color.BLACK)
} else if (action == 'joinWhite') {
game.join(req, connecticut.Color.WHITE)
}
/* Go to the play area */
res.redirect('/play/' + game.gameId.toString())
})
/* Handler for a player attempting to join a game */
app.get('/play/:gameId', (req, res) => {
var gameId = req.params.gameId
game = GameConnection.gamesInPlay[gameId]
if (!game) {
res.redirect('/')
return
}
/* Figure out if the player is already in the game or must join in */
if (game.blackPlayer.sessionID == req.sessionID) {
res.render('play', {gameId: gameId})
return
}
if (game.whitePlayer.sessionID == req.sessionID) {
res.render('play', {gameId: gameId})
return
}
/* If the player must join in, select the right color or join as a viewer */
if (!game.blackPlayer.sessionID) {
game.join(req, connecticut.Color.BLACK)
} else if (!game.whitePlayer.sessionID) {
game.join(req, connecticut.Color.WHITE)
} else {
res.redirect('/view/' + gameId.toString())
return
}
res.render('play', {gameId: gameId})
})
/* Handler for a client attempting to view the game */
app.get('/view/:gameId', (req, res) => {
res.render('play', {gameId: req.params.gameId, viewer: 'viewer'})
})
/* Set up high-level event for incoming connections */
io.on('connection', (socket) => {
/* A connected socket makes a request to join a game */
socket.on('join', (game) => {
sessionID = socket.handshake.session.id
/* If the game does not exist, it cannot be joined */
if (!GameConnection.gamesInPlay[game.gameId]) {
return
}
/* Join the game */
GameConnection.gamesInPlay[game.gameId].connect(sessionID, socket)
})
})
/* A class to wrap around the connections to the players of a game,
* as well as the id of the game and other metadata
*/
class GameConnection {
/* Shared memory to keep trac of all played games */
static gamesInPlay = {}
/* Creates a new game with a proper ID */
static newGame() {
let newId
/* Generate game id's until the id is unused */
do {
newId = randomWords({min: 2, max: 3, join: '-'})
} while (newId in GameConnection.gamesInPlay)
return GameConnection.gamesInPlay[newId] = new GameConnection(newId)
}
constructor(gameId) {
/* Set the game ID */
this.gameId = gameId
/* Set default values */
this.blackPlayer = {}
this.whitePlayer = {}
/* Intialize a list to contain all of the connections viewing the game */
this.viewers = []
this.game = new connecticut.Game()
}
/* Event handler for black move made */
makeBlackMove(x, y) {
this.game.makeMove(x, y, connecticut.Color.BLACK)
this.sync()
}
/* Event handler for white move made */
makeWhiteMove(x, y) {
this.game.makeMove(x, y, connecticut.Color.WHITE)
this.sync()
}
/* Match the given session to the given color */
join(req, viewer) {
if (viewer == connecticut.Color.BLACK) {
if (!this.blackPlayer.sessionID) {
this.blackPlayer.sessionID = req.sessionID
}
} else if (viewer == connecticut.Color.WHITE) {
if (!this.whitePlayer.sessionID) {
this.whitePlayer.sessionID = req.sessionID
}
}
}
/* Connect the given socket to the player of the given color */
connect(sessionID, socket) {
if (sessionID == this.blackPlayer.sessionID) {
this.connectBlack(socket)
} else if (sessionID == this.whitePlayer.sessionID) {
this.connectWhite(socket)
} else {
this.connectViewer(socket)
}
this.sync()
}
/* Function to add a viewer to the game */
connectViewer(socket) {
this.viewers.push(socket)
}
/* Function to add a black player to the game */
connectBlack(socket) {
this.blackPlayer.socket = socket
/* Make sure the new player can send moves to the server */
socket.on('requestmove', (move) => {
this.makeBlackMove(move.x, move.y)
})
/* Handle disconnected event */
socket.on('disconnect', () => {
this.blackPlayer.socket = null
this.clientDisconnected()
})
}
/* Function to add a white player to the game */
connectWhite(socket) {
this.whitePlayer.socket = socket
/* Make sure the new player can send moves to the server */
socket.on('requestmove', (move) => {
this.makeWhiteMove(move.x, move.y)
})
/* Handle disconnected event */
socket.on('disconnect', () => {
this.whitePlayer.socket = null
this.clientDisconnected()
})
}
/* A general handler for when a client has disconnected */
clientDisconnected() {
/* If there are no players left, the connection is removed from memory */
if (!this.blackPlayer.socket && !this.whitePlayer.socket) {
delete(GameConnection.gamesInPlay[this.gameId])
}
}
/* Synchronize the board between all viewers and players */
sync() {
this.update(this.blackPlayer.socket)
this.update(this.whitePlayer.socket)
for (var viewer of this.viewers) {
this.update(viewer)
}
}
/* Synchronize the board for a specific socket */
update(socket) {
if (socket == null) {
return
}
/* Figure out the color of the given socket */
let viewer = 'viewer'
if (socket == this.whitePlayer.socket) {
viewer = connecticut.Color.WHITE
}
if (socket == this.blackPlayer.socket) {
viewer = connecticut.Color.BLACK
}
/* Send the sync event with the proper data to the client */
socket.emit('sync', {
squares: this.game.squares,
viewer: viewer,
winner: this.game.winner,
lastMove: this.game.lastMove
})
}
}