Skip to content

Commit 69429b0

Browse files
committed
1.0.0 release
1 parent 816b3a2 commit 69429b0

13 files changed

+420
-114
lines changed

classes/Game.js

+82-18
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
1+
const dayjs = require('dayjs');
12
const crypto = require('crypto');
23

34
const Team = require('./Team');
45

56
module.exports = class Game {
7+
onStateChange = null;
8+
69
id = crypto.randomUUID();
7-
name = null;
10+
champions = null;
811
teams = null;
12+
expiration = dayjs().add(24, 'hour');
13+
14+
roundExpiration = null;
915

1016
bans = [[], []];
1117
picks = [[], []];
1218

19+
hover = [null, null];
1320
ready = [false, false];
1421

1522
order = [
@@ -37,12 +44,18 @@ module.exports = class Game {
3744
];
3845
current = 0;
3946

40-
constructor(name, teamNames) {
41-
this.name = name;
47+
constructor(teamNames, champions, onStateChange) {
48+
this.champions = champions;
4249
this.teams = [
4350
new Team(teamNames[0]),
4451
new Team(teamNames[1]),
4552
];
53+
54+
this.onStateChange = onStateChange;
55+
}
56+
57+
getExpiration() {
58+
return this.expiration;
4659
}
4760

4861
getId() {
@@ -52,12 +65,14 @@ module.exports = class Game {
5265
getState() {
5366
return {
5467
id: this.id,
55-
name: this.name,
5668

5769
ready: this.ready,
70+
hover: this.hover,
5871
bans: this.bans,
5972
picks: this.picks,
6073

74+
roundExpiration: Number(this.roundExpiration),
75+
6176
order: this.order,
6277
current: this.current,
6378

@@ -85,7 +100,25 @@ module.exports = class Game {
85100
return index === null ? null : this.teams[index];
86101
}
87102

88-
canAct(id, action) {
103+
endRound() {
104+
this.roundExpiration = null;
105+
clearTimeout(this.roundTimeout);
106+
}
107+
108+
startRound() {
109+
this.endRound();
110+
111+
const ROUND_LENGTH_SECONDS = 33;
112+
113+
this.roundExpiration = dayjs().add(ROUND_LENGTH_SECONDS, 'second');
114+
this.roundTimeout = setTimeout(() => this.autoAct(this.order[this.current][1]), ROUND_LENGTH_SECONDS * 1000);
115+
}
116+
117+
canAct(id, action, value) {
118+
if(value === null) {
119+
return false;
120+
}
121+
89122
const teamIndex = this.getTeamIndexById(id);
90123

91124
if(teamIndex === null) {
@@ -98,28 +131,59 @@ module.exports = class Game {
98131

99132
const currentRound = this.order[this.current];
100133

101-
return currentRound[0] === teamIndex && currentRound[1] === action;
134+
if([this.picks, this.bans].flat(Infinity).includes(value)) {
135+
return false;
136+
}
137+
138+
return currentRound[0] === teamIndex && ['hover', currentRound[1]].includes(action);
102139
}
103140

104-
act(id, action, championId) {
105-
if(!this.canAct(id, action)) {
106-
return false;
141+
act(id, action, value, bypass = false) {
142+
if(!bypass && !this.canAct(id, action, value)) {
143+
return;
107144
}
108145

109146
const teamIndex = this.getTeamIndexById(id);
110147

111-
if(action === 'ban') {
112-
this.bans[teamIndex].push(championId);
148+
switch(action) {
149+
case 'ban':
150+
case 'pick':
151+
this[action + 's'][teamIndex].push(value);
152+
this.hover[teamIndex] = null;
153+
++this.current;
113154

114-
++this.current;
115-
} else if(action === 'pick') {
116-
this.picks[teamIndex].push(championId);
155+
if(this.order[this.current][1] === 'done') {
156+
this.endRound();
157+
} else {
158+
this.startRound();
159+
}
160+
161+
break;
162+
163+
case 'hover':
164+
case 'ready':
165+
this[action][teamIndex] = value;
117166

118-
++this.current;
119-
} else if(action === 'ready') {
120-
this.ready[teamIndex] = true;
167+
if(action === 'ready' && !this.ready.includes(false)) {
168+
this.startRound();
169+
}
170+
171+
break;
172+
}
173+
174+
this.onStateChange(this);
175+
}
176+
177+
autoAct(action) {
178+
const teamIndex = this.order[this.current][0];
179+
180+
let value = null;
181+
if(this.hover[teamIndex] !== null) {
182+
value = this.hover[teamIndex];
183+
} else if(action === 'pick') {
184+
value = this.champions.filter(champion => ![this.picks, this.bans].flat().includes(champion.id)).sort(() => Math.random() < 0.5 ? 1 : -1)[0].id;
121185
}
122186

123-
return true;
187+
this.act(this.teams[teamIndex].getId(), action, value, true);
124188
}
125189
};

classes/GameList.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const dayjs = require('dayjs');
2+
3+
module.exports = class GameList {
4+
static games = {};
5+
6+
static add(id, game) {
7+
this.games[id] = game;
8+
}
9+
10+
static get(id) {
11+
return this.games[id];
12+
}
13+
14+
static flushExpired() {
15+
const now = dayjs();
16+
const expiredIds = [];
17+
18+
for(let id in this.games) {
19+
if(this.games.hasOwnProperty(id) && now.isAfter(this.games[id].getExpiration())) {
20+
delete this.games[id];
21+
22+
expiredIds.push(id);
23+
}
24+
}
25+
26+
return expiredIds;
27+
}
28+
};

create-server.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
module.exports = function createServer(createGame, onJoinGame, onGameAction) {
22
const express = require('express');
33
const app = express();
4+
const compression = require('compression');
45
const bodyParser = require('body-parser');
56
const http = require('http').Server(app);
67
const io = require('socket.io')(http);
78
const HTTP_PORT = 80;
89
const SOCKET_PORT = 3000;
910

11+
app.use(compression());
1012
app.use(express.static('public'));
1113
app.use(bodyParser.json());
1214

1315
app.post('/game', createGame);
1416
io.on('connection', function (socket) {
15-
onJoinGame(io, socket);
17+
onJoinGame(socket);
1618

1719
socket.on('game-action', function (data) {
18-
onGameAction(io, socket, data);
20+
onGameAction(socket, data);
1921
});
2022
});
2123

2224
http.listen(HTTP_PORT);
2325
io.listen(SOCKET_PORT);
26+
27+
return io;
2428
};

index.js

+24-22
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
11
const ChampionList = require('./classes/ChampionList');
2+
const GameList = require('./classes/GameList');
23
const Game = require('./classes/Game');
34
const createServer = require('./create-server');
45

5-
global.games = {};
6-
7-
setInterval(ChampionList.update, 3600); // Hourly
8-
ChampionList.update();
9-
106
function validateGameCreation(input) {
11-
if(typeof input.name !== 'string') {
12-
return false;
13-
}
14-
157
if(!Array.isArray(input.teams) || input.teams.length !== 2 || typeof input.teams[0] !== 'string' || typeof input.teams[1] !== 'string') {
168
return false;
179
}
1810

1911
const cleanse = string => string.trim().substr(0, 30);
2012

2113
return {
22-
name: cleanse(input.name),
2314
teams: [
2415
cleanse(input.teams[0]),
2516
cleanse(input.teams[1]),
@@ -34,44 +25,55 @@ function createGame(req, res) {
3425
res.end();
3526
}
3627

37-
const game = new Game(input.name, input.teams);
28+
const game = new Game(input.teams, ChampionList.get(), game => io.to(`game.${game.getId()}`).emit('game-state', game.getState()));
3829
const teams = game.getTeams();
3930

40-
global.games[game.getId()] = game;
31+
GameList.add(game.getId(), game);
4132

4233
res.send({
4334
game: game.getId(),
4435
teams: teams.map(team => team.getId()),
4536
});
4637
}
4738

48-
function onJoinGame(io, socket) {
49-
const game = global.games[socket.handshake.query.game];
39+
function onJoinGame(socket) {
40+
const game = GameList.get(socket.handshake.query.game);
5041

5142
if(game === undefined) {
43+
socket.emit('game-expired');
5244
socket.disconnect(true);
5345

5446
return;
5547
}
5648

57-
socket.emit('game-state', game.getState());
5849
socket.emit('champions', ChampionList.get());
50+
socket.emit('assign-team', game.getTeamIndexById(socket.handshake.query.team));
51+
socket.emit('game-state', game.getState());
5952
socket.join(`game.${game.getId()}`);
6053
}
6154

62-
function onGameAction(io, socket, data) {
63-
const game = global.games[socket.handshake.query.game];
55+
function onGameAction(socket, data) {
56+
const game = GameList.get(socket.handshake.query.game);
6457

6558
if(game === undefined) {
59+
socket.emit('game-expired');
6660
socket.disconnect(true);
6761

6862
return;
6963
}
7064

71-
const success = game.act(socket.handshake.query.team, data.action, data.champion);
72-
const recipient = success ? io.to(`game.${game.getId()}`) : socket;
73-
74-
recipient.emit('game-state', game.getState());
65+
game.act(socket.handshake.query.team, data.action, data.value);
7566
}
7667

77-
createServer(createGame, onJoinGame, onGameAction);
68+
const io = createServer(createGame, onJoinGame, onGameAction);
69+
70+
setInterval(() => ChampionList.update(), 3600000); // 1 hour
71+
ChampionList.update();
72+
73+
setInterval(() => {
74+
const expired = GameList.flushExpired();
75+
76+
for(let id of expired) {
77+
io.to(`game.${id}`).emit('game-expired');
78+
}
79+
}, 3600000); // 1 hour

0 commit comments

Comments
 (0)