@@ -19,6 +19,10 @@ import (
19
19
"github.com/gorilla/websocket"
20
20
)
21
21
22
+ const (
23
+ maxOperativesPerTeam = 10
24
+ )
25
+
22
26
type Srv struct {
23
27
sc * securecookie.SecureCookie
24
28
hub * hub.Hub
@@ -59,18 +63,18 @@ func (s *Srv) initMux() *mux.Router {
59
63
// Pending games.
60
64
m .HandleFunc ("/api/games" , s .servePendingGames ).Methods ("GET" )
61
65
// Get game.
62
- m .HandleFunc ("/api/game/{id}" , s .serveGame ).Methods ("GET" )
66
+ m .HandleFunc ("/api/game/{id}" , s .requireGameAuth ( s . serveGame ) ).Methods ("GET" )
63
67
// Join game.
64
68
m .HandleFunc ("/api/game/{id}/join" , s .serveJoinGame ).Methods ("POST" )
65
69
// Start game.
66
- m .HandleFunc ("/api/game/{id}/start" , s .serveStartGame ).Methods ("POST" )
70
+ m .HandleFunc ("/api/game/{id}/start" , s .requireGameAuth ( s . serveStartGame ) ).Methods ("POST" )
67
71
// Serve a clue to a game.
68
- m .HandleFunc ("/api/game/{id}/clue" , s .serveClue ).Methods ("POST" )
72
+ m .HandleFunc ("/api/game/{id}/clue" , s .requireGameAuth ( s . serveClue ) ).Methods ("POST" )
69
73
// Serve a card guess to a game.
70
- m .HandleFunc ("/api/game/{id}/guess" , s .serveGuess ).Methods ("POST" )
74
+ m .HandleFunc ("/api/game/{id}/guess" , s .requireGameAuth ( s . serveGuess ) ).Methods ("POST" )
71
75
72
76
// WebSocket handler for games.
73
- m .HandleFunc ("/api/game/{id}/ws" , s .serveData ).Methods ("GET" )
77
+ m .HandleFunc ("/api/game/{id}/ws" , s .requireGameAuth ( s . serveData ) ).Methods ("GET" )
74
78
75
79
return m
76
80
}
@@ -172,19 +176,9 @@ func (s *Srv) servePendingGames(w http.ResponseWriter, r *http.Request) {
172
176
jsonResp (w , gIDs )
173
177
}
174
178
175
- func (s * Srv ) serveGame (w http.ResponseWriter , r * http.Request ) {
176
- vars := mux .Vars (r )
177
- id , ok := vars ["id" ]
178
- if ! ok {
179
- http .Error (w , "no game ID provided" , http .StatusBadRequest )
180
- return
181
- }
182
- gID := codenames .GameID (id )
183
-
184
- game , err := s .db .Game (gID )
185
- if err != nil {
186
- http .Error (w , "failed to load game" , http .StatusInternalServerError )
187
- return
179
+ func (s * Srv ) serveGame (w http.ResponseWriter , r * http.Request , u * codenames.User , game * codenames.Game , userPR * codenames.PlayerRole , prs []* codenames.PlayerRole ) {
180
+ if userPR .Role != codenames .SpymasterRole {
181
+ game .State .Board = codenames .Revealed (game .State .Board )
188
182
}
189
183
190
184
jsonResp (w , game )
@@ -248,22 +242,37 @@ func (s *Srv) serveJoinGame(w http.ResponseWriter, r *http.Request) {
248
242
http .Error (w , err .Error (), http .StatusInternalServerError )
249
243
return
250
244
}
251
- spymasters := make (map [codenames.Team ]bool )
245
+ roleCount := make (map [codenames.Role ] map [codenames. Team ]int )
252
246
for _ , pr := range prs {
253
- if pr .Role != codenames .SpymasterRole {
254
- continue
247
+ rc , ok := roleCount [pr .Role ]
248
+ if ! ok {
249
+ rc = make (map [codenames.Team ]int )
255
250
}
256
- if spymasters [pr .Team ] {
251
+ if pr .PlayerID .IsUser (u .ID ) {
252
+ errMsg := fmt .Sprintf ("can't join game as %q %q, already joined as %q %q" , desiredTeam , desiredRole , pr .Team , pr .Role )
253
+ http .Error (w , errMsg , http .StatusBadRequest )
254
+ return
255
+ }
256
+ if pr .Role == codenames .SpymasterRole && rc [pr .Team ] > 1 {
257
257
http .Error (w , fmt .Sprintf ("multiple players set as %q spymaster" , pr .Team ), http .StatusInternalServerError )
258
258
return
259
259
}
260
- spymasters [pr .Team ] = true
260
+ if pr .Role == codenames .OperativeRole && rc [pr .Team ] > maxOperativesPerTeam {
261
+ http .Error (w , fmt .Sprintf ("too many players set as %q operatives" , pr .Team ), http .StatusInternalServerError )
262
+ return
263
+ }
264
+ rc [pr .Team ]++
265
+ roleCount [pr .Role ] = rc
261
266
}
262
267
263
- if desiredRole == codenames .SpymasterRole && spymasters [ desiredTeam ] {
268
+ if desiredRole == codenames .SpymasterRole && roleCount [codenames. SpymasterRole ][ desiredTeam ] > 0 {
264
269
http .Error (w , fmt .Sprintf ("team %q already has a spymaster" , desiredTeam ), http .StatusBadRequest )
265
270
return
266
271
}
272
+ if desiredRole == codenames .OperativeRole && roleCount [codenames.OperativeRole ][desiredTeam ] >= maxOperativesPerTeam {
273
+ http .Error (w , fmt .Sprintf ("team %q already has max operatives" , desiredTeam ), http .StatusBadRequest )
274
+ return
275
+ }
267
276
268
277
if err := s .db .JoinGame (gID , & codenames.PlayerRole {
269
278
PlayerID : codenames.PlayerID {
@@ -282,32 +291,7 @@ func (s *Srv) serveJoinGame(w http.ResponseWriter, r *http.Request) {
282
291
}{true })
283
292
}
284
293
285
- func (s * Srv ) serveStartGame (w http.ResponseWriter , r * http.Request ) {
286
- vars := mux .Vars (r )
287
- id , ok := vars ["id" ]
288
- if ! ok {
289
- http .Error (w , "no game ID provided" , http .StatusBadRequest )
290
- return
291
- }
292
- gID := codenames .GameID (id )
293
-
294
- u , err := s .loadUser (r )
295
- if err != nil {
296
- http .Error (w , err .Error (), http .StatusInternalServerError )
297
- return
298
- }
299
-
300
- if u == nil {
301
- http .Error (w , "Not logged in" , http .StatusUnauthorized )
302
- return
303
- }
304
-
305
- game , err := s .db .Game (gID )
306
- if err != nil {
307
- http .Error (w , "failed to load game" , http .StatusInternalServerError )
308
- return
309
- }
310
-
294
+ func (s * Srv ) serveStartGame (w http.ResponseWriter , r * http.Request , u * codenames.User , game * codenames.Game , userPR * codenames.PlayerRole , prs []* codenames.PlayerRole ) {
311
295
if game .CreatedBy != u .ID {
312
296
http .Error (w , "only the game creator can start the game" , http .StatusForbidden )
313
297
return
@@ -318,73 +302,80 @@ func (s *Srv) serveStartGame(w http.ResponseWriter, r *http.Request) {
318
302
return
319
303
}
320
304
321
- prs , err := s .db .PlayersInGame (gID )
322
- if err != nil {
323
- http .Error (w , err .Error (), http .StatusInternalServerError )
324
- return
325
- }
326
-
327
305
if err := codenames .AllRolesFilled (prs ); err != nil {
328
306
http .Error (w , fmt .Sprintf ("can't start game yet: %v" , err ), http .StatusBadRequest )
329
307
return
330
308
}
331
309
332
310
// If we're here, all the right roles are filled, the game is pending, and
333
311
// the caller is the one who created the game, let's start it.
334
- if err := s .db .StartGame (gID ); err != nil {
312
+ if err := s .db .StartGame (game . ID ); err != nil {
335
313
http .Error (w , err .Error (), http .StatusInternalServerError )
336
314
return
337
315
}
338
316
game .Status = codenames .Playing
339
317
340
- if err := s .hub .ToGame (gID , & GameStart {
341
- Game : game ,
342
- Players : prs ,
343
- }); err != nil {
344
- http .Error (w , fmt .Sprintf ("failed to send game start: %v" , err ), http .StatusInternalServerError )
345
- return
318
+ // First, send the full board to the spymasters.
319
+ for _ , pr := range prs {
320
+ if pr .Role != codenames .SpymasterRole {
321
+ continue
322
+ }
323
+ if pr .PlayerID .PlayerType != codenames .PlayerTypeHuman {
324
+ continue
325
+ }
326
+
327
+ uID := codenames .UserID (pr .PlayerID .ID )
328
+ if err := s .hub .ToUser (game .ID , uID , & GameStart {
329
+ Game : game ,
330
+ Players : prs ,
331
+ }); err != nil {
332
+ http .Error (w , fmt .Sprintf ("failed to send game start: %v" , err ), http .StatusInternalServerError )
333
+ return
334
+ }
335
+ }
336
+
337
+ // Now, clear out the card agent colorings and send that board to everyone
338
+ // else.
339
+ game .State .Board = codenames .Revealed (game .State .Board )
340
+ for _ , pr := range prs {
341
+ if pr .Role != codenames .OperativeRole {
342
+ continue
343
+ }
344
+ if pr .PlayerID .PlayerType != codenames .PlayerTypeHuman {
345
+ continue
346
+ }
347
+
348
+ uID := codenames .UserID (pr .PlayerID .ID )
349
+ if err := s .hub .ToUser (game .ID , uID , & GameStart {
350
+ Game : game ,
351
+ Players : prs ,
352
+ }); err != nil {
353
+ http .Error (w , fmt .Sprintf ("failed to send game start: %v" , err ), http .StatusInternalServerError )
354
+ return
355
+ }
346
356
}
347
357
348
358
jsonResp (w , struct {
349
359
Success bool `json:"success"`
350
360
}{true })
351
361
}
352
362
353
- func (s * Srv ) serveClue (w http.ResponseWriter , r * http.Request ) {
363
+ func (s * Srv ) serveClue (w http.ResponseWriter , r * http.Request , u * codenames. User , game * codenames. Game , userPR * codenames. PlayerRole , prs [] * codenames. PlayerRole ) {
354
364
355
365
}
356
366
357
- func (s * Srv ) serveGuess (w http.ResponseWriter , r * http.Request ) {
367
+ func (s * Srv ) serveGuess (w http.ResponseWriter , r * http.Request , u * codenames. User , game * codenames. Game , userPR * codenames. PlayerRole , prs [] * codenames. PlayerRole ) {
358
368
359
369
}
360
370
361
- func (s * Srv ) serveData (w http.ResponseWriter , r * http.Request ) {
362
- vars := mux .Vars (r )
363
- id , ok := vars ["id" ]
364
- if ! ok {
365
- http .Error (w , "no game ID provided" , http .StatusBadRequest )
366
- return
367
- }
368
- gID := codenames .GameID (id )
369
-
370
- u , err := s .loadUser (r )
371
- if err != nil {
372
- http .Error (w , err .Error (), http .StatusInternalServerError )
373
- return
374
- }
375
-
376
- if u == nil {
377
- http .Error (w , "Not logged in" , http .StatusUnauthorized )
378
- return
379
- }
380
-
371
+ func (s * Srv ) serveData (w http.ResponseWriter , r * http.Request , u * codenames.User , game * codenames.Game , userPR * codenames.PlayerRole , prs []* codenames.PlayerRole ) {
381
372
conn , err := s .ws .Upgrade (w , r , nil )
382
373
if err != nil {
383
374
http .Error (w , err .Error (), http .StatusInternalServerError )
384
375
return
385
376
}
386
377
387
- s .hub .Register (conn , gID , u .ID )
378
+ s .hub .Register (conn , game . ID , u .ID )
388
379
}
389
380
390
381
type jsBoard struct {
@@ -478,3 +469,61 @@ func (s *Srv) loadUser(r *http.Request) (*codenames.User, error) {
478
469
479
470
return u , nil
480
471
}
472
+
473
+ type gameHandler = func (w http.ResponseWriter , r * http.Request , u * codenames.User , game * codenames.Game , userPR * codenames.PlayerRole , prs []* codenames.PlayerRole )
474
+
475
+ func (s * Srv ) requireGameAuth (handler gameHandler ) http.HandlerFunc {
476
+ return func (w http.ResponseWriter , r * http.Request ) {
477
+ vars := mux .Vars (r )
478
+ id , ok := vars ["id" ]
479
+ if ! ok {
480
+ http .Error (w , "no game ID provided" , http .StatusBadRequest )
481
+ return
482
+ }
483
+ gID := codenames .GameID (id )
484
+
485
+ u , err := s .loadUser (r )
486
+ if err != nil {
487
+ http .Error (w , err .Error (), http .StatusInternalServerError )
488
+ return
489
+ }
490
+
491
+ if u == nil {
492
+ http .Error (w , "Not logged in" , http .StatusUnauthorized )
493
+ return
494
+ }
495
+
496
+ game , err := s .db .Game (gID )
497
+ if err != nil {
498
+ http .Error (w , "failed to load game" , http .StatusInternalServerError )
499
+ return
500
+ }
501
+
502
+ prs , err := s .db .PlayersInGame (gID )
503
+ if err != nil {
504
+ http .Error (w , err .Error (), http .StatusInternalServerError )
505
+ return
506
+ }
507
+
508
+ userPR , ok := findRole (u .ID , prs )
509
+ if ! ok {
510
+ http .Error (w , "You're not in this game" , http .StatusForbidden )
511
+ return
512
+ }
513
+
514
+ handler (w , r , u , game , userPR , prs )
515
+ }
516
+ }
517
+
518
+ func findRole (uID codenames.UserID , prs []* codenames.PlayerRole ) (* codenames.PlayerRole , bool ) {
519
+ for _ , pr := range prs {
520
+ if pr .PlayerID .PlayerType != codenames .PlayerTypeHuman {
521
+ continue
522
+ }
523
+
524
+ if pr .PlayerID .ID == string (uID ) {
525
+ return pr , true
526
+ }
527
+ }
528
+ return nil , false
529
+ }
0 commit comments