4
* Copyright © 2003 Keith Packard
6
* Permission to use, copy, modify, distribute, and sell this software and its
7
* documentation for any purpose is hereby granted without fee, provided that
8
* the above copyright notice appear in all copies and that both that
9
* copyright notice and this permission notice appear in supporting
10
* documentation, and that the name of Keith Packard not be used in
11
* advertising or publicity pertaining to distribution of the software without
12
* specific, written prior permission. Keith Packard makes no
13
* representations about the suitability of this software for any purpose. It
14
* is provided "as is" without express or implied warranty.
16
* KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
18
* EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
19
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
20
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
21
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
22
* PERFORMANCE OF THIS SOFTWARE.
28
autoload Server::Boards
29
autoload Server::Clients
31
extend namespace Server {
38
public typedef Pos[*] Loc;
40
public void note (&Loc loc, Color color, int x, int y) {
41
for (int i = 0; i < dim (loc); i++)
42
if (loc[i].color == color) {
47
Array::push (&loc, (Pos) {
57
public void report (void (Color c, int x, int y) call, &Loc loc) {
58
for (int i = 0; i < dim (loc); i++)
59
call (loc[i].color, loc[i].x, loc[i].y);
62
public namespace Games {
63
/* array of all games */
64
public (&Game)[0] games = {};
67
return Array::append (&games, reference ((Game) {}));
70
void remove (&Game g) {
71
Array::remove (&games, &g);
74
/* iterate over the available games */
75
public void iterate (void (&Game g) f) {
76
Array::iterate (&games, f);
79
void assert_active (&Game g, &Client c) {
80
if (g.active != (ClientRef.client) (&c))
81
raise rr_error (Error.NOTACTIVE);
84
void assert_active_or_done (&Game g, &Client c) {
85
if (g.state == GameState.DONE)
87
if (g.state == GameState.SHOW &&
88
g.active == (ClientRef.client) (&c))
90
raise rr_error (Error.NOTACTIVE);
93
void assert_playing (&Game g, &Client c) {
95
raise rr_error (Error.NOTPLAYING);
98
void assert_done (&Game g) {
99
if (g.state == GameState.DONE)
101
raise rr_error (Error.NOTDONE);
104
/* find a game by name */
105
public &Game find (string name) {
106
exception found (&Game g);
108
iterate (void func (&Game g) {
112
} catch found (&Game g) {
115
raise rr_error (Error.NOGAME);
118
/* list clients associated with a game */
119
public void iterate_client (&Game g, void (&Client c) f,
120
bool playing, bool watching) {
121
void pick (&Client c) {
122
if ((playing && c.playing) || (watching && !c.playing))
125
Array::iterate (&g.clients, pick);
128
/* broadcast message to all game users */
129
void game_send (&Game g, string fmt, poly args...) {
130
void message_client (&Client o) {
131
Clients::client_send (&o, fmt, args...);
133
iterate_client (&g, message_client, true, true);
136
/* broadcast message including players and scores to all game users */
137
void game_send_client_scores (&Game g, string fmt, poly args...) {
138
void message_client (&Client c) {
139
Clients::client_send (&c, fmt, args...);
141
void print_client_score (&Client o) {
142
Clients::print_client_score (&c, &o);
144
iterate_client (&g, print_client_score, true, false);
145
Clients::client_send (&c, "\n");
147
iterate_client (&g, message_client, true, true);
150
void game_send_loc (&Game g, &Track::Loc loc) {
151
void report_position (Color c, int x, int y)
153
game_send (&g, "NOTICE POSITION %C %d %d\n", c, x, y);
155
Track::report (report_position, &loc);
158
void game_send_board (&Game g, string fmt, poly args...) {
159
void board_client (&Client c) {
160
Clients::client_send (&c, fmt, args);
161
File::fprintf (c.f, " \"\n");
162
Show::show (c.f, &g.board);
163
File::fprintf (c.f, "\"\n");
165
iterate_client (&g, board_client, true, true);
168
/* does the given game exist? */
169
bool exists (string name) {
170
exception found (&Game g);
172
iterate (void func (&Game g) {
176
} catch found (&Game g) {
182
bool any_bids (&Game g) {
184
void bid_set (&Client c) {
185
if (c.bid != Bid.none)
188
iterate_client (&g, bid_set, true, false);
192
bool all_abandon (&Game g) {
194
void abandon_set (&Client c) {
198
iterate_client (&g, abandon_set, true, false);
202
bool all_nobid (&Game g) {
204
void nobid_set (&Client c) {
208
iterate_client (&g, nobid_set, true, false);
212
public ClientRef lowest_bidder (&Game g) {
214
ClientRef min_client = ClientRef.none;
215
void lower_bid (&Client c) {
216
union switch (c.bid) {
220
if (min == Bid.none ||
221
min.bid.number > b.number ||
222
(min.bid.number == b.number &&
223
min.bid.sequence > b.sequence))
226
min_client = (ClientRef.client) (&c);
231
iterate_client (&g, lower_bid, true, false);
235
void set_state (&Game g, GameState state);
237
void set_active (&Game g) {
238
ClientRef active = lowest_bidder (&g);
240
if (active != g.active) {
242
union switch (active) {
244
set_state (&g, GameState.DONE);
247
game_send (&g, "NOTICE ACTIVE %s %d\n",
248
c.user.username, c.bid.bid.number);
249
Clients::client_send (&c, "NOTICE ACTIVATE %d\n",
256
void check_solved (&Game g) {
257
if (g.state != GameState.DONE && Boards::solved (&g.board))
259
if (g.active != ClientRef.none)
262
g.active.client.score++;
263
game_send (&g, "NOTICE SCORE %s %d\n",
264
g.active.client.user.username,
265
g.active.client.score);
267
set_state (&g, GameState.DONE);
271
void set_state (&Game g, GameState state) {
272
if (g.state == state)
275
game_send (&g, "NOTICE GAMESTATE %G\n", state);
278
int timer_serial = ++g.timer_serial;
281
if (g.state != GameState.BID ||
282
g.timer_serial != timer_serial)
287
void notify (int remain) {
288
game_send (&g, "NOTICE TIMER %d\n", remain);
292
set_state (&g, GameState.SHOW);
295
g.expire_time = Timer::start (g.expire_interval, 10,
297
validate, notify, expire);
300
g.active = ClientRef.none;
305
g.active = ClientRef.none;
306
g.done_robots = (ObjectLoc[4]) {
307
Boards::find_robot (&g.board, Color.Red),
308
Boards::find_robot (&g.board, Color.Yellow),
309
Boards::find_robot (&g.board, Color.Green),
310
Boards::find_robot (&g.board, Color.Blue)
316
void undo_move (&Game g, &Track::Loc loc)
318
ObjectLoc ol = Array::pop (&g.history);
319
Boards::position_robot (&g.board, ol.object.robot.robot.color,
321
Track::note (&loc, ol.object.robot.robot.color,
325
void reset_move (&Game g, &Track::Loc loc)
327
if (dim (g.history) > 0)
329
while (dim (g.history) > 0)
330
undo_move (&g, &loc);
334
void make_move (&Game g, &Track::Loc loc, Color color, Direction dir) {
335
ObjectLoc src = Boards::find_robot (&g.board, color);
336
ObjectLoc dst = Boards::move_robot (&g.board, color, dir);
338
raise rr_error (Error.BLOCKED);
339
Array::push (&g.history, src);
340
Track::note (&loc, color, dst.x, dst.y);
341
Boards::position_robot (&g.board, color, dst.x, dst.y);
344
void next_game (&Game g);
346
/* select the next target */
347
void next_target (&Game g) {
349
if (dim(g.targets) == 0)
356
* Move robots to finish positions. First, remove them from the
359
Track::Loc loc = Track::new();
360
for (int i = 0; i < dim (g.done_robots); i++)
362
Color color = g.done_robots[i].object.robot.robot.color;
363
int x = g.done_robots[i].x;
364
int y = g.done_robots[i].y;
365
ObjectLoc now = Boards::find_robot (&g.board, color);
367
g.board[now.x,now.y].robot = RobotOrNone.none;
368
if (now.x != x || now.y != y)
369
Track::note (&loc, color, x, y);
373
* Now reposition them in the new spots
375
for (int i = 0; i < dim (g.done_robots); i++)
377
Color color = g.done_robots[i].object.robot.robot.color;
378
int x = g.done_robots[i].x;
379
int y = g.done_robots[i].y;
381
g.board[x,y].robot = g.done_robots[i].object.robot;
384
g.target = g.targets[0];
385
g.targets = (Target[dim(g.targets)-1]) { [i] = g.targets[i+1] };
386
g.history = (ObjectLoc[*]) {};
389
Boards::set_target (&g.board, g.target.color, g.target.shape);
391
void reset_client (&Client c) {
396
iterate_client (&g, reset_client, true, false);
397
game_send_loc (&g, &loc);
398
game_send (&g, "NOTICE TURN %C %S\n", g.target.color, g.target.shape);
399
g.state = GameState.DONE;
400
set_state(&g, GameState.NEW);
403
Target[*] random_targets () {
404
static Color[4] colors = {
405
Color.Red, Color.Yellow, Color.Green, Color.Blue
407
static Shape[4] shapes = {
408
Shape.Triangle, Shape.Square, Shape.Octagon, Shape.Circle
410
Target[17] t = { [i] = i < 16 ?
412
color = colors[i // 4],
413
shape = shapes[i % 4],
421
Shuffle::shuffle (&t);
425
void init (&Game g) {
426
g.board = Boards::random_board ();
427
g.targets = random_targets ();
428
g.active = ClientRef.none;
430
g.done_robots = (ObjectLoc[0]) {};
431
game_send_board (&g, "NOTICE BOARD");
435
void next_game (&Game g) {
436
printf ("next_game\n");
437
ClientRef winner = ClientRef.none;
438
void find_winner (&Client c) {
440
(winner == ClientRef.none || c.score >
441
winner.client.score))
442
winner = (ClientRef.client) (&c);
445
iterate_client (&g, find_winner, true, false);
446
if (winner != ClientRef.none)
447
winner.client.games++;
448
game_send_client_scores (&g, "NOTICE GAMEOVER");
452
void assert_bidding (&Game g) {
459
raise rr_error (Error.NOTBIDDING);
465
public int count (&Game g) {
466
return dim (g.history);
470
* Game management commands
473
public &Game new (string suggestion) {
476
exists (name = (n != 0) ?
477
sprintf ("%s-%d", suggestion, n) : suggestion);
480
&Game g = &insert ();
482
g.clients = ((&Client)[*]) {};
483
g.expire_interval = 60;
485
Clients::server_send ("NOTICE GAME %s\n", g.name);
489
public void dispose (&Game g) {
490
void bail (&Client c) {
491
raise rr_error (Error.NOTEMPTY);
493
iterate_client (&g, bail, true, false);
495
Clients::server_send ("NOTICE DISPOSE %s\n", g.name);
498
/* remove a client from any game */
499
public void remove_client (&Client c) {
500
if (c.game == GameRef.none)
502
&Game g = &c.game.game;
503
Array::remove (&g.clients, &c);
504
c.game = GameRef.none;
505
Clients::server_send ("NOTICE PART %s %s\n", c.user.username, g.name);
506
/* correct the state if necessary */
512
set_state (&g, GameState.NEW);
513
else if (all_nobid (&g))
514
set_state (&g, GameState.SHOW);
524
/* add a client to a game */
525
public &Client add_client (&Game g, &Client c, bool playing) {
527
c.game = (GameRef.game) (&g);
533
Array::append (&g.clients, &c);
534
Clients::server_send ("NOTICE %s %s %s\n",
535
playing ? "JOIN" : "WATCH",
545
public void bid (&Game g, &Client c, int number) {
546
assert_playing (&g, &c);
548
if (g.state == GameState.NEW)
549
set_state (&g, GameState.BID);
551
if (c.bid != Bid.none && c.bid.bid.number <= number)
552
raise rr_error (Error.NOTLOWER);
554
c.bid = (Bid.bid) (BidValue) {
556
sequence = g.bid_sequence++
558
game_send (&g, "NOTICE BID %s %d\n", c.user.username, number);
561
public void revoke (&Game g, &Client c) {
562
assert_playing (&g, &c);
564
if (c.bid == Bid.none)
565
raise rr_error (Error.NOBID);
567
game_send (&g, "NOTICE REVOKE %s\n", c.user.username);
569
set_state (&g, GameState.NEW);
572
public void abandon (&Game g, &Client c) {
573
assert_playing (&g, &c);
577
game_send (&g, "NOTICE ABANDON %s\n", c.user.username);
578
if (c.bid != Bid.none)
580
if (all_abandon (&g))
581
set_state (&g, GameState.DONE);
585
public void nobid (&Game g, &Client c) {
586
assert_playing (&g, &c);
590
game_send (&g, "NOTICE NOBID %s\n", c.user.username);
591
if (any_bids (&g) && all_nobid (&g))
592
set_state (&g, GameState.SHOW);
597
* MOVE state commands
600
public void undo (&Game g, &Client c) {
601
assert_playing (&g, &c);
602
assert_active_or_done (&g, &c);
603
if (dim (g.history) > 0)
605
Track::Loc loc = Track::new ();
606
undo_move (&g, &loc);
607
game_send (&g, "NOTICE UNDO\n");
608
game_send_loc (&g, &loc);
612
public void reset (&Game g, &Client c) {
613
assert_playing (&g, &c);
614
assert_active_or_done (&g, &c);
615
Track::Loc loc = Track::new();
616
reset_move (&g, &loc);
617
game_send (&g, "NOTICE RESET\n");
618
game_send_loc (&g, &loc);
621
public void pass (&Game g, &Client c) {
622
assert_playing (&g, &c);
623
assert_active (&g, &c);
629
public void move (&Game g, &Client c, Color color, Direction dir) {
630
assert_playing (&g, &c);
631
assert_active_or_done (&g, &c);
632
if (g.state == GameState.SHOW &&
633
count (&g) >= c.bid.bid.number)
634
raise rr_error (Error.TOOMANYMOVES);
635
Track::Loc loc = Track::new();
636
make_move (&g, &loc, color, dir);
637
game_send (&g, "NOTICE MOVE %d %C %D\n", count (&g), color, dir);
638
game_send_loc (&g, &loc);
646
public void turn (&Game g, &Client c) {
647
assert_playing (&g, &c);