forked from olavfosse/liracer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
140 lines (120 loc) · 4.21 KB
/
index.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
const { clear } = require('console')
const express = require('express')
const app = express()
const server = require('http').createServer(app)
const options = {
serveClient: false
}
const io = require('socket.io')(server, options)
// The frontend assumes that the backend is on same port as backend in production and on port 3101 otherwise
const port = process.env.PORT || 3101
const buildPath = `${__dirname}/front/build`
app.use(express.static(buildPath))
app.get('/*', (_request, response) => {
response.sendFile(`${buildPath}/index.html`)
})
const snippets = require('./snippets.js')
const randomSnippet = () => snippets[Math.floor(snippets.length * Math.random())]
const games = {}
const createPseudoRandomString = _ => Math.random().toString(36).replace(/[^a-z]+/g, '')
const createGame = _ => ({
snippet: randomSnippet(),
roundID: createPseudoRandomString(),
startingTime: new Date().getTime()
})
io.on('connection', socket => {
let gameID
const sendAnonLeftMessage = id => {
io.to(id).emit('chat message', {
sender: 'liracer',
content: 'anon left'
})
}
const sendAnonJoinedMessage = id => {
io.to(id).emit('chat message', {
sender: 'liracer',
content: 'anon joined'
})
}
const sendGameCreatedMessage = id => {
io.to(id).emit('chat message', {
sender: 'liracer',
content: 'Game created'
})
}
const sendCurrentSnippetMessage = id => {
io.to(id).emit('chat message', {
sender: 'liracer',
content: `The current snippet is ${games[id].snippet.name}`
})
}
const sendSnippetCompletedMessage = id => {
const timeToComplete = (new Date().getTime() - games[id].startingTime) / 1000
io.to(id).emit('chat message', {
sender: 'liracer',
content : `${games[id].snippet.name} completed in ${timeToComplete} seconds`
})
}
const clearCursor = id => {
// This is kind of hackish
// If position is -1 it won't be rendered, and it will be cleared from front end memory on 'game state' message
socket.to(id).emit('cursor position update', {
sid: socket.id,
position: -1
})
}
socket.on('disconnecting', () => {
clearCursor(gameID)
sendAnonLeftMessage(gameID)
})
socket.on('join game', id => {
socket.leave(gameID)
sendAnonLeftMessage(gameID)
socket.join(id)
if(games[id]) {
sendAnonJoinedMessage(id)
} else {
sendGameCreatedMessage(id)
games[id] = createGame()
sendCurrentSnippetMessage(id)
}
gameID = id
socket.emit('game state', games[id])
})
// TL;DR: roundID is used to verify that the received 'cursor position update' message refers to the current round
// roundID is a random hash used to identify a round
// a new round is started each time a new snippet is used
// this is used so that messages can be invalidated if they contain an outdated roundID
// this prevents the following bug:
// user1 sends a 'cursor position update' message causing a 'code snippet' message
// user2, which has not yet received the 'code snippet' message, sends a 'cursor position update' message.
// since user2 has not yet received the 'cursor position update' message the position had not been reset to 0.
// therefore the position sent is representative of how much of new 'code snippet' user2 has actually typed.
// worst case scenario the inaccurate position user2 sent matches the length of the new code snippet causing user2 to instantly win the round.
socket.on('cursor position update', ({ position, roundID }) => {
if(games[gameID].roundID !== roundID) { // See the wall of text above :^)
return
}
if(position === games[gameID].snippet.code.length) {
// New game
sendSnippetCompletedMessage(gameID)
games[gameID] = createGame()
sendCurrentSnippetMessage(gameID)
io.to(gameID).emit('game state', games[gameID])
} else {
// Send cursor position update to other players
socket.to(gameID).emit('cursor position update', {
sid: socket.id,
position
})
}
})
socket.on('chat message', content => {
io.to(gameID).emit('chat message', {
sender: 'anon',
content
})
})
})
console.log(`listening on ${port}`)
server.listen(port)