[Commit] rrserver clients.5c,1.1,1.2 dispatch.5c,1.5,1.6 games.5c,1.3,1.4 main.5c,1.2,1.3 protocol,1.14,1.15 rr.5c,1.1,1.2 send.5c,1.1,1.2 server.5c,1.3,1.4
Keith Packard
commit@keithp.com
Fri May 30 07:47:08 PDT 2003
Committed by: keithp
Update of /local/src/CVS/rrserver
In directory home.keithp.com:/tmp/cvs-serv32219
Modified Files:
clients.5c dispatch.5c games.5c main.5c protocol rr.5c send.5c
server.5c
Log Message:
Add bidding
Index: clients.5c
===================================================================
RCS file: /local/src/CVS/rrserver/clients.5c,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- clients.5c 29 May 2003 17:46:29 -0000 1.1
+++ clients.5c 30 May 2003 06:47:05 -0000 1.2
@@ -22,6 +22,9 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
+autoload Array
+autoload RR
+autoload RR::Send
autoload Server
extend namespace Server {
@@ -29,12 +32,31 @@
(&Client)[0] clients = {};
public void iterate (void (&Client c) f) {
- for (int i = 0; i < dim (clients); i++)
- f (&clients[i]);
+ Array::iterate (&clients, f);
}
public exception no_such_client (file f);
+ public void client_send (&Client c, string fmt, poly args...) {
+ RR::Send::send (c.f, fmt, args...);
+ File::flush (c.f);
+ }
+
+ public void server_send (string fmt, poly args...) {
+ void message_client (&Client o) {
+ client_send (&o, fmt, args...);
+ }
+ Clients::iterate (message_client);
+ }
+
+ public void game_send (&Game g, string fmt, poly args...) {
+ void message_client (&Client o) {
+ if (o.game == (GameRef.game) (&g))
+ client_send (&o, fmt, args...);
+ }
+ Clients::iterate (message_client);
+ }
+
public &Client select (file f) {
exception found (&Client c);
try {
@@ -64,27 +86,18 @@
}
public &Client new (file f) {
- clients = ((&Client)[dim(clients)+1]) {
- [i] = i < dim(clients) ?
- &clients[i] :
- reference ((Client) {
- f = f,
- user = User.none,
- game = GameRef.none,
- score = 0,
- })
- };
+ return Array::append (&clients,
+ reference ((Client) {
+ f = f,
+ user = User.none,
+ game = GameRef.none,
+ score = 0,
+ }));
return &clients[dim(clients)-1];
}
public void dispose (&Client c) {
- bool found = false;
-
- clients = ((&Client)[dim(clients)-1]) {
- [i] = found ? &clients[i+1] : (&clients[i] == &c ?
- (found=true, &clients[i+1]) :
- &clients[i])
- };
+ Array::remove (&clients, &c);
}
public void print (&Client c) {
Index: dispatch.5c
===================================================================
RCS file: /local/src/CVS/rrserver/dispatch.5c,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- dispatch.5c 29 May 2003 17:46:29 -0000 1.5
+++ dispatch.5c 30 May 2003 06:47:05 -0000 1.6
@@ -32,36 +32,16 @@
extend namespace Server {
public namespace Dispatch {
- string server_id = "RicochetServer";
-
- public void set_server_id (string id) { server_id = id; }
-
- void client_locked (file f) {
- &Client c = Clients::new (f);
+
+ void client_locked (file f, &Client c) {
exception notactive ();
exception notingame ();
exception invalidname ();
exception nonameset ();
- void broadcast (string fmt, poly args...) {
- RR::Send::send (stdout, "broadcast: ");
- RR::Send::send (stdout, fmt, args...);
- void message_client (&Client o) {
- if (&o != &c)
- {
- RR::Send::send (o.f, fmt, args...);
- File::flush (o.f);
- }
- }
- Clients::iterate (message_client);
- RR::Send::send (stdout, "done\n");
- }
-
void respond (string fmt, poly args...) {
- RR::Send::send (stdout, "respond: ");
- RR::Send::send (stdout, fmt, args...);
- RR::Send::send (f, fmt, args...);
+ Clients::client_send (&c, fmt, args...);
}
void print_client (&Client o) {
@@ -84,21 +64,35 @@
}
}
+ void assert_user ()
+ {
+ if (c.user == User.none)
+ raise nonameset ();
+ }
+
+ void assert_game ()
+ {
+ if (c.game == GameRef.none)
+ raise notingame ();
+ }
+
void helo (string username) {
if (Clients::find (username) != ClientRef.none)
raise invalidname ();
c.user.username = username;
respond ("HELO %s\n", server_id);
- broadcast ("NOTICE USER %s\n", username);
+ Clients::server_send ("NOTICE USER %s\n", username);
}
void who () {
+ assert_user ();
respond ("WHO");
Clients::iterate (print_client);
respond ("\n");
}
void games() {
+ assert_user ();
respond ("GAMES");
Games::iterate (void func(&Game g) {
respond (" %s", g.name);
@@ -107,6 +101,7 @@
}
void users(string game) {
+ assert_user ();
&Game g = Games::find (game);
respond ("USERS");
Games::iterate_client (&g, print_client_score);
@@ -114,96 +109,75 @@
}
void join (string game) {
+ assert_user ();
&Game g = Games::find (game);
- Games::add_client (&g, &c);
+ Games::add_client (&g, &c, true);
respond ("JOIN\n");
- broadcast ("NOTICE JOIN %s %s\n", g.name, c.user.username);
}
void new(string game) {
+ assert_user ();
&Game g =Games::new (game);
- Games::add_client (&g, &c);
+ Games::add_client (&g, &c, false);
respond ("NEW %s\n", g.name);
- broadcast ("NOTICE NEW %s\n", g.name);
+ Clients::server_send ("NOTICE NEW %s\n", g.name);
}
void show () {
- union switch (c.game) {
- case none:
- raise notingame ();
- break;
- case game g:
- File::fprintf (f, "SHOW \"\n");
- Show::show (f, &g.board);
- File::fprintf (f, "\"\n");
- break;
- }
+ assert_user ();
+ assert_game ();
+ File::fprintf (f, "SHOW \"\n");
+ Show::show (f, &c.game.game.board);
+ File::fprintf (f, "\"\n");
}
void bid (int number) {
+ assert_user ();
+ assert_game ();
+ Games::bid (&c.game.game, &c, number);
respond ("BID\n");
- broadcast ("BID %s %d\n", c.user.username, number);
}
void move (Color color, Direction direction) {
- union switch (c.game) {
- case none:
- raise notingame ();
- case game g:
- Games::move (&g, &c, color, direction);
- respond ("MOVE\n");
- break;
- }
- broadcast ("NOTICE MOVE %C %D\n", color, direction);
+ assert_user ();
+ assert_game ();
+ Games::move (&c.game.game, &c, color, direction);
+ respond ("MOVE %d\n", Games::count (&c.game.game));
}
void undo () {
- union switch (c.game) {
- case none:
- raise notingame ();
- break;
- case game g:
- Games::undo (&g, &c);
- respond ("UNDO\n");
- break;
- }
- broadcast ("NOTICE UNDO\n");
+ assert_user ();
+ assert_game ();
+ Games::undo (&c.game.game, &c);
+ respond ("UNDO\n");
}
void reset () {
- union switch (c.game) {
- case none:
- raise notingame ();
- break;
- case game g:
- Games::reset (&g, &c);
- respond ("RESET\n");
- break;
- }
- broadcast ("NOTICE RESET\n");
+ assert_user ();
+ assert_game ();
+ Games::reset (&c.game.game, &c);
+ respond ("RESET\n");
}
void turn () {
- union switch (c.game) {
- case none:
- raise notingame ();
- case game g:
- if (Games::solved (&g))
- Games::next_turn (&g);
- respond ("TURN\n");
- break;
- }
- broadcast ("NOTICE TURN\n");
+ assert_user ();
+ assert_game ();
+ if (Games::solved (&c.game.game))
+ Games::next_turn (&c.game.game);
+ respond ("TURN\n");
}
void pass () {
+ assert_user ();
+ assert_game ();
}
void message (string text) {
+ assert_user ();
respond ("MESSAGE\n");
string u = (c.user == User.none ? "anonymous" :
c.user.username);
- broadcast ("NOTICE MESSAGE %s %s\n", u, text);
+ Clients::server_send ("NOTICE MESSAGE %s %s\n", u, text);
}
void quit () {
@@ -217,15 +191,7 @@
try {
File::flush (f);
Request r = Readreq::read (f);
- union switch (r) {
- case HELO:
- case QUIT:
- break;
- default:
- if (c.user == User.none)
- raise nonameset ();
- break;
- }
+
union switch (r) {
case HELO h:
helo (h.username);
@@ -296,22 +262,36 @@
respond ("ERROR NONAMESET\n");
} catch invalidname () {
respond ("ERROR INVALIDNAME\n");
+ } catch Games::notbidding (&Game g) {
+ respond ("ERROR NOTBIDDING\n");
+ } catch Games::toomanymoves (&Game g, &Client c) {
+ respond ("ERROR TOOMANYMOVES\n");
+ } catch Games::blocked (&Game g, &Client c) {
+ respond ("ERROR BLOCKED\n");
} catch Readreq::request_closed () {
User user = c.user;
printf ("Client closed %v\n", c.user);
Clients::dispose (&c);
- File::close (f);
if (c.user != User.none)
- broadcast ("NOTICE PART %s\n", c.user.username);
+ Clients::server_send ("NOTICE PART %s\n", c.user.username);
return;
}
}
}
+ void client_cleanup (&Client c) {
+ if (c.game != GameRef.none)
+ Games::remove_client (&c.game.game, &c);
+ Clients::dispose (&c);
+ }
+
public void client (file f)
{
- twixt (lock (); unlock ())
- client_locked (f);
+ twixt (true; File::close (f))
+ twixt (lock (); unlock ())
+ twixt ((&Client c = Clients::new (f)), true;
+ client_cleanup (&c))
+ client_locked (f, &c);
}
}
}
Index: games.5c
===================================================================
RCS file: /local/src/CVS/rrserver/games.5c,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- games.5c 29 May 2003 17:46:29 -0000 1.3
+++ games.5c 30 May 2003 06:47:05 -0000 1.4
@@ -24,7 +24,9 @@
autoload Server
autoload Array
+autoload Timer
autoload Server::Boards
+autoload Server::Clients
extend namespace Server {
public namespace Games {
@@ -43,7 +45,12 @@
}
public exception no_such_game (string name);
-
+ public exception notactive (&Game g, &Client c);
+ public exception notbidding (&Game g);
+ public exception notlower (&Game g, &Client c, int bid);
+ public exception toomanymoves (&Game g, &Client c);
+ public exception blocked (&Game g, &Client c);
+
public &Game find (string name) {
exception found (&Game g);
try {
@@ -70,6 +77,30 @@
return false;
}
+ public void remove_client (&Game g, &Client c) {
+ if (g.active == (ClientRef.client) (&c))
+ g.active = ClientRef.none;
+ Array::remove (&g.clients, &c);
+ }
+
+ public &Client add_client (&Game g, &Client c, bool playing) {
+ if (c.game != GameRef.none)
+ remove_client (&c.game.game, &c);
+ c.game = (GameRef.game) (&g);
+ c.playing = playing;
+ c.score = 0;
+ c.bid = Bid.none;
+ Array::append (&g.clients, &c);
+ Clients::game_send (&g, "NOTICE %s %s %s\n",
+ playing ? "JOIN" : "WATCH",
+ g.name, c.user.username);
+ return &c;
+ }
+
+ public void iterate_client (&Game g, void (&Client c) f) {
+ Array::iterate (&g.clients, f);
+ }
+
Target[*] random_targets () {
static Color[4] colors = {
Color.Red, Color.Yellow, Color.Green, Color.Blue
@@ -97,7 +128,14 @@
g.targets = (Target[dim(g.targets)-1]) { [i] = g.targets[i+1] };
g.history = (ObjectLoc[*]) {};
g.time = Time.none;
+ g.state = GameState.New;
Boards::set_target (&g.board, t.color, t.shape);
+
+ void reset_client (&Client c) {
+ c.bid = Bid.none;
+ }
+ iterate_client (&g, reset_client);
+ Clients::game_send (&g, "NOTICE TURN %C %S\n", t.color, t.shape);
}
public void next_turn (&Game g) {
@@ -117,61 +155,142 @@
g.board = Boards::random_board ();
g.targets = random_targets ();
g.active = ClientRef.none;
+ g.timer_serial = 0;
next_target (&g);
return &g;
}
- public void remove_client (&Game g, &Client c) {
- Array::remove (&g.clients, &c);
- }
-
- public &Client add_client (&Game g, &Client c) {
- if (c.game != GameRef.none)
- remove_client (&c.game.game, &c);
- c.game = (GameRef.game) (&g);
- if (g.active == ClientRef.none)
- g.active = (ClientRef.client) (&c);
- return Array::append (&g.clients, &c);
- }
-
- public void iterate_client (&Game g, void (&Client c) f) {
- Array::iterate (&g.clients, f);
- }
-
public ClientRef active_client (&Game g) {
return g.active;
}
- public exception notactive (&Game g, &Client c);
-
- public bool undo (&Game g, &Client c) {
+ void assert_active (&Game g, &Client c) {
if (g.active != (ClientRef.client) (&c))
raise notactive (&g, &c);
- try {
- ObjectLoc ol = Array::pop (&g.history);
- Boards::position_robot (&g.board, ol.object.robot.robot.color,
- ol.x, ol.y);
- } catch Array::empty (&ObjectLoc[*] a) {
- return false;
+ }
+
+ void undo_move (&Game g)
+ {
+ ObjectLoc ol = Array::pop (&g.history);
+ Boards::position_robot (&g.board, ol.object.robot.robot.color,
+ ol.x, ol.y);
+ }
+
+ public void undo (&Game g, &Client c) {
+ assert_active (&g, &c);
+ if (dim (g.history) > 0)
+ {
+ undo_move (&g);
+ Clients::game_send (&g, "NOTICE UNDO\n");
}
- return true;
}
public void reset (&Game g, &Client c) {
+ assert_active (&g, &c);
while (dim (g.history) > 0)
undo (&g, &c);
+ Clients::game_send (&g, "NOTICE RESEt\n");
}
- public bool move (&Game g, &Client c, Color color, Direction dir) {
- if (g.active != (ClientRef.client) (&c))
- raise notactive (&g, &c);
+ ClientRef lowest_bidder (&Game g) {
+ Bid min = Bid.none;
+ ClientRef min_client = ClientRef.none;
+ void lower_bid (&Client c) {
+ union switch (c.bid) {
+ case none:
+ break;
+ case number n:
+ if (min == Bid.none || min.number > n)
+ {
+ min = (Bid.number) n;
+ min_client = (ClientRef.client) (&c);
+ }
+ break;
+ }
+ }
+ iterate_client (&g, lower_bid);
+ return min_client;
+ }
+
+ void set_active (&Game g) {
+ g.active = lowest_bidder (&g);
+
+ union switch (g.active) {
+ case none:
+ break;
+ case client c:
+ Clients::game_send (&g, "NOTICE ACTIVE %s %d\n",
+ c.user.username, c.bid.number);
+ break;
+ }
+ }
+
+ void set_state (&Game g, GameState state) {
+ g.state = state;
+ Clients::game_send (&g, "NOTICE GAMESTATE %G\n", state);
+ switch (state) {
+ case GameState.Bidding:
+ int timer_serial = ++g.timer_serial;
+
+ bool validate () {
+ if (g.state != GameState.Bidding ||
+ g.timer_serial != timer_serial)
+ return false;
+ return true;
+ }
+
+ void notify (int remain) {
+ Clients::game_send (&g, "NOTICE TIMER %d\n",
+ remain);
+ }
+
+ void expire () {
+ set_state (&g, GameState.Showing);
+ }
+
+ Timer::start (60, 10, lock, unlock, validate, notify, expire);
+ break;
+ case GameState.Showing:
+ set_active (&g);
+ break;
+ }
+ }
+
+ public void bid (&Game g, &Client c, int number) {
+ switch (g.state) {
+ case GameState.New:
+ set_state (&g, GameState.Bidding);
+ case GameState.Bidding:
+ break;
+ case GameState.Showing:
+ case GameState.Solved:
+ raise notbidding (&g);
+ break;
+ }
+ if (c.bid != Bid.none && c.bid.number <= number)
+ raise notlower (&g, &c, number);
+ c.bid = (Bid.number) number;
+ Clients::game_send (&g, "NOTICE BID %s %d\n", c.user.username, number);
+ }
+
+ public int count (&Game g) {
+ return dim (g.history);
+ }
+
+ public void move (&Game g, &Client c, Color color, Direction dir) {
+ assert_active (&g, &c);
+ if (count (&g) >= c.bid.number)
+ raise toomanymoves (&g, &c);
ObjectLoc src = Boards::find_robot (&g.board, color);
ObjectLoc dst = Boards::move_robot (&g.board, color, dir);
if (src == dst)
- return false;
+ raise blocked (&g, &c);
Array::push (&g.history, src);
Boards::position_robot (&g.board, color, dst.x, dst.y);
- return true;
+ Clients::game_send (&g, "NOTICE MOVE %d %C %D\n",
+ Games::count (&g), color, dir);
+ if (Boards::solved (&g.board))
+ set_state (&g, GameState.Solved);
}
public bool solved (&Game g) {
Index: main.5c
===================================================================
RCS file: /local/src/CVS/rrserver/main.5c,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- main.5c 29 May 2003 18:02:52 -0000 1.2
+++ main.5c 30 May 2003 06:47:05 -0000 1.3
@@ -22,12 +22,14 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
+autoload PRNG;
autoload Server::Net
autoload Server::Dispatch
extend namespace Server {
public namespace Main {
public void main () {
+ PRNG::dev_srandom (32);
file f = Net::create (RR::Port);
File::fprintf (stderr, "RR server started on port %d.\n", RR::Port);
Net::listen (Dispatch::client, f);
Index: protocol
===================================================================
RCS file: /local/src/CVS/rrserver/protocol,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- protocol 30 May 2003 04:10:38 -0000 1.14
+++ protocol 30 May 2003 06:47:05 -0000 1.15
@@ -82,12 +82,12 @@
<turn> is a number from 1 to 17 indicating the current turn
<color> <shape> indicate the active piece
<state> is one of:
- NEW Turn just started, no bids yet
- BID Bidding opened. <time> indicates time remaining,
+ new Turn just started, no bids yet
+ bid Bidding opened. <time> indicates time remaining,
<bid> indicates the minimum bid
- SHOW Bidding closed and solution being demonstrated
+ show Bidding closed and solution being demonstrated
<active> indicates the person demonstrating
- SOLVED Solution succesfully demonstrated that
+ solved Solution succesfully demonstrated that
is less than the active users bid. <active>
indicates the winner.
<time> is valid only in BID state, else it's 0
@@ -193,7 +193,7 @@
BID
- Possible errors: NOTINGAME, TIMEOUT, NOTNUMBER
+ Possible errors: NOTINGAME, NOTBIDDING, NOTNUMBER, NOTLOWER
1.12. Move
@@ -201,12 +201,12 @@
->
- MOVE
+ MOVE <count>
<color> is one of 'R', 'Y', 'G' or 'B', <dir> is one of 'N', 'E',
'S' or 'W'.
- Possible errors: NOTINGAME, NOTACTIVE, BLOCKED
+ Possible errors: NOTINGAME, NOTACTIVE, BLOCKED, TOOMANYMOVES
1.13. Undo
@@ -307,7 +307,7 @@
2.1. Move notice (game)
- NOTICE MOVE <color> <dir>
+ NOTICE MOVE <count> <color> <dir>
2.2. New users (all)
@@ -337,10 +337,14 @@
2.8. Select active player (game)
- NOTICE ACTIVE <username>
+ NOTICE ACTIVE <username> <bid>
Only the active player may move the robots
+2.9. Game state change
+
+ NOTICE GAMESTATE <state>
+
2.9. Undo (game)
NOTICE UNDO
@@ -392,15 +396,23 @@
Possibly returned by: SHOW, MOVE, RESET, UNDO, TURN, PASS,
MESSAGE.
-3.2. Time out
+3.2. Not bidding
- ERROR TIMEOUT
+ ERROR NOTBIDDING
- A bid was submitted after the timer expired.
+ A bid was submitted after the bidding closed
Possibly returned by: BID
-3.3. Not active
+3.3. Not lower
+
+ ERROR NOTLOWER
+
+ A bid was submitted that was higher than previous bid.
+
+ Possibly returned by: BID
+
+3.4. Not active
ERROR NOTACTIVE
@@ -409,13 +421,13 @@
Possibly returned by: MOVE, RESET, UNDO
-3.4. Not number
+3.5. Not number
ERROR NOTNUMBER
A non-numeric value was supplied where a number was required
-3.5. Blocked
+3.6. Blocked
ERROR BLOCKED
Index: rr.5c
===================================================================
RCS file: /local/src/CVS/rrserver/rr.5c,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- rr.5c 29 May 2003 06:45:37 -0000 1.1
+++ rr.5c 30 May 2003 06:47:05 -0000 1.2
@@ -62,6 +62,13 @@
Walls walls;
} Object;
+ public typedef enum {
+ New, /* no bids yet */
+ Bidding, /* some bids, timer running */
+ Showing, /* timer expired, waiting for solution */
+ Solved /* game solved */
+ } GameState;
+
public typedef Object[Width,Height] Board;
public typedef union {
Index: send.5c
===================================================================
RCS file: /local/src/CVS/rrserver/send.5c,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- send.5c 29 May 2003 17:46:29 -0000 1.1
+++ send.5c 30 May 2003 06:47:05 -0000 1.2
@@ -27,7 +27,10 @@
extend namespace RR {
public namespace Send {
bool needsquote (string s) {
- for (int i = 0; i < String::length (s); i++)
+ int len = String::length (s);
+ if (len == 0)
+ return true;
+ for (int i = 0; i < len; i++)
if (Ctype::isblank (s[i]) || s[i] == '"' || s[i] == '\\')
return true;
return false;
@@ -86,6 +89,15 @@
}
}
+ void put_gamestate (GameState s) {
+ switch (s) {
+ case GameState.New: File::fprintf (f, "new"); break;
+ case GameState.Bidding: File::fprintf (f, "bid"); break;
+ case GameState.Showing: File::fprintf (f, "show"); break;
+ case GameState.Solved: File::fprintf (f, "solved"); break;
+ }
+ }
+
void put_number (int n) {
File::fprintf (f, "%d", n);
}
@@ -172,6 +184,7 @@
case 'C': put_color (a); break;
case 'D': put_direction (a); break;
case 'S': put_shape (a); break;
+ case 'G': put_gamestate (a); break;
}
}
else
Index: server.5c
===================================================================
RCS file: /local/src/CVS/rrserver/server.5c,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- server.5c 29 May 2003 17:46:29 -0000 1.3
+++ server.5c 30 May 2003 06:47:05 -0000 1.4
@@ -33,14 +33,18 @@
mutex server_mutex = Mutex::new ();
+ string server_id = "RicochetServer";
+
+ public void set_server_id (string id) { server_id = id; }
+
public bool lock () {
Mutex::acquire (server_mutex);
- printf ("lock %v\n", Thread::current ());
+# printf ("lock %v\n", Thread::current());
return true;
}
public bool unlock () {
- printf ("unlock %v\n", Thread::current ());
+# printf ("unlock %v\n", Thread::current());
Mutex::release (server_mutex);
return true;
}
@@ -60,11 +64,18 @@
&Game game;
} GameRef;
+ public typedef union {
+ void none;
+ int number;
+ } Bid;
+
public typedef struct {
file f;
User user;
GameRef game;
int score;
+ Bid bid;
+ bool playing;
} Client;
public typedef union {
@@ -79,12 +90,14 @@
public typedef struct {
string name;
+ GameState state;
(&Client)[*] clients;
Target[*] targets;
Board board;
Time time;
ClientRef active;
ObjectLoc[*] history;
+ int timer_serial;
} Game;
public exception notreached ();
More information about the Commit
mailing list