~ubuntu-branches/ubuntu/trusty/ricochet/trusty

« back to all changes in this revision

Viewing changes to ricochet_0.1.orig/server-games.5c

  • Committer: Package Import Robot
  • Author(s): Keith Packard
  • Date: 2012-05-30 15:19:02 UTC
  • Revision ID: package-import@ubuntu.com-20120530151902-z47w0cxilb0p6wmi
Tags: 0.2
* Rename 'rrclient' to 'ricochet'
* Add name/host selection dialog
* Add build dependency on 'nickle'. Closes: #674302.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * $Id$
 
3
 *
 
4
 * Copyright © 2003 Keith Packard
 
5
 *
 
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.
 
15
 *
 
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.
 
23
 */
 
24
 
 
25
autoload Server
 
26
autoload Array
 
27
autoload Timer
 
28
autoload Server::Boards
 
29
autoload Server::Clients
 
30
 
 
31
extend namespace Server {
 
32
    namespace Track {
 
33
        typedef struct {
 
34
            Color   color;
 
35
            int     x, y;
 
36
        } Pos;
 
37
 
 
38
        public typedef Pos[*] Loc;
 
39
 
 
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) {
 
43
                    loc[i].x = x;
 
44
                    loc[i].y = y;
 
45
                    return;
 
46
                }
 
47
            Array::push (&loc, (Pos) {
 
48
                color = color,
 
49
                x = x,
 
50
                y = y });
 
51
        }
 
52
 
 
53
        public Loc new () {
 
54
            return (Pos[0]) {};
 
55
        }
 
56
 
 
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);
 
60
        }
 
61
    }
 
62
    public namespace Games {
 
63
        /* array of all games */
 
64
        public (&Game)[0]       games = {};
 
65
 
 
66
        &Game insert () {
 
67
            return Array::append (&games, reference ((Game) {}));
 
68
        }
 
69
 
 
70
        void remove (&Game g) {
 
71
            Array::remove (&games, &g);
 
72
        }
 
73
        
 
74
        /* iterate over the available games */
 
75
        public void iterate (void (&Game g) f) {
 
76
            Array::iterate (&games, f);
 
77
        }
 
78
    
 
79
        void assert_active (&Game g, &Client c) {
 
80
            if (g.active != (ClientRef.client) (&c))
 
81
                raise rr_error (Error.NOTACTIVE);
 
82
        }
 
83
 
 
84
        void assert_active_or_done (&Game g, &Client c) {
 
85
            if (g.state == GameState.DONE)
 
86
                return;
 
87
            if (g.state == GameState.SHOW &&
 
88
                g.active == (ClientRef.client) (&c))
 
89
                return;
 
90
            raise rr_error (Error.NOTACTIVE);
 
91
        }
 
92
 
 
93
        void assert_playing (&Game g, &Client c) {
 
94
            if (!c.playing)
 
95
                raise rr_error (Error.NOTPLAYING);
 
96
        }
 
97
 
 
98
        void assert_done (&Game g) {
 
99
            if (g.state == GameState.DONE)
 
100
                return;
 
101
            raise rr_error (Error.NOTDONE);
 
102
        }
 
103
        
 
104
        /* find a game by name */
 
105
        public &Game find (string name) {
 
106
            exception   found (&Game g);
 
107
            try {
 
108
                iterate (void func (&Game g) {
 
109
                    if (g.name == name)
 
110
                        raise found (&g);
 
111
                });
 
112
            } catch found (&Game g) {
 
113
                return &g;
 
114
            }
 
115
            raise rr_error (Error.NOGAME);
 
116
        }
 
117
 
 
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))
 
123
                    f (&c);
 
124
            }
 
125
            Array::iterate (&g.clients, pick);
 
126
        }
 
127
 
 
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...);
 
132
            }
 
133
            iterate_client (&g, message_client, true, true);
 
134
        }
 
135
 
 
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...);
 
