~ubuntu-branches/ubuntu/saucy/ricochet/saucy

« back to all changes in this revision

Viewing changes to ricochet-0.1/server-games.5c

  • Committer: Package Import Robot
  • Author(s): Keith Packard
  • Date: 2012-06-11 13:37:57 UTC
  • Revision ID: package-import@ubuntu.com-20120611133757-zn0ukd22vz56ymto
Tags: 0.3
* Improve appearance of board
* Fix user list when removing/adding same user

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
 
}