140
 
 
141
                void    print_client_score (&Client o) {
 
142
                    Clients::print_client_score (&c, &o);
 
143
                }
 
144
                iterate_client (&g, print_client_score, true, false);
 
145
                Clients::client_send (&c, "\n");
 
146
            }
 
147
            iterate_client (&g, message_client, true, true);
 
148
        }
 
149
 
 
150
        void game_send_loc (&Game g, &Track::Loc loc) {
 
151
            void report_position (Color c, int x, int y)
 
152
            {
 
153
                game_send (&g, "NOTICE POSITION %C %d %d\n", c, x, y);
 
154
            }
 
155
            Track::report (report_position, &loc);
 
156
        }
 
157
 
 
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");
 
164
            }
 
165
            iterate_client (&g, board_client, true, true);
 
166
        }
 
167
        
 
168
        /* does the given game exist? */
 
169
        bool exists (string name) {
 
170
            exception   found (&Game g);
 
171
            try {
 
172
                iterate (void func (&Game g) {
 
173
                    if (g.name == name)
 
174
                        raise found (&g);
 
175
                });
 
176
            } catch found (&Game g) {
 
177
                return true;
 
178
            }
 
179
            return false;
 
180
        }
 
181
 
 
182
        bool any_bids (&Game g) {
 
183
            bool    any = false;
 
184
            void bid_set (&Client c) {
 
185
                if (c.bid != Bid.none)
 
186
                    any = true;
 
187
            }
 
188
            iterate_client (&g, bid_set, true, false);
 
189
            return any;
 
190
        }
 
191
 
 
192
        bool all_abandon (&Game g) {
 
193
            bool    all = true;
 
194
            void abandon_set (&Client c) {
 
195
                if (!c.abandon)
 
196
                    all = false;
 
197
            }
 
198
            iterate_client (&g, abandon_set, true, false);
 
199
            return all;
 
200
        }
 
201
 
 
202
        bool all_nobid (&Game g) {
 
203
            bool    all = true;
 
204
            void nobid_set (&Client c) {
 
205
                if (!c.nobid)
 
206
                    all = false;
 
207
            }
 
208
            iterate_client (&g, nobid_set, true, false);
 
209
            return all;
 
210
        }
 
211
        
 
212
        public ClientRef lowest_bidder (&Game g) {
 
213
            Bid         min = Bid.none;
 
214
            ClientRef   min_client = ClientRef.none;
 
215
            void lower_bid (&Client c) {
 
216
                union switch (c.bid) {
 
217
                case none:
 
218
                    break;
 
219
                case bid b:
 
220
                    if (min == Bid.none || 
 
221
                        min.bid.number > b.number ||
 
222
                        (min.bid.number == b.number && 
 
223
                         min.bid.sequence > b.sequence))
 
224
                    {
 
225
                        min = (Bid.bid) b;
 
226
                        min_client = (ClientRef.client) (&c);
 
227
                    }
 
228
                    break;
 
229
                }
 
230
            }
 
231
            iterate_client (&g, lower_bid, true, false);
 
232
            return min_client;
 
233
        }
 
234
 
 
235
        void set_state (&Game g, GameState state);
 
236
            
 
237
        void set_active (&Game g) {
 
238
            ClientRef   active = lowest_bidder (&g);
 
239
 
 
240
            if (active != g.active) {
 
241
                g.active = active;
 
242
                union switch (active) {
 
243
                case none:
 
244
                    set_state (&g, GameState.DONE);
 
245
                    break;
 
246
                case client c:
 
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",
 
250
                                          c.bid.bid.number);
 
251
                    break;
 
252
                }
 
253
            }
 
254
        }
 
255
 
 
256
        void check_solved (&Game g) {
 
257
            if (g.state != GameState.DONE && Boards::solved (&g.board))
 
258
            {
 
259
                if (g.active != ClientRef.none)
 
260
                {
 
261
                    /* score */
 
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);
 
266
                }
 
267
                set_state (&g, GameState.DONE);
 
268
            }
 
269
        }
 
270
        
 
271
        void set_state (&Game g, GameState state) {
 
272
            if (g.state == state)
 
273
                return;
 
274
            g.state = state;
 
275
            game_send (&g, "NOTICE GAMESTATE %G\n", state);
 
276
            switch (state) {
 
277
            case GameState.BID:
 
278
                int timer_serial = ++g.timer_serial;
 
279
                
 
280
                bool validate () {
 
281
                    if (g.state != GameState.BID || 
 
282
                        g.timer_serial != timer_serial)
 
283
                            return false;
 
284
                    return true;
 
285
                }
 
286
                
 
287
                void notify (int remain) {
 
288
                    game_send (&g, "NOTICE TIMER %d\n", remain);
 
289
                }
 
290
 
 
291
                void expire () {
 
292
                    set_state (&g, GameState.SHOW);
 
293
                }
 
294
 
 
295
                g.expire_time = Timer::start (g.expire_interval, 10, 
 
296
                                              lock, unlock, 
 
297
                                              validate, notify, expire);
 
298
                break;
 
299
            case GameState.SHOW:
 
300
                g.active = ClientRef.none;
 
301
                set_active (&g);
 
302
                check_solved (&g);
 
303
                break;
 
304
            case GameState.DONE:
 
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)
 
311
                };
 
312
                break;
 
313
            }
 
314
        }
 
315
 
 
316
        void undo_move (&Game g, &Track::Loc loc)
 
317
        {
 
318
            ObjectLoc ol = Array::pop (&g.history);
 
319
            Boards::position_robot (&g.board, ol.object.robot.robot.color,
 
320
                                    ol.x, ol.y);
 
321
            Track::note (&loc, ol.object.robot.robot.color,
 
322
                         ol.x, ol.y);
 
323
        }
 
324
 
 
325
        void reset_move (&Game g, &Track::Loc loc)
 
326
        {
 
327
            if (dim (g.history) > 0)
 
328
            {
 
329
                while (dim (g.history) > 0)
 
330
                    undo_move (&g, &loc);
 
331
            }
 
332
        }
 
333
        
 
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);
 
337
            if (src == dst)
 
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);
 
342
        }
 
343
        
 
344
        void next_game (&Game g);
 
345
 
 
346
        /* select the next target */
 
347
        void next_target (&Game g) {
 
348
 
 
349
            if (dim(g.targets) == 0)
 
350
            {
 
351
                next_game (&g);
 
352
                return;
 
353
            }
 
354
 
 
355
            /*
 
356
             * Move robots to finish positions.  First, remove them from the
 
357
             * board
 
358
             */
 
359
            Track::Loc  loc = Track::new();
 
360
            for (int i = 0; i < dim (g.done_robots); i++)
 
361
            {
 
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);
 
366
                
 
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);
 
370
            }
 
371
 
 
372
            /*
 
373
             * Now reposition them in the new spots
 
374
             */
 
375
            for (int i = 0; i < dim (g.done_robots); i++)
 
376
            {
 
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;
 
380
                
 
381
                g.board[x,y].robot = g.done_robots[i].object.robot;
 
382
            }
 
383
            
 
384
            g.target = g.targets[0];
 
385
            g.targets = (Target[dim(g.targets)-1]) { [i] = g.targets[i+1] };
 
386
            g.history = (ObjectLoc[*]) {};
 
387
            g.expire_time = 0;
 
388
            g.bid_sequence = 0;
 
389
            Boards::set_target (&g.board, g.target.color, g.target.shape);
 
390
 
 
391
            void reset_client (&Client c) {
 
392
                c.bid = Bid.none;
 
393
                c.abandon = false;
 
394
                c.nobid = false;
 
395
            }
 
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);
 
401
        }
 
402
 
 
403
        Target[*] random_targets () {
 
404
            static Color[4]    colors = {
 
405
                Color.Red, Color.Yellow, Color.Green, Color.Blue
 
406
            };
 
407
            static Shape[4]    shapes = {
 
408
                Shape.Triangle, Shape.Square, Shape.Octagon, Shape.Circle
 
409
            };
 
410
            Target[17] t = { [i] = i < 16 ? 
 
411
                (Target) { 
 
412
                    color = colors[i // 4], 
 
413
                    shape = shapes[i % 4],
 
414
                    active = false
 
415
                } :
 
416
                (Target) {
 
417
                    color = Color.Whirl,
 
418
                    shape = Shape.Whirl,
 
419
                    active = false
 
420
                } };
 
421
            Shuffle::shuffle (&t);
 
422
            return t;
 
423
        }
 
424
            
 
425
        void init (&Game g) {
 
426
            g.board = Boards::random_board ();
 
427
            g.targets = random_targets ();
 
428
            g.active = ClientRef.none;
 
429
            g.timer_serial = 0;
 
430
            g.done_robots = (ObjectLoc[0]) {};
 
431
            game_send_board (&g, "NOTICE BOARD");
 
432
            next_target (&g);
 
433
        }
 
434
        
 
435
        void next_game (&Game g) {
 
436
            printf ("next_game\n");
 
437
            ClientRef   winner = ClientRef.none;
 
438
            void find_winner (&Client c) {
 
439
                if (c.score > 0 &&
 
440
                    (winner == ClientRef.none || c.score >
 
441
                    winner.client.score))
 
442
                    winner = (ClientRef.client) (&c);
 
443
                c.score = 0;
 
444
            }
 
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");
 
449
            init (&g);
 
450
        }
 
451
        
 
452
        void assert_bidding (&Game g) {
 
453
            switch (g.state) {
 
454
            case GameState.NEW:
 
455
            case GameState.BID:
 
456
                break;
 
457
            case GameState.SHOW:
 
458
            case GameState.DONE:
 
459
                raise rr_error (Error.NOTBIDDING);
 
460
                break;
 
461
            }
 
462
            
 
463
        }
 
464
        
 
465
        public int count (&Game g) {
 
466
            return dim (g.history);
 
467
        }
 
468
 
 
469
        /*
 
470
         * Game management commands
 
471
         */
 
472
 
 
473
        public &Game new (string suggestion) {
 
474
            string name;
 
475
            for (int n = 0; 
 
476
                 exists (name = (n != 0) ? 
 
477
                         sprintf ("%s-%d", suggestion, n) : suggestion);
 
478
                 n++)
 
479
                 ;
 
480
            &Game g = &insert ();
 
481
            g.name = name;
 
482
            g.clients = ((&Client)[*]) {};
 
483
            g.expire_interval = 60;
 
484
            init (&g);
 
485
            Clients::server_send ("NOTICE GAME %s\n", g.name);
 
486
            return &g;
 
487
        }
 
488
 
 
489
        public void dispose (&Game g) {
 
490
            void bail (&Client c) {
 
491
                raise rr_error (Error.NOTEMPTY);
 
492
            }
 
493
            iterate_client (&g, bail, true, false);
 
494
            remove (&g);
 
495
            Clients::server_send ("NOTICE DISPOSE %s\n", g.name);
 
496
        }
 
497
 
 
498
        /* remove a client from any game */
 
499
        public void remove_client (&Client c) {
 
500
            if (c.game == GameRef.none)
 
501
                return;
 
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 */
 
507
            switch (g.state) {
 
508
            case GameState.NEW:
 
509
                break;
 
510
            case GameState.BID:
 
511
                if (!any_bids (&g))
 
512
                    set_state (&g, GameState.NEW);
 
513
                else if (all_nobid (&g))
 
514
                    set_state (&g, GameState.SHOW);
 
515
                break;
 
516
            case GameState.SHOW:
 
517
                set_active (&g);
 
518
                break;
 
519
            case GameState.DONE:
 
520
                break;
 
521
            }
 
522
        }
 
523
 
 
524
        /* add a client to a game */
 
525
        public &Client add_client (&Game g, &Client c, bool playing) {
 
526
            remove_client (&c);
 
527
            c.game = (GameRef.game) (&g);
 
528
            c.playing = playing;
 
529
            c.score = 0;
 
530
            c.bid = Bid.none;
 
531
            c.abandon = false;
 
532
            c.nobid = false;
 
533
            Array::append (&g.clients, &c);
 
534
            Clients::server_send ("NOTICE %s %s %s\n", 
 
535
                                  playing ? "JOIN" : "WATCH",
 
536
                                  c.user.username,
 
537
                                  g.name);
 
538
            return &c;
 
539
        }
 
540
 
 
541
        /*
 
542
         * BID state commands
 
543
         */
 
544
        
 
545
        public void bid (&Game g, &Client c, int number) {
 
546
            assert_playing (&g, &c);
 
547
            assert_bidding (&g);
 
548
            if (g.state == GameState.NEW)
 
549
                set_state (&g, GameState.BID);
 
550
            /*
 
551
            if (c.bid != Bid.none && c.bid.bid.number <= number)
 
552
                raise rr_error (Error.NOTLOWER);
 
553
             */
 
554
            c.bid = (Bid.bid) (BidValue) { 
 
555
                number = number, 
 
556
                sequence = g.bid_sequence++
 
557
            };
 
558
            game_send (&g, "NOTICE BID %s %d\n", c.user.username, number);
 
559
        }
 
560
 
 
561
        public void revoke (&Game g, &Client c) {
 
562
            assert_playing (&g, &c);
 
563
            assert_bidding (&g);
 
564
            if (c.bid == Bid.none)
 
565
                raise rr_error (Error.NOBID);
 
566
            c.bid = Bid.none;
 
567
            game_send (&g, "NOTICE REVOKE %s\n", c.user.username);
 
568
            if (!any_bids(&g))
 
569
                set_state (&g, GameState.NEW);
 
570
        }
 
571
 
 
572
        public void abandon (&Game g, &Client c) {
 
573
            assert_playing (&g, &c);
 
574
            assert_bidding (&g);
 
575
            if (!c.abandon) {
 
576
                c.abandon = true;
 
577
                game_send (&g, "NOTICE ABANDON %s\n", c.user.username);
 
578
                if (c.bid != Bid.none)
 
579
                    revoke (&g, &c);
 
580
                if (all_abandon (&g))
 
581
                    set_state (&g, GameState.DONE);
 
582
            }
 
583
        }
 
584
 
 
585
        public void nobid (&Game g, &Client c) {
 
586
            assert_playing (&g, &c);
 
587
            assert_bidding (&g);
 
588
            if (!c.nobid) {
 
589
                c.nobid = true;
 
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);
 
593
            }
 
594
        }
 
595
        
 
596
        /*
 
597
         * MOVE state commands
 
598
         */
 
599
        
 
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)
 
604
            {
 
605
                Track::Loc loc = Track::new ();
 
606
                undo_move (&g, &loc);
 
607
                game_send (&g, "NOTICE UNDO\n");
 
608
                game_send_loc (&g, &loc);
 
609
            }
 
610
        }
 
611
 
 
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);
 
619
        }
 
620
 
 
621
        public void pass (&Game g, &Client c) {
 
622
            assert_playing (&g, &c);
 
623
            assert_active (&g, &c);
 
624
            reset (&g, &c);
 
625
            c.bid = Bid.none;
 
626
            set_active (&g);
 
627
        }
 
628
        
 
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);
 
639
            check_solved (&g);
 
640
        }
 
641
 
 
642
        /*
 
643
         * DONE state command
 
644
         */
 
645
        
 
646
        public void turn (&Game g, &Client c) {
 
647
            assert_playing (&g, &c);
 
648
            assert_done (&g);
 
649
            next_target (&g);
 
650
        }
 
651
    }
 
652
}