mj-1.17-src/0000755006717300001440000000000015002771312011323 5ustar jcbusersmj-1.17-src/gtkrc.h0000444006717300001440000001071315002771311012605 0ustar jcbusers/* the minimal style definitions required for functionality */ static char *gtkrc_minimal_styles = "style \"table\" {\n" " bg[NORMAL] = \"darkgreen\"\n" "}\n" "style \"playerlabel\" {\n" " fg[NORMAL] = \"white\"\n" "}\n" "\n" "style \"claim\" {\n" " bg[NORMAL] = \"yellow\"\n" " font_name = \"Sans Bold 20px\"\n" "}\n" "style \"thinking\" {\n" " fg[NORMAL] = \"yellow\"\n" " font_name = \"Sans Bold 20px\"\n" "}\n" "\n" "binding \"topwindow\" {\n" " bind \"Left\" {\n" " \"selectleft\"()\n" " }\n" " bind \"Right\" {\n" " \"selectright\"()\n" " }\n" " bind \"Left\" {" "\n" " \"moveleft\"()" "\n" " }" "\n" " bind \"Right\" {" "\n" " \"moveright\"()" "\n" " }" "\n" "}\n" "\n" "style \"text\" {\n" "}\n" "widget \"*.table\" style \"table\"" "\n" "widget \"*.claim\" style \"claim\"" "\n" "widget \"*.think\" style \"thinking\"\n" "widget \"topwindow\" binding \"topwindow\"" "\n" "widget \"*.GtkTextView*\" style \"text\"" "\n" "widget \"*.GtkEntry*\" style \"text\"" "\n" "widget \"*.playerlabel\" style \"playerlabel\"" "\n" ; /* style settings when no clearlooks is available. Supposed to look more or less like the GTK1 default. */ static char *gtkrc_plain_styles = #ifdef WIN32 "gtk-font-name = \"Sans 12px\"\n" #else "gtk-font-name = \"Verdana Condensed 12px\"\n" #endif "\n" "style \"default\" { \n" " bg[NORMAL] = \"#CCC\"\n" " fg[NORMAL] = \"#000\"\n" " bg[PRELIGHT] = \"#EEE\"\n" " fg[PRELIGHT] = \"#000\"\n" " bg[ACTIVE] = \"#AAA\"\n" " fg[ACTIVE] = \"#000\"\n" " bg[SELECTED] = \"#228\"\n" " fg[SELECTED] = \"#FFF\"\n" " bg[INSENSITIVE] = \"#888\"\n" " fg[INSENSITIVE] = \"#444\"\n" " xthickness = 2\n" " ythickness = 2\n" "}\n" "\n" "widget \"*\" style \"default\"\n" "\n" "style \"table\" {\n" " bg[NORMAL] = \"darkgreen\"\n" "}\n" "style \"playerlabel\" {\n" " fg[NORMAL] = \"white\"\n" "}\n" "\n" "style \"tile\" {\n" " bg[NORMAL] = \"white\"\n" " fg[NORMAL] = \"black\"\n" " xthickness = 1\n" " ythickness = 1\n" " GtkButton::inner-border = { 0, 0, 0, 0 }\n" "}\n" "\n" "style \"mytile\" {\n" " bg[NORMAL] = \"white\"\n" " bg[PRELIGHT] = \"yellow\"\n" " bg[ACTIVE] = \"magenta\"\n" " GtkButton::inner-border = { 0, 0, 0, 0 }\n" " xthickness = 1\n" " ythickness = 1\n" "}\n" "\n" "style \"claim\" {\n" " bg[NORMAL] = \"yellow\"\n" " font_name = \"Sans Bold 20px\"\n" "}\n" "style \"thinking\" {\n" " fg[NORMAL] = \"yellow\"\n" " font_name = \"Sans Bold 20px\"\n" "}\n" "\n" "binding \"topwindow\" {\n" " bind \"Left\" {\n" " \"selectleft\"()\n" " }\n" " bind \"Right\" {\n" " \"selectright\"()\n" " }\n" " bind \"Left\" {" "\n" " \"moveleft\"()" "\n" " }" "\n" " bind \"Right\" {" "\n" " \"moveright\"()" "\n" " }" "\n" "}\n" "\n" "style \"text\" {\n" " font_name = \"Courier New 16px\"\n" "}\n" ; /* style settings when clearlooks is available */ static char *gtkrc_clearlooks_styles = "########### from here on is overrides to the Clearlooks theme." "\n" "" "\n" #ifdef WIN32 "gtk-font-name = \"Sans 12px\"\n" #else "gtk-font-name = \"Verdana Condensed 12px\"\n" #endif "" "\n" "style \"table\" {" "\n" " bg[NORMAL] = \"darkgreen\"" "\n" "}" "\n" "style \"playerlabel\" {\n" " fg[NORMAL] = \"white\"\n" "}\n" "" "\n" "style \"tile\" {" "\n" " bg[NORMAL] = \"white\"" "\n" " xthickness = 0" "\n" " ythickness = 0" "\n" "}" "\n" "" "\n" "style \"mytile\" {" "\n" " bg[NORMAL] = \"white\"" "\n" " bg[PRELIGHT] = \"yellow\"" "\n" " bg[ACTIVE] = \"magenta\"" "\n" " xthickness = 1" "\n" " ythickness = 1" "\n" "}" "\n" "" "\n" "style \"claim\" {" "\n" " bg[NORMAL] = \"yellow\"" "\n" " font_name = \"Sans Bold 20px\"" "\n" "}" "\n" "style \"thinking\" {" "\n" " fg[NORMAL] = \"yellow\"" "\n" " font_name = \"Sans Bold 20px\"" "\n" "}" "\n" "" "\n" "binding \"topwindow\" {" "\n" " bind \"Left\" {" "\n" " \"selectleft\"()" "\n" " }" "\n" " bind \"Right\" {" "\n" " \"selectright\"()" "\n" " }" "\n" " bind \"Left\" {" "\n" " \"moveleft\"()" "\n" " }" "\n" " bind \"Right\" {" "\n" " \"moveright\"()" "\n" " }" "\n" "}" "\n" "" "\n" "style \"text\" {" "\n" " font_name = \"Courier New 16px\"" "\n" "}" "\n" ; static char *gtkrc_apply_styles = "widget \"*.table\" style \"table\"" "\n" "widget \"*.tile\" style \"tile\"" "\n" "widget \"*.mytile\" style \"mytile\"" "\n" "widget \"*.claim\" style \"claim\"" "\n" "widget \"*.think\" style \"thinking\"" "\n" "widget \"topwindow\" binding \"topwindow\"" "\n" "widget \"*.GtkTextView*\" style \"text\"" "\n" "widget \"*.GtkEntry*\" style \"text\"" "\n" "widget \"*.playerlabel\" style \"playerlabel\"" "\n" ; mj-1.17-src/game.h0000444006717300001440000003445315002771311012413 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/game.h,v 12.1 2012/02/01 14:10:41 jcb Rel $ * game.h * This file contains the game data structure and associated definitions, * and prototypes for some functions that work on it. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #ifndef GAME_H_INCLUDED #define GAME_H_INCLUDED 1 #include "tiles.h" #include "player.h" /* At this point, we should have option definitions. However, they're needed by the protocol, and therefore included in protocol.h, not this file. */ #include "protocol.h" /* A game is a finite state machine. This type gives the states. */ typedef enum { HandComplete = 0, /* a hand has been finished, scored, and scores settled, but the deal has not yet rotated. The player member contains the seat of the player who went mah jong, or -1 if the hand was drawn. This is also the initial state (with seat -1) after a Game message is handled. The claims array is used to note which players have requested to start the next hand. */ Dealing = 1, /* the deal is in progress */ DeclaringSpecials = 2, /* the deal is complete, and we are declaring flowers and seasons. The player member contains the seat (NB) of the player due to declare */ Discarding = 3, /* the current player is due to discard. The player member contains the current player as a seat. The tile member notes the tile that was drawn. The whence member tells where the extra tile obtained by the player came from: FromWall, FromDiscard, FromLoose, FromRobbedKong. If it was FromDiscard, the supplier holds the *seat* of the discarder. This is needed for scoring systems that punish such people. The needs records if the player needs to draw another tile, because of, say, declaring specials or kongs. It is FromNone, FromWall, or FromLoose. The konging records if the player has just melded to a pung (or declared a concealed kong) which is potentially robbable. It is NotKonging (unset), AddingToPung, DeclaringKong; if it is set, then tile is the tile of the kong. The claims record may contain MahJongClaims to indicate a desire to rob the kong. */ Discarded = 4, /* the current player has just discarded; other players are making claims. The info record contains the following members: player the player that has discarded tile the tile that was discarded serial serial number of this discard claims[seats] contains Discard/Chow/Pung/Kong/MahJong/NoClaim accordingly, or 0 if no claim has yet been received. cpos the ChowPosition, if the right hand player has made a chow claim chowpending true if an unspecified chow claim has been granted, but no specified claim has yet been received. */ MahJonging = 5, /* the current player has mah jong (which we've verified) and we are declaring the hands The info record contains the following members: player the player going mah jong whence where did the mahjong tile come from supplier if a discard/kong, who discarded (a seat) tile the tile that completed the hand mjpending if true, the mahjong was claimed from a discard or robbed kong, but the discard has not yet been declared in a set chowpending mjpending, and the player has declared a chow without specifying the position. */ } GameState; /* This is how we index arrays of the players in the current game. We used to have noseat #defined to be (seats)-1. However, negative enums are allowed, so use them! */ typedef enum { east = 0, south = 1, west = 2, north = 3, noseat = -1 } seats; #define NUM_SEATS 4 /* typedef for where the extra tile came from */ typedef enum { FromNone = 0, FromWall = 1, FromDiscard = 2, FromLoose = 3, FromRobbedKong = 4 } Whence; /* typedef for robbing kong info */ typedef enum { NotKonging = 0, AddingToPung = 1, DeclaringKong = 2 } Konging; /* this typedef represents the possible claims on a discard */ /* It is defined that these are in priority order up to MahJong claim, since these are the claims that can be made during the game. The remaining values are for use during the claim of the last discard, after the MahJong has been granted. */ typedef enum { UnknownClaim = 0, NoClaim = 1, ChowClaim = 2, PungClaim = 3, KongClaim = 4, MahJongClaim = 5, PairClaim = 6, SpecialSetClaim = 7 } Claims; /* this is a bunch of miscellaneous flags used to keep track of stuff used in scoring. They are all zeroed at start of hand */ typedef enum { /* these two flags are used to detect kong-upon-kong. They are both cleared on every discard; Kong is set upon a kong being made, and KongUponKong upon a kong being made with Kong set */ GFKong, GFKongUponKong, /* the following two flags relate to letting off a cannon. If checking is on, they are set by the routines that check for dangerous discards. Otherwise, they should be set in response to a dangerousdiscard message from the controller. */ GFDangerousDiscard, /* cleared on discard, and set if the discard is dangerous and claimed */ GFNoChoice /* likewise, set if the dangerous discarder had no choice */ } GameFlags; /* given a pointer to game, test the flag. This returns a boolean. */ #define game_flag(g,f) ((g->flags & (1 << f)) && 1) /* set, clear */ #define game_setflag(g,f) (g->flags |= (1 << f)) #define game_clearflag(g,f) (g->flags &= ~(1 << f)) /* define a game option table */ typedef struct { GameOptionEntry *options; int numoptions; } GameOptionTable; /* This structure represents an active game. This is like a protocol GameMsg, plus some other stuff. */ typedef struct _Game { PlayerP players[NUM_SEATS]; /* the players */ TileWind round; /* wind of the current round */ int hands_as_east; /* number of hands completed with this dealer (excluding current hand, except in state HandComplete) */ int firsteast; /* id of player who was east in first hand of game */ GameState state; /* the state (see above) of the game */ /* state dependent extra information */ int active; /* players may not move unless the game is active */ char *paused; /* This also stops moves; but it is a pause waiting for players' permission to receive. The non-NULL value is the reason for the pause. If this is set, the ready array records who we're still waiting for */ /* The next fields are used for lots of auxiliary state information */ seats player; /* the player doing something */ Whence whence; /* where did the player's last tile come from? */ seats supplier; /* and if from a discard, who supplied it? */ Tile tile; /* and what was it? */ Whence needs; /* where does the player need to get a tile from? */ int serial; /* serial number of current discard/kong */ Claims claims[NUM_SEATS]; /* who's claimed for the current discard */ ChowPosition cpos; /* if a chow has been claimed, for which position? */ int chowpending; /* is a chow in progress? */ int mjpending; /* is a mah-jong from discard in progress? */ Konging konging; /* is a kong in progress? */ int ready[NUM_SEATS]; /* who is ready in a pause ? */ unsigned int flags; /* misc flags. This will be zeroed at start of hand */ /* This represents the wall. There is a hard-wired array, which needs to be big enough to accommodate all walls we might see. The live_used member gives the number of tiles used from the live wall; the live_end gives the end of the live wall; and the dead_end gives the end of the dead wall. Thus the live wall goes from live_used to (live_end-1) and the dead wall from live_end to (dead_end-1). Game options (will) determine how these are changed. The current setting is a fixed 16-tile kong box. */ struct { Tile tiles[MAX_WALL_SIZE]; /* the tiles */ int live_used; /* number of live tiles drawn */ int live_end; /* start of dead wall */ int dead_end; /* end of wall */ int size; /* size of wall as dealt */ } wall; /* This is a convenience to track the number of tiles of each value displayed on the table. It is (always) maintained by handle_cmsg. */ Tile exposed_tile_count[MaxTile]; /* This is the same, but tracks only discards */ Tile discarded_tile_count[MaxTile]; /* the following can be set to 1 to make the handle_cmsg function check the legality of all the messages. Otherwise, it will apply them blindly. (So client programs using this module will probably leave it zero.) */ int cmsg_check; /* This is used by handle_cmsg to return error messages when it fails. */ char *cmsg_err; int protversion; /* protocol version level for this game */ int manager; /* id of the player who owns this game and can set the game options. By default, 0, in which case any player can set options. In that case, any player can claim ownership, first come first served. Can be set to -1 to prohibit any use of managerial functions. */ /* option table. */ GameOptionTable option_table; int fd; /* for use by client programs */ int cseqno; /* for use by the client_ routines */ void *userdata; /* for client extensions */ } Game; /* game_id_to_player: return the player with the given id in the given game */ /* N.B. If passed an id of zero, will return the first free player structure. This is a feature. */ PlayerP game_id_to_player(Game *g,int id); /* game_id_to_seat: convert an id to a seat position */ /* N.B. If passed an id of zero, will return the seat of the first free player structure. This is a feature. */ seats game_id_to_seat(Game *g, int id); /* game_draw_tile: draw a tile from the game's live wall. Return ErrorTile if no wall. If the wall contents are unknown to us, this function returns HiddenTile (assuming the game's wall is correctly initialized!). */ Tile game_draw_tile(Game *g); /* game_peek_tile: as above, but just returns the next tile without actually updating the wall */ Tile game_peek_tile(Game *g); /* game_draw_loose_tile: draw one of the loose tiles, or ErrorTile if no wall. Returns HiddenTile if we don't know the wall. */ Tile game_draw_loose_tile(Game *g); /* game_peek_loose_tile: look at loose tile or ErrorTile if no wall. Returns HiddenTile if we don't know the wall. */ Tile game_peek_loose_tile(Game *g); /* game_handle_cmsg: takes a CMsg, and updates the game to implement the CMsg. There are some important things to note: The function only checks the legality of moves if the game's cmsg_check field is positive. In this case, this function enforces the rules of the game, at considerable cost. There is no point in doing this (or trying to do this) in a game structure where we don't have full knowledge; hence client programs should not check cmsgs. Much of the logic that used to be in the controller program has migrated into this file, to avoid some code duplication. The return value is a convenience: the id of the affected player, for messages that affect one player; 0, for messages that affect all or no players; -1 on legality error. If there is an error, the game's cmsg_err field points to a human readable error message; and the game is guaranteed to be unchanged. -2 on consistency errors. These are "this can't happen" errors. In this case the game state is not guaranteed to be unchanged, but it was inconsistent before. */ int game_handle_cmsg(Game *g, CMsgMsg *m); /* game_has_started: returns true if the game has started (i.e. dealing of first hand has started), false otherwise */ int game_has_started(Game *g); /* This is mainly for the server. Clients should query the server to determine the available options */ extern GameOptionTable game_default_optiontable; /* the default option table */ /* game_clear_option_table: clear an option table, freeing the storage */ int game_clear_option_table(GameOptionTable *t); /* game_copy_option_table: copy old option table into new. Will refuse to work on new table with existing data. */ int game_copy_option_table(GameOptionTable *newt, GameOptionTable *oldt); /* game_set_options_from_defaults: set the option table to be a copy of the default, freeing any existing table; mark options enabled according to the value of g->protversion */ int game_set_options_from_defaults(Game *g); /* find an option entry in an option table, searching by integer (if known) or name */ GameOptionEntry *game_get_option_entry_from_table(GameOptionTable *t, GameOption option, char *name); /* find an option entry in the game table, searching by integer (if known) or name */ GameOptionEntry *game_get_option_entry(Game *g, GameOption option, char *name); /* find an option entry in the default table, searching by integer (if known) or name */ GameOptionEntry *game_get_default_option_entry(GameOption option, char *name); /* get an option value. First look in the game table; if that fails, look in the default table; if that fails return the default default. */ GameOptionValue game_get_option_value(Game *g, GameOption option, char *name); /* set an option in table. Function will allocate space as required */ int game_set_option_in_table(GameOptionTable *t, GameOptionEntry *e); /* set an option. Function will allocate space as required */ int game_set_option(Game *g, GameOptionEntry *e); /* debugging function: return a (possibly static) buffer holding a textual dump of the game state. Second argument returns number of spare bytes in the returned buffer */ char *game_print_state(Game *g, int *bytes_left); /* include enum parsing options */ #include "game-enums.h" #endif /* GAME_H_INCLUDED */ mj-1.17-src/proto-encode-msg.pl0000444006717300001440000001453015002771311015042 0ustar jcbusers# $Header: /home/jcb/MahJong/newmj/RCS/proto-encode-msg.pl,v 12.1 2012/02/01 15:29:24 jcb Rel $ # proto-encode-msg.pl # generate the protocol conversion switch for c/pmsgs # Also, generate in cmsg_union.h a definition of a # massive union type. # Also, in cmsg_size.c, a function to return the size of a given # type of cmsg. If the size is -n, that indicates that the # last field (last sizeof(char*) bytes) is a char *. #***************** COPYRIGHT STATEMENT ********************** #* This file is Copyright (c) 2000 by J. C. Bradfield. * #* Distribution and use is governed by the LICENCE file that * #* accompanies this file. * #* The moral rights of the author are asserted. * #* * #***************** DISCLAIMER OF WARRANTY ******************** #* This code is not warranted fit for any purpose. See the * #* LICENCE file for further information. * #* * #************************************************************* # debugging $debug = $ENV{'DEBUG'}; # are we doing cmsg or pmsg ? if ( $ARGV[0] eq '-cmsg' ) { $L = 'C' ; $l = 'c'; $msgtype = "Controller"; $infile = "protocol.h"; } elsif ( $ARGV[0] eq '-pmsg' ) { $L = 'P' ; $l = 'p' ; $msgtype = "Player"; $infile = "protocol.h"; } elsif ( $ARGV[0] eq '-mcmsg' ) { $L = 'MC' ; $l = 'mc' ; $msgtype = "MController"; $infile = "mprotocol.h"; } elsif ( $ARGV[0] eq '-mpmsg' ) { $L = 'MP' ; $l = 'mp' ; $msgtype = "MPlayer"; $infile = "mprotocol.h"; } else { die("No function argument"); } open(IN,"<$infile") or die("No infile"); open(STDOUT,">enc_${l}msg.c"); open(UNION,">${l}msg_union.h"); open(SIZE,">${l}msg_size.c"); print UNION "typedef union _${L}MsgUnion { /* Note that this type field relies on the fact that all messages have type as their first field */\n"; print UNION $msgtype, "MsgType type;\n"; print SIZE "static int ${l}msg_size[] = {\n"; while ( ! eof(IN) ) { while ( ) { # unfortunately, we need to catch the enum values if ( /^\s*${L}Msg(\S+)\s*=\s*(\d+)/ ) { $enums[$2] = $1; } next if ! /struct _${L}Msg/; chop; s/^.*${L}Msg//; s/Msg\s*\{// ; s,\s*/\*.*$,, ; # strip comment $type = $_ ; if ( ! $type ) { # reached the dummy type $doneall = 1; last; } print " case ${L}Msg$type: \n"; print " { ${L}Msg${type}Msg *m UNUSED = (${L}Msg${type}Msg *) msg;\n"; $format = "$type"; # special hack for the Comment message if ( $type eq 'Comment' ) { $format = "#"; } $args = ''; $dofinish = 1; $_ = ; # skip type while ( ) { chop; if ( s/^\s*int\s+// ) { $format .= " %d"; s/;.*//; $args .= ", m->$_"; } elsif ( s/^\s*bool\s+// ) { $format .= " %d"; s/;.*//; print " badfield = 0;\n"; print " if ( m->$_ < 0 || m->$_ > 1 ) { warn(\"bad boolean in message, assuming TRUE\"); badfield = 1 ; };\n"; $args .= ", (badfield ? 1 : m->$_)"; } elsif ( s/^\s*char \*// ) { s/;.*//; # if the argument happens to start with a space or backslash, then # we need to escape it with a backslash $format .= " %s%s"; # if the argument is null, don't pass it to printf, # but pass the empty string instead $args .= ",(m->$_ && (m->$_\[0] == ' ' || m->$_\[0] == '\\\\')) ? \"\\\\\" : \"\", m->$_ ? m->$_ : \"\" "; # and flag this type: $charstar{$type} = $_; # see below ... } elsif ( s/^\s*word\d+\s+// ) { $format .= " %s"; s/;.*//; $args .= ", m->$_ "; } elsif ( s/^\s*TileWind\s+// ) { $format .= " %c"; s/;.*//; $args .= ", windletter(m->$_)"; } elsif ( s/^\s*PlayerOption\s+// ) { $format .= " %s"; s/;.*//; $args .= ", player_print_PlayerOption(m->$_)"; } elsif ( s/^\s*Tile\s+// ) { $format .= " %s"; s/;.*//; $args .= ", tile_code(m->$_)"; } elsif ( s/^\s*ChowPosition\s+// ) { $format .= " %s"; s/;.*//; $args .= ", cpos_string(m->$_)"; } elsif ( s/^\s*GameOptionEntry\s+// ) { $format .= " %s"; s/;.*//; $args .= ", protocol_print_GameOptionEntry(&m->$_)"; } elsif ( /^\}/ ) { last ; } else { print STDERR "hope this is a comment: $_\n" if $debug; } } next if ! $dofinish; $format .= '\r\n'; print " while (1) {\n"; print " int size = snprintf(buf,buf_size,\"$format\"$args);\n"; print " if ((size >= 0)&&(size < (int)buf_size))\n"; print " break;\n"; print " buf_size = (size >= 0)?((size_t)size+1):(buf_size > 0)?(buf_size*2):1024;\n"; print " buf = realloc(buf, buf_size);\n"; print " if (!buf) {\n"; print " perror(\"encode_" . $l . "msg\");\n"; print " exit(-1);\n"; print " }\n"; print " }\n"; print " }\n"; print " break;\n"; } last if $doneall; } for ( $i = 0; $i <= $#enums; $i++ ) { $t = $enums[$i]; if ( $t ) { $s = $t; $s =~ tr/A-Z/a-z/; print UNION "${L}Msg${t}Msg $s;\n"; } if ( defined($charstar{$t}) ) { $neg = '-' ; } else { $neg = '' ; } print SIZE +($t ? "${neg}(int)sizeof(${L}Msg${t}Msg)" : 0), ",\n"; } print UNION "} ${L}MsgUnion;\n"; print SIZE "}; int ${l}msg_size_of(",$msgtype,"MsgType t) { return ${l}msg_size[t]; }\n\n"; # While we're at it, we'll also generate functions to do # a deep copy (malloc'ing all space), and to deep free # the structures. print SIZE "${L}MsgMsg *${l}msg_deepcopy(${L}MsgMsg *m) { ${L}MsgMsg *n; int size; char *mc,*nc; size = ${l}msg_size_of(m->type); if ( size < 0 ) size *= -1; n = (${L}MsgMsg *)malloc(size); if ( ! n ) return n; memcpy((void *)n,(const void *)m,size); switch ( m->type ) { "; foreach $k ( keys %charstar ) { $c = $charstar{$k}; print SIZE " case ${L}Msg$k: mc = ((${L}Msg${k}Msg *)m)->$c; if ( mc ) { nc = (char *)malloc(strlen(mc)+1); if ( ! nc ) return NULL; strcpy(nc,mc); } else { nc = NULL; } ((${L}Msg${k}Msg *)n)->$c = nc; break; "; } print SIZE " default: ; } return n; }\n\n"; print SIZE "void ${l}msg_deepfree(${L}MsgMsg *m) { switch ( m->type ) { "; foreach $k ( keys %charstar ) { $c = $charstar{$k}; print SIZE " case ${L}Msg$k: if ( ((${L}Msg${k}Msg *)m)->$c ) free((void *)((${L}Msg${k}Msg *)m)->$c); break; "; } print SIZE " default: ; } free((void *)m); }\n\n"; mj-1.17-src/gui.h0000444006717300001440000004341315002771311012262 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/gui.h,v 12.9 2025/04/25 17:40:34 jcb Exp $ * gui.h * type defns and forward declarations for the gui module. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include #include #include "lazyfixed.h" #include "vlazyfixed.h" #include "sysdep.h" #include #include "client.h" #include /********************* TYPE DEFINITIONS *********************/ /* a tilesetbox is a TileSet and a widget that boxes it. The TileSet is stored to avoid unnecessary updating of the box */ typedef struct _TileSetBox { TileSet set; GtkWidget *widget; /* the widget itself */ GtkWidget *tiles[4]; /* the children buttons */ } TileSetBox; /* This struct represents the display of a player. It gives access to relevant widgets without requiring excessive casting and pointer chasing. */ typedef struct _PlayerDisp { PlayerP player; /* pointer to the game player structure */ /* this is the whole box representing the player */ GtkWidget *widget; /* the orientation says which way round tiles go (equivalently, which player we are in the table). The tilepixmaps are those appropriate assuming that players look at their own tiles. If the players politely display their sets to be readable by the others, then the orientation of exposed tiles is flipped from these. that the orientations are: 0: upright (for our own tiles) 1: top to left: player to the right (south, when we're east) 2: upside down (player opposite) 3: top to right (north). */ int orientation; /* and accordingly the following macro can be used if concealed and exposed tiles are oriented differently */ /* # define flipori(ori) ((ori+2)%4) */ #define flipori(ori) ori /* this is an array of buttons representing the concealed tiles. For initial size calculation, it should be filled with blanks. (Ideally, unmapped, but I can't see how to do that.) They appear from the left in the "concealed" row. */ GtkWidget *conc[MAX_CONCEALED]; /* This is an array representing the declared specials that have overflowed into the concealed row. Initially, exactly five of these should be shown: that makes the whole "concealed" row 19 tiles wide, which gives enough room for reasonable collections of specials, and makes the "exposed" row big enough for four kongs and a pair. They appear to the right of the concealed row. */ GtkWidget *extras[8]; /* These are the specials that have found room where we want them, at the end of the exposed row */ GtkWidget *spec[8]; /* each of these is a box representing a declared set. Each set is packed into the exposed row, from the left, with a 1/4 tile spacing between them (thus fitting 4 kongs and a pair in exactly). Size is determined by the concealed row, so there is no need to show these initially. */ /* This is the tong box. It appears to the right of the exposed row specials*/ GtkWidget *tongbox; TileSetBox esets[MAX_TILESETS]; /* each of these is a box representing a concealed set. They appear to the left of the concealed tile set, with the 1/4 tile spacing. They are initially hidden, and appear only in scoring. */ TileSetBox csets[MAX_TILESETS]; /* these is not actually in the player area, but to do with discards */ GtkWidget *discards[32]; /* buttons for this player's discards */ int dx[32], dy[32]; /* save calculated posns of discard tiles */ int num_discards; gint16 x,y; /* x and y for next discard */ int row; /* which row is next discard in */ gint16 xmin[5],xmax[5]; /* in each row, first point used, leftmost point free*/ int plane; /* if desperate, we start stacking tiles */ /* This is the window for pung! claims etc. */ GtkWidget *claimw; GtkWidget *claimlab; /* and the label inside it */ GtkWidget *thinkbox; /* and the box holding the ... for thinking */ int claim_serial; /* the discard for which this window was popped up */ int claim_time; /* time popped up (in millisecs since start of program) */ /* this is a label widget which is put the right of each player when the wall is shown, to use the otherwise empty space. It displays the same info as the info window. It's only used in GTK2, because in order to make it clear which label goes with which, we use rotated text. */ GtkLabel *infolab; } PlayerDisp; /* This is a one use structure representing the dialog box used for claiming discards. It has two personalities: normally it shows buttons Noclaim Chow Pung Kong MahJong but after a mahjong claim it shows Eyes Chow Pung Special Hand */ typedef struct { GtkWidget *widget; /* the box itself */ GtkWidget *tilename; /* the label for the tile name */ GtkWidget *tiles[4]; /* the buttons for the tiles as discarded by each player. Element 0 is unused. */ /* the various buttons */ GtkWidget *noclaim; GtkWidget *eyes; GtkWidget *chow; GtkWidget *pung; GtkWidget *special; GtkWidget *kong; GtkWidget *mahjong; GtkWidget *robkong; int mode; /* 0 normally, 1 in mahjong mode, 2 for robbing kongs */ } DiscardDialog; /* this is used by functions that display or remove tiles to pass back information to the animation routines. The structure contains information about the tiles. */ typedef struct { GtkWidget *target; /* the widget to which this information refers, if it is being newly displayed. In this case, the widget is not shown, and it is the animator's job to show it at the end of animation. If target is null, then the widget is being undisplayed. */ Tile t; /* the tile displayed by the widget */ int ori; /* and its orientation */ int x, y; /* x and y coordinates relative to the boardframe */ } AnimInfo; /* enums used in the dialog popup function to specify position */ typedef enum { DPCentred, /* centered over main window */ DPOnDiscard, /* bottom left corner in same place as discard dialog */ DPErrorPos, /* for error dialogs: centred over top of main window */ DPNone, /* don't touch the positioning at all */ DPCentredOnce, /* centre it on first popup, then don't fiddle */ DPOnDiscardOnce, /* on discard dialog first time, then don't fiddle */ } DPPosn; /* Where to put the dialog boxes */ typedef enum { DialogsUnspecified = 0, DialogsCentral = 1, DialogsBelow = 2, DialogsPopup = 3 } DialogPosition; /* when to sort the tiles in our hand */ typedef enum { SortAlways = 0, SortDeal = 1, SortNever = 2, } SortTiles; /* Whether to file fault reports over the network */ typedef enum { DebugReportsUnspecified = 0, DebugReportsNever = 1, DebugReportsAsk = 2, DebugReportsAlways = 3 } DebugReports; /* The rcfile is used to store assorted persistent data. We often need to read or update only selected parts of it. This set of flag bits identifies the groups that can be independently read or updated. */ typedef enum { XmjrcNone = 0x00, XmjrcDisplay = 0x01, /* preferences regarding the display */ XmjrcPlayer = 0x02, /* player option settings */ XmjrcGame = 0x04, /* game option preferences */ XmjrcMisc = 0x08, /* odds and sods */ XmjrcOpen = 0x10, /* sticky fields in the connection dialogs */ XmjrcPlaying = 0x20, /* playing preferences */ XmjrcAll = 0xFFFFFFFF /* everything */ } XmjrcGroup; /* extra data in the game */ typedef struct { int orig_live_end; /* value of wall.live_end at start of hand */ } GameExtras; #define gextras(g) ((GameExtras *)(g->userdata)) /***************** FORWARD DECLARATIONS *****************/ /* FUNCTIONS */ void apply_game_prefs(void); GtkWidget *build_or_refresh_option_panel(GameOptionTable *got,GtkWidget *panel); GtkWidget *build_or_refresh_prefs_panel(GameOptionTable *got,GtkWidget *panel); void button_set_tile(GtkWidget *b, Tile t, int ori); void chow_dialog_init(void); void close_connection(int keepconnection); void close_saving_posn(GtkWidget *w); void conc_callback(GtkWidget *w, gpointer data); void continue_dialog_init(void); void continue_dialog_popup(void); void control_server_processing(int on); void create_dialogs(void); void create_display(void); void debug_options_dialog_popup(void); void destroy_dialogs(void); void destroy_display(void); void dialog_popup(GtkWidget *dialog, DPPosn posn); void disc_callback(GtkWidget *w, gpointer data); void discard_dialog_init(void); void discard_dialog_popup(Tile t, int ori, int mode); void do_chow(GtkWidget *w, gpointer data); gint doubleclicked(GtkWidget *w, GdkEventButton *eb,gpointer data); void ds_dialog_init(void); void ds_dialog_popup(void); void end_dialog_init(void); void end_dialog_popup(void); void error_dialog_init(void); void error_dialog_popup(char *msg); void game_option_init(void); void game_prefs_init(void); void info_dialog_popup(char *msg); GtkWidget *menubar_create(void); GtkWidget *make_or_refresh_option_updater(GameOptionEntry *goe, int prefsp); void messagewindow_init(void); void nag_popup(void); void option_reset_callback(GtkWidget *w, gpointer data); void option_updater_callback(GtkWidget *w, gpointer data); void open_connection(GtkWidget *w UNUSED, gpointer data, int fd, char *newaddr); void open_dialog_init(char *idt, char *nt); void open_dialog_popup(GtkWidget *w UNUSED, gpointer data); void password_showraise(void); void playing_prefs_init(void); void prefs_updater_callback(GtkWidget *w, gpointer data); int read_or_update_rcfile(char *rcfile, XmjrcGroup read_groups, XmjrcGroup update_groups); void scorehistory_init(void); void scoring_dialog_init(void); void scoring_dialog_popup(void); void set_dialog_posn(DialogPosition p); void set_animation(int a); void setup_dialogs(void); void showraise(GtkWidget *w); void status_init(void); void status_showraise(void); void status_update(int game_over); void textwindow_init(void); void tilesetbox_highlight_nth(TileSetBox *tb,int n); void tilesetbox_init(TileSetBox *tb, int ori,GtkSignalFunc func,gpointer func_data); void tilesetbox_set(TileSetBox *tb, const TileSet *ts, int ori); void turn_dialog_init(void); void turn_dialog_popup(void); void usage(char *pname,char *msg); void warningwindow_init(void); int log_msg_add(LogLevel l,char *warning); void warning_clear(void); /* Convenience function */ #define send_packet(m) client_send_packet(the_game,(PMsgMsg *)m) /* VARIABLES */ extern const char *windnames[]; extern const char *shortwindnames[]; extern int debug; extern int server_pversion; /* protocol version that server speaks (independently of game) */ extern PlayerP our_player; extern int our_id; extern seats our_seat; /* our seat in the game */ extern Game *the_game; extern int selected_button; /* the index of the user's selected tile, or -1 if none */ extern int ptimeout; /* claim timeout time in milliseconds */ extern int local_timeouts; /* are we handling timeouts ourselves? */ extern int monitor; /* if 1, send no messages other than in direct response to user action. Used for monitoring a stream in debugging. */ extern int game_over; /* if the game is over, but we're waiting for the user to confirm the disconnect. In this state, we do not close our connection when the other end closes. */ extern int closed_set_in_progress; /* Set when the user clicks a button to declare a closed set in scoring. Cleared by the receipt of a FormClosed... message, or by an Error message. */ extern GdkPixmap **tilepixmaps[]; /* pixmaps for the tiles */ extern GtkWidget *topwindow; /* main window */ extern GtkWidget *menubar; /* menubar */ extern GtkWidget *board; /* the table area itself */ extern GtkWidget *boardframe; /* fixed widget wrapping the board */ extern GtkWidget *outerframe; /* the outermost frame widget */ extern GtkStyle *tablestyle; /* for the dark green stuff */ extern GtkStyle *highlightstyle; /* to highlight tiles */ extern GtkWidget *highlittile; /* the unique highlighted tile */ extern GdkFont *fixed_font; /* a fixed width font */ extern GdkFont *big_font; /* big font for claim windows */ extern GtkWidget *dialoglowerbox; /* encloses dialogs when dialogs are below */ /* Why an array? So I can pass pointers around */ extern DiscardDialog discard_dialog[1]; /* dialog box for specifying chows */ extern GtkWidget *chow_dialog; /* dialog box for declaring specials */ extern GtkWidget *ds_dialog; /* dialog box for continuing with next hand */ extern GtkWidget *continue_dialog; /* dialog box for disconnect at end of game */ extern GtkWidget *end_dialog; /* dialog for opening connection */ extern GtkWidget *open_dialog; extern GtkWidget *openmenuentry, *newgamemenuentry, *resumegamemenuentry, *savemenuentry, *saveasmenuentry, *closemenuentry, *gameoptionsmenuentry; extern GtkWidget *openallowdisconnectbutton,*opensaveonexitbutton,*openrandomseatsbutton, *openplayercheckboxes[3],*openplayernames[3],*openplayeroptions[3],*opentimeoutspinbutton, *opendifficultyspinbutton; /* dialog box for action when it's our turn. Actions: Discard Kong Add to Pung Mah Jong */ extern GtkWidget *turn_dialog; /* dialog box for closed sets when scoring. Actions: Eyes Chow Pung Done */ extern GtkWidget *scoring_dialog; /* window for game status display */ extern GtkWidget *status_window; /* an array of text widgets for displaying scores etc. Element 4 is for settlements. The others are for each player: currently, I think these should be table relative. */ extern GtkWidget *scoring_notebook; extern GtkWidget *textpages[5]; extern GtkWidget *textlabels[5]; /* labels for the pages */ extern GtkWidget *textwindow; /* and the window for it */ /* a window for keeping the score history */ extern GtkWidget *scorehistorywindow; extern GtkWidget *scorehistorytext; /* option stuff */ extern char robot_names[3][128]; extern char robot_options[3][128]; /* The window for messages, and the display text widget */ extern GtkWidget *messagewindow, *messagetext; /* and for warnings */ extern GtkWidget *warningwindow, *warningtext; extern GtkWidget *info_box; /* box to hold info windows */ /* completed games, for nagging */ extern int completed_games; extern int nag_state; /* has user been nagged to pay? */ /* window to nag for donations */ extern GtkWidget *nag_window; /* This gives the width of a player display in units of tiles */ extern int pdispwidth; extern int display_size; /* and this is the preference value for pdispwidth */ extern int square_aspect; /* force a square table */ extern char address[]; /* server address */ extern char name[]; /* player's name */ extern int difficulty; /* robot difficulty level */ extern char main_font_name[]; /* font used in all dialogs etc */ extern char text_font_name[]; /* font used for text in windows */ extern char fallback_text_font_name[]; /* one that worked */ extern int use_system_gtkrc; /* as it says */ extern char gtk2_rcfile[512]; /* gtkrc file to use */ extern char table_colour_name[]; /* colour of the table background */ extern int animate; /* do fancy animation */ extern int nopopups; /* suppress automatic popup of message/scoring windows */ extern int tiletips; /* show tiletips constantly */ extern int rotatelabels; /* rotate player info labels on table */ extern int showwall; /* show the wall or not */ extern int sort_tiles; /* when to sort tiles */ extern int info_windows_in_main; /* should the message and game info windows be included in the main window, or be separate popups ? */ extern int iconify_dialogs_with_main; /* as it says ... */ extern int thinking_claim; /* show ... before players have noclaimed */ extern int alert_mahjong; /* pop up notice when can mahjong */ extern char *tileset, *tileset_path; /* tile sets */ extern int pref_showwall; /* preferred value of showwall */ /* playing options */ extern int playing_auto_declare_specials; extern int playing_auto_declare_losing; extern int playing_auto_declare_winning; /* debug options */ extern DebugReports debug_reports; /* send debugging reports? */ /* the player display areas */ extern PlayerDisp pdisps[NUM_SEATS]; extern int calling; /* disgusting global flag */ /* the widget in which we put discards */ extern GtkWidget *discard_area; /* This is an allocation structure in which we note its allocated size, at some point when we know the allocation is valid. */ extern GtkAllocation discard_area_alloc; extern GtkWidget *just_doubleclicked; /* yech yech yech. See doubleclicked */ /* space round edge of dialog boxes */ extern const gint dialog_border_width; /* horiz space between buttons */ extern const gint dialog_button_spacing; /* vert space between text and buttons etc */ extern const gint dialog_vert_spacing; /* space around player boxes. N.B. this should only apply to the outermost boxes */ extern const int player_border_width; extern DialogPosition dialogs_position; /* in gui-dial.c */ extern GtkWidget *openfile, *openhost, *openport, *openfiletext, *openhosttext, *openporttext, *openidtext, *opennametext, *opengamefiletext; extern GtkWidget *display_option_dialog; extern GtkWidget *game_option_dialog; extern GtkWidget *game_prefs_dialog; extern GtkWidget *game_option_panel; extern GtkWidget *game_prefs_panel; extern GameOptionTable prefs_table; extern GtkWidget *playing_prefs_dialog; /* static for each module */ #include "version.h" mj-1.17-src/makedep0000555006717300001440000000521215002771311012654 0ustar jcbusers#!/bin/sh # # makedepend which uses 'gcc -MM' # # tiny change to the gccmakedep in XFree86 # # Based on mdepend.cpp and code supplied by Hongjiu Lu # TMP=/tmp/mdep$$ CC="gcc" RM="rm -f" LN="ln -s" MV="mv -f" trap "$RM ${TMP}*; exit 1" 1 2 15 trap "$RM ${TMP}*; exit 0" 1 2 13 files= makefile= endmarker= magic_string='# DO NOT DELETE' append=n args= asmfiles= # if we have a gcc version after 3.0, we have to muck with the # flags, because post-3.0 gcc breaks (deliberately!) -MM . gcc --version | head -1 | grep -i 'GCC.* 3\.[1-9]' >/dev/null if [ $? = 0 ] ; then lategcc=Y ; else lategcc="" ; fi gcc --version | head -1 | grep -i 'GCC.* [4-9]\.' >/dev/null if [ $? = 0 ] ; then lategcc=Y ; fi while [ $# != 0 ]; do if [ "$endmarker"x != x -a "$endmarker" = "$1" ]; then endmarker= else case "$1" in -D*) args="$args '$1'" ;; -I*) if [ "$lategcc" ] ; then args="$args -isystem '`echo $1 | sed -e s/-I//`'" else args="$args '$1'" fi ;; -g|-o) ;; *) if [ "$endmarker"x = x ]; then case $1 in # ignore these flags -w|-o|-cc) shift ;; -v) ;; -s) magic_string="$2" shift ;; -f-) makefile="-" ;; -f) makefile="$2" shift ;; --*) endmarker=`echo $1 | sed 's/^\-\-//'` if [ "$endmarker"x = x ]; then endmarker="--" fi ;; -a) append=y ;; -*) echo "Unknown option '$1' ignored" 1>&2 ;; *) files="$files $1" ;; esac fi ;; esac fi shift done if [ x"$files" = x ]; then # Nothing to do exit 0 fi case "$makefile" in '') if [ -r makefile ]; then makefile=makefile elif [ -r Makefile ]; then makefile=Makefile else echo 'no makefile or Makefile found' 1>&2 exit 1 fi ;; esac if [ X"$makefile" != X- ]; then if [ x"$append" = xn ]; then sed -e "/^$magic_string/,\$d" < $makefile > $TMP echo "$magic_string" >> $TMP else cp $makefile $TMP fi fi # need to link .s files to .S for i in $files; do case $i in *.s) dir=`dirname $i` base=`basename $i .s` (cd $dir; $RM ${base}.S; $LN ${base}.s ${base}.S) asmfiles="$asmfiles ${base}.S" ;; esac done CMD="$CC -MM $args `echo $files | sed -e 's,\.s$,\.S,g' -e 's,\.s ,\.S ,g'` | sed -e 's,\.S$,\.s,g' -e 's,\.S ,\.s ,g'" if [ X"$makefile" != X- ]; then CMD="$CMD >> $TMP" fi eval $CMD if [ X"$makefile" != X- ]; then $RM ${makefile}.bak $MV $makefile ${makefile}.bak $MV $TMP $makefile fi if [ x"$asmfiles" != x ]; then $RM $asmfiles fi $RM ${TMP}* exit 0 mj-1.17-src/tiles-numbered/0000755006717300001440000000000010627612767014262 5ustar jcbusersmj-1.17-src/tiles-numbered/2D.xpm0000444006717300001440000000221507404770710015243 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #002065", ". c #EAEAEA", /* pixels */ ".........................", ".........................", ".........................", ".......... ..........", "......... ... .........", "........ .. .. ........", "....... . . .......", "...... . . . . . ......", "...... . . . . ......", "...... . . . . . ......", "...... . . . . ......", "...... . . . . . ......", "....... . . .......", "........ .. .. ........", "......... ... .........", ".......... ..........", ".........................", ".........................", ".........................", ".......... ..........", "......... ... .........", "........ .. .. ........", "....... . . .......", "...... . . . . . ......", "...... . . . . ......", "...... . . . . . ......", "...... . . . . ......", "...... . . . . . ......", "....... . . .......", "........ .. .. ........", "......... ... .........", ".......... ..........", ".........................", ".........................", "........................." }; mj-1.17-src/tiles-numbered/tongW.xpm0000644006717300001440000000305607252321510016071 0ustar jcbusers/* XPM */ static char * tongW_xpm[] = { "35 35 11 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #FF5555", "# c #FF8E8E", "$ c #FFC7C7", "% c #FFE3E3", "& c #FF3939", "* c #FFAAAA", "= c #FF7272", "- c #380000", " ......... ", " ...+++++++++... ", " ..+++++++++++++++.. ", " .+++++++++++++++++++. ", " .+@++@++@+++++++++++++. ", " .++#$%&#+@++++++++++++++. ", " .+++****@+@+++++++++++++++. ", " .+++++@=%@**+++++++..+++++++. ", " .++++++@@+@=%++++++....+++++++. ", " .++++++#=+$&+++..........+++++. ", " .+++++++%%+.-....+++++.....+++++. ", " .+++++......+++++...+......+++++. ", " .++++++..+++++++++....++++++++++. ", ".++++++++++++++++++...++++++++++++.", ".++++++++++..+++++...+++++++++++++.", ".+++++++++++..++++...+++++++++++++.", ".+++++++++++..+++.+..+++++++++++++.", ".++++++++++++..++......+++++++++++.", ".+++++++++++.........+...+++++++++.", ".++++++++++....+.++..++...++++++++.", ".+++++++....+..+.++..+++...+++++++.", ".+++++++...+++...++.++++...+++++++.", " .++++++...+++...+..+++....++++++. ", " .+++++++...+++...........+++++++. ", " .++++++++...............++++++++. ", " .++++++++.....++++....++++++++. ", " .+++++++++..++++++...+++++++++. ", " .+++++++++++++++++++++++++++. ", " .+++++++++++++++++++++++++. ", " .+++++++++++++++++++++++. ", " .+++++++++++++++++++++. ", " .+++++++++++++++++++. ", " ..+++++++++++++++.. ", " ...+++++++++... ", " ......... "}; mj-1.17-src/tiles-numbered/1D.xpm0000444006717300001440000000223407404770707015251 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #002065", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXX XXXXXXXXX", "XXXXXXX X X X X XXXXXXX", "XXXXXX X X X X X XXXXXX", "XXXXX XX X X XX XXXXX", "XXXX XX X X XX XXXX", "XXX X XX XX X XXX", "XXX X ..... X XXX", "XX X XX ..X.X.. XX X XX", "XX X ..X.X.X.. X XX", "XX X X .X.....X. X X XX", "XX X ..X.X.X.. X XX", "XX X X .X.....X. X X XX", "XX X ..X.X.X.. X XX", "XX X XX ..X.X.. XX X XX", "XXX X ..... X XXX", "XXX X XX XX X XXX", "XXXX XX X X XX XXXX", "XXXXX XX X X XX XXXXX", "XXXXXX X X X X X XXXXXX", "XXXXXXX X X X X XXXXXXX", "XXXXXXXXX XXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-numbered/8D.xpm0000444006717300001440000000221507404770712015253 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #002065", ". c #EAEAEA", /* pixels */ ".........................", "...... ....... ......", ".... ... ... ... ....", ".... . . ... . . ....", "... . . . . . . . ...", "... . . . . . . . . . ...", "... . . . . . . . ...", ".... . . ... . . ....", ".... ... ... ... ....", "...... ....... ......", ".... ... ... ... ....", ".... . . ... . . ....", "... . . . . . . . ...", "... . . . . . . . . . ...", "... . . . . . . . ...", ".... . . ... . . ....", ".... ... ... ... ....", "...... ....... ......", ".... ... ... ... ....", ".... . . ... . . ....", "... . . . . . . . ...", "... . . . . . . . . . ...", "... . . . . . . . ...", ".... . . ... . . ....", ".... ... ... ... ....", "...... ....... ......", ".... ... ... ... ....", ".... . . ... . . ....", "... . . . . . . . ...", "... . . . . . . . . . ...", "... . . . . . . . ...", ".... . . ... . . ....", ".... ... ... ... ....", "...... ....... ......", "........................." }; mj-1.17-src/tiles-numbered/2F.xpm0000644006717300001440000000231507252320143015240 0ustar jcbusers/* XPM */ static char * 2F_xpm[] = { "25 35 11 1", " c None", ". c #EAEAEA", "+ c #ECD0D0", "@ c #F56868", "# c #000000", "$ c #F84E4E", "% c #063A30", "& c #EEB6B6", "* c #F19C9C", "= c #FA3434", "- c #F38282", ".........................", ".+@@+.................#..", ".@++$.....%.........###..", ".&..$......%......###..#.", "..+$*......%......#....#.", ".+=+........%.....#...#..", ".$-**.......%....########", ".&***.......%......##....", "............%.....##.....", "............%..%.#.#.....", "............%%...#.##....", "............%.....#......", "...........%%............", "...%.......%......%%%%%%.", "..%......%.%..%%%%%.%..%.", ".%%.......%..%.........%%", ".%...%%%..%%%%.........%%", "%%.....%..%%%.........%%.", "%%.....%.%%...........%..", "..%....%.%...............", "....%%.%%%..........%....", ".....%%%%........%.......", ".......%%........%.%.....", ".......%%%........%.%%...", ".......%%%........%%.....", ".......%.%..%.....%......", ".......%.%.%%.....%......", ".......%.%.%......%......", ".......%%%%%......%......", "........%%%%%.....%......", ".......%%%%%%....%%......", "..........%%%....%.......", "...........%%%..%%.......", ".........................", "........................."}; mj-1.17-src/tiles-numbered/RD.xpm0000444006717300001440000000221507404770712015305 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #B00000", ". c #EAEAEA", /* pixels */ ".........................", ".........................", ".........................", ".........................", "............ ............", ".......... ...........", "........... ..........", "............ ...........", "............ ...........", "............ ...........", "............ .... .....", ".. ....... ...", "... .... ..... ..", ".... . ... ..... ..", ".... .... ..... ..", "..... .... .... ..", "..... ... ... . ...", "...... .....", "...... ... .. ......", "....... ... ... .......", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ............", "............ ............", ".........................", ".........................", "........................." }; mj-1.17-src/tiles-numbered/6D.xpm0000444006717300001440000000225307404770711015252 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 4 1", /* colors */ " c #002065", ". c #B00000", "X c #EAEAEA", "o c #FFFFFF", /* pixels */ "XXXo oXXXXXo oXXX", "XXo ooo oXXXo ooo oXX", "Xo o o oXo o o oX", "X o o o o X o o o o X", "X X o o o X X X o o o X X", "X X o o X X X o o X X", "X o o o o o X o o o o o X", "X o o o o X o o o o X", "XX o o XXX o o XX", "XXX ooo XXXXX ooo XXX", "XXXX XXXXXXX XXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXX.....XXXXXXX.....XXXX", "XXX..ooo..XXXXX..ooo..XXX", "XX..o...o..XXX..o...o..XX", "X..o.o.o.o..X..o.o.o.o..X", "X.o.o.o.o.o.X.o.o.o.o.o.X", "X.X..o.o..X.X.X..o.o..X.X", "X.X.o.o.o.X.X.X.o.o.o.X.X", "X..o.o.o.o..X..o.o.o.o..X", "Xo..o...o..oXo..o...o..oX", "XXo..ooo..oXXXo..ooo..oXX", "XXXo.....oXXXXXo.....oXXX", "XXX..ooo..XXXXX..ooo..XXX", "XX..o...o..XXX..o...o..XX", "X..o.o.o.o..X..o.o.o.o..X", "X.o.o.o.o.o.X.o.o.o.o.o.X", "X.X..o.o..X.X.X..o.o..X.X", "X.X.o.o.o.X.X.X.o.o.o.X.X", "X..o.o.o.o..X..o.o.o.o..X", "Xo..o...o..oXo..o...o..oX", "XXo..ooo..oXXXo..ooo..oXX", "XXXo.....oXXXXXo.....oXXX" }; mj-1.17-src/tiles-numbered/tongS.xpm0000644006717300001440000000303707252321636016075 0ustar jcbusers/* XPM */ static char * tongS_xpm[] = { "35 35 10 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #FF7272", "# c #FF5555", "$ c #FFAAAA", "% c #FF8E8E", "& c #FFE3E3", "* c #FFC7C7", "= c #FF3939", " ......... ", " ...+++++++++... ", " ..+++++++++++++++.. ", " .++++++..+++++++++++. ", " .++@##$++....+++++++++. ", " .++$%++%+++.....++++++++. ", " .+++&#@*+++++...++++++++++. ", " .++++++&@#++++...+++++++++++. ", " .+++++%+++#++++..+++++++++++++. ", " .+++++$=$%#++++..+.+++++++++++. ", " .+++++++&$*+++++.....+++++++++++. ", " .++++++++++++++.......++++++++++. ", " .+++++++++++...+....++++++++++++. ", ".++++++++++++++++..+..++++++++++++.", ".++++++++++++++++.++...+++++++++++.", ".+++++++++++.++++......+++++++++++.", ".+++++++++++...+...........+++++++.", ".++++++++++++......+..++....++++++.", ".+++++++++++.....++...+++...++++++.", ".++++++.+++.....+....++++...++++++.", ".+++++++.+.+++.....++..++..+++++++.", ".+++++++..+++...+.......+..+++++++.", " .+++++++..++++.....+++++..++++++. ", " .+++++++...+...+..++++++..++++++. ", " .++++++++...++++.++++++...++++++. ", " .++++++++..+++++.........+++++. ", " .+++++++++.++++++++......+++++. ", " .++++++++++++++++++++..+++++. ", " .+++++++++++++++++++++++++. ", " .+++++++++++++++++++++++. ", " .+++++++++++++++++++++. ", " .+++++++++++++++++++. ", " ..+++++++++++++++.. ", " ...+++++++++... ", " ......... "}; mj-1.17-src/tiles-numbered/6B.xpm0000444006717300001440000000221507404770711015246 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ..... ..... ..... ", "... ... ... ... ... ...", " .. .. .. .. .. .. ", " . . . . . . ", " . . . . . . ", " . . . . . . ", " ..... ..... ..... ", "... .. ... ... ... ...", " ..... ..... ..... ", " . . . . . . ", " . . . . . . ", " . . . . . . ", " .. .. .. .. .. .. ", "... ... ... ... ... ...", " ..... ..... ..... ", " ", " ", " ", " ", " ", " ..... ..... ..... ", "... ... ... ... ... ...", " .. .. .. .. .. .. ", " . . . . . . ", " . . . . . . ", " . . . . . . ", " ..... ..... ..... ", "... .. ... ... ... ...", " ..... ..... ..... ", " . . . . . . ", " . . . . . . ", " . . . . . . ", " .. .. .. .. .. .. ", "... ... ... ... ... ...", " ..... ..... ..... " }; mj-1.17-src/tiles-numbered/tongE.xpm0000644006717300001440000000305607252321554016057 0ustar jcbusers/* XPM */ static char * tongE_xpm[] = { "35 35 11 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #FFAAAA", "# c #FF3939", "$ c #FF5555", "% c #FF8E8E", "& c #FF7272", "* c #FFE3E3", "= c #380000", "- c #FFC7C7", " ......... ", " ...+++++++++... ", " ..+++++++++++++++.. ", " .++++++++...++++++++. ", " .+@#$$%++++...++++++++. ", " .++@@+++++++...+++++++++. ", " .+++@&@@*++++..+++++++++++. ", " .++++@&@@*++++..+..+++++++++. ", " .+++++@@+++++++......+++++++++. ", " .+++++@&@@=..........+++++++++. ", " .++++++*@@@-+.....++++++++++++++. ", " .+++++++++++++++..+...++++++++++. ", " .++++++++++++++........+++++++++. ", ".++++++++++..+...........+++++++++.", ".++++++++++....++..++++...++++++++.", ".++++++++++...++......+...++++++++.", ".++++++++++...............++++++++.", ".+++++++++++...++..+++...+++++++++.", ".++++++++++++...........++++++++++.", ".+++++++++++++..+..++..+++++++++++.", ".++++++++++++++......+++++++++++++.", ".+++++++++++++.....+..++++++++++++.", " .+++++++++++...+..++..++++++++++. ", " .++++++++++...++..++...+++++++++. ", " .+++++++++...+++..+++...++++++++. ", " .+++++++.....++..+++.....+++++. ", " .+++++..++++.....++++.....++++. ", " .+++++++++++....++++++++++++. ", " .+++++++++++...+++++++++++. ", " .+++++++++++..++++++++++. ", " .++++++++++.++++++++++. ", " .+++++++++++++++++++. ", " ..+++++++++++++++.. ", " ...+++++++++... ", " ......... "}; mj-1.17-src/tiles-numbered/5C.xpm0000644006717300001440000000227607252317440015254 0ustar jcbusers/* XPM */ static char * 5C_xpm[] = { "25 35 10 1", " c None", ". c #EAEAEA", "+ c #EEB6B6", "@ c #F84E4E", "# c #F38282", "$ c #F19C9C", "% c #000000", "& c #F56868", "* c #ECD0D0", "= c #B00000", ".........................", ".+@@#....................", ".$$............%.........", ".$@&*.........%%%%.......", ".*+*@.%%....%%%%%%%......", ".+..@..%%.%%%%%..........", ".&##&.%%%....%%..........", "..++..%%%...%%.%%........", "......%%....%%%%%%.......", ".....%%%.%%%%%%%%%%......", ".....%%%..%%%..%%........", "....%%.%...%..%%.%%%.....", "....%..%..%%%%%%%%%%%%...", "...%..%%%%%%.......%%%%..", "..%...%%............%%%..", ".......%.............%...", ".............=...........", "............====.........", "........=..=..==.........", "........==...==..........", ".........=.======........", ".......=====.=...........", "...........=====.........", ".......=.====..==........", ".......==..======........", "........=.===.===........", "........==..====.........", "........=====..====......", "....==...=.=====..===....", ".....=========..=..===...", "..=====.....=.====..===..", "...=.==========.==..===..", ".....=..==......==..===..", "..............====.===...", "................=====...."}; mj-1.17-src/tiles-numbered/1C.xpm0000644006717300001440000000223707252317247015252 0ustar jcbusers/* XPM */ static char * 1C_xpm[] = { "25 35 8 1", " c None", ". c #EAEAEA", "+ c #F38282", "@ c #EEB6B6", "# c #FA3434", "$ c #F84E4E", "% c #000000", "& c #B00000", ".........................", "...+.....................", ".@#$.....................", "...$.....................", "...$.....................", "...$.....................", "...$..............%%.....", "...@.......%%%%%%%%%%%...", "..%%%%%%%%%%........%%%..", "...%%%%.............%%%..", "....%................%%..", ".........................", ".........................", ".........................", ".........................", ".........................", ".............&...........", "............&&&&.........", "........&..&..&&.........", "........&&...&&..........", ".........&.&&&&&&........", ".......&&&&&.&...........", "...........&&&&&.........", ".......&.&&&&..&&........", ".......&&..&&&&&&........", "........&.&&&.&&&........", "........&&..&&&&.........", "........&&&&&..&&&&......", "....&&...&.&&&&&..&&&....", ".....&&&&&&&&&..&..&&&...", "..&&&&&.....&.&&&&..&&&..", "...&.&&&&&&&&&&.&&..&&&..", ".....&..&&......&&..&&&..", "..............&&&&.&&&...", "................&&&&&...."}; mj-1.17-src/tiles-numbered/2C.xpm0000644006717300001440000000231507252317303015241 0ustar jcbusers/* XPM */ static char * 2C_xpm[] = { "25 35 11 1", " c None", ". c #EAEAEA", "+ c #ECD0D0", "@ c #F56868", "# c #F84E4E", "$ c #EEB6B6", "% c #F19C9C", "& c #000000", "* c #FA3434", "= c #F38282", "- c #B00000", ".........................", ".+@@+....................", ".@++#....................", ".$..#....................", "..+#%........&&&.........", ".+*+......&&&&&&&........", ".#=%%&&&&&&..............", ".$%%%..&&................", ".........................", ".................&&......", "...........&&&&&&&&&&....", "...&&&&&&&&&.......&&&...", "....&&&&...........&&&...", ".....&..............&&...", ".........................", ".........................", ".............-...........", "............----.........", "........-..-..--.........", "........--...--..........", ".........-.------........", ".......-----.-...........", "...........-----.........", ".......-.----..--........", ".......--..------........", "........-.---.---........", "........--..----.........", "........-----..----......", "....--...-.-----..---....", ".....---------..-..---...", "..-----.....-.----..---..", "...-.----------.--..---..", ".....-..--......--..---..", "..............----.---...", "................-----...."}; mj-1.17-src/tiles-numbered/7C.xpm0000644006717300001440000000227607252317554015264 0ustar jcbusers/* XPM */ static char * 7C_xpm[] = { "25 35 10 1", " c None", ". c #EAEAEA", "+ c #F38282", "@ c #F84E4E", "# c #ECD0D0", "$ c #F56868", "% c #000000", "& c #EEB6B6", "* c #FA3434", "= c #B00000", ".........................", ".+@@@....................", "...#@....................", "...$#....................", "..#$....%........%%%%%...", "..$&....%%%.....%%%%%....", "..*......%%....%%........", "..&......%%...%..........", "..........%..%...........", "..........%.%............", "..........%%.............", ".........%%%%..%%........", "........%%.%%%%%%%.......", "......%%%...%%%%%%%......", "...%%%%......%%%%%.......", "....%%...................", ".............=...........", "............====.........", "........=..=..==.........", "........==...==..........", ".........=.======........", ".......=====.=...........", "...........=====.........", ".......=.====..==........", ".......==..======........", "........=.===.===........", "........==..====.........", "........=====..====......", "....==...=.=====..===....", ".....=========..=..===...", "..=====.....=.====..===..", "...=.==========.==..===..", ".....=..==......==..===..", "..............====.===...", "................=====...."}; mj-1.17-src/tiles-numbered/7D.xpm0000444006717300001440000000212707404770711015253 0ustar jcbusers/* XPM */ static char * 7D_xpm[] = { "25 35 3 1", ". c #EAEAEA", "+ c #002065", "@ c #B00000", "...+++...................", ".++...++.................", ".+.+++.+.................", "+.++.++.+..+++...........", "+.+.+.+.+++...++.........", "+.++.++.++.+++.+.........", ".+.+++.++.++.++.+..+++...", ".++...+++.+.+.+.+++...++.", "...+++..+.++.++.++.+++.+.", ".........+.+++.++.++.++.+", ".........++...+++.+.+.+.+", "...........+++..+.++.++.+", ".................+.+++.+.", ".................++...++.", "...................+++...", ".........................", ".....@@@.........@@@.....", "...@@...@@.....@@...@@...", "...@.@@@.@.....@.@@@.@...", "..@.@@.@@.@...@.@@.@@.@..", "..@.@.@.@.@...@.@.@.@.@..", "..@.@@.@@.@...@.@@.@@.@..", "...@.@@@.@.....@.@@@.@...", "...@@...@@.....@@...@@...", ".....@@@.........@@@.....", ".....@@@.........@@@.....", "...@@...@@.....@@...@@...", "...@.@@@.@.....@.@@@.@...", "..@.@@.@@.@...@.@@.@@.@..", "..@.@.@.@.@...@.@.@.@.@..", "..@.@@.@@.@...@.@@.@@.@..", "...@.@@@.@.....@.@@@.@...", "...@@...@@.....@@...@@...", ".....@@@.........@@@.....", "........................."}; mj-1.17-src/tiles-numbered/8B.xpm0000444006717300001440000000221507404770711015250 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ..... ..... ", "... ... ... ...", " .. .. .. .. ", " . . . . ", " . . ..... . . ", " . . .. .. . . ", " ..... . ... ..... ", "... ... .. ... .. ... ...", " ..... .... .... ..... ", " . . .. .. .. .. . . ", " . . .... .... . . ", " . .. .. .. .. . ", " .. . . ... .. ", "... ... ... ...", " ..... ..... ", " ", " ", " ", " ", " ", " ..... ..... ", "... ... ... ...", " .. . . . . .. ", " . .. .. .. .. . ", " . . .... .... . . ", " . . .. .. .. .. . . ", " ..... .... .... ..... ", "... ... .. ... .. ... ...", " ..... ... . ..... ", " . . .. .. . . ", " . . ..... . . ", " . . . . ", " .. .. .. .. ", "... ... ... ...", " ..... ..... " }; mj-1.17-src/tiles-numbered/1S.xpm0000644006717300001440000000223707252320414015260 0ustar jcbusers/* XPM */ static char * 1S_xpm[] = { "25 35 8 1", " c None", ". c #EAEAEA", "+ c #B00000", "@ c #828282", "# c #B6B6B6", "$ c #343434", "% c #4E4E4E", "& c #063A30", "......+..................", "...++++................@.", "....+.+++............#$%.", "....+.+...........&....%.", "....+.+++++.......&....%.", "...++++++.......&&&.&..%.", ".++++...+........&&&&..%.", "...+++++++.......&&&...#.", "..+++..++.++.....&&......", ".++.+++++........&.......", "....+..++......&&&&&.....", "....++++.......&&&&&&....", "...............&&.&&&....", "...............&&&&......", "..............&&&...&&&&.", "...&&&&...........&&...&.", "..&&..&&...+++++..&&&.&&.", "..&&&&&&..++...++.&.&&&..", ".....&.++++....++........", "......++..++..+++++......", "......+...++++++..+......", "......+....+..+...++.....", ".......+...++++...++.....", ".......+..++++++.++......", ".......+++++..++++..&&&..", "...........+...++..&&.&&.", "...........+...+.&.&..&&.", "...........++++..&.&..&..", "..&&&&&&&&&&&.&..&&&&&...", ".&&&&..&&&&&&&&&..&&&&...", "&&&&....&&.&&&&&&&.&&&...", "&.&....&&&&..&&&&&&&&&&..", "&&&....&&&........&.&&&&.", "&&......&.............&&.", ".......................&."}; mj-1.17-src/tiles-numbered/3C.xpm0000644006717300001440000000227607252317340015251 0ustar jcbusers/* XPM */ static char * 3C_xpm[] = { "25 35 10 1", " c None", ". c #EAEAEA", "+ c #ECD0D0", "@ c #F56868", "# c #F84E4E", "$ c #EEB6B6", "% c #000000", "& c #F38282", "* c #F19C9C", "= c #B00000", ".........................", ".+@#+....................", ".@++@....................", ".$.+@.........%%.........", "..$#&......%%%%%%%.......", ".$..#...%%%%....%%.......", ".&&&@.%%%................", "..$*.........%%%.........", "..........%%%%%%%........", "........%%%....%.........", "........%................", "...............%%%.......", ".........%%%%%%%%%%%.....", "....%%%%%%........%%%....", ".....%%............%%....", ".........................", ".............=...........", "............====.........", "........=..=..==.........", "........==...==..........", ".........=.======........", ".......=====.=...........", "...........=====.........", ".......=.====..==........", ".......==..======........", "........=.===.===........", "........==..====.........", "........=====..====......", "....==...=.=====..===....", ".....=========..=..===...", "..=====.....=.====..===..", "...=.==========.==..===..", ".....=..==......==..===..", "..............====.===...", "................=====...."}; mj-1.17-src/tiles-numbered/SW.xpm0000644006717300001440000000227607252316455015343 0ustar jcbusers/* XPM */ static char * SW_xpm[] = { "25 35 10 1", " c None", ". c #EAEAEA", "+ c #F56868", "@ c #F84E4E", "# c #F19C9C", "$ c #F38282", "% c #ECD0D0", "& c #EEB6B6", "* c #000000", "= c #FA3434", ".........................", "..+@@#...................", ".#$..$...................", ".%@+&....**..............", "...%+@....****...........", ".$...@.....*****.........", ".#=#$@......***..........", "..%#&.......***..........", "............**...........", "............**.*.........", "............*****........", "...........*******.......", "........***.****.........", "............**.**........", "............*..***.......", ".......*....******.......", ".......***.***********...", "........******.**..****..", ".......*****..***...***..", "..*...*****.****....***..", "...*.*...*****..**..**...", "...**...***.*******.**...", "....**....*****.....**...", "....***.***.**......**...", ".....***....*......***...", "......**.....*********...", ".......*........******...", "...................**....", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................."}; mj-1.17-src/tiles-numbered/EW.xpm0000644006717300001440000000227607252316310015313 0ustar jcbusers/* XPM */ static char * EW_xpm[] = { "25 35 10 1", " c None", ". c #EAEAEA", "+ c #F19C9C", "@ c #FA3434", "# c #F84E4E", "$ c #F38282", "% c #F56868", "& c #ECD0D0", "* c #000000", "= c #EEB6B6", ".........................", ".+@##$...................", ".++......................", ".+%++&.....***...........", ".+%++&......***..........", ".++.........***..........", ".+%++=......**...........", ".&+++=......**.**........", "............******.......", ".......***********.......", ".........*****...........", "............**.***.......", "...........********......", "......**.***********.....", "......****..**....***....", "......***..******.***....", "......***************....", ".......***..**...***.....", "........***********......", ".........**.**..**.......", "..........******.........", ".........*****.**........", "........***.**..**.......", ".......***..**..***......", "......***...**...***.....", ".....*****..**...*****...", "...**....*****....*****..", "..........****...........", "...........***...........", "............**...........", "............*............", ".........................", ".........................", ".........................", "........................."}; mj-1.17-src/tiles-numbered/4D.xpm0000444006717300001440000000221507404770711015246 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #002065", ". c #EAEAEA", /* pixels */ ".... ....... ....", "... ... ..... ... ...", ".. . . ... . . ..", ". . . . . . . . . . .", ". . . . . . . . . . . . .", ". . . . . . . . . . .", ". . . . . . . . . . . . .", ". . . . . . . . . . .", ".. . . ... . . ..", "... ... ..... ... ...", ".... ....... ....", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".... ....... ....", "... ... ..... ... ...", ".. . . ... . . ..", ". . . . . . . . . . .", ". . . . . . . . . . . . .", ". . . . . . . . . . .", ". . . . . . . . . . . . .", ". . . . . . . . . . .", ".. . . ... . . ..", "... ... ..... ... ...", ".... ....... ...." }; mj-1.17-src/tiles-numbered/4B.xpm0000444006717300001440000000221507404770711015244 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ..... ..... ", " ... ... ... ... ", " .. .. .. .. ", " . . . . ", " . . . . ", " . . . . ", " ..... ..... ", " ... ... ... ... ", " ..... ..... ", " . . . . ", " . . . . ", " . . . . ", " .. .. .. .. ", " ... ... ... ... ", " ..... ..... ", " ", " ", " ", " ", " ", " ..... ..... ", " ... ... ... ... ", " .. .. .. .. ", " . . . . ", " . . . . ", " . . . . ", " ..... ..... ", " ... ... ... ... ", " ..... ..... ", " . . . . ", " . . . . ", " . . . . ", " .. .. .. .. ", " ... ... ... ... ", " ..... ..... " }; mj-1.17-src/tiles-numbered/GD.xpm0000444006717300001440000000221507404770712015272 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ", " ", " ", " ", " .. ", " ... ", " ... ", " . .. ", " .. . ", " . ... . . ", " ..... . .. ", " . ..... ... ", " .. . ... .. .. ", " .. ... . ... ", " .. ... .. .. ", " . . .. ....... ", " .. .. .. ... ", " . .. ........ ", " . .. .. .. .... ", " . .... .... .... ", " . .. ... .......... ", " . .. ...... .... ... ", " . ... ............. ", " . ... ... .... .... ", " .. ........... . ", " .... . .. ... ", " .. .. .... ", " . ... ", " ... ", " .. ", " ", " ", " ", " ", " " }; mj-1.17-src/tiles-numbered/5D.xpm0000444006717300001440000000223407404770711015250 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #002065", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXX XXXXXXX XXXX", "XXX XXX XXXXX XXX XXX", "XX X X XXX X X XX", "X X X X X X X X X X X", "X X X X X X X X X X X X X", "X X X X X X X X X X X", "X X X X X X X X X X X X X", "X X X X X X X X X X X", "XX X X XXX X X XX", "XXX XXX XXXXX XXX XXX", "XXXX XXXXXXX XXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXX.....XXXXXXXXXX", "XXXXXXXXX..XXX..XXXXXXXXX", "XXXXXXXX..X...X..XXXXXXXX", "XXXXXXX..X.X.X.X..XXXXXXX", "XXXXXXX.X.X.X.X.X.XXXXXXX", "XXXXXXX.X..X.X..X.XXXXXXX", "XXXXXXX.X.X.X.X.X.XXXXXXX", "XXXXXXX..X.X.X.X..XXXXXXX", "XXXXXXXX..X...X..XXXXXXXX", "XXXXXXXXX..XXX..XXXXXXXXX", "XXXXXXXXXX.....XXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXX XXXXXXX XXXX", "XXX XXX XXXXX XXX XXX", "XX X X XXX X X XX", "X X X X X X X X X X X", "X X X X X X X X X X X X X", "X X X X X X X X X X X", "X X X X X X X X X X X X X", "X X X X X X X X X X X", "XX X X XXX X X XX", "XXX XXX XXXXX XXX XXX", "XXXX XXXXXXX XXXX" }; mj-1.17-src/tiles-numbered/3B.xpm0000444006717300001440000000221507404770710015242 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ..... ", " ... ... ", " .. .. ", " . . ", " . . ", " . . ", " ..... ", " ... ... ", " ..... ", " . . ", " . . ", " . . ", " .. .. ", " ... ... ", " ..... ", " ", " ", " ", " ", " ", " ..... ..... ", "... ... ... ...", " .. .. .. .. ", " . . . . ", " . . . . ", " . . . . ", " ..... ..... ", "... ... ... ...", " ..... ..... ", " . . . . ", " . . . . ", " . . . . ", " .. .. .. .. ", "... ... ... ...", " ..... ..... " }; mj-1.17-src/tiles-numbered/4F.xpm0000644006717300001440000000231507252320256015247 0ustar jcbusers/* XPM */ static char * 4F_xpm[] = { "25 35 11 1", " c None", ". c #EAEAEA", "+ c #F19C9C", "@ c #EEB6B6", "# c #ECD0D0", "$ c #FC1A1A", "% c #F84E4E", "& c #F38282", "* c #063A30", "= c #000000", "- c #FA3434", ".........................", "...+@....................", "..#$+....................", "..%&+.......*....=.==....", ".+&++......*...=======...", ".%%--.....*......==.=....", "...++.....*......==.=....", "...##.....*......=...=...", "..........*.....==...=...", "..........**....=....=...", "..........**....=...==...", "..........**.....*.......", "....**....***....*.......", "...***.*...**...**.......", "...*....*...*.........*..", "..***....*..**......****.", "..........*..*..******...", "..........*.*****.*..**..", "......******.**.*..*..**.", "....***.***...*.**...*.*.", "...**.*****...**.....*.*.", "..***.***.*****..***..**.", ".*..*.**..**..****.**..*.", ".*....*...*...**.....***.", ".*.*..**..******......**.", ".***..**...**.**......*..", "..***.**...*...*...*.....", "..*....*...*...**.**.....", ".....***...********..**..", ".......**..**...**..*....", "........*..*....**.*...*.", "........**.********...*..", ".....*...***.........*...", "..........*..............", "........................."}; mj-1.17-src/tiles-numbered/3F.xpm0000644006717300001440000000231507252320213015237 0ustar jcbusers/* XPM */ static char * 3F_xpm[] = { "25 35 11 1", " c None", ". c #EAEAEA", "+ c #ECD0D0", "@ c #F56868", "# c #F84E4E", "$ c #000000", "% c #EEB6B6", "& c #F38282", "* c #063A30", "= c #F19C9C", "- c #B00000", ".........................", ".+@#+....................", ".@++@.................$$.", ".%.+@....................", "..%#&..............$$$$$$", ".%..#......*..*..........", ".&&&@......****.....$....", "..%=........**....$.$..$.", ".......****.*.......$....", ".......****.*.....$$$$$$.", "........*****.......$....", ".........***.....$$.$....", "...***....**.............", "...****...*..............", "......*..-----...........", "......*----.-----........", "......-----..----........", "......--.--..--.--.**....", ".....---------..--*......", "***..------------.***....", "***..-..---------........", "**...----.----..--.......", ".....----------..-.......", ".....--..---------.......", ".....-...--------...**...", ".....-----..--.....***...", "......----..-.....**.....", ".*......-----.....*......", "********.---.....*.......", "***....***.......*.......", ".......******....******..", ".....***...************..", ".........................", ".........................", "........................."}; mj-1.17-src/tiles-numbered/5B.xpm0000444006717300001440000000223407404770711015246 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #B00000", ". c #EAEAEA", "X c #063A30", /* pixels */ ".XXXXX.............XXXXX.", "XXX.XXX...........XXX.XXX", ".XX.XX.............XX.XX.", "..X.X...............X.X..", "..X.X...............X.X..", "..X.X...............X.X..", ".XXXXX.............XXXXX.", "XXX.XXX...........XXX.XXX", ".XXXXX.............XXXXX.", "..X.X...............X.X..", "..X.X..... .....X.X..", "..X.X.... . ....X.X..", ".XX.XX.... . ....XX.XX.", "XXX.XXX.... . ....XXX.XXX", ".XXXXX..... . .....XXXXX.", "........... . ...........", ".......... ..........", "......... . .........", ".......... ..........", "........... . ...........", ".XXXXX..... . .....XXXXX.", "XXX.XXX.... . ....XXX.XXX", ".XX.XX.... . ....XX.XX.", "..X.X.... . ....X.X..", "..X.X..... .....X.X..", "..X.X...............X.X..", ".XXXXX.............XXXXX.", "XXX.XXX...........XXX.XXX", ".XXXXX.............XXXXX.", "..X.X...............X.X..", "..X.X...............X.X..", "..X.X...............X.X..", ".XX.XX.............XX.XX.", "XXX.XXX...........XXX.XXX", ".XXXXX.............XXXXX." }; mj-1.17-src/tiles-numbered/--.xpm0000444006717300001440000000217607404770707015223 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 1 1", /* colors */ " c #948D13", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " }; mj-1.17-src/tiles-numbered/WW.xpm0000644006717300001440000000227607252316544015346 0ustar jcbusers/* XPM */ static char * WW_xpm[] = { "25 35 10 1", " c None", ". c #EAEAEA", "+ c #F84E4E", "@ c #F38282", "# c #EEB6B6", "$ c #ECD0D0", "% c #FA3434", "& c #F19C9C", "* c #F56868", "= c #000000", ".........................", ".+..+..+.................", ".@#$%@.+.................", ".&&&&+.+.................", "..+*$+&&.................", "..++.+*$.................", "..@*.#%..................", "..$$..#..........==......", "................====.....", "............==========...", ".......======.....=====..", "..======.....===.======..", "...==.........====.......", "..............===........", "......==.....===.........", ".......==....===.........", ".......==...=.==.........", "........==..======.......", ".......=========.===.....", "......====.=..==..===....", "...====.==.=..==...===...", "...===...===..=....===...", "...===...===.==...====...", "....===...===========....", ".....===============.....", "......=====....====......", ".......==......===.......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................."}; mj-1.17-src/tiles-numbered/1F.xpm0000644006717300001440000000225607252320007015242 0ustar jcbusers/* XPM */ static char * 1F_xpm[] = { "25 35 9 1", " c None", ". c #EAEAEA", "+ c #F38282", "@ c #000000", "# c #EEB6B6", "$ c #FA3434", "% c #F84E4E", "& c #063A30", "* c #B00000", ".........................", "...+............@........", ".#$%....&&......@@..@@@@.", "...%...&&&&&....@........", "...%..&&&&.&....@...@@@..", "...%..&&&&.&...@@.@.@....", "...%....&&.....@@@@@@@@@@", "...#.&&&&.......@.@.@....", ".....&..&&&&....@...@@@..", "......&&&&..&...@...@.@..", ".......&&...&........@...", "....&..&.................", "...&.&&&.................", "...&&.&&&&&&......&......", ".....&&............&.....", "........****......&......", ".......**..**....&&......", ".....****..*****&.&&.....", "....*************.&.&....", "...**..****.*..**.&.&....", "....*..**...****...&.....", "....******..****..&......", ".....******.***...&......", "...&&.**..***.*...&......", ".&&&&.**..**..*..&&......", "&&&&&.*********..&&......", ".&&&&............&&......", ".&&&&..&&&&&&&&..&&......", "..&&&&&&&....&&&&&&......", ".&&.&&.........&&&&&.....", ".....&...........&&&&....", "...................&&&&&.", "....................&&&&.", "......................&&.", "........................."}; mj-1.17-src/tiles-numbered/4C.xpm0000644006717300001440000000237207252317375015257 0ustar jcbusers/* XPM */ static char * 4C_xpm[] = { "25 35 14 1", " c None", ". c #EAEAEA", "+ c #F19C9C", "@ c #EEB6B6", "# c #ECD0D0", "$ c #FC1A1A", "% c #F84E4E", "& c #F38282", "* c #000000", "= c #FA3434", "- c #C60000", "; c #550000", "> c #1C0000", ", c #B00000", ".........................", "...+@....................", "..#$+....................", "..%&+....................", ".+&++......*********.....", ".%%=-..*****..**..***....", "...;;*****....***..***...", "...>>*..***...***..***...", "...***...***..**..****...", "....**....**.**...***....", ".....**..***********.....", "......****.......**......", ".......*.................", ".........................", ".........................", ".........................", ".............,...........", "............,,,,.........", "........,..,..,,.........", "........,,...,,..........", ".........,.,,,,,,........", ".......,,,,,.,...........", "...........,,,,,.........", ".......,.,,,,..,,........", ".......,,..,,,,,,........", "........,.,,,.,,,........", "........,,..,,,,.........", "........,,,,,..,,,,......", "....,,...,.,,,,,..,,,....", ".....,,,,,,,,,..,..,,,...", "..,,,,,.....,.,,,,..,,,..", "...,.,,,,,,,,,,.,,..,,,..", ".....,..,,......,,..,,,..", "..............,,,,.,,,...", "................,,,,,...."}; mj-1.17-src/tiles-numbered/7B.xpm0000444006717300001440000000223407404770711015250 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #B00000", ". c #EAEAEA", "X c #063A30", /* pixels */ ".......... ..........", "......... . .........", ".......... . ..........", "........... . ...........", ".......... ..........", "......... . .........", ".......... ..........", "........... . ...........", ".......... . ..........", "......... . .........", ".......... ..........", ".........................", ".XXXXX....XXXXX....XXXXX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XX.XX....XX.XX....XX.XX.", "..X.X......X.X......X.X..", ".XXXXX....XXXXX....XXXXX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XXXXX....XXXXX....XXXXX.", "..X.X......X.X......X.X..", ".XX.XX....XX.XX....XX.XX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XXXXX....XXXXX....XXXXX.", ".........................", ".XXXXX....XXXXX....XXXXX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XX.XX....XX.XX....XX.XX.", "..X.X......X.X......X.X..", ".XXXXX....XXXXX....XXXXX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XXXXX....XXXXX....XXXXX.", "..X.X......X.X......X.X..", ".XX.XX....XX.XX....XX.XX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XXXXX....XXXXX....XXXXX." }; mj-1.17-src/tiles-numbered/9B.xpm0000444006717300001440000000223407404770712015253 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #B00000", ". c #EAEAEA", "X c #063A30", /* pixels */ ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XX.XX.... . ....XX.XX.", "..X.X...... . ......X.X..", ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX.", "..X.X...... . ......X.X..", ".XX.XX.... . ....XX.XX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX.", ".........................", ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XX.XX.... . ....XX.XX.", "..X.X...... . ......X.X..", ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX.", "..X.X...... . ......X.X..", ".XX.XX.... . ....XX.XX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX.", ".........................", ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XX.XX.... . ....XX.XX.", "..X.X...... . ......X.X..", ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX.", "..X.X...... . ......X.X..", ".XX.XX.... . ....XX.XX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX." }; mj-1.17-src/tiles-numbered/3S.xpm0000644006717300001440000000227607252320534015270 0ustar jcbusers/* XPM */ static char * 3S_xpm[] = { "25 35 10 1", " c None", ". c #EAEAEA", "+ c #B00000", "@ c #063A30", "# c #D0D0D0", "$ c #686868", "% c #4E4E4E", "& c #B6B6B6", "* c #828282", "= c #9C9C9C", ".........................", "...+....+.......@...#$%#.", "...++...+.......@@..$##$.", "...+++..++++.....@@.&.#$.", ".+++..+++......@@@@@.&%*.", "...++...+......@..@.&..%.", "...+++.+++.....@@.@.***$.", ".+++..+..++.....@@@..&=..", "...+++....++....@@@......", "...++......++....@@......", "...+.............@@......", ".................@@......", ".@@@@@.....@@@@@@@@@.....", "@@@..@......@..@@..@.....", ".@@@@@......@@.@@..@.....", ".........++..@@@@@@@.....", "......+++.++...@...@.....", ".....++....++....@@@@....", ".....+......+...@@..@....", "....+++++...++..@@@@@....", "...++.+++++..++...@@.....", "...++.++..+...+..........", "...++++++..+..+..........", "...+.+..++.+..+..........", "...+.++.++.+..+.....@@@..", "...++++++++++++....@@.@..", "....++++.++++...@.@..@@..", ".....+++++++...@@@@.@@...", "@@@@@@@@@@......@@@@@....", "@..@@.@@@@@@@@@@@@@@.....", "..@@..@..@@.....@@@@@....", "@@....@.@@........@@@@@..", "......@@@...........@@@@@", ".......@................@", "........................."}; mj-1.17-src/tiles-numbered/1B.xpm0000444006717300001440000000223407404770707015247 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #B00000", ". c #EAEAEA", "X c #063A30", /* pixels */ ".........................", "...X........... . . . ...", "...X..... .X.... . . . ..", "..XX. . X... . . . . .", ".XX..... ..X. . . . . ", ".XX..... X.. . . .", ".XX... . ..X. . . . . ", "X.X.. . . ...X. . .", "X.X.. ... X..X X X . ", "X.X.. .. ..X.X.XX . .", "X.X.. .X...X.X X X . . ", "X.X.. ..X..X.X.X X X . .", "X..X. ..X..XXXXXXXX . . ", "X.XX. ..X.X . . .XX .. .", "X .X. ..XXX X X XXX.. ...", "X. XXXX.X. . . . XX......", "X .XX.X.XX X X X XXXX....", "X. XX.X.X . . . .XXXXX...", "X .XX...XXXXXXXXX.XX..XX.", "X.XXX...XXX. . XX...X...X", "XXX.XX..XX. . .X.XX..X...", "XX...XXXXX . . .X.XXX....", "X.........X . .X.X.X.XX..", ".......... XXXXXXXXXXXXXX", "......... .. . .......", "........ . .. .. .......", "....... ... .. ... ......", "...... ....... .........", "...... . ...... .........", "...... ...XXXXX X........", "...XXXX XX.... ..X.X.X...", ".X.X.......... ..X..X....", "..XX....... . ...XXX.X...", "...XX....... .....X...X.", "XXXX..... .. .....XXXXX" }; mj-1.17-src/tiles-numbered/NW.xpm0000644006717300001440000000227607252316407015333 0ustar jcbusers/* XPM */ static char * NW_xpm[] = { "25 35 10 1", " c None", ". c #EAEAEA", "+ c #F19C9C", "@ c #FC1A1A", "# c #F56868", "$ c #FA3434", "% c #ECD0D0", "& c #EEB6B6", "* c #F38282", "= c #000000", ".........................", ".+@..++..................", ".+##.++..................", ".++$%++..................", ".++&#++..................", ".++.$*+..................", ".++.%@+..................", ".%%..&%..................", ".........................", ".............===.........", ".............=====.......", "..............====.......", ".........=....====.......", ".......====...===........", ".......=====..==..===....", "........====..==..====...", ".........==...==.======..", "...=====.==...=======....", "...=====.==...====.......", "....===..==...==.........", ".........==...==.........", ".........==.=.==.........", "...=.....===..===........", "...==...===...=========..", "...========....========..", "..======.=.......=====...", "...===...=...............", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................."}; mj-1.17-src/tiles-numbered/4S.xpm0000644006717300001440000000231507252320606015263 0ustar jcbusers/* XPM */ static char * 4S_xpm[] = { "25 35 11 1", " c None", ". c #EAEAEA", "+ c #B00000", "@ c #9C9C9C", "# c #B6B6B6", "$ c #063A30", "% c #D0D0D0", "& c #1A1A1A", "* c #4E4E4E", "= c #828282", "- c #343434", ".........................", "......+++.............@#.", "...++++++...$........%&@.", "....++.+....$........*=@.", ".....+++....$$......@=@@.", "....++++....$$......**--.", "..++...++...$$........@@.", ".++.....++.$.$........%%.", ".+...+++..+$$$...........", "............$$.....$$$$..", "....++++....$.....$$..$$.", "...........$$$$$.$$....$.", "...........$$$...$$$$$$$.", "......$$$.$$.....$$......", ".....$$.$$$.....$$$$.....", ".....$$$$$$.....$$.$$....", ".....$..$$......$..$.....", "........$.++++++$$$$.....", "..........+....+.........", ".......++++...++++.......", "......++.++++.+++++......", "......++..+++++...+......", ".......+..+..++...+......", ".......++++.+++...+......", ".........+++.++..++......", ".....$$..+...+++++.......", "..$$$$$$.+...+.....$$....", "..$....$$+++++..$$$$$$...", "..$$$...$$......$....$$..", "....$$$$$$.$$$.$$.....$$.", "........$$$$.$$$$$$$...$.", ".........$$..$$$...$$$$$.", ".........$$...$..........", ".........................", "........................."}; mj-1.17-src/tiles-numbered/XX.xpm0000444006717300001440000000217607404770712015345 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 1 1", /* colors */ " c #FF0000", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " }; mj-1.17-src/tiles-numbered/tongN.xpm0000644006717300001440000000303707252321606016065 0ustar jcbusers/* XPM */ static char * tongN_xpm[] = { "35 35 10 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #FFAAAA", "# c #FF1D1D", "$ c #FF7272", "% c #FF3939", "& c #FFE3E3", "* c #FFC7C7", "= c #FF8E8E", " ......... ", " ...+++++++++... ", " ..+++++++++++++++.. ", " .+++++++++++++++++++. ", " .+@#++@@++++++++++++++. ", " .++@$$+@@+++++++++++++++. ", " .+++@@%&@@++++++++++++++++. ", " .++++@@*$@@+++++++++++++++++. ", " .+++++@@+%=@++++++++++++++++++. ", " .+++++@@+&#@++++...+++++++++++. ", " .++++++&&++*&++++.....++++++++++. ", " .+++++++++++++++++....++++++++++. ", " .++++++++++++.++++....++++++++++. ", ".+++++++++++....+++...++++++++++++.", ".+++++++++++.....++..++...++++++++.", ".++++++++++++....++..++....+++++++.", ".+++++++++++++..+++..+......++++++.", ".+++++++.....+..+++.......++++++++.", ".+++++++.....+..+++....+++++++++++.", ".++++++++...++..+++..+++++++++++++.", ".+++++++++++++..+++..+++++++++++++.", ".+++++++++++++..+.+..+++++++++++++.", " .++++++.+++++...++...+++++++++++. ", " .++++++..+++...+++.........+++++. ", " .++++++........++++........+++++. ", " .++++......+.+++++++.....+++++. ", " .+++++...+++.+++++++++++++++++. ", " .+++++++++++++++++++++++++++. ", " .+++++++++++++++++++++++++. ", " .+++++++++++++++++++++++. ", " .+++++++++++++++++++++. ", " .+++++++++++++++++++. ", " ..+++++++++++++++.. ", " ...+++++++++... ", " ......... "}; mj-1.17-src/tiles-numbered/WD.xpm0000444006717300001440000000221507404770712015312 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #002060", /* pixels */ " ", " ", " ...................... ", " . . ", " . .................. . ", " . . . . . . ", " . .. .. . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . .. .. . ", " . . . . . . ", " . .................. . ", " . . ", " ...................... ", " ", " " }; mj-1.17-src/tiles-numbered/8C.xpm0000644006717300001440000000231507252317615015255 0ustar jcbusers/* XPM */ static char * 8C_xpm[] = { "25 35 11 1", " c None", ". c #EAEAEA", "+ c #F38282", "@ c #F56868", "# c #ECD0D0", "$ c #EEB6B6", "% c #000000", "& c #F84E4E", "* c #F19C9C", "= c #FA3434", "- c #B00000", ".........................", "..+@#....................", ".$+#@....................", ".$+#@.......%............", ".#&&*.....%%%%...........", ".&#.=.......%............", ".+@+&........%...........", "..$*...%.....%%..........", "........%%....%%.........", ".......%%%%....%%%.......", ".......%%.%.....%%%%%....", "......%%.........%%%%%%%.", "....%%............%%%%...", ".%%%.....................", ".........................", ".........................", ".............-...........", "............----.........", "........-..-..--.........", "........--...--..........", ".........-.------........", ".......-----.-...........", "...........-----.........", ".......-.----..--........", ".......--..------........", "........-.---.---........", "........--..----.........", "........-----..----......", "....--...-.-----..---....", ".....---------..-..---...", "..-----.....-.----..---..", "...-.----------.--..---..", ".....-..--......--..---..", "..............----.---...", "................-----...."}; mj-1.17-src/tiles-numbered/3D.xpm0000444006717300001440000000223407404770711015246 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #002065", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXX XXXXXXXXXXXXXXXXX", "XX XXX XXXXXXXXXXXXXXXX", "X X X XXXXXXXXXXXXXXX", " X X X X XXXXXXXXXXXXXX", " X X X X X XXXXXXXXXXXXXX", " X X X X XXXXXXXXXXXXXX", " X X X X X XXXXXXXXXXXXXX", " X X X X XXXXXXXXXXXXXX", "X X X XXXXXXXXXXXXXXX", "XX XXX XXXXXXXXXXXXXXXX", "XXX XXXXXXXXXXXXXXXXX", "XXXXXXXXXX.....XXXXXXXXXX", "XXXXXXXXX..XXX..XXXXXXXXX", "XXXXXXXX..X...X..XXXXXXXX", "XXXXXXX..X.X.X.X..XXXXXXX", "XXXXXXX.X.X.X.X.X.XXXXXXX", "XXXXXXX.X..X.X..X.XXXXXXX", "XXXXXXX.X.X.X.X.X.XXXXXXX", "XXXXXXX..X.X.X.X..XXXXXXX", "XXXXXXXX..X...X..XXXXXXXX", "XXXXXXXXX..XXX..XXXXXXXXX", "XXXXXXXXXX.....XXXXXXXXXX", "XXXXXXXXXXXXXXXXX XXX", "XXXXXXXXXXXXXXXX XXX XX", "XXXXXXXXXXXXXXX X X X", "XXXXXXXXXXXXXX X X X X ", "XXXXXXXXXXXXXX X X X X X ", "XXXXXXXXXXXXXX X X X X ", "XXXXXXXXXXXXXX X X X X X ", "XXXXXXXXXXXXXX X X X X ", "XXXXXXXXXXXXXXX X X X", "XXXXXXXXXXXXXXXX XXX XX", "XXXXXXXXXXXXXXXXX XXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-numbered/2B.xpm0000444006717300001440000000221507404770710015241 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ..... ", " ... ... ", " .. .. ", " . . ", " . . ", " . . ", " ..... ", " ... ... ", " ..... ", " . . ", " . . ", " . . ", " .. .. ", " ... ... ", " ..... ", " ", " ", " ", " ", " ", " ..... ", " ... ... ", " .. .. ", " . . ", " . . ", " . . ", " ..... ", " ... ... ", " ..... ", " . . ", " . . ", " . . ", " .. .. ", " ... ... ", " ..... " }; mj-1.17-src/tiles-numbered/9D.xpm0000444006717300001440000000223407404770712015255 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #002065", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXX XXXXX XXXXX XXX", "X XXX X XXX X XXX X", "X X X X X X X X X X", " X X X X X X X X X ", " X X X X X X X X X X X X ", " X X X X X X X X X ", "X X X X X X X X X X", "X XXX X XXX X XXX X", "XXX XXXXX XXXXX XXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXX...XXXXX...XXXXX...XXX", "X..XXX..X..XXX..X..XXX..X", "X.X...X.X.X...X.X.X...X.X", ".X..X..X.X..X..X.X..X..X.", ".X.X.X.X.X.X.X.X.X.X.X.X.", ".X..X..X.X..X..X.X..X..X.", "X.X...X.X.X...X.X.X...X.X", "X..XXX..X..XXX..X..XXX..X", "XXX...XXXXX...XXXXX...XXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXX XXXXX XXXXX XXX", "X XXX X XXX X XXX X", "X X X X X X X X X X", " X X X X X X X X X ", " X X X X X X X X X X X X ", " X X X X X X X X X ", "X X X X X X X X X X", "X XXX X XXX X XXX X", "XXX XXXXX XXXXX XXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-numbered/2S.xpm0000644006717300001440000000231507252320461015260 0ustar jcbusers/* XPM */ static char * 2S_xpm[] = { "25 35 11 1", " c None", ". c #EAEAEA", "+ c #B00000", "@ c #D0D0D0", "# c #686868", "$ c #063A30", "% c #4E4E4E", "& c #B6B6B6", "* c #9C9C9C", "= c #343434", "- c #828282", ".........................", ".......+............@##@.", ".+++++++...$........#@@%.", "....++.....$$.......&..%.", "...+++++....$...$$...@%*.", "...+...+....$$..$...@=@..", "...+++++.....$..$...%-**.", "..++++.+.....$.$$$..&***.", ".++..+++.....$.$$$.......", ".....++......$$..$.......", "..+++++++....$$$$........", ".............$$$.....$$$.", "..$$$...$$...$$.....$$$..", "....$$.$$....$$....$$$$$.", ".....$$$..........$$.....", ".....$$.....++....$......", ".....$$...+++++...$$$....", "......$..+++..++.$$......", "......$..+.++..+.$.......", "......$..+..+..++........", "......$.+++.++..+........", ".$$.....+.+++++.+........", "..$$....+..++++.+........", "....$...+..++.++++.......", "....$$.$++++..++++.$$....", ".....$$$..++...++.$$.....", ".....$$....+++++..$......", "..$$..$$..$$$............", "...$...$$...$$$$$$.......", "....$...$................", ".....$..$$...$$..........", "......$.$$$$$$$$$$.......", "......$$$$......$$.......", ".................$.......", "........................."}; mj-1.17-src/tiles-numbered/6C.xpm0000644006717300001440000000227607252317516015261 0ustar jcbusers/* XPM */ static char * 6C_xpm[] = { "25 35 10 1", " c None", ". c #EAEAEA", "+ c #F38282", "@ c #F84E4E", "# c #EEB6B6", "$ c #000000", "% c #ECD0D0", "& c #F19C9C", "* c #F56868", "= c #B00000", ".........................", "..+@#....................", ".++.@......$$............", ".@%&........$$...........", ".@*+*.......$$$..$.......", ".@..@.......$$..$$$......", ".+*&@......$$$$$$$$$.....", "..#&$$$$$$$$$.....$$$....", ".....$$....$......$$$....", "........$$...$$$...$.....", ".........$$$..$$$........", ".........$$....$$$$......", "........$$......$$$......", ".......$$........$$......", "......$...........$......", ".........................", ".............=...........", "............====.........", "........=..=..==.........", "........==...==..........", ".........=.======........", ".......=====.=...........", "...........=====.........", ".......=.====..==........", ".......==..======........", "........=.===.===........", "........==..====.........", "........=====..====......", "....==...=.=====..===....", ".....=========..=..===...", "..=====.....=.====..===..", "...=.==========.==..===..", ".....=..==......==..===..", "..............====.===...", "................=====...."}; mj-1.17-src/tiles-numbered/9C.xpm0000644006717300001440000000233407252317650015256 0ustar jcbusers/* XPM */ static char * 9C_xpm[] = { "25 35 12 1", " c None", ". c #EAEAEA", "+ c #ECD0D0", "@ c #F56868", "# c #F84E4E", "$ c #EEB6B6", "% c #F19C9C", "& c #000000", "* c #F38282", "= c #FC1A1A", "- c #FA3434", "; c #B00000", ".........................", ".+@#$....................", ".@%+#....................", ".#..#..&&&&...&&.........", ".*#*=...&&&&...&&&.......", "..$$-....&&...&&&&&......", ".@**@....&&&&&&&.&&......", "..$$.&&&&&&&&&&..........", ".........&&..&.....&.....", "........&&&..&.....&.....", "........&&...&.....&&....", ".......&&....&&...&&&....", "......&&......&&&&&&&&...", ".....&&........&&&&&&....", "...&&....................", ".........................", ".............;...........", "............;;;;.........", "........;..;..;;.........", "........;;...;;..........", ".........;.;;;;;;........", ".......;;;;;.;...........", "...........;;;;;.........", ".......;.;;;;..;;........", ".......;;..;;;;;;........", "........;.;;;.;;;........", "........;;..;;;;.........", "........;;;;;..;;;;......", "....;;...;.;;;;;..;;;....", ".....;;;;;;;;;..;..;;;...", "..;;;;;.....;.;;;;..;;;..", "...;.;;;;;;;;;;.;;..;;;..", ".....;..;;......;;..;;;..", "..............;;;;.;;;...", "................;;;;;...."}; mj-1.17-src/tiles-small/0000755006717300001440000000000010627612770013563 5ustar jcbusersmj-1.17-src/tiles-small/2D.xpm0000444006717300001440000000226407623736151014562 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 28 1", /* colors */ " c #6779A0", ". c #2F4880", "X c #96A2BA", "o c #5E719A", "O c #26407A", "+ c #D7DADF", "@ c #385185", "# c #002065", "$ c #EAEAEA", "% c #133070", "& c #C4CAD5", "* c #8C99B5", "= c #546895", "- c #BBC2CF", "; c #8391AF", ": c #25407A", "> c #CED2DA", ", c #7081A5", "< c #385085", "1 c #9FAABF", "2 c #67799F", "3 c #09286A", "4 c #8391B0", "5 c #4B6090", "6 c #7A89AA", "7 c #546995", "8 c #4B618F", "9 c None", /* pixels */ "$$$$$$$$$$$$$$$$$$$$", "$$$$$$$$$$$$$$$$$$$$", "$$$$$$$$oooo$$$$$$$$", "$$$$$$$=:--:=$$$$$$$", "$$$$$+ 1X##X1 +$$$$$", "$$$$+7*.8OO8.*7+$$$$", "$$$$-<,%2662%,<-$$$$", "$$$$-oo#1 1#oo-$$$$", "$$$$-8 35;;53 8-$$$$", "$$$$>@*.,<<,.*@>$$$$", "$$$$$&,4,##,4,&$$$$$", "$$$$$$>55--55>$$$$$$", "$$$$$$$$....$$$$$$$$", "$$$$$$$$$$$$$$$$$$$$", "$$$$$$$$$$$$$$$$$$$$", "$$$$$$$$....$$$$$$$$", "$$$$$$>55--55>$$$$$$", "$$$$$&,4,##,4,&$$$$$", "$$$$>@*.,<<,.*@>$$$$", "$$$$-8 35;;53 8-$$$$", "$$$$-oo#1 1#oo-$$$$", "$$$$-<,%2662%,<-$$$$", "$$$$+7*.8OO8.*7+$$$$", "$$$$$+ 1X##X1 +$$$$$", "$$$$$$$=:--:=$$$$$$$", "$$$$$$$$oooo$$$$$$$$", "$$$$$$$$$$$$$$$$$$$$", "$$$$$$$$$$$$$$$$$$$$" }; mj-1.17-src/tiles-small/tongW.xpm0000444006717300001440000000253007623736151015407 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "28 28 24 1", /* colors */ " c #000000", ". c #ADADAD", "X c #A3A3A3", "o c #999999", "O c #8F8F8F", "+ c #858585", "@ c #3D3D3D", "# c #333333", "$ c #E0E0E0", "% c #292929", "& c #D6D6D6", "* c #1F1F1F", "= c #CCCCCC", "- c #C2C2C2", "; c #7A7A7A", ": c #707070", "> c #666666", ", c #5C5C5C", "< c #525252", "1 c #FFFFFF", "2 c #EBEBEB", "3 c #141414", "4 c #0A0A0A", "5 c None", /* pixels */ "5555555555*######*5555555555", "55555555>>-111111->>55555555", "555555,o111111111111o,555555", "55555<2111111111111112<55555", "5555,111111111111111111,5555", "555<1111111111111&o21111<555", "55,2111111111111$@ <11112,55", "55o1111111111.##% #+111o55", "5>1112===;*###O=&2< o111>5", "5>111-% >>-111X3*,@>>>-111>5", "*-1111-o1111111# @&1111111-*", "#1111111&#.111+4%$111111111#", "#11111111o =12:4#1111111111#", "#11111111-%;1o,**o&11111111#", "#11111111-% >@ *,@>1111111#", "#1111111&* X<=%#2< ,111111#", "#11111> 4++4X>1#,11# >11111#", "*-1111> #11# >-*11-* >1111-*", "5>1111-*3X1. %% >>% *-1111>5", "5>11111& 3## &11111>5", "55o11111&* %,111 *&11111o55", "55,211111->$1111>>-111112,55", "555<11111111111111111111<555", "5555,111111111111111111,5555", "55555<2111111111111112<55555", "555555,o111111111111o,555555", "55555555>>-111111->>55555555", "5555555555*######*5555555555" }; mj-1.17-src/tiles-small/1D.xpm0000444006717300001440000000237707623736151014566 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 33 1", /* colors */ " c #C5CAD5", ". c #C4CAD4", "X c #6779A0", "o c #2F4880", "O c #5E719A", "+ c #26407A", "@ c #002065", "# c #6A0D28", "$ c #B92626", "% c #EAEAEA", "& c #B2B9CA", "* c #8C99B5", "= c #CC7070", "- c #710B24", "; c #8391AF", ": c #D59696", "> c #B92525", ", c #26407B", "< c #25407A", "1 c #C24B4B", "2 c #9FA9C0", "3 c #7081A5", "4 c #9FAABF", "5 c #67799F", "6 c #E1E2E5", "7 c #09286A", "8 c #8391B0", "9 c #B2BACA", "0 c #546995", "q c #1C3875", "w c #BA2626", "e c #4B618F", "r c None", /* pixels */ "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%2****2%%%%%%%", "%%%%%9O8q00q8O9%%%%%", "%%%% Xe,4XX4,eX %%%%", "%%%.7o;6<33<6;o7.%%%", "%%&5o%O3@@@@3O%o5&%%", "%%Oe8O+@####@+O8eO%%", "%2;<63@->==>-@36<;2%", "%*q4,@#w:11:w#@,4q*%", "%*053@#=1$$1=#@350*%", "%*053@#=1$$1=#@350*%", "%*q4,@#w:11:w#@,4q*%", "%2;<63@->==>-@36<;2%", "%%Oe8O+@####@+O8eO%%", "%%&5o%O3@@@@3O%o5&%%", "%%%.7o;6<33<6;o7.%%%", "%%%% Xe,4XX4,eX %%%%", "%%%%%9O8q00q8O9%%%%%", "%%%%%%%2****2%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%" }; mj-1.17-src/tiles-small/8D.xpm0000444006717300001440000000222607623736151014566 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 26 1", /* colors */ " c #C4CAD4", ". c #6779A0", "X c #2F4880", "o c #5E719A", "O c #26407A", "+ c #EAEAEA", "@ c #B2B9CA", "# c #8C99B5", "$ c #BBC2CF", "% c #8391AF", "& c #26407B", "* c #25407A", "= c #9FA9C0", "- c #96A1BA", "; c #7081A5", ": c #385085", "> c #9FAABF", ", c #67799F", "< c #E1E2E5", "1 c #09286A", "2 c #8391B0", "3 c #4B6090", "4 c #7A89AA", "5 c #1C3875", "6 c #4B618F", "7 c None", /* pixels */ "++++<$$<++++<$$<++++", "+++=2oo2=++=2oo2=+++", "+++X2oo2X++X2oo2X+++", "++4>&66&>44>&66&>4++", "++o$*%%*$oo$*%%*$o++", "++-26::62--26::62-++", "+++X,##,X++X,##,X+++", "+++ >XX> ++ >XX> +++", "+++X3$$3X++X3$$3X+++", "++@,;OO;,@@,;OO;,@++", "++o$5445$oo$5445$o++", "++o$1..1$oo$1..1$o++", "+++X>XX>X++X>XX>X+++", "+++4,##,4++4,##,4+++", "+++4,##,4++4,##,4+++", "+++X>XX>X++X>XX>X+++", "++o$1..1$oo$1..1$o++", "++o$5445$oo$5445$o++", "++@,;OO;,@@,;OO;,@++", "+++X3$$3X++X3$$3X+++", "+++ >XX> ++ >XX> +++", "+++X,##,X++X,##,X+++", "++-26::62--26::62-++", "++o$*%%*$oo$*%%*$o++", "++4>&66&>44>&66&>4++", "+++X2oo2X++X2oo2X+++", "+++=2oo2=++=2oo2=+++", "++++<$$<++++<$$<++++" }; mj-1.17-src/tiles-small/2F.xpm0000444006717300001440000000322407623736151014561 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 60 1", /* colors */ " c #CED5D4", ". c #2B564E", "X c #9F9F9F", "o c #D89F9F", "O c #A1B2AE", "+ c #BCC7C5", "@ c #7C9590", "# c #C55454", "$ c #838383", "% c #587A73", "& c #B00000", "* c #214F46", "= c #E1C4C4", "- c #98ABA7", "; c #4B4B4B", ": c #738E89", "> c #EAEAEA", ", c #869D98", "< c #252525", "1 c #2A564E", "2 c #A1B2AF", "3 c #CC7070", "4 c #61807A", "5 c #CECECE", "6 c #D8DCDB", "7 c #131313", "8 c #C4C4C4", "9 c #18483F", "0 c #B2B2B2", "q c #8FA4A0", "w c #4F726B", "e c #C6CECC", "r c #C24B4B", "t c #E1E3E3", "y c #969696", "u c #063A30", "i c #3D645C", "p c #8C8C8C", "a c #587973", "s c #CFD5D4", "d c #707070", "f c #D38C8C", "g c #5E5E5E", "h c #545454", "j c #98AAA7", "k c #748F8A", "l c #738F89", "z c #345D55", "x c #4F726C", "c c #C5CECC", "v c #D7D7D7", "b c #BC2F2F", "n c #224F46", "m c #1C1C1C", "M c #3D645D", "N c #B3C0BD", "B c #BBBBBB", "V c #BA2626", "C c #E5D7D7", "Z c None", /* pixels */ ">>>>>>>>>>>>>>>>>vv>", ">=ffo>>>2>>>>>>>php>", ">f#fb>>>,j>>>>ygmpd5", ">CC#b>>>+4>>>>gy8>gB", ">CrV>>>>>qq>>v;yBddB", ">f&3o>>>>qq>>0g77ggg", ">=ffo>>>>qq>>>y7X>>>", ">>>>>>>>>qq>aX$<>>>>", ">>>>>>>>>qne>X$;h>>>", ">>>>>>>>6%q>>>y5>>>>", ">>js>>>>+Me>>>j4444s", ">O,t>>>a+4>azz9-1+x+", "+9O>++6>1+x+>>>>>>4u", "k4>>4iq>uuis>>>>>eMw", "u4>>>qq@*qe>>>>>>q%6", "+,O>>qqzc>>>>>>>>66>", ">>>a1lu.>>>>>66>a>>>", ">>>>4iu+>>>>>qq2>>>>", ">>>>>quw>>>>>e:,4j>>", ">>>>>quu>>>>>>4.+ >>", ">>>>>qqztlq>>>4+>>>>", ">>>>>qqz+1N>>>4+>>>>", ">>>>>qi9w4>>>>4+>>>>", ">>>>>6xuu9O>>>4+>>>>", ">>>>>Ozzuuq>>qne>>>>", ">>>>>>>>wu%66%q>>>>>", ">>>>>>>>6qq66qe>>>>>", ">>>>>>>>>>>>>>>>>>>>" }; mj-1.17-src/tiles-small/RD.xpm0000444006717300001440000000220707623736151014617 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 25 1", /* colors */ " c #E3CECE", ". c #D89F9F", "X c #C55454", "o c #CE7A7A", "O c #B20909", "+ c #B00000", "@ c #E1C4C4", "# c #B92626", "$ c #EAEAEA", "% c #E8E1E1", "& c #CC7070", "* c #D59696", "= c #C24B4B", "- c #B71C1C", "; c #B51313", ": c #DEBBBB", "> c #D38C8C", ", c #DCB2B2", "< c #D18383", "1 c #BE3838", "2 c #C75E5E", "3 c #BC2F2F", "4 c #BA2626", "5 c #E5D7D7", "6 c None", /* pixels */ "$$$$$$$$$$$$$$$$$$$$", "$$$$$$$$$$$$$$$$$$$$", "$$$$$$$$$$$$$$$$$$$$", "$$$$$$$$$..$$$$$$$$$", "$$$$$$$$4++*$$$$$$$$", "$$$$$$$$ 1+=$$$$$$$$", "$$$$$$$$$>+:$$$$$$$$", "$$$$$$$$$>+:$$$$$$$$", "$5:%$$$$%&+*::=+: $$", "$,#&$$$.&++=222;+1@$", "$$,-o,2<>X+:$$$3++>$", "$$$3O-&%$>+:$$$3++>$", "$$$$++&%$>+:$5=+4;.$", "$$$$=++&>X+&>X++ *$$", "$$$$:++&>X+&&++&$$$$", "$$$$%&+:$>+:%&&%$$$$", "$$$$$$$$$>+:$$$$$$$$", "$$$$$$$$$>+:$$$$$$$$", "$$$$$$$$$>+:$$$$$$$$", "$$$$$$$$$>+:$$$$$$$$", "$$$$$$$$$>+:$$$$$$$$", "$$$$$$$$$>+:$$$$$$$$", "$$$$$$$$$>+:$$$$$$$$", "$$$$$$$$$>+:$$$$$$$$", "$$$$$$$$$>>$$$$$$$$$", "$$$$$$$$$,,$$$$$$$$$", "$$$$$$$$$$$$$$$$$$$$", "$$$$$$$$$$$$$$$$$$$$" }; mj-1.17-src/tiles-small/6D.xpm0000444006717300001440000000362307623736151014566 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 77 1", /* colors */ " c #5F729C", ". c #EFDADA", "X c #E6ADAD", "o c #C5CAD5", "O c #C4CAD4", "+ c #C95252", "@ c #E5D0D0", "# c #C95F5F", "$ c #5C709C", "% c #DADDE6", "& c #DC8F8F", "* c #A3AEC7", "= c #D0D3DC", "- c #2F4880", "; c #9BA6BF", ": c #B61414", "> c #E1C5C5", ", c #96A2BA", "< c #C55454", "1 c #EFCCCC", "2 c #5E719A", "3 c #ABB5CB", "4 c #3D558A", "5 c #D7DADF", "6 c #BEC5D3", "7 c #0A296B", "8 c #526797", "9 c #C33D3D", "0 c #ECECEC", "q c #EAEAEA", "w c #8594B5", "e c #B2B9CA", "r c #143271", "t c #E3ABAB", "y c #D67A7A", "u c #CCD2E0", "i c #546895", "p c #8F9DBB", "a c #BBC2CF", "s c #CC5C5C", "d c #BD2929", "f c #526896", "g c #D59696", "h c #CC6969", "j c #99A6C1", "k c #DF9999", "l c #ADB7CE", "z c #29447E", "x c #C24B4B", "c c #B71C1C", "v c #7082A9", "b c #697CA2", "n c #334D84", "m c #EACECE", "M c #E0C4C4", "N c #7A8BAF", "B c #D98585", "V c #3D568A", "C c #CA5252", "Z c #B30A0A", "A c #DEBBBB", "S c #DCB2B2", "D c #EFEFEF", "F c #EDEDED", "G c #E2BEBE", "H c #C03333", "J c #C75E5E", "K c #BC2F2F", "L c #546995", "P c #E2A3A3", "I c #1C3875", "U c #D37070", "Y c #DA9B9B", "T c #E5D7D7", "R c #CED4DE", "E c #4B618F", "W c None", /* pixels */ "q0%znnr*FqqF*rnnz%0q", "0RV8jjNr3DD3rNjj8VR0", "=4$vNVNpr;;rpNVNv$4=", "aEblfppf,22,fppflbEa", "a zlvv762267vvlz a", "aVNpvwwvN22NvwwvpNVa", "5L4p8zwvIeeIvwz8p4L5", "q5Ezuuv7OqqO7vuuzE5q", "qqqi---oqqqqo---iqqq", "qqqqqqqqqqqqqqqqqqqq", "qqqqqqqqqqqqqqqqqqqq", "qqqqqqq>KKK c #666666", ", c #5C5C5C", "< c #525252", "1 c #FFFFFF", "2 c #F5F5F5", "3 c #EBEBEB", "4 c #141414", "5 c #0A0A0A", "6 c None", /* pixels */ "6666666666*######*6666666666", "66666666>>-111111->>66666666", "666666,o111++1111111o,666666", "66666<31111&5##&111113<66666", "6666,1111111&* ,111111,6666", "666<111111111o 11111111<666", "66,3111111111o ;111111113,66", "66o1111111111o =,11111111o66", "6>11111111112; <31111111>6", "6>11111111-oO@ 4>-1111111>6", "*-11111111-o.o ;4X11111111-*", "#111111111111o;2 4.11111111#", "#11111111o;=2; ;==$11111#", "#11111111-% ; < %>4 @&1111#", "#11111111-% @o: >1. o1111#", "#1111..1&* %O#5%$11 o1111#", "#11111>X,1$%5* XX ;2 >11111#", "*-1111X4.1X>*, >>>$ >1111-*", "6>11111#4XX>*, ;1111 >1111>6", "6>11111& 4O=&o;2111, >1111>6", "66o11111&*o111+#% >111o66", "66,311111--11111$>>44X113,66", "666<111111111111111..111<666", "6666,111111111111111111,6666", "66666<3111111111111113<66666", "666666,o111111111111o,666666", "66666666>>-111111->>66666666", "6666666666*######*6666666666" }; mj-1.17-src/tiles-small/6B.xpm0000444006717300001440000000224507623736151014563 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 27 1", /* colors */ " c #CED5D4", ". c #BCC7C5", "X c #7C9590", "o c #587A73", "O c #214F46", "+ c #98ABA7", "@ c #B3C0BE", "# c #EAEAEA", "$ c #0F4137", "% c #2A564E", "& c #A1B2AF", "* c #61807A", "= c #D8DCDB", "- c #18483F", "; c #8FA4A0", ": c #4F726B", "> c #E1E3E3", ", c #063A30", "< c #3D645C", "1 c #98AAA7", "2 c #748F8A", "3 c #738F89", "4 c #345D55", "5 c #4F726C", "6 c #C5CECC", "7 c #224F46", "8 c None", /* pixels */ "+,7$% #6,--,6# %$7,+", ":,;4-1#X,**,X#1-4;,:", "=o;4&###2**2###&4;o=", "#;;4####.**.####4;;#", ">33%6###+55+###6%33>", "2,<-4##&,%%,&#@O-<,2", "2,<-4##&,%%,&#@O-<,2", ">33%6###+55+###6%33>", "#;;4####.**.####4;;#", "=o;4&###2**2###&4;o=", ":,;4-1#X,**,X#1-4;,:", "+,7$% #6,--,6# %$7,+", "####################", "####################", "####################", "####################", "+,7$% #6,--,6# %$7,+", ":,;4-1#X,**,X#1-4;,:", "=o;4&###2**2###&4;o=", "#;;4####.**.####4;;#", ">33%6###+55+###6%33>", "2,<-4##&,%%,&#@O-<,2", "2,<-4##&,%%,&#@O-<,2", ">33%6###+55+###6%33>", "#;;4####.**.####4;;#", "=o;4&###2**2###&4;o=", ":,;4-1#X,**,X#1-4;,:", "+,7$% #6,--,6# %$7,+" }; mj-1.17-src/tiles-small/tongE.xpm0000444006717300001440000000251107623736151015364 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "28 28 23 1", /* colors */ " c #000000", ". c #ADADAD", "X c #A3A3A3", "o c #999999", "O c #858585", "+ c #3D3D3D", "@ c #333333", "# c #E0E0E0", "$ c #292929", "% c #D6D6D6", "& c #1F1F1F", "* c #CCCCCC", "= c #C2C2C2", "- c #7A7A7A", "; c #666666", ": c #5C5C5C", "> c #525252", ", c #FFFFFF", "< c #F5F5F5", "1 c #EBEBEB", "2 c #141414", "3 c #0A0A0A", "4 c None", /* pixels */ "4444444444&@@@@@@&4444444444", "44444444;;=,,,,,,=;;44444444", "444444:o,,,,#;;#,,,,o:444444", "44444>1,,,,,<- $,,,,,1>44444", "4444:,,,,,,,,o $,,,,,,,:4444", "444>,,,,,,,,,o *o=,,,,,,>444", "44:1,,,,,,,,,o > $=,,,,,1:44", "44o,,,,,,.@@@& o,,,,,,o44", "4;,,,,,,,,,:@& ***1,,,,,,,;4", "4;,,,,,,,,,,1: - :1,,,,,,;4", "&=,,,,,,#;=O> >,,,,,,=&", "@,,,,,,,* &3*- X**> :,,,,,,@", "@,,,,,,,* XX >> @,,,,,,@", "@,,,,,,,#+ ;+ >;+ O,,,,,,@", "@,,,,,,,,%+ ;+ >;+ -,,,,,,,@", "@,,,,,,,,,#$$- XX -<,,,,,,,@", "@,,,,,,,,,,% $$#,,,,,,,,,@", "&=,,,,,,,,=&2+ *>+%,,,,,,,=&", "4;,,,,,,,=$ .o ** +#,,,,,,;4", "4;,,,,,,%& X,o *<- $,,,,,,;4", "44o,,,#*@@@3*- *,.2 >1,,o44", "44:1,,X;,,,O *,,X;;;=,1:44", "444>,,,,,,,,- *,,,,,,,,>444", "4444:,,,,,,,<- *,,,,,,,:4444", "44444>1,,,,,,..,,,,,,1>44444", "444444:o,,,,,,,,,,,,o:444444", "44444444;;=,,,,,,=;;44444444", "4444444444&@@@@@@&4444444444" }; mj-1.17-src/tiles-small/5C.xpm0000444006717300001440000000301507623736151014557 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 51 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #E3CECE", "o c #D89F9F", "O c #C55454", "+ c #838383", "@ c #CE7A7A", "# c #B20909", "$ c #676767", "% c #B00000", "& c #E1C4C4", "* c #4B4B4B", "= c #B92626", "- c #EAEAEA", "; c #2F2F2F", ": c #D79F9F", "> c #E8E1E1", ", c #CC7070", "< c #CECECE", "1 c #131313", "2 c #C4C4C4", "3 c #090909", "4 c #D59696", "5 c #B92525", "6 c #B2B2B2", "7 c #C24B4B", "8 c #B71C1C", "9 c #969696", "0 c #8C8C8C", "q c #B51313", "w c #E0C4C4", "e c #7A7A7A", "r c #707070", "t c #DEBBBB", "y c #D38C8C", "u c #5E5E5E", "i c #545454", "p c #DCB2B2", "a c #BE3838", "s c #383838", "d c #E1E1E1", "f c #262626", "g c #D7D7D7", "h c #C75E5E", "j c #BC2F2F", "k c #1C1C1C", "l c #C5C5C5", "z c #BBBBBB", "x c #BA2626", "c c #E5D7D7", "v c None", /* pixels */ "--------------------", "------------.-------", "-----------e1u6-----", "----l;.--.;3 kl----", "----dr z;;1 --------", "----z z-2s*06------", "----z ig-0 * f6-----", "----f 0i;k kl----", "---23k0-f1.23$zz----", "---;e00-rs0kki 06--", "--9+----le--", "---------&a,o-------", "------4XX,y8j-------", "------hx>X@#w-------", "-----ct54qqxj@------", "-----phh7aa,o-------", "-----pp@7%Oyq4------", "-----y8:4%8j%h------", "------h4j8,5#@------", "------h%yOah8yyc----", "---@@-p8,%ah8ya7@---", "---w#jj#x%%4:@X5#@--", "-oqx%7ttt,,5x%y-%%y-", "--4Xqh=%hhhht%y-%%y-", "----o-py---@7%y@%a&-", "-----------w4%8#5X--" }; mj-1.17-src/tiles-small/1C.xpm0000444006717300001440000000256707623736151014566 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 41 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #E3CECE", "o c #D89F9F", "O c #C55454", "+ c #CE7A7A", "@ c #B20909", "# c #B00000", "$ c #E1C4C4", "% c #4B4B4B", "& c #B92626", "* c #EAEAEA", "= c #2F2F2F", "- c #D79F9F", "; c #E8E1E1", ": c #CC7070", "> c #CECECE", ", c #D59696", "< c #B92525", "1 c #B2B2B2", "2 c #C24B4B", "3 c #B71C1C", "4 c #8C8C8C", "5 c #B51313", "6 c #E0C4C4", "7 c #DEBBBB", "8 c #D38C8C", "9 c #5E5E5E", "0 c #545454", "q c #DCB2B2", "w c #BE3838", "e c #383838", "r c #262626", "t c #D7D7D7", "y c #C75E5E", "u c #BC2F2F", "i c #C5C5C5", "p c #BBBBBB", "a c #BA2626", "s c #E5D7D7", "d c None", /* pixels */ "********************", "********************", "********************", "********************", "**************>p****", "********t44444e 41**", "*1999999%e444444 r1*", "*t% %ppp>****** 4*", "***0************i=.*", "********************", "********************", "********************", "**********X;********", "*********$w:o*******", "******,XX:83u*******", "******ya;X+@6*******", "*****s7<,55au+******", "*****qyy2ww:o*******", "*****qq+2#O85,******", "*****83-,#3u#y******", "******y,u3:<@+******", "******y#8Owy388s****", "***++*q3:#wy38w2+***", "***6@uu@a##,-+X<@+**", "*o5a#2777:: c #CC7070", ", c #CECECE", "< c #131313", "1 c #C4C4C4", "2 c #090909", "3 c #D59696", "4 c #B92525", "5 c #C24B4B", "6 c #B71C1C", "7 c #969696", "8 c #8C8C8C", "9 c #B51313", "0 c #E0C4C4", "q c #7A7A7A", "w c #DEBBBB", "e c #D38C8C", "r c #5E5E5E", "t c #545454", "y c #DCB2B2", "u c #BE3838", "i c #383838", "p c #D7D7D7", "a c #C75E5E", "s c #BC2F2F", "d c #1C1C1C", "f c #C5C5C5", "g c #BBBBBB", "h c #BA2626", "j c #E5D7D7", "k c None", /* pixels */ "********************", "********************", "********************", "**********q=t*******", "****gggg2====q******", "****ri %q***********", "*****18p************", "*************.=f****", "**,ggggg7<=====2-,**", "**7< irr7*****= r**", "***.d81********. r**", "****1***********g,**", "**********X:********", "*********$u>o*******", "******3XX>e6s*******", "******ah:X+@0*******", "*****jw4399hs+******", "*****yaa5uu>o*******", "*****yy+5#Oe93******", "*****e6;3#6s#a******", "******a3s6>4@+******", "******a#eOua6eej****", "***++*y6>#ua6eu5+***", "***0@ss@h##3;+X4@+**", "*o9h#5www>>4h#e*##e*", "**3X9a&#aaaaw#e*##e*", "****o*ye***+5#e+#u$*", "***********03#6@4X**" }; mj-1.17-src/tiles-small/7C.xpm0000444006717300001440000000277607623736151014576 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 50 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #E3CECE", "o c #D89F9F", "O c #C55454", "+ c #838383", "@ c #CE7A7A", "# c #B20909", "$ c #B00000", "% c #E1C4C4", "& c #4B4B4B", "* c #B92626", "= c #EAEAEA", "- c #2F2F2F", "; c #D79F9F", ": c #E8E1E1", "> c #CC7070", ", c #CECECE", "< c #131313", "1 c #C4C4C4", "2 c #090909", "3 c #D59696", "4 c #B92525", "5 c #B2B2B2", "6 c #C24B4B", "7 c #B71C1C", "8 c #969696", "9 c #8C8C8C", "0 c #B51313", "q c #E0C4C4", "w c #7A7A7A", "e c #707070", "r c #DEBBBB", "t c #D38C8C", "y c #5E5E5E", "u c #545454", "i c #DCB2B2", "p c #BE3838", "a c #383838", "s c #E1E1E1", "d c #262626", "f c #D7D7D7", "g c #C75E5E", "h c #BC2F2F", "j c #1C1C1C", "k c #C5C5C5", "l c #BBBBBB", "z c #BA2626", "x c #E5D7D7", "c c None", /* pixels */ "====================", "====================", "====================", "======wk=====.---w==", "======w2-===8<--u===", "=======--==.y8======", "=======.-=8+========", "========-.+s========", "=======1 &f=l,======", "======5j& u9 a1=====", "====,ydefu a,====", "==w-d&ls=f& es====", "===uu=====X:========", "=========%p>o=======", "======3XX>t7h=======", "======gz:X@#q=======", "=====xr4300zh@======", "=====igg6pp>o=======", "=====ii@6$Ot03======", "=====t7;3$7h$g======", "======g3h7>4#@======", "======g$tOpg7ttx====", "===@@=i7>$pg7tp6@===", "===q#hh#z$$3;@X4#@==", "=o0z$6rrr>>4z$t=$$t=", "==3X0g*$ggggr$t=$$t=", "====o=it===@6$t@$p%=", "===========q3$7#4X==" }; mj-1.17-src/tiles-small/7D.xpm0000444006717300001440000000277607623736151014577 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 50 1", /* colors */ " c #C4CAD4", ". c #E3CECE", "X c #D89F9F", "o c #6779A0", "O c #2F4880", "+ c #D08383", "@ c #96A2BA", "# c #C55454", "$ c #5E719A", "% c #26407A", "& c #D7DADF", "* c #385185", "= c #CE7A7A", "- c #C34B4B", "; c #002065", ": c #EAEAEA", "> c #133070", ", c #8C99B5", "< c #D79F9F", "1 c #CC7070", "2 c #546895", "3 c #BBC2CF", "4 c #8391AF", "5 c #B92525", "6 c #CA6767", "7 c #26407B", "8 c #C24B4B", "9 c #B71C1C", "0 c #CED2DA", "q c #B51313", "w c #7081A5", "e c #385085", "r c #DEBBBB", "t c #D38C8C", "y c #67799F", "u c #E1E2E5", "i c #DCB2B2", "p c #D18383", "a c #09286A", "s c #8391B0", "d c #C96767", "f c #BE3838", "g c #4B6090", "h c #7A89AA", "j c #C75E5E", "k c #BC2F2F", "l c #546995", "z c #E5D7D7", "x c #4B618F", "c c None", /* pixels */ "u3oOg3&:::::::::::::", "3%h,s*,:::::::::::::", "yh%w>ww00$$0::::::::", "O,wg@$$7g33g2:::::::", "g4>@aox7@>>@g0: 30::", "3ew$yl;3>ww>3e,y$w,&", "&,w$y,e3>ww>3;ly$we3", "::03 :0g@>>@7xoa@>4g", ":::::::2g33g7$$@gw,O", "::::::::0$$00ww>w%hy", ":::::::::::::,*s,h%3", ":::::::::::::&3gOo3u", "::::rrz::::::zrr::::", "::itjj=t::::t=jjti::", "::j1jj=9::::9=jj1j::", ":X+-519<#::#<915-+X:", ":ttk<68rk::kr86 c #E1E3E3", ", c #063A30", "< c #7D9691", "1 c #3D645C", "2 c #CFD5D4", "3 c #98AAA7", "4 c #748F8A", "5 c #738F89", "6 c #345D55", "7 c #4F726C", "8 c #C5CECC", "9 c #224F46", "0 c None", /* pixels */ "O,9#$ @@@@@@@@ $#9,O", ";,-6=3@@@@@@@@3=6-,;", "*X-6%@@@@@@@@@@%6-X*", "@--6@@@@6666@@@@6--@", ">55$8@@@#:7,@@@8$55>", "4,1=o++-:X,;-++o=1,4", "4,1=o<$,:--:,$55$%95$6@@6$59%$55>", "@--6%9,.@@@@.,9%6--@", "*X-6:<&2@@@@2&55$%95$6@@6$59%$55>", "4,1=o<$,:--:,$55$8@@@,7:#@@@8$55>", "@--6@@@@6666@@@@6--@", "*X-6%@@@@@@@@@@%6-X*", ";,-6=3@@@@@@@@3=6-,;", "O,9#$ @@@@@@@@ $#9,O" }; mj-1.17-src/tiles-small/1S.xpm0000444006717300001440000000354607623736151014604 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 74 1", /* colors */ " c #CED5D4", ". c #2B564E", "X c #9F9F9F", "o c #E3CECE", "O c #5C6D67", "+ c #A1B2AE", "@ c #A34240", "# c #BCC7C5", "$ c #7C9590", "% c #E1C5C5", "& c #C55454", "* c #587A73", "= c #CE7A7A", "- c #B20909", "; c #B00000", ": c #214F46", "> c #E1C4C4", ", c #98ABA7", "< c #BC6C6B", "1 c #4B4B4B", "2 c #653833", "3 c #B3C0BE", "4 c #738E89", "5 c #B92626", "6 c #EAEAEA", "7 c #0F4137", "8 c #2A564E", "9 c #E8E1E1", "0 c #A1B2AF", "q c #CC7070", "w c #61807A", "e c #CECECE", "r c #D8DCDB", "t c #131313", "y c #D59696", "u c #B92525", "i c #18483F", "p c #8FA4A0", "a c #4F726B", "s c #C6CECC", "d c #C24B4B", "f c #B71C1C", "g c #E1E3E3", "h c #969696", "j c #063A30", "k c #3D645C", "l c #B51313", "z c #E0C4C4", "x c #4A231D", "c c #587973", "v c #CFD5D4", "b c #BCA9A7", "n c #9C6260", "m c #DEBBBB", "M c #D38C8C", "N c #5E5E5E", "B c #DCB2B2", "V c #98AAA7", "C c #748F8A", "Z c #738F89", "A c #345D55", "S c #BE3838", "D c #4F726C", "F c #C5CECC", "G c #C75E5E", "H c #BC2F2F", "J c #224F46", "K c #816C68", "L c #3D645D", "P c #B3C0BD", "I c #BBBBBB", "U c #BA2626", "Y c #E5D7D7", "T c None", /* pixels */ "66omyG66666666666666", "66yldSMY666666666666", "666HmSMY666666Vv66he", "666HmlHH&66666w#6XtI", "9md;HHlm6666sJj,AXtI", "oG5;MMSq66666pjj$6NI", "6B5;M&;qGy666pjC66N1", "%fquHf;mmo666pZg66eI", "666Hmqf%6666jjjjF666", "666=GGB66666j88jA666", "666666666666j88C0666", "66666666666cjD#gAAAs", "6rD..Dr69mmmz6wjFrD#", "6pjCCjp6q5GGfBwa:*kv", "6spp:K@GfB66;G3rpps6", "6666bfqm-=6&;lH%6666", "6666mG66UlH-&6Gy6666", "6666oq>6mSMf66G;6666", "66666MM6d;;;=B5q6666", "66666MfH;dmu-fq9AA+6", "66666666mG66- c #CC7070", ", c #CECECE", "< c #131313", "1 c #C4C4C4", "2 c #090909", "3 c #D59696", "4 c #B92525", "5 c #B2B2B2", "6 c #C24B4B", "7 c #B71C1C", "8 c #969696", "9 c #8C8C8C", "0 c #B51313", "q c #E0C4C4", "w c #7A7A7A", "e c #DEBBBB", "r c #D38C8C", "t c #5E5E5E", "y c #545454", "u c #DCB2B2", "i c #BE3838", "p c #383838", "a c #E1E1E1", "s c #262626", "d c #D7D7D7", "f c #C75E5E", "g c #BC2F2F", "h c #C5C5C5", "j c #BBBBBB", "k c #BA2626", "l c #E5D7D7", "z c None", /* pixels */ "********************", "********************", "***********ww*******", "********h==22=.*****", "****aj%s=w**h=.*****", "****,tt,**59.*******", "********tts <8******", "******w=-jjj-,******", "******wh****jjd*****", "*******.9999 y9****", "***wttt<999999p w***", "***1 %jj******,-=***", "**********X:********", "*********$i>o*******", "******3XX>r7g*******", "******fk:X+@q*******", "*****le4300kg+******", "*****uff6ii>o*******", "*****uu+6#Or03******", "*****r7;3#7g#f******", "******f3g7>4@+******", "******f#rOif7rrl****", "***++*u7>#if7ri6+***", "***q@gg@k##3;+X4@+**", "*o0k#6eee>>4k#r*##r*", "**3X0f&#ffffe#r*##r*", "****o*ur***+6#r+#i$*", "***********q3#7@4X**" }; mj-1.17-src/tiles-small/SW.xpm0000444006717300001440000000224507623736151014645 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 27 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #838383", "o c #676767", "O c #4B4B4B", "+ c #EAEAEA", "@ c #2F2F2F", "# c #252525", "$ c #CECECE", "% c #131313", "& c #C4C4C4", "* c #090909", "= c #B2B2B2", "- c #969696", "; c #8C8C8C", ": c #7A7A7A", "> c #707070", ", c #5E5E5E", "< c #545454", "1 c #383838", "2 c #E1E1E1", "3 c #262626", "4 c #D7D7D7", "5 c #1C1C1C", "6 c #C5C5C5", "7 c #BBBBBB", "8 c None", /* pixels */ "++++++++++++++++++++", "++++++++++++++++++++", "+++++++::+++++++++++", "+++++++&*@@6++++++++", "++++++++65 <+++++++", "+++++++++; ++++++++", "+++++++++; >++++++++", "+++++++++; 7<+++++++", "++++++++2> O4+++++", "++++++=;X1 %,=+++++", "++++++=;.; >%-++++++", "+++++++++;>2 %.+++++", "+++++;>72> >77$++", "+++++=3 > O 3,% 1&+", "+++++=3 1;o ,+. ;+", "+..+65 #X@*#$++ ;+", "++,-<+$#*5 -- >2 ,++", "++-%.+-,5< ,,,$ ,++", "+++@%--,5< >++++ ,++", "+++& %X7&;>2+++< ,++", "++++65;+++:@3 ,++", "+++++==+++++$,,%%-++", "+++++++++++++++..+++", "++++++++++++++++++++", "++++++++++++++++++++", "++++++++++++++++++++", "++++++++++++++++++++", "++++++++++++++++++++" }; mj-1.17-src/tiles-small/EW.xpm0000444006717300001440000000220707623736151014625 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 25 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #4B4B4B", "o c #EAEAEA", "O c #2F2F2F", "+ c #252525", "@ c #CECECE", "# c #131313", "$ c #C4C4C4", "% c #090909", "& c #B2B2B2", "* c #969696", "= c #8C8C8C", "- c #7A7A7A", "; c #707070", ": c #5E5E5E", "> c #545454", ", c #383838", "< c #E1E1E1", "1 c #262626", "2 c #D7D7D7", "3 c #1C1C1C", "4 c #C5C5C5", "5 c #BBBBBB", "6 c None", /* pixels */ "oooooooooooooooooooo", "oooooooooooooooooooo", "oooooooo@::@oooooooo", "oooooooo<; 1oooooooo", "ooooooooo= 1oooooooo", "ooooooooo= 5=&oooooo", "ooooooooo= X 1&ooooo", "ooooo.OOO3 =ooooo", "ooooooo>O3 5552ooooo", "oooooooo2> ; >2oooo", "oooo@:&-X Xoooo", "oooo5 3%5; *55X >ooo", "oooo5 ** XX Oooo", "oooo@, :, X:, -ooo", "ooooo$, :, X:, ;oooo", "oooooo@++; ** ; c #8391AF", ", c #26407B", "< c #25407A", "1 c #9FA9C0", "2 c #CED2DA", "3 c #96A1BA", "4 c #7081A5", "5 c #385085", "6 c #9FAABF", "7 c #67799F", "8 c #09286A", "9 c #8391B0", "0 c #4B6090", "q c #7A89AA", "w c #546995", "e c #1C3875", "r c #4B618F", "t c None", /* pixels */ "%%2>0O++O0>>06Xr:", ":++,6XX8:++:8XX6,++:", ":5497qq74++47qq7945:", "#w$9r@q7e&&e7q@r9$w#", "%#r,::X8.%%.8X::,r#%", "%%%;ooo %%%% ooo;%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%", "%%%;ooo %%%% ooo;%%%", "%#r,::X8.%%.8X::,r#%", "#w$9r@q7e&&e7q@r9$w#", ":5497qq74++47qq7945:", ":++,6XX8:++:8XX6,++:", ":rX60>>0O++O0>>06Xr:", "2$w74549*33*94547w$2", "%=5r--4*1%%1*4--r5=%", "%%2 c #CFD5D4", ", c #738F89", "< c #345D55", "1 c #4F726C", "2 c #3D645D", "3 c #B3C0BD", "4 c None", /* pixels */ "#&1;.;,-####-,;.;1&#", "#3$;X;:>####>:;X;$3#", "##@OX2=######=2XO@##", "#######>:;X;$3#", "#&1;.;,-####-,;.;1&#", "####################", "####################", "####################", "####################", "#&1;.;,-####-,;.;1&#", "#3$;X;:>####>:;X;$3#", "##@OX2=######=2XO@##", "#######>:;X;$3#", "#&1;.;,-####-,;.;1&#" }; mj-1.17-src/tiles-small/GD.xpm0000444006717300001440000000247307623736151014611 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 37 1", /* colors */ " c #CED5D4", ". c #2B564E", "X c #A1B2AE", "o c #BCC7C5", "O c #7D9591", "+ c #7C9590", "@ c #587A73", "# c #214F46", "$ c #98ABA7", "% c #B3C0BE", "& c #738E89", "* c #EAEAEA", "= c #0F4137", "- c #869D98", "; c #2A564E", ": c #A1B2AF", "> c #61807A", ", c #D8DCDB", "< c #18483F", "1 c #8FA4A0", "2 c #4F726B", "3 c #C6CECC", "4 c #6A8782", "5 c #E1E3E3", "6 c #063A30", "7 c #3D645C", "8 c #587973", "9 c #CFD5D4", "0 c #98AAA7", "q c #748F8A", "w c #738F89", "e c #345D55", "r c #4F726C", "t c #224F46", "y c #3D645D", "u c #B3C0BD", "i c None", /* pixels */ "********************", "********************", "********************", "***********88*******", "************.6w5****", "********,%**o679****", "********o;u*oy3*****", "******O3o6t32-X*****", "******>.66w;*16$****", "***::3&-6;><:162****", "***:<&3e6>0-<7@,****", "****6>O=;-%#6666q666->>9", "9&&<#%>6#@66#@66e***", "5 ;eX<6=t66=twoe***", "****.6t32wt3o66$****", "****9>u*->u*9766****", "********:****166****", "*************16$****", "********************", "********************", "********************", "********************" }; mj-1.17-src/tiles-small/5D.xpm0000444006717300001440000000264407623736151014567 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 44 1", /* colors */ " c #C5CAD5", ". c #C4CAD4", "X c #6779A0", "o c #2F4880", "O c #D08383", "+ c #96A2BA", "@ c #5E719A", "# c #26407A", "$ c #D7DADF", "% c #385185", "& c #CE7A7A", "* c #B20909", "= c #C34B4B", "- c #EAEAEA", "; c #B2B9CA", ": c #133070", "> c #C4CAD5", ", c #8C99B5", "< c #D79F9F", "1 c #CC7070", "2 c #546895", "3 c #BBC2CF", "4 c #8391AF", "5 c #26407B", "6 c #25407A", "7 c #B71C1C", "8 c #9FA9C0", "9 c #CED2DA", "0 c #96A1BA", "q c #7081A5", "w c #385085", "e c #D38C8C", "r c #9FAABF", "t c #67799F", "y c #D18383", "u c #09286A", "i c #8391B0", "p c #4B6090", "a c #7A89AA", "s c #BC2F2F", "d c #546995", "f c #1C3875", "g c #4B618F", "h c None", /* pixels */ "--96oo:+----+:oo69--", "->wg,,q:8--8:q,,gw>-", "9%dtqwqi:00:iqwqtd%9", "3gXrp44p+@@+p44prXg3", "3@@5rXXu3@@3uXXr5@@3", "3wqitaatq@@qtaatiqw3", "$d%ig#atf;;fta#gi%d$", "-$g533Xu.--.uX335g$-", "---2ooo ---- ooo2---", "--------eeee--------", "-------&7ee7&-------", "------&*wg,,q:8--8:q,,gw>-", "--96oo:+----+:oo69--" }; mj-1.17-src/tiles-small/3B.xpm0000444006717300001440000000224507623736151014560 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 27 1", /* colors */ " c #CED5D4", ". c #BCC7C5", "X c #7C9590", "o c #587A73", "O c #214F46", "+ c #98ABA7", "@ c #B3C0BE", "# c #EAEAEA", "$ c #0F4137", "% c #2A564E", "& c #A1B2AF", "* c #61807A", "= c #D8DCDB", "- c #18483F", "; c #8FA4A0", ": c #4F726B", "> c #E1E3E3", ", c #063A30", "< c #3D645C", "1 c #98AAA7", "2 c #748F8A", "3 c #738F89", "4 c #345D55", "5 c #4F726C", "6 c #C5CECC", "7 c #224F46", "8 c None", /* pixels */ "#######6,--,6#######", "#######X,**,X#######", "########2**2########", "########.**.########", "########+55+########", "#######&,%%,&#######", "#######&,%%,&#######", "########+55+########", "########.**.########", "########2**2########", "#######X,**,X#######", "#######6,--,6#######", "####################", "####################", "####################", "####################", "+,7$% ######## %$7,+", ":,;4-1########1-4;,:", "=o;4&##########&4;o=", "#;;4############4;;#", ">33%6##########6%33>", "2,<-O@########@O-<,2", "2,<-O@########@O-<,2", ">33%6##########6%33>", "#;;4############4;;#", "=o;4&##########&4;o=", ":,;4-1########1-4;,:", "+,7$% ######## %$7,+" }; mj-1.17-src/tiles-small/4F.xpm0000444006717300001440000000335607623736151014571 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 66 1", /* colors */ " c #000000", ". c #CED5D4", "X c #2B564E", "o c #E3CECE", "O c #A1B2AE", "+ c #BCC7C5", "@ c #676C6B", "# c #7D9591", "$ c #E1C5C5", "% c #587A73", "& c #676767", "* c #B00000", "= c #214F46", "- c #E1C4C4", "; c #98ABA7", ": c #4B4B4B", "> c #B3C0BE", ", c #738E89", "< c #EAEAEA", "1 c #0F4137", "2 c #2F2F2F", "3 c #869D98", "4 c #2A564E", "5 c #E8E1E1", "6 c #A1B2AF", "7 c #61807A", "8 c #CECECE", "9 c #D8DCDB", "0 c #090909", "q c #D59696", "w c #18483F", "e c #B2B2B2", "r c #8FA4A0", "t c #4F726B", "y c #C6CECC", "u c #6A8782", "i c #C24B4B", "p c #E1E3E3", "a c #063A30", "s c #7D9691", "d c #3D645C", "f c #8C8C8C", "g c #B51313", "h c #587973", "j c #CFD5D4", "k c #7A7A7A", "l c #DEBBBB", "z c #5E5E5E", "x c #98AAA7", "c c #748F8A", "v c #738F89", "b c #345D55", "n c #BE3838", "m c #383838", "M c #4F726C", "N c #C5CECC", "B c #D7D7D7", "V c #BC2F2F", "C c #224F46", "Z c #1C1C1C", "A c #C5C5C5", "S c #3D645D", "D c #B3C0BD", "F c #BBBBBB", "G c #BA2626", "H c None", /* pixels */ "<u9%dj", "<<>=<<<<", "<<<<<<9><<", "<<<<<<< c #214F46", ", c #E1C4C4", "< c #98ABA7", "1 c #B3C0BE", "2 c #B92626", "3 c #EAEAEA", "4 c #0F4137", "5 c #2F2F2F", "6 c #869D98", "7 c #2A564E", "8 c #E8E1E1", "9 c #A1B2AF", "0 c #CC7070", "q c #61807A", "w c #CECECE", "e c #D8DCDB", "r c #C4C4C4", "t c #C3C4C3", "y c #971311", "u c #090909", "i c #B92525", "p c #18483F", "a c #B2B2B2", "s c #8FA4A0", "d c #4F726B", "f c #C24B4B", "g c #B71C1C", "h c #063A30", "j c #7D9691", "k c #3D645C", "l c #8C8C8C", "z c #B51313", "x c #587973", "c c #814D49", "v c #CFD5D4", "b c #7A7A7A", "n c #DEBBBB", "m c #D38C8C", "M c #5E5E5E", "N c #545454", "B c #98AAA7", "V c #738F89", "C c #345D55", "Z c #BE3838", "A c #4F726C", "S c #E1E1E1", "D c #D7D7D7", "F c #A58785", "G c #C75E5E", "H c #5C6E68", "J c #BC2F2F", "K c #224F46", "L c #C5C5C5", "P c #B3C0BD", "I c #BBBBBB", "U c #BA2626", "Y c #E5D7D7", "T c None", /* pixels */ "33333333333333333333", "Ymmm3333333333333rlD", "nZmg-333333333333rlD", "8o-;J3333333333N5555", "33-JJ333@A@73333r333", "3,,OJ333vkhd33aD53aD", "3,0zOPqq$s=e33aD53aD", "33on3shhCss333b5u55L", "333333#4hp+33DIS5333", "331s933$hq333aMwb333", "33qhpB33>13333333333", "33 @ c #18483F", ", c #8FA4A0", "< c #4F726B", "1 c #C24B4B", "2 c #E1E3E3", "3 c #063A30", "4 c #3D645C", "5 c #B51313", "6 c #E0C4C4", "7 c #DEBBBB", "8 c #98AAA7", "9 c #748F8A", "0 c #738F89", "q c #345D55", "w c #C5CECC", "e c #C75E5E", "r c #224F46", "t c None", /* pixels */ "@3r&* %%%%%%%% *&r3@", "<3,q>8%%%%%%%%8>q,3<", ";X,q=%%%%%%%%%%=q,X;", "%,,q%%%%%%%%%%%%q,,%", "200*w%%%%%%%%%%w*002", "934>+#%%%%%%%%#+>439", "934>+#%%%%%%%%#+>439", "200*w%%%%%%%%%%w*002", "%,,q%%%6O55O6%%%q,,%", ";X,q=%%oOeeOo%%=q,X;", "<3,q>8%%-ee-%%8>q,3<", "@3r&* %%7ee7%% *&r3@", "%%%%%%%%:11:%%%%%%%%", "%%%%%%%.O$$O.%%%%%%%", "%%%%%%%.O$$O.%%%%%%%", "%%%%%%%%:11:%%%%%%%%", "@3r&* %%7ee7%% *&r3@", "<3,q>8%%-ee-%%8>q,3<", ";X,q=%%oOeeOo%%=q,X;", "%,,q%%%6O55O6%%%q,,%", "200*w%%%%%%%%%%w*002", "934>+#%%%%%%%%#+>439", "934>+#%%%%%%%%#+>439", "200*w%%%%%%%%%%w*002", "%,,q%%%%%%%%%%%%q,,%", ";X,q=%%%%%%%%%%=q,X;", "<3,q>8%%%%%%%%8>q,3<", "@3r&* %%%%%%%% *&r3@" }; mj-1.17-src/tiles-small/--.xpm0000444006717300001440000000145507623736151014527 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 2 1", /* colors */ " c #948D13", ". c None", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " }; mj-1.17-src/tiles-small/WW.xpm0000444006717300001440000000222607623736151014650 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 26 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #838383", "o c #676767", "O c #4B4B4B", "+ c #EAEAEA", "@ c #2F2F2F", "# c #252525", "$ c #CECECE", "% c #131313", "& c #C4C4C4", "* c #090909", "= c #B2B2B2", "- c #969696", "; c #8C8C8C", ": c #7A7A7A", "> c #707070", ", c #5E5E5E", "< c #545454", "1 c #383838", "2 c #262626", "3 c #D7D7D7", "4 c #1C1C1C", "5 c #C5C5C5", "6 c #BBBBBB", "7 c None", /* pixels */ "++++++++++++++++++++", "++++++++++++++++++++", "++++++++++++++++++++", "++++++++++++++++++++", "++++++++++++++++++++", "+++++++++++++&;3++++", "++++++++++++$1 O++++", "+++++++++.@@2 @:++", "+3666>4@@@X6&3O ;+", "+=2 ,,=+++-%4<1,,,=+", "++=;+++++++@ 1&+++++", "++++5@.+++:*#$++++++", "+++++; 6+3o*@+++++++", "+++++=2>+;<44;&+++++", "+++++=2 ,1 4<1,++++", "++++54 -O6#@3O <+++", "++, *::*-,+@<++@ ,++", "++, @++@ ,=4++=4 ,++", "++=4%-+. 22 ,,2 4=++", "+++& %@@ &+++", "++++54 2<+++ 45++++", "+++++=,$++++,,=+++++", "++++++++++++++++++++", "++++++++++++++++++++", "++++++++++++++++++++", "++++++++++++++++++++", "++++++++++++++++++++", "++++++++++++++++++++" }; mj-1.17-src/tiles-small/1F.xpm0000444006717300001440000000370007623736151014557 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 80 1", /* colors */ " c #000000", ". c #CED5D4", "X c #2B564E", "o c #9F9F9F", "O c #D89F9F", "+ c #A1B2AE", "@ c #A20504", "# c #BCC7C5", "$ c #7D9591", "% c #7C9590", "& c #E1C5C5", "* c #C55454", "= c #587A73", "- c #CE7A7A", "; c #B20909", ": c #B00000", "> c #214F46", ", c #98ABA7", "< c #4B4B4B", "1 c #B3C0BE", "2 c #EAEAEA", "3 c #0F4137", "4 c #2F2F2F", "5 c #869D98", "6 c #252525", "7 c #2A564E", "8 c #A1B2AF", "9 c #CC7070", "0 c #61807A", "q c #CECECE", "w c #D8DCDB", "e c #131313", "r c #C4C4C4", "t c #D59696", "y c #B92525", "u c #18483F", "i c #B2B2B2", "p c #C59190", "a c #8FA4A0", "s c #4F726B", "d c #C6CECC", "f c #6A8782", "g c #C24B4B", "h c #B71C1C", "j c #E1E3E3", "k c #969696", "l c #063A30", "z c #7D9691", "x c #3D645C", "c c #8C8C8C", "v c #9E6B69", "b c #B51313", "n c #587973", "m c #CFD5D4", "M c #7A7A7A", "N c #707070", "B c #DEBBBB", "V c #D38C8C", "C c #5E5E5E", "Z c #98AAA7", "A c #748F8A", "S c #738F89", "D c #345D55", "F c #BE3838", "G c #383838", "H c #4F726C", "J c #E1E1E1", "K c #C5CECC", "L c #262626", "P c #D7D7D7", "I c #C75E5E", "U c #BC2F2F", "Y c #224F46", "T c #C5C5C5", "R c #3D645D", "E c #B3C0BD", "W c #BBBBBB", "Q c #BA2626", "! c #E5D7D7", "~ c None", /* pixels */ "BB!222222222Jq222222", "IFV2221a2222WGr2cccP", "2VV22E7l0Z22WGr2cccP", "2VV2dYll,022WC2244o2", "&UU&dDuld$22 <aRs0Z22WCiPeCi2", "22228Dul#5+2WC226Nc2", "222K2aYd2++22222TM22", "221fa=a2222222222222", "220sAlx00Z2222Zm2222", "22.#XH###.2222.s2222", "22222!gQQg!22wH#2222", "2222V*:BB:*Vpz7A2222", "222-:::gg:::@v0A%222", "22-;B9::y99B:I0#D222", "222UB9:t2V::U2.s2222", "222-::::OV::-20#2222", "22Z09:*VbF*h220#2222", "dDulB:V2:g!U2+u#2222", "Xlll&UUUUUUU2al#2222", "#lll2daaaaaa2al#2222", "w=ll0xlAaaRl0xl#2222", "dYS77##j22.#lllX2222", "2222n22222222+D37##j", "222222222222222%lll#", "2222222222222222a=l#", "22222222222222222w#j" }; mj-1.17-src/tiles-small/4C.xpm0000444006717300001440000000270207623736151014560 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 46 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #E3CECE", "o c #D89F9F", "O c #C55454", "+ c #CE7A7A", "@ c #B20909", "# c #B00000", "$ c #E1C4C4", "% c #4B4B4B", "& c #B92626", "* c #EAEAEA", "= c #2F2F2F", "- c #252525", "; c #D79F9F", ": c #E8E1E1", "> c #CC7070", ", c #CECECE", "< c #131313", "1 c #C4C4C4", "2 c #090909", "3 c #D59696", "4 c #B92525", "5 c #B2B2B2", "6 c #C24B4B", "7 c #B71C1C", "8 c #969696", "9 c #B51313", "0 c #E0C4C4", "q c #7A7A7A", "w c #707070", "e c #DEBBBB", "r c #D38C8C", "t c #5E5E5E", "y c #DCB2B2", "u c #BE3838", "i c #383838", "p c #262626", "a c #C75E5E", "s c #BC2F2F", "d c #1C1C1C", "f c #C5C5C5", "g c #BBBBBB", "h c #BA2626", "j c #E5D7D7", "k c None", /* pixels */ "********************", "********************", "********************", "********f=======****", "**,-gw =q*=-,q2-,**", "**t o*******", "******3XX>r7s*******", "******ah:X+@0*******", "*****je4399hs+******", "*****yaa6uu>o*******", "*****yy+6#Or93******", "*****r7;3#7s#a******", "******a3s7>4@+******", "******a#rOua7rrj****", "***++*y7>#ua7ru6+***", "***0@ss@h##3;+X4@+**", "*o9h#6eee>>4h#r*##r*", "**3X9a&#aaaae#r*##r*", "****o*yr***+6#r+#u$*", "***********03#7@4X**" }; mj-1.17-src/tiles-small/7B.xpm0000444006717300001440000000262507623736151014566 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 43 1", /* colors */ " c #CED5D4", ". c #2B564E", "X c #7D9591", "o c #7C9590", "O c #C55454", "+ c #587A73", "@ c #CE7A7A", "# c #B00000", "$ c #214F46", "% c #98ABA7", "& c #B3C0BE", "* c #B92626", "= c #EAEAEA", "- c #0F4137", "; c #2A564E", ": c #A1B2AF", "> c #CC7070", ", c #61807A", "< c #D8DCDB", "1 c #18483F", "2 c #8FA4A0", "3 c #4F726B", "4 c #C6CECC", "5 c #C24B4B", "6 c #E1E3E3", "7 c #063A30", "8 c #3D645C", "9 c #B51313", "0 c #E0C4C4", "q c #587973", "w c #CFD5D4", "e c #98AAA7", "r c #748F8A", "t c #738F89", "y c #345D55", "u c #4F726C", "i c #C5CECC", "p c #C75E5E", "a c #BC2F2F", "s c #224F46", "d c #3D645D", "f c #BA2626", "g c None", /* pixels */ "=======0#99#0=======", "=======@#pp#@=======", "========>pp>========", "========f99f========", "=======O#55#O=======", "========5**5========", "========5pp5========", "=======O#pp#O=======", "========aaaa========", "<222:===2222===:222<", "37+$1e=o7dd7o=e1$+73", "%72y; =i7,,7i= ;y27%", "6tt;i===%uu%===i;tt6", "r781$&=:7;;7:=&$187r", "r781$&=:7;;7:=&$187r", "6tt;i===%uu%===i;tt6", "%72y; =i7,,7i= ;y27%", "37+$1e=o7dd7o=e1$+73", "<222:===2222===:222<", "4yyyq===yyyy===qyyy4", ".72y-X=q7,,7q=X-y27.", "w82yo===3,,3===oy28w", "w881o===3;;3===o188w", ".7t;-X=q7uu7q=X-;t7.", "4ss-q===.11.===q-ss4", "<+2y:===r,,r===:y2+<", "372y1e=o7,,7o=e1y273", "%7s-; =i7117i= ;-s7%" }; mj-1.17-src/tiles-small/9B.xpm0000444006717300001440000000264407623736151014571 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 44 1", /* colors */ " c #CED5D4", ". c #2B564E", "X c #D89F9F", "o c #7D9591", "O c #7C9590", "+ c #C55454", "@ c #587A73", "# c #CE7A7A", "$ c #B00000", "% c #214F46", "& c #98ABA7", "* c #B3C0BE", "= c #B92626", "- c #EAEAEA", "; c #0F4137", ": c #2A564E", "> c #A1B2AF", ", c #CC7070", "< c #D8DCDB", "1 c #D59696", "2 c #18483F", "3 c #8FA4A0", "4 c #4F726B", "5 c #C6CECC", "6 c #C24B4B", "7 c #E1E3E3", "8 c #063A30", "9 c #3D645C", "0 c #B51313", "q c #E0C4C4", "w c #587973", "e c #CFD5D4", "r c #D38C8C", "t c #98AAA7", "y c #748F8A", "u c #738F89", "i c #345D55", "p c #BE3838", "a c #C5CECC", "s c #C75E5E", "d c #BC2F2F", "f c #224F46", "g c #BA2626", "h c None", /* pixels */ "&8f;: -q$00$q- :;f8&", "483i2t-#$ss$#-t2i384", "<@3i>---,ss,--->i3@<", "5ff;w---g00g---w;ff5", ".8u:;o-+$66$+-o;:u8.", "e992O---6==6---O299e", "e93iO---6ss6---Oi39e", ".83i;o-+$ss$+-o;i38.", "5iiiw---dddd---wiii5", "<333>---rrrr--->333<", "48@%2t-#$pp$#-t2%@84", "&83i: -q$ss$q- :i38&", "7uu:a---1661---a:uu7", "y892%*-X$==$X-*%298y", "y892%*-X$==$X-*%298y", "7uu:a---1661---a:uu7", "&83i: -q$ss$q- :i38&", "48@%2t-#$pp$#-t2%@84", "<333>---rrrr--->333<", "5iiiw---dddd---wiii5", ".83i;o-+$ss$+-o;i38.", "e93iO---6ss6---Oi39e", "e992O---6==6---O299e", ".8u:;o-+$66$+-o;:u8.", "5ff;w---g00g---w;ff5", "<@3i>---,ss,--->i3@<", "483i2t-#$ss$#-t2i384", "&8f;: -q$00$q- :;f8&" }; mj-1.17-src/tiles-small/3S.xpm0000444006717300001440000000364207623736151014603 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 78 1", /* colors */ " c #CED5D4", ". c #2B564E", "X c #E3CECE", "o c #D89F9F", "O c #A1B2AE", "+ c #BCC7C5", "@ c #7D9591", "# c #7C9590", "$ c #E1C5C5", "% c #C55454", "& c #587A73", "* c #CE7A7A", "= c #B20909", "- c #C34B4B", "; c #676767", ": c #B00000", "> c #214F46", ", c #E1C4C4", "< c #98ABA7", "1 c #B3C0BE", "2 c #B92626", "3 c #EAEAEA", "4 c #0F4137", "5 c #2F2F2F", "6 c #869D98", "7 c #2A564E", "8 c #D79F9F", "9 c #E8E1E1", "0 c #A1B2AF", "q c #CC7070", "w c #61807A", "e c #CECECE", "r c #D8DCDB", "t c #131313", "y c #C4C4C4", "u c #D59696", "i c #B92525", "p c #18483F", "a c #8FA4A0", "s c #4F726B", "d c #C6CECC", "f c #C24B4B", "g c #B71C1C", "h c #E1E3E3", "j c #063A30", "k c #8C8C8C", "l c #3D645C", "z c #B51313", "x c #E0C4C4", "c c #4A231D", "v c #587973", "b c #CFD5D4", "n c #DEBBBB", "m c #D38C8C", "M c #5E5E5E", "N c #545454", "B c #DCB2B2", "V c #98AAA7", "C c #748F8A", "Z c #738F89", "A c #345D55", "S c #C96767", "D c #BE3838", "F c #383838", "G c #4F726C", "H c #E1E1E1", "J c #C5CECC", "K c #262626", "L c #D7D7D7", "P c #C75E5E", "I c #BC2F2F", "U c #224F46", "Y c #C5C5C5", "T c #3D645D", "R c #B3C0BD", "E c #BBBBBB", "W c #E5D7D7", "Q c None", /* pixels */ "33X933X93333h 33HEEH", "33Pq33Pn3333+Td3eMKE", "33P:*3PfPu33r&lb33ME", "$Izu8IzunX33AUj.Y5tE", "33P:xWfu33337 w+HEL5", "WmDfS*Pzo333sTT+eFN;", "WmDfSB3ozu33+jj+3ykL", "33P:x333uzo3hZj+3333", "33*$333333333aj+3333", "raaa0333raaaa&jC3333", "sj&aA333r&&ajTa>3333", "0333", "3333gB33W%m3bl&aA333", "333%=II$3mg$+jUAA333", "33P:n:gII3*=33@A3333", "33P:q:%WSB3I33333333", "33PqgmDfnP3I33333333", "33Pn=*P:nP3I3333AAO3", "33*=::g=::gIh ssZa3", "333*::%g:2B3CTT+C7R3", "wwwwccccmB33Cjjs>133", "7+Gj", "33333rr333333333333J" }; mj-1.17-src/tiles-small/1B.xpm0000444006717300001440000000616107623736151014557 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 115 2", /* colors */ " c #CED5D4", " . c #5E1C17", " X c #B3A19F", " o c #2B564E", " O c #E3CECE", " + c #D89F9F", " @ c #5C6D67", " # c #A1B2AE", " $ c #A20504", " % c #BCC7C5", " & c #7D9591", " * c #7C9590", " = c #D08383", " - c #C55454", " ; c #7E2522", " : c #B22827", " > c #587A73", " , c #7C3B37", " < c #AE5251", " 1 c #282E26", " 2 c #CE7A7A", " 3 c #B20909", " 4 c #C34B4B", " 5 c #585B55", " 6 c #BE7574", " 7 c #888884", " 8 c #6C1713", " 9 c #BA9F9E", " 0 c #31544C", " q c #B00000", " w c #214F46", " e c #7A1310", " r c #E1C4C4", " t c #9A9592", " y c #98ABA7", " u c #BC6C6B", " i c #B3C0BE", " p c #738E89", " a c #B92626", " s c #EAEAEA", " d c #B77776", " f c #651915", " g c #869D98", " h c #632F2A", " j c #2A564E", " k c #D79F9F", " l c #E8E1E1", " z c #A1B2AF", " x c #CC7070", " c c #61807A", " v c #3C2821", " b c #D8DCDB", " n c #0D382E", " m c #971311", " M c #51211B", " N c #D59696", " B c #B92525", " V c #CA6767", " C c #18483F", " Z c #8FA4A0", " A c #4F726B", " S c #73332F", " D c #C6CECC", " F c #98312F", " G c #2D4139", " H c #6A8782", " J c #C24B4B", " K c #B71C1C", " L c #E1E3E3", " P c #063A30", " I c #7D9691", " U c #3D645C", " Y c #B51313", " T c #E0C4C4", " R c #587973", " E c #4D4A44", " W c #7A504C", " Q c #CFD5D4", " ! c #6A4B46", " ~ c #8E0C0A", " ^ c #DEBBBB", " / c #D38C8C", " ( c #812E2B", " ) c #DCB2B2", " _ c #D18383", " ` c #43251F", " ' c #98AAA7", " ] c #7A4F4B", " [ c #AA7C7A", " { c #748F8A", " } c #870E0C", " | c #738F89", ". c #345D55", ".. c #C96767", ".X c #BE3838", ".o c #534842", ".O c #D7BDBD", ".+ c #4F726C", ".@ c #C5CECC", ".# c #B3A2A0", ".$ c #97312F", ".% c #C75E5E", ".& c #636B65", ".* c #BC2F2F", ".= c #224F46", ".- c #A05755", ".; c #3D645D", ".: c #2A3830", ".> c #B3C0BD", "., c #51201B", ".< c #BA2626", ".1 c #E5D7D7", ".2 c #1A332A", ".3 c #4F3630", ".4 c None", /* pixels */ " s s L s s s s s s s s T.1.1 T l O s s", " s s c % s s s + b i s s.. 2 2.. _ x r s", " s.> j %.%.% ).*.- c s s.. 2 2.. _ x x O", " D.= | L ^ ^ V 3.O g # - k V V k.< Y = 4", " % P Z s l ^ V 3.* S Z T.< Y Y.< N J V k", " g U Z s _.X /.* s.> I u x.X.X x q a 2..", ". Z Z s.* r r.* * s c.- . } } q K 2 a J", ". Z Z s.* s 2 k z & c X n., 6 B 3 K q N", ". Z Z s.* # g L % c c F 1 $., e N Y = 4", ". .> I b.* s c % %.;.; {.2 ` ` M Y x x _", ". .> j %.* s c %.& ] ] 7 S > P ~.* r x _", ".: u.+ %.* s c o 1 ; ; E m.= P X k 2 O l", " 0 =.3 P o c c y : d d : 9 8 P y.@ s s s", " G x 5 P % c c v < , , < h W P P w i s s", " G x 5 P b i c v @.o.o @.3 p.; P Z I c Q", " 0 t C P s s c P j [ [.$ P c % R b % A", " P C # R j %.+ P k V V k A g C y z & s s", " C ' s s c c c c ( 2 2.. g p p C H Z D s", " z s s s s s s s f.o.o @ C U U C A P U c", " s s s s s s s - : %.- ~.# ~ [ % % % % %", " s s s s s.1 V.* ^.% s.* s + = l s s s s", " s s s s.1 - / s O N s 2 + s N O s s s s", " s s s s ^.X x O s s s s.* s s s s s s s", " s s s s ^.% O l. . . . m & s s s s s s", " L c o. ! !. s s s.* s Z Z R z & s s", " Q p.; % s s s s.1 ) ).. s Z > Z H i s s", " s D.; A s s s s.1 2 a ^ s D Z w b i ' Q", ". . C y s s s -.* V ^ 4 s s s.@. . C o" }; mj-1.17-src/tiles-small/NW.xpm0000444006717300001440000000213207623736151014633 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 22 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #4B4B4B", "o c #EAEAEA", "O c #2F2F2F", "+ c #252525", "@ c #CECECE", "# c #131313", "$ c #C4C4C4", "% c #090909", "& c #B2B2B2", "* c #969696", "= c #8C8C8C", "- c #7A7A7A", "; c #5E5E5E", ": c #545454", "> c #383838", ", c #262626", "< c #D7D7D7", "1 c #1C1C1C", "2 c #BBBBBB", "3 c None", /* pixels */ "oooooooooooooooooooo", "oooooooooooooooooooo", "oooooooooooooooooooo", "oooooooooooooooooooo", "oooooooooooooooooooo", "oooooooooooooooooooo", "oooooooooooooooooooo", "oooooooooo-O:ooooooo", "oooooooooo-% =ooooo", "ooooooo.oooO =ooooo", "ooooo&;#-ooO >$ooooo", "ooooo= %-oO+@-O:ooo", "oooooo-%%-oOO c #E1C4C4", ", c #4B4B4B", "< c #B3C0BE", "1 c #B92626", "2 c #EAEAEA", "3 c #A92120", "4 c #0F4137", "5 c #2A564E", "6 c #E8E1E1", "7 c #A1B2AF", "8 c #CC7070", "9 c #61807A", "0 c #3C2821", "q c #D8DCDB", "w c #B92525", "e c #CA6767", "r c #18483F", "t c #B2B2B2", "y c #8FA4A0", "u c #4F726B", "i c #C6CECC", "p c #C24B4B", "a c #B71C1C", "s c #E1E3E3", "d c #063A30", "f c #7D9691", "g c #3D645C", "h c #8C8C8C", "j c #B51313", "k c #E0C4C4", "l c #587973", "z c #CFD5D4", "x c #707070", "c c #DEBBBB", "v c #D38C8C", "b c #5E5E5E", "n c #545454", "m c #DCB2B2", "M c #98AAA7", "N c #AA7C7A", "B c #748F8A", "V c #738F89", "C c #345D55", "Z c #BE3838", "A c #D7BDBD", "S c #4F726C", "D c #C5CECC", "F c #262626", "G c #D7D7D7", "H c #C75E5E", "J c #BC2F2F", "K c #224F46", "L c #A05755", "P c #3D645D", "I c #B3C0BD", "U c #BBBBBB", "Y c #BA2626", "T c #E5D7D7", "R c None", /* pixels */ "22226cc6222222222GG2", "22mv8;;c2ii22222Gnh2", "22maa%%T2yy22222U h2", "222k=av22yKi2222,xh2", "2Tc-Ja862yd@2222 F", "T%Zo2m18qf5@2222bbFU", "cZ>2HH*vL55@222222tG", "6o22ccT2AVd@222lCC+2", "222%JJO2sVV@DqSX2+r@", "22222222@ddu$ydByyP@", "2222z99zuPyq2ydByyyq", "2222XSSX5.22iKdX2222", "22224Crdl222@r+Cl222", "2222$29uvvvvNPy:2222", "222222 c #FFFFFF", ", c #EBEBEB", "< c #141414", "1 c #0A0A0A", "2 c None", /* pixels */ "2222222222&@@@@@@&2222222222", "22222222--=>>>>>>=--22222222", "222222;o>>>>>>>>>>>>o;222222", "22222:,>>>>>>>>>>>>>>,:22222", "2222;>>>>>>>>>>>>>>>>>>;2222", "222:>>>>>>>>>>>>>>>>>>>>:222", "22;,>>>>>>>>>>>>>>>>>>>>,;22", "22o>>>>>>>>>>>O@;>>>>>>>>o22", "2->>>>>>>>>>>>O1 o>>>>>>>-2", "2->>>>>>>>>.>>>@ o>>>>>>>-2", "&=>>>>>>>=->@ +%>>>>>>>=&", "@>>>>>>>>o 1O>@$#O@;>>>>>>@", "@>>>>>>>>>O11O>@@,: :,>>>>@", "@>>>>>=ooo%@@>>@&; <-=>>>>@", "@>>>>>- o@@>>@ ;o.>>>>>>@", "@>>>>>#$ :,@@>>@$*,>>>>>>>>@", "@>>>>>>>>>>@@,,@@>>>>>>>>>>@", "&=>>>>=,>>>@&O=@&=>>>>>>>>=&", "2->>>>-:>>X<&=>@ $----=>>>-2", "2->>>>- @@< @>>% o>>>-2", "22o>>.< 1@.@>>>>>.@@@O>>>o22", "22;,>>X-O>>O>>>>>>>>>>>>,;22", "222:>>>>>>>>>>>>>>>>>>>>:222", "2222;>>>>>>>>>>>>>>>>>>;2222", "22222:,>>>>>>>>>>>>>>,:22222", "222222;o>>>>>>>>>>>>o;222222", "22222222--=>>>>>>=--22222222", "2222222222&@@@@@@&2222222222" }; mj-1.17-src/tiles-small/WD.xpm0000444006717300001440000000176107623736151014630 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 15 1", /* colors */ " c #C4CAD4", ". c #BBC2CE", "X c #5E7197", "o c #D7DADF", "O c #7A89A8", "+ c #385081", "@ c #EAEAEA", "# c #9FAABE", "$ c #546892", "% c #96A2B8", "& c #8C99B3", "* c #2F487C", "= c #546992", "- c #4B618C", "; c None", /* pixels */ "@@@@@@@@@@@@@@@@@@@@", "@ &&&&&&&&&&&&&&&&&o", "@&=&&&&&&&&&&&&&&&+.", "@&&$*************OX.", "@&&*#O@@@@@@@@@$%XX.", "@&&*O@@@@@@@@@@@-XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*@@@@@@@@@@@@.XX.", "@&&*O@@@@@@@@@@@-XX.", "@&&*#O@@@@@@@@@$%XX.", "@&&$*************OX.", "@&=&&&&&&&&&&&&&&&+.", "@ &&&&&&&&&&&&&&&&&o", "@@@@@@@@@@@@@@@@@@@@" }; mj-1.17-src/tiles-small/8C.xpm0000444006717300001440000000272107623736151014565 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 47 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #E3CECE", "o c #D89F9F", "O c #C55454", "+ c #838383", "@ c #CE7A7A", "# c #B20909", "$ c #B00000", "% c #E1C4C4", "& c #B92626", "* c #EAEAEA", "= c #2F2F2F", "- c #252525", "; c #D79F9F", ": c #E8E1E1", "> c #CC7070", ", c #CECECE", "< c #131313", "1 c #C4C4C4", "2 c #090909", "3 c #D59696", "4 c #B92525", "5 c #B2B2B2", "6 c #C24B4B", "7 c #B71C1C", "8 c #8C8C8C", "9 c #B51313", "0 c #E0C4C4", "q c #7A7A7A", "w c #707070", "e c #DEBBBB", "r c #D38C8C", "t c #5E5E5E", "y c #545454", "u c #DCB2B2", "i c #BE3838", "p c #E1E1E1", "a c #262626", "s c #C75E5E", "d c #BC2F2F", "f c #1C1C1C", "g c #C5C5C5", "h c #BBBBBB", "j c #BA2626", "k c #E5D7D7", "l c None", /* pixels */ "********************", "********************", "*********55*********", "********=ffg********", "*********.+p********", "*****11***tw********", "*****1wt**5fq*******", "*****.< y**12=.*****", "****pwfgy***gf -hhp", "***.+t5******5a at,", ",tt+.*********5885**", "phhp****************", "**********X:********", "*********%i>o*******", "******3XX>r7d*******", "******sj:X@#0*******", "*****ke4399jd@******", "*****uss6ii>o*******", "*****uu@6$Or93******", "*****r7;3$7d$s******", "******s3d7>4#@******", "******s$rOis7rrk****", "***@@*u7>$is7ri6@***", "***0#dd#j$$3;@X4#@**", "*o9j$6eee>>4j$r*$$r*", "**3X9s&$sssse$r*$$r*", "****o*ur***@6$r@$i%*", "***********03$7#4X**" }; mj-1.17-src/tiles-small/3D.xpm0000444006717300001440000000264407623736151014565 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 44 1", /* colors */ " c #C5CAD5", ". c #C4CAD4", "X c #6779A0", "o c #2F4880", "O c #D08383", "+ c #96A2BA", "@ c #5E719A", "# c #26407A", "$ c #D7DADF", "% c #385185", "& c #CE7A7A", "* c #B20909", "= c #C34B4B", "- c #EAEAEA", "; c #133070", ": c #C4CAD5", "> c #8C99B5", ", c #D79F9F", "< c #CC7070", "1 c #546895", "2 c #BBC2CF", "3 c #8391AF", "4 c #26407B", "5 c #25407A", "6 c #B71C1C", "7 c #9FA9C0", "8 c #CED2DA", "9 c #96A1BA", "0 c #7081A5", "q c #385085", "w c #D38C8C", "e c #9FAABF", "r c #67799F", "t c #D18383", "y c #09286A", "u c #8391B0", "i c #4B6090", "p c #B2BACA", "a c #7A89AA", "s c #BC2F2F", "d c #546995", "f c #1C3875", "g c #4B618F", "h c None", /* pixels */ "--8222$-------------", "-:qg@%d$------------", "8%dr@0qg------------", "4gXe53351-----------", "o>0ieXg2o-----------", "o>%ura#2o-----------", ";00uraaro-----------", "+;3iyXXy.-----------", "-7;+20f ------------", "--9@@@p-wwww--------", "-------&6ww6&-------", "------&*,ss,*&------", "-----w6,=OO=,6w-----", "-----wwst<o", "-----------o2gXei0>o", "-----------15335eXg4", "------------gq0@rd%8", "------------$d%@gq:-", "-------------$2228--" }; mj-1.17-src/tiles-small/2B.xpm0000444006717300001440000000172307623736151014557 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 13 1", /* colors */ " c #BCC7C5", ". c #7C9590", "X c #98ABA7", "o c #EAEAEA", "O c #2A564E", "+ c #A1B2AF", "@ c #61807A", "# c #18483F", "$ c #063A30", "% c #748F8A", "& c #4F726C", "* c #C5CECC", "= c None", /* pixels */ "ooooooo*$##$*ooooooo", "ooooooo.$@@$.ooooooo", "oooooooo%@@%oooooooo", "oooooooo @@ oooooooo", "ooooooooX&&Xoooooooo", "ooooooo+$OO$+ooooooo", "ooooooo+$OO$+ooooooo", "ooooooooX&&Xoooooooo", "oooooooo @@ oooooooo", "oooooooo%@@%oooooooo", "ooooooo.$@@$.ooooooo", "ooooooo*$##$*ooooooo", "oooooooooooooooooooo", "oooooooooooooooooooo", "oooooooooooooooooooo", "oooooooooooooooooooo", "ooooooo*$##$*ooooooo", "ooooooo.$@@$.ooooooo", "oooooooo%@@%oooooooo", "oooooooo @@ oooooooo", "ooooooooX&&Xoooooooo", "ooooooo+$OO$+ooooooo", "ooooooo+$OO$+ooooooo", "ooooooooX&&Xoooooooo", "oooooooo @@ oooooooo", "oooooooo%@@%oooooooo", "ooooooo.$@@$.ooooooo", "ooooooo*$##$*ooooooo" }; mj-1.17-src/tiles-small/9D.xpm0000444006717300001440000000277607623736151014601 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 50 1", /* colors */ " c #C5CAD5", ". c #E3CECE", "X c #D89F9F", "o c #6779A0", "O c #2F4880", "+ c #D08383", "@ c #E1C5C5", "# c #C55454", "$ c #5E719A", "% c #26407A", "& c #D7DADF", "* c #CE7A7A", "= c #C34B4B", "- c #EAEAEA", "; c #B2B9CA", ": c #133070", "> c #8C99B5", ", c #D79F9F", "< c #CC7070", "1 c #546895", "2 c #BBC2CF", "3 c #8391AF", "4 c #D59696", "5 c #B92525", "6 c #CA6767", "7 c #26407B", "8 c #C24B4B", "9 c #9FA9C0", "0 c #CED2DA", "q c #B51313", "w c #7081A5", "e c #385085", "r c #DEBBBB", "t c #D38C8C", "y c #9FAABF", "u c #67799F", "i c #D18383", "p c #09286A", "a c #8391B0", "s c #BE3838", "d c #4B6090", "f c #B2BACA", "g c #7A89AA", "h c #C75E5E", "j c #BC2F2F", "k c #546995", "l c #1C3875", "z c #BA2626", "x c #4B618F", "c c None", /* pixels */ "--------------------", "--;>9---&>>&---9>;--", "0$g>a$fgu>>ugf$a>g$0", "2xoOdw>OyOOyO>wdOox2", "O>ly7$$2poop2$$7yl>O", "O>kuw$$2lggl2$$wuk>O", "awexlgguw%%wugglxewa", "2:32yl>Od22dO>ly23:2", "--gO1--- OO ---1Og--", "--------------------", "--4h*---.hh.---*h4--", "@j+r,jX#=rr=#Xj,r+j@", ",68z5++=4qq4=++5z86,", "jtsi8hhrq<Od22dO>ly23:2", "awexlgguw%%wugglxewa", "O>kuw$$2lggl2$$wuk>O", "O>ly7$$2poop2$$7yl>O", "2xoOdw>OyOOyO>wdOox2", "0$g>a$fgu>>ugf$a>g$0", "--;>9---&>>&---9>;--", "--------------------" }; mj-1.17-src/tiles-small/2S.xpm0000444006717300001440000000366107623736151014603 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 79 1", /* colors */ " c #CED5D4", ". c #2B564E", "X c #9F9F9F", "o c #E3CECE", "O c #D89F9F", "+ c #A1B2AE", "@ c #A34240", "# c #BCC7C5", "$ c #7D9591", "% c #7C9590", "& c #E1C5C5", "* c #C55454", "= c #838383", "- c #7E2522", "; c #587A73", ": c #CE7A7A", "> c #B20909", ", c #676767", "< c #888884", "1 c #6C1713", "2 c #B00000", "3 c #214F46", "4 c #E1C4C4", "5 c #98ABA7", "6 c #BC6C6B", "7 c #B3C0BE", "8 c #B92626", "9 c #EAEAEA", "0 c #0F4137", "q c #2F2F2F", "w c #252525", "e c #2A564E", "r c #E8E1E1", "t c #CC7070", "y c #61807A", "u c #D8DCDB", "i c #C4C4C4", "p c #D59696", "a c #B92525", "s c #18483F", "d c #B2B2B2", "f c #8FA4A0", "g c #4F726B", "h c #C6CECC", "j c #6A8782", "k c #C24B4B", "l c #B71C1C", "z c #E1E3E3", "x c #063A30", "c c #3D645C", "v c #8C8C8C", "b c #B51313", "n c #587973", "m c #CFD5D4", "M c #7A7A7A", "N c #DEBBBB", "B c #D38C8C", "V c #5E5E5E", "C c #DCB2B2", "Z c #98AAA7", "A c #748F8A", "S c #738F89", "D c #345D55", "F c #BE3838", "G c #383838", "H c #4F726C", "J c #E1E1E1", "K c #C5CECC", "L c #D7D7D7", "P c #C75E5E", "I c #BC2F2F", "U c #224F46", "Y c #C5C5C5", "T c #3D645D", "R c #B3C0BD", "E c #BBBBBB", "W c #BA2626", "Q c #E5D7D7", "! c None", /* pixels */ "99999QQ999999999JEL9", "QBBBB*B9u799999X=Gv9", "QBBllB49#eR9999qXvv9", "99:>>IO9zSf9hD+iY,L9", "99PpNtB99+s##y9iwEL9", "94F2bFB999y#AThMVVd9", "oF*BbFB999y#xxf99999", "rNQ92kQ999y.#Sf99999", "9OIIIII&99yx0$99z##z", "9hff997f99yx%999Axcm", "9hf3%ReA99yx999%xxcm", "999K0USz99 #99$0###z", "9999xy99Nt2p99y5K999", "9999gy9O28PbOhTg%999", "9999#y9It8COIf;u9999", "9999#y9IrtB9>6u99999", "z#u9h$PWat2pNP999999", "mc;u99PNk222NP999999", "9hfj99PNN2*lk8C99999", "999Dn+-WWkQI22Bnn999", "9999xs+9WkNN>:y.9999", "9hfugTh9<1@P:9Zm9999", "9hT#u;cmf;cyyyR99999", "99 g9uH#9u####u99999", "9999g yx##Hx##u99999", "9999#TTxyyyygxf99999", "9999ufff9999u;f99999", "9999999999999uu99999" }; mj-1.17-src/tiles-small/6C.xpm0000444006717300001440000000277607623736151014575 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 50 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #E3CECE", "o c #D89F9F", "O c #C55454", "+ c #CE7A7A", "@ c #B20909", "# c #676767", "$ c #B00000", "% c #E1C4C4", "& c #4B4B4B", "* c #B92626", "= c #EAEAEA", "- c #2F2F2F", "; c #252525", ": c #D79F9F", "> c #E8E1E1", ", c #CC7070", "< c #CECECE", "1 c #C4C4C4", "2 c #090909", "3 c #D59696", "4 c #B92525", "5 c #B2B2B2", "6 c #C24B4B", "7 c #B71C1C", "8 c #969696", "9 c #8C8C8C", "0 c #B51313", "q c #E0C4C4", "w c #7A7A7A", "e c #707070", "r c #DEBBBB", "t c #D38C8C", "y c #5E5E5E", "u c #545454", "i c #DCB2B2", "p c #BE3838", "a c #383838", "s c #E1E1E1", "d c #262626", "f c #D7D7D7", "g c #C75E5E", "h c #BC2F2F", "j c #1C1C1C", "k c #C5C5C5", "l c #BBBBBB", "z c #BA2626", "x c #E5D7D7", "c c None", /* pixels */ "====================", "========f91=========", "========fua<========", "=========9 d=..=====", "========se 88 8====", "===.9999e ayyyd .===", "===. a99ea1===y -===", "====l========", "=========%p,o=======", "======3XX,t7h=======", "======gz>X+@q=======", "=====xr4300zh+======", "=====igg6pp,o=======", "=====ii+6$Ot03======", "=====t7:3$7h$g======", "======g3h7,4@+======", "======g$tOpg7ttx====", "===++=i7,$pg7tp6+===", "===q@hh@z$$3:+X4@+==", "=o0z$6rrr,,4z$t=$$t=", "==3X0g*$ggggr$t=$$t=", "====o=it===+6$t+$p%=", "===========q3$7@4X==" }; mj-1.17-src/tiles-small/9C.xpm0000444006717300001440000000274007623736151014567 0ustar jcbusers/* XPM */ static char *[] = { /* width height ncolors chars_per_pixel */ "20 28 48 1", /* colors */ " c #000000", ". c #9F9F9F", "X c #E3CECE", "o c #D89F9F", "O c #C55454", "+ c #CE7A7A", "@ c #B20909", "# c #B00000", "$ c #E1C4C4", "% c #4B4B4B", "& c #B92626", "* c #EAEAEA", "= c #2F2F2F", "- c #252525", "; c #D79F9F", ": c #E8E1E1", "> c #CC7070", ", c #CECECE", "< c #131313", "1 c #C4C4C4", "2 c #090909", "3 c #D59696", "4 c #B92525", "5 c #B2B2B2", "6 c #C24B4B", "7 c #B71C1C", "8 c #969696", "9 c #8C8C8C", "0 c #B51313", "q c #E0C4C4", "w c #7A7A7A", "e c #707070", "r c #DEBBBB", "t c #D38C8C", "y c #5E5E5E", "u c #545454", "i c #DCB2B2", "p c #BE3838", "a c #383838", "s c #262626", "d c #D7D7D7", "f c #C75E5E", "g c #BC2F2F", "h c #1C1C1C", "j c #BBBBBB", "k c #BA2626", "l c #E5D7D7", "z c None", /* pixels */ "********************", "********************", "*****5yyw**ww*******", "*****d% 2w*12=.*****", "*******=-jj-2h j****", "****999h w5y,****", "****999hh9ae***w****", "******w2=*yj***=****", "*****d%s**y8**,-=***", "****dua,**8<99a h5**", "****%a1****. h5**", "**w=j,******jjjj1***", "**********X:********", "*********$p>o*******", "******3XX>t7g*******", "******fk:X+@q*******", "*****lr4300kg+******", "*****iff6pp>o*******", "*****ii+6#Ot03******", "*****t7;3#7g#f******", "******f3g7>4@+******", "******f#tOpf7ttl****", "***++*i7>#pf7tp6+***", "***q@gg@k##3;+X4@+**", "*o0k#6rrr>>4k#t*##t*", "**3X0f&#ffffr#t*##t*", "****o*it***+6#t+#p$*", "***********q3#7@4X**" }; mj-1.17-src/rules.txt0000444006717300001440000006246015002771312013224 0ustar jcbusersRULES USED BY THE MAH-JONG PROGRAMS The game currently implemented is a version of the classical Chinese game. The most convenient and comprehensive set of rules is that provided by A. D. Millington, "The Complete Book of Mah-Jongg", Weidenfield & Nicolson (1993), ISBN 0 297 81340 4. In the following, M 103 denotes item 103 of the rules laid out in Chapter 3 of that book. I here describe only the differences from these rules, some of which differences are consequences of using computers, and some of which are points where my house rules differ from Millington's version. In due course, all variations (of Chinese classical) will be accommodated, if there is sufficient desire. Classification of tiles (M 1-8): the tiles are a standard Chinese set. The tiles do not have Arabic numerals, except for the flowers and seasons, where the identifying Chinese characters are too small to be legible. A numbered set is included in the distribution and can be used via the Tileset display preference. The flowers and seasons may be removed from the tile set by unsetting the Flowers game option. Preliminary (M 9-10): nothing to say. Duration of the game (M 11-14): standard rules. In particular, the title of East does not pass after a wash-out. Selection of seats (M 15): the players are seated in the order they connect to the server, or randomly, according to the option given to the server. The deal etc. (M 16-27): There is no attempt to simulate the usual dealing ritual (M 16-20, 23-26); the wall is built randomly by the server. The dead wall is also maintained by the server. The existence of a dead wall is controlled by the DeadWall game option; normally there is a dead wall. The deal wall is either 14 tiles and kept at 13 or 14 during play (as in most authors), or is 16 tiles, not extended during play (per Millington (M 22)), according to the DeadWall16 game option. Replacement tiles for kongs are always taken from the loose tiles, but replacements for bonus tiles may be drawn from the live wall (M 31), or from the loose tiles, according to the FlowersLoose game option. Object of game (M 28-31): all winning hands must comprise four sets and a pair, with the exception of the Thirteen Unique Wonders. If the SevenPairs game option is set, then a hand of any seven pairs is also allowed as a winning hand. Bonus tiles (M 31): M requires that bonus tiles must be declared in the turn in which they are drawn; otherwise the player may not exchange or score them (and thus they cannot go out). We do not make this restriction, as it is (a) pointless (b) unenforceable in real life. Bonus tiles may be declared at any time after drawing from the wall. (Obviously, there is no reason not to declare them immediately.) Commencement of the Game (M 32-33): standard. Playing procedure (M 34-38): standard. In particular, the other players have to give permission for east to start playing (M 34). The display of discards cannot be controlled by the server; the current X client displays them in an organized fashion, rather than the random layout required by M 35. Chow (M 39-42): standard. Pung (M 43-45): standard. Kongs (M 46-52): M distinguishes three types of kong: concealed, claimed (by Kong), and annexed (formed by adding a discard to an exposed pung), and allows claimed kongs to be counted as concealed for the purposes of doubling combinations. I have not seen this anywhere else; normally, a claimed kong is treated as exposed for all purposes. We follow the normal convention; however, the game option KongHas3Types can be set to implement M's rules. In this case, the xmj program will distinguish claimed kongs by displaying them with the last tile face down, whereas annexed kongs are all face up. Players may declare a concealed kong, or add to a pung, only when they have just drawn a tile from the wall (live or dead); not just after a claiming a discard. (A silly restriction in my view, but one that all rule sets seem to have (M 51).) As from program version 1.11 (protocol version 1110), we also allow a player to add to a pung they have just claimed (see note above in the description of play). Calling and Mah Jong (M 53-54): standard. (I.e. there is no "Calling" declaration.) NOTE: M permits players to change their mind about making a claim (M 69); we do not, and all claims are irrevocable. As a special concession, we allow adding to a just claimed pung, so simulating the effect of correcting a pung claim to a kong. Original Call (M 55): the Original Call declaration must be made simultaneously with the first discard, rather than afterwards. NOTE: the server does *not* check that the declarer does indeed have a calling hand, as a mistaken original call does not damage the other players or the progress of the game. The server does, however, thereafter prevent the declarer from changing their hand; therefore a mistaken original call will make it impossible to go out. (Note: in M, an Original Caller may change their hand, but will thereby lose the ability to go out (M 55(b)); is this a better way to treat it?) Note also: as per M, an original call can be made even if another player has claimed a discard before, unlike the Japanese version. Robbing a Kong (M 57-60): Robbing a kong is implemented. However, as with discards, we require that kongs are robbed before anything else happens, and in particular before the konger draws a replacement tile. Therefore, after a kong, all other players must either claim Mah Jong or pass. (The provided programs will pass automatically if robbing is not possible.) As for discards, there is a time limit. Precedence of claims for discard (M 61-65): Many rules allow a discard to be claimed up until the time the next discard is made. M does this, with elaborate rules for the precise specification. For ease of implementation, we do not allow this: instead, all players are required to make a claim or pass, and once all players have claimed, the successful claim is implemented irrevocably. The server imposes a time limit; players that do not claim within the limit are deemed to have passed. This defaults to 15 seconds, but can be changed or disabled by the Timeout game option. Irregularities in Play (M 66-81): the server does not permit unlawful moves, and so no irregularities can arise. False Declaration of Mah Jong (M 82-83): such declarations are not permitted by the server. False Naming of Discards (M 84-88): this also cannot happen. Incorrect Hands (M 89): cannot happen. Letting Off a Cannon (M 90-96): as in M. However, if a player makes a dangerous discard, but has no choice, the server will determine this; it is not necessary to plead "no choice" explicitly, and neither is the player's hand revealed to the other players. Wash-Out (M 97-99): standard. Points of Etiquette (M 100-102): not applicable. Displaying the Hand (M 103-106): The format of display is a matter for the client program, and cannot be controlled by the server. After Mah Jong, the players are responsible for declaring concealed sets in whatever way they wish. The winner, of course, is required to declare a complete hand; but the losers may declare as they wish. Once a set is declared, it cannot be revoked. Note that the losers may declare multiple scoring pairs. Procedure in Settlement (M 107-111): The settlement is classical: that is, the winner gets the value of their hand from all players; the losers pay one another the differences between their scores; except all payments to or from East are doubled; and if players let off a cannon, they pay everybody's debt. Unlike normal play (M 110), all hands are scored by the server, rather than by the players. Settlement is also computed by the server. Some variations in settlement are provided: if the LosersSettle game option is set to false, there are no payments between losers; if the EastDoubles game option is set to false, payments to or from East are not doubled; if the DiscDoubles game option is set to true, then the discarder of the tile that gave Mah-Jong will pay double to the winner, and a self-draw is paid double by everybody. Method of Scoring (M 112-122): The method is standard (M 112), viz calculate points obtained from sets and bonuses, and then apply doubles. The following points are given for tiles: Bonus tiles: 4 each (M 114(a)) Pungs: 2 for exposed minor tiles; 4 for exposed major or concealed minor; 8 for concealed major. (M 114(b)) Kongs: 8 for exposed minor; 16 for exposed major or concealed minor; 32 for concealed major. (M 114(c)) Chows: no score. (M 114(d)) Pair: 2 for a pair of Dragons, Own Wind, or Prevailing Wind. A pair that is both Own and Prevailing Wind scores 4. (M 114(e)) Non-winning hands may score more than one pair. Basic points: the winner gets 20 points for going Mah Jong. This can be changed by the MahJongScore game option (M 115(a) has 10 points). Seven Pairs hand: If Seven Pairs hands are allowed, they receive an additional score of 20 points, changed by the SevenPairsVal game option. Winning from wall: if the final tile is drawn from the wall, 2 points are added (M 115(b)). Filling the only place: if the final tile is the only denomination that could have completed the hand, 2 points are added (M 115(c)). NOTE: As in M, if all four copies of a tile are exposed on the table, it does not count as available for completing the hand. Fishing the eyes: a player who completes by obtaining a pair gets 2 points if the pair is minor, or 4 if major (M 115(d)). Note: to obtain these points for a discard, the player must actually claim the discard for a pair: e.g. if waiting on 5677, and 7 is discarded, the player must claim for the pair, not the chow. The following doubles apply to all hands. All possible clauses apply unless stated otherwise. Having own flower or own season. No extra score. Changed by the FlowersOwnEach game option. Having own flower AND own season, 1 double. (M 116(a)). Changed by the FlowersOwnBoth game option. Having all four flowers, 1 double. (M 116(b)). Changed by the FlowersBouquet game option. Having all four seasons, 1 double. (M 116(b)). Changed by the FlowersBouquet game option. Each set of dragons, 1 double. (M 116(d)) A set of the player's own wind, 1 double. (M 116(e)) A set of the prevailing wind, 1 double. (M 116(f)) "Little Three Dragons": two sets and a pair of dragons. 1 double. (M 116(g)) "Big Three Dragons": three sets of dragons. 2 doubles. (M 116(h)) "Little Four Winds": three sets and a pair of winds. 1 double. (M 116(i)) "Big Four Winds": four sets of winds. 2 doubles. (M 116(j)) (Note: the definitions of these last four doubles when applied to non-winning hands are subject to wide variations. Possibly there should be options to allow other possibilities.) Three concealed pungs: 1 double. (M 116(k)) (Note: if the KongHas3Types game option is set, a claimed kong counts as concealed for this hand; see the note above under "Kongs".) The following doubles apply to the winning hand only: No score hand: four chows and a non-scoring pair. 1 double. (M 117(a)) (Note: like M, we allow any of the extra points (Fishing the Eyes, etc) to go with this double. Some rules say that the extra points invalidate this hand. Possibly there should be an option for this.) No chows: 1 double. (M 117(b)) Concealed hand: 1 double (M 117(c)), changeable with the ConcealedFully game option. (Note: this means a hand that is fully concealed after going out. Another common value for this is 3 doubles, in which case 1 double is usually given for a semi-concealed hand (see below).) (Note: if the KongHas3Types game option is set, a claimed kong counts as concealed for this hand; see the note above under "Kongs".) The following doubles normally apply to the winning hand only; however, the LosersPurity game option can be set to allow losing hands to score them (this is a highly deprecated American feature, but has been requested by a user). Semi-concealed hand: no doubles, changeable with the ConcealedAlmost game option. (Not in M) (Note: this means a winning hand that is concealed up to the point of going out, or, if enabled, a concealed losing hand. According to a discussion on rec.games.mahjong, a winning semi-concealed hand is classically awarded one double (with three given for fully concealed). One book in my possession (U.S.A., early 1920s) awards this double only to a hand that is concealed except for the pair.) (Note: if the KongHas3Types game option is set, a claimed kong counts as concealed for this hand; see the note above under "Kongs".) One suit with honours: 1 double. (M 117(d)) One suit only: 3 doubles. (M 117(e)) All majors: 1 double. (M 117(f)) All honours (in an unlimited game): 2 doubles. (M 117(g)) (Note: such a hand will also score the double for all majors.) All terminals (in an unlimited game): 2 doubles. (Not in M) (Note: such a hand will also score the double for all majors.) The following doubles apply only to the winning hand: Winning with loose tile: 1 double. (M 117(h)) (Note: with the default settings, replacements for bonus tiles come from the live wall. Hence this double applies only to winning after Kong.) Winning from the bottom of the sea (winning with last tile), 1 double. (M 117(i)) Catching a fish from the bottom of the sea (winning with last discard), 1 double. (M 117(j)) Robbing a kong, 1 double. (M 117(k)) Completing Original Call, 1 double. (M 117(l)) Limit (M 118-120): the limit is 1000 by default, and can be changed by the ScoreLimit game option. The NoLimit game option can be used to play a game "with the roof off". The following hands are limit hands: Heaven's Blessing: East wins with dealt hand. (M 122(a)) Earth's Blessing: player wins with East's first discard. (M 122(b)) Gathering Plum Blossom from the Roof: winning with 5 Circles from the loose wall. (M 122(c)) Catching the Moon from the Bottom of the Sea: winning with 1 Circle as the last tile. (M 122(d)) (Note: M says that the tile must be drawn. It seems more reasonable also to allow it to be the last discard, which is what we do. Objections?) Scratching a Carrying Pole: robbing a kong of 2 Bamboos. (M 122(e)) (Note: these last three limits are rather arbitrary, but of the arbitrary limits they are apparently the most common. There should be options to disable them.) Kong upon Kong: making a Kong, making another Kong with the loose tile, and with the second loose tile obtaining Mah Jong. (Also, of course, with three or four successive kongs.) (M 122(f)) Four Kongs. (M 122(g)) Buried Treasure: all concealed and no chows. (M 122(h)) The Three Great Scholars: three sets of dragons and no chows. (M 122(i)) (Note: in most rules I have seen, there is no restriction to a no chow hand. Since in M's rules, three sets and a chow scores at least (10 (M has 10 for Mah Jong) + 12 (at least 3 pungs)) times 8 (2 for each set of dragons) times 4 (for Big Three Dragons) = 704, this is significant with the default limit. For us, with 20 for going out, Big Three Dragons is over the default limit anyway.) Four Blessings o'er the Door: four sets of winds and a pair. (M 122(j)) All Honours. (M 122(k)) Heads and Tails: all terminals. (M 122(l)) Imperial Jade: contains only Green Dragon and 2,3,4,6,8 Bamboo. (M 122(m)) (Note: another rather arbitrary hand, but widely adopted.) Nine Gates: calling on 1-1-1-2-3-4-5-6-7-8-9-9-9 of one suit. (M 122(n)). Wriggling Snake: 1-1-1-2-3-4-5-6-7-8-9-9-9 plus 2, 5 or 8 of one suit (M 122(o)). (Note: another rather arbitrary hand.) Concealed Clear Suit: one suit only and all concealed. (M 122(p)) Thirteen Unique Wonders: one of each major tile, and a match to any of them. (M 122(q)) East's 13th consecutive Mah-Jong. (M 122(r)) General note: there are many other doubles and limits kicking around. I welcome opinions on which should be possible options; and also on which of the above I should eject from the default set. I dislike Imperial Jade, Wriggling Snake, and the ones depending on a specific tile (Gathering Plum Blossom, Catching the Moon, Scratching a Carrying Pole): which of these are so commonly adopted that they should be in even a fairly minimalist default set? GAME OPTIONS ------------ This section describes the options that can be set in the game. Whether an option can be used, depends on the version of the programs. This is described by a "protocol version number"; this is not strictly speaking a version just of the communication protocol, but a version number reflecting the combination of protocol and programs. When playing by oneself, this does not matter, but in the case of a networked game, players might have different versions of the software, in which case the game is played according to the lowest version of any player. Game options can be controlled in two ways: the --option-file argument to the mj-server program gives options to be applied to the game, or options can be set by the players, using the interface described in the manual section for xmj . In the user interface, the options are referred to by a one line description, but each option also has a short name, given here. Options are of several types: bool boolean, or on/off, options. int integer options nat non-negative integer options string is a miscellaneous type, whose values are strings of at most 127 characters which must not contain white space score is the type used for options that give the score of some combination or feature in a hand. A score is either a limit (or a half-limit; the underlying protocol supports percentages of limits, but the current user programs only support limits and half limits); or a number of doubles to be awarded; or a number of points to be added. It is possible (though never needed) to have both points and doubles. If points/doubles are specified as well as a limit, they will be used in a no-limit game. (The server implements a hard limit of 100000000 on all scores to avoid arithmetic overflow, but that's unlikely to worry anybody.) Currently supported options --------------------------- The following options are implemented in the versions of the program with which this document is distributed. If playing against people with older versions of the software, some options may not be available. The list gives for each option the short name, type, and short description, followed by a detailed explanation. Timeout ( nat ) time limit for claims This is the time in seconds allowed to claim a discard, or to rob a kong. If set to zero, there is no timeout. The default is 15 seconds. TimeoutGrace ( nat ) grace period when clients handle timeouts This period (in seconds) is added to the Timeout above before the server actually forces a timeout. This is for when clients handle timeouts locally, and allows for network lags. If this option is zero, clients are not permitted to handle timeouts locally. The current server also only allows players to handle timeouts locally if all of them wish to do so. ScoreLimit ( nat ) limit on hand score This is the limit for the score of a hand. In a no-limit game, it is the notional value of a "limit" hand. The default is 1000. NoLimit ( bool ) no-limit game If this option is set, the game has no limit on hand scores. The default is unset. MahJongScore ( score ) base score for going out This is the number of points for obtaining Mah-Jong. The default is 20. SevenPairs ( bool ) seven pairs hand allowed If this option is set, then Mah-Jong hands of seven pairs (any seven pairs) are allowed. The default is unset. SevenPairsVal ( score ) score for a seven pair hand This gives the score (in addition to the base Mah-Jong score) for a seven pairs hand. The default is 20. Flowers ( bool ) play using flowers and seasons If this option is set, the deal includes four flowers and four seasons in the Chinese Classical style. If unset, only the 136 standard tiles are used. The default is set. FlowersLoose ( bool ) flowers replaced by loose tiles If playing with flowers, this option determines whether flowers and seasons are replaced from the live wall (unset), or by loose tiles (set). The default is unset. FlowersOwnEach ( score ) score for each own flower or season This option gives the score for having one's own flower or season. If one has both, this score will be given twice. The default is no score. FlowersOwnBoth ( score ) score for own flower and own season This is the score for having both one's own flower and one's own season. Note that this is awarded in addition to twice the previous score. The default is 1 double. FlowersBouquet ( score ) score for all four flowers or all four seasons This is the score for having all four flowers or all four seasons. The default is 1 double. DeadWall ( bool ) there is a dead wall This determines whether there is a dead wall, so that play ends when it is reached (set), or whether all tiles may be drawn (unset). The default is set. DeadWall16 ( bool ) dead wall is 16 tiles, unreplenished If this option is set, then the dead wall initially has 16 tiles, and does not have any more tiles added to it (this is the set-up described by Millington). If the option is unset, then the dead wall initially has 14 tiles, and after two loose tiles have been taken, two tiles are moved from the live wall to the dead wall (this is the set-up described by almost everyone else). The default is unset in versions 1.1 onwards, and set previously. (To be precise, the protocol level default is set, but all servers from 1.1 onwards will change this to unset.) ConcealedFully ( score ) score for fully concealed hand This is the score for a winning hand with no open sets. The default is 1 double. ConcealedAlmost ( score ) score for almost concealed hand This is the score for a hand that is concealed up to the point of going out. The default is no additional score. LosersPurity ( bool ) losing hands score doubles for pure, concealed etc. If this option is set, losing hands will score various doubles for one suit, almost concealed, etc. See the rules for details. This option is an (Anglo-)Americanism alien to Chinese Classical (see Foster for a spirited but faulty argument in its favour, and Millington for the rejoinder). The default is unset. KongHas3Types ( bool ) claimed kongs count as concealed for doubling If this option is set, claimed kongs count as concealed for various doubling combinations, although they score as exposed for basic points. See the note above under "Kongs". The default is unset. LosersSettle ( bool ) losers pay each other If this option is set, the losers pay each other the difference between their scores. If it unset, they pay only the winner. The default is set. EastDoubles ( bool ) east pays and receives double If this option is set, payments to and from East Wind are doubled, as in the Chinese Classical game. The default is set. DiscDoubles ( bool ) the discarder pays double If this option is set, the settlement procedure is changed to a style common in Singapore. That is, if the winning player wins off a discard, the discarder pays double the hand value, and the other players pay the hand value. If the winner wins from the wall, then all other players pay double the hand value. The default is unset. Note: EastDoubles and DiscDoubles can be set together, but nobody plays such a rule. ShowOnWashout ( bool ) reveal tiles on washout If this option is set, the players' hands will be revealed in the event of a washout. NumRounds ( nat ) number of rounds to play This option says how many rounds to play in the game. For aesthetic reasons, the possible values are 1, 2, or a multiple of 4. In the 2 round case, the East and South rounds will be played. It defaults to the usual 4 rounds. Option file format ------------------ Both in the option file and in the .xmjrc file, options are recorded in the format used by the server protocol. This is a line of the form GameOption 0 NAME TYPE MINPROT ENABLED VALUE DESC The meanings of the elements are: GameOption 0 identifies this as a game option line (the 0 is an irrelevant field from the protocol). NAME is the name of the option. TYPE is the type of the option. MINPROT is the minimum protocol version with which the option can be used (which is not necessarily the version at which it was introduced). ENABLED will always be 1. VALUE is the value: a decimal (signed) integer for nat and int ; 0 or 1 for bool ; the string for string ; and for score , if the score is C centi-limits, D doubles and P points, the value is C*1000000 + D*10000 + P. DESC is a short description of the option, which is not required but is usually copied in from the server. mj-1.17-src/gtkrc-minimal0000444006717300001440000000126715002771311014007 0ustar jcbusersstyle "table" { bg[NORMAL] = "darkgreen" } style "playerlabel" { fg[NORMAL] = "white" } style "claim" { bg[NORMAL] = "yellow" font_name = "Sans Bold 20px" } style "thinking" { fg[NORMAL] = "yellow" font_name = "Sans Bold 20px" } binding "topwindow" { bind "Left" { "selectleft"() } bind "Right" { "selectright"() } bind "Left" { "moveleft"() } bind "Right" { "moveright"() } } style "text" { } widget "*.table" style "table" widget "*.claim" style "claim" widget "*.think" style "thinking" widget "topwindow" binding "topwindow" widget "*.GtkTextView*" style "text" widget "*.GtkEntry*" style "text" widget "*.playerlabel" style "playerlabel" mj-1.17-src/iconres.rs0000444006717300001440000000005215002771311013325 0ustar jcbusers#include 1234 ICON "icon.ico" mj-1.17-src/malloc.c0000444006717300001440000054160315002771311012744 0ustar jcbusers/* This is a version (aka dlmalloc) of malloc/free/realloc written by Doug Lea and released to the public domain. Use, modify, and redistribute this code without permission or acknowledgement in any way you wish. Send questions, comments, complaints, performance data, etc to dl@cs.oswego.edu * VERSION 2.7.2 Sat Aug 17 09:07:30 2002 Doug Lea (dl at gee) Note: There may be an updated version of this malloc obtainable at ftp://gee.cs.oswego.edu/pub/misc/malloc.c Check before installing! * Quickstart This library is all in one file to simplify the most common usage: ftp it, compile it (-O), and link it into another program. All of the compile-time options default to reasonable values for use on most unix platforms. Compile -DWIN32 for reasonable defaults on windows. You might later want to step through various compile-time and dynamic tuning options. For convenience, an include file for code using this malloc is at: ftp://gee.cs.oswego.edu/pub/misc/malloc-2.7.1.h You don't really need this .h file unless you call functions not defined in your system include files. The .h file contains only the excerpts from this file needed for using this malloc on ANSI C/C++ systems, so long as you haven't changed compile-time options about naming and tuning parameters. If you do, then you can create your own malloc.h that does include all settings by cutting at the point indicated below. * Why use this malloc? This is not the fastest, most space-conserving, most portable, or most tunable malloc ever written. However it is among the fastest while also being among the most space-conserving, portable and tunable. Consistent balance across these factors results in a good general-purpose allocator for malloc-intensive programs. The main properties of the algorithms are: * For large (>= 512 bytes) requests, it is a pure best-fit allocator, with ties normally decided via FIFO (i.e. least recently used). * For small (<= 64 bytes by default) requests, it is a caching allocator, that maintains pools of quickly recycled chunks. * In between, and for combinations of large and small requests, it does the best it can trying to meet both goals at once. * For very large requests (>= 128KB by default), it relies on system memory mapping facilities, if supported. For a longer but slightly out of date high-level description, see http://gee.cs.oswego.edu/dl/html/malloc.html You may already by default be using a C library containing a malloc that is based on some version of this malloc (for example in linux). You might still want to use the one in this file in order to customize settings or to avoid overheads associated with library versions. * Contents, described in more detail in "description of public routines" below. Standard (ANSI/SVID/...) functions: malloc(size_t n); calloc(size_t n_elements, size_t element_size); free(Void_t* p); realloc(Void_t* p, size_t n); memalign(size_t alignment, size_t n); valloc(size_t n); mallinfo() mallopt(int parameter_number, int parameter_value) Additional functions: independent_calloc(size_t n_elements, size_t size, Void_t* chunks[]); independent_comalloc(size_t n_elements, size_t sizes[], Void_t* chunks[]); pvalloc(size_t n); cfree(Void_t* p); malloc_trim(size_t pad); malloc_usable_size(Void_t* p); malloc_stats(); * Vital statistics: Supported pointer representation: 4 or 8 bytes Supported size_t representation: 4 or 8 bytes Note that size_t is allowed to be 4 bytes even if pointers are 8. You can adjust this by defining INTERNAL_SIZE_T Alignment: 2 * sizeof(size_t) (default) (i.e., 8 byte alignment with 4byte size_t). This suffices for nearly all current machines and C compilers. However, you can define MALLOC_ALIGNMENT to be wider than this if necessary. Minimum overhead per allocated chunk: 4 or 8 bytes Each malloced chunk has a hidden word of overhead holding size and status information. Minimum allocated size: 4-byte ptrs: 16 bytes (including 4 overhead) 8-byte ptrs: 24/32 bytes (including, 4/8 overhead) When a chunk is freed, 12 (for 4byte ptrs) or 20 (for 8 byte ptrs but 4 byte size) or 24 (for 8/8) additional bytes are needed; 4 (8) for a trailing size field and 8 (16) bytes for free list pointers. Thus, the minimum allocatable size is 16/24/32 bytes. Even a request for zero bytes (i.e., malloc(0)) returns a pointer to something of the minimum allocatable size. The maximum overhead wastage (i.e., number of extra bytes allocated than were requested in malloc) is less than or equal to the minimum size, except for requests >= mmap_threshold that are serviced via mmap(), where the worst case wastage is 2 * sizeof(size_t) bytes plus the remainder from a system page (the minimal mmap unit); typically 4096 or 8192 bytes. Maximum allocated size: 4-byte size_t: 2^32 minus about two pages 8-byte size_t: 2^64 minus about two pages It is assumed that (possibly signed) size_t values suffice to represent chunk sizes. `Possibly signed' is due to the fact that `size_t' may be defined on a system as either a signed or an unsigned type. The ISO C standard says that it must be unsigned, but a few systems are known not to adhere to this. Additionally, even when size_t is unsigned, sbrk (which is by default used to obtain memory from system) accepts signed arguments, and may not be able to handle size_t-wide arguments with negative sign bit. Generally, values that would appear as negative after accounting for overhead and alignment are supported only via mmap(), which does not have this limitation. Requests for sizes outside the allowed range will perform an optional failure action and then return null. (Requests may also also fail because a system is out of memory.) Thread-safety: NOT thread-safe unless USE_MALLOC_LOCK defined When USE_MALLOC_LOCK is defined, wrappers are created to surround every public call with either a pthread mutex or a win32 spinlock (depending on WIN32). This is not especially fast, and can be a major bottleneck. It is designed only to provide minimal protection in concurrent environments, and to provide a basis for extensions. If you are using malloc in a concurrent program, you would be far better off obtaining ptmalloc, which is derived from a version of this malloc, and is well-tuned for concurrent programs. (See http://www.malloc.de) Note that even when USE_MALLOC_LOCK is defined, you can can guarantee full thread-safety only if no threads acquire memory through direct calls to MORECORE or other system-level allocators. Compliance: I believe it is compliant with the 1997 Single Unix Specification (See http://www.opennc.org). Also SVID/XPG, ANSI C, and probably others as well. * Synopsis of compile-time options: People have reported using previous versions of this malloc on all versions of Unix, sometimes by tweaking some of the defines below. It has been tested most extensively on Solaris and Linux. It is also reported to work on WIN32 platforms. People also report using it in stand-alone embedded systems. The implementation is in straight, hand-tuned ANSI C. It is not at all modular. (Sorry!) It uses a lot of macros. To be at all usable, this code should be compiled using an optimizing compiler (for example gcc -O3) that can simplify expressions and control paths. (FAQ: some macros import variables as arguments rather than declare locals because people reported that some debuggers otherwise get confused.) OPTION DEFAULT VALUE Compilation Environment options: __STD_C derived from C compiler defines WIN32 NOT defined HAVE_MEMCPY defined USE_MEMCPY 1 if HAVE_MEMCPY is defined HAVE_MMAP defined as 1 MMAP_CLEARS 1 HAVE_MREMAP 0 unless linux defined malloc_getpagesize derived from system #includes, or 4096 if not HAVE_USR_INCLUDE_MALLOC_H NOT defined LACKS_UNISTD_H NOT defined unless WIN32 LACKS_SYS_PARAM_H NOT defined unless WIN32 LACKS_SYS_MMAN_H NOT defined unless WIN32 LACKS_FCNTL_H NOT defined Changing default word sizes: INTERNAL_SIZE_T size_t MALLOC_ALIGNMENT 2 * sizeof(INTERNAL_SIZE_T) PTR_UINT unsigned long CHUNK_SIZE_T unsigned long Configuration and functionality options: USE_DL_PREFIX NOT defined USE_PUBLIC_MALLOC_WRAPPERS NOT defined USE_MALLOC_LOCK NOT defined DEBUG NOT defined REALLOC_ZERO_BYTES_FREES NOT defined MALLOC_FAILURE_ACTION errno = ENOMEM, if __STD_C defined, else no-op TRIM_FASTBINS 0 FIRST_SORTED_BIN_SIZE 512 Options for customizing MORECORE: MORECORE sbrk MORECORE_CONTIGUOUS 1 MORECORE_CANNOT_TRIM NOT defined MMAP_AS_MORECORE_SIZE (1024 * 1024) Tuning options that are also dynamically changeable via mallopt: DEFAULT_MXFAST 64 DEFAULT_TRIM_THRESHOLD 256 * 1024 DEFAULT_TOP_PAD 0 DEFAULT_MMAP_THRESHOLD 256 * 1024 DEFAULT_MMAP_MAX 65536 There are several other #defined constants and macros that you probably don't want to touch unless you are extending or adapting malloc. */ /* WIN32 sets up defaults for MS environment and compilers. Otherwise defaults are for unix. */ /* #define WIN32 */ #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #include /* Win32 doesn't supply or need the following headers */ /* #define LACKS_UNISTD_H */ /* #define LACKS_SYS_PARAM_H */ #define LACKS_SYS_MMAN_H /* Use the supplied emulation of sbrk */ /* #define MORECORE sbrk */ /* #define MORECORE_CONTIGUOUS 1 */ /* #define MORECORE_FAILURE ((void*)(-1)) */ /* Use the supplied emulation of mmap and munmap */ /* #define HAVE_MMAP 1 */ /* #define MUNMAP_FAILURE (-1) */ /* #define MMAP_CLEARS 1 */ /* These values don't really matter in windows mmap emulation */ #define MAP_PRIVATE 1 #define MAP_ANONYMOUS 2 #define PROT_READ 1 #define PROT_WRITE 2 /* Emulation functions defined at the end of this file */ /* If USE_MALLOC_LOCK, use supplied critical-section-based lock functions */ #ifdef USE_MALLOC_LOCK static int slwait(int *sl); static int slrelease(int *sl); #endif static long getpagesize(void); static long getregionsize(void); static void *sbrk(long size); static void vminfo (unsigned long*free, unsigned long*reserved, unsigned long*committed); static int cpuinfo (int whole, unsigned long*kernel, unsigned long*user); #endif /* __STD_C should be nonzero if using ANSI-standard C compiler, a C++ compiler, or a C compiler sufficiently close to ANSI to get away with it. */ #ifndef __STD_C #if defined(__STDC__) || defined(_cplusplus) #define __STD_C 1 #else #define __STD_C 0 #endif #endif /*__STD_C*/ /* Void_t* is the pointer type that malloc should say it returns */ #ifndef Void_t #if (__STD_C || defined(WIN32)) #define Void_t void #else #define Void_t char #endif #endif /*Void_t*/ #if __STD_C #include /* for size_t */ #else #include #endif #ifdef __cplusplus extern "C" { #endif /* define LACKS_UNISTD_H if your system does not have a . */ /* #define LACKS_UNISTD_H */ #ifndef LACKS_UNISTD_H #include #endif /* define LACKS_SYS_PARAM_H if your system does not have a . */ /* #define LACKS_SYS_PARAM_H */ #include /* needed for malloc_stats */ #include /* needed for optional MALLOC_FAILURE_ACTION */ /* Debugging: Because freed chunks may be overwritten with bookkeeping fields, this malloc will often die when freed memory is overwritten by user programs. This can be very effective (albeit in an annoying way) in helping track down dangling pointers. If you compile with -DDEBUG, a number of assertion checks are enabled that will catch more memory errors. You probably won't be able to make much sense of the actual assertion errors, but they should help you locate incorrectly overwritten memory. The checking is fairly extensive, and will slow down execution noticeably. Calling malloc_stats or mallinfo with DEBUG set will attempt to check every non-mmapped allocated and free chunk in the course of computing the summmaries. (By nature, mmapped regions cannot be checked very much automatically.) Setting DEBUG may also be helpful if you are trying to modify this code. The assertions in the check routines spell out in more detail the assumptions and invariants underlying the algorithms. Setting DEBUG does NOT provide an automated mechanism for checking that all accesses to malloced memory stay within their bounds. However, there are several add-ons and adaptations of this or other mallocs available that do this. */ #if DEBUG #include #else #define assert(x) ((void)0) #endif /* The unsigned integer type used for comparing any two chunk sizes. This should be at least as wide as size_t, but should not be signed. */ #ifndef CHUNK_SIZE_T #define CHUNK_SIZE_T unsigned long #endif /* The unsigned integer type used to hold addresses when they are are manipulated as integers. Except that it is not defined on all systems, intptr_t would suffice. */ #ifndef PTR_UINT #define PTR_UINT unsigned long #endif /* INTERNAL_SIZE_T is the word-size used for internal bookkeeping of chunk sizes. The default version is the same as size_t. While not strictly necessary, it is best to define this as an unsigned type, even if size_t is a signed type. This may avoid some artificial size limitations on some systems. On a 64-bit machine, you may be able to reduce malloc overhead by defining INTERNAL_SIZE_T to be a 32 bit `unsigned int' at the expense of not being able to handle more than 2^32 of malloced space. If this limitation is acceptable, you are encouraged to set this unless you are on a platform requiring 16byte alignments. In this case the alignment requirements turn out to negate any potential advantages of decreasing size_t word size. Implementors: Beware of the possible combinations of: - INTERNAL_SIZE_T might be signed or unsigned, might be 32 or 64 bits, and might be the same width as int or as long - size_t might have different width and signedness as INTERNAL_SIZE_T - int and long might be 32 or 64 bits, and might be the same width To deal with this, most comparisons and difference computations among INTERNAL_SIZE_Ts should cast them to CHUNK_SIZE_T, being aware of the fact that casting an unsigned int to a wider long does not sign-extend. (This also makes checking for negative numbers awkward.) Some of these casts result in harmless compiler warnings on some systems. */ #ifndef INTERNAL_SIZE_T #define INTERNAL_SIZE_T size_t #endif /* The corresponding word size */ #define SIZE_SZ (sizeof(INTERNAL_SIZE_T)) /* MALLOC_ALIGNMENT is the minimum alignment for malloc'ed chunks. It must be a power of two at least 2 * SIZE_SZ, even on machines for which smaller alignments would suffice. It may be defined as larger than this though. Note however that code and data structures are optimized for the case of 8-byte alignment. */ #ifndef MALLOC_ALIGNMENT #define MALLOC_ALIGNMENT (2 * SIZE_SZ) #endif /* The corresponding bit mask value */ #define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1) /* REALLOC_ZERO_BYTES_FREES should be set if a call to realloc with zero bytes should be the same as a call to free. Some people think it should. Otherwise, since this malloc returns a unique pointer for malloc(0), so does realloc(p, 0). */ /* #define REALLOC_ZERO_BYTES_FREES */ /* TRIM_FASTBINS controls whether free() of a very small chunk can immediately lead to trimming. Setting to true (1) can reduce memory footprint, but will almost always slow down programs that use a lot of small chunks. Define this only if you are willing to give up some speed to more aggressively reduce system-level memory footprint when releasing memory in programs that use many small chunks. You can get essentially the same effect by setting MXFAST to 0, but this can lead to even greater slowdowns in programs using many small chunks. TRIM_FASTBINS is an in-between compile-time option, that disables only those chunks bordering topmost memory from being placed in fastbins. */ #ifndef TRIM_FASTBINS #define TRIM_FASTBINS 0 #endif /* USE_DL_PREFIX will prefix all public routines with the string 'dl'. This is necessary when you only want to use this malloc in one part of a program, using your regular system malloc elsewhere. */ /* #define USE_DL_PREFIX */ /* USE_MALLOC_LOCK causes wrapper functions to surround each callable routine with pthread mutex lock/unlock. USE_MALLOC_LOCK forces USE_PUBLIC_MALLOC_WRAPPERS to be defined */ /* #define USE_MALLOC_LOCK */ /* If USE_PUBLIC_MALLOC_WRAPPERS is defined, every public routine is actually a wrapper function that first calls MALLOC_PREACTION, then calls the internal routine, and follows it with MALLOC_POSTACTION. This is needed for locking, but you can also use this, without USE_MALLOC_LOCK, for purposes of interception, instrumentation, etc. It is a sad fact that using wrappers often noticeably degrades performance of malloc-intensive programs. */ #ifdef USE_MALLOC_LOCK #define USE_PUBLIC_MALLOC_WRAPPERS #else /* #define USE_PUBLIC_MALLOC_WRAPPERS */ #endif /* Two-phase name translation. All of the actual routines are given mangled names. When wrappers are used, they become the public callable versions. When DL_PREFIX is used, the callable names are prefixed. */ #ifndef USE_PUBLIC_MALLOC_WRAPPERS #define cALLOc public_cALLOc #define fREe public_fREe #define cFREe public_cFREe #define mALLOc public_mALLOc #define mEMALIGn public_mEMALIGn #define rEALLOc public_rEALLOc #define vALLOc public_vALLOc #define pVALLOc public_pVALLOc #define mALLINFo public_mALLINFo #define mALLOPt public_mALLOPt #define mTRIm public_mTRIm #define mSTATs public_mSTATs #define mUSABLe public_mUSABLe #define iCALLOc public_iCALLOc #define iCOMALLOc public_iCOMALLOc #endif #ifdef USE_DL_PREFIX #define public_cALLOc dlcalloc #define public_fREe dlfree #define public_cFREe dlcfree #define public_mALLOc dlmalloc #define public_mEMALIGn dlmemalign #define public_rEALLOc dlrealloc #define public_vALLOc dlvalloc #define public_pVALLOc dlpvalloc #define public_mALLINFo dlmallinfo #define public_mALLOPt dlmallopt #define public_mTRIm dlmalloc_trim #define public_mSTATs dlmalloc_stats #define public_mUSABLe dlmalloc_usable_size #define public_iCALLOc dlindependent_calloc #define public_iCOMALLOc dlindependent_comalloc #else /* USE_DL_PREFIX */ #define public_cALLOc calloc #define public_fREe free #define public_cFREe cfree #define public_mALLOc malloc #define public_mEMALIGn memalign #define public_rEALLOc realloc #define public_vALLOc valloc #define public_pVALLOc pvalloc #define public_mALLINFo mallinfo #define public_mALLOPt mallopt #define public_mTRIm malloc_trim #define public_mSTATs malloc_stats #define public_mUSABLe malloc_usable_size #define public_iCALLOc independent_calloc #define public_iCOMALLOc independent_comalloc #endif /* USE_DL_PREFIX */ /* HAVE_MEMCPY should be defined if you are not otherwise using ANSI STD C, but still have memcpy and memset in your C library and want to use them in calloc and realloc. Otherwise simple macro versions are defined below. USE_MEMCPY should be defined as 1 if you actually want to have memset and memcpy called. People report that the macro versions are faster than libc versions on some systems. Even if USE_MEMCPY is set to 1, loops to copy/clear small chunks (of <= 36 bytes) are manually unrolled in realloc and calloc. */ #define HAVE_MEMCPY #ifndef USE_MEMCPY #ifdef HAVE_MEMCPY #define USE_MEMCPY 1 #else #define USE_MEMCPY 0 #endif #endif #if (__STD_C || defined(HAVE_MEMCPY)) #ifdef WIN32 /* On Win32 memset and memcpy are already declared in windows.h */ #else #if __STD_C void* memset(void*, int, size_t); void* memcpy(void*, const void*, size_t); #else Void_t* memset(); Void_t* memcpy(); #endif #endif #endif /* MALLOC_FAILURE_ACTION is the action to take before "return 0" when malloc fails to be able to return memory, either because memory is exhausted or because of illegal arguments. By default, sets errno if running on STD_C platform, else does nothing. */ #ifndef MALLOC_FAILURE_ACTION #if __STD_C #define MALLOC_FAILURE_ACTION \ errno = ENOMEM; #else #define MALLOC_FAILURE_ACTION #endif #endif /* MORECORE-related declarations. By default, rely on sbrk */ #ifdef LACKS_UNISTD_H #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) #if __STD_C extern Void_t* sbrk(ptrdiff_t); #else extern Void_t* sbrk(); #endif #endif #endif /* MORECORE is the name of the routine to call to obtain more memory from the system. See below for general guidance on writing alternative MORECORE functions, as well as a version for WIN32 and a sample version for pre-OSX macos. */ #ifndef MORECORE #define MORECORE sbrk #endif /* MORECORE_FAILURE is the value returned upon failure of MORECORE as well as mmap. Since it cannot be an otherwise valid memory address, and must reflect values of standard sys calls, you probably ought not try to redefine it. */ #ifndef MORECORE_FAILURE #define MORECORE_FAILURE (-1) #endif /* If MORECORE_CONTIGUOUS is true, take advantage of fact that consecutive calls to MORECORE with positive arguments always return contiguous increasing addresses. This is true of unix sbrk. Even if not defined, when regions happen to be contiguous, malloc will permit allocations spanning regions obtained from different calls. But defining this when applicable enables some stronger consistency checks and space efficiencies. */ #ifndef MORECORE_CONTIGUOUS #define MORECORE_CONTIGUOUS 1 #endif /* Define MORECORE_CANNOT_TRIM if your version of MORECORE cannot release space back to the system when given negative arguments. This is generally necessary only if you are using a hand-crafted MORECORE function that cannot handle negative arguments. */ /* #define MORECORE_CANNOT_TRIM */ /* Define HAVE_MMAP as true to optionally make malloc() use mmap() to allocate very large blocks. These will be returned to the operating system immediately after a free(). Also, if mmap is available, it is used as a backup strategy in cases where MORECORE fails to provide space from system. This malloc is best tuned to work with mmap for large requests. If you do not have mmap, operations involving very large chunks (1MB or so) may be slower than you'd like. */ #ifndef HAVE_MMAP /* #define HAVE_MMAP 1 */ #endif #if HAVE_MMAP /* Standard unix mmap using /dev/zero clears memory so calloc doesn't need to. */ #ifndef MMAP_CLEARS #define MMAP_CLEARS 1 #endif #else /* no mmap */ #ifndef MMAP_CLEARS #define MMAP_CLEARS 0 #endif #endif /* MMAP_AS_MORECORE_SIZE is the minimum mmap size argument to use if sbrk fails, and mmap is used as a backup (which is done only if HAVE_MMAP). The value must be a multiple of page size. This backup strategy generally applies only when systems have "holes" in address space, so sbrk cannot perform contiguous expansion, but there is still space available on system. On systems for which this is known to be useful (i.e. most linux kernels), this occurs only when programs allocate huge amounts of memory. Between this, and the fact that mmap regions tend to be limited, the size should be large, to avoid too many mmap calls and thus avoid running out of kernel resources. */ #ifndef MMAP_AS_MORECORE_SIZE #define MMAP_AS_MORECORE_SIZE (1024 * 1024) #endif /* Define HAVE_MREMAP to make realloc() use mremap() to re-allocate large blocks. This is currently only possible on Linux with kernel versions newer than 1.3.77. */ #ifndef HAVE_MREMAP #ifdef linux #define HAVE_MREMAP 1 #else #define HAVE_MREMAP 0 #endif #endif /* HAVE_MMAP */ /* The system page size. To the extent possible, this malloc manages memory from the system in page-size units. Note that this value is cached during initialization into a field of malloc_state. So even if malloc_getpagesize is a function, it is only called once. The following mechanics for getpagesize were adapted from bsd/gnu getpagesize.h. If none of the system-probes here apply, a value of 4096 is used, which should be OK: If they don't apply, then using the actual value probably doesn't impact performance. */ #ifndef malloc_getpagesize #ifndef LACKS_UNISTD_H # include #endif # ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */ # ifndef _SC_PAGE_SIZE # define _SC_PAGE_SIZE _SC_PAGESIZE # endif # endif # ifdef _SC_PAGE_SIZE # define malloc_getpagesize sysconf(_SC_PAGE_SIZE) # else # if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE) extern size_t getpagesize(); # define malloc_getpagesize getpagesize() # else # ifdef WIN32 /* use supplied emulation of getpagesize */ # define malloc_getpagesize getpagesize() # else # ifndef LACKS_SYS_PARAM_H # include # endif # ifdef EXEC_PAGESIZE # define malloc_getpagesize EXEC_PAGESIZE # else # ifdef NBPG # ifndef CLSIZE # define malloc_getpagesize NBPG # else # define malloc_getpagesize (NBPG * CLSIZE) # endif # else # ifdef NBPC # define malloc_getpagesize NBPC # else # ifdef PAGESIZE # define malloc_getpagesize PAGESIZE # else /* just guess */ # define malloc_getpagesize (4096) # endif # endif # endif # endif # endif # endif # endif #endif /* This version of malloc supports the standard SVID/XPG mallinfo routine that returns a struct containing usage properties and statistics. It should work on any SVID/XPG compliant system that has a /usr/include/malloc.h defining struct mallinfo. (If you'd like to install such a thing yourself, cut out the preliminary declarations as described above and below and save them in a malloc.h file. But there's no compelling reason to bother to do this.) The main declaration needed is the mallinfo struct that is returned (by-copy) by mallinfo(). The SVID/XPG malloinfo struct contains a bunch of fields that are not even meaningful in this version of malloc. These fields are are instead filled by mallinfo() with other numbers that might be of interest. HAVE_USR_INCLUDE_MALLOC_H should be set if you have a /usr/include/malloc.h file that includes a declaration of struct mallinfo. If so, it is included; else an SVID2/XPG2 compliant version is declared below. These must be precisely the same for mallinfo() to work. The original SVID version of this struct, defined on most systems with mallinfo, declares all fields as ints. But some others define as unsigned long. If your system defines the fields using a type of different width than listed here, you must #include your system version and #define HAVE_USR_INCLUDE_MALLOC_H. */ /* #define HAVE_USR_INCLUDE_MALLOC_H */ #ifdef HAVE_USR_INCLUDE_MALLOC_H #include "/usr/include/malloc.h" #else /* SVID2/XPG mallinfo structure */ struct mallinfo { int arena; /* non-mmapped space allocated from system */ int ordblks; /* number of free chunks */ int smblks; /* number of fastbin blocks */ int hblks; /* number of mmapped regions */ int hblkhd; /* space in mmapped regions */ int usmblks; /* maximum total allocated space */ int fsmblks; /* space available in freed fastbin blocks */ int uordblks; /* total allocated space */ int fordblks; /* total free space */ int keepcost; /* top-most, releasable (via malloc_trim) space */ }; /* SVID/XPG defines four standard parameter numbers for mallopt, normally defined in malloc.h. Only one of these (M_MXFAST) is used in this malloc. The others (M_NLBLKS, M_GRAIN, M_KEEP) don't apply, so setting them has no effect. But this malloc also supports other options in mallopt described below. */ #endif /* ---------- description of public routines ------------ */ /* malloc(size_t n) Returns a pointer to a newly allocated chunk of at least n bytes, or null if no space is available. Additionally, on failure, errno is set to ENOMEM on ANSI C systems. If n is zero, malloc returns a minumum-sized chunk. (The minimum size is 16 bytes on most 32bit systems, and 24 or 32 bytes on 64bit systems.) On most systems, size_t is an unsigned type, so calls with negative arguments are interpreted as requests for huge amounts of space, which will often fail. The maximum supported value of n differs across systems, but is in all cases less than the maximum representable value of a size_t. */ #if __STD_C Void_t* public_mALLOc(size_t); #else Void_t* public_mALLOc(); #endif /* free(Void_t* p) Releases the chunk of memory pointed to by p, that had been previously allocated using malloc or a related routine such as realloc. It has no effect if p is null. It can have arbitrary (i.e., bad!) effects if p has already been freed. Unless disabled (using mallopt), freeing very large spaces will when possible, automatically trigger operations that give back unused memory to the system, thus reducing program footprint. */ #if __STD_C void public_fREe(Void_t*); #else void public_fREe(); #endif /* calloc(size_t n_elements, size_t element_size); Returns a pointer to n_elements * element_size bytes, with all locations set to zero. */ #if __STD_C Void_t* public_cALLOc(size_t, size_t); #else Void_t* public_cALLOc(); #endif /* realloc(Void_t* p, size_t n) Returns a pointer to a chunk of size n that contains the same data as does chunk p up to the minimum of (n, p's size) bytes, or null if no space is available. The returned pointer may or may not be the same as p. The algorithm prefers extending p when possible, otherwise it employs the equivalent of a malloc-copy-free sequence. If p is null, realloc is equivalent to malloc. If space is not available, realloc returns null, errno is set (if on ANSI) and p is NOT freed. if n is for fewer bytes than already held by p, the newly unused space is lopped off and freed if possible. Unless the #define REALLOC_ZERO_BYTES_FREES is set, realloc with a size argument of zero (re)allocates a minimum-sized chunk. Large chunks that were internally obtained via mmap will always be reallocated using malloc-copy-free sequences unless the system supports MREMAP (currently only linux). The old unix realloc convention of allowing the last-free'd chunk to be used as an argument to realloc is not supported. */ #if __STD_C Void_t* public_rEALLOc(Void_t*, size_t); #else Void_t* public_rEALLOc(); #endif /* memalign(size_t alignment, size_t n); Returns a pointer to a newly allocated chunk of n bytes, aligned in accord with the alignment argument. The alignment argument should be a power of two. If the argument is not a power of two, the nearest greater power is used. 8-byte alignment is guaranteed by normal malloc calls, so don't bother calling memalign with an argument of 8 or less. Overreliance on memalign is a sure way to fragment space. */ #if __STD_C Void_t* public_mEMALIGn(size_t, size_t); #else Void_t* public_mEMALIGn(); #endif /* valloc(size_t n); Equivalent to memalign(pagesize, n), where pagesize is the page size of the system. If the pagesize is unknown, 4096 is used. */ #if __STD_C Void_t* public_vALLOc(size_t); #else Void_t* public_vALLOc(); #endif /* mallopt(int parameter_number, int parameter_value) Sets tunable parameters The format is to provide a (parameter-number, parameter-value) pair. mallopt then sets the corresponding parameter to the argument value if it can (i.e., so long as the value is meaningful), and returns 1 if successful else 0. SVID/XPG/ANSI defines four standard param numbers for mallopt, normally defined in malloc.h. Only one of these (M_MXFAST) is used in this malloc. The others (M_NLBLKS, M_GRAIN, M_KEEP) don't apply, so setting them has no effect. But this malloc also supports four other options in mallopt. See below for details. Briefly, supported parameters are as follows (listed defaults are for "typical" configurations). Symbol param # default allowed param values M_MXFAST 1 64 0-80 (0 disables fastbins) M_TRIM_THRESHOLD -1 256*1024 any (-1U disables trimming) M_TOP_PAD -2 0 any M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support) M_MMAP_MAX -4 65536 any (0 disables use of mmap) */ #if __STD_C int public_mALLOPt(int, int); #else int public_mALLOPt(); #endif /* mallinfo() Returns (by copy) a struct containing various summary statistics: arena: current total non-mmapped bytes allocated from system ordblks: the number of free chunks smblks: the number of fastbin blocks (i.e., small chunks that have been freed but not use resused or consolidated) hblks: current number of mmapped regions hblkhd: total bytes held in mmapped regions usmblks: the maximum total allocated space. This will be greater than current total if trimming has occurred. fsmblks: total bytes held in fastbin blocks uordblks: current total allocated space (normal or mmapped) fordblks: total free space keepcost: the maximum number of bytes that could ideally be released back to system via malloc_trim. ("ideally" means that it ignores page restrictions etc.) Because these fields are ints, but internal bookkeeping may be kept as longs, the reported values may wrap around zero and thus be inaccurate. */ #if __STD_C struct mallinfo public_mALLINFo(void); #else struct mallinfo public_mALLINFo(); #endif /* independent_calloc(size_t n_elements, size_t element_size, Void_t* chunks[]); independent_calloc is similar to calloc, but instead of returning a single cleared space, it returns an array of pointers to n_elements independent elements that can hold contents of size elem_size, each of which starts out cleared, and can be independently freed, realloc'ed etc. The elements are guaranteed to be adjacently allocated (this is not guaranteed to occur with multiple callocs or mallocs), which may also improve cache locality in some applications. The "chunks" argument is optional (i.e., may be null, which is probably the most typical usage). If it is null, the returned array is itself dynamically allocated and should also be freed when it is no longer needed. Otherwise, the chunks array must be of at least n_elements in length. It is filled in with the pointers to the chunks. In either case, independent_calloc returns this pointer array, or null if the allocation failed. If n_elements is zero and "chunks" is null, it returns a chunk representing an array with zero elements (which should be freed if not wanted). Each element must be individually freed when it is no longer needed. If you'd like to instead be able to free all at once, you should instead use regular calloc and assign pointers into this space to represent elements. (In this case though, you cannot independently free elements.) independent_calloc simplifies and speeds up implementations of many kinds of pools. It may also be useful when constructing large data structures that initially have a fixed number of fixed-sized nodes, but the number is not known at compile time, and some of the nodes may later need to be freed. For example: struct Node { int item; struct Node* next; }; struct Node* build_list() { struct Node** pool; int n = read_number_of_nodes_needed(); if (n <= 0) return 0; pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); if (pool == 0) die(); // organize into a linked list... struct Node* first = pool[0]; for (i = 0; i < n-1; ++i) pool[i]->next = pool[i+1]; free(pool); // Can now free the array (or not, if it is needed later) return first; } */ #if __STD_C Void_t** public_iCALLOc(size_t, size_t, Void_t**); #else Void_t** public_iCALLOc(); #endif /* independent_comalloc(size_t n_elements, size_t sizes[], Void_t* chunks[]); independent_comalloc allocates, all at once, a set of n_elements chunks with sizes indicated in the "sizes" array. It returns an array of pointers to these elements, each of which can be independently freed, realloc'ed etc. The elements are guaranteed to be adjacently allocated (this is not guaranteed to occur with multiple callocs or mallocs), which may also improve cache locality in some applications. The "chunks" argument is optional (i.e., may be null). If it is null the returned array is itself dynamically allocated and should also be freed when it is no longer needed. Otherwise, the chunks array must be of at least n_elements in length. It is filled in with the pointers to the chunks. In either case, independent_comalloc returns this pointer array, or null if the allocation failed. If n_elements is zero and chunks is null, it returns a chunk representing an array with zero elements (which should be freed if not wanted). Each element must be individually freed when it is no longer needed. If you'd like to instead be able to free all at once, you should instead use a single regular malloc, and assign pointers at particular offsets in the aggregate space. (In this case though, you cannot independently free elements.) independent_comallac differs from independent_calloc in that each element may have a different size, and also that it does not automatically clear elements. independent_comalloc can be used to speed up allocation in cases where several structs or objects must always be allocated at the same time. For example: struct Head { ... } struct Foot { ... } void send_message(char* msg) { int msglen = strlen(msg); size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; void* chunks[3]; if (independent_comalloc(3, sizes, chunks) == 0) die(); struct Head* head = (struct Head*)(chunks[0]); char* body = (char*)(chunks[1]); struct Foot* foot = (struct Foot*)(chunks[2]); // ... } In general though, independent_comalloc is worth using only for larger values of n_elements. For small values, you probably won't detect enough difference from series of malloc calls to bother. Overuse of independent_comalloc can increase overall memory usage, since it cannot reuse existing noncontiguous small chunks that might be available for some of the elements. */ #if __STD_C Void_t** public_iCOMALLOc(size_t, size_t*, Void_t**); #else Void_t** public_iCOMALLOc(); #endif /* pvalloc(size_t n); Equivalent to valloc(minimum-page-that-holds(n)), that is, round up n to nearest pagesize. */ #if __STD_C Void_t* public_pVALLOc(size_t); #else Void_t* public_pVALLOc(); #endif /* cfree(Void_t* p); Equivalent to free(p). cfree is needed/defined on some systems that pair it with calloc, for odd historical reasons (such as: cfree is used in example code in the first edition of K&R). */ #if __STD_C void public_cFREe(Void_t*); #else void public_cFREe(); #endif /* malloc_trim(size_t pad); If possible, gives memory back to the system (via negative arguments to sbrk) if there is unused memory at the `high' end of the malloc pool. You can call this after freeing large blocks of memory to potentially reduce the system-level memory requirements of a program. However, it cannot guarantee to reduce memory. Under some allocation patterns, some large free blocks of memory will be locked between two used chunks, so they cannot be given back to the system. The `pad' argument to malloc_trim represents the amount of free trailing space to leave untrimmed. If this argument is zero, only the minimum amount of memory to maintain internal data structures will be left (one page or less). Non-zero arguments can be supplied to maintain enough trailing space to service future expected allocations without having to re-obtain memory from the system. Malloc_trim returns 1 if it actually released any memory, else 0. On systems that do not support "negative sbrks", it will always rreturn 0. */ #if __STD_C int public_mTRIm(size_t); #else int public_mTRIm(); #endif /* malloc_usable_size(Void_t* p); Returns the number of bytes you can actually use in an allocated chunk, which may be more than you requested (although often not) due to alignment and minimum size constraints. You can use this many bytes without worrying about overwriting other allocated objects. This is not a particularly great programming practice. malloc_usable_size can be more useful in debugging and assertions, for example: p = malloc(n); assert(malloc_usable_size(p) >= 256); */ #if __STD_C size_t public_mUSABLe(Void_t*); #else size_t public_mUSABLe(); #endif /* malloc_stats(); Prints on stderr the amount of space obtained from the system (both via sbrk and mmap), the maximum amount (which may be more than current if malloc_trim and/or munmap got called), and the current number of bytes allocated via malloc (or realloc, etc) but not yet freed. Note that this is the number of bytes allocated, not the number requested. It will be larger than the number requested because of alignment and bookkeeping overhead. Because it includes alignment wastage as being in use, this figure may be greater than zero even when no user-level chunks are allocated. The reported current and maximum system memory can be inaccurate if a program makes other calls to system memory allocation functions (normally sbrk) outside of malloc. malloc_stats prints only the most commonly interesting statistics. More information can be obtained by calling mallinfo. */ #if __STD_C void public_mSTATs(void); #else void public_mSTATs(); #endif /* mallopt tuning options */ /* M_MXFAST is the maximum request size used for "fastbins", special bins that hold returned chunks without consolidating their spaces. This enables future requests for chunks of the same size to be handled very quickly, but can increase fragmentation, and thus increase the overall memory footprint of a program. This malloc manages fastbins very conservatively yet still efficiently, so fragmentation is rarely a problem for values less than or equal to the default. The maximum supported value of MXFAST is 80. You wouldn't want it any higher than this anyway. Fastbins are designed especially for use with many small structs, objects or strings -- the default handles structs/objects/arrays with sizes up to 16 4byte fields, or small strings representing words, tokens, etc. Using fastbins for larger objects normally worsens fragmentation without improving speed. M_MXFAST is set in REQUEST size units. It is internally used in chunksize units, which adds padding and alignment. You can reduce M_MXFAST to 0 to disable all use of fastbins. This causes the malloc algorithm to be a closer approximation of fifo-best-fit in all cases, not just for larger requests, but will generally cause it to be slower. */ /* M_MXFAST is a standard SVID/XPG tuning option, usually listed in malloc.h */ #ifndef M_MXFAST #define M_MXFAST 1 #endif #ifndef DEFAULT_MXFAST #define DEFAULT_MXFAST 64 #endif /* M_TRIM_THRESHOLD is the maximum amount of unused top-most memory to keep before releasing via malloc_trim in free(). Automatic trimming is mainly useful in long-lived programs. Because trimming via sbrk can be slow on some systems, and can sometimes be wasteful (in cases where programs immediately afterward allocate more large chunks) the value should be high enough so that your overall system performance would improve by releasing this much memory. The trim threshold and the mmap control parameters (see below) can be traded off with one another. Trimming and mmapping are two different ways of releasing unused memory back to the system. Between these two, it is often possible to keep system-level demands of a long-lived program down to a bare minimum. For example, in one test suite of sessions measuring the XF86 X server on Linux, using a trim threshold of 128K and a mmap threshold of 192K led to near-minimal long term resource consumption. If you are using this malloc in a long-lived program, it should pay to experiment with these values. As a rough guide, you might set to a value close to the average size of a process (program) running on your system. Releasing this much memory would allow such a process to run in memory. Generally, it's worth it to tune for trimming rather tham memory mapping when a program undergoes phases where several large chunks are allocated and released in ways that can reuse each other's storage, perhaps mixed with phases where there are no such chunks at all. And in well-behaved long-lived programs, controlling release of large blocks via trimming versus mapping is usually faster. However, in most programs, these parameters serve mainly as protection against the system-level effects of carrying around massive amounts of unneeded memory. Since frequent calls to sbrk, mmap, and munmap otherwise degrade performance, the default parameters are set to relatively high values that serve only as safeguards. The trim value must be greater than page size to have any useful effect. To disable trimming completely, you can set to (unsigned long)(-1) Trim settings interact with fastbin (MXFAST) settings: Unless TRIM_FASTBINS is defined, automatic trimming never takes place upon freeing a chunk with size less than or equal to MXFAST. Trimming is instead delayed until subsequent freeing of larger chunks. However, you can still force an attempted trim by calling malloc_trim. Also, trimming is not generally possible in cases where the main arena is obtained via mmap. Note that the trick some people use of mallocing a huge space and then freeing it at program startup, in an attempt to reserve system memory, doesn't have the intended effect under automatic trimming, since that memory will immediately be returned to the system. */ #define M_TRIM_THRESHOLD -1 #ifndef DEFAULT_TRIM_THRESHOLD #define DEFAULT_TRIM_THRESHOLD (256 * 1024) #endif /* M_TOP_PAD is the amount of extra `padding' space to allocate or retain whenever sbrk is called. It is used in two ways internally: * When sbrk is called to extend the top of the arena to satisfy a new malloc request, this much padding is added to the sbrk request. * When malloc_trim is called automatically from free(), it is used as the `pad' argument. In both cases, the actual amount of padding is rounded so that the end of the arena is always a system page boundary. The main reason for using padding is to avoid calling sbrk so often. Having even a small pad greatly reduces the likelihood that nearly every malloc request during program start-up (or after trimming) will invoke sbrk, which needlessly wastes time. Automatic rounding-up to page-size units is normally sufficient to avoid measurable overhead, so the default is 0. However, in systems where sbrk is relatively slow, it can pay to increase this value, at the expense of carrying around more memory than the program needs. */ #define M_TOP_PAD -2 #ifndef DEFAULT_TOP_PAD #define DEFAULT_TOP_PAD (0) #endif /* M_MMAP_THRESHOLD is the request size threshold for using mmap() to service a request. Requests of at least this size that cannot be allocated using already-existing space will be serviced via mmap. (If enough normal freed space already exists it is used instead.) Using mmap segregates relatively large chunks of memory so that they can be individually obtained and released from the host system. A request serviced through mmap is never reused by any other request (at least not directly; the system may just so happen to remap successive requests to the same locations). Segregating space in this way has the benefits that: 1. Mmapped space can ALWAYS be individually released back to the system, which helps keep the system level memory demands of a long-lived program low. 2. Mapped memory can never become `locked' between other chunks, as can happen with normally allocated chunks, which means that even trimming via malloc_trim would not release them. 3. On some systems with "holes" in address spaces, mmap can obtain memory that sbrk cannot. However, it has the disadvantages that: 1. The space cannot be reclaimed, consolidated, and then used to service later requests, as happens with normal chunks. 2. It can lead to more wastage because of mmap page alignment requirements 3. It causes malloc performance to be more dependent on host system memory management support routines which may vary in implementation quality and may impose arbitrary limitations. Generally, servicing a request via normal malloc steps is faster than going through a system's mmap. The advantages of mmap nearly always outweigh disadvantages for "large" chunks, but the value of "large" varies across systems. The default is an empirically derived value that works well in most systems. */ #define M_MMAP_THRESHOLD -3 #ifndef DEFAULT_MMAP_THRESHOLD #define DEFAULT_MMAP_THRESHOLD (256 * 1024) #endif /* M_MMAP_MAX is the maximum number of requests to simultaneously service using mmap. This parameter exists because . Some systems have a limited number of internal tables for use by mmap, and using more than a few of them may degrade performance. The default is set to a value that serves only as a safeguard. Setting to 0 disables use of mmap for servicing large requests. If HAVE_MMAP is not set, the default value is 0, and attempts to set it to non-zero values in mallopt will fail. */ #define M_MMAP_MAX -4 #ifndef DEFAULT_MMAP_MAX #if HAVE_MMAP #define DEFAULT_MMAP_MAX (65536) #else #define DEFAULT_MMAP_MAX (0) #endif #endif #ifdef __cplusplus }; /* end of extern "C" */ #endif /* ======================================================================== To make a fully customizable malloc.h header file, cut everything above this line, put into file malloc.h, edit to suit, and #include it on the next line, as well as in programs that use this malloc. ======================================================================== */ /* #include "malloc.h" */ /* --------------------- public wrappers ---------------------- */ #ifdef USE_PUBLIC_MALLOC_WRAPPERS /* Declare all routines as internal */ #if __STD_C static Void_t* mALLOc(size_t); static void fREe(Void_t*); static Void_t* rEALLOc(Void_t*, size_t); static Void_t* mEMALIGn(size_t, size_t); static Void_t* vALLOc(size_t); static Void_t* pVALLOc(size_t); static Void_t* cALLOc(size_t, size_t); static Void_t** iCALLOc(size_t, size_t, Void_t**); static Void_t** iCOMALLOc(size_t, size_t*, Void_t**); static void cFREe(Void_t*); static int mTRIm(size_t); static size_t mUSABLe(Void_t*); static void mSTATs(); static int mALLOPt(int, int); static struct mallinfo mALLINFo(void); #else static Void_t* mALLOc(); static void fREe(); static Void_t* rEALLOc(); static Void_t* mEMALIGn(); static Void_t* vALLOc(); static Void_t* pVALLOc(); static Void_t* cALLOc(); static Void_t** iCALLOc(); static Void_t** iCOMALLOc(); static void cFREe(); static int mTRIm(); static size_t mUSABLe(); static void mSTATs(); static int mALLOPt(); static struct mallinfo mALLINFo(); #endif /* MALLOC_PREACTION and MALLOC_POSTACTION should be defined to return 0 on success, and nonzero on failure. The return value of MALLOC_POSTACTION is currently ignored in wrapper functions since there is no reasonable default action to take on failure. */ #ifdef USE_MALLOC_LOCK #ifdef WIN32 static int mALLOC_MUTEx; #define MALLOC_PREACTION slwait(&mALLOC_MUTEx) #define MALLOC_POSTACTION slrelease(&mALLOC_MUTEx) #else #include static pthread_mutex_t mALLOC_MUTEx = PTHREAD_MUTEX_INITIALIZER; #define MALLOC_PREACTION pthread_mutex_lock(&mALLOC_MUTEx) #define MALLOC_POSTACTION pthread_mutex_unlock(&mALLOC_MUTEx) #endif /* USE_MALLOC_LOCK */ #else /* Substitute anything you like for these */ #define MALLOC_PREACTION (0) #define MALLOC_POSTACTION (0) #endif Void_t* public_mALLOc(size_t bytes) { Void_t* m; if (MALLOC_PREACTION != 0) { return 0; } m = mALLOc(bytes); if (MALLOC_POSTACTION != 0) { } return m; } void public_fREe(Void_t* m) { if (MALLOC_PREACTION != 0) { return; } fREe(m); if (MALLOC_POSTACTION != 0) { } } Void_t* public_rEALLOc(Void_t* m, size_t bytes) { if (MALLOC_PREACTION != 0) { return 0; } m = rEALLOc(m, bytes); if (MALLOC_POSTACTION != 0) { } return m; } Void_t* public_mEMALIGn(size_t alignment, size_t bytes) { Void_t* m; if (MALLOC_PREACTION != 0) { return 0; } m = mEMALIGn(alignment, bytes); if (MALLOC_POSTACTION != 0) { } return m; } Void_t* public_vALLOc(size_t bytes) { Void_t* m; if (MALLOC_PREACTION != 0) { return 0; } m = vALLOc(bytes); if (MALLOC_POSTACTION != 0) { } return m; } Void_t* public_pVALLOc(size_t bytes) { Void_t* m; if (MALLOC_PREACTION != 0) { return 0; } m = pVALLOc(bytes); if (MALLOC_POSTACTION != 0) { } return m; } Void_t* public_cALLOc(size_t n, size_t elem_size) { Void_t* m; if (MALLOC_PREACTION != 0) { return 0; } m = cALLOc(n, elem_size); if (MALLOC_POSTACTION != 0) { } return m; } Void_t** public_iCALLOc(size_t n, size_t elem_size, Void_t** chunks) { Void_t** m; if (MALLOC_PREACTION != 0) { return 0; } m = iCALLOc(n, elem_size, chunks); if (MALLOC_POSTACTION != 0) { } return m; } Void_t** public_iCOMALLOc(size_t n, size_t sizes[], Void_t** chunks) { Void_t** m; if (MALLOC_PREACTION != 0) { return 0; } m = iCOMALLOc(n, sizes, chunks); if (MALLOC_POSTACTION != 0) { } return m; } void public_cFREe(Void_t* m) { if (MALLOC_PREACTION != 0) { return; } cFREe(m); if (MALLOC_POSTACTION != 0) { } } int public_mTRIm(size_t s) { int result; if (MALLOC_PREACTION != 0) { return 0; } result = mTRIm(s); if (MALLOC_POSTACTION != 0) { } return result; } size_t public_mUSABLe(Void_t* m) { size_t result; if (MALLOC_PREACTION != 0) { return 0; } result = mUSABLe(m); if (MALLOC_POSTACTION != 0) { } return result; } void public_mSTATs() { if (MALLOC_PREACTION != 0) { return; } mSTATs(); if (MALLOC_POSTACTION != 0) { } } struct mallinfo public_mALLINFo() { struct mallinfo m; if (MALLOC_PREACTION != 0) { struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; return nm; } m = mALLINFo(); if (MALLOC_POSTACTION != 0) { } return m; } int public_mALLOPt(int p, int v) { int result; if (MALLOC_PREACTION != 0) { return 0; } result = mALLOPt(p, v); if (MALLOC_POSTACTION != 0) { } return result; } #endif /* ------------- Optional versions of memcopy ---------------- */ #if USE_MEMCPY /* Note: memcpy is ONLY invoked with non-overlapping regions, so the (usually slower) memmove is not needed. */ #define MALLOC_COPY(dest, src, nbytes) memcpy(dest, src, nbytes) #define MALLOC_ZERO(dest, nbytes) memset(dest, 0, nbytes) #else /* !USE_MEMCPY */ /* Use Duff's device for good zeroing/copying performance. */ #define MALLOC_ZERO(charp, nbytes) \ do { \ INTERNAL_SIZE_T* mzp = (INTERNAL_SIZE_T*)(charp); \ CHUNK_SIZE_T mctmp = (nbytes)/sizeof(INTERNAL_SIZE_T); \ long mcn; \ if (mctmp < 8) mcn = 0; else { mcn = (mctmp-1)/8; mctmp %= 8; } \ switch (mctmp) { \ case 0: for(;;) { *mzp++ = 0; \ case 7: *mzp++ = 0; \ case 6: *mzp++ = 0; \ case 5: *mzp++ = 0; \ case 4: *mzp++ = 0; \ case 3: *mzp++ = 0; \ case 2: *mzp++ = 0; \ case 1: *mzp++ = 0; if(mcn <= 0) break; mcn--; } \ } \ } while(0) #define MALLOC_COPY(dest,src,nbytes) \ do { \ INTERNAL_SIZE_T* mcsrc = (INTERNAL_SIZE_T*) src; \ INTERNAL_SIZE_T* mcdst = (INTERNAL_SIZE_T*) dest; \ CHUNK_SIZE_T mctmp = (nbytes)/sizeof(INTERNAL_SIZE_T); \ long mcn; \ if (mctmp < 8) mcn = 0; else { mcn = (mctmp-1)/8; mctmp %= 8; } \ switch (mctmp) { \ case 0: for(;;) { *mcdst++ = *mcsrc++; \ case 7: *mcdst++ = *mcsrc++; \ case 6: *mcdst++ = *mcsrc++; \ case 5: *mcdst++ = *mcsrc++; \ case 4: *mcdst++ = *mcsrc++; \ case 3: *mcdst++ = *mcsrc++; \ case 2: *mcdst++ = *mcsrc++; \ case 1: *mcdst++ = *mcsrc++; if(mcn <= 0) break; mcn--; } \ } \ } while(0) #endif /* ------------------ MMAP support ------------------ */ #if HAVE_MMAP #ifndef LACKS_FCNTL_H #include #endif #ifndef LACKS_SYS_MMAN_H #include #endif #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) #define MAP_ANONYMOUS MAP_ANON #endif /* Nearly all versions of mmap support MAP_ANONYMOUS, so the following is unlikely to be needed, but is supplied just in case. */ #ifndef MAP_ANONYMOUS static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */ #define MMAP(addr, size, prot, flags) ((dev_zero_fd < 0) ? \ (dev_zero_fd = open("/dev/zero", O_RDWR), \ mmap((addr), (size), (prot), (flags), dev_zero_fd, 0)) : \ mmap((addr), (size), (prot), (flags), dev_zero_fd, 0)) #else #define MMAP(addr, size, prot, flags) \ (mmap((addr), (size), (prot), (flags)|MAP_ANONYMOUS, -1, 0)) #endif #endif /* HAVE_MMAP */ /* ----------------------- Chunk representations ----------------------- */ /* This struct declaration is misleading (but accurate and necessary). It declares a "view" into memory allowing access to necessary fields at known offsets from a given base. See explanation below. */ struct malloc_chunk { INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; }; typedef struct malloc_chunk* mchunkptr; /* malloc_chunk details: (The following includes lightly edited explanations by Colin Plumb.) Chunks of memory are maintained using a `boundary tag' method as described in e.g., Knuth or Standish. (See the paper by Paul Wilson ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such techniques.) Sizes of free chunks are stored both in the front of each chunk and at the end. This makes consolidating fragmented chunks into bigger chunks very fast. The size fields also hold bits representing whether chunks are free or in use. An allocated chunk looks like this: chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of previous chunk, if allocated | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of chunk, in bytes |P| mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | User data starts here... . . . . (malloc_usable_space() bytes) . . | nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of chunk | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Where "chunk" is the front of the chunk for the purpose of most of the malloc code, but "mem" is the pointer that is returned to the user. "Nextchunk" is the beginning of the next contiguous chunk. Chunks always begin on even word boundries, so the mem portion (which is returned to the user) is also on an even word boundary, and thus at least double-word aligned. Free chunks are stored in circular doubly-linked lists, and look like this: chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of previous chunk | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ `head:' | Size of chunk, in bytes |P| mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Forward pointer to next chunk in list | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Back pointer to previous chunk in list | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Unused space (may be 0 bytes long) . . . . | nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ `foot:' | Size of chunk, in bytes | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ The P (PREV_INUSE) bit, stored in the unused low-order bit of the chunk size (which is always a multiple of two words), is an in-use bit for the *previous* chunk. If that bit is *clear*, then the word before the current chunk size contains the previous chunk size, and can be used to find the front of the previous chunk. The very first chunk allocated always has this bit set, preventing access to non-existent (or non-owned) memory. If prev_inuse is set for any given chunk, then you CANNOT determine the size of the previous chunk, and might even get a memory addressing fault when trying to do so. Note that the `foot' of the current chunk is actually represented as the prev_size of the NEXT chunk. This makes it easier to deal with alignments etc but can be very confusing when trying to extend or adapt this code. The two exceptions to all this are 1. The special chunk `top' doesn't bother using the trailing size field since there is no next contiguous chunk that would have to index off it. After initialization, `top' is forced to always exist. If it would become less than MINSIZE bytes long, it is replenished. 2. Chunks allocated via mmap, which have the second-lowest-order bit (IS_MMAPPED) set in their size fields. Because they are allocated one-by-one, each must contain its own trailing size field. */ /* ---------- Size and alignment checks and conversions ---------- */ /* conversion from malloc headers to user pointers, and back */ #define chunk2mem(p) ((Void_t*)((char*)(p) + 2*SIZE_SZ)) #define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ)) /* The smallest possible chunk */ #define MIN_CHUNK_SIZE (sizeof(struct malloc_chunk)) /* The smallest size we can malloc is an aligned minimal chunk */ #define MINSIZE \ (CHUNK_SIZE_T)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)) /* Check if m has acceptable alignment */ #define aligned_OK(m) (((PTR_UINT)((m)) & (MALLOC_ALIGN_MASK)) == 0) /* Check if a request is so large that it would wrap around zero when padded and aligned. To simplify some other code, the bound is made low enough so that adding MINSIZE will also not wrap around sero. */ #define REQUEST_OUT_OF_RANGE(req) \ ((CHUNK_SIZE_T)(req) >= \ (CHUNK_SIZE_T)(INTERNAL_SIZE_T)(-2 * MINSIZE)) /* pad request bytes into a usable size -- internal version */ #define request2size(req) \ (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \ MINSIZE : \ ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK) /* Same, except also perform argument check */ #define checked_request2size(req, sz) \ if (REQUEST_OUT_OF_RANGE(req)) { \ MALLOC_FAILURE_ACTION; \ return 0; \ } \ (sz) = request2size(req); /* --------------- Physical chunk operations --------------- */ /* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */ #define PREV_INUSE 0x1 /* extract inuse bit of previous chunk */ #define prev_inuse(p) ((p)->size & PREV_INUSE) /* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */ #define IS_MMAPPED 0x2 /* check for mmap()'ed chunk */ #define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED) /* Bits to mask off when extracting size Note: IS_MMAPPED is intentionally not masked off from size field in macros for which mmapped chunks should never be seen. This should cause helpful core dumps to occur if it is tried by accident by people extending or adapting this malloc. */ #define SIZE_BITS (PREV_INUSE|IS_MMAPPED) /* Get size, ignoring use bits */ #define chunksize(p) ((p)->size & ~(SIZE_BITS)) /* Ptr to next physical malloc_chunk. */ #define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->size & ~PREV_INUSE) )) /* Ptr to previous physical malloc_chunk */ #define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_size) )) /* Treat space at ptr + offset as a chunk */ #define chunk_at_offset(p, s) ((mchunkptr)(((char*)(p)) + (s))) /* extract p's inuse bit */ #define inuse(p)\ ((((mchunkptr)(((char*)(p))+((p)->size & ~PREV_INUSE)))->size) & PREV_INUSE) /* set/clear chunk as being inuse without otherwise disturbing */ #define set_inuse(p)\ ((mchunkptr)(((char*)(p)) + ((p)->size & ~PREV_INUSE)))->size |= PREV_INUSE #define clear_inuse(p)\ ((mchunkptr)(((char*)(p)) + ((p)->size & ~PREV_INUSE)))->size &= ~(PREV_INUSE) /* check/set/clear inuse bits in known places */ #define inuse_bit_at_offset(p, s)\ (((mchunkptr)(((char*)(p)) + (s)))->size & PREV_INUSE) #define set_inuse_bit_at_offset(p, s)\ (((mchunkptr)(((char*)(p)) + (s)))->size |= PREV_INUSE) #define clear_inuse_bit_at_offset(p, s)\ (((mchunkptr)(((char*)(p)) + (s)))->size &= ~(PREV_INUSE)) /* Set size at head, without disturbing its use bit */ #define set_head_size(p, s) ((p)->size = (((p)->size & PREV_INUSE) | (s))) /* Set size/use field */ #define set_head(p, s) ((p)->size = (s)) /* Set size at footer (only when chunk is not in use) */ #define set_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_size = (s)) /* -------------------- Internal data structures -------------------- All internal state is held in an instance of malloc_state defined below. There are no other static variables, except in two optional cases: * If USE_MALLOC_LOCK is defined, the mALLOC_MUTEx declared above. * If HAVE_MMAP is true, but mmap doesn't support MAP_ANONYMOUS, a dummy file descriptor for mmap. Beware of lots of tricks that minimize the total bookkeeping space requirements. The result is a little over 1K bytes (for 4byte pointers and size_t.) */ /* Bins An array of bin headers for free chunks. Each bin is doubly linked. The bins are approximately proportionally (log) spaced. There are a lot of these bins (128). This may look excessive, but works very well in practice. Most bins hold sizes that are unusual as malloc request sizes, but are more usual for fragments and consolidated sets of chunks, which is what these bins hold, so they can be found quickly. All procedures maintain the invariant that no consolidated chunk physically borders another one, so each chunk in a list is known to be preceeded and followed by either inuse chunks or the ends of memory. Chunks in bins are kept in size order, with ties going to the approximately least recently used chunk. Ordering isn't needed for the small bins, which all contain the same-sized chunks, but facilitates best-fit allocation for larger chunks. These lists are just sequential. Keeping them in order almost never requires enough traversal to warrant using fancier ordered data structures. Chunks of the same size are linked with the most recently freed at the front, and allocations are taken from the back. This results in LRU (FIFO) allocation order, which tends to give each chunk an equal opportunity to be consolidated with adjacent freed chunks, resulting in larger free chunks and less fragmentation. To simplify use in double-linked lists, each bin header acts as a malloc_chunk. This avoids special-casing for headers. But to conserve space and improve locality, we allocate only the fd/bk pointers of bins, and then use repositioning tricks to treat these as the fields of a malloc_chunk*. */ typedef struct malloc_chunk* mbinptr; /* addressing -- note that bin_at(0) does not exist */ #define bin_at(m, i) ((mbinptr)((char*)&((m)->bins[(i)<<1]) - (SIZE_SZ<<1))) /* analog of ++bin */ #define next_bin(b) ((mbinptr)((char*)(b) + (sizeof(mchunkptr)<<1))) /* Reminders about list directionality within bins */ #define first(b) ((b)->fd) #define last(b) ((b)->bk) /* Take a chunk off a bin list */ #define unlink(P, BK, FD) { \ FD = P->fd; \ BK = P->bk; \ FD->bk = BK; \ BK->fd = FD; \ } /* Indexing Bins for sizes < 512 bytes contain chunks of all the same size, spaced 8 bytes apart. Larger bins are approximately logarithmically spaced: 64 bins of size 8 32 bins of size 64 16 bins of size 512 8 bins of size 4096 4 bins of size 32768 2 bins of size 262144 1 bin of size what's left The bins top out around 1MB because we expect to service large requests via mmap. */ #define NBINS 96 #define NSMALLBINS 32 #define SMALLBIN_WIDTH 8 #define MIN_LARGE_SIZE 256 #define in_smallbin_range(sz) \ ((CHUNK_SIZE_T)(sz) < (CHUNK_SIZE_T)MIN_LARGE_SIZE) #define smallbin_index(sz) (((unsigned)(sz)) >> 3) /* Compute index for size. We expect this to be inlined when compiled with optimization, else not, which works out well. */ static int largebin_index(unsigned int sz) { unsigned int x = sz >> SMALLBIN_WIDTH; unsigned int m; /* bit position of highest set bit of m */ if (x >= 0x10000) return NBINS-1; /* On intel, use BSRL instruction to find highest bit */ #if defined(__GNUC__) && defined(i386) __asm__("bsrl %1,%0\n\t" : "=r" (m) : "g" (x)); #else { /* Based on branch-free nlz algorithm in chapter 5 of Henry S. Warren Jr's book "Hacker's Delight". */ unsigned int n = ((x - 0x100) >> 16) & 8; x <<= n; m = ((x - 0x1000) >> 16) & 4; n += m; x <<= m; m = ((x - 0x4000) >> 16) & 2; n += m; x = (x << m) >> 14; m = 13 - n + (x & ~(x>>1)); } #endif /* Use next 2 bits to create finer-granularity bins */ return NSMALLBINS + (m << 2) + ((sz >> (m + 6)) & 3); } #define bin_index(sz) \ ((in_smallbin_range(sz)) ? smallbin_index(sz) : largebin_index(sz)) /* FIRST_SORTED_BIN_SIZE is the chunk size corresponding to the first bin that is maintained in sorted order. This must be the smallest size corresponding to a given bin. Normally, this should be MIN_LARGE_SIZE. But you can weaken best fit guarantees to sometimes speed up malloc by increasing value. Doing this means that malloc may choose a chunk that is non-best-fitting by up to the width of the bin. Some useful cutoff values: 512 - all bins sorted 2560 - leaves bins <= 64 bytes wide unsorted 12288 - leaves bins <= 512 bytes wide unsorted 65536 - leaves bins <= 4096 bytes wide unsorted 262144 - leaves bins <= 32768 bytes wide unsorted -1 - no bins sorted (not recommended!) */ #define FIRST_SORTED_BIN_SIZE MIN_LARGE_SIZE /* #define FIRST_SORTED_BIN_SIZE 65536 */ /* Unsorted chunks All remainders from chunk splits, as well as all returned chunks, are first placed in the "unsorted" bin. They are then placed in regular bins after malloc gives them ONE chance to be used before binning. So, basically, the unsorted_chunks list acts as a queue, with chunks being placed on it in free (and malloc_consolidate), and taken off (to be either used or placed in bins) in malloc. */ /* The otherwise unindexable 1-bin is used to hold unsorted chunks. */ #define unsorted_chunks(M) (bin_at(M, 1)) /* Top The top-most available chunk (i.e., the one bordering the end of available memory) is treated specially. It is never included in any bin, is used only if no other chunk is available, and is released back to the system if it is very large (see M_TRIM_THRESHOLD). Because top initially points to its own bin with initial zero size, thus forcing extension on the first malloc request, we avoid having any special code in malloc to check whether it even exists yet. But we still need to do so when getting memory from system, so we make initial_top treat the bin as a legal but unusable chunk during the interval between initialization and the first call to sYSMALLOc. (This is somewhat delicate, since it relies on the 2 preceding words to be zero during this interval as well.) */ /* Conveniently, the unsorted bin can be used as dummy top on first call */ #define initial_top(M) (unsorted_chunks(M)) /* Binmap To help compensate for the large number of bins, a one-level index structure is used for bin-by-bin searching. `binmap' is a bitvector recording whether bins are definitely empty so they can be skipped over during during traversals. The bits are NOT always cleared as soon as bins are empty, but instead only when they are noticed to be empty during traversal in malloc. */ /* Conservatively use 32 bits per map word, even if on 64bit system */ #define BINMAPSHIFT 5 #define BITSPERMAP (1U << BINMAPSHIFT) #define BINMAPSIZE (NBINS / BITSPERMAP) #define idx2block(i) ((i) >> BINMAPSHIFT) #define idx2bit(i) ((1U << ((i) & ((1U << BINMAPSHIFT)-1)))) #define mark_bin(m,i) ((m)->binmap[idx2block(i)] |= idx2bit(i)) #define unmark_bin(m,i) ((m)->binmap[idx2block(i)] &= ~(idx2bit(i))) #define get_binmap(m,i) ((m)->binmap[idx2block(i)] & idx2bit(i)) /* Fastbins An array of lists holding recently freed small chunks. Fastbins are not doubly linked. It is faster to single-link them, and since chunks are never removed from the middles of these lists, double linking is not necessary. Also, unlike regular bins, they are not even processed in FIFO order (they use faster LIFO) since ordering doesn't much matter in the transient contexts in which fastbins are normally used. Chunks in fastbins keep their inuse bit set, so they cannot be consolidated with other free chunks. malloc_consolidate releases all chunks in fastbins and consolidates them with other free chunks. */ typedef struct malloc_chunk* mfastbinptr; /* offset 2 to use otherwise unindexable first 2 bins */ #define fastbin_index(sz) ((((unsigned int)(sz)) >> 3) - 2) /* The maximum fastbin request size we support */ #define MAX_FAST_SIZE 80 #define NFASTBINS (fastbin_index(request2size(MAX_FAST_SIZE))+1) /* FASTBIN_CONSOLIDATION_THRESHOLD is the size of a chunk in free() that triggers automatic consolidation of possibly-surrounding fastbin chunks. This is a heuristic, so the exact value should not matter too much. It is defined at half the default trim threshold as a compromise heuristic to only attempt consolidation if it is likely to lead to trimming. However, it is not dynamically tunable, since consolidation reduces fragmentation surrounding loarge chunks even if trimming is not used. */ #define FASTBIN_CONSOLIDATION_THRESHOLD \ ((unsigned long)(DEFAULT_TRIM_THRESHOLD) >> 1) /* Since the lowest 2 bits in max_fast don't matter in size comparisons, they are used as flags. */ /* ANYCHUNKS_BIT held in max_fast indicates that there may be any freed chunks at all. It is set true when entering a chunk into any bin. */ #define ANYCHUNKS_BIT (1U) #define have_anychunks(M) (((M)->max_fast & ANYCHUNKS_BIT)) #define set_anychunks(M) ((M)->max_fast |= ANYCHUNKS_BIT) #define clear_anychunks(M) ((M)->max_fast &= ~ANYCHUNKS_BIT) /* FASTCHUNKS_BIT held in max_fast indicates that there are probably some fastbin chunks. It is set true on entering a chunk into any fastbin, and cleared only in malloc_consolidate. */ #define FASTCHUNKS_BIT (2U) #define have_fastchunks(M) (((M)->max_fast & FASTCHUNKS_BIT)) #define set_fastchunks(M) ((M)->max_fast |= (FASTCHUNKS_BIT|ANYCHUNKS_BIT)) #define clear_fastchunks(M) ((M)->max_fast &= ~(FASTCHUNKS_BIT)) /* Set value of max_fast. Use impossibly small value if 0. */ #define set_max_fast(M, s) \ (M)->max_fast = (((s) == 0)? SMALLBIN_WIDTH: request2size(s)) | \ ((M)->max_fast & (FASTCHUNKS_BIT|ANYCHUNKS_BIT)) #define get_max_fast(M) \ ((M)->max_fast & ~(FASTCHUNKS_BIT | ANYCHUNKS_BIT)) /* morecore_properties is a status word holding dynamically discovered or controlled properties of the morecore function */ #define MORECORE_CONTIGUOUS_BIT (1U) #define contiguous(M) \ (((M)->morecore_properties & MORECORE_CONTIGUOUS_BIT)) #define noncontiguous(M) \ (((M)->morecore_properties & MORECORE_CONTIGUOUS_BIT) == 0) #define set_contiguous(M) \ ((M)->morecore_properties |= MORECORE_CONTIGUOUS_BIT) #define set_noncontiguous(M) \ ((M)->morecore_properties &= ~MORECORE_CONTIGUOUS_BIT) /* ----------- Internal state representation and initialization ----------- */ struct malloc_state { /* The maximum chunk size to be eligible for fastbin */ INTERNAL_SIZE_T max_fast; /* low 2 bits used as flags */ /* Fastbins */ mfastbinptr fastbins[NFASTBINS]; /* Base of the topmost chunk -- not otherwise kept in a bin */ mchunkptr top; /* The remainder from the most recent split of a small request */ mchunkptr last_remainder; /* Normal bins packed as described above */ mchunkptr bins[NBINS * 2]; /* Bitmap of bins. Trailing zero map handles cases of largest binned size */ unsigned int binmap[BINMAPSIZE+1]; /* Tunable parameters */ CHUNK_SIZE_T trim_threshold; INTERNAL_SIZE_T top_pad; INTERNAL_SIZE_T mmap_threshold; /* Memory map support */ int n_mmaps; int n_mmaps_max; int max_n_mmaps; /* Cache malloc_getpagesize */ unsigned int pagesize; /* Track properties of MORECORE */ unsigned int morecore_properties; /* Statistics */ INTERNAL_SIZE_T mmapped_mem; INTERNAL_SIZE_T sbrked_mem; INTERNAL_SIZE_T max_sbrked_mem; INTERNAL_SIZE_T max_mmapped_mem; INTERNAL_SIZE_T max_total_mem; }; typedef struct malloc_state *mstate; /* There is exactly one instance of this struct in this malloc. If you are adapting this malloc in a way that does NOT use a static malloc_state, you MUST explicitly zero-fill it before using. This malloc relies on the property that malloc_state is initialized to all zeroes (as is true of C statics). */ static struct malloc_state av_; /* never directly referenced */ /* All uses of av_ are via get_malloc_state(). At most one "call" to get_malloc_state is made per invocation of the public versions of malloc and free, but other routines that in turn invoke malloc and/or free may call more then once. Also, it is called in check* routines if DEBUG is set. */ #define get_malloc_state() (&(av_)) /* Initialize a malloc_state struct. This is called only from within malloc_consolidate, which needs be called in the same contexts anyway. It is never called directly outside of malloc_consolidate because some optimizing compilers try to inline it at all call points, which turns out not to be an optimization at all. (Inlining it in malloc_consolidate is fine though.) */ #if __STD_C static void malloc_init_state(mstate av) #else static void malloc_init_state(av) mstate av; #endif { int i; mbinptr bin; /* Establish circular links for normal bins */ for (i = 1; i < NBINS; ++i) { bin = bin_at(av,i); bin->fd = bin->bk = bin; } av->top_pad = DEFAULT_TOP_PAD; av->n_mmaps_max = DEFAULT_MMAP_MAX; av->mmap_threshold = DEFAULT_MMAP_THRESHOLD; av->trim_threshold = DEFAULT_TRIM_THRESHOLD; #if MORECORE_CONTIGUOUS set_contiguous(av); #else set_noncontiguous(av); #endif set_max_fast(av, DEFAULT_MXFAST); av->top = initial_top(av); av->pagesize = malloc_getpagesize; } /* Other internal utilities operating on mstates */ #if __STD_C static Void_t* sYSMALLOc(INTERNAL_SIZE_T, mstate); static int sYSTRIm(size_t, mstate); static void malloc_consolidate(mstate); static Void_t** iALLOc(size_t, size_t*, int, Void_t**); #else static Void_t* sYSMALLOc(); static int sYSTRIm(); static void malloc_consolidate(); static Void_t** iALLOc(); #endif /* Debugging support These routines make a number of assertions about the states of data structures that should be true at all times. If any are not true, it's very likely that a user program has somehow trashed memory. (It's also possible that there is a coding error in malloc. In which case, please report it!) */ #if ! DEBUG #define check_chunk(P) #define check_free_chunk(P) #define check_inuse_chunk(P) #define check_remalloced_chunk(P,N) #define check_malloced_chunk(P,N) #define check_malloc_state() #else #define check_chunk(P) do_check_chunk(P) #define check_free_chunk(P) do_check_free_chunk(P) #define check_inuse_chunk(P) do_check_inuse_chunk(P) #define check_remalloced_chunk(P,N) do_check_remalloced_chunk(P,N) #define check_malloced_chunk(P,N) do_check_malloced_chunk(P,N) #define check_malloc_state() do_check_malloc_state() /* Properties of all chunks */ #if __STD_C static void do_check_chunk(mchunkptr p) #else static void do_check_chunk(p) mchunkptr p; #endif { mstate av = get_malloc_state(); CHUNK_SIZE_T sz = chunksize(p); /* min and max possible addresses assuming contiguous allocation */ char* max_address = (char*)(av->top) + chunksize(av->top); char* min_address = max_address - av->sbrked_mem; if (!chunk_is_mmapped(p)) { /* Has legal address ... */ if (p != av->top) { if (contiguous(av)) { assert(((char*)p) >= min_address); assert(((char*)p + sz) <= ((char*)(av->top))); } } else { /* top size is always at least MINSIZE */ assert((CHUNK_SIZE_T)(sz) >= MINSIZE); /* top predecessor always marked inuse */ assert(prev_inuse(p)); } } else { #if HAVE_MMAP /* address is outside main heap */ if (contiguous(av) && av->top != initial_top(av)) { assert(((char*)p) < min_address || ((char*)p) > max_address); } /* chunk is page-aligned */ assert(((p->prev_size + sz) & (av->pagesize-1)) == 0); /* mem is aligned */ assert(aligned_OK(chunk2mem(p))); #else /* force an appropriate assert violation if debug set */ assert(!chunk_is_mmapped(p)); #endif } } /* Properties of free chunks */ #if __STD_C static void do_check_free_chunk(mchunkptr p) #else static void do_check_free_chunk(p) mchunkptr p; #endif { mstate av = get_malloc_state(); INTERNAL_SIZE_T sz = p->size & ~PREV_INUSE; mchunkptr next = chunk_at_offset(p, sz); do_check_chunk(p); /* Chunk must claim to be free ... */ assert(!inuse(p)); assert (!chunk_is_mmapped(p)); /* Unless a special marker, must have OK fields */ if ((CHUNK_SIZE_T)(sz) >= MINSIZE) { assert((sz & MALLOC_ALIGN_MASK) == 0); assert(aligned_OK(chunk2mem(p))); /* ... matching footer field */ assert(next->prev_size == sz); /* ... and is fully consolidated */ assert(prev_inuse(p)); assert (next == av->top || inuse(next)); /* ... and has minimally sane links */ assert(p->fd->bk == p); assert(p->bk->fd == p); } else /* markers are always of size SIZE_SZ */ assert(sz == SIZE_SZ); } /* Properties of inuse chunks */ #if __STD_C static void do_check_inuse_chunk(mchunkptr p) #else static void do_check_inuse_chunk(p) mchunkptr p; #endif { mstate av = get_malloc_state(); mchunkptr next; do_check_chunk(p); if (chunk_is_mmapped(p)) return; /* mmapped chunks have no next/prev */ /* Check whether it claims to be in use ... */ assert(inuse(p)); next = next_chunk(p); /* ... and is surrounded by OK chunks. Since more things can be checked with free chunks than inuse ones, if an inuse chunk borders them and debug is on, it's worth doing them. */ if (!prev_inuse(p)) { /* Note that we cannot even look at prev unless it is not inuse */ mchunkptr prv = prev_chunk(p); assert(next_chunk(prv) == p); do_check_free_chunk(prv); } if (next == av->top) { assert(prev_inuse(next)); assert(chunksize(next) >= MINSIZE); } else if (!inuse(next)) do_check_free_chunk(next); } /* Properties of chunks recycled from fastbins */ #if __STD_C static void do_check_remalloced_chunk(mchunkptr p, INTERNAL_SIZE_T s) #else static void do_check_remalloced_chunk(p, s) mchunkptr p; INTERNAL_SIZE_T s; #endif { INTERNAL_SIZE_T sz = p->size & ~PREV_INUSE; do_check_inuse_chunk(p); /* Legal size ... */ assert((sz & MALLOC_ALIGN_MASK) == 0); assert((CHUNK_SIZE_T)(sz) >= MINSIZE); /* ... and alignment */ assert(aligned_OK(chunk2mem(p))); /* chunk is less than MINSIZE more than request */ assert((long)(sz) - (long)(s) >= 0); assert((long)(sz) - (long)(s + MINSIZE) < 0); } /* Properties of nonrecycled chunks at the point they are malloced */ #if __STD_C static void do_check_malloced_chunk(mchunkptr p, INTERNAL_SIZE_T s) #else static void do_check_malloced_chunk(p, s) mchunkptr p; INTERNAL_SIZE_T s; #endif { /* same as recycled case ... */ do_check_remalloced_chunk(p, s); /* ... plus, must obey implementation invariant that prev_inuse is always true of any allocated chunk; i.e., that each allocated chunk borders either a previously allocated and still in-use chunk, or the base of its memory arena. This is ensured by making all allocations from the the `lowest' part of any found chunk. This does not necessarily hold however for chunks recycled via fastbins. */ assert(prev_inuse(p)); } /* Properties of malloc_state. This may be useful for debugging malloc, as well as detecting user programmer errors that somehow write into malloc_state. If you are extending or experimenting with this malloc, you can probably figure out how to hack this routine to print out or display chunk addresses, sizes, bins, and other instrumentation. */ static void do_check_malloc_state() { mstate av = get_malloc_state(); int i; mchunkptr p; mchunkptr q; mbinptr b; unsigned int binbit; int empty; unsigned int idx; INTERNAL_SIZE_T size; CHUNK_SIZE_T total = 0; int max_fast_bin; /* internal size_t must be no wider than pointer type */ assert(sizeof(INTERNAL_SIZE_T) <= sizeof(char*)); /* alignment is a power of 2 */ assert((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-1)) == 0); /* cannot run remaining checks until fully initialized */ if (av->top == 0 || av->top == initial_top(av)) return; /* pagesize is a power of 2 */ assert((av->pagesize & (av->pagesize-1)) == 0); /* properties of fastbins */ /* max_fast is in allowed range */ assert(get_max_fast(av) <= request2size(MAX_FAST_SIZE)); max_fast_bin = fastbin_index(av->max_fast); for (i = 0; i < NFASTBINS; ++i) { p = av->fastbins[i]; /* all bins past max_fast are empty */ if (i > max_fast_bin) assert(p == 0); while (p != 0) { /* each chunk claims to be inuse */ do_check_inuse_chunk(p); total += chunksize(p); /* chunk belongs in this bin */ assert(fastbin_index(chunksize(p)) == i); p = p->fd; } } if (total != 0) assert(have_fastchunks(av)); else if (!have_fastchunks(av)) assert(total == 0); /* check normal bins */ for (i = 1; i < NBINS; ++i) { b = bin_at(av,i); /* binmap is accurate (except for bin 1 == unsorted_chunks) */ if (i >= 2) { binbit = get_binmap(av,i); empty = last(b) == b; if (!binbit) assert(empty); else if (!empty) assert(binbit); } for (p = last(b); p != b; p = p->bk) { /* each chunk claims to be free */ do_check_free_chunk(p); size = chunksize(p); total += size; if (i >= 2) { /* chunk belongs in bin */ idx = bin_index(size); assert(idx == i); /* lists are sorted */ if ((CHUNK_SIZE_T) size >= (CHUNK_SIZE_T)(FIRST_SORTED_BIN_SIZE)) { assert(p->bk == b || (CHUNK_SIZE_T)chunksize(p->bk) >= (CHUNK_SIZE_T)chunksize(p)); } } /* chunk is followed by a legal chain of inuse chunks */ for (q = next_chunk(p); (q != av->top && inuse(q) && (CHUNK_SIZE_T)(chunksize(q)) >= MINSIZE); q = next_chunk(q)) do_check_inuse_chunk(q); } } /* top chunk is OK */ check_chunk(av->top); /* sanity checks for statistics */ assert(total <= (CHUNK_SIZE_T)(av->max_total_mem)); assert(av->n_mmaps >= 0); assert(av->n_mmaps <= av->max_n_mmaps); assert((CHUNK_SIZE_T)(av->sbrked_mem) <= (CHUNK_SIZE_T)(av->max_sbrked_mem)); assert((CHUNK_SIZE_T)(av->mmapped_mem) <= (CHUNK_SIZE_T)(av->max_mmapped_mem)); assert((CHUNK_SIZE_T)(av->max_total_mem) >= (CHUNK_SIZE_T)(av->mmapped_mem) + (CHUNK_SIZE_T)(av->sbrked_mem)); } #endif /* ----------- Routines dealing with system allocation -------------- */ /* sysmalloc handles malloc cases requiring more memory from the system. On entry, it is assumed that av->top does not have enough space to service request for nb bytes, thus requiring that av->top be extended or replaced. */ #if __STD_C static Void_t* sYSMALLOc(INTERNAL_SIZE_T nb, mstate av) #else static Void_t* sYSMALLOc(nb, av) INTERNAL_SIZE_T nb; mstate av; #endif { mchunkptr old_top; /* incoming value of av->top */ INTERNAL_SIZE_T old_size; /* its size */ char* old_end; /* its end address */ long size; /* arg to first MORECORE or mmap call */ char* brk; /* return value from MORECORE */ long correction; /* arg to 2nd MORECORE call */ char* snd_brk; /* 2nd return val */ INTERNAL_SIZE_T front_misalign; /* unusable bytes at front of new space */ INTERNAL_SIZE_T end_misalign; /* partial page left at end of new space */ char* aligned_brk; /* aligned offset into brk */ mchunkptr p; /* the allocated/returned chunk */ mchunkptr remainder; /* remainder from allocation */ CHUNK_SIZE_T remainder_size; /* its size */ CHUNK_SIZE_T sum; /* for updating stats */ size_t pagemask = av->pagesize - 1; /* If there is space available in fastbins, consolidate and retry malloc from scratch rather than getting memory from system. This can occur only if nb is in smallbin range so we didn't consolidate upon entry to malloc. It is much easier to handle this case here than in malloc proper. */ if (have_fastchunks(av)) { assert(in_smallbin_range(nb)); malloc_consolidate(av); return mALLOc(nb - MALLOC_ALIGN_MASK); } #if HAVE_MMAP /* If have mmap, and the request size meets the mmap threshold, and the system supports mmap, and there are few enough currently allocated mmapped regions, try to directly map this request rather than expanding top. */ if ((CHUNK_SIZE_T)(nb) >= (CHUNK_SIZE_T)(av->mmap_threshold) && (av->n_mmaps < av->n_mmaps_max)) { char* mm; /* return value from mmap call*/ /* Round up size to nearest page. For mmapped chunks, the overhead is one SIZE_SZ unit larger than for normal chunks, because there is no following chunk whose prev_size field could be used. */ size = (nb + SIZE_SZ + MALLOC_ALIGN_MASK + pagemask) & ~pagemask; /* Don't try if size wraps around 0 */ if ((CHUNK_SIZE_T)(size) > (CHUNK_SIZE_T)(nb)) { mm = (char*)(MMAP(0, size, PROT_READ|PROT_WRITE, MAP_PRIVATE)); if (mm != (char*)(MORECORE_FAILURE)) { /* The offset to the start of the mmapped region is stored in the prev_size field of the chunk. This allows us to adjust returned start address to meet alignment requirements here and in memalign(), and still be able to compute proper address argument for later munmap in free() and realloc(). */ front_misalign = (INTERNAL_SIZE_T)chunk2mem(mm) & MALLOC_ALIGN_MASK; if (front_misalign > 0) { correction = MALLOC_ALIGNMENT - front_misalign; p = (mchunkptr)(mm + correction); p->prev_size = correction; set_head(p, (size - correction) |IS_MMAPPED); } else { p = (mchunkptr)mm; p->prev_size = 0; set_head(p, size|IS_MMAPPED); } /* update statistics */ if (++av->n_mmaps > av->max_n_mmaps) av->max_n_mmaps = av->n_mmaps; sum = av->mmapped_mem += size; if (sum > (CHUNK_SIZE_T)(av->max_mmapped_mem)) av->max_mmapped_mem = sum; sum += av->sbrked_mem; if (sum > (CHUNK_SIZE_T)(av->max_total_mem)) av->max_total_mem = sum; check_chunk(p); return chunk2mem(p); } } } #endif /* Record incoming configuration of top */ old_top = av->top; old_size = chunksize(old_top); old_end = (char*)(chunk_at_offset(old_top, old_size)); brk = snd_brk = (char*)(MORECORE_FAILURE); /* If not the first time through, we require old_size to be at least MINSIZE and to have prev_inuse set. */ assert((old_top == initial_top(av) && old_size == 0) || ((CHUNK_SIZE_T) (old_size) >= MINSIZE && prev_inuse(old_top))); /* Precondition: not enough current space to satisfy nb request */ assert((CHUNK_SIZE_T)(old_size) < (CHUNK_SIZE_T)(nb + MINSIZE)); /* Precondition: all fastbins are consolidated */ assert(!have_fastchunks(av)); /* Request enough space for nb + pad + overhead */ size = nb + av->top_pad + MINSIZE; /* If contiguous, we can subtract out existing space that we hope to combine with new space. We add it back later only if we don't actually get contiguous space. */ if (contiguous(av)) size -= old_size; /* Round to a multiple of page size. If MORECORE is not contiguous, this ensures that we only call it with whole-page arguments. And if MORECORE is contiguous and this is not first time through, this preserves page-alignment of previous calls. Otherwise, we correct to page-align below. */ size = (size + pagemask) & ~pagemask; /* Don't try to call MORECORE if argument is so big as to appear negative. Note that since mmap takes size_t arg, it may succeed below even if we cannot call MORECORE. */ if (size > 0) brk = (char*)(MORECORE(size)); /* If have mmap, try using it as a backup when MORECORE fails or cannot be used. This is worth doing on systems that have "holes" in address space, so sbrk cannot extend to give contiguous space, but space is available elsewhere. Note that we ignore mmap max count and threshold limits, since the space will not be used as a segregated mmap region. */ #if HAVE_MMAP if (brk == (char*)(MORECORE_FAILURE)) { /* Cannot merge with old top, so add its size back in */ if (contiguous(av)) size = (size + old_size + pagemask) & ~pagemask; /* If we are relying on mmap as backup, then use larger units */ if ((CHUNK_SIZE_T)(size) < (CHUNK_SIZE_T)(MMAP_AS_MORECORE_SIZE)) size = MMAP_AS_MORECORE_SIZE; /* Don't try if size wraps around 0 */ if ((CHUNK_SIZE_T)(size) > (CHUNK_SIZE_T)(nb)) { brk = (char*)(MMAP(0, size, PROT_READ|PROT_WRITE, MAP_PRIVATE)); if (brk != (char*)(MORECORE_FAILURE)) { /* We do not need, and cannot use, another sbrk call to find end */ snd_brk = brk + size; /* Record that we no longer have a contiguous sbrk region. After the first time mmap is used as backup, we do not ever rely on contiguous space since this could incorrectly bridge regions. */ set_noncontiguous(av); } } } #endif if (brk != (char*)(MORECORE_FAILURE)) { av->sbrked_mem += size; /* If MORECORE extends previous space, we can likewise extend top size. */ if (brk == old_end && snd_brk == (char*)(MORECORE_FAILURE)) { set_head(old_top, (size + old_size) | PREV_INUSE); } /* Otherwise, make adjustments: * If the first time through or noncontiguous, we need to call sbrk just to find out where the end of memory lies. * We need to ensure that all returned chunks from malloc will meet MALLOC_ALIGNMENT * If there was an intervening foreign sbrk, we need to adjust sbrk request size to account for fact that we will not be able to combine new space with existing space in old_top. * Almost all systems internally allocate whole pages at a time, in which case we might as well use the whole last page of request. So we allocate enough more memory to hit a page boundary now, which in turn causes future contiguous calls to page-align. */ else { front_misalign = 0; end_misalign = 0; correction = 0; aligned_brk = brk; /* If MORECORE returns an address lower than we have seen before, we know it isn't really contiguous. This and some subsequent checks help cope with non-conforming MORECORE functions and the presence of "foreign" calls to MORECORE from outside of malloc or by other threads. We cannot guarantee to detect these in all cases, but cope with the ones we do detect. */ if (contiguous(av) && old_size != 0 && brk < old_end) { set_noncontiguous(av); } /* handle contiguous cases */ if (contiguous(av)) { /* We can tolerate forward non-contiguities here (usually due to foreign calls) but treat them as part of our space for stats reporting. */ if (old_size != 0) av->sbrked_mem += brk - old_end; /* Guarantee alignment of first new chunk made from this space */ front_misalign = (INTERNAL_SIZE_T)chunk2mem(brk) & MALLOC_ALIGN_MASK; if (front_misalign > 0) { /* Skip over some bytes to arrive at an aligned position. We don't need to specially mark these wasted front bytes. They will never be accessed anyway because prev_inuse of av->top (and any chunk created from its start) is always true after initialization. */ correction = MALLOC_ALIGNMENT - front_misalign; aligned_brk += correction; } /* If this isn't adjacent to existing space, then we will not be able to merge with old_top space, so must add to 2nd request. */ correction += old_size; /* Extend the end address to hit a page boundary */ end_misalign = (INTERNAL_SIZE_T)(brk + size + correction); correction += ((end_misalign + pagemask) & ~pagemask) - end_misalign; assert(correction >= 0); snd_brk = (char*)(MORECORE(correction)); if (snd_brk == (char*)(MORECORE_FAILURE)) { /* If can't allocate correction, try to at least find out current brk. It might be enough to proceed without failing. */ correction = 0; snd_brk = (char*)(MORECORE(0)); } else if (snd_brk < brk) { /* If the second call gives noncontiguous space even though it says it won't, the only course of action is to ignore results of second call, and conservatively estimate where the first call left us. Also set noncontiguous, so this won't happen again, leaving at most one hole. Note that this check is intrinsically incomplete. Because MORECORE is allowed to give more space than we ask for, there is no reliable way to detect a noncontiguity producing a forward gap for the second call. */ snd_brk = brk + size; correction = 0; set_noncontiguous(av); } } /* handle non-contiguous cases */ else { /* MORECORE/mmap must correctly align */ assert(aligned_OK(chunk2mem(brk))); /* Find out current end of memory */ if (snd_brk == (char*)(MORECORE_FAILURE)) { snd_brk = (char*)(MORECORE(0)); av->sbrked_mem += snd_brk - brk - size; } } /* Adjust top based on results of second sbrk */ if (snd_brk != (char*)(MORECORE_FAILURE)) { av->top = (mchunkptr)aligned_brk; set_head(av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE); av->sbrked_mem += correction; /* If not the first time through, we either have a gap due to foreign sbrk or a non-contiguous region. Insert a double fencepost at old_top to prevent consolidation with space we don't own. These fenceposts are artificial chunks that are marked as inuse and are in any case too small to use. We need two to make sizes and alignments work out. */ if (old_size != 0) { /* Shrink old_top to insert fenceposts, keeping size a multiple of MALLOC_ALIGNMENT. We know there is at least enough space in old_top to do this. */ old_size = (old_size - 3*SIZE_SZ) & ~MALLOC_ALIGN_MASK; set_head(old_top, old_size | PREV_INUSE); /* Note that the following assignments completely overwrite old_top when old_size was previously MINSIZE. This is intentional. We need the fencepost, even if old_top otherwise gets lost. */ chunk_at_offset(old_top, old_size )->size = SIZE_SZ|PREV_INUSE; chunk_at_offset(old_top, old_size + SIZE_SZ)->size = SIZE_SZ|PREV_INUSE; /* If possible, release the rest, suppressing trimming. */ if (old_size >= MINSIZE) { INTERNAL_SIZE_T tt = av->trim_threshold; av->trim_threshold = (INTERNAL_SIZE_T)(-1); fREe(chunk2mem(old_top)); av->trim_threshold = tt; } } } } /* Update statistics */ sum = av->sbrked_mem; if (sum > (CHUNK_SIZE_T)(av->max_sbrked_mem)) av->max_sbrked_mem = sum; sum += av->mmapped_mem; if (sum > (CHUNK_SIZE_T)(av->max_total_mem)) av->max_total_mem = sum; check_malloc_state(); /* finally, do the allocation */ p = av->top; size = chunksize(p); /* check that one of the above allocation paths succeeded */ if ((CHUNK_SIZE_T)(size) >= (CHUNK_SIZE_T)(nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset(p, nb); av->top = remainder; set_head(p, nb | PREV_INUSE); set_head(remainder, remainder_size | PREV_INUSE); check_malloced_chunk(p, nb); return chunk2mem(p); } } /* catch all failure paths */ MALLOC_FAILURE_ACTION; return 0; } /* sYSTRIm is an inverse of sorts to sYSMALLOc. It gives memory back to the system (via negative arguments to sbrk) if there is unused memory at the `high' end of the malloc pool. It is called automatically by free() when top space exceeds the trim threshold. It is also called by the public malloc_trim routine. It returns 1 if it actually released any memory, else 0. */ #if __STD_C static int sYSTRIm(size_t pad, mstate av) #else static int sYSTRIm(pad, av) size_t pad; mstate av; #endif { long top_size; /* Amount of top-most memory */ long extra; /* Amount to release */ long released; /* Amount actually released */ char* current_brk; /* address returned by pre-check sbrk call */ char* new_brk; /* address returned by post-check sbrk call */ size_t pagesz; pagesz = av->pagesize; top_size = chunksize(av->top); /* Release in pagesize units, keeping at least one page */ extra = ((top_size - pad - MINSIZE + (pagesz-1)) / pagesz - 1) * pagesz; if (extra > 0) { /* Only proceed if end of memory is where we last set it. This avoids problems if there were foreign sbrk calls. */ current_brk = (char*)(MORECORE(0)); if (current_brk == (char*)(av->top) + top_size) { /* Attempt to release memory. We ignore MORECORE return value, and instead call again to find out where new end of memory is. This avoids problems if first call releases less than we asked, of if failure somehow altered brk value. (We could still encounter problems if it altered brk in some very bad way, but the only thing we can do is adjust anyway, which will cause some downstream failure.) */ MORECORE(-extra); new_brk = (char*)(MORECORE(0)); if (new_brk != (char*)MORECORE_FAILURE) { released = (long)(current_brk - new_brk); if (released != 0) { /* Success. Adjust top. */ av->sbrked_mem -= released; set_head(av->top, (top_size - released) | PREV_INUSE); check_malloc_state(); return 1; } } } } return 0; } /* ------------------------------ malloc ------------------------------ */ #if __STD_C Void_t* mALLOc(size_t bytes) #else Void_t* mALLOc(bytes) size_t bytes; #endif { mstate av = get_malloc_state(); INTERNAL_SIZE_T nb; /* normalized request size */ unsigned int idx; /* associated bin index */ mbinptr bin; /* associated bin */ mfastbinptr* fb; /* associated fastbin */ mchunkptr victim; /* inspected/selected chunk */ INTERNAL_SIZE_T size; /* its size */ int victim_index; /* its bin index */ mchunkptr remainder; /* remainder from a split */ CHUNK_SIZE_T remainder_size; /* its size */ unsigned int block; /* bit map traverser */ unsigned int bit; /* bit map traverser */ unsigned int map; /* current word of binmap */ mchunkptr fwd; /* misc temp for linking */ mchunkptr bck; /* misc temp for linking */ /* Convert request size to internal form by adding SIZE_SZ bytes overhead plus possibly more to obtain necessary alignment and/or to obtain a size of at least MINSIZE, the smallest allocatable size. Also, checked_request2size traps (returning 0) request sizes that are so large that they wrap around zero when padded and aligned. */ checked_request2size(bytes, nb); /* Bypass search if no frees yet */ if (!have_anychunks(av)) { if (av->max_fast == 0) /* initialization check */ malloc_consolidate(av); goto use_top; } /* If the size qualifies as a fastbin, first check corresponding bin. */ if ((CHUNK_SIZE_T)(nb) <= (CHUNK_SIZE_T)(av->max_fast)) { fb = &(av->fastbins[(fastbin_index(nb))]); if ( (victim = *fb) != 0) { *fb = victim->fd; check_remalloced_chunk(victim, nb); return chunk2mem(victim); } } /* If a small request, check regular bin. Since these "smallbins" hold one size each, no searching within bins is necessary. (For a large request, we need to wait until unsorted chunks are processed to find best fit. But for small ones, fits are exact anyway, so we can check now, which is faster.) */ if (in_smallbin_range(nb)) { idx = smallbin_index(nb); bin = bin_at(av,idx); if ( (victim = last(bin)) != bin) { bck = victim->bk; set_inuse_bit_at_offset(victim, nb); bin->bk = bck; bck->fd = bin; check_malloced_chunk(victim, nb); return chunk2mem(victim); } } /* If this is a large request, consolidate fastbins before continuing. While it might look excessive to kill all fastbins before even seeing if there is space available, this avoids fragmentation problems normally associated with fastbins. Also, in practice, programs tend to have runs of either small or large requests, but less often mixtures, so consolidation is not invoked all that often in most programs. And the programs that it is called frequently in otherwise tend to fragment. */ else { idx = largebin_index(nb); if (have_fastchunks(av)) malloc_consolidate(av); } /* Process recently freed or remaindered chunks, taking one only if it is exact fit, or, if this a small request, the chunk is remainder from the most recent non-exact fit. Place other traversed chunks in bins. Note that this step is the only place in any routine where chunks are placed in bins. */ while ( (victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) { bck = victim->bk; size = chunksize(victim); /* If a small request, try to use last remainder if it is the only chunk in unsorted bin. This helps promote locality for runs of consecutive small requests. This is the only exception to best-fit, and applies only when there is no exact fit for a small chunk. */ if (in_smallbin_range(nb) && bck == unsorted_chunks(av) && victim == av->last_remainder && (CHUNK_SIZE_T)(size) > (CHUNK_SIZE_T)(nb + MINSIZE)) { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset(victim, nb); unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks(av); set_head(victim, nb | PREV_INUSE); set_head(remainder, remainder_size | PREV_INUSE); set_foot(remainder, remainder_size); check_malloced_chunk(victim, nb); return chunk2mem(victim); } /* remove from unsorted list */ unsorted_chunks(av)->bk = bck; bck->fd = unsorted_chunks(av); /* Take now instead of binning if exact fit */ if (size == nb) { set_inuse_bit_at_offset(victim, size); check_malloced_chunk(victim, nb); return chunk2mem(victim); } /* place chunk in bin */ if (in_smallbin_range(size)) { victim_index = smallbin_index(size); bck = bin_at(av, victim_index); fwd = bck->fd; } else { victim_index = largebin_index(size); bck = bin_at(av, victim_index); fwd = bck->fd; if (fwd != bck) { /* if smaller than smallest, place first */ if ((CHUNK_SIZE_T)(size) < (CHUNK_SIZE_T)(bck->bk->size)) { fwd = bck; bck = bck->bk; } else if ((CHUNK_SIZE_T)(size) >= (CHUNK_SIZE_T)(FIRST_SORTED_BIN_SIZE)) { /* maintain large bins in sorted order */ size |= PREV_INUSE; /* Or with inuse bit to speed comparisons */ while ((CHUNK_SIZE_T)(size) < (CHUNK_SIZE_T)(fwd->size)) fwd = fwd->fd; bck = fwd->bk; } } } mark_bin(av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; } /* If a large request, scan through the chunks of current bin to find one that fits. (This will be the smallest that fits unless FIRST_SORTED_BIN_SIZE has been changed from default.) This is the only step where an unbounded number of chunks might be scanned without doing anything useful with them. However the lists tend to be short. */ if (!in_smallbin_range(nb)) { bin = bin_at(av, idx); for (victim = last(bin); victim != bin; victim = victim->bk) { size = chunksize(victim); if ((CHUNK_SIZE_T)(size) >= (CHUNK_SIZE_T)(nb)) { remainder_size = size - nb; unlink(victim, bck, fwd); /* Exhaust */ if (remainder_size < MINSIZE) { set_inuse_bit_at_offset(victim, size); check_malloced_chunk(victim, nb); return chunk2mem(victim); } /* Split */ else { remainder = chunk_at_offset(victim, nb); unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder; remainder->bk = remainder->fd = unsorted_chunks(av); set_head(victim, nb | PREV_INUSE); set_head(remainder, remainder_size | PREV_INUSE); set_foot(remainder, remainder_size); check_malloced_chunk(victim, nb); return chunk2mem(victim); } } } } /* Search for a chunk by scanning bins, starting with next largest bin. This search is strictly by best-fit; i.e., the smallest (with ties going to approximately the least recently used) chunk that fits is selected. The bitmap avoids needing to check that most blocks are nonempty. */ ++idx; bin = bin_at(av,idx); block = idx2block(idx); map = av->binmap[block]; bit = idx2bit(idx); for (;;) { /* Skip rest of block if there are no more set bits in this block. */ if (bit > map || bit == 0) { do { if (++block >= BINMAPSIZE) /* out of bins */ goto use_top; } while ( (map = av->binmap[block]) == 0); bin = bin_at(av, (block << BINMAPSHIFT)); bit = 1; } /* Advance to bin with set bit. There must be one. */ while ((bit & map) == 0) { bin = next_bin(bin); bit <<= 1; assert(bit != 0); } /* Inspect the bin. It is likely to be non-empty */ victim = last(bin); /* If a false alarm (empty bin), clear the bit. */ if (victim == bin) { av->binmap[block] = map &= ~bit; /* Write through */ bin = next_bin(bin); bit <<= 1; } else { size = chunksize(victim); /* We know the first chunk in this bin is big enough to use. */ assert((CHUNK_SIZE_T)(size) >= (CHUNK_SIZE_T)(nb)); remainder_size = size - nb; /* unlink */ bck = victim->bk; bin->bk = bck; bck->fd = bin; /* Exhaust */ if (remainder_size < MINSIZE) { set_inuse_bit_at_offset(victim, size); check_malloced_chunk(victim, nb); return chunk2mem(victim); } /* Split */ else { remainder = chunk_at_offset(victim, nb); unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder; remainder->bk = remainder->fd = unsorted_chunks(av); /* advertise as last remainder */ if (in_smallbin_range(nb)) av->last_remainder = remainder; set_head(victim, nb | PREV_INUSE); set_head(remainder, remainder_size | PREV_INUSE); set_foot(remainder, remainder_size); check_malloced_chunk(victim, nb); return chunk2mem(victim); } } } use_top: /* If large enough, split off the chunk bordering the end of memory (held in av->top). Note that this is in accord with the best-fit search rule. In effect, av->top is treated as larger (and thus less well fitting) than any other available chunk since it can be extended to be as large as necessary (up to system limitations). We require that av->top always exists (i.e., has size >= MINSIZE) after initialization, so if it would otherwise be exhuasted by current request, it is replenished. (The main reason for ensuring it exists is that we may need MINSIZE space to put in fenceposts in sysmalloc.) */ victim = av->top; size = chunksize(victim); if ((CHUNK_SIZE_T)(size) >= (CHUNK_SIZE_T)(nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset(victim, nb); av->top = remainder; set_head(victim, nb | PREV_INUSE); set_head(remainder, remainder_size | PREV_INUSE); check_malloced_chunk(victim, nb); return chunk2mem(victim); } /* If no space in top, relay to handle system-dependent cases */ return sYSMALLOc(nb, av); } /* ------------------------------ free ------------------------------ */ #if __STD_C void fREe(Void_t* mem) #else void fREe(mem) Void_t* mem; #endif { mstate av = get_malloc_state(); mchunkptr p; /* chunk corresponding to mem */ INTERNAL_SIZE_T size; /* its size */ mfastbinptr* fb; /* associated fastbin */ mchunkptr nextchunk; /* next contiguous chunk */ INTERNAL_SIZE_T nextsize; /* its size */ int nextinuse; /* true if nextchunk is used */ INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */ mchunkptr bck; /* misc temp for linking */ mchunkptr fwd; /* misc temp for linking */ /* free(0) has no effect */ if (mem != 0) { p = mem2chunk(mem); size = chunksize(p); check_inuse_chunk(p); /* If eligible, place chunk on a fastbin so it can be found and used quickly in malloc. */ if ((CHUNK_SIZE_T)(size) <= (CHUNK_SIZE_T)(av->max_fast) #if TRIM_FASTBINS /* If TRIM_FASTBINS set, don't place chunks bordering top into fastbins */ && (chunk_at_offset(p, size) != av->top) #endif ) { set_fastchunks(av); fb = &(av->fastbins[fastbin_index(size)]); p->fd = *fb; *fb = p; } /* Consolidate other non-mmapped chunks as they arrive. */ else if (!chunk_is_mmapped(p)) { set_anychunks(av); nextchunk = chunk_at_offset(p, size); nextsize = chunksize(nextchunk); /* consolidate backward */ if (!prev_inuse(p)) { prevsize = p->prev_size; size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); unlink(p, bck, fwd); } if (nextchunk != av->top) { /* get and clear inuse bit */ nextinuse = inuse_bit_at_offset(nextchunk, nextsize); set_head(nextchunk, nextsize); /* consolidate forward */ if (!nextinuse) { unlink(nextchunk, bck, fwd); size += nextsize; } /* Place the chunk in unsorted chunk list. Chunks are not placed into regular bins until after they have been given one chance to be used in malloc. */ bck = unsorted_chunks(av); fwd = bck->fd; p->bk = bck; p->fd = fwd; bck->fd = p; fwd->bk = p; set_head(p, size | PREV_INUSE); set_foot(p, size); check_free_chunk(p); } /* If the chunk borders the current high end of memory, consolidate into top */ else { size += nextsize; set_head(p, size | PREV_INUSE); av->top = p; check_chunk(p); } /* If freeing a large space, consolidate possibly-surrounding chunks. Then, if the total unused topmost memory exceeds trim threshold, ask malloc_trim to reduce top. Unless max_fast is 0, we don't know if there are fastbins bordering top, so we cannot tell for sure whether threshold has been reached unless fastbins are consolidated. But we don't want to consolidate on each free. As a compromise, consolidation is performed if FASTBIN_CONSOLIDATION_THRESHOLD is reached. */ if ((CHUNK_SIZE_T)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) { if (have_fastchunks(av)) malloc_consolidate(av); #ifndef MORECORE_CANNOT_TRIM if ((CHUNK_SIZE_T)(chunksize(av->top)) >= (CHUNK_SIZE_T)(av->trim_threshold)) sYSTRIm(av->top_pad, av); #endif } } /* If the chunk was allocated via mmap, release via munmap() Note that if HAVE_MMAP is false but chunk_is_mmapped is true, then user must have overwritten memory. There's nothing we can do to catch this error unless DEBUG is set, in which case check_inuse_chunk (above) will have triggered error. */ else { #if HAVE_MMAP int ret; INTERNAL_SIZE_T offset = p->prev_size; av->n_mmaps--; av->mmapped_mem -= (size + offset); ret = munmap((char*)p - offset, size + offset); /* munmap returns non-zero on failure */ assert(ret == 0); #endif } } } /* ------------------------- malloc_consolidate ------------------------- malloc_consolidate is a specialized version of free() that tears down chunks held in fastbins. Free itself cannot be used for this purpose since, among other things, it might place chunks back onto fastbins. So, instead, we need to use a minor variant of the same code. Also, because this routine needs to be called the first time through malloc anyway, it turns out to be the perfect place to trigger initialization code. */ #if __STD_C static void malloc_consolidate(mstate av) #else static void malloc_consolidate(av) mstate av; #endif { mfastbinptr* fb; /* current fastbin being consolidated */ mfastbinptr* maxfb; /* last fastbin (for loop control) */ mchunkptr p; /* current chunk being consolidated */ mchunkptr nextp; /* next chunk to consolidate */ mchunkptr unsorted_bin; /* bin header */ mchunkptr first_unsorted; /* chunk to link to */ /* These have same use as in free() */ mchunkptr nextchunk; INTERNAL_SIZE_T size; INTERNAL_SIZE_T nextsize; INTERNAL_SIZE_T prevsize; int nextinuse; mchunkptr bck; mchunkptr fwd; /* If max_fast is 0, we know that av hasn't yet been initialized, in which case do so below */ if (av->max_fast != 0) { clear_fastchunks(av); unsorted_bin = unsorted_chunks(av); /* Remove each chunk from fast bin and consolidate it, placing it then in unsorted bin. Among other reasons for doing this, placing in unsorted bin avoids needing to calculate actual bins until malloc is sure that chunks aren't immediately going to be reused anyway. */ maxfb = &(av->fastbins[fastbin_index(av->max_fast)]); fb = &(av->fastbins[0]); do { if ( (p = *fb) != 0) { *fb = 0; do { check_inuse_chunk(p); nextp = p->fd; /* Slightly streamlined version of consolidation code in free() */ size = p->size & ~PREV_INUSE; nextchunk = chunk_at_offset(p, size); nextsize = chunksize(nextchunk); if (!prev_inuse(p)) { prevsize = p->prev_size; size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); unlink(p, bck, fwd); } if (nextchunk != av->top) { nextinuse = inuse_bit_at_offset(nextchunk, nextsize); set_head(nextchunk, nextsize); if (!nextinuse) { size += nextsize; unlink(nextchunk, bck, fwd); } first_unsorted = unsorted_bin->fd; unsorted_bin->fd = p; first_unsorted->bk = p; set_head(p, size | PREV_INUSE); p->bk = unsorted_bin; p->fd = first_unsorted; set_foot(p, size); } else { size += nextsize; set_head(p, size | PREV_INUSE); av->top = p; } } while ( (p = nextp) != 0); } } while (fb++ != maxfb); } else { malloc_init_state(av); check_malloc_state(); } } /* ------------------------------ realloc ------------------------------ */ #if __STD_C Void_t* rEALLOc(Void_t* oldmem, size_t bytes) #else Void_t* rEALLOc(oldmem, bytes) Void_t* oldmem; size_t bytes; #endif { mstate av = get_malloc_state(); INTERNAL_SIZE_T nb; /* padded request size */ mchunkptr oldp; /* chunk corresponding to oldmem */ INTERNAL_SIZE_T oldsize; /* its size */ mchunkptr newp; /* chunk to return */ INTERNAL_SIZE_T newsize; /* its size */ Void_t* newmem; /* corresponding user mem */ mchunkptr next; /* next contiguous chunk after oldp */ mchunkptr remainder; /* extra space at end of newp */ CHUNK_SIZE_T remainder_size; /* its size */ mchunkptr bck; /* misc temp for linking */ mchunkptr fwd; /* misc temp for linking */ CHUNK_SIZE_T copysize; /* bytes to copy */ unsigned int ncopies; /* INTERNAL_SIZE_T words to copy */ INTERNAL_SIZE_T* s; /* copy source */ INTERNAL_SIZE_T* d; /* copy destination */ #ifdef REALLOC_ZERO_BYTES_FREES if (bytes == 0) { fREe(oldmem); return 0; } #endif /* realloc of null is supposed to be same as malloc */ if (oldmem == 0) return mALLOc(bytes); checked_request2size(bytes, nb); oldp = mem2chunk(oldmem); oldsize = chunksize(oldp); check_inuse_chunk(oldp); if (!chunk_is_mmapped(oldp)) { if ((CHUNK_SIZE_T)(oldsize) >= (CHUNK_SIZE_T)(nb)) { /* already big enough; split below */ newp = oldp; newsize = oldsize; } else { next = chunk_at_offset(oldp, oldsize); /* Try to expand forward into top */ if (next == av->top && (CHUNK_SIZE_T)(newsize = oldsize + chunksize(next)) >= (CHUNK_SIZE_T)(nb + MINSIZE)) { set_head_size(oldp, nb); av->top = chunk_at_offset(oldp, nb); set_head(av->top, (newsize - nb) | PREV_INUSE); return chunk2mem(oldp); } /* Try to expand forward into next chunk; split off remainder below */ else if (next != av->top && !inuse(next) && (CHUNK_SIZE_T)(newsize = oldsize + chunksize(next)) >= (CHUNK_SIZE_T)(nb)) { newp = oldp; unlink(next, bck, fwd); } /* allocate, copy, free */ else { newmem = mALLOc(nb - MALLOC_ALIGN_MASK); if (newmem == 0) return 0; /* propagate failure */ newp = mem2chunk(newmem); newsize = chunksize(newp); /* Avoid copy if newp is next chunk after oldp. */ if (newp == next) { newsize += oldsize; newp = oldp; } else { /* Unroll copy of <= 36 bytes (72 if 8byte sizes) We know that contents have an odd number of INTERNAL_SIZE_T-sized words; minimally 3. */ copysize = oldsize - SIZE_SZ; s = (INTERNAL_SIZE_T*)(oldmem); d = (INTERNAL_SIZE_T*)(newmem); ncopies = copysize / sizeof(INTERNAL_SIZE_T); assert(ncopies >= 3); if (ncopies > 9) MALLOC_COPY(d, s, copysize); else { *(d+0) = *(s+0); *(d+1) = *(s+1); *(d+2) = *(s+2); if (ncopies > 4) { *(d+3) = *(s+3); *(d+4) = *(s+4); if (ncopies > 6) { *(d+5) = *(s+5); *(d+6) = *(s+6); if (ncopies > 8) { *(d+7) = *(s+7); *(d+8) = *(s+8); } } } } fREe(oldmem); check_inuse_chunk(newp); return chunk2mem(newp); } } } /* If possible, free extra space in old or extended chunk */ assert((CHUNK_SIZE_T)(newsize) >= (CHUNK_SIZE_T)(nb)); remainder_size = newsize - nb; if (remainder_size < MINSIZE) { /* not enough extra to split off */ set_head_size(newp, newsize); set_inuse_bit_at_offset(newp, newsize); } else { /* split remainder */ remainder = chunk_at_offset(newp, nb); set_head_size(newp, nb); set_head(remainder, remainder_size | PREV_INUSE); /* Mark remainder as inuse so free() won't complain */ set_inuse_bit_at_offset(remainder, remainder_size); fREe(chunk2mem(remainder)); } check_inuse_chunk(newp); return chunk2mem(newp); } /* Handle mmap cases */ else { #if HAVE_MMAP #if HAVE_MREMAP INTERNAL_SIZE_T offset = oldp->prev_size; size_t pagemask = av->pagesize - 1; char *cp; CHUNK_SIZE_T sum; /* Note the extra SIZE_SZ overhead */ newsize = (nb + offset + SIZE_SZ + pagemask) & ~pagemask; /* don't need to remap if still within same page */ if (oldsize == newsize - offset) return oldmem; cp = (char*)mremap((char*)oldp - offset, oldsize + offset, newsize, 1); if (cp != (char*)MORECORE_FAILURE) { newp = (mchunkptr)(cp + offset); set_head(newp, (newsize - offset)|IS_MMAPPED); assert(aligned_OK(chunk2mem(newp))); assert((newp->prev_size == offset)); /* update statistics */ sum = av->mmapped_mem += newsize - oldsize; if (sum > (CHUNK_SIZE_T)(av->max_mmapped_mem)) av->max_mmapped_mem = sum; sum += av->sbrked_mem; if (sum > (CHUNK_SIZE_T)(av->max_total_mem)) av->max_total_mem = sum; return chunk2mem(newp); } #endif /* Note the extra SIZE_SZ overhead. */ if ((CHUNK_SIZE_T)(oldsize) >= (CHUNK_SIZE_T)(nb + SIZE_SZ)) newmem = oldmem; /* do nothing */ else { /* Must alloc, copy, free. */ newmem = mALLOc(nb - MALLOC_ALIGN_MASK); if (newmem != 0) { MALLOC_COPY(newmem, oldmem, oldsize - 2*SIZE_SZ); fREe(oldmem); } } return newmem; #else /* If !HAVE_MMAP, but chunk_is_mmapped, user must have overwritten mem */ check_malloc_state(); MALLOC_FAILURE_ACTION; return 0; #endif } } /* ------------------------------ memalign ------------------------------ */ #if __STD_C Void_t* mEMALIGn(size_t alignment, size_t bytes) #else Void_t* mEMALIGn(alignment, bytes) size_t alignment; size_t bytes; #endif { INTERNAL_SIZE_T nb; /* padded request size */ char* m; /* memory returned by malloc call */ mchunkptr p; /* corresponding chunk */ char* brk; /* alignment point within p */ mchunkptr newp; /* chunk to return */ INTERNAL_SIZE_T newsize; /* its size */ INTERNAL_SIZE_T leadsize; /* leading space before alignment point */ mchunkptr remainder; /* spare room at end to split off */ CHUNK_SIZE_T remainder_size; /* its size */ INTERNAL_SIZE_T size; /* If need less alignment than we give anyway, just relay to malloc */ if (alignment <= MALLOC_ALIGNMENT) return mALLOc(bytes); /* Otherwise, ensure that it is at least a minimum chunk size */ if (alignment < MINSIZE) alignment = MINSIZE; /* Make sure alignment is power of 2 (in case MINSIZE is not). */ if ((alignment & (alignment - 1)) != 0) { size_t a = MALLOC_ALIGNMENT * 2; while ((CHUNK_SIZE_T)a < (CHUNK_SIZE_T)alignment) a <<= 1; alignment = a; } checked_request2size(bytes, nb); /* Strategy: find a spot within that chunk that meets the alignment request, and then possibly free the leading and trailing space. */ /* Call malloc with worst case padding to hit alignment. */ m = (char*)(mALLOc(nb + alignment + MINSIZE)); if (m == 0) return 0; /* propagate failure */ p = mem2chunk(m); if ((((PTR_UINT)(m)) % alignment) != 0) { /* misaligned */ /* Find an aligned spot inside chunk. Since we need to give back leading space in a chunk of at least MINSIZE, if the first calculation places us at a spot with less than MINSIZE leader, we can move to the next aligned spot -- we've allocated enough total room so that this is always possible. */ brk = (char*)mem2chunk((PTR_UINT)(((PTR_UINT)(m + alignment - 1)) & -((signed long) alignment))); if ((CHUNK_SIZE_T)(brk - (char*)(p)) < MINSIZE) brk += alignment; newp = (mchunkptr)brk; leadsize = brk - (char*)(p); newsize = chunksize(p) - leadsize; /* For mmapped chunks, just adjust offset */ if (chunk_is_mmapped(p)) { newp->prev_size = p->prev_size + leadsize; set_head(newp, newsize|IS_MMAPPED); return chunk2mem(newp); } /* Otherwise, give back leader, use the rest */ set_head(newp, newsize | PREV_INUSE); set_inuse_bit_at_offset(newp, newsize); set_head_size(p, leadsize); fREe(chunk2mem(p)); p = newp; assert (newsize >= nb && (((PTR_UINT)(chunk2mem(p))) % alignment) == 0); } /* Also give back spare room at the end */ if (!chunk_is_mmapped(p)) { size = chunksize(p); if ((CHUNK_SIZE_T)(size) > (CHUNK_SIZE_T)(nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset(p, nb); set_head(remainder, remainder_size | PREV_INUSE); set_head_size(p, nb); fREe(chunk2mem(remainder)); } } check_inuse_chunk(p); return chunk2mem(p); } /* ------------------------------ calloc ------------------------------ */ #if __STD_C Void_t* cALLOc(size_t n_elements, size_t elem_size) #else Void_t* cALLOc(n_elements, elem_size) size_t n_elements; size_t elem_size; #endif { mchunkptr p; CHUNK_SIZE_T clearsize; CHUNK_SIZE_T nclears; INTERNAL_SIZE_T* d; Void_t* mem = mALLOc(n_elements * elem_size); if (mem != 0) { p = mem2chunk(mem); if (!chunk_is_mmapped(p)) { /* Unroll clear of <= 36 bytes (72 if 8byte sizes) We know that contents have an odd number of INTERNAL_SIZE_T-sized words; minimally 3. */ d = (INTERNAL_SIZE_T*)mem; clearsize = chunksize(p) - SIZE_SZ; nclears = clearsize / sizeof(INTERNAL_SIZE_T); assert(nclears >= 3); if (nclears > 9) MALLOC_ZERO(d, clearsize); else { *(d+0) = 0; *(d+1) = 0; *(d+2) = 0; if (nclears > 4) { *(d+3) = 0; *(d+4) = 0; if (nclears > 6) { *(d+5) = 0; *(d+6) = 0; if (nclears > 8) { *(d+7) = 0; *(d+8) = 0; } } } } } #if ! MMAP_CLEARS else { d = (INTERNAL_SIZE_T*)mem; /* Note the additional SIZE_SZ */ clearsize = chunksize(p) - 2*SIZE_SZ; MALLOC_ZERO(d, clearsize); } #endif } return mem; } /* ------------------------------ cfree ------------------------------ */ #if __STD_C void cFREe(Void_t *mem) #else void cFREe(mem) Void_t *mem; #endif { fREe(mem); } /* ------------------------- independent_calloc ------------------------- */ #if __STD_C Void_t** iCALLOc(size_t n_elements, size_t elem_size, Void_t* chunks[]) #else Void_t** iCALLOc(n_elements, elem_size, chunks) size_t n_elements; size_t elem_size; Void_t* chunks[]; #endif { size_t sz = elem_size; /* serves as 1-element array */ /* opts arg of 3 means all elements are same size, and should be cleared */ return iALLOc(n_elements, &sz, 3, chunks); } /* ------------------------- independent_comalloc ------------------------- */ #if __STD_C Void_t** iCOMALLOc(size_t n_elements, size_t sizes[], Void_t* chunks[]) #else Void_t** iCOMALLOc(n_elements, sizes, chunks) size_t n_elements; size_t sizes[]; Void_t* chunks[]; #endif { return iALLOc(n_elements, sizes, 0, chunks); } /* ------------------------------ ialloc ------------------------------ ialloc provides common support for independent_X routines, handling all of the combinations that can result. The opts arg has: bit 0 set if all elements are same size (using sizes[0]) bit 1 set if elements should be zeroed */ #if __STD_C static Void_t** iALLOc(size_t n_elements, size_t* sizes, int opts, Void_t* chunks[]) #else static Void_t** iALLOc(n_elements, sizes, opts, chunks) size_t n_elements; size_t* sizes; int opts; Void_t* chunks[]; #endif { mstate av = get_malloc_state(); INTERNAL_SIZE_T element_size; /* chunksize of each element, if all same */ INTERNAL_SIZE_T contents_size; /* total size of elements */ INTERNAL_SIZE_T array_size; /* request size of pointer array */ Void_t* mem; /* malloced aggregate space */ mchunkptr p; /* corresponding chunk */ INTERNAL_SIZE_T remainder_size; /* remaining bytes while splitting */ Void_t** marray; /* either "chunks" or malloced ptr array */ mchunkptr array_chunk; /* chunk for malloced ptr array */ int mmx; /* to disable mmap */ INTERNAL_SIZE_T size; size_t i; /* Ensure initialization */ if (av->max_fast == 0) malloc_consolidate(av); /* compute array length, if needed */ if (chunks != 0) { if (n_elements == 0) return chunks; /* nothing to do */ marray = chunks; array_size = 0; } else { /* if empty req, must still return chunk representing empty array */ if (n_elements == 0) return (Void_t**) mALLOc(0); marray = 0; array_size = request2size(n_elements * (sizeof(Void_t*))); } /* compute total element size */ if (opts & 0x1) { /* all-same-size */ element_size = request2size(*sizes); contents_size = n_elements * element_size; } else { /* add up all the sizes */ element_size = 0; contents_size = 0; for (i = 0; i != n_elements; ++i) contents_size += request2size(sizes[i]); } /* subtract out alignment bytes from total to minimize overallocation */ size = contents_size + array_size - MALLOC_ALIGN_MASK; /* Allocate the aggregate chunk. But first disable mmap so malloc won't use it, since we would not be able to later free/realloc space internal to a segregated mmap region. */ mmx = av->n_mmaps_max; /* disable mmap */ av->n_mmaps_max = 0; mem = mALLOc(size); av->n_mmaps_max = mmx; /* reset mmap */ if (mem == 0) return 0; p = mem2chunk(mem); assert(!chunk_is_mmapped(p)); remainder_size = chunksize(p); if (opts & 0x2) { /* optionally clear the elements */ MALLOC_ZERO(mem, remainder_size - SIZE_SZ - array_size); } /* If not provided, allocate the pointer array as final part of chunk */ if (marray == 0) { array_chunk = chunk_at_offset(p, contents_size); marray = (Void_t**) (chunk2mem(array_chunk)); set_head(array_chunk, (remainder_size - contents_size) | PREV_INUSE); remainder_size = contents_size; } /* split out elements */ for (i = 0; ; ++i) { marray[i] = chunk2mem(p); if (i != n_elements-1) { if (element_size != 0) size = element_size; else size = request2size(sizes[i]); remainder_size -= size; set_head(p, size | PREV_INUSE); p = chunk_at_offset(p, size); } else { /* the final element absorbs any overallocation slop */ set_head(p, remainder_size | PREV_INUSE); break; } } #if DEBUG if (marray != chunks) { /* final element must have exactly exhausted chunk */ if (element_size != 0) assert(remainder_size == element_size); else assert(remainder_size == request2size(sizes[i])); check_inuse_chunk(mem2chunk(marray)); } for (i = 0; i != n_elements; ++i) check_inuse_chunk(mem2chunk(marray[i])); #endif return marray; } /* ------------------------------ valloc ------------------------------ */ #if __STD_C Void_t* vALLOc(size_t bytes) #else Void_t* vALLOc(bytes) size_t bytes; #endif { /* Ensure initialization */ mstate av = get_malloc_state(); if (av->max_fast == 0) malloc_consolidate(av); return mEMALIGn(av->pagesize, bytes); } /* ------------------------------ pvalloc ------------------------------ */ #if __STD_C Void_t* pVALLOc(size_t bytes) #else Void_t* pVALLOc(bytes) size_t bytes; #endif { mstate av = get_malloc_state(); size_t pagesz; /* Ensure initialization */ if (av->max_fast == 0) malloc_consolidate(av); pagesz = av->pagesize; return mEMALIGn(pagesz, (bytes + pagesz - 1) & ~(pagesz - 1)); } /* ------------------------------ malloc_trim ------------------------------ */ #if __STD_C int mTRIm(size_t pad) #else int mTRIm(pad) size_t pad; #endif { mstate av = get_malloc_state(); /* Ensure initialization/consolidation */ malloc_consolidate(av); #ifndef MORECORE_CANNOT_TRIM return sYSTRIm(pad, av); #else return 0; #endif } /* ------------------------- malloc_usable_size ------------------------- */ #if __STD_C size_t mUSABLe(Void_t* mem) #else size_t mUSABLe(mem) Void_t* mem; #endif { mchunkptr p; if (mem != 0) { p = mem2chunk(mem); if (chunk_is_mmapped(p)) return chunksize(p) - 2*SIZE_SZ; else if (inuse(p)) return chunksize(p) - SIZE_SZ; } return 0; } /* ------------------------------ mallinfo ------------------------------ */ struct mallinfo mALLINFo() { mstate av = get_malloc_state(); struct mallinfo mi; int i; mbinptr b; mchunkptr p; INTERNAL_SIZE_T avail; INTERNAL_SIZE_T fastavail; int nblocks; int nfastblocks; /* Ensure initialization */ if (av->top == 0) malloc_consolidate(av); check_malloc_state(); /* Account for top */ avail = chunksize(av->top); nblocks = 1; /* top always exists */ /* traverse fastbins */ nfastblocks = 0; fastavail = 0; for (i = 0; i < (int)NFASTBINS; ++i) { for (p = av->fastbins[i]; p != 0; p = p->fd) { ++nfastblocks; fastavail += chunksize(p); } } avail += fastavail; /* traverse regular bins */ for (i = 1; i < NBINS; ++i) { b = bin_at(av, i); for (p = last(b); p != b; p = p->bk) { ++nblocks; avail += chunksize(p); } } mi.smblks = nfastblocks; mi.ordblks = nblocks; mi.fordblks = avail; mi.uordblks = av->sbrked_mem - avail; mi.arena = av->sbrked_mem; mi.hblks = av->n_mmaps; mi.hblkhd = av->mmapped_mem; mi.fsmblks = fastavail; mi.keepcost = chunksize(av->top); mi.usmblks = av->max_total_mem; return mi; } /* ------------------------------ malloc_stats ------------------------------ */ void mSTATs(void) { struct mallinfo mi = mALLINFo(); #ifdef WIN32 { CHUNK_SIZE_T free, reserved, committed; vminfo (&free, &reserved, &committed); fprintf(stderr, "free bytes = %10lu\n", free); fprintf(stderr, "reserved bytes = %10lu\n", reserved); fprintf(stderr, "committed bytes = %10lu\n", committed); } #endif fprintf(stderr, "max system bytes = %10lu\n", (CHUNK_SIZE_T)(mi.usmblks)); fprintf(stderr, "system bytes = %10lu\n", (CHUNK_SIZE_T)(mi.arena + mi.hblkhd)); fprintf(stderr, "in use bytes = %10lu\n", (CHUNK_SIZE_T)(mi.uordblks + mi.hblkhd)); #ifdef WIN32 { CHUNK_SIZE_T kernel, user; if (cpuinfo (TRUE, &kernel, &user)) { fprintf(stderr, "kernel ms = %10lu\n", kernel); fprintf(stderr, "user ms = %10lu\n", user); } } #endif } /* ------------------------------ mallopt ------------------------------ */ #if __STD_C int mALLOPt(int param_number, int value) #else int mALLOPt(param_number, value) int param_number; int value; #endif { mstate av = get_malloc_state(); /* Ensure initialization/consolidation */ malloc_consolidate(av); switch(param_number) { case M_MXFAST: if (value >= 0 && value <= MAX_FAST_SIZE) { set_max_fast(av, value); return 1; } else return 0; case M_TRIM_THRESHOLD: av->trim_threshold = value; return 1; case M_TOP_PAD: av->top_pad = value; return 1; case M_MMAP_THRESHOLD: av->mmap_threshold = value; return 1; case M_MMAP_MAX: #if !HAVE_MMAP if (value != 0) return 0; #endif av->n_mmaps_max = value; return 1; default: return 0; } } /* -------------------- Alternative MORECORE functions -------------------- */ /* General Requirements for MORECORE. The MORECORE function must have the following properties: If MORECORE_CONTIGUOUS is false: * MORECORE must allocate in multiples of pagesize. It will only be called with arguments that are multiples of pagesize. * MORECORE(0) must return an address that is at least MALLOC_ALIGNMENT aligned. (Page-aligning always suffices.) else (i.e. If MORECORE_CONTIGUOUS is true): * Consecutive calls to MORECORE with positive arguments return increasing addresses, indicating that space has been contiguously extended. * MORECORE need not allocate in multiples of pagesize. Calls to MORECORE need not have args of multiples of pagesize. * MORECORE need not page-align. In either case: * MORECORE may allocate more memory than requested. (Or even less, but this will generally result in a malloc failure.) * MORECORE must not allocate memory when given argument zero, but instead return one past the end address of memory from previous nonzero call. This malloc does NOT call MORECORE(0) until at least one call with positive arguments is made, so the initial value returned is not important. * Even though consecutive calls to MORECORE need not return contiguous addresses, it must be OK for malloc'ed chunks to span multiple regions in those cases where they do happen to be contiguous. * MORECORE need not handle negative arguments -- it may instead just return MORECORE_FAILURE when given negative arguments. Negative arguments are always multiples of pagesize. MORECORE must not misinterpret negative args as large positive unsigned args. You can suppress all such calls from even occurring by defining MORECORE_CANNOT_TRIM, There is some variation across systems about the type of the argument to sbrk/MORECORE. If size_t is unsigned, then it cannot actually be size_t, because sbrk supports negative args, so it is normally the signed type of the same width as size_t (sometimes declared as "intptr_t", and sometimes "ptrdiff_t"). It doesn't much matter though. Internally, we use "long" as arguments, which should work across all reasonable possibilities. Additionally, if MORECORE ever returns failure for a positive request, and HAVE_MMAP is true, then mmap is used as a noncontiguous system allocator. This is a useful backup strategy for systems with holes in address spaces -- in this case sbrk cannot contiguously expand the heap, but mmap may be able to map noncontiguous space. If you'd like mmap to ALWAYS be used, you can define MORECORE to be a function that always returns MORECORE_FAILURE. Malloc only has limited ability to detect failures of MORECORE to supply contiguous space when it says it can. In particular, multithreaded programs that do not use locks may result in rece conditions across calls to MORECORE that result in gaps that cannot be detected as such, and subsequent corruption. If you are using this malloc with something other than sbrk (or its emulation) to supply memory regions, you probably want to set MORECORE_CONTIGUOUS as false. As an example, here is a custom allocator kindly contributed for pre-OSX macOS. It uses virtually but not necessarily physically contiguous non-paged memory (locked in, present and won't get swapped out). You can use it by uncommenting this section, adding some #includes, and setting up the appropriate defines above: #define MORECORE osMoreCore #define MORECORE_CONTIGUOUS 0 There is also a shutdown routine that should somehow be called for cleanup upon program exit. #define MAX_POOL_ENTRIES 100 #define MINIMUM_MORECORE_SIZE (64 * 1024) static int next_os_pool; void *our_os_pools[MAX_POOL_ENTRIES]; void *osMoreCore(int size) { void *ptr = 0; static void *sbrk_top = 0; if (size > 0) { if (size < MINIMUM_MORECORE_SIZE) size = MINIMUM_MORECORE_SIZE; if (CurrentExecutionLevel() == kTaskLevel) ptr = PoolAllocateResident(size + RM_PAGE_SIZE, 0); if (ptr == 0) { return (void *) MORECORE_FAILURE; } // save ptrs so they can be freed during cleanup our_os_pools[next_os_pool] = ptr; next_os_pool++; ptr = (void *) ((((CHUNK_SIZE_T) ptr) + RM_PAGE_MASK) & ~RM_PAGE_MASK); sbrk_top = (char *) ptr + size; return ptr; } else if (size < 0) { // we don't currently support shrink behavior return (void *) MORECORE_FAILURE; } else { return sbrk_top; } } // cleanup any allocated memory pools // called as last thing before shutting down driver void osCleanupMem(void) { void **ptr; for (ptr = our_os_pools; ptr < &our_os_pools[MAX_POOL_ENTRIES]; ptr++) if (*ptr) { PoolDeallocate(*ptr); *ptr = 0; } } */ /* -------------------------------------------------------------- Emulation of sbrk for win32. Donated by J. Walter . For additional information about this code, and malloc on Win32, see http://www.genesys-e.de/jwalter/ */ #ifdef WIN32 #ifdef _DEBUG /* #define TRACE */ #endif /* Support for USE_MALLOC_LOCK */ #ifdef USE_MALLOC_LOCK /* Wait for spin lock */ static int slwait (int *sl) { while (InterlockedCompareExchange ((void **) sl, (void *) 1, (void *) 0) != 0) Sleep (0); return 0; } /* Release spin lock */ static int slrelease (int *sl) { InterlockedExchange (sl, 0); return 0; } #ifdef NEEDED /* Spin lock for emulation code */ static int g_sl; #endif #endif /* USE_MALLOC_LOCK */ /* getpagesize for windows */ static long getpagesize (void) { static long g_pagesize = 0; if (! g_pagesize) { SYSTEM_INFO system_info; GetSystemInfo (&system_info); g_pagesize = system_info.dwPageSize; } return g_pagesize; } static long getregionsize (void) { static long g_regionsize = 0; if (! g_regionsize) { SYSTEM_INFO system_info; GetSystemInfo (&system_info); g_regionsize = system_info.dwAllocationGranularity; } return g_regionsize; } /* A region list entry */ typedef struct _region_list_entry { void *top_allocated; void *top_committed; void *top_reserved; long reserve_size; struct _region_list_entry *previous; } region_list_entry; /* Allocate and link a region entry in the region list */ static int region_list_append (region_list_entry **last, void *base_reserved, long reserve_size) { region_list_entry *next = HeapAlloc (GetProcessHeap (), 0, sizeof (region_list_entry)); if (! next) return FALSE; next->top_allocated = (char *) base_reserved; next->top_committed = (char *) base_reserved; next->top_reserved = (char *) base_reserved + reserve_size; next->reserve_size = reserve_size; next->previous = *last; *last = next; return TRUE; } /* Free and unlink the last region entry from the region list */ static int region_list_remove (region_list_entry **last) { region_list_entry *previous = (*last)->previous; if (! HeapFree (GetProcessHeap (), sizeof (region_list_entry), *last)) return FALSE; *last = previous; return TRUE; } #define CEIL(size,to) (((size)+(to)-1)&~((to)-1)) #define FLOOR(size,to) ((size)&~((to)-1)) #define SBRK_SCALE 0 /* #define SBRK_SCALE 1 */ /* #define SBRK_SCALE 2 */ /* #define SBRK_SCALE 4 */ /* sbrk for windows */ static void *sbrk (long size) { static long g_pagesize, g_my_pagesize; static long g_regionsize, g_my_regionsize; static region_list_entry *g_last; void *result = (void *) MORECORE_FAILURE; #ifdef TRACE printf ("sbrk %d\n", size); #endif #if defined (USE_MALLOC_LOCK) && defined (NEEDED) /* Wait for spin lock */ slwait (&g_sl); #endif /* First time initialization */ if (! g_pagesize) { g_pagesize = getpagesize (); g_my_pagesize = g_pagesize << SBRK_SCALE; } if (! g_regionsize) { g_regionsize = getregionsize (); g_my_regionsize = g_regionsize << SBRK_SCALE; } if (! g_last) { if (! region_list_append (&g_last, 0, 0)) goto sbrk_exit; } /* Assert invariants */ assert (g_last); assert ((char *) g_last->top_reserved - g_last->reserve_size <= (char *) g_last->top_allocated && g_last->top_allocated <= g_last->top_committed); assert ((char *) g_last->top_reserved - g_last->reserve_size <= (char *) g_last->top_committed && g_last->top_committed <= g_last->top_reserved && (unsigned) g_last->top_committed % g_pagesize == 0); assert ((unsigned) g_last->top_reserved % g_regionsize == 0); assert ((unsigned) g_last->reserve_size % g_regionsize == 0); /* Allocation requested? */ if (size >= 0) { /* Allocation size is the requested size */ long allocate_size = size; /* Compute the size to commit */ long to_commit = (char *) g_last->top_allocated + allocate_size - (char *) g_last->top_committed; /* Do we reach the commit limit? */ if (to_commit > 0) { /* Round size to commit */ long commit_size = CEIL (to_commit, g_my_pagesize); /* Compute the size to reserve */ long to_reserve = (char *) g_last->top_committed + commit_size - (char *) g_last->top_reserved; /* Do we reach the reserve limit? */ if (to_reserve > 0) { /* Compute the remaining size to commit in the current region */ long remaining_commit_size = (char *) g_last->top_reserved - (char *) g_last->top_committed; if (remaining_commit_size > 0) { /* Assert preconditions */ assert ((unsigned) g_last->top_committed % g_pagesize == 0); assert (0 < remaining_commit_size && remaining_commit_size % g_pagesize == 0); { /* Commit this */ void *base_committed = VirtualAlloc (g_last->top_committed, remaining_commit_size, MEM_COMMIT, PAGE_READWRITE); /* Check returned pointer for consistency */ if (base_committed != g_last->top_committed) goto sbrk_exit; /* Assert postconditions */ assert ((unsigned) base_committed % g_pagesize == 0); #ifdef TRACE printf ("Commit %p %d\n", base_committed, remaining_commit_size); #endif /* Adjust the regions commit top */ g_last->top_committed = (char *) base_committed + remaining_commit_size; } } { /* Now we are going to search and reserve. */ int contiguous = -1; int found = FALSE; MEMORY_BASIC_INFORMATION memory_info; void *base_reserved; long reserve_size; do { /* Assume contiguous memory */ contiguous = TRUE; /* Round size to reserve */ reserve_size = CEIL (to_reserve, g_my_regionsize); /* Start with the current region's top */ memory_info.BaseAddress = g_last->top_reserved; /* Assert preconditions */ assert ((unsigned) memory_info.BaseAddress % g_pagesize == 0); assert (0 < reserve_size && reserve_size % g_regionsize == 0); while (VirtualQuery (memory_info.BaseAddress, &memory_info, sizeof (memory_info))) { /* Assert postconditions */ assert ((unsigned) memory_info.BaseAddress % g_pagesize == 0); #ifdef TRACE printf ("Query %p %d %s\n", memory_info.BaseAddress, memory_info.RegionSize, memory_info.State == MEM_FREE ? "FREE": (memory_info.State == MEM_RESERVE ? "RESERVED": (memory_info.State == MEM_COMMIT ? "COMMITTED": "?"))); #endif /* Region is free, well aligned and big enough: we are done */ if (memory_info.State == MEM_FREE && (unsigned) memory_info.BaseAddress % g_regionsize == 0 && memory_info.RegionSize >= (unsigned) reserve_size) { found = TRUE; break; } /* From now on we can't get contiguous memory! */ contiguous = FALSE; /* Recompute size to reserve */ reserve_size = CEIL (allocate_size, g_my_regionsize); memory_info.BaseAddress = (char *) memory_info.BaseAddress + memory_info.RegionSize; /* Assert preconditions */ assert ((unsigned) memory_info.BaseAddress % g_pagesize == 0); assert (0 < reserve_size && reserve_size % g_regionsize == 0); } /* Search failed? */ if (! found) goto sbrk_exit; /* Assert preconditions */ assert ((unsigned) memory_info.BaseAddress % g_regionsize == 0); assert (0 < reserve_size && reserve_size % g_regionsize == 0); /* Try to reserve this */ base_reserved = VirtualAlloc (memory_info.BaseAddress, reserve_size, MEM_RESERVE, PAGE_NOACCESS); if (! base_reserved) { int rc = GetLastError (); if (rc != ERROR_INVALID_ADDRESS) goto sbrk_exit; } /* A null pointer signals (hopefully) a race condition with another thread. */ /* In this case, we try again. */ } while (! base_reserved); /* Check returned pointer for consistency */ if (memory_info.BaseAddress && base_reserved != memory_info.BaseAddress) goto sbrk_exit; /* Assert postconditions */ assert ((unsigned) base_reserved % g_regionsize == 0); #ifdef TRACE printf ("Reserve %p %d\n", base_reserved, reserve_size); #endif /* Did we get contiguous memory? */ if (contiguous) { long start_size = (char *) g_last->top_committed - (char *) g_last->top_allocated; /* Adjust allocation size */ allocate_size -= start_size; /* Adjust the regions allocation top */ g_last->top_allocated = g_last->top_committed; /* Recompute the size to commit */ to_commit = (char *) g_last->top_allocated + allocate_size - (char *) g_last->top_committed; /* Round size to commit */ commit_size = CEIL (to_commit, g_my_pagesize); } /* Append the new region to the list */ if (! region_list_append (&g_last, base_reserved, reserve_size)) goto sbrk_exit; /* Didn't we get contiguous memory? */ if (! contiguous) { /* Recompute the size to commit */ to_commit = (char *) g_last->top_allocated + allocate_size - (char *) g_last->top_committed; /* Round size to commit */ commit_size = CEIL (to_commit, g_my_pagesize); } } } /* Assert preconditions */ assert ((unsigned) g_last->top_committed % g_pagesize == 0); assert (0 < commit_size && commit_size % g_pagesize == 0); { /* Commit this */ void *base_committed = VirtualAlloc (g_last->top_committed, commit_size, MEM_COMMIT, PAGE_READWRITE); /* Check returned pointer for consistency */ if (base_committed != g_last->top_committed) goto sbrk_exit; /* Assert postconditions */ assert ((unsigned) base_committed % g_pagesize == 0); #ifdef TRACE printf ("Commit %p %d\n", base_committed, commit_size); #endif /* Adjust the regions commit top */ g_last->top_committed = (char *) base_committed + commit_size; } } /* Adjust the regions allocation top */ g_last->top_allocated = (char *) g_last->top_allocated + allocate_size; result = (char *) g_last->top_allocated - size; /* Deallocation requested? */ } else if (size < 0) { long deallocate_size = - size; /* As long as we have a region to release */ while ((char *) g_last->top_allocated - deallocate_size < (char *) g_last->top_reserved - g_last->reserve_size) { /* Get the size to release */ long release_size = g_last->reserve_size; /* Get the base address */ void *base_reserved = (char *) g_last->top_reserved - release_size; /* Assert preconditions */ assert ((unsigned) base_reserved % g_regionsize == 0); assert (0 < release_size && release_size % g_regionsize == 0); { /* Release this */ int rc = VirtualFree (base_reserved, 0, MEM_RELEASE); /* Check returned code for consistency */ if (! rc) goto sbrk_exit; #ifdef TRACE printf ("Release %p %d\n", base_reserved, release_size); #endif } /* Adjust deallocation size */ deallocate_size -= (char *) g_last->top_allocated - (char *) base_reserved; /* Remove the old region from the list */ if (! region_list_remove (&g_last)) goto sbrk_exit; } { /* Compute the size to decommit */ long to_decommit = (char *) g_last->top_committed - ((char *) g_last->top_allocated - deallocate_size); if (to_decommit >= g_my_pagesize) { /* Compute the size to decommit */ long decommit_size = FLOOR (to_decommit, g_my_pagesize); /* Compute the base address */ void *base_committed = (char *) g_last->top_committed - decommit_size; /* Assert preconditions */ assert ((unsigned) base_committed % g_pagesize == 0); assert (0 < decommit_size && decommit_size % g_pagesize == 0); { /* Decommit this */ int rc = VirtualFree ((char *) base_committed, decommit_size, MEM_DECOMMIT); /* Check returned code for consistency */ if (! rc) goto sbrk_exit; #ifdef TRACE printf ("Decommit %p %d\n", base_committed, decommit_size); #endif } /* Adjust deallocation size and regions commit and allocate top */ deallocate_size -= (char *) g_last->top_allocated - (char *) base_committed; g_last->top_committed = base_committed; g_last->top_allocated = base_committed; } } /* Adjust regions allocate top */ g_last->top_allocated = (char *) g_last->top_allocated - deallocate_size; /* Check for underflow */ if ((char *) g_last->top_reserved - g_last->reserve_size > (char *) g_last->top_allocated || g_last->top_allocated > g_last->top_committed) { /* Adjust regions allocate top */ g_last->top_allocated = (char *) g_last->top_reserved - g_last->reserve_size; goto sbrk_exit; } result = g_last->top_allocated; } /* Assert invariants */ assert (g_last); assert ((char *) g_last->top_reserved - g_last->reserve_size <= (char *) g_last->top_allocated && g_last->top_allocated <= g_last->top_committed); assert ((char *) g_last->top_reserved - g_last->reserve_size <= (char *) g_last->top_committed && g_last->top_committed <= g_last->top_reserved && (unsigned) g_last->top_committed % g_pagesize == 0); assert ((unsigned) g_last->top_reserved % g_regionsize == 0); assert ((unsigned) g_last->reserve_size % g_regionsize == 0); sbrk_exit: #if defined (USE_MALLOC_LOCK) && defined (NEEDED) /* Release spin lock */ slrelease (&g_sl); #endif return result; } static void vminfo (CHUNK_SIZE_T *free, CHUNK_SIZE_T *reserved, CHUNK_SIZE_T *committed) { MEMORY_BASIC_INFORMATION memory_info; memory_info.BaseAddress = 0; *free = *reserved = *committed = 0; while (VirtualQuery (memory_info.BaseAddress, &memory_info, sizeof (memory_info))) { switch (memory_info.State) { case MEM_FREE: *free += memory_info.RegionSize; break; case MEM_RESERVE: *reserved += memory_info.RegionSize; break; case MEM_COMMIT: *committed += memory_info.RegionSize; break; } memory_info.BaseAddress = (char *) memory_info.BaseAddress + memory_info.RegionSize; } } static int cpuinfo (int whole, CHUNK_SIZE_T *kernel, CHUNK_SIZE_T *user) { if (whole) { __int64 creation64, exit64, kernel64, user64; int rc = GetProcessTimes (GetCurrentProcess (), (FILETIME *) &creation64, (FILETIME *) &exit64, (FILETIME *) &kernel64, (FILETIME *) &user64); if (! rc) { *kernel = 0; *user = 0; return FALSE; } *kernel = (CHUNK_SIZE_T) (kernel64 / 10000); *user = (CHUNK_SIZE_T) (user64 / 10000); return TRUE; } else { __int64 creation64, exit64, kernel64, user64; int rc = GetThreadTimes (GetCurrentThread (), (FILETIME *) &creation64, (FILETIME *) &exit64, (FILETIME *) &kernel64, (FILETIME *) &user64); if (! rc) { *kernel = 0; *user = 0; return FALSE; } *kernel = (CHUNK_SIZE_T) (kernel64 / 10000); *user = (CHUNK_SIZE_T) (user64 / 10000); return TRUE; } } #endif /* WIN32 */ /* ------------------------------------------------------------ History: V2.7.2 Sat Aug 17 09:07:30 2002 Doug Lea (dl at gee) * Fix malloc_state bitmap array misdeclaration V2.7.1 Thu Jul 25 10:58:03 2002 Doug Lea (dl at gee) * Allow tuning of FIRST_SORTED_BIN_SIZE * Use PTR_UINT as type for all ptr->int casts. Thanks to John Belmonte. * Better detection and support for non-contiguousness of MORECORE. Thanks to Andreas Mueller, Conal Walsh, and Wolfram Gloger * Bypass most of malloc if no frees. Thanks To Emery Berger. * Fix freeing of old top non-contiguous chunk im sysmalloc. * Raised default trim and map thresholds to 256K. * Fix mmap-related #defines. Thanks to Lubos Lunak. * Fix copy macros; added LACKS_FCNTL_H. Thanks to Neal Walfield. * Branch-free bin calculation * Default trim and mmap thresholds now 256K. V2.7.0 Sun Mar 11 14:14:06 2001 Doug Lea (dl at gee) * Introduce independent_comalloc and independent_calloc. Thanks to Michael Pachos for motivation and help. * Make optional .h file available * Allow > 2GB requests on 32bit systems. * new WIN32 sbrk, mmap, munmap, lock code from . Thanks also to Andreas Mueller , and Anonymous. * Allow override of MALLOC_ALIGNMENT (Thanks to Ruud Waij for helping test this.) * memalign: check alignment arg * realloc: don't try to shift chunks backwards, since this leads to more fragmentation in some programs and doesn't seem to help in any others. * Collect all cases in malloc requiring system memory into sYSMALLOc * Use mmap as backup to sbrk * Place all internal state in malloc_state * Introduce fastbins (although similar to 2.5.1) * Many minor tunings and cosmetic improvements * Introduce USE_PUBLIC_MALLOC_WRAPPERS, USE_MALLOC_LOCK * Introduce MALLOC_FAILURE_ACTION, MORECORE_CONTIGUOUS Thanks to Tony E. Bennett and others. * Include errno.h to support default failure action. V2.6.6 Sun Dec 5 07:42:19 1999 Doug Lea (dl at gee) * return null for negative arguments * Added Several WIN32 cleanups from Martin C. Fong * Add 'LACKS_SYS_PARAM_H' for those systems without 'sys/param.h' (e.g. WIN32 platforms) * Cleanup header file inclusion for WIN32 platforms * Cleanup code to avoid Microsoft Visual C++ compiler complaints * Add 'USE_DL_PREFIX' to quickly allow co-existence with existing memory allocation routines * Set 'malloc_getpagesize' for WIN32 platforms (needs more work) * Use 'assert' rather than 'ASSERT' in WIN32 code to conform to usage of 'assert' in non-WIN32 code * Improve WIN32 'sbrk()' emulation's 'findRegion()' routine to avoid infinite loop * Always call 'fREe()' rather than 'free()' V2.6.5 Wed Jun 17 15:57:31 1998 Doug Lea (dl at gee) * Fixed ordering problem with boundary-stamping V2.6.3 Sun May 19 08:17:58 1996 Doug Lea (dl at gee) * Added pvalloc, as recommended by H.J. Liu * Added 64bit pointer support mainly from Wolfram Gloger * Added anonymously donated WIN32 sbrk emulation * Malloc, calloc, getpagesize: add optimizations from Raymond Nijssen * malloc_extend_top: fix mask error that caused wastage after foreign sbrks * Add linux mremap support code from HJ Liu V2.6.2 Tue Dec 5 06:52:55 1995 Doug Lea (dl at gee) * Integrated most documentation with the code. * Add support for mmap, with help from Wolfram Gloger (Gloger@lrz.uni-muenchen.de). * Use last_remainder in more cases. * Pack bins using idea from colin@nyx10.cs.du.edu * Use ordered bins instead of best-fit threshhold * Eliminate block-local decls to simplify tracing and debugging. * Support another case of realloc via move into top * Fix error occuring when initial sbrk_base not word-aligned. * Rely on page size for units instead of SBRK_UNIT to avoid surprises about sbrk alignment conventions. * Add mallinfo, mallopt. Thanks to Raymond Nijssen (raymond@es.ele.tue.nl) for the suggestion. * Add `pad' argument to malloc_trim and top_pad mallopt parameter. * More precautions for cases where other routines call sbrk, courtesy of Wolfram Gloger (Gloger@lrz.uni-muenchen.de). * Added macros etc., allowing use in linux libc from H.J. Lu (hjl@gnu.ai.mit.edu) * Inverted this history list V2.6.1 Sat Dec 2 14:10:57 1995 Doug Lea (dl at gee) * Re-tuned and fixed to behave more nicely with V2.6.0 changes. * Removed all preallocation code since under current scheme the work required to undo bad preallocations exceeds the work saved in good cases for most test programs. * No longer use return list or unconsolidated bins since no scheme using them consistently outperforms those that don't given above changes. * Use best fit for very large chunks to prevent some worst-cases. * Added some support for debugging V2.6.0 Sat Nov 4 07:05:23 1995 Doug Lea (dl at gee) * Removed footers when chunks are in use. Thanks to Paul Wilson (wilson@cs.texas.edu) for the suggestion. V2.5.4 Wed Nov 1 07:54:51 1995 Doug Lea (dl at gee) * Added malloc_trim, with help from Wolfram Gloger (wmglo@Dent.MED.Uni-Muenchen.DE). V2.5.3 Tue Apr 26 10:16:01 1994 Doug Lea (dl at g) V2.5.2 Tue Apr 5 16:20:40 1994 Doug Lea (dl at g) * realloc: try to expand in both directions * malloc: swap order of clean-bin strategy; * realloc: only conditionally expand backwards * Try not to scavenge used bins * Use bin counts as a guide to preallocation * Occasionally bin return list chunks in first scan * Add a few optimizations from colin@nyx10.cs.du.edu V2.5.1 Sat Aug 14 15:40:43 1993 Doug Lea (dl at g) * faster bin computation & slightly different binning * merged all consolidations to one part of malloc proper (eliminating old malloc_find_space & malloc_clean_bin) * Scan 2 returns chunks (not just 1) * Propagate failure in realloc if malloc returns 0 * Add stuff to allow compilation on non-ANSI compilers from kpv@research.att.com V2.5 Sat Aug 7 07:41:59 1993 Doug Lea (dl at g.oswego.edu) * removed potential for odd address access in prev_chunk * removed dependency on getpagesize.h * misc cosmetics and a bit more internal documentation * anticosmetics: mangled names in macros to evade debugger strangeness * tested on sparc, hp-700, dec-mips, rs6000 with gcc & native cc (hp, dec only) allowing Detlefs & Zorn comparison study (in SIGPLAN Notices.) Trial version Fri Aug 28 13:14:29 1992 Doug Lea (dl at g.oswego.edu) * Based loosely on libg++-1.2X malloc. (It retains some of the overall structure of old version, but most details differ.) */ mj-1.17-src/game.c0000444006717300001440000020420615002771311012401 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/game.c,v 12.3 2020/05/30 17:35:40 jcb Exp $ * game.c * Routines for manipulating game data structures. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include "game.h" #include "sysdep.h" static const char rcs_id[] UNUSED = "$Header: /home/jcb/MahJong/newmj/RCS/game.c,v 12.3 2020/05/30 17:35:40 jcb Exp $"; /* This macro implements "tiles are equal, or one is unknown". It is used to check consistency when handling cmsgs */ #define teq(t1,t2) (t1 == t2 || !t1 || !t2) /* next seat */ #define nextseat(s) ((s+1)%NUM_SEATS) #define prevseat(s) ((s+3)%NUM_SEATS) static void set_danger_flags(Game *g UNUSED, PlayerP p); /* at end */ static void mark_dangerous_discards(Game *g); /* at end */ /* game_id_to_player: return the player with the given id in the given game */ PlayerP game_id_to_player(Game *g,int id) { int i; for ( i = 0 ; i < NUM_SEATS ; i++ ) if ( g->players[i]->id == id ) return g->players[i]; return 0; } /* game_id_to_seat: convert an id to a seat position */ seats game_id_to_seat(Game *g, int id) { seats i; for ( i = 0 ; i < NUM_SEATS ; i++ ) if ( g->players[i]->id == id ) return i; return -1; } /* game_draw_tile: draw a tile from the game's live wall. Return ErrorTile if no wall. */ Tile game_draw_tile(Game *g) { if ( g->wall.live_used >= g->wall.live_end ) return ErrorTile; return g->wall.tiles[g->wall.live_used++]; } /* game_peek_tile: return the next tile from the game's live wall. Return ErrorTile if no wall. */ Tile game_peek_tile(Game *g) { if ( g->wall.live_used >= g->wall.live_end ) return ErrorTile; return g->wall.tiles[g->wall.live_used]; } /* game_draw_loose_tile: draw one of the loose tiles, or ErrorTile if no wall */ Tile game_draw_loose_tile(Game *g) { if ( game_get_option_value(g,GODeadWall,NULL).optbool && g->wall.dead_end == g->wall.live_end ) return ErrorTile; /* we have a loose tile; adjust the wall */ g->wall.dead_end--; /* end of dead wall moves back */ if ( game_get_option_value(g,GODeadWall,NULL).optbool ) { /* Now if we are in the !DeadWall16 (non-Millington case), extend the dead wall if necessary. It's OK if this goes past the beginning of the live wall; we'll get an error on next live draw, which is how we understand it. (The alternative would be to say there has to be a washout now, which I don't like.) */ if ( !game_get_option_value(g,GODeadWall16,NULL).optbool && ( g->wall.dead_end - g->wall.live_end < 13) ) g->wall.live_end -= 2; /* if we're in the millington case, do nothing */ } else { /* we're not playing with a dead wall. So the live end moves back as well */ g->wall.live_end--; } return g->wall.tiles[g->wall.dead_end]; /* the tile at the end */ } /* game_peek_loose_tile: look at next loose tile or ErrorTile if no wall */ Tile game_peek_loose_tile(Game *g) { if ( game_get_option_value(g,GODeadWall,NULL).optbool ) { if ( g->wall.dead_end == g->wall.live_end ) return ErrorTile; } else { if ( g->wall.dead_end == g->wall.live_used ) return ErrorTile; } return g->wall.tiles[g->wall.dead_end-1]; } /* defines used in the following table */ #define DBL 10000 #define LIM 100000000 static GameOptionEntry default_optiontable[] = { /* a real option has a name. This is just a filler. */ { GOUnknown, "" , GOTBool, 1000000, 0, { 0 }, "", 0 }, { GOTimeout, "Timeout" , GOTNat, 0, 0, { 15 }, "time limit for claims",0 }, { GOTimeoutGrace, "TimeoutGrace", GOTNat, 0, 0, { 5 }, "grace period when clients handle timeouts" , 0}, { GOScoreLimit, "ScoreLimit", GOTNat, 0, 0, { 1000 }, "limit on hand score", 0 }, { GONoLimit, "NoLimit", GOTBool, 0, 0, { 0 }, "no-limit game", 0 }, { GOMahJongScore, "MahJongScore", GOTScore, 0, 0, { 20 }, "base score for going out", 0 }, { GOSevenPairs, "SevenPairs", GOTBool, 1020, 0, { 0 }, "seven pairs hand allowed", 0 }, { GOSevenPairsVal, "SevenPairsVal", GOTScore, 1020, 0, { 20 }, "score for a seven pair hand", 0 }, { GOFlowers, "Flowers", GOTBool, 1020, 0, { 1 }, "play using flowers and seasons", 0 }, { GOFlowersLoose, "FlowersLoose", GOTBool, 0, 0, { 0 }, "flowers replaced by loose tiles", 0 }, { GOFlowersOwnEach, "FlowersOwnEach", GOTScore, 0, 0, { 0 }, "score for each own flower or season", 0 }, { GOFlowersOwnBoth, "FlowersOwnBoth", GOTScore, 0, 0, { DBL }, "score for own flower and own season", 0 }, { GOFlowersBouquet, "FlowersBouquet", GOTScore, 0, 0, { DBL }, "score for all four flowers or all four seasons", 0 }, { GODeadWall, "DeadWall", GOTBool, 1020, 0, { 1 }, "there is a dead wall", 0 }, { GODeadWall16, "DeadWall16", GOTBool, 1020, 0, { 1 }, "dead wall is 16 tiles, unreplenished", 0 }, { GOConcealedFully, "ConcealedFully", GOTScore, 0, 0, { DBL }, "score for fully concealed hand", 0 }, { GOConcealedAlmost, "ConcealedAlmost", GOTScore, 0, 0, { 0 }, "score for almost concealed hand", 0 }, { GOLosersPurity, "LosersPurity", GOTBool, 0, 0, { 0 }, "losing hands score doubles for pure, concealed etc.", 0 }, { GOKongHas3Types, "KongHas3Types", GOTBool, 1034, 0, { 0 }, "claimed kongs count as concealed for doubling", 0 }, { GOEastDoubles, "EastDoubles", GOTBool, 0, 0, { 1 }, "east pays and receives double", 0 }, { GOLosersSettle, "LosersSettle", GOTBool, 0, 0, { 1 }, "losers pay each other", 0 }, { GODiscDoubles, "DiscDoubles", GOTBool, 0, 0, { 0 }, "the discarder pays double", 0 }, { GOShowOnWashout, "ShowOnWashout", GOTBool, 0, 0, { 0}, "tiles revealed on washout", 0 }, { GONumRounds, "NumRounds", GOTNat, 0, 0, { 4 }, "number of rounds to play", 0 }, { GOEnd, "End" , GOTBool, 0, 0, { 1 }, "end of option list", 0 } }; GameOptionTable game_default_optiontable = { default_optiontable, GONumOptions } ; /* game_clear_option_table: clear an option table, freeing the storage */ int game_clear_option_table(GameOptionTable *got) { free(got->options); got->options = NULL; got->numoptions = 0; return 0; } /* game_copy_option_table: copy old option table into new. Will refuse to work on new table with existing data. */ int game_copy_option_table(GameOptionTable *newt, GameOptionTable *oldt) { if (newt->options) { warn("game_copy_option_table called on non-empty target"); return 0; } newt->options = calloc(oldt->numoptions,oldt->numoptions*sizeof(GameOptionEntry)); if ( newt->options == NULL ) { warn("failed to allocate option table"); return 0; } memcpy(newt->options,oldt->options,oldt->numoptions*sizeof(GameOptionEntry)); newt->numoptions = oldt->numoptions; return 1; } /* game_set_options_from_defaults: set the option table to be a copy of the default, freeing any existing table; mark options enabled according to the value of g->protversion. Also convert Nat options to Int if the protocol version is low. */ int game_set_options_from_defaults(Game *g) { int i; game_clear_option_table(&g->option_table); g->option_table.options = calloc(GONumOptions,sizeof(GameOptionEntry)); if ( g->option_table.options == NULL ) { warn("failed to allocate option table"); return 0; } memcpy(g->option_table.options,default_optiontable,GONumOptions*sizeof(GameOptionEntry)); g->option_table.numoptions = GONumOptions; /* now go through the option list marking options enabled */ for ( i=0; i < g->option_table.numoptions; i++ ) { if ( g->protversion >= g->option_table.options[i].protversion ) g->option_table.options[i].enabled = 1; /* and convert nat to int */ if ( g->protversion < 1020 && g->option_table.options[i].type == GOTNat ) g->option_table.options[i].type = GOTInt; } return 1; } /* find an option entry in an option table, searching by integer (if known) or name (if known) */ GameOptionEntry *game_get_option_entry_from_table(GameOptionTable *t, GameOption option, char *name) { GameOptionEntry *goe; int i; /* usually, the option will be in the right slot, so check for this first */ if ( option && (option < (unsigned int)t->numoptions) && option == t->options[option].option ) return &t->options[option]; /* do we have this option in the table? */ for (i=0; inumoptions; i++) { goe = &t->options[i]; if ( (option && goe->option == option) /* we may have options that we don't know about, in which case we have to check the names */ || (!(option && goe->option) && name && strcmp(goe->name,name) == 0) ) return goe; } return NULL; } GameOptionEntry *game_get_option_entry(Game *g, GameOption option, char *name) { return game_get_option_entry_from_table(&g->option_table,option,name); } /* find an option entry in the default table, searching by integer (if known) or name */ GameOptionEntry *game_get_default_option_entry(GameOption option, char *name) { return game_get_option_entry_from_table(&game_default_optiontable, option,name); } /* see game.h for spec */ GameOptionValue game_get_option_value(Game *g, GameOption option, char *name) { GameOptionEntry *goe = NULL; if ( g ) goe = game_get_option_entry(g,option,name); if ( ! goe ) goe = game_get_default_option_entry(option,name); if ( ! goe ) return (GameOptionValue)0; if ( goe->type == GOTString ) return (GameOptionValue) goe->value.optstring; else return (GameOptionValue) goe->value.optint; } /* game_set_option_in_table: set an option. Function will allocate space as required. */ int game_set_option_in_table(GameOptionTable *t, GameOptionEntry *e) { GameOptionEntry *goe = NULL; int i; /* do we already have this option in the table? */ for (i=0; inumoptions; i++) { goe = &t->options[i]; if ( (goe->option && goe->option == e->option) /* we may have options that we don't know about, in which case we have to check the names */ || (goe->option == GOUnknown && e->option == GOUnknown && strcmp(goe->name,e->name) == 0) ) break; } if ( i == t->numoptions ) { /* allocate more space */ GameOptionEntry *ngoe; t->numoptions++; if ( t->options ) { ngoe = realloc(t->options,t->numoptions*sizeof(GameOptionEntry)); } else { ngoe = malloc(t->numoptions*sizeof(GameOptionEntry)); } if ( t == NULL ) { warn("unable to extend option table"); return 0; } t->options = ngoe; goe = &t->options[i]; /* fill in fields for new option */ goe->option = e->option; strmcpy(goe->name,e->name,16); goe->type = e->type; goe->protversion = e->protversion; strmcpy(goe->desc,e->desc,128); goe->userdata = NULL; /* NOT copied */ } else { /* do a typecheck. */ if ( goe->type != e->type ) { warn("type clash when setting option"); return 0; } } /* set fields that might have changed */ goe->enabled = e->enabled; memcpy(goe->value.optstring,e->value.optstring,sizeof(e->value.optstring)); /* don't set description if e has null value */ if ( e->desc[0] ) { strmcpy(goe->desc,e->desc,128); } return 1; } int game_set_option(Game *g, GameOptionEntry *e) { if ( ! game_set_option_in_table(&g->option_table,e) ) { g->cmsg_err = "Error setting option"; return 0; } return 1; } /* act on CMsg: see spec in header file */ /* convenience macro to setup s and p for the id */ #define setups p = game_id_to_player(g,m->id); \ s = game_id_to_seat(g,m->id); affected_id = m->id int game_handle_cmsg(Game *g, CMsgMsg *cm) { PlayerP p; seats s; int i; const int chk = g->cmsg_check; int affected_id; MJSpecialHandFlags mjspecflags; /* special hands allowed in this game */ affected_id = 0; mjspecflags = 0; if ( game_get_option_value(g,GOSevenPairs,NULL).optbool ) mjspecflags |= MJSevenPairs; switch ( cm->type ) { case CMsgReconnect: /* nothing to do - we'll be destroyed in a moment anyway */ return 0; case CMsgConnectReply: { CMsgConnectReplyMsg *m = (CMsgConnectReplyMsg *)cm; /* It is assumed here that the game seats point to four player structures, and that "we" are in the east seat. Clients are responsible for making sure this is true. */ if ( m->id == 0 ) return affected_id; /* connection refused */ set_player_id(g->players[0],m->id); affected_id = m->id; return affected_id; } case CMsgPlayer: { CMsgPlayerMsg *m = (CMsgPlayerMsg *)cm; /* do we already know this player? */ setups; if ( p ) { /* after protocol version 1034, a player message with an empty name is defined to mean "delete this player". Note that we have to refer to the global protocol_version, since the game protversion field will not be set at the only plausible times to get this command */ if ( (m->name && m->name[0]) || protocol_version <= 1034 ) { set_player_name(p,m->name); } else { /* player message with empty name means delete it */ /* if this happens at any time other than during initial connection, something's wrong. We do not allow the deletion of a player from a game. (Maybe we should, to allow substitution, but not yet!) At pversion 1038, this was changed to allow deletion until the game actually starts, rather than only until the game is set up. */ if ( game_has_started(g) ) { warn("Can't delete a player after game has started"); return -1; } initialize_player(p); } } else { if ( protocol_version > 1034 && ! ( m->name && m->name[0] ) ) { warn("trying to delete non-existent player"); return -1; } /* find first free player */ p = game_id_to_player(g,0); if ( ! p ) { warn("received Player message when table full"); return -1; } initialize_player(p); set_player_id(p,m->id); set_player_name(p,m->name); } } return affected_id; case CMsgGame: { CMsgGameMsg *m = (CMsgGameMsg *)cm; PlayerP players[NUM_SEATS]; /* First, we need to rearrange the players so they're in the given seats */ players[east] = game_id_to_player(g,m->east); players[south] = game_id_to_player(g,m->south); players[west] = game_id_to_player(g,m->west); players[north] = game_id_to_player(g,m->north); for ( i=0; i < NUM_SEATS; i++ ) { if ( !players[i] ) { g->cmsg_err = "Starting game with unknown player"; return -1; } g->players[i] = players[i]; } /* now set the round */ g->round = m->round; /* and so on */ g->hands_as_east = m->hands_as_east; g->firsteast = m->firsteast; set_player_cumulative_score(g->players[east],m->east_score); set_player_cumulative_score(g->players[south],m->south_score); set_player_cumulative_score(g->players[west],m->west_score); set_player_cumulative_score(g->players[north],m->north_score); /* initialize state */ g->state = HandComplete; g->player = noseat; g->serial = 0; for (i=0;iclaims[i] = 0; g->protversion = m->protversion; g->manager = m->manager; game_set_options_from_defaults(g); /* and games always start inactive */ g->active = 0; } return affected_id; case CMsgInfoTiles: /* Note that this always forces the player into compliance. It's up to the client not to call this routine when it's not wanted */ { CMsgInfoTilesMsg *m = (CMsgInfoTilesMsg *)cm; setups; set_player_tiles(p,m->tileinfo); } return affected_id; case CMsgNewRound: { CMsgNewRoundMsg *m = (CMsgNewRoundMsg *)cm; g->round = m->round; g->hands_as_east = 0; /* superfluous */ } return affected_id; case CMsgPause: { CMsgPauseMsg *m = (CMsgPauseMsg *)cm; int i; seats s; if ( g->paused ) { /* it is legitimate to start a new pause before one is complete */ free(g->paused); } g->paused = (char *)malloc(strlen(m->reason)+1); if ( g->paused == NULL ) { warn("unable to malloc space for pause reason"); return -2; } strcpy(g->paused,m->reason); for ( i=0; i < NUM_SEATS; i++) g->ready[i] = 0; s = game_id_to_seat(g,m->exempt); if ( s != noseat ) g->ready[s] = 1; affected_id = 0; /* affects everybody, really */ } return affected_id; case CMsgPlayerReady: { CMsgPlayerReadyMsg *m = (CMsgPlayerReadyMsg *)cm; int i,ready; setups; affected_id = m->id; if ( g->paused ) { g->ready[s] = 1; ready = 1; for ( i=0 ; i < NUM_SEATS; i++ ) ready = (ready && g->ready[i]); if ( ready ) { free(g->paused); g->paused = NULL; } } } return affected_id; case CMsgNewHand: { CMsgNewHandMsg *m = (CMsgNewHandMsg *)cm; PlayerP players[NUM_SEATS]; int n; if ( chk ) { if ( g->state != HandComplete ) { g->cmsg_err = "Still playing current hand"; return -1; } } /* If east has changed, reset hands_as_east */ if ( m->east != g->players[east]->id ) g->hands_as_east = 0; /* first we have to rotate the players so that the current east is correct. */ for (i=0; i < NUM_SEATS; i++) players[i] = g->players[i]; n = game_id_to_seat(g,m->east); for (i=0; i < NUM_SEATS; i++) g->players[i] = players[(i+n)%NUM_SEATS]; /* game state */ g->state = Dealing; /* reset the hand scores and clear the flags */ for ( i = 0 ; i < NUM_SEATS ; i++ ) { /* Warning: this knows TileWind = seats+1 */ player_newhand(g->players[i],i+1); } /* clear game flags */ g->flags = 0; g->konging = NotKonging; g->wall.live_used = 0; g->wall.size = game_get_option_value(g,GOFlowers,NULL).optbool ? 144 : 136; g->wall.dead_end = g->wall.size; g->wall.live_end = g->wall.dead_end - (game_get_option_value(g,GODeadWall,NULL).optbool ? (game_get_option_value(g,GODeadWall16,NULL).optbool ? 16 : 14) : 0) ; g->serial = 0; /* must be initialized */ for ( i = 0 ; i < MaxTile ; i++ ) { g->exposed_tile_count[i] = 0; g->discarded_tile_count[i] = 0; } } return affected_id; case CMsgPlayerDraws: { CMsgPlayerDrawsMsg *m = (CMsgPlayerDrawsMsg *)cm; Tile t; setups; affected_id = m->id; if ( chk ) { /* can this player draw? */ if ( g->state == Dealing ) { /* ought to check, but currently don't */ } else if ( g->state == Discarded ) { seats i; seats ds = g->player; /* legal if it's our turn and all claims have been received */ if ( s != nextseat(ds) ) { g->cmsg_err = "Drawing out of turn"; return -1; } for ( i = 0 ; i < NUM_SEATS ; i++ ) if ( i != ds && g->claims[i] == UnknownClaim ) { g->cmsg_err = "Drawing before all claims received"; return -1; } /* better not be a claim */ for ( i = 0 ; i < NUM_SEATS ; i++ ) if ( i != ds && g->claims[i] > NoClaim ) { g->cmsg_err = "Somebody has claimed the discard"; return -1; } } else if ( g->state == DeclaringSpecials ) { /* legal if it's our turn and we need a tile */ if ( s != g->player ) { g->cmsg_err = "Can't draw out of turn"; return -1; } if ( g->needs != FromWall ) { g->cmsg_err = "Don't need a tile now"; return -1; } } else if ( g->state == Discarding ) { /* only legal if we're drawing a replacement for a special under the rules where this comes from the live wall */ if ( g->needs != FromWall ) { g->cmsg_err = "Already drawn or claimed a tile"; return -1; } } else { /* In any other state */ g->cmsg_err = "Can't draw a tile now"; return -1; } } /* end of legality checking */ t = game_draw_tile(g); if ( t == ErrorTile ) { g->cmsg_err = "Wall exhausted"; return -1; } if ( chk && !teq(t,m->tile) ) { g->cmsg_err = "Drawing tile not the first in wall"; warn(g->cmsg_err); return -2; } /* if this fails, we're in a mess */ if ( ! player_draws_tile(p,m->tile) ) { g->cmsg_err = "Unexpected failure drawing tile (player)"; warn(g->cmsg_err); return -2; } /* next state of game */ if ( g->state == Dealing ) { /* if this is east, store the tile */ if ( s == east ) g->tile = m->tile; /* if deal is complete ... */ if ( g->players[east]->num_concealed == MAX_CONCEALED && g->players[south]->num_concealed == MAX_CONCEALED-1 && g->players[west]->num_concealed == MAX_CONCEALED-1 && g->players[north]->num_concealed == MAX_CONCEALED-1 ) { g->state = DeclaringSpecials; /* g->tile was set before if nec */ g->needs = FromNone; g->player = east; g->whence = FromWall; } } else { if ( g->state == Discarded ) { g->state = Discarding; } g->tile = m->tile; g->needs = FromNone; g->player = s; g->whence = FromWall; g->konging = NotKonging; } } return affected_id; case CMsgPlayerDrawsLoose: { CMsgPlayerDrawsLooseMsg *m = (CMsgPlayerDrawsLooseMsg *)cm; Tile t; setups; affected_id = m->id; if ( chk ) { /* can this player draw a loose tile? */ /* can only happen when it's currently our turn */ if ( s != g->player ) { g->cmsg_err = "Can't draw a loose tile out of turn"; return -1; } else if ( (g->state == Discarding || g->state == DeclaringSpecials) && g->needs == FromLoose ) { /* OK */ } else { /* In any other state */ g->cmsg_err = "Can't draw a tile now"; return -1; } } /* end of legality checking */ t = game_draw_loose_tile(g); if ( t == ErrorTile ) { g->cmsg_err = "Dead wall exhausted"; return -1; } if ( chk && !teq(t,m->tile) ) { g->cmsg_err = "Drawing tile not the first loose tile"; warn(g->cmsg_err); return -2; } /* if this fails, we're in a mess */ if ( ! player_draws_loose_tile(p,m->tile) ) { g->cmsg_err = "Unexpected failure drawing tile (player)"; warn(g->cmsg_err); return -2; } /* next state of game is unchanged apart from tile info */ g->needs = FromNone; g->whence = FromLoose; g->tile = m->tile; g->konging = NotKonging; } return affected_id; case CMsgPlayerDeclaresSpecial: { CMsgPlayerDeclaresSpecialMsg *m = (CMsgPlayerDeclaresSpecialMsg *)cm; setups; affected_id = m->id; if ( chk ) { /* Specials may be declared during the initial phase, or if it is the player's turn to discard, and at no other time. Morever, all rules I've seen say that specials can only be declared after drawing from the wall. Silly, but there it is. Pre-release version didn't have this, so we need to keep it for compatability. */ if ( g->state == DeclaringSpecials || (g->state == Discarding && (g->protversion < 1010 || g->whence != FromDiscard) ) ) { if ( g->player != s ) { g->cmsg_err = "Can only declare in turn"; return -1; } } else { g->cmsg_err = "Can't discard specials now"; return -1; } } if ( m->tile == HiddenTile ) { if ( chk && g->state == Discarding ) { /* this shouldn't happen. Although it's harmless, we will nonetheless return an error */ g->cmsg_err = "Can't declare blank special now"; return -1; } if ( s == north ) { g->state = Discarding; g->konging = NotKonging; g->needs = FromNone; /* we need to set whence. It could be from loose or wall, according to what happened during declaring specials. However, if east is going to go out immediately, it doesn't matter, since that's a limit anyway. So we just set it to FromWall. FIXME: in fact the only reason we need to do this is because the controller deals itself, rather than passing cmsgs to the game. Sometime we should fix the controller. But that probably means making this routine fill in details of cmsgs: which seems right anyway. */ g->whence = FromWall; g->player = east; } else { /* why do we set the state? So that resumption works: otherwise, there's no cue to enter the ds state */ g->state = DeclaringSpecials; g->player = nextseat(s); } } else { if ( ! player_declares_special(p,m->tile) ) { /* This can only fail if the player doesn't hold the tile */ /* Actually, that's not true. It can also fail if the "hiddenness" is inconsistent. In this case, player will scream anyway. */ g->cmsg_err = "Don't have that special"; return -1; } if ( game_get_option_value(g,GOFlowersLoose,NULL).optbool ) g->needs = FromLoose; else g->needs = FromWall; g->exposed_tile_count[m->tile]++; /* who's counting specials ? */ } } return affected_id; case CMsgStartPlay: affected_id = ((CMsgStartPlayMsg *)cm)->id; g->active = 1; return affected_id; case CMsgStopPlay: g->active = 0; return affected_id; case CMsgPlayerDiscards: { CMsgPlayerDiscardsMsg *m = (CMsgPlayerDiscardsMsg *)cm; int nodiscard; setups; affected_id = m->id; if ( chk ) { /* if it's not time to discard, return error */ if ( ! (g->state == Discarding && g->player == s) ) { g->cmsg_err = "Can't discard now"; return -1; } /* give a more helpful error message when the player is waiting for a tile */ if ( g->needs ) { g->cmsg_err = "Need to draw a tile before discarding"; return -1; } /* players aren't allowed to discard if the game is paused */ if ( g->paused ) { g->cmsg_err = "Can't discard until everybody's ready"; return -1; } /* check legitimacy of calling declaration */ /* must be no sets declared */ if ( m->calling && p->num_concealed < 14 ) { g->cmsg_err = "Must have concealed hand to declare calling"; return -1; } /* multiple calling declarations are silly */ if ( m->calling && pflag(p,Calling) ) { g->cmsg_err = "Already calling"; return -1; } /* at present, only original call allowed */ if ( m->calling && ! pflag(p,NoDiscard) ) { g->cmsg_err = "Only Original Call declaration allowed"; return -1; } /* if player is calling, can only discard the tile drawn. It is supposed to be impossible for a calling player to have claimed from a discard; but just as a precaution we'll check for it and panic. */ if ( pflag(p,Calling) ) { if ( g->whence == FromDiscard ) { g->cmsg_err = "Calling player discarding after claim??"; warn("Calling player discarding after claim"); return -2; } if ( m->tile != g->tile ) { g->cmsg_err = "Calling: can't discard that tile"; return -1; } } } nodiscard = pflag(p,NoDiscard); /* save as will be cleared by this */ if ( ! player_discards(p,m->tile) ) { g->cmsg_err = "You can't discard that tile"; return -1; } g->state = Discarded; g->player = s; g->tile = m->tile; g->serial = m->discard; for ( i = 0 ; i < NUM_SEATS ; i++ ) g->claims[i] = UnknownClaim; g->chowpending = 0; game_clearflag(g,GFKong); game_clearflag(g,GFKongUponKong); game_clearflag(g,GFDangerousDiscard); game_clearflag(g,GFNoChoice); if ( m->calling ) { psetflag(p,Calling); if ( nodiscard ) { psetflag(p,OriginalCall); } } g->exposed_tile_count[m->tile]++; g->discarded_tile_count[m->tile]++; return affected_id; } case CMsgPlayerDoesntClaim: { CMsgPlayerDoesntClaimMsg *m = (CMsgPlayerDoesntClaimMsg *)cm; setups; affected_id = m->id; /* because clients might easily send noclaims after a timeout, we do not return error for bad noclaims. However, this is a problem for entirely human clients, if there is no timeout, since then a typo will hang the game. FIXME: we should return error if the game does not have a time out set. */ if ( chk ) { /* noclaim is legitimate while konging */ if ( ! ( (g->state == Discarding || g->state == DeclaringSpecials) && g->konging ) ) { if ( g->state != Discarded ) { g->cmsg_err = "No discard to pass on"; return affected_id; } if ( g->serial != m->discard ) { g->cmsg_err = "Too late to pass on that discard"; return affected_id;; } } } /* We should only record this no claim if the discard serial is correct. (Otherwise it's probably a duplicate caused by a race condition.) If it doesn't match, just ignore it, don't return error. */ if ( g->serial == m->discard ) g->claims[s] = NoClaim; return affected_id; } case CMsgDangerousDiscard: { CMsgDangerousDiscardMsg *m = (CMsgDangerousDiscardMsg *)cm; setups; affected_id = m->id; if ( chk ) { if ( m->discard != g->serial ) { g->cmsg_err = "Not current discard (dangerous discard message)"; return -1; } if ( m->id != g->players[g->supplier]->id ) { g->cmsg_err = "Dangerous discarder doesn't match supplier"; return -1; } } game_setflag(g,GFDangerousDiscard); if ( m->nochoice ) game_setflag(g,GFNoChoice); return affected_id; } case CMsgPlayerClaimsPung: { CMsgPlayerClaimsPungMsg *m = (CMsgPlayerClaimsPungMsg *)cm; setups; affected_id = m->id; if ( chk ) { if ( g->state != Discarded ) { g->cmsg_err = "Can't pung now"; return -1; } if ( g->serial != m->discard ) { g->cmsg_err = "Too late: next discard has been made"; return -1; } if ( g->player == s ) { g->cmsg_err = "Can't claim own discard!"; return -1; } if ( ! player_can_pung(p,g->tile) ) { g->cmsg_err = "Can't pung that tile"; return -1; } if ( pflag(p,Calling) ) { g->cmsg_err = "Calling players can only claim Mah-Jong"; return -1; } } /* end of checking */ g->claims[s] = PungClaim; return affected_id; } case CMsgPlayerClaimsKong: { CMsgPlayerClaimsKongMsg *m = (CMsgPlayerClaimsKongMsg *)cm; setups; affected_id = m->id; if ( chk ) { if ( g->state != Discarded ) { g->cmsg_err = "Can't kong now"; return -1; } if ( g->serial != m->discard ) { g->cmsg_err = "Too late: next discard has been made"; return -1; } if ( g->player == s ) { g->cmsg_err = "Can't claim own discard!"; return -1; } if ( ! player_can_kong(p,g->tile) ) { g->cmsg_err = "Can't kong that tile"; return -1; } if ( pflag(p,Calling) ) { g->cmsg_err = "Calling players can only claim Mah-Jong"; return -1; } } /* end of checking */ g->claims[s] = KongClaim; return affected_id; } case CMsgPlayerClaimsChow: { CMsgPlayerClaimsChowMsg *m = (CMsgPlayerClaimsChowMsg *)cm; setups; affected_id = m->id; if ( chk ) { if ( g->state != Discarded ) { g->cmsg_err = "Can't chow now"; return -1; } if ( g->serial != m->discard ) { g->cmsg_err = "Too late: next discard has been made"; return -1; } if ( g->player == s ) { g->cmsg_err = "Can't claim own discard!"; return -1; } if ( s != nextseat(g->player) ) { g->cmsg_err = "Can't chow out of turn"; return -1; } if ( ! player_can_chow(p,g->tile,m->cpos) ) { g->cmsg_err = "Can't make that chow"; return -1; } if ( pflag(p,Calling) ) { g->cmsg_err = "Calling players can only claim Mah-Jong"; return -1; } } /* end of checking */ g->claims[s] = ChowClaim; g->cpos = m->cpos; return affected_id; } case CMsgPlayerClaimsMahJong: { CMsgPlayerClaimsMahJongMsg *m = (CMsgPlayerClaimsMahJongMsg *)cm; setups; affected_id = m->id; if ( g->state == Discarded ) { if ( chk ) { if ( g->serial != m->discard ) { g->cmsg_err = "Too late: next discard has been made"; return -1; } if ( g->player == s ) { g->cmsg_err = "Can't claim own discard!"; return -1; } if ( ! player_can_mah_jong(p,g->tile,mjspecflags) ) { g->cmsg_err = "Can't mah-jong with that tile"; return -1; } } g->claims[s] = MahJongClaim; } else if ( (g->state == Discarding || g->state == DeclaringSpecials) && g->konging ) { /* trying to rob a kong */ /* arguably one should be able to abandon one's kong and claim mahjong instead. However, at present we have no provision for retracting other claims, so we don't have it here either. */ if ( chk ) { if ( g->serial != m->discard ) { g->cmsg_err = "Too late: next discard has been made"; return -1; } if ( g->player == s ) { g->cmsg_err = "Can't rob own kong"; return -1; } if ( ! player_can_mah_jong(p,g->tile,mjspecflags) ) { g->cmsg_err = "Can't mah-jong with that tile"; return -1; } /* closed kongs can be robbed only for thirteen unique wonders */ if ( g->konging == DeclaringKong ) { if ( ! player_can_thirteen_wonders(p,g->tile) ) { g->cmsg_err = "Can only rob a closed kong for Thirteen Wonders"; return -1; } } /* in principle, we ought to check that the robbee actually has the kong. However, that would be a consistency error, and in that case we'll die soon enough anyway! */ } g->claims[s] = MahJongClaim; } else { g->cmsg_err = "Nothing to claim"; return -1; } return affected_id; } case CMsgClaimDenied: return affected_id; /* nothing to do */ case CMsgStateSaved: return affected_id; /* nothing to do */ case CMsgPlayerPairs: { CMsgPlayerPairsMsg *m = (CMsgPlayerPairsMsg *)cm; setups; affected_id = m->id; if ( chk ) { /* sanity checking */ /* Note that normally we assume the legality of the pair was previously checked at claim time */ if ( ! ( g->state == MahJonging && g->mjpending && s == g->player ) ) { g->cmsg_err = "Nothing to pair"; return -1; } if ( m->tile != g->tile ) { g->cmsg_err = "Paired tile isn't discard"; return -2; } } if ( g->state == MahJonging ) { /* checking is complex: not only must the player be able make the pair, but it must then be able to complete the hand */ Player cp; if ( chk ) copy_player(&cp,p); /* in case next fails */ if ( ! player_pairs(p,m->tile) ) { g->cmsg_err = "Can't pair that tile"; return -1; } if ( chk && ! player_can_mah_jong(p,HiddenTile,mjspecflags) ) { g->cmsg_err = "Can't go out with that pair"; copy_player(p,&cp); /* restore status quo */ return -1; } g->mjpending = 0; if ( chk ) mark_dangerous_discards(g); } else { g->cmsg_err = "Nothing to pair"; return -1; } return affected_id; } /* this is a bit awkward: the discard is exposed, so really the special set is exposed. But we have no means of putting a special set into the player's hand. I think we will rely on the external logic using the whence game field to distinguish */ case CMsgPlayerSpecialSet: { CMsgPlayerSpecialSetMsg *m = (CMsgPlayerSpecialSetMsg *)cm; setups; affected_id = m->id; if ( chk ) { /* sanity checking */ /* Note that normally we assume the legality of the set was previously checked at claim time */ if ( ! ( g->state == MahJonging && g->mjpending && s == g->player ) ) { g->cmsg_err = "Nothing to claim"; return -1; } if ( m->tile != g->tile ) { g->cmsg_err = "Claimed tile isn't discard"; return -2; } } if ( g->state == MahJonging ) { /* It is a rule that the special set must be formed last, and use up all the remaining tiles */ Player cp; int res; if ( chk ) copy_player(&cp,p); /* in case next fails */ if ( chk ) { /* only currently recognized special hand is 13 wonders */ if ( ! player_can_thirteen_wonders(p,g->tile) ) { g->cmsg_err = "No special hand to form"; return -1; } } /* first we use the given tiles as in a ShowTiles */ res = player_shows_tiles(p,m->tiles); if ( ! res ) { /* we are in deep trouble */ g->cmsg_err = "player_shows_tiles failed"; return -2; } if ( chk ) { /* better be the same number of tiles before and after */ if ( p->num_concealed != cp.num_concealed ) { g->cmsg_err = "Wrong number of tiles in special set"; copy_player(p,&cp); /* restore status quo */ return -1; } } /* now we'll add the discard to the hand */ if ( ! player_draws_tile(p,m->tile) ) { /* How can this happen ? */ g->cmsg_err = "Error in player_draws_tile"; return -2; } if ( chk && ! player_can_mah_jong(p,HiddenTile,mjspecflags) ) { g->cmsg_err = "No mah jong hand!"; copy_player(p,&cp); /* restore status quo */ return -1; } g->mjpending = 0; } else { g->cmsg_err = "Can't declare special set now"; return -1; } return affected_id; } case CMsgPlayerPungs: { CMsgPlayerPungsMsg *m = (CMsgPlayerPungsMsg *)cm; setups; affected_id = m->id; if ( chk ) { /* sanity checking */ /* Note that normally we assume the legality of the pung was previously checked at claim time */ if ( ! (g->state == Discarded || (g->state == MahJonging && g->mjpending && g->player == s) ) ) { g->cmsg_err = "Nothing to pung"; return -1; } if ( m->tile != g->tile ) { g->cmsg_err = "Punged tile isn't discard"; return -2; } } /* what happens now depends on whether this is a claim for MahJong or not */ if ( g->state == Discarded ) { if ( ! player_pungs(p,m->tile) ) { g->cmsg_err = "Can't pung that tile"; return -1; } g->state = Discarding; g->konging = NotKonging; g->tile = m->tile; g->needs = FromNone; g->supplier = g->player; g->player = s; g->whence = FromDiscard; g->exposed_tile_count[m->tile] += 2; } else if ( g->state == MahJonging ) { /* checking is more complex: not only must the player be able make the pung, but it must then be able to complete the hand */ Player cp; if ( chk ) copy_player(&cp,p); /* in case next fails */ if ( ! player_pungs(p,m->tile) ) { g->cmsg_err = "Can't pung that tile"; return -1; } if ( chk && ! player_can_mah_jong(p,HiddenTile,mjspecflags) ) { g->cmsg_err = "Can't go out with that pung"; copy_player(p,&cp); /* restore status quo */ return -1; } g->mjpending = 0; } else { g->cmsg_err = "Nothing to chow"; return -1; } if ( chk ) { mark_dangerous_discards(g); set_danger_flags(g,p); /* the player may now be dangerous */ } return affected_id; } case CMsgPlayerKongs: { CMsgPlayerKongsMsg *m = (CMsgPlayerKongsMsg *)cm; setups; affected_id = m->id; if ( chk ) { /* we'll assume the legality of the claim was checked */ if ( g->state != Discarded ) { g->cmsg_err = "Nothing to kong"; return -1; } } if ( ! player_kongs(p,m->tile) ) { g->cmsg_err = "Can't kong that tile"; return -2; /* This is a consistency error. */ } g->state = Discarding; g->needs = FromLoose; g->supplier = g->player; g->player = s; g->konging = NotKonging; /* yes! This is not robbable. */ if ( game_flag(g,GFKong) ) { game_setflag(g,GFKongUponKong); } game_setflag(g,GFKong); g->exposed_tile_count[m->tile] += 3; if ( chk ) set_danger_flags(g,p); /* the player may now be dangerous */ return affected_id; } case CMsgPlayerChows: { CMsgPlayerChowsMsg *m = (CMsgPlayerChowsMsg *)cm; setups; affected_id = m->id; if ( chk ) { /* sanity checking */ /* Note that normally we assume the legality of the chow was previously checked at claim time */ if ( ! (g->state == Discarded || (g->state == MahJonging && g->mjpending && g->player == s) ) ) { g->cmsg_err = "Nothing to chow"; return -1; } if ( m->tile != g->tile ) { g->cmsg_err = "Chowed tile isn't discard"; return -2; } } /* if the position is AnyPos, then this is addressed to a successful claimant to tell it to specify the position. There is therefore nothing to do here, except set the pending flag. Oops! Actually, if we're mah-jonging, we have to check that the player can actually go out by chowing the tile. */ if ( m->cpos == AnyPos ) { if ( chk && g->state == MahJonging ) { /* checking is more complex: not only must the player be able make the chow in some position, but it must then be able to complete the hand */ ChowPosition pos; int ok = 0; for ( pos = Lower; pos <= Upper; pos++ ) { Player cp; copy_player(&cp,p); if ( ! player_chows(&cp,m->tile,pos) ) { continue; } if ( player_can_mah_jong(&cp,HiddenTile,mjspecflags) ) { ok = 1; break; } } if ( ! ok ) { g->cmsg_err = "Can't go out by chowing"; return -1; } } g->chowpending = 1; return affected_id; } /* is there a Mah Jong pending? */ if ( g->state == Discarded ) { if ( ! player_chows(p,m->tile,m->cpos) ) { g->cmsg_err = "Can't chow that tile that way"; return -1; } g->state = Discarding; g->konging = NotKonging; g->tile = m->tile; g->needs = FromNone; g->supplier = g->player; g->player = s; g->whence = FromDiscard; /* g->exposed_tile_count[m->tile] is unchanged */ switch ( m->cpos ) { case Lower: g->exposed_tile_count[m->tile+1]++; g->exposed_tile_count[m->tile+2]++; break; case Middle: g->exposed_tile_count[m->tile+1]++; g->exposed_tile_count[m->tile-1]++; break; case Upper: g->exposed_tile_count[m->tile-1]++; g->exposed_tile_count[m->tile-2]++; break; default: warn(g->cmsg_err = "Impossible chowposition"); return -2; } } else if ( g->state == MahJonging ) { /* checking is more complex: not only must the player be able make the chow, but it must then be able to complete the hand */ Player cp; if ( chk ) copy_player(&cp,p); /* in case next fails */ if ( ! player_chows(p,m->tile,m->cpos) ) { g->cmsg_err = "Can't chow that tile that way"; return -1; } if ( chk && ! player_can_mah_jong(p,HiddenTile,mjspecflags) ) { g->cmsg_err = "Can't go out with that chow"; copy_player(p,&cp); /* restore status quo */ return -1; } g->chowpending = 0; g->mjpending = 0; } else { g->cmsg_err = "Nothing to chow"; return -1; } if ( chk ) { mark_dangerous_discards(g); set_danger_flags(g,p); /* the player may now be dangerous */ } return affected_id; } case CMsgWashOut: g->state = HandComplete; g->player = noseat; for (i=0;iclaims[i] = 0; return affected_id; case CMsgPlayerMahJongs: { CMsgPlayerMahJongsMsg *m = (CMsgPlayerMahJongsMsg *)cm; setups; affected_id = m->id; if ( m->tile != HiddenTile ) { /* should be currently in the Discarding state */ if ( chk ) { if ( ! (g->state == Discarding && g->player == s ) ) { g->cmsg_err = "Don't have a complete hand"; return -1; } if ( !player_can_mah_jong(p,HiddenTile,mjspecflags) ) { g->cmsg_err = "Don't have a Mah Jong"; return -1; } /* consistency check */ if ( g->tile != HiddenTile && m->tile != g->tile ) { g->cmsg_err = "Tile in mah-jong claim doesn't match drawn tile"; return -2; } } /* g->supplier is unchanged */ /* g->whence is unchanged */ /* g->player is unchanged */ /* g->tile is unchanged */ g->mjpending = 0; g->chowpending = 0; g->state = MahJonging; } else { /* We should be currently in the Discarded state */ if ( chk ) { if ( g->state != Discarded ) { g->cmsg_err = "No discard to use"; return -1; } } g->supplier = g->player; g->whence = FromDiscard; g->player = s; /* g->tile is unchanged */ g->mjpending = 1; g->chowpending = 0; g->state = MahJonging; } return affected_id; } case CMsgPlayerFormsClosedPair: { CMsgPlayerFormsClosedPairMsg *m = (CMsgPlayerFormsClosedPairMsg *)cm; setups; affected_id = m->id; if ( chk ) { if ( g->state != MahJonging ) { g->cmsg_err = "Not scoring now"; return -1; } } /* if this is a non-mah-jonging player, it's easy. */ if ( s != g->player ) { if ( ! player_forms_closed_pair(p,m->tile) ) { g->cmsg_err = "Don't have that pair" ; return -1; } } else { /* checking is complex: not only must the player be able make the pair, but it must then be able to complete the hand */ Player cp; if ( chk ) copy_player(&cp,p); /* in case next fails */ if ( ! player_forms_closed_pair(p,m->tile) ) { g->cmsg_err = "Don't have that pair"; return -1; } if ( chk && ! player_can_mah_jong(p,HiddenTile,mjspecflags) ) { g->cmsg_err = "Can't go out with that pair"; copy_player(p,&cp); /* restore status quo */ return -1; } if ( p->num_concealed == 0 ) psetflag(p,HandDeclared); } return affected_id; } case CMsgPlayerFormsClosedSpecialSet: { CMsgPlayerFormsClosedSpecialSetMsg *m = (CMsgPlayerFormsClosedSpecialSetMsg *)cm; setups; affected_id = m->id; if ( chk ) { /* sanity checking */ /* Note that normally we assume the legality of the set was previously checked at claim time */ if ( ! ( g->state == MahJonging && s == g->player ) ) { g->cmsg_err = "Not going out"; return -1; } } if ( g->state == MahJonging ) { /* It is a rule that the special set must be formed last, and use up all the remaining tiles */ Player cp; int res; if ( chk ) copy_player(&cp,p); /* in case next fails */ if ( chk ) { /* only currently recognized special hand is 13 wonders */ if ( ! player_can_thirteen_wonders(p,HiddenTile) ) { g->cmsg_err = "No special hand to form"; return -1; } } /* first we use the given tiles as in a ShowTiles */ res = player_shows_tiles(p,m->tiles); if ( ! res ) { /* we are in deep trouble */ g->cmsg_err = "player_shows_tiles failed"; return -2; } if ( chk ) { /* better be the same number of tiles before and after */ if ( p->num_concealed != cp.num_concealed ) { g->cmsg_err = "Wrong number of tiles in special set"; copy_player(p,&cp); /* restore status quo */ return -1; } } if ( chk && ! player_can_mah_jong(p,HiddenTile,mjspecflags) ) { g->cmsg_err = "No mah jong hand!"; copy_player(p,&cp); /* restore status quo */ return -1; } } else { g->cmsg_err = "Can't declare special set now"; return -1; } return affected_id; } case CMsgPlayerFormsClosedPung: { CMsgPlayerFormsClosedPungMsg *m = (CMsgPlayerFormsClosedPungMsg *)cm; setups; affected_id = m->id; if ( chk ) { if ( g->state != MahJonging ) { g->cmsg_err = "Not scoring now"; return -1; } } /* if this is a non-mah-jonging player, it's easy. */ if ( s != g->player ) { if ( ! player_forms_closed_pung(p,m->tile) ) { g->cmsg_err = "Don't have that pung" ; return -1; } } else { /* checking is complex: not only must the player be able make the pung, but it must then be able to complete the hand */ Player cp; if ( chk ) copy_player(&cp,p); /* in case next fails */ if ( ! player_forms_closed_pung(p,m->tile) ) { g->cmsg_err = "Don't have that pung"; return -1; } if ( chk && ! player_can_mah_jong(p,HiddenTile,mjspecflags) ) { g->cmsg_err = "Can't go out with that pung"; copy_player(p,&cp); /* restore status quo */ return -1; } if ( p->num_concealed == 0 ) psetflag(p,HandDeclared); } return affected_id; } case CMsgPlayerFormsClosedChow: { CMsgPlayerFormsClosedChowMsg *m = (CMsgPlayerFormsClosedChowMsg *)cm; setups; affected_id = m->id; if ( chk ) { if ( g->state != MahJonging ) { g->cmsg_err = "Not scoring now"; return -1; } } /* if this is a non-mah-jonging player, it's easy. */ if ( s != g->player ) { if ( ! player_forms_closed_chow(p,m->tile,Lower) ) { g->cmsg_err = "Don't have that chow" ; return -1; } } else { /* checking is complex: not only must the player be able make the chow, but it must then be able to complete the hand */ Player cp; if ( chk ) copy_player(&cp,p); /* in case next fails */ if ( ! player_forms_closed_chow(p,m->tile,Lower) ) { g->cmsg_err = "Don't have that chow"; return -1; } if ( chk && ! player_can_mah_jong(p,HiddenTile,mjspecflags) ) { g->cmsg_err = "Can't go out with that chow"; copy_player(p,&cp); /* restore status quo */ return -1; } if ( p->num_concealed == 0 ) psetflag(p,HandDeclared); } return affected_id; } case CMsgPlayerDeclaresClosedKong: { CMsgPlayerDeclaresClosedKongMsg *m = (CMsgPlayerDeclaresClosedKongMsg *)cm; setups; affected_id = m->id; if ( chk ) { if ( ! (((g->state == Discarding && (g->protversion < 1010 || g->whence != FromDiscard)) || g->state == DeclaringSpecials) && g->player == s ) ) { g->cmsg_err = "Can't declare a closed kong now"; return -1; } /* We have to use the test function here, because if either kong or tile drawing fails we must leave the game unchanged */ if ( !player_can_declare_closed_kong(p,m->tile) ) { g->cmsg_err = "Don't have that kong"; return -1; } } /* This can't fail in the chking case */ player_declares_closed_kong(p,m->tile); g->needs = FromLoose; g->konging = DeclaringKong; g->tile = m->tile; g->serial = m->discard; for (i=0;iclaims[i] = 0; if ( game_flag(g,GFKong) ) { game_setflag(g,GFKongUponKong); } game_setflag(g,GFKong); g->exposed_tile_count[m->tile] += 4; if ( chk ) set_danger_flags(g,p); /* the player may now be dangerous */ return affected_id; } case CMsgPlayerAddsToPung: { CMsgPlayerAddsToPungMsg *m = (CMsgPlayerAddsToPungMsg *)cm; setups; affected_id = m->id; if ( chk ) { if ( ! (g->state == Discarding && g->player == s /* before 1010, adding to pung was allowed anytime, which it shouldn't have been. Since version 1.11 (protocol 1110), it is allowed to add to a pung that you have just claimed. */ && (g->protversion < 1010 || g->whence != FromDiscard || (g->protversion >= 1110 /* the last discard was claimed by us for Pung */ /* these two conditions are not prima facie enough to guarantee that the last thing we did was actually to make the pung, but in fact they are. There's no way we can reach this state apart from that. */ && g->claims[s] == PungClaim /* the pung just claimed must be the one we're adding the tile to */ && g->tile == m->tile) ) ) ) { g->cmsg_err = "Can't make a kong now"; return -1; } /* We have to use the test function here, because if either kong or tile drawing fails we must leave the game unchanged */ if ( !player_can_add_to_pung(p,m->tile) ) { g->cmsg_err = "Don't have the tiles for that kong"; return -1; } } /* This can't fail in the chking case */ player_adds_to_pung(p,m->tile); g->needs = FromLoose; g->konging = AddingToPung; g->tile = m->tile; g->serial = m->discard; for (i=0;iclaims[i] = 0; if ( game_flag(g,GFKong) ) { game_setflag(g,GFKongUponKong); } game_setflag(g,GFKong); g->exposed_tile_count[m->tile]++ ; if ( chk ) set_danger_flags(g,p); /* the player may now be dangerous */ /* a kong can be robbed, so is like a discard */ game_clearflag(g,GFDangerousDiscard); game_clearflag(g,GFNoChoice); return affected_id; } case CMsgCanMahJong: /* this just a reply to a query, and requires no action */ return affected_id; case CMsgPlayerRobsKong: { CMsgPlayerRobsKongMsg *m = (CMsgPlayerRobsKongMsg *)cm; setups; affected_id = m->id; if ( chk ) { if ( ! ((g->state == Discarding || g->state == DeclaringSpecials) && g->konging ) ) { g->cmsg_err = "No kong to rob"; return -1; } if ( m->tile != g->tile ) { g->cmsg_err = "Claimed tile doesn't match that of kong"; return -1; } if ( ! player_can_mah_jong(p,m->tile,mjspecflags) ) { g->cmsg_err = "Can't mah-jong with that tile"; return -1; } } /* To rob a kong, we will rob the player, and then move into the MahJonging state with a pending "discard", since the winning player needs to say which set it's forming. */ if ( ! player_kong_is_robbed(g->players[g->player],m->tile) ) { g->cmsg_err = "Victim doesn't have that kong!"; return -1; } g->state = MahJonging; g->whence = FromRobbedKong; g->needs = FromNone; /* this was set by the AddToPung that we robbed */ g->supplier = g->player; g->player = s; g->tile = m->tile; g->mjpending = 1; g->chowpending = 0; g->konging = NotKonging; game_clearflag(g,GFKong); game_clearflag(g,GFKongUponKong); return affected_id; } case CMsgPlayerShowsTiles: { CMsgPlayerShowsTilesMsg *m = (CMsgPlayerShowsTilesMsg *)cm; setups; affected_id = m->id; if ( chk ) { /* it's not allowed to show tiles twice */ if ( pflag(p,HandDeclared) ) { g->cmsg_err = "Hand already declared"; return -1; } if ( p->num_concealed > 0 ) { /* the HandComplete case arises when tiles are revealed after a washout */ if ( g->state != MahJonging && g->state != HandComplete ) { g->cmsg_err = "Can't reveal your tiles now!"; return -1; } if ( g->state == MahJonging && s == g->player ) { g->cmsg_err = "Must finish making your sets"; return -1; } } } if ( ! player_shows_tiles(p,m->tiles) && chk ) { g->cmsg_err = "Couldn't show those tiles"; return -1; } /* the player_shows_tiles fn already set the HandDeclared flag */ return affected_id; } case CMsgSwapTile: { CMsgSwapTileMsg *m = (CMsgSwapTileMsg *)cm; setups; affected_id = m->id; if ( ! player_swap_tile(p,m->oldtile,m->newtile) ) { g->cmsg_err = p->err; return -1; } return affected_id; } case CMsgHandScore: { CMsgHandScoreMsg *m = (CMsgHandScoreMsg *)cm; setups; affected_id = m->id; set_player_hand_score(p,m->score); return affected_id; } case CMsgSettlement: { CMsgSettlementMsg *m = (CMsgSettlementMsg *)cm; change_player_cumulative_score(g->players[east],m->east); change_player_cumulative_score(g->players[south],m->south); change_player_cumulative_score(g->players[west],m->west); change_player_cumulative_score(g->players[north],m->north); g->state = HandComplete; for ( i=0;iclaims[i] = 0; /* g->player is still correct */ if ( g->player == east ) g->hands_as_east++; return affected_id; } case CMsgError: /* it is an error to pass this function an error */ g->cmsg_err = "Game shouldn't be given error messages"; return -1; case CMsgGameOver: /* nothing to do: up to client */ return affected_id; case CMsgGameOption: { CMsgGameOptionMsg *m = (CMsgGameOptionMsg *)cm; setups; if ( chk && (g->manager != 0) && g->manager != m->id ) { g->cmsg_err = "Not authorized to set game options"; return -1; } if ( game_set_option(g,&m->optentry) == 0 && chk ) { return -1; } return affected_id; } case CMsgChangeManager: { CMsgChangeManagerMsg *m = (CMsgChangeManagerMsg *)cm; setups; if ( chk && (g->manager != 0) && g->manager != m->id ) { g->cmsg_err = "Not authorized to change the manager"; return -1; } g->manager = m->manager; return affected_id; } case CMsgWall: { CMsgWallMsg *m = (CMsgWallMsg *)cm; int i; int n; char tn[5]; char *tp; Tile t; if ( g->state != HandComplete ) { g->cmsg_err = "Can only set the wall between hands"; return -1; } for ( i = 0, tp = m->wall ; i < MAX_WALL_SIZE ; i++, tp += n ) { if ( sscanf(tp,"%2s%n",tn,&n) == 0 ) break; t = tile_decode(tn); if ( t == ErrorTile ) { g->cmsg_err = "bad wall in WallMsg"; return -1; } g->wall.tiles[i] = t; } return 0; } case CMsgMessage: /* nothing to do for us */ return ((CMsgMessageMsg *)cm)->addressee; case CMsgComment: return 0; case CMsgPlayerOptionSet: /* This should be dealt with by client code, not us */ g->cmsg_err = "Player options are not dealt with by the game code"; return -1; case CMsgAuthReqd: /* This should be dealt with by client code, not us */ g->cmsg_err = "Authorization are not dealt with by the game code"; return -1; case CMsgRedirect: /* This should be dealt with by client code, not us */ g->cmsg_err = "Redirection is not dealt with by the game code"; return -1; } /* if we get to here, we were passed a bad CMsg type, which should be impossible */ warn("Unknown Controller message type %d",cm->type); return -2; } /* say whether a game has started or not */ int game_has_started(Game *g) { if ( g == NULL ) return 0; if ( g->round == UnknownWind ) return 0; if ( g->round != EastWind ) return 1; /* this next one is tricky: if there's no player in east, we've already deleted it, so it must be OK to delete others */ if ( g->players[east]->id == 0 ) return 0; if ( g->players[east]->id != g->firsteast ) return 1; if ( g->hands_as_east > 0 ) return 1; if ( g->state != HandComplete ) return 1; return 0; } /* this internal function sets the danger signal flags for a player. It does NOT deal with the DangerEnd flag. The game argument is currently unused. It should be called whenever a player declares a set. */ static void set_danger_flags(Game *g UNUSED, PlayerP p) { int i,s; int numchows,numpungs,numkongs,allhonours, allterminals,dragonsets,windsets, allgreen,allbamboo,allcharacter,allcircle, allbamboohonour,allcharacterhonour,allcirclehonour; numchows = 0; numkongs = 0; numpungs = 0; allterminals = 1; allhonours = 1; windsets = 0; dragonsets = 0; allgreen = 1; allbamboo = 1; allcharacter = 1; allcircle = 1; allbamboohonour = 1; allcharacterhonour = 1; allcirclehonour = 1; s = p->wind-1; /* player's seat */ presetdflags(p,s); /* clear all flags */ /* go through collecting information. This is distressingly similar to code in scoring.c */ for ( i = 0; i < MAX_TILESETS; i++ ) { TileSetP t = (TileSetP) &p->tilesets[i]; if ( t->type == Empty ) continue; if ( t->type == Chow ) numchows++,allterminals=allhonours=0; if ( t->type == Pung ) numpungs++; if ( t->type == Kong ) numkongs++; dragonsets += is_dragon(t->tile); windsets += is_wind(t->tile); switch ( suit_of(t->tile) ) { case BambooSuit: allcharacter = allcharacterhonour = 0; allcircle = allcirclehonour = 0; switch ( t->type ) { case Chow: if ( !is_green(t->tile) || !is_green(t->tile+1) || !is_green(t->tile+2) ) allgreen = 0; break; default: if ( !is_green(t->tile) ) allgreen = 0; } break; case CharacterSuit: allbamboo = allbamboohonour = 0; allcircle = allcirclehonour = 0; allgreen = 0; break; case CircleSuit: allbamboo = allbamboohonour = 0; allcharacter = allcharacterhonour = 0; allgreen = 0; break; case DragonSuit: allbamboo = allcharacter = allcircle = 0; if ( !is_green(t->tile) ) allgreen = 0; break; case WindSuit: allbamboo = allcharacter = allcircle = 0; allgreen = 0; break; default: warn("Strange suit seen in hand"); } if ( !is_honour(t->tile) ) allhonours = 0; if ( !is_terminal(t->tile) ) allterminals = 0; } /* now set the flags again */ if ( numpungs+numkongs+numchows >= 3 && allbamboo ) psetdflags(p,s,DangerBamboo); if ( numpungs+numkongs+numchows >= 3 && allcharacter ) psetdflags(p,s,DangerCharacter); if ( numpungs+numkongs+numchows >= 3 && allcircle ) psetdflags(p,s,DangerCircle); if ( windsets >= 3 ) psetdflags(p,s,DangerWind); if ( dragonsets >= 2 ) psetdflags(p,s,DangerDragon); if ( numpungs+numkongs+numchows >= 3 && allhonours ) psetdflags(p,s,DangerHonour); if ( numpungs+numkongs+numchows >= 3 && allgreen ) psetdflags(p,s,DangerGreen); if ( numpungs+numkongs+numchows >= 3 && allterminals ) psetdflags(p,s,DangerTerminal); /* This is always set (for convenience); it's not a property of the player, but of the game as a whole, and will only be applied if a dangerous discard is made. */ psetdflags(p,s,DangerEnd); } /* This internal function sets the flags for the supply of a dangerous discard. It is called (by handle_cmsg) after a claim has been implemented, but before the claiming player's danger flags are re-evaluated. */ static void mark_dangerous_discards(Game *g) { PlayerP p; seats s,sup; /* seat of player, seat of supplier */ Tile t; /* the discard claimed */ unsigned int danger; int nochoice; p = g->players[g->player]; s = p->wind-1; sup = g->supplier; t = g->tile; danger = 0; nochoice = 0; if ( pdflag(p,s,DangerBamboo) && suit_of(t) == BambooSuit ) danger |= DangerBamboo; if ( pdflag(p,s,DangerCharacter) && suit_of(t) == CharacterSuit ) danger |= DangerCharacter; if ( pdflag(p,s,DangerCircle) && suit_of(t) == CircleSuit ) danger |= DangerCircle; if ( pdflag(p,s,DangerWind) && suit_of(t) == WindSuit ) danger |= DangerWind; if ( pdflag(p,s,DangerDragon) && suit_of(t) == DragonSuit ) danger |= DangerDragon; if ( pdflag(p,s,DangerHonour) && is_honour(t) ) danger |= DangerHonour; if ( pdflag(p,s,DangerTerminal) && is_terminal(t) ) danger |= DangerTerminal; if ( pdflag(p,s,DangerGreen) && is_green(t) ) danger |= DangerGreen; /* End danger is special */ if ( g->wall.live_end - g->wall.live_used <= 4 && g->discarded_tile_count[t] <= 1 ) danger |= DangerEnd; /* if we've determined that the discard was dangerous, we now have to see whether the supplier had no choice */ if ( danger && !pflag(g->players[sup],Hidden) ) { seats i; int j; PlayerP psup = g->players[sup]; PlayerP pp; Tile t; int dangerous; nochoice = 1; for ( j = 0; j < psup->num_concealed ; j++ ) { t = psup->concealed[j]; dangerous = 0; for ( i = 0 ; i < NUM_SEATS; i++ ) { if ( i == sup ) continue; pp = g->players[i]; if ( ( pdflag(pp,i,DangerBamboo) && suit_of(t) == BambooSuit ) || ( pdflag(pp,i,DangerCharacter) && suit_of(t) == CharacterSuit ) || ( pdflag(pp,i,DangerCircle) && suit_of(t) == CircleSuit ) || ( pdflag(pp,i,DangerWind) && suit_of(t) == WindSuit ) || ( pdflag(pp,i,DangerDragon) && suit_of(t) == DragonSuit ) || ( pdflag(pp,i,DangerHonour) && is_honour(t) ) || ( pdflag(pp,i,DangerTerminal) && is_terminal(t) ) || ( pdflag(pp,i,DangerGreen) && is_green(t) ) || ( g->wall.live_end - g->wall.live_used <= 4 && g->discarded_tile_count[t] <= 1 ) ) dangerous = 1; } if ( ! dangerous ) { nochoice = 0; break; } } } if ( danger && ! nochoice ) { seats i; /* The supplier has supplied a dangerous discard without excuse */ psetdflags(p,sup,danger); /* and this discharges the liability of any other player that previously made a dangerous discard */ for ( i = 0 ; i < NUM_SEATS ; i++ ) { if ( i != s && i != sup ) presetdflags(p,i); } } /* set the game danger flag */ if ( danger ) game_setflag(g,GFDangerousDiscard); if ( danger && nochoice ) game_setflag(g,GFNoChoice); } /* dump the game state in human-readable form into a large internal buffer and return it. This is intended for use in possibly dire situations, so it doesn't do any memory management. As a service to the caller, it returns in the second argument (if non-null) the number of spare bytes in the returned buffer (counting from after the string terminator) */ char *game_print_state(Game *g, int *bytes_left) { static char buf[4096]; static char claims[NUM_SEATS*15], ready[NUM_SEATS*3], flags[128]; static char walltiles[MAX_WALL_SIZE*4]; int i; int n; char *b; int tot; if ( g == NULL ) { strcat(buf,"NO GAME\n"); return buf; } claims[0] = 0; ready[0] = 0; flags[0] = 0; for (i=0; iclaims[i]))); strcat(claims," "); sprintf(buf,"%d ",g->ready[i]); strcat(ready,buf); } for ( i = 0; i < 32; i++ ) { if ( game_flag(g,i) ) { strcat(flags,nullprotect(game_print_GameFlags(i))); strcat(flags," "); } } walltiles[0] = 0; for ( i = 0; i < g->wall.size; i++ ) { strcat(walltiles,tile_code(g->wall.tiles[i])); strcat(walltiles," "); } buf[0] = 0; n = sprintf(buf,"" "Game State:\n" "players: (see later)\n" "round: %s\n" "hands_as_east: %d\n" "firsteast: %d\n" "state: %s\n" "active: %d\n" "paused: %s\n" "player: %s\n" "whence: %s\n" "supplier: %s\n" "tile: %s\n" "needs: %s\n" "serial: %d\n" "claims: %s\n" "cpos: %s\n" "chowpending: %d\n" "mjpending: %d\n" "konging: %s\n" "ready: %s\n" "flags: %s\n" "wall.tiles: %s\n" "wall.live_used: %d\n" "wall.live_end: %d\n" "wall.dead_end: %d\n" "wall.size: %d\n" "cmsg_check: %d\n" "cmsg_err: %s\n" "protversion: %d\n" "manager: %d\n" "option_table: (see later)\n" "fd: %d\n" "cseqno: %d\n" "userdata: %p\n", nullprotect(tiles_print_TileWind(g->round)), g->hands_as_east, g->firsteast, nullprotect(game_print_GameState(g->state)), g->active, g->paused ? g->paused : "-no-", nullprotect(game_print_seats(g->player)), nullprotect(game_print_Whence(g->whence)), nullprotect(game_print_seats(g->supplier)), tile_code(g->tile), nullprotect(game_print_Whence(g->needs)), g->serial, claims, nullprotect(player_print_ChowPosition(g->cpos)), g->chowpending, g->mjpending, nullprotect(game_print_Konging(g->konging)), ready, flags, walltiles, g->wall.live_used, g->wall.live_end, g->wall.dead_end, g->wall.size, g->cmsg_check, g->cmsg_err ? g->cmsg_err : "-none-", g->protversion, g->manager, g->fd, g->cseqno, g->userdata ); b = buf + n; tot = n; for ( i = 0; i < NUM_SEATS; i++ ) { n = sprintf(b,"%s",g->players[i] ? player_print_state(g->players[i]) : "NO PLAYER\n"); b += n; tot += n; } if ( bytes_left ) *bytes_left = n-1; return buf; } #include "game-enums.c" mj-1.17-src/README0000444006717300001440000001631415002771311012205 0ustar jcbusers$Header: /home/jcb/MahJong/newmj/RCS/README,v 12.2 2020/05/26 17:56:47 jcb Exp $ This is the README file for the Unix mah-jong programs by J. C. Bradfield. NOTICES: -------- Please see the file LICENCE for terms and conditions of these programs. Source distribution only: ------------------------- The code in the tiles-v1/ directory was not written by me; see tiles-v1/README for information and licensing. The file MANIFEST contains a description of all the files you should find in this distribution. Binary distributions only: -------------------------- In the distribution directory, you will find the following files: mj-server the controller program. ( mj-server.exe in Windows ) mj-player a computer player. ( mj-player.exe in Windows ) xmj a graphical client. ( xmj.exe in Windows ) README this file LICENCE the licence rules.txt the rules as implemented by the programs use.txt documentation on how to use the programs CHANGES summary of changes between releases xmj.1 man page (not in Windows) mj-player.1 man page (not in Windows) mj-server.1 man page (not in Windows) tiles-numbered an alternative tileset with some arabic numbers tiles-small a set of smaller (3/4 size) tiles gtkrc-minimal sample gtkrc files gtkrc-plain gtkrc-clearlooks DESCRIPTION: ------------ These programs allow the user of a Unix (or Windows) computer to play Mah-Jong, in the Chinese Classical style, against other users (locally or over the Internet), against programmed players, or both. OBTAINING: ---------- Latest releases should be available from: http://mahjong.julianbradfield.org/MahJong/ CONTACTING: ----------- If you need to contact me about these programs, please mail me at mahjong@stevens-bradfield.com . INSTALLATION - BUILDING FROM SOURCE: ------------------------------------ Systems: -------- These programs should, in theory, compile on any reasonably modern 32-bit or 64-bit Unix with an ANSI C compiler and the X Window System. They have been known to compile (and run) under GNU/Linux, recent Solaris (on Sparc), and recent Irix (on Silicon Graphics workstations). They also compile and run on MacOS 10 with X Windows, and on iOS with some work. If you try to compile on any "reasonably modern" system, and have a problem, do please tell me. In addition, they should compile and run under Win32 systems with GNU compilers and utilities; they are known to work under NT 4.0 with the mingw compiler (you must use the version with the MSVCRT dll). Adam Kao got a pre-release version to compile with Microsoft Visual C and nmake, but as I don't have these I don't support them. If you wish to try to do this, the file makefile.msvc.old is the makefile Adam used; however, it has not been updated to match the current release, and is now extremely old. They cross-compile for Windows under Linux using the mingw suite. Prerequisites: -------------- On Unix, as of version 1.10 of xmj, you need GTK+ 2.0 or later, preferably with the Clearlooks theme installed. If you do not have GTK+, obtain it from www.gtk.org or mirrors. On Windows, you need the GTK+2 development bundle (note that GTK+2 relies on a lot of other stuff. A bundle is available from www.gtk.org.) To compile the program, you need Perl, version 5 (version 4 is almost certainly OK, but I haven't checked). Perl should be installed on any decent system. (It is, of course not installed by default on Windows, but if you do any development, you've probably already installed it.) You do not need any Perl modules. You need the GNU make utility. If you do not have this, you will need to remove the GNU-specific features from the Makefile, as directed therein. (This may be hard. There is no good reason not to use GNU make, as far as I know.) (Windows: see also note above about makefile.msvc.old) Installing: ----------- Unpack the distribution, and cd into the distribution directory. Edit the Makefile, and change as directed by the comments. Then make You should not see any warnings or errors. If you do get any warning or error, please complain to me (unless you've switched on lots of superfluous message via the C debug flags, in which case you know what you're doing, so don't complain to me!). Then (Unix only) su to an appropriate user if you need this for installation, and make install will install the binaries in the place you chose. Alternatively, just copy the three binaries mj-server mj-player xmj into your chosen directory. make install.man (Unix only) will install the man pages into the appropriate directory. You may also wish to put the pre-formatted text files use.txt and rules.txt (which are basically the two halves of the man page) somewhere. INSTALLATION - UNIX BINARY DISTRIBUTIONS: ----------------------------------------- Unpack the distribution, and cd into the distribution directory. Just copy the three binaries mj-server mj-player xmj into your chosen directory. If you don't have GTK+, then see above. The user documentation is use.txt and rules.txt ; put these in an appropriate place. Alternatively, or as well, copy the man pages xmj.1, mj-server.1, mj-player.1 to the appropriate man directory (e.g. /usr/local/man/man1). Make sure that the binary directory is in your PATH; in particular, if you run from the source directory, you need to have it in your PATH either explicitly or as . . INSTALLATION - WINDOWS BINARY DISTRIBUTION: ------------------------------------------- Unpack the distribution. Put the three *.exe files plus the DLLs into an appropriate folder. Creating menu items and shortcuts is up to you... Currently the relevant DLLs are packaged into the binary distribution. On older versions, these were provided separately in the file gtk2dlls.zip in the xmj download directory. If you need to run an old version, put these DLLs in the same directory as the xmj binaries. Put the documentation use.txt and rules.txt somewhere sensible. RUNNING: -------- Please see the file use.txt in the distribution for usage information. The file rules.txt describes the rules applied by the programs. (Or see the man page.) PAYMENT & REGISTRATION: ----------------------- What payment? What registration? These programs are free. However, if you would like to encourage me to continue development, or thank me for what I have already done, you can (a) make a donation to your local branch of Amnesty International (please send me a note if you do); or, (b) send me a donation, either via PayPal, or by credit card by the link on the xmj home page. If you want to be informed when new versions are released, etc., please sign up via the web site. WINDOWS: -------- The Windows port exists thanks to the efforts of Adam Kao , who ported the pre-release version, and thus showed me what to do. Only Win32 is catered for. I do not have a development environment for Windows, so Windows-specific bugs will not have a high priority. SUGGESTIONS: ------------ xmj is no longer under active development. You're welcome to send me suggestions for improvements, but it may take a year or two for me to do anything with them. BUGS: ----- Please report any bugs, or anything that might be a bug. Any known bugs that cannot be fixed are documented in the user manual. mj-1.17-src/tiles.c0000444006717300001440000002351015002771311012605 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/tiles.c,v 12.1 2020/05/30 17:32:46 jcb Exp $ * tiles.c * contains the access functions for the tile datatype */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include "tiles.h" /* This is used only to get a couple of defines */ #include "protocol.h" /* required for host to network format conversion functions, and the warn function */ #include "sysdep.h" static const char rcs_id[] UNUSED = "$Header: /home/jcb/MahJong/newmj/RCS/tiles.c,v 12.1 2020/05/30 17:32:46 jcb Exp $"; /* see tiles.h for specifications of public functions */ #define tileit(s,v) ((Tile) (s*10 + v)) int is_green(Tile t) { int green; switch ( suit_of(t) ) { case DragonSuit: green = (value_of(t) == GreenDragon); break; case BambooSuit: switch ( value_of(t) ) { case 2: case 3: case 4: case 6: case 8: green = 1; break; default: green = 0; } break; default: green = 0; } return green; } Tile thirteen_wonders[] = { tileit(BambooSuit,1), tileit(BambooSuit,9), tileit(CharacterSuit,1), tileit(CharacterSuit,9), tileit(CircleSuit,1), tileit(CircleSuit,9), tileit(WindSuit,EastWind), tileit(WindSuit,SouthWind), tileit(WindSuit,WestWind), tileit(WindSuit,NorthWind), tileit(DragonSuit,RedDragon), tileit(DragonSuit,GreenDragon), tileit(DragonSuit,WhiteDragon) }; TileWind next_wind(TileWind w) { switch ( w ) { case EastWind: return SouthWind; case SouthWind: return WestWind; case WestWind: return NorthWind; case NorthWind: return EastWind; default: return UnknownWind; } } TileWind prev_wind(TileWind w) { switch ( w ) { case EastWind: return NorthWind; case SouthWind: return EastWind; case WestWind: return SouthWind; case NorthWind: return WestWind; default: return UnknownWind; } } Tile make_tile(TileSuit s,int v) { if ( 1 <= s && s <= 3 ) { if ( 1 <= v && v <= 9 ) return tileit(s,v); else warn("make_tile: Bad value %d for suit %d\n",v,s); /* fall through to return of error at end ... */ } else if ( s == WindSuit ) { if ( 1 <= v && v <= 4 ) return tileit(s,v); else warn("make_tile: Bad value %d for winds\n",v); } else if ( s == DragonSuit ) { if ( 1 <= v && v <= 3 ) return tileit(s,v); else warn("make_tile: Bad value %d for dragons\n",v); } else if ( s == FlowerSuit ) { if ( 1 <= v && v <= 4 ) return tileit(s,v); else warn("make_tile: Bad value %d for flowers\n",v); } else if ( s == SeasonSuit ) { if ( 1 <= v && v <= 4 ) return tileit(s,v); else warn("make_tile: Bad value %d for seasons\n",v); } return ErrorTile; } /* This wastes quite a lot of memory, in old-timers' terms; but these days, who worries about a few hundred bytes? tilenames is a [suit][value] array. */ #define MAXTNLENGTH 20 /* max length of a tile name string, including the null terminator WARNING: inittilenames assumes this value is indeed a bound */ static char tilenames[8][10][MAXTNLENGTH]; static int tilenamesinited = 0; static void inittilenames(void) { int i; for ( i = 1 ; i <= 9 ; i++ ) { sprintf((char *)(tilenames[CharacterSuit][i]), "%d of Characters",i); sprintf((char *)(tilenames[BambooSuit][i]), "%d of Bamboo",i); sprintf((char *)(tilenames[CircleSuit][i]), "%d of Circles",i); } strcpy((char *)(tilenames[DragonSuit][RedDragon]),"Red Dragon"); strcpy((char *)(tilenames[DragonSuit][GreenDragon]),"Green Dragon"); strcpy((char *)(tilenames[DragonSuit][WhiteDragon]),"White Dragon"); strcpy((char *)(tilenames[WindSuit][EastWind]),"East Wind"); strcpy((char *)(tilenames[WindSuit][SouthWind]),"South Wind"); strcpy((char *)(tilenames[WindSuit][WestWind]),"West Wind"); strcpy((char *)(tilenames[WindSuit][NorthWind]),"North Wind"); strcpy((char *)(tilenames[FlowerSuit][Plum]),"Plum"); strcpy((char *)(tilenames[FlowerSuit][Orchid]),"Orchid"); strcpy((char *)(tilenames[FlowerSuit][Chrysanthemum]),"Chrysanthemum"); strcpy((char *)(tilenames[FlowerSuit][Bamboo]),"Bamboo"); strcpy((char *)(tilenames[SeasonSuit][Spring]),"Spring"); strcpy((char *)(tilenames[SeasonSuit][Summer]),"Summer"); strcpy((char *)(tilenames[SeasonSuit][Autumn]),"Autumn"); strcpy((char *)(tilenames[SeasonSuit][Winter]),"Winter"); strcpy((char *)(tilenames[0][0]),"blank"); tilenamesinited = 1; } const char *tile_name(Tile t) { if ( ! tilenamesinited ) inittilenames(); if (t == ErrorTile) { static const char erstr[] = "Error"; warn("tile_name: called on error tile\n"); return erstr; } return tilenames[suit_of(t)][value_of(t)]; } /* These functions give two letter codes for tiles. They are mainly used in the protocol module. */ /* two letter codes for tiles */ /* N.B. The zero values are errors. */ static char *chartiles[] = { "XX", "1C", "2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C" }; static char *dottiles[] = { "XX", "1D", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D" }; static char *bamtiles[] = { "XX", "1B", "2B", "3B", "4B", "5B", "6B", "7B", "8B", "9B" }; /* Given a tile, return its two letter string. The string must be in static storage and immutable. */ const char *tile_code(Tile t) { if ( t == HiddenTile ) return "--"; if ( suit_of(t) == CharacterSuit ) return chartiles[value_of(t)]; if ( suit_of(t) == CircleSuit ) return dottiles[value_of(t)]; if ( suit_of(t) == BambooSuit ) return bamtiles[value_of(t)]; if ( suit_of(t) == DragonSuit ) switch ( value_of(t) ) { case RedDragon: return "RD"; case GreenDragon: return "GD"; case WhiteDragon: return "WD"; } if ( suit_of(t) == WindSuit ) switch ( value_of(t) ) { case EastWind: return "EW"; case SouthWind: return "SW"; case WestWind: return "WW"; case NorthWind: return "NW"; } if ( suit_of(t) == SeasonSuit ) switch ( value_of(t) ) { case Spring: return "1S"; case Summer: return "2S"; case Autumn: return "3S"; case Winter: return "4S"; } if ( suit_of(t) == FlowerSuit ) switch ( value_of(t) ) { case Plum: return "1F"; case Orchid: return "2F"; case Chrysanthemum: return "3F"; case Bamboo: return "4F"; } return "XX" ; /* error */ } /* Given a two letter string, return a tile */ Tile tile_decode(const char *s) { /* errors will anyway generate ErrorTile; we don't need to validate the string ourselves */ if ( s[1] == 'B' ) return make_tile(BambooSuit, s[0]-'0'); if ( s[1] == 'C' ) return make_tile(CharacterSuit, s[0]-'0'); if ( s[1] == 'D' && isdigit(s[0]) ) return make_tile(CircleSuit, s[0]-'0'); if ( s[1] == 'D' ) return make_tile(DragonSuit, s[0] == 'R' ? RedDragon : s[0] == 'G' ? GreenDragon : s[0] == 'W' ? WhiteDragon : 0); if ( s[1] == 'W' ) return make_tile(WindSuit, s[0] == 'E' ? EastWind : s[0] == 'S' ? SouthWind : s[0] == 'W' ? WestWind : s[0] == 'N' ? NorthWind : 0); if ( s[1] == 'F' ) return make_tile(FlowerSuit, s[0] == '1' ? Plum : s[0] == '2' ? Orchid : s[0] == '3' ? Chrysanthemum : s[0] == '4' ? Bamboo : 0); if ( s[1] == 'S' ) return make_tile(SeasonSuit, s[0] == '1' ? Spring : s[0] == '2' ? Summer : s[0] == '3' ? Autumn : s[0] == '4' ? Winter : 0); if ( s[0] == '-' && s[1] == '-' ) return HiddenTile; return ErrorTile; } /* see tiles.h */ void random_tiles(Tile *tp, int includespecials) { Tile tmp[MAX_WALL_SIZE],t; int i; int j; int tiles_left; /* fill the tmp array with all the tiles */ i = 0; t = HiddenTile; while ( (t = tile_iterate(t,includespecials)) != HiddenTile ) { /* if it's a special we want one of them, else we want four */ tmp[i++] = t; if ( ! is_special(t) ) { tmp[i++] = t; tmp[i++] = t; tmp[i++] = t; } } /* Now pick a tile randomly, and put in it in the next slot of the argument array */ tiles_left = i; i = 0; while ( tiles_left > 0 ) { if ( tiles_left == 1 ) j = 0; else j = rand_index(tiles_left-1); /* random int from 0 to arg */ tp[i++] = tmp[j]; /* copy top tile into this place, and decrement top */ tmp[j] = tmp[--tiles_left]; } } Tile tile_iterate(Tile t,int includespecials) { int s,v; if ( t == HiddenTile ) return make_tile(BambooSuit,1); s = suit_of(t); v = value_of(t); if ( s == BambooSuit ) { if ( v < 9 ) return make_tile(s,v+1); else return make_tile(CharacterSuit,1); } else if ( s == CharacterSuit ) { if ( v < 9 ) return make_tile(s,v+1); else return make_tile(CircleSuit,1); } else if ( s == CircleSuit ) { if ( v < 9 ) return make_tile(s,v+1); else return make_tile(WindSuit,1); } else if ( s == WindSuit ) { if ( v < 4 ) return make_tile(s,v+1); else return make_tile(DragonSuit,1); } else if ( s == DragonSuit ) { if ( v < 3 ) return make_tile(s,v+1); else return includespecials ? make_tile(FlowerSuit,1) : HiddenTile; } else if ( s == FlowerSuit ) { if ( v < 4 ) return make_tile(s,v+1); else return make_tile(SeasonSuit,1); } else if ( s == SeasonSuit ) { if ( v < 4 ) return make_tile(s,v+1); else return HiddenTile; } warn("Bad argument to tile_iterate"); return ErrorTile; } #include "tiles-enums.c" mj-1.17-src/sysdep.c0000444006717300001440000006076115002771311013005 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/sysdep.c,v 12.4 2020/05/30 17:37:37 jcb Exp $ * sysdep.c * By intention, this file contains all functions that might need * fiddling with to cope with different variants of Unix. * In particular, all the networking code is in here. * Some parts of the other code assume POSIXish functions for file * descriptors and the like; this file is responsible for providing them * if they're not native. All such cases that occurred to me as I was * writing them can be found by searching for sysdep.c in the other * files. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include "sysdep.h" #include static const char rcs_id[] UNUSED = "$Header: /home/jcb/MahJong/newmj/RCS/sysdep.c,v 12.4 2020/05/30 17:37:37 jcb Exp $"; /* This is missing in my windows mingw */ #ifdef WIN32 char *index(const char *s, int c) { while ( 1 ) { if (*s == 0) return NULL; else if (*s == c) return (char *)s; s++; } } #endif #include #include #ifndef WIN32 #include #include #include /* needed for HP-UX 11 with _XOPEN_SOURCE_EXTENDED */ #include #include /* this is modern, I think. */ #include #include #endif /* not WIN32 */ #include #include #include #include #ifdef WIN32 #include #include #endif /* warn: = fprintf(stderr,...), with automatic new line */ /* log_msg is a generalized version with a syslog like warning level. */ int (*log_msg_hook)(LogLevel l,char *); char *(*log_msg_add_note_hook)(LogLevel l); static char *log_prefixes[] = { "" , "-I- ", "+W+ ", "*E* " }; /* internal function */ static int vlog_msg(LogLevel l, char *format,va_list args) { char buf[1024]; char *note = NULL; int ret; int n; if ( l > LogError ) l = LogError; strcpy(buf,log_prefixes[l]); n = strlen(log_prefixes[l]); /* reserve 1 char for extra '\n' */ ret = vsnprintf(buf+n,sizeof(buf)-n-1,format,args); buf[1023] = 0; n = strlen(buf); /* do quick check for terminators */ #ifdef WIN32 if ( buf[n-2] == '\r' ) { /* OK */ ; } else if ( buf[n-1] == '\n' ) { /* already have unix terminator */ buf[n-1] = '\r' ; buf[n] = '\n' ; buf[n+1] = 0 ; } else { /* no terminator */ strcat(buf,"\r\n"); } #else if ( buf[n-1] != '\n' ) strcat(buf,"\n"); #endif /* add program supplied note? */ n = 0; if ( log_msg_add_note_hook ) { note = (*log_msg_add_note_hook)(l); if ( note ) n = strlen(note); } { char nbuf[1024+n+1]; strcpy(nbuf,buf); if ( n > 0 ) { strcat(nbuf,note); } /* call the hook */ if ( log_msg_hook == NULL || log_msg_hook(l,nbuf) == 0 ) { fprintf(stderr,"%s",nbuf); } } return ret; } int log_msg(LogLevel l,char *format,...) { va_list args; va_start(args,format); return vlog_msg(l,format,args); } int log_error(const char *function, const char *file, const int line, const char *format,...) { char nformat[1024]; va_list args; sprintf(nformat,"Error occurred in %s(), at %s:%d\n",function,file,line); strmcat(nformat,format,1023-(strlen(nformat)+strlen(format))); va_start(args,format); return vlog_msg(LogError,nformat,args); } int warn(char *format,...) { va_list args; va_start(args,format); return vlog_msg(LogWarning,format,args); } int info(char *format,...) { va_list args; va_start(args,format); return vlog_msg(LogInfo,format,args); } /* ignore the SIGPIPE signal. Return 0 on success, -1 on error */ int ignore_sigpipe(void) { #ifdef WIN32 return 0; #else /* this is just posix at present */ struct sigaction act; act.sa_handler = SIG_IGN; sigemptyset(&act.sa_mask); act.sa_flags = 0; return sigaction(SIGPIPE,&act,NULL); #endif /* WIN32 */ } /* functions to be applied by the socket routines */ static void *(*skt_open_transform)(SOCKET) = NULL; static int (*skt_closer)(void *) = NULL; static int (*skt_reader)(void *, void *, size_t) = NULL; static int (*skt_writer)(void *, const void *,size_t) = NULL; static int sockets_initialized = 0; #ifdef WIN32 static void shutdown_sockets(void) { WSACleanup(); /* I don't care what it returns */ } #endif /* WIN32 */ /* initialize sockets */ int initialize_sockets(void *(*open_transform)(SOCKET), int (*closer)(void *), int (*reader)(void *, void *, size_t), int (*writer)(void *, const void *,size_t)) { skt_open_transform = open_transform; skt_closer = closer; skt_reader = reader; skt_writer = writer; #ifdef WIN32 if ( ! sockets_initialized ) { WORD version; WSADATA data; int err; version = MAKEWORD( 2, 2 ); err = WSAStartup( version, &data ); if (err != 0) { return 0; } if ((LOBYTE( data.wVersion ) != 2) || (HIBYTE( data.wVersion ) != 2)) { WSACleanup(); return 0; } if ( atexit(shutdown_sockets) != 0 ) { warn("couldn't register socket shutdown routine"); } } #endif /* WIN32 */ sockets_initialized = 1; return 1; } /* this function takes a string and parses it as an address. It returns 0 for an Internet address, 1 for a Unix address. For Internet addresses, the host name and port are placed in the given variables (the string had better be long enough); for Unix, the file name is copied to the given variable. */ static int parse_address(const char *address, char *ret_host, unsigned short *ret_port, char *ret_file, size_t n) { if ( strchr(address,':') ) { /* grrr */ if ( address[0] == ':' ) { strcpy(ret_host,"localhost"); sscanf(address,":%hu",ret_port); } else { sscanf(address,"%[^:]:%hu",ret_host,ret_port); } return 0; } else { if ( ret_file) strmcpy(ret_file,address,n); return 1; } } /* set_up_listening_socket: Set up a socket listening on the given address. Return its fd. */ SOCKET set_up_listening_socket(const char *address) { SOCKET sfd; struct protoent *prstruc = NULL; struct sockaddr_in inaddr; #ifndef WIN32 struct sockaddr_un unaddr; #endif int unixsockets; unsigned short port; char name[256]; int sockopt; if ( ! sockets_initialized ) initialize_sockets(NULL,NULL,NULL,NULL); unixsockets = #ifdef WIN32 parse_address(address,name,&port,NULL,0); #else parse_address(address,name,&port,unaddr.sun_path,sizeof(unaddr.sun_path)); #endif /* WIN32 */ #ifdef WIN32 if ( unixsockets ) { warn("unix sockets not available on Windows"); return INVALID_SOCKET; } #endif /* WINDOWS */ if ( !unixsockets ) { prstruc = getprotobyname("tcp"); if ( prstruc == NULL ) { perror("getprotobyname failed"); return INVALID_SOCKET; } } sfd = socket(unixsockets ? AF_UNIX : AF_INET, SOCK_STREAM, unixsockets ? 0 : prstruc->p_proto); if ( sfd == INVALID_SOCKET ) { perror("socket failed"); return INVALID_SOCKET; } sockopt=1; setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,(void *)&sockopt,sizeof(int)); #ifndef WIN32 if ( unixsockets ) { unaddr.sun_family = AF_UNIX; unlink(unaddr.sun_path); /* need to check success FIXME */ if ( bind(sfd,(struct sockaddr *)&unaddr,sizeof(struct sockaddr_un)) < 0 ) { perror("bind failed"); return INVALID_SOCKET; } } else { #else if ( 1 ) { #endif /* WIN32 */ inaddr.sin_family = AF_INET; inaddr.sin_port = htons(port); inaddr.sin_addr.s_addr = INADDR_ANY; if ( bind(sfd,(struct sockaddr *)&inaddr,sizeof(struct sockaddr_in)) < 0 ) { perror("bind failed"); return INVALID_SOCKET; } } if ( listen(sfd,5) < 0 ) { perror("listen failed"); return INVALID_SOCKET; } if ( skt_open_transform ) { return (SOCKET) skt_open_transform(sfd); } else { return sfd; } } /* accept_new_connection: A connection has arrived on the socket fd. Accept the connection, and return the new fd, or INVALID_SOCKET on error. */ SOCKET accept_new_connection(SOCKET fd) { SOCKET newfd; struct sockaddr saddr; struct linger l = { 1, 1000 } ; /* linger on close for 10 seconds */ socklen_t saddrlen = sizeof(saddr); if ( ! sockets_initialized ) initialize_sockets(NULL,NULL,NULL,NULL); newfd = accept(fd,&saddr,&saddrlen); if ( newfd == INVALID_SOCKET ) { perror("accept failed"); } /* we should make sure that data is sent when this socket is closed */ if ( setsockopt(newfd,SOL_SOCKET,SO_LINGER,(void *)&l,sizeof(l)) < 0 ) { warn("setsockopt failed"); } if ( skt_open_transform ) { return (SOCKET) skt_open_transform(newfd); } else { return newfd; } } /* connect_to_host: Establish a connection to the given address. Return the file descriptor or INVALID_SOCKET on error. If unixsockets, only the port number is used in making the name The returned socket is marked close-on-exec . */ static SOCKET _connect_to_host(int plain, const char *address) { SOCKET fd; struct sockaddr_in inaddr; #ifndef WIN32 struct sockaddr_un unaddr; #endif char name[512]; int unixsockets; unsigned short port; struct hostent *hent = NULL; struct protoent *prstruc = NULL; if ( ! sockets_initialized ) initialize_sockets(NULL,NULL,NULL,NULL); #ifdef WIN32 unixsockets = parse_address(address,name,&port,NULL,0); if ( unixsockets ) { warn("Unix sockets not supported on Windows"); return INVALID_SOCKET; } #else unixsockets = parse_address(address,name,&port,unaddr.sun_path,sizeof(unaddr.sun_path)); #endif /* WIN32 */ if ( !unixsockets ) { hent = gethostbyname(name); if ( ! hent ) { perror("connect_to_host: gethostbyname failed"); return INVALID_SOCKET; } prstruc = getprotobyname("tcp"); if ( prstruc == NULL ) { perror("connect_to_host: getprotobyname failed"); return INVALID_SOCKET; } } fd = socket(unixsockets ? AF_UNIX : AF_INET, SOCK_STREAM, unixsockets ? 0 : prstruc->p_proto); if ( fd == INVALID_SOCKET ) { perror("connect_to_host: socket failed"); return INVALID_SOCKET; } #ifndef WIN32 if ( unixsockets ) { unaddr.sun_family = AF_UNIX; if ( connect(fd,(struct sockaddr *)&unaddr,sizeof(struct sockaddr_un)) < 0 ) { perror("connect_to_host: connect failed"); return INVALID_SOCKET; } } else { #else if ( 1 ) { #endif /* WIN32 */ inaddr.sin_family = AF_INET; inaddr.sin_port = htons(port); inaddr.sin_addr = *((struct in_addr *)hent->h_addr); if ( connect(fd,(struct sockaddr *)&inaddr,sizeof(struct sockaddr_in)) < 0 ) { perror("connect_to_host: connect failed"); return INVALID_SOCKET; } } #ifndef WIN32 /* mark close on exec */ fcntl(fd,F_SETFD,1); #endif /* WIN32 */ /* Set a five-minute keepalive in order to dissuade natting routers from dropping the connection */ { int data = 1; int n; n = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&data, sizeof(int)); if ( n < 0 ) { perror("connect_to_host: setsockopt keepalive failed"); } else { #ifdef TCP_KEEPIDLE /* this call exists on modern Linuxes, but not everywhere */ data = 300; n = setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &data, sizeof(int)); if ( n < 0 ) { /* of course, this is not supported for unix sockets. We could have checked the address family, but it's probably simpler just to ignore errors */ if ( errno != ENOTSUP ) { perror("connect_to_host: setsockopt keepidle failed"); } } #endif } } if ( !plain && skt_open_transform ) { return (SOCKET) skt_open_transform(fd); } else { return fd; } } SOCKET connect_to_host(const char *address) { return _connect_to_host(0,address); } SOCKET plain_connect_to_host(const char *address) { return _connect_to_host(1,address); } /* close a network connection */ static int _close_socket(int plain, SOCKET s) { if ( !plain && skt_closer ) { return skt_closer((void *)s); } else { return closesocket(s); } } int close_socket(SOCKET s) { return _close_socket(0,s); } int plain_close_socket(SOCKET s) { return _close_socket(1,s); } /* read a line, up to lf/crlf terminator, from the socket, and return it. Internal backslashes are removed, unless doubled. If the line ends with an unescaped backslash, replace by newline, and append the next line. If there is an error or end of file before seeing a terminator, NULL is returned. At present, error includes reading a partial line. The returned string is valid until the next call of get_line. This is the internal function implementing the next two; the second arg says whether the first is int or SOCKET. */ static char *_get_line(SOCKET fd,int is_socket) { int length,i,j; int offset=0; static char *buffer = NULL; static size_t buffer_size = 0; while ( 1 ) { while ( 1 ) { while (offset+1 >= (int)buffer_size) { /* +1 to keep some space for '\0' */ buffer_size = (buffer_size == 0) ? 1024 : (2 * buffer_size); buffer = realloc(buffer, buffer_size); if (!buffer) { perror("get_line"); exit(-1); } } length = (is_socket ? (skt_reader ? skt_reader((void *)fd,buffer+offset,1) : #ifdef WIN32 recv(fd,buffer+offset,1,0) #else read(fd,buffer+offset,1) #endif ) : read(fd,buffer+offset,1)); if ((length <= 0) || (buffer[offset] == '\n')) break; offset += length; } if ( offset == 0 ) return NULL; if ( length <= 0 ) { fprintf(stderr,"get_line read partial line"); return NULL; } offset += length; buffer[offset] = '\0'; /* now check for newline */ if ( buffer[offset-1] == '\n' ) { if ( buffer[offset-2] == '\r' && buffer[offset-3] == '\\' ) { /* zap the CR */ buffer[offset-2] = '\n'; buffer[offset-1] = '\0'; offset -= 1; /* and go onto next clause */ } if ( buffer[offset-2] == '\\' ) { /* we have to decide whether this is an escaped backslash */ int j = 1,k=offset-3; while ( k >= 0 && buffer[k--] == '\\' ) j++; if ( j & 1 ) { /* odd number, not escaped */ buffer[offset-2] = '\n' ; buffer[--offset] = '\0' ; /* read another line */ } else break; /* return this line */ } else break; /* return this line */ } /* no newline, keep going */ } /* now remove internal backslashes */ for ( i = j = 0; 1; i++, j++ ) { if ( buffer[j] == '\\' ) { j++; } buffer[i] = buffer[j]; if ( !buffer[i] ) break; } return buffer; } char *get_line(SOCKET fd) { /* are we actually being asked to read from stdout? Then read from stdin */ if ( fd == STDOUT_FILENO ) return _get_line(STDIN_FILENO,0); return _get_line((int)fd,1); } char *fd_get_line(int fd) { return _get_line(fd,0); } static int _put_line_aux(SOCKET fd, char* line, int length, int is_socket) { int n; if ( is_socket ) { n = skt_writer ? skt_writer((void *)fd,line,length) : #ifdef WIN32 send(fd,line,length,0) #else write(fd,line,length) #endif ; } else { n = write(fd,line,length); } if ( n < length ) { perror("put_line: write failed"); return -1; } return n; } /* write a line (which is assumed to have any terminator included) to the given socket. Return -1 on error, length of line on success. Replace internal newlines by backslash CRLF and write as lines. Double all internal backslashes. Third arg says whether first arg is really an int, or a SOCKET. This is the internal function implemented the next two. */ static int _put_line(int fd,char *line,int is_socket) { int length,n; static char *buffer = NULL; static size_t buf_size = 0; int i,j; /* the backslash/newline escaping can at worst treble the size of the input */ length = strlen(line) + 1; /* include null */ while ( (int)buf_size <= 3*length ) { buf_size = buf_size ? 2*buf_size : 1024; buffer = realloc(buffer,buf_size); } /* FIXME: should retry if only write partial line */ i = j = 0; while ( *line ) { if ( *line == '\\' ) { buffer[i++] = '\\' ; } if ( *line == '\n' && *(line+1) ) { buffer[i++] = '\\' ; buffer[i++] = '\r' ; buffer[i++] = *(line++) ; n = _put_line_aux(fd,buffer,i,is_socket); if ( n < 0 ) { return -1 ; } i = 0; } else { buffer[i++] = *(line++); } } return _put_line_aux(fd,buffer,i,is_socket); } int put_line(SOCKET fd,char *line) { /* are we asked to write to stdout? */ if ( fd == STDOUT_FILENO ) return _put_line(STDOUT_FILENO,line,0); return _put_line((int)fd,line,1); } int fd_put_line(int fd,char *line) { return _put_line(fd,line,0); } int put_data(SOCKET fd, char *data, int len) { int n; if ( len == 0 ) { # ifdef WIN32 # ifdef XCOMPILE n = shutdown(fd, SHUT_WR); # else n = shutdown(fd, SD_SEND); # endif # else n = shutdown(fd, SHUT_WR); # endif if ( n < 0 ) { warn("put_data failed to shutdown: %s",strerror(errno)); } return n; } # ifdef WIN32 n = send(fd,data,len,0); # else n = write(fd,data,len); # endif if ( n < 0 ) { warn("put_data failed: %s",strerror(errno)); } return n; } int get_data(SOCKET fd, char *data, int len) { int n; # ifdef WIN32 n = recv(fd,data,len,0); # else n = read(fd,data,len); # endif if ( n < 0 ) { warn("put_data failed: %s",strerror(errno)); } return n; } /* random integer from 0 to top inclusive */ static int seed = 0; unsigned int rand_index(int top) { if ( seed == 0 ) { /* seed the generator */ rand_seed(0); } return (unsigned int)((1.0+top)*rand()/(RAND_MAX+1.0)); } /* and seed it */ void rand_seed(int s) { if ( s == 0 ) s = time(NULL); srand(seed = s); } /* This is like strmcat, but it also quotes the string (in a system dependent way) for adding it to a shell command line. */ char *qstrmcat(char *dest, const char *src, int len) { char *d = dest; char *s = (char *)src; while ( *d ) d++; if ( len == 0 ) return dest; #ifdef WIN32 *d = '"'; d++; len--; #else *d = '\''; d++; len--; #endif while ( *s && len > 0 ) { #ifdef WIN32 if ( *s == '"' ) { *d++ = '"'; len--; if ( len > 0 ) { *d++ = *s++; len--; } } else { *d++ = *s++; len--; } #else if ( *s == '\'' || *s == '\\' ) { *d++ = '\''; len--; if ( len > 2 ) { *d++ = '\\'; len--; *d++ = *s++; len--; *d++ = '\''; len--; } } else { *d++ = *s++; len--; } #endif } if ( len > 0 ) { #ifdef WIN32 *d++ = '"'; len--; #else *d++ = '\''; len--; #endif } if ( len == 0 ) d--; *d = 0; return dest; } /* utility function for following: return "NULL" for null string */ char *nullprotect(char *s) { return s ? s : "NULL"; } /* feed a command to the system to be started in background. No fancy argument processing, just pass to shell or equiv. Return 1 on success, 0 on failure. */ int start_background_program(const char *cmd) { char buf[1024]; # ifdef WIN32 int res; STARTUPINFO si; PROCESS_INFORMATION pi; strmcpy(buf,cmd,1023); buf[1023] = 0; memset((void *)&si,0,sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); res = CreateProcess(NULL,buf,NULL,NULL, 0,DETACHED_PROCESS,NULL,NULL,&si,&pi); if ( res ) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { warn("Failed to start process %s",cmd); } return res; # else strmcpy(buf,cmd,sizeof(buf)-strlen(buf)-1); strcat(buf," &"); # endif return ! system(buf); } /* unfortunately we need a slightly more sophisticated one where we can get at the output. This passes out a write handle for the new process's stdin, and a read handle for its stdout */ #ifndef WIN32 int start_background_program_with_io(const char *cmd,int *childin, int *childout) { int tochild[2], fromchild[2]; int cpid; if ( pipe(tochild) < 0 ) { perror("Unable to create pipe"); return 0; } if ( pipe(fromchild) < 0 ) { perror("Unable to create pipe: %s"); return 0; } cpid = fork(); if ( cpid < 0 ) { perror("Unable to fork: %s"); } if ( cpid ) { /* parent */ close(tochild[0]); close(fromchild[1]); *childin = tochild[1]; *childout = fromchild[0]; return 1; } else { close(tochild[1]); close(fromchild[0]); dup2(tochild[0],0); close(tochild[0]); dup2(fromchild[1],1); close(fromchild[1]); execl("/bin/sh","sh","-c",cmd,NULL); /* what can we do if we get here? Not a lot... */ perror("Exec of child failed"); exit(1); } return 0; } #else /* This windows version is loosely based on the example Creating a Child Process with Redirected Input and Output from the MSDN Library. See that example for all the comments explaining what's going on. In fact it's (a) easier than that, since instead of saving, setting and inheriting handles, we can put the pipes into the startup info of the CreateProcess call. (Thanks to somebody on Usenet, no thanks to Microsoft!); (b) harder, because we need to pass back C-style file descriptors, not Windows handles. Hence the mystic call to _open_osfhandle. */ int start_background_program_with_io(const char *cmd,int *childin, int *childout) { int res; char buf[1024]; STARTUPINFO si; PROCESS_INFORMATION pi; SECURITY_ATTRIBUTES sa; HANDLE tochildr, tochildw, tochildw2, fromchildr, fromchildr2, fromchildw; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; if ( ! CreatePipe(&fromchildr,&fromchildw,&sa,0 ) ) { perror("couldn't create pipe"); return 0; } DuplicateHandle(GetCurrentProcess(), fromchildr,GetCurrentProcess(),&fromchildr2,0, FALSE, DUPLICATE_SAME_ACCESS); CloseHandle(fromchildr); if ( ! CreatePipe(&tochildr,&tochildw,&sa,0 ) ) { perror("couldn't create pipe"); return 0; } DuplicateHandle(GetCurrentProcess(), tochildw,GetCurrentProcess(),&tochildw2,0, FALSE, DUPLICATE_SAME_ACCESS); CloseHandle(tochildw); strmcpy(buf,cmd,1023); buf[1023] = 0; memset((void *)&si,0,sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESTDHANDLES; si.hStdOutput = fromchildw; si.hStdInput = tochildr; si.hStdError = 0; res = CreateProcess(NULL,buf,NULL,NULL, TRUE,DETACHED_PROCESS,NULL,NULL,&si,&pi); if ( res ) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { warn("Failed to start process %s",cmd); } /* I think the mingw has the wrong type for _open_osfhandle ? */ *childin = _open_osfhandle((int)tochildw2,_O_WRONLY); *childout = _open_osfhandle((int)fromchildr2,_O_RDONLY); /* do we, as in unix, close our versions of the handles we don't want? */ if ( 1 ) { CloseHandle(tochildr); CloseHandle(fromchildw); } return res; } #endif /* return a home directory */ char *get_homedir(void) { char *p; # ifdef WIN32 static char buf[512]; buf[0] = 0; if ( (p = getenv("HOMEDRIVE")) ) strcpy(buf,p); if ( (p = getenv("HOMEPATH")) ) strcat(buf,p); if ( buf[0] ) return buf; return NULL; # else struct passwd *pw; if ( (p = getenv("HOME")) ) return p; if ( (pw = getpwuid(getuid())) ) return pw->pw_dir; return NULL; # endif } /* stuff for Windows */ #ifdef WIN32 int gettimeofday(struct timeval *tv, void *tz UNUSED) { long sec, usec; LONGLONG ll; FILETIME ft; GetSystemTimeAsFileTime(&ft); ll = ft.dwHighDateTime; ll <<= 32; ll |= ft.dwLowDateTime; /* this magic number comes from Microsoft's knowledge base ' */ ll -= 116447360000000000LL; /* 100 nanoseconds since 1970 */ ll /= 10; /* microseconds since 1970 */ sec = (long)(ll / 1000000); usec = (long)(ll - 1000000*((LONGLONG) sec)); if ( tv ) { tv->tv_sec = sec; tv->tv_usec = usec; } return 0; } char *getlogin(void) { static char buf[128]; DWORD len = 128; buf[0] = 0; GetUserName(buf,&len); return buf; } int getpid(void) { return GetCurrentProcessId(); } int unlink(const char *f) { return DeleteFile(f); } unsigned int sleep(unsigned int t) { Sleep(1000*t); return 0; } #ifndef XCOMPILE /* I don't think we really need submillisecond resolution ... */ unsigned int usleep(unsigned int t) { Sleep(t/1000); return 0; } #endif #endif /* WIN32 */ mj-1.17-src/player.h0000444006717300001440000003740615002771311012777 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/player.h,v 12.0 2009/06/28 20:43:13 jcb Rel $ * player.h * Contains datatypes representing the state of players. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #ifndef PLAYER_H_INCLUDED #define PLAYER_H_INCLUDED #include "tiles.h" /* Repeated from game.h. Eventually, this should be a game option, if we ever want to play three handed */ #define NUM_SEATS 4 /* A TileSet represents a (potentially) declared set. It is represented as the type, which may be Empty, Chow, Pung, Kong, ClosedPung (only really used at the end), or ClosedKong. Also Pair and ClosedPair (for scoring). The second element is a Tile, which is the tile for Pungs and Kongs, and the first tile for chows. To account for the Millington rule whereby a claimed kong counts as concealed for doubling purposes, there is a third element annexed which is true if a kong was made by adding to an existing pung. Note: the value of the annexed field is only meaningful if the type is Kong. */ typedef enum { Empty = 0, Chow = 1, Pung = 2, Kong = 3, ClosedPung = 4, ClosedKong = 5, ClosedChow = 6, /* used in scoring */ Pair = 7, /* used in scoring etc */ ClosedPair = 8 } TileSetType; typedef struct _TileSet { TileSetType type; Tile tile; int annexed; } TileSet; /* Pointer to TileSet */ typedef TileSet *TileSetP; /* The PlayerOption type names options that the player can ask the controller to apply. */ typedef enum { /* make-enums sub { $_[0] =~ s/^PO//; } */ POUnknown = 0, POInfoTiles, /* should we be sent InfoTiles messages at every event ? */ PODelayTime, /* A time in deciseconds. The controller is requested to leave at least this time between successive actions (where an action is a tile movement). This might be just to slow robot players down to human speed, or to allow time for animation. The controller does not have to take any notice! */ POLocalTimeouts, /* The client wishes to handle timeouts locally */ PONumOptions /* number of options available */ } PlayerOption; /* num_tiles_in_set: takes a TileSetP and returns the number of tiles in it. */ int num_tiles_in_set(TileSetP tp); /* The Player datatype represents the state of a player. See internal comments for elucidation. The structure is public for convenience, but should be considered read-only, and modified by the provided functions. */ typedef struct _Player { int id; /* unique identifier assigned by the master controller, and used in communication. Always non-zero for a real player. */ char *name; /* the name of this player */ TileWind wind; /* which wind this player currently is */ int num_concealed; /* how many concealed tiles */ #define MAX_CONCEALED 14 Tile concealed[MAX_CONCEALED]; /* the concealed tiles (if known) */ int discard_hint; /* this is used by programs to tell player_discards_tile which tile to discard, when the order of identical tiles is important, as is the case when maintaining a display of the hand according to the user's preferences. */ #define MAX_TILESETS 7 /* may have up to seven pairs. Note: this happens even without the seven pairs hand, since the player_can_mah_jong function blindly looks for pairs. Perhaps it shouldn't. */ TileSet tilesets[MAX_TILESETS]; /* exposed tiles */ int num_specials; /* number of flowers and seasons held */ Tile specials[8]; /* flowers and seasons */ unsigned int flags; /* for bit definitions, see below */ unsigned int dflags[NUM_SEATS]; /* see below */ int cumulative_score; /* score so far in the game (excluding current hand)*/ int hand_score; /* score in this hand. Set to -1 during hand. */ char *err; /* used to return error messages by methods. Value is only meaningful after a method has failed (including test functions returning false). */ void *userdata; /* for client programs */ } Player ; /* mask bit numbers for player flags */ typedef enum { Hidden, /* true if we can't see the player's hand */ MahJongged, /* true if this hand has gone mah jongg */ HandDeclared, /* true if the hand has been declared, and accordingly the "concealed" tiles are no longer. Implies Hidden is false */ /* the next three flags are used to handle calling declarations */ /* the next flag is set internally by the player module */ NoDiscard, /* true before the first discard */ /* the next two flags must be set by the user of this module, and are cleared by the player_newhand function */ OriginalCall, /* true if the player has made an original call */ Calling, /* true if the player has made a calling declaration (when implemented) */ } PlayerFlags; /* pointer to Player. In order to discourage people from modifying players directly, this is actually declared as const, unless this is the player module being compiled. */ #ifdef PLAYER_C typedef Player *PlayerP; #else typedef const Player *PlayerP; #endif /* given a pointer to player, test the flag */ /* Cast to avoid problems */ #define pflag(p,f) (p->flags & (1 << f)) /* set, clear */ #define psetflag(p,f) (((Player *)p)->flags |= (1 << f)) #define pclearflag(p,f) (((Player *)p)->flags &= ~(1 << f)) /* The dflags field of the player is used for keeping track of dangerous discards. The element of the player's own seat records whether the player's hand is dangerous: the elements corresponding to the other players record when another player supplies a dangerous discard. NOTE: these flags are maintained by the game module, and then only if checking is on. NOTE: these flags are all related and may be combined. They are therefore given as actual bits, so they can be combined with & and |. */ /* The dangerous hands for letting off a cannon */ typedef enum { DangerBamboo=1, DangerCharacter=2, DangerCircle=4, DangerWind=8, DangerDragon=16, DangerHonour=32, DangerGreen=64, DangerTerminal=128, DangerEnd=256 /* when the wall is nearly empty */ } DangerSignals; #define pdflag(p,s,f) (p->dflags[s] & f) /* set, clear */ #define psetdflags(p,s,f) (((Player *)p)->dflags[s] |= f) #define pcleardflags(p,s,f) (((Player *)p)->dflags[s] &= ~f) #define presetdflags(p,s) (((Player *)p)->dflags[s] = 0) /* Some of these functions return 1 for success and 0 for failure. In the case of failure, they leave the argument player unchanged. */ /* initialize_player: takes a PlayerP and fills in the fields with initial values; most are zero, but the hand_score is set to -1. */ void initialize_player(PlayerP p); /* player_newhand: sets up the player for the start of a new hand. The given wind is the wind of this player. (N.B. This is a TileWind, not a seat.) */ void player_newhand(PlayerP p,TileWind w); /* copy_player: copy old player into new (which must already be allocated). Returns new. */ PlayerP copy_player(PlayerP newp,const PlayerP oldp); /* set_player_id: sets the id of a player. Complains if the player already has an id (but continues). */ void set_player_id(PlayerP p, const int id); /* set_player_name: sets the name of a player. The argument string is copied. Warning: any existing name is freed. The name may be NULL, which is not the same as an empty string. */ void set_player_name(PlayerP p, const char *n); /* set_player_cumulative_score: sets the score of the player */ void set_player_cumulative_score(PlayerP p, int s); /* change_player_cumulative_score: add d to the player's score */ void change_player_cumulative_score(PlayerP p, int d); /* set_player_hand_score: set the hand_score field. */ void set_player_hand_score(PlayerP p, int h); void set_player_userdata(PlayerP p, void *ud); /* utility to count occurrences of tile in player's hand */ int player_count_tile(PlayerP p, Tile t); /* player_draws_tile: first arg is a PlayerP. Second argument is a tile; it is HiddenTile if we can't see it. Returns 1 on success. */ int player_draws_tile(PlayerP p, Tile t); /* player_draws_loose_tile: first arg is a PlayerP. Second argument is a tile; it is HiddenTile if we can't see it. Returns 1 on success. */ int player_draws_loose_tile(PlayerP p, Tile t); /* player_declares_special: the player declares the special tile given as the second argument, which is removed from the concealed tiles and added to the special tiles. The player is given the tile specified by the third argument to replace the special; this is HiddenTile if we cannot observe it. May be called on players that are known or hidden. */ int player_declares_special(PlayerP p, Tile spec); /* player_can_declare_special: just tests for legality of this move. Should only be called on players that are known. */ int player_can_declare_special(PlayerP p, Tile spec); /* the following functions declare sets. If they leave the player with no concealed tiles, so that the hand is complete, they set the HandDeclared flag. */ /* player_pungs: player takes the tile, and uses it to form a pung. */ int player_pungs(PlayerP p, Tile d); int player_can_pung(PlayerP p, Tile d); /* player_pairs: player takes the tile, and uses it to form a pair. Only used after mahjong. */ int player_pairs(PlayerP p, Tile d); int player_can_pair(PlayerP p, Tile d); /* player_kongs: player takes tile, forms kong */ int player_kongs(PlayerP p, Tile d); int player_can_kong(PlayerP p, Tile d); /* player_declares_closed_kong: player declares a closed kong of d. NOTE that these functions do not check whether it's player's turn, since they do not have access to that information. */ int player_declares_closed_kong(PlayerP p, Tile d); int player_can_declare_closed_kong(PlayerP p, Tile d); /* player_adds_to_pung: player has drawn t, and adds it to an existing open pung. */ int player_adds_to_pung(PlayerP p, Tile t); int player_can_add_to_pung(PlayerP p, Tile t); /* player_kong_robbed: the player has formed a kong of t, and it is robbed */ int player_kong_is_robbed(PlayerP p, Tile t); /* player_forms_closed_pung: player has a pung of d. This is used only when preparing hands for scoring. */ int player_forms_closed_pung(PlayerP p, Tile d); int player_can_form_closed_pung(PlayerP p, Tile d); /* player_forms_closed_pair: player has a pair of d. This is used only when preparing hands for scoring. */ int player_forms_closed_pair(PlayerP p, Tile d); int player_can_form_closed_pair(PlayerP p, Tile d); /* Chow declarations have to say where the claimed tile is being inserted. This is partly so that it's easy to check, and partly because some esoteric scoring rules need this information. The final argument is Lower, Middle, or Upper, depending on where the tile d is to go in the chow. The testing functions will accept an argument of AnyPos, in which case they will iterate over the three possible values. NOTE that these functions do not check that it's player's turn. */ /* NOTE NOTE: these values are assumed, and must not be changed. (Because a chow is defined by its lower tile, so claimedtile = chowdefiningtile + chowposition.) The AnyPos value is defined to be MaxTile, so that adding it or subtracting it from a Tile is guaranteed to produce an error. */ typedef enum { Lower = 0, Middle = 1, Upper = 2, AnyPos = MaxTile } ChowPosition; int player_chows(PlayerP p, Tile d, ChowPosition r); int player_can_chow(PlayerP p, Tile d, ChowPosition r); /* used only in scoring. Although in the client/server protocol, a closed chow is always specified as Lower, in evaluating hands it is often wanted to check for closed chows with a given tile in a given position. Therefore these functions take a position. */ int player_forms_closed_chow(PlayerP p, Tile d, ChowPosition r); int player_can_form_closed_chow(PlayerP p, Tile d, ChowPosition r); /* tests whether a player has a mah-jong hand. The d argument is the discard tile available to the player, or HiddenTile if the player already has 14 tiles. The third arg says which special hands should be tested for. Currently, thirteen wonders is always allowed, so the only possible value here is 0 or seven pairs. */ typedef enum { MJSevenPairs = 1 } MJSpecialHandFlags; int player_can_mah_jong(PlayerP p, Tile d, MJSpecialHandFlags flags); /* tests whether a player has the thirteen unique wonders. This is included in the above test, but is separately exported as a convenience. */ int player_can_thirteen_wonders(PlayerP p, Tile d); /* set the discard_hint: which concealed tile should be discarded (as opposed to the default choice of the first tile that is right). Values are -1 for no hint, or 0..num_concealed-1. The discard_hit is cleared on discard. */ int player_set_discard_hint(PlayerP p, int n); /* player discards a tile */ int player_discards(PlayerP p, Tile t); int player_can_discard(PlayerP p, Tile t); /* utility to sort the concealed tiles. This sorts the tiles into increasing order; however, the only safe assumptions about the values of the tiles are those allowed by tiles.h. Thus it *is* safe to assume that tile appear in increasing value and that tiles of the same suit appear together; but it is *not* safe to assume the suits appear in any particular order. */ void player_sort_tiles(PlayerP p); /* utility to exchange two concealed tiles. Moves the tile in position old to position new */ int player_reorder_tile(PlayerP p, int old, int new); /* player_print_tiles: puts into buf, which is required to be at least 80 characters, a human readable representation of the player's tiles. Each tile is represented by its code, and the hand is represented as: space separated concealed tiles; ' * '; space separated tilesets, represented as tile-tile for exposed sets and tile+tile for closed sets; ' * '; space separated specials. If the third argument is true, then the concealed tiles are printed as blanks. This happens anyway if they are hidden in the player structure. */ void player_print_tiles(char *buf, PlayerP p, int hide); /* set_player_tiles: converse of the above. Takes a string description of the player's tiles, and forces the Player to agree. Forces the Hidden flag on or off if appropriate. Returns 1 on success, 0 on error. If error, all bets are off. */ int set_player_tiles(PlayerP p, char *desc); /* player_shows_tiles: takes a string representation of a tile list (as above), and sets the player's concealed tiles to it. Also sets the HandDeclared flag. */ int player_shows_tiles(PlayerP p, char *desc); /* player_swap_tile: swaps the oldtile for the newtile in the concealed tiles. Only used in testing, of course */ int player_swap_tile(PlayerP p, Tile oldtile, Tile newtile); /* tileset_string: utility function, returning the printed representation of a tileset, as above. N.B. this function returns a string in static storage, which will be overwritten on the next call. */ char *tileset_string(TileSetP tp); /* diagnostic function: print a text representation of the player's state into a (possibly static) buffer and return it */ char *player_print_state(PlayerP p); /* now import the functions for printing and scanning enums */ #include "player-enums.h" #endif /* PLAYER_H_INCLUDED */ mj-1.17-src/tiles-v1/0000755006717300001440000000000010627612770013001 5ustar jcbusersmj-1.17-src/tiles-v1/2D.xpm0000444006717300001440000000221507005667026013772 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #002065", ". c #EAEAEA", /* pixels */ ".........................", ".........................", ".........................", ".......... ..........", "......... ... .........", "........ .. .. ........", "....... . . .......", "...... . . . . . ......", "...... . . . . ......", "...... . . . . . ......", "...... . . . . ......", "...... . . . . . ......", "....... . . .......", "........ .. .. ........", "......... ... .........", ".......... ..........", ".........................", ".........................", ".........................", ".......... ..........", "......... ... .........", "........ .. .. ........", "....... . . .......", "...... . . . . . ......", "...... . . . . ......", "...... . . . . . ......", "...... . . . . ......", "...... . . . . . ......", "....... . . .......", "........ .. .. ........", "......... ... .........", ".......... ..........", ".........................", ".........................", "........................." }; mj-1.17-src/tiles-v1/tongW.xpm0000444006717300001440000000276707005667026014637 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "35 35 3 1", /* colors */ " c #000000", ". c None", "X c #FFFFFF", /* pixels */ "............. .............", ".......... XXXXXXXXX ..........", "........ XXXXXXXXXXXXXXX ........", "....... XXXXXXXXXXXXXXXXXXX .......", "...... XXXXXXXXXXXXXXXXXXXXX ......", "..... XXXXXXXXXXXXXXXXXXXXXXX .....", ".... XXXXXXXXXXXXXXXXXXXXXXXXX ....", "... XXXXXXXXXXXXXXXXXX XXXXXXX ...", ".. XXXXXXXXXXXXXXXXXX XXXXXXX ..", ".. XXXXXXXXXXXXXX XXXXX ..", ". XXXXXXXXXX XXXXX XXXXX .", ". XXXXX XXXXX X XXXXX .", ". XXXXXX XXXXXXXXX XXXXXXXXXX .", " XXXXXXXXXXXXXXXXXX XXXXXXXXXXXX ", " XXXXXXXXXX XXXXX XXXXXXXXXXXXX ", " XXXXXXXXXXX XXXX XXXXXXXXXXXXX ", " XXXXXXXXXXX XXX X XXXXXXXXXXXXX ", " XXXXXXXXXXXX XX XXXXXXXXXXX ", " XXXXXXXXXXX X XXXXXXXXX ", " XXXXXXXXXX X XX XX XXXXXXXX ", " XXXXXXX X X XX XXX XXXXXXX ", " XXXXXXX XXX XX XXXX XXXXXXX ", ". XXXXXX XXX X XXX XXXXXX .", ". XXXXXXX XXX XXXXXXX .", ". XXXXXXXX XXXXXXXX .", ".. XXXXXXXX XXXX XXXXXXXX ..", ".. XXXXXXXXX XXXXXX XXXXXXXXX ..", "... XXXXXXXXXXXXXXXXXXXXXXXXXXX ...", ".... XXXXXXXXXXXXXXXXXXXXXXXXX ....", "..... XXXXXXXXXXXXXXXXXXXXXXX .....", "...... XXXXXXXXXXXXXXXXXXXXX ......", "....... XXXXXXXXXXXXXXXXXXX .......", "........ XXXXXXXXXXXXXXX ........", ".......... XXXXXXXXX ..........", "............. ............." }; mj-1.17-src/tiles-v1/1D.xpm0000444006717300001440000000223407005667026013772 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #002065", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXX XXXXXXXXX", "XXXXXXX X X X X XXXXXXX", "XXXXXX X X X X X XXXXXX", "XXXXX XX X X XX XXXXX", "XXXX XX X X XX XXXX", "XXX X XX XX X XXX", "XXX X ..... X XXX", "XX X XX ..X.X.. XX X XX", "XX X ..X.X.X.. X XX", "XX X X .X.....X. X X XX", "XX X ..X.X.X.. X XX", "XX X X .X.....X. X X XX", "XX X ..X.X.X.. X XX", "XX X XX ..X.X.. XX X XX", "XXX X ..... X XXX", "XXX X XX XX X XXX", "XXXX XX X X XX XXXX", "XXXXX XX X X XX XXXXX", "XXXXXX X X X X X XXXXXX", "XXXXXXX X X X X XXXXXXX", "XXXXXXXXX XXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-v1/8D.xpm0000444006717300001440000000221507005667026014000 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #002065", ". c #EAEAEA", /* pixels */ ".........................", "...... ....... ......", ".... ... ... ... ....", ".... . . ... . . ....", "... . . . . . . . ...", "... . . . . . . . . . ...", "... . . . . . . . ...", ".... . . ... . . ....", ".... ... ... ... ....", "...... ....... ......", ".... ... ... ... ....", ".... . . ... . . ....", "... . . . . . . . ...", "... . . . . . . . . . ...", "... . . . . . . . ...", ".... . . ... . . ....", ".... ... ... ... ....", "...... ....... ......", ".... ... ... ... ....", ".... . . ... . . ....", "... . . . . . . . ...", "... . . . . . . . . . ...", "... . . . . . . . ...", ".... . . ... . . ....", ".... ... ... ... ....", "...... ....... ......", ".... ... ... ... ....", ".... . . ... . . ....", "... . . . . . . . ...", "... . . . . . . . . . ...", "... . . . . . . . ...", ".... . . ... . . ....", ".... ... ... ... ....", "...... ....... ......", "........................." }; mj-1.17-src/tiles-v1/2F.xpm0000444006717300001440000000224707005667026014001 0ustar jcbusers/* XPM */ static char *2F[] = { /* width height ncolors chars_per_pixel */ "25 35 4 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", "o c #063A30", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXX XX", "XX....XXXXoXXXXXXXXX XX", "XX.XX.XXXXXoXXXXXX XX X", "XXXX..XXXXXoXXXXXX XXXX X", "XXX..XXXXXXXoXXXXX XXX XX", "XX..XXXXXXXXoXXXX ", "XX....XXXXXXoXXXXXX XXXX", "XXXXXXXXXXXXoXXXXX XXXXX", "XXXXXXXXXXXXoXXoX X XXXXX", "XXXXXXXXXXXXooXXX X XXXX", "XXXXXXXXXXXXoXXXXX XXXXXX", "XXXXXXXXXXXooXXXXXXXXXXXX", "XXXoXXXXXXXoXXXXXXooooooX", "XXoXXXXXXoXoXXoooooXoXXoX", "XooXXXXXXXoXXoXXXXXXXXXoo", "XoXXXoooXXooooXXXXXXXXXoo", "ooXXXXXoXXoooXXXXXXXXXooX", "ooXXXXXoXooXXXXXXXXXXXoXX", "XXoXXXXoXoXXXXXXXXXXXXXXX", "XXXXooXoooXXXXXXXXXXoXXXX", "XXXXXooooXXXXXXXXoXXXXXXX", "XXXXXXXooXXXXXXXXoXoXXXXX", "XXXXXXXoooXXXXXXXXoXooXXX", "XXXXXXXoooXXXXXXXXooXXXXX", "XXXXXXXoXoXXoXXXXXoXXXXXX", "XXXXXXXoXoXooXXXXXoXXXXXX", "XXXXXXXoXoXoXXXXXXoXXXXXX", "XXXXXXXoooooXXXXXXoXXXXXX", "XXXXXXXXoooooXXXXXoXXXXXX", "XXXXXXXooooooXXXXooXXXXXX", "XXXXXXXXXXoooXXXXoXXXXXXX", "XXXXXXXXXXXoooXXooXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-v1/RD.xpm0000444006717300001440000000221507005667026014032 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #B00000", ". c #EAEAEA", /* pixels */ ".........................", ".........................", ".........................", ".........................", "............ ............", ".......... ...........", "........... ..........", "............ ...........", "............ ...........", "............ ...........", "............ .... .....", ".. ....... ...", "... .... ..... ..", ".... . ... ..... ..", ".... .... ..... ..", "..... .... .... ..", "..... ... ... . ...", "...... .....", "...... ... .. ......", "....... ... ... .......", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ...........", "............ ............", "............ ............", ".........................", ".........................", "........................." }; mj-1.17-src/tiles-v1/6D.xpm0000444006717300001440000000225307005667026014000 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 4 1", /* colors */ " c #002065", ". c #B00000", "X c #EAEAEA", "o c #FFFFFF", /* pixels */ "XXXo oXXXXXo oXXX", "XXo ooo oXXXo ooo oXX", "Xo o o oXo o o oX", "X o o o o X o o o o X", "X X o o o X X X o o o X X", "X X o o X X X o o X X", "X o o o o o X o o o o o X", "X o o o o X o o o o X", "XX o o XXX o o XX", "XXX ooo XXXXX ooo XXX", "XXXX XXXXXXX XXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXX.....XXXXXXX.....XXXX", "XXX..ooo..XXXXX..ooo..XXX", "XX..o...o..XXX..o...o..XX", "X..o.o.o.o..X..o.o.o.o..X", "X.o.o.o.o.o.X.o.o.o.o.o.X", "X.X..o.o..X.X.X..o.o..X.X", "X.X.o.o.o.X.X.X.o.o.o.X.X", "X..o.o.o.o..X..o.o.o.o..X", "Xo..o...o..oXo..o...o..oX", "XXo..ooo..oXXXo..ooo..oXX", "XXXo.....oXXXXXo.....oXXX", "XXX..ooo..XXXXX..ooo..XXX", "XX..o...o..XXX..o...o..XX", "X..o.o.o.o..X..o.o.o.o..X", "X.o.o.o.o.o.X.o.o.o.o.o.X", "X.X..o.o..X.X.X..o.o..X.X", "X.X.o.o.o.X.X.X.o.o.o.X.X", "X..o.o.o.o..X..o.o.o.o..X", "Xo..o...o..oXo..o...o..oX", "XXo..ooo..oXXXo..ooo..oXX", "XXXo.....oXXXXXo.....oXXX" }; mj-1.17-src/tiles-v1/README0000444006717300001440000000132307005671121013645 0ustar jcbusersMost of tile pixmaps in this directory derive from the pixmaps distributed with the old xmahjongg solitaire game. The tiles were originally black and white, produced by Mark A. Holm, subject to a "non-profit only" licence. The coloured versions were produced by Eddie Kohler, and in so far as they are different works (a debatable point, I think), are distributed under the Gnu General Public License Version 2 or later, which may be found in the file GPL. The files have been renamed, one pixel of border trimmed off, and a change made to the 7 Circle. Only the suit and honour tiles are covered by this; the flowers and seasons are by J. C. Bradfield (based on a real tile set) and are placed in the public domain. mj-1.17-src/tiles-v1/tongS.xpm0000444006717300001440000000276707005667026014633 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "35 35 3 1", /* colors */ " c #000000", ". c None", "X c #FFFFFF", /* pixels */ "............. .............", ".......... XXXXXXXXX ..........", "........ XXXXXXXXXXXXXXX ........", "....... XXXXXX XXXXXXXXXXX .......", "...... XXXXXXXX XXXXXXXXX ......", "..... XXXXXXXXXX XXXXXXXX .....", ".... XXXXXXXXXXXX XXXXXXXXXX ....", "... XXXXXXXXXXXXX XXXXXXXXXXX ...", ".. XXXXXXXXXXXXXX XXXXXXXXXXXXX ..", ".. XXXXXXXXXXXXXX X XXXXXXXXXXX ..", ". XXXXXXXXXXXXXXX XXXXXXXXXXX .", ". XXXXXXXXXXXXXX XXXXXXXXXX .", ". XXXXXXXXXXX X XXXXXXXXXXXX .", " XXXXXXXXXXXXXXXX X XXXXXXXXXXXX ", " XXXXXXXXXXXXXXXX XX XXXXXXXXXXX ", " XXXXXXXXXXX XXXX XXXXXXXXXXX ", " XXXXXXXXXXX X XXXXXXX ", " XXXXXXXXXXXX X XX XXXXXX ", " XXXXXXXXXXX XX XXX XXXXXX ", " XXXXXX XXX X XXXX XXXXXX ", " XXXXXXX X XXX XX XX XXXXXXX ", " XXXXXXX XXX X X XXXXXXX ", ". XXXXXXX XXXX XXXXX XXXXXX .", ". XXXXXXX X X XXXXXX XXXXXX .", ". XXXXXXXX XXXX XXXXXX XXXXXX .", ".. XXXXXXXX XXXXX XXXXX ..", ".. XXXXXXXXX XXXXXXXX XXXXX ..", "... XXXXXXXXXXXXXXXXXXXX XXXXX ...", ".... XXXXXXXXXXXXXXXXXXXXXXXXX ....", "..... XXXXXXXXXXXXXXXXXXXXXXX .....", "...... XXXXXXXXXXXXXXXXXXXXX ......", "....... XXXXXXXXXXXXXXXXXXX .......", "........ XXXXXXXXXXXXXXX ........", ".......... XXXXXXXXX ..........", "............. ............." }; mj-1.17-src/tiles-v1/6B.xpm0000444006717300001440000000221507005667026013774 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ..... ..... ..... ", "... ... ... ... ... ...", " .. .. .. .. .. .. ", " . . . . . . ", " . . . . . . ", " . . . . . . ", " ..... ..... ..... ", "... .. ... ... ... ...", " ..... ..... ..... ", " . . . . . . ", " . . . . . . ", " . . . . . . ", " .. .. .. .. .. .. ", "... ... ... ... ... ...", " ..... ..... ..... ", " ", " ", " ", " ", " ", " ..... ..... ..... ", "... ... ... ... ... ...", " .. .. .. .. .. .. ", " . . . . . . ", " . . . . . . ", " . . . . . . ", " ..... ..... ..... ", "... .. ... ... ... ...", " ..... ..... ..... ", " . . . . . . ", " . . . . . . ", " . . . . . . ", " .. .. .. .. .. .. ", "... ... ... ... ... ...", " ..... ..... ..... " }; mj-1.17-src/tiles-v1/tongE.xpm0000444006717300001440000000276707005667026014615 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "35 35 3 1", /* colors */ " c #000000", ". c None", "X c #FFFFFF", /* pixels */ "............. .............", ".......... XXXXXXXXX ..........", "........ XXXXXXXXXXXXXXX ........", "....... XXXXXXXX XXXXXXXX .......", "...... XXXXXXXXXX XXXXXXXX ......", "..... XXXXXXXXXXX XXXXXXXXX .....", ".... XXXXXXXXXXXX XXXXXXXXXXX ....", "... XXXXXXXXXXXXX X XXXXXXXXX ...", ".. XXXXXXXXXXXXXX XXXXXXXXX ..", ".. XXXXXXXXX XXXXXXXXX ..", ". XXXXXXXXXXXX XXXXXXXXXXXXXX .", ". XXXXXXXXXXXXXXX X XXXXXXXXXX .", ". XXXXXXXXXXXXXX XXXXXXXXX .", " XXXXXXXXXX X XXXXXXXXX ", " XXXXXXXXXX XX XXXX XXXXXXXX ", " XXXXXXXXXX XX X XXXXXXXX ", " XXXXXXXXXX XXXXXXXX ", " XXXXXXXXXXX XX XXX XXXXXXXXX ", " XXXXXXXXXXXX XXXXXXXXXX ", " XXXXXXXXXXXXX X XX XXXXXXXXXXX ", " XXXXXXXXXXXXXX XXXXXXXXXXXXX ", " XXXXXXXXXXXXX X XXXXXXXXXXXX ", ". XXXXXXXXXXX X XX XXXXXXXXXX .", ". XXXXXXXXXX XX XX XXXXXXXXX .", ". XXXXXXXXX XXX XXX XXXXXXXX .", ".. XXXXXXX XX XXX XXXXX ..", ".. XXXXX XXXX XXXX XXXX ..", "... XXXXXXXXXXX XXXXXXXXXXXX ...", ".... XXXXXXXXXXX XXXXXXXXXXX ....", "..... XXXXXXXXXXX XXXXXXXXXX .....", "...... XXXXXXXXXX XXXXXXXXXX ......", "....... XXXXXXXXXXXXXXXXXXX .......", "........ XXXXXXXXXXXXXXX ........", ".......... XXXXXXXXX ..........", "............. ............." }; mj-1.17-src/tiles-v1/5C.xpm0000444006717300001440000000223407005667026013775 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXX XXXXXXXXX", "XXXXXXXXXXXXXX XXXXXXX", "XXXXXX XXXX XXXXXX", "XXXXXXX X XXXXXXXXXX", "XXXXXX XXXX XXXXXXXXXX", "XXXXXX XXX X XXXXXXXX", "XXXXXX XXXX XXXXXXX", "XXXXX X XXXXXX", "XXXXX XX XX XXXXXXXX", "XXXX X XXX XX X XXXXX", "XXXX XX XX XXX", "XXX XX XXXXXXX XX", "XX XXX XXXXXXXXXXXX XX", "XXXXXXX XXXXXXXXXXXXX XXX", "XXXXXXXXXXXXX.XXXXXXXXXXX", "XXXXXXXXXXXX....XXXXXXXXX", "XXXXXXXX.XX.XX..XXXXXXXXX", "XXXXXXXX..XXX..XXXXXXXXXX", "XXXXXXXXX.X......XXXXXXXX", "XXXXXXX.....X.XXXXXXXXXXX", "XXXXXXXXXXX.....XXXXXXXXX", "XXXXXXX.X....XX..XXXXXXXX", "XXXXXXX..XX......XXXXXXXX", "XXXXXXXX.X...X...XXXXXXXX", "XXXXXXXX..XX....XXXXXXXXX", "XXXXXXXX.....XX....XXXXXX", "XXXX..XXX.X.....XX...XXXX", "XXXXX.........XX.XX...XXX", "XX.....XXXXX.X....XX...XX", "XXX.X..........X..XX...XX", "XXXXX.XX..XXXXXX..XX...XX", "XXXXXXXXXXXXXX....X...XXX", "XXXXXXXXXXXXXXXX.....XXXX" }; mj-1.17-src/tiles-v1/1C.xpm0000444006717300001440000000223407005667026013771 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXX XXXXX", "XXXXXXXXXXX XXX", "XX XXXXXXXX XX", "XXX XXXXXXXXXXXXX XX", "XXXX XXXXXXXXXXXXXXXX XX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXX.XXXXXXXXXXX", "XXXXXXXXXXXX....XXXXXXXXX", "XXXXXXXX.XX.XX..XXXXXXXXX", "XXXXXXXX..XXX..XXXXXXXXXX", "XXXXXXXXX.X......XXXXXXXX", "XXXXXXX.....X.XXXXXXXXXXX", "XXXXXXXXXXX.....XXXXXXXXX", "XXXXXXX.X....XX..XXXXXXXX", "XXXXXXX..XX......XXXXXXXX", "XXXXXXXX.X...X...XXXXXXXX", "XXXXXXXX..XX....XXXXXXXXX", "XXXXXXXX.....XX....XXXXXX", "XXXX..XXX.X.....XX...XXXX", "XXXXX.........XX.XX...XXX", "XX.....XXXXX.X....XX...XX", "XXX.X..........X..XX...XX", "XXXXX.XX..XXXXXX..XX...XX", "XXXXXXXXXXXXXX....X...XXX", "XXXXXXXXXXXXXXXX.....XXXX" }; mj-1.17-src/tiles-v1/2C.xpm0000444006717300001440000000223407005667026013772 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXX XXXXXXXXX", "XXXXXXXXXX XXXXXXXX", "XXXXX XXXXXXXXXXXXXX", "XXXXXXX XXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXX XXXXXX", "XXXXXXXXXXX XXXX", "XXX XXXXXXX XXX", "XXXX XXXXXXXXXXX XXX", "XXXXX XXXXXXXXXXXXXX XXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXX.XXXXXXXXXXX", "XXXXXXXXXXXX....XXXXXXXXX", "XXXXXXXX.XX.XX..XXXXXXXXX", "XXXXXXXX..XXX..XXXXXXXXXX", "XXXXXXXXX.X......XXXXXXXX", "XXXXXXX.....X.XXXXXXXXXXX", "XXXXXXXXXXX.....XXXXXXXXX", "XXXXXXX.X....XX..XXXXXXXX", "XXXXXXX..XX......XXXXXXXX", "XXXXXXXX.X...X...XXXXXXXX", "XXXXXXXX..XX....XXXXXXXXX", "XXXXXXXX.....XX....XXXXXX", "XXXX..XXX.X.....XX...XXXX", "XXXXX.........XX.XX...XXX", "XX.....XXXXX.X....XX...XX", "XXX.X..........X..XX...XX", "XXXXX.XX..XXXXXX..XX...XX", "XXXXXXXXXXXXXX....X...XXX", "XXXXXXXXXXXXXXXX.....XXXX" }; mj-1.17-src/tiles-v1/7C.xpm0000444006717300001440000000223407005667026013777 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXX XXXXXXXX XXX", "XXXXXXXX XXXXX XXXX", "XXXXXXXXX XXXX XXXXXXXX", "XXXXXXXXX XXX XXXXXXXXXX", "XXXXXXXXXX XX XXXXXXXXXXX", "XXXXXXXXXX X XXXXXXXXXXXX", "XXXXXXXXXX XXXXXXXXXXXXX", "XXXXXXXXX XX XXXXXXXX", "XXXXXXXX X XXXXXXX", "XXXXXX XXX XXXXXX", "XXX XXXXXX XXXXXXX", "XXXX XXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXX.XXXXXXXXXXX", "XXXXXXXXXXXX....XXXXXXXXX", "XXXXXXXX.XX.XX..XXXXXXXXX", "XXXXXXXX..XXX..XXXXXXXXXX", "XXXXXXXXX.X......XXXXXXXX", "XXXXXXX.....X.XXXXXXXXXXX", "XXXXXXXXXXX.....XXXXXXXXX", "XXXXXXX.X....XX..XXXXXXXX", "XXXXXXX..XX......XXXXXXXX", "XXXXXXXX.X...X...XXXXXXXX", "XXXXXXXX..XX....XXXXXXXXX", "XXXXXXXX.....XX....XXXXXX", "XXXX..XXX.X.....XX...XXXX", "XXXXX.........XX.XX...XXX", "XX.....XXXXX.X....XX...XX", "XXX.X..........X..XX...XX", "XXXXX.XX..XXXXXX..XX...XX", "XXXXXXXXXXXXXX....X...XXX", "XXXXXXXXXXXXXXXX.....XXXX" }; mj-1.17-src/tiles-v1/7D.xpm0000444006717300001440000000212707005667026014001 0ustar jcbusers/* XPM */ static char * 7D_xpm[] = { "25 35 3 1", ". c #EAEAEA", "+ c #002065", "@ c #B00000", "...+++...................", ".++...++.................", ".+.+++.+.................", "+.++.++.+..+++...........", "+.+.+.+.+++...++.........", "+.++.++.++.+++.+.........", ".+.+++.++.++.++.+..+++...", ".++...+++.+.+.+.+++...++.", "...+++..+.++.++.++.+++.+.", ".........+.+++.++.++.++.+", ".........++...+++.+.+.+.+", "...........+++..+.++.++.+", ".................+.+++.+.", ".................++...++.", "...................+++...", ".........................", ".....@@@.........@@@.....", "...@@...@@.....@@...@@...", "...@.@@@.@.....@.@@@.@...", "..@.@@.@@.@...@.@@.@@.@..", "..@.@.@.@.@...@.@.@.@.@..", "..@.@@.@@.@...@.@@.@@.@..", "...@.@@@.@.....@.@@@.@...", "...@@...@@.....@@...@@...", ".....@@@.........@@@.....", ".....@@@.........@@@.....", "...@@...@@.....@@...@@...", "...@.@@@.@.....@.@@@.@...", "..@.@@.@@.@...@.@@.@@.@..", "..@.@.@.@.@...@.@.@.@.@..", "..@.@@.@@.@...@.@@.@@.@..", "...@.@@@.@.....@.@@@.@...", "...@@...@@.....@@...@@...", ".....@@@.........@@@.....", "........................."}; mj-1.17-src/tiles-v1/8B.xpm0000444006717300001440000000221507005667026013776 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ..... ..... ", "... ... ... ...", " .. .. .. .. ", " . . . . ", " . . ..... . . ", " . . .. .. . . ", " ..... . ... ..... ", "... ... .. ... .. ... ...", " ..... .... .... ..... ", " . . .. .. .. .. . . ", " . . .... .... . . ", " . .. .. .. .. . ", " .. . . ... .. ", "... ... ... ...", " ..... ..... ", " ", " ", " ", " ", " ", " ..... ..... ", "... ... ... ...", " .. . . . . .. ", " . .. .. .. .. . ", " . . .... .... . . ", " . . .. .. .. .. . . ", " ..... .... .... ..... ", "... ... .. ... .. ... ...", " ..... ... . ..... ", " . . .. .. . . ", " . . ..... . . ", " . . . . ", " .. .. .. .. ", "... ... ... ...", " ..... ..... " }; mj-1.17-src/tiles-v1/1S.xpm0000444006717300001440000000224707005667026014015 0ustar jcbusers/* XPM */ static char *1S[] = { /* width height ncolors chars_per_pixel */ "25 35 4 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", "o c #063A30", /* pixels */ "XXXXXX.XXXXXXXXXXXXXXXXXX", "XXX....XXXXXXXXXXXXXXXXXX", "XXXX.X...XXXXXXXXXXXXXXXX", "XXXX.X.XXXXXXXXXXXoXXXX X", "XXXX.X.....XXXXXXXoXXX X", "XXX......XXXXXXXoooXoX X", "X....XXX.XXXXXXXXooooXX X", "XXX.......XXXXXXXoooXXX X", "XX...XX..X..XXXXXooXXXX ", "X..X.....XXXXXXXXoXXXXXXX", "XXXX.XX..XXXXXXoooooXXXXX", "XXXX....XXXXXXXooooooXXXX", "XXXXXXXXXXXXXXXooXoooXXXX", "XXXXXXXXXXXXXXXooooXXXXXX", "XXXXXXXXXXXXXXoooXXXooooX", "XXXooooXXXXXXXXXXXooXXXoX", "XXooXXooXXX.....XXoooXooX", "XXooooooXX..XXX..XoXoooXX", "XXXXXoX....XXXX..XXXXXXXX", "XXXXXX..XX..XX.....XXXXXX", "XXXXXX.XXX......XX.XXXXXX", "XXXXXX.XXXX.XX.XXX..XXXXX", "XXXXXXX.XXX....XXX..XXXXX", "XXXXXXX.XX......X..XXXXXX", "XXXXXXX.....XX....XXoooXX", "XXXXXXXXXXX.XXX..XXooXooX", "XXXXXXXXXXX.XXX.XoXoXXooX", "XXXXXXXXXXX....XXoXoXXoXX", "XXoooooooooooXoXXoooooXXX", "XooooXXoooooooooXXooooXXX", "ooooXXXXooXoooooooXoooXXX", "oXoXXXXooooXXooooooooooXX", "oooXXXXoooXXXXXXXXoXooooX", "ooXXXXXXoXXXXXXXXXXXXXooX", "XXXXXXXXXXXXXXXXXXXXXXXoX" }; mj-1.17-src/tiles-v1/3C.xpm0000444006717300001440000000223407005667026013773 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXX XXXXXXXXX", "XXXXXXXXXXX XXXXXXX", "XXXXXXXX XXXX XXXXXXX", "XXXXXX XXXXXXXXXXXXXXXX", "XXXXXXXXXXXXX XXXXXXXXX", "XXXXXXXXXX XXXXXXXX", "XXXXXXXX XXXX XXXXXXXXX", "XXXXXXXX XXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXX XXXXXXX", "XXXXXXXXX XXXXX", "XXXX XXXXXXXX XXXX", "XXXXX XXXXXXXXXXXX XXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXX.XXXXXXXXXXX", "XXXXXXXXXXXX....XXXXXXXXX", "XXXXXXXX.XX.XX..XXXXXXXXX", "XXXXXXXX..XXX..XXXXXXXXXX", "XXXXXXXXX.X......XXXXXXXX", "XXXXXXX.....X.XXXXXXXXXXX", "XXXXXXXXXXX.....XXXXXXXXX", "XXXXXXX.X....XX..XXXXXXXX", "XXXXXXX..XX......XXXXXXXX", "XXXXXXXX.X...X...XXXXXXXX", "XXXXXXXX..XX....XXXXXXXXX", "XXXXXXXX.....XX....XXXXXX", "XXXX..XXX.X.....XX...XXXX", "XXXXX.........XX.XX...XXX", "XX.....XXXXX.X....XX...XX", "XXX.X..........X..XX...XX", "XXXXX.XX..XXXXXX..XX...XX", "XXXXXXXXXXXXXX....X...XXX", "XXXXXXXXXXXXXXXX.....XXXX" }; mj-1.17-src/tiles-v1/SW.xpm0000444006717300001440000000221507005667026014056 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000000", ". c #EAEAEA", /* pixels */ ".........................", ".........................", ".........................", "......... ..............", ".......... ...........", "........... .........", "............ ..........", "............ ..........", "............ ...........", "............ . .........", "............ ........", "........... .......", "........ . .........", "............ . ........", "............ .. .......", "....... .... .......", "....... . ...", "........ . .. ..", "....... .. ... ..", ".. ... . .... ..", "... . ... .. .. ...", "... ... . . ...", ".... .... ..... ...", ".... . . ...... ...", "..... .... ...... ...", "...... ..... ...", "....... ........ ...", "................... ....", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/tiles-v1/EW.xpm0000444006717300001440000000221507005667026014040 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000000", ". c #EAEAEA", /* pixels */ ".........................", ".........................", ".........................", "........... ...........", "............ ..........", "............ ..........", "............ ...........", "............ . ........", "............ .......", "....... .......", "......... ...........", "............ . .......", "........... ......", "...... . .....", "...... .. .... ....", "...... .. . ....", "...... ....", "....... .. ... .....", "........ ......", "......... . .. .......", ".......... .........", "......... . ........", "........ . .. .......", "....... .. .. ......", "...... ... ... .....", "..... .. ... ...", "... .... .... ..", ".......... ...........", "........... ...........", "............ ...........", "............ ............", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/tiles-v1/4D.xpm0000444006717300001440000000221507005667026013774 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #002065", ". c #EAEAEA", /* pixels */ ".... ....... ....", "... ... ..... ... ...", ".. . . ... . . ..", ". . . . . . . . . . .", ". . . . . . . . . . . . .", ". . . . . . . . . . .", ". . . . . . . . . . . . .", ". . . . . . . . . . .", ".. . . ... . . ..", "... ... ..... ... ...", ".... ....... ....", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".... ....... ....", "... ... ..... ... ...", ".. . . ... . . ..", ". . . . . . . . . . .", ". . . . . . . . . . . . .", ". . . . . . . . . . .", ". . . . . . . . . . . . .", ". . . . . . . . . . .", ".. . . ... . . ..", "... ... ..... ... ...", ".... ....... ...." }; mj-1.17-src/tiles-v1/4B.xpm0000444006717300001440000000221507005667026013772 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ..... ..... ", " ... ... ... ... ", " .. .. .. .. ", " . . . . ", " . . . . ", " . . . . ", " ..... ..... ", " ... ... ... ... ", " ..... ..... ", " . . . . ", " . . . . ", " . . . . ", " .. .. .. .. ", " ... ... ... ... ", " ..... ..... ", " ", " ", " ", " ", " ", " ..... ..... ", " ... ... ... ... ", " .. .. .. .. ", " . . . . ", " . . . . ", " . . . . ", " ..... ..... ", " ... ... ... ... ", " ..... ..... ", " . . . . ", " . . . . ", " . . . . ", " .. .. .. .. ", " ... ... ... ... ", " ..... ..... " }; mj-1.17-src/tiles-v1/GD.xpm0000444006717300001440000000221507005667026014017 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ", " ", " ", " ", " .. ", " ... ", " ... ", " . .. ", " .. . ", " . ... . . ", " ..... . .. ", " . ..... ... ", " .. . ... .. .. ", " .. ... . ... ", " .. ... .. .. ", " . . .. ....... ", " .. .. .. ... ", " . .. ........ ", " . .. .. .. .... ", " . .... .... .... ", " . .. ... .......... ", " . .. ...... .... ... ", " . ... ............. ", " . ... ... .... .... ", " .. ........... . ", " .... . .. ... ", " .. .. .... ", " . ... ", " ... ", " .. ", " ", " ", " ", " ", " " }; mj-1.17-src/tiles-v1/5D.xpm0000444006717300001440000000223407005667026013776 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #002065", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXX XXXXXXX XXXX", "XXX XXX XXXXX XXX XXX", "XX X X XXX X X XX", "X X X X X X X X X X X", "X X X X X X X X X X X X X", "X X X X X X X X X X X", "X X X X X X X X X X X X X", "X X X X X X X X X X X", "XX X X XXX X X XX", "XXX XXX XXXXX XXX XXX", "XXXX XXXXXXX XXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXX.....XXXXXXXXXX", "XXXXXXXXX..XXX..XXXXXXXXX", "XXXXXXXX..X...X..XXXXXXXX", "XXXXXXX..X.X.X.X..XXXXXXX", "XXXXXXX.X.X.X.X.X.XXXXXXX", "XXXXXXX.X..X.X..X.XXXXXXX", "XXXXXXX.X.X.X.X.X.XXXXXXX", "XXXXXXX..X.X.X.X..XXXXXXX", "XXXXXXXX..X...X..XXXXXXXX", "XXXXXXXXX..XXX..XXXXXXXXX", "XXXXXXXXXX.....XXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXX XXXXXXX XXXX", "XXX XXX XXXXX XXX XXX", "XX X X XXX X X XX", "X X X X X X X X X X X", "X X X X X X X X X X X X X", "X X X X X X X X X X X", "X X X X X X X X X X X X X", "X X X X X X X X X X X", "XX X X XXX X X XX", "XXX XXX XXXXX XXX XXX", "XXXX XXXXXXX XXXX" }; mj-1.17-src/tiles-v1/3B.xpm0000444006717300001440000000221507005667026013771 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ..... ", " ... ... ", " .. .. ", " . . ", " . . ", " . . ", " ..... ", " ... ... ", " ..... ", " . . ", " . . ", " . . ", " .. .. ", " ... ... ", " ..... ", " ", " ", " ", " ", " ", " ..... ..... ", "... ... ... ...", " .. .. .. .. ", " . . . . ", " . . . . ", " . . . . ", " ..... ..... ", "... ... ... ...", " ..... ..... ", " . . . . ", " . . . . ", " . . . . ", " .. .. .. .. ", "... ... ... ...", " ..... ..... " }; mj-1.17-src/tiles-v1/4F.xpm0000444006717300001440000000224707005667026014003 0ustar jcbusers/* XPM */ static char *4F[] = { /* width height ncolors chars_per_pixel */ "25 35 4 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", "o c #063A30", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXX.XXXXXXXXXXXXXXXXXXXXX", "XX..XXXXXXXXXXXXXXXXXXXXX", "X...XXXXXXXXoXXXX X XXXX", "X.X.XXXXXXXoXXX XXX", "X....XXXXXoXXXXXX X XXXX", "XXX.XXXXXXoXXXXXX X XXXX", "XXXXXXXXXXoXXXXXX XXX XXX", "XXXXXXXXXXoXXXXX XXX XXX", "XXXXXXXXXXooXXXX XXXX XXX", "XXXXXXXXXXooXXXX XXX XXX", "XXXXXXXXXXooXXXXXoXXXXXXX", "XXXXooXXXXoooXXXXoXXXXXXX", "XXXoooXoXXXooXXXooXXXXXXX", "XXXoXXXXoXXXoXXXXXXXXXoXX", "XXoooXXXXoXXooXXXXXXooooX", "XXXXXXXXXXoXXoXXooooooXXX", "XXXXXXXXXXoXoooooXoXXooXX", "XXXXXXooooooXooXoXXoXXooX", "XXXXoooXoooXXXoXooXXXoXoX", "XXXooXoooooXXXooXXXXXoXoX", "XXoooXoooXoooooXXoooXXooX", "XoXXoXooXXooXXooooXooXXoX", "XoXXXXoXXXoXXXooXXXXXoooX", "XoXoXXooXXooooooXXXXXXooX", "XoooXXooXXXooXooXXXXXXoXX", "XXoooXooXXXoXXXoXXXoXXXXX", "XXoXXXXoXXXoXXXooXooXXXXX", "XXXXXoooXXXooooooooXXooXX", "XXXXXXXooXXooXXXooXXoXXXX", "XXXXXXXXoXXoXXXXooXoXXXoX", "XXXXXXXXooXooooooooXXXoXX", "XXXXXoXXXoooXXXXXXXXXoXXX", "XXXXXXXXXXoXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-v1/3F.xpm0000444006717300001440000000224707005667026014002 0ustar jcbusers/* XPM */ static char *3F[] = { /* width height ncolors chars_per_pixel */ "25 35 4 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", "o c #063A30", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "X....XXXXXXXXXXXXXXXXX X", "X.XX..XXXXXXXXXXXXXXXXXXX", "XXX...XXXXXXXXXXXXX ", "XXX...XXXXXoXXoXXXXXXXXXX", "XXXXX.XXXXXooooXXXXX XXXX", "XX.X..XXXXXXooXXXX X XX X", "XXX..XXooooXoXXXXXXX XXXX", "XXXXXXXooooXoXXXXX X", "XXXXXXXXoooooXXXXXXX XXXX", "XXXXXXXXXoooXXXXX X XXXX", "XXXoooXXXXooXXXXXXXXXXXXX", "XXXooooXXXoXXXXXXXXXXXXXX", "XXXXXXoXX.....XXXXXXXXXXX", "XXXXXXo....X.....XXXXXXXX", "XXXXXX.....XX....XXXXXXXX", "XXXXXX..X..XX..X..XooXXXX", "XXXXX.........XX..oXXXXXX", "oooXX............XoooXXXX", "oooXX.XX.........XXXXXXXX", "ooXXX....X....XX..XXXXXXX", "XXXXX..........XX.XXXXXXX", "XXXXX..XX.........XXXXXXX", "XXXXX.XXX........XXXooXXX", "XXXXX.....XX..XXXXXoooXXX", "XXXXXX....XX.XXXXXooXXXXX", "XoXXXXXX.....XXXXXoXXXXXX", "ooooooooX...XXXXXoXXXXXXX", "oooXXXXoooXXXXXXXoXXXXXXX", "XXXXXXXooooooXXXXooooooXX", "XXXXXoooXXXooooooooooooXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-v1/5B.xpm0000444006717300001440000000223407005667026013774 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #B00000", ". c #EAEAEA", "X c #063A30", /* pixels */ ".XXXXX.............XXXXX.", "XXX.XXX...........XXX.XXX", ".XX.XX.............XX.XX.", "..X.X...............X.X..", "..X.X...............X.X..", "..X.X...............X.X..", ".XXXXX.............XXXXX.", "XXX.XXX...........XXX.XXX", ".XXXXX.............XXXXX.", "..X.X...............X.X..", "..X.X..... .....X.X..", "..X.X.... . ....X.X..", ".XX.XX.... . ....XX.XX.", "XXX.XXX.... . ....XXX.XXX", ".XXXXX..... . .....XXXXX.", "........... . ...........", ".......... ..........", "......... . .........", ".......... ..........", "........... . ...........", ".XXXXX..... . .....XXXXX.", "XXX.XXX.... . ....XXX.XXX", ".XX.XX.... . ....XX.XX.", "..X.X.... . ....X.X..", "..X.X..... .....X.X..", "..X.X...............X.X..", ".XXXXX.............XXXXX.", "XXX.XXX...........XXX.XXX", ".XXXXX.............XXXXX.", "..X.X...............X.X..", "..X.X...............X.X..", "..X.X...............X.X..", ".XX.XX.............XX.XX.", "XXX.XXX...........XXX.XXX", ".XXXXX.............XXXXX." }; mj-1.17-src/tiles-v1/--.xpm0000444006717300001440000000217607005667026013744 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 1 1", /* colors */ " c #948D13", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " }; mj-1.17-src/tiles-v1/WW.xpm0000444006717300001440000000221507005667026014062 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000000", ". c #EAEAEA", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "................. ......", "................ .....", "............ ...", "....... ..... ..", ".. ..... . ..", "... ......... .......", ".............. ........", "...... ..... .........", "....... .... .........", "....... ... . .........", "........ .. .......", "....... . .....", "...... . .. .. ....", "... . . .. ... ...", "... ... .. .... ...", "... ... . ... ...", ".... ... ....", "..... .....", "...... .... ......", "....... ...... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/tiles-v1/1F.xpm0000444006717300001440000000224707005667026014000 0ustar jcbusers/* XPM */ static char *1F[] = { /* width height ncolors chars_per_pixel */ "25 35 4 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", "o c #063A30", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "...XXXXXXXXXXXXX XXXXXXXX", "XX.XXXXXooXXXXXX XX X", "XX.XXXXoooooXXXX XXXXXXXX", "XX.XXXooooXoXXXX XXX XX", "X...XXooooXoXXX X X XXXX", "XXXXXXXXooXXXXX ", "XXXXXooooXXXXXXX X X XXXX", "XXXXXoXXooooXXXX XXX XX", "XXXXXXooooXXoXXX XXX X XX", "XXXXXXXooXXXoXXXXXXXX XXX", "XXXXoXXoXXXXXXXXXXXXXXXXX", "XXXoXoooXXXXXXXXXXXXXXXXX", "XXXooXooooooXXXXXXoXXXXXX", "XXXXXooXXXXXXXXXXXXoXXXXX", "XXXXXXXX....XXXXXXoXXXXXX", "XXXXXXX..XX..XXXXooXXXXXX", "XXXXX....XX.....oXooXXXXX", "XXXX.............XoXoXXXX", "XXX..XX....X.XX..XoXoXXXX", "XXXX.XX..XXX....XXXoXXXXX", "XXXX......XX....XXoXXXXXX", "XXXXX......X...XXXoXXXXXX", "XXXooX..XX...X.XXXoXXXXXX", "XooooX..XX..XX.XXooXXXXXX", "oooooX.........XXooXXXXXX", "XooooXXXXXXXXXXXXooXXXXXX", "XooooXXooooooooXXooXXXXXX", "XXoooooooXXXXooooooXXXXXX", "XooXooXXXXXXXXXoooooXXXXX", "XXXXXoXXXXXXXXXXXooooXXXX", "XXXXXXXXXXXXXXXXXXXoooooX", "XXXXXXXXXXXXXXXXXXXXooooX", "XXXXXXXXXXXXXXXXXXXXXXooX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-v1/4C.xpm0000444006717300001440000000223407005667026013774 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXX XXXXX", "XXXX XX XX XX XXXX", "XXX XXXX XX XXX", "XXX XX XXX XX XXX", "XXX XXX XX XX XXX", "XXXX XXXX X XXX XXXX", "XXXXX XX XXXXX", "XXXXXX XXXXXXX XXXXXX", "XXXXXXX XXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXX.XXXXXXXXXXX", "XXXXXXXXXXXX....XXXXXXXXX", "XXXXXXXX.XX.XX..XXXXXXXXX", "XXXXXXXX..XXX..XXXXXXXXXX", "XXXXXXXXX.X......XXXXXXXX", "XXXXXXX.....X.XXXXXXXXXXX", "XXXXXXXXXXX.....XXXXXXXXX", "XXXXXXX.X....XX..XXXXXXXX", "XXXXXXX..XX......XXXXXXXX", "XXXXXXXX.X...X...XXXXXXXX", "XXXXXXXX..XX....XXXXXXXXX", "XXXXXXXX.....XX....XXXXXX", "XXXX..XXX.X.....XX...XXXX", "XXXXX.........XX.XX...XXX", "XX.....XXXXX.X....XX...XX", "XXX.X..........X..XX...XX", "XXXXX.XX..XXXXXX..XX...XX", "XXXXXXXXXXXXXX....X...XXX", "XXXXXXXXXXXXXXXX.....XXXX" }; mj-1.17-src/tiles-v1/7B.xpm0000444006717300001440000000223407005667026013776 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #B00000", ". c #EAEAEA", "X c #063A30", /* pixels */ ".......... ..........", "......... . .........", ".......... . ..........", "........... . ...........", ".......... ..........", "......... . .........", ".......... ..........", "........... . ...........", ".......... . ..........", "......... . .........", ".......... ..........", ".........................", ".XXXXX....XXXXX....XXXXX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XX.XX....XX.XX....XX.XX.", "..X.X......X.X......X.X..", ".XXXXX....XXXXX....XXXXX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XXXXX....XXXXX....XXXXX.", "..X.X......X.X......X.X..", ".XX.XX....XX.XX....XX.XX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XXXXX....XXXXX....XXXXX.", ".........................", ".XXXXX....XXXXX....XXXXX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XX.XX....XX.XX....XX.XX.", "..X.X......X.X......X.X..", ".XXXXX....XXXXX....XXXXX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XXXXX....XXXXX....XXXXX.", "..X.X......X.X......X.X..", ".XX.XX....XX.XX....XX.XX.", "XXX.XXX..XXX.XXX..XXX.XXX", ".XXXXX....XXXXX....XXXXX." }; mj-1.17-src/tiles-v1/9B.xpm0000444006717300001440000000223407005667026014000 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #B00000", ". c #EAEAEA", "X c #063A30", /* pixels */ ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XX.XX.... . ....XX.XX.", "..X.X...... . ......X.X..", ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX.", "..X.X...... . ......X.X..", ".XX.XX.... . ....XX.XX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX.", ".........................", ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XX.XX.... . ....XX.XX.", "..X.X...... . ......X.X..", ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX.", "..X.X...... . ......X.X..", ".XX.XX.... . ....XX.XX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX.", ".........................", ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XX.XX.... . ....XX.XX.", "..X.X...... . ......X.X..", ".XXXXX.... ....XXXXX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX.", "..X.X...... . ......X.X..", ".XX.XX.... . ....XX.XX.", "XXX.XXX.. . ..XXX.XXX", ".XXXXX.... ....XXXXX." }; mj-1.17-src/tiles-v1/3S.xpm0000444006717300001440000000224707005667026014017 0ustar jcbusers/* XPM */ static char *3S[] = { /* width height ncolors chars_per_pixel */ "25 35 4 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", "o c #063A30", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXX.XXXX.XXXXXXXoXXXX X", "XXX..XXX.XXXXXXXooXXXXX X", "XXX...XX....XXXXXooXXXX X", "X...XX...XXXXXXoooooX X", "XXX..XXX.XXXXXXoXXoXXXXX ", "XXX...X...XXXXXooXoXX X ", "X...XX.XX..XXXXXoooXXX X", "XXX...XXXX..XXXXoooXXXXXX", "XXX..XXXXXX..XXXXooXXXXXX", "XXX.XXXXXXXXXXXXXooXXXXXX", "XXXXXXXXXXXXXXXXXooXXXXXX", "XoooooXXXXXoooooooooXXXXX", "oooXXoXXXXXXoXXooXXoXXXXX", "XoooooXXXXXXooXooXXoXXXXX", "XXXXXXXXX..XXoooooooXXXXX", "XXXXXX...X..XXXoXXXoXXXXX", "XXXXX..XXXX..XXXXooooXXXX", "XXXXX.XXXXXX.XXXooXXoXXXX", "XXXX.....XXX..XXoooooXXXX", "XXX..X.....XX..XXXooXXXXX", "XXX..X..XX.XXX.XXXXXXXXXX", "XXX......XX.XX.XXXXXXXXXX", "XXX.X.XX..X.XX.XXXXXXXXXX", "XXX.X..X..X.XX.XXXXXoooXX", "XXX............XXXXooXoXX", "XXXX....X....XXXoXoXXooXX", "XXXXX.......XXXooooXooXXX", "ooooooooooXXXXXXoooooXXXX", "oXXooXooooooooooooooXXXXX", "XXooXXoXXooXXXXXoooooXXXX", "ooXXXXoXooXXXXXXXXoooooXX", "XXXXXXoooXXXXXXXXXXXooooo", "XXXXXXXoXXXXXXXXXXXXXXXXo", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-v1/1B.xpm0000444006717300001440000000223407005667026013770 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #B00000", ". c #EAEAEA", "X c #063A30", /* pixels */ ".........................", "...X........... . . . ...", "...X..... .X.... . . . ..", "..XX. . X... . . . . .", ".XX..... ..X. . . . . ", ".XX..... X.. . . .", ".XX... . ..X. . . . . ", "X.X.. . . ...X. . .", "X.X.. ... X..X X X . ", "X.X.. .. ..X.X.XX . .", "X.X.. .X...X.X X X . . ", "X.X.. ..X..X.X.X X X . .", "X..X. ..X..XXXXXXXX . . ", "X.XX. ..X.X . . .XX .. .", "X .X. ..XXX X X XXX.. ...", "X. XXXX.X. . . . XX......", "X .XX.X.XX X X X XXXX....", "X. XX.X.X . . . .XXXXX...", "X .XX...XXXXXXXXX.XX..XX.", "X.XXX...XXX. . XX...X...X", "XXX.XX..XX. . .X.XX..X...", "XX...XXXXX . . .X.XXX....", "X.........X . .X.X.X.XX..", ".......... XXXXXXXXXXXXXX", "......... .. . .......", "........ . .. .. .......", "....... ... .. ... ......", "...... ....... .........", "...... . ...... .........", "...... ...XXXXX X........", "...XXXX XX.... ..X.X.X...", ".X.X.......... ..X..X....", "..XX....... . ...XXX.X...", "...XX....... .....X...X.", "XXXX..... .. .....XXXXX" }; mj-1.17-src/tiles-v1/NW.xpm0000444006717300001440000000221507005667026014051 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000000", ". c #EAEAEA", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "............. .........", "............. .......", ".............. .......", "......... .... .......", "....... ... ........", "....... .. .. ....", "........ .. .. ...", "......... ... . ..", "... . ... ....", "... . ... .......", ".... .. ... .........", "......... ... .........", "......... . . .........", "... ..... .. ........", "... ... ... ..", "... .... ..", ".. . ....... ...", "... ... ...............", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/tiles-v1/4S.xpm0000444006717300001440000000224707005667026014020 0ustar jcbusers/* XPM */ static char *4S[] = { /* width height ncolors chars_per_pixel */ "25 35 4 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", "o c #063A30", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXX...XXXXXXXXXXXXX XX", "XXX......XXXoXXXXXXXX XX", "XXXX..X.XXXXoXXXXXXXX XX", "XXXXX...XXXXooXXXXXX X XX", "XXXX....XXXXooXXXXXX ", "XX..XXX..XXXooXXXXXX X", "X..XXXXX..XoXoXXXXXXXXX X", "X.XXX...XX.oooXXXXXXXXXXX", "XXXXXXXXXXXXooXXXXXooooXX", "XXXX....XXXXoXXXXXooXXooX", "XXXXXXXXXXXoooooXooXXXXoX", "XXXXXXXXXXXoooXXXoooooooX", "XXXXXXoooXooXXXXXooXXXXXX", "XXXXXooXoooXXXXXooooXXXXX", "XXXXXooooooXXXXXooXooXXXX", "XXXXXoXXooXXXXXXoXXoXXXXX", "XXXXXXXXoX......ooooXXXXX", "XXXXXXXXXX.XXXX.XXXXXXXXX", "XXXXXXX....XXX....XXXXXXX", "XXXXXX..X....X.....XXXXXX", "XXXXXX..XX.....XXX.XXXXXX", "XXXXXXX.XX.XX..XXX.XXXXXX", "XXXXXXX....X...XXX.XXXXXX", "XXXXXXXXX...X..XX..XXXXXX", "XXXXXooXX.XXX.....XXXXXXX", "XXooooooX.XXX.XXXXXooXXXX", "XXoXXXXoo.....XXooooooXXX", "XXoooXXXooXXXXXXoXXXXooXX", "XXXXooooooXoooXooXXXXXooX", "XXXXXXXXooooXoooooooXXXoX", "XXXXXXXXXooXXoooXXXoooooX", "XXXXXXXXXooXXXoXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-v1/XX.xpm0000444006717300001440000000217607005667026014072 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 1 1", /* colors */ " c #FF0000", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " }; mj-1.17-src/tiles-v1/tongN.xpm0000444006717300001440000000276707005667026014626 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "35 35 3 1", /* colors */ " c #000000", ". c None", "X c #FFFFFF", /* pixels */ "............. .............", ".......... XXXXXXXXX ..........", "........ XXXXXXXXXXXXXXX ........", "....... XXXXXXXXXXXXXXXXXXX .......", "...... XXXXXXXXXXXXXXXXXXXXX ......", "..... XXXXXXXXXXXXXXXXXXXXXXX .....", ".... XXXXXXXXXXXXXXXXXXXXXXXXX ....", "... XXXXXXXXXXXXXXXXXXXXXXXXXXX ...", ".. XXXXXXXXXXXXXXXXXXXXXXXXXXXXX ..", ".. XXXXXXXXXXXXXXX XXXXXXXXXXX ..", ". XXXXXXXXXXXXXXXX XXXXXXXXXX .", ". XXXXXXXXXXXXXXXXX XXXXXXXXXX .", ". XXXXXXXXXXXX XXXX XXXXXXXXXX .", " XXXXXXXXXXX XXX XXXXXXXXXXXX ", " XXXXXXXXXXX XX XX XXXXXXXX ", " XXXXXXXXXXXX XX XX XXXXXXX ", " XXXXXXXXXXXXX XXX X XXXXXX ", " XXXXXXX X XXX XXXXXXXX ", " XXXXXXX X XXX XXXXXXXXXXX ", " XXXXXXXX XX XXX XXXXXXXXXXXXX ", " XXXXXXXXXXXXX XXX XXXXXXXXXXXXX ", " XXXXXXXXXXXXX X X XXXXXXXXXXXXX ", ". XXXXXX XXXXX XX XXXXXXXXXXX .", ". XXXXXX XXX XXX XXXXX .", ". XXXXXX XXXX XXXXX .", ".. XXXX X XXXXXXX XXXXX ..", ".. XXXXX XXX XXXXXXXXXXXXXXXXX ..", "... XXXXXXXXXXXXXXXXXXXXXXXXXXX ...", ".... XXXXXXXXXXXXXXXXXXXXXXXXX ....", "..... XXXXXXXXXXXXXXXXXXXXXXX .....", "...... XXXXXXXXXXXXXXXXXXXXX ......", "....... XXXXXXXXXXXXXXXXXXX .......", "........ XXXXXXXXXXXXXXX ........", ".......... XXXXXXXXX ..........", "............. ............." }; mj-1.17-src/tiles-v1/WD.xpm0000444006717300001440000000221507005667026014037 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #002060", /* pixels */ " ", " ", " ...................... ", " . . ", " . .................. . ", " . . . . . . ", " . .. .. . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . . . . ", " . .. .. . ", " . . . . . . ", " . .................. . ", " . . ", " ...................... ", " ", " " }; mj-1.17-src/tiles-v1/8C.xpm0000444006717300001440000000223407005667026014000 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXX XXXXXXXXXXXX", "XXXXXXXXXX XXXXXXXXXXX", "XXXXXXXXXXXX XXXXXXXXXXXX", "XXXXXXXXXXXXX XXXXXXXXXXX", "XXXXXXX XXXXX XXXXXXXXXX", "XXXXXXXX XXXX XXXXXXXXX", "XXXXXXX XXXX XXXXXXX", "XXXXXXX X XXXXX XXXX", "XXXXXX XXXXXXXXX X", "XXXX XXXXXXXXXXXX XXX", "X XXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXX.XXXXXXXXXXX", "XXXXXXXXXXXX....XXXXXXXXX", "XXXXXXXX.XX.XX..XXXXXXXXX", "XXXXXXXX..XXX..XXXXXXXXXX", "XXXXXXXXX.X......XXXXXXXX", "XXXXXXX.....X.XXXXXXXXXXX", "XXXXXXXXXXX.....XXXXXXXXX", "XXXXXXX.X....XX..XXXXXXXX", "XXXXXXX..XX......XXXXXXXX", "XXXXXXXX.X...X...XXXXXXXX", "XXXXXXXX..XX....XXXXXXXXX", "XXXXXXXX.....XX....XXXXXX", "XXXX..XXX.X.....XX...XXXX", "XXXXX.........XX.XX...XXX", "XX.....XXXXX.X....XX...XX", "XXX.X..........X..XX...XX", "XXXXX.XX..XXXXXX..XX...XX", "XXXXXXXXXXXXXX....X...XXX", "XXXXXXXXXXXXXXXX.....XXXX" }; mj-1.17-src/tiles-v1/3D.xpm0000444006717300001440000000223407005667026013774 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #002065", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXX XXXXXXXXXXXXXXXXX", "XX XXX XXXXXXXXXXXXXXXX", "X X X XXXXXXXXXXXXXXX", " X X X X XXXXXXXXXXXXXX", " X X X X X XXXXXXXXXXXXXX", " X X X X XXXXXXXXXXXXXX", " X X X X X XXXXXXXXXXXXXX", " X X X X XXXXXXXXXXXXXX", "X X X XXXXXXXXXXXXXXX", "XX XXX XXXXXXXXXXXXXXXX", "XXX XXXXXXXXXXXXXXXXX", "XXXXXXXXXX.....XXXXXXXXXX", "XXXXXXXXX..XXX..XXXXXXXXX", "XXXXXXXX..X...X..XXXXXXXX", "XXXXXXX..X.X.X.X..XXXXXXX", "XXXXXXX.X.X.X.X.X.XXXXXXX", "XXXXXXX.X..X.X..X.XXXXXXX", "XXXXXXX.X.X.X.X.X.XXXXXXX", "XXXXXXX..X.X.X.X..XXXXXXX", "XXXXXXXX..X...X..XXXXXXXX", "XXXXXXXXX..XXX..XXXXXXXXX", "XXXXXXXXXX.....XXXXXXXXXX", "XXXXXXXXXXXXXXXXX XXX", "XXXXXXXXXXXXXXXX XXX XX", "XXXXXXXXXXXXXXX X X X", "XXXXXXXXXXXXXX X X X X ", "XXXXXXXXXXXXXX X X X X X ", "XXXXXXXXXXXXXX X X X X ", "XXXXXXXXXXXXXX X X X X X ", "XXXXXXXXXXXXXX X X X X ", "XXXXXXXXXXXXXXX X X X", "XXXXXXXXXXXXXXXX XXX XX", "XXXXXXXXXXXXXXXXX XXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-v1/2B.xpm0000444006717300001440000000221507005667026013770 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #EAEAEA", ". c #063A30", /* pixels */ " ..... ", " ... ... ", " .. .. ", " . . ", " . . ", " . . ", " ..... ", " ... ... ", " ..... ", " . . ", " . . ", " . . ", " .. .. ", " ... ... ", " ..... ", " ", " ", " ", " ", " ", " ..... ", " ... ... ", " .. .. ", " . . ", " . . ", " . . ", " ..... ", " ... ... ", " ..... ", " . . ", " . . ", " . . ", " .. .. ", " ... ... ", " ..... " }; mj-1.17-src/tiles-v1/9D.xpm0000444006717300001440000000223407005667026014002 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #002065", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXX XXXXX XXXXX XXX", "X XXX X XXX X XXX X", "X X X X X X X X X X", " X X X X X X X X X ", " X X X X X X X X X X X X ", " X X X X X X X X X ", "X X X X X X X X X X", "X XXX X XXX X XXX X", "XXX XXXXX XXXXX XXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXX...XXXXX...XXXXX...XXX", "X..XXX..X..XXX..X..XXX..X", "X.X...X.X.X...X.X.X...X.X", ".X..X..X.X..X..X.X..X..X.", ".X.X.X.X.X.X.X.X.X.X.X.X.", ".X..X..X.X..X..X.X..X..X.", "X.X...X.X.X...X.X.X...X.X", "X..XXX..X..XXX..X..XXX..X", "XXX...XXXXX...XXXXX...XXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXX XXXXX XXXXX XXX", "X XXX X XXX X XXX X", "X X X X X X X X X X", " X X X X X X X X X ", " X X X X X X X X X X X X ", " X X X X X X X X X ", "X X X X X X X X X X", "X XXX X XXX X XXX X", "XXX XXXXX XXXXX XXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-v1/GPL0000444006717300001440000004324307005667605013355 0ustar jcbusers GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. mj-1.17-src/tiles-v1/2S.xpm0000444006717300001440000000224707005667026014016 0ustar jcbusers/* XPM */ static char *2S[] = { /* width height ncolors chars_per_pixel */ "25 35 4 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", "o c #063A30", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXX.XXXXXXXXXXXXX XX", "X.......XXXoXXXXXXX X XX", "XXXX..XXXXXooXXXXXX XX XX", "XXX.....XXXXoXXXooXXX XXX", "XXX.XXX.XXXXooXXoXXX XXXX", "XXX.....XXXXXoXXoXX XX", "XX....X.XXXXXoXoooXXXXXXX", "X..XX...XXXXXoXoooXXXXXXX", "XXXXX..XXXXXXooXXoXXXXXXX", "XX.......XXXXooooXXXXXXXX", "XXXXXXXXXXXXXoooXXXXXoooX", "XXoooXXXooXXXooXXXXXoooXX", "XXXXooXooXXXXooXXXXoooooX", "XXXXXoooXXXXXXXXXXooXXXXX", "XXXXXooXXXXX..XXXXoXXXXXX", "XXXXXooXXX.....XXXoooXXXX", "XXXXXXoXX...XX..XooXXXXXX", "XXXXXXoXX.X..XX.XoXXXXXXX", "XXXXXXoXX.XX.XX..XXXXXXXX", "XXXXXXoX...X..XX.XXXXXXXX", "XooXXXXX.X.....X.XXXXXXXX", "XXooXXXX.XX....X.XXXXXXXX", "XXXXoXXX.XX..X....XXXXXXX", "XXXXooXo....XX....XooXXXX", "XXXXXoooXX..XXX..XooXXXXX", "XXXXXooXXXX.....XXoXXXXXX", "XXooXXooXXoooXXXXXXXXXXXX", "XXXoXXXooXXXooooooXXXXXXX", "XXXXoXXXoXXXXXXXXXXXXXXXX", "XXXXXoXXooXXXooXXXXXXXXXX", "XXXXXXoXooooooooooXXXXXXX", "XXXXXXooooXXXXXXooXXXXXXX", "XXXXXXXXXXXXXXXXXoXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX" }; mj-1.17-src/tiles-v1/6C.xpm0000444006717300001440000000223407005667026013776 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXX XXXXXXXXXXXX", "XXXXXXXXXXXX XXXXXXXXXXX", "XXXXXXXXXXXX XX XXXXXXX", "XXXXXXXXXXXX XX XXXXXX", "XXXXXXXXXXX XXXXX", "XXXX XXXXX XXXX", "XXXXX XXXX XXXXXX XXXX", "XXXXXXXX XXX XXX XXXXX", "XXXXXXXXX XX XXXXXXXX", "XXXXXXXXX XXXX XXXXXX", "XXXXXXXX XXXXXX XXXXXX", "XXXXXXX XXXXXXXX XXXXXX", "XXXXXX XXXXXXXXXXX XXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXX.XXXXXXXXXXX", "XXXXXXXXXXXX....XXXXXXXXX", "XXXXXXXX.XX.XX..XXXXXXXXX", "XXXXXXXX..XXX..XXXXXXXXXX", "XXXXXXXXX.X......XXXXXXXX", "XXXXXXX.....X.XXXXXXXXXXX", "XXXXXXXXXXX.....XXXXXXXXX", "XXXXXXX.X....XX..XXXXXXXX", "XXXXXXX..XX......XXXXXXXX", "XXXXXXXX.X...X...XXXXXXXX", "XXXXXXXX..XX....XXXXXXXXX", "XXXXXXXX.....XX....XXXXXX", "XXXX..XXX.X.....XX...XXXX", "XXXXX.........XX.XX...XXX", "XX.....XXXXX.X....XX...XX", "XXX.X..........X..XX...XX", "XXXXX.XX..XXXXXX..XX...XX", "XXXXXXXXXXXXXX....X...XXX", "XXXXXXXXXXXXXXXX.....XXXX" }; mj-1.17-src/tiles-v1/9C.xpm0000444006717300001440000000223407005667026014001 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 3 1", /* colors */ " c #000000", ". c #B00000", "X c #EAEAEA", /* pixels */ "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXX XXX XXXXXXXXX", "XXXXXXXX XXX XXXXXXX", "XXXXXXXXX XXX XXXXXX", "XXXXXXXXX X XXXXXX", "XXXXX XXXXXXXXXX", "XXXXXXXXX XX XXXXX XXXXX", "XXXXXXXX XX XXXXX XXXXX", "XXXXXXXX XXX XXXXX XXXX", "XXXXXXX XXXX XXX XXXX", "XXXXXX XXXXXX XXX", "XXXXX XXXXXXXX XXXX", "XXX XXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXXXXXXXXXXX", "XXXXXXXXXXXXX.XXXXXXXXXXX", "XXXXXXXXXXXX....XXXXXXXXX", "XXXXXXXX.XX.XX..XXXXXXXXX", "XXXXXXXX..XXX..XXXXXXXXXX", "XXXXXXXXX.X......XXXXXXXX", "XXXXXXX.....X.XXXXXXXXXXX", "XXXXXXXXXXX.....XXXXXXXXX", "XXXXXXX.X....XX..XXXXXXXX", "XXXXXXX..XX......XXXXXXXX", "XXXXXXXX.X...X...XXXXXXXX", "XXXXXXXX..XX....XXXXXXXXX", "XXXXXXXX.....XX....XXXXXX", "XXXX..XXX.X.....XX...XXXX", "XXXXX.........XX.XX...XXX", "XX.....XXXXX.X....XX...XX", "XXX.X..........X..XX...XX", "XXXXX.XX..XXXXXX..XX...XX", "XXXXXXXXXXXXXX....X...XXX", "XXXXXXXXXXXXXXXX.....XXXX" }; mj-1.17-src/vlazyfixed.h0000444006717300001440000000472415002771311013665 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/vlazyfixed.h,v 12.0 2009/06/28 20:43:31 jcb Rel $ * vlazyfixed.h * Another slight variation on the GTK+ fixed widget. * It doesn't call queue_resize when children are removed, added, * or move, EXCEPT when the first child is added. * This has the consequence that move DOESN'T work, unles you * explicitly call resize. But that's OK, since we only use this * in a situation where we can physically move the child window. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2001 by J. C. Bradfield. * * This file may be used under the terms of the * * GNU Lesser General Public License (any version). * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #ifndef __VLAZY_FIXED_H__ #define __VLAZY_FIXED_H__ #include #include "sysdep.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #define VLAZY_FIXED(obj) (GTK_CHECK_CAST ((obj), vlazy_fixed_get_type(), VlazyFixed)) #define VLAZY_FIXED_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), vlazy_fixed_get_type(), VlazyFixedClass)) #define IS_VLAZY_FIXED(obj) (GTK_CHECK_TYPE ((obj), vlazy_fixed_get_type())) typedef struct _VlazyFixed VlazyFixed; typedef struct _VlazyFixedClass VlazyFixedClass; struct _VlazyFixed { GtkFixed fixed; }; struct _VlazyFixedClass { GtkFixedClass parent_class; }; GtkType vlazy_fixed_get_type (void); GtkWidget* vlazy_fixed_new (void); void vlazy_fixed_put (VlazyFixed *fixed, GtkWidget *widget, gint16 x, gint16 y); void vlazy_fixed_move (VlazyFixed *fixed, GtkWidget *widget, gint16 x, gint16 y); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __VLAZY_FIXED_H__ */ mj-1.17-src/xmj.man0000444006717300001440000020544215002771311012622 0ustar jcbusers.\" $Header: /home/jcb/MahJong/newmj/RCS/xmj.man,v 12.12 2025/04/25 17:39:37 jcb Exp $ .\" Copyright (c) 2000 J. C. Bradfield .de TQ .br .ns .TP \\$1 .. .de GO .br .TP \fB\\$1\fP (\fB\\$2\fP) \\$3 .. .TH XMJ 1 "Mah-Jong" "J.C.Bradfield" .SH NAME xmj, mj\-server, mj\-player \- programs for playing Mah\-Jong .SH SYNOPSIS .TP \fBxmj\fP [\fB\-\-id \fIidnumber\fP] [\fB\-\-server \fIaddress\fP] [\fB\-\-name \fIplayername\fP] [\fB\-\-connect\fP] [\fB\-\-show\-wall\fP | \fB\-\-no\-show\-wall\fP] [\fB\-\-size N\fP] [\fB\-\-animate | \fB\-\-no\-animate\fP] [\fB\-\-tileset \fIdirectory\fP] [\fB\-\-tileset\-path \fIdirectory-path\fP] [\fB\-\-dialogs\-popup\fP | \fB\-\-dialogs\-below\fP | \fB\-\-dialogs\-central\fP] [\fB\-\-use\-system\-gtkrc\fP | \fB\-\-no\-use\-system\-gtkrc\fP] [\fB\-\-gtk2\-rcfile \fIfile\fP] [\fB\-\-echo\-server\fP] [\fB\-\-pass\-stdin\fP] [\fB\-\-monitor\fP] .TP \fBmj\-server\fP [\fB\-\-server \fIaddress\fP] [\fB\-\-timeout \fIseconds\fP] [\fB\-\-pause \fIdeciseconds\fP] [\fB\-\-random\-seats | \-\-id\-order\-seats\fP] [\fB\-\-disconnect\-penalties \fIN1,N2,N3\fP] [\fB\-\-end-on-disconnect\fP] [\fB\-\-exit-on-disconnect\fP] [\fB\-\-save-on-exit\fP] [\fB\-\-option-file \fIfile\fP] [\fB\-\-load-game \fIfile\fP] [\fB\-\-no-id-required\fP] [\fB\-\-no-manager\fP] [\fB\-\-auth-basic \fIid:password\fP]*4 [\fB\-\-debug\fP] [\fB\-\-logfile \fIfile\fP] [\fB\-\-no-special-scores\fP] [\fB\-\-seed N\fP] [\fB\-\-wallfile \fIfile\fP] [\fB\-\-hand-history\fP] [\fB\-\-nohist\fP] .TP \fBmj\-player\fP [\fB\-\-id \fIidnumber\fP] [\fB\-\-name \fIplayername\fP] [\fB\-\-server \fIaddress\fP] [\fB\-\-password \fIpassword\fP] [\fIstrategy options\fP] .SH DESCRIPTION A set of three programs to play Mah-Jong on Unix systems, against people or programs, over the Internet. .TP .B mj\-server is the program that handles communications and control of the game; the rules and scoring are enforced there. Players, human or computer, connect to a server via the network. .TP .B mj\-player is a computer player. At present, it is fairly simplistic, having only offensive tactics with no knowledge of defensive play. .TP .B xmj is the X client for human players. .SH QUICK START If you don't want to read this long document: to start a game against three computer players, start \fBxmj\fR, select "New local game..." from the "Game" menu, and click "Start Game". (Wait about ten seconds for everything to start up.) .SH OPTIONS .SS All Programs .TP .BI --server " address" specifies the network address to listen on (for \fBmj-server\fR) or to connect to (for \fBmj-player\fR and \fBxmj\fR). If \fIaddress\fR contains a colon, it specifies an Internet socket, and should have the form \fIhost:port\fR . If \fIaddress\fR does not contain a colon, it is interpreted as a Unix file name and a Unix socket is used. The default value for \fIaddress\fR is localhost:5000 . \fIaddress\fR can also be set in a dialog box in \fBxmj\fR. .SS xmj and mj-player .TP .BI \-\-id " idnumber" The server assigns a unique integer ID (which is currently just 1 to 4 in order of connection) to each player. This ID should be quoted when reconnecting to a game in progress (after, for example, losing a network connection or accidentally killing \fBxmj\fR). The default ID is 0, which denotes no pre-assigned ID. .TP .BI \-\-name " name" Players can give themselves names which will be used by client programs. This option specifies the name. For \fBxmj\fR, the default is the value of the environment variable LOGNAME, or failing that the username of the logged in user. For \fBmj-player\fR, the default is "Robot(\fIPID\fR)" where \fIPID\fR is the process id. .SS xmj .TP .B \-\-connect By default, \fBxmj\fR does not automatically connect to a server, but waits for the user to connect via a menu. If this option is specified, \fBxmj\fR immediately connects. .TP .B \-\-show\-wall .TQ .B \-\-no\-show\-wall Tells \fBxmj\fR (not) to display the wall. By default, the wall is shown only if running on a big enough screen. This option is also controllable via the Display Options preference panel. .TP .BI \-\-size " number" This option adjusts the size of the main window. It should be thought of as the length of a tile rack, measured in tiles. The default, and the largest value accepted, is 19, or 18 if on an 800x600 display. The smallest usable value is 14. This option is also controllable via the Display Options preference panel. .br If the \fB\-\-show\-wall\fR option is given, a \fB\-\-size\fR smaller than 19 will have no effect. .TP .B \-\-animate .TQ .B \-\-no\-animate This option switches on (off) some animation. Not all tile movements are animated: only those that involve moving tiles to or from a hand from outside. This option is also controllable via the Display Options preference panel. .TP .BI \-\-tileset " directory" \fBxmj\fR needs pixmaps to display the tiles and the tong box. This option tells it which directory to find them in. The default is set at compilation time; the default default is to use the compiled\-in tiles. .TP .BI \-\-tileset\-path " directory-path" This gives a colon-separated (or semicolon-separated under Microsoft Windows) list of directories in which to look for the directory named by the \fB\-\-tileset\fP option. .TP .B \-\-dialogs\-popup By default, most of the dialog boxes for player actions are part of the main window. If this option is used, they will instead appear as separate transient windows. .TP .B \-\-dialogs\-below By default, dialog boxes appear in the centre of the table. If this option is given, dialogs (apart from some popups) are positioned below the table area. Please let me know which style you prefer! .TP .B \-\-dialogs\-central The default: dialog boxes appear in the middle of the table. These options are also controllable via the Display Options preference panel. .TP .BI \-\-gtk2\-rcfile " file" If xmj is compiled with GTK+2, this option specifies a GTK rc file to be used instead of the program's compiled-in style file. This may be used to change the appearance of the program. See description under the Display Options... panel for more details. The \fIfile\fP should be an absolute filename; if it is relative, it will be sought in the current directory (Unix) or the program directory (Windows). This option is also controllable via the Display Options preference panel. .TP .B \-\-use\-system\-gtkrc .TQ .B \-\-no\-use\-system\-gtkrc When xmj is compiled with GTK+2, by default it ignores the system provided settings, to ensure a consistent behaviour across systems. If you wish it to use your system settings, set this option. This option is also controllable via the Display Options preference panel. .TP .B \-\-echo\-server If this option is given, \fBxmj\fR will echo to stdout all the protocol messages received from the server. This option is for use in debugging. .TP .B \-\-pass\-stdin If this option is given, \fBxmj\fR will send any text given on stdin to the server. This option is for use in debugging. .TP .B \-\-monitor If this option is given, \fBxmj\fR will send requests to the server only in direct response to user actions; it will take no action itself (and hence all auto-declaring and playing is also disabled). This option is for use in debugging. .SS mj-server .TP .BI --timeout " seconds" When a discard is made, there is a limit on the time players have to claim it. This option sets the timeout; a value of zero disables it. The default is 15 seconds. .br This value can also be set via a GameOption request from a player. .TP .BI --pause " deciseconds" This will make the server enforce a delay of \fIdeciseconds\fR/10 seconds between each action in the game; the purpose is to slow programmed players down to human speed (or, in a teaching situation, to slow the game even more). The current server considers that 50 (i.e. 5 seconds) is the maximum reasonable value for this option. .br The option can also be requested by players, via a PlayerOption protocol request. .TP .B --random\-seats By default, players are seated in order of connection to the server. This option seats them randomly. It will become the default later. .TP .B --id-order\-seats This option causes the players to be seated in numerical order of their ids. It is used by the xmj program to make the New local game.. work as expected. .TP .B --disconnect-penalties \fIN1,N2,N3\fP This specifies the penalties applied by the following option for players who disconnect before the end of a game. \fIN1\fP is the penalty for disconnecting in the middle of a hand; \fIN2\fP at the end of a hand but in the middle of a round; \fIN3\fP at the end of a round (other than end of game). They all default to 0 if not specified. .TP .B --end-on-disconnect If this option is given, a disconnection by one player will gracefully terminate the game. Mid-hand, the hand is declared a wash-out; after Mah-Jong has been declared, then if a losing player disconnects, their tiles are shown, the hand is scored, and then the game ends; if a winning player disconnects, the hand is a wash-out. The disconnecting player may be assigned a penalty, according to the \fB--disconnect-penalties\fP option, which will be included in the scores printed out by the server. (The penalties will not be visible to the other players.) .TP .B --exit-on-disconnect If this option is given, the server will quit if any player disconnects, rather than waiting indefinitely for reconnection. .TP .B --save-on-exit If this option is given, the server will save the state of the game if it quits as a result of a player disconnecting. (It will not save the state if it quits as the result of an internal error.) .TP .BI --option-file " file" This names a file of protocol commands which will be applied to every game when it starts. Its main purpose is to set non-default game options, via the GameOption protocol message (note that this is a CMsg, not a PMsg). However, users will normally set options and preferences via the \fBxmj\fP control panel, not by this means. .TP .BI --load-game " file" This names a file containing a saved game (as a suitable sequence of protocol commands). The server will load the game; clients connecting will be treated as if they had disconnected and rejoined the game. .TP .B --no-id-required In the most common case of resuming a saved game, namely one human playing against three robots, the robots will not have the same names or ids as the robots in the original game. This option tells the server that if it cannot match a reconnecting player by id or name, it should anyway match it to one of the previously disconnected players. (In this case, the human normally connects first with the same name, so is correctly matched.) .TP .B --no-manager Usually, the first player to connect becomes the game manager, and can change all the game settings. If this option is given, no player will be allowed to change the game settings. .TP .BI --auth-basic " id:password" This provides basic (insecure, since the password is transmitted in plaintext) authorization: the player with id \fI\id\fP must give the specified password to connect. Note that if this argument is given, it must be given four times, once for each authorized player - any player id not mentioned will not be allowed to connect. A player may be allowed to connect without a password by making \fIpassword\fP empty. .TP .B --debug This enables various debugging features. In particular, it enables protocol commands that allow one to change the tiles in a hand... .TP .BI --logfile " file" The server will write a complete record of the game to \fIfile\fR; this will be quite large, and is only useful for automatic comparison of different computer players. .TP .B --no-special-scores This option suppresses the scoring of points and doubles for flowers and seasons. It is primarily intended for running tests of different players; for human use, a game option will be provided to eliminate the specials altogether. .TP .BI --seed " n" This option specifies the seed for the random number functions. Used for repeatable tests. .TP .BI --wallfile " file" This names a file containing space separated tile codes giving the wall; used for repeatable tests. (This is a testing option; it is not robust.) .TP .B --hand-history This is an option to facilitate certain automatic analyses; if set, a history of each hand is dumped to the file hand-NN.mjs . .TP .B --nohist Another option only used in automatic comparison: this saves some CPU time by disabling the book-keeping required to allow players to disconnect and reconnect. .SS mj-player .TP .BI --password " password" sets the password if basic authorization is in use. .TP \fIstrategy options\fP The player has some options which can be used to change its "personality". The meanings are rather approximate, since they actually change parameters which are used in a rather complex way, but the idea is right. These options, each of which takes a floating point value in the given range, are: .TP .B --randomness 0.0 .. 1.0 The most generally useful option: adds random noise to the player's choice calculations, thus reducing its skill level. Values above 1.0 are possible, but don't seem to make matters worse. The dumbed-down players are still not stupid. .TP .B --chowness -1.0 .. 1.0 This affects how much the player likes chows: at 1.0, it will go all out for the chicken hand, at -1.0 it will never chow. The default is 0.0. .TP .B --hiddenness 0.0 .. 1.0 Increasing this makes the player reluctant to make exposed sets. At 1.0, it will never claim (except possibly to go mah-jong). The default is 0.0. .TP .B --majorness 0.0 .. 1.0 Increasing this biases the player towards collecting major tiles. At 1.0, it will discard all minor tiles, if possible. The default is 0.0. .TP .B --suitness 0.0 .. 1.0 Increasing this makes the player try to go for one-suit hands. The default is 0.0 .PP In practice, the \fB--majorness\fP option seems not to be very useful, but the other options change the personality without completely destroying the playing ability. In fact, all these options take a comma-separated list of values, which allows the specifications of a set of strategies, which the player will switch between. In this case, the \fB--hysteresis \fIhhh\fP option specifies how much better a strategy should be to switch to it. However, use of this option, and multiple strategies, is probably only useful if you first read the code to see how it works. .SH USING THE XMJ PROGRAM The main window contains a menu-bar and a table area; the table is in a tasteful shade of dark green. The table displays a stylized version of the game: stylized in that there is no jazzy graphics or perspective, and the tiles are not intended to be pictures of real objects, and so on. Otherwise, the layout is as one would expect of a real game. However, the wall may or may not be displayed, depending on option settings and screen size. (See above.) Specifically, the four players are arranged around the four edges of the table, with "us" at the bottom. For each player, the concealed tiles are displayed nearest the edge of the table; our own tiles are visible, the other players' tiles are face-down. The rightmost concealed tile of other players is highlighted in red when it is their turn to discard. In front of the concealed tiles are (to the player's left) any declared sets, and (to the player's right) flowers and seasons, and the tong box if the player is East. The tong box displays the wind of the round in a white circle. If necessary, the flowers and seasons will overflow into the concealed row. The discards are displayed face-up in the middle of the board: they are laid down in order by each player, in the natural orientation. TODO: add options to display discards randomly, or face-down. If animation (see \fB--animate\fR option) is not being used, then the most recent discard will be highlighted in red. The name of a face-up tile can be displayed by right-clicking in the tile. Alternatively, the Tiletips display option can be set, in which case the name of a tile is displayed whenever the mouse enters it. Our tiles are displayed in sorted order, which happens to be Bamboos (1-9), Characters (1-9), Circles (1-9), Winds (ESWN), Dragons (RWG), Flowers, Seasons. We can also arrange the tiles ourselves - see the "Sort tiles in hand" display preference described below. Actions are generally carried out by clicking a button in a dialog box that appears in the middle of the board. For many actions, a tile must be selected. A tile is selected or unselected by single-clicking it; when selected, it appears as a depressed button. The program will generally pre-select a sensible tile: specifically: .br during the initial declaration of special tiles, the rightmost special is selected; .br after we draw a tile from the wall, the drawn tile is selected; .br when declaring concealed sets after going Mah Jong, the first undeclared tile is selected. To describe the possible actions, let us run through the course of a game. First select "New local game..." from the "Game" menu. A panel will appear. The default options are to play a game against the computer, so click "Start Game". After a second or two, a game will start. (NOTE: this assumes correct installation. If this fails, start a server and players manually, and use the "Join server..." menu item.) The first thing that happens is a dialog box "Ready to start next hand". The server will not start playing a hand until all players have indicated their willingness to continue play. Next, the tiles are dealt. Then each player in turn is expected to declare flowers and seasons. When it is our turn, a dialog will appear with the following buttons: .TP Declare declare the selected flower or season. (Note: the program auto-selects the rightmost special tile.) If no tile is selected, this finishes declarations. This button will not appear if the game is being played without flowers and seasons. .TP Kong If we have a concealed kong, we can declare it now with this button. .TP Finish Finish declaring specials and kongs. .PP When all players have finished declaring specials and kongs, a dialog box appears, asking (on East's behalf) permission to continue. During play, when we draw a tile from the wall, it will be auto-selected. We may also of course select a different tile. A dialog will appear giving us the following possibilities: .TP Discard discard the selected tile. This button also serves to declare a flower or season, and the label changes to "Declare" when one is selected. .TP &Calling discard the selected tile and declare a calling hand. This button is only shown when calling is allowed (by default, only Original Call is allowed). .TP Kong declare a concealed kong of the selected tile, or add the selected tile to an exposed pung, as appropriate. \fBNote:\fR In most rules, a concealed kong can only be declared (or a tile added to an existing pung) immediately after drawing from the wall, but not after claiming somebody else's discard. Up to and including version 1.10, the server enforced this rule strictly. As from version 1.11, it allows a tile to be added to a pung that you have just claimed: in real life, this corresponds to correcting your Pung! claim to a Kong! claim, which is allowed by all rules. (Obscure note: if you are playing the KongHas3Types option, the resulting kong will be counted as annexed, instead of the exposed kong that would have resulted from a genuine change of claim. This is a bug, but not worth the trouble of fixing.) .TP Mah Jong! declare Mah Jong! (no selection needed) .PP If the wall is not being shown, the dialog will note the number of tiles left in the live wall. A tile can also be discarded simply by double-clicking it. When another player discards, a dialog appears to allow us to claim it. If the dialogs are in the middle of the table, the dialog displays the tile in a position and orientation to indicate the player who discarded; if the dialogs are at the bottom, this is not done, to save space. In any case the dialog displays the name of the tile, and buttons for the possible claims. If the wall is not being shown, the dialog will note the number of tiles left in the live wall. There is also a `progress bar' which shows how time is running out. The buttons use one variant of traditional English terminology, viz: .TP No claim we don't claim this tile. If there is no timeout in operation, it is necessary to click this to indicate a "pass", and in any case it is desirable to speed up play. .TP Chow claim for a sequence. If our claim is successful and there is more than one possible sequence to be made, a dialog will appear asking us to specify which one. .TP Pung claim for a triplet. .TP Kong claim for quadruplet. .TP Mah Jong! claim for Mah Jong. If the claim succeeds, a dialog box will appear asking whether we want the tile for "Eyes", "Chow", "Pung", or a "Special Hand" (such as Thirteen Unique Wonders). (The term "Eyes" is used instead of "Pair" so that in the keyboard accelerators, E can be used, leaving P for "Pung".) .PP When a player (including us) claims, the word "Chow!" etc. will appear (in big letters on a yellow background, by default) for a couple of seconds above the player's tiles. When all players have claimed, or timed out, the successful claim is implemented; no additional announcement is made of this. If a player adds a tile to an exposed pung, and that tile would give us Mah Jong, then a dialog box pops up to ask whether we wish to rob the kong. After somebody goes Mah Jong, we are asked to declare our concealed sets. A dialog appears with buttons for "Eyes", "Chow", "Pung". To declare a set, select a tile, which must be the \fBfirst\fR tile in the set for a chow, and click the appropriate button. (If we are going Mah Jong, the first undeclared tile is auto-selected.) When finished, click "Finished" to reveal the remaining tiles to the other players. If we are the winner, there will be a button for "Special Hand": this is used to declare hands of non-standard shape, such as Thirteen Unique Wonders. (Note: the Seven Pairs hand, if in use, should be declared by means of the "Eyes" button, not the "Special Hand" button.) At this point, a new top-level window appears to display the scoring information. The scoring is done entirely by the server, not by the players; the server sends a text description of the score calculation, and this is displayed for each player in the Scoring window. The information in the Scoring window remains there until the next hand is scored; the window can be brought up at any time via the "Show" menu. Finally, the "continue with next hand" dialog appears. The hand just completed will remain visible on the table until the next hand starts. .PP \fBKeyboard Accelerators\fP .br There are keyboard accelerators for all the actions in the course of play. For selecting tiles, the Left and Right arrow keys can be used to move the selection left or right along the row of tiles. In all dialogs, Space or Return will activate the shadowed button, which is usually the commonest choice. Each button can also be activated by typing the underlined letter. (In the Windows GTK1 build, use l (ell) and r instead of Left and Right. The button accelerators do not work, for reasons unknown to me.) .br The menus are also accessible via accelerators. To open a menu, press Meta-X (Alt-X on Windows), where X is the underlined letter in the menu name. (Meta-X is often (confusingly) Alt-X on Linux systems.) Then each entry has an underlined letter which if pressed will activate it. .PP An additional top-level window showing the state of the game can be obtained by selecting "Game info" from the "Show" menu. .PP A record of the scores so far in the game can be found by selecting "Scoring history" from the "Show" menu. The players are listed in board order, with the original east marked by @. In each hand, the player's hand score appears in parentheses, and then their gain or loss for the hand, beneath which is the running total .PP There is also a facility for sending text messages to the other players. Select "Messages" from the "Show" menu, and a window will appear: in the top is a display of all messages sent, and below is a single line in which you can enter your message. It will be sent when you hit Return. The message window pops up automatically whenever a message is received, unless prevented by a display preference. If the "Display status and messages in main window" display option is set, then this window will instead appear in the main window, above the table. In that case, there is a checkbox "Keep cursor here" next to the message entry line. Checking this box will ensure that the keyboard focus stays in the message entry field, even when you click on buttons in the game. (Consequently, you will be unable to use keyboard accelerators while this option is checked.) .SS Starting games and re-connecting The "Game" menu has the "New local game..." item to start a new game on your local computer, and the "Join server..." item to connect to an existing game. The dialogs for both these have the following entries: .TP Checkboxes for Internet/Unix server These specify whether the server is listening on an Internet socket or a Unix socket. If an Internet (TCP) socket, the host name ("Join Game..." only) and port number should be entered in the appropriate boxes; if a Unix socket, the file name of the socket may be entered, or if it is left blank, a temporary file will be used. These fields are remembered from game to game. .TP "Player ID" and "Name" fields The "Player ID" should be left at 0, unless reconnecting to an existing game, in which case it should be the ID assigned by the server on first connecting to that game. The "Name" field can be anything. When reconnecting to an existing game, if the ID is given as 0, the server will try to use the "Name" to identify the player. (This may not be true in future.) The "Name" field is remembered from game to game. .PP The "Join server..." dialog then simply has a "Connect" button to establish the connection. The "New local game..." has the following fields: .TP For each of three further players, A checkbox to say whether to start a computer player. (Some of) these should be unchecked if you wish other humans to join the games. If checked, there is a text entry to set the players' names, and a text entry field in which options can be given to the players; the latter should only be used if you understand the options! The options are remembered from game to game. .TP An "allow disconnection" checkbox If this is checked, the server that is started will continue to run even if players disconnect. If it is not checked, the server will quit if any player disconnects. If you are playing one against the computer, this should generally be left unchecked, in order to avoid server processes accidentally being left lying around. If playing against people, it should be checked, to allow players to go away, or to guard against network outages. .TP As "save game state on exit" checkbox If this is checked, the server will save the game state (see below on on saving and resuming games) when a player disconnects and causes it to quit. .TP A "seat players randomly" checkbox If this is left unchecked, players will be initially seated as East, South, West, North in order of connection. (We always connect first.) If it is checked, the seating will be random. .TP A "robot difficulty" numeric entry field values from 0 to 10 making the robots more skilful. Actually, lower values effectively make the robots a bit tired, so they make mistakes. Beginners probably want 0 to stand a chance of winning sometimes. .TP A numeric entry field to specify the time limit for claiming discards. If set to 0, there will be no time limit. .TP A button to start the game Note that it takes a few seconds to start a game, during which time the dialog stays up with the button pressed. (TODO: fix this!) .SS Saving and resuming games At any time during the play of a game, you can choose the "Save" entry from the "Game" menu. This causes the server to save the current state of the game in a file. The file will be named \fBgame-\fP\fIdate\fP\fB.mjs\fP by default; if a name has previously been specified, or if the game was resumed from a file, that name will be used. To specify a name, use the "Save as..." entry in the "Game" menu. Note that for security, directories cannot be specified (except by resuming a game), so the file will be created in the working directory of the server. To resume a saved game, use the "Resume game..." entry from the "Game" menu. This is just like the "New local game..." panel, but it has a box to specify the file containing the saved game. You can either type the file name into the box, or click the "Browse..." button to get a file chooser dialog. (File chooser not available on Windows GTK1 build.) .SS Setting display and game options The "Options" menu of \fBxmj\fP brings up panels to set various options related to the display and to the game rules. Most of these options can be stored in the preferences file, which is \fB.xmjrc\fP in your home directory on Unix, and \fBxmj.ini\fP in your home (whatever that means) directory on Microsoft Windows. .SS Display Options This panel controls options related to the local display. At the bottom are three buttons: "Save & Apply" applies changes and saves them in the preferences file for future sessions; "Apply (no save)" applies any changes, but does not save them; "Cancel" ignores changes. Note that many display options can also be controlled by command-line arguments; if an option is specified both in the preferences file and on the command line, the command line takes priority. .TP Position of action dialogs. This determines where the dialogs for user actions in the game are popped up; see the description of the \fB--dialogs-central\fP etc. options above. This option is stored in the preferences file as .br Display DialogPosition \fIposn\fP .br where \fIposn\fP is one of "central", "below" or "popup". .TP Animation determines whether tile movements are animated (see the \fB--animate\fP option above). This option is stored in the preferences file as .br Display Animate \fIbool\fP .br where \fIbool\fP is "0" or "1". .TP Display status and messages in main window puts the game status and message (chat) windows in the main window, above the table, instead of having separate popup windows. This option is stored in the preferences file as .br Display InfoInMain \fIbool\fP .br where \fIbool\fP is "0" or "1". .TP Don't popup scoring/message windows will prevent the automatic popup of the scoring window at the end of a hand, the message window on the arrival of a message, and the game status window at the end of the game. This option is stored in the preferences file as .br Display NoPopups \fIbool\fP .br where \fIbool\fP is "0" or "1". .TP Tiletips always shown means that the name of a tile is displayed whenever the mouse enters it, and the name of the selected tile is always shown. (Otherwise, right-click to display the name.) This option is stored in the preferences file as .br Display Tiletips \fIbool\fP .br where \fIbool\fP is "0" or "1". .TP Rotate player info text determines whether the player information labels on the main board are rotated vertically for the left and right players, or kept horizontal. The default is to rotate them. This option is stored in the preferences file as .br Display RotateLabels \fIbool\fP .br where \fIbool\fP is "0" or "1". .TP Show when players are thinking When this is on, "..." will be displayed in other player's claim alerts while they are "thinking", that is, have not yet claimed or passed. This option requires a recent server. The default is off. This option is stored in the preferences file as .br Display ThinkingClaim \fIbool\fP .br where \fIbool\fP is "0" or "1". .TP Alert on possible mah-jong determines whether to pop up an alert when a discard or drawn tile makes mah-jong possible. Beware that only the server knows the full rules, so this is not infallible. The default is off. This option is stored in the preferences file as .br Display AlertMahjong \fIbool\fP .br where \fIbool\fP is "0" or "1". .TP Display size This drop-down list specifies the size of the display. The size should be thought of as the length of a tile rack. This is only relevant if the wall is not being displayed. Values range from 14 to 19; if "(auto)" (the default) is specified, the client tries to choose a size as big as will fit in the display. This option can also be specified by the command line \fB--size\fP argument. This option is stored in the preferences file as .br Display Size \fIn\fP .TP Show the wall "always" is equivalent to the \fB--show-wall\fP option; "never" is equivalent to the \fB--no-show-wall\fP option; and "when room" is the default. This option is stored in the preferences file as .br Display ShowWall \fIwhen\fP .br where \fIwhen\fP is one of "always", "when-room" or "never". .TP Sort tiles in hand By default, the program maintains your own tiles in sorted order. If you prefer to leave them unsorted (which is often recommended in real life, to avoid giving information to your opponents), or to arrange them yourself, you can set this option to "never", or to "on deal" if you want them to be sorted at the beginning, but then left alone. To rearrange tiles, use the Shift-Left and Shift-Right (i.e. the left and right arrow keys while holding Shift) - these move the selected tile left or right in your hand. (In the Windows GTK1 build, use L (Shift-l) and R (Shift-r) instead.) On GTK2 builds, you can also drag a tile to its new position with the mouse. This option is stored in the preferences file as .br Display SortTiles \fIwhen\fP .br where \fIwhen\fP is one of "always", "deal" or "never". .TP Iconify all windows with main If this option is set (the default), then when the main xmj window is iconified, (almost) all other open windows such as dialogs will also be iconified; when the main window is uniconified, the other windows will also be uniconified. If it is not set, all windows are independent of one another. This option is stored in the preferences file as .br Display IconifyDialogs \fIbool\fP .br This option is not currently supported under Microsoft Windows. .TP Tileset this is the tile pixmap directory, also given by the \fB--tileset\fP option. This option is stored in the preferences file as .br Display Tileset \fIdirname\fP .TP Tileset Path this is the search path for tileset directories, also given by the \fB--tileset-path\fP option. This option is stored in the preferences file as .br Display TilesetPath \fIsearch-path\fP .TP Main font selection... This button brings up a font selection dialog to choose the font used in buttons, menus, etc. in the client. This option is stored in the preferences file as .br Display MainFont \fIfont-name\fP .br where \fIfont-name\fP is a font name, which may be an X LFD in the Unix GTK+1 version, or a Pango font name in the Windows and Unix GTK+2 versions. .TP Text font selection... This button brings up a font selection dialog to choose the font used in text display (such as scoring info and chat) in the client. This option is stored in the preferences file as .br Display TextFont \fIfont-name\fP .TP Table colour selection... Unaccountably, not everybody likes my choice of dark green for the table background. This button brings up a colour selection box to allow the table colour to be changed.This option is stored in the preferences file as .br Display TableColour \fIcol\fP .br where \fIcol\fP is a GTK colour specification. The format depends on whether xmj is built with GTK+1 - in which case it is an X color of the form rgb:RRRR/GGGG/BBBB - or GTK+2 - in which case it is a GTK2 color of the form #RRRRGGGGBBBB. GTK+2 programs will convert an old GTK1 specification. .TP Gtk2 Rcfile: In the GTK+2 build, xmj by default ignores completely the system and user settings for look and feel, and uses its own built in settings. These settings use the Clearlooks theme, if it is available, to provide a simple but clean look with slightly rounded tiles; and fall back to a plain theme, as compact as possible with the standard engine. If you wish, you can use this option to specify the name of a GTK rcfile which will be read instead of the built in settings. A minimal set of settings will be read before your file is read. Such a file can specify many details of the appearance, provided that you know how to write a GTK rcfile. You will need to know that xmj uses the following styles and bindings: .br gtk-font-name = \fIfontname\fP .br can be used to change the overall font used by widgets. This will overridden by the font specified by the Main Font option, if set. .br style "table" .br is used to give the green (or whatever you set) colour to the table. All widgets that should have this style are named "table", so the appropriate binding (already set in the minimal set) is .br widget "*.table" style "table" .br style "playerlabel" .br is used to give the white text colour to the player status labels in the corners of the board (if shown). All widgets that should have this style are named "playerlabel", so the appropriate binding (already set in the minimal set) is .br widget "*.playerlabel" style "playerlabel" .br style "tile" .br is used in the default settings for all widgets named "tile", which are all tiles except the tiles in your own concealed hand. This style is not used in the minimal settings, but if set it should be bound with .br widget "*.tile" style "tile" .br style "mytile" .br is used in the default settings for the concealed tiles in your hand, which are active buttons. These tiles are all named "mytile". This style is not used in the minimal settings, but if set it should be bound with .br widget "*.mytile" style "mytile" .br style "claim" .br is used to set the yellow background and large font of the claim announcement popups. These popups are named "claim", so the appropriate binding (already set in the minimal set) is .br widget "*.claim" style "claim" .br style "text" .br is used to change the font for the text widgets such as message boxes and input fields. In the minimal settings, it is empty, but is defined and bound to the relevant widgets. The binding should not be changed, but the style itself can be redefined. If the Text Font option is set, this style will be redefined in order to implement it. .br binding "topwindow" .br is defined and bound to the top-level window to implement the use of the left and right arrow keys to change the selected tile. It is probably not helpful to change this. .br The distribution contains three example gtkrc files, called \fBgtkrc-minimal\fP, \fBgtkrc-plain\fP, and \fBgtkrc-clearlooks\fP, which contain the program's compiled in settings. .br This option is stored in the preferences files as .br Display Gtk2Rcfile \fIfile-name\fP .br Note that if the file-name is relative, it will be interpreted relative to the current directory in Unix, or the program directory in Windows. .TP Use system gtkrc As noted above, xmj does not normally load the system settings in the GTK+2 build. If this option is checked, it will (after the minimal settings, but before the default or user-specified settings). This option is stored in the preferences files as .br Display UseSystemGtkrc \fIbool\fP .br where \fIbool\fP is 0 or 1. .TP Note for GTK+1 builds Under a GTK+1 build, xmj does what any other application does. This should allow the use of a .gtkrc file to change colours, using the styles and bindings given above. However, this is not a supported activity. .SS Playing Preferences This panel controls what actions the client may take on your behalf. The first (and currently only) section specifies when the client should declare tiles and sets for you. It has the following checkboxes: .TP flowers and seasons if checked, will be automatically declared as soon as drawn. .TP losing hands if this is checked, then when somebody else goes out, the client will declare your closed sets. It declares in the order pungs, pairs, chows. .TP winning hands this is the same for when you go out. .PP The panel has "Save & Apply", "Apply (no save)" and "Cancel" buttons, as in the display options panel. .SS Game Option Preferences This panel controls preferred game options which will be sent to the server when a game starts. Preferences will only be applied if we are the game manager, or the game has no manager. (Normally, the first human player to connect to the server becomes the game manager.) .br For details of options and their meanings, see the Game Options section in the rules. .br The panel has two action buttons, "Save Changes" and "Cancel", with the obvious meanings. Note if a game is in progress, changed preferences are NOT applied to it; however, there is a button in the Current Game Options panel to apply preferences. .br The main body of the panel is a scrollable window listing all the known options. If no preference is stored for the FooBar option, then there is an "Add pref" button next to a description of the FooBar option. If this button is clicked, an entry for setting the option appears. The format of this entry depends on the type of the option (see the Game Options section of the rules for details of types): .TP Boolean (on/off) options have a checkbox. .TP Integer options have a spinbutton for numerical entry: the value can be typed in, or the up and down arrows can be used to change it .TP Score options have radio buttons for selecting Limit, Half-Limit, or other; for other, the number of doubles and/or points is entered with spinbuttons. (Note: the underlying protocol allows percentages (possibly more than 100%) of limits to be specified for scores; however, the current graphical interfaces allow only limits or half-limits. Even half-limits are pretty strange, but some bizarre sets of rules, such as those of the British Mah-Jong Association (which plays a weird American/Western/Chinese mix), allow other fractions of limits.) .TP String options have a simple text entry field. .PP All option entries have a "Reset" button which returns the entry to its previous state. .br A preference is removed by clicking the "Remove pref" button. .SS Current Game Options When there is a connected game, this panel allows its game options to be modified (if we have permission to do so). The three action buttons are "Apply changes", which applies the panel's settings to the current game; "Apply prefs", which applies our preferences (as described above) to the current game; and "Cancel". .br The body of the panel contains entries for all the options of the current game, in the same format as the preferences panel (see above). .SH UPDATES The latest release of the Unix Mah-Jong programs should be available at .br \fBhttp://mahjong.julianbradfield.org/\fR .SH RULES The game currently implemented is a version of the classical Chinese game. The most convenient and comprehensive set of rules is that provided by A. D. Millington, "The Complete Book of Mah-Jongg", Weidenfield & Nicolson (1993), ISBN 0 297 81340 4. In the following, M 103 denotes item 103 of the rules laid out in Chapter 3 of that book. I here describe only the differences from these rules, some of which differences are consequences of using computers, and some of which are points where my house rules differ from Millington's version. In due course, all variations (of Chinese classical) will be accommodated, if there is sufficient desire. Classification of tiles (M 1-8): the tiles are a standard Chinese set. The tiles do not have Arabic numerals, except for the flowers and seasons, where the identifying Chinese characters are too small to be legible. A numbered set is included in the distribution and can be used via the Tileset display preference. .br The flowers and seasons may be removed from the tile set by unsetting the \fBFlowers\fP game option. Preliminary (M 9-10): nothing to say. Duration of the game (M 11-14): standard rules. In particular, the title of East does not pass after a wash-out. Selection of seats (M 15): the players are seated in the order they connect to the server, or randomly, according to the option given to the server. The deal etc. (M 16-27): There is no attempt to simulate the usual dealing ritual (M 16-20, 23-26); the wall is built randomly by the server. The dead wall is also maintained by the server. .br The existence of a dead wall is controlled by the \fBDeadWall\fP game option; normally there is a dead wall. .br The deal wall is either 14 tiles and kept at 13 or 14 during play (as in most authors), or is 16 tiles, not extended during play (per Millington (M 22)), according to the \fBDeadWall16\fP game option. .br Replacement tiles for kongs are always taken from the loose tiles, but replacements for bonus tiles may be drawn from the live wall (M 31), or from the loose tiles, according to the \fBFlowersLoose\fP game option. Object of game (M 28-31): all winning hands must comprise four sets and a pair, with the exception of the Thirteen Unique Wonders. If the \fBSevenPairs\fP game option is set, then a hand of any seven pairs is also allowed as a winning hand. Bonus tiles (M 31): M requires that bonus tiles must be declared in the turn in which they are drawn; otherwise the player may not exchange or score them (and thus they cannot go out). We do not make this restriction, as it is (a) pointless (b) unenforceable in real life. Bonus tiles may be declared at any time after drawing from the wall. (Obviously, there is no reason not to declare them immediately.) Commencement of the Game (M 32-33): standard. Playing procedure (M 34-38): standard. In particular, the other players have to give permission for east to start playing (M 34). The display of discards cannot be controlled by the server; the current X client displays them in an organized fashion, rather than the random layout required by M 35. Chow (M 39-42): standard. Pung (M 43-45): standard. Kongs (M 46-52): M distinguishes three types of kong: concealed, claimed (by Kong), and annexed (formed by adding a discard to an exposed pung), and allows claimed kongs to be counted as concealed for the purposes of doubling combinations. I have not seen this anywhere else; normally, a claimed kong is treated as exposed for all purposes. We follow the normal convention; however, the game option KongHas3Types can be set to implement M's rules. In this case, the xmj program will distinguish claimed kongs by displaying them with the last tile face down, whereas annexed kongs are all face up. .br Players may declare a concealed kong, or add to a pung, only when they have just drawn a tile from the wall (live or dead); not just after a claiming a discard. (A silly restriction in my view, but one that all rule sets seem to have (M 51).) As from program version 1.11 (protocol version 1110), we also allow a player to add to a pung they have just claimed (see note above in the description of play). Calling and Mah Jong (M 53-54): standard. (I.e. there is no "Calling" declaration.) NOTE: M permits players to change their mind about making a claim (M 69); we do not, and all claims are irrevocable. As a special concession, we allow adding to a just claimed pung, so simulating the effect of correcting a pung claim to a kong. Original Call (M 55): the Original Call declaration must be made simultaneously with the first discard, rather than afterwards. NOTE: the server does *not* check that the declarer does indeed have a calling hand, as a mistaken original call does not damage the other players or the progress of the game. The server does, however, thereafter prevent the declarer from changing their hand; therefore a mistaken original call will make it impossible to go out. (Note: in M, an Original Caller may change their hand, but will thereby lose the ability to go out (M 55(b)); is this a better way to treat it?) Note also: as per M, an original call can be made even if another player has claimed a discard before, unlike the Japanese version. Robbing a Kong (M 57-60): Robbing a kong is implemented. However, as with discards, we require that kongs are robbed before anything else happens, and in particular before the konger draws a replacement tile. Therefore, after a kong, all other players must either claim Mah Jong or pass. (The provided programs will pass automatically if robbing is not possible.) As for discards, there is a time limit. Precedence of claims for discard (M 61-65): Many rules allow a discard to be claimed up until the time the next discard is made. M does this, with elaborate rules for the precise specification. For ease of implementation, we do not allow this: instead, all players are required to make a claim or pass, and once all players have claimed, the successful claim is implemented irrevocably. The server imposes a time limit; players that do not claim within the limit are deemed to have passed. This defaults to 15 seconds, but can be changed or disabled by the \fBTimeout\fP game option. Irregularities in Play (M 66-81): the server does not permit unlawful moves, and so no irregularities can arise. False Declaration of Mah Jong (M 82-83): such declarations are not permitted by the server. False Naming of Discards (M 84-88): this also cannot happen. Incorrect Hands (M 89): cannot happen. Letting Off a Cannon (M 90-96): as in M. However, if a player makes a dangerous discard, but has no choice, the server will determine this; it is not necessary to plead "no choice" explicitly, and neither is the player's hand revealed to the other players. Wash-Out (M 97-99): standard. Points of Etiquette (M 100-102): not applicable. Displaying the Hand (M 103-106): The format of display is a matter for the client program, and cannot be controlled by the server. .br After Mah Jong, the players are responsible for declaring concealed sets in whatever way they wish. The winner, of course, is required to declare a complete hand; but the losers may declare as they wish. Once a set is declared, it cannot be revoked. Note that the losers may declare multiple scoring pairs. Procedure in Settlement (M 107-111): The settlement is classical: that is, the winner gets the value of their hand from all players; the losers pay one another the differences between their scores; except all payments to or from East are doubled; and if players let off a cannon, they pay everybody's debt. Unlike normal play (M 110), all hands are scored by the server, rather than by the players. Settlement is also computed by the server. Some variations in settlement are provided: if the LosersSettle game option is set to false, there are no payments between losers; if the EastDoubles game option is set to false, payments to or from East are not doubled; if the DiscDoubles game option is set to true, then the discarder of the tile that gave Mah-Jong will pay double to the winner, and a self-draw is paid double by everybody. Method of Scoring (M 112-122): The method is standard (M 112), viz calculate points obtained from sets and bonuses, and then apply doubles. The following points are given for tiles: .TP Bonus tiles: 4 each (M 114(a)) .TP Pungs: 2 for exposed minor tiles; 4 for exposed major or concealed minor; 8 for concealed major. (M 114(b)) .TP Kongs: 8 for exposed minor; 16 for exposed major or concealed minor; 32 for concealed major. (M 114(c)) .TP Chows: no score. (M 114(d)) .TP Pair: 2 for a pair of Dragons, Own Wind, or Prevailing Wind. A pair that is both Own and Prevailing Wind scores 4. (M 114(e)) Non-winning hands may score more than one pair. .TP Basic points: the winner gets 20 points for going Mah Jong. This can be changed by the \fBMahJongScore\fP game option (M 115(a) has 10 points). .TP Seven Pairs hand: If Seven Pairs hands are allowed, they receive an additional score of 20 points, changed by the \fBSevenPairsVal\fP game option. .TP Winning from wall: if the final tile is drawn from the wall, 2 points are added (M 115(b)). .TP Filling the only place: if the final tile is the only denomination that could have completed the hand, 2 points are added (M 115(c)). NOTE: As in M, if all four copies of a tile are exposed on the table, it does not count as available for completing the hand. .TP Fishing the eyes: a player who completes by obtaining a pair gets 2 points if the pair is minor, or 4 if major (M 115(d)). Note: to obtain these points for a discard, the player must actually claim the discard for a pair: e.g. if waiting on 5677, and 7 is discarded, the player must claim for the pair, not the chow. .PP The following doubles apply to all hands. All possible clauses apply unless stated otherwise. .TP Having own flower or own season. No extra score. Changed by the \fBFlowersOwnEach\fP game option. .TP Having own flower AND own season, 1 double. (M 116(a)). Changed by the \fBFlowersOwnBoth\fP game option. .TP Having all four flowers, 1 double. (M 116(b)). Changed by the \fBFlowersBouquet\fP game option. .TP Having all four seasons, 1 double. (M 116(b)). Changed by the \fBFlowersBouquet\fP game option. .TP Each set of dragons, 1 double. (M 116(d)) .TP A set of the player's own wind, 1 double. (M 116(e)) .TP A set of the prevailing wind, 1 double. (M 116(f)) .TP "Little Three Dragons": two sets and a pair of dragons. 1 double. (M 116(g)) .TP "Big Three Dragons": three sets of dragons. 2 doubles. (M 116(h)) .TP "Little Four Winds": three sets and a pair of winds. 1 double. (M 116(i)) .TP "Big Four Winds": four sets of winds. 2 doubles. (M 116(j)) (Note: the definitions of these last four doubles when applied to non-winning hands are subject to wide variations. Possibly there should be options to allow other possibilities.) .TP Three concealed pungs: 1 double. (M 116(k)) (Note: if the KongHas3Types game option is set, a claimed kong counts as concealed for this hand; see the note above under "Kongs".) .PP The following doubles apply to the winning hand only: .TP No score hand: four chows and a non-scoring pair. 1 double. (M 117(a)) (Note: like M, we allow any of the extra points (Fishing the Eyes, etc) to go with this double. Some rules say that the extra points invalidate this hand. Possibly there should be an option for this.) .TP No chows: 1 double. (M 117(b)) .TP Concealed hand: 1 double (M 117(c)), changeable with the \fBConcealedFully\fP game option. (Note: this means a hand that is fully concealed after going out. Another common value for this is 3 doubles, in which case 1 double is usually given for a semi-concealed hand (see below).) (Note: if the KongHas3Types game option is set, a claimed kong counts as concealed for this hand; see the note above under "Kongs".) .PP The following doubles normally apply to the winning hand only; however, the \fBLosersPurity\fP game option can be set to allow losing hands to score them (this is a highly deprecated American feature, but has been requested by a user). .TP Semi-concealed hand: no doubles, changeable with the \fBConcealedAlmost\fP game option. (Not in M) (Note: this means a winning hand that is concealed up to the point of going out, or, if enabled, a concealed losing hand. According to a discussion on rec.games.mahjong, a winning semi-concealed hand is classically awarded one double (with three given for fully concealed). One book in my possession (U.S.A., early 1920s) awards this double only to a hand that is concealed except for the pair.) (Note: if the KongHas3Types game option is set, a claimed kong counts as concealed for this hand; see the note above under "Kongs".) .TP One suit with honours: 1 double. (M 117(d)) .TP One suit only: 3 doubles. (M 117(e)) .TP All majors: 1 double. (M 117(f)) .TP All honours (in an unlimited game): 2 doubles. (M 117(g)) (Note: such a hand will also score the double for all majors.) .TP All terminals (in an unlimited game): 2 doubles. (Not in M) (Note: such a hand will also score the double for all majors.) .PP The following doubles apply only to the winning hand: .TP Winning with loose tile: 1 double. (M 117(h)) (Note: with the default settings, replacements for bonus tiles come from the live wall. Hence this double applies only to winning after Kong.) .TP Winning from the bottom of the sea (winning with last tile), 1 double. (M 117(i)) .TP Catching a fish from the bottom of the sea (winning with last discard), 1 double. (M 117(j)) .TP Robbing a kong, 1 double. (M 117(k)) .TP Completing Original Call, 1 double. (M 117(l)) .PP Limit (M 118-120): the limit is 1000 by default, and can be changed by the \fBScoreLimit\fP game option. The \fBNoLimit\fP game option can be used to play a game "with the roof off". The following hands are limit hands: .TP Heaven's Blessing: East wins with dealt hand. (M 122(a)) .TP Earth's Blessing: player wins with East's first discard. (M 122(b)) .TP Gathering Plum Blossom from the Roof: winning with 5 Circles from the loose wall. (M 122(c)) .TP Catching the Moon from the Bottom of the Sea: winning with 1 Circle as the last tile. (M 122(d)) (Note: M says that the tile must be drawn. It seems more reasonable also to allow it to be the last discard, which is what we do. Objections?) .TP Scratching a Carrying Pole: robbing a kong of 2 Bamboos. (M 122(e)) .TP (Note: these last three limits are rather arbitrary, but of the arbitrary limits they are apparently the most common. There should be options to disable them.) .TP Kong upon Kong: making a Kong, making another Kong with the loose tile, and with the second loose tile obtaining Mah Jong. (Also, of course, with three or four successive kongs.) (M 122(f)) .TP Four Kongs. (M 122(g)) .TP Buried Treasure: all concealed and no chows. (M 122(h)) .TP The Three Great Scholars: three sets of dragons and no chows. (M 122(i)) (Note: in most rules I have seen, there is no restriction to a no chow hand. Since in M's rules, three sets and a chow scores at least (10 (M has 10 for Mah Jong) + 12 (at least 3 pungs)) times 8 (2 for each set of dragons) times 4 (for Big Three Dragons) = 704, this is significant with the default limit. For us, with 20 for going out, Big Three Dragons is over the default limit anyway.) .TP Four Blessings o'er the Door: four sets of winds and a pair. (M 122(j)) .TP All Honours. (M 122(k)) .TP Heads and Tails: all terminals. (M 122(l)) .TP Imperial Jade: contains only Green Dragon and 2,3,4,6,8 Bamboo. (M 122(m)) (Note: another rather arbitrary hand, but widely adopted.) .TP Nine Gates: calling on 1-1-1-2-3-4-5-6-7-8-9-9-9 of one suit. (M 122(n)). .TP Wriggling Snake: 1-1-1-2-3-4-5-6-7-8-9-9-9 plus 2, 5 or 8 of one suit (M 122(o)). (Note: another rather arbitrary hand.) .TP Concealed Clear Suit: one suit only and all concealed. (M 122(p)) .TP Thirteen Unique Wonders: one of each major tile, and a match to any of them. (M 122(q)) .TP East's 13th consecutive Mah-Jong. (M 122(r)) .PP General note: there are many other doubles and limits kicking around. I welcome opinions on which should be possible options; and also on which of the above I should eject from the default set. I dislike Imperial Jade, Wriggling Snake, and the ones depending on a specific tile (Gathering Plum Blossom, Catching the Moon, Scratching a Carrying Pole): which of these are so commonly adopted that they should be in even a fairly minimalist default set? .SH GAME OPTIONS This section describes the options that can be set in the game. Whether an option can be used, depends on the version of the programs. This is described by a "protocol version number"; this is not strictly speaking a version just of the communication protocol, but a version number reflecting the combination of protocol and programs. When playing by oneself, this does not matter, but in the case of a networked game, players might have different versions of the software, in which case the game is played according to the lowest version of any player. Game options can be controlled in two ways: the \fB--option-file\fP argument to the \fBmj-server\fP program gives options to be applied to the game, or options can be set by the players, using the interface described in the manual section for \fBxmj\fP. In the user interface, the options are referred to by a one line description, but each option also has a short name, given here. Options are of several types: .TP .B bool boolean, or on/off, options. .TP .B int integer options .TP .B nat non-negative integer options .TP .B string is a miscellaneous type, whose values are strings of at most 127 characters which must not contain white space .TP .B score is the type used for options that give the score of some combination or feature in a hand. A score is either a limit (or a half-limit; the underlying protocol supports percentages of limits, but the current user programs only support limits and half limits); or a number of doubles to be awarded; or a number of points to be added. It is possible (though never needed) to have both points and doubles. If points/doubles are specified as well as a limit, they will be used in a no-limit game. (The server implements a hard limit of 100000000 on all scores to avoid arithmetic overflow, but that's unlikely to worry anybody.) .SS Currently supported options The following options are implemented in the versions of the program with which this document is distributed. If playing against people with older versions of the software, some options may not be available. The list gives for each option the short name, type, and short description, followed by a detailed explanation. .GO Timeout nat "time limit for claims" This is the time in seconds allowed to claim a discard, or to rob a kong. If set to zero, there is no timeout. The default is 15 seconds. .GO TimeoutGrace nat "grace period when clients handle timeouts" This period (in seconds) is added to the Timeout above before the server actually forces a timeout. This is for when clients handle timeouts locally, and allows for network lags. If this option is zero, clients are not permitted to handle timeouts locally. The current server also only allows players to handle timeouts locally if all of them wish to do so. .GO ScoreLimit nat "limit on hand score" This is the limit for the score of a hand. In a no-limit game, it is the notional value of a "limit" hand. The default is 1000. .GO NoLimit bool "no-limit game" If this option is set, the game has no limit on hand scores. The default is unset. .GO MahJongScore score "base score for going out" This is the number of points for obtaining Mah-Jong. The default is 20. .GO SevenPairs bool "seven pairs hand allowed" If this option is set, then Mah-Jong hands of seven pairs (any seven pairs) are allowed. The default is unset. .GO SevenPairsVal score "score for a seven pair hand" This gives the score (in addition to the base Mah-Jong score) for a seven pairs hand. The default is 20. .GO Flowers bool "play using flowers and seasons" If this option is set, the deal includes four flowers and four seasons in the Chinese Classical style. If unset, only the 136 standard tiles are used. The default is set. .GO FlowersLoose bool "flowers replaced by loose tiles" If playing with flowers, this option determines whether flowers and seasons are replaced from the live wall (unset), or by loose tiles (set). The default is unset. .GO FlowersOwnEach score "score for each own flower or season" This option gives the score for having one's own flower or season. If one has both, this score will be given twice. The default is no score. .GO FlowersOwnBoth score "score for own flower and own season" This is the score for having both one's own flower and one's own season. Note that this is awarded in addition to twice the previous score. The default is 1 double. .GO FlowersBouquet score "score for all four flowers or all four seasons" This is the score for having all four flowers or all four seasons. The default is 1 double. .GO DeadWall bool "there is a dead wall" This determines whether there is a dead wall, so that play ends when it is reached (set), or whether all tiles may be drawn (unset). The default is set. .GO DeadWall16 bool "dead wall is 16 tiles, unreplenished" If this option is set, then the dead wall initially has 16 tiles, and does not have any more tiles added to it (this is the set-up described by Millington). If the option is unset, then the dead wall initially has 14 tiles, and after two loose tiles have been taken, two tiles are moved from the live wall to the dead wall (this is the set-up described by almost everyone else). The default is unset in versions 1.1 onwards, and set previously. (To be precise, the protocol level default is set, but all servers from 1.1 onwards will change this to unset.) .GO ConcealedFully score "score for fully concealed hand" This is the score for a winning hand with no open sets. The default is 1 double. .GO ConcealedAlmost score "score for almost concealed hand" This is the score for a hand that is concealed up to the point of going out. The default is no additional score. .GO LosersPurity bool "losing hands score doubles for pure, concealed etc." If this option is set, losing hands will score various doubles for one suit, almost concealed, etc. See the rules for details. This option is an (Anglo-)Americanism alien to Chinese Classical (see Foster for a spirited but faulty argument in its favour, and Millington for the rejoinder). The default is unset. .GO KongHas3Types bool "claimed kongs count as concealed for doubling" If this option is set, claimed kongs count as concealed for various doubling combinations, although they score as exposed for basic points. See the note above under "Kongs". The default is unset. .GO LosersSettle bool "losers pay each other" If this option is set, the losers pay each other the difference between their scores. If it unset, they pay only the winner. The default is set. .GO EastDoubles bool "east pays and receives double" If this option is set, payments to and from East Wind are doubled, as in the Chinese Classical game. The default is set. .GO DiscDoubles bool "the discarder pays double" If this option is set, the settlement procedure is changed to a style common in Singapore. That is, if the winning player wins off a discard, the discarder pays double the hand value, and the other players pay the hand value. If the winner wins from the wall, then all other players pay double the hand value. The default is unset. Note: EastDoubles and DiscDoubles can be set together, but nobody plays such a rule. .GO ShowOnWashout bool "reveal tiles on washout" If this option is set, the players' hands will be revealed in the event of a washout. .GO NumRounds nat "number of rounds to play" This option says how many rounds to play in the game. For aesthetic reasons, the possible values are 1, 2, or a multiple of 4. In the 2 round case, the East and South rounds will be played. It defaults to the usual 4 rounds. .SS Option file format Both in the option file and in the \fB.xmjrc\fP file, options are recorded in the format used by the server protocol. This is a line of the form \fBGameOption 0\fP \fIname type minprot enabled value desc\fP The meanings of the elements are: .TP .B GameOption 0 identifies this as a game option line (the 0 is an irrelevant field from the protocol). .TP .I name is the name of the option. .TP .I type is the type of the option. .TP .I minprot is the minimum protocol version with which the option can be used (which is not necessarily the version at which it was introduced). .TP .I enabled will always be 1. .TP .I value is the value: a decimal (signed) integer for \fBnat\fP and \fBint\fP; 0 or 1 for \fBbool\fP; the string for \fBstring\fP; and for \fBscore\fP, if the score is \fIc\fP centi-limits, \fId\fP doubles and \fIp\fP points, the value is \fIc\fP*1000000 + \fId\fP*10000 + \fIp\fP. .TP .I desc is a short description of the option, which is not required but is usually copied in from the server. mj-1.17-src/protocol.c0000444006717300001440000003305015002771311013326 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/protocol.c,v 12.1 2020/05/30 17:36:13 jcb Exp $ * protocol.c * implements the over-the-wire protocol for messages */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include "protocol.h" #include "tiles.h" #include "sysdep.h" static const char rcs_id[] UNUSED = "$Header: /home/jcb/MahJong/newmj/RCS/protocol.c,v 12.1 2020/05/30 17:36:13 jcb Exp $"; /* See protocol.h for documentation */ int protocol_version = PROTOCOL_VERSION; /* The protocol is, for all the usual reasons, text-based, and human readable. (So all you need to play is telnet...) A message usually occupies a single line, terminated by CRLF or LF. To transmit messages containing newlines, the internal newlines are replaced by backslash-newline pairs; thus a telnet line ending in a backslash indicates newline followed by the next telnet line. Integers are represented in decimal. The message type is given in text, obtained by removing the CMsg or PMsg prefix from the type name. Tiles are represented as a 2-character code, as follows: 1C ... 9C characters 1D ... 9D circles (dots) 1B ... 9B bamboos EW ... NW winds RD, WD, GD dragons 1S ... 4S seasons 1F ... 4F flowers -- blank Winds are represented as single letters. The code for representing tiles in this way comes from the tiles module, as it has uses outside this protocol. ChowPositions are represented as the words Lower, Middle, Upper,AnyPos. Strings are deemed to extend to end of line. This is safe because the current protocol has at most one string field per message, which is always final. There is unlikely to be a reason to change this assumption. */ /* Takes a TileWind and returns the corresponding letter */ static char windletter(TileWind w) { switch ( w ) { case EastWind: return 'E'; case SouthWind: return 'S'; case WestWind: return 'W'; case NorthWind: return 'N'; default: return '?'; } } /* given a wind letter, return the wind */ static TileWind letterwind(char c) { if ( c == 'E' ) return EastWind; if ( c == 'S' ) return SouthWind; if ( c == 'W' ) return WestWind; if ( c == 'N' ) return NorthWind; return UnknownWind; } /* given a chowposition string, return the chowposition */ /* it is an undocumented :-) fact that this function will accept l,m,u,a for Lower, Upper, Middle, AnyPos */ static ChowPosition string_cpos(char *s) { if ( strcmp(s,"Lower") == 0 ) return Lower; if ( strcmp(s,"l") == 0 ) return Lower; if ( strcmp(s,"Middle") == 0 ) return Middle; if ( strcmp(s,"m") == 0 ) return Middle; if ( strcmp(s,"Upper") == 0 ) return Upper; if ( strcmp(s,"u") == 0 ) return Upper; if ( strcmp(s,"AnyPos") == 0 ) return AnyPos; if ( strcmp(s,"a") == 0 ) return AnyPos; return -1; } /* return a string code for a chowposition */ static char *cpos_string(ChowPosition r) { switch ( r ) { case Lower: return "Lower"; case Middle: return "Middle"; case Upper: return "Upper"; case AnyPos: return "AnyPos"; } return "error"; } /* functions to print and scan a GameOptionEntry; at the end */ static char *protocol_print_GameOptionEntry(const GameOptionEntry *t); /* This function uses static storage; it's not reentrant */ static GameOptionEntry* protocol_scan_GameOptionEntry(const char *s); /* This function takes a pointer to a controller message, and returns a string (including terminating CRLF) encoding it. The returned string is in static storage, and will be overwritten the next time this function is called. */ char *encode_cmsg(CMsgMsg *msg) { static char *buf = NULL; static size_t buf_size = 0; int badfield; /* used by internal code */ switch ( msg->type ) { /* The body of this switch statement is automatically generated from protocol.h by proto-enc-cmsg.pl; */ # include "enc_cmsg.c" default: warn("unknown message type %d\n",msg->type); return (char *)0; } return buf; } /* This function takes a string, which is expected to be a complete line (including terminators, although their absence is ignored). It returns a pointer to a message structure. Both the message structure and any strings it contains are malloc'd. The argument string will be destroyed by this function. */ CMsgMsg *decode_cmsg(char *arg) { char *s; char *type; int n; int i; int an_int; char a_char; char little_string[32]; GameOptionEntry *goe; /* first eliminate the white space from the end of the string */ i=strlen(arg) - 1; while ( i >= 0 && isspace(arg[i]) ) arg[i--] = '\000' ; /* first get the type of the message, which is the first word */ /* SPECIAL hack: the comment message has a non alpha num type */ type = arg; i = 0; while ( isalnum(arg[i]) || (arg[i] == '#') ) i++; arg[i++] = '\000' ; /* if this wasn't white space, summat's wrong anyway */ type = arg; while ( isspace(arg[i]) ) i++; /* may as well advance to next item */ s = arg+i; /* s is what the rest of the code uses */ /* The rest of this function body is generated automatically from protocol.h by proto-dec-cmsg.pl */ # include "dec_cmsg.c" /* if we come out of this, it's an error because we haven't found the keyword */ warn("decode_cmsg: unknown key in %s",arg); return NULL; } /* The next two functions do the same for pmsgs */ /* This function takes a pointer to a player message, and returns a string (including terminating CRLF) encoding it. The returned string is in static storage, and will be overwritten the next time this function is called. */ char *encode_pmsg(PMsgMsg *msg) { static char *buf = NULL; static size_t buf_size = 0; int badfield; /* used by internal code */ switch ( msg->type ) { /* The body of this switch statement is automatically generated from protocol.h by proto-enc-pmsg.pl; */ # include "enc_pmsg.c" default: warn("unknown message type\n"); return (char *)0; } return buf; } /* This function takes a string, which is expected to be a complete line (including terminators, although their absence is ignored). It returns a pointer to a message structure. Both the message structure and any strings it contains are malloc'd. The argument string will be destroyed by this function. */ PMsgMsg *decode_pmsg(char *arg) { char *s; char *type; int n; int i; int an_int; char little_string[32]; /* first eliminate the white space from the end of the string */ i=strlen(arg)-1; while ( i >= 0 && isspace(arg[i]) ) arg[i--] = '\000' ; /* first get the type of the message, which is the first word */ type = arg; i = 0; while ( isalnum(arg[i]) ) i++; arg[i++] = '\000' ; /* if this wasn't white space, summat's wrong anyway */ type = arg; while ( isspace(arg[i]) ) i++; /* may as well advance to next item */ s = arg+i; /* s is what the rest of the code uses */ /* The rest of this function body is generated automatically from protocol.h by proto-dec-pmsg.pl */ # include "dec_pmsg.c" /* if we come out of this, it's an error because we haven't found the keyword */ warn("decode_pmsg: unknown key in %s",arg); return NULL; } /* the size functions */ #include "cmsg_size.c" #include "pmsg_size.c" /* enum functions */ #include "protocol-enums.c" /* functions for GameOptionEntry */ static GameOptionEntry *protocol_scan_GameOptionEntry(const char *s) { const char *sp; char tmp[100]; static GameOptionEntry goe; int n; sp = s; if ( sscanf(sp,"%15s%n",goe.name,&n) == 0 ) { warn("protocol error"); return NULL; } sp += n; goe.option = protocol_scan_GameOption(goe.name); if ( goe.option == (GameOption)(-1) ) goe.option = GOUnknown; if ( sscanf(sp,"%15s%n",tmp,&n) == 0 ) { warn("protocol error"); return NULL; } sp += n; goe.type = protocol_scan_GameOptionType(tmp); if ( goe.type == (GameOptionType)(-1) ) { warn("unknown game option type"); return NULL; } if ( sscanf(sp,"%d%n",&goe.protversion,&n) == 0 ) { warn("protocol error"); return NULL; } sp += n; if ( sscanf(sp,"%d%n",&goe.enabled,&n) == 0 ) { warn("protocol error"); return NULL; } sp += n; switch ( goe.type ) { case GOTBool: if ( sscanf(sp,"%d%n",&goe.value.optbool,&n) == 0 ) { warn("protocol error"); return NULL; } if ( goe.value.optbool != 0 && goe.value.optbool != 1 ) { warn("bad value for boolean game option"); return NULL; } sp += n; break; case GOTNat: case GOTInt: case GOTScore: if ( sscanf(sp,"%d%n",&goe.value.optint,&n) == 0 ) { warn("protocol error"); return NULL; } sp += n; break; case GOTString: if ( sscanf(sp,"%127s%n",goe.value.optstring,&n) == 0 ) { warn("protocol error"); return NULL; } sp += n; break; } if ( sscanf(sp," %127[^\r\n]",goe.desc) == 0 ) { warn("protocol error"); return NULL; } goe.userdata = NULL; return &goe; } static char *protocol_print_GameOptionEntry(const GameOptionEntry *goe) { static char tmp[2*sizeof(GameOptionEntry)]; sprintf(tmp,"%s %s %d %d ", goe->name, protocol_print_GameOptionType(goe->type), goe->protversion, goe->enabled); switch (goe->type) { case GOTBool: sprintf(tmp+strlen(tmp),"%d ",goe->value.optbool); break; case GOTNat: case GOTInt: case GOTScore: sprintf(tmp+strlen(tmp),"%d ",goe->value.optint); break; case GOTString: sprintf(tmp+strlen(tmp),"%s ",goe->value.optstring); break; } strcat(tmp,goe->desc); return tmp; } /* basic_expand_protocol_text: do the expansion of protocol text according to the rules set out in protocol.h. We don't recognize any codes or types. bloody emacs ' */ int basic_expand_protocol_text(char *dest,const char *src,int n) { int destlen = 0; /* chars placed in destination string */ const char *argp[10]; /* pointers to start of argument texts (excluding {); arg 0 is replacement text */ const char *curarg = NULL; int i,j; int numargs = 0; int state= 0; int done; /* sanity check */ if ( !dest || !n ) { warn("basic_expand_protocol_text: strange args"); return -1; } /* null source expands to empty string */ if ( !src ) { dest[0] = 0; return 0; } /* initialization */ for ( i=0 ; i<10 ; argp[i++] = NULL ); j = 0; /* current arg */ done = 0; while ( !done ) { switch ( state ) { case 0: /* outside macro */ if ( ! *src ) { done=1 ; break ; } if ( *src == '\\' ) { src++; if ( *src ) dest[destlen++] = *(src++); } else if ( *src == '{' ) { src++; state = 1; /* reading macro defn */ /* skip the code */ while ( *src && (*src != '#') && (*src != ':') && (*src != '}') ) src++; numargs = 0; } else { dest[destlen++] = *(src++); } break; case 1: /* reading macro definition */ if ( ! *src ) { done=1; break; } if ( *src == '#' ) { numargs++; src++; /* skip the type */ while ( *src && (*src != '#') && (*src != ':') && (*src != '}') ) src++; } else if ( *src == ':' ) { state = 2; src++; argp[0] = src; } else if ( *src == '}' ) { state = 2; /* and read it again */ } break; case 2: /* reading replacement text */ /* do nothing; we've stored a pointer to it */ if ( !*src ) { done=1; break;} if ( *src == '\\' ) { src++; if ( *src ) src++; } else if ( *src == '}' ) { src++; /* are there any arguments? */ if ( numargs > 0 ) { state = 3; /* reading argument texts */ j = 1; /* current arg */ } else { state = 4; /* expanding macro */ } } else src++; break; case 3: /* reading argument texts */ if ( !*src ) { done=1; break;} if ( *src == '{' ) { src++; argp[j] = src; } else if ( *src == '}' ) { src++; if ( j == numargs ) { state = 4; /* expand the macro */ } else j++; } else src++; break; case 4: /* expand the macro */ /* now we're looking at argp[0] */ if ( ! *argp[0] ) { state = 0; } else if ( *argp[0] == '\\' ) { argp[0]++; if ( *argp[0] ) dest[destlen++] = *argp[0]; argp[0]++; } else if ( *argp[0] == '}' ) { state = 0; } else if ( *argp[0] == '#' ) { /* expand the argument */ argp[0]++; j = *argp[0] - '0'; if ( j >= 1 && j <= 9 ) { argp[0]++; curarg = argp[j]; state = 5; } } else dest[destlen++] = *(argp[0]++); break; case 5: /* copying an argument */ if ( ! *curarg ) { state = 4; } else if ( *curarg == '\\' ) { curarg++; if ( *curarg ) { dest[destlen++] = *(curarg++); } } else if ( *curarg == '}' ) { state = 4; } else dest[destlen++] = *(curarg++); break; } if ( destlen >= n - 1 ) { break ; } } if ( destlen > n-1 ) { dest[n-1] = '\000'; return -1; } dest[destlen] = '\000'; if ( state > 0 ) { warn("basic_expand_protocol_text: some error in expansion"); return 0;} return 1; } int expand_protocol_text(char *dest,const char *src,int n) { return basic_expand_protocol_text(dest,src,n); } mj-1.17-src/controller.h0000444006717300001440000000411615002771311013656 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/controller.h,v 12.2 2020/05/30 17:53:17 jcb Exp $ * controller.h * Contains type definitions etc used by the controller program. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #ifndef CONTROLLER_H_INCLUDED #define CONTROLLER_H_INCLUDED 1 #include "tiles.h" #include "player.h" #include "protocol.h" #include "game.h" /* extra data in the game */ typedef struct { PlayerP caller; /* used to keep a copy of the winning player just before mah-jong */ /* array of pointers to CMsgs issued in this hand. This is used when reconnecting. The array is 512, since the maximum number of messages that can (relevantly) be issued in a hand is around 4 per tile, but that's highly unlikely ever to be reached -- but it can be, so say 1024. This ought to be dynamic... */ int histcount; CMsgMsg *history[1024]; /* this is used to keep the state at the start of the last hand, so we can print out the hand just completed */ /* needs one Game Message and as many options as there may be */ int prehistcount; CMsgMsg *prehistory[1+GOEnd]; unsigned int completed_rounds; /* number of completed rounds. This ought to be added to the Game structure and message, but that's too much work, and we only really need it internally. Unsigned to avoid a warning in the main file */ } GameExtras; #define gextras(g) ((GameExtras *)((g)->userdata)) #endif /* CONTROLLER_H_INCLUDED */ mj-1.17-src/ChangeLog0000444006717300001440000026276015002771311013107 0ustar jcbusers2025-04-25 Julian Bradfield * makefallbacktiles: make types const * ChangeLog: 1.17 * gui.c, player.c: squish a couple of warnings * gui-dial.c, gui.h, gui.c, xmj.man: Add robot difficulty field to xmj open dialog 2025-04-23 Julian Bradfield * xmj.man, Makefile.in, greedy.c: Add --randomness option to robot 2024-12-12 Julian Bradfield * gui-dial.c: remove reference to tumj 2020-06-09 Julian Bradfield * controller.c: clear first_id when player disconnects 2020-05-30 Julian Bradfield * greedy.c: suppress warnings, and fix ancient bug in is_doubler - will they play better? * tiles.h: change suit_of and value_of to return unsigned values, to suppress warnings in client code. We never look inside ErrorTile, anyway. * scoring.c, controller.c: suppress warnings * controller.h: change to suppress warning * sysdep.c, protocol.c: fix warning * game.c: fix ancient logic error (triggered only on player error); fix warning * tiles.c, player.c, vlazyfixed.c, lazyfixed.c: fix warning * client.c: fix latter * client.c: fix warning * gui-dial.c: fix warnings * gui.c: fix some warnings, and remove unused function * gui.c: checkpoint - using pixbufs for tile creation (not yet tongboxes) 2020-05-26 Julian Bradfield * README, Makefile.in, gui.h, gui-dial.c, gui.c: remove GTK1 support * README: update windows info * make-release: forgot src and full suffixes! * CHANGES: 1.16 * ChangeLog: 1.15 * make-release: add support for the cross compiled win32-i686 * sysdep.h, sysdep.c: add hacks for cross-compiling from Linux to Windows (Win32=3) * Makefile.in: add switches for cross-compiling from Linux to Windows (Win32=3) 2020-05-26 Julian Bradfield * make-release: add support for the cross compiled win32-i686 * sysdep.h, sysdep.c: add hacks for cross-compiling from Linux to Windows (Win32=3) * Makefile.in: add switches for cross-compiling from Linux to Windows (Win32=3) 2020-05-23 Julian Bradfield * gui.h, gtkrc-minimal, gtkrc-plain, gtkrc.h, gui.c: make the thinking box separate and less obtrusive 2020-05-16 Julian Bradfield * gui.c: correction: thinking support depends only on server version, not on game protocol version * gui.h, gui-dial.c, gui.c, xmj.man: implement ThinkingClaim and AlertMahjong display options. * gui.c: show thinking claims (...) before players noclaim - checkpoint * controller.c: when people reconnect by name without id, make sure they aren't already connected * protocol.h: increment protocol version by 1, to mark that the server now sends doesntclaims to all players * controller.c: send doesntclaim messages to all, so they can see who's thinking * gui.c: separate out popup of claim window into function 2020-05-12 Julian Bradfield * controller.c: improve logging of all-player messages * controller.c: disable localtimeouts when a player initially connects (in case they had been in operation0 * controller.c: very long-standing and stupid bug - using timeout grace period even when no timeout, when localtimeouts! 2019-06-07 Julian Bradfield * ChangeLog: 1.15 correction * gui.c: fix potential buffer overflow 2019-06-06 Julian Bradfield * ChangeLog, CHANGES: 1.15 * xmj.man, gui.h, gui.c, gui-dial.c: add Display RotateLabels option to say whether player info labels on board should be rotated or all horizontal. 2017-04-29 Julian Bradfield * gui.h: ancient misdeclaration fixed * gtkrc-plain, gtkrc.h, gtkrc-clearlooks: increase visibility of selected tile 2015-05-13 Julian Bradfield * gui.c: check_claim_window: check for ended game 2014-01-06 Julian Bradfield * CHANGES, ChangeLog: 1.14 2014-01-05 Julian Bradfield * gui.c, gui-dial.c: gtk1 fix * gui.c: gtk1 fixes * xmj.man: score history window * gui.c: avoid crashing if copying to a pipe stdout, or if input dies. * gui.c: Move stdin callback setting into control_server_processing (should not be handling stdin for pass-stdin unless there's a server to send it to). Fix slip in previous code. * gui.c, gui.h, gui-dial.c: add scoring history window 2014-01-04 Julian Bradfield * xmj.man: document resume file chooser * ChangeLog: mysterious wrong date in entry for 1.13 changes * gui-dial.c: win32 fix to previous 2014-01-03 Julian Bradfield * gui-dial.c: add a file chooser for the resume game open option * gui-dial.c: update About * gui-dial.c: fix bug when Finish declaring specials gives error if tile selected. * gui.c: add highlighting to show whose turn it is * greedy.c, gui.c: Remove abuse of printf - thanks Göran Uddeborg * gui.h, gui-dial.c, gui.c: grey out the scoring dialog while waiting for a declaration to be acknowledged 2014-01-02 Julian Bradfield * gui.c: avoid changing the state of non-windows * controller.c: fix still remaining bug in save: deep copy of game needs to copy extras. * controller.h: tidy gextras macro 2014-01-02 Julian Bradfield * gui.c, gui-dial.c: gtk1 fix * gui.c: avoid changing the state of non-windows * controller.c: fix still remaining bug in save: deep copy of game needs to copy extras. * controller.h: tidy gextras macro * gui.c: gtk1 fixes * gui-dial.c: add a file chooser for the resume game open option * xmj.man: score history window * gui-dial.c: update About * gui.c: avoid crashing if copying to a pipe stdout, or if input dies. * gui-dial.c: fix bug when Finish declaring specials gives error if tile selected. * gui.c: add highlighting to show whose turn it is * greedy.c, gui.c: Remove abuse of printf - thanks Göran Uddeborg * gui.c: Move stdin callback setting into control_server_processing (should not be handling stdin for pass-stdin unless there's a server to send it to). Fix slip in previous code. * gui.h, gui-dial.c, gui.c: grey out the scoring dialog while waiting for a declaration to be acknowledged * gui.c, gui.h, gui-dial.c: add scoring history window * xmj.man: document resume file chooser * ChangeLog: mysterious wrong date in entry for 1.13 changes * gui-dial.c: win32 fix to previous 2013-08-28 Julian Bradfield * CHANGES, ChangeLog: 1.13 * xmj.man: typos * gui.c: fix showing of scoring window after washout * Makefile.in: add -lm to gui link line * controller.c: non-trivial fixes for game saving and resumption at end of hand. * controller.c: fix uninitialized field in comment in saved game file 2012-05-04 Julian Bradfield * makedep: be laxer about finding gcc version numbers 2012-02-03 Julian Bradfield * ChangeLog, CHANGES: 1.12 2012-02-02 Julian Bradfield * gui.c: better fix for time_t ? 2012-02-01 Julian Bradfield * gui.c, gui-dial.c, proto-encode-msg.pl, sysdep.h, sysdep.c: make 64-bit clean * protocol.h, gui.c, gui-dial.c, game.h, game.c, controller.c, scoring.c, greedy.c: Use GameOptionValue (union type) instead of punning all the possible types. 2011-12-25 Julian Bradfield * xmj.man: remove accidentally pasted in url 2011-11-27 Julian Bradfield * xmj.man, gui.h, gui-dial.c, gui.c: Save open dialog robot player options in rc file. 2011-11-26 Julian Bradfield * controller.c: fix stupid bug in id-order-seats setup causing seg fault. 2011-11-22 Julian Bradfield * gui.c, xmj.man: Use /tmp/mj-$$ if Unix socket selected in dialog without specifying filename. 2011-08-29 Julian Bradfield * gui.c: revert the iconification fix for gtk1 builds where it doesn't work * ChangeLog, CHANGES: 1.11 2011-08-28 Julian Bradfield * gui-dial.c: do the previous fix properly * game.c: Allow a just claimed pung to be converted to a kong. * xmj.man: more on adding to just claimed pungs * xmj.man: a few tidies, and the new add-to-pung ruling 2011-08-27 Julian Bradfield * protocol.h: bump to 1110 for fixing addtopung after claiming 2011-08-26 Julian Bradfield * gui-dial.c: fix about xmj (nmenu_items error). 2010-12-19 Julian Bradfield * gui.c: Finally fix the iconification of subsidiary windows. 2010-12-18 Julian Bradfield * gui-dial.c: status_update: always do it, as now status also appears in main window. 2009-07-10 Julian Bradfield * ChangeLog: really really really 1.10 ? * sysdep.c: maybe shut up warning in long long constant? * gui.c: only create the player labels if they're going to be used. * gui.c: try another technique to force notebook pages to tell their size correctly * Makefile.in: forgot -mwindows flag in gtk2 2009-07-09 Julian Bradfield * CHANGES: really really 1.10? * ChangeLog: really really 1.10 ? * gui.c: adjust size request for textpages * gui.c: On windows, allow for EAGAIN errors in the glib io reader routine. 2009-07-05 Julian Bradfield * make-release: for final release * make-release: include gtk2 dlls etc. in win32 gtk2 releases. * CHANGES: 1.10 * ChangeLog: really 1.10 * gtkrc.h: use basic fonts only for windows * gui.c: add some stuff for windows clearlooks, which however doesn't yet work. * Makefile.in: give Windows with and without msys versions (since installing msys broke my non-msys setup) * gtkrc-clearlooks: add theme-name 2009-07-03 Julian Bradfield * makedep: for last release 2009-06-28 Julian Bradfield * version.h.in, vlazyfixed.c, vlazyfixed.h, xmj.man, mprotocol.c, mprotocol.h, mutate, player.c, player.h, pmon, proto-decode-msg.pl, proto-encode-msg.pl, protocol.c, protocol.h, runtest, scorenew, scoring.c, scoring.h, stats, sysdep.c, sysdep.h, tiles.c, tiles.h, MANIFEST, README, client.c, client.h, controller.c, controller.h, cull, ga.c, game.c, game.h, greedy.c, gtkrc-minimal, gtkrc-plain, gui-dial.c, gui.h, iconres.rs, lazyfixed.c, lazyfixed.h, logstats, make-enums.pl, make-release, makefallbacktiles, makefile.msvc.old, maketxt, malloc.c, manager.c, mj-player.man, mj-server.man, CHANGES, FILES-binary, FILES-src, LICENCE: new major version for final release * CHANGES: 1.10 * ChangeLog: changes up to 1.10 * README: bring it up to date * gui.c: gtk1ify new code * gui.c: warnings tidy * make-release: give the windows special treatmnet for any suffix starting win32-i386 * xmj.man: add --id-order-seats * gui.c: start server with --id-order-seats * controller.c: add --id-order-seats * greedy.c: undocument -> strategy * xmj.man: documen the strategy options * gui-dial.c, gui.c: arrange for scoring info window to popup with natural size * xmj.man, gui.c: drag and drop rearrangement of tiles implemented. * gui.c: fix old stupid bug in calling conc_callback, while developing drag and drop 2009-06-27 Julian Bradfield * xmj.man: moving tile, different accels on windoze * gtkrc.h: typo * gui.c: add l,r accelerator keys for Windows gtk1 * gtkrc-clearlooks, gtkrc-plain, gtkrc-minimal, gtkrc.h, gui.c: Add the Shift-Left and Shift-Right keys to move selected tile * xmj.man, gui-dial.c, gui.c, gui.h: add the SortTiles display option * player.c, player.h: add player_reorder_tiles and player_set_discard_hint 2009-06-26 Julian Bradfield * makedep: check for gcc 4 etc. * Makefile.in: tidy Gtk * controller.c: bug in state loading of completed rounds * gui-dial.c, gui.c: Warnings tidy. * controller.c: don't exit on end of game, wait for first client disconnect. * gui.h, gui.c, gui-dial.c: improve end of game handling 2009-06-25 Julian Bradfield * gtkrc.h: add playerlabel styles * controller.h, protocol.h: add NumRounds option pversion 1011. * game.c, controller.c, xmj.man: add NumRounds option 2009-06-23 Julian Bradfield * FILES-binary, FILES-src: add the gtkrc-* files * controller.c: send the GameOver message at the end of score_hand, instead of waiting until the next hand is started. * xmj.man: document playerlabel * gui.c: setting label ellipsis breaks rotated text. Don't justify in board labels for left and right players. * gui.c: old stupid bug: not handling spaces in player name for open dialog * gui.h, gui-dial.c, gui.c: implement the player status in the spare corners * gtkrc-minimal, gtkrc-plain, gtkrc-clearlooks: add playerlabel 2009-06-22 Julian Bradfield * gui-dial.c: attempt to make dialogs take focus, but without success. Fix repeated coding. * gui.c: finally, we expand the left and right players to have the right size. * gtkrc-clearlooks, gtkrc-plain, gtkrc-minimal: New file. * xmj.man: document gtk2 settings. * gui.c: fix gtk1 breakages introduced by gtk2 changes * gui-dial.c, gui.c, gtkrc.h: simplify the styling 2009-06-21 Julian Bradfield * gui.c: read gtk1 color names * gui.h, gui.c, gui-dial.c: implement use_system_gtkrc * gui.c, gui-dial.c, gui.h: put gtk2rcfile in display options dialog * gui.c: tidy gtk2-rcfile a bit * gui.h: gtk2_rcfile * gtkrc.h: oops. \n was n in default 2009-06-20 Julian Bradfield * gui-dial.c: save posn of display dialog on close * gtkrc.h: restructure, add default, remove the clearlooks copy * gui.c, gui-dial.c: fix the gtk2 style setting to use a default or clearlooks * gui.c: fix the tile size discovery * gui.c: add --gtk2rcfile option * gtkrc.h: tweaks - enforce default style 2009-06-19 Julian Bradfield * gui.c, gui-dial.c: allow the iconify code on windows. It doesn't work, but there's no harm having it there, in case it ever does work. * Makefile.in: fixes for windows 2009-06-18 Julian Bradfield * gui.c: oops. don't iconify non-windows * README: update for 1.10 situation. * FILES-src, MANIFEST: add gtkrc.h * Makefile.in: switch to gtk2 * gtkrc.h: New file. * gui.c, gui-dial.c: handle font and colour setting * gui-dial.c: remove more warnings * gui.c: clean up more warnings * gui-dial.c: remove warnings * gui.c: eliminate warnings. * gui.c: fix iconification * controller.c: fix bug causing segfault when player connects twice. 2009-06-17 Julian Bradfield * gui-dial.c: gtk2 fixes for dialog windows not keeping their position * gui.c: use compiled in gtkrc * gui.c: fix animation. Check that it still works in gtk1, since we fixed a silly problem that maybe we wrongly hacked around before? (Had boardframe inside boardfixed instead of vice versa, so coordinates of windows didn't match in gtk2.) 2009-06-16 Julian Bradfield * gui-dial.c: fixes to GTK2 problems with the warning window * gui-dial.c: set word wrap in notebook * gui-dial.c: remove cursors 2009-06-15 Julian Bradfield * gui-dial.c: more accelerator fixes (disable when focus is in chat window) 2009-06-14 Julian Bradfield * gui-dial.c, gui.c: more messagetext fixes * gui.c, gui-dial.c: basic fix for messagetext * gui.c, gui-dial.c: fix the scoring notebook * gui-dial.c: fix up accelerator problems * gui-dial.c: get dialog positioning right for gtk2 (for central; now try it with the other position settings). * gui.c: finally sorted out the key-binding mess. * gui.c: checkpoint again. Highlighting of discarded tiles now ok. 2009-06-13 Julian Bradfield * gui.c: checkpoint - basics working 2009-06-11 Julian Bradfield * gui-dial.c, gui.c, Makefile.in: patch from Barry deFreese for gtk2 2008-08-24 Julian Bradfield * gui.c: remove debugging statement accidentally left in. 2008-08-22 Julian Bradfield * sysdep.h, sysdep.c: add index() in windows * xmj.man: document authorization options in 1.9 * CHANGES: 1.9 * Makefile.in: use native Windows del rather than cygwin rm * gui-dial.c: cast to suppress warnings introduced by fixing gtk headers. * greedy.c: (don't) handle Redirect; suppress a warning. * controller.c: handle more new messages in 1100. * game.c: complain if unexpected Redirect and AuthReqd cmsgs. * gui.h: change to params for redirect handling * gui.c: handle Redirect and AuthReqd * protocol.h: add PMsgNewAuthInfo while we're doing it. 2008-08-21 Julian Bradfield * greedy.c: allow basic authorization on command line (--password). * protocol.h, controller.c, gui-dial.c: Change basic authorization type name to "basic" from "password". * sysdep.h, sysdep.c: Add qstrmcat function for quoting strings for the shell. * gui.c: Quote player names when passing to shell 2007-11-12 Julian Bradfield * protocol.h: Implemented basic authorization pversion to 1100. * gui.h, gui.c, gui-dial.c, controller.c: Implemented basic authorization 2007-06-06 Julian Bradfield * xmj.man, controller.c: add --hand-history option. 2007-05-22 Julian Bradfield * xmj.man: ShowOnWashout * stats: add inc line * scorenew: mj-player not ga * mutate: checkpoint * logstats: add inc line * cull: take first arg * CHANGES, ChangeLog: 1.8 * client.c, client.h: client_find_set: return pairs before chows * xmj.man, gui-dial.c: change the Discard label in the turn dialogue to Declare when it's a flower. * gui-dial.c, xmj.man: Remove the Add To Pung button in the turn dialogue, and move its functionality into the Kong button. * protocol.h: Add ShowOnWashout (request from Ulrich Schwartz) pversion 1072 * game.c, controller.c: Add ShowOnWashout (request from Ulrich Schwartz) 2006-12-16 Julian Bradfield * greedy.c: remove one magic value 2006-02-14 Julian Bradfield * runtest: Why was I putting the pid in /tmp/mj. even when not usepid? * README: remove dcs web page and email 2006-02-12 Julian Bradfield * greedy.c: amalgamate magic and magic2. Try fiddling with adding scores for different branches. 2006-02-01 Julian Bradfield * greedy.c: send eval debugging output to debugf, which is currently stderr * greedy.c: move some other magic numbers into magic2 and have --magic2 option. 2006-01-31 Julian Bradfield * pmon: pass any additional args to the player 2006-01-30 Julian Bradfield * pmon: put in the pause until oked by user (x to skip) * pmon: remove sleep * gui.c: Re-Implement echo-server via put_line, so that raw protocol comes out. * pmon: New file. * gui.c: create the open dialog in create_display. Not sure why it was separately in the main routine. * gui.c: monitor fixes 2006-01-29 Julian Bradfield * gui.c: don't send connect in monitor mode 2006-01-28 Julian Bradfield * xmj.man: add --monitor option for xmj * gui.h, gui-dial.c, gui.c: add --monitor option * greedy.c: move some magic numbers into an array settable via parameter file, and change the defaults to the results of a long random search 2006-01-26 Julian Bradfield * runtest: temp changes for ga use * Makefile.in: add a pattern rule for variants of greedy-FOO * ga.c: well, this didn't work particularly. We'll keep it here for the record * cull, mutate, scorenew: New file. 2006-01-24 Julian Bradfield * ga.c: New file. * Makefile.in: add ga target * sysdep.c: tcp_keepidle doesn't work on unix sockets (duh!) 2006-01-22 Julian Bradfield * sysdep.c: Windoze uses SD_SEND instead of SHUT_WR * CHANGES: Release 1.7 * ChangeLog: 1.7 * sysdep.c, sysdep.h: make put_data send eof if passed zero data; add get_data. * gui-dial.c: pop up message returned from crash report server, if any. 2006-01-18 Julian Bradfield * sysdep.c, malloc.c, client.c, gui-dial.c, gui.c: More warning suppressions (mostly on Windows) * Makefile.in: control cc warning options with a Warnings make variable * sysdep.c: use TCP_KEEPIDLE only if it's defined as a constant * gui.c: small tweaks to remove type-related warnings * sysdep.c, sysdep.h: add some const to log_error to suppress warnings (and because they should be const) 2006-01-17 Julian Bradfield * README: updating for 1.7 and new gtkdlls1.zip (also removed some obsolete sentences). * Makefile.in: changes for my new Windows compilation setup 2006-01-14 Julian Bradfield * sysdep.c: set keepalive on outgoing connections * greedy.c: handle Reconnect * controller.c: Changes to support Reconnect. * gui.c: Changes to support Reconnect. (Not yet on interface.) * gui.h: Changes to support Reconnect * client.h: additional routines to support Reconnect * protocol.h: change semantics for reconnect * client.c: additional routines to support Reconnect 2006-01-13 Julian Bradfield * game.c: Fix very old stupid bug: closed kongs weren't being counted when checking for Kong upon Kong. 2006-01-09 Julian Bradfield * game.c: handle reconnect (by doing nothing) * protocol.h: Add Reconnect and RequestReconnect. pversion to 1070. 2005-04-17 Julian Bradfield * controller.c: Reject multiple connects from same connection (thanks to Len Budney) 2005-03-22 Julian Bradfield * sysdep.c, scoring.c, gui.c, controller.c: buffer overflow precautions from Len Budney * sysdep.h: add strmcat * gui-dial.c: increase saved-warning buffer size 2005-02-28 Julian Bradfield * gui.c: Oops. In introducing a GTK2 version of some code, deleted the 1.2 version by accident. 2005-02-05 Julian Bradfield * MANIFEST, FILES-src: add malloc.c * Makefile.in: Use Doug Lea's malloc for Windows * malloc.c: hack it around to get it to compile in my environment - mostly remove mmap, and add unistd back * malloc.c: *** empty log message *** * Makefile.in: add warning not to use gtk+2 2004-08-26 Julian Bradfield * gui-dial.c: more gkt2ery * gui-dial.c: suppress const warning * greedy.c: only rob closed kong if can get thirteen wonders 2004-08-22 Julian Bradfield * gui-dial.c, gui.c: Started porting for GTK2. Just got to compilation, but still far to go. * Makefile.in: added GTK2 flags 2004-08-09 Julian Bradfield * gui.c: protect against null messages 2004-08-08 Julian Bradfield * gui.c: fix display bug when robbing kong. * controller.c: Fix bug in game resumption: need to pass options to players before they get a NewHand message. 2004-08-05 Julian Bradfield * gui.c: protect against nulls in state recording; warn on bad messages from controller 2004-08-03 Julian Bradfield * gui.c: fix bug in last addition * gui.c: hopefully change reporting hook to send everything * gui.c: add databuffer struct and record server history in it 2004-07-29 Julian Bradfield * gui-dial.c: put version in error reports * gui.c: hmm. Check for error in handle_cmsg and report it. 2004-07-07 Julian Bradfield * make-release: no longer make a static linux version * gui-dial.c: shunt report filing out to a sysdep routine. * sysdep.h: *** empty log message *** * sysdep.c: Add some routines for dealing with sockets as-is, without applying the transforms. 2004-07-05 Julian Bradfield * gui-dial.c: don't file reports before the windows are up (since can't ask for permission) * sysdep.c: declaration in wrong place * gui-dial.c, gui.h, gui.c: Add facility for Internet debug reports, with appropriate preference options and querying. 2004-06-24 Julian Bradfield * gui.c: buffer safety in log_msg_dump_state * game.c, game.h: change game_print_state to pass back number of spare bytes in buffer. 2004-06-22 Julian Bradfield * gui.c: nag at intervals of not less than one month. 2004-06-10 Julian Bradfield * gui.c: display of RobsKong should then fall through and do MahJongs processing * ChangeLog: delete duplicate entry 2004-06-04 Julian Bradfield * gui-dial.c: don't try to pop up error if no window system yet * gui-dial.c: Arrgh. Must use "send" to write to sockets in windoze 2004-05-21 Julian Bradfield * sysdep.h, sysdep.c: Add source code ident into the error function - actual function is now log_error. 2004-05-15 Julian Bradfield * player.c: replace assertion by error call. * gui.c: 1. Add debugging to cmsg processing - where errors occur in some players. 2. Switch on nagging for Windows! Why didn't I go this before! * gui-dial.c: warn rather than popup when can't file report. Pop up error dialog to tell user when error occurs. 2004-05-14 Julian Bradfield * gui.c: dump on errors, not warning * gui-dial.c, gui.c: dump game state on errors and file report * sysdep.c: oops...don't copy null note. * game.c: handle null game in print_state * game.c: add players to game state printout 2004-05-10 Julian Bradfield * sysdep.h, sysdep.c: Add log_msg_add_note_hook 2004-05-09 Julian Bradfield * player.c: player_discard functions were not clearing the NoDiscard flag of hidden players. * CHANGES: 1.6.3 * player.c: Fix the bug pointed out by Nicolas in thirteen wonders * player.h, player.c: Add player_print_state() * game.c: move nullprotect into sysdep * sysdep.h, sysdep.c: Add the nullprotect utility function. * gui.c, gui-dial.c: initialize some variables to avoid (hopefully inapplicable) warnings. * sysdep.h, sysdep.c: add error level to log functions * sysdep.h: remove superfluous declarations of (v)snprintf * player.c: another error in thirteen wonders, introduced last time. 2004-05-08 Julian Bradfield * game.c: fix typos in game_print_state; remove unused variable. * game.c: *** empty log message *** * controller.c: use seats instead of unsigned int for seats variables, since we've changed from unsigned to signed, and should have been doing it anyway. * game.c: Add game_print_state, in basic form (doesn't print players or options). * tiles.h: Curiously, we never included the tiles-enum functions! * game.h: change noseat from a #define to an enum constant. Add game_print_state as debugging function. * tiles.c: Curiously, we never included the tiles-enum functions! 2004-05-03 Julian Bradfield * protocol.c: typo in warning message (said pmsg, meant cmsg) 2004-04-16 Julian Bradfield * xmj.man: remove ref to dcs web site * CHANGES, ChangeLog: 1.6.2 * sysdep.c, sysdep.h: (v)snprintf does exist in Windows, it's just missing from mingw headers. So use it. 2004-04-14 Julian Bradfield * gui.c: tilesetbox_set: should not refer to the .tile value of an empty tileset. 2004-04-10 Julian Bradfield * CHANGES, ChangeLog: 1.6.1 * player.c: Since time immemorial, the thirteen wonders checking function has been wrong, in that it didn't actually check for the pair! * controller.c: Server was still setting up player with blank name, causing segfault. 2003-10-12 Julian Bradfield * Makefile.in: Oops...don't install table manager when not built! 2003-10-08 Julian Bradfield * sysdep.c: fake (v)snprintf for Windoze. 2003-10-07 Julian Bradfield * ChangeLog: really 1.6 * sysdep.h: fake (v)snprintf for Windoze. * CHANGES, ChangeLog: 1.6 2003-10-06 Julian Bradfield * protocol.h: pversion to 1060, to mark changes in backslash processing. Really, these should be bug fixes, but the protocol was unclear before. 2003-10-01 Julian Bradfield * gui.c: When auto-declaring specials, players should still get the chance to declare kongs if they have some. 2003-09-30 Julian Bradfield * controller.c: The processing of chow claims was confused, with the result that if other duplicate claims arrived while waiting for a chow to be fully specificied, a confused state resulted. The logic is now (hopefully) more straightforward, and duplicate claims are silently ignored. 2003-09-29 Julian Bradfield * proto-decode-msg.pl: remove redundant condition * proto-encode-msg.pl, sysdep.c, protocol.c: eliminate warnings introduced by previous changes. * proto-decode-msg.pl, proto-encode-msg.pl, sysdep.c: Sort out backslashes at last. Still inelegant in places, but hopefully correct and overflow-safe. * gui.c: Messages should not undergo brace expansion. 2003-09-28 Julian Bradfield * proto-encode-msg.pl, sysdep.c, protocol.c: Folded in Nicolas Boullis's fixes for buffer overflow. Backslash escaping is still a mess. 2003-09-27 Julian Bradfield * proto-encode-msg.pl, proto-decode-msg.pl, gui-dial.c, controller.c: Patches against buffer overruns from Nicolas Boullis. 2003-08-24 Julian Bradfield * proto-decode-msg.pl: Do backslash unescaping, for backslashes only. (i.e. \\ - \, but single backslashes not changed) 2003-08-21 Julian Bradfield * proto-decode-msg.pl: Has backslash escaping; but this is done in expand_protocol_text, so we shouldn't do it here, but rather should make all char* fields subject to expansion. 2003-08-20 Julian Bradfield * protocol.c, proto-encode-msg.pl: Escape backslashes in char* arguments. * sysdep.c: fix bug in backslash counting 2003-06-08 Julian Bradfield * Makefile.in: control table manager compilation by TableManager variable, (currently set to off) to avoid problems in releases. * manager.c: This now compiles, and handles (basically) the login message. 2003-06-07 Julian Bradfield * mprotocol.c: protocol-enums -> mprotocol-enums remove superfluous variable * manager.c: New file. 2003-06-04 Julian Bradfield * CHANGES, ChangeLog: 1.5.6 2003-06-02 Julian Bradfield * game.c: clear the konging flag after robbing a kong. (Otherwise get consistency error in next hand.) 2003-06-01 Julian Bradfield * controller.c: Ouch. Since the year dot, we haven't been setting cmsg_check when loading a game from file! This causes state inconsistencies. 2003-05-03 Julian Bradfield * Makefile.in: comment out mj-manager from programs, so it isn't made by default * Makefile.in: Add targets for the table manager. [not in release] 2003-04-21 Julian Bradfield * gui.c: Don't showraise messagewindow when info in main; for some reason, this seems to raise the whole top-level window. 2003-04-20 Julian Bradfield * Makefile.in: add the auto-generation for the mproto files. [not yet in release] * proto-decode-msg.pl, proto-encode-msg.pl: generalize to deal with mcmsg and mpmsg [not yet in release] * mprotocol.c, mprotocol.h: New file. [not yet in release] * xmj.man: table colour, change to DiscDoubles semantics. 2003-04-17 Julian Bradfield * controller.c: Change semantics of DiscarderDoubles to match singaporean play (as it should have been): discarder pays double, but on self-draw everybody pays double. Simplify cannoning logic. 2003-04-16 Julian Bradfield * gui.h, gui.c, gui-dial.c: Add table colour display option. 2003-04-13 Julian Bradfield * game.c, scoring.c: Trivial code tidy: make more use of is_green instead of checking for individual green tiles. 2003-04-12 Julian Bradfield * gui-dial.c: Tidy: set discard to 0 when MahJong from hand, instead of leaving it uninitialized. 2003-04-11 Julian Bradfield * xmj.man, gui-dial.c: Change GUI description of auto-declare options. * ChangeLog, CHANGES: 1.5.5 * scoring.c: Bug fix: was noting Imperial Jade when having Thirteen Orphans. * gui.c: Auto-declaration of winning hand now claims the discard too. * gui.c: Fix auto declaration of winning hands to allow for Special Sets. 2003-04-10 Julian Bradfield * ChangeLog, CHANGES: 1.5.4 * gui.c: fix error in Message processing: was displaying (possibly NULL) unexpanded text instead of expanded! * gui.c: Change xmj.ini searching behaviour under Windows. * sysdep.h, sysdep.c: Change get_homedir to return NULL when there's no homedir defined, so calling program can decide where to look. 2003-04-07 Julian Bradfield * gui-dial.c: Initialize the open dialog fields during initialization, so that an automatic connect picks up the right values. 2003-04-05 Julian Bradfield * CHANGES, ChangeLog: 1.5.3 2003-04-04 Julian Bradfield * gui-dial.c, gui.c, gui.h, xmj.man: Add a selector for the text font used for fixed width text. * game.c: Add discarded_tile_count field to game structure. Use it in End cannon. * game.h: Add discarded_tile_count field to game structure. 2003-04-01 Julian Bradfield * CHANGES: indentation typos. * ChangeLog: 1.5.2 * xmj.man: message text entry update, and "keep cursor here" box. * CHANGES: 1.5.2 2003-03-31 Julian Bradfield * gui-dial.c: Pop down chowdialog after using it when dealing with old server. * gui.c: setup dialogs after error messages, in case something went wrong. (Only really needed for one case: mistaken chow at mah jong when dealing with old server.) 2003-03-30 Julian Bradfield * controller.c, greedy.c, game.h, gui.c, gui-dial.c, game.c, protocol.h:: Break apart the pending flag into chowpending and mjpending. Do legality testing for an AnyPos chow claim during MahJong. This requires a protocol change: before now, AnyPos was illegal during MahJonging. Pversion now 1050. * gui.h: add server_pversion. * game.c: Add checking for an anypos chow in the mahjonging state. 2003-03-25 Julian Bradfield * gui-dial.c: Add a checkbox to keep the keyboard focus in the message entry. 2003-03-23 Julian Bradfield * gui.c, client.h, sysdep.h, sysdep.c, client.c: Add some hacks to allow an address of "-" to mean stdin and stdout. Only really likely to work under Unix, but who knows... * controller.c: print errno when get_line returns null. * controller.c: Don't allow reconnects while end_on_disconnect is in progress. 2003-03-22 Julian Bradfield * protocol.c, protocol.h: protocol expansion should yield empty string when given NULL source. 2003-03-16 Julian Bradfield * ChangeLog, CHANGES: 1.5.1 * proto-decode-msg.pl: Work round bugs in Microsoft sscanf: instead of using " %n" in format to advance to next item, use "%n" and explicit loop over white space. (Symptom: %n was returning ridiculous (or possibly no) values). * controller.c: Fix the handling of disconnection before game starts, and the assignnent of ids to players, and the finding of a player structure in the game for a new player. 2003-03-15 Julian Bradfield * ChangeLog: 1.5 * README: add tiles-small * CHANGES: 1.5 * gui.c: Change XmjOption to Display. (Still read XmjOption, of course.) * xmj.man: Change XmjOption to Display. 2003-03-14 Julian Bradfield * xmj.man: Describe LosersSettle, EastDoubles, DiscDoubles. * protocol.h: Add GODiscDoubles. Go to pversion 1040. * game.c: Add GODiscDoubles * controller.c: Add DiscDoubles 2003-03-10 Julian Bradfield * FILES-src, FILES-binary, MANIFEST: Add tiles-small. * controller.c: fix cannon scoring when no east doubles. * controller.c, protocol.h, game.c: Add GOLosersSettle. * controller.c: remove historical relict. * game.c, controller.c: Add GOEastDoubles. * protocol.h: Add GOEastDoubles. Pversion set to 1039 while adding options. 2003-03-06 Julian Bradfield * gui-dial.c: If the message entry window has focus, don't take it away. 2003-03-05 Julian Bradfield * controller.c: Implement the LoadState pmsg. * game.h: Add game_has_started. * gui.c: handle a second ConnectReply (e.g. in response to loadstate) * game.c: Add game_has_started. Allow deletion of players until game has started. Set active=0 on receipt of Game msg. 2003-03-01 Julian Bradfield * protocol.h: Add the LoadState pmsg. Protocol version 1038. * gui.c, game.c, gui-dial.c, controller.c: Remove some compiler warnings. 2003-02-27 Julian Bradfield * ChangeLog: up to 1.5pre5 * xmj.man: Bring the documentation up to 1.5pre5 level. 2003-02-20 Julian Bradfield * controller.c: I believe this implements disconnect penalties, but it needs some testing. 2003-02-19 Julian Bradfield * controller.c: --exit-on-disconnect still means it, even before a game has started. * gui.h, gui.c, gui-dial.c, controller.c: Implement client local timeouts. * protocol.h: Define empty name in CMsgPlayerMsg to mean "delete player". Add GOTimeoutGrace game option. Protocol version 1036. * game.c: Add the TimeoutGrace game option. * player.h: Add POLocalTimeouts option. * controller.c: If players disconnect before the game starts, don't exit. (Assuming all connected players support the deletion of players.) * game.c: Handle the new meaning of CMsgPlayer with empty name as "delete player". * game.h: Renumber the states so that HandComplete is zero. This has the useful side effect that if a Game is initialized in the usual way by zeroing, it starts in HandComplete, which makes more sense than "Dealing". It should have no-non side effects, since nothing assumes anything about the numerical values. 2003-02-18 Julian Bradfield * gui.c: rename temp new rcfile just in case it helps on ME. * sysdep.c: downgrade two warnings to info; remove / in C:/ for Windows root. * gui.c: fix typo in warning * gui.c, gui.h: *** empty log message *** * gui-dial.c: Change warning stuff for new log_msg. * sysdep.h, sysdep.c: Generalized warn() to a log_msg with different warning levels. Keep warn(), and add info(). Change warning_hook to log_msg_hook taking warning level. * gui-dial.c: try to avoid accidental discards * gui-dial.c: remove stuff that was in wrong place (and commented out) * controller.c: I think this correctly implements --end-on-disconnect. Further testing would be good. * gui.c: Fix long-standing stupid bug in washout handling causing gtk-critical errors. Also, print the WashOut reason field. * gui.c: Well, well. We segfaulted on messages from the controller! 2003-02-17 Julian Bradfield * game.c: Add explanatory comment. * game.c: Prevent players discarding when they should be drawing a tile. (How did this one last so long?) * gui-dial.c: Argh! Fix erroneous updating of short status window. 2003-02-16 Julian Bradfield * gui.c: choose smaller display sizes on Windows. * sysdep.c: Windows (or mine at least) doesn't have vsnprintf. Increase the buffer size in warn(); and make it non-static. * makedep: Handle the change in semantics of -MM in gcc versions 3.1 and later. * gui-dial.c, gui.c: catch warnings during re-creation of display * gui.h, gui.c, gui-dial.c: Add display size choosing to display prefs panel * gui-dial.c: Whoops. Finish last change. * gui.c, gui-dial.c: remove the robot player name preferences from Display prefs; make the entries in the open dialog sticky instead. * gui.h, gui.c, gui-dial.c: Get warnings into the display even if they happen before the warnings window is available. * gui.h, gui-dial.c, gui.c: Add options to select main font. 2003-02-15 Julian Bradfield * gui-dial.c: warningentry shd be initially hidden. * gui.c: add rcfile name to some of the warnings. * sysdep.h, sysdep.c: Add the warning_hook to warn(). * gui.h, gui.c, gui-dial.c: Use new warning hook to put warnings into a new window, and add a menu entry to display them. * gui-dial.c: remove dcs from "about" text * sysdep.c: re-arrange warn so that it formats the string into a buffer first and then prints it. * client.c: client_find_sets: returns pungs ahead of chows * client.h: correct and change spec in comment for client_find_sets, concerning order of returned sets * gui.c: fix bug in auto-declaring specials when a new one is drawn. * controller.c: Add --no-manager option. * game.c: clarification in manager testing. * game.h: add comment allowing -1 as manager value 2003-02-14 Julian Bradfield * gui-dial.c: add id to short status window 2003-02-11 Julian Bradfield * gui.c: Oops. Had non-block-initial declaration. gcc-3 must be forgiving. * gui-dial.c: change New game and Join game menu entries to be more suggestive. * gui.c: *** empty log message *** * gui-dial.c: Add the playing_auto_winning/losing options to preference dialog. * gui-dial.c: if'd out thing preventing discard callback being invoked when discard dialog is not visible (which should be impossible anyway, so I'm not convinced this happens). * gui.h, gui.c: Implement auto declaring of closed sets in winning and losing hands. 2003-02-09 Julian Bradfield * gui.h, gui.c, gui-dial.c: Add the playing preferences dialog, with the auto_declare_specials preference in it. * gui.h, gui.c: Implement the programming of auto declaring specials. * controller.c: insist on a non-null name in Connect messages. * gui-dial.c: remove game info and messages entry from show menu when they're in the main window. * gui.h, gui-dial.c, gui.c: Make the name field in the open dialog sticky when connecting to a remote. * gui.c: Make Address setting a subgroup of OpenDialog. * gui.c, gui-dial.c: Implement info_in_main as a display option. * gui-dial.c: in short info window, put "suspended" in separate line * gui-dial.c: Handle the inclusion of the messaging window into the main window. * gui.c: Improve format of arriving messages (don't waste line at end of messagetext); don't set up the dialogs when a message arrives (to avoid reverting focus to the playing dialogs). 2003-02-08 Julian Bradfield * gui-dial.c: I think this is a reasonable info window implementation. Couldn't put in on the board (there's no space when we'ren not showing the wall), but this is fairly compact. * gui.h: add some variables for info windows in main * gui-dial.c: Implement info window in main, but I don't think I like it. Can I put them over the main window instead? * gui.c: move partway to implementing info_windows_in_main, and fix bug introduced in last change (wasn't starting robots with correct address) * gui.c: move the creation of text,message,status windows into create_display, and destroy them in destroy_display 2003-02-07 Julian Bradfield * gui-dial.c, gui.h, gui.c: Make the address sticky (Address entry in .xmjrc) when joining remote games. * gui.c: strip trailing white space from host names * gui.h, gui.c, xmj.man, gui-dial.c: Add the NoPopups display option to xmj; change text on apply/save buttons in display options panel. * scoring.c: Fix the treatment of scoring options with limits, and dbls/pts in a no-limit game; impose a hard limit of 1E8 to avoid arith overflow. 2003-02-04 Julian Bradfield * gui-dial.c: protect add_or_remove_..._accelerators against the accelerators not being defined. * gui.c: Make WALL_SIZE work when there is a game but no wall; and don't set up the wall if there is no wall (because the size is zero, e.g. between hands). * xmj.man: typo * gui.h, gui.c, gui-dial.c: Change read_or_update_rcfile parameters to be read/update flag bit sets. New enum XmjrcGroup for these. 2003-02-03 Julian Bradfield * gui-dial.c: In the new game panel, don't show the host entry (makes no sense, since we're starting a local server). But set the host entry to localhost so that clients are correctly started. * game.c: Sigh. Previous bug fix needs to be applied in both places cannons are detected. There should be a macro or something. Thanks to Thorsten Michels again. 2002-08-09 Julian Bradfield * CHANGES, ChangeLog: 1.4.1 * game.c: Bug fix in cannon with last four tiles. (Thanks to Thorsten Michels.) 2002-04-29 Julian Bradfield * gui.c: remove some unused variables * protocol.h, protocol.c: Add the protocol_version global variable. (N.B. This variable is not yet used or set; this has strayed in from the development version.) 2002-03-31 Julian Bradfield * controller.c: Bug fix: when resuming a game, need to set out timeout from the game option. 2002-03-30 Julian Bradfield * ChangeLog, CHANGES: 1.4 * xmj.man: Add KongHas3Types. * scoring.c, player.c, gui.c, player.h: Used annexed instead of melded, since melded is inaccurate. * gui.c: Handle Millington kongs * scoring.c: Handle GOKongHas3Types * game.c: Add GOKongHas3Types. * player.h: Add melded flag to Tileset for Millington kongs. * player.c: Handle three types of kong. Note that the three types are distinguished, and the printed forms distinguished (by printing TT-TT-TT-TT for a melded kong and TT-TT-TT+TT for a claimed kong) always, regardless of options or protocol versions, since to do anything else would involve importing external info into the module. This is thus not strictly backward compatible; but it happens to be backward compatible by implementation, since the previous tileset parser only looks at the first separator to distinguish open and closed kongs. * protocol.h: Add GOKongHas3Types; proto version 1034 2002-02-04 Julian Bradfield * ChangeLog, CHANGES: 1.3.2 * gui-dial.c, gui.c: Fix for infelicity in spinbuttons: they do not change the internal value when numbers are typed until return is hit, so call update explicitly before getting value. 2002-01-08 Julian Bradfield * ChangeLog: really 1.3.1 * gui.c: Remove ugly hack that Nicolas justly rejects. * ChangeLog, CHANGES: 1.3.1 * gui.c: code for animation both with internal widgets and with popups. Default to popups for Windows, where it seems to smooth animation. 2002-01-07 Julian Bradfield * gui.c: don't uniconify auxilary windows that have been closed while the main window was iconified. * Makefile.in: remove a mysterious 0240(nbsp) character that got in at some point. * gui.c: (a) sort out bugs in the new tiletip implementation. (b) fix bug in option reading (IconifyDialogs). 2002-01-06 Julian Bradfield * FILES-src: oops -- add vlazyfixed * xmj.man: tiletips shown for selected tile when "always". * gui.c: (a) Make tiletips of mouse and selected tile interact correctly. (b) Don't install toggle callback on non-toggles! * gui.c: fix bug in get_relative_posn. * gui.c: Changed tiletips into children of boarfixed. Unfortunately, this has exposed a bug in get_relative_posn, sigh. * gui.c: If tiletips always shown, show the tiletip of the selected tile. * gui.c: Remove the top_window visibility tracking. No longer needed, since now there are no more independent popups/animations to suppress. * gui.c: Make the claim windows descendants of the main window as well. (The positioning is no longer quite perfect, since I'm not bothering to reposition them for Mah Jong!, but just doing it once on Pung!. Never mind.) 2002-01-05 Julian Bradfield * Makefile.in, gui.h, gui.c: Introduced the vlazy_fixed widget, and used it to finally fix up animation so that the floating animations are part of the display window instead of being independent. * vlazyfixed.c, vlazyfixed.h: *** empty log message *** * MANIFEST: Introduced the vlazy_fixed widget, and used it to finally fix up animation so that the floating animations are part of the display window instead of being independent. 2002-01-02 Julian Bradfield * ChangeLog: Really 1.3 * protocol.h: game opt optnat field should be unsigned * greedy.c: suppress a warning on really picky compilers * sysdep.h: define socklen_t for windows * sysdep.c: hpup fix and warning suppression * CHANGES: 1.3 (hopefully...) * ChangeLog: 1.3, I hope. 2001-12-29 Julian Bradfield * README: add PATH reminder * MANIFEST: credit for arabic tiles 2001-12-28 Julian Bradfield * sysdep.h: try to remove warning in hpux * xmj.man: note that left/right accelerators don't work under Windows. * gui.c: vis_callback only works under X. 2001-12-27 Julian Bradfield * Makefile.in: Add some example LDLIBS settings. * gui-dial.c, player.c, controller.c, gui.c: Make sure all functions pre-declared as static are then declared as static, to suppress warnings on HP compiler. 2001-12-26 Julian Bradfield * make-release: Add -Wl,--noinhibit-exec to link options for static xmj. This is because in order to get arrow key accelerators we are redefining a symbol in libgtk.a, and normally ld will consider that an error. * MANIFEST: Add tiles-numbered * ChangeLog, CHANGES: 1.3pre1 * gui-dial.c: close button takes focus in scoring window * gui.c, gui-dial.c, xmj.man: IconifyDialogs not supported under Windows. * gui.c: eliminate warnings 2001-12-24 Julian Bradfield * gui.c: Don't try the X-specific (un)iconify stuff. * sysdep.c: start_background_program_with_io for Windows. * sysdep.h: comment update 2001-12-23 Julian Bradfield * gui.c, gui.h, gui-dial.c: fix dialog_popup: wasn't handling CentredOnce etc. correctly! Also, open_dialog wasn't being closed with saving_posn. * gui-dial.c, gui.c, gui.h, xmj.man: Implement the IconifyDialogs display option. * gui.c: avoid animating or popping up claim windows if the top window is unmapped or obscured. (Unfortunately, this still doesn't seem to cover the case of the window being offscreen, but I'm not sure I can be bothered to do anything about that.) * gui-dial.c: fix accelerator key for save as... 2001-12-17 Julian Bradfield * Makefile.in: make version.h no longer depend on version.h.in . * make-release: remove version.h before making it. 2001-12-16 Julian Bradfield * gui.c: suppress warning * scoring.c: remove a warning * controller.c, gui-dial.c: remove multi-line string literals 2001-12-09 Julian Bradfield * README: add tiles-numbered * FILES-binary, FILES-src: Add tiles-numbered 2001-12-07 Julian Bradfield * gui.c: wall tiles should not take focus. * xmj.man: document menu accelerators * gui-dial.c: Add accelerators to menu, and have start button take focus in menus. * sysdep.c: Add the start_background_program_with_io function. However, the Windows version is entirely unchecked, and may not even compile. Check it at the office. * sysdep.h: Add start_background_program_with_io * gui.c: Use the new server output feature to detect when it's started, and reduce the sleep times to speed up game starting. * controller.c: change output to OK: address or FAILED: reason 2001-12-06 Julian Bradfield * controller.c: print the successful socket (as a valid --address argument) on stdout as Socket: socket. If fail, print Socket: -1. * gui.h: Add a game extras struct * gui.c: Change dead wall positioning so that it's always determined by the original start of the dead wall. * xmj.man: document keyboard accelerators and change of Done to Finished in scoring dialogue. * gui-dial.c: add keyboard accels to scoring dialog; change "done" to "finished". 2001-12-05 Julian Bradfield * gui-dial.c: Add accelerators to chow dialog * gui.c: fix behaviour of arrow keys when no tile selected 2001-12-04 Julian Bradfield * gui-dial.c: add accelerators to ds dialog * gui-dial.c: add accelerators to turn dialog * gui-dial.c: Add accelerators for the discard dialog. * gui.c: move include gdkkeysyms to gui.h * gui.h: include gdkkeysyms * gui.c: Implemented, with extreme difficulty owing to GTK's arrogant ideas about what you're allowed to do, left/right arrow keys for tile selection changing. We're now dependend on gtk sources... yech. 2001-12-03 Julian Bradfield * scoring.c, xmj.man: Add ConcealedFully, ConcealedAlmost and LosersPurity game options and implement their scoring. Change All Honours and All Terminals to get All Majors also. * game.c: Add ConcealedFully, ConcealedAlmost and LosersPurity game options. * protocol.h: Add ConcealedFully, ConcealedAlmost and LosersPurity game options. Increase protocol version to 1032. 2001-12-02 Julian Bradfield * sysdep.h: Add sys/time.h back again, for gettimeofday. * gui.h, gui-dial.c, gui.c, xmj.man, greedy.c: Add --name for mj-player (which was supposed to be there all along), add entries for it in the new game panel, and add display options to give defaults for it. 2001-11-07 Julian Bradfield * sysdep.h: use time.h instead of sys/time.h 2001-09-06 Julian Bradfield * ChangeLog, CHANGES: 1.2.3 * gui.c: Aaargh! Was clearing discard_history in the wrong place! So it wasn't getting cleared at all. 2001-09-04 Julian Bradfield * ChangeLog: This really is release 1.2.2, unless something else goes wrong. * gui.c: Copy pref_showwall from --[no-]show-wall option. * xmj.man: Show-wall display option now has immediate effect. * CHANGES: 1.2.2 really: includes display option changing. * gui.c: when recreating topwindow, put it where it was. * gui.c: handle discard placement when recreating display. * gui.c: display rebuilding now handles wall. * gui.c: display recreation handles players and dialogs. Now for wall and discard... * ChangeLog: includes changes not in 1.2.2 * ChangeLog: for 1.2.2 * CHANGES: new date for 1.2.2; maybe finally got it right. * sysdep.h: include unistd.h in windows as well. * gui.h, gui-dial.c, gui.c: Checkpoint: moved display creation into separate function, made destroy function, remake display when options change. But still need to handle redisplay of players, discards and dialog setup. * client.c: client_close should set g->fd to invalid 2001-09-03 Julian Bradfield * gui.h, gui-dial.c, gui.c: Checkpoint: moved display creation into separate function, made destroy function, remake display when options change. But still need to handle redisplay of players, discards and dialog setup. * client.c: client_close should set g->fd to invalid * Makefile.in: Use NULL for tileset path, for Windoze. 2001-09-02 Julian Bradfield * Makefile.in: remove quotes wrong for Windows * ChangeLog, CHANGES: 1.2.2 * gui.h, gui-dial.c, gui.c, xmj.man: Add Tileset and Tileset Path display options to gui. * Makefile.in: Change for TILESET(PATH) * xmj.man: Remove --tilepixmapdir, replace by --tileset and --tileset-path . 2001-08-21 Julian Bradfield * ChangeLog, CHANGES: for 1.2.1 * README: change donation text. 2001-08-19 Julian Bradfield * scoring.c: fix bug in filling only place (was counting claimed discard as an exposed tile when calculating "all exposed"). * game.c: the game_draw/peek_tile functions must return error if the live_used is >=, not just ==, the live_end, because the draw_loose_tile assumes this (with the non-Millington dead wall). * controller.h: hike number of history els to 1024 2001-08-15 Julian Bradfield * gui.c, xmj.man: Remove --tilepixmapdir, replace by --tileset and --tileset-path . (NOT IN 1.2.1.) 2001-08-11 Julian Bradfield * Makefile.in: make version.h if not there. 2001-08-06 Julian Bradfield * version.h.in: *** empty log message *** * ChangeLog, CHANGES: For 1.2 * version.h: File deleted. * make-release: put version in version.h * version.h: have version subbed in by make-release. * controller.c: Add the hack for dumping history of hand by second save state request. 2001-08-05 Julian Bradfield * proto-encode-msg.pl, gui.c, game.c, controller.c, greedy.c, protocol.c, protocol.h: Add CMsgComment (with hacks to give it code #); protocol version to 1030. * xmj.man: Update for new features. * gui-dial.c, gui.h, controller.c, gui.c: Add --save-on-exit to server and gui panel. 2001-08-01 Julian Bradfield * controller.c: remember to initialize the game extras structure. 2001-07-31 Julian Bradfield * gui-dial.c, gui.c, gui.h: Add Resume game... entry to Game menu. * controller.c: Add --no-id-required option. * controller.c: fix game loading. * gui.c, gui.h, gui-dial.c: Add Save as... menu entry to Game menu. * controller.c: More intelligent choice of default save file name: previous explicit name, failing which: file given by --load-game option, failing which: game-DATE-SEQ up to seq of 9. 2001-07-30 Julian Bradfield * gui.h, gui.c, gui-dial.c: Popup notice when state saved. Add save option to connect menu. Rename Connect menu to Game. * controller.c: Take notice of the filename in SaveState, and return a StateSaved message. 2001-07-29 Julian Bradfield * greedy.c, controller.c, game.c: Handle CMsgStateSaved. * gui.c: Handle (by doing nothing) CMsgStateSaved. * protocol.h: Add filename field to PMsgSaveState. Add CMsgStateSaved. * Makefile.in: add EXTRA_CFLAGS for command line use. * controller.c: On resumption, feed the history to all reconnecting players at once, rather than one after the other. * controller.c: implemented restoring of game state (via --load-game option), though file name is currently ignored. * game.c: handling CMsgWall: load until MAX_WALL_SIZE or end of message. (game.wall.size is actually set at NewHand, not before). * game.c: set options from default when handling a Game CMsg, rather than clearing them. * controller.h, controller.c: Adjusted save_state so that it doesn't save completed hands any more. Added prehistory to game structure so we _can_ save a completed hand if we want to later. 2001-07-28 Julian Bradfield * controller.c: implemented game saving again. Next: loading it! * sysdep.h, sysdep.c: Add fd_put_line and fd_get_line. * gui.c, game.c, controller.c, greedy.c: Handle CMsgWallMsg. * protocol.h: Add CMsgWallMsg. Inc pversion to 1025 during development. * controller.h, controller.c: Get rid of player histories, and just keep a single game history. 2001-05-17 Julian Bradfield * xmj.man: New major version starting at 1.1 release * xmj.man: remove mistaken backslashes * CHANGES: 1.1 really (slightly more human text) * tiles.h, version.h, proto-encode-msg.pl, protocol.c, protocol.h, runtest, scoring.c, scoring.h, stats, sysdep.c, sysdep.h, tiles.c, make-release, makedep, makefallbacktiles, makefile.msvc.old, maketxt, mj-player.man, mj-server.man, player.c, player.h, proto-decode-msg.pl, gui-dial.c, gui.c, gui.h, iconres.rs, lazyfixed.c, lazyfixed.h, logstats, make-enums.pl, controller.h, game.c, game.h, greedy.c, ChangeLog, FILES-binary, FILES-src, LICENCE, MANIFEST, Makefile.in, README, client.c, client.h, controller.c: New major version starting at 1.1 release * version.h: Version 1.1 * CHANGES: Change for 1.1 * ChangeLog: Changes up to 1.1 * gui.c: Tidy; remove the .xmjrc.new file after using it. * sysdep.c: Back out last change (socklen_t not defined in windoze). Add unlink() for windoze. * sysdep.h: Add a defn for unlink() in Windows (provided by mingw anyway, but we prob shouldn't assume that). 2001-05-16 Julian Bradfield * README: typo * sysdep.c: use socklen_t where appropriate * gui-dial.c: remove superfluous semi-colon * sysdep.h, sysdep.c: Add get_homedir. * gui.c: Use get_homedir. * game.c: don't use uint; not always defined. 2001-05-15 Julian Bradfield * xmj.man: fix error in proto desc * protocol.c: Skip leading white space when reading game option entry description field. * make-release: Change mj-*-static.tar to static-mj-*.tar in a desperate attempt to stop the idiots who download the static version unnecessarily. 2001-05-14 Julian Bradfield * gui-dial.c: change wording * maketxt: fixed a bug or two, and add clause for my game option macro. * xmj.man: Documented all the game options. 2001-05-13 Julian Bradfield * gui.h, gui.c, gui-dial.c: Add nagware. * protocol.h: remove old comment. * gui.c: update sequence number in stdin callback * client.h, client.c: client_send_packet maintains and returns packet sequence number. * game.h: Add cseqno field for client_ routines. * controller.c: was wrongly incrementing sequence number twice 2001-05-11 Julian Bradfield * gui.c: During kong, always query for Mah Jong. (It's possible for player_can_mah_jong(..,0) to be true, but for us not to be able to go out, because of minimum double requirements.) * xmj.man: Change DisplayOption to XmjOption. * gui.c: (a) make last change work (shd compile before checking in...) (b) Change DisplayOption to XmjOption. * gui.c: Don't put up the kong claim window until we know we need it. * gui.c: remove incorrect guard in canmahjong answer clause (has no id). 2001-05-10 Julian Bradfield * protocol.h, game.c, scoring.c: Add MahJongScore option. 2001-05-08 Julian Bradfield * xmj.man: Documented changes to xmj. Next, document game options. (And possible player strategy options.) * controller.c: Don't make robot players managers. * gui.c, gui-dial.c: Only apply game preferences/options when allowed to. * controller.c: Set the game manager to be the first player to connect. * gui-dial.c: Initialize new game timeout option entry from preference table. * gui.h, gui.c, gui-dial.c: Add showwall preference to display options panel. * gui.c, gui-dial.c: Use player names in scoring window tabs, and make homogeneous tabs. * xmj.man: remove old mj-player option example. * xmj.man: update mj-server options. 2001-05-07 Julian Bradfield * gui-dial.c: Make all buttons on prefs panel close window. * controller.c: Add --option-file argument. * greedy.c: Make sure to get the game options. * protocol.h, gui.c, game.c: Add DeadWall option. * game.c: Bug fix: game_set_option_in_table was not correctly setting options with unknown codes. * protocol.h, controller.c, game.c, gui.c: Make dead wall type option DeadWall16 (Millington) rather than DeadWall14 (non-Millington). 2001-05-06 Julian Bradfield * lazyfixed.h, lazyfixed.c: Now only overrides the remove method of fixed, as this seems to suffice to eliminate flicking. Don't ask me why. * gui.c: Changed all the lazy calls back to fixed, since we've made lazyfixed inherit all the external methods of fixed. * lazyfixed.c: It seems that it's only the remove method that causes flickering. That should mean we can use all the supplied methods. * gui.c: remove now unnecessary resize on discard_area * game.c: game_handle_cmsg should ignore NoClaims for old discards! * FILES-src, MANIFEST: Add the lazyfixed.[ch] files. * Makefile.in: Add the lazyfixed widget to the Makefile. * gui.c, gui.h: Use my own lazyfixed widget for the discard area. This reduces (eliminates!) flickering in the wall. * lazyfixed.c, lazyfixed.h: New file. * controller.c: Have DeadWall14 as default when enabled. * gui.c, gui.h, gui-dial.c: Add tiletips display option. Current game options... menu entry only sensitive during game. 2001-05-05 Julian Bradfield * protocol.h, game.c, scoring.c, gui.c: Add DeadWall14 option. * protocol.h, game.c, controller.c: Add FlowersLoose option. 2001-05-03 Julian Bradfield * gui-dial.c, gui.c: make declaring option dialog handle Flowers option. * game.c: make game_get_option_value handle null game. * protocol.h, controller.c, game.c: Handle the Flowers option. * gui.c: Display now handles walls smaller than the maximum. 2001-05-02 Julian Bradfield * controller.c, gui.c: Use wall.size field of game struct instead of 144. * gui.c, gui-dial.c, game.c, game.h, controller.c, scoring.c, greedy.c: Unpack the internal info struct in the game struct; just have fields directly in the main struct. * game.h, game.c, controller.c: Remove the superfluous discard_serial field from the game struct. * gui.c: Remove a fixed FIXME comment. * game.c, game.h: Add wall.size field to game struct. * tiles.c: replace 144 by MAX_WALL_SIZE where appropriate. And add includespecials arg to random_tiles. * controller.c, tiles.h: Add includespecials arg to random_tiles. * game.h: replace 144 by MAX_WALL_SIZE where appropriate. * protocol.h: Add a MAX_WALL_SIZE define. 2001-05-01 Julian Bradfield * gui.c: Query all options on getting game message, instead of just querying timeout on connect. * controller.c: GameOption and PlayerOptionSet messages should not go into the history. Clients must always query/set. * game.c: And make handle_cmsg clear the option table on GameMsg, not copy from default. * game.c, game.h: add game_clear_option_table * gui-dial.c: more slight improvs to prefs panel. * gui.h, gui-dial.c, gui.c: Handle rcfile reading better: changed spec and name of read_or_update_rcfile. * gui-dial.c: Improvement to prefs panel. * scoring.c: rationalize the own flower/season scoring a bit. * gui.c: make showwall global; remove the tilesleft label, which is now done in the dialogs. * gui.h: Make showwall global. * gui-dial.c: When not showing the wall, put the tiles left in the dialog labels. * gui.c: Add a tilesleft indication when not showing the wall. 2001-04-30 Julian Bradfield * controller.c: Add some protection against flooding: call the timeout handler if the timeleft goes negative. * gui.c: Handle querying of mj after kong properly (was getting into loop). * gui-dial.c: Don't put disabled options in the game options panel. * protocol.h, game.c: Add Flower scoring options. * scoring.c: Implement Flower scoring options. (And add parens round score arg in doscore.) * scoring.c: Use GOSevenPairsVal for scoring. * gui-dial.c: In option panels, make reset sensitive only on change. 2001-04-29 Julian Bradfield * proto-encode-msg.pl: If a (char *) arg starts with a space, escape it with backslash. * gui.c: Call expand_protocol_text on scoring and settlement messages. * scoring.c: Rationalize the scoring code; it's now set up to take GOTScore values. * player.c: get rid of qsort, and use bubblesort instead. Doesn't actually make a huge difference, around 8%. * protocol.c: increase buffer sizes. 2001-04-28 Julian Bradfield * controller.c, scoring.c, greedy.c, game.c, game.h: Change the names of the option entry returning functions to make it clear what they return; add game_get_option_value to get a value from a game without fuss. * gui-dial.c: Change Score type in option dialog to allow half limits. * protocol.h: Changed defn of Score type to have centi-limits. * protocol.h, game.c: Add SevenPairsVal option. * gui-dial.c: Simplify game option panels: don't show current value separately. * player.h: Change player_can_mah_jong to take flags indicating certain special hands are allowed; flags given (as bits) by MJSpecialHandFlags enum. * gui.c: Handle change to player_can_mah_jong spec. * greedy.c: Handle SevenPairs. (Don't try to get it, but don't pass it up if it just happens.) * client.h, client.c: Handle special sets in mah-jong; change to spec of client_find_sets. * player.c: Handle seven pairs. Required change to spec of player_can_mah_jong. * protocol.h: Add SevenPairs game option. * controller.c: Handle SevenPairs. * game.c: Add SevenPairs game option and handle it. * scoring.c: Handle scoring for seven pairs. * game.c: Correct types of option function args. And optimize option lookup. * game.h: Correct types of option function args. * gui.c: Ask server about ability to mah-jong, when robbing kongs. * gui-dial.c: change style of prefs panel a bit * gui.c: clear game option dialog on new game. * scoring.c: Implement the ScoreLimit and NoLimit options. * game.c: Add game_copy_option_table. * gui.h: Implement game preferences panel. * game.h: Add game_copy_option_table. * gui.c, gui-dial.c: Implement game preferences panel. 2001-04-18 Julian Bradfield * protocol.c, gui-dial.c, controller.c: Add the GOTNat option type. * game.c: Add the GOTNat option type; add ScoreLimit and NoLimit options. Change Timeout to Nat. * protocol.h: Add the GOTNat option type; add ScoreLimit and NoLimit options. 2001-04-09 Julian Bradfield * gui.h, gui.c, gui-dial.c: More work on option dialog: reset button for each entry, and global update button. Make it update over creation and killing of game. This is awful mess. Make it properly OO sometime. 2001-04-05 Julian Bradfield * gui.c, gui.h, gui-dial.c: Game option panel basically in place. Now add some options and test it. * gui.c: Have --size default to 17, not 18, for 800x600. (for 1.0.4, included now in devel version so I don't forget.) * CHANGES: 1.0.4 changes * gui.c: Have --size default to 17, not 18, for 800x600. * gui.h, gui-dial.c: checkpoint * scoring.c: Bug fix: was always giving 2 for fishing the eyes, instead of 4 for a major pair. 2001-04-03 Julian Bradfield * game.c: give unknown option null name in default table, and infinite protocol, so always disabled. 2001-04-02 Julian Bradfield * gui.h, gui-dial.c: checkpoint while implementing game option dialog * game.c: fix warnings resulting from last change * game.c, protocol.c, protocol.h: add a userdata field to GameOptionEntry * MANIFEST, FILES-src: add icon files * Makefile.in: add stuff for Windows icon resource. * iconres.rs: New file. 2001-04-01 Julian Bradfield * gui-dial.c: checkpoint; adding game option setting... 2001-03-31 Julian Bradfield * game.c, controller.c, game.h: Introduced struct for option table, and changed game accordingly. Added functions to get/set options in tables. 2001-03-30 Julian Bradfield * gui.h, gui-dial.c, gui.c: Saving of display options done. 2001-03-29 Julian Bradfield * gui.c: Basic rcfile reading for display options in place. 2001-03-23 Julian Bradfield * greedy.c: added a little defensiveness: slight reluctance to discard to the right player. Makes a small (not sig) improv. 2001-03-06 Julian Bradfield * gui.c: pop down turn dialog at handcomplete (for washouts). 2001-03-04 Julian Bradfield * gui-dial.c: Add animation to the display options panel. (And fix bug.) 2001-03-03 Julian Bradfield * gui.h, gui.c, gui-dial.c: Added the Options menu, and the display options panel, which allows switching dialog posns. There are bugs when the program starts with -below and then one switches to -central, but I don't think these are mine; I can't be bothered to track down the gtk bugs. 2001-03-01 Julian Bradfield * gui.c: hide discard dialog at start * gui.h, gui-dial.c, gui.c: move dialog (dependent on --dialog-XXX) creation into single function 2001-02-26 Julian Bradfield * ChangeLog: 1.0.3 * CHANGES: 1.0.3 changes * gui.c: add read_rcfile stub * gui-dial.c: Oops. Positioning in dialogs-below has been broken for some time. Now fixed. 2001-02-20 Julian Bradfield * controller.c: added --wallfile option to load wall from file. 2001-02-11 Julian Bradfield * Makefile.in: remove warnings and optimizations from production code makefile * xmj.man: Add server --seed option. * CHANGES: --seed server option. * ChangeLog: 1.0.2 * CHANGES: 1.0.2 changes * greedy.c: If check_discard called while declaring specials, it's to try robbing a kong. In fact, the players would cope correctly with this situation anyway, but that was by accident! 2001-02-10 Julian Bradfield * version.h: release 1.0.2 * gui.c: Fix bugs in handling of kongs declared while declaring specials. * controller.c: Fix bugs in handling of kongs declared while declaring specials. Was not drawing a loose tile for the declarer! Also, must allow claims, since theoretically possible to rob such kongs. * game.c: Fix bugs in handling of kongs declared while declaring specials. (Must allow claims, since theoretically possible to rob them.) * scoring.c: Correct the detection of Heaven's/Earth's Blessing. Was relying on discard_serial, which would be incremented by kongs declared in the initial phase. Now use the player NoDiscard flags. 2001-02-09 Julian Bradfield * gui-dial.c: correct GPL name; extend copyright date. * LICENCE: Correct name of GPL. 2001-02-07 Julian Bradfield * runtest: Add various useful options. * greedy.c: further extensive simplification; several really stupid bugs fixed; extensive tuning. It is now definitely better: 1-2 sd better than 10.21, and 4sd better than release 1.0. Details of intervening changes are in StuffToKeep. 2001-01-30 Julian Bradfield * controller.c: Make sure that the caller slot is correctly filled even in the case of Heaven's Blessing (to avoid warning from scoring module). * controller.c: provide a --seed option to seed the random number generator. * runtest: Use a fixed series of games, given by initial seed 101, and then incrementing for each game. * greedy.c: This is an almost complete restructing of the strategy code. The same basic ideas are used, but now they are controlled at user level by a smaller number of orthogonal (more or less) parameters, which then generate the old-style parameters. This is (will be) now documented, as it works well enough to let people use it. This rewrite was done on another system; the history of the changes is in the separate file greedy.c-10.20-10.21,v in the StuffToKeep directory. 2001-01-29 Julian Bradfield * sysdep.h, sysdep.c: add rand_seed * version.h: 1.0.1 2001-01-28 Julian Bradfield * CHANGES: 1.0.1 * ChangeLog: for 1.0.1 * xmj.man: correct when scoring info window appears * greedy.c: more fiddles. This file has been transferred to the big pc for experimentation. 2001-01-27 Julian Bradfield * greedy.c: This does OK compared to the 1.0 release. (The same.) It's overly keen on no score hands (more than 25%), and not quite keen enough on concealed hands. Now strip out the old stuff. 2001-01-26 Julian Bradfield * logstats: helps to count Limit hands as wins... * protocol.c: add variable for use by switch code. * proto-encode-msg.pl: allow bad entries in bool fields of incoming structures; treat as TRUE, to match normal C practice. * greedy.c: This did reasonably, but is still very chow-biased. * game.h: game_flag should return a boolean. * greedy.c: checkpoint on the way to revising. This currently does very badly (-5s.d.). It no-scores more often, but gets no-chows less often, never gets concealed (why not?), and wins less often, and has a lower score when not winning. See last results of 2001-01-25. 2001-01-25 Julian Bradfield * controller.c: wasn't setting discard field of DangerousDiscard message. * logstats: Add one suit counting. 2001-01-22 Julian Bradfield * make-release: Include -lXi in static build arguments. Exit if static build fails. * gui.h: , not "errno.h" * gui-dial.c: The scoring info window needs to be a bit bigger on Windows, as the font one gets by default is bigger. * controller.c: Must allow querying game options while game is suspended (so that clients can get the timeout on startup). * client.c: include system assert.h, not local. * Makefile.in: A few changes to make it more Windows-friendly. * gui.c: Move the big font loading to be with the fixed font loading. * CHANGES: changes for 1.0 * ChangeLog: Update for release. * greedy.c: rcs_id and version.h * scoring.c: rcs_id * controller.c: rcs_id and version.h * sysdep.c, game.c, protocol.c, tiles.c, player.c, client.c: rcs_id * index.html: Update for release. * xmj.man: Add the discard timeout selector in new game. * README: note about makefile.msvc.old * FILES-src, MANIFEST: Add makefile.msvc.old * makefile.msvc.old: *** empty log message *** 2001-01-21 Julian Bradfield * gui.c: handle tile selection at mah jong a bit better * gui-dial.c: handle timeout progress bar better * gui.c: Query the game timeout option after connect. * controller.c: Set timeout game option from command line. * gui.h: add timeout selector to new game. * protocol.c, game.c, sysdep.c: suppress uninitialized variable warning * gui-dial.c, gui.c: add timeout selector to new game. Suppress uninitialized variable warning. * controller.c, scoring.c: suppress uninitialized variable warning * README-binary: file deleted ; README has everything now. * FILES-binary: Only one README file now. * README: Updated for Makefile only, and include binary info. * Makefile.in: Tidied it up and commented it. Call with Win32=1 to compile under windows. * make-release: more windows; and full release. * make-release: good idea to include the binaries... * make-release: windoze uses zip * make-release: Cope with Windows, and add -norcs option. * FILES-binary: don't include tiles-v1 in binary distribution * make-release: if no RCS directory, build from existing files * make-release: make static release only for linux * Makefile.in: fbtiles now depends on Makefile, not Imakefile * MANIFEST, FILES-src: add version.h * make-release: Change from Imakefile to Makefile.in * gui.c: rcs_id * gui.h: include version.h * gui-dial.c: version and rcs strings * version.h: *** empty log message *** * MANIFEST: Imakefile/Makefile changes * FILES-src: delete Imakefile; add Makefile and Makefile.in * Imakefile: don't use imake; it's just too unlikely to be correctly installed. * Makefile.in: *** empty log message *** * make-release: use the make-release of the appropriate release * MANIFEST, FILES-src: add makedep * makedep: *** empty log message *** * sysdep.c: make warn() add terminators intelligently * controller.c, game.c: ANSIfication 2001-01-20 Julian Bradfield * sysdep.c: Numerous changes to accommodate Windows: warn adds appropriate terminator. headers included appropriately. Socket routines redefined: use a type SOCKET, which in Windows is provided, and in Unix typedef'd to int; the error code is INVALID_SOCKET, not -1; the routines can be given functions to use to transform sockets, and to read/write/close the results (used by the gtk code); Unix socket domain ifdef'd out for Windows; get_line returns error if any read fails; new routine to start background jobs; Windows emulation for some Unix library routines. * README: Change Windows stuff * xmj.man: change --randomseats to --random-seats. Change quick start to use xmj menu. * controller.c: type tidies; one or two leftovers of restructuring fixed. Change --randomseats to --random-seats. Allow - to mean stdout for logfile. * client.c: use sysdep's close_socket to close. * greedy.c: type and brace tidies * gui.c: clear wall in appropriate places. load two fonts and use appropriately. type tidies. In Windows, need to intercept sockets and turn them into glib channels, and tell the socket routines to use appropriate functions to access them. display message while game suspended. use sysdep's start_background_program instead of doing it in Unix. use strerror instead of sys_errlist. adjust_wall_loose() always returns a valid pointer (or alternatively all uses of it would have to check; former was easier). * gui-dial.c: Some type tidies. Increased pbar_timeout interval to 50 ms. Display a message while a game is suspended. Fix bug in New Game dialog: wasn't matching button to sensitivity of options. Load the two fonts (fixed and big) in main routine, for use elsewhere. Don't show Unix options in connect window when on Windows. * gui.h: added global variables for the two fonts used * sysdep.h: Numerous changes to accommodate Windows: warn adds appropriate terminator. headers included appropriately. Socket routines redefined: use a type SOCKET, which in Windows is provided, and in Unix typedef'd to int; the error code is INVALID_SOCKET, not -1; the routines can be given functions to use to transform sockets, and to read/write/close the results (used by the gtk code); Unix socket domain ifdef'd out for Windows; get_line returns error if any read fails; new routine to start background jobs; Windows emulation for some Unix library routines. * tiles.c: couple of type tidies 2001-01-16 Julian Bradfield * Imakefile: a few tidies 2001-01-14 Julian Bradfield * gui-dial.c: ansification * controller.c: restructured the connection book-keeping so that sockets are no longer assumed to be small integers. (So things will work on Windows.) * sysdep.c, sysdep.h: checkpoint on the way to windows 2001-01-13 Julian Bradfield * protocol.h: up the protocol version to 1010 to mark the change in kong/flower rules (we now agree with the rest of the world in only allowing them after drawing from the wall). * game.c: reinstate the old rule (allow kong etc after claiming discard) for old protocol versions. 2001-01-07 Julian Bradfield * Imakefile: further warning fiddling * proto-encode-msg.pl, controller.c, scoring.c, greedy.c: ansification * tiles.c: remove superfluous ; * gui.c: ansification * tiles.c, tiles.h: change type of make_tile * Imakefile: more vicious warning options. * sysdep.h, gui.c, gui.h, player.c, controller.c, greedy.c: prototype/static fixes. * gui-dial.c: (a) prototype fixes (b) wasn't using right function to close error messages. 2001-01-06 Julian Bradfield * greedy.c: tried to make evaluation of singletons better. No effect, save to reduce scores a bit and slightly increase frequency of winning. Time to stop fiddling. 2001-01-05 Julian Bradfield * gui.c: on second thoughts, don't raise the scoring window at settlement. * gui.c: Raise the scoring info window on settlement, not on first score. (This apparently makes it more friendly for Windows users.) * gui-dial.c: showraise also does a gtk_window_show to force uniconification. * gui-dial.c: Fix the progress bar timeout; stops the Mah-Jong claim window suddenly disappearing! * greedy.c: This is as 10.9, but only considers changing strategy after drawing from the wall. It gives a semi-significant improvement (1.8sd), mainly by winning more frequently. It seems to get the concealed hand less often. 2001-01-04 Julian Bradfield * controller.c: stupid bug in score_hand meant that cannon detection was failing. (was changing s, but left p over from previous s). * greedy.c: this has the supposedly better principle of working out the discard before claiming. It didn't work well, so tried hiking expc penalty, and explicitly banning discard of claimed tile. After this, go back to previous, and try the simple fixes. 2001-01-03 Julian Bradfield * greedy.c: (a) restructured code, preparatory to changes. (b) Put a small exposure penalty in the fast strategy. * xmj.man: Document the new Connect menu. * runtest: add --no-special-scores to server options. * xmj.man: Add the mj-server no-special-scores option to suppress flower/season scores. * controller.c: Add the no-special-scores option to suppress flower/season scores. * scoring.h, scoring.c: Add the no_special_scores variable to suppress flower/season scores. * greedy.c: change concealed partchow to match others. Seems to make no difference. 2001-01-02 Julian Bradfield * runtest: put evals into the player execution commands so that redirections can be included. 2001-01-01 Julian Bradfield * greedy.c: (a) Only switch to fast when opponent displays four sets (not three). Not very significant (1sd), but seems harmless. (b) remove old stuff. * greedy.c: Various fixes to response logic so that it doesn't try to do things twice. * game.c: Amazingly enough, game_handle_cmsg was in most cases not actually returning the correct affected id. * greedy.c: Don't claim mah jong on a concealed hand if we still have a reasonable chance of picking up the tile. (Need to take account of the number of tiles left to make this more accurate.) This does about 1.8 sd better than 10.4. * greedy.c: correct erroneous comments 2000-12-31 Julian Bradfield * gui.h, gui.c, gui-dial.c: Basic new game panel now working. Now tidy it, capturing children diagnostics etc. * sysdep.h, sysdep.c: connect_to_host: mark the connection close on exec. * gui-dial.c: checkpoint: added fields etc to open panel for starting game. 2000-12-30 Julian Bradfield * greedy.c: remove the easy switch back to default; treat as others. 2000-12-29 Julian Bradfield * runtest: put date in results file * game.c: set discard_serial to one, as the controller did. * controller.c: controller was doing things that should have been done by processing a newhand message. There's still more to do on this. 2000-12-27 Julian Bradfield * game.c: Set info.whence to FromWall at start of play. * greedy.c: trying to avoid scoring pairs in chow hands * greedy.c: make kong declaring rules match M: only after drawing from wall. * greedy.c: trying to avoid scoring pairs in chow hands 2000-12-26 Julian Bradfield * controller.c: State saving is broken, so don't try to do it. * xmj.man, controller.c: Add --exit-on-disconnect server option. * gui-dial.c: add an "about" window. * xmj.man, scoring.c: Cut bouquets down to one double. 2000-12-25 Julian Bradfield * ChangeLog: update; strip out stuff before the pre-release. * game.c: CMsgSpecialSetMsg wasn't adding the discard when checking for existence of the set! * gui-dial.c: Use new tilesetbox_init function. * gui.c: Several changes to fix robbing kong bugs: (1) use new tilesetbox_init function to create all widgets in initialization, rather than on the fly; associated changes to tilesetbox_set; (2) playerdisp_update_exposed must be able to handle a closed kong being robbed; (3) when forming a special set, the destination tile is found in the concealed tiles, not the exposed. * gui.h: Stripped func out of tilesetbox, and added tilesetbox_init function. * game.c: allow closed kong robbing for thirteen wonders * xmj.man: clarifications * xmj.man: make kong declaring rules match M: only after drawing from wall. * game.c: Make rules match the rest of the world: specials and kongs can only be declared after drawing a tile from the wall, not after taking a discard. 2000-12-23 Julian Bradfield * controller.c: The send_infotiles function was sending the message to all players depending on the option of the affected player, instead of depending on their own option setting. 2000-12-21 Julian Bradfield * xmj.man: fix some slips in option argument typog * gui.c: showwall should start undetermined 2000-12-20 Julian Bradfield * gui-dial.c: fix the radio group functionality in the concealed buttons. This is not concurrency-safe: it relies on induced callbacks being executed synchronously. I should fix this. 2000-12-17 Julian Bradfield * gui.c: clear discards on game over 2000-12-14 Julian Bradfield * make-release: replace - by _ in symname * index.html: update for beta release 2000-12-07 Julian Bradfield * stats, runtest, logstats: increase version to 10 for post release * xmj.man, tiles.h, tiles.c, sysdep.h, sysdep.c, scoring.h, scoring.c, protocol.h, protocol.c, proto-encode-msg.pl, proto-decode-msg.pl, player.h, player.c, mj-server.man, mj-player.man, maketxt, makefallbacktiles, make-release, make-enums.pl, gui.h, gui.c, gui-dial.c, greedy.c, game.h, game.c, controller.h, controller.c, client.h, client.c, README-binary, README, MANIFEST, LICENCE, Imakefile, FILES-src, FILES-binary: 1.0 pre-release [ removed older stuff ] mj-1.17-src/icon.ico0000444006717300001440000000427607262154624012770 0ustar jcbusers ¨( @€ÿÿÿþêýü~ú÷ûþêzöñÿûfæü*çºíkåõjù25ÞýÅxéöÕCÔIèó(á-öÅA~FN()* ''     & %#"$ ""       !                mj-1.17-src/use.txt0000444006717300001440000012513615002771312012666 0ustar jcbusers xmj, mj-server, mj-player - programs for playing Mah-Jong SYNOPSIS -------- xmj [ --id IDNUMBER] [ --server ADDRESS] [ --name PLAYERNAME] [ --connect ] [ --show-wall | --no-show-wall ] [ --size N ] [ --animate | --no-animate] [ --tileset DIRECTORY] [ --tileset-path DIRECTORY-PATH] [ --dialogs-popup | --dialogs-below | --dialogs-central ] [ --use-system-gtkrc | --no-use-system-gtkrc ] [ --gtk2-rcfile FILE] [ --echo-server ] [ --pass-stdin ] [ --monitor ] mj-server [ --server ADDRESS] [ --timeout SECONDS] [ --pause DECISECONDS] [ --random-seats | --id-order-seats ] [ --disconnect-penalties N1,N2,N3] [ --end-on-disconnect ] [ --exit-on-disconnect ] [ --save-on-exit ] [ --option-file FILE] [ --load-game FILE] [ --no-id-required ] [ --no-manager ] [ --auth-basic ID:PASSWORD]*4 [ --debug ] [ --logfile FILE] [ --no-special-scores ] [ --seed N ] [ --wallfile FILE] [ --hand-history ] [ --nohist ] mj-player [ --id IDNUMBER] [ --name PLAYERNAME] [ --server ADDRESS] [ --password PASSWORD] [STRATEGY OPTIONS] DESCRIPTION ----------- A set of three programs to play Mah-Jong on Unix systems, against people or programs, over the Internet. mj-server is the program that handles communications and control of the game; the rules and scoring are enforced there. Players, human or computer, connect to a server via the network. mj-player is a computer player. At present, it is fairly simplistic, having only offensive tactics with no knowledge of defensive play. xmj is the X client for human players. QUICK START ----------- If you don't want to read this long document: to start a game against three computer players, start xmj , select "New local game..." from the "Game" menu, and click "Start Game". (Wait about ten seconds for everything to start up.) OPTIONS ------- All Programs ------------ --server ADDRESS specifies the network address to listen on (for mj-server ) or to connect to (for mj-player and xmj ). If ADDRESS contains a colon, it specifies an Internet socket, and should have the form HOST:PORT . If ADDRESS does not contain a colon, it is interpreted as a Unix file name and a Unix socket is used. The default value for ADDRESS is localhost:5000 . ADDRESS can also be set in a dialog box in xmj . xmj and mj-player ----------------- --id IDNUMBER The server assigns a unique integer ID (which is currently just 1 to 4 in order of connection) to each player. This ID should be quoted when reconnecting to a game in progress (after, for example, losing a network connection or accidentally killing xmj ). The default ID is 0, which denotes no pre-assigned ID. --name NAME Players can give themselves names which will be used by client programs. This option specifies the name. For xmj , the default is the value of the environment variable LOGNAME, or failing that the username of the logged in user. For mj-player , the default is "Robot(PID)" where PID is the process id. xmj --- --connect By default, xmj does not automatically connect to a server, but waits for the user to connect via a menu. If this option is specified, xmj immediately connects. --show-wall --no-show-wall Tells xmj (not) to display the wall. By default, the wall is shown only if running on a big enough screen. This option is also controllable via the Display Options preference panel. --size NUMBER This option adjusts the size of the main window. It should be thought of as the length of a tile rack, measured in tiles. The default, and the largest value accepted, is 19, or 18 if on an 800x600 display. The smallest usable value is 14. This option is also controllable via the Display Options preference panel. If the --show-wall option is given, a --size smaller than 19 will have no effect. --animate --no-animate This option switches on (off) some animation. Not all tile movements are animated: only those that involve moving tiles to or from a hand from outside. This option is also controllable via the Display Options preference panel. --tileset DIRECTORY xmj needs pixmaps to display the tiles and the tong box. This option tells it which directory to find them in. The default is set at compilation time; the default default is to use the compiled-in tiles. --tileset-path DIRECTORY-PATH This gives a colon-separated (or semicolon-separated under Microsoft Windows) list of directories in which to look for the directory named by the --tileset option. --dialogs-popup By default, most of the dialog boxes for player actions are part of the main window. If this option is used, they will instead appear as separate transient windows. --dialogs-below By default, dialog boxes appear in the centre of the table. If this option is given, dialogs (apart from some popups) are positioned below the table area. Please let me know which style you prefer! --dialogs-central The default: dialog boxes appear in the middle of the table. These options are also controllable via the Display Options preference panel. --gtk2-rcfile FILE If xmj is compiled with GTK+2, this option specifies a GTK rc file to be used instead of the program's compiled-in style file. This may be used to change the appearance of the program. See description under the Display Options... panel for more details. The FILE should be an absolute filename; if it is relative, it will be sought in the current directory (Unix) or the program directory (Windows). This option is also controllable via the Display Options preference panel. --use-system-gtkrc --no-use-system-gtkrc When xmj is compiled with GTK+2, by default it ignores the system provided settings, to ensure a consistent behaviour across systems. If you wish it to use your system settings, set this option. This option is also controllable via the Display Options preference panel. --echo-server If this option is given, xmj will echo to stdout all the protocol messages received from the server. This option is for use in debugging. --pass-stdin If this option is given, xmj will send any text given on stdin to the server. This option is for use in debugging. --monitor If this option is given, xmj will send requests to the server only in direct response to user actions; it will take no action itself (and hence all auto-declaring and playing is also disabled). This option is for use in debugging. mj-server --------- --timeout SECONDS When a discard is made, there is a limit on the time players have to claim it. This option sets the timeout; a value of zero disables it. The default is 15 seconds. This value can also be set via a GameOption request from a player. --pause DECISECONDS This will make the server enforce a delay of DECISECONDS/10 seconds between each action in the game; the purpose is to slow programmed players down to human speed (or, in a teaching situation, to slow the game even more). The current server considers that 50 (i.e. 5 seconds) is the maximum reasonable value for this option. The option can also be requested by players, via a PlayerOption protocol request. --random-seats By default, players are seated in order of connection to the server. This option seats them randomly. It will become the default later. --id-order-seats This option causes the players to be seated in numerical order of their ids. It is used by the xmj program to make the New local game.. work as expected. --disconnect-penalties N1,N2,N3 This specifies the penalties applied by the following option for players who disconnect before the end of a game. N1 is the penalty for disconnecting in the middle of a hand; N2 at the end of a hand but in the middle of a round; N3 at the end of a round (other than end of game). They all default to 0 if not specified. --end-on-disconnect If this option is given, a disconnection by one player will gracefully terminate the game. Mid-hand, the hand is declared a wash-out; after Mah-Jong has been declared, then if a losing player disconnects, their tiles are shown, the hand is scored, and then the game ends; if a winning player disconnects, the hand is a wash-out. The disconnecting player may be assigned a penalty, according to the --disconnect-penalties option, which will be included in the scores printed out by the server. (The penalties will not be visible to the other players.) --exit-on-disconnect If this option is given, the server will quit if any player disconnects, rather than waiting indefinitely for reconnection. --save-on-exit If this option is given, the server will save the state of the game if it quits as a result of a player disconnecting. (It will not save the state if it quits as the result of an internal error.) --option-file FILE This names a file of protocol commands which will be applied to every game when it starts. Its main purpose is to set non-default game options, via the GameOption protocol message (note that this is a CMsg, not a PMsg). However, users will normally set options and preferences via the xmj control panel, not by this means. --load-game FILE This names a file containing a saved game (as a suitable sequence of protocol commands). The server will load the game; clients connecting will be treated as if they had disconnected and rejoined the game. --no-id-required In the most common case of resuming a saved game, namely one human playing against three robots, the robots will not have the same names or ids as the robots in the original game. This option tells the server that if it cannot match a reconnecting player by id or name, it should anyway match it to one of the previously disconnected players. (In this case, the human normally connects first with the same name, so is correctly matched.) --no-manager Usually, the first player to connect becomes the game manager, and can change all the game settings. If this option is given, no player will be allowed to change the game settings. --auth-basic ID:PASSWORD This provides basic (insecure, since the password is transmitted in plaintext) authorization: the player with id ID must give the specified password to connect. Note that if this argument is given, it must be given four times, once for each authorized player - any player id not mentioned will not be allowed to connect. A player may be allowed to connect without a password by making PASSWORD empty. --debug This enables various debugging features. In particular, it enables protocol commands that allow one to change the tiles in a hand... --logfile FILE The server will write a complete record of the game to FILE; this will be quite large, and is only useful for automatic comparison of different computer players. --no-special-scores This option suppresses the scoring of points and doubles for flowers and seasons. It is primarily intended for running tests of different players; for human use, a game option will be provided to eliminate the specials altogether. --seed N This option specifies the seed for the random number functions. Used for repeatable tests. --wallfile FILE This names a file containing space separated tile codes giving the wall; used for repeatable tests. (This is a testing option; it is not robust.) --hand-history This is an option to facilitate certain automatic analyses; if set, a history of each hand is dumped to the file hand-NN.mjs . --nohist Another option only used in automatic comparison: this saves some CPU time by disabling the book-keeping required to allow players to disconnect and reconnect. mj-player --------- --password PASSWORD sets the password if basic authorization is in use. STRATEGY OPTIONS The player has some options which can be used to change its "personality". The meanings are rather approximate, since they actually change parameters which are used in a rather complex way, but the idea is right. These options, each of which takes a floating point value in the given range, are: --randomness 0.0 .. 1.0 The most generally useful option: adds random noise to the player's choice calculations, thus reducing its skill level. Values above 1.0 are possible, but don't seem to make matters worse. The dumbed-down players are still not stupid. --chowness -1.0 .. 1.0 This affects how much the player likes chows: at 1.0, it will go all out for the chicken hand, at -1.0 it will never chow. The default is 0.0. --hiddenness 0.0 .. 1.0 Increasing this makes the player reluctant to make exposed sets. At 1.0, it will never claim (except possibly to go mah-jong). The default is 0.0. --majorness 0.0 .. 1.0 Increasing this biases the player towards collecting major tiles. At 1.0, it will discard all minor tiles, if possible. The default is 0.0. --suitness 0.0 .. 1.0 Increasing this makes the player try to go for one-suit hands. The default is 0.0 In practice, the --majorness option seems not to be very useful, but the other options change the personality without completely destroying the playing ability. In fact, all these options take a comma-separated list of values, which allows the specifications of a set of strategies, which the player will switch between. In this case, the --hysteresis HHH option specifies how much better a strategy should be to switch to it. However, use of this option, and multiple strategies, is probably only useful if you first read the code to see how it works. USING THE XMJ PROGRAM --------------------- The main window contains a menu-bar and a table area; the table is in a tasteful shade of dark green. The table displays a stylized version of the game: stylized in that there is no jazzy graphics or perspective, and the tiles are not intended to be pictures of real objects, and so on. Otherwise, the layout is as one would expect of a real game. However, the wall may or may not be displayed, depending on option settings and screen size. (See above.) Specifically, the four players are arranged around the four edges of the table, with "us" at the bottom. For each player, the concealed tiles are displayed nearest the edge of the table; our own tiles are visible, the other players' tiles are face-down. The rightmost concealed tile of other players is highlighted in red when it is their turn to discard. In front of the concealed tiles are (to the player's left) any declared sets, and (to the player's right) flowers and seasons, and the tong box if the player is East. The tong box displays the wind of the round in a white circle. If necessary, the flowers and seasons will overflow into the concealed row. The discards are displayed face-up in the middle of the board: they are laid down in order by each player, in the natural orientation. TODO: add options to display discards randomly, or face-down. If animation (see --animate option) is not being used, then the most recent discard will be highlighted in red. The name of a face-up tile can be displayed by right-clicking in the tile. Alternatively, the Tiletips display option can be set, in which case the name of a tile is displayed whenever the mouse enters it. Our tiles are displayed in sorted order, which happens to be Bamboos (1-9), Characters (1-9), Circles (1-9), Winds (ESWN), Dragons (RWG), Flowers, Seasons. We can also arrange the tiles ourselves - see the "Sort tiles in hand" display preference described below. Actions are generally carried out by clicking a button in a dialog box that appears in the middle of the board. For many actions, a tile must be selected. A tile is selected or unselected by single-clicking it; when selected, it appears as a depressed button. The program will generally pre-select a sensible tile: specifically: during the initial declaration of special tiles, the rightmost special is selected; after we draw a tile from the wall, the drawn tile is selected; when declaring concealed sets after going Mah Jong, the first undeclared tile is selected. To describe the possible actions, let us run through the course of a game. First select "New local game..." from the "Game" menu. A panel will appear. The default options are to play a game against the computer, so click "Start Game". After a second or two, a game will start. (NOTE: this assumes correct installation. If this fails, start a server and players manually, and use the "Join server..." menu item.) The first thing that happens is a dialog box "Ready to start next hand". The server will not start playing a hand until all players have indicated their willingness to continue play. Next, the tiles are dealt. Then each player in turn is expected to declare flowers and seasons. When it is our turn, a dialog will appear with the following buttons: Declare declare the selected flower or season. (Note: the program auto-selects the rightmost special tile.) If no tile is selected, this finishes declarations. This button will not appear if the game is being played without flowers and seasons. Kong If we have a concealed kong, we can declare it now with this button. Finish Finish declaring specials and kongs. When all players have finished declaring specials and kongs, a dialog box appears, asking (on East's behalf) permission to continue. During play, when we draw a tile from the wall, it will be auto-selected. We may also of course select a different tile. A dialog will appear giving us the following possibilities: Discard discard the selected tile. This button also serves to declare a flower or season, and the label changes to "Declare" when one is selected. &Calling discard the selected tile and declare a calling hand. This button is only shown when calling is allowed (by default, only Original Call is allowed). Kong declare a concealed kong of the selected tile, or add the selected tile to an exposed pung, as appropriate. Note: In most rules, a concealed kong can only be declared (or a tile added to an existing pung) immediately after drawing from the wall, but not after claiming somebody else's discard. Up to and including version 1.10, the server enforced this rule strictly. As from version 1.11, it allows a tile to be added to a pung that you have just claimed: in real life, this corresponds to correcting your Pung! claim to a Kong! claim, which is allowed by all rules. (Obscure note: if you are playing the KongHas3Types option, the resulting kong will be counted as annexed, instead of the exposed kong that would have resulted from a genuine change of claim. This is a bug, but not worth the trouble of fixing.) Mah Jong! declare Mah Jong! (no selection needed) If the wall is not being shown, the dialog will note the number of tiles left in the live wall. A tile can also be discarded simply by double-clicking it. When another player discards, a dialog appears to allow us to claim it. If the dialogs are in the middle of the table, the dialog displays the tile in a position and orientation to indicate the player who discarded; if the dialogs are at the bottom, this is not done, to save space. In any case the dialog displays the name of the tile, and buttons for the possible claims. If the wall is not being shown, the dialog will note the number of tiles left in the live wall. There is also a `progress bar' which shows how time is running out. The buttons use one variant of traditional English terminology, viz: No claim we don't claim this tile. If there is no timeout in operation, it is necessary to click this to indicate a "pass", and in any case it is desirable to speed up play. Chow claim for a sequence. If our claim is successful and there is more than one possible sequence to be made, a dialog will appear asking us to specify which one. Pung claim for a triplet. Kong claim for quadruplet. Mah Jong! claim for Mah Jong. If the claim succeeds, a dialog box will appear asking whether we want the tile for "Eyes", "Chow", "Pung", or a "Special Hand" (such as Thirteen Unique Wonders). (The term "Eyes" is used instead of "Pair" so that in the keyboard accelerators, E can be used, leaving P for "Pung".) When a player (including us) claims, the word "Chow!" etc. will appear (in big letters on a yellow background, by default) for a couple of seconds above the player's tiles. When all players have claimed, or timed out, the successful claim is implemented; no additional announcement is made of this. If a player adds a tile to an exposed pung, and that tile would give us Mah Jong, then a dialog box pops up to ask whether we wish to rob the kong. After somebody goes Mah Jong, we are asked to declare our concealed sets. A dialog appears with buttons for "Eyes", "Chow", "Pung". To declare a set, select a tile, which must be the first tile in the set for a chow, and click the appropriate button. (If we are going Mah Jong, the first undeclared tile is auto-selected.) When finished, click "Finished" to reveal the remaining tiles to the other players. If we are the winner, there will be a button for "Special Hand": this is used to declare hands of non-standard shape, such as Thirteen Unique Wonders. (Note: the Seven Pairs hand, if in use, should be declared by means of the "Eyes" button, not the "Special Hand" button.) At this point, a new top-level window appears to display the scoring information. The scoring is done entirely by the server, not by the players; the server sends a text description of the score calculation, and this is displayed for each player in the Scoring window. The information in the Scoring window remains there until the next hand is scored; the window can be brought up at any time via the "Show" menu. Finally, the "continue with next hand" dialog appears. The hand just completed will remain visible on the table until the next hand starts. Keyboard Accelerators There are keyboard accelerators for all the actions in the course of play. For selecting tiles, the Left and Right arrow keys can be used to move the selection left or right along the row of tiles. In all dialogs, Space or Return will activate the shadowed button, which is usually the commonest choice. Each button can also be activated by typing the underlined letter. (In the Windows GTK1 build, use l (ell) and r instead of Left and Right. The button accelerators do not work, for reasons unknown to me.) The menus are also accessible via accelerators. To open a menu, press Meta-X (Alt-X on Windows), where X is the underlined letter in the menu name. (Meta-X is often (confusingly) Alt-X on Linux systems.) Then each entry has an underlined letter which if pressed will activate it. An additional top-level window showing the state of the game can be obtained by selecting "Game info" from the "Show" menu. A record of the scores so far in the game can be found by selecting "Scoring history" from the "Show" menu. The players are listed in board order, with the original east marked by @. In each hand, the player's hand score appears in parentheses, and then their gain or loss for the hand, beneath which is the running total There is also a facility for sending text messages to the other players. Select "Messages" from the "Show" menu, and a window will appear: in the top is a display of all messages sent, and below is a single line in which you can enter your message. It will be sent when you hit Return. The message window pops up automatically whenever a message is received, unless prevented by a display preference. If the "Display status and messages in main window" display option is set, then this window will instead appear in the main window, above the table. In that case, there is a checkbox "Keep cursor here" next to the message entry line. Checking this box will ensure that the keyboard focus stays in the message entry field, even when you click on buttons in the game. (Consequently, you will be unable to use keyboard accelerators while this option is checked.) Starting games and re-connecting -------------------------------- The "Game" menu has the "New local game..." item to start a new game on your local computer, and the "Join server..." item to connect to an existing game. The dialogs for both these have the following entries: Checkboxes for Internet/Unix server These specify whether the server is listening on an Internet socket or a Unix socket. If an Internet (TCP) socket, the host name ("Join Game..." only) and port number should be entered in the appropriate boxes; if a Unix socket, the file name of the socket may be entered, or if it is left blank, a temporary file will be used. These fields are remembered from game to game. "Player ID" and "Name" fields The "Player ID" should be left at 0, unless reconnecting to an existing game, in which case it should be the ID assigned by the server on first connecting to that game. The "Name" field can be anything. When reconnecting to an existing game, if the ID is given as 0, the server will try to use the "Name" to identify the player. (This may not be true in future.) The "Name" field is remembered from game to game. The "Join server..." dialog then simply has a "Connect" button to establish the connection. The "New local game..." has the following fields: For each of three further players, A checkbox to say whether to start a computer player. (Some of) these should be unchecked if you wish other humans to join the games. If checked, there is a text entry to set the players' names, and a text entry field in which options can be given to the players; the latter should only be used if you understand the options! The options are remembered from game to game. An "allow disconnection" checkbox If this is checked, the server that is started will continue to run even if players disconnect. If it is not checked, the server will quit if any player disconnects. If you are playing one against the computer, this should generally be left unchecked, in order to avoid server processes accidentally being left lying around. If playing against people, it should be checked, to allow players to go away, or to guard against network outages. As "save game state on exit" checkbox If this is checked, the server will save the game state (see below on on saving and resuming games) when a player disconnects and causes it to quit. A "seat players randomly" checkbox If this is left unchecked, players will be initially seated as East, South, West, North in order of connection. (We always connect first.) If it is checked, the seating will be random. A "robot difficulty" numeric entry field values from 0 to 10 making the robots more skilful. Actually, lower values effectively make the robots a bit tired, so they make mistakes. Beginners probably want 0 to stand a chance of winning sometimes. A numeric entry field to specify the time limit for claiming discards. If set to 0, there will be no time limit. A button to start the game Note that it takes a few seconds to start a game, during which time the dialog stays up with the button pressed. (TODO: fix this!) Saving and resuming games ------------------------- At any time during the play of a game, you can choose the "Save" entry from the "Game" menu. This causes the server to save the current state of the game in a file. The file will be named game- DATE .mjs by default; if a name has previously been specified, or if the game was resumed from a file, that name will be used. To specify a name, use the "Save as..." entry in the "Game" menu. Note that for security, directories cannot be specified (except by resuming a game), so the file will be created in the working directory of the server. To resume a saved game, use the "Resume game..." entry from the "Game" menu. This is just like the "New local game..." panel, but it has a box to specify the file containing the saved game. You can either type the file name into the box, or click the "Browse..." button to get a file chooser dialog. (File chooser not available on Windows GTK1 build.) Setting display and game options -------------------------------- The "Options" menu of xmj brings up panels to set various options related to the display and to the game rules. Most of these options can be stored in the preferences file, which is .xmjrc in your home directory on Unix, and xmj.ini in your home (whatever that means) directory on Microsoft Windows. Display Options --------------- This panel controls options related to the local display. At the bottom are three buttons: "Save & Apply" applies changes and saves them in the preferences file for future sessions; "Apply (no save)" applies any changes, but does not save them; "Cancel" ignores changes. Note that many display options can also be controlled by command-line arguments; if an option is specified both in the preferences file and on the command line, the command line takes priority. Position of action dialogs. This determines where the dialogs for user actions in the game are popped up; see the description of the --dialogs-central etc. options above. This option is stored in the preferences file as Display DialogPosition POSN where POSN is one of "central", "below" or "popup". Animation determines whether tile movements are animated (see the --animate option above). This option is stored in the preferences file as Display Animate BOOL where BOOL is "0" or "1". Display status and messages in main window puts the game status and message (chat) windows in the main window, above the table, instead of having separate popup windows. This option is stored in the preferences file as Display InfoInMain BOOL where BOOL is "0" or "1". Don't popup scoring/message windows will prevent the automatic popup of the scoring window at the end of a hand, the message window on the arrival of a message, and the game status window at the end of the game. This option is stored in the preferences file as Display NoPopups BOOL where BOOL is "0" or "1". Tiletips always shown means that the name of a tile is displayed whenever the mouse enters it, and the name of the selected tile is always shown. (Otherwise, right-click to display the name.) This option is stored in the preferences file as Display Tiletips BOOL where BOOL is "0" or "1". Rotate player info text determines whether the player information labels on the main board are rotated vertically for the left and right players, or kept horizontal. The default is to rotate them. This option is stored in the preferences file as Display RotateLabels BOOL where BOOL is "0" or "1". Show when players are thinking When this is on, "..." will be displayed in other player's claim alerts while they are "thinking", that is, have not yet claimed or passed. This option requires a recent server. The default is off. This option is stored in the preferences file as Display ThinkingClaim BOOL where BOOL is "0" or "1". Alert on possible mah-jong determines whether to pop up an alert when a discard or drawn tile makes mah-jong possible. Beware that only the server knows the full rules, so this is not infallible. The default is off. This option is stored in the preferences file as Display AlertMahjong BOOL where BOOL is "0" or "1". Display size This drop-down list specifies the size of the display. The size should be thought of as the length of a tile rack. This is only relevant if the wall is not being displayed. Values range from 14 to 19; if "(auto)" (the default) is specified, the client tries to choose a size as big as will fit in the display. This option can also be specified by the command line --size argument. This option is stored in the preferences file as Display Size N Show the wall "always" is equivalent to the --show-wall option; "never" is equivalent to the --no-show-wall option; and "when room" is the default. This option is stored in the preferences file as Display ShowWall WHEN where WHEN is one of "always", "when-room" or "never". Sort tiles in hand By default, the program maintains your own tiles in sorted order. If you prefer to leave them unsorted (which is often recommended in real life, to avoid giving information to your opponents), or to arrange them yourself, you can set this option to "never", or to "on deal" if you want them to be sorted at the beginning, but then left alone. To rearrange tiles, use the Shift-Left and Shift-Right (i.e. the left and right arrow keys while holding Shift) - these move the selected tile left or right in your hand. (In the Windows GTK1 build, use L (Shift-l) and R (Shift-r) instead.) On GTK2 builds, you can also drag a tile to its new position with the mouse. This option is stored in the preferences file as Display SortTiles WHEN where WHEN is one of "always", "deal" or "never". Iconify all windows with main If this option is set (the default), then when the main xmj window is iconified, (almost) all other open windows such as dialogs will also be iconified; when the main window is uniconified, the other windows will also be uniconified. If it is not set, all windows are independent of one another. This option is stored in the preferences file as Display IconifyDialogs BOOL This option is not currently supported under Microsoft Windows. Tileset this is the tile pixmap directory, also given by the --tileset option. This option is stored in the preferences file as Display Tileset DIRNAME Tileset Path this is the search path for tileset directories, also given by the --tileset-path option. This option is stored in the preferences file as Display TilesetPath SEARCH-PATH Main font selection... This button brings up a font selection dialog to choose the font used in buttons, menus, etc. in the client. This option is stored in the preferences file as Display MainFont FONT-NAME where FONT-NAME is a font name, which may be an X LFD in the Unix GTK+1 version, or a Pango font name in the Windows and Unix GTK+2 versions. Text font selection... This button brings up a font selection dialog to choose the font used in text display (such as scoring info and chat) in the client. This option is stored in the preferences file as Display TextFont FONT-NAME Table colour selection... Unaccountably, not everybody likes my choice of dark green for the table background. This button brings up a colour selection box to allow the table colour to be changed.This option is stored in the preferences file as Display TableColour COL where COL is a GTK colour specification. The format depends on whether xmj is built with GTK+1 - in which case it is an X color of the form rgb:RRRR/GGGG/BBBB - or GTK+2 - in which case it is a GTK2 color of the form #RRRRGGGGBBBB. GTK+2 programs will convert an old GTK1 specification. Gtk2 Rcfile: In the GTK+2 build, xmj by default ignores completely the system and user settings for look and feel, and uses its own built in settings. These settings use the Clearlooks theme, if it is available, to provide a simple but clean look with slightly rounded tiles; and fall back to a plain theme, as compact as possible with the standard engine. If you wish, you can use this option to specify the name of a GTK rcfile which will be read instead of the built in settings. A minimal set of settings will be read before your file is read. Such a file can specify many details of the appearance, provided that you know how to write a GTK rcfile. You will need to know that xmj uses the following styles and bindings: gtk-font-name = FONTNAME can be used to change the overall font used by widgets. This will overridden by the font specified by the Main Font option, if set. style "table" is used to give the green (or whatever you set) colour to the table. All widgets that should have this style are named "table", so the appropriate binding (already set in the minimal set) is widget "*.table" style "table" style "playerlabel" is used to give the white text colour to the player status labels in the corners of the board (if shown). All widgets that should have this style are named "playerlabel", so the appropriate binding (already set in the minimal set) is widget "*.playerlabel" style "playerlabel" style "tile" is used in the default settings for all widgets named "tile", which are all tiles except the tiles in your own concealed hand. This style is not used in the minimal settings, but if set it should be bound with widget "*.tile" style "tile" style "mytile" is used in the default settings for the concealed tiles in your hand, which are active buttons. These tiles are all named "mytile". This style is not used in the minimal settings, but if set it should be bound with widget "*.mytile" style "mytile" style "claim" is used to set the yellow background and large font of the claim announcement popups. These popups are named "claim", so the appropriate binding (already set in the minimal set) is widget "*.claim" style "claim" style "text" is used to change the font for the text widgets such as message boxes and input fields. In the minimal settings, it is empty, but is defined and bound to the relevant widgets. The binding should not be changed, but the style itself can be redefined. If the Text Font option is set, this style will be redefined in order to implement it. binding "topwindow" is defined and bound to the top-level window to implement the use of the left and right arrow keys to change the selected tile. It is probably not helpful to change this. The distribution contains three example gtkrc files, called gtkrc-minimal , gtkrc-plain , and gtkrc-clearlooks , which contain the program's compiled in settings. This option is stored in the preferences files as Display Gtk2Rcfile FILE-NAME Note that if the file-name is relative, it will be interpreted relative to the current directory in Unix, or the program directory in Windows. Use system gtkrc As noted above, xmj does not normally load the system settings in the GTK+2 build. If this option is checked, it will (after the minimal settings, but before the default or user-specified settings). This option is stored in the preferences files as Display UseSystemGtkrc BOOL where BOOL is 0 or 1. Note for GTK+1 builds Under a GTK+1 build, xmj does what any other application does. This should allow the use of a .gtkrc file to change colours, using the styles and bindings given above. However, this is not a supported activity. Playing Preferences ------------------- This panel controls what actions the client may take on your behalf. The first (and currently only) section specifies when the client should declare tiles and sets for you. It has the following checkboxes: flowers and seasons if checked, will be automatically declared as soon as drawn. losing hands if this is checked, then when somebody else goes out, the client will declare your closed sets. It declares in the order pungs, pairs, chows. winning hands this is the same for when you go out. The panel has "Save & Apply", "Apply (no save)" and "Cancel" buttons, as in the display options panel. Game Option Preferences ----------------------- This panel controls preferred game options which will be sent to the server when a game starts. Preferences will only be applied if we are the game manager, or the game has no manager. (Normally, the first human player to connect to the server becomes the game manager.) For details of options and their meanings, see the Game Options section in the rules. The panel has two action buttons, "Save Changes" and "Cancel", with the obvious meanings. Note if a game is in progress, changed preferences are NOT applied to it; however, there is a button in the Current Game Options panel to apply preferences. The main body of the panel is a scrollable window listing all the known options. If no preference is stored for the FooBar option, then there is an "Add pref" button next to a description of the FooBar option. If this button is clicked, an entry for setting the option appears. The format of this entry depends on the type of the option (see the Game Options section of the rules for details of types): Boolean (on/off) options have a checkbox. Integer options have a spinbutton for numerical entry: the value can be typed in, or the up and down arrows can be used to change it Score options have radio buttons for selecting Limit, Half-Limit, or other; for other, the number of doubles and/or points is entered with spinbuttons. (Note: the underlying protocol allows percentages (possibly more than 100%) of limits to be specified for scores; however, the current graphical interfaces allow only limits or half-limits. Even half-limits are pretty strange, but some bizarre sets of rules, such as those of the British Mah-Jong Association (which plays a weird American/Western/Chinese mix), allow other fractions of limits.) String options have a simple text entry field. All option entries have a "Reset" button which returns the entry to its previous state. A preference is removed by clicking the "Remove pref" button. Current Game Options -------------------- When there is a connected game, this panel allows its game options to be modified (if we have permission to do so). The three action buttons are "Apply changes", which applies the panel's settings to the current game; "Apply prefs", which applies our preferences (as described above) to the current game; and "Cancel". The body of the panel contains entries for all the options of the current game, in the same format as the preferences panel (see above). UPDATES ------- The latest release of the Unix Mah-Jong programs should be available at http://mahjong.julianbradfield.org/ mj-1.17-src/version.h0000644006717300001440000000072115002771311013160 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/version.h.in,v 12.0 2009/06/28 20:43:31 jcb Rel $ * version.h * Provide a version variable and defines. */ /* a string for the version number: the version is substituted in by the make-release script */ #define VERSION "1.17" #define XSTRINGIFY(FOO) #FOO #define STRINGIFY(FOO) XSTRINGIFY(FOO) #include "protocol.h" static const char version[] UNUSED = VERSION " (protocol version " STRINGIFY(PROTOCOL_VERSION) ")" ; mj-1.17-src/gtkrc-clearlooks0000444006717300001440000000173715002771311014521 0ustar jcbusersgtk-theme-name = "Clearlooks" gtk-font-name = "Verdana Condensed 12px" style "table" { bg[NORMAL] = "darkgreen" } style "playerlabel" { fg[NORMAL] = "white" } style "tile" { bg[NORMAL] = "white" xthickness = 0 ythickness = 0 } style "mytile" { bg[NORMAL] = "white" bg[PRELIGHT] = "yellow" bg[ACTIVE] = "magenta" xthickness = 1 ythickness = 1 } style "claim" { bg[NORMAL] = "yellow" font_name = "Sans Bold 20px" } binding "topwindow" { bind "Left" { "selectleft"() } bind "Right" { "selectright"() } bind "Left" { "moveleft"() } bind "Right" { "moveright"() } } style "text" { font_name = "Courier New 16px" } widget "*.table" style "table" widget "*.tile" style "tile" widget "*.mytile" style "mytile" widget "*.claim" style "claim" widget "topwindow" binding "topwindow" widget "*.GtkTextView*" style "text" widget "*.GtkEntry*" style "text" widget "*.playerlabel" style "playerlabel" mj-1.17-src/player.c0000444006717300001440000007375715002771311013003 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/player.c,v 12.2 2025/04/25 20:09:25 jcb Exp $ * player.c * Implements functions on players. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #define PLAYER_C /* so that PlayerP is not const */ #include "player.h" #include "sysdep.h" static const char rcs_id[] UNUSED = "$Header: /home/jcb/MahJong/newmj/RCS/player.c,v 12.2 2025/04/25 20:09:25 jcb Exp $"; /* At present, this function is not exported. If it becomes exported, the name should perhaps be made better. player_has_mah_jong: return true if the player's hand in its current state of organization is a complete mah-jong hand. (That is, the hand has been organized into four sets and a pair, etc.) The last arg is flags for seven pairs etc. */ static int player_has_mah_jong(PlayerP p,MJSpecialHandFlags flags); /* utility function: copy player record into (already allocated) space. Returns new (for consistency with memcpy) */ PlayerP copy_player(PlayerP new,const PlayerP old) { return (PlayerP) memcpy((void *)new, (void *)old, sizeof(Player)); } int num_tiles_in_set(TileSetP tp) { switch ( tp->type ) { case Empty: return 0; case Pair: case ClosedPair: return 2; case Chow: case ClosedChow: case Pung: case ClosedPung: return 3; case Kong: case ClosedKong: return 4; default: warn("num_tiles_in_set: unknown tile set type %d\n",tp->type); /* this will cause chaos, but that's the idea ... */ return -1; } } void initialize_player(PlayerP p) { p->err = NULL; p->id = 0; p->name = NULL; p->wind = UnknownWind; p->flags = 0; p->cumulative_score = 0; player_newhand(p,UnknownWind); } void player_newhand(PlayerP p,TileWind w) { int i; p->err = NULL; p->wind = w; p->num_concealed = 0; p->discard_hint = -1; for ( i = 0; i < MAX_CONCEALED; i++ ) (p->concealed)[i] = HiddenTile; for ( i = 0; i < MAX_TILESETS; i++ ) { (p->tilesets)[i].type = Empty; (p->tilesets)[i].tile = HiddenTile; (p->tilesets)[i].annexed = 0; } p->num_specials = 0; for ( i = 0; i < 8; i++ ) (p->specials)[i] = HiddenTile; psetflag(p,Hidden); pclearflag(p,HandDeclared); pclearflag(p,MahJongged); psetflag(p,NoDiscard); pclearflag(p,Calling); pclearflag(p,OriginalCall); for (i=0; i < NUM_SEATS; i++) p->dflags[i] = 0; p->hand_score = -1; } void set_player_id(PlayerP p, int id) { p->err = NULL; p->id = id; } void set_player_name(PlayerP p, const char *n) { p->err = NULL; if ( p->name ) free(p->name); if ( n ) { p->name = (char *)malloc(strlen(n)+1); strcpy(p->name,n); } else p->name = (char *)0; } void set_player_cumulative_score(PlayerP p, int s) { p->err = NULL; p->cumulative_score = s; } void change_player_cumulative_score(PlayerP p, int d) { p->err = NULL; p->cumulative_score += d; } void set_player_hand_score(PlayerP p, int h) { p->err = NULL; p->hand_score = h; } void set_player_userdata(PlayerP p, void *ud) { p->err = NULL; p->userdata = ud; } /* utility functions. Maybe they should be exported. But we should probably adhere to the convention that exported functions do not leave data structures in intermediate states, as these do. */ /* player_count_tile: looks for copies of t in p's concealed tiles. returns number found or -1 on error. Should not be called on unknown players. */ int player_count_tile(PlayerP p, Tile t) { int n,i; p->err = NULL; if ( pflag(p,Hidden) ) { p->err = "Can't count tiles of hidden player"; return -1; } for ( i = 0, n = 0 ; i < p->num_concealed ; i++ ) { if ( p->concealed[i] == t ) n++ ; } return n; } /* add_tile: adds t to p's concealed tiles. Should not be called on hidden players. */ static int add_tile(PlayerP p, Tile t) { assert(!pflag(p,Hidden)); /* nonsense to call this on hidden player */ // assert(p->num_concealed < MAX_CONCEALED); if ( ! (p->num_concealed < MAX_CONCEALED) ) { error("player.c:add_tile(): player id %d has too many tiles: num_concealed = %d\n",p->id,p->num_concealed); return 0; } p->concealed[p->num_concealed++] = t; return 1; } int player_set_discard_hint(PlayerP p, int h) { if ( h < 0 || h >= p->num_concealed ) { warn("player_discard_hint: out of range hint %d",h); return 0; } p->discard_hint = h; return 1; } /* remove_tile: removes one instance of t from p's concealed tiles. Returns 1 on success, 0 on failure. Should not be called on hidden players. */ static int remove_tile(PlayerP p, Tile t) { int i,d; assert(!pflag(p,Hidden)); /* nonsense to call this on hidden player */ i = 0; d = p->discard_hint; p->discard_hint = -1; /* clear it whatever happens next */ if ( d >= 0 ) { if ( d >= p->num_concealed ) { warn("Bad discard hint %d -- too big",d); d = -1; } else if ( p->concealed[d] != t ) { warn("Bad discard hint -- tile %d is not %d",d,t); d = -1; } } if ( d >= 0 ) i = d; else while ( i < p->num_concealed && p->concealed[i] != t ) i++ ; if ( i == p->num_concealed ) return 0; while ( i+1 < p->num_concealed ) { p->concealed[i] = p->concealed[i+1]; i++ ; } p->num_concealed -= 1 ; return 1; } /* add_tileset: adds a tile set of type ty and tile t to p. Returns 1 on success, 0 on failure. Always sets the annexed flag to 0. */ static int add_tileset(PlayerP p, TileSetType ty, Tile t) { int s = 0; while ( s < MAX_TILESETS && p->tilesets[s].type != Empty ) s++ ; assert(s < MAX_TILESETS); /* serious problem if run out of slots */ p->tilesets[s].type = ty; p->tilesets[s].tile = t; p->tilesets[s].annexed = 0; return 1; } int player_draws_tile(PlayerP p, Tile t) { p->err = NULL; if ( p->num_concealed == 0 ) { if ( t == HiddenTile ) psetflag(p,Hidden); else pclearflag(p,Hidden); } if ( pflag(p,Hidden) ) p->num_concealed++; else { if ( t == HiddenTile ) { p->err = "player_draws_tile: HiddenTile, but we know the player!"; return 0; } if ( add_tile(p,t) == 0 ) { p->err = "player_draws_tile: add_tile failed"; return 0; } } return 1; } int player_draws_loose_tile(PlayerP p, Tile t) { p->err = NULL; if ( pflag(p,Hidden) ) p->num_concealed++; else { if ( t == HiddenTile ) { p->err = "player_draws_loose_tile: HiddenTile, but we know the player!"; return 0; } if ( add_tile(p,t) == 0 ) { p->err = "player_draws_loose_tile: add_tile failed"; return 0; } } return 1; } /* NOTE: it is a required feature of the following functions that if they fail, they leave the player structure unchanged. */ /* implements the two following functions */ static int int_pds(PlayerP p, Tile spec, int testonly) { p->err = NULL; if ( ! is_special(spec) ) { p->err = "player_declares_special: the special tile isn't special\n"; return 0; } /* paranoid, but why not? */ if ( p->num_specials == 8 ) { p->err = "player_declares_special: player already has all specials!"; return 0; } if ( pflag(p,Hidden) ) { if ( p->num_concealed < 1 ) { p->err = "player_can_declare_special: player has no tiles\n"; return 0; } if ( testonly ) return 1; p->specials[p->num_specials++] = spec; p->num_concealed--; return 1; } if ( player_count_tile(p,spec) < 1 ) return 0; if ( testonly ) return 1; /* now do it */ p->specials[p->num_specials++] = spec; remove_tile(p,spec); return 1; } int player_declares_special(PlayerP p, Tile spec) { return int_pds(p,spec,0); } int player_can_declare_special(PlayerP p, Tile spec) { return int_pds(p,spec,1); } /* implements the several pung functions. Determines whether p can use d to form a pung; and if not testonly, does it. If the last arg is 0, then d is a discard; otherwise, if the last arg is 1, d is also to be found in hand, and we are forming a closed pung (this is used in scoring). */ static int int_pp(PlayerP p, Tile d, int testonly, int tileinhand) { p->err = NULL; if ( pflag(p,Hidden) ) { /* just assume it can be done */ if ( p->num_concealed < (tileinhand ? 3 : 2) ) { p->err = "player_pungs: can't pung with too few concealed tiles\n"; return 0; } if ( testonly ) return 1; p->num_concealed -= (tileinhand ? 3: 2); add_tileset(p,(tileinhand ? ClosedPung : Pung),d); if ( p->num_concealed == 0 ) psetflag(p,HandDeclared); return 1; } /* otherwise, we know the player, and need to check legality */ if ( player_count_tile(p,d) < (tileinhand ? 3 : 2) ) { p->err = "player_pungs: not enough matching tiles\n"; return 0; } /* OK, it's legal */ if ( testonly ) return 1; /* add the tileset */ add_tileset(p, (tileinhand ? ClosedPung : Pung), d); /* remove the tiles from the hand */ remove_tile(p,d); remove_tile(p,d); if ( tileinhand ) remove_tile(p,d); if ( p->num_concealed == 0 ) psetflag(p,HandDeclared); return 1; } int player_pungs(PlayerP p, Tile d) { return int_pp(p,d,0,0); } int player_can_pung(PlayerP p, Tile d) { return int_pp(p,d,1,0); } int player_forms_closed_pung(PlayerP p, Tile d) { return int_pp(p, d, 0, 1); } int player_can_form_closed_pung(PlayerP p, Tile d) { return int_pp(p, d, 1, 1); } /* likewise implements the several pair functions. Determines whether p can use d to form a pair; and if not testonly, does it. If the last arg is 0, then d is a discard; otherwise, if the last arg is 1, d is also to be found in hand, and we are forming a closed pair (this is used in scoring). */ static int int_ppr(PlayerP p, Tile d, int testonly, int tileinhand) { p->err = NULL; if ( pflag(p,Hidden) ) { /* just assume it can be done */ if ( p->num_concealed < (tileinhand ? 2 : 1) ) { p->err = "player_pairs: can't pair with too few concealed tiles\n"; return 0; } p->num_concealed -= (tileinhand ? 2: 1); add_tileset(p,(tileinhand ? ClosedPair : Pair),d); if ( p->num_concealed == 0 ) psetflag(p,HandDeclared); return 1; } /* otherwise, we know the player, and need to check legality */ if ( player_count_tile(p,d) < (tileinhand ? 2 : 1) ) { p->err = "player_pairs: not enough matching tiles\n"; return 0; } /* OK, it's legal */ if ( testonly ) return 1; /* add the tileset */ add_tileset(p, (tileinhand ? ClosedPair : Pair), d); /* remove the tiles from the hand */ remove_tile(p,d); if ( tileinhand ) remove_tile(p,d); if ( p->num_concealed == 0 ) psetflag(p,HandDeclared); return 1; } int player_pairs(PlayerP p, Tile d) { return int_ppr(p,d,0,0); } int player_can_pair(PlayerP p, Tile d) { return int_ppr(p,d,1,0); } int player_forms_closed_pair(PlayerP p, Tile t) { return int_ppr(p,t,0,1); } int player_can_form_closed_pair(PlayerP p, Tile t) { return int_ppr(p,t,1,1); } /* implements several following functions; testonly and tileinhand as before */ static int int_pk(PlayerP p, Tile d, int testonly, int tileinhand) { p->err = NULL; if ( pflag(p,Hidden) ) { /* just assume it can be done */ if ( p->num_concealed < (tileinhand ? 4 : 3) ) { p->err = "player_kongs: can't kong with too few concealed tiles\n"; return 0; } p->num_concealed -= (tileinhand ? 4 : 3); /* lose three/four, gain replacement */ add_tileset(p,(tileinhand ? ClosedKong : Kong), d); return 1; } /* otherwise, we know the player, and need to check legality */ if ( player_count_tile(p,d) < (tileinhand ? 4 : 3) ) { p->err = "player_kongs: not enough matching tiles\n"; return 0; } /* OK, it's legal */ if ( testonly ) return 1; /* add the tileset */ add_tileset(p,(tileinhand ? ClosedKong : Kong),d); /* remove the tiles from the hand */ remove_tile(p,d); remove_tile(p,d); remove_tile(p,d); if ( tileinhand ) remove_tile(p,d); return 1; } int player_kongs(PlayerP p, Tile d) { return int_pk(p,d,0,0); } int player_can_kong(PlayerP p, Tile d) { return int_pk(p, d, 1, 0); } int player_declares_closed_kong(PlayerP p, Tile d) { return int_pk(p, d, 0, 1); } int player_can_declare_closed_kong(PlayerP p, Tile d) { return int_pk(p, d, 1, 1); } /* Implements two following functions. */ static int int_pap(PlayerP p, Tile t, int testonly) { int i; p->err = NULL; /* First check that the player has an exposed pung of the tile */ for ( i=0 ; i < MAX_TILESETS ; i++ ) { if ( p->tilesets[i].type == Pung && p->tilesets[i].tile == t ) break; } if ( i == MAX_TILESETS ) { p->err = "player_adds_to_pung called with no pung"; return 0; } /* now check (if poss) that the tile is in hand */ if ( pflag(p,Hidden) ) { /* can't see the tiles, so trust it */ if ( p->num_concealed < 1 ) { /* err, this is actually impossible */ p->err = "player_adds_to_pung: no concealed tiles"; return 0; } /* lose one concealed tile */ p->num_concealed--; p->tilesets[i].type = Kong; p->tilesets[i].annexed = 1; return 1; } /* otherwise, we know the player and need to check legality */ if ( player_count_tile(p,t) < 1 ) { p->err = "player_adds_to_pung: tile not found in hand"; return 0; } /* OK, it's legal */ if ( testonly ) return 1; /* modify the tileset */ p->tilesets[i].type = Kong; p->tilesets[i].annexed = 1; /* remove the tile from the hand */ remove_tile(p,t); return 1; } int player_adds_to_pung(PlayerP p, Tile t) { return int_pap(p,t,0); } int player_can_add_to_pung(PlayerP p,Tile t) { return int_pap(p,t,1); } /* player_kong_robbed: the player has formed a kong of t, and it is robbed */ int player_kong_is_robbed(PlayerP p, Tile t) { int i; p->err = NULL; /* find the kong in question */ for ( i=0 ; i < MAX_TILESETS ; i++ ) { if ( p->tilesets[i].tile == t && ( p->tilesets[i].type == Kong || p->tilesets[i].type == ClosedKong ) ) break; } if ( i == MAX_TILESETS ) { p->err = "player_kong_is_robbed called with no kong"; return 0; } /* and downgrade it */ if ( p->tilesets[i].type == Kong ) p->tilesets[i].type = Pung ; else p->tilesets[i].type = ClosedPung; p->tilesets[i].annexed = 0; return 1; } /* the chow handling function */ static int int_pc(PlayerP p, Tile d, ChowPosition r, int testonly, int tileinhand) { Tile low, mid, high; p->err = NULL; if ( ! is_suit(d) ) { p->err = "Can't chow a non-suit tile\n"; return 0; } if ( r == AnyPos ) { if ( !testonly ) { p->err = "Can't make a chow with AnyPos"; return 0; } return int_pc(p,d,Lower,testonly,tileinhand) || int_pc(p,d,Middle,testonly,tileinhand) || int_pc(p,d,Upper,testonly,tileinhand); } if ( r == Lower ) { if ( value_of(d) > 7 ) { p->err = "Impossible chow\n"; return 0; } low = d; mid = d+1; high = d+2; } else if ( r == Middle ) { if ( value_of(d) == 1 || value_of(d) == 9 ) { p->err = "Impossible chow\n"; return 0; } mid = d; low = d-1; high = d+1; } else /* r == Upper */ { if ( value_of(d) < 3 ) { p->err = "Impossible chow\n"; return 0; } high = d; mid = d-1; low = d-2; } if ( pflag(p,Hidden) ) { if ( testonly ) return 1; p->num_concealed -= (tileinhand ? 3 : 2); add_tileset(p,(tileinhand ? ClosedChow : Chow),low); if ( p->num_concealed == 0 ) psetflag(p,HandDeclared); return 1; } /* Check we have the tiles */ if ( tileinhand || r != Lower ) { if ( player_count_tile(p,low) < 1 ) { p->err = "Don't have the other tiles for the chow"; return 0; } } if ( tileinhand || r != Middle ) { if ( player_count_tile(p,mid) < 1 ) { p->err = "Don't have the other tiles for the chow"; return 0; } } if ( tileinhand || r != Upper ) { if ( player_count_tile(p,high) < 1 ) { p->err = "Don't have the other tiles for the chow"; return 0; } } /* OK, we're ready to go */ if ( testonly ) return 1; /* this might fail if the state is inconsistent, in which case we should leave p unchanged. Hence it comes first. */ if ( !add_tileset(p,(tileinhand ? ClosedChow : Chow),low) ) { p->err = "int_pc: add_tileset failed!"; return 0; } /* remove the tiles */ if ( tileinhand || r != Lower ) { remove_tile(p,low); } if ( tileinhand || r != Middle ) { remove_tile(p,mid); } if ( tileinhand || r != Upper ) { remove_tile(p,high); } if ( p->num_concealed == 0 ) psetflag(p,HandDeclared); return 1; } int player_chows(PlayerP p, Tile d, ChowPosition r) { return int_pc(p,d,r,0,0); } int player_can_chow(PlayerP p, Tile d, ChowPosition r) { return int_pc(p,d,r,1,0); } int player_forms_closed_chow(PlayerP p, Tile d, ChowPosition r) { return int_pc(p,d,r,0,1); } int player_can_form_closed_chow(PlayerP p, Tile d, ChowPosition r) { return int_pc(p,d,r,1,1); } /* discarding a tile */ static int int_pd(PlayerP p, Tile t, int testonly) { p->err = NULL; if ( pflag(p,Hidden) ) { if ( p->num_concealed < 1 ) { p->err = "No tiles to discard!\n"; return 0; } if ( testonly ) return 1; p->num_concealed -= 1; pclearflag(p,NoDiscard); return 1; } if ( player_count_tile(p,t) < 1 ) { p->err = "Tile not found in hand\n"; return 0; } if ( testonly ) return 1; remove_tile(p,t); pclearflag(p,NoDiscard); return 1; } int player_discards(PlayerP p, Tile t) { return int_pd(p,t,0); } int player_can_discard(PlayerP p, Tile t) { return int_pd(p,t,1); } /* player_can_mah_jong: determine whether this hand (perhaps with an added discard tile) is a mah-jong hand. This function is completely brain-dead about finding a possible mah jong; given the small search space, there seems no point in being at all clever. */ int player_can_mah_jong(PlayerP p,Tile d,MJSpecialHandFlags flags) { Player pcopy; /* we will manipulate this copy of the player */ PlayerP pcp = &pcopy; int answer = 0; Tile t; p->err = NULL; /* Technique is depth-first search of possible hands: just try applying making all possible tilesets until we succeed */ /* The base case of the recursion */ if ( d == HiddenTile && p->num_concealed <= 1 ) { answer = player_has_mah_jong(p,flags); goto done; } /* otherwise, try making tilesets. First deal with the discard tile, if it exists. For each possible way of using the discard, make a copy of the player, use the discard, and see if the result is mah-jongable. */ if ( d != HiddenTile ) { copy_player(pcp,p); answer = (player_pungs(pcp,d) && player_can_mah_jong(pcp,HiddenTile,flags)); if (answer) goto done; copy_player(pcp,p); answer = (player_chows(pcp,d,Lower) && player_can_mah_jong(pcp,HiddenTile,flags)); if (answer) goto done; copy_player(pcp,p); answer = (player_chows(pcp,d,Middle) && player_can_mah_jong(pcp,HiddenTile,flags)); if (answer) goto done; copy_player(pcp,p); answer = (player_chows(pcp,d,Upper) && player_can_mah_jong(pcp,HiddenTile,flags)); if (answer) goto done; copy_player(pcp,p); answer = (player_pairs(pcp,d) && player_can_mah_jong(pcp,HiddenTile,flags)); goto done; } else { /* otherwise, for each concealed tile, try making something of it */ int i; for ( i = 0; i < p->num_concealed; i++ ) { t = p->concealed[i]; copy_player(pcp,p); answer = (player_forms_closed_pung(pcp,t) && player_can_mah_jong(pcp,HiddenTile,flags)); if (answer) goto done; copy_player(pcp,p); answer = (player_forms_closed_chow(pcp,t,Lower) && player_can_mah_jong(pcp,HiddenTile,flags)); if (answer) goto done; copy_player(pcp,p); answer = (player_forms_closed_pair(pcp,t) && player_can_mah_jong(pcp,HiddenTile,flags)); if (answer) goto done; } } done: /* if all else fails, try 13 unique wonders */ if ( (! answer) && ((p->num_concealed == 13 && d != HiddenTile) || (p->num_concealed == 14 && d == HiddenTile))) answer = player_can_thirteen_wonders(p,d); return answer; } /* does the player have 13 unique wonders? */ int player_can_thirteen_wonders(PlayerP p, Tile d) { Player pcopy; /* we will manipulate this copy of the player */ PlayerP pcp = &pcopy; int answer = 0; int i, havetwo, n; if ( d != HiddenTile ) { copy_player(pcp,p); answer = (player_draws_tile(pcp,d) && player_can_thirteen_wonders(pcp,HiddenTile)); } else { answer = 1; havetwo = 0; for ( i=0; i < 13; i++ ) { n = player_count_tile(p,thirteen_wonders[i]); if ( n > 2 ) { answer = 0; break; } else if ( n == 2 ) { if ( havetwo ) { answer = 0; break; } else { havetwo = 1; } } else if ( n == 0 ) { answer = 0; break; } } if ( ! havetwo ) answer = 0; } return answer; } /* This internal function defines the mah jong hands. (Currently it does not include thirteen wonders). Last arg allows seven pairs etc. */ static int player_has_mah_jong(PlayerP p,MJSpecialHandFlags flags) { int i,s,n; int answer; p->err = NULL; /* all tiles must be in sets */ if ( p->num_concealed > 0 ) return 0; answer = 0; /* there must be exactly five non-empty sets, exactly one of which must be a pair */ for (i=0, n=0, s=0; itilesets[i].type != Empty ) s++; if ( num_tiles_in_set(&(p->tilesets[i])) == 2) n++; } if ( s == 5 && n == 1 ) answer = 1; /* seven pairs may be allowed */ if ( (flags & MJSevenPairs) && s == 7 && n == 7 ) answer = 1; /* that's it */ return answer; } /* sort the player's concealed tiles. */ void player_sort_tiles(PlayerP p) { int nconc; int i,j; Tile *tp = p->concealed; Tile t; p->err = NULL; if ( pflag(p,Hidden) ) return; /* pretty dumb ... */ nconc = p->num_concealed; /* bubble sort */ for ( i = 0 ; i < nconc-1 ; i++ ) { if ( tp[i+1] < tp[i] ) { /* sink it to the bottom */ for ( j = i+1 ; tp[j] < tp[j-1] && j > 0 ; j-- ) { t = tp[j-1]; tp[j-1] = tp[j]; tp[j] = t; } } } } /* move a tile */ int player_reorder_tile(PlayerP p, int old, int new) { int i; if ( old < 0 || new < 0 || old >= p->num_concealed || new >= p->num_concealed ) { warn("player_reorder_tile: arguments out of range %d, %d",old,new); return 0; } if ( old == new ) return 1; Tile t = p->concealed[old]; if ( new > old ) { for (i = old+1; i <= new; i++) p->concealed[i-1] = p->concealed[i]; p->concealed[new] = t; } else { for (i = old-1; i >= new; i--) p->concealed[i+1] = p->concealed[i]; p->concealed[new] = t; } return 1; } /* This function generates a string repn of a players tiles. */ void player_print_tiles(char *buf, PlayerP p, int hide) { int i,j; Tile *tp; Tile t; Tile ctiles[MAX_CONCEALED]; int nconc; p->err = NULL; if ( pflag(p,Hidden) ) hide = 1; /* can't see the tiles anyway */ /* copy the concealed files and sort them. This assumes knowledge that Tiles are shorts. */ nconc = p->num_concealed; if ( ! hide ) { memcpy(ctiles,p->concealed,nconc*sizeof(Tile)); tp = ctiles; /* bubble sort */ for ( i = 0 ; i < nconc-1 ; i++ ) { if ( tp[i+1] < tp[i] ) { /* sink it to the bottom */ for ( j = i+1 ; tp[j] < tp[j-1] && j > 0 ; j-- ) { t = tp[j-1]; tp[j-1] = tp[j]; tp[j] = t; } } } } buf[0] = '\000' ; for (i=0; i < nconc; i++) { if ( i > 0 ) strcat(buf," "); strcat(buf, hide ? "--" : tile_code(ctiles[i])); } strcat(buf," *"); for (i = 0; itilesets[i].type == Empty ) continue; strcat(buf," "); strcat(buf,tileset_string(&p->tilesets[i])); } strcat(buf," * "); for (i=0; i < p->num_specials; i++) { if ( i > 0 ) strcat(buf," "); strcat(buf,tile_code(p->specials[i])); } } int set_player_tiles(PlayerP p,char *desc) { int i = 0; char tc[3]; /* for tile codes */ int ts; int closed, annexed; Tile tile; TileSetType ttype; p->err = NULL; /* clear the current information */ p->num_concealed = 0; for (i=0; i < MAX_TILESETS ; i++) p->tilesets->type = Empty; p->num_specials = 0; pclearflag(p,Hidden); while ( *desc && isspace(*desc) ) desc++; /* read the tiles in hand */ while ( *desc && *desc != '*' ) { tc[0] = *(desc++); tc[1] = *(desc++); tc[2] = '\000'; tile = tile_decode(tc); if ( tile == ErrorTile ) { warn("format error in set_player_tile"); return 0; } if ( tile == HiddenTile ) { p->num_concealed++; psetflag(p,Hidden); } else { p->concealed[p->num_concealed++] = tile; } while ( *desc && isspace(*desc) ) desc++; } if ( ! *desc ) { warn("format error in set_player_tiles"); return 0; } desc++; /* skip the * */ while ( *desc && isspace(*desc) ) desc++; /* parse the tilesets */ ts = 0; while ( *desc && *desc != '*' ) { annexed = 0; closed = 0; tc[0] = *(desc++); tc[1] = *(desc++); tc[2] = 0; tile = tile_decode(tc); if ( tile == ErrorTile ) { warn("format error in set_player_tiles"); return 0; } if ( *(desc++) == '+' ) closed = 1; /* we're lazy now: if the next tile is different, we know it's a chow; otherwise we just skip to the next white space, counting letters */ tc[0] = *(desc++); tc[1] = *(desc++); if ( tile_decode(tc) != tile ) { ttype = closed ? ClosedChow : Chow; desc += 3; /* skip last tile */ } else if ( isspace(*desc) ) { /* two tiles; it's a pair */ ttype = closed ? ClosedPair : Pair; } else { /* skip next tile */ desc += 3; if ( isspace(*desc) ) { /* it's a pung */ ttype = closed ? ClosedPung : Pung; } else { /* it's a kong */ ttype = closed ? ClosedKong : Kong; /* millington rules ? */ if ( ! closed ) annexed = (*desc == '-'); desc += 3; } } p->tilesets[ts].type = ttype; p->tilesets[ts].tile = tile; p->tilesets[ts].annexed = annexed; ts++; while ( *desc && isspace(*desc) ) desc++; } /* end of matching tilesets loop */ if ( ! *desc ) { warn("format error in set_player_tiles"); return 0; } desc++; /* skip the * */ while ( *desc && isspace(*desc) ) desc++; /* parse the specials */ while ( *desc && *desc != '*' ) { tc[0] = *(desc++); tc[1] = *(desc++); tc[2] = '\000'; tile = tile_decode(tc); if ( tile == ErrorTile ) { warn("format error in set_player_tile"); return 0; } p->specials[p->num_specials++] = tile; while ( *desc && isspace(*desc) ) desc++; } /* phew */ return 1; } int player_shows_tiles(PlayerP p, char *desc) { char tc[3]; Tile tile; p->err = NULL; /* If the player is not hidden, then we needn't do anything to the tiles */ if ( pflag(p,Hidden) ) { /* clear the current information */ p->num_concealed = 0; pclearflag(p,Hidden); while ( *desc && isspace(*desc) ) desc++; /* read the tiles in hand */ while ( *desc ) { tc[0] = *(desc++); tc[1] = *(desc++); tc[2] = '\000'; tile = tile_decode(tc); if ( tile == ErrorTile ) { warn("format error in player_shows_tiles"); return 0; } p->concealed[p->num_concealed++] = tile; while ( *desc && isspace(*desc) ) desc++; } } psetflag(p,HandDeclared); return 1; } /* player_swap_tile: swaps the oldtile for the newtile in the concealed tiles. Only used in testing, of course */ int player_swap_tile(PlayerP p, Tile oldtile, Tile newtile) { p->err = NULL; if ( ! remove_tile(p,oldtile) ) { p->err = "player doesn't have old tile"; return 0; } add_tile(p,newtile); return 1; } /* return string representing a tileset */ char *tileset_string(TileSetP tp) { char *sep; static char buf[20]; short v; TileSuit s; int j; buf[0] = '\000'; sep = "-"; switch ( tp->type ) { case ClosedPung: case ClosedKong: case ClosedPair: sep = "+"; // fall through case Pung: case Kong: case Pair: for ( j = 0; j < num_tiles_in_set(tp); j++ ) { if ( tp->type == Kong && !tp->annexed && j == num_tiles_in_set(tp) - 1 ) sep = "+"; if ( j > 0 ) strcat(buf,sep); strcat(buf,tile_code(tp->tile)); } break; case ClosedChow: sep = "+"; // fall through case Chow: v = value_of(tp->tile); s = suit_of(tp->tile); strcat(buf,tile_code(tp->tile)); strcat(buf,sep); strcat(buf,tile_code(make_tile(s,v+1))); strcat(buf,sep); strcat(buf,tile_code(make_tile(s,v+2))); break; case Empty: /* errrr */ return ""; } return buf; } /* print into an internal buffer a representation of the player */ char *player_print_state(PlayerP p) { static char buf[512],tiles[80],flags[80],dflags[80]; int i,j; flags[0] = 0; for ( i = 0; i < 32; i++ ) { if ( pflag(p,i) ) { strcat(flags,nullprotect(player_print_PlayerFlags(i))); strcat(flags," "); } } dflags[0] = 0; for ( j = 0; j < NUM_SEATS; j++ ) { for ( i = 0; i < 32; i++ ) { if ( pdflag(p,j,1 << i) ) { strcat(dflags,nullprotect(player_print_DangerSignals(1 << i))); strcat(dflags," "); } } strcat(dflags,"; "); } player_print_tiles(tiles,p,0); sprintf(buf,"Player:\n" "id: %d\n" "name: %.100s\n" "wind: %s\n" "tiles: %s\n" "flags: %s\n" "dflags: %s\n" "cumulative_score: %d\n" "hand_score: %d\n" "err: %s\n" "userdata: %p\n", p->id, nullprotect(p->name), nullprotect(tiles_print_TileWind(p->wind)), tiles, flags, dflags, p->cumulative_score, p->hand_score, nullprotect(p->err), p->userdata); return buf; } /* enum functions */ #include "player-enums.c" mj-1.17-src/lazyfixed.h0000444006717300001440000000500015002771311013463 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/lazyfixed.h,v 12.0 2009/06/28 20:43:12 jcb Rel $ * lazyfixed.h * A slight variation on the GTK+ fixed widget. * It doesn't call queue_resize when children removed; * this can reduce flickering in widgets with many children. * Of course, it means that you must call gtk_widget_queue_resize manually * if you *do* want to remove something and have the size change. * Also, of course, if you remove a non-window widget, nothing will * happen until the next time something causes redisplay. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2001 by J. C. Bradfield. * * This file may be used under the terms of the * * GNU Lesser General Public License (any version). * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #ifndef __LAZY_FIXED_H__ #define __LAZY_FIXED_H__ #include #include "sysdep.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #define LAZY_FIXED(obj) (GTK_CHECK_CAST ((obj), lazy_fixed_get_type(), LazyFixed)) #define LAZY_FIXED_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), lazy_fixed_get_type(), LazyFixedClass)) #define IS_LAZY_FIXED(obj) (GTK_CHECK_TYPE ((obj), lazy_fixed_get_type())) typedef struct _LazyFixed LazyFixed; typedef struct _LazyFixedClass LazyFixedClass; struct _LazyFixed { GtkFixed fixed; }; struct _LazyFixedClass { GtkFixedClass parent_class; }; GtkType lazy_fixed_get_type (void); GtkWidget* lazy_fixed_new (void); void lazy_fixed_put (LazyFixed *fixed, GtkWidget *widget, gint16 x, gint16 y); void lazy_fixed_move (LazyFixed *fixed, GtkWidget *widget, gint16 x, gint16 y); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __LAZY_FIXED_H__ */ mj-1.17-src/proto-decode-msg.pl0000444006717300001440000001521115002771311015025 0ustar jcbusers# $Header: /home/jcb/MahJong/newmj/RCS/proto-decode-msg.pl,v 12.0 2009/06/28 20:43:13 jcb Rel $ # proto-decode-msg.pl # script to generate body of decode_[cp]msg function in protocol.c # it is not defensively programmed! # The code it generates *is* defensively programmed, of course. # Well, somewhat. #***************** COPYRIGHT STATEMENT ********************** #* This file is Copyright (c) 2000 by J. C. Bradfield. * #* Distribution and use is governed by the LICENCE file that * #* accompanies this file. * #* The moral rights of the author are asserted. * #* * #***************** DISCLAIMER OF WARRANTY ******************** #* This code is not warranted fit for any purpose. See the * #* LICENCE file for further information. * #* * #************************************************************* # debugging $debug = $ENV{'DEBUG'}; # are we doing cmsg or pmsg ? if ( $ARGV[0] eq '-cmsg' ) { $L = 'C' ; $l = 'c'; $msgtype = "Controller"; $infile = "protocol.h"; } elsif ( $ARGV[0] eq '-pmsg' ) { $L = 'P' ; $l = 'p' ; $msgtype = "Player"; $infile = "protocol.h"; } elsif ( $ARGV[0] eq '-mcmsg' ) { $L = 'MC' ; $l = 'mc' ; $msgtype = "MController"; $infile = "mprotocol.h"; } elsif ( $ARGV[0] eq '-mpmsg' ) { $L = 'MP' ; $l = 'mp' ; $msgtype = "MPlayer"; $infile = "mprotocol.h"; } else { die("No function argument"); } open(STDIN,"<$infile") or die("No infile"); open(STDOUT,">dec_${l}msg.c"); # The code we're trying to generate looks like this: # if ( strcmp(type,"PlayerPungs") == 0) { # CMsgPlayerPungsMsg *m; # # m = (CMsgPlayerPungsMsg *)malloc(sizeof(CMsgPlayerPungsMsg)); # if ( ! m ) { warn("malloc failed\n") ; return (CMsgMsg *)0; } # # m->type = CMsgPlayerPungs; # # if ( sscanf(s,"%d%n",&an_int,&n) == 0 ) { warn("protocol error\n"); return (CMsgMsg *)0; } # m->id = an_int; # s += n; # # and so on and so on. Note that for (char *) args, we need to # malloc the space. # a function to print the first few lines, given the type. sub firstchunk { my($type) = $_[0]; my($atext) = ''; if ( $alias ) { $atext = " || strcmp(type,\"${alias}\") == 0 "; } print " if ( strcmp(type,\"${type}\") == 0 ${atext}) { ${L}Msg${type}Msg *m; m = (${L}Msg${type}Msg *)malloc(sizeof(${L}Msg${type}Msg)); if ( ! m ) { warn(\"malloc failed\\n\") ; return (${L}MsgMsg *)0; } m->type = ${L}Msg${type}; "; } while ( ! eof(STDIN) ) { while ( ) { chop; # look for the beginning of the structure definition next unless s/^.*struct _${L}Msg// ; s/Msg \{//; # it is an undocumented fact that certain common commands have # short aliases, for ease in debugging via telnet sessions. # these are implemented via a comment of the form /* alias c */ # on the typedef line. $alias = undef; if ( s,\s*/\*\s+alias\s+(\S+).*$,, ) { $alias = $1; } last if ! $_ ; # got to the dummy type $type = $_ ; $_ = ; # skip the type &firstchunk($type) ; # print the first few lines # now deal with the components while ( ) { chop; s/;.*$// ; # junk all but type and name if ( s/^\s*int\s+// ) { print ' if ( sscanf(s,"%d%n",&an_int,&n) == 0 ) { warn("protocol error\n"); return (' . $L . 'MsgMsg *)0; }', "\n"; print " m->$_ = an_int;\n"; print " s += n;\n"; print " while ( isspace(*s) ) s++;\n\n"; next; } if ( s/^\s*bool\s+// ) { print ' if ( sscanf(s,"%d%n",&an_int,&n) == 0 ) { warn("protocol error\n"); return (' . $L . 'MsgMsg *)0; }', "\n"; print ' if ( an_int < 0 || an_int > 1 ) { warn("protocol error\n") ; return (' . $L . 'MsgMsg *)0; }', "\n"; print " m->$_ = an_int;\n"; print " s += n;\n"; print " while ( isspace(*s) ) s++;\n\n"; next; } if ( s/^\s*PlayerOption\s+// ) { print ' if ( sscanf(s,"%31s%n",little_string,&n) ==0 ) { warn("protocol error\n"); return (' . $L . 'MsgMsg *)0; }', "\n"; print " m->$_ = player_scan_PlayerOption(little_string);\n"; print " s += n;\n"; print " while ( isspace(*s) ) s++;\n\n"; next; } if ( s/^\s*TileWind\s+// ) { print ' if ( sscanf(s,"%c%n",&a_char,&n) == 0 ) { warn("protocol error\n"); return (' . $L . 'MsgMsg *)0; }', "\n"; print " m->$_ = letterwind(a_char);\n"; print " s += n;\n"; print " while ( isspace(*s) ) s++;\n\n"; next; } if ( s/^\s*Tile\s+// ) { print ' if ( sscanf(s,"%2s%n",little_string,&n) == 0 ) { warn("protocol error\n"); return (' . $L . 'MsgMsg *)0; }', "\n"; print " m->$_ = tile_decode(little_string);\n"; print " if ( m->$_ == ErrorTile ) { warn(\"protocol error\\n\"); return (${L}MsgMsg *)0; }\n"; print " s += n;\n"; print " while ( isspace(*s) ) s++;\n\n"; next; } if ( s/^\s*word(\d+)\s+// ) { $wordlen = $1; print ' if ( sscanf(s,"%',$wordlen,'s%n",little_string,&n) == 0 ) { warn("protocol error\n"); return (' . $L . 'MsgMsg *)0; }', "\n"; print " strmcpy(m->$_,little_string,$wordlen);\n"; print " s += n;\n"; print " while ( isspace(*s) ) s++;\n\n"; next; } if ( s/^\s*ChowPosition\s+// ) { # the 31 field width is for safety. This string should always # be lower, middle or upper! # Argh. This code does not defend against protocol errors. # FIXME print ' if ( sscanf(s,"%31s%n",little_string,&n) == 0 ) { warn("protocol error\n"); return (' . $L . 'MsgMsg *)0; }', "\n"; print " m->$_ = string_cpos(little_string);\n"; print " s += n;\n"; print " while ( isspace(*s) ) s++;\n\n"; next; } if ( s/^\s*char\s+\*\s*// ) { # we are allowed to assume this is the last field print " if ( strlen(s) == 0 ) m->$_ = (char *)0;\n"; print " else {\n"; print " m->$_ = (char *)malloc(strlen(s)+1);\n"; print " if ( ! m->$_ ) { warn(\"malloc failed\\n\"); return (${L}MsgMsg *)0; }\n"; # we now need to do a little backslash unescaping. # At this level of protocol, all we do is say: if arg starts # with \, remove it - any initial backslash must be there # to protect the next character print " strcpy(m->$_,s+(s[0] == '\\\\'));\n"; print " }\n"; next; } if ( s/^\s*GameOptionEntry\s+// ) { print ' if ( (goe = protocol_scan_GameOptionEntry(s)) == 0 ) { warn("protocol error\n"); return (' . $L . 'MsgMsg *)0; }', "\n"; print " memcpy(&m->$_,goe,sizeof(GameOptionEntry));\n"; next; } last if s/^\}// ; # the end print STDERR "I hope this is a comment: $_\n" if $debug; } print " return (${L}MsgMsg *) m;\n"; print " }\n"; } } mj-1.17-src/client.h0000444006717300001440000000717315002771311012757 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/client.h,v 12.0 2009/06/28 20:43:12 jcb Rel $ * client.h * header file for client support routines. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #ifndef CLIENT_H_INCLUDED #define CLIENT_H_INCLUDED 1 #include "game.h" /* client_init: takes an address, attempts to connect to a controller on that address. Internally, allocates game structure etc. Returns a pointer to the allocated game structure, or NULL. It's a documented feature that if the address is "-", then the game fd will be set to be STDOUT. The get_line function will automatically read from STDIN if this is the case. */ Game *client_init(char *address); /* client_reinit: the same, but uses already established network connection passed as fd/handle */ Game *client_reinit(int fd); /* client_connect: take an id and a name, and send a connect message. Return 1 on success, or 0 on failure. */ int client_connect(Game *g, int id, char *name); /* client_close: given game, close the connection and deallocate the storage. Returns NULL. */ Game *client_close(Game *g); /* client_close_keepconnection: as above, but doesn't actually close the connection */ Game *client_close_keepconnection(Game *g); /* client_send_packet sends a packet to the controller. The return value is 0 on failure, or the sequence number of the packet on success. N.B. If packets are sent bypassing this routine, the sequence numbers will not be correct, unless the user updates the game's cseqno field explicitly. */ int client_send_packet(Game *g, PMsgMsg *m); /* client_find_sets: this takes a player and a discard (or HiddenTile) and finds sets that can be declared. If a discard is supplied, only sets involving the discard are returned. It returns a pointer to an array of TileSets, terminated by an Empty TileSet; but if no sets are found, NULL is returned. The returned array is in static storage, and is valid until the next call of this function. If the mj flag is 1, then the hand is assumed to be complete, and the sets must lead to a complete hand. Otherwise, all possible declarations are returned. The sets are returned with pungs first, then pairs, then chows. This is so that scores for fishing the eyes are obtained when this function is used for auto-declaration. Prior to 11.4, chows were returned before pairs. The return value is a TileSet: empty if no set could be found. NOTE: kongs are never returned if either there is no discard (obviously) or if the mj flag is true (equally obviously). As a convenience, if the pcopies argument is nonnull, then it will be updated to point to an array of players, which are the results of executing the related sets. These are also in static storage. (N.B. For a kong declaration, the associated player is not valid.) Final arg is flags for non-standard hands. */ TileSet *client_find_sets(PlayerP p, Tile d, int mj, PlayerP *pcopies, MJSpecialHandFlags flags); #endif /* CLIENT_H_INCLUDED */ mj-1.17-src/mj-player.man0000444006717300001440000000015315002771311013714 0ustar jcbusers.\" $Header: /home/jcb/MahJong/newmj/RCS/mj-player.man,v 12.0 2009/06/28 20:43:12 jcb Rel $ .so man1/xmj.1 mj-1.17-src/Makefile0000644006717300001440000004303115002771312012764 0ustar jcbusers# $Header: /home/jcb/MahJong/newmj/RCS/Makefile.in,v 12.5 2025/04/23 20:22:44 jcb Exp $ # Master makefile for the xmj suite. ### NOTE NOTE NOTE ### This makefile uses some features specific to GNU make. ### If you do not have GNU make, you will need to adjust it. ### In particular, example Windows specific settings are included here ### by means of ifdef Win32 conditionals (where Win32 is a Makefile ### variable that can be set on the command line, for example). ### It should be easy to edit these out. ### As far as I know, the only other non-portable feature is the ### use of %-pattern rules, but they occur in many makes. # Options can be passed by setting variables from the command line: # pass Win32=1 to build with MinGW on Win32 without MSYS # pass Win32=2 to build with MinGW and MSYS # pass Win32=3 to cross-compile on Linux for MinGW # pass Warnings=1 to switch on almost all compiler warnings # The things you are most likely to change come first! # first target: don't change! all:: ### Local configuration options. Read and edit as necessary. # Gtk version - 2, or maybe one day 3 # if you're using GTK+ 2.n, for ANY n, set Gtk to be 2.0 . Gtk = 2.0 # This is not available in current public releases. # Uncomment the following line to build the table manager. # TableManager=1 # The directory in which to find the tile pixmaps that are # compiled into the program. Beware of licensing considerations. # (The tiles-v1 pixmaps may only be linked into GPLed programs.) FALLBACKTILES=./tiles-v1 # The directory in which to find tile pixmaps. # Defaults to NULL, causing the compiled-in tiles to be used. TILESET=NULL # The search path for finding tile pixmap directories. # Defaults to NULL, meaning current directory. TILESETPATH=NULL # Where to install the programs. # (Don't bother with this on Windows; I don't have an install target # for Windows.) # The binaries go into $(DESTDIR)$(BINDIR) DESTDIR = /usr/local/ BINDIR = bin # The man pages go into $(DESTDIR)$(MANDIR) MANDIR = man/man1 # and the appropriate suffix is 1 MANSUFFIX = 1 # if you are cross-compiling from Linux to Windows, # MGW should be the root of your MinGW hierarchy for the GTK libraries MGW=/home/jcb/MinGW ### End of local configuration. ### System dependent settings. # This section contains compiler settings etc. # If you think this reminds of you something, you're right: this # makefile was made by stripping out most of an imake-generated file. # It's best to use gcc if you can. CC = gcc # C debugging and optimization flags. # In development, we turn on all reasonable warnings. # However, in the production releases, we don't because other people's # code (e.g. glib) may provoke warnings. # Also, we do NOT enable optimization by default, because RedHat 6 # Linux distributions have a seriously buggy C compiler. # I thought the preceding comment should be deleted...but in 2009, # SuSE ships with a seriously buggy C compiler (gcc 4.3). So I'll # just leave optimization switched off. ifdef Warnings CDEBUGFLAGS = -g -Wall -W -Wstrict-prototypes -Wmissing-prototypes else CDEBUGFLAGS = -g endif # The -Wconversion flag is also useful to detect (more than usual) # abuse of enums, but it generates many superfluous warnings, so # is not on by default. # To check for non-ANSI usages, use -ansi; # to get all the ANSI-mandated warnings, use -pedantic, but # this is so pedantic it's not worth it. # Defines for the compilation. ifdef Win32 WINDEFINES = -DWIN32=1 endif ifeq ($(Win32),2) # This doesn't work if one tries to set these. Does anybody know # how to get a double quote through DOS or whatever to gcc ? DEFINES = -DTILESET=$(TILESET) -DTILESETPATH=$(TILESETPATH) $(WINDEFINES) else DEFINES = -DTILESET='$(TILESET)' -DTILESETPATH='$(TILESETPATH)' $(WINDEFINES) endif GTKDEFINES = -DGTK2=1 # Options for compiling. ifdef Win32 # this is suitable for recent mingw releases (the equivalent # of the old -fnative-struct) CCOPTIONS = -mms-bitfields endif # Extra libraries needed above whatever the system provides you with. # Things like -lnsl on Solaris are the most likely examples. # -lm now needed for greedy only ifdef Win32 # We need the winsock libraries LDLIBS = -lwsock32 -lm else # Red Hat and Mandrake (and probably most other) Linux systems # need nothing, as any needed libraries are brought in for gtk anyway. LDLIBS = -lm # If you are on Solaris, you may need the following: # LDLIBS = -lsocket -lnsl # If you are on HP-UX, you may need # LDLIBS = -lm endif # some systems have buggy malloc/realloc etc, so we may use # Doug Lea's implementation. This is switched on for Windows # at the moment ifdef Win32 MALLOCSRC = malloc.c MALLOCOBJ = malloc.o endif # The xmj program is built with gtk+, so it needs to know where the # libraries and headers are. ifdef Win32 ifeq ($(Win32),3) EXTRA_DEFINES=-DXCOMPILE=1 CC=i686-w64-mingw32-gcc EXTRA_INCLUDES=-I$(MGW)/include/glib-2.0 -I$(MGW)/include/gtk-2.0 -I$(MGW)/include/pango-1.0 -I$(MGW)/include/cairo -I$(MGW)/lib/glib-2.0/include -I$(MGW)/lib/gtk-2.0/include -I$(MGW)/include/gdk-pixbuf-2.0 -I$(MGW)/include/atk-1.0 GUILIBS=-L$(MGW)/lib -lgtk-win32-2.0 -lgdk-win32-2.0 -lgdk_pixbuf-2.0 -lglib-2.0 -lgobject-2.0 -mwindows else # should be the same as unix, if it can find pkg-config EXTRA_INCLUDES=$(shell pkg-config --cflags gtk+-$(Gtk)) # We also add the flag that makes xmj.exe be a GUI program GUILIBS=$(shell pkg-config --libs gtk+-$(Gtk)) -mwindows endif else # Not Windows. If gtk+ is properly installed, this is all that's needed. # except that gtk no longer pulls in -lm EXTRA_INCLUDES=`pkg-config --cflags gtk+-$(Gtk)` GUILIBS=`pkg-config --libs gtk+-$(Gtk)` -lm endif # We use gcc to link as well CCLINK = $(CC) # don't mess with these ALLINCLUDES = $(INCLUDES) $(EXTRA_INCLUDES) ALLDEFINES = $(ALLINCLUDES) $(EXTRA_DEFINES) $(DEFINES) $(GTKDEFINES) CFLAGS = $(EXTRA_CFLAGS) $(CDEBUGFLAGS) $(CCOPTIONS) $(ALLDEFINES) LDOPTIONS = $(CDEBUGFLAGS) $(CCOPTIONS) $(EXTRA_LDOPTIONS) # various auxiliary program and settings. ifeq ($(Win32),) # GNU make, we hope MAKE = make CP = cp -f RM = rm -f # makedep is a gcc-based makedepend that doesn't look at the # standard files. DEPEND = ./makedep DEPENDFLAGS = # perl is needed for generating several files PERL = perl # suffix of executables EXE = # programs used in installation MKDIRHIER = mkdir -p INSTALL = install INSTALLFLAGS = -c INSTPGMFLAGS = -s INSTMANFLAGS = -m 0444 else ifeq ($(Win32),1) # Windows with mingw but not msyste # GNU make, we hope MAKE = make CP = xcopy # suppress errors, since del complains if file not there to remove RM = -del /f # we don't have a makedepend on windows ... DEPEND = DEPENDFLAGS = # perl is needed for generating several files PERL = perl # suffix of executables EXE = .exe # programs used in installation: not in windows MKDIRHIER = INSTALL = INSTALLFLAGS = INSTPGMFLAGS = INSTMANFLAGS = else # windows with msys MAKE = make CP = cp -f RM = rm -f # we don't have a makedepend on windows ... DEPEND = DEPENDFLAGS = # perl is needed for generating several files PERL = perl # suffix of executables EXE = .exe # programs used in installation: not in windows MKDIRHIER = INSTALL = INSTALLFLAGS = INSTPGMFLAGS = INSTMANFLAGS = endif endif ### There should be no need to edit after this point, ### unless you're using a non-GNU make. all:: Makefile Makefile: Makefile.in $(RM) $@ $(CP) $< $@ make depend # Autogenerated files to do with the table manager ifdef TableManager TMAUTOGENS = enc_mcmsg.c enc_mpmsg.c dec_mcmsg.c dec_mpmsg.c mcmsg_union.h mcmsg_size.c mpmsg_union.h mpmsg_size.c mprotocol-enums.c mprotocol-enums.h else TMAUTOGENS = endif # This variable lists all the auto-generated program files. AUTOGENS = enc_cmsg.c enc_pmsg.c dec_cmsg.c dec_pmsg.c cmsg_union.h cmsg_size.c pmsg_union.h pmsg_size.c game-enums.c game-enums.h player-enums.c player-enums.h protocol-enums.c protocol-enums.h tiles-enums.c tiles-enums.h fbtiles.c version.h $(TMAUTOGENS) # This is a trick to make the auto-generated files be created # by make depend, if they're not already there. Otherwise the # make depend won't put them in the dependencies. depend:: $(AUTOGENS) echo Remade some auto-generated files # The programs ifdef TableManager PROGRAMS = xmj$(EXE) mj-server$(EXE) mj-player$(EXE) mj-manager$(EXE) else PROGRAMS = xmj$(EXE) mj-server$(EXE) mj-player$(EXE) endif all:: $(PROGRAMS) # The controller target. Requires player, tiles, protocol, sysdep SRCS1 = controller.c player.c tiles.c protocol.c game.c scoring.c sysdep.c $(MALLOCSRC) OBJS1 = controller.o player.o tiles.o protocol.o game.o scoring.o sysdep.o $(MALLOCOBJ) OBJS = $(OBJS1) $(OBJS2) $(OBJS3) $(OBJS4) SRCS = $(SRCS1) $(SRCS2) $(SRCS3) $(SRCS4) mj-server$(EXE): $(OBJS1) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) $(OBJS1) $(LDLIBS) $(EXTRA_LOAD_FLAGS) ifndef Win32 install:: mj-server$(EXE) @if [ -d $(DESTDIR)$(BINDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(BINDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTPGMFLAGS) mj-server$(EXE) $(DESTDIR)$(BINDIR)/mj-server install.man:: mj-server.man @if [ -d $(DESTDIR)$(MANDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(MANDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTMANFLAGS) mj-server.man $(DESTDIR)$(MANDIR)/mj-server.$(MANSUFFIX) endif # Win32 ifdef TableManager # The table manager. Requires mprotocol and sysdep SRCS4 = manager.c mprotocol.c sysdep.c $(MALLOCSRC) OBJS4 = manager.o mprotocol.o sysdep.o $(MALLOCOBJ) mj-manager$(EXE): $(OBJS4) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) $(OBJS4) $(LDLIBS) $(EXTRA_LOAD_FLAGS) ifndef Win32 install:: mj-manager$(EXE) @if [ -d $(DESTDIR)$(BINDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(BINDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTPGMFLAGS) mj-manager$(EXE) $(DESTDIR)$(BINDIR)/mj-manager install.man:: mj-manager.man @if [ -d $(DESTDIR)$(MANDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(MANDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTMANFLAGS) mj-manager.man $(DESTDIR)$(MANDIR)/mj-manager.$(MANSUFFIX) endif # Win32 endif # TableManager # the greedy player SRCS2 = greedy.c client.c player.c tiles.c protocol.c game.c sysdep.c $(MALLOCSRC) SRCS2A = client.c player.c tiles.c protocol.c game.c sysdep.c $(MALLOCSRC) OBJS2 = greedy.o client.o player.o tiles.o protocol.o game.o sysdep.o $(MALLOCOBJ) OBJS2A = client.o player.o tiles.o protocol.o game.o sysdep.o $(MALLOCOBJ) mj-player$(EXE): $(OBJS2) $(DEPLIBS2) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) $(OBJS2) $(LDLIBS) $(EXTRA_LOAD_FLAGS) ga: ga.o $(OBJS2A) $(DEPLIBS2) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) ga.o $(OBJS2A) $(LDLIBS) $(EXTRA_LOAD_FLAGS) # generic greedy variants greedy-%.o: greedy-%.c $(SRCS2A) greedy-%: greedy-%.o $(OBJS2A) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) $< $(OBJS2A) $(LDLIBS) $(EXTRA_LOAD_FLAGS) ifndef Win32 install:: mj-player$(EXE) @if [ -d $(DESTDIR)$(BINDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(BINDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTPGMFLAGS) mj-player$(EXE) $(DESTDIR)$(BINDIR)/mj-player install.man:: mj-player.man @if [ -d $(DESTDIR)$(MANDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(MANDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTMANFLAGS) mj-player.man $(DESTDIR)$(MANDIR)/mj-player.$(MANSUFFIX) endif # Win32 # the gui ifdef Win32 ifeq ($(Win32),3) WR = i686-w64-mingw32-windres else WR = windres endif # the icon resource file and object ICONOBJ = iconres.o iconres.o: iconres.rs icon.ico $(WR) iconres.rs iconres.o else ICONOBJ = endif SRCS3 = gui.c gui-dial.c lazyfixed.c vlazyfixed.c client.c player.c tiles.c fbtiles.c protocol.c game.c sysdep.c $(MALLOCSRC) OBJS3 = gui.o gui-dial.o client.o lazyfixed.o vlazyfixed.o player.o tiles.o fbtiles.o protocol.o game.o sysdep.o $(MALLOCOBJ) $(ICONOBJ) xmj$(EXE): $(OBJS3) $(DEPLIBS3) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) $(OBJS3) $(GUILIBS) $(LDLIBS) $(EXTRA_LOAD_FLAGS) install:: xmj$(EXE) @if [ -d $(DESTDIR)$(BINDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(BINDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTPGMFLAGS) xmj$(EXE) $(DESTDIR)$(BINDIR)/xmj install.man:: xmj.man @if [ -d $(DESTDIR)$(MANDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(MANDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTMANFLAGS) xmj.man $(DESTDIR)$(MANDIR)/xmj.$(MANSUFFIX) # version.h is now made from version.h.in by the release script. # so if we are not making a release, we'd better provide it if # it isn't there. version.h: cp version.h.in $@ chmod u+w $@ # rule to generate the fallback tiles fbtiles.c: $(FALLBACKTILES) makefallbacktiles Makefile $(PERL) makefallbacktiles $(FALLBACKTILES) >fbtiles.c # Rules for the auto generated files enc_cmsg.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -cmsg enc_pmsg.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -pmsg dec_cmsg.c: protocol.h proto-decode-msg.pl $(PERL) proto-decode-msg.pl -cmsg dec_pmsg.c: protocol.h proto-decode-msg.pl $(PERL) proto-decode-msg.pl -pmsg # rules for the other auxiliary files # the commands will be executed twice, but never mind. cmsg_union.h cmsg_size.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -cmsg pmsg_union.h pmsg_size.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -pmsg enc_mcmsg.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -mcmsg enc_mpmsg.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -mpmsg dec_mcmsg.c: protocol.h proto-decode-msg.pl $(PERL) proto-decode-msg.pl -mcmsg dec_mpmsg.c: protocol.h proto-decode-msg.pl $(PERL) proto-decode-msg.pl -mpmsg # rules for the other auxiliary files # the commands will be executed twice, but never mind. mcmsg_union.h mcmsg_size.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -mcmsg mpmsg_union.h mpmsg_size.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -mpmsg # Rule for the enum parsing files: %-enums.h: %.h make-enums.pl $(PERL) make-enums.pl $< %-enums.c: %.h make-enums.pl $(PERL) make-enums.pl $< # distclean also removes the auto-generated files and the Makefile distclean:: clean $(RM) Makefile $(AUTOGENS) # used to build a source release textdocs:: use.txt rules.txt use.txt rules.txt: xmj.man $(RM) use.txt rules.txt $(PERL) maketxt xmj.man chmod 444 use.txt rules.txt depend:: $(DEPEND) $(DEPENDFLAGS) -- $(ALLDEFINES) $(DEPEND_DEFINES) -- $(SRCS) clean:: $(RM) $(PROGRAMS) clean:: $(RM) *.o core errs *~ # DO NOT DELETE controller.o: controller.c controller.h tiles.h tiles-enums.h player.h \ player-enums.h protocol.h cmsg_union.h pmsg_union.h protocol-enums.h \ game.h game-enums.h scoring.h sysdep.h version.h player.o: player.c player.h tiles.h tiles-enums.h player-enums.h sysdep.h \ player-enums.c tiles.o: tiles.c tiles.h tiles-enums.h protocol.h player.h player-enums.h \ cmsg_union.h pmsg_union.h protocol-enums.h sysdep.h tiles-enums.c protocol.o: protocol.c protocol.h tiles.h tiles-enums.h player.h \ player-enums.h cmsg_union.h pmsg_union.h protocol-enums.h sysdep.h \ enc_cmsg.c dec_cmsg.c enc_pmsg.c dec_pmsg.c cmsg_size.c pmsg_size.c \ protocol-enums.c game.o: game.c game.h tiles.h tiles-enums.h player.h player-enums.h \ protocol.h cmsg_union.h pmsg_union.h protocol-enums.h game-enums.h \ sysdep.h game-enums.c scoring.o: scoring.c tiles.h tiles-enums.h player.h player-enums.h \ controller.h protocol.h cmsg_union.h pmsg_union.h protocol-enums.h \ game.h game-enums.h scoring.h sysdep.h sysdep.o: sysdep.c sysdep.h greedy.o: greedy.c client.h game.h tiles.h tiles-enums.h player.h \ player-enums.h protocol.h cmsg_union.h pmsg_union.h protocol-enums.h \ game-enums.h sysdep.h version.h client.o: client.c sysdep.h client.h game.h tiles.h tiles-enums.h \ player.h player-enums.h protocol.h cmsg_union.h pmsg_union.h \ protocol-enums.h game-enums.h player.o: player.c player.h tiles.h tiles-enums.h player-enums.h sysdep.h \ player-enums.c tiles.o: tiles.c tiles.h tiles-enums.h protocol.h player.h player-enums.h \ cmsg_union.h pmsg_union.h protocol-enums.h sysdep.h tiles-enums.c protocol.o: protocol.c protocol.h tiles.h tiles-enums.h player.h \ player-enums.h cmsg_union.h pmsg_union.h protocol-enums.h sysdep.h \ enc_cmsg.c dec_cmsg.c enc_pmsg.c dec_pmsg.c cmsg_size.c pmsg_size.c \ protocol-enums.c game.o: game.c game.h tiles.h tiles-enums.h player.h player-enums.h \ protocol.h cmsg_union.h pmsg_union.h protocol-enums.h game-enums.h \ sysdep.h game-enums.c sysdep.o: sysdep.c sysdep.h gui.o: gui.c gui.h lazyfixed.h sysdep.h vlazyfixed.h client.h game.h \ tiles.h tiles-enums.h player.h player-enums.h protocol.h cmsg_union.h \ pmsg_union.h protocol-enums.h game-enums.h version.h gtkrc.h gui-dial.o: gui-dial.c gui.h lazyfixed.h sysdep.h vlazyfixed.h client.h \ game.h tiles.h tiles-enums.h player.h player-enums.h protocol.h \ cmsg_union.h pmsg_union.h protocol-enums.h game-enums.h version.h lazyfixed.o: lazyfixed.c lazyfixed.h sysdep.h vlazyfixed.o: vlazyfixed.c vlazyfixed.h sysdep.h client.o: client.c sysdep.h client.h game.h tiles.h tiles-enums.h \ player.h player-enums.h protocol.h cmsg_union.h pmsg_union.h \ protocol-enums.h game-enums.h player.o: player.c player.h tiles.h tiles-enums.h player-enums.h sysdep.h \ player-enums.c tiles.o: tiles.c tiles.h tiles-enums.h protocol.h player.h player-enums.h \ cmsg_union.h pmsg_union.h protocol-enums.h sysdep.h tiles-enums.c fbtiles.o: fbtiles.c protocol.o: protocol.c protocol.h tiles.h tiles-enums.h player.h \ player-enums.h cmsg_union.h pmsg_union.h protocol-enums.h sysdep.h \ enc_cmsg.c dec_cmsg.c enc_pmsg.c dec_pmsg.c cmsg_size.c pmsg_size.c \ protocol-enums.c game.o: game.c game.h tiles.h tiles-enums.h player.h player-enums.h \ protocol.h cmsg_union.h pmsg_union.h protocol-enums.h game-enums.h \ sysdep.h game-enums.c sysdep.o: sysdep.c sysdep.h mj-1.17-src/gui-dial.c0000444006717300001440000045744215002771311013177 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/gui-dial.c,v 12.20 2025/04/25 17:40:40 jcb Exp $ * gui-dial.c * dialog box functions. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include "gui.h" #define GTK_WINDOW_DIALOG GTK_WINDOW_TOPLEVEL static const char rcs_id[] UNUSED = "$Header: /home/jcb/MahJong/newmj/RCS/gui-dial.c,v 12.20 2025/04/25 17:40:40 jcb Exp $"; static void continue_callback(GtkWidget *w, gpointer data); static void turn_callback(GtkWidget *w, gpointer data); static void debug_report_query_popup(void); /* useful things */ const char *windnames[] = { "none", "East", "South", "West", "North" }; const char *shortwindnames[] = { " ", "E", "S", "W", "N" }; /* check box to keep keyboard focus in message window */ static GtkWidget *mfocus; /* This grabs focus, unless the chat window is in the main window and has focus. This stops focus being lost from the chat entry box every time a dialog pops up. (Requested by users, to avoid accidental discards. Moreover, if the appropriate checkbox is set, it keeps the focus in the message window. */ static void grab_focus_if_appropriate(GtkWidget *w); /* Used sordidly and internally */ static GtkRequisition discard_req = { 0, 0}; /* Why an array? So I can pass pointers around */ DiscardDialog discard_dialog[1]; /* dialog box for specifying chows */ GtkWidget *chow_dialog; /* Stores the three TileSetBoxes */ static TileSetBox chowtsbs[3]; /* and the three buttons */ static GtkWidget *chowbuttons[3]; /* dialog box for declaring specials */ GtkWidget *ds_dialog; /* dialog box for continuing with next hand */ GtkWidget *continue_dialog; /* for end of game */ GtkWidget *end_dialog; /* dialog for opening connection */ GtkWidget *open_dialog; GtkWidget *openmenuentry, *newgamemenuentry, *resumegamemenuentry, *savemenuentry, *saveasmenuentry, *closemenuentry, *gameoptionsmenuentry; /* entry for showing warnings window */ static GtkWidget *warningentry; /* dialog box for action when it's our turn. Actions: Discard Kong Mah Jong As of 11.80, Kong includes adding a tile to an existing pung */ GtkWidget *turn_dialog; GtkWidget *turn_dialog_label; /* used to display number of tiles left */ /* dialog box for closed sets when scoring. Actions: Eyes Chow Pung Done */ GtkWidget *scoring_dialog; /* window for game status display */ GtkWidget *status_window; /* window for "about" information */ GtkWidget *about_window; /* window to nag for donations */ GtkWidget *nag_window; /* an array of text widgets for displaying scores etc. Element 4 is for settlements. The others are for each player: currently, I think these should be table relative. */ GtkWidget *scoring_notebook; GtkWidget *textpages[5]; GtkWidget *textlabels[5]; /* labels for the pages */ GtkWidget *textwindow; /* and the window for it */ /* Window for scoring history */ GtkWidget *scorehistorywindow; GtkWidget *scorehistorytext; /* The window for messages, and the display text widget */ GtkWidget *messagewindow, *messagetext; GtkTextBuffer *messagetextbuf; GtkTextIter messagetextiter; /* Warning window */ GtkWidget *warningwindow, *warningtext; GtkTextBuffer *warningtextbuf; GtkTextIter warningtextiter; /* The Save As.. dialog */ GtkWidget *save_window; /* and its text entry widget */ GtkWidget *save_text; /* Password entry dialog */ GtkWidget *password_window; /* and its text entry widget */ GtkWidget *password_text; /* The window for display options */ GtkWidget *display_option_dialog = NULL; /* the window for updating the game options */ GtkWidget *game_option_dialog = NULL; GtkWidget *game_option_panel = NULL; /* and some of its buttons */ GtkWidget *game_option_apply_button = NULL; GtkWidget *game_option_prefs_button = NULL; /* and a very similar one for option preferences */ GtkWidget *game_prefs_dialog = NULL; GtkWidget *game_prefs_panel = NULL; /* and one for playing preferences */ GtkWidget *playing_prefs_dialog = NULL; /* and one for debugging options */ static GtkWidget *debug_options_dialog = NULL; /* message entry widget */ static GtkWidget *message_entry = NULL; /* time at which progress bar was started */ static struct timeval pstart; static int pinterval = 25; /* timeout interval in ms */ static intptr_t pbar_timeout_instance = 0; /* track dead timeouts */ static GtkWidget *pbar; /* timeout handler for the dialog progress bar */ static int pbar_timeout(gpointer instance) { int timeleft; struct timeval timenow; if ( pbar_timeout_instance != (intptr_t) instance ) return FALSE; /* dead timeout */ if ( ! GTK_WIDGET_VISIBLE(discard_dialog->widget) ) return FALSE; gettimeofday(&timenow,NULL); timeleft = ptimeout-(1000*(timenow.tv_sec-pstart.tv_sec) +(timenow.tv_usec-pstart.tv_usec)/1000); if ( timeleft <= 0 ) { /* we should not hide the claim dialog: the timeout is really controlled by the server, not us */ /* However, if we are supposed to be handling timeouts locally, we'd better send a noclaim! */ if ( local_timeouts ) { disc_callback(NULL,(gpointer)NoClaim); } return FALSE; } gtk_progress_bar_update(GTK_PROGRESS_BAR(pbar),1.0-(timeleft+0.0)/(ptimeout+0.0)); return TRUE; } /* popup the discard dialog. Arguments: Tile, player whence it came (as an ori), mode = 0 (normal), 1 (claiming tile for mah jong), 2 (claiming from kong) */ void discard_dialog_popup(Tile t, int ori, int mode) { int i; char buf[128]; static Tile lastt; static int lastori, lastmode; /* So that we don't work if it's already popped up: */ if ( GTK_WIDGET_VISIBLE(discard_dialog->widget) && t == lastt && lastori == ori && lastmode == mode ) return; lastt = t; lastori = ori; lastmode = mode; if ( mode != discard_dialog->mode ) { discard_dialog->mode = mode; if ( mode == 0 ) { gtk_widget_show(discard_dialog->noclaim); gtk_widget_hide(discard_dialog->eyes); gtk_widget_show(discard_dialog->chow); gtk_widget_show(discard_dialog->pung); gtk_widget_hide(discard_dialog->special); gtk_widget_show(discard_dialog->kong); gtk_widget_show(discard_dialog->mahjong); gtk_widget_hide(discard_dialog->robkong); } else if ( mode == 1 ) { gtk_widget_hide(discard_dialog->noclaim); gtk_widget_show(discard_dialog->eyes); gtk_widget_show(discard_dialog->chow); gtk_widget_show(discard_dialog->pung); gtk_widget_show(discard_dialog->special); gtk_widget_hide(discard_dialog->kong); gtk_widget_hide(discard_dialog->mahjong); gtk_widget_hide(discard_dialog->robkong); } else { gtk_widget_show(discard_dialog->noclaim); gtk_widget_hide(discard_dialog->eyes); gtk_widget_hide(discard_dialog->chow); gtk_widget_hide(discard_dialog->pung); gtk_widget_hide(discard_dialog->special); gtk_widget_hide(discard_dialog->kong); gtk_widget_hide(discard_dialog->mahjong); gtk_widget_show(discard_dialog->robkong); } } if ( mode == 0 ) grab_focus_if_appropriate(discard_dialog->noclaim); /* set the appropriate tile */ for ( i=1 ; i < 4 ; i++ ) { if ( i == ori ) { button_set_tile(discard_dialog->tiles[i],t,i); gtk_widget_show(discard_dialog->tiles[i]); } else { gtk_widget_hide(discard_dialog->tiles[i]); } } gtk_widget_hide(discard_dialog->tilename); if ( mode == 1 ) { gtk_label_set_text(GTK_LABEL(discard_dialog->tilename), "Claim discard for:"); } else { /* if not showing wall, say how many tiles left */ if ( ! showwall ) { if ( ori == 1 ) sprintf(buf,"(%d tiles left) %s", the_game->wall.live_end-the_game->wall.live_used, tile_name(the_game->tile)); else sprintf(buf,"%s (%d tiles left)", tile_name(the_game->tile), the_game->wall.live_end-the_game->wall.live_used); } else { strcpy(buf,tile_name(the_game->tile)); } gtk_label_set_text(GTK_LABEL(discard_dialog->tilename),buf); } if ( mode == 0 ) { static const gfloat xal[] = { 0.5,1.0,0.5,0.0 }; gtk_misc_set_alignment(GTK_MISC(discard_dialog->tilename), xal[ori],0.5); } else { gtk_misc_set_alignment(GTK_MISC(discard_dialog->tilename), 0.5,0.5); } gtk_widget_show(discard_dialog->tilename); dialog_popup(discard_dialog->widget,DPCentred); /* and start the progress bar timeout if appropriate */ if ( the_game->state != MahJonging && ptimeout > 0 ) { gtk_widget_show(pbar); gettimeofday(&pstart,NULL); /* we may as well calculate an appropriate value of pbar_timeout each time... we want it to update every half pixel, or 40 times a second, whichever is slower */ if ( pbar->allocation.width > 1 ) { /* in case it isn't realized yet */ pinterval = ptimeout/(2*pbar->allocation.width); } if ( pinterval < 25 ) pinterval = 25; gtk_timeout_add(pinterval,pbar_timeout,(gpointer) ++pbar_timeout_instance); } else { gtk_widget_hide(pbar); } } /* this macro generates a variable for an accelerator group, and a function to add or remove it. The macro argument is included in the name of the variable and function */ #define ACCELFUN(NAME) \ static GtkAccelGroup *NAME ## _accel;\ static void add_or_remove_ ## NAME ## _accels(GtkWidget *w UNUSED, gpointer data)\ {\ static int there = 0;\ if ( NAME ## _accel == NULL ) return;\ if ( data ) {\ if (GTK_HAS_FOCUS & GTK_WIDGET_FLAGS(message_entry)) return;\ if ( ! there )\ gtk_window_add_accel_group(GTK_WINDOW(topwindow),NAME ## _accel);\ there = 1;\ } else {\ if ( there )\ gtk_window_remove_accel_group(GTK_WINDOW(topwindow),NAME ## _accel);\ there = 0;\ }\ } /* an accelerator group for the discard dialog */ ACCELFUN(discard) /* initialize it */ /* Structure: If the dialog is in the middle: lefttile opptile righttile tile name progress bar Pass/Draw Chow Pung Kong MahJong otherwise: tilename progress bar buttons */ void discard_dialog_init(void) { GtkWidget *box, *tilebox, *left, *opp, *right, *lbl, *butbox, *but, *pixm; DiscardDialog *dd = &discard_dialog[0]; if ( dd->widget ) { gtk_widget_destroy(dd->widget); dd->widget = NULL; } switch ( dialogs_position ) { case DialogsCentral: case DialogsUnspecified: /* event box so there's a window to have background */ dd->widget = gtk_event_box_new(); gtk_fixed_put(GTK_FIXED(discard_area),dd->widget,0,0); /* it'll be moved later */ break; case DialogsBelow: dd->widget = gtk_event_box_new(); gtk_box_pack_start(GTK_BOX(dialoglowerbox),dd->widget,1,0,0); /* show it, so that the top window includes it when first mapped */ gtk_widget_show(dd->widget); break; case DialogsPopup: dd->widget = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (dd->widget), "delete_event", GTK_SIGNAL_FUNC (gtk_widget_hide), NULL); } box = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(dd->widget),box); dd->mode = -1; /* so that personality will be set */ gtk_container_set_border_width(GTK_CONTAINER(box), dialog_border_width); tilebox = gtk_hbox_new(0,0); if ( dialogs_position != DialogsBelow ) gtk_widget_show(tilebox); left = gtk_button_new(); GTK_WIDGET_UNSET_FLAGS(left,GTK_CAN_FOCUS); gtk_widget_show(left); pixm = gtk_pixmap_new(tilepixmaps[3][HiddenTile],NULL); gtk_widget_show(pixm); gtk_container_add(GTK_CONTAINER(left),pixm); opp = gtk_button_new(); GTK_WIDGET_UNSET_FLAGS(opp,GTK_CAN_FOCUS); gtk_widget_show(opp); pixm = gtk_pixmap_new(tilepixmaps[2][HiddenTile],NULL); gtk_widget_show(pixm); gtk_container_add(GTK_CONTAINER(opp),pixm); right = gtk_button_new(); GTK_WIDGET_UNSET_FLAGS(right,GTK_CAN_FOCUS); gtk_widget_show(right); pixm = gtk_pixmap_new(tilepixmaps[1][HiddenTile],NULL); gtk_widget_show(pixm); gtk_container_add(GTK_CONTAINER(right),pixm); gtk_box_pack_start(GTK_BOX(tilebox),left,0,0,0); gtk_box_pack_start(GTK_BOX(tilebox),opp,1,0,0); gtk_box_pack_end(GTK_BOX(tilebox),right,0,0,0); dd->tiles[1] = right; dd->tiles[2] = opp; dd->tiles[3] = left; lbl = gtk_label_new("name of tile"); gtk_widget_show(lbl); dd->tilename = lbl; butbox = gtk_hbox_new(1,dialog_button_spacing); /* homogeneous, spaced */ gtk_widget_show(butbox); /* accelerators for discard dialog actions */ discard_accel = gtk_accel_group_new(); but = gtk_button_new_with_label("No claim"); gtk_widget_show(but); gtk_box_pack_start(GTK_BOX(butbox),but,1,1,0); gtk_signal_connect(GTK_OBJECT(but),"clicked", GTK_SIGNAL_FUNC(disc_callback),(gpointer)NoClaim); dd->noclaim = but; gtk_widget_add_accelerator(GTK_WIDGET(but),"clicked",discard_accel,GDK_n,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(but)->child),"_"); but = gtk_button_new_with_label("Eyes"); GTK_WIDGET_UNSET_FLAGS(but,GTK_CAN_FOCUS); /* not shown in normal state */ gtk_box_pack_start(GTK_BOX(butbox),but,1,1,0); gtk_signal_connect(GTK_OBJECT(but),"clicked" ,GTK_SIGNAL_FUNC(disc_callback),(gpointer)PairClaim); dd->eyes = but; gtk_widget_add_accelerator(GTK_WIDGET(but),"clicked",discard_accel,GDK_e,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(but)->child),"_"); but = gtk_button_new_with_label("Chow"); GTK_WIDGET_UNSET_FLAGS(but,GTK_CAN_FOCUS); gtk_widget_show(but); gtk_box_pack_start(GTK_BOX(butbox),but,1,1,0); gtk_signal_connect(GTK_OBJECT(but),"clicked" ,GTK_SIGNAL_FUNC(disc_callback),(gpointer)ChowClaim); dd->chow = but; gtk_widget_add_accelerator(GTK_WIDGET(but),"clicked",discard_accel,GDK_c,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(but)->child),"_"); but = gtk_button_new_with_label("Pung"); GTK_WIDGET_UNSET_FLAGS(but,GTK_CAN_FOCUS); gtk_widget_show(but); gtk_box_pack_start(GTK_BOX(butbox),but,1,1,0); gtk_signal_connect(GTK_OBJECT(but),"clicked",GTK_SIGNAL_FUNC(disc_callback),(gpointer)PungClaim); dd->pung = but; gtk_widget_add_accelerator(GTK_WIDGET(but),"clicked",discard_accel,GDK_p,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(but)->child),"_"); but = gtk_button_new_with_label("Special Hand"); GTK_WIDGET_UNSET_FLAGS(but,GTK_CAN_FOCUS); /* gtk_widget_show(but); */ /* don't show this; uses other space */ gtk_box_pack_start(GTK_BOX(butbox),but,1,1,0); gtk_signal_connect(GTK_OBJECT(but),"clicked",GTK_SIGNAL_FUNC(disc_callback),(gpointer)SpecialSetClaim); dd->special = but; gtk_widget_add_accelerator(GTK_WIDGET(but),"clicked",discard_accel,GDK_s,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(but)->child),"_"); but = gtk_button_new_with_label("Kong"); GTK_WIDGET_UNSET_FLAGS(but,GTK_CAN_FOCUS); gtk_widget_show(but); gtk_box_pack_start(GTK_BOX(butbox),but,1,1,0); gtk_signal_connect(GTK_OBJECT(but),"clicked",GTK_SIGNAL_FUNC(disc_callback),(gpointer)KongClaim); dd->kong = but; gtk_widget_add_accelerator(GTK_WIDGET(but),"clicked",discard_accel,GDK_k,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(but)->child),"_"); but = gtk_button_new_with_label("Mah Jong"); GTK_WIDGET_UNSET_FLAGS(but,GTK_CAN_FOCUS); gtk_widget_show(but); gtk_box_pack_start(GTK_BOX(butbox),but,1,1,0); gtk_signal_connect(GTK_OBJECT(but),"clicked",GTK_SIGNAL_FUNC(disc_callback),(gpointer)MahJongClaim); dd->mahjong = but; gtk_widget_add_accelerator(GTK_WIDGET(but),"clicked",discard_accel,GDK_m,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(but)->child),"_"); but = gtk_button_new_with_label("Rob the Kong - Mah Jong!"); GTK_WIDGET_UNSET_FLAGS(but,GTK_CAN_FOCUS); /* gtk_widget_show(but); */ /* don't show this; it uses the space of others */ gtk_box_pack_start(GTK_BOX(butbox),but,1,1,0); gtk_signal_connect(GTK_OBJECT(but),"clicked",GTK_SIGNAL_FUNC(disc_callback),(gpointer)MahJongClaim); dd->robkong = but; gtk_widget_add_accelerator(GTK_WIDGET(but),"clicked",discard_accel,GDK_r,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(but)->child),"_"); pbar = gtk_progress_bar_new(); gtk_widget_show(pbar); /* These are packed in reverse order so they float to the bottom */ gtk_box_pack_end(GTK_BOX(box),butbox,0,0,0); gtk_box_pack_end(GTK_BOX(box),lbl,0,0,0); gtk_box_pack_end(GTK_BOX(box),pbar,0,0,0); gtk_box_pack_end(GTK_BOX(box),tilebox,0,0,0); /* OK, now ask its size: store the result, and keep it this size for ever more */ gtk_widget_size_request(dd->widget,&discard_req); gtk_widget_set_usize(dd->widget,discard_req.width,discard_req.height); /* we have to ensure that the accelerators are only available when the widget is popped up */ gtk_signal_connect(GTK_OBJECT(discard_dialog->widget),"hide",GTK_SIGNAL_FUNC(add_or_remove_discard_accels),(gpointer)0); gtk_signal_connect(GTK_OBJECT(discard_dialog->widget),"show",GTK_SIGNAL_FUNC(add_or_remove_discard_accels),(gpointer)1); } static GtkWidget *turn_dialog_discard_button; static GtkWidget *turn_dialog_calling_button; void turn_dialog_popup(void) { /* only original call is allowed, so hide the calling button after first discard */ if ( pflag(our_player,NoDiscard) ) gtk_widget_show(turn_dialog_calling_button); else gtk_widget_hide(turn_dialog_calling_button); /* if not showing wall, put tiles left in label */ if ( ! showwall ) { char buf[128]; sprintf(buf,"(%d tiles left) Select tile and:", the_game->wall.live_end-the_game->wall.live_used); gtk_label_set_text(GTK_LABEL(turn_dialog_label),buf); } else { gtk_label_set_text(GTK_LABEL(turn_dialog_label),"Select tile and:"); } dialog_popup(turn_dialog,DPOnDiscardOnce); grab_focus_if_appropriate(turn_dialog_discard_button); } /* callback when we toggle one of our tiles. data is our index number. If data also has bit 7 set, force the tile active. If called with NULL widget, clear selection */ void conc_callback(GtkWidget *w, gpointer data) { int active; int i; int force=0, index=-1; active = (w && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))); if ( active ) index = ((int)(intptr_t)data)& 127; force = (w && (((int)(intptr_t)data) & 128)); if ( w && just_doubleclicked == w) force = 1; /* make sure all other tiles are unselected, if we're active, or if we're called to clear: we don't just rely on the selected variable, since under some circumstances we can end up with two tiles active, by accident as it were */ /* FIXME: this relies on induced callbacks being executed synchronously */ if ( active || w == NULL) { for ( i = 0; i<14; i++ ) { if ( pdisps[0].conc[i] && pdisps[0].conc[i] != w) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pdisps[0].conc[i]), FALSE); } } } selected_button = index; if ( w == NULL ) return; /* change the label on the discard button to be declare if this is a flower */ gtk_label_set_text(GTK_LABEL(GTK_BIN(turn_dialog_discard_button)->child), is_special(our_player->concealed[index]) ? "Declare" : "Discard"); if ( force ) { /* if it's not active, set it active: we'll then be invoked normally, so we just return now. */ if ( ! active ) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),TRUE); return; } } /* if we were double clicked, invoke the turn callback directly */ if ( w && just_doubleclicked == w) { just_doubleclicked = 0; turn_callback(w,(gpointer)PMsgDiscard); } } /* This detects doubleclicks on the concealed buttons */ gint doubleclicked(GtkWidget *w, GdkEventButton *eb,gpointer data UNUSED) { if ( eb->type != GDK_2BUTTON_PRESS ) return FALSE; /* This is disgusting. We set a global doubleclicked flag, which is noticed by the toggle callback */ just_doubleclicked = w; return FALSE; } /* callback attached to the buttons of the discarding dialog. They pass PMsgDiscard, PMsgDeclareClosedKong, PMsgAddToPung, or PMsgMahJong. As of 11.80, PMsgDeclareClosedKong may mean that or AddToPung, and we must work out which here. Passed PMsgDiscard + 1000000 to declare calling. Also invoked by the declaring special callback: with DeclareSpecial to declare a special, Kong if appropriate, and NoClaim to indicate the end of declaration. Also invoked by scoring dialog with appropriate values */ static void turn_callback(GtkWidget *w UNUSED, gpointer data) { PMsgUnion m; Tile selected_tile; /* it is possible for this to be invoked when it shouldn't be, for example double-clicking on a tile (or hitting space on a tile?) when no dialog is up. So let's check that one of possible dialogs is up */ if ( ! (GTK_WIDGET_VISIBLE(turn_dialog) || GTK_WIDGET_VISIBLE(ds_dialog) || GTK_WIDGET_VISIBLE(scoring_dialog) ) ) return; selected_tile = (selected_button < 0) ? HiddenTile : our_player->concealed[selected_button]; int calling = 0; m.type = (PlayerMsgType)data; /* unwind the dirty hack */ if ( m.type > 1000000 ) { m.type -= 1000000 ; calling = 1 ; } if ( m.type == PMsgMahJong ) { // set the discard to 0 for cleanliness. m.mahjong.discard = 0; send_packet(&m); return; } if ( m.type == PMsgDeclareClosedKong ) { if ( player_can_declare_closed_kong(our_player,selected_tile) ) { /* that's fine */ } else { /* assume they mean add to pung */ m.type = PMsgAddToPung; } } if ( m.type == PMsgShowTiles || m.type == PMsgFormClosedSpecialSet ) { closed_set_in_progress = 1; send_packet(&m); return; } if ( the_game->state == DeclaringSpecials && m.type == PMsgNoClaim ) { selected_tile = HiddenTile; m.type = PMsgDeclareSpecial; conc_callback(NULL,NULL); /* clear the selection */ } /* in declaring specials, use this to finish */ if ( selected_tile == HiddenTile ) { if ( the_game->state == DeclaringSpecials ) m.type = PMsgDeclareSpecial; else { error_dialog_popup("No tile selected!"); return; } } if ( is_special(selected_tile) ) m.type = PMsgDeclareSpecial; switch ( m.type ) { case PMsgDeclareSpecial: m.declarespecial.tile = selected_tile; break; case PMsgDiscard: if ( selected_button >= 0 ) player_set_discard_hint(our_player,selected_button); m.discard.tile = selected_tile; m.discard.calling = calling; break; case PMsgDeclareClosedKong: m.declareclosedkong.tile = selected_tile; break; case PMsgAddToPung: m.addtopung.tile = selected_tile; break; case PMsgFormClosedPair: m.formclosedpair.tile = selected_tile; closed_set_in_progress = 1; break; case PMsgFormClosedChow: m.formclosedchow.tile = selected_tile; closed_set_in_progress = 1; break; case PMsgFormClosedPung: m.formclosedpung.tile = selected_tile; closed_set_in_progress = 1; break; default: warn("bad type in turn_callback"); return; } send_packet(&m); } /* callback when one of the discard dialog buttons is clicked */ void disc_callback(GtkWidget *w UNUSED, gpointer data) { PMsgUnion m; switch ( (intptr_t) data) { case NoClaim: m.type = PMsgNoClaim; m.noclaim.discard = the_game->serial; break; case ChowClaim: m.type = PMsgChow; m.chow.discard = the_game->serial; m.chow.cpos = AnyPos; /* worry about it later */ break; case PairClaim: m.type = PMsgPair; break; case SpecialSetClaim: m.type = PMsgSpecialSet; break; case PungClaim: m.type = PMsgPung; m.pung.discard = the_game->serial; break; case KongClaim: m.type = PMsgKong; m.kong.discard = the_game->serial; break; case MahJongClaim: m.type = PMsgMahJong; m.mahjong.discard = the_game->serial; break; default: warn("disc callback called with unexpected data"); return; } /* If the server has a protocol version before 1050, we mustn't send an AnyPos while mahjonging, as it doesn't know how to handle it; so pop up the chow dialog directly */ if ( server_pversion < 1050 && the_game->state == MahJonging && m.type == PMsgChow ) { do_chow(NULL,(gpointer)AnyPos); } else { send_packet(&m); } } ACCELFUN(chow) /* Now create the Chow dialog box: Structure: three boxes, each containing a tileset showing a possible chow. Below that, a label "which chow?". Below that, three buttons to select. */ void chow_dialog_init(void) { GtkWidget *box,*u,*v; int i; if ( chow_dialog ) { gtk_widget_destroy(chow_dialog); chow_dialog = NULL; } switch ( dialogs_position ) { case DialogsCentral: case DialogsUnspecified: chow_dialog = gtk_event_box_new(); gtk_fixed_put(GTK_FIXED(discard_area),chow_dialog,0,0); break; case DialogsPopup: case DialogsBelow: chow_dialog = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (chow_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); /* This one is allowed to shrink, and should */ gtk_window_set_policy(GTK_WINDOW(chow_dialog),1,1,1); } box = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(box), dialog_border_width); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(chow_dialog),box); u = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(u); gtk_box_pack_start(GTK_BOX(box),u,0,0,0); for (i=0;i<3;i++) { v = gtk_hbox_new(0,0); gtk_widget_show(v); gtk_box_pack_start(GTK_BOX(u),v,0,0,0); chowtsbs[i].widget = v; tilesetbox_init(&chowtsbs[i],0,GTK_SIGNAL_FUNC(do_chow),(gpointer)(intptr_t)i); } u = gtk_label_new("Which chow?"); gtk_widget_show(u); gtk_box_pack_start(GTK_BOX(box),u,0,0,0); u = gtk_hbox_new(1,dialog_button_spacing); gtk_widget_show(u); gtk_box_pack_start(GTK_BOX(box),u,0,0,0); chow_accel = gtk_accel_group_new(); for (i=0;i<3;i++) { static int keys[] = { GDK_l, GDK_m, GDK_u }; v = gtk_button_new_with_label(player_print_ChowPosition(i)); GTK_WIDGET_UNSET_FLAGS(v,GTK_CAN_FOCUS); gtk_widget_show(v); gtk_box_pack_start(GTK_BOX(u),v,1,1,0); chowbuttons[i] = v; gtk_signal_connect(GTK_OBJECT(v),"clicked",GTK_SIGNAL_FUNC(do_chow),(gpointer)(intptr_t)i); gtk_widget_add_accelerator(GTK_WIDGET(v),"clicked",chow_accel,keys[i],0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(v)->child),"_"); } gtk_signal_connect(GTK_OBJECT(chow_dialog),"hide",GTK_SIGNAL_FUNC(add_or_remove_chow_accels),(gpointer)0); gtk_signal_connect(GTK_OBJECT(chow_dialog),"show",GTK_SIGNAL_FUNC(add_or_remove_chow_accels),(gpointer)1); } /* now create the declaring specials dialog. Contains a button for declare (special), kong, and done. If no flowers, just kongs. */ static GtkWidget *ds_dialog_declare_button; static GtkWidget *ds_dialog_finish_button; ACCELFUN(ds) void ds_dialog_init(void) { GtkWidget *box, *bbox, *w; if ( ds_dialog ) { gtk_widget_destroy(ds_dialog); ds_dialog = NULL; } switch ( dialogs_position ) { case DialogsCentral: case DialogsUnspecified: ds_dialog = gtk_event_box_new(); gtk_fixed_put(GTK_FIXED(discard_area),ds_dialog,0,0); break; case DialogsBelow: ds_dialog = gtk_event_box_new(); gtk_box_pack_start(GTK_BOX(dialoglowerbox),ds_dialog,1,0,0); break; case DialogsPopup: ds_dialog = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (ds_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); } box = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(box), dialog_border_width); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(ds_dialog),box); bbox = gtk_hbox_new(1,dialog_button_spacing); gtk_widget_show(bbox); gtk_box_pack_end(GTK_BOX(box),bbox,0,0,0); if ( game_get_option_value(the_game,GOFlowers,NULL).optbool ) w = gtk_label_new("Declare Flowers/Seasons (and kongs)\nSelect tile and:"); else w = gtk_label_new("Declare kongs\nSelect tile and:"); gtk_widget_show(w); gtk_box_pack_end(GTK_BOX(box),w,0,0,0); ds_accel = gtk_accel_group_new(); if ( game_get_option_value(the_game,GOFlowers,NULL).optbool ) { ds_dialog_declare_button = w = gtk_button_new_with_label("Declare"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(bbox),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgDeclareSpecial); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",ds_accel,GDK_d,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); } w = gtk_button_new_with_label("Kong"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(bbox),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgDeclareClosedKong); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",ds_accel,GDK_k,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); ds_dialog_finish_button = w = gtk_button_new_with_label("Finish"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(bbox),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgNoClaim); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",ds_accel,GDK_f,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); gtk_signal_connect(GTK_OBJECT(ds_dialog),"hide",GTK_SIGNAL_FUNC(add_or_remove_ds_accels),(gpointer)0); gtk_signal_connect(GTK_OBJECT(ds_dialog),"show",GTK_SIGNAL_FUNC(add_or_remove_ds_accels),(gpointer)1); } void ds_dialog_popup(void) { dialog_popup(ds_dialog,DPCentredOnce); if ( game_get_option_value(the_game,GOFlowers,NULL).optbool ) grab_focus_if_appropriate(ds_dialog_declare_button); else grab_focus_if_appropriate(ds_dialog_finish_button); } /* dialog to ask to continue with next hand */ static GtkWidget *continue_dialog_continue_button; static GtkWidget *continue_dialog_label; void continue_dialog_init(void) { GtkWidget *box, *w; if ( continue_dialog ) { gtk_widget_destroy(continue_dialog); continue_dialog = NULL; } switch ( dialogs_position ) { case DialogsCentral: case DialogsUnspecified: continue_dialog = gtk_event_box_new(); gtk_fixed_put(GTK_FIXED(discard_area),continue_dialog,0,0); break; case DialogsBelow: continue_dialog = gtk_event_box_new(); gtk_box_pack_start(GTK_BOX(dialoglowerbox),continue_dialog,1,0,0); break; case DialogsPopup: continue_dialog = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (continue_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); } box = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(box), dialog_border_width); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(continue_dialog),box); continue_dialog_continue_button = w = gtk_button_new_with_label("Ready"); gtk_widget_show(w); gtk_box_pack_end(GTK_BOX(box),w,0,0,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(continue_callback),(gpointer)(intptr_t)PMsgDiscard); /* created and packed second so things float to bottom */ continue_dialog_label = w = gtk_label_new("Here is some dummy text to space"); gtk_widget_show(w); gtk_box_pack_end(GTK_BOX(box),w,0,0,0); } void continue_dialog_popup(void) { static char buf[256]; /* the text of the display depends on whether we've said we're ready */ if ( ! the_game->active ) { strcpy(buf,"Waiting for game to (re)start"); gtk_label_set_text(GTK_LABEL(continue_dialog_label),buf); gtk_widget_hide(continue_dialog_continue_button); } else if ( the_game->ready[our_seat] ) { strcpy(buf,"Waiting for others "); strcat(buf,the_game->paused); gtk_label_set_text(GTK_LABEL(continue_dialog_label),buf); gtk_widget_hide(continue_dialog_continue_button); } else { strcpy(buf,"Ready "); strcat(buf,the_game->paused); strcat(buf,"?"); gtk_label_set_text(GTK_LABEL(continue_dialog_label),buf); gtk_widget_show(continue_dialog_continue_button); grab_focus_if_appropriate(continue_dialog_continue_button); } dialog_popup(continue_dialog,DPCentredOnce); } static void continue_callback(GtkWidget *w UNUSED, gpointer data UNUSED) { PMsgReadyMsg rm; rm.type = PMsgReady; send_packet(&rm); } /* dialog to ask to close at end of game */ static GtkWidget *end_dialog_end_button; static GtkWidget *end_dialog_label; static void end_callback(GtkWidget *w UNUSED, gpointer data UNUSED); void end_dialog_init(void) { GtkWidget *box, *w; if ( end_dialog ) { gtk_widget_destroy(end_dialog); end_dialog = NULL; } switch ( dialogs_position ) { case DialogsCentral: case DialogsUnspecified: end_dialog = gtk_event_box_new(); gtk_fixed_put(GTK_FIXED(discard_area),end_dialog,0,0); break; case DialogsBelow: end_dialog = gtk_event_box_new(); gtk_box_pack_start(GTK_BOX(dialoglowerbox),end_dialog,1,0,0); break; case DialogsPopup: end_dialog = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (end_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); } box = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(box), dialog_border_width); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(end_dialog),box); end_dialog_end_button = w = gtk_button_new_with_label("Done"); gtk_widget_show(w); gtk_box_pack_end(GTK_BOX(box),w,0,0,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(end_callback),(gpointer)(intptr_t)PMsgDiscard); /* created and packed second so things float to bottom */ end_dialog_label = w = gtk_label_new("GAME OVER"); gtk_widget_show(w); gtk_box_pack_end(GTK_BOX(box),w,0,0,0); } void end_dialog_popup(void) { grab_focus_if_appropriate(end_dialog_end_button); dialog_popup(end_dialog,DPCentredOnce); } static void end_callback(GtkWidget *w UNUSED, gpointer data UNUSED) { close_connection(0); gtk_widget_hide(end_dialog); status_showraise(); } /* how many are up; used for positioning - not multi-thread safe! */ static int num_error_dialogs = 0; /* function called to close an error dialog */ static void kill_error(GtkObject *data) { gtk_widget_destroy(GTK_WIDGET(data)); num_error_dialogs--; } /* popup an error box (new for each message), or an info box (which doesn't have error tile in it)' */ static void _error_dialog_popup(char *msg,int iserror) { GtkWidget *box, *hbox, *w, *error_dialog, *error_message; if ( topwindow == NULL ) { warn("error_dialog_popup: windows not initialized"); return; } if ( num_error_dialogs >= 10 ) { warn("Too many error dialogs up to print error: %s",msg); return; } error_dialog = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect_object(GTK_OBJECT (error_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(error_dialog)); gtk_container_set_border_width(GTK_CONTAINER(error_dialog), dialog_border_width); box = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(error_dialog),box); hbox = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(hbox); gtk_box_pack_start(GTK_BOX(box),hbox,0,0,0); if ( iserror ) { w = gtk_pixmap_new(tilepixmaps[0][ErrorTile],NULL); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(hbox),w,0,0,0); } error_message = gtk_label_new(msg); gtk_widget_show(error_message); gtk_box_pack_start(GTK_BOX(hbox),error_message,0,0,0); w = gtk_button_new_with_label("OK"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(box),w,0,0,0); gtk_signal_connect_object(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(kill_error),GTK_OBJECT(error_dialog)); gtk_window_set_focus(GTK_WINDOW(error_dialog),w); dialog_popup(error_dialog,DPErrorPos); num_error_dialogs++; gdk_window_raise(error_dialog->window); } void error_dialog_popup(char *msg) { _error_dialog_popup(msg,1); } void info_dialog_popup(char *msg) { _error_dialog_popup(msg,0); } GtkWidget *openfile, *openhost, *openport, *openfiletext, *openhosttext, *openporttext, *openidtext, *opennametext, *opengamefilechooser, *opendifficulty, *opendifficultyspinbutton, *opengamefile, *opengamefiletext, *opentimeout; GtkWidget *openallowdisconnectbutton,*opensaveonexitbutton,*openrandomseatsbutton, *openplayercheckboxes[3],*openplayernames[3],*openplayeroptions[3],*opentimeoutspinbutton; static GtkWidget *opengamepanel,*openplayeroptionboxes[3], *openconnectbutton,*openstartbutton, *openresumebutton, *openhosttoggle, *openunixtoggle; /* callback used in next function */ static void openbut_callback(GtkWidget *w, gpointer data) { int active = GTK_TOGGLE_BUTTON(w)->active; switch ( (int)(intptr_t)data ) { case 1: if ( active ) { /* selected network */ gtk_widget_set_sensitive(openfile,0); gtk_widget_set_sensitive(openhost,1); gtk_widget_set_sensitive(openport,1); } break; case 2: if ( active ) { /* selected Unix socket */ gtk_widget_set_sensitive(openfile,1); gtk_widget_set_sensitive(openhost,0); gtk_widget_set_sensitive(openport,0); } break; case 10: case 11: case 12: gtk_widget_set_sensitive(openplayeroptionboxes[((int)(intptr_t)data)-10],active); break; } } /* callback for the file chooser dialog used below */ static void chooser_callback(GtkDialog *w UNUSED, gint rid, gpointer userdata UNUSED) { if ( rid == GTK_RESPONSE_ACCEPT ) { gchar *f = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(opengamefilechooser)); gchar *buf; if ( f != NULL && (buf = g_filename_to_utf8(f,-1,NULL,NULL,NULL)) != NULL ) { gchar cbuf[2*PATH_MAX+1]; getcwd(cbuf,2*PATH_MAX); gchar *b = buf; if ( strncmp(cbuf,buf,strlen(cbuf)) == 0 ) { b = buf + strlen(cbuf); #ifdef WIN32 if ( *b == '\\' ) b++; #else if ( *b == '/' ) b++; #endif } gtk_entry_set_text(GTK_ENTRY(opengamefiletext),b); } else { error_dialog_popup("Unexpected error choosing file!"); } gtk_widget_hide(opengamefilechooser); g_free(f); g_free(buf); } else { gtk_widget_hide(opengamefilechooser); } } /* the dialog for opening a connection. Arguments are initial values for id, name text entry fields, */ void open_dialog_init(char *idt, char *nt) { /* Layout of the box: x Connect to host x Use Unix socket Hostname ........ Port .... Filename .... Player ID .... Name .... OPEN CANCEL */ int i; GtkWidget *box, *bbox, *but1, *but2, *w1, *w2; open_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_policy(GTK_WINDOW(open_dialog),FALSE,FALSE,TRUE); gtk_signal_connect (GTK_OBJECT (open_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); gtk_container_set_border_width(GTK_CONTAINER(open_dialog), dialog_border_width); box = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(open_dialog),box); w1 = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(box),w1,0,0,0); openhosttoggle = but1 = gtk_radio_button_new_with_label(NULL,"Internet server"); GTK_WIDGET_UNSET_FLAGS(but1,GTK_CAN_FOCUS); #ifndef WIN32 gtk_widget_show(but1); #endif gtk_box_pack_start(GTK_BOX(w1),but1,0,0,0); openunixtoggle = but2 = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(but1)),"Unix socket server"); GTK_WIDGET_UNSET_FLAGS(but2,GTK_CAN_FOCUS); #ifndef WIN32 gtk_widget_show(but2); #endif gtk_box_pack_end(GTK_BOX(w1),but2,0,0,0); gtk_signal_connect(GTK_OBJECT(but1),"toggled",GTK_SIGNAL_FUNC(openbut_callback),(gpointer)1); gtk_signal_connect(GTK_OBJECT(but2),"toggled",GTK_SIGNAL_FUNC(openbut_callback),(gpointer)2); w2 = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(box),w2,0,0,0); openhost = gtk_hbox_new(0,0); gtk_widget_show(openhost); gtk_box_pack_start(GTK_BOX(w2),openhost,0,0,0); w1 = gtk_label_new("Host: "); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(openhost),w1,0,0,0); openhosttext = gtk_entry_new(); gtk_widget_show(openhosttext); gtk_box_pack_start(GTK_BOX(openhost),openhosttext,0,0,0); openport = gtk_hbox_new(0,0); gtk_widget_show(openport); gtk_box_pack_start(GTK_BOX(w2),openport,0,0,0); w1 = gtk_label_new("Port: "); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(openport),w1,0,0,0); openporttext = gtk_entry_new_with_max_length(5); gtk_widget_set_usize(openporttext,75,0); gtk_widget_show(openporttext); gtk_box_pack_start(GTK_BOX(openport),openporttext,0,0,0); openfile = gtk_hbox_new(0,0); #ifndef WIN32 gtk_widget_show(openfile); #endif gtk_box_pack_start(GTK_BOX(box),openfile,0,0,0); w1 = gtk_label_new("Socket file: "); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(openfile),w1,0,0,0); openfiletext = gtk_entry_new(); gtk_widget_show(openfiletext); gtk_box_pack_start(GTK_BOX(openfile),openfiletext,0,0,0); opengamefile = gtk_hbox_new(0,0); gtk_widget_show(opengamefile); gtk_box_pack_start(GTK_BOX(box),opengamefile,0,0,0); w1 = gtk_label_new("Saved game file: "); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(opengamefile),w1,0,0,0); opengamefiletext = gtk_entry_new(); gtk_widget_show(opengamefiletext); gtk_box_pack_start(GTK_BOX(opengamefile),opengamefiletext,0,0,0); opengamefilechooser = gtk_file_chooser_dialog_new("Resume from...", GTK_WINDOW(open_dialog), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(opengamefilechooser),1); char fbuf[PATH_MAX+1]; if ( getcwd(fbuf,PATH_MAX) != NULL ) { gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(opengamefilechooser), fbuf); } GtkFileFilter *filter = gtk_file_filter_new(); gtk_file_filter_add_pattern(filter, "*.mjs"); gtk_file_filter_set_name(filter,".mjs files"); GtkFileFilter *afilter = gtk_file_filter_new(); gtk_file_filter_add_pattern(afilter, "*"); gtk_file_filter_set_name(afilter,"All files"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(opengamefilechooser),filter); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(opengamefilechooser),afilter); gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(opengamefilechooser),filter); gtk_signal_connect(GTK_OBJECT(opengamefilechooser),"response", GTK_SIGNAL_FUNC(chooser_callback),NULL); w1 = gtk_button_new_with_label("Browse..."); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(opengamefile),w1,0,0,0); gtk_signal_connect_object(GTK_OBJECT(w1),"clicked",GTK_SIGNAL_FUNC(gtk_widget_show),GTK_OBJECT(opengamefilechooser)); w2 = gtk_hbox_new(0,0); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(box),w2,0,0,0); w1 = gtk_label_new("Player ID: "); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); openidtext = gtk_entry_new(); gtk_widget_show(openidtext); gtk_entry_set_text(GTK_ENTRY(openidtext),idt); gtk_box_pack_start(GTK_BOX(w2),openidtext,0,0,0); w2 = gtk_hbox_new(0,0); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(box),w2,0,0,0); w1 = gtk_label_new("Name: "); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); opennametext = gtk_entry_new(); gtk_widget_show(opennametext); if ( ! nt || !nt[0] ) nt = getenv("LOGNAME"); if ( ! nt ) nt = getlogin(); /* may need to be in sysdep.c */ gtk_entry_set_text(GTK_ENTRY(opennametext),nt); gtk_box_pack_start(GTK_BOX(w2),opennametext,0,0,0); /* Now some stuff for when this panel is in its personality as a start game panel */ opengamepanel = bbox = gtk_vbox_new(0,dialog_vert_spacing); /* gtk_widget_show(bbox); */ gtk_box_pack_start(GTK_BOX(box),bbox,0,0,0); for ( i = 0 ; i < 3 ; i++ ) { static const char *playerlabs[] = { "Second player:" , "Third player:", "Fourth player:" }; w2 = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(bbox),w2,0,0,0); w1 = gtk_label_new(playerlabs[i]); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); openplayercheckboxes[i] = w1 = gtk_check_button_new_with_label("Start computer player "); gtk_widget_show(w1); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w1),1); gtk_box_pack_end(GTK_BOX(w2),w1,0,0,0); gtk_signal_connect(GTK_OBJECT(w1),"toggled",GTK_SIGNAL_FUNC(openbut_callback),(gpointer)(intptr_t)(10+i)); openplayeroptionboxes[i] = w2 = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(bbox),w2,0,0,0); w1 = gtk_label_new(" Name:"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); openplayernames[i] = w1 = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(w1),robot_names[i]); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); w1 = gtk_label_new(" Options:"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); openplayeroptions[i] = w1 = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(w1),robot_options[i]); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); } opendifficultyspinbutton = w1 = gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(difficulty,0,10,1,10,10)), 0.0,0); gtk_widget_show(w1); opendifficulty = w2 = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); w1 = gtk_label_new("robot difficulty level"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); gtk_box_pack_start(GTK_BOX(bbox),w2,0,0,0); openallowdisconnectbutton = w1 = gtk_check_button_new_with_label("Allow disconnection from game"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); opensaveonexitbutton = w1 = gtk_check_button_new_with_label("Save game state on exit"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); openrandomseatsbutton = w1 = gtk_check_button_new_with_label("Seat players randomly"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); opentimeoutspinbutton = w1 = gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(1.0*game_get_option_entry_from_table(&prefs_table,GOTimeout,NULL)->value.optnat,0.0,300.0,1.0,10.0,0.0)), 0.0,0); gtk_widget_show(w1); opentimeout = w2 = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); w1 = gtk_label_new("seconds allowed for claims"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); gtk_box_pack_start(GTK_BOX(bbox),w2,0,0,0); w1 = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(box),w1,0,0,0); openconnectbutton = w2 = gtk_button_new_with_label("Connect"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(open_connection),0); openstartbutton = w2 = gtk_button_new_with_label("Start Game"); /* gtk_widget_show(w2); */ gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(open_connection),(gpointer)1); openresumebutton = w2 = gtk_button_new_with_label("Resume Game"); /* gtk_widget_show(w2); */ gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(open_connection),(gpointer)2); w2 = gtk_button_new_with_label("Cancel"); gtk_widget_show(w2); gtk_box_pack_end(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect_object(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(close_saving_posn),GTK_OBJECT(open_dialog)); /* initialize dialog values */ open_dialog_popup(NULL,(gpointer)-1); } /* if data is -1, just set the values of the open dialog fields, to be picked up by do_connect */ void open_dialog_popup(GtkWidget *w UNUSED, gpointer data) { int new = 0, join = 0, resume = 0; char buf[256]; char ht[256], pt[10], ft[256], idt[10]; int usehost = 1; ht[0] = pt[0] = idt[0] = ft[0] = 0; if ( strchr(address,':') ) { /* grrr */ if ( address[0] == ':' ) { strcpy(ht,"localhost"); strcpy(pt,address+1); } else { sscanf(address,"%[^:]:%s",ht,pt); } } else { strcpy(ft,address); usehost = 0; } /* set the default id to be our current id */ sprintf(buf,"%d",our_id); if ( (int)(intptr_t)data == 1 ) new = 1; if ( (int)(intptr_t)data == 0 ) join = 1; if ( (int)(intptr_t)data == 2 ) resume = 1; gtk_entry_set_text(GTK_ENTRY(openidtext),buf); /* set the host and port etc from the address */ gtk_widget_set_sensitive(openhost,usehost); gtk_entry_set_text(GTK_ENTRY(openhosttext),ht); gtk_widget_set_sensitive(openport,usehost); gtk_entry_set_text(GTK_ENTRY(openporttext),pt); gtk_widget_set_sensitive(openfile,!usehost); gtk_entry_set_text(GTK_ENTRY(openfiletext),ft); if ( (int)(intptr_t)data == -1 ) return; if ( join ) { gtk_widget_show(openhost); } else { gtk_widget_hide(openhost); gtk_entry_set_text(GTK_ENTRY(openhosttext),"localhost"); } gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(usehost ? openhosttoggle : openunixtoggle),1); if ( resume ) gtk_widget_show(opengamefile); else gtk_widget_hide(opengamefile); if ( new || resume ) gtk_widget_show(opengamepanel); else gtk_widget_hide(opengamepanel); if ( new ) gtk_widget_show(opentimeout); else gtk_widget_hide(opentimeout); if ( new ) gtk_widget_show(openrandomseatsbutton); else gtk_widget_hide(openrandomseatsbutton); if ( new ) gtk_widget_show(openstartbutton); else gtk_widget_hide(openstartbutton); if ( join ) gtk_widget_show(openconnectbutton); else gtk_widget_hide(openconnectbutton); if ( resume ) gtk_widget_show(openresumebutton); else gtk_widget_hide(openresumebutton); dialog_popup(open_dialog,DPCentredOnce); if ( new ) gtk_widget_grab_focus(openstartbutton); if ( join ) gtk_widget_grab_focus(openconnectbutton); if ( resume ) gtk_widget_grab_focus(openresumebutton); } ACCELFUN(turn) /* the turn dialog: buttons for Discard (also declares specs), Kong (concealed or adding to pung), Mah Jong */ void turn_dialog_init(void) { GtkWidget *box, *butbox, *w; if ( turn_dialog ) { gtk_widget_destroy(turn_dialog); turn_dialog = NULL; } switch ( dialogs_position ) { case DialogsCentral: case DialogsUnspecified: turn_dialog = gtk_event_box_new(); gtk_fixed_put(GTK_FIXED(discard_area),turn_dialog,0,0); break; case DialogsBelow: turn_dialog = gtk_event_box_new(); gtk_box_pack_start(GTK_BOX(dialoglowerbox),turn_dialog,1,0,0); break; case DialogsPopup: turn_dialog = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (turn_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); } box = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(box), dialog_border_width); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(turn_dialog),box); butbox = gtk_hbox_new(1,dialog_button_spacing); gtk_widget_show(butbox); gtk_box_pack_end(GTK_BOX(box),butbox,0,0,0); turn_dialog_label = w = gtk_label_new("Select tile and:"); gtk_widget_show(w); gtk_box_pack_end(GTK_BOX(box),w,0,0,0); turn_accel = gtk_accel_group_new(); w = gtk_button_new_with_label("Discard"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(butbox),w,1,1,0); /* so other function can set it */ turn_dialog_discard_button = w; gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgDiscard); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",turn_accel,GDK_d,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); w = gtk_button_new_with_label("& Calling"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(butbox),w,1,1,0); /* so other function can set it */ turn_dialog_calling_button = w; /* this assumes knowledge that protocol enums don't go above 1000000 */ gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)(PMsgDiscard+1000000)); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",turn_accel,GDK_c,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child)," _"); w = gtk_button_new_with_label("Kong"); GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_FOCUS); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(butbox),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgDeclareClosedKong); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",turn_accel,GDK_k,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); w = gtk_button_new_with_label("Mah Jong!"); GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_FOCUS); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(butbox),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgMahJong); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",turn_accel,GDK_m,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); gtk_signal_connect(GTK_OBJECT(turn_dialog),"hide",GTK_SIGNAL_FUNC(add_or_remove_turn_accels),(gpointer)0); gtk_signal_connect(GTK_OBJECT(turn_dialog),"show",GTK_SIGNAL_FUNC(add_or_remove_turn_accels),(gpointer)1); } /* dialog for scoring phase: forming closed sets */ static GtkWidget *scoring_done, *scoring_special; ACCELFUN(scoring) void scoring_dialog_init(void) { GtkWidget *box, *butbox, *w; if ( scoring_dialog ) { gtk_widget_destroy(scoring_dialog); scoring_dialog = NULL; } switch ( dialogs_position ) { case DialogsCentral: case DialogsUnspecified: scoring_dialog = gtk_event_box_new(); gtk_fixed_put(GTK_FIXED(discard_area),scoring_dialog,0,0); break; case DialogsBelow: scoring_dialog = gtk_event_box_new(); gtk_box_pack_start(GTK_BOX(dialoglowerbox),scoring_dialog,1,0,0); break; case DialogsPopup: scoring_dialog = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (scoring_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); } box = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(box), dialog_border_width); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(scoring_dialog),box); butbox = gtk_hbox_new(1,dialog_button_spacing); gtk_widget_show(butbox); gtk_box_pack_end(GTK_BOX(box),butbox,0,0,0); w = gtk_label_new("Declare concealed sets\nSelect 1st tile and:"); gtk_widget_show(w); gtk_box_pack_end(GTK_BOX(box),w,0,0,0); scoring_accel = gtk_accel_group_new(); w = gtk_button_new_with_label("Eyes"); GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_FOCUS); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(butbox),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgFormClosedPair); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",scoring_accel,GDK_e,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); w = gtk_button_new_with_label("Chow"); GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_FOCUS); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(butbox),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgFormClosedChow); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",scoring_accel,GDK_c,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); w = gtk_button_new_with_label("Pung"); GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_FOCUS); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(butbox),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgFormClosedPung); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",scoring_accel,GDK_p,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); scoring_special = w = gtk_button_new_with_label("Special Hand"); GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_FOCUS); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(butbox),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgFormClosedSpecialSet); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",scoring_accel,GDK_s,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); scoring_done = w = gtk_button_new_with_label("Finished"); GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_FOCUS); /* gtk_widget_show(w); */ /* uses same space as special hand */ gtk_box_pack_start(GTK_BOX(butbox),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(turn_callback),(gpointer)(intptr_t)PMsgShowTiles); gtk_widget_add_accelerator(GTK_WIDGET(w),"clicked",scoring_accel,GDK_f,0,0); gtk_label_set_pattern(GTK_LABEL(GTK_BIN(w)->child),"_"); gtk_signal_connect(GTK_OBJECT(scoring_dialog),"hide",GTK_SIGNAL_FUNC(add_or_remove_scoring_accels),(gpointer)0); gtk_signal_connect(GTK_OBJECT(scoring_dialog),"show",GTK_SIGNAL_FUNC(add_or_remove_scoring_accels),(gpointer)1); } void scoring_dialog_popup(void) { if ( the_game->player == our_seat ) { gtk_widget_show(scoring_special); gtk_widget_hide(scoring_done); } else { gtk_widget_hide(scoring_special); gtk_widget_show(scoring_done); } dialog_popup(scoring_dialog,DPCentredOnce); } /* used above - it removes all the accelerators when the focus enters the chat widget, to avoid accidents */ static gint mentry_focus_callback(GtkWidget *w UNUSED,GdkEventFocus e UNUSED,gpointer u UNUSED) { add_or_remove_discard_accels(NULL,0); add_or_remove_chow_accels(NULL,0); add_or_remove_ds_accels(NULL,0); add_or_remove_turn_accels(NULL,0); add_or_remove_scoring_accels(NULL,0); return 0; } /* close a widget, saving its position for next open */ void close_saving_posn(GtkWidget *w) { gint x,y; gdk_window_get_deskrelative_origin(w->window,&x,&y); gtk_widget_set_uposition(w,x,y); /* gtk2 seems to lose the information over unmap/map */ gtk_object_set_data(GTK_OBJECT(w),"position-set",(gpointer)1); gtk_object_set_data(GTK_OBJECT(w),"position-x",(gpointer)(intptr_t)x); gtk_object_set_data(GTK_OBJECT(w),"position-y",(gpointer)(intptr_t)y); gtk_widget_hide(w); } /* the textwindow for scoring information etc */ void textwindow_init(void) { int i; GtkWidget *obox, *box, *tmp, *lbl,*textw; textwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (textwindow), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); /* must allow shrinking */ gtk_window_set_policy(GTK_WINDOW(textwindow),TRUE,TRUE,FALSE); gtk_window_set_title(GTK_WINDOW(textwindow),"Scoring calculations"); // we used to set the size here, but I think that's better done // on popup obox = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(obox),dialog_border_width); gtk_widget_show(obox); gtk_container_add(GTK_CONTAINER(textwindow),obox); scoring_notebook = gtk_notebook_new(); GTK_WIDGET_UNSET_FLAGS(scoring_notebook,GTK_CAN_FOCUS); gtk_notebook_set_homogeneous_tabs(GTK_NOTEBOOK(scoring_notebook),1); gtk_widget_show(scoring_notebook); gtk_box_pack_start(GTK_BOX(obox),scoring_notebook,1,1,0); for (i=0;i<5;i++) { textw = gtk_text_view_new(); gtk_text_view_set_editable(GTK_TEXT_VIEW(textw),FALSE); gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textw),FALSE); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textw),GTK_WRAP_WORD_CHAR); gtk_widget_show(textw); textpages[i] = textw; box = gtk_scrolled_window_new(NULL,NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(box),GTK_POLICY_NEVER,GTK_POLICY_AUTOMATIC); gtk_container_add(GTK_CONTAINER(box),textw); gtk_widget_show(box); lbl = gtk_label_new((i==4) ? "Settlement" : ""); gtk_widget_show(lbl); textlabels[i] = lbl; gtk_notebook_append_page(GTK_NOTEBOOK(scoring_notebook),box,lbl); gtk_widget_realize(textw); } tmp = gtk_button_new_with_label("Close"); gtk_signal_connect_object(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(close_saving_posn),GTK_OBJECT(textwindow)); gtk_widget_show(tmp); gtk_box_pack_end(GTK_BOX(obox),tmp,0,0,0); gtk_widget_grab_focus(tmp); } /* the textwindow for scoring history */ void scorehistory_init(void) { GtkWidget *obox, *box, *tmp, *textw; scorehistorywindow = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (scorehistorywindow), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); /* must allow shrinking */ gtk_window_set_policy(GTK_WINDOW(scorehistorywindow),TRUE,TRUE,FALSE); gtk_window_set_title(GTK_WINDOW(scorehistorywindow),"Scoring history"); gtk_widget_set_usize(scorehistorywindow,0,400); obox = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(obox),dialog_border_width); gtk_widget_show(obox); gtk_container_add(GTK_CONTAINER(scorehistorywindow),obox); scorehistorytext = textw = gtk_text_view_new(); gtk_text_view_set_editable(GTK_TEXT_VIEW(textw),FALSE); gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textw),FALSE); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textw),GTK_WRAP_NONE); gtk_widget_show(textw); box = gtk_scrolled_window_new(NULL,NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(box),GTK_POLICY_NEVER,GTK_POLICY_AUTOMATIC); gtk_container_add(GTK_CONTAINER(box),textw); gtk_box_pack_start(GTK_BOX(obox),box,1,1,0); gtk_widget_show(box); gtk_widget_realize(textw); tmp = gtk_button_new_with_label("Close"); gtk_signal_connect_object(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(close_saving_posn),GTK_OBJECT(scorehistorywindow)); gtk_widget_show(tmp); gtk_box_pack_end(GTK_BOX(obox),tmp,0,0,0); gtk_widget_grab_focus(tmp); } /* the callback when user hits return in the message composition window */ static void mentry_callback(GtkWidget *widget UNUSED,GtkWidget *mentry) { const gchar *mentry_text; PMsgSendMessageMsg smm; mentry_text = gtk_entry_get_text(GTK_ENTRY(mentry)); smm.type = PMsgSendMessage; smm.addressee = 0; /* broadcast only at present ... */ smm.text = (char *)mentry_text; send_packet(&smm); gtk_entry_set_text(GTK_ENTRY(mentry),""); } static gint mentry_focus_callback(GtkWidget *w,GdkEventFocus e,gpointer u); /* the window for messages */ void messagewindow_init(void) { GtkWidget *obox, *box, *tmp, *mentry, *label; if ( !info_windows_in_main ) { messagewindow = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (messagewindow), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); /* must allow shrinking */ gtk_window_set_policy(GTK_WINDOW(messagewindow),TRUE,TRUE,FALSE); gtk_window_set_title(GTK_WINDOW(messagewindow),"Messages"); /* reasonable size is ... */ gtk_window_set_default_size(GTK_WINDOW(messagewindow),400,300); } obox = gtk_vbox_new(0,info_windows_in_main ? 0 : dialog_vert_spacing); if ( !info_windows_in_main ) { gtk_container_set_border_width(GTK_CONTAINER(obox),dialog_border_width); } gtk_widget_show(obox); if ( info_windows_in_main ) { messagewindow = obox; gtk_box_pack_end(GTK_BOX(info_box),messagewindow,1,1,0); } else { gtk_container_add(GTK_CONTAINER(messagewindow),obox); } messagetext = gtk_text_view_new(); gtk_text_view_set_editable(GTK_TEXT_VIEW(messagetext),FALSE); gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(messagetext),FALSE); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(messagetext),GTK_WRAP_WORD_CHAR); messagetextbuf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (messagetext)); gtk_text_buffer_get_iter_at_offset (messagetextbuf, &messagetextiter, 0); if ( info_windows_in_main ) { gtk_widget_set_usize(messagetext,0,50); } gtk_widget_show(messagetext); box = gtk_scrolled_window_new(NULL,NULL); gtk_container_add(GTK_CONTAINER(box),messagetext); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(box),GTK_POLICY_NEVER,GTK_POLICY_AUTOMATIC); gtk_widget_show(box); gtk_box_pack_start(GTK_BOX(obox),box,1,1,0); gtk_widget_realize(messagetext); GTK_WIDGET_UNSET_FLAGS(messagetext,GTK_CAN_FOCUS); /* entry widget shd focus */ label = gtk_label_new(info_windows_in_main ? "Chat:" : "Send message:"); GTK_WIDGET_UNSET_FLAGS(label,GTK_CAN_FOCUS); gtk_misc_set_alignment(GTK_MISC(label),0.0,0.5); gtk_widget_show(label); /* the thing for sending messages */ message_entry = mentry = gtk_entry_new(); gtk_widget_show(mentry); gtk_signal_connect(GTK_OBJECT(mentry),"activate",GTK_SIGNAL_FUNC(mentry_callback),mentry); gtk_signal_connect(GTK_OBJECT(mentry),"focus-in-event",GTK_SIGNAL_FUNC(mentry_focus_callback),mentry); if ( !info_windows_in_main ) { gtk_box_pack_start(GTK_BOX(obox),label,0,0,0); gtk_box_pack_start(GTK_BOX(obox),mentry,0,0,0); } else { GtkWidget *w = gtk_hbox_new(0,0); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(w),label,0,0,0); gtk_box_pack_start(GTK_BOX(w),mentry,0,0,0); mfocus = gtk_check_button_new_with_label("keep cursor here"); GTK_WIDGET_UNSET_FLAGS(mfocus,GTK_CAN_FOCUS); gtk_widget_show(mfocus); gtk_box_pack_start(GTK_BOX(w),mfocus,0,0,0); gtk_box_pack_start(GTK_BOX(obox),w,0,0,0); } if ( !info_windows_in_main ) { tmp = gtk_button_new_with_label("Close"); GTK_WIDGET_UNSET_FLAGS(tmp,GTK_CAN_FOCUS); /* entry widget shd focus */ gtk_signal_connect_object(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(close_saving_posn),GTK_OBJECT(messagewindow)); gtk_widget_show(tmp); gtk_box_pack_end(GTK_BOX(obox),tmp,0,0,0); /* god knows how focus would work if this happened in the topwindow! */ gtk_widget_grab_focus(mentry); } } /* the window for showing warnings */ void warningwindow_init(void) { GtkWidget *obox, *box, *tmp, *label; if ( warningwindow ) { gtk_widget_destroy(warningwindow); } warningwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (warningwindow), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); /* must allow shrinking */ gtk_window_set_policy(GTK_WINDOW(warningwindow),TRUE,TRUE,FALSE); gtk_window_set_title(GTK_WINDOW(warningwindow),"Warnings"); /* reasonable size is ... */ gtk_window_set_default_size(GTK_WINDOW(warningwindow),400,300); obox = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(obox),dialog_border_width); gtk_widget_show(obox); gtk_container_add(GTK_CONTAINER(warningwindow),obox); label = gtk_label_new( "Apart from when a player disconnects,\n" "warnings usually indicate a bug or other\n" "unexpected situation. If in doubt,\n" "please mail mahjong@stevens-bradfield.com\n" "with the warning text and a description\n" "of the situation in which it occurred."); GTK_WIDGET_UNSET_FLAGS(label,GTK_CAN_FOCUS); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(obox),label,0,0,0); warningtext = gtk_text_view_new(); warningtextbuf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (warningtext)); gtk_text_buffer_get_iter_at_offset (warningtextbuf, &warningtextiter, 0); gtk_widget_show(warningtext); box = gtk_scrolled_window_new(NULL,NULL); gtk_container_add(GTK_CONTAINER(box),warningtext); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(box),GTK_POLICY_NEVER,GTK_POLICY_AUTOMATIC); gtk_widget_show(box); gtk_box_pack_start(GTK_BOX(obox),box,1,1,0); gtk_widget_realize(warningtext); GTK_WIDGET_UNSET_FLAGS(warningtext,GTK_CAN_FOCUS); box = gtk_hbox_new(0,0); gtk_widget_show(box); gtk_box_pack_end(GTK_BOX(obox),box,0,0,0); tmp = gtk_button_new_with_label("Clear Warnings"); gtk_signal_connect_object(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(warning_clear),NULL); gtk_widget_show(tmp); gtk_box_pack_start(GTK_BOX(box),tmp,1,1,0); tmp = gtk_button_new_with_label("Close"); gtk_signal_connect_object(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(close_saving_posn),GTK_OBJECT(warningwindow)); gtk_widget_show(tmp); gtk_box_pack_end(GTK_BOX(box),tmp,1,1,0); /* check for cached warnings */ log_msg_add(0,NULL); } static void warningraise(void) { showraise(warningwindow); } static int warning_numbers = 0; void warning_clear(void) { warning_numbers = 0; gtk_text_buffer_set_text(GTK_TEXT_BUFFER(warningtextbuf),"",0); gtk_text_buffer_get_iter_at_offset (warningtextbuf, &warningtextiter, 0); gtk_widget_hide(warningentry); close_saving_posn(warningwindow); } static GtkWidget *debug_options_reporting; /* query dialog */ static void file_report(char *warning) { SOCKET fd; static int in_progress = 0; static int waiting_permission = 0; /* don't enter this routine twice. However, if we're waiting for permission, the permission is given by calling this routine with a non-null or null argument. */ if ( in_progress && ! waiting_permission ) return; if ( debug_reports == DebugReportsNever ) return; in_progress = 1; if ( debug_reports != DebugReportsAlways ) { static char saved_warning[50000]; if ( waiting_permission ) { waiting_permission = 0; gtk_widget_hide(debug_options_reporting); control_server_processing(1); if ( warning ) { // permission received warning = saved_warning; } else { // permission denied in_progress = 0; return; } } else { // need to ask for permission waiting_permission = 1; // save the warning text strmcpy(saved_warning,warning,49999); // disable processing of input from the server, to avoid confusion control_server_processing(0); debug_report_query_popup(); return; } } // now file the report fd = plain_connect_to_host("www.stevens-bradfield.com:9000"); if ( fd == INVALID_SOCKET ) { warn("unable to file error report"); } else { static char header[] = "From: \n" "To: mahjong@stevens-bradfield.com\n" "Subject: XMJ internal error report\n" "\n" "XMJ version: "; char msg[1024]; int n; put_data(fd,header,strlen(header)); put_data(fd,(char *)version,strlen(version)); put_data(fd,"\n",1); put_data(fd,warning,strlen(warning)); put_data(fd,NULL,0); n = get_data(fd,msg,1023); if ( n > 0 ) { info_dialog_popup(msg); } plain_close_socket(fd); } in_progress = 0; } /* a second argument of NULL just checks for stashed warnings to be transferred to the window */ int log_msg_add(LogLevel l,char *warning) { static char buf[1024] = ""; if ( warning && l >= LogWarning) warning_numbers++; /* if the warning window is not currently available, cache the warning until it is */ if ( warningwindow == NULL ) { if ( warning ) { strncat(buf,warning,1023-strlen(buf)); } } else { if ( buf[0] ) { gtk_text_buffer_insert(warningtextbuf,&warningtextiter,buf,-1); gtk_text_buffer_get_end_iter(warningtextbuf,&warningtextiter); buf[0] = 0; } if ( warning ) { gtk_text_buffer_insert(warningtextbuf,&warningtextiter,warning,-1); gtk_text_buffer_get_end_iter(warningtextbuf,&warningtextiter); } if ( warning_numbers > 0 ) gtk_widget_show(warningentry); /* if the log level exceeds warning, try to file a report, and give user a message */ if ( l > LogWarning ) { file_report(warning); error_dialog_popup("The program has encountered an internal error.\n" "This will probably cause it to crash very soon. Sorry!"); } } /* tell warn to print the warning anyway */ return 0; } /* callback for saving as */ static void save_callback(GtkWidget *w UNUSED, gpointer data UNUSED) { PMsgSaveStateMsg m; m.type = PMsgSaveState; m.filename = (char *)gtk_entry_get_text(GTK_ENTRY(save_text)); send_packet(&m); close_saving_posn(save_window); } /* window for Save As ... function */ static void save_init(void) { GtkWidget *box, *bbox, *w1, *tmp; save_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (save_window), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); gtk_container_set_border_width(GTK_CONTAINER(save_window), dialog_border_width); bbox = gtk_vbox_new(0,0); gtk_widget_show(bbox); gtk_container_add(GTK_CONTAINER(save_window),bbox); w1 = gtk_label_new("Save as:"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); box = gtk_hbox_new(0,0); gtk_widget_show(box); gtk_box_pack_start(GTK_BOX(bbox),box,0,0,0); save_text = gtk_entry_new(); gtk_widget_show(save_text); gtk_box_pack_start(GTK_BOX(box),save_text,0,0,0); w1 = gtk_label_new(".mjs"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(box),w1,0,0,0); box = gtk_hbox_new(0,0); gtk_widget_show(box); gtk_box_pack_start(GTK_BOX(bbox),box,0,0,0); tmp = gtk_button_new_with_label("Save"); gtk_signal_connect_object(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(save_callback),0); gtk_widget_show(tmp); gtk_box_pack_start(GTK_BOX(box),tmp,1,1,0); tmp = gtk_button_new_with_label("Cancel"); gtk_signal_connect_object(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(close_saving_posn),GTK_OBJECT(save_window)); gtk_widget_show(tmp); gtk_box_pack_start(GTK_BOX(box),tmp,1,1,0); } /* callback for password */ static void password_callback(GtkWidget *w UNUSED, gpointer data UNUSED) { PMsgAuthInfoMsg m; m.type = PMsgAuthInfo; strcpy(m.authtype,"basic"); m.authdata = (char *)gtk_entry_get_text(GTK_ENTRY(password_text)); send_packet(&m); close_saving_posn(password_window); } /* window for password request */ static void password_init(void) { GtkWidget *box, *bbox, *w1, *tmp; password_window = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (password_window), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); gtk_container_set_border_width(GTK_CONTAINER(password_window), dialog_border_width); bbox = gtk_vbox_new(0,0); gtk_widget_show(bbox); gtk_container_add(GTK_CONTAINER(password_window),bbox); w1 = gtk_label_new("Password required:"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); box = gtk_hbox_new(0,0); gtk_widget_show(box); gtk_box_pack_start(GTK_BOX(bbox),box,0,0,0); password_text = gtk_entry_new(); gtk_widget_show(password_text); gtk_box_pack_start(GTK_BOX(box),password_text,0,0,0); box = gtk_hbox_new(0,0); gtk_widget_show(box); gtk_box_pack_start(GTK_BOX(bbox),box,0,0,0); tmp = gtk_button_new_with_label("OK"); gtk_signal_connect_object(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(password_callback),0); gtk_widget_show(tmp); gtk_box_pack_start(GTK_BOX(box),tmp,1,1,0); gtk_widget_show(tmp); gtk_box_pack_start(GTK_BOX(box),tmp,1,1,0); } /* radio buttons for dialog positions */ static GtkWidget *display_option_dialog_dialogposn[DialogsPopup+1]; /* checkbox for animation */ static GtkWidget *display_option_dialog_animation; /* checkbox for nopopups */ static GtkWidget *display_option_dialog_nopopups; /* checkbox for tiletips */ static GtkWidget *display_option_dialog_tiletips; /* checkbox for rotatelabels */ static GtkWidget *display_option_dialog_rotatelabels; /* checkbox for thinking_claim */ static GtkWidget *display_option_dialog_thinking_claim; /* checkbox for alert_mahjong */ static GtkWidget *display_option_dialog_alert_mahjong; /* check box for iconify option */ static GtkWidget *display_option_dialog_iconify; /* check box for info in main */ static GtkWidget *display_option_dialog_info_in_main; /* radiobuttons for show wall */ static GtkWidget *display_option_dialog_showwall[3]; static GtkWidget *display_option_dialog_tileset, *display_option_dialog_tileset_path; /* text entry for size */ static GtkWidget *display_option_size_entry; /* radio buttons for sort tiles */ static GtkWidget *display_option_dialog_sort_tiles[3]; /* text entry for gtk2rc file */ static GtkWidget *display_option_dialog_gtk2_rcfile; /* checkbox entry for use_system_gtkrc */ static GtkWidget *display_option_dialog_use_system_gtkrc; static void display_option_dialog_apply(void); static void display_option_dialog_save(void); static void display_option_dialog_refresh(void); /* things used below */ static GtkWidget *mfontwindow; static GtkWidget *mfont_selector; static void mfontsel_callback(GtkWidget *w UNUSED, gpointer data) { if ( data ) { /* use default */ main_font_name[0] = 0; } else { /* use selection */ strmcpy(main_font_name,gtk_font_selection_get_font_name(GTK_FONT_SELECTION(mfont_selector)),256); } gtk_widget_hide(mfontwindow); } static GtkWidget *tfontwindow; static GtkWidget *tfont_selector; static void tfontsel_callback(GtkWidget *w UNUSED, gpointer data) { if ( data ) { /* use default */ text_font_name[0] = 0; } else { /* use selection */ strmcpy(text_font_name,gtk_font_selection_get_font_name(GTK_FONT_SELECTION(tfont_selector)),256); } gtk_widget_hide(tfontwindow); } static GtkWidget *tcolwindow; static GtkWidget *tcolor_selector; static void tcolsel_callback(GtkWidget *w UNUSED, gpointer data) { if ( data ) { /* use default */ table_colour_name[0] = 0; } else { /* use selection */ gdouble c[4]; gtk_color_selection_get_color(GTK_COLOR_SELECTION(tcolor_selector),c); sprintf(table_colour_name,"#%04X%04X%04X",(int)(0xFFFF*c[0]),(int)(0xFFFF*c[1]),(int)(0xFFFF*c[2])); } gtk_widget_hide(tcolwindow); } /* window for display options */ static void display_option_dialog_init(void) { GtkWidget *box, *bbox, *hbox, *but1, *but2, *but3, *w1, *w2; display_option_dialog = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (display_option_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); gtk_container_set_border_width(GTK_CONTAINER(display_option_dialog), dialog_border_width); box = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(display_option_dialog),box); bbox = gtk_vbox_new(0,0); gtk_widget_show(bbox); gtk_box_pack_start(GTK_BOX(box),bbox,0,0,0); w1 = gtk_label_new("Position of action dialogs:"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); hbox = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(hbox); gtk_box_pack_start(GTK_BOX(bbox),hbox,0,0,0); but1 = gtk_radio_button_new_with_label(NULL,"central"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(hbox),but1,0,0,0); display_option_dialog_dialogposn[DialogsCentral] = but1 ; but2 = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(but1)),"below"); gtk_widget_show(but2); gtk_box_pack_start(GTK_BOX(hbox),but2,0,0,0); display_option_dialog_dialogposn[DialogsBelow] = but2; but3 = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(but1)),"popup"); gtk_widget_show(but3); gtk_box_pack_start(GTK_BOX(hbox),but3,0,0,0); display_option_dialog_dialogposn[DialogsPopup] = but3; /* animation */ display_option_dialog_animation = but1 = gtk_check_button_new_with_label("Animation"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* info in main */ display_option_dialog_info_in_main = but1 = gtk_check_button_new_with_label("Display status and messages in main window"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* nopopups */ display_option_dialog_nopopups = but1 = gtk_check_button_new_with_label("Don't popup scoring/message windows"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* tiletips */ display_option_dialog_tiletips = but1 = gtk_check_button_new_with_label("Tiletips always shown"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* rotatelabels */ display_option_dialog_rotatelabels = but1 = gtk_check_button_new_with_label("Rotate player info text"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* thinking_claim */ display_option_dialog_thinking_claim = but1 = gtk_check_button_new_with_label("Show when players are thinking"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* mah-jong */ display_option_dialog_alert_mahjong = but1 = gtk_check_button_new_with_label("Alert on possible mah-jong"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* display size */ hbox = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(hbox); gtk_box_pack_start(GTK_BOX(box),hbox,0,0,0); w1 = gtk_label_new("Display size (in tile-widths):"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(hbox),w1,0,0,0); w1 = gtk_combo_new(); gtk_entry_set_max_length(GTK_ENTRY(GTK_COMBO(w1)->entry),6); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(hbox),w1,0,0,0); { GList *gl = NULL; gl = g_list_append(gl,"19"); gl = g_list_append(gl,"18"); gl = g_list_append(gl,"17"); gl = g_list_append(gl,"16"); gl = g_list_append(gl,"15"); gl = g_list_append(gl,"14"); gl = g_list_append(gl,"(auto)"); gtk_combo_set_popdown_strings(GTK_COMBO(w1),gl); } gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(w1)->entry),FALSE); display_option_size_entry = GTK_COMBO(w1)->entry; /* showwall setting */ bbox = gtk_vbox_new(0,0); gtk_widget_show(bbox); gtk_box_pack_start(GTK_BOX(box),bbox,0,0,0); w1 = gtk_label_new("Show the wall:"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); hbox = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(hbox); gtk_box_pack_start(GTK_BOX(bbox),hbox,0,0,0); but1 = gtk_radio_button_new_with_label(NULL,"always"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(hbox),but1,0,0,0); display_option_dialog_showwall[1] = but1 ; but2 = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(but1)),"when room"); gtk_widget_show(but2); gtk_box_pack_start(GTK_BOX(hbox),but2,0,0,0); display_option_dialog_showwall[2] = but2; but3 = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(but1)),"never"); gtk_widget_show(but3); gtk_box_pack_start(GTK_BOX(hbox),but3,0,0,0); display_option_dialog_showwall[0] = but3; /* sort tiles setting */ bbox = gtk_vbox_new(0,0); gtk_widget_show(bbox); gtk_box_pack_start(GTK_BOX(box),bbox,0,0,0); w1 = gtk_label_new("Sort tiles in hand:"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); hbox = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(hbox); gtk_box_pack_start(GTK_BOX(bbox),hbox,0,0,0); but1 = gtk_radio_button_new_with_label(NULL,"always"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(hbox),but1,0,0,0); display_option_dialog_sort_tiles[SortAlways] = but1 ; but2 = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(but1)),"during deal"); gtk_widget_show(but2); gtk_box_pack_start(GTK_BOX(hbox),but2,0,0,0); display_option_dialog_sort_tiles[SortDeal] = but2; but3 = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(but1)),"never"); gtk_widget_show(but3); gtk_box_pack_start(GTK_BOX(hbox),but3,0,0,0); display_option_dialog_sort_tiles[SortNever] = but3; /* iconify */ display_option_dialog_iconify = but1 = gtk_check_button_new_with_label("Iconify all windows with main"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* tileset name */ bbox = gtk_hbox_new(0,0); gtk_widget_show(bbox); gtk_box_pack_start(GTK_BOX(box),bbox,0,0,0); w1 = gtk_label_new("Tileset: "); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); display_option_dialog_tileset = w1 = gtk_entry_new(); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); /* tileset path */ bbox = gtk_hbox_new(0,0); gtk_widget_show(bbox); gtk_box_pack_start(GTK_BOX(box),bbox,0,0,0); w1 = gtk_label_new("Tileset Path: "); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); display_option_dialog_tileset_path = w1 = gtk_entry_new(); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); /* deal with the selection of the system font */ { GtkWidget *box, *tmp, *w, *fsdial; if ( mfontwindow ) { gtk_widget_destroy(mfontwindow); } mfontwindow = gtk_window_new(GTK_WINDOW_DIALOG); gtk_window_set_title(GTK_WINDOW(mfontwindow),"Main font selection"); box = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(mfontwindow),box); mfont_selector = fsdial = gtk_font_selection_new(); gtk_widget_show(fsdial); gtk_font_selection_set_preview_text(GTK_FONT_SELECTION(fsdial), "I hope you're enjoying the game"); gtk_box_pack_start(GTK_BOX(box),fsdial,0,0,0); tmp = gtk_hbox_new(1,dialog_button_spacing); gtk_widget_show(tmp); gtk_box_pack_start(GTK_BOX(box),tmp,0,0,0); w = gtk_button_new_with_label("Select"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(tmp),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(mfontsel_callback),(gpointer) 0); w = gtk_button_new_with_label("Use default"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(tmp),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(mfontsel_callback),(gpointer) 1); w = gtk_button_new_with_label("Cancel"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(tmp),w,1,1,0); gtk_signal_connect_object(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(gtk_widget_hide),GTK_OBJECT(mfontwindow)); } w1 = gtk_button_new_with_label("Main font selection..."); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(box),w1,0,0,0); gtk_signal_connect_object(GTK_OBJECT(w1),"clicked",GTK_SIGNAL_FUNC(showraise),GTK_OBJECT(mfontwindow)); /* deal with the selection of the text font */ { GtkWidget *box, *tmp, *w, *fsdial; if ( tfontwindow ) { gtk_widget_destroy(tfontwindow); } tfontwindow = gtk_window_new(GTK_WINDOW_DIALOG); gtk_window_set_title(GTK_WINDOW(tfontwindow),"Text font selection"); box = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(tfontwindow),box); tfont_selector = fsdial = gtk_font_selection_new(); gtk_widget_show(fsdial); gtk_font_selection_set_preview_text(GTK_FONT_SELECTION(fsdial), "I hope you're enjoying the game"); gtk_box_pack_start(GTK_BOX(box),fsdial,0,0,0); tmp = gtk_hbox_new(1,dialog_button_spacing); gtk_widget_show(tmp); gtk_box_pack_start(GTK_BOX(box),tmp,0,0,0); w = gtk_button_new_with_label("Select"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(tmp),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(tfontsel_callback),(gpointer) 0); w = gtk_button_new_with_label("Use default"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(tmp),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(tfontsel_callback),(gpointer) 1); w = gtk_button_new_with_label("Cancel"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(tmp),w,1,1,0); gtk_signal_connect_object(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(gtk_widget_hide),GTK_OBJECT(tfontwindow)); } w1 = gtk_button_new_with_label("Text font selection..."); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(box),w1,0,0,0); gtk_signal_connect_object(GTK_OBJECT(w1),"clicked",GTK_SIGNAL_FUNC(showraise),GTK_OBJECT(tfontwindow)); /* deal with selection of table colour */ { GtkWidget *box, *tmp, *w, *csdial; if ( tcolwindow ) { gtk_widget_destroy(tcolwindow); } tcolwindow = gtk_window_new(GTK_WINDOW_DIALOG); gtk_window_set_title(GTK_WINDOW(tcolwindow),"Table colour selection"); box = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(tcolwindow),box); tcolor_selector = csdial = gtk_color_selection_new(); gtk_widget_show(csdial); gtk_box_pack_start(GTK_BOX(box),csdial,0,0,0); tmp = gtk_hbox_new(1,dialog_button_spacing); gtk_widget_show(tmp); gtk_box_pack_start(GTK_BOX(box),tmp,0,0,0); w = gtk_button_new_with_label("Select"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(tmp),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(tcolsel_callback),(gpointer) 0); w = gtk_button_new_with_label("Use default"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(tmp),w,1,1,0); gtk_signal_connect(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(tcolsel_callback),(gpointer) 1); w = gtk_button_new_with_label("Cancel"); gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(tmp),w,1,1,0); gtk_signal_connect_object(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(gtk_widget_hide),GTK_OBJECT(tcolwindow)); } w1 = gtk_button_new_with_label("Table colour selection..."); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(box),w1,0,0,0); gtk_signal_connect_object(GTK_OBJECT(w1),"clicked",GTK_SIGNAL_FUNC(showraise),GTK_OBJECT(tcolwindow)); /* gtk2rcfile */ bbox = gtk_hbox_new(0,0); gtk_widget_show(bbox); gtk_box_pack_start(GTK_BOX(box),bbox,0,0,0); w1 = gtk_label_new("Gtk2 Rcfile: "); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); display_option_dialog_gtk2_rcfile = w1 = gtk_entry_new(); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(bbox),w1,0,0,0); /* use_system_gtkrc */ display_option_dialog_use_system_gtkrc = but1 = gtk_check_button_new_with_label("Use system gtkrc"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* apply, save and close buttons */ w1 = gtk_hbox_new(1,dialog_button_spacing); gtk_widget_show(w1); gtk_box_pack_end(GTK_BOX(box),w1,0,0,0); w2 = gtk_button_new_with_label("Save & Apply"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,1,1,0); gtk_signal_connect(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(display_option_dialog_save),NULL); w2 = gtk_button_new_with_label("Apply (no save)"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,1,1,0); gtk_signal_connect(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(display_option_dialog_apply),NULL); w2 = gtk_button_new_with_label("Cancel"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,1,1,0); gtk_signal_connect_object(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(gtk_widget_hide),GTK_OBJECT(display_option_dialog)); } static char old_main_font_name[256]; static char old_text_font_name[256]; static char old_table_colour_name[256]; /* make the panel match reality */ static void display_option_dialog_refresh(void) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_dialogposn[dialogs_position]),1); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_animation),animate); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_nopopups),nopopups); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_info_in_main),info_windows_in_main); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_tiletips),tiletips); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_rotatelabels),rotatelabels); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_thinking_claim),thinking_claim); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_alert_mahjong),alert_mahjong); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_showwall[pref_showwall == -1 ? 2 : pref_showwall]),1); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_sort_tiles[sort_tiles]),1); if ( display_size == 0 ) { gtk_entry_set_text(GTK_ENTRY(display_option_size_entry),"(auto)"); } else { char buf[5]; sprintf(buf,"%d",display_size); gtk_entry_set_text(GTK_ENTRY(display_option_size_entry),buf); } gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_iconify),iconify_dialogs_with_main); gtk_entry_set_text(GTK_ENTRY(display_option_dialog_tileset), tileset ? tileset : ""); gtk_entry_set_text(GTK_ENTRY(display_option_dialog_gtk2_rcfile), gtk2_rcfile); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_option_dialog_use_system_gtkrc),use_system_gtkrc); gtk_entry_set_text(GTK_ENTRY(display_option_dialog_tileset_path), tileset_path ? tileset_path : ""); strmcpy(old_main_font_name,main_font_name,256); if ( main_font_name[0] ) { gtk_font_selection_set_font_name(GTK_FONT_SELECTION(mfont_selector), main_font_name); } strmcpy(old_text_font_name,text_font_name,256); if ( text_font_name[0] ) { gtk_font_selection_set_font_name(GTK_FONT_SELECTION(tfont_selector), text_font_name); } else if ( fallback_text_font_name[0] ) { gtk_font_selection_set_font_name(GTK_FONT_SELECTION(tfont_selector), fallback_text_font_name); } strmcpy(old_table_colour_name,table_colour_name,256); } /* apply the selected options */ static void display_option_dialog_apply(void) { int i; unsigned int newdp = dialogs_position; int old; char *newt; GtkWidget *wwindow; int restart = 0; /* set to 1 if we need to recreate the display */ /* if we produce any warnings now, and then restart, they won't be seen in the gui, since the warning window will be destroyed. So disable the warning window, and reinstate it later */ wwindow = warningwindow; warningwindow = NULL; for (i=DialogsCentral; i <= DialogsPopup; i++) { if ( GTK_TOGGLE_BUTTON(display_option_dialog_dialogposn[i])->active ) newdp = i; } if ( newdp != dialogs_position ) restart = 1; dialogs_position = newdp; set_animation(GTK_TOGGLE_BUTTON(display_option_dialog_animation)->active); old = info_windows_in_main; info_windows_in_main = GTK_TOGGLE_BUTTON(display_option_dialog_info_in_main)->active; if ( old != info_windows_in_main ) restart = 1; nopopups = GTK_TOGGLE_BUTTON(display_option_dialog_nopopups)->active; tiletips = GTK_TOGGLE_BUTTON(display_option_dialog_tiletips)->active; int old_rotatelabels = rotatelabels; rotatelabels = GTK_TOGGLE_BUTTON(display_option_dialog_rotatelabels)->active; if ( old_rotatelabels != rotatelabels ) { gtk_label_set_angle(pdisps[3].infolab, rotatelabels ? 270 : 0); gtk_label_set_angle(pdisps[1].infolab, rotatelabels ? 90 : 0); } thinking_claim = GTK_TOGGLE_BUTTON(display_option_dialog_thinking_claim)->active; alert_mahjong = GTK_TOGGLE_BUTTON(display_option_dialog_alert_mahjong)->active; iconify_dialogs_with_main = GTK_TOGGLE_BUTTON(display_option_dialog_iconify)->active; i = (GTK_TOGGLE_BUTTON(display_option_dialog_showwall[0])->active ? 0 : GTK_TOGGLE_BUTTON(display_option_dialog_showwall[1])->active ? 1 : -1 ); if ( pref_showwall != i ) { restart = 1; showwall = pref_showwall = i; } for (i=0;i<3 && !GTK_TOGGLE_BUTTON(display_option_dialog_sort_tiles[i])->active; i++); if ( i < 3 ) sort_tiles = i; old = display_size; newt = (char *)gtk_entry_get_text(GTK_ENTRY(display_option_size_entry)); if ( strcmp(newt,"(auto)") == 0 ) { display_size = 0; } else { sscanf(newt,"%d",&display_size); } if ( display_size != old ) restart = 1; newt = (char *)gtk_entry_get_text(GTK_ENTRY(display_option_dialog_tileset)); if ( ! tileset ) tileset = ""; if ( strcmp(newt,tileset) != 0 ) { restart = 1; tileset = newt; } newt = (char *)gtk_entry_get_text(GTK_ENTRY(display_option_dialog_tileset_path)); if ( ! tileset_path ) tileset_path = ""; if ( strcmp(newt,tileset_path) != 0 ) { restart = 1; tileset_path = newt; } newt = (char *)gtk_entry_get_text(GTK_ENTRY(display_option_dialog_gtk2_rcfile)); if ( strcmp(newt,gtk2_rcfile) != 0 ) { /* we can't restart for this one */ error_dialog_popup("Restart xmj for gtk2_rcfile change to take effect"); strmcpy(gtk2_rcfile,newt,sizeof(gtk2_rcfile)); } old = use_system_gtkrc; use_system_gtkrc = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(display_option_dialog_use_system_gtkrc)); if ( old != use_system_gtkrc ) { /* we can't restart for this one */ error_dialog_popup("Restart xmj for use_system_gtkrc change to take effect"); } if ( strncmp(old_main_font_name,main_font_name,256) != 0 ) { char c[300]; strcpy(c,"gtk-font-name = \""); char *d = c + strlen(c); strmcpy(d,main_font_name,256); strcat(c,"\""); gtk_rc_parse_string(c); gtk_rc_reset_styles(gtk_settings_get_default()); } if ( strncmp(old_text_font_name,text_font_name,256) != 0 ) { char c[300]; strcpy(c,"style \"text\" { font_name = \""); char *d = c + strlen(c); strmcpy(d,text_font_name,256); strcat(c,"\" }"); gtk_rc_parse_string(c); gtk_rc_reset_styles(gtk_settings_get_default()); } if ( strncmp(old_table_colour_name,table_colour_name,256) != 0 ) { char c[300]; strcpy(c,"style \"table\" { bg[NORMAL] = \""); char *d = c + strlen(c); strmcpy(d,table_colour_name,256); strcat(c,"\" }"); gtk_rc_parse_string(c); gtk_rc_reset_styles(gtk_settings_get_default()); } close_saving_posn(display_option_dialog); if ( restart ) { /* need to destroy the warning window ourselves, since we've nulled it */ if ( wwindow ) gtk_widget_destroy(wwindow); destroy_display(); create_display(); } else { /* re-instate warnings */ warningwindow = wwindow; log_msg_add(0,NULL); } } /* save options */ static void display_option_dialog_save(void) { /* first apply */ display_option_dialog_apply(); /* then save */ if ( read_or_update_rcfile(NULL,XmjrcNone,XmjrcDisplay) == 0 ) { error_dialog_popup("Error updating rc file"); } } /* various widgets needed in the playing prefs dialog */ static GtkWidget *playing_prefs_auto_specials; static GtkWidget *playing_prefs_auto_losing; static GtkWidget *playing_prefs_auto_winning; static void playing_prefs_dialog_apply(void); static void playing_prefs_dialog_save(void); static void playing_prefs_dialog_refresh(void); /* dialog for specifying playing preferences */ static void playing_prefs_dialog_init(void) { GtkWidget *box, *but1, *w1, *w2; playing_prefs_dialog = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (playing_prefs_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); gtk_container_set_border_width(GTK_CONTAINER(playing_prefs_dialog), dialog_border_width); box = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(playing_prefs_dialog),box); w1 = gtk_label_new("Automatic declarations:"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(box),w1,0,0,0); /* declaring specials */ playing_prefs_auto_specials = but1 = gtk_check_button_new_with_label("flowers and seasons"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* declaring losing hands */ playing_prefs_auto_losing = but1 = gtk_check_button_new_with_label("losing hands"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* declaring winning hands */ playing_prefs_auto_winning = but1 = gtk_check_button_new_with_label("winning hands"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); /* apply, save and close buttons */ w1 = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(w1); gtk_box_pack_end(GTK_BOX(box),w1,0,0,0); w2 = gtk_button_new_with_label("Save & Apply"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(playing_prefs_dialog_save),NULL); w2 = gtk_button_new_with_label("Apply (no save)"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(playing_prefs_dialog_apply),NULL); w2 = gtk_button_new_with_label("Cancel"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect_object(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(gtk_widget_hide),GTK_OBJECT(playing_prefs_dialog)); } /* make the playing prefs panel match reality */ static void playing_prefs_dialog_refresh(void) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(playing_prefs_auto_specials), playing_auto_declare_specials); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(playing_prefs_auto_losing), playing_auto_declare_losing); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(playing_prefs_auto_winning), playing_auto_declare_winning); gtk_widget_hide(playing_prefs_dialog); } /* apply the selected playing options */ static void playing_prefs_dialog_apply(void) { playing_auto_declare_specials = GTK_TOGGLE_BUTTON(playing_prefs_auto_specials)->active; playing_auto_declare_losing = GTK_TOGGLE_BUTTON(playing_prefs_auto_losing)->active; playing_auto_declare_winning = GTK_TOGGLE_BUTTON(playing_prefs_auto_winning)->active; gtk_widget_hide(playing_prefs_dialog); } /* save playing options */ static void playing_prefs_dialog_save(void) { /* first apply */ playing_prefs_dialog_apply(); /* then save */ if ( read_or_update_rcfile(NULL,XmjrcNone,XmjrcPlaying) == 0 ) { error_dialog_popup("Error updating rc file"); } } /* debug options dialog */ static void debug_options_dialog_apply(void); static void debug_options_dialog_save(void); static void debug_options_dialog_refresh(void); static GtkWidget *debug_options_report_buttons[DebugReportsAlways+1]; static void debug_options_dialog_init(void); static void debug_report_query_popup(void) { if ( ! debug_options_dialog ) debug_options_dialog_init(); dialog_popup(debug_options_reporting,DPCentred); } static void debug_options_dialog_init(void) { GtkWidget *box, *but0, *but1, *but2, *but3, *w1, *w2; debug_options_dialog = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (debug_options_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); gtk_container_set_border_width(GTK_CONTAINER(debug_options_dialog), dialog_border_width); box = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(debug_options_dialog),box); w1 = gtk_label_new("Fault reporting:\n" "When certain internal errors occur, this program\n" "can file a debugging report over the Internet to the author.\n" "This report includes the current game status; it may include\n" "the entire game history. Therefore the report may contain\n" "sensitive information such as your username and password for the\n" "server you are using.\n" "When may a debugging report be sent over the Internet?"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(box),w1,0,0,0); but0 = gtk_radio_button_new_with_label(NULL,"unspecified"); /* gtk_widget_show(but0); */ gtk_box_pack_start(GTK_BOX(box),but0,0,0,0); debug_options_report_buttons[DebugReportsUnspecified] = but0; but1 = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(but0)),"never"); gtk_widget_show(but1); gtk_box_pack_start(GTK_BOX(box),but1,0,0,0); debug_options_report_buttons[DebugReportsNever] = but1; but2 = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(but0)),"ask each time"); gtk_widget_show(but2); gtk_box_pack_start(GTK_BOX(box),but2,0,0,0); debug_options_report_buttons[DebugReportsAsk] = but2; but3 = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(but0)),"always"); gtk_widget_show(but3); gtk_box_pack_start(GTK_BOX(box),but3,0,0,0); debug_options_report_buttons[DebugReportsAlways] = but3; /* apply, save and close buttons */ w1 = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(w1); gtk_box_pack_end(GTK_BOX(box),w1,0,0,0); w2 = gtk_button_new_with_label("Save & Apply"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(debug_options_dialog_save),NULL); w2 = gtk_button_new_with_label("Apply (no save)"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(debug_options_dialog_apply),NULL); w2 = gtk_button_new_with_label("Cancel"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect_object(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(gtk_widget_hide),GTK_OBJECT(debug_options_dialog)); /* Now create the dialog that is popped up each time (if necessary) to ask whether to file a report */ if ( debug_options_reporting ) { gtk_widget_destroy(debug_options_reporting); } debug_options_reporting = gtk_window_new(GTK_WINDOW_DIALOG); gtk_signal_connect (GTK_OBJECT (debug_options_reporting), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); gtk_container_set_border_width(GTK_CONTAINER(debug_options_reporting), dialog_border_width); box = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(box); gtk_container_add(GTK_CONTAINER(debug_options_reporting),box); w1 = gtk_label_new("This program has encountered an unexpected error.\n" "It can file a debugging report over the Internet to the author.\n" "This report includes the current game status; it may include\n" "the entire game history. Therefore the report may contain\n" "sensitive information such as your username and password for the\n" "www.TUMJ.com game server.\n" "May the report be sent?"); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(box),w1,0,0,0); w1 = gtk_hbox_new(0,dialog_button_spacing); gtk_widget_show(w1); gtk_box_pack_end(GTK_BOX(box),w1,0,0,0); w2 = gtk_button_new_with_label("Send report"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect_object(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(file_report),(gpointer)1); w2 = gtk_button_new_with_label("Cancel"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); gtk_signal_connect_object(GTK_OBJECT(w2),"clicked",GTK_SIGNAL_FUNC(file_report),NULL); } static void debug_options_dialog_refresh(void) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(debug_options_report_buttons[debug_reports]),1); } static void debug_options_dialog_apply(void) { int i; for ( i=DebugReportsNever; i <= DebugReportsAlways; i++ ) { if ( GTK_TOGGLE_BUTTON(debug_options_report_buttons[i])->active ) debug_reports = i; } gtk_widget_hide(debug_options_dialog); } static void debug_options_dialog_save(void) { /* first apply */ debug_options_dialog_apply(); /* then save */ if ( read_or_update_rcfile(NULL,XmjrcNone,XmjrcMisc) == 0 ) { error_dialog_popup("Error updating rc file"); } } /* callback used below */ static void enabler_callback(GtkWidget *w UNUSED, gpointer data) { GameOptionEntry *goe = data; goe->enabled = ! goe->enabled; make_or_refresh_option_updater(goe,0); } /* ditto */ static void make_sensitive(GtkWidget *w) { gtk_widget_set_sensitive(w,1); } /* given a GameOptionEntry, create or refresh an updater widget, stored in the userdata field of the entry. Second arg says if this is preference panel (or option panel) */ GtkWidget *make_or_refresh_option_updater(GameOptionEntry *goe,int prefsp) { /* description of option current value new value update */ GtkWidget *vbox = NULL, *hbox = NULL, *ohbox, *w1 = NULL, *w2, *old, *lim, *halflim, *nolim, *resetter; GtkAdjustment *adj; int dbls, pts; int centilims; old = goe->userdata; if ( ! old ) { resetter = gtk_button_new_with_label("Reset"); /* needed early */ } else { resetter = (GtkWidget *)gtk_object_get_data(GTK_OBJECT(old),"reset"); } if ( ! old ) { vbox = gtk_vbox_new(0,5); gtk_widget_show(vbox); if ( prefsp ) { w2 = gtk_hbox_new(0,5); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(vbox),w2,0,0,0); w1 = gtk_button_new_with_label("Add pref"); GTK_WIDGET_UNSET_FLAGS(w1,GTK_CAN_FOCUS); gtk_object_set_data(GTK_OBJECT(vbox),"enabled",(gpointer) w1); gtk_signal_connect(GTK_OBJECT(w1),"clicked",GTK_SIGNAL_FUNC(enabler_callback),(gpointer) goe); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); w1 = gtk_label_new(goe->desc); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(w2),w1,0,0,0); } else { w1 = gtk_label_new(goe->desc); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(vbox),w1,0,0,0); } if ( prefsp ) { ohbox = gtk_hbox_new(0,0); gtk_widget_show(ohbox); gtk_box_pack_start(GTK_BOX(vbox),ohbox,0,0,0); hbox = gtk_hbox_new(0,0); gtk_object_set_data(GTK_OBJECT(vbox),"hbox",(gpointer) hbox); gtk_widget_show(hbox); gtk_box_pack_start(GTK_BOX(ohbox),hbox,1,1,0); } else { hbox = gtk_hbox_new(0,0); gtk_widget_show(hbox); gtk_box_pack_start(GTK_BOX(vbox),hbox,0,0,0); } } else { vbox = old; } switch ( goe->type ) { case GOTBool: if ( ! old ) { w1 = gtk_hbox_new(0,0); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(hbox),w1,0,0,5); w2 = gtk_check_button_new_with_label("on/off"); gtk_signal_connect_object(GTK_OBJECT(w2),"toggled",GTK_SIGNAL_FUNC(make_sensitive),GTK_OBJECT(resetter)); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,5); gtk_object_set_data(GTK_OBJECT(vbox),"checkbox",w2); } else { w2 = gtk_object_get_data(GTK_OBJECT(vbox),"checkbox"); } gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w2), goe->value.optbool); break; case GOTNat: case GOTInt: if ( ! old ) { w1 = gtk_hbox_new(0,0); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(hbox),w1,0,0,5); adj = (GtkAdjustment *)gtk_adjustment_new(goe->value.optint, (goe->type == GOTNat ) ? 0 : -1000000,1000000, 1,10,0); w2 = gtk_spin_button_new(adj,1.0,0); gtk_widget_set_usize(w2,70,0); gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(w2),1); gtk_signal_connect_object(GTK_OBJECT(w2),"changed",GTK_SIGNAL_FUNC(make_sensitive),GTK_OBJECT(resetter)); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,5); gtk_object_set_data(GTK_OBJECT(vbox),"spinbutton",w2); } else { w2 = gtk_object_get_data(GTK_OBJECT(vbox),"spinbutton"); gtk_spin_button_set_value(GTK_SPIN_BUTTON(w2),goe->value.optint); } break; case GOTString: if ( ! old ) { w1 = gtk_hbox_new(0,0); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(hbox),w1,0,0,5); w2 = gtk_entry_new(); gtk_signal_connect_object(GTK_OBJECT(w2),"changed",GTK_SIGNAL_FUNC(make_sensitive),GTK_OBJECT(resetter)); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,5); gtk_object_set_data(GTK_OBJECT(vbox),"entry",w2); } else { w2 = gtk_object_get_data(GTK_OBJECT(vbox),"entry"); } gtk_entry_set_text(GTK_ENTRY(w2),goe->value.optstring); break; case GOTScore: pts = goe->value.optscore % 10000; dbls = (goe->value.optscore / 10000) % 100; centilims = goe->value.optscore/1000000; if ( ! old ) { w1 = gtk_hbox_new(0,0); gtk_widget_show(w1); gtk_box_pack_start(GTK_BOX(hbox),w1,0,0,5); lim = w2 = gtk_radio_button_new_with_label(NULL,"lim"); gtk_signal_connect_object(GTK_OBJECT(w2),"toggled",GTK_SIGNAL_FUNC(make_sensitive),GTK_OBJECT(resetter)); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,5); gtk_object_set_data(GTK_OBJECT(vbox),"lim",w2); halflim = w2 = gtk_radio_button_new_with_label(gtk_radio_button_group (GTK_RADIO_BUTTON(lim)),"1/2 lim"); gtk_signal_connect_object(GTK_OBJECT(w2),"toggled",GTK_SIGNAL_FUNC(make_sensitive),GTK_OBJECT(resetter)); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,5); gtk_object_set_data(GTK_OBJECT(vbox),"halflim",w2); nolim = w2 = gtk_radio_button_new_with_label(gtk_radio_button_group (GTK_RADIO_BUTTON(lim)),""); gtk_signal_connect_object(GTK_OBJECT(w2),"toggled",GTK_SIGNAL_FUNC(make_sensitive),GTK_OBJECT(resetter)); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,5); gtk_object_set_data(GTK_OBJECT(vbox),"nolim",w2); } else { lim = gtk_object_get_data(GTK_OBJECT(vbox),"lim"); halflim = gtk_object_get_data(GTK_OBJECT(vbox),"halflim"); nolim = gtk_object_get_data(GTK_OBJECT(vbox),"nolim"); } switch ( centilims ) { case 100: gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lim),1); break; case 50: gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(halflim),1); break; case 0: gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(nolim),1); break; default: warn("Unexpected fraction (%d) of a limit in score; setting to limit", centilims); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lim),1); break; } if ( ! old ) { adj = (GtkAdjustment *)gtk_adjustment_new(dbls, 0,100, 1,10,0); w2 = gtk_spin_button_new(adj,1.0,0); gtk_widget_set_usize(w2,40,0); gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(w2),1); gtk_signal_connect_object(GTK_OBJECT(w2),"changed",GTK_SIGNAL_FUNC(make_sensitive),GTK_OBJECT(resetter)); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,5); gtk_object_set_data(GTK_OBJECT(vbox),"dbls",w2); w2 = gtk_label_new("dbls"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); } else { w2 = gtk_object_get_data(GTK_OBJECT(vbox),"dbls"); gtk_spin_button_set_value(GTK_SPIN_BUTTON(w2),dbls); } if ( ! old ) { adj = (GtkAdjustment *)gtk_adjustment_new(pts, 0,10000, 1,10,0); w2 = gtk_spin_button_new(adj,1.0,0); gtk_widget_set_usize(w2,60,0); gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(w2),1); gtk_signal_connect_object(GTK_OBJECT(w2),"changed",GTK_SIGNAL_FUNC(make_sensitive),GTK_OBJECT(resetter)); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,5); gtk_object_set_data(GTK_OBJECT(vbox),"pts",w2); w2 = gtk_label_new("pts"); gtk_widget_show(w2); gtk_box_pack_start(GTK_BOX(w1),w2,0,0,0); } else { w2 = gtk_object_get_data(GTK_OBJECT(vbox),"pts"); gtk_spin_button_set_value(GTK_SPIN_BUTTON(w2),pts); } break; } if ( ! old ) { /* The button is created at the top so we can feed it to callbacks * w1 = gtk_button_new_with_label("Reset"); */ w1 = resetter; gtk_widget_show(w1); gtk_box_pack_end(GTK_BOX(hbox),w1,0,0,5); gtk_object_set_data(GTK_OBJECT(vbox),"reset",w1); gtk_signal_connect(GTK_OBJECT(w1),"clicked",GTK_SIGNAL_FUNC(option_reset_callback), (gpointer) goe); } if ( ! old ) goe->userdata = vbox; /* If there's an enabled button, deal with it */ w1 = (GtkWidget *)gtk_object_get_data(GTK_OBJECT(vbox),"enabled"); if ( w1 ) { hbox = (GtkWidget *)gtk_object_get_data(GTK_OBJECT(vbox),"hbox"); if ( goe->enabled ) { gtk_widget_show(hbox); gtk_label_set_text(GTK_LABEL(GTK_BIN(w1)->child),"Remove pref"); } else { gtk_widget_hide(hbox); gtk_label_set_text(GTK_LABEL(GTK_BIN(w1)->child),"Add pref"); } } /* make the reset button insensitive */ gtk_widget_set_sensitive(resetter,0); return vbox; } /* given an optiontable, pack a list of updating widgets into a vbox; or just refresh if already there (2nd arg) If third arg is 1, this is prefs panel, else options panel */ static GtkWidget *build_or_refresh_optionprefs_panel(GameOptionTable *got, GtkWidget *panel, int prefsp) { int i; GtkWidget *vbox,*hs,*u; int first = 1; if ( ! got ) { /* destroy the panel's children, if any */ if ( panel ) { gtk_container_foreach(GTK_CONTAINER(panel),(GtkCallback) gtk_widget_destroy,NULL); } } if ( ! panel ) { vbox = gtk_vbox_new(0,0); gtk_widget_show(vbox); } else { vbox = panel; } for ( i = 0 ; got && i < got->numoptions; i++ ) { /* if this is actually a filler unknown option, skip it */ if ( got->options[i].name[0] == 0 ) continue; /* likewise for end */ if ( got->options[i].option == GOEnd ) continue; /* if this is the options panel, and the option is not enabled, skip */ if ( ! prefsp && ! got->options[i].enabled ) continue; u = got->options[i].userdata; if ( ! first && u == NULL ) { hs = gtk_hseparator_new(); gtk_widget_show(hs); gtk_box_pack_start(GTK_BOX(vbox),hs,0,0,0); } else first = 0; if ( u ) { make_or_refresh_option_updater(&got->options[i],prefsp); } else { u = make_or_refresh_option_updater(&got->options[i],prefsp); gtk_box_pack_start(GTK_BOX(vbox),u,0,0,5); } } return vbox; } GtkWidget *build_or_refresh_option_panel(GameOptionTable *got, GtkWidget *panel) { return build_or_refresh_optionprefs_panel(got,panel,0); } GtkWidget *build_or_refresh_prefs_panel(GameOptionTable *got, GtkWidget *panel) { return build_or_refresh_optionprefs_panel(got,panel,1); } void option_reset_callback(GtkWidget *w UNUSED, gpointer data) { make_or_refresh_option_updater((GameOptionEntry *)data,0); } void option_updater_callback(GtkWidget *w UNUSED, gpointer data UNUSED) { GameOptionTable *got; PMsgSetGameOptionMsg psgom; char buf[1024]; int centilims,dbls,pts; int i, changed, val; if ( ! the_game ) return; got = &the_game->option_table; for ( i = 0 ; i < got->numoptions ; i++ ) { w = got->options[i].userdata; if ( ! w ) continue; changed = 0; psgom.type = PMsgSetGameOption; strncpy(psgom.optname,got->options[i].name,16); switch ( got->options[i].type ) { case GOTBool: val = GTK_TOGGLE_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"checkbox"))->active; psgom.optvalue = val ? "1" : "0"; if ( val != got->options[i].value.optbool ) changed = 1; break; case GOTNat: case GOTInt: gtk_spin_button_update(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"spinbutton"))); val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"spinbutton"))); sprintf(buf,"%d",val); psgom.optvalue = buf; if ( val != got->options[i].value.optint ) changed = 1; break; case GOTString: psgom.optvalue = (char *)gtk_entry_get_text(GTK_ENTRY(gtk_object_get_data(GTK_OBJECT(w),"entry"))); if ( strcmp(psgom.optvalue,got->options[i].value.optstring) != 0 ) changed = 1; break; case GOTScore: centilims = 100*GTK_TOGGLE_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"lim"))->active; centilims += 50 * GTK_TOGGLE_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"halflim"))->active; gtk_spin_button_update(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"dbls"))); dbls = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"dbls"))); gtk_spin_button_update(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"pts"))); pts = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"pts"))); val = 1000000*centilims + 10000 * dbls + pts; sprintf(buf,"%d",val); psgom.optvalue = buf; if ( val != got->options[i].value.optscore ) changed = 1; break; } if ( changed ) send_packet(&psgom); } gtk_widget_hide(game_option_dialog); } void prefs_updater_callback(GtkWidget *w UNUSED, gpointer data UNUSED) { GameOptionTable *got; char buf[1024]; int centilims,dbls,pts; int i, changed, val; char *vals; got = &prefs_table; for ( i = 0 ; i < got->numoptions ; i++ ) { w = got->options[i].userdata; if ( ! w ) continue; changed = 0; switch ( got->options[i].type ) { case GOTBool: val = GTK_TOGGLE_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"checkbox"))->active; if ( val != got->options[i].value.optbool ) changed = 1; got->options[i].value.optbool = val; break; case GOTNat: case GOTInt: gtk_spin_button_update(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"spinbutton"))); val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"spinbutton"))); sprintf(buf,"%d",val); if ( val != got->options[i].value.optint ) changed = 1; got->options[i].value.optint = val; break; case GOTString: vals = (char *)gtk_entry_get_text(GTK_ENTRY(gtk_object_get_data(GTK_OBJECT(w),"entry"))); if ( strcmp(vals,got->options[i].value.optstring) != 0 ) changed = 1; strmcpy(got->options[i].value.optstring,vals,128); break; case GOTScore: centilims = 100*GTK_TOGGLE_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"lim"))->active; centilims += 50*GTK_TOGGLE_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"halflim"))->active; gtk_spin_button_update(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"dbls"))); dbls = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"dbls"))); gtk_spin_button_update(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"pts"))); pts = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(gtk_object_get_data(GTK_OBJECT(w),"pts"))); val = 1000000*centilims + 10000 * dbls + pts; if ( val != got->options[i].value.optscore ) changed = 1; got->options[i].value.optscore = val; break; } if ( changed ) make_or_refresh_option_updater(&got->options[i],1); } /* now actually save it */ read_or_update_rcfile(NULL,XmjrcNone,XmjrcGame); /* and close */ gtk_widget_hide(game_prefs_dialog); } static void apply_game_prefs_callback(GtkWidget *w UNUSED, gpointer data UNUSED) { apply_game_prefs(); gtk_widget_hide(game_prefs_dialog); } void game_option_init(void) { GtkWidget *sbar, *obox, *bbox, *tmp; game_option_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (game_option_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); /* must allow shrinking */ gtk_window_set_policy(GTK_WINDOW(game_option_dialog),TRUE,TRUE,FALSE); gtk_window_set_title(GTK_WINDOW(game_option_dialog),"Current Game Options"); /* reasonable size is ... */ gtk_window_set_default_size(GTK_WINDOW(game_option_dialog),450,400); obox = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(obox),dialog_border_width); gtk_widget_show(obox); gtk_container_add(GTK_CONTAINER(game_option_dialog),obox); game_option_panel = build_or_refresh_option_panel(the_game ? &the_game->option_table : NULL, game_option_panel); sbar = gtk_scrolled_window_new(NULL,NULL); gtk_widget_show(sbar); gtk_box_pack_start(GTK_BOX(obox),sbar,1,1,0); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sbar), game_option_panel); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sbar), GTK_POLICY_NEVER,GTK_POLICY_AUTOMATIC); bbox = gtk_hbox_new(1,dialog_button_spacing); gtk_widget_show(bbox); gtk_box_pack_end(GTK_BOX(obox),bbox,0,0,0); tmp = gtk_button_new_with_label("Close"); GTK_WIDGET_UNSET_FLAGS(tmp,GTK_CAN_FOCUS); /* entry widget shd focus */ gtk_signal_connect_object(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(close_saving_posn),GTK_OBJECT(game_option_dialog)); gtk_widget_show(tmp); gtk_box_pack_end(GTK_BOX(bbox),tmp,1,1,0); game_option_prefs_button = tmp = gtk_button_new_with_label("Apply preferences"); GTK_WIDGET_UNSET_FLAGS(tmp,GTK_CAN_FOCUS); /* entry widget shd focus */ gtk_signal_connect(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(apply_game_prefs_callback),NULL); gtk_widget_show(tmp); gtk_box_pack_end(GTK_BOX(bbox),tmp,1,1,0); game_option_apply_button = tmp = gtk_button_new_with_label("Apply changes"); GTK_WIDGET_UNSET_FLAGS(tmp,GTK_CAN_FOCUS); /* entry widget shd focus */ gtk_signal_connect(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(option_updater_callback),NULL); gtk_widget_show(tmp); gtk_box_pack_end(GTK_BOX(bbox),tmp,1,1,0); } void game_prefs_init(void) { GtkWidget *sbar, *obox, *bbox, *tmp; game_prefs_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (game_prefs_dialog), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); /* must allow shrinking */ gtk_window_set_policy(GTK_WINDOW(game_prefs_dialog),TRUE,TRUE,FALSE); gtk_window_set_title(GTK_WINDOW(game_prefs_dialog),"Game Preferences"); /* reasonable size is ... */ gtk_window_set_default_size(GTK_WINDOW(game_prefs_dialog),450,400); obox = gtk_vbox_new(0,dialog_vert_spacing); gtk_container_set_border_width(GTK_CONTAINER(obox),dialog_border_width); gtk_widget_show(obox); gtk_container_add(GTK_CONTAINER(game_prefs_dialog),obox); game_prefs_panel = build_or_refresh_prefs_panel(&prefs_table, game_prefs_panel); sbar = gtk_scrolled_window_new(NULL,NULL); gtk_widget_show(sbar); gtk_box_pack_start(GTK_BOX(obox),sbar,1,1,0); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sbar), game_prefs_panel); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sbar), GTK_POLICY_NEVER,GTK_POLICY_AUTOMATIC); bbox = gtk_hbox_new(1,dialog_button_spacing); gtk_widget_show(bbox); gtk_box_pack_end(GTK_BOX(obox),bbox,0,0,0); tmp = gtk_button_new_with_label("Cancel"); GTK_WIDGET_UNSET_FLAGS(tmp,GTK_CAN_FOCUS); /* entry widget shd focus */ gtk_signal_connect_object(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(close_saving_posn),GTK_OBJECT(game_prefs_dialog)); gtk_widget_show(tmp); gtk_box_pack_end(GTK_BOX(bbox),tmp,1,1,0); tmp = gtk_button_new_with_label("Save changes"); GTK_WIDGET_UNSET_FLAGS(tmp,GTK_CAN_FOCUS); /* entry widget shd focus */ gtk_signal_connect(GTK_OBJECT(tmp),"clicked",GTK_SIGNAL_FUNC(prefs_updater_callback),NULL); gtk_widget_show(tmp); gtk_box_pack_end(GTK_BOX(bbox),tmp,1,1,0); } /* set the dialog position, changing if necessary */ void set_dialog_posn(DialogPosition p) { if ( p != dialogs_position ) { /* if the new position is not below, we need to allow the top window to shrink down */ if ( p != DialogsBelow ) { gtk_window_set_policy(GTK_WINDOW(topwindow),1,1,1); } else { /* don't let it shrink */ gtk_window_set_policy(GTK_WINDOW(topwindow),0,1,0); } dialogs_position = p; create_dialogs(); } } void set_animation(int a) { animate = a; /* request appropriate pause time */ if ( !monitor && the_game ) { PMsgSetPlayerOptionMsg spom; spom.type = PMsgSetPlayerOption; spom.option = PODelayTime; spom.ack = 0; spom.value = animate ? 10 : 5; /* 0.5 or 1 second min delay */ spom.text = NULL; send_packet(&spom); } } /* do_chow: if there's only one possible chow, do it, otherwise put up a dialog box to choose. Sneakily, this is defined as a callback, so that the choosing buttons can call this specifying the chow pos, and the main program can call it with AnyPos. */ void do_chow(GtkWidget *widg UNUSED, gpointer data) { ChowPosition cpos = (ChowPosition)data; PMsgChowMsg cm; int i,n,j; static int lastdiscard; TileSet ts; cm.type = PMsgChow; cm.discard = the_game->serial; if ( !monitor && cpos == AnyPos ) { /* we want to avoid working and popping up again if for some reason we're already popped up. (So that this procedure is idempotent.) */ if ( GTK_WIDGET_VISIBLE(chow_dialog) && cm.discard == lastdiscard ) return; lastdiscard = cm.discard; ts.type = Chow; /* set up the boxes */ for (n=0,i=0,j=0;i<3;i++) { if ( player_can_chow(our_player,the_game->tile,i) ) { ts.tile = the_game->tile - i; tilesetbox_set(&chowtsbs[i],&ts,0); tilesetbox_highlight_nth(&chowtsbs[i],i); gtk_widget_show(chowbuttons[i]); n++; j = i; } else { gtk_widget_hide(chowtsbs[i].widget); gtk_widget_hide(chowbuttons[i]); } } /* if there is only one possibility, don't bother to ask. Also if there is no possibility: we'll then get an error from the server, which saves us having to worry about it */ if ( n <= 1 ) { cpos = j; } else { /* pop down the discard dialog */ gtk_widget_hide(discard_dialog->widget); dialog_popup(chow_dialog,DPCentred); } } /* Now we might have found the position */ if ( cpos != AnyPos ) { cm.cpos = cpos; send_packet(&cm); /* if the chowpending flag isn't set, we must be working with an old server and be in the mahjonging state. Pop ourselves down, so that if something goes wrong it's the discard dialog that comes back up */ if ( ! the_game->chowpending ) gtk_widget_hide(chow_dialog); } } /* Generic popup centered over main window. Positioning is given by DPCentred - centered over main window DPOnDiscard - bottom left corner in same place as discard dialog DPErrorPos - for error dialogs: centred over top of main window, and offset by a multiple of num_error_dialogs DPNone - don't touch the positioning at all ' DPCentredOnce - centre it on first popup, then don't fiddle ' DPOnDiscardOnce - on discard dialog first time, then don't fiddle ' If the widget is not a window, then DPCentredOnce and DPNone are equivalent to DPCentred, and DPOnDiscardOnce is equivalent to DPOnDiscard. */ void dialog_popup(GtkWidget *dialog,DPPosn posn) { gint x,y,w,h; GtkRequisition r = { 0, 0}; /* So that we don't work if it's already popped up: */ if ( GTK_WIDGET_VISIBLE(dialog) ) return; /* if the position has been set, don't mess */ if ( GTK_IS_WINDOW(dialog) && gtk_object_get_data(GTK_OBJECT(dialog),"position-set") ) { gtk_widget_set_uposition(dialog, (gint)(intptr_t)gtk_object_get_data(GTK_OBJECT(dialog),"position-x"), (gint)(intptr_t)gtk_object_get_data(GTK_OBJECT(dialog),"position-y")); gtk_widget_show(dialog); // This ought to work, but seems to confuse my wm //gtk_window_present(GTK_WINDOW(dialog)); return; } if ( ! GTK_IS_WINDOW(dialog) ) { if ( posn == DPCentredOnce ) posn = DPCentred; if ( posn == DPOnDiscardOnce ) posn = DPOnDiscard; if ( posn == DPNone ) posn = DPCentred; } /* get size of discard widget if necessary */ if ( (posn == DPOnDiscard || posn == DPOnDiscardOnce) && discard_req.width == 0 ) { gtk_widget_size_request(discard_dialog->widget,&discard_req); gtk_widget_set_usize(discard_dialog->widget, discard_req.width, discard_req.height); } gtk_widget_size_request(dialog,&r); if ( GTK_IS_WINDOW(dialog) ) { gdk_window_get_size(topwindow->window,&w,&h); gdk_window_get_deskrelative_origin(topwindow->window,&x,&y); } else { w = discard_area_alloc.width; h = discard_area_alloc.height; x = y = 0; } if ( dialogs_position != DialogsBelow || GTK_IS_WINDOW(dialog) ) { if ( posn == DPOnDiscard ) { if ( GTK_IS_WINDOW(dialog) ) gtk_widget_set_uposition(dialog, x + w/2 - r.width/2 - (discard_req.width - r.width)/2, y + h/2 - r.height/2 + (discard_req.height - r.height)/2); else gtk_fixed_move(GTK_FIXED(discard_area),dialog, x + w/2 - r.width/2 - (discard_req.width - r.width)/2, y + h/2 - r.height/2 + (discard_req.height - r.height)/2); } else if ( posn == DPOnDiscardOnce ) { if ( gtk_object_get_data(GTK_OBJECT(dialog),"position-set") ) { /* do nothing */ } else { if ( GTK_IS_WINDOW(dialog) ) gtk_widget_set_uposition(dialog, x + w/2 - r.width/2 - (discard_req.width - r.width)/2, y + h/2 - r.height/2 + (discard_req.height - r.height)/2); else gtk_fixed_move(GTK_FIXED(discard_area),dialog, x + w/2 - r.width/2 - (discard_req.width - r.width)/2, y + h/2 - r.height/2 + (discard_req.height - r.height)/2); gtk_object_set_data(GTK_OBJECT(dialog),"position-set",(gpointer)1); } } else if ( posn == DPCentred ) { if ( GTK_IS_WINDOW(dialog) ) gtk_widget_set_uposition(dialog, x + w/2 - r.width/2, y + h/2 - r.height/2); else gtk_fixed_move(GTK_FIXED(discard_area),dialog, x + w/2 - r.width/2, y + h/2 - r.height/2); } else if ( posn == DPCentredOnce ) { if ( gtk_object_get_data(GTK_OBJECT(dialog),"position-set") ) { /* do nothing */ if ( GTK_IS_WINDOW(dialog) ) { gtk_widget_set_uposition(dialog, (gint)(intptr_t)gtk_object_get_data(GTK_OBJECT(dialog),"position-x"), (gint)(intptr_t)gtk_object_get_data(GTK_OBJECT(dialog),"position-y")); } } else { if ( GTK_IS_WINDOW(dialog) ) gtk_widget_set_uposition(dialog, x + w/2 - r.width/2, y + h/2 - r.height/2); else gtk_fixed_move(GTK_FIXED(discard_area),dialog, x + w/2 - r.width/2, y + h/2 - r.height/2); gtk_object_set_data(GTK_OBJECT(dialog),"position-set",(gpointer)1); } } else if ( posn == DPErrorPos ) { if ( GTK_IS_WINDOW(dialog) ) gtk_widget_set_uposition(dialog, x + w/2 - r.width/2, y + h/4 - r.height/2 + 10*num_error_dialogs); else gtk_fixed_move(GTK_FIXED(discard_area),dialog, x + w/2 - r.width/2, y + h/4 - r.height/2 + 10*num_error_dialogs); } } gtk_widget_show(dialog); if ( GTK_IS_WINDOW(dialog) ) { // This ought to work, but seems to confuse my wm // gtk_window_present(GTK_WINDOW(dialog)); } else { // This ought to work, but seems to confuse my wm // gtk_window_present(GTK_WINDOW(topwindow)); } } /* window to display game status. 0 1 2... 3.... We are WIND; id: n Name: name total score: nnn and for right, opposite, left */ static char *status_poses[] = { "We are ", "Right is ", "Opp. is ", "Left is " }; static GtkWidget *status_poslabels[4]; static GtkWidget *status_windlabels[4]; static GtkWidget *status_idlabels[4]; static GtkWidget *status_namelabels[4]; static GtkWidget *status_scorelabels[4]; static GtkWidget *status_pwind; static GtkWidget *status_status; static GtkWidget *status_suspended; static GtkWidget *status_tilesleft; /* and for the short version */ static GtkWidget *status_chairs[4]; void status_init(void) { int i; GtkWidget *table, *w; if ( info_windows_in_main ) { status_window = gtk_hbox_new(0,0); gtk_widget_show(status_window); gtk_box_pack_start(GTK_BOX(info_box),status_window,0,0,0); } else { status_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect(GTK_OBJECT (status_window), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); gtk_window_set_title(GTK_WINDOW(status_window),"Game information"); } gtk_container_set_border_width(GTK_CONTAINER(status_window), dialog_border_width); /* this is what we used to do, and will still do for separate windows */ if ( ! info_windows_in_main ) { table = gtk_table_new(12,4,0); gtk_widget_show(table); gtk_container_add(GTK_CONTAINER(status_window),table); for ( i = 0 ; i < 4 ; i++ ) { status_poslabels[i] = w = gtk_label_new(status_poses[i]); gtk_widget_show(w); gtk_label_set_justify(GTK_LABEL(w),GTK_JUSTIFY_LEFT); gtk_table_attach_defaults(GTK_TABLE(table),w, 0,1,2*i,2*i+1); status_windlabels[i] = w = gtk_label_new("none"); gtk_widget_show(w); gtk_label_set_justify(GTK_LABEL(w),GTK_JUSTIFY_LEFT); gtk_table_attach_defaults(GTK_TABLE(table),w, 1,2,2*i,2*i+1); status_idlabels[i] = w = gtk_label_new(" ID: 0"); gtk_widget_show(w); gtk_label_set_justify(GTK_LABEL(w),GTK_JUSTIFY_LEFT); gtk_table_attach_defaults(GTK_TABLE(table),w, 2,3,2*i,2*i+1); status_namelabels[i] = w = gtk_label_new(" Name: unknown"); gtk_widget_show(w); gtk_label_set_justify(GTK_LABEL(w),GTK_JUSTIFY_LEFT); gtk_table_attach_defaults(GTK_TABLE(table),w, 3,4,2*i,2*i+1); status_scorelabels[i] = w = gtk_label_new("total score: 0"); gtk_widget_show(w); gtk_label_set_justify(GTK_LABEL(w),GTK_JUSTIFY_LEFT); gtk_table_attach_defaults(GTK_TABLE(table),w, 1,4,2*i+1,2*i+2); } status_pwind = w = gtk_label_new("Prevailing wind: none"); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w, 0,4,8,9); status_status = w = gtk_label_new("no game"); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w, 0,4,9,10); status_tilesleft = w = gtk_label_new(""); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w, 0,4,10,11); w = gtk_button_new_with_label("Close"); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w, 0,4,11,12); gtk_signal_connect_object(GTK_OBJECT(w),"clicked",GTK_SIGNAL_FUNC(close_saving_posn),GTK_OBJECT(status_window)); } else { /* and this is for the new in-window information */ table = gtk_table_new(4,7,0); gtk_widget_show(table); gtk_container_add(GTK_CONTAINER(status_window),table); /* opposite player */ status_chairs[2] = w = gtk_label_new("(seat empty)"); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w,1,4,0,1); /* left player */ status_chairs[3] = w = gtk_label_new("(seat empty)"); gtk_label_set_justify(GTK_LABEL(w),GTK_JUSTIFY_LEFT); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w,0,3,1,2); /* right player */ status_chairs[1] = w = gtk_label_new("(seat empty)"); gtk_label_set_justify(GTK_LABEL(w),GTK_JUSTIFY_RIGHT); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w,2,5,2,3); /* us */ status_chairs[0] = w = gtk_label_new("(seat empty)"); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w,1,4,3,4); /* spacing */ w = gtk_label_new(" "); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w,5,6,0,1); /* round */ status_pwind = w = gtk_label_new(" "); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w,6,7,0,1); /* game status */ status_status = w = gtk_label_new("no game"); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w,6,7,1,2); /* tiles left */ status_tilesleft = w = gtk_label_new(""); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w,6,7,2,3); /* game suspended */ status_suspended = w = gtk_label_new(""); gtk_widget_show(w); gtk_table_attach_defaults(GTK_TABLE(table),w,6,7,3,4); } } void status_update(int game_over) { int i,s; const char *pn; PlayerP p; static char buf[256]; if ( ! the_game ) return; for ( i=0 ; i < 4 ; i++ ) { s = (our_seat+i)%NUM_SEATS; p = the_game->players[s]; if ( !info_windows_in_main ) { gtk_label_set_text(GTK_LABEL(status_windlabels[i]), windnames[p->wind]); sprintf(buf," ID: %d",p->id); gtk_label_set_text(GTK_LABEL(status_idlabels[i]),buf); snprintf(buf,256," Name: %s",p->name); gtk_label_set_text(GTK_LABEL(status_namelabels[i]),buf); sprintf(buf,"total score: %5d",p->cumulative_score); gtk_label_set_text(GTK_LABEL(status_scorelabels[i]),buf); } else { snprintf(buf,256,p->hand_score >= 0 ? "(%s) %s[%d]: %d (%d)" : "(%s) %s[%d]: %d", shortwindnames[p->wind],p->name,p->id,p->cumulative_score, p->hand_score); gtk_label_set_text(GTK_LABEL(status_chairs[i]),buf); } if ( showwall ) { snprintf(buf,256,p->hand_score >= 0 ? "%s [%d]\n%s\nTotal: %d\nThis hand: %d" : "%s [%d]\n%s\nTotal: %d\nThis hand: ", p->name,p->id,windnames[p->wind],p->cumulative_score, p->hand_score); gtk_label_set_text(pdisps[i].infolab,buf); } } sprintf(buf,info_windows_in_main ? "%s round" : "Prevailing wind: %s", windnames[the_game->round]); gtk_label_set_text(GTK_LABEL(status_pwind),buf); pn = (info_windows_in_main ? shortwindnames : windnames)[the_game->player+1]; /* +1 for seat to wind */ switch (the_game->state) { case Dealing: sprintf(buf,"Dealing"); break; case DeclaringSpecials: sprintf(buf,"%s declaring specials",pn); break; case Discarding: sprintf(buf,"%s to discard",pn); break; case Discarded: sprintf(buf,"%s has discarded",pn); break; case MahJonging: sprintf(buf,"%s has Mah Jong",pn); break; case HandComplete: sprintf(buf,"Hand finished"); break; } if ( !info_windows_in_main ) { if ( !the_game->active ) strcat(buf," (Play suspended)"); } if ( game_over ) strcpy(buf,"GAME OVER"); gtk_label_set_text(GTK_LABEL(status_status),buf); if ( ! info_windows_in_main ) { sprintf(buf,"%3d tiles left + %2d dead tiles", the_game->wall.live_end-the_game->wall.live_used, the_game->wall.dead_end-the_game->wall.live_end); } else { sprintf(buf,"%3d tiles left + %2d dead tiles", the_game->wall.live_end-the_game->wall.live_used, the_game->wall.dead_end-the_game->wall.live_end); } gtk_label_set_text(GTK_LABEL(status_tilesleft),buf); if ( info_windows_in_main ) { gtk_label_set_text(GTK_LABEL(status_suspended), the_game->active ? "" : "Play suspended"); } } void showraise(GtkWidget *w) { if ( GTK_IS_WINDOW(w) && gtk_object_get_data(GTK_OBJECT(w),"position-set") ) { gtk_widget_set_uposition(w, (gint)(intptr_t)gtk_object_get_data(GTK_OBJECT(w),"position-x"), (gint)(intptr_t)gtk_object_get_data(GTK_OBJECT(w),"position-y")); } gtk_widget_show(w); gdk_window_raise(w->window); } static void about_init(void) { GtkWidget *closebutton, *textw, *vbox; static char *abouttxt = "This is xmj , part of the Mah-Jong for Unix (etc)\n" "set of programs.\n" "Copyright (c) J. C. Bradfield 2000-2014.\n" "Distributed under the Gnu General Public License, version 2.\n" "This is version " VERSION " (protocol version " STRINGIFY(PROTOCOL_VERSION) ").\n" "User documentation is in the xmj manual page.\n" "Latest versions, information etc. may be found at\n" " http://mahjong.julianbradfield.org/ .\n" "Comments and suggestions should be mailed to\n" " mahjong@stevens-bradfield.com" ; about_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (about_window), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); gtk_window_set_title(GTK_WINDOW(about_window),"About xmj"); gtk_container_set_border_width(GTK_CONTAINER(about_window), dialog_border_width); vbox = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(vbox); gtk_container_add(GTK_CONTAINER(about_window),vbox); GtkTextBuffer *textwbuf; GtkTextIter textwiter; textw = gtk_text_view_new(); textwbuf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textw)); gtk_text_buffer_get_iter_at_offset (textwbuf, &textwiter, 0); // let it choose its own // gtk_widget_set_usize(textw,300,200); gtk_text_buffer_insert(textwbuf,&textwiter,abouttxt,-1); gtk_widget_show(textw); GTK_WIDGET_UNSET_FLAGS(textw,GTK_CAN_FOCUS); gtk_box_pack_start(GTK_BOX(vbox),textw,0,0,0); closebutton = gtk_button_new_with_label("Close"); gtk_widget_show(closebutton); gtk_signal_connect_object(GTK_OBJECT(closebutton),"clicked",GTK_SIGNAL_FUNC(close_saving_posn),GTK_OBJECT(about_window)); gtk_box_pack_start(GTK_BOX(vbox),closebutton,0,0,0); } static void nag_callback(GtkWidget *widg UNUSED, gpointer data) { nag_state = (int)(intptr_t) data; read_or_update_rcfile(NULL,XmjrcNone,XmjrcMisc); gtk_widget_hide(nag_window); } static void nag_init(void) { GtkWidget *yesbutton, *maybebutton, *nobutton, *textw, *vbox; GtkTextBuffer *textwbuf; GtkTextIter textwiter; char buf[1024]; static char *nagtxt = "" "Congratulations: you've completed %d full games using\n" "this set of programs.\n" "That suggests that you are getting some enjoyment\n" "out of them.\n" "\n" "If you haven't already, perhaps you would like to\n" "think about making a small donation to me by way of thanks?\n" "\n" "You can do this with a credit card via the home page\n" "hhtp://www.stevens-bradfield.com/MahJong/"; nag_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (nag_window), "delete_event",GTK_SIGNAL_FUNC(gtk_widget_hide), NULL); gtk_window_set_title(GTK_WINDOW(nag_window),"mu4 juan1"); gtk_container_set_border_width(GTK_CONTAINER(nag_window), dialog_border_width); vbox = gtk_vbox_new(0,dialog_vert_spacing); gtk_widget_show(vbox); gtk_container_add(GTK_CONTAINER(nag_window),vbox); textw = gtk_text_view_new(); // Better not to set this, I think //gtk_widget_set_usize(textw,400,200); sprintf(buf,nagtxt,completed_games); textwbuf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textw)); gtk_text_buffer_get_iter_at_offset (textwbuf, &textwiter, 0); gtk_text_buffer_insert(textwbuf,&textwiter,buf,-1); gtk_widget_show(textw); GTK_WIDGET_UNSET_FLAGS(textw,GTK_CAN_FOCUS); gtk_box_pack_start(GTK_BOX(vbox),textw,0,0,0); yesbutton = gtk_button_new_with_label("Yes, I have/will"); gtk_widget_show(yesbutton); gtk_signal_connect(GTK_OBJECT(yesbutton),"clicked",GTK_SIGNAL_FUNC(nag_callback),(gpointer)2); gtk_box_pack_start(GTK_BOX(vbox),yesbutton,0,0,0); maybebutton = gtk_button_new_with_label("Maybe: remind me again later"); gtk_widget_show(maybebutton); gtk_signal_connect(GTK_OBJECT(maybebutton),"clicked",GTK_SIGNAL_FUNC(nag_callback),(gpointer)0); gtk_box_pack_start(GTK_BOX(vbox),maybebutton,0,0,0); nobutton = gtk_button_new_with_label("No, I won't"); gtk_widget_show(nobutton); gtk_signal_connect(GTK_OBJECT(nobutton),"clicked",GTK_SIGNAL_FUNC(nag_callback),(gpointer)1); gtk_box_pack_start(GTK_BOX(vbox),nobutton,0,0,0); } void nag_popup(void) { if ( ! nag_window ) nag_init(); dialog_popup(nag_window,DPCentred); } /* create the dialogs that depend on --dialog-XXX */ void create_dialogs(void) { if ( dialogs_position == DialogsBelow ) { GtkWidget *t; t = gtk_event_box_new(); /* so there's a window to have a style */ gtk_widget_show(t); dialoglowerbox = gtk_hbox_new(0,0); gtk_widget_show(dialoglowerbox); gtk_container_add(GTK_CONTAINER(t),dialoglowerbox); gtk_box_pack_end(GTK_BOX(outerframe),t,0,0,0); gtk_widget_show(dialoglowerbox->parent); } /* create the discard dialog */ discard_dialog_init(); /* and the chow dialog */ chow_dialog_init(); /* and the declaring specials dialog */ ds_dialog_init(); /* and create the turn dialog */ turn_dialog_init(); /* now create the scoring dialog */ scoring_dialog_init(); /* and the continue dialog */ continue_dialog_init(); /* and the end dialog */ end_dialog_init(); } /* and destroy them */ void destroy_dialogs(void) { #define zapit(w) if ( w ) gtk_widget_destroy(w) ; w = NULL ; zapit(discard_dialog->widget); zapit(chow_dialog); zapit(ds_dialog); zapit(turn_dialog); zapit(scoring_dialog); zapit(continue_dialog); } static void scorehistory_showraise(void) { if ( ! scorehistorywindow ) scorehistory_init(); showraise(scorehistorywindow); } static void scoring_showraise(void) { if ( ! textwindow ) textwindow_init(); showraise(textwindow); } static void message_showraise(void) { if ( ! messagewindow ) messagewindow_init(); showraise(messagewindow); } static void about_showraise(void) { if ( ! about_window ) about_init(); showraise(about_window); } static void save_showraise(void) { if ( ! save_window ) save_init(); showraise(save_window); } void password_showraise(void) { if ( ! password_window ) password_init(); showraise(password_window); } void status_showraise(void) { if (!nopopups) { showraise(status_window); } status_update(0) ; } static void display_option_dialog_popup(void) { if ( ! display_option_dialog ) display_option_dialog_init(); display_option_dialog_refresh(); dialog_popup(display_option_dialog,DPCentredOnce) ; } static void playing_prefs_dialog_popup(void) { if ( ! playing_prefs_dialog ) playing_prefs_dialog_init(); playing_prefs_dialog_refresh(); dialog_popup(playing_prefs_dialog,DPCentredOnce) ; } void debug_options_dialog_popup(void) { if ( ! debug_options_dialog ) debug_options_dialog_init(); debug_options_dialog_refresh(); dialog_popup(debug_options_dialog,DPCentredOnce) ; } static void game_option_popup(void) { int b; if ( ! the_game ) return; if ( ! game_option_dialog ) game_option_init(); game_option_panel = build_or_refresh_option_panel(&the_game->option_table, game_option_panel); /* we can't apply options if somebody else is the manager */ b = (the_game->manager == 0 || the_game->manager == our_id); gtk_widget_set_sensitive(game_option_apply_button,b); gtk_widget_set_sensitive(game_option_prefs_button,b); dialog_popup(game_option_dialog,DPNone); } static void game_prefs_popup(void) { if ( ! game_prefs_dialog ) game_prefs_init(); read_or_update_rcfile(NULL,XmjrcGame,XmjrcNone); game_prefs_panel = build_or_refresh_prefs_panel(&prefs_table,game_prefs_panel); dialog_popup(game_prefs_dialog,DPNone); } static void save_state(void) { PMsgSaveStateMsg m; m.type = PMsgSaveState; m.filename = NULL; send_packet(&m); } /* see comment at declaration */ static void grab_focus_if_appropriate(GtkWidget *w) { if ( info_windows_in_main ) { if ( GTK_TOGGLE_BUTTON(mfocus)->active ) { gtk_widget_grab_focus(message_entry); } else if ( ! (GTK_HAS_FOCUS & GTK_WIDGET_FLAGS(message_entry)) ) { gtk_widget_grab_focus(w); } } else { gtk_widget_grab_focus(w); } } #define NULLEX , NULL /* the menu items */ static GtkItemFactoryEntry menu_items[] = { { "/_Game", NULL, NULL, 0, "" NULLEX}, { "/Game/_New local game...", NULL, GTK_SIGNAL_FUNC(open_dialog_popup), 1, NULL NULLEX}, { "/Game/_Join server...", NULL, GTK_SIGNAL_FUNC(open_dialog_popup), 0, NULL NULLEX}, { "/Game/_Resume game...", NULL, GTK_SIGNAL_FUNC(open_dialog_popup), 2, NULL NULLEX}, { "/Game/_Save", NULL, save_state, 0, NULL NULLEX}, { "/Game/Save _as...", NULL, save_showraise, 0, NULL NULLEX}, { "/Game/_Close", NULL, GTK_SIGNAL_FUNC(close_connection), 0, NULL NULLEX}, { "/Game/_Quit", NULL, GTK_SIGNAL_FUNC(exit), 0, NULL NULLEX}, { "/_Show", NULL, NULL, 0, "" NULLEX}, { "/Show/_Scoring info", NULL, scoring_showraise, 0, NULL NULLEX}, { "/Show/_Game info", NULL, status_showraise, 0, NULL NULLEX}, { "/Show/_Messages", NULL, message_showraise, 0, NULL NULLEX}, { "/Show/Scoring _history", NULL, scorehistory_showraise, 0, NULL NULLEX}, { "/Show/_Warnings", NULL, warningraise, 0, NULL NULLEX}, { "/_Options",NULL,NULL,0,"" NULLEX}, { "/Options/_Display Options...", NULL, display_option_dialog_popup,0,NULL NULLEX}, { "/Options/_Playing Preferences...",NULL, playing_prefs_dialog_popup,0,NULL NULLEX}, { "/Options/_Game Option Preferences...", NULL, game_prefs_popup,0,NULL NULLEX}, { "/Options/_Current Game Options...", NULL, game_option_popup,0,NULL NULLEX}, { "/Options/_Debugging Options...", NULL, debug_options_dialog_popup,0,NULL NULLEX}, /* a button in the title bar doesn't quite work properly in gtk2. It seems somehow to lose a click. But since ItemFactory is deprecated, there's no chance of a fix. */ { "/Show _Warnings", NULL, NULL,0,"" NULLEX}, { "/Show _Warnings/Show _Warnings", NULL, warningraise,0,NULL NULLEX}, { "/_About", NULL, NULL, 0, "" NULLEX}, { "/About/_About xmj", NULL, about_showraise, 0, NULL NULLEX}, }; static const int nmenu_items = sizeof(menu_items)/sizeof(GtkItemFactoryEntry); /* create the menubar, and return it */ GtkWidget *menubar_create(void) { GtkItemFactory *item_factory; GtkAccelGroup *accel; GtkWidget *m; int connected = the_game && the_game->fd; accel = gtk_accel_group_new(); item_factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "
",accel); gtk_item_factory_create_items(item_factory,nmenu_items, menu_items,NULL); gtk_window_add_accel_group(GTK_WINDOW(topwindow),accel); m = gtk_item_factory_get_widget(item_factory,"
"); gtk_widget_show(m); openmenuentry = gtk_item_factory_get_widget(item_factory, "/Game/Join server..."); assert(openmenuentry); gtk_widget_set_sensitive(openmenuentry,!connected); newgamemenuentry = gtk_item_factory_get_widget(item_factory, "/Game/New local game..."); assert(newgamemenuentry); gtk_widget_set_sensitive(newgamemenuentry,!connected); resumegamemenuentry = gtk_item_factory_get_widget(item_factory, "/Game/Resume game..."); assert(resumegamemenuentry); gtk_widget_set_sensitive(resumegamemenuentry,!connected); savemenuentry = gtk_item_factory_get_widget(item_factory, "/Game/Save"); assert(savemenuentry); gtk_widget_set_sensitive(savemenuentry,connected); saveasmenuentry = gtk_item_factory_get_widget(item_factory, "/Game/Save as..."); assert(saveasmenuentry); gtk_widget_set_sensitive(saveasmenuentry,connected); closemenuentry = gtk_item_factory_get_widget(item_factory,"/Game/Close"); assert(closemenuentry); gtk_widget_set_sensitive(closemenuentry,connected); gameoptionsmenuentry = gtk_item_factory_get_widget(item_factory,"/Options/Current Game Options..."); gtk_widget_set_sensitive(gameoptionsmenuentry,connected); warningentry = gtk_item_factory_get_item(item_factory, "/Show Warnings"); assert(warningentry); /* This doesn't actually work. But we'll leave it here in case it works in gtk 2.0 */ gtk_menu_item_right_justify(GTK_MENU_ITEM(warningentry)); gtk_widget_set_name(warningentry,"warningentry"); /* it's very tedious simply to set this to have red text */ { GdkColor c; gdk_color_parse("red",&c); gtk_widget_modify_fg(gtk_bin_get_child(GTK_BIN(warningentry)),GTK_STATE_NORMAL,&c); } gtk_widget_hide(warningentry); /* if the relevant windows are in the main window, zap the menu entries */ if ( info_windows_in_main ) { gtk_widget_destroy(gtk_item_factory_get_widget(item_factory,"/Show/Game info")); gtk_widget_destroy(gtk_item_factory_get_widget(item_factory,"/Show/Messages")); } return m; } mj-1.17-src/lazyfixed.c0000444006717300001440000000632715002771311013473 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/lazyfixed.c,v 12.1 2020/05/30 17:27:18 jcb Exp $ * lazyfixed.c * A slight variation on the GTK+ fixed widget. * It doesn't call queue_resize when children removed; * this can reduce flickering in widgets with many children. * Of course, it means that you must call gtk_widget_queue_resize manually * if you *do* want to remove something and have the size change. * Also, of course, if you remove a non-window widget, nothing will * happen until the next time something causes redisplay. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2001 by J. C. Bradfield. * * This file may be used under the terms of the * * GNU Lesser General Public License (any version). * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include "lazyfixed.h" static void lazy_fixed_class_init (GtkFixedClass *klass); static void lazy_fixed_init (LazyFixed *widget); static void lazy_fixed_remove (GtkContainer *container, GtkWidget *widget); static GtkFixedClass *parent_class = NULL; GtkType lazy_fixed_get_type (void) { static GtkType fixed_type = 0; if (!fixed_type) { static const GtkTypeInfo fixed_info = { "LazyFixed", sizeof (LazyFixed), sizeof (LazyFixedClass), (GtkClassInitFunc) lazy_fixed_class_init, (GtkObjectInitFunc) lazy_fixed_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc) NULL, }; fixed_type = gtk_type_unique (GTK_TYPE_FIXED, &fixed_info); } return fixed_type; } static void lazy_fixed_class_init (GtkFixedClass *class) { // GtkObjectClass *object_class; // GtkWidgetClass *widget_class; GtkContainerClass *container_class; // object_class = (GtkObjectClass*) class; // widget_class = (GtkWidgetClass*) class; container_class = (GtkContainerClass*) class; parent_class = gtk_type_class (GTK_TYPE_FIXED); /* override the remove method */ container_class->remove = lazy_fixed_remove; } static void lazy_fixed_init(LazyFixed *w UNUSED) { } GtkWidget* lazy_fixed_new (void) { GtkFixed *fixed; fixed = gtk_type_new (lazy_fixed_get_type()); return GTK_WIDGET (fixed); } static void lazy_fixed_remove (GtkContainer *container, GtkWidget *widget) { GtkFixed *fixed; GtkFixedChild *child; GList *children; g_return_if_fail (container != NULL); g_return_if_fail (IS_LAZY_FIXED (container)); g_return_if_fail (widget != NULL); fixed = GTK_FIXED (container); children = fixed->children; while (children) { child = children->data; if (child->widget == widget) { gtk_widget_unparent (widget); fixed->children = g_list_remove_link (fixed->children, children); g_list_free (children); g_free (child); break; } children = children->next; } } mj-1.17-src/MANIFEST0000444006717300001440000000506315002771311012455 0ustar jcbusers$Header: /home/jcb/MahJong/newmj/RCS/MANIFEST,v 12.0 2009/06/28 20:43:12 jcb Rel $ Files in this directory: CHANGES User-visible changes between releases. ChangeLog Change log entries from the code. LICENCE Licensing information. MANIFEST This file. Makefile.in Master make file. Makefile Copy of Makefile.in with dependencies added. sysdep.h Miscellaneous system functions. tiles.h Declares the tile datatype and access functions. At present it also contains some of the implementation, which is done by macros; but of course nobody's supposed to know that. tiles.c Contains the other tile access functions. player.h Contains datatypes representing players and functions on them. player.c Functions concerned with players. protocol.h Defines the messages passed between players and the controller. protocol.c Implements conversion between API and wire protocol. proto-encode-msg.pl Auxiliary script. proto-decode-msg.pl Auxiliary script. make-enums.pl Utility to generate enum parsing and printing functions. makedep A makedepend variant. makefallbacktiles Auxiliary script. makefile.msvc.old See the README file. Concerns Windows users only. malloc.c Doug Lea's malloc implementation, slightly cut down, for Windows. game.h Headers for game data structure and functions. game.c Functions on games. controller.h Headers for controller.c controller.c The master controller program. scoring.h Headers for scoring routines. scoring.c Routines for scoring. client.c client support routines client.h and headers gtkrc.h Contains some gtkrc information as strings for use by xmj. greedy.c A programmed player with simple offensive strategy. gui.c The graphical user interface. gui.h Header file for gui. gui-dial.c Dialog box functions for gui lazyfixed.h lazyfixed.c vlazyfixed.h vlazyfixed.c These implement two slight modifications of the GTK+ fixed widget for use in the gui. version.h Version strings. fallbacktiles/ Directory containing pixmaps with tile letters on them. tiles-v1/ Directory containing the pixmaps for the tiles. tiles-numbered/ An alternative tileset in which the characters and winds are numbered for those who haven't learned the Chinese characters yet. (Supplied by Gary Wong.) tiles-small/ A smaller tileset -- this the standard (tiles-v1) set, but shrunk by 25%. icon.ico iconres.rs Files to give xmj an icon under Windows. gtkrc-minimal gtkrc-plain gtkrc-clearlooks Sample gtkrc files matching the compiled-in themes. xmj.man mj-server.man mj-player.man use.txt rules.txt User documentation. mj-1.17-src/sysdep.h0000444006717300001440000002166015002771311013005 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/sysdep.h,v 12.2 2020/05/26 09:34:13 jcb Exp $ * sysdep.h * Miscellaneous system stuff that may require attention * by configuration procedures. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ /* network conversion utilities */ #ifndef SYSDEP_H_INCLUDED #define SYSDEP_H_INCLUDED #include /* The following define prevents HP-UX from using the antiquated version of select() with ints instead of fd_sets */ #define _XOPEN_SOURCE_EXTENDED 1 #include #ifdef WIN32 // these may be missing #ifndef SHUT_RDWR #define SHUT_RD 0x00 #define SHUT_WR 0x01 #define SHUT_RDWR 0x02 #endif #include typedef int socklen_t; /* following functions don't exist in Windows, so we need to emulate them */ /* Windows doesn't define the struct timezone, so just use void * */ int gettimeofday(struct timeval *tv, void *tz); char *getlogin(void); unsigned int sleep(unsigned int t); // provided on modern mingw #ifndef XCOMPILE unsigned int usleep(unsigned int t); #endif int getpid(void); int unlink(const char *f); #else #include /* need this for gettimeofday */ #include #endif /* WIN32 */ #include #include /* In the mingw I use, these functions are missing from the headers */ #ifdef WIN32 #ifndef XCOMPILE int _snprintf(char *str, size_t n, const char *format, ...); int _vsnprintf(char *buffer,size_t count,const char *format,va_list argptr); #define snprintf _snprintf #define vsnprintf _vsnprintf #endif #endif /* for memset and friends */ #include /* This is missing in my windows mingw */ #ifdef WIN32 char * index(const char *s, int c); #endif #include #include /* define SOCKET types so we can also work with windows */ #ifndef WIN32 typedef intptr_t SOCKET; #define INVALID_SOCKET -1 #define closesocket close #endif /* If we're using GNU CC, we can suppress warnings for unused variables with this. */ #if defined( __GNUC__) && !defined(__GNUG__) # define UNUSED __attribute ((unused)) #else # define UNUSED #endif /* functions in sysdep.c */ /* warn is fprintf(stderr,...) . */ typedef enum { LogInfo = 1, LogWarning = 2, LogError = 3 } LogLevel; /* If the global variable log_msg_add_note_hook is non-null, it is called with the level, and returns a (char *) which will be appended to the log message. The returned value should be static, or at least long-lived enough. */ extern char *(*log_msg_add_note_hook)(LogLevel l); /* If the global variable log_msg_hook is non-null, it should be a pointer to a function which will be passed the formatted warning. If the hook returns non-zero, the warning will not be printed to stderr. */ extern int (*log_msg_hook)(LogLevel l,char *); /* log_msg is a generalized version with a syslog like warning level. */ int log_msg(LogLevel l, char *format,...); /* and here are abbreviations for the levels */ int info(char *format,...); int warn(char *format,...); /* The error function is called log_error, because on error we almost certainly want to add CPP information, which we pass in via a macro */ int log_error(const char *function, const char *file, const int line, const char *format,...); /* like this: */ #define error(format, args...) log_error(__FUNCTION__,__FILE__,__LINE__,format,## args) /* ignore the SIGPIPE signal. Return 0 on success , -1 on error */ int ignore_sigpipe(void); /* Initialize the socket library functions. This is only needed under Windows. It need not be called explicitly, as the library functions will call it (with null arguments) if necessary. The arguments, if non-NULL, are used by programs that need to do strange things to the sockets, such as gtk applications under windows. open_transform is a function which is applied to all sockets created, and had better return something that fits in a (void *); all functions that return or store SOCKET, will instead return or store the result of open_transform, cast to a SOCKET. (So SOCKET had better fit a void*; this is true on all systems I know of.) closer is a function to close the resulting object; reader is a function to read from it; it should have the same type as the system read function, mutatis mutandis; writer is a function to write. */ int initialize_sockets(void *(*open_transform)(SOCKET), int (*closer)(void *), int (*reader)(void *, void *, size_t), int (*writer)(void *, const void *,size_t)); /* In the following function, a socket address is a string. If it contains a colon, then it is assumed to be a TCP address host:port (if host is omitted, it is localhost). Otherwise, it is assumed to be a Unix filename, and Unix domain sockets are used. */ /* set_up_listening_socket: Set up a socket, listening on the given address. Return its system level SOCKET. */ SOCKET set_up_listening_socket(const char *address); /* accept_new_connection: A connection has arrived on the socket fd. Accept the connection, and return the new fd, or INVALID_SOCKET on error. */ SOCKET accept_new_connection(SOCKET fd); /* connect_to_host: Establish a connection to the given address. Return the file descriptor or INVALID_SOCKET on error. The file descriptor is marked close-on-exec. */ SOCKET connect_to_host(const char *address); /* same as above, but ignores the socket transformers */ SOCKET plain_connect_to_host(const char *address); /* close a network connection with or without transforms */ int close_socket(SOCKET s); int plain_close_socket(SOCKET s); /* read a line, up to lf/crlf terminator, from the socket, and return it. If there is an error or end of file before seeing a terminator, NULL is returned. At present, error includes reading a partial line. The returned string is valid until the next call of get_line. As a special hack, if fd is STDOUT_FILENO, then read from STDIN_FILENO as an fd. */ char *get_line(SOCKET fd); /* write a line (which should include any terminator) to a socket. Return -1 on error, length of line on success. If fd is equal to STDOUT_FILENO, then print to it as an fd. */ int put_line(SOCKET fd,char *line); /* These are the same, but they take a file descriptor rather than a socket. (I hate Windows) */ char *fd_get_line(int fd); int fd_put_line(int fd, char *line); /* stuff arbitrary data down a socket. If len is zero, send EOF */ int put_data(SOCKET fd, char *data, int len); /* and read it - data must be preallocated */ int get_data(SOCKET fd, char *data, int len); /* rand_index: return a random integer from 0 to arg (inclusive). */ unsigned int rand_index(int top); /* rand_seed: if the random number generation has a seed value, use this argument, if non-zero; if zero, seed with a system dependent variable value (actually time(), but needn't be). If this function is not called explicitly, it will be called with arg 0 on first use of rand_index. */ void rand_seed(int seed); /* utility function: this is just like strncpy, except that it guarantees that the target is null-terminated. The m parameter is the length of the target field INCLUDING the terminating null. */ #define strmcpy(dest,src,m) { strncpy(dest,src,m) ; dest[m-1] = '\000' ; } /* similarly */ #define strmcat(dest,src,m) { strncat(dest,src,m) ; dest[m-1] = '\000' ; } /* This is like strmcat, but it also quotes the string (in a system dependent way) for adding it to a shell command line. */ char *qstrmcat(char *dest, const char *src, int len); /* another utility function: return arg, or "NULL" if it's null */ char *nullprotect(char *s); /* feed a command to the system to be started in background. No fancy argument processing, just pass to shell or equiv Return 1 on success, 0 on failure */ int start_background_program(const char *cmd); /* unfortunately we need a slightly more sophisticated one where we can get at the output. This passes out a write handle for the new process's stdin, and a read handle for its stdout */ /* Under Windows, this is fiddled to return "file descriptors" */ int start_background_program_with_io(const char *cmd,int *childin, int *childout); /* return a path for the home directory, or equivalent concept. Returns NULL if no sensible homedir is available (i.e. HOME is unset on Unix, or HOMEPATH is unset on Windows) */ char *get_homedir(void); #endif /* SYSDEP_H_INCLUDED */ mj-1.17-src/controller.c0000444006717300001440000034216715002771311013664 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/controller.c,v 12.12 2020/06/09 15:14:11 jcb Exp $ * controller.c * This program implements the master controller, which accepts * connections from players and runs the game. * At present, this is designed around the assumption that it is * only running one game. Realistically, I see no immediate need * to be more general. However, I trust that nothing in the design * would make that awkward. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include #include #include #include #include #include "controller.h" #include "scoring.h" #include "sysdep.h" #include "version.h" static const char rcs_id[] UNUSED = "$Header: /home/jcb/MahJong/newmj/RCS/controller.c,v 12.12 2020/06/09 15:14:11 jcb Exp $"; /* extra data in players */ typedef struct { /* option settings */ int options[PONumOptions]; /* this variable is set to one to indicate that the player has been disconnected or otherwise out of touch, and therefore needs to be told the current state of the game. */ int disconnected; int auth_state; /* authorization state */ #define AUTH_NONE 0 #define AUTH_PENDING 1 #define AUTH_DONE 2 char *auth_data; /* point to relevant auth data */ int protversion; /* protocol version supported by this player */ int localtimeouts; /* 1 if this player has requested LocalTimeouts */ } PlayerExtras; #define popts(p) ((PlayerExtras *)(p->userdata))->options #define pextras(p) ((PlayerExtras *)(p->userdata)) /* authorization data (basic only) */ typedef struct { int id; char passwd[16]; } authinfo; #define AUTH_NUM 4 authinfo auth_info[AUTH_NUM]; /* this is The Game that we will use */ Game *the_game = NULL; int num_connected_players; #define NUM_PLAYERS 4 int first_id = 0; /* id assigned to first player to connect */ /* This array stores information about current connections. When a new connection arrives, we use the first free slot. */ #define MAX_CONNECTIONS 8 struct { int inuse; /* slot in use */ SOCKET skt; /* the system level socket of this connection. */ PlayerP player; /* player, if any */ int seqno; /* sequence number of messages on this connection */ PMsgMsg *cm; /* stored copy of the player's connect message */ } connections[MAX_CONNECTIONS]; /* This is the fd of the socket on which we listen */ SOCKET socket_fd; /* This is the master set of fds on which we are listening for events. If we're on some foul system that doesn't have this type, it is sysdep.h's responsibility to make sure we do. */ fd_set event_fds; /* this is used by auxiliary functions to pass back an error message */ char *failmsg; /* This is a logfile, if specified */ FILE *logfile = NULL; /* This is the name of a game option file */ char *optfilename = NULL; /* forward declarations */ static void despatch_line(int cnx, char *line); static int close_connection(int cnx); static int new_connection(SOCKET skt); /* returns new connection number */ static void setup_maps(int cnx, PlayerP p); static void remove_from_maps(int cnx); /* if logit is 2, print with cnx* to indicate being sent to all players */ static void send_packet(int cnx,CMsgMsg *m, int logit); /* not much used except in next two */ static void _send_id(int id,CMsgMsg *m, int logit); static void _send_all(Game *g,CMsgMsg *m); static void _send_others(Game *g, int id,CMsgMsg *m); static void _send_seat(Game *g, seats s, CMsgMsg *m); static void _send_other_seats(Game *g, seats s, CMsgMsg *m); /* always casting is boring, so ... */ /* send_id logs (if logging is enabled */ #define send_id(id,m) _send_id(id,(CMsgMsg *)(m),1) #define send_all(g,m) _send_all(g,(CMsgMsg *)(m)) #define send_others(g,id,m) _send_others(g,id,(CMsgMsg *)(m)) #define send_seat(g,s,m) _send_seat(g,s,(CMsgMsg *)(m)) #define send_other_seats(g,s,m) _send_other_seats(g,s,(CMsgMsg *)(m)) static void resend(int id,CMsgMsg *m); static int load_state(Game *g); static char *save_state(Game *g, char *filename); static void save_and_exit(void); /* save if save-on-exit, and quit */ static int load_wall(char *wfname, Game *g); static int id_to_cnx(int id); static int cnx_to_id(int cnx); static void clear_history(Game *g); static void history_add(Game *g, CMsgMsg *m); /* N.B. we do not use the game_id_to_player function, since we don't always have players in a game. */ static PlayerP id_to_player(int id); /* A convenience definition. */ #define id_to_seat(id) game_id_to_seat(the_game,id) /* and another */ #define handle_cmsg(g,m) game_handle_cmsg(g,(CMsgMsg *)m) static int start_hand(Game *g, int rotate_only); static void check_claims(Game *g); static void send_infotiles(PlayerP p); static void score_hand(Game *g, seats s); static void washout(char *reason); static void handle_cnx_error(int cnx); static int hand_history = 0; /* dump history file for each hand */ static int loadstate = 0; /* if we have to load state */ /* order in which to seat players */ typedef enum { SODefault = 0, SORandom = 1, SOIdOrder = 2, } SeatingOrder; static SeatingOrder seating_order = SODefault; static int noidrequired = 0; /* allow arbitrary joining on resumption */ static int nomanager = 0; /* forbid managers */ static char *wallfilename = NULL; /*file to load wall from*/ static char loadfilename[1024]; /* file to load game from */ static int debug = 0; /* allow debugging messages */ static int usehist = 1; /* if we keep history to allow resumption */ static int end_on_disconnect = 0; /* end game (gracefully) on disconnect */ /* penalties for disconnection */ static int disconnect_penalty_end_of_round = 0; static int disconnect_penalty_end_of_hand = 0; static int disconnect_penalty_in_hand = 0; static int exit_on_disconnect = 0; /* if we die on losing a player */ static int save_on_exit = 0; /* save on exit other than at end of game */ static int localtimeouts = 0; /* are we using local timeouts ? */ static int game_over = 0; /* we hang around so players can still exchange messages, and quit on first disconnect */ static void usage(char *pname,char *msg) { fprintf(stderr,"%s: %s\nUsage: %s [ --server ADDR ]" " [ --timeout N ]\n" " [ --pause N ]\n" " [ --random-seats | --id-order-seats ]\n" " [ --debug ]\n" " [ --disconnect-penalties N1,N2,N3 ]\n" " [ --end-on-disconnect ]\n" " [ --exit-on-disconnect ]\n" " [ --save-on-exit ]\n" " [ --option-file FILE ]\n" " [ --load-game FILE ]\n" " [ --no-id-required ]\n" " [ --auth-basic AUTHINFO ]\n" " [ --no-manager ]\n" " [ --logfile FILE]\n" " [ --hand-history ]\n" " [ --no-special-scores ]\n" " [ --seed N ]\n" " [ --wallfile FILE ]\n" " [ --nohist ]\n", pname,msg,pname); exit(1); } /* This global is used to specify a timeout for claims in milliseconds. If it is non-zero on entry to the select call, then it will be used as the timeout for select; and if data is read before timeout, it will be adjusted appropriately for the next call. If a select expires due to timeout, then the timeout_handler is called. */ static void timeout_handler(Game *g); static int timeout = 0; /* this is the user level timeout in seconds.*/ static int timeout_time = 15; /* this function extracts the timeout_time from a game according the Timeout and TimeoutGrace options, and the passed in value of localtimeouts */ static int get_timeout_time(Game *g,int localtimeouts); /* this is the minimum time between discards and draws, keep the game down to human speed */ static int min_time = 0; /* deciseconds */ /* and this is the value requested on the command line */ static int min_time_opt = 0; /* A player has disconnected, and we're cleaning up the mess. Used to keep the game going during MahJonging */ static int end_on_disconnect_in_progress = 0; /* This is for a hack: if we get two savestate requests while a hand is completed, on the second one we save the history of the hand, rather than just the game state. So this variable gets incremented when save_state is called in handcomplete, and is reset by start_hand. */ static int save_state_hack = 0; int main(int argc, char *argv[]) { char *address = ":5000"; char *logfilename = NULL; int seed = 0; int nfds; struct timeval timenow, timethen, timetimeout; fd_set rfds, efds; int i; /* clear auth_info */ for ( i=0; iuserdata = malloc(sizeof(GameExtras)); if ( the_game->userdata == NULL ) { warn("Couldn't malloc game extra structure"); exit(1); } gextras(the_game)->histcount = gextras(the_game)->prehistcount = 0; gextras(the_game)->caller = (PlayerP) malloc(sizeof(Player)); if ( gextras(the_game)->caller == NULL ) { warn("Couldn't malloc game extra structure"); exit(1); } gextras(the_game)->completed_rounds = 0; for ( i=0 ; i < NUM_SEATS ; i++ ) { void *tmp; if ( (the_game->players[i] = (PlayerP) malloc(sizeof(Player))) == NULL ) { warn("couldn't malloc player structure"); exit(1); } memset((void *)the_game->players[i],0,sizeof(Player)); if ( (tmp = malloc(sizeof(PlayerExtras))) == NULL ) { warn("couldn't malloc player options"); exit(1); } memset((void *)tmp,0,sizeof(PlayerExtras)); set_player_userdata(the_game->players[i],tmp); } /* some game initialization happens here */ the_game->protversion = PROTOCOL_VERSION; /* may be reduced by players */ game_set_options_from_defaults(the_game); if ( loadstate ) { if ( load_state(the_game) == 0 ) { warn("Couldn't load the game state"); loadstate = 0; } } /* enter the main loop */ while ( 1 ) { struct timeval *tvp; rfds = efds = event_fds; tvp = NULL; if ( the_game->active && !the_game->paused && timeout > 0 ) { gettimeofday(&timethen,NULL); tvp = &timetimeout; tvp->tv_sec = timeout/1000; tvp->tv_usec = (timeout%1000)*1000; } nfds = select(32,&rfds,NULL,&efds,tvp); /* n, read, write, except, timeout */ if ( tvp ) { gettimeofday(&timenow,NULL); timeout -= (timenow.tv_sec*1000 + timenow.tv_usec/1000) - (timethen.tv_sec*1000 + timethen.tv_usec/1000); } if ( nfds < 0 ) { perror("select failed"); exit(1); } /* if the timeout has gone negative, that means that something is throwing input at us so fast we never get round to cancelling the timeout. This probably means something is in a loop. So call the timeout handler anyway */ if ( nfds == 0 || timeout < 0 ) { timeout_handler(the_game); continue; } for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) { SOCKET fd; if ( ! connections[i].inuse ) continue; fd = connections[i].skt; if ( FD_ISSET(fd,&efds) ) { handle_cnx_error(i); continue; } if ( FD_ISSET(fd,&rfds) ) { if ( fd == socket_fd ) { SOCKET newfd; newfd = accept_new_connection(fd); if (newfd == INVALID_SOCKET) { warn("failure in accepting connection"); exit(1); } /* if we are shutting down after a disconnect, accept no more connections */ if ( end_on_disconnect_in_progress ) { CMsgErrorMsg em; em.type = CMsgError; em.seqno = 0; em.error = "Game shutting down; no reconnect allowed"; put_line(newfd,encode_cmsg((CMsgMsg *)&em)); closesocket(newfd); } else /* add to the connection list */ if ( new_connection(newfd) < 0) { CMsgErrorMsg em; em.type = CMsgError; em.seqno = 0; em.error = "Unable to accept new connections"; put_line(newfd,encode_cmsg((CMsgMsg *)&em)); closesocket(newfd); } } else { char *line; /* get_line will fail if there is only a partial line available. This is wrong, since it is possible for lines to arrive fragmentedly (not that it should normally happen). However, it would also be wrong to wait forever for a partial line to complete. There should be a timeout. */ /* get_line provides us with a pointer to the text line, which we can mess with as we like, provided we don't expect it to survive the next call to get_line */ line = get_line(connections[i].skt); /* errors dealt with by despatch line */ /* except that we want to know the error code */ if ( line == NULL ) { info("get_line returned null: %s",strerror(errno)); } despatch_line(i,line); } } } } } /* function to act on Player Messages. NB it takes a connection number, not an id, since ids may be unassigned. */ static void handle_pmsg(PMsgMsg *pmp, int cnx); /* despatch_line: process the line of input received on cnx. */ static void despatch_line(int cnx, char *line) { PMsgMsg *pmp; if ( logfile ) { fprintf(logfile," 0 ) { int n; if ( disctime.tv_sec > 0 ) { /* not firsttime */ gettimeofday(&timenow,NULL); n = (timenow.tv_sec * 1000 + timenow.tv_usec/1000) - (disctime.tv_sec * 1000 + disctime.tv_usec/1000); n = 100*min_time_this_time - n; if ( n > 0 ) usleep(1000*n); } gettimeofday(&disctime,NULL); } min_time_this_time = (int)(min_time*factor); } static void handle_pmsg(PMsgMsg *pmp, int cnx) { /* We mustn't act on most requests if the game is not active. There's a race possible otherwise: we can suspend the game, and meanwhile a player sends a request: we then act on this, but it doesn't get into the history records of the suspended players. */ if ( ! the_game->active ) { CMsgErrorMsg em; em.type = CMsgError; em.error = "Game not active"; em.seqno = connections[cnx].seqno; switch ( pmp->type ) { case PMsgSaveState: case PMsgLoadState: case PMsgConnect: case PMsgDisconnect: case PMsgAuthInfo: case PMsgRequestReconnect: case PMsgSetPlayerOption: case PMsgSendMessage: case PMsgQueryGameOption: case PMsgListGameOptions: break; /* these are OK */ default: send_packet(cnx,(CMsgMsg *)&em,0); return; } } /* check for debugging messages */ if ( ! debug && pmp->type >= DebugMsgsStart ) { CMsgErrorMsg em; em.type = CMsgError; em.seqno = connections[cnx].seqno; em.error = "Debugging not enabled"; send_packet(cnx,(CMsgMsg *)&em,0); return; } switch ( pmp->type ) { case PMsgSaveState: { PMsgSaveStateMsg *m = (PMsgSaveStateMsg *)pmp; CMsgErrorMsg em; PlayerP p UNUSED; int id; seats seat UNUSED; char *res; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); em.type = CMsgError; em.seqno = connections[cnx].seqno; if ( (res = save_state(the_game,m->filename)) ) { if ( the_game->protversion >= 1025 ) { CMsgStateSavedMsg ssm; ssm.type = CMsgStateSaved; ssm.id = id; ssm.filename = res; send_all(the_game,&ssm); } } else { em.error = "Unable to save state"; send_id(id,&em); } return; } case PMsgLoadState: { PMsgLoadStateMsg *m = (PMsgLoadStateMsg *) pmp; CMsgErrorMsg em; PlayerP p UNUSED; int id; seats seat UNUSED; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); em.type = CMsgError; em.seqno = connections[cnx].seqno; em.error = NULL; /* I think we can load state any time before the game starts, and only then */ if ( protocol_version < 1038 ) { em.error = "Not all players support dynamic state loading"; } else if ( game_has_started(the_game) ) { em.error = "Can't load game state when already playing"; } else if ( m->filename == NULL || m->filename[0] == 0 ) { em.error = "No game file specified in LoadState"; } else { int i; CMsgPlayerMsg pm; loadstate = 1; strmcpy(loadfilename,m->filename,1023); /* first delete all players from the clients */ pm.type = CMsgPlayer; pm.name = NULL; for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) { if ( connections[i].inuse && connections[i].player ) { pm.id = connections[i].player->id; send_all(the_game,&pm); } } /* now remove from maps ... */ for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) { if ( connections[i].inuse && connections[i].player ) { remove_from_maps(i); num_connected_players--; } } if ( ! load_state(the_game) ) { em.error = "Loading game state failed"; } the_game->active = 0; /* now reprocess connection messages */ for ( i = 0 ; i < MAX_CONNECTIONS ; i++ ) { if ( connections[i].inuse && connections[i].cm) { handle_pmsg(connections[i].cm,i); } } } if ( em.error ) { send_id(id,&em); } return; } case PMsgDisconnect: { close_connection(cnx); return; } case PMsgRequestReconnect: { CMsgReconnectMsg rm; CMsgErrorMsg em UNUSED; PlayerP p; int id; seats seat UNUSED; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); rm.type = CMsgReconnect; em.type = CMsgError; /* no reason to refuse, so far */ /* first, stop play */ { char msg[1024]; CMsgStopPlayMsg spm; spm.type = CMsgStopPlay; sprintf(msg,"Player %s requested reconnect - please wait...", p->name); spm.reason = msg; send_all(the_game,&spm); the_game->active = 0; } /* now send the reconnect */ send_id(id,&rm); /* remove player from maps and decrement player count */ remove_from_maps(cnx); num_connected_players--; return; } case PMsgNewAuthInfo: { CMsgErrorMsg em; int id; id = cnx_to_id(cnx); em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = "Password changing not supported"; send_id(id,&em); return; } case PMsgAuthInfo: { PMsgAuthInfoMsg *m = (PMsgAuthInfoMsg *) pmp; int id; PlayerP p = NULL; CMsgConnectReplyMsg cm; id = cnx_to_id(cnx); p = id_to_player(id); /* if auth fails, we'll send a negative connection reply. Otherwise, we'll drop through and re-process the connect. */ cm.type = CMsgConnectReply; if ( strcmp(m->authdata,pextras(p)->auth_data) != 0 ) { cm.reason = "Authentication failure"; send_packet(cnx,(CMsgMsg *)&cm,1); close_connection(cnx); return; } pextras(p)->auth_state = AUTH_DONE; /* now fake reprocessing the connect message */ pmp = connections[cnx].cm; } // fall through case PMsgConnect: { PMsgConnectMsg *m = (PMsgConnectMsg *) pmp; PlayerP p = NULL; CMsgConnectReplyMsg cm; CMsgPlayerMsg thisplayer; CMsgGameMsg gamemsg; char *refusal = NULL; static int player_id = 0; int i; /* start filling in reply message */ cm.type = CMsgConnectReply; cm.pvers = PROTOCOL_VERSION; if ( m->pvers/1000 != PROTOCOL_VERSION/1000 ) refusal = "Protocol major version mismatch"; /* we may be seeing the message for the second time, so allocation may already have been done */ if ( cnx_to_id(cnx) < 0 && num_connected_players == NUM_PLAYERS ) refusal = "Already have players"; /* it's reasonable to insist on a name being supplied */ if ( m->name == NULL ) refusal = "A non-empty name must be given"; if ( cnx_to_id(cnx) >= 0 && cnx_to_id(cnx) != m->last_id ) refusal = "You already have a player ID"; /* If we have a previous game state loaded, we need to associate this player with one in the game. Match on ids */ int foundbyname = 0; if ( loadstate && !refusal ) { /* if no id specified, match on names */ if ( m->last_id != 0 ) { for ( i = 0; i < NUM_SEATS && m->last_id != the_game->players[i]->id ; i++ ) ; } else { for ( i = 0; i < NUM_SEATS && (strcmp(m->name,the_game->players[i]->name) != 0) ; i++ ) ; if ( i < NUM_SEATS ) { m->last_id = the_game->players[i]->id; foundbyname = 1; } } /* if no id required, just match this to the first player not currently connected */ if ( i == NUM_SEATS && noidrequired ) { for ( i = 0; i < NUM_SEATS && id_to_cnx(the_game->players[i]->id) >= 0 ; i++ ); } if ( i == NUM_SEATS ) { refusal = "Can't find you in the resumed game"; } else { /* better check that this player isn't already connected */ if ( id_to_cnx(m->last_id) >= 0 && id_to_cnx(m->last_id) != cnx ) { if ( foundbyname ) { refusal = "Your name is connected elsewhere - please give your id"; } else { refusal = "Your id is already connected elsewhere"; } } else { p = the_game->players[i]; set_player_name(p,m->name); /* not preserved by saving */ if ( cnx_to_id(cnx) < 0 ) { setup_maps(cnx,p); /* set up maps between id and cnx etc*/ num_connected_players++; } } } } else if ( ! refusal) { /* not loadstate */ /* in change from previous versions, honour the last_id field */ if ( m->last_id ) { /* better check that this player isn't already connected */ if ( id_to_cnx(m->last_id) >= 0 && id_to_cnx(m->last_id) != cnx ) refusal = "Your id is already connected elsewhere"; } else { while ( id_to_cnx(++player_id) >= 0 ) { } m->last_id = player_id; } if ( ! refusal && cnx_to_id(cnx) < 0 ) { /* the second test is because we may already be initialized */ p = game_id_to_player(the_game,0); /* get free player structure */ if ( p == NULL ) { warn("can't get player structure; exiting"); exit(1); } initialize_player(p); num_connected_players++; set_player_id(p,m->last_id); set_player_name(p,m->name); /* store the id of the first human player to connect. This assumes that computer players are called Robot.. */ if ( ! first_id && strncmp(p->name,"Robot",5) != 0 ) first_id = num_connected_players; setup_maps(cnx,p); /* set up maps between id and cnx etc*/ } } if (refusal) { cm.id = 0; cm.reason = refusal; send_packet(cnx,(CMsgMsg *)&cm,1); close_connection(cnx); return; } /* set the player in case this is second pass */ if ( ! p ) p = game_id_to_player(the_game,m->last_id); /* now do basic auth checking */ if ( auth_info[0].id && pextras(p)->auth_state != AUTH_DONE ) { int j = 0; while ( j < AUTH_NUM && auth_info[j].id != p->id ) j++; if ( j == AUTH_NUM ) { refusal = "id not authorized for this game"; } else { pextras(p)->auth_state = AUTH_PENDING; pextras(p)->auth_data = auth_info[j].passwd; if ( pextras(p)->auth_data[0] == 0 ) pextras(p)->auth_state = AUTH_DONE; } } else { pextras(p)->auth_state = AUTH_DONE; } if (refusal) { cm.id = 0; cm.reason = refusal; send_packet(cnx,(CMsgMsg *)&cm,1); close_connection(cnx); return; } /* store the protocol version of this player */ pextras(p)->protversion = m->pvers; /* Yes, this is right: the fact that this player is connecting means that it has been disconnected! */ pextras(p)->disconnected = 1; /* keep a copy of the message if not already there */ if ( connections[cnx].cm == NULL ) { connections[cnx].cm = pmsg_deepcopy(pmp); } if ( pextras(p)->auth_state == AUTH_DONE ) { /* send the reply */ cm.id = p->id; cm.reason = NULL; send_packet(cnx,(CMsgMsg *)&cm,1); } else { /* ask for authorization, and then bail out. When we get authorization, we'll come back through all this code again. */ CMsgAuthReqdMsg cm; cm.type = CMsgAuthReqd; strcpy(cm.authtype,"basic"); cm.authdata = NULL; send_packet(cnx,(CMsgMsg *)&cm,0); /* don't log this */ return; } /* if we had localtimeouts in operation, turn them off, and (in the loop below ) tell the other players to */ pextras(p)->localtimeouts = 0; /* Now we need to tell this player who's already here, and tell the others about this one */ thisplayer.type = CMsgPlayer; thisplayer.id = p->id; thisplayer.name = p->name; for ( i = 0; i < NUM_SEATS; i++ ) { CMsgPlayerMsg pm; PlayerP q; q = the_game->players[i]; if ( q->id == 0 ) continue; if ( q->id == p->id ) continue; pm.type = CMsgPlayer; pm.id = q->id; pm.name = q->name; send_packet(cnx,(CMsgMsg *)&pm,1); send_id(q->id,&thisplayer); /* fails harmlessly if not connected */ CMsgPlayerOptionSetMsg posm; posm.type = CMsgPlayerOptionSet; posm.option = POLocalTimeouts; posm.value = 0; posm.ack = 0; posm.text = NULL; send_id(q->id,(CMsgMsg *)&posm); } localtimeouts = 0; timeout_time = get_timeout_time(the_game,localtimeouts); /* set the protocol version to the greatest supported version. We inspect all players again in case somebody disconnected before the game was set up */ protocol_version = PROTOCOL_VERSION; for ( i = 0; i < NUM_SEATS ; i++ ) { PlayerP q = the_game->players[i]; if ( q->id == 0 ) continue; if ( protocol_version > pextras(q)->protversion ) protocol_version = pextras(q)->protversion; } /* if not everybody is connected, just wait for the next */ if ( num_connected_players < NUM_SEATS ) return; /* otherwise, set up the game state (if we aren't already in the middle of an interrupted game */ if ( ! loadstate ) { GameOptionEntry goe; /* set up the seating order */ switch ( seating_order ) { default: break; case SORandom: { int i,j,n; PlayerP temp[NUM_SEATS]; for (i=0; iplayers[i]; for (i=0; iplayers[i] = temp[j]; temp[j] = NULL; } } break; case SOIdOrder: { int i,j; PlayerP t; for ( i = 0 ; i < NUM_SEATS-1 ; i++ ) { if ( the_game->players[i+1]->id < the_game->players[i]->id ) { /* sink it to the bottom */ for ( j = i+1 ; j > 0 && the_game->players[j]->id < the_game->players[j-1]->id; j-- ) { t = the_game->players[j-1]; the_game->players[j-1] = the_game->players[j]; the_game->players[j] = t; } } } } break; } the_game->state = HandComplete; the_game->player = noseat; the_game->round = EastWind; the_game->hands_as_east = 0; the_game->firsteast = the_game->players[east]->id; /* protocol_version has been maintained during the connection process */ the_game->protversion = protocol_version; the_game->manager = nomanager ? -1 : first_id; game_set_options_from_defaults(the_game); /* initialize the timeout time from the command line option */ goe = *game_get_option_entry(the_game,GOTimeout,NULL); goe.value.optint = timeout_time; game_set_option(the_game,&goe); /* And I think we will have the normal dead wall as default, rather than millington style */ goe = *game_get_option_entry(the_game,GODeadWall16,NULL); if ( goe.enabled ) { goe.value.optbool = 0; game_set_option(the_game,&goe); } /* Now load the game option file, if there is one */ if ( optfilename ) { FILE *optfile; char buf[1024]; optfile = fopen(optfilename,"r"); if ( optfile ) { CMsgMsg *m; while ( ! feof(optfile) ) { fgets(buf,1024,optfile); m = decode_cmsg(buf); if ( ! m ) { warn("Error decoding game option file entry %s",buf); } else { if ( handle_cmsg(the_game,m) < 0 ) { warn("Error applying game option (%s)",the_game->cmsg_err); } cmsg_deepfree(m); } } } else { warn("couldn't open game option file %s (%s)", optfilename,strerror(errno)); } } } the_game->cmsg_check = 1; gamemsg.type = CMsgGame; gamemsg.east = the_game->players[east]->id; gamemsg.south = the_game->players[south]->id; gamemsg.west = the_game->players[west]->id; gamemsg.north = the_game->players[north]->id; gamemsg.round = the_game->round; gamemsg.hands_as_east = the_game->hands_as_east; gamemsg.firsteast = the_game->firsteast; gamemsg.east_score = the_game->players[east]->cumulative_score; gamemsg.south_score = the_game->players[south]->cumulative_score; gamemsg.west_score = the_game->players[west]->cumulative_score; gamemsg.north_score = the_game->players[north]->cumulative_score; gamemsg.protversion = the_game->protversion; gamemsg.manager = the_game->manager; /* we only send the game message to the players who have been disconnected. (In the usual case, this is all players...) */ for ( i=0 ; i < NUM_SEATS; i++ ) { PlayerP p = the_game->players[i]; PMsgListGameOptionsMsg plgo; if ( ! pextras(p)->disconnected ) continue; send_id(p->id,&gamemsg); /* we also have to tell them the game options now, so that they do any necessary setup corrrectly before the new hand message. This is most easily done by faking a ListOptions request. */ plgo.type = PMsgListGameOptions; plgo.include_disabled = 0; handle_pmsg((PMsgMsg *)&plgo,id_to_cnx(p->id)); /* we don't actually clear the history records until the NewHand message is sent. But we don't want to send history for a complete hand. However, if the game is paused, we need to tell the player so. */ if ( the_game->state == HandComplete ) { if ( the_game->paused ) { CMsgPauseMsg pm; CMsgPlayerReadyMsg prm; int i; pm.type = CMsgPause; pm.exempt = 0; pm.requestor = 0; pm.reason = the_game->paused; send_id(p->id,&pm); prm.type = CMsgPlayerReady; for ( i = 0; i < NUM_SEATS; i++ ) { if ( the_game->ready[i] ) { prm.id = the_game->players[i]->id; send_id(p->id,&prm); } } } } } /* now we need to send the history to disconnected players.*/ if ( the_game->state != HandComplete ) { int j; if ( ! usehist ) { CMsgErrorMsg em; em.type = CMsgError; em.seqno = connections[cnx].seqno; em.error = "No history kept: resumption not supported"; send_all(the_game,&em); warn(em.error); exit(1); } for ( j=0; j < gextras(the_game)->histcount; j++ ) { int sleeptime; for ( i=0 ; i < NUM_SEATS; i++ ) { PlayerP p = the_game->players[i]; if ( ! pextras(p)->disconnected ) continue; resend(p->id,gextras(the_game)->history[j]); } /* this is an undocumented feature to make things a bit nicer for humans reconnecting: we'll unilaterally slow down the feed, by adding a 0.15 second delay between items, or 1 second after a claim implementation. */ switch ( gextras(the_game)->history[j]->type ) { case CMsgPlayerPairs: case CMsgPlayerChows: case CMsgPlayerPungs: case CMsgPlayerKongs: case CMsgPlayerSpecialSet: sleeptime = 1000; /* ms */ break; default: sleeptime = 150; } usleep(sleeptime*1000); } } /* and now mark those players connected again */ for ( i=0 ; i < NUM_SEATS; i++ ) { PlayerP p = the_game->players[i]; if ( ! pextras(p)->disconnected ) continue; pextras(p)->disconnected = 0; } /* Now we should tell somebody to do something. Namely: In state HandComplete: tell everybody, and start a hand if things aren't paused. In state Dealing: everybody. In Discarded, several players might want to do something, so we don't specify the id. Ditto in MahJonging. Otherwise, it's the player. */ { CMsgStartPlayMsg sm; CMsgPauseMsg pm; sm.type = CMsgStartPlay; switch (the_game->state) { case HandComplete: case Dealing: case Discarded: case MahJonging: sm.id = 0; break; default: sm.id = the_game->players[the_game->player]->id; break; } /* now we should ask the players whether they are ready to continue. We send the pause request *before* making the game active */ pm.type = CMsgPause; pm.reason = (the_game->state == HandComplete) ? "to start hand" : "to continue play"; pm.exempt = 0; pm.requestor = 0; handle_cmsg(the_game,&pm); send_all(the_game,&pm); send_all(the_game,&sm); /* and set the game active */ the_game->active = 1; } /* now set loadstate */ loadstate = 1; return; } /* end of case PMsgConnect */ case PMsgRequestPause: { PMsgRequestPauseMsg *m = (PMsgRequestPauseMsg *)pmp; PlayerP p UNUSED; int id; seats seat UNUSED; CMsgPauseMsg pm; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ pm.type = CMsgPause; pm.exempt = 0; pm.requestor = id; pm.reason = m->reason; em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; if ( !(the_game->state == Discarding || the_game->state == HandComplete) ) { em.error = "Not reasonable to pause at this point"; send_id(id,&em); return; } send_all(the_game,&pm); break; } /* end of PMsgRequestPauseMsg */ case PMsgSetPlayerOption: { PMsgSetPlayerOptionMsg *m = (PMsgSetPlayerOptionMsg *)pmp; PlayerP p; int id; seats seat; CMsgPlayerOptionSetMsg posm; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ posm.type = CMsgPlayerOptionSet; posm.option = m->option; posm.value = m->value; posm.text = m->text; posm.ack = 1; em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; if ( m->option == (unsigned)-1 ) m->option = POUnknown; if ( m->option == POUnknown ) em.error = "Unknown option"; else switch ( m->option ) { /* validity checking */ /* boolean options */ case POInfoTiles: if ( m->value < 0 || m->value > 1 ) em.error = "Bad value for InfoTiles"; break; case POLocalTimeouts: /* This is a bit messy. As a matter of policy, we only allow a client to do local timeouts if everybody else is too. So we simply remember the requests, unless all have requested, in which case we ack everybody. */ /* if this is an ack, ignore it. We should only get acks after we've forcibly set things to zero, and then we don't care about the ack. */ if ( m->ack ) break; /* if this is a request to start local timeouts: */ if ( m->value ) { int i,lt; pextras(p)->localtimeouts = 1; for ( i=0, lt=1; i < NUM_SEATS; i++ ) { lt = (lt && pextras(the_game->players[i])->localtimeouts); } if ( ! lt ) return; /* just wait */ localtimeouts = 1; timeout_time = get_timeout_time(the_game,localtimeouts); for ( i=0; i < NUM_SEATS; i++ ) { popts(the_game->players[i])[m->option] = 1; } send_all(the_game,&posm); /* and that's it -- we don't want to drop through */ return; } else { seats i; /* request to disable local timeouts */ localtimeouts = 0; timeout_time = get_timeout_time(the_game,localtimeouts); pextras(p)->localtimeouts = 0; /* instruct all the other players to drop local timeouts */ posm.ack = 0; for ( i=0; i < NUM_SEATS; i++ ) { if ( i != seat ) { send_seat(the_game,i,&posm); } } posm.ack = 1; /* now drop through to ack and set this one. */ } break; /* non-boolean options */ case PODelayTime: /* players *can* request any positive value */ if ( m->value < 0 ) em.error = "Bad value for DelayTime"; break; default: ; } if ( em.error ) { send_id(id,&em); return; } if ( ! m->ack ) { send_id(id,&posm); } popts(p)[m->option] = m->value; /* action may now be required */ switch ( m->option ) { int i; case PODelayTime: /* find the highest value requested by any player, or option */ min_time = min_time_opt; for ( i = 0; i < NUM_SEATS; i++ ) { if ( popts(the_game->players[i])[PODelayTime] > min_time ) min_time = popts(the_game->players[i])[PODelayTime]; } /* highest reasonable value is 5 seconds */ if ( min_time > 50 ) min_time = 50; break; default: ; } return; } case PMsgReady: { PlayerP p UNUSED; int id; seats seat; CMsgPlayerReadyMsg prm; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ prm.type = CMsgPlayerReady; prm.id = id; em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; /* if the player is already ready, ignore this message */ if ( the_game->ready[seat] ) return; if ( handle_cmsg(the_game,&prm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } send_all(the_game,&prm); /* if state is handcomplete and no longer pausing, start the next hand */ if ( the_game->state == HandComplete && ! the_game->paused ) start_hand(the_game,0); /* and in the discarded or konging state, reinstate the timeout */ if ( the_game->state == Discarded || (the_game->state == Discarding && the_game->konging ) ) timeout = 1000*timeout_time; return; } /* end of case PMsgReady */ case PMsgDeclareSpecial: { PMsgDeclareSpecialMsg *m = (PMsgDeclareSpecialMsg *) pmp; PlayerP p UNUSED; int id; seats seat UNUSED; CMsgErrorMsg em; CMsgPlayerDeclaresSpecialMsg pdsm; CMsgPlayerDrawsMsg pdlm; /* FIXME */ int res; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ pdlm.id = pdsm.id = id; em.type = CMsgError ; em.seqno = connections[cnx].seqno; pdsm.type = CMsgPlayerDeclaresSpecial; pdlm.type = CMsgPlayerDraws; em.error = NULL; /* Legality checking is done by handle cmsg, so just set up the cmsg and apply it */ pdsm.tile = m->tile; res = handle_cmsg(the_game,&pdsm); if ( res < -1 ) { warn("Consistency error: giving up"); exit(1); } if ( res < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pdsm); if ( pdsm.tile == HiddenTile ) { /* if we've now started play (i.e. state == Discarding), ask everybody (except east) if they're ready */ if ( the_game->state == Discarding ) { CMsgPauseMsg pm; pm.type = CMsgPause; pm.reason = "to start play"; pm.exempt = the_game->players[east]->id; pm.requestor = 0; if ( handle_cmsg(the_game,&pm) >= 0 ) { send_all(the_game,&pm); } else { warn("Failed to pause at start of play: %s",the_game->cmsg_err); } } return; } /* and now draw the replacement */ if ( game_get_option_value(the_game,GOFlowersLoose,NULL).optbool ) { pdlm.tile = game_peek_loose_tile(the_game); pdlm.type = CMsgPlayerDrawsLoose; } else { pdlm.tile = game_peek_tile(the_game); pdlm.type = CMsgPlayerDraws; } if ( pdlm.tile == ErrorTile ) { washout(NULL); return; } if ( the_game->state == Discarding ) { /* don't do this if declaring specials */ /* stash a copy of the player, in case it goes mahjong */ copy_player(gextras(the_game)->caller,p); } if ( handle_cmsg(the_game,&pdlm) < 0 ) { /* error should be impossible */ warn("Consistency error: giving up"); exit(1); } send_id(id,&pdlm); pdlm.tile = HiddenTile; check_min_time(1); send_others(the_game,id,&pdlm); send_infotiles(p); return; } /* end of case PMsgDeclareSpecial */ case PMsgDiscard: { PMsgDiscardMsg *m = (PMsgDiscardMsg *) pmp; PlayerP p; int id; seats seat UNUSED; CMsgErrorMsg em; CMsgPlayerDiscardsMsg pdm; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pdm.type = CMsgPlayerDiscards; pdm.id = id; pdm.tile = m->tile; pdm.discard = the_game->serial+1; pdm.calling = m->calling; if ( handle_cmsg(the_game,&pdm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pdm); send_infotiles(p); /* set the timeout */ timeout = 1000*timeout_time; return; } /* end of case PMsgDiscard */ case PMsgNoClaim: { PMsgNoClaimMsg *m = (PMsgNoClaimMsg *) pmp; PlayerP p UNUSED; int id; seats seat UNUSED; CMsgErrorMsg em; CMsgPlayerDoesntClaimMsg pdcm; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pdcm.type = CMsgPlayerDoesntClaim; pdcm.id = id; pdcm.discard = m->discard; pdcm.timeout = 0; if ( handle_cmsg(the_game,&pdcm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } /* handle_cmsg ignores noclaims in wrong state, so we need to check it */ if ( ! (the_game->state == Discarded || ( (the_game->state == Discarding || the_game->state == DeclaringSpecials) && the_game->konging ) ) ) return; /* acknowledge to the player only */ /* No, now we send to all players, so they can see who's thinking */ send_all(the_game,&pdcm); /* if all claims received, process */ check_claims(the_game); return; } /* end of case PMsgNoClaim */ case PMsgPung: { PMsgPungMsg *m = (PMsgPungMsg *) pmp; PlayerP p; int id; seats seat; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; if ( the_game->state != MahJonging ) { CMsgPlayerClaimsPungMsg pcpm; pcpm.type = CMsgPlayerClaimsPung; pcpm.id = id; pcpm.discard = m->discard; if ( handle_cmsg(the_game,&pcpm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } send_all(the_game,&pcpm); /* if all claims received, process */ check_claims(the_game); return; } else { /* MahJonging */ CMsgPlayerPungsMsg ppm; ppm.type = CMsgPlayerPungs; ppm.id = id; ppm.tile = the_game->tile; if ( handle_cmsg(the_game,&ppm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&ppm); send_infotiles(p); /* if that came from a dangerous discard, tell the other players */ if ( game_flag(the_game,GFDangerousDiscard) ) { CMsgDangerousDiscardMsg ddm; ddm.type = CMsgDangerousDiscard; ddm.id = the_game->players[the_game->supplier]->id; ddm.discard = the_game->serial; ddm.nochoice = game_flag(the_game,GFNoChoice); send_all(the_game,&ddm); } /* do scoring if we've finished */ if ( pflag(p,HandDeclared) ) score_hand(the_game,seat); return; } } /* end of case PMsgPung */ case PMsgPair: { PlayerP p; int id; seats seat; CMsgErrorMsg em; CMsgPlayerPairsMsg ppm; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; ppm.type = CMsgPlayerPairs; ppm.id = id; ppm.tile = the_game->tile; if ( handle_cmsg(the_game,&ppm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&ppm); send_infotiles(p); /* if that came from a dangerous discard, tell the other players */ if ( game_flag(the_game,GFDangerousDiscard) ) { CMsgDangerousDiscardMsg ddm; ddm.type = CMsgDangerousDiscard; ddm.id = the_game->players[the_game->supplier]->id; ddm.discard = the_game->serial; ddm.nochoice = game_flag(the_game,GFNoChoice); send_all(the_game,&ddm); } /* do scoring if we've finished */ if ( pflag(p,HandDeclared) ) score_hand(the_game,seat); return; } /* end of case PMsgPair */ case PMsgSpecialSet: { PlayerP p; int id; seats seat; CMsgErrorMsg em; CMsgPlayerSpecialSetMsg pssm; char tiles[100]; int i; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pssm.type = CMsgPlayerSpecialSet; pssm.id = id; pssm.tile = the_game->tile; tiles[0] = '\000'; for ( i = 0; i < p->num_concealed; i++ ) { if ( i > 0 ) strcat(tiles," "); strcat(tiles,tile_code(p->concealed[i])); } pssm.tiles = tiles; if ( handle_cmsg(the_game,&pssm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } send_all(the_game,&pssm); send_infotiles(p); /* do scoring if we've finished */ if ( pflag(p,HandDeclared) ) score_hand(the_game,seat); return; } /* end of case PMsgSpecialSet */ case PMsgFormClosedPair: { PMsgFormClosedPairMsg *m = (PMsgFormClosedPairMsg *) pmp; CMsgPlayerFormsClosedPairMsg pfcpm; PlayerP p; int id; seats seat; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pfcpm.type = CMsgPlayerFormsClosedPair; pfcpm.id = id; pfcpm.tile = m->tile; if ( handle_cmsg(the_game,&pfcpm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pfcpm); send_infotiles(p); /* do scoring if we've finished */ if ( pflag(p,HandDeclared) ) score_hand(the_game,seat); return; } /* end of case PMsgFormClosedPair */ case PMsgFormClosedSpecialSet: { PlayerP p; int id; seats seat; CMsgErrorMsg em; CMsgPlayerFormsClosedSpecialSetMsg pfcssm; char tiles[100]; int i; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pfcssm.type = CMsgPlayerFormsClosedSpecialSet; pfcssm.id = id; tiles[0] = '\000'; for ( i = 0; i < p->num_concealed; i++ ) { if ( i > 0 ) strcat(tiles," "); strcat(tiles,tile_code(p->concealed[i])); } pfcssm.tiles = tiles; if ( handle_cmsg(the_game,&pfcssm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pfcssm); send_infotiles(p); /* do scoring if we've finished */ if ( pflag(p,HandDeclared) ) score_hand(the_game,seat); return; } /* end of case PMsgFormClosedSpecialSet */ case PMsgFormClosedPung: { PMsgFormClosedPungMsg *m = (PMsgFormClosedPungMsg *) pmp; CMsgPlayerFormsClosedPungMsg pfcpm; PlayerP p; int id; seats seat; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pfcpm.type = CMsgPlayerFormsClosedPung; pfcpm.id = id; pfcpm.tile = m->tile; if ( handle_cmsg(the_game,&pfcpm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pfcpm); send_infotiles(p); /* do scoring if we've finished */ if ( pflag(p,HandDeclared) ) score_hand(the_game,seat); return; } /* end of case PMsgFormClosedPung */ case PMsgFormClosedChow: { PMsgFormClosedChowMsg *m = (PMsgFormClosedChowMsg *) pmp; CMsgPlayerFormsClosedChowMsg pfccm; PlayerP p; int id; seats seat; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pfccm.type = CMsgPlayerFormsClosedChow; pfccm.id = id; pfccm.tile = m->tile; if ( handle_cmsg(the_game,&pfccm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pfccm); send_infotiles(p); /* do scoring if we've finished */ if ( pflag(p,HandDeclared) ) score_hand(the_game,seat); return; } /* end of case PMsgFormClosedChow */ case PMsgKong: { PMsgKongMsg *m = (PMsgKongMsg *) pmp; PlayerP p UNUSED; int id; seats seat UNUSED; CMsgPlayerClaimsKongMsg pckm; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pckm.type = CMsgPlayerClaimsKong; pckm.id = id; pckm.discard = m->discard; if ( handle_cmsg(the_game,&pckm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } send_all(the_game,&pckm); /* if all claims received, process */ check_claims(the_game); return; } /* end of case PMsgKong */ case PMsgChow: { PMsgChowMsg *m = (PMsgChowMsg *) pmp; PlayerP p; int id; seats seat; CMsgPlayerClaimsChowMsg pccm; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pccm.type = CMsgPlayerClaimsChow; pccm.id = id; pccm.discard = m->discard; pccm.cpos = m->cpos; if ( the_game->state == Discarded ) { /* special case: if chowpending is set, then this claim should be to specify the previously unspecified position after we've told the player that its claim has succeeded. So implement the chow and announce */ if ( the_game->chowpending ) { CMsgPlayerChowsMsg pcm; pcm.type = CMsgPlayerChows; pcm.id = id; pcm.tile = the_game->tile; pcm.cpos = m->cpos; if ( handle_cmsg(the_game,&pcm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pcm); return; } if ( handle_cmsg(the_game,&pccm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } send_all(the_game,&pccm); /* if all claims received, process */ check_claims(the_game); return; } else { /* This had better be mahjonging */ CMsgPlayerChowsMsg pcm; pcm.type = CMsgPlayerChows; pcm.id = id; pcm.tile = the_game->tile; pcm.cpos = m->cpos; if ( handle_cmsg(the_game,&pcm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } /* if any pos, just tell the player */ if ( m->cpos == AnyPos ) { send_id(id,&pcm); } else { check_min_time(1); send_all(the_game,&pcm); send_infotiles(p); /* if that came from a dangerous discard, tell the other players */ if ( game_flag(the_game,GFDangerousDiscard) ) { CMsgDangerousDiscardMsg ddm; ddm.type = CMsgDangerousDiscard; ddm.id = the_game->players[the_game->supplier]->id; ddm.discard = the_game->serial; ddm.nochoice = game_flag(the_game,GFNoChoice); send_all(the_game,&ddm); } if ( pflag(p,HandDeclared) ) score_hand(the_game,seat); } return; } } /* end of case PMsgChow */ case PMsgDeclareWashOut: { PlayerP p UNUSED; int id; seats seat UNUSED; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; /* at present, we don't have any rules allowing a player to declare a washout */ em.error = "Can't declare a Wash-Out"; send_id(id,&em); return; } /* end of case PMsgDeclareWashOut */ case PMsgMahJong: { PMsgMahJongMsg *m = (PMsgMahJongMsg *) pmp; PlayerP p UNUSED; int id; seats seat UNUSED; CMsgPlayerClaimsMahJongMsg pcmjm; CMsgPlayerMahJongsMsg pmjm; CMsgErrorMsg em; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; pcmjm.type = CMsgPlayerClaimsMahJong; pcmjm.id = id; pmjm.type = CMsgPlayerMahJongs; pmjm.id = id; if ( the_game->state == Discarded || ((the_game->state == Discarding || the_game->state == DeclaringSpecials) && the_game->konging ) ) { pcmjm.discard = m->discard; if ( handle_cmsg(the_game,&pcmjm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } send_all(the_game,&pcmjm); /* if all claims received, process */ check_claims(the_game); return; } else { /* this should be discarding */ pmjm.tile = the_game->tile; /* that's the tile drawn */ if ( handle_cmsg(the_game,&pmjm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } send_all(the_game,&pmjm); /* the players will now start declaring their hands */ return; } } /* end of case PMsgMahJong */ case PMsgDeclareClosedKong: { PMsgDeclareClosedKongMsg *m = (PMsgDeclareClosedKongMsg *) pmp; PlayerP p; int id; seats seat UNUSED; CMsgErrorMsg em; CMsgPlayerDeclaresClosedKongMsg pdckm; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ pdckm.id = id; em.type = CMsgError ; em.seqno = connections[cnx].seqno; pdckm.type = CMsgPlayerDeclaresClosedKong; em.error = NULL; pdckm.tile = m->tile; pdckm.discard = the_game->serial+1; if ( handle_cmsg(the_game,&pdckm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pdckm); send_infotiles(p); /* now we need to wait for people to try to rob the kong */ timeout = 1000*timeout_time; return; } /* end of case PMsgDeclareClosedKong */ case PMsgAddToPung: { PMsgAddToPungMsg *m = (PMsgAddToPungMsg *) pmp; PlayerP p; int id; seats seat UNUSED; CMsgErrorMsg em; CMsgPlayerAddsToPungMsg patpm; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ patpm.id = id; em.type = CMsgError ; em.seqno = connections[cnx].seqno; patpm.type = CMsgPlayerAddsToPung; em.error = NULL; patpm.tile = m->tile; patpm.discard = the_game->serial+1; if ( handle_cmsg(the_game,&patpm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&patpm); send_infotiles(p); /* now we need to wait for people to try to rob the kong */ timeout = 1000*timeout_time; return; } /* end of case PMsgAddToPung */ case PMsgQueryMahJong: { PMsgQueryMahJongMsg *m = (PMsgQueryMahJongMsg *) pmp; PlayerP p; int id; seats seat UNUSED; CMsgCanMahJongMsg cmjm; MJSpecialHandFlags mjf; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); cmjm.type = CMsgCanMahJong; cmjm.tile = m->tile; mjf = 0; if ( (int)game_get_option_value(the_game,GOSevenPairs,NULL).optbool ) mjf |= MJSevenPairs; cmjm.answer = player_can_mah_jong(p,m->tile,mjf); send_id(id,&cmjm); return; } case PMsgShowTiles: { PlayerP p; int id; seats seat; CMsgErrorMsg em; CMsgPlayerShowsTilesMsg pstm; id = cnx_to_id(cnx); p = id_to_player(id); seat = id_to_seat(id); /* fill in basic fields of possible replies */ pstm.id = id; em.type = CMsgError ; em.seqno = connections[cnx].seqno; pstm.type = CMsgPlayerShowsTiles; em.error = NULL; /* if there are no concealed tiles, just ignore the message; this can only happen from the mahjonging player or by duplication */ if ( p->num_concealed > 0 ) { char tiles[100]; int i; tiles[0] = '\000'; for ( i = 0; i < p->num_concealed; i++ ) { if ( i > 0 ) strcat(tiles," "); strcat(tiles,tile_code(p->concealed[i])); } pstm.tiles = tiles; if ( handle_cmsg(the_game,&pstm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } check_min_time(1); send_all(the_game,&pstm); score_hand(the_game,seat); return; } return; } /* end of case PMsgShowTiles */ case PMsgSetGameOption: { PMsgSetGameOptionMsg *m = (PMsgSetGameOptionMsg *)pmp; PlayerP p UNUSED; int id; CMsgErrorMsg em; CMsgGameOptionMsg gom; GameOptionEntry *goe; id = cnx_to_id(cnx); /* fill in basic fields of possible replies */ gom.type = CMsgGameOption; gom.id = id; p = id_to_player(id); em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; /* do we have the option ? */ goe = game_get_option_entry(the_game,GOUnknown,m->optname); if ( ! goe ) { em.error = "Trying to set unknown option"; send_id(id,&em); return; } /* if the option is actually unknown or end, ignore it */ if ( goe->option == GOUnknown || goe->option == GOEnd ) break; if ( goe->enabled == 0 ) { em.error = "Option not available in this game"; send_id(id,&em); return; } gom.optentry = *goe; switch ( goe->type ) { case GOTBool: if ( sscanf(m->optvalue,"%d",&gom.optentry.value.optbool) == 0 ) { em.error = "No boolean value found for option"; send_id(id,&em); return; } if ( gom.optentry.value.optbool != 0 && gom.optentry.value.optbool != 1 ) { em.error = "No boolean value found for option"; send_id(id,&em); return; } break; case GOTNat: if ( sscanf(m->optvalue,"%u",&gom.optentry.value.optnat) == 0 ) { em.error = "No integer value found for option"; send_id(id,&em); return; } break; case GOTInt: if ( sscanf(m->optvalue,"%d",&gom.optentry.value.optint) == 0 ) { em.error = "No integer value found for option"; send_id(id,&em); return; } break; case GOTScore: if ( sscanf(m->optvalue,"%d",&gom.optentry.value.optscore) == 0 ) { em.error = "No score value found for option"; send_id(id,&em); return; } break; case GOTString: strmcpy(gom.optentry.value.optstring, m->optvalue, sizeof(gom.optentry.value)); break; } /* specific validity checking not done by handle_cmsg */ if ( gom.optentry.option == GOTimeout && gom.optentry.value.optint < 0 ) { em.error = "Can't set negative timeout!"; send_id(id,&em); return; } if ( gom.optentry.option == GONumRounds && ( gom.optentry.value.optnat == 0 || gom.optentry.value.optnat == 3 || (gom.optentry.value.optnat > 4 && gom.optentry.value.optnat % 4 != 0) ) ) { em.error = "Number of rounds must be 1, 2 or a multiple of 4"; send_id(id,&em); return; } if ( handle_cmsg(the_game,&gom) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); } send_all(the_game,&gom); if ( gom.optentry.option == GOTimeout || gom.optentry.option == GOTimeoutGrace ) { timeout_time = get_timeout_time(the_game,localtimeouts); } break; } case PMsgQueryGameOption: { PMsgQueryGameOptionMsg *m = (PMsgQueryGameOptionMsg *)pmp; int id; CMsgErrorMsg em; CMsgGameOptionMsg gom; GameOptionEntry *goe; id = cnx_to_id(cnx); /* fill in basic fields of possible replies */ gom.type = CMsgGameOption; gom.id = 0; em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; goe = game_get_option_entry(the_game,GOUnknown,m->optname); if ( goe == NULL ) { em.error = "Option not known"; send_id(id,&em); return; } gom.optentry = *goe; send_id(id,&gom); break; } case PMsgListGameOptions: { PMsgListGameOptionsMsg *m = (PMsgListGameOptionsMsg *)pmp; int id; CMsgGameOptionMsg gom; GameOptionEntry *goe; unsigned int i; id = cnx_to_id(cnx); /* fill in basic fields of possible replies */ gom.type = CMsgGameOption; gom.id = 0; /* this relies on the fact that we know our option table contains only known options in numerical order. This would not necessarily be the case for clients, but it is for us, since we don't allow unknown options to be set */ for ( i = 1 ; i <= GOEnd ; i++ ) { goe = &the_game->option_table.options[i]; if ( !goe->enabled && ! m->include_disabled ) continue; gom.optentry = *goe; send_id(id,&gom); } break; } case PMsgChangeManager: { PMsgChangeManagerMsg *m = (PMsgChangeManagerMsg *)pmp; int id; CMsgChangeManagerMsg cmm; CMsgErrorMsg em; id = cnx_to_id(cnx); /* fill in basic fields of possible replies */ cmm.type = CMsgChangeManager; cmm.id = id; cmm.manager = m->manager; em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; if ( handle_cmsg(the_game,&cmm) < 0 ) { em.error = the_game->cmsg_err; send_id(id,&em); return; } send_all(the_game,&cmm); break; } case PMsgSendMessage: { PMsgSendMessageMsg *m = (PMsgSendMessageMsg *)pmp; int id; CMsgMessageMsg mm; CMsgErrorMsg em; id = cnx_to_id(cnx); mm.type = CMsgMessage; mm.sender = id; mm.addressee = m->addressee; mm.text = m->text; em.type = CMsgError; em.seqno = connections[cnx].seqno; em.error = NULL; if ( mm.addressee == 0 ) { send_all(the_game,&mm); } else { if ( id_to_cnx(mm.addressee) < 0 ) { em.error = "Addressee not found in game"; send_id(id,&em); return; } else { send_id(mm.addressee,&mm); } } break; } case PMsgSwapTile: { PMsgSwapTileMsg *m = (PMsgSwapTileMsg *) pmp; PlayerP p; int id; seats seat UNUSED; CMsgErrorMsg em; CMsgSwapTileMsg stm; int i; id = cnx_to_id(cnx); /* fill in basic fields of possible replies */ stm.type = CMsgSwapTile; stm.id = m->id; stm.oldtile = m->oldtile; stm.newtile = m->newtile; p = id_to_player(stm.id); seat = id_to_seat(stm.id); em.type = CMsgError ; em.seqno = connections[cnx].seqno; em.error = NULL; if ( p == NULL ) { em.error = "SwapTile: no such player"; send_id(id,&em); return; } /* find the new tile in the wall. Because this is only used for debugging and testing, we don't care that we're diving inside the game structure! Look for the tile from the end, so as to avoid depleting the early wall. */ for ( i = the_game->wall.dead_end-1 ; the_game->wall.tiles[i] != m->newtile && i >= the_game->wall.live_used ; i-- ) ; if ( i < the_game->wall.live_used ) em.error = "No new tile in wall"; else { if ( handle_cmsg(the_game,&stm) < 0 ) { em.error = the_game->cmsg_err; } else { send_id(stm.id,&stm); the_game->wall.tiles[i] = stm.oldtile; } } if ( em.error ) send_id(id,&em); return; } /* end of case PMsgSwapTile */ } } /* setup_maps: we need to be able to map between connections, player structures, and player ids. player to id is in the player structure, so we need the others. It's a pity we can't declare private variables here. */ /* set up data for a new connection */ static int new_connection(SOCKET skt) { int i; for (i= 0; i < MAX_CONNECTIONS; i++) { if ( connections[i].inuse ) continue; connections[i].inuse = 1; connections[i].skt = skt; connections[i].player = NULL; connections[i].seqno = 0; /* add to the event set */ FD_SET(skt,&event_fds); return i; } warn("No space for new connection"); return -1; } /* close connection */ static int close_connection(int cnx) { if ( connections[cnx].player ) { if ( cnx_to_id(cnx) == first_id ) { // clear first_id in case we haven't started a game and this player // has been made manager, but is not coming back first_id = 0; } /* clear maps and decrement counter */ remove_from_maps(cnx); num_connected_players--; } FD_CLR(connections[cnx].skt,&event_fds); closesocket(connections[cnx].skt); connections[cnx].inuse = 0; return 1; } static int cnx_to_id(int cnx) { PlayerP p; p = connections[cnx].player; return p ? p->id : -1; } static int id_to_cnx(int id) { int i; i = 0; while ( i < MAX_CONNECTIONS ) { if ( connections[i].inuse && connections[i].player && connections[i].player->id == id ) return i; i++; } return -1; } static PlayerP id_to_player(int id) { int f; f = id_to_cnx(id); if ( f < 0 ) return NULL; return connections[f].player; } static void setup_maps(int cnx, PlayerP p) { connections[cnx].player = p; } static void remove_from_maps(int cnx) { /* clear authorization data */ pextras(connections[cnx].player)->auth_state = AUTH_NONE; connections[cnx].player = (PlayerP) 0; } /* send_packet: send the given packet out on the given cnx, perhaps logging */ /* logit == 2 flags a message to all players of which this is one copy */ static void send_packet(int cnx,CMsgMsg *m, int logit) { char *l; if ( cnx < 0 ) return; l = encode_cmsg(m); if ( l == NULL ) { /* this shouldn't happen */ warn("send_packet: protocol conversion failed"); /* in fact, it so much shouldn't happen that we'll dump core */ assert(0); return; } if ( logit && logfile ) { if ( logit > 1 ) { fprintf(logfile,">cnx* %s",l); } else { fprintf(logfile,">cnx%d %s",cnx,l); } fflush(logfile); } if ( put_line(connections[cnx].skt,l) < 0 ) { warn("send_packet: write on cnx %d failed",cnx); /* maybe we should shutdown the descriptor here? */ return; } } /* send player id a packet. Maybe log it. Enter the packet into the player's history, if appropriate */ static void _send_id(int id,CMsgMsg *m,int logit) { int cnx = id_to_cnx(id); if ( cnx < 0 ) return; send_packet(cnx,m,logit); if ( usehist && logit && the_game->active ) { history_add(the_game,m); } } /* send to all players in a game, logging one copy */ static void _send_all(Game *g,CMsgMsg *m) { _send_id(g->players[east]->id,m,2); _send_id(g->players[south]->id,m,0); _send_id(g->players[west]->id,m,0); _send_id(g->players[north]->id,m,0); } /* send to all players in a game EXCEPT the id. Don't log it. */ static void _send_others(Game *g, int id,CMsgMsg *m) { if ( g->players[east]->id != id ) _send_id(g->players[east]->id,m,0); if ( g->players[south]->id != id ) _send_id(g->players[south]->id,m,0); if ( g->players[west]->id != id ) _send_id(g->players[west]->id,m,0); if ( g->players[north]->id != id ) _send_id(g->players[north]->id,m,0); } /* send to a particular seat in a game. Logit. */ static void _send_seat(Game *g, seats s, CMsgMsg *m) { _send_id(g->players[s]->id,m,1); } /* send to seats other than s. Don't log it: a more informative copy will have gone to another player. */ static void _send_other_seats(Game *g, seats s, CMsgMsg *m) { seats i; for ( i = 0 ; i < NUM_SEATS ; i++ ) if ( i != s ) _send_id(g->players[i]->id,m,0); } /* finish the game: send GameOver message, and print scores */ static void finish_game(Game *g) { seats i,j; CMsgGameOverMsg gom; gom.type = CMsgGameOver; send_all(g,&gom); fprintf(stdout,"Game over. Final scores:\n"); /* May as well print these in the order of original easts */ for (i=0;iplayers[j]->id, g->players[j]->name, g->players[j]->cumulative_score); } #ifdef WIN32 sleep(10); /* give the client time to close the connection */ #endif game_over = 1; the_game->active = 0; } /* start_hand: start a new hand. The game should be in state HandComplete. Send a NewHand message to each player. Shuffle and build the new wall. Deal the initial tiles to each player. Draw east's 14th tile. Enter state DeclaringSpecials, with player set to east. If the second argument is true, then don't send messages or actually start the hand; just rotate the deal (to get from a "final" HandComplete to an "initial" HandComplete, for use in game saving and resumption code). Return 1 on success. On error, return 0, and put human readable explanation in global variable failmsg. */ static int start_hand(Game *g, int rotate_only) { int stack,wall; CMsgNewHandMsg cm; cm.type = CMsgNewHand; if ( g->state != HandComplete ) { failmsg = "start_hand called when hand not complete."; return 0; } if ( ! rotate_only ) { save_state_hack = 0; /* see explanation by variable declaration */ } cm.east = g->players[east]->id; /* assume no change */ /* Does the deal rotate? */ if ( g->player != noseat /* there was a mah jong */ && (g->player != east /* and either it wasn't east */ || g->hands_as_east == 13)) { /* or this was east's 13th win */ /* does the round change? (the current south will be the next east; if that is the firsteast, the round changes) */ if ( g->players[south]->id == g->firsteast ) { CMsgNewRoundMsg nrm; /* with num_rounds code, this is more complex. Don't duplicate */ #if 0 if ( g->round == NorthWind ) { assert(0); /* this should have been caught earlier, now */ finish_game(g); } #endif nrm.round = next_wind(g->round); nrm.type = CMsgNewRound; handle_cmsg(g,&nrm); gextras(g)->completed_rounds++; if ( ! rotate_only ) { send_all(g,&nrm); } } /* rotate the seats. The new east is the current south */ cm.east = g->players[south]->id; } /* we haven't yet processed the newhand message */ if ( ! rotate_only ) { clear_history(g); } /* now set up the prehistory: game message and options */ if ( ! rotate_only ) { CMsgGameMsg gamemsg; CMsgGameOptionMsg gom; GameOptionEntry *goe; int i; gamemsg.type = CMsgGame; gamemsg.east = g->players[east]->id; gamemsg.south = g->players[south]->id; gamemsg.west = g->players[west]->id; gamemsg.north = g->players[north]->id; gamemsg.round = g->round; gamemsg.hands_as_east = g->hands_as_east; gamemsg.firsteast = g->firsteast; gamemsg.east_score = g->players[east]->cumulative_score; gamemsg.south_score = g->players[south]->cumulative_score; gamemsg.west_score = g->players[west]->cumulative_score; gamemsg.north_score = g->players[north]->cumulative_score; gamemsg.protversion = g->protversion; gamemsg.manager = g->manager; gextras(g)->prehistory[gextras(g)->prehistcount++] = cmsg_deepcopy((CMsgMsg *)&gamemsg); gom.type = CMsgGameOption; gom.id = 0; /* this relies on the fact that we know our option table contains only known options in numerical order. This would not necessarily be the case for clients, but it is for us, since we don't allow unknown options to be set */ for ( i = 1 ; i <= GOEnd ; i++ ) { goe = &g->option_table.options[i]; gom.optentry = *goe; gextras(g)->prehistory[gextras(g)->prehistcount++] = cmsg_deepcopy((CMsgMsg *)&gom); } } /* process and send a new hand msg */ if ( wallfilename ) { if ( load_wall(wallfilename,g) == 0 ) { warn("Unable to load wall"); exit(1); } } else { /* set up the wall. Should this be done by the game routine? I think not, on the whole. */ random_tiles(g->wall.tiles,game_get_option_value(g,GOFlowers,NULL).optbool); } /* we're also supposed to choose the place where the deal starts. Just for fun, we'll follow the real procedure. */ wall = 1+rand_index(5) + 1+rand_index(5); stack = wall + 1+rand_index(5) + 1+rand_index(5); stack++; /* the dice give the end of the dead wall */ if ( stack > (g->wall.size/NUM_SEATS)/2 ) { stack -= (g->wall.size/NUM_SEATS)/2; wall++; } cm.start_wall = (wall-1) % NUM_SEATS; cm.start_stack = stack -1; /* convert from 1 counting to 0 counting */ if ( handle_cmsg(g,&cm) < 0 ) { warn("handle_cmsg of NewHand message return error; dumping core"); abort(); } if ( rotate_only ) { return 1; } send_all(g,&cm); /* deal out the tiles into deal messages. We will actually deal in the traditional way: if we ever get round to implementing a semi-random shuffle, instead of a really random re-deal, this will make a difference. */ { CMsgPlayerDrawsMsg pdm; int i,j; seats s; pdm.type = CMsgPlayerDraws; for ( j=0; j < 3; j++ ) { for ( s=east; s<=north; s++) { check_min_time(1); pdm.id = g->players[s]->id; for ( i = 0; i < 4; i++ ) { pdm.tile = game_draw_tile(g); send_id(pdm.id,&pdm); player_draws_tile(g->players[s],pdm.tile); pdm.tile = HiddenTile; send_others(g,pdm.id,&pdm); } } } for ( s=east; s<=north; s++) { check_min_time(1); pdm.id = g->players[s]->id; pdm.tile = game_draw_tile(g); send_id(pdm.id,&pdm); player_draws_tile(g->players[s],pdm.tile); pdm.tile = HiddenTile; send_others(g,pdm.id,&pdm); } /* at this point, we should copy east to the caller slot in case it wins with heaven's blessing; otherwise scoring.c will be unhappy. (Yes, this happened.) */ copy_player(gextras(g)->caller,g->players[east]); s=east; pdm.id = g->players[s]->id; pdm.tile = game_draw_tile(g); check_min_time(1); send_id(pdm.id,&pdm); player_draws_tile(g->players[s],pdm.tile); pdm.tile = HiddenTile; send_others(g,pdm.id,&pdm); } /* send out info messages */ send_infotiles(g->players[east]); send_infotiles(g->players[south]); send_infotiles(g->players[west]); send_infotiles(g->players[north]); /* Enter the next state and return */ g->state = DeclaringSpecials; g->player = east; return 1; } /* timeout_handler: force no claims to be sent, and then check claims */ static void timeout_handler(Game *g) { seats i; CMsgPlayerDoesntClaimMsg pdcm; if ( g->state != Discarded && ! ( g->state == Discarding && g->konging ) ) { warn("timeout_handler called in unexpected state"); return; } pdcm.type = CMsgPlayerDoesntClaim; pdcm.discard = g->serial; pdcm.timeout = 1; for ( i=0; i < NUM_SEATS; i++ ) { if ( i == g->player ) continue; if ( g->claims[i] != UnknownClaim ) continue; pdcm.id = g->players[i]->id; handle_cmsg(g,&pdcm); send_id(pdcm.id,&pdcm); } check_claims(g); } /* check_claims: when a player makes a claim on the discard, (or tries to rob a kong) we need to check whether all claims have been lodged, and if so, process them. This function does that */ static void check_claims(Game *g) { seats i, s, ds; CMsgClaimDeniedMsg cdm; if ( g->state != Discarded && ! ( (g->state == Discarding || g->state == DeclaringSpecials) && g->konging ) ) { warn("check_claims: called in wrong game state"); return; } /* ds is the seat of the discarder/konger */ ds = g->player; /* return if not all claims are lodged */ for ( i = 0 ; i < NUM_SEATS ; i++ ) if ( i != ds && g->claims[i] == UnknownClaim ) return; /* if we are in a chowpending state, return silently now: it must be a duplicate claim that triggered this */ if ( g->chowpending ) { info("check_claim: Duplicate claim received, ignoring"); return; } /* OK, process. First, cancel timeout */ timeout = 0; /* s will be the successful claim */ s = noseat ; /* is there a mah-jong? */ for ( i = 0 ; i < NUM_SEATS ; i++ ) if ( g->claims[(ds+i)%NUM_SEATS] == MahJongClaim ) { s = (ds+i)%NUM_SEATS ; break ; } /* is there a kong? */ if ( s == noseat ) for ( i = 0 ; i < NUM_SEATS ; i++ ) if ( g->claims[(ds+i)%NUM_SEATS] == KongClaim ) { s = (ds+i)%NUM_SEATS ; break ; } /* is there a pung ? */ if ( s == noseat ) for ( i = 0 ; i < NUM_SEATS ; i++ ) if ( g->claims[(ds+i)%NUM_SEATS] == PungClaim ) { s = (ds+i)%NUM_SEATS ; break ; } /* or is there a chow? */ if ( s == noseat ) if ( g->claims[(ds+1)%NUM_SEATS] == ChowClaim ) { s = (ds+1)%NUM_SEATS ; } /* finished checking; now process */ if ( s == noseat ) { if ( g->state == Discarded ) { /* no claim; play passes to the next player, who draws */ CMsgPlayerDrawsMsg m; s = (ds+1)%NUM_SEATS; m.type = CMsgPlayerDraws; m.id = g->players[s]->id; /* peek at the tile: we'll pass the CMsg to the game to handle state changes */ m.tile = game_peek_tile(g); if ( m.tile == ErrorTile ) { /* run out of wall; washout */ washout(NULL); return; } /* stash a copy of the player, in case it goes mahjong */ copy_player(gextras(g)->caller,g->players[s]); if ( handle_cmsg(g,&m) < 0 ) { warn("Consistency error drawing tile"); abort(); } check_min_time(1); send_seat(g,s,&m); m.tile = HiddenTile; send_other_seats(g,s,&m); send_infotiles(g->players[s]); return; } else { /* the player formed a kong, and (surprise) nobody tried to rob it */ CMsgPlayerDrawsLooseMsg pdlm; pdlm.type = CMsgPlayerDrawsLoose; pdlm.id = g->players[ds]->id; /* find the loose tile. */ pdlm.tile = game_peek_loose_tile(the_game); if ( pdlm.tile == ErrorTile ) { washout(NULL); return; } /* stash a copy of the player, in case it goes mahjong */ copy_player(gextras(g)->caller,g->players[ds]); if ( handle_cmsg(the_game,&pdlm) < 0 ) { /* error should be impossible */ warn("Consistency error: giving up"); exit(1); } check_min_time(1); send_id(pdlm.id,&pdlm); pdlm.tile = HiddenTile; send_others(the_game,pdlm.id,&pdlm); send_infotiles(g->players[ds]); return; } } if ( g->claims[s] == ChowClaim ) { /* there can be no other claims, so just implement this one */ CMsgPlayerChowsMsg pcm; pcm.type = CMsgPlayerChows; pcm.id = g->players[s]->id; pcm.tile = g->tile; pcm.cpos = g->cpos; /* if the cpos is AnyPos, then we instruct the player (only) to send in another chow claim, and just wait for it */ if ( pcm.cpos == AnyPos ) { if ( handle_cmsg(g,&pcm) < 0 ) { CMsgErrorMsg em; em.type = CMsgError; em.seqno = connections[id_to_cnx(pcm.id)].seqno; em.error = g->cmsg_err; send_id(pcm.id,&em); return; } send_id(pcm.id,&pcm); return; } /* stash a copy of the player, in case it goes mahjong */ copy_player(gextras(g)->caller,g->players[s]); if ( handle_cmsg(g,&pcm) < 0 ) { warn("Consistency error: failed to implement claimed chow."); exit(1); } check_min_time(2); send_all(g,&pcm); send_infotiles(g->players[s]); /* if that came from a dangerous discard, tell the other players */ if ( game_flag(the_game,GFDangerousDiscard) ) { CMsgDangerousDiscardMsg ddm; ddm.type = CMsgDangerousDiscard; ddm.discard = the_game->serial; ddm.id = g->players[g->supplier]->id; ddm.nochoice = game_flag(g,GFNoChoice); send_all(the_game,&ddm); } return; } /* In the remaining cases, there may be other claims; send denial messages to them */ cdm.type = CMsgClaimDenied; cdm.reason = "There was a prior claim"; for ( i = 0 ; i < NUM_SEATS ; i++ ) { if ( i != ds && i != s && g->claims[i] != NoClaim) { cdm.id = g->players[i]->id; send_id(cdm.id,&cdm); } } if ( g->claims[s] == PungClaim ) { CMsgPlayerPungsMsg ppm; ppm.type = CMsgPlayerPungs; ppm.id = g->players[s]->id; ppm.tile = g->tile; /* stash a copy of the player, in case it goes mahjong */ copy_player(gextras(g)->caller,g->players[s]); if ( handle_cmsg(g,&ppm) < 0 ) { warn("Consistency error: failed to implement claimed pung."); exit(1); } check_min_time(2); send_all(g,&ppm); send_infotiles(g->players[s]); /* if that came from a dangerous discard, tell the other players */ if ( game_flag(the_game,GFDangerousDiscard) ) { CMsgDangerousDiscardMsg ddm; ddm.type = CMsgDangerousDiscard; ddm.id = g->players[g->supplier]->id; ddm.discard = the_game->serial; ddm.nochoice = game_flag(g,GFNoChoice); send_all(the_game,&ddm); } return; } if ( g->claims[s] == KongClaim ) { CMsgPlayerKongsMsg pkm; CMsgPlayerDrawsLooseMsg pdlm; pdlm.type = CMsgPlayerDrawsLoose; pkm.type = CMsgPlayerKongs; pdlm.id = pkm.id = g->players[s]->id; pkm.tile = g->tile; if ( handle_cmsg(g,&pkm) < 0 ) { warn("Consistency error: failed to implement claimed kong."); exit(1); } check_min_time(2); send_all(g,&pkm); /* find the loose tile. */ pdlm.tile = game_peek_loose_tile(g); if ( pdlm.tile == ErrorTile ) { washout(NULL); return; } /* stash a copy of the player, in case it goes mahjong */ copy_player(gextras(g)->caller,g->players[s]); if ( handle_cmsg(the_game,&pdlm) < 0 ) { /* error should be impossible */ warn("Consistency error: giving up"); exit(1); } check_min_time(1); send_id(pdlm.id,&pdlm); pdlm.tile = HiddenTile; send_others(g,pdlm.id,&pdlm); send_infotiles(g->players[s]); return; } if ( g->claims[s] == MahJongClaim ) { /* stash a copy of the player before it grabs the discard */ copy_player(gextras(g)->caller,g->players[s]); if ( g->state == Discarded ) { /* the normal situation */ CMsgPlayerMahJongsMsg pmjm; pmjm.type = CMsgPlayerMahJongs; pmjm.id = g->players[s]->id; pmjm.tile = HiddenTile; if ( handle_cmsg(g,&pmjm) < 0 ) { /* this should be impossible */ CMsgErrorMsg em; em.type = CMsgError; em.seqno = connections[id_to_cnx(pmjm.id)].seqno; em.error = g->cmsg_err; send_id(pmjm.id,&em); return; } send_all(g,&pmjm); return; } else { /* the kong's been robbed */ CMsgPlayerRobsKongMsg prkm; prkm.type = CMsgPlayerRobsKong; prkm.id = g->players[s]->id; prkm.tile = g->tile; if ( handle_cmsg(g,&prkm) < 0 ) { /* this should be impossible */ CMsgErrorMsg em; em.type = CMsgError; em.seqno = connections[id_to_cnx(prkm.id)].seqno; em.error = g->cmsg_err; send_id(prkm.id,&em); return; } check_min_time(2); send_all(g,&prkm); return; } } /* NOTREACHED */ } /* This function sends an InfoTiles message about the given player to all players who are receiving info tiles */ static void send_infotiles(PlayerP p) { static CMsgInfoTilesMsg itm; PlayerP q; static char buf[128]; int i; itm.type = CMsgInfoTiles; itm.id = p->id; if ( popts(p)[POInfoTiles] ) { player_print_tiles(buf,p,0); itm.tileinfo = buf; send_id(itm.id,&itm); } player_print_tiles(buf,p,1); for ( i = 0 ; i < NUM_SEATS ; i++ ) { q = the_game->players[i]; if ( q != p && popts(q)[POInfoTiles] ) { send_id(q->id,&itm); } } } /* Save state in the file. The state is saved as a sequence of CMsgs. First, four Players; then a Game; then GameOptions; then if a hand is in progress, Wall NewHand the history of the hand. */ static char *save_state(Game *g, char *filename) { int fd; int i,j; /* unsigned to suppress warnings */ static char gfile[512]; CMsgGameMsg gamemsg; CMsgGameOptionMsg gom; GameOptionEntry *goe; if ( filename && filename[0] ) { char *f; /* strip any directories, for security */ f = strrchr(filename,'/'); if ( f ) filename = f+1; f = strrchr(filename,'\\'); if ( f ) filename = f+1; strmcpy(gfile,filename,500); /* if it doesn't already end with .mjs, add it */ if ( strlen(gfile) < 4 || strcmp(gfile+strlen(gfile)-4,".mjs") ) { strcat(gfile,".mjs"); } } else { /* if we have no name already in use, construct one */ if ( gfile[0] == 0 ) { if ( loadfilename[0] ) { strmcpy(gfile,loadfilename,510); } else { struct tm *ts; time_t t; int i; struct stat junk; t = time(NULL); ts = localtime(&t); i = 0; while ( i < 10 ) { sprintf(gfile,"game-%d-%02d-%02d-%d.mjs", ts->tm_year+1900,ts->tm_mon+1,ts->tm_mday,i); if ( stat(gfile,&junk) != 0 ) break; i++; } } } } fd = open(gfile,O_WRONLY|O_CREAT|O_TRUNC,0777); if ( fd < 0 ) { warn("Unable to write game state file: %s",strerror(errno)); return 0; } for ( i = 0; i < NUM_SEATS; i++ ) { CMsgPlayerMsg pm; PlayerP q; q = g->players[i]; if ( q->id == 0 ) continue; pm.type = CMsgPlayer; pm.id = q->id; pm.name = q->name; fd_put_line(fd,encode_cmsg((CMsgMsg *)&pm)); } /* Normally, we write out a game message describing the current state of the game, and then the history. However, if this is the second time we've been called at the end of the same hand, we need to print the prehistory instead. */ if ( g->state == HandComplete ) save_state_hack++; if ( save_state_hack > 1 ) { int j; /* save the number of completed rounds in a comment */ CMsgCommentMsg cm; cm.type = CMsgComment; char c[] = "CompletedRounds nnnnnn"; sprintf(c,"CompletedRounds %6d",gextras(g)->completed_rounds); cm.comment = c; fd_put_line(fd,encode_cmsg((CMsgMsg *)&cm)); for ( j=0; j < gextras(g)->prehistcount; j++ ) { fd_put_line(fd,encode_cmsg(gextras(g)->prehistory[j])); } } else { /* This is a mess. Because of our initial design error, the protocol can't distinguish between an "initial" HandComplete and a "final" HandComplete. So if we're saving state in HandComplete, we need to rotate the deal before saving, so that the resumed game loads in an "initial" HandComplete for the next hand. But we can't do this to the real game, because that would mess up the game in progress. So we have to make a deep copy of the game. As this is only done here, we won't encapsulate it. */ Game ng = *g; GameExtras ngx = *(gextras(g)); ng.userdata = (void *)&ngx; Player pp[NUM_SEATS]; int j; Game *gg; if ( g->state == HandComplete ) { for (j = 0; j < NUM_SEATS; j++) { copy_player(&pp[j],g->players[j]); ng.players[j] = &pp[j]; } gg = &ng; start_hand(gg,1); } else { gg = g; } gamemsg.type = CMsgGame; gamemsg.east = gg->players[east]->id; gamemsg.south = gg->players[south]->id; gamemsg.west = gg->players[west]->id; gamemsg.north = gg->players[north]->id; gamemsg.round = gg->round; gamemsg.hands_as_east = gg->hands_as_east; gamemsg.firsteast = gg->firsteast; gamemsg.east_score = gg->players[east]->cumulative_score; gamemsg.south_score = gg->players[south]->cumulative_score; gamemsg.west_score = gg->players[west]->cumulative_score; gamemsg.north_score = gg->players[north]->cumulative_score; gamemsg.protversion = gg->protversion; gamemsg.manager = gg->manager; fd_put_line(fd,encode_cmsg((CMsgMsg *)&gamemsg)); /* save the number of completed rounds in a comment */ CMsgCommentMsg cm; cm.type = CMsgComment; char c[] = "CompletedRounds nnnnnn"; sprintf(c,"CompletedRounds %6d",gextras(gg)->completed_rounds); cm.comment = c; fd_put_line(fd,encode_cmsg((CMsgMsg *)&cm)); gom.type = CMsgGameOption; gom.id = 0; /* this relies on the fact that we know our option table contains only known options in numerical order. This would not necessarily be the case for clients, but it is for us, since we don't allow unknown options to be set */ for ( i = 1 ; i <= GOEnd ; i++ ) { goe = &gg->option_table.options[i]; gom.optentry = *goe; fd_put_line(fd,encode_cmsg((CMsgMsg *)&gom)); } } /* We are saving state for resumption here, so we do not need to save the history of a complete hand */ if ( g->state != HandComplete || save_state_hack > 1 ) { CMsgWallMsg wm; char wall[MAX_WALL_SIZE*3+1]; wm.type = CMsgWall; wm.wall = wall; wall[0] = 0; for ( i = 0 ; i < g->wall.size ; i++ ) { if ( i ) strcat(wall," "); strcat(wall,tile_code(g->wall.tiles[i])); } fd_put_line(fd,encode_cmsg((CMsgMsg *)&wm)); for ( j=0; j < gextras(g)->histcount; j++ ) { fd_put_line(fd,encode_cmsg(gextras(g)->history[j])); } } close(fd); return gfile; } /* Load the state into the (pre-allocated) game pointed to by g. N.B. The player structures must also be allocated. */ static int load_state(Game *g) { int fd; char *l; CMsgMsg *m; int manager = 0; fd = open(loadfilename,O_RDONLY); if ( fd < 0 ) { warn("unable to open game state file: %s",strerror(errno)); return 0; } clear_history(g); g->cmsg_check = 1; while ( (l = fd_get_line(fd)) ) { m = decode_cmsg(l); if ( ! m ) { warn("Bad cmsg in game state file, line %s",l); return 0; } if ( m->type == CMsgComment ) { char *p = ((CMsgCommentMsg *)m)->comment; if ( strncmp("CompletedRounds ",p,16) == 0 ) { if ( sscanf(p+16,"%d",&(gextras(g)->completed_rounds)) == 0 ) warn("unable to read number of completed rounds in game state file line %s",l); } continue; } if ( game_handle_cmsg(g,m) < 0 ) { warn("Error handling cmsg: line %s, error %s",l,g->cmsg_err); return 0; } /* if the game has a manager, clear it temporarily, so that the gameoptions we're about to process do get processed */ if ( m->type == CMsgGame ) { manager = g->manager; g->manager = 0; } history_add(g,m); } /* reinstate manager */ g->manager = manager; close(fd); /* We need to copy the timeout option value from the resumed game */ timeout_time = get_timeout_time(g,localtimeouts); return 1; } /* This function is responsible for handling the scoring. It is called in the following circumstances: The mahjonging player has made all their sets, or a non-mahjonging player has issued a ShowTiles message (the showing of the tiles will already have been done). It then (a) computes the score for the hand, together with a text description of the computation, and announces it. (b) if all players are now declared, computes the settlements, announces the net change for each player, together with a text description of the calculation; implements the settlements; and moves to the state HandComplete, incrementing hands_as_east if necessary. If the game is being terminated early because of disconnect, it will finish the game at Settlement. Arguments: g is the game, s is the *seat* of the player on which something has happened. */ static void score_hand(Game *g, seats s) { PlayerP p = g->players[s]; Score score; char buf[1000], tmp[100]; static char *seatnames[] = { "East ", "South", "West ", "North" }; static int changes[NUM_SEATS]; CMsgHandScoreMsg hsm; CMsgSettlementMsg sm; seats i,j; int m; int cannon=0; seats cannoner = noseat; bool eastdoubles; bool loserssettle; bool discarderdoubles; buf[0] = 0; score = score_of_hand(g,s); hsm.type = CMsgHandScore; hsm.id = p->id; hsm.score = score.value; hsm.explanation = score.explanation; send_all(g,&hsm); if ( s == g->player ) { psetflag(p,MahJongged); } set_player_hand_score(p,score.value); /* have we scored all the players ? */ for (i=0; i < NUM_SEATS; i++) if ( g->players[i]->hand_score < 0 ) return; /* now compute the settlements. East pays and receives double, etc? */ eastdoubles = game_get_option_value(g,GOEastDoubles,NULL).optbool; loserssettle = game_get_option_value(g,GOLosersSettle,NULL).optbool; discarderdoubles = game_get_option_value(g,GODiscDoubles,NULL).optbool; for ( i = 0; i < NUM_SEATS; i++ ) changes[i] = 0; /* first the winner; let's re-use s and p */ s = g->player; p = g->players[s]; /* Firstly, check to see if somebody let off a cannon. At this point, we assume we know that the dflags field is a bit field! */ for ( i = 0; i < NUM_SEATS; i++ ) { if ( i == s ) continue; if ( p->dflags[i] & p->dflags[s] ) { /* Only one person can be liable, so let's have a little consistency check */ if ( cannon ) { warn("Two people found letting off a cannon!"); exit(1); } cannon=1; cannoner=i; } } if ( cannon ) { sprintf(tmp,"%s let off a cannon\n\n",seatnames[cannoner]); strcat(buf,tmp); } for ( i = 0; i < NUM_SEATS; i++ ) { if ( i == s ) continue; m = g->players[s]->hand_score; if ( eastdoubles && i*s == 0 ) m *= 2; /* sneaky test for one being east */ /* singaporean rules: discarder pays double, on self-draw (including loose - query, is this right?) everybody pays double */ if ( discarderdoubles && (g->whence != FromDiscard || i == g->supplier) ) m *= 2; changes[s] += m; changes[cannon ? cannoner : i] -= m; sprintf(tmp,"%s pays %s",seatnames[cannon ? cannoner : i],seatnames[s]); strcat(buf,tmp); if ( cannon && cannoner != i ) { sprintf(tmp," (for %s)",seatnames[i]); strcat(buf,tmp); } sprintf(tmp," %5d\n",m); strcat(buf,tmp); } sprintf(tmp,"%s GAINS %5d\n\n",seatnames[s],changes[s]); strcat(buf,tmp); /* and now the others */ for ( i = 0; i < NUM_SEATS; i++ ) { if ( i == s ) continue; for ( j = i+1; j < NUM_SEATS; j++ ) { if ( j == s ) continue; if ( cannon ) continue; if ( ! loserssettle ) continue; m = g->players[i]->hand_score - g->players[j]->hand_score; if ( eastdoubles && i*j == 0 ) m *= 2; changes[i] += m; changes[j] -= m; if ( m > 0 ) { sprintf(tmp,"%s pays %s %5d\n",seatnames[j],seatnames[i],m); } else if ( m < 0 ) { sprintf(tmp,"%s pays %s %5d\n",seatnames[i],seatnames[j],-m); } else { sprintf(tmp,"%s and %s are even\n",seatnames[i],seatnames[j]); } strcat(buf,tmp); } sprintf(tmp,"%s %s %5d\n\n",seatnames[i], changes[i] >= 0 ? "GAINS" : "LOSES", abs(changes[i])); strcat(buf,tmp); } sm.type = CMsgSettlement; sm.east = changes[east]; sm.south = changes[south]; sm.west = changes[west]; sm.north = changes[north]; sm.explanation = buf; handle_cmsg(g,&sm); send_all(g,&sm); /* dump the hand history to a log file if requested */ if ( hand_history ) { char buf[256]; /* hack hack hack FIXME */ static int handnum = 1; sprintf(buf,"hand-%02d.mjs",handnum++); save_state_hack = 1; save_state(g,buf); } /* finish game if terminating on disconnect */ if ( end_on_disconnect_in_progress ) finish_game(g); /* is the game over? */ if ( g->player != noseat /* there was a mah jong */ && (g->player != east /* and either it wasn't east */ || g->hands_as_east == 13) /* or this was east's 13th win */ /* does the round change? (the current south will be the next east; if that is the firsteast, the round changes) */ && g->players[south]->id == g->firsteast && gextras(g)->completed_rounds == game_get_option_value(g,GONumRounds,NULL).optnat - 1 ) // it's the last round //// && g->round == NorthWind ) /* and it's the last round already */ finish_game(g); /* otherwise pause for the next hand */ { CMsgPauseMsg pm; pm.type = CMsgPause; pm.exempt = 0; pm.requestor = 0; pm.reason = "to start next hand"; handle_cmsg(g,&pm); send_all(g,&pm); } } /* implement washout. The argument is the reason for the washout; if NULL, the default reason of "wall exhausted" is assumed. */ static void washout(char *reason) { CMsgPauseMsg pm; CMsgWashOutMsg wom; wom.type = CMsgWashOut; if ( reason ) wom.reason = reason; else wom.reason = "wall exhausted"; handle_cmsg(the_game,&wom); send_all(the_game,&wom); /* if ShowOnWashout is set, show all tiles */ if ( game_get_option_value(the_game,GOShowOnWashout,NULL).optbool ) { int i; PlayerP p; CMsgPlayerShowsTilesMsg pstm; /* fill in basic fields of possible replies */ pstm.type = CMsgPlayerShowsTiles; for ( i = 0; i < NUM_SEATS; i++ ) { p = the_game->players[i]; /* if there are no concealed tiles, skip */ if ( p->num_concealed > 0 ) { char tiles[100]; int j; tiles[0] = '\000'; for ( j = 0; j < p->num_concealed; j++ ) { if ( j > 0 ) strcat(tiles," "); strcat(tiles,tile_code(p->concealed[j])); } pstm.id = p->id; pstm.tiles = tiles; if ( handle_cmsg(the_game,&pstm) < 0 ) { warn("Error handling cmsg in ShowOnWashout: error %s",the_game->cmsg_err); return; } check_min_time(1); send_all(the_game,&pstm); } } } /* and now pause for the next hand */ pm.type = CMsgPause; pm.exempt = 0; pm.requestor = 0; pm.reason = "to start next hand"; handle_cmsg(the_game,&pm); send_all(the_game,&pm); } /* called for an error on an cnx. Removes the cnx, suspends the game if appropriate, etc */ static void handle_cnx_error(int cnx) { CMsgStopPlayMsg spm; char emsg[100]; int id; /* for the moment, die noisily. Ultimately, just notify other players, and re-open the listening socket */ if ( connections[cnx].skt == socket_fd ) { /* hard to see how this could happen, but ... */ warn("Exception condition on listening fd\n"); exit(1); } warn("Exception/eof on cnx %d, player %d\n",cnx, cnx_to_id(cnx)); spm.type = CMsgStopPlay; /* We clear the player's entry before sending the message so that it fails harmlessly on the player's file instead of writing to a broken pipe. */ id = cnx_to_id(cnx); /* we need to keep the connection in the table for use below. This will cause some writes to broken pipes, but we're ignoring those anyway */ if ( id >= 0 ) { /* if the game hasn't yet started, and all the players support the necessary protocol version, we do not need to exit; we can just delete the disconnecting player. Of course, we don't need the disconnecting player to support the necessary protocol version, but I can't be bothered to do that. However, I think if --exit-on-disconnect is given, we should exit anyway, since the purpose of that option is to stop unwanted servers hanging around. */ if ( the_game->round == UnknownWind && ! exit_on_disconnect && protocol_version > 1034 ) { CMsgPlayerMsg pm; pm.type = CMsgPlayer; pm.id = id; pm.name = NULL; handle_cmsg(the_game,&pm); close_connection(cnx); send_all(the_game,&pm); return; } sprintf(emsg,"Lost connection to player %d%s", id,exit_on_disconnect ? "quitting" : loadstate ? "; game suspended" : ""); spm.reason = emsg; if ( ! end_on_disconnect ) { send_all(the_game,&spm); the_game->active = 0; } else { int washed_out = 0; int dcpen = 0; /* apply disconnection penalty ? */ CMsgMessageMsg mm; mm.type = CMsgMessage; mm.sender = mm.addressee = 0; mm.text = emsg; sprintf(emsg,"Lost connection to player %d - ",id); /* to handle disconnection gracefully ... */ if ( the_game->state == HandComplete ) { /* nothing to do at this point */ strcat(emsg,"game will end now"); } else if ( the_game->state == MahJonging ) { PlayerP p; seats s; /* if it's the mahjonging player we've lost, and they haven't yet declared their hand, I think that's just tough -- we don't want to do auto-declaration */ s = id_to_seat(id); p = id_to_player(id); if ( the_game->player == s && !pflag(p,HandDeclared) ) { CMsgWashOutMsg wom; wom.type = CMsgWashOut; wom.reason = "Lost the winner"; washed_out = 1; handle_cmsg(the_game,&wom); send_all(the_game,&wom); strcat(emsg,"ending game"); } else { PMsgShowTilesMsg stm; /* we have to continue processing, since other players may continue to declare tiles. We fake a showtiles for this player, and handle the end of game later */ end_on_disconnect_in_progress = 1; if ( !pflag(p,HandDeclared) ) { stm.type = PMsgShowTiles; handle_pmsg((PMsgMsg *)&stm,cnx); } strcat(emsg,"game will end after scoring this hand"); } } else { /* not much we can do except declare a washout */ CMsgWashOutMsg wom; wom.type = CMsgWashOut; wom.reason = "Lost a player"; handle_cmsg(the_game,&wom); send_all(the_game,&wom); strcat(emsg,"ending game"); washed_out = 1; } send_all(the_game,&mm); /* do we need to apply a disconnection penalty? If the state is MahJonging, then it's morally the same as HandComplete. Well, not if it's the winner who dc'ed, but we'll quietly pass over that. The washouts will have set handcomplete, so we must apply the usual logic */ /* are we at the end of a hand ? */ /* If the game hasn't actually got under way, we won't charge a penalty */ if ( the_game->state == HandComplete && the_game->round == EastWind && the_game->players[east]->id == the_game->firsteast && the_game->hands_as_east == 0 && ! washed_out ) { dcpen = 0; } else if ( (the_game->state == HandComplete || the_game->state == MahJonging) /* It's a pain that I can't test for this easily at the *end* of a hand. Have to duplicate logic elsewhere */ && the_game->player != noseat /* there was a mah jong */ ) { /* are we at the end of a round? */ if ( (the_game->player != east /* and either it wasn't east */ || the_game->hands_as_east == 13) /* or this was east's 13th win */ /* does the round change? (the current south will be the next east; if that is the firsteast, the round changes) */ && the_game->players[south]->id == the_game->firsteast ) { if ( the_game->round == NorthWind ) { /* end of game. No penalty can apply */ } else { dcpen = disconnect_penalty_end_of_round; } } else { /* in middle of round, but end of hand */ dcpen = disconnect_penalty_end_of_hand; } } else { dcpen = disconnect_penalty_in_hand; } if ( dcpen > 0 ) { info("Disconnect penalty for player %d: %d",id,dcpen); change_player_cumulative_score(id_to_player(id),-dcpen); } /* if we have nothing further to do, we should end the game */ if ( ! end_on_disconnect_in_progress ) { finish_game(the_game); } } timeout = 0; /* clear timeout */ } if ( exit_on_disconnect ) save_and_exit() ; /* and close the offending connection */ close_connection(cnx); } /* loads a wall from a file; puts stack and wall of start in last two arguments */ static int load_wall(char *wfname, Game *g) { FILE *wfp; int i; char tn[5]; Tile t; wfp = fopen(wfname,"r"); if ( wfp == NULL ) { warn("couldn't open wall file"); return 0; } for ( i = 0 ; i < g->wall.size ; i++ ) { fscanf(wfp,"%2s",tn); t = tile_decode(tn); if ( t == ErrorTile ) { warn("Error parsing wall file"); return 0; } g->wall.tiles[i] = t; } return 1; } /* resend: given a player id and a message, send it to the player if appropriate, censoring as required. */ static void resend(int id, CMsgMsg *m) { switch ( m->type ) { /* The following messages are always sent */ case CMsgNewHand: case CMsgPlayerDeclaresSpecial: case CMsgPause: case CMsgPlayerReady: case CMsgPlayerDiscards: case CMsgDangerousDiscard: case CMsgPlayerClaimsPung: case CMsgPlayerPungs: case CMsgPlayerFormsClosedPung: case CMsgPlayerClaimsKong: case CMsgPlayerKongs: case CMsgPlayerDeclaresClosedKong: case CMsgPlayerAddsToPung: case CMsgPlayerRobsKong: case CMsgPlayerClaimsChow: case CMsgPlayerChows: case CMsgPlayerFormsClosedChow: case CMsgWashOut: case CMsgPlayerClaimsMahJong: case CMsgPlayerMahJongs: case CMsgPlayerPairs: case CMsgPlayerFormsClosedPair: case CMsgPlayerShowsTiles: case CMsgPlayerSpecialSet: case CMsgPlayerFormsClosedSpecialSet: case CMsgHandScore: case CMsgSettlement: send_id(id,m); break; /* The following messages are sent only to the player concerned */ case CMsgPlayerDoesntClaim: if ( id == ((CMsgPlayerDoesntClaimMsg *)m)->id ) send_id(id,m); break; case CMsgSwapTile: if ( id == ((CMsgSwapTileMsg *)m)->id ) send_id(id,m); break; /* The following messages are sent as is to the player concerned, and with censoring to other players */ case CMsgPlayerDraws: if ( id == ((CMsgPlayerDrawsMsg *)m)->id ) { send_id(id,m); } else { CMsgPlayerDrawsMsg mm = *(CMsgPlayerDrawsMsg *)m; mm.tile = HiddenTile; send_id(id,&mm); } break; case CMsgPlayerDrawsLoose: if ( id == ((CMsgPlayerDrawsLooseMsg *)m)->id ) { send_id(id,m); } else { CMsgPlayerDrawsLooseMsg mm = *(CMsgPlayerDrawsLooseMsg *)m; mm.tile = HiddenTile; send_id(id,&mm); } break; /* The following message can occur in history, but is not sent to clients */ case CMsgComment: break; /* The following should not occur in history */ case CMsgCanMahJong: case CMsgClaimDenied: case CMsgConnectReply: case CMsgReconnect: case CMsgRedirect: case CMsgAuthReqd: case CMsgError: case CMsgGame: case CMsgGameOption: case CMsgGameOver: case CMsgInfoTiles: case CMsgNewRound: case CMsgPlayer: case CMsgPlayerOptionSet: case CMsgMessage: case CMsgChangeManager: case CMsgStartPlay: case CMsgStopPlay: case CMsgWall: case CMsgStateSaved: abort(); } } static void clear_history(Game *g) { /* now clear the history records */ while ( gextras(g)->histcount > 0 ) { cmsg_deepfree(gextras(g)->history[--gextras(g)->histcount]); } /* and the prehistory */ while ( gextras(g)->prehistcount > 0 ) { cmsg_deepfree(gextras(g)->prehistory[--gextras(g)->prehistcount]); } } /* add a cmsg (if appropriate) to the history of a game */ static void history_add(Game *g, CMsgMsg *m) { switch ( m->type ) { /* These messages do go into history */ case CMsgNewHand: case CMsgPlayerDeclaresSpecial: case CMsgPause: case CMsgPlayerReady: case CMsgPlayerDraws: case CMsgPlayerDrawsLoose: case CMsgPlayerDiscards: case CMsgPlayerDoesntClaim: case CMsgDangerousDiscard: case CMsgPlayerClaimsPung: case CMsgPlayerPungs: case CMsgPlayerFormsClosedPung: case CMsgPlayerClaimsKong: case CMsgPlayerKongs: case CMsgPlayerDeclaresClosedKong: case CMsgPlayerAddsToPung: case CMsgPlayerRobsKong: case CMsgPlayerClaimsChow: case CMsgPlayerChows: case CMsgPlayerFormsClosedChow: case CMsgWashOut: case CMsgPlayerClaimsMahJong: case CMsgPlayerMahJongs: case CMsgPlayerPairs: case CMsgPlayerFormsClosedPair: case CMsgPlayerShowsTiles: case CMsgPlayerSpecialSet: case CMsgPlayerFormsClosedSpecialSet: case CMsgHandScore: case CMsgSettlement: case CMsgComment: case CMsgSwapTile: gextras(g)->history[gextras(g)->histcount++] = cmsg_deepcopy(m); break; /* These messages do not go into history */ case CMsgStateSaved: case CMsgCanMahJong: case CMsgClaimDenied: case CMsgConnectReply: case CMsgReconnect: case CMsgAuthReqd: case CMsgRedirect: case CMsgError: case CMsgGame: case CMsgGameOption: case CMsgGameOver: case CMsgInfoTiles: case CMsgNewRound: case CMsgPlayer: case CMsgPlayerOptionSet: case CMsgMessage: case CMsgChangeManager: case CMsgStartPlay: case CMsgStopPlay: case CMsgWall: ; } } static void save_and_exit() { if ( save_on_exit ) save_state(the_game,NULL); exit(0); } /* this function extracts the timeout_time from a game according the Timeout and TimeoutGrace options, and the passed in value of localtimeouts */ static int get_timeout_time(Game *g,int localtimeouts) { int t = game_get_option_value(g,GOTimeout,NULL).optnat; if ( t > 0 && localtimeouts ) t += game_get_option_value(g,GOTimeoutGrace,NULL).optnat; return t; } mj-1.17-src/tiles.h0000444006717300001440000001346615002771311012623 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/tiles.h,v 12.1 2020/05/30 18:15:18 jcb Exp $ * tiles.h * Declares the datatype representing a tile, and the access functions. * At present, the access functions are actually macros, but you're not * supposed to know that. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #ifndef TILES_H_INCLUDED #define TILES_H_INCLUDED /* A tile is actually a (signed) short. */ typedef short Tile; /* The internal representation is viewed as a two digit decimal number (for ease of debugging, ahem; but why not?). The first digit is the suit, the second the value. Specials hacked in. The code -1 is returned as an error. The code 0 denotes a blank tile. (Distinguished from White Dragon!). Otherwise: first digit 1,2,3 denotes characters,bamboo,circles, and second digit 1-9 gives the value; first digit 4 is winds, second digit 1,2,3,4 is E,S,W,N; first digit 5 is dragons, second digit 1,2,3 is Red,White,Green; first digit 6 is flowers, 1,2,3,4 is erm...whatever first digit 7 is seasons, 1,2,3,4 is erm...what order are they? (The fact that Winds, Dragons, etc are honorary suits is public information; but the is_suit function refers to real suits. Hm. This is maybe wrong.) The following commitments are made about these datatype; external code that relies on anything else is asking for trouble: (1) Tile is a signed integer (of some length) datatype. (2) the HiddenTile value is 0. (3) the ErrorTile value is -1. (4) within each suit, the tile values form consecutive integers; in particular, 1 or 2 can be added to/subtracted from a suit tile to obtain the tile 1 or 2 away. (5) for wind and dragon tiles, the value_of is the appropriate wind or dragon enum. (7) the flowers and seasons appear in the order corresponding to the winds, and the value_of a flower or season is the Wind corresponding to it. */ #define ErrorTile ((Tile) -1) #define HiddenTile ((Tile) 0) /* MaxTile is guaranteed to be bigger than any real tile value, and so can be used to specify the sizes of arrays that want to be indexed by tile values */ #define MaxTile 80 /* these values are not guaranteed */ typedef enum { BambooSuit = 1, CharacterSuit = 2, CircleSuit = 3, WindSuit = 4, DragonSuit = 5, FlowerSuit = 6, SeasonSuit = 7 } TileSuit ; /* These values are guaranteed */ typedef enum { UnknownWind = 0, /* for convenience ... */ EastWind = 1, SouthWind = 2, WestWind = 3, NorthWind = 4 } TileWind ; /* These values are guaranteed */ typedef enum { RedDragon = 1, WhiteDragon = 2, GreenDragon = 3 } TileDragon ; /* These values are guaranteed, by the requirement that flowers/seasons match their winds */ typedef enum { Plum = 1, Orchid = 2, Chrysanthemum = 3, Bamboo = 4 } TileFlower; typedef enum { Spring = 1, Summer = 2, Autumn = 3, Winter = 4 } TileSeason; #define suit_of(t) ((TileSuit)((unsigned short) t/10)) #define value_of(t) ((unsigned short) t % 10) #define is_suit(t) (suit_of(t) >= 1 && suit_of(t) <= 3) #define is_wind(t) (suit_of(t) == (short)WindSuit) #define is_dragon(t) (suit_of(t) == (short)DragonSuit) #define is_season(t) (suit_of(t) == (short)SeasonSuit) #define is_flower(t) (suit_of(t) == (short)FlowerSuit) #define is_normal(t) (is_suit(t)||is_wind(t)||is_dragon(t)) #define is_special(t) (is_season(t)||is_flower(t)) #define is_honour(t) (is_wind(t)||is_dragon(t)) #define is_terminal(t) (is_suit(t) && (value_of(t) == 1 || value_of(t) == 9)) #define is_simple(t) (is_suit(t) && (value_of(t) >= 2 || value_of(t) <= 8)) #define is_minor is_simple #define is_major(t) (is_honour(t) || is_terminal(t)) /* this says whether a tile is green in the sense of Imperial Jade */ int is_green(Tile t); /* this is an array of thirteen tiles listing the 13 unique wonders */ extern Tile thirteen_wonders[]; /* this function is a convenience for iterating over all tiles. The initial argument should be HiddenTile; if then fed its result, it will generate all the tiles (in some order), finishing by returning HiddenTile. The second argument says whether to include the flowers and seasons. */ Tile tile_iterate(Tile t,int includespecials); /* return the next wind in playing order */ TileWind next_wind(TileWind w); /* and the previous */ TileWind prev_wind(TileWind w); /* make_tile: takes a suit and a value, and returns the tile. The value arg is an int, as it will frequently be calculated */ Tile make_tile(TileSuit s,int v); /* tile_name: takes a tile and returns a string describing it The returned string is guaranteed to be in static storage and not to change.*/ const char *tile_name(Tile t); /* tile_code: returns a two letter code for the tile. Result is static and immutable. */ const char *tile_code(Tile t); /* tile_decode: turn a two-letter code into a tile. */ Tile tile_decode(const char *c); /* random_tiles: the argument should point to an array of tiles big enough to take all the required tiles. This function will deal tiles into the array randomly. The second argument says whether to include flowers and seasons. */ void random_tiles(Tile *tp,int includespecials); #include "tiles-enums.h" #endif /* TILES_H_INCLUDED */ mj-1.17-src/LICENCE0000444006717300001440000004617315002771311012320 0ustar jcbusersLICENCE for the Unix mah-jong program by J. C. Bradfield. $Header: /home/jcb/MahJong/newmj/RCS/LICENCE,v 12.0 2009/06/28 20:42:57 jcb Rel $ This LICENCE file gives the permissions for distribution and use of the Unix mah-jong suite of programs written by me, J. C. Bradfield. The programs are distributed under the GNU General Public License, version 2, or at your discretion any later version. Parts of the distribution are not written by me; these parts, and the terms applying to them, are identified in the README file. Note: The GPL is used partly as a matter of convenience, and partly so that other GPLed items can be included. If you have a need or desire to use these programs under a less restrictive licence, ask me. Note: If you are reading this file in a binary distribution, the distributor should also have made available to you a source distribution. The latest source distribution should be available on at least one of http://www.dcs.ed.ac.uk/home/jcb/MahJong/ http://www.stevens-bradfield.com/MahJong/ REQUEST: The GPL does not permit me to restrict what you may do in the way of modification. However, I request you to ensure that any derived works respect the protocol definition, and I request you not to distribute incompatible modifications under the same name. As a corollary to this request, please tell me about any modifications you make that are of interest to anybody else. The GNU General Public License, version 2, follows here (to end of file): GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. mj-1.17-src/scoring.h0000444006717300001440000000263115002771311013137 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/scoring.h,v 12.0 2009/06/28 20:43:13 jcb Rel $ * scoring.h * header file for scoring routines. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #ifndef SCORING_H_INCLUDED #define SCORING_H_INCLUDED 1 #include "game.h" /* This structure is used to return score components. The value is either a number of points, or a number of doubles, as appropriate; the explanation is a human readable explanation of this score. The explanation must be copied before another scoring function is called. */ typedef struct _Score { int value; char *explanation; } Score; Score score_of_hand(Game *g, seats s); /* this variable disables scoring for flowers and seasons */ extern int no_special_scores; #endif /* SCORING_H_INCLUDED */ mj-1.17-src/CHANGES0000444006717300001440000004254215002771311012322 0ustar jcbusers1.17 2025-04-25 Minor feature. Player: add --randomness option to degrade performance. Graphical client: add "robot difficulty" option to control above. Code: suppress some warnings. 1.16 2020-05-26 Bug fixes/minor features. Protocol: bumped to 1111 to indicate server now sending DoesntClaim to all players Graphical client: add option (Display AlertMahjong) to warn player when a mah-jong is (probably) possible. Add option (Display ShowThinking) to display three dots by players who have yet to claim or pass. Server: fix yet more still remaining bugs in reconnection when players reconnect by name. When a player NoClaims, inform all players, not just that player (to allow ShowThinking). Code: various tidies. Changes for cross-compilation. New release type (win32-i686 - cross-compiled, gtk2 only). 1.15 2019-06-06 Bug fixes/minor features. Graphical client: add option (Display RotateLabels) to control rotation of player info text on board. Increase the visibility of the selected tile (by giving it a magenta border). Fix minor bug in checking claims at end of game. 1.14 2014-01-06 Bug fixes/minor features. Server: fix still remaining bugs in game saving at end of round. Graphical client: add file chooser in open dialog for resuming. Add scoring history window. Update About. Highlight player whose turn it is. Grey out scoring dialog to avoid confusion at end of game. Various minor bug fixes. Code: avoid printf abuse (Göran Uddeborg). 1.13 2013-08-28 Bug fixes. Server: fix major bug in (presumably unused) feature: game saving and resumption was broken for saves at the end of a hand. Graphical client: popup scoring window on washout even if iconified. Code: add math libraries to gui client for recent linuxen, where it's not included by other library configs. 1.12.1 2012-05-04 Bug fix. Code: for a while, system files had been wrongly included in the dependencies in the Makefile. 1.12 2012-02-03 Bug fixes/minor features. Code: fairly extensive changes to make it 64-bit clean. Includes one change to function types. Server: bug fix for --id-order-seats option. Graphical client: use a default Unix socket if no name given; save the robot player options between sessions. 1.11 2011-08-29 Bug fixes. Protocol: 1110 corrects konging rule: a just-claimed pung can now be converted to a kong, rather than having to wait till next draw. Code: game module implements above. Graphical client: game status was not always being updated; make iconification of subsidiary windows work properly; fix non-appearance of "About xmj" window. 1.10 2009-06-28 Substantial enhancements. Bug fixes. Port to GTK+2. Protocol: 1101 adds NumRounds game option. Code: a few functions added to player and game modules. Makefile options tidied, and code for Windows with and without msys added. Server: fix couple of bugs, one causing crash. Handle NumRounds option. Add --id-order-seats option. End game at end of hand, instead of start of next. Graphical client: port to GTK+2. Allow customization via gtkrc files. Fix long-standing bug in display of tiles (positioning of left and right players' hands). Provide player status info to right of rack, when the wall is shown. Handle game ending at end of last hand. Allow user to arrange tiles in hand (SortTiles display option). Fix an old and silly bug (causing only subtle UI misbehaviour). Remove wrong size setting of scoring information window. Arrange for players to be seated in order of specification when starting local game (if not random). Handle interrupted system calls on windows gtk2. Player: document the strategy options (partly, at least). 1.9 2008-08-22 Bug fixes and enhancements. Protocol: 1100 adds authorization messages, and a redirection message. Code: new function to build quoted strings for shell; various tidies of warnings. Minor change to Windows makefile. Add index() for Windows. Server: handle basic authorization. Add --hand-history option. Graphical client: handle basic authorization. Fix bug: failure if robot player names included spaces and quotes etc. 1.8 2007-05-22 Bug fixes and enhancements. Protocol: 1072 adds ShowOnWashout option. Code: a few minor fixes and tweaks; in particular, change order of automatic declarations to do pairs before chows. Server: add option to reveal tiles on washout. Graphical client: amalgamate a couple of buttons in dialogues for more intuitive presentation. Player: rehashed somewhat, with various parameters redistributed into a bunch of magic numbers, with values chosen by experiment. Should play a somewhat tougher game. 1.7 2006-01-22 Bug fixes and enhancements. Code: much diagnostic and logging code added. More socket code. Buffer overflow exposures fixed (Len Budney). Add Reconnect command. Fix very old bug in Kong Upon Kong checking (didn't account for closed kongs). Server: fix bug in sending options to players when resuming a game. Allow clients to refetch game state by Reconnect. Graphical client: add crash reporting via Internet. Fix bug in UI after a kong is robbed. Windows binary: build with modern libraries and tool chain - seems to fix the mysterious crashes. 1.6.3 2004-05-09 Bug fixes. Code: the fix for thirteen wonders in 1.6.1 needed fixing. 1.6.2 2004-04-16 Bug fixes. Code: use (v)snprintf in Windows code, since it does exist, rather than faking it with inadequate substitute. 1.6.1 2004-04-10 Bug fixes. Server: segfaulted when players connected with null names. Code: the thirteen wonders checking function was not checking correctly. 1.6 2003-10-07 Bug fixes and security fixes. Protocol: use of backslash escaping has been tidied up. Arguably this is only a bug fix, but protocol version has been bumped to 1060 anyway. Graphical client: when auto-declaration of specials was on, players did not get the chance to declare kongs in their dealt hands - fixed. Server: receiving duplicate "NoClaim"s while waiting for a Chow claim to complete could result in the chow claim being ignored - fixed this. Code: backslash escaping tidied up, and infinite loop bug removed. Several potential buffer overrun exploits closed (thanks to Nicolas Boullis for these). 1.5.6 2003-06-04 Bug fixes and minor enhancements. Graphical client: add option to select table colour. Minor wording changes. Code tidies. Server: Change meaning of DiscarderDoubles to match Singaporean play (as originally intended). Bug fix in loading games from file. Code: fix bug in handling of kong robbing (konging flag wasn't cleared, causing crash in next hand). 1.5.5 2003-04-11 Bug fixes. Graphical client: fix bug in automatic declaration of winning hands when having Thirteen Orphans. Extend auto-declaration so that it claims the discard as well. Server: hands with Thirteen Orphans were being given Imperial Jade as well. 1.5.4 2003-04-10 Bugfixes. Graphical client: fix bug in interaction between sticky open dialog fields and the --connect command-line option. Change the location for xmj.ini under Windows: it is now in HOMEPATH, current (if writable), or root (in that order). Also, look for old xmj.ini in all locations if not found in the expected location. Fix bug in Message processing causing crashes. Code: changed spec of get_homedir(). 1.5.3 2003-04-05 Bugfix, minor enhancement. Graphical client: add display option to choose text font. Code: fix bug in handling of "last 4 tiles" cannon (again). 1.5.2 2003-04-01 Bugfixes, minor enhancement. Protocol: version now 1050, as we now accept Chow AnyPos while MahJonging. Graphical client: add a checkbox to keep focus in message window. Fix handling of chows when MahJonging so that erroneous chows don't cause deadlock. Server: don't allow reconnects while terminating a game. Code: make protocol text expansion accept null source. (Prevents errors in handling empty CMsgMessages.) Add hacks to allow "server" to be stdin/stdout. Change handling of chows to allow an AnyPos chow declaration when claiming the discard for Mah-Jong. 1.5.1 2003-03-16 Bugfixes. Server: fix bugs in handling of disconnecting players before game starts. Code: in protocol decoding, work round bug in Microsoft implementation of scanf. 1.5 2003-03-15 Feature enhancements, bugfixes. Rules/protocol: protocol can now remove players from a game (1036). Added TimeoutGrace game option and LocalTimeouts player option (1036). Added LoadState command (1038). Added LosersSettle, EastDoubles, DiscDoubles game options (1040). Protocol version now 1040. Server: handle new game options. Add --no-manager option to stop players changing game options. Add --end-on-disconnect option to end the game gracefully when a player disconnects, and --disconnect-penalties to specify penalties for this. Allow clients to handle timeouts locally. Handle dynamic loading of game state in response to LoadState. Graphical client: re-arrange new game and connection menu and panels. Several bugfixes. Add display options: suppress popups, include info and message windows in main window. Make some fields sticky in dialogs. Add options to declare specials and sets automatically. New window to display warnings from internal code. Add font selection for main font. Add display size to the preferences panel. Handle timeouts locally if allowed. Include small tile set in distribution. Use "Display" instead of "XmjOption" in rc file. Code: bugfix to cannon handling. Fix bug in treatment of scoring options, avoid overflow. Some changes to spec of internal functions. Prevent players discarding when they should draw a tile. New functions for warnings. 1.4.1 2002-08-09 Minor bugfix. Server: set timeout option correctly when resuming game. Code: fix bug in handling of cannons in last four tiles. 1.4 2002-03-30 Feature enhancement. Rules/protocol: add option to implement Millington's rules on kongs. Protocol version now 1034. Server: implement scoring with Millington's rules on kongs. Warning: server is not perfectly backward compatible, as annexed and claimed kongs are now distinguished in scoring information, regardless of the appropriate option setting. However, earlier clients using my code will not be worried by this. Graphical client: handle display of Millington's kong variations. Code: handle Millington's kongs. (Changes to several modules.) 1.3.2 2002-02-04 Minor bugfix. Graphical client: fix small bug in handling of numerical option fields. 1.3.1 2002-01-08 Minor bugfixes and invisible enhancements. Graphical client: fix bug in option parsing. Change tiletip positions; when tiletips always on, have the selected tile's tip always visible. Move tiletips, claim popups, and animation into the main window (except under Windows, where animation is still done by popups). Fix bug in uniconification of auxiliary windows. Code: introduced the vlazy_fixed widget, another variation on the gtk_fixed, for use with "popups". 1.3 2002-01-02 Feature enhancements and bugfixes. Rules/protocol: added options for fully and partially concealed hand scores. Added (deprecated) option to allow losers to score pure and concealed doubles. All Honours and All Terminals now also get the All Majors score. Protocol version now 1032. Server: prints OK: socket on startup (or FAILED: if it fails); used by xmj. Some minor code tidies. Graphical client: on new game panel, allow specification of robot names. Include preferences for default robot names. Add keyboard accelerators for selecting tiles and all menus. Be more intelligent about starting server. Don't animate tiles if the main window is unmapped or obscured. Add option to iconify all subsidiary windows along with main. Various small fixes. Player: add --name option. 1.2.3 2001-09-06 Bugfix. Gui: fix stupid bug introduced in last release causing crash after about five hands. 1.2.2 2001-09-04 Feature enhancements. Graphical client: add Tileset and Tileset search path options. Allow display option changes to take immediate effect. 1.2.1 2001-08-16 Bugfix release. Server: fixed rare but major bug in dead wall handling. Fixed minor scoring bug (filling the only place). 1.2 2001-08-06 Feature enhancement. Rules/protocol: added file name field to SaveState cmsg. Added Comment cmsg. Added Wall cmsg. Protocol version now 1030. Server: handle new options. Add --load-game and --save-on-exit options. Implemented game saving and restoring. Graphical client: added Save and Resume commands. Changed some menu and menu option names. Code: some tidying in addition to new features. 1.1 2001-05-18 Major feature enhancements. Rules/protocol: added a "nat" (unsigned in) game option type. Added game options: Limits on score, and no-limit games. Seven Pair hands. Flowers/Seasons optional. Scores for own Flower etc. changeable. Dead Wall may be per Millington, per everybody else, or not used. Base score for Mah Jong changeable. Protocol version now 1020. Server: handle new game options. Make DeadWall16 unset by default. Added --wallfile option (testing only). Added --option-file option. Make first human to connect be the game manager. Graphical client: added an Options menu, giving access to: display options: dialog position, animation, tiletips, wall showing. game option preferences: options applied to all games. current game options: control over currently connected game. Uses an rc file to store preferences. Added tiletips facility. When not showing wall, say how many live tiles left. Handle new game options, such as no flowers. Eliminated flickering problem when the wall is shown. Suggests donation after ten full games. Player: complete re-write of strategy engine. Now plays much more strongly in offensive play. Still has only rudimentary defence. New strategy control options, but still undocumented. Probably stable enough to document: next release, perhaps. Handles the new game options (though never tries to get seven pairs). Code: in addition to all the code required for new features, there has been a large amount of tidying up. Still not enough... 1.0.4 2001-04-05 Bugfix etc: Server: bug in scoring: was only giving 2 pts for fishing a major pair (despite saying 4 in the messsage!). Graphical client: default to a smaller size on 800x600 displays. Add an icon to the Windows version. 1.0.3 2001-02-25 Bugfix release. Graphical client: the --dialogs-below option has been broken since the pre-release (and nobody's noticed). Now fixed. 1.0.2 2001-02-10 Bugfix release. Server: error in handling kongs declared while declaring specials: it wasn't drawing a replacement tile for the player. Also failing to handle properly the (vanishingly unlikely) case of such a kong being robbed. In addition, the Heaven's/Earth's Blessing hands were being incorrectly detected, and would not be granted if a kong had been declared before start of play. Added a delay that may help with a Windows problem, when the client crashes at end of game. Added a --seed option for the random number generator. Graphical client: now copes correctly with robbing a kong declared before start of play. 1.0.1 2001-01-28 Bugfix patch: Server: rare but fatal and annoying bug fixed: when a player makes a dangerous discard and has no choice but to do so, the server would abort. Fixed by making the appropriate macro in game.h return a bool; also made encode_cmsg more liberal about what it accepts, to prevent further such bugs. 1.0 2001-01-22 First full release. Changes since the pre-release: Rules: the rules now match the rest of the world in that konging and declaring flowers is allowed only after drawing from the wall. The protocol version has been incremented to 1010 to mark this change. (The old rule will still apply if playing with old clients.) Robbing a closed kong to complete Thirteen Unique Wonders is now implemented. The default score for bouquets has been cut back to one double. One or two outright bugs fixed (handling of Thirteen Wonders failed). Server: several bugs fixed (in ptic, letting off cannons was buggy). --exit-on-disconnect option added. --no-special-scores option added. --randomseats option changed to --random-seats. Player: several bugs fixed, and many (not very successful) attempts to improve the tactics. Graphical client: many bug fixes. Added "About" window. Added "New Game..." entry to "Connect" menu, to start a game with a local server and clients. The scoring window is now popped up at the end of scoring, rather than the beginning. A message is displayed while waiting for a suspended game to restart. Code: lots of restructuring, not least to accommodate Windows. Stopped using imake, just use a GNU Makefile instead. 1.0beta-pre1 2000-12-07 Pre-release of first public source release. mj-1.17-src/vlazyfixed.c0000444006717300001440000001206515002771311013655 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/vlazyfixed.c,v 12.1 2020/05/30 17:29:26 jcb Exp $ * vlazyfixed.c * Another slight variation on the GTK+ fixed widget. * It doesn't call queue_resize when children are removed, added, * or move, EXCEPT when the first child is added. * This has the consequence that move DOESN'T work, unles you * explicitly call resize. But that's OK, since we only use this * in a situation where we can physically move the child window. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2001 by J. C. Bradfield. * * This file may be used under the terms of the * * GNU Lesser General Public License (any version). * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include "vlazyfixed.h" static void vlazy_fixed_class_init (GtkFixedClass *klass); static void vlazy_fixed_init (VlazyFixed *widget); static void vlazy_fixed_remove (GtkContainer *container, GtkWidget *widget); static void vlazy_fixed_add (GtkContainer *container, GtkWidget *widget); static GtkFixedClass *parent_class = NULL; GtkType vlazy_fixed_get_type (void) { static GtkType fixed_type = 0; if (!fixed_type) { static const GtkTypeInfo fixed_info = { "VlazyFixed", sizeof (VlazyFixed), sizeof (VlazyFixedClass), (GtkClassInitFunc) vlazy_fixed_class_init, (GtkObjectInitFunc) vlazy_fixed_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc) NULL, }; fixed_type = gtk_type_unique (GTK_TYPE_FIXED, &fixed_info); } return fixed_type; } static void vlazy_fixed_class_init (GtkFixedClass *class) { // GtkObjectClass *object_class; // GtkWidgetClass *widget_class; GtkContainerClass *container_class; // object_class = (GtkObjectClass*) class; // widget_class = (GtkWidgetClass*) class; container_class = (GtkContainerClass*) class; parent_class = gtk_type_class (GTK_TYPE_FIXED); /* override the remove, put and move method */ container_class->remove = vlazy_fixed_remove; container_class->add = vlazy_fixed_add; } static void vlazy_fixed_init(VlazyFixed *w UNUSED) { } GtkWidget* vlazy_fixed_new (void) { GtkFixed *fixed; fixed = gtk_type_new (vlazy_fixed_get_type()); return GTK_WIDGET (fixed); } static void vlazy_fixed_add (GtkContainer *container, GtkWidget *widget) { g_return_if_fail (container != NULL); g_return_if_fail (IS_VLAZY_FIXED (container)); g_return_if_fail (widget != NULL); vlazy_fixed_put (VLAZY_FIXED (container), widget, 0, 0); } static void vlazy_fixed_remove (GtkContainer *container, GtkWidget *widget) { GtkFixed *fixed; GtkFixedChild *child; GList *children; g_return_if_fail (container != NULL); g_return_if_fail (IS_VLAZY_FIXED (container)); g_return_if_fail (widget != NULL); fixed = GTK_FIXED (container); children = fixed->children; while (children) { child = children->data; if (child->widget == widget) { gtk_widget_unparent (widget); fixed->children = g_list_remove_link (fixed->children, children); g_list_free (children); g_free (child); break; } children = children->next; } } void vlazy_fixed_put (VlazyFixed *vlfixed, GtkWidget *widget, gint16 x, gint16 y) { GtkFixed *fixed; GtkFixedChild *child_info; g_return_if_fail (vlfixed != NULL); g_return_if_fail (IS_VLAZY_FIXED (vlfixed)); g_return_if_fail (widget != NULL); fixed = GTK_FIXED(vlfixed); child_info = g_new (GtkFixedChild, 1); child_info->widget = widget; child_info->x = x; child_info->y = y; gtk_widget_set_parent (widget, GTK_WIDGET (fixed)); fixed->children = g_list_append (fixed->children, child_info); if (GTK_WIDGET_REALIZED (fixed)) gtk_widget_realize (widget); if (GTK_WIDGET_VISIBLE (fixed) && GTK_WIDGET_VISIBLE (widget)) { if (GTK_WIDGET_MAPPED (fixed)) gtk_widget_map (widget); if ( ! fixed->children->prev ) gtk_widget_queue_resize (GTK_WIDGET (fixed)); } } void vlazy_fixed_move (VlazyFixed *vlfixed, GtkWidget *widget, gint16 x, gint16 y) { GtkFixedChild *child; GtkFixed *fixed; GList *children; g_return_if_fail (vlfixed != NULL); g_return_if_fail (IS_VLAZY_FIXED (vlfixed)); g_return_if_fail (widget != NULL); fixed = GTK_FIXED(vlfixed); children = fixed->children; while (children) { child = children->data; children = children->next; if (child->widget == widget) { child->x = x; child->y = y; break; } } } mj-1.17-src/greedy.c0000444006717300001440000014231415002771311012750 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/greedy.c,v 12.4 2025/04/25 20:37:52 jcb Exp $ * greedy.c * This is a computer player. Currently offensive only. * Options not documented. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ static int debugeval = 0; #include #include #include #include "client.h" #include "sysdep.h" static const char rcs_id[] UNUSED = "$Header: /home/jcb/MahJong/newmj/RCS/greedy.c,v 12.4 2025/04/25 20:37:52 jcb Exp $"; #include "version.h" static double normal_random_double(double sigma); static double magic[28+13] = { 12, 2, 1, 0.15, 1, 4, 4.97465626805558, 4, 1, 0.00638363133042069, 0, 1, 0.025, 0.4, 0.34, 0.4, 0.033333333, 0.3, 2, 25, 2, 25, 90.2519293502827, 3, 0.99, 1, -0.0011160627477312, 0.25, 6.0, 2.0, 25.0, 1.0, 12.0, 0.5, 3.0, 5.0, 0.5, 0.5, 0.5, 0.5, 0.5 }; static double *magic2 = &magic[28]; /* is this tile a doubling tile (or scoring pair) */ #define is_doubler(t) (is_dragon(t) || (is_wind(t) && \ (value_of(t) == our_player->wind || value_of(t) == the_game->round))) Game *the_game; int our_id = 0; PlayerP our_player; seats our_seat; static char *password = NULL; static FILE *debugf; /* New style strategy */ typedef struct { double chowness; /* how much do we like/dislike chows? From -1.0 (no chows) to +1.0 (no pungs) */ double hiddenness; /* how much do we want to keep things concealed? From 0.0 (don't care) to 1.0 (absolutely no claims) */ double majorness; /* are we trying to get all majors? From 0.0 (don't care) to 1.0 (discard all minors) */ double suitness; /* are we trying to collect one suit? From 0.0 to 1.0 */ TileSuit suit; /* if so, which? */ } strategy; /* values to try out */ typedef enum { chowness, hiddenness, majorness, suitness } stratparamtype; struct { int ncomps; double values[5]; } stratparams[suitness+1] = { { 3, { 0.0, -0.9, 0.5 } } , /* chowness */ { 2, { 0.0, 0.8 } } , /* hiddenness */ { 1, { 0.0 } } , /* majorness */ { 2, { 0.0, 0.5 } } /* suitness */ } ; /* routine to parse a comma separated list of floats into the given strat param. Return num of comps, or -1 on error */ static int parsefloatlist(const char *fl, stratparamtype spt) { char *start, *end; int i; if ( ! fl ) { warn("null arg to parsefloatlist"); return -1; } start = (char *)fl; i = 0; while ( i < 5 ) { /* it is a feature that this will return zero on an empty string, since we shd have at least one value */ stratparams[spt].values[i] = strtod(start,&end); i++; start = end; if ( ! *start ) break; if ( *start == ',' ) start++; } if ( *start ) warn("too many components, ignoring excess"); stratparams[spt].ncomps = i; return i; } static strategy curstrat; /* These are names for computed strategy values. See the awful mess below... */ enum {pungbase,pairbase,chowbase,seqbase,sglbase, partpung,partchow,exposedpungpenalty,exposedchowpenalty, suitfactor, mjbonus, kongbonus,weight}; /* the value of a new strategy must exceed the current by this amount for it to be chosen */ static double hysteresis = 4.0; /* standard deviation of noise added to eval values to reduce skill */ static double randomness = 0.0; /* Used to note availability of tiles */ static int tilesleft[MaxTile]; /* track discards of player to right */ static int rightdiscs[MaxTile]; /* disc_ser of last of each tile discarded */ static int strategy_chosen; static int despatch_line(char *line); static void do_something(void); static void check_discard(PlayerP p,strategy *strat,int closed_kong); static Tile decide_discard(PlayerP p, double *score, strategy *newstrat); static void update_tilesleft(CMsgUnion *m); static void maybe_switch_strategy(strategy *strat); static double eval(Tile *tp, strategy *strat, double *stratpoints,int reclevel, int *ninc, int *npr, double *breadth); static double evalhand(PlayerP p, strategy *strat); static int chances_to_win(PlayerP p); /* copy old tile array into new */ #define tcopy(new,old) memcpy((void *)new,(void *)old,(MAX_CONCEALED+1)*sizeof(Tile)) /* Convenience function */ #define send_packet(m) client_send_packet(the_game,(PMsgMsg *)m) static void usage(char *pname,char *msg) { fprintf(stderr,"%s: %s\nUsage: %s [ --id N ] [ --name NAME ] [ --password PASSWORD ] [ --server ADDR ]\n\ [ strategy options ]\n", pname,msg,pname); exit(1); } int main(int argc, char *argv[]) { char buf[1000]; char *l; int i; char *evalh = NULL ; /* just evaluate hand with default strategy and all debugging, and exit */ char *address = ":5000"; char *name = NULL; debugf = stderr; /* options. I should use getopt ... */ for (i=1;ifd); if ( ! l ) { exit(1); } despatch_line(l); } } /* despatch_line: this is the mega-switch which deals with the input from the controller */ static int despatch_line(char *line) { CMsgMsg *cm; if ( line == NULL ) { warn("receive error on controller connexion\n"); exit(1); } cm = decode_cmsg(line); if ( cm == NULL ) { warn("Protocol error on controller connexion; ignoring\n"); return 0; } update_tilesleft((CMsgUnion *)cm); switch ( cm->type ) { case CMsgError: break; /* damn all we can do */ case CMsgGameOver: exit(0); case CMsgReconnect: /* we should never receive this */ warn("Received Reconnect command\n"); exit(1); case CMsgInfoTiles: /* We ignore these. */ break; case CMsgCanMahJong: /* Currently we ignore these, as we don't issue queries */ break; case CMsgConnectReply: game_handle_cmsg(the_game,cm); our_id = the_game->players[0]->id; our_player = the_game->players[0]; break; case CMsgAuthReqd: { CMsgAuthReqdMsg *carm = (CMsgAuthReqdMsg *)cm; PMsgAuthInfoMsg paim; paim.type = PMsgAuthInfo; if ( strncmp(carm->authtype,"basic",16) != 0 ) { warn("Asked for unknown authorization type %.16s",carm->authtype); exit(1); } strcpy(paim.authtype,"basic"); if ( password == NULL ) { warn("Asked for password, don't have it"); exit(1); } paim.authdata = password; send_packet(&paim); } break; /* In these cases, our seat might have changed, so we need to calculate it */ case CMsgGame: /* In this case, we need to ask for the game options */ { PMsgListGameOptionsMsg plgom; plgom.type = PMsgListGameOptions; plgom.include_disabled = 0; send_packet(&plgom); } /* and then ... */ // fall through case CMsgNewRound: case CMsgNewHand: game_handle_cmsg(the_game,cm); our_seat = game_id_to_seat(the_game,our_id); if ( debugeval ) fprintf(debugf,"New hand\n"); /* reset strategy to default */ curstrat.chowness = stratparams[chowness].values[0]; curstrat.hiddenness = stratparams[hiddenness].values[0]; curstrat.majorness = stratparams[majorness].values[0]; curstrat.suitness = stratparams[suitness].values[0]; curstrat.suit = 0; break; /* in all these cases, game_handle_cmsg does all the work we want */ case CMsgPlayer: case CMsgStopPlay: case CMsgClaimDenied: case CMsgPlayerDoesntClaim: case CMsgPlayerClaimsPung: case CMsgPlayerClaimsKong: case CMsgPlayerClaimsChow: case CMsgPlayerClaimsMahJong: case CMsgPlayerShowsTiles: case CMsgDangerousDiscard: case CMsgGameOption: case CMsgChangeManager: case CMsgWall: case CMsgComment: case CMsgStateSaved: case CMsgMessage: game_handle_cmsg(the_game,cm); break; case CMsgHandScore: /* if that was the winner, we should start scoring our hand */ if ( ( game_handle_cmsg(the_game,cm) == the_game->players[the_game->player]->id) && the_game->active ) do_something(); break; /* after a Settlement or Washout message, do something: start next hand */ case CMsgWashOut: case CMsgSettlement: game_handle_cmsg(the_game,cm); if ( the_game->active ) do_something(); break; /* likewise after a washout */ /* after a MahJong message, we should do something: namely start making our scoring sets. */ case CMsgPlayerRobsKong: case CMsgPlayerMahJongs: game_handle_cmsg(the_game,cm); if ( the_game->active ) do_something(); break; /* in the case of a PlayerDeclaresSpecials message, we need to do something if it is now our turn; but this isn't given by the affected id. However, if the state is Discarding, and no tiles have so far been discarded, we shouldn't do something now, since we are about to be asked to pause. */ case CMsgPlayerDeclaresSpecial: game_handle_cmsg(the_game,cm); if ( the_game->player == our_seat && the_game->active && ! ( the_game->state == Discarding && the_game->serial == 0 )) do_something(); break; /* in these cases, we need to do something if the message is addressed to us. */ case CMsgPlayerDraws: case CMsgPlayerDrawsLoose: case CMsgPlayerPungs: case CMsgPlayerKongs: case CMsgPlayerChows: case CMsgPlayerFormsClosedPung: case CMsgPlayerFormsClosedChow: case CMsgPlayerPairs: case CMsgPlayerFormsClosedPair: case CMsgPlayerSpecialSet: case CMsgPlayerFormsClosedSpecialSet: case CMsgSwapTile: if ( game_handle_cmsg(the_game,cm) == our_id && the_game->active) do_something(); break; /* in this case, we need to do something else if it's not our turn! */ case CMsgPlayerDiscards: if ( game_handle_cmsg(the_game,cm) != our_id && the_game->active) check_discard(our_player,&curstrat,0); break; /* if this is us, we need to do something, and if it's somebody else, we might be able to rob the kong */ case CMsgPlayerDeclaresClosedKong: if ( game_handle_cmsg(the_game,cm) == our_id && the_game->active) do_something(); else if ( the_game->active ) check_discard(our_player,&curstrat,1); /* actually this checks the kong */ break; case CMsgPlayerAddsToPung: if ( game_handle_cmsg(the_game,cm) == our_id && the_game->active) do_something(); else if ( the_game->active ) check_discard(our_player,&curstrat,0); /* actually this checks the kong */ break; /* In this case, it depends on the state of the game */ case CMsgStartPlay: /* We need to do something if the id is us, or 0. */ { int id; id = game_handle_cmsg(the_game,cm); if ( id == our_id || id == 0 ) do_something(); } break; /* similarly */ case CMsgPlayerReady: game_handle_cmsg(the_game,cm); if ( ! the_game->paused ) do_something(); break; case CMsgPause: game_handle_cmsg(the_game,cm); do_something(); break; case CMsgPlayerOptionSet: /* we don't recognize any options, so ignore it */ break; case CMsgRedirect: warn("Redirect command not currently supported"); exit(1); } return 1; } /* do something when it's our turn. */ static void do_something(void) { int i; MJSpecialHandFlags mjspecflags; mjspecflags = 0; if ( game_get_option_value(the_game,GOSevenPairs,NULL).optbool ) mjspecflags |= MJSevenPairs; /* if the game is paused, and we haven't said we're ready, say so */ if ( the_game->paused ) { if ( !the_game->ready[our_seat] ) { PMsgReadyMsg pm; pm.type = PMsgReady; send_packet(&pm); } return; } /* if the game state is handcomplete, do nothing */ if ( the_game->state == HandComplete ) return; /* If the game state is discarded, then it must mean this has been called in response to a StartPlay message after resuming an old hand. So actually we want to check the discard, unless of course we are the discarder, or we have already claimed. */ if ( the_game->state == Discarded ) { if ( the_game->player != our_seat && the_game->claims[our_seat] == UnknownClaim ) check_discard(our_player,&curstrat,0); return; } /* If the game state is Dealing, then we should not do anything. */ if ( the_game->state == Dealing ) return; /* if we're called in declaring specials or discarding, but it's not our turn, do nothing */ if ( (the_game->state == DeclaringSpecials || the_game->state == Discarding) && the_game->player != our_seat ) return; /* if we're waiting to draw another tile, do nothing */ if ( the_game->needs != FromNone ) return; /* if we have a special, declare it. N.B. we'll be called again as a result of this, so only look for first. */ for ( i=0; i < our_player->num_concealed && ! is_special(our_player->concealed[i]) ; i++); if ( i < our_player->num_concealed ) { PMsgDeclareSpecialMsg m; m.type = PMsgDeclareSpecial; m.tile = our_player->concealed[i]; send_packet(&m); return; } /* OK, no specials */ if ( the_game->state == DeclaringSpecials ) { PMsgDeclareSpecialMsg m; m.type = PMsgDeclareSpecial; m.tile = HiddenTile; send_packet(&m); /* and at this point, we should decide our strategy */ maybe_switch_strategy(&curstrat); return; } /* if the game is in the mahjonging state, and our hand is not declared, then we should declare a set. */ if ( the_game->state == MahJonging ) { TileSet *tsp; PMsgUnion m; if ( pflag(our_player,HandDeclared) ) return; /* as courtesy, if we're not the winner, we shouldn't score until the winner has */ if ( our_seat != the_game->player && ! pflag(the_game->players[the_game->player],HandDeclared) ) return; /* get the list of possible decls */ tsp = client_find_sets(our_player, (the_game->player == our_seat && the_game->mjpending) ? the_game->tile : HiddenTile, the_game->player == our_seat, (PlayerP *)0,mjspecflags); if ( !tsp && our_player->num_concealed > 0 ) { m.type = PMsgShowTiles; send_packet(&m); return; } /* just do the first one */ switch ( tsp->type ) { case Kong: /* we can't declare a kong now, so declare the pung instead */ case Pung: m.type = PMsgPung; m.pung.discard = 0; break; case Chow: m.type = PMsgChow; m.chow.discard = 0; m.chow.cpos = the_game->tile - tsp->tile; break; case Pair: m.type = PMsgPair; break; case ClosedPung: m.type = PMsgFormClosedPung; m.formclosedpung.tile = tsp->tile; break; case ClosedChow: m.type = PMsgFormClosedChow; m.formclosedchow.tile = tsp->tile; break; case ClosedPair: m.type = PMsgFormClosedPair; m.formclosedpair.tile = tsp->tile; break; case Empty: /* can't happen, just to suppress warning */ case ClosedKong: /* ditto */ ; } send_packet(&m); return; } /* if we can declare MahJong, do it */ if ( player_can_mah_jong(our_player,HiddenTile,mjspecflags) ) { PMsgMahJongMsg m; m.type = PMsgMahJong; m.discard = 0; send_packet(&m); return; } else if ( the_game->whence != FromDiscard ) { /* check for concealed kongs and melded kongs. Just declare them. */ int i; double val; Player pc; val = evalhand(our_player,&curstrat); /* a side effect of the above call is that our concealed tiles are sorted (in reverse order), so we can avoid duplicating effort */ for (i=0;inum_concealed;i++) { /* don't look at same tile twice */ if ( i && our_player->concealed[i] == our_player->concealed[i-1] ) continue; if ( player_can_declare_closed_kong(our_player,our_player->concealed[i]) ) { PMsgDeclareClosedKongMsg m; copy_player(&pc,our_player); player_declares_closed_kong(&pc,our_player->concealed[i]); if ( evalhand(&pc,&curstrat) > val ) { m.type = PMsgDeclareClosedKong; m.tile = our_player->concealed[i]; send_packet(&m); return; } } } /* Now check for pungs we can meld to */ for (i=0;itilesets[i].type == Pung && player_can_add_to_pung(our_player,our_player->tilesets[i].tile) ) { PMsgAddToPungMsg m; copy_player(&pc,our_player); player_adds_to_pung(&pc,our_player->tilesets[i].tile); if ( evalhand(&pc,&curstrat) > val ) { m.type = PMsgAddToPung; m.tile = our_player->tilesets[i].tile; send_packet(&m); return; } } } } /* if we get here, we have to discard */ { PMsgDiscardMsg m; Tile t; /* strategy switching only after drawing tile from wall */ if ( the_game->whence != FromDiscard || !strategy_chosen) { maybe_switch_strategy(&curstrat); } t = decide_discard(our_player,NULL,&curstrat); m.type = PMsgDiscard; m.tile = t; m.calling = 0; /* we don't bother looking for original call */ send_packet(&m); return; } } /* Check if we want the discard, and claim it. Arg is strategy. Also called to check whether a kong can be robbed - the last arg says if being called on a closed kong */ static void check_discard(PlayerP p, strategy *strat,int closedkong) { PMsgUnion m; double bestval,val; int canmj; char buf[100]; MJSpecialHandFlags mjspecflags; mjspecflags = 0; if ( game_get_option_value(the_game,GOSevenPairs,NULL).optbool ) mjspecflags |= MJSevenPairs; if ( the_game->state == Discarding || the_game->state == DeclaringSpecials ) { /* this means we're being called to check whether a kong can be robbed. Since robbing a kong gets us an extra double, this is probably always worth doing, unless we're trying for some limit hand */ if ( player_can_mah_jong(p,the_game->tile,mjspecflags) && (!closedkong || player_can_thirteen_wonders(p,the_game->tile)) ) { m.type = PMsgMahJong; m.mahjong.discard = the_game->serial; } else { m.type = PMsgNoClaim; m.noclaim.discard = the_game->serial; } send_packet(&m); return; } if ( debugeval ) { player_print_tiles(buf,p,0); fprintf(debugf,"Hand: %s ; discard %s\n",buf,tile_code(the_game->tile)); } bestval = evalhand(p,strat); if ( debugeval ) { fprintf(debugf,"Hand value before claim %.3f\n",bestval); } canmj = player_can_mah_jong(p,the_game->tile,mjspecflags); m.type = PMsgNoClaim; m.noclaim.discard = the_game->serial; if ( player_can_kong(p,the_game->tile) ) { Player pc; copy_player(&pc,p); player_kongs(&pc,the_game->tile); val = evalhand(&pc,strat); /* we won't discard a tile here */ if ( debugeval ) { fprintf(debugf,"Hand after kong %.3f\n",val); } if ( val > bestval ) { m.type = PMsgKong; m.kong.discard = the_game->serial; bestval = val; } else if ( debugeval ) { fprintf(debugf,"Chose not to kong\n"); } } if ( player_can_pung(p,the_game->tile) ) { Player pc; copy_player(&pc,p); player_pungs(&pc,the_game->tile); decide_discard(&pc,&val,&curstrat); if ( debugeval ) { fprintf(debugf,"Hand after pung %.3f\n",val); } if ( val > bestval ) { m.type = PMsgPung; m.pung.discard = the_game->serial; bestval = val; } else if ( debugeval ) { fprintf(debugf,"Chose not to pung\n"); } } if ( (canmj || our_seat == (the_game->player+1)%NUM_SEATS) && is_suit(the_game->tile) ) { ChowPosition cpos = (unsigned) -1; Player pc; int chowposs = 0; Tile t = the_game->tile; copy_player(&pc,p); if ( player_chows(&pc,t,Lower) ) { decide_discard(&pc,&val,&curstrat); if ( debugeval ) { fprintf(debugf,"Hand after lower chow: %.3f\n",val); } chowposs = 1; if ( val > bestval ) { bestval = val; cpos = Lower; } copy_player(&pc,p); } if ( player_chows(&pc,t,Middle) ) { decide_discard(&pc,&val,&curstrat); if ( debugeval ) { fprintf(debugf,"Hand after middle chow: %.3f\n",val); } chowposs = 1; if ( val > bestval ) { bestval = val; cpos = Middle; } copy_player(&pc,p); } if ( player_chows(&pc,t,Upper) ) { chowposs = 1; decide_discard(&pc,&val,&curstrat); if ( debugeval ) { fprintf(debugf,"Hand after upper chow: %.3f\n",val); } if ( val > bestval ) { bestval = val; cpos = Upper; } copy_player(&pc,p); } if ( cpos != (unsigned)-1 ) { m.type = PMsgChow; m.chow.discard = the_game->serial; m.chow.cpos = cpos; } else if ( debugeval ) { if ( chowposs ) fprintf(debugf,"chose not to chow\n"); } } /* mah jong */ if ( canmj ) { m.type = PMsgMahJong; m.mahjong.discard = the_game->serial; #if 1 /* if we're following a concealed strategy, and we still have four chances (ex?cluding this one) of going out, then don't claim */ /* instead of four chances, make it depend on number of tiles left */ /* test: if hidden >1, then never claim */ if ( (strat->hiddenness > 1.0) || (strat->hiddenness*chances_to_win(p) * (the_game->wall.live_end-the_game->wall.live_used) / (the_game->wall.dead_end-the_game->wall.live_used) >= 1.5) ) { m.type = PMsgNoClaim; m.noclaim.discard = the_game->serial; } #endif if ( m.type != PMsgNoClaim ) { if ( debugeval && strat->hiddenness > 0.0 ) fprintf(debugf,"claiming mahjong on hidden strategy\n"); m.type = PMsgMahJong; m.mahjong.discard = the_game->serial; } else { if ( debugeval ) { fprintf(debugf,"CHOSE NOT TO MAHJONG\n"); } } } if ( debugeval ) { fprintf(debugf,"Result: %s",encode_pmsg((PMsgMsg *)&m)); } send_packet(&m); return; } /* Here is a data structure to track the number of (assumed) available tiles elsewhere. The update fn should be called on every CMsg. */ static void update_tilesleft(CMsgUnion *m) { int i; /* note that we don't need to check whether the tile is blank, since the HiddenTile entry of tilesleft has no meaning. */ switch ( m->type ) { case CMsgNewHand: for (i=0; i < MaxTile; i++) { tilesleft[i] = 4; rightdiscs[i] = 0; } return; case CMsgPlayerDeclaresSpecial: return; case CMsgPlayerDraws: tilesleft[m->playerdraws.tile]--; return; case CMsgPlayerDrawsLoose: tilesleft[m->playerdrawsloose.tile]--; return; case CMsgPlayerDiscards: /* if this is us, we've already counted it */ if ( m->playerdiscards.id != our_id ) tilesleft[m->playerdiscards.tile]--; if ( game_id_to_seat(the_game,m->playerdiscards.id) == (our_seat+1)%4 ) rightdiscs[m->playerdiscards.tile] = m->playerdiscards.discard; return; case CMsgPlayerPungs: /* if somebody else pungs, then two more tiles become dead (the discard was already noted). If we pung, nothing new is known */ if ( m->playerpungs.id != our_id ) tilesleft[m->playerpungs.tile] -= 2; return; case CMsgPlayerKongs: /* if somebody else kongs, then three more tiles become dead (the discard was already noted). */ if ( m->playerkongs.id != our_id ) tilesleft[m->playerkongs.tile] -= 3; return; case CMsgPlayerDeclaresClosedKong: if ( m->playerdeclaresclosedkong.id != our_id ) tilesleft[m->playerdeclaresclosedkong.tile] -= 4; return; case CMsgPlayerAddsToPung: if ( m->playeraddstopung.id != our_id ) tilesleft[m->playeraddstopung.tile]--; return; case CMsgPlayerChows: if ( m->playerchows.id != our_id ) { Tile t = m->playerchows.tile; ChowPosition c = m->playerchows.cpos; tilesleft[(c==Lower)?t+1:(c==Middle)?t-1:t-2]--; tilesleft[(c==Lower)?t+2:(c==Middle)?t+1:t-1]--; } return; case CMsgSwapTile: tilesleft[m->swaptile.oldtile]++; tilesleft[m->swaptile.newtile]--; return; default: return; } } /* This sorts the tiles (as below) into reverse order. Why reverse order? So that HiddenTile terminates the hand. */ static void tsort(Tile *tp) { int i,m; Tile t; /* bubblesort */ m = 1; while ( m ) { m = 0; for (i=0; i 1 ) { fprintf(debugf,"\n"); if (reclevel) sprintf(&totbuf[11*(reclevel-1)+4],"\n"); } /* if there is one "incomplete" set and one pair, we have a complete hand */ #if 0 if ( ninc == 1 && npr == 1 ) val = stratpoints[mjbonus]; else val = 0.0; #else val = 0.0; #endif if ( debugeval > 1 ) { sprintf(tb,"%4.1f",val); strncpy(&totbuf[11*reclevel],tb,4); } return val; } /* does it pung? */ ppr = 0; if ( tp[1] == tp[0] ) { if ( tp[2] == tp[0] ) { val = 0.0; /* add the value of this set. */ val += stratpoints[pungbase]; /* if we're trying for a no score hand, scoring pairs are bad */ if ( is_doubler(tp[0]) ) val += (-strat->chowness) * magic2[0]; if ( is_major(tp[0]) ) { val += magic2[1]; } else { val -= magic2[2]*strat->majorness; } if ( is_suit(tp[0]) && suit_of(tp[0]) != strat->suit ) val *= stratpoints[suitfactor]; if ( debugeval > 1 ) { fprintf(debugf,"%sPu%s:%4.1f+ ",notfirst++?prefix:"",tile_code(tp[0]),val); } /* remove the tiles and recurse */ tcopy(copy,tp); copy[0] = copy[1] = copy[2] = HiddenTile; valr = eval(copy,strat,stratpoints,reclevel+1,ninc,&ppr,breadth); if ( debugeval > 1 ) { sprintf(tb,"%4.1f",val); strncpy(&totbuf[11*reclevel],tb,4); } val += valr; if ( val > pval ) { pval = val; pvalr = valr ; } } else { val = 0.0; /* A pair is worth something for itself, plus something for its chances of becoming a pung. A doubler pair is worth an extra 2 points. A major pair is worth an extra point. Beware the risk of arranging effect that a chow and a pair is worth much more than a pung and an inner sequence, so that we'd break a pung rather than a chow. */ ppr++; val += stratpoints[partpung]*stratpoints[pungbase] * tilesleft[tp[0]]; /* doublers are bad for noscore */ if ( is_doubler(tp[0]) ) val += 2 - (strat->chowness)*4.0; if ( is_major(tp[0]) ) { val += magic2[3]; } else { val -= magic2[4]*strat->majorness; } if ( is_suit(tp[0]) && suit_of(tp[0]) != strat->suit ) val *= stratpoints[suitfactor]; if ( debugeval > 1 ) { fprintf(debugf,"%sPr%s:%4.1f+ ",notfirst++?prefix:"",tile_code(tp[0]),val); } /* remove the tiles and recurse */ tcopy(copy,tp); copy[0] = copy[1] = HiddenTile; (*ninc)++; valr = eval(copy,strat,stratpoints,reclevel+1,ninc,&ppr,breadth); val += valr; if ( debugeval > 1 ) { sprintf(tb,"%4.1f",val); strncpy(&totbuf[11*reclevel],tb,4); } if ( val > pval ) { pval = val; pvalr = valr; } } } /* OK, now deal with chows. */ mpr = 0; if ( is_suit(tp[0]) ) { Tile t1,t2; int i1,i2; /* other tiles, and their indices */ /* NB tiles are in reverse order!!!!! */ t1 = ((value_of(tp[0]) > 1) ? tp[0]-1 : 0); t2 = ((value_of(tp[0]) > 2) ? tp[0]-2 : 0); for (i1=0;t1 && tp[i1] && tp[i1]!=t1;i1++); if ( ! tp[i1] ) i1=0; for (i2=0;t2 && tp[i2] && tp[i2]!=t2;i2++); if ( ! tp[i2] ) i2=0; /* if we have a chow */ if ( i1 && i2 ) { val = 0.0; /* A chow is deemed to be worth 12 points also. (Quick and dirty ... hike pungbonus to avoid chows.) */ val += stratpoints[chowbase]; if ( is_suit(tp[0]) && suit_of(tp[0]) != strat->suit ) val *= stratpoints[suitfactor]; if ( debugeval > 1 ) { if ( notfirst ) fprintf(debugf,"%s%s",prefix,&totbuf[11*reclevel]); fprintf(debugf,"%sCh%s:%4.1f+ ",notfirst++?prefix:"",tile_code(tp[0]),val); } /* remove the tiles and recurse */ tcopy(copy,tp); copy[0] = copy[i1] = copy[i2] = HiddenTile; valr = eval(copy,strat,stratpoints,reclevel+1,ninc,&mpr,breadth); val += valr; if ( debugeval > 1 ) { sprintf(tb,"%4.1f",val); strncpy(&totbuf[11*reclevel],tb,4); } if ( val > mval ) { mval = val; mvalr = valr; } } /* If we have an inner sequence... note that it's intentional that we do this as well, since maybe if we split the chow, we'll find a pung. But we' */ if ( i1 ) { val = 0.0; /* An inner sequence is worth the number of chances of completion, allowing for the fact that on average, the tiles in right and opposite and half the tiles in the wall will not be available for completing a chow. NOTE: this needs to change when we're nearly at MahJong. */ val += stratpoints[seqbase]; if ( value_of(tp[0]) < 9 ) { val += stratpoints[partchow]*stratpoints[chowbase] * tilesleft[tp[0]+1]; } if ( value_of(t1) > 1 ) { val += stratpoints[partchow]*stratpoints[chowbase] * tilesleft[t1-1]; } if ( is_suit(tp[0]) && suit_of(tp[0]) != strat->suit ) val *= stratpoints[suitfactor]; if ( debugeval > 1 ) { if ( notfirst ) fprintf(debugf,"%s%s",prefix,&totbuf[11*reclevel]); fprintf(debugf,"%sIn%s:%4.1f+ ",notfirst++?prefix:"",tile_code(tp[0]),val); } /* remove the tiles and recurse */ tcopy(copy,tp); copy[0] = copy[i1] = HiddenTile; valr = eval(copy,strat,stratpoints,reclevel+1,ninc,&mpr,breadth); val += valr; if ( debugeval > 1 ) { sprintf(tb,"%4.1f",val); strncpy(&totbuf[11*reclevel],tb,4); } if ( val > mval ) { mval = val; mvalr = valr; } } /* If we have a split sequence ... Here we don't do this if there's been a chow. Hmm, why not? I thought I had a reason. Fill it in sometime. */ else if ( i2 ) { val = 0.0; /* Likewise */ val += stratpoints[seqbase]; val += stratpoints[partchow]*stratpoints[chowbase] * tilesleft[t1]; if ( is_suit(tp[0]) && suit_of(tp[0]) != strat->suit ) val *= stratpoints[suitfactor]; if ( debugeval > 1 ) { if ( notfirst ) fprintf(debugf,"%s%s",prefix,&totbuf[11*reclevel]); fprintf(debugf,"%sOu%s:%4.1f+ ",notfirst++?prefix:"",tile_code(tp[0]),val); } /* remove the tiles and recurse */ tcopy(copy,tp); copy[0] = copy[i2] = HiddenTile; valr = eval(copy,strat,stratpoints,reclevel+1,ninc,&mpr,breadth); val += valr; if ( debugeval > 1 ) { sprintf(tb,"%4.1f",val); strncpy(&totbuf[11*reclevel],tb,4); } if ( val > mval ) { mval = val; mvalr = valr; } } } /* Finally, the score for a singleton. */ /* let's also add .25 times the number of neighbouring tiles */ spr = 0; val = 0.0; val += stratpoints[sglbase] * (tilesleft[tp[0]] + ( is_suit(tp[0]) ? (magic2[5]*strat->chowness*(tilesleft[tp[0]-1]+ tilesleft[tp[0]+1])) : 0)) * (magic2[6]-strat->chowness); if ( is_doubler(tp[0]) ) { if (tilesleft[tp[0]] == 0) val -= magic2[7]; /* completely useless */ else { if ( (magic2[8]-strat->chowness) > 0 ) val += (tilesleft[tp[0]])*stratpoints[sglbase]*(magic2[9]-strat->chowness); else val += (3-tilesleft[tp[0]])*stratpoints[sglbase]*(magic2[10]-strat->chowness); } } if ( is_suit(tp[0]) && suit_of(tp[0]) != strat->suit ) { /* val could be negative, in which case we don't want to shrink it. So just substract a constant */ val -= magic2[11]*strat->suitness*stratpoints[sglbase]; } if ( ! is_major(tp[0]) ) { val -= magic2[12]*strat->majorness*stratpoints[sglbase]; } if ( debugeval > 1 ) { if ( notfirst ) fprintf(debugf,"%s%s",prefix,&totbuf[11*reclevel]); fprintf(debugf,"%sSi%s:%4.1f+ ",notfirst++?prefix:"",tile_code(tp[0]),val); } tcopy(copy,tp); copy[0] = HiddenTile; valr = eval(copy,strat,stratpoints,reclevel+1,ninc,&spr,breadth); val += valr; if ( debugeval > 1 ) { sprintf(tb,"%4.1f",val); strncpy(&totbuf[11*reclevel],tb,4); } /* at this point, we have pair/seq/single based scores. */ if ( mval > pval ) { if ( val > mval ) { if ( spr ) (*npr)++; mval = val; } else { if ( mpr ) (*npr)++; } } else { if ( val > pval ) { mval = val; if ( spr ) (*npr)++; } else { mval = pval; if ( ppr ) (*npr)++; } } if ( debugeval > 1 ) { if ( reclevel ) { sprintf(tb,"(%4.1f) ",mval); strncpy(&totbuf[11*(reclevel-1)+4],tb,7); } else fputs(totbuf,debugf); } return mval; } /* This function uses the above to evaluate a player's hand, including the completed sets. */ static double evalhand(PlayerP p,strategy *strat) { Tile tcopy[MAX_CONCEALED+1]; double val,breadth; int i, npr, ninc; double stratpoints[weight+1]; if ( debugeval ) { char buf[100]; fprintf(debugf,"eval with strat params c=%.1f, h=%.1f, m=%.1f, s=%.1f (%d)\n", strat->chowness,strat->hiddenness,strat->majorness, strat->suitness,strat->suit); player_print_tiles(buf,p,0); fprintf(debugf,"Hand: %s\n",buf); } /* calculate old style strategy values from new ones */ stratpoints[pungbase] = magic[0] - ( strat->chowness > 0 ? strat->chowness * magic[0] : strat->chowness * magic[1] ); stratpoints[pungbase] *= (magic[2] + magic[3] * strat->hiddenness); stratpoints[pairbase] = magic[4]*(magic[5] + (magic[6]*fabs(strat->chowness))); stratpoints[chowbase] = magic[0] + ( strat->chowness > 0 ? strat->chowness * magic[7] : strat->chowness * magic[0] ); stratpoints[chowbase] *= (magic[8] + magic[9] * strat->hiddenness) * (1.0 - strat->majorness); stratpoints[seqbase] = magic[10]*(magic[11] + ( strat->chowness < 0 ? strat->chowness : 0)) * (1.0 - strat->majorness); stratpoints[sglbase] = magic[12]; stratpoints[partpung] = magic[13]*(magic[14])*(1-magic[15]*strat->hiddenness); stratpoints[partchow] = magic[16]*(1-magic[17]*strat->hiddenness) * (1.0 - strat->majorness); stratpoints[exposedpungpenalty] = magic[18] + strat->hiddenness * stratpoints[pungbase] + ( (strat->chowness > 0) ? magic[19] * strat->chowness : 0.0 ); stratpoints[exposedchowpenalty] = magic[20] + strat->hiddenness * stratpoints[chowbase] + ( (strat->chowness < 0) ? -1*magic[21] * strat->chowness : 0.0 ) + magic[21] * strat->majorness; stratpoints[mjbonus] = magic[22]; stratpoints[kongbonus] = magic[23]; stratpoints[suitfactor] = ((strat->suitness >= 1.0) ? 0.01 : (1.0 - magic[24]*strat->suitness)); stratpoints[weight] = magic[25] + (strat->suitness >= 1.0 ? magic[26]*(strat->suitness-1.0) : magic[26]*strat->suitness) + magic[27]*strat->majorness; for (i=0; inum_concealed; i++) tcopy[i] = p->concealed[i]; for ( ; i < MAX_CONCEALED+1; i++) tcopy[i] = HiddenTile; val = 0.0; ninc = npr = 0; /* number of "incomplete"/pairs in hand */ /* note that if we see any closed pungs in here, they are actually hacks representing hypothetical open sets */ for (i=0; i<5; i++) { double sval = 0.0; switch (p->tilesets[i].type) { case Chow: case ClosedChow: sval -= stratpoints[exposedchowpenalty]; sval += stratpoints[chowbase]; break; case ClosedKong: sval += stratpoints[exposedpungpenalty]; /* cancel the penalty later */ // fall through case Kong: sval += stratpoints[kongbonus]; // fall through case Pung: case ClosedPung: sval -= stratpoints[exposedpungpenalty]; sval += stratpoints[pungbase]; /* these shadow evalhand above */ if ( is_doubler(p->tilesets[i].tile) ) sval += (-strat->chowness) * 6.0; if ( is_major(p->tilesets[i].tile) ) { sval += 2.0 ; /* small bonus for luck */ } else { sval -= 25.0*strat->majorness; } break; case Pair: case ClosedPair: ninc = npr = 1; /* for correct evaluation of pairs */ sval += stratpoints[pairbase]; break; default: ; } if ( p->tilesets[i].type != Empty && is_suit(p->tilesets[i].tile) && suit_of(p->tilesets[i].tile) != strat->suit ) sval -= 10.0*strat->suitness; if ( debugeval > 1 ) { fprintf(debugf,"Set %s: %.1f\n",player_print_TileSetType(p->tilesets[i].type), sval); } val += sval; } breadth = 0.0; val += eval(tcopy,strat,stratpoints,-1,&ninc,&npr,&breadth); /* add the pairbase if we have at least one pair */ if ( npr > 0 ) { if ( debugeval > 1 ) fprintf(debugf,"+pairbase %.1f\n",stratpoints[pairbase]); val += stratpoints[pairbase]; } val *= stratpoints[weight]; val += normal_random_double(randomness); if ( debugeval ) fprintf(debugf,"Value %.2f\n",val); return val; } /* random double via box muller */ static double normal_random_double(double sigma) { if ( sigma == 0.0 ) return 0.0; static int second = 0; static double z1; if ( second ) { second = 0; return z1; } second = 1; double x = (double) #ifdef WIN32 rand #else random #endif () / RAND_MAX, y = (double) #ifdef WIN32 rand #else random #endif () / RAND_MAX, z = sqrt(-2 * log(x)) * cos(2 * M_PI * y); z1 = sqrt(-2 * log(x)) * sin(2 * M_PI * y); return sigma*z; } /* compute the number of ways a calling hand can go out */ static int chances_to_win(PlayerP p) { Tile t; int n; MJSpecialHandFlags mjspecflags; mjspecflags = 0; if ( game_get_option_value(the_game,GOSevenPairs,NULL).optbool ) mjspecflags |= MJSevenPairs; t = HiddenTile; n = 0; while ( (t = tile_iterate(t,0)) != HiddenTile ) { if ( tilesleft[t] && player_can_mah_jong(p,t,mjspecflags) ) n += tilesleft[t]; } return n; } /* compute tile to discard. Return value is tile. Second arg returns score of resulting hand. Third arg returns the new strategy. */ static Tile decide_discard(PlayerP p, double *score, strategy *strat) { /* how do we choose a tile to discard? remove the tile, and evaluate the remaining hand. Discard the tile giving the greatest residual value. */ int i,best; double values[MAX_CONCEALED+1]; Tile tilesa[MAX_CONCEALED+1],tiles[MAX_CONCEALED+1] UNUSED; char buf[80]; const Tile *t = p->concealed; for (i=0;inum_concealed;i++) tilesa[i] = t[i]; for (;inum_concealed;i++) { Player cp; if ( i && tilesa[i] == tilesa[i-1] ) continue; if ( debugeval ) { fprintf(debugf,"Trying %s: \n",tile_code(tilesa[i])); } copy_player(&cp,p); player_discards(&cp,tilesa[i]); values[i] = evalhand(&cp,strat); /* add a bonus for tiles recently discarded by the player to the right */ if ( rightdiscs[tilesa[i]] ) values[i] += 0.5; /* /(the_game->serial-rightdiscs[tilesa[i]]) */; if ( is_suit(tilesa[i]) && ((value_of(tilesa[i]) < 7 && rightdiscs[tilesa[i]+3]) || (value_of(tilesa[i]) > 3 && rightdiscs[tilesa[i]-3])) ) values[i] += 0.4; /* the 1-4-7 argument */ /* Best tile to discard leaves highest residual score */ if ( values[i] > values[best] ) best = i; if ( debugeval ) { fprintf(debugf,"Tile %s has value %.1f\n",tile_code(tilesa[i]),values[i]); } } if ( debugeval ) { fprintf(debugf,"Discarding %s\n",tile_code(tilesa[best])); } if ( score ) *score = values[best]; return tilesa[best]; } /* Maybe switch strategy: takes a strat pointer as argument, and updates it in place. */ static void maybe_switch_strategy(strategy *strat) { strategy tmpstrat; int i,j; double val,oval, bestval; strategy beststrat; int dofast; PlayerP p = our_player; oval = evalhand(p,strat); bestval = -1000.0; tmpstrat.chowness = stratparams[chowness].values[0]; tmpstrat.hiddenness = stratparams[hiddenness].values[0]; tmpstrat.majorness = stratparams[majorness].values[0]; /* it makes no sense to try to evaluate pung/chowness with a non-zero suitness, cos we don't know which suit, and evaluating with suit=0 is equiv to all honours! */ /* tmpstrat.suitness = stratparams[suitness].values[0]; */ tmpstrat.suitness = 0.0; tmpstrat.suit = 0; beststrat.chowness = tmpstrat.chowness; for ( i=0 ; i < stratparams[chowness].ncomps ; i++ ) { tmpstrat.chowness = stratparams[chowness].values[i]; val = evalhand(p,&tmpstrat); if ( val > bestval ) { bestval = val ; beststrat.chowness = tmpstrat.chowness; } } tmpstrat.chowness = beststrat.chowness; beststrat.hiddenness = tmpstrat.hiddenness; /* from i = 1, since we had [0] just above */ for ( i = 1 ; i < stratparams[hiddenness].ncomps ; i++ ) { tmpstrat.hiddenness = stratparams[hiddenness].values[i]; val = evalhand(p,&tmpstrat); if ( val > bestval ) { bestval = val ; beststrat.hiddenness = tmpstrat.hiddenness; } } /* and for suit */ tmpstrat.hiddenness = beststrat.hiddenness; /* now we need to reset the best val, since only now are we considering the values the user had */ bestval = -1000; for ( i = 0; i < stratparams[suitness].ncomps; i++ ) { tmpstrat.suitness = stratparams[suitness].values[i]; /* j = 0 corresponds to all honours, which we shd get if we can ?? */ for ( j = 0 ; j <= ((tmpstrat.suitness == 0.0)? 0 : 3) ; j++ ) { tmpstrat.suit = j; val = evalhand(p,&tmpstrat); if ( val > bestval ) { bestval = val ; beststrat = tmpstrat; } } } /* we'll now try for all majors... */ /* however, there's no point in doing this unless we already think that a pung-based strategy is best */ if ( beststrat.chowness < 0.0 ) { tmpstrat = beststrat; for ( i = 1; i < stratparams[majorness].ncomps; i++ ) { tmpstrat.majorness = stratparams[majorness].values[i]; val = evalhand(p,&tmpstrat); if ( val > bestval ) { bestval = val ; beststrat = tmpstrat; } } } /* if some other player has four sets declared, then switch to Fast */ dofast = 0; #if 0 for ( i = 0 ; i < NUM_SEATS ; i++ ) { PlayerP p1 = the_game->players[i]; if ( p1 == p ) continue; if ( p1->num_concealed <= 1 ) dofast = 1; } #endif if ( dofast ) { /* strat = Fast; */ if ( debugeval ) { fprintf(debugf,"Using fast\n"); } } else { if ( bestval >= oval + hysteresis ) { *strat = beststrat; if ( debugeval ) { static char buf[128]; fprintf(debugf,"Switching to strat c=%.1f h=%.1f s=%.1f (%d) m=%.1f\n", strat->chowness,strat->hiddenness, strat->suitness,strat->suit,strat->majorness); player_print_tiles(buf,p,0); fprintf(debugf," with hand %s\n",buf); } } } } mj-1.17-src/scoring.c0000444006717300001440000005703015002771311013135 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/scoring.c,v 12.2 2020/05/30 18:01:00 jcb Exp $ * scoring.c * This file contains the routines to handle the scoring * of hands. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include "tiles.h" #include "player.h" #include "controller.h" #include "scoring.h" #include "sysdep.h" static const char rcs_id[] UNUSED = "$Header: /home/jcb/MahJong/newmj/RCS/scoring.c,v 12.2 2020/05/30 18:01:00 jcb Exp $"; int no_special_scores = 0; /* disable scoring of flowers and seasons */ /* score_of_tileset: takes a TileSetP, and returns a score. * It needs to know the wind of this player, and the prevailing wind. It puts the left text, score and right text in the following ptrs; they are valid until the next call */ static char *tsltext,*tsrtext; static int tsscore; static int score_of_tileset(Game *g, TileSetP tp, TileWind own, TileWind prevailing) { static char tmp[50] ; int n,d; tmp[0] = 0; if ( tp->type == Empty ) { /* I don't think this should happen */ warn("score_of_tileset: called on Empty tileset"); tsltext = NULL; tsrtext = NULL; return 0; } d = is_major(tp->tile) ? 2 : 1; n = 0; switch ( tp->type ) { case Chow: case ClosedChow: n = 0; break; case Pung: n = 2*d; strcat(tmp,"open pung of "); strcat(tmp, (d==2) ? "majors" : "minors"); break; case ClosedPung: n = 4*d; strcat(tmp,"closed pung of "); strcat(tmp, (d==2) ? "majors" : "minors"); break; case Kong: n = 8*d; /* if we are using Millington's kong rules, we should distinguish claimed and annexed kongs here */ if ( game_get_option_value(g,GOKongHas3Types,NULL).optbool ) { if ( tp->annexed ) strcat(tmp,"annexed kong of "); else strcat(tmp,"claimed kong of "); } else { strcat(tmp,"open kong of "); } strcat(tmp, (d==2) ? "majors" : "minors"); break; case ClosedKong: n = 16*d; strcat(tmp,"closed kong of "); strcat(tmp, (d==2) ? "majors" : "minors"); break; case Pair: case ClosedPair: n = 0; if ( is_dragon(tp->tile) ) { n = 2; strcat(tmp,"pair of dragons"); } else if ( is_wind(tp->tile) && (TileWind)value_of(tp->tile) == own ) { if ( own == prevailing ) { n = 4; strcat(tmp,"pair of own and prevailing wind"); } else { n = 2; strcat(tmp,"pair of own wind"); } } else if ( is_wind(tp->tile) && (TileWind)value_of(tp->tile) == prevailing ) { n = 2; strcat(tmp,"pair of prevailing wind"); } break; case Empty: break; /* to suppress warning */ } tsltext = tileset_string(tp); tsscore = n; tsrtext = tmp; return n; } /* score_of_hand: score the given seat in the given game. In addition, set the danger flags to indicate if the player achieved any of the dangerous hands. */ Score score_of_hand(Game *g, seats s) { Score tot; /* there are three buffers in which scoring info is built up: a points buffer, a doubles buffer, and a limit buffer */ static char buf[2048], dblsbuf[1024], limsbuf[1024]; char tbuf[100]; PlayerP p; int limit, nolimit; int winner; int i,doubles,centilims; MJSpecialHandFlags mjspecflags; Tile eyestile; int claimedpair; int numchows,numpairs,numpungs,numkongs,allclosed,noscore,allhonours, allmajors,allterminals,dragonsets,windsets,dragonpairs,windpairs, numclosedpks,allgreen,allbamboo,allcharacter,allcircle, allbamboohonour,allcharacterhonour,allcirclehonour; p = g->players[s]; assert(g->state == MahJonging); limit = game_get_option_value(g,GOScoreLimit,NULL).optnat; nolimit = game_get_option_value(g,GONoLimit,NULL).optnat; mjspecflags = 0; if ( game_get_option_value(g,GOSevenPairs,NULL).optbool ) mjspecflags |= MJSevenPairs; buf[0] = 0; tot.explanation = buf; tot.value = 0; dblsbuf[0] = 0; limsbuf[0] = 0; doubles = 0; centilims = 0; /* This macro takes three args: text for left, score, and text for right. It then looks at the score and adds appropriate bits to the relevant buffers and scoring info */ #define doscore(ltext,score,rtext) \ { int pts, dbls, clims; \ pts = (score) % 10000; \ dbls = ((score) / 10000) % 100; \ clims = ((score) / 1000000); \ if ( (!clims || nolimit) && pts ) { \ tot.value += pts; sprintf(tbuf,"%-12s %2d (%s)\n",ltext,pts,rtext); \ strmcat(buf,tbuf,sizeof(buf)-strlen(buf)); \ } \ if ( (!clims || nolimit) && dbls ) { \ doubles += dbls; sprintf(tbuf,"%-12s %1d dbl (%s)\n",ltext,dbls,rtext); \ strmcat(dblsbuf,tbuf,sizeof(dblsbuf)-strlen(dblsbuf)); \ } \ if ( clims && !(nolimit && (pts || dbls)) ) { \ if ( clims > centilims ) centilims = clims; \ if ( clims == 100 ) { \ sprintf(tbuf,"%-10s %4d (%s)\n","Limit Hand",limit,rtext); \ } else { \ sprintf(tbuf,"%4g Limit %4d (%s)\n",clims*1.0/100,clims*limit/100,rtext); \ } \ strmcat(limsbuf,tbuf,sizeof(limsbuf)-strlen(limsbuf)); \ } \ } /* and a couple of defines for hard-coded scores */ #define DBLS *10000 #define LIMIT 100000000 /* variables for detecting special hands */ numchows = 0; numkongs = 0; numpairs = 0; numpungs = 0; allclosed = 1; noscore = 1; allterminals = 1; allhonours = 1; allmajors = 1; windsets = 0; dragonsets = 0; windpairs = 0; dragonpairs = 0; allgreen = 1; allbamboo = 1; allcharacter = 1; allcircle = 1; allbamboohonour = 1; allcharacterhonour = 1; allcirclehonour = 1; numclosedpks = 0; /* closed pungs/kongs */ eyestile = HiddenTile; claimedpair = 0; /* Basic points */ winner = 0; if ( s == g->player ) { doscore("basic",game_get_option_value(g,GOMahJongScore,NULL).optscore, "going Mah Jong"); winner = 1; } if ( winner ) { presetdflags(p,s); psetdflags(p,s,DangerEnd); } /* Now, get the points for the tilesets */ /* Also, stash information for doubles */ for ( i = 0; i < MAX_TILESETS; i++ ) { TileSetP t = (TileSetP) &p->tilesets[i]; if ( t->type == Empty ) continue; score_of_tileset(g,t,p->wind,g->round); if ( tsscore ) { noscore = 0; doscore(tsltext,tsscore,tsrtext); } if ( t->type == Chow ) numchows++,allclosed=0,allterminals=allhonours=allmajors=0; if ( t->type == ClosedChow ) numchows++,allterminals=allhonours=allmajors=0; if ( t->type == Pung ) allclosed=0,numpungs++; if ( t->type == ClosedPung ) numpungs++,numclosedpks++; if ( t->type == Kong ) { numkongs++ ; /* in Millington's rules, a claimed kong counts as a closed kong for doubling purposes */ if ( game_get_option_value(g,GOKongHas3Types,NULL).optbool && ! t->annexed ) { numclosedpks++; } else { allclosed=0; } } if ( t->type == ClosedKong ) numkongs++,numclosedpks++; if ( t->type == Pair ) { numpairs++; allclosed=0; claimedpair = 1 ; eyestile = t->tile; /* this fails for seven pairs, but we don't care */ } if ( t->type == ClosedPair ) { numpairs++; eyestile = t->tile; /* this fails for seven pairs, but we don't care */ } if ( num_tiles_in_set(t) >= 3 ) { dragonsets += is_dragon(t->tile); windsets += is_wind(t->tile); } else { dragonpairs += is_dragon(t->tile); windpairs += is_wind(t->tile); } switch ( suit_of(t->tile) ) { case BambooSuit: allcharacter = allcharacterhonour = 0; allcircle = allcirclehonour = 0; switch ( t->type ) { case Chow: case ClosedChow: if ( !is_green(t->tile) || !is_green(t->tile+1) || !is_green(t->tile+2) ) allgreen = 0; break; default: if ( !is_green(t->tile) ) allgreen = 0; } break; case CharacterSuit: allbamboo = allbamboohonour = 0; allcircle = allcirclehonour = 0; allgreen = 0; break; case CircleSuit: allbamboo = allbamboohonour = 0; allcharacter = allcharacterhonour = 0; allgreen = 0; break; case DragonSuit: allbamboo = allcharacter = allcircle = 0; if ( !is_green(t->tile) ) allgreen = 0; break; case WindSuit: allbamboo = allcharacter = allcircle = 0; allgreen = 0; break; default: warn("Strange suit seen in hand"); } if ( !is_honour(t->tile) ) allhonours = 0; if ( !is_terminal(t->tile) ) allterminals = 0; if ( !is_major(t->tile) ) allmajors = 0; } /* now go through the concealed tiles, in case we're playing some weird game where losers are allowed to score for pure and concealed hands. Seems a pity to duplicate all this code, but there we are */ /* There's another reason to go through the concealed tiles: the existence of special hands, where the concealed tiles are a declared set */ for ( i = 0; i < p->num_concealed; i++ ) { Tile ti = p->concealed[i]; switch ( suit_of(ti) ) { case BambooSuit: allcharacter = allcharacterhonour = 0; allcircle = allcirclehonour = 0; if ( !is_green(ti) ) allgreen = 0; break; case CharacterSuit: allbamboo = allbamboohonour = 0; allcircle = allcirclehonour = 0; allgreen = 0; break; case CircleSuit: allbamboo = allbamboohonour = 0; allcharacter = allcharacterhonour = 0; allgreen = 0; break; case DragonSuit: allbamboo = allcharacter = allcircle = 0; if ( !is_green(ti) ) allgreen = 0; break; case WindSuit: allbamboo = allcharacter = allcircle = 0; allgreen = 0; break; default: warn("Strange suit seen in hand"); } if ( !is_honour(ti) ) allhonours = 0; if ( !is_terminal(ti) ) allterminals = 0; if ( !is_major(ti) ) allmajors = 0; } /* if we have seven pairs, give the base points */ if ( numpairs == 7 ) { doscore("basic points",game_get_option_value(g,GOSevenPairsVal,NULL).optscore,"seven pairs"); noscore = 0; } /* Now points for flowers and seasons (which don't count to stop a noscore hand) */ if ( p->num_specials > 0 && ! no_special_scores ) { char s[16]; sprintf(s,"%d special%s",p->num_specials,p->num_specials > 1 ? "s" : ""); doscore(s,4*p->num_specials,"flowers and seasons"); } /* odds and sods */ if ( winner && g->whence == FromWall ) { doscore("extra",2,"winning from wall"); } /* fishing the eyes: if we claimed a pair to go out, then the tile had better match the discard. Otherwise, the tiles of the (necessarily closed) pair had better match the tile drawn from the wall. We don't give this for seven pairs (or other multi pair hands). */ if ( winner && eyestile == g->tile && numpairs == 1 && ( claimedpair || g->whence == FromWall || g->whence == FromLoose ) ) { doscore("extra",is_major(eyestile) ? 4 : 2,"fishing the eyes"); } /* this ridiculous amount of code deals with filling the only place. All this work for two points... */ /* this is a sanity check; if this fails, the controller code has not put the right player in the caller slot */ /* we don't give this for seven pairs either */ if ( numpairs == 1 ) { if ( winner ) { if ( p->id != gextras(g)->caller->id ) { warn("Wrong player found in caller slot of game structure!"); } else { PlayerP pp = gextras(g)->caller; int n = 0; int i; Tile t; /* see how many tiles can complete the hand in theory */ t = HiddenTile; while ( (t = tile_iterate(t,0)) != HiddenTile ) { /* if all four copies of the tile are exposed, it doesn't count as available */ /* However, if this is the tile that was claimed for Mah-Jong, it wasn't all exposed when we called, even if the exposed count is now 4. */ if ( g->exposed_tile_count[t] == 4 && g->tile != t ) continue; i = player_can_mah_jong(pp,t,mjspecflags); if ( i < 0 ) { warn("error in player_can_mah_jong while checking only place") ; } else n += i; } if ( n <= 1 ) { doscore("extra",2,"filling the only place"); } } } } #define dbl(n,ex) doscore("",n DBLS,ex) /* Now start looking for doubles from the tilesets */ for ( i = 0; i < MAX_TILESETS; i++ ) { TileSetP t = (TileSetP) &p->tilesets[i]; if ( t->type == Empty ) continue; if ( num_tiles_in_set(t) >= 3 ) { if ( is_dragon(t->tile) ) doscore(tileset_string(t),1 DBLS,"pung/kong of dragons"); if ( is_wind(t->tile) && (TileWind)value_of(t->tile) == p->wind ) doscore(tileset_string(t),1 DBLS,"pung/kong of own wind"); if ( is_wind(t->tile) && (TileWind)value_of(t->tile) == g->round ) doscore(tileset_string(t),1 DBLS,"pung/kong of prevailing wind"); } } /* We may want to suppress these when comparing strategies, as they introduce a lot of randomness */ /* Flower and season doubles */ if ( ! no_special_scores ) { int ownflower = 0, ownseason = 0; int numflowers = 0; int numseasons = 0; int i; for ( i = 0; i < p->num_specials; i++ ) { if ( suit_of(p->specials[i]) == FlowerSuit ) { numflowers++; if ( (TileWind)value_of(p->specials[i]) == p->wind ) ownflower = 1; } if ( suit_of(p->specials[i]) == SeasonSuit ) { numseasons++; if ( (TileWind)value_of(p->specials[i]) == p->wind ) ownseason = 1; } } if ( ownflower ) doscore("",game_get_option_value(g,GOFlowersOwnEach,NULL).optscore, "own flower"); if ( ownseason ) doscore("",game_get_option_value(g,GOFlowersOwnEach,NULL).optscore, "own season"); if ( ownflower && ownseason ) doscore("",game_get_option_value(g,GOFlowersOwnBoth,NULL).optscore, "own flower and season"); if ( numflowers == 4 ) doscore("",game_get_option_value(g,GOFlowersBouquet,NULL).optscore, "all four flowers"); if ( numseasons == 4 ) doscore("",game_get_option_value(g,GOFlowersBouquet,NULL).optscore, "all four seasons"); } /* doubles applying to all hands */ /* Little/Big Three Dragons */ if ( dragonsets == 3 ) { dbl(2,"Big Three Dragons"); if ( winner ) psetdflags(p,s,DangerDragon); } else if ( dragonsets == 2 && dragonpairs ) { dbl(1,"Little Three Dragons"); if ( winner ) psetdflags(p,s,DangerDragon); } /* Little/Big Four Joys */ if ( windsets == 4 ) { dbl(2,"Big Four Joys"); if ( winner ) psetdflags(p,s,DangerWind); } else if ( windsets == 3 && windpairs ) { dbl(1,"Little Four Joys"); if ( winner ) psetdflags(p,s,DangerWind); } /* three concealed pungs */ if ( numclosedpks >= 3 ) { dbl(1,"three concealed pungs"); } /* other doubles mostly applying only to the mahjong hand */ if ( winner ) { if ( numchows == 0 && numpairs == 1 ) dbl(1,"no chows"); if ( noscore ) dbl(1,"no score hand"); } /* some losers want these doubles to apply to losing hands also */ if ( winner || game_get_option_value(g,GOLosersPurity,NULL).optbool ) { if ( allhonours ) { /* This should score 2 doubles, and then get a double for all majors. However, we must be careful not to give it the double for one-suit-with-honours! */ dbl(2,"all honours"); psetdflags(p,s,DangerHonour); } else if ( allterminals ) { /* This should score 2 doubles, and then get a double for all majors. */ dbl(2,"all terminals"); psetdflags(p,s,DangerTerminal); } if ( allmajors ) { dbl(1,"all majors"); } } /* double for fully concealed hand */ if ( winner ) { /* we may have claimed a discard for a special set */ if ( g->whence == FromDiscard ) allclosed = 0; if ( allclosed ) doscore("",game_get_option_value( g,GOConcealedFully,NULL).optscore,"concealed hand"); } /* double for almost concealed hand. This is a pain */ if ( winner && !allclosed ) { int semiclosed = 1; /* need to check the hand before mahjong. Hooray, another use for the caller slot ! */ if ( p->id != gextras(g)->caller->id ) { warn("Wrong player found in caller slot of game structure!"); semiclosed = 0; } else { PlayerP pp = gextras(g)->caller; int i; for ( i = 0; i < MAX_TILESETS; i++ ) { switch ( pp->tilesets[i].type ) { case Chow: case Pung: case Kong: case Pair: semiclosed = 0; break; default: break; /* keep ANSI quiet */ } } } if ( semiclosed ) doscore("",game_get_option_value( g,GOConcealedAlmost,NULL).optscore,"almost concealed hand"); } else if ( !winner && allclosed && game_get_option_value(g,GOLosersPurity,NULL).optbool ) { doscore("",game_get_option_value( g,GOConcealedAlmost,NULL).optscore,"almost concealed hand"); } /* we may give these to losers, if somebody really wants */ if ( winner || game_get_option_value(g,GOLosersPurity,NULL).optbool ) { /* NB. If no suit tiles, then we already have all honours, and don't give another double! */ if ( allbamboo || allcharacter || allcircle ) { dbl(3,"one suit only"); if ( allbamboo ) psetdflags(p,s,DangerBamboo); if ( allcharacter ) psetdflags(p,s,DangerCharacter); if ( allcircle ) psetdflags(p,s,DangerCircle); } else if ( !allhonours && (allbamboohonour || allcharacterhonour || allcirclehonour) ) { dbl(1,"one suit with honours"); } } if ( winner ) { /* Should the following two be exclusive? */ /* loose tile */ if ( g->whence == FromLoose ) dbl(1,"winning with loose tile"); /* last tile. This is >=, not ==, because if playing with a replenishing dead wall, it's possible to move the end of the live wall back past tiles already taken, if the last tile is a redeemed for a loose tile with 13 in the dead wall. */ if ( g->wall.live_used >= g->wall.live_end ) dbl(1,"winning with last tile"); /* robbing a kong */ if ( g->whence == FromRobbedKong ) dbl(1,"robbing a kong"); /* original call */ if ( pflag(p,OriginalCall) ) dbl(1,"completing Original Call"); } /* having done all that work, we now look for limit hands. Note that all the danger signals except all green have already been set while we were looking for doubles */ #define limh(l) doscore("",LIMIT,l) /* we may as well record all the attained limits! */ if ( winner ) { if ( g->player == east && pflag(p,NoDiscard) ) { /* heaven's blessing */ limh("Heaven's Blessing"); } if ( g->player != east && pflag(p,NoDiscard) && g->whence == FromDiscard ) { seats s; int eb = 1; /* might be earth's blessing, but we need to check that nobody else has discarded in between */ for ( s = south; s < NUM_SEATS; s++ ) { if ( !pflag(g->players[s],NoDiscard) ) eb = 0; } if ( eb ) limh("Earth's Blessing"); } if ( g->whence == FromLoose && g->tile == make_tile(CircleSuit,5) ) { limh("Gathering Plum Blossom from the Roof"); } if ( g->tile == make_tile(CircleSuit,1) && g->wall.live_used == g->wall.live_end && g->whence == FromWall ) { limh("Catching the Moon from the Bottom of the Sea"); } if ( g->whence == FromRobbedKong && g->tile == make_tile(BambooSuit,2) ) { limh("Scratching a Carrying Pole"); } if ( game_flag(g,GFKongUponKong) ) { limh("Kong upon Kong"); } if ( numkongs == 4 ) { limh("Fourfold Plenty"); } if ( allclosed && numpungs+numkongs == 4 ) { limh("Buried Treasure"); } if ( player_can_thirteen_wonders(p,HiddenTile) ) { limh("Thirteen Unique Wonders"); } if ( dragonsets == 3 && numchows == 0 ) { limh("Three Great Scholars"); } if ( windsets == 4 ) { limh("Four Blessing o'er the Door"); } if ( allhonours ) { limh("All Honours"); } if ( allterminals ) { limh("Heads and Tails"); } if ( allclosed && (allbamboo || allcharacter || allcircle) ) { limh("Concealed Clear Suit"); } if ( g->hands_as_east == 12 && g->player == east ) { /* sic */ limh("East's 13th consecutive win"); } if ( allgreen ) { limh("Imperial Jade"); psetdflags(p,s,DangerGreen); } /* Nine Gates */ if ( allbamboo || allcharacter || allcircle ) { /* worth checking in this case */ PlayerP pp = gextras(g)->caller; int ng = 1; if ( pp->id != p->id ) { warn("Wrong player found in caller slot of game structure!"); ng = 0; } else { int i; if ( is_honour(g->tile) ) { ng = 0; } else { int s = suit_of(g->tile); player_sort_tiles(pp); if ( pp->num_concealed < 13 ) ng = 0; for ( i = 0 ; i < 3 ; i++ ) { if ( pp->concealed[i] != make_tile(s,1) ) ng = 0; } for ( i = 2 ; i < 11 ; i++ ) { if ( pp->concealed[i] != make_tile(s,i-1) ) ng = 0; } for ( i = 11 ; i < 13 ; i++ ) { if ( pp->concealed[i] != make_tile(s,9) ) ng = 0; } } } if ( ng ) limh("Nine Gates"); } /* The Wriggling Snake, another silly hand */ if ( allbamboo || allcharacter || allcircle ) { /* check for it */ int i; int set1=0,set9=0,pair2=0,pair5=0,pair8=0, chow2=0,chow3=0,chow5=0,chow6=0; for ( i = 0 ; i < MAX_TILESETS ; i++ ) { switch( p->tilesets[i].type ) { case Empty: break; case Pung: case ClosedPung: case Kong: case ClosedKong: set1 |= (value_of(p->tilesets[i].tile) == 1); set9 |= (value_of(p->tilesets[i].tile) == 9); break; case Pair: case ClosedPair: pair2 |= (value_of(p->tilesets[i].tile) == 2); pair5 |= (value_of(p->tilesets[i].tile) == 5); pair8 |= (value_of(p->tilesets[i].tile) == 8); break; case Chow: case ClosedChow: chow2 |= (value_of(p->tilesets[i].tile) == 2); chow3 |= (value_of(p->tilesets[i].tile) == 3); chow5 |= (value_of(p->tilesets[i].tile) == 5); chow6 |= (value_of(p->tilesets[i].tile) == 6); break; } } if ( set1 && set9 && ( (pair2 && chow3 && chow6) || (pair5 && chow2 && chow6) || (pair8 && chow2 && chow5) ) ) limh("Wriggling Snake"); } } /* display the total points so far */ sprintf(tbuf,"%-11s %3d\n","total pts",tot.value); strmcat(buf,tbuf,sizeof(buf)-strlen(buf)); /* add the double descriptions */ strmcat(buf,dblsbuf,sizeof(buf)-strlen(buf)); /* calculate the doubles */ sprintf(tbuf,"%-12s %2d\n","total dbls",doubles); strmcat(buf,tbuf,sizeof(buf)-strlen(buf)); /* to avoid arithmetic overflow when people set crazy options, we'll put a hard limit of 1E8 in */ while ( doubles-- > 0 ) { tot.value *= 2; if ( tot.value > 100000000 ) tot.value = 100000000 ; } if ( ! nolimit && tot.value > limit ) { sprintf(tbuf,"%-9s %5d (over limit)\n","score:",tot.value); strmcat(buf,tbuf,sizeof(buf)-strlen(buf)); tot.value = limit; } /* and the limit descriptions */ strmcat(buf,limsbuf,sizeof(buf)-strlen(buf)); /* calculate the limit */ if ( centilims ) { if ( nolimit ) { if ( centilims*limit/100 > tot.value ) tot.value = centilims*limit/100; } else { tot.value = centilims*limit/100; /* all the scoring info is irrelevant */ strcpy(buf,limsbuf); } } sprintf(tbuf,"%-9s %5d","SCORE:",tot.value); strmcat(buf,tbuf,sizeof(buf)-strlen(buf)); /* just return what we've got, to see if it's working */ return tot; } mj-1.17-src/fallbacktiles/0000755006717300001440000000000010627612767014143 5ustar jcbusersmj-1.17-src/fallbacktiles/2D.xpm0000644006717300001440000000220707005052037015117 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... . .......", "....... ... .. ... ......", "....... ... .. ... ......", ".......... ... ... ......", "......... .... ... ......", "........ ..... ... ......", "....... ... .. ... ......", "....... . .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/tongW.xpm0000644006717300001440000000270407005053367015761 0ustar jcbusers/* XPM */ static char * tongW_xpm[] = { "35 35 4 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #F0F0F0", " ......... ", " ...+++++++++... ", " ..+++++++++++++++.. ", " .+++++++++++++++++++. ", " .+++++++++++++++++++++. ", " .++++@@@@@@@@@@@@@@@++++. ", " .+++++@@@@@@@@@@@@@@@+++++. ", " .++++++@@@@@@@@@@@@@@@++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@...@......@...++++++++. ", ".+++++++++@@.@@@.@@.@@@.@+++++++++.", ".+++++++++@@.@.@.@@.@.@.@+++++++++.", ".+++++++++@@.@.@.@@.@.@.@+++++++++.", ".+++++++++@@.@.@.@@.@.@.@+++++++++.", ".+++++++++@@..@..@@..@..@+++++++++.", ".+++++++++@@@.@.@@@@.@.@@+++++++++.", ".+++++++++@@@.@.@@@@.@.@@+++++++++.", ".+++++++++@@@@@@@@@@@@@@@+++++++++.", ".+++++++++@@@@@@@@@@@@@@@+++++++++.", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .++++++@@@@@@@@@@@@@@@++++++. ", " .+++++@@@@@@@@@@@@@@@+++++. ", " .++++@@@@@@@@@@@@@@@++++. ", " .+++++++++++++++++++++. ", " .+++++++++++++++++++. ", " ..+++++++++++++++.. ", " ...+++++++++... ", " ......... "}; mj-1.17-src/fallbacktiles/1D.xpm0000644006717300001440000000220707005052037015116 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "......... ...............", "....... ... .......", "......... .... ... ......", "......... .... ... ......", "......... .... ... ......", "......... .... ... ......", "......... .... ... ......", "......... .... ... ......", "....... . .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/8D.xpm0000644006717300001440000000220707005052041015120 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... . .......", "....... ... .. ... ......", "....... ... .. ... ......", "........ ... ... ......", "....... ... .. ... ......", "....... ... .. ... ......", "....... ... .. ... ......", "........ .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/2F.xpm0000644006717300001440000000220707005054145015123 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... . ......", "....... ... .. ... ......", "....... ... .. . . ......", ".......... ... ........", "......... .... . ........", "........ ..... ..........", "....... ... .. ..........", "....... . ........", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/RD.xpm0000644006717300001440000000220707005054144015160 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "...... .. .......", "....... ... .. ... ......", "....... ... .. ... ......", "....... ... ... ......", "....... .. ... ... ......", "....... ... .. ... ......", "....... ... .. ... ......", "...... .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/6D.xpm0000644006717300001440000000220707005052040015115 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "......... .............", "........ .... .......", "....... ...... ... ......", "....... ...... ... ......", "....... ... ... ......", "....... ... .. ... ......", "....... ... .. ... ......", "....... ... .. ... ......", "........ .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/tongS.xpm0000644006717300001440000000270407005053311015742 0ustar jcbusers/* XPM */ static char * tongS_xpm[] = { "35 35 4 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #F0F0F0", " ......... ", " ...+++++++++... ", " ..+++++++++++++++.. ", " .+++++++++++++++++++. ", " .+++++++++++++++++++++. ", " .++++@@@@@@@@@@@@@@@++++. ", " .+++++@@@@@@@@@@@@@@@+++++. ", " .++++++@@@@@@@@@@@@@@@++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@....@...@...++++++++. ", ".+++++++++@@.@@@.@@.@@@.@+++++++++.", ".+++++++++@@.@@@@@@.@.@.@+++++++++.", ".+++++++++@@...@@@@.@.@.@+++++++++.", ".+++++++++@@@@...@@.@.@.@+++++++++.", ".+++++++++@@@@@@.@@..@..@+++++++++.", ".+++++++++@@.@@@.@@@.@.@@+++++++++.", ".+++++++++@@....@@@@.@.@@+++++++++.", ".+++++++++@@@@@@@@@@@@@@@+++++++++.", ".+++++++++@@@@@@@@@@@@@@@+++++++++.", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .++++++@@@@@@@@@@@@@@@++++++. ", " .+++++@@@@@@@@@@@@@@@+++++. ", " .++++@@@@@@@@@@@@@@@++++. ", " .+++++++++++++++++++++. ", " .+++++++++++++++++++. ", " ..+++++++++++++++.. ", " ...+++++++++... ", " ......... "}; mj-1.17-src/fallbacktiles/6B.xpm0000644006717300001440000000220707005052040015113 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "......... .............", "........ .... .......", "....... ...... ... ......", "....... ...... ... ......", "....... ... .......", "....... ... .. ... ......", "....... ... .. ... ......", "....... ... .. ... ......", "........ .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/tongE.xpm0000644006717300001440000000270407005053221015724 0ustar jcbusers/* XPM */ static char * tongE_xpm[] = { "35 35 4 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #F0F0F0", " ......... ", " ...+++++++++... ", " ..+++++++++++++++.. ", " .+++++++++++++++++++. ", " .+++++++++++++++++++++. ", " .++++@@@@@@@@@@@@@@@++++. ", " .+++++@@@@@@@@@@@@@@@+++++. ", " .++++++@@@@@@@@@@@@@@@++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@......@...@...++++++++. ", ".+++++++++@@.@@@.@@.@@@.@+++++++++.", ".+++++++++@@.@.@@@@.@.@.@+++++++++.", ".+++++++++@@...@@@@.@.@.@+++++++++.", ".+++++++++@@.@.@@@@.@.@.@+++++++++.", ".+++++++++@@.@@@@@@..@..@+++++++++.", ".+++++++++@@.@@@.@@@.@.@@+++++++++.", ".+++++++++@......@@@.@.@@+++++++++.", ".+++++++++@@@@@@@@@@@@@@@+++++++++.", ".+++++++++@@@@@@@@@@@@@@@+++++++++.", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .++++++@@@@@@@@@@@@@@@++++++. ", " .+++++@@@@@@@@@@@@@@@+++++. ", " .++++@@@@@@@@@@@@@@@++++. ", " .+++++++++++++++++++++. ", " .+++++++++++++++++++. ", " ..+++++++++++++++.. ", " ...+++++++++... ", " ......... "}; mj-1.17-src/fallbacktiles/5C.xpm0000644006717300001440000000220707005052040015113 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "....... .............", "....... ....... .......", "....... ...... ... ......", "....... ...... ... ......", "....... ... ..........", "........... .. ..........", "........... .. ... ......", "...... .... .. ... ......", "....... .... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/1C.xpm0000644006717300001440000000220707005052037015115 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "......... ...............", "....... ..... .......", "......... .... ... ......", "......... .... ... ......", "......... .... ..........", "......... .... ..........", "......... .... ... ......", "......... .... ... ......", "....... ... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/2C.xpm0000644006717300001440000000220707005052037015116 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... ... .......", "....... ... .. ... ......", "....... ... .. ... ......", ".......... ... ..........", "......... .... ..........", "........ ..... ... ......", "....... ... .. ... ......", "....... ... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/7C.xpm0000644006717300001440000000220707005052040015115 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "....... .............", "....... ... ... .......", ".......... ... ... ......", ".......... ... ... ......", ".......... ... ..........", ".......... ... ..........", "......... .... ... ......", "......... .... ... ......", "......... ..... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/7D.xpm0000644006717300001440000000220707005052041015117 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "....... .............", "....... ... . .......", ".......... ... ... ......", ".......... ... ... ......", ".......... ... ... ......", ".......... ... ... ......", "......... .... ... ......", "......... .... ... ......", "......... ... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/8B.xpm0000644006717300001440000000220707005052041015116 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... . .......", "....... ... .. ... ......", "....... ... .. ... ......", "........ ... .......", "....... ... .. ... ......", "....... ... .. ... ......", "....... ... .. ... ......", "........ .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/1S.xpm0000644006717300001440000000220707005054145015137 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "......... ...............", "....... ..... ......", "......... .... ... ......", "......... .... ..........", "......... .... ........", "......... ...... ......", "......... ........ ......", "......... .... ... ......", "....... .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/3C.xpm0000644006717300001440000000220707005052040015111 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... ... .......", "........... .. ... ......", "........... .. ... ......", "......... ... ..........", "........... .. ..........", "........... .. ... ......", "....... ... .. ... ......", "........ .... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/SW.xpm0000644006717300001440000000220707005054145015205 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ . . .....", "....... ... .. ... ......", "....... ...... . . ......", "....... .... . . ......", "......... .. . . ......", "........... .. . ......", "....... ... ... . .......", "....... .... . .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/EW.xpm0000644006717300001440000000220707005054145015167 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "...... . . .....", "....... ... .. ... ......", "....... . .... . . ......", "....... .... . . ......", "....... . .... . . ......", "....... ...... . ......", "....... ... ... . .......", "...... ... . .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/4D.xpm0000644006717300001440000000220707005052040015113 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".......... ..............", "......... .. .......", "........ . ... ... ......", "........ . ... ... ......", "....... .. ... ... ......", "...... ... ... ... ......", "...... .. ... ......", ".......... ... ... ......", "......... . .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/4B.xpm0000644006717300001440000000220707005052040015111 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".......... ..............", "......... .. .......", "........ . ... ... ......", "........ . ... ... ......", "....... .. ... .......", "...... ... ... ... ......", "...... .. ... ......", ".......... ... ... ......", "......... . .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/GD.xpm0000644006717300001440000000220707005054144015145 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ .. .......", "....... ... .. ... ......", "....... ... .. ... ......", "....... ...... ... ......", "....... .. .. ... ......", "....... ... .. ... ......", "....... ... .. ... ......", "........ .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/5D.xpm0000644006717300001440000000220707005052040015114 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "....... .............", "....... ..... .......", "....... ...... ... ......", "....... ...... ... ......", "....... ... ... ......", "........... .. ... ......", "........... .. ... ......", "...... .... .. ... ......", "....... .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/3B.xpm0000644006717300001440000000220707005052040015110 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... . .......", "........... .. ... ......", "........... .. ... ......", "......... ... .......", "........... .. ... ......", "........... .. ... ......", "....... ... .. ... ......", "........ .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/4F.xpm0000644006717300001440000000220707005054145015125 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".......... ..............", "......... .. ......", "........ . ... ... ......", "........ . ... . . ......", "....... .. ... ........", "...... ... ... . ........", "...... .. ..........", ".......... ... ..........", "......... . ........", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/3F.xpm0000644006717300001440000000220707005054145015124 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... . ......", "........... .. ... ......", "........... .. . . ......", "......... ... ........", "........... .. . ........", "........... .. ..........", "....... ... .. ..........", "........ .. ........", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/5B.xpm0000644006717300001440000000220707005052040015112 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "....... .............", "....... ..... .......", "....... ...... ... ......", "....... ...... ... ......", "....... ... .......", "........... .. ... ......", "........... .. ... ......", "...... .... .. ... ......", "....... .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/--.xpm0000644006717300001440000000217607005055007015070 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 1 1", /* colors */ " c #948D13", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " }; mj-1.17-src/fallbacktiles/WW.xpm0000644006717300001440000000220707005054145015211 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "...... . . .....", "....... ... .. ... ......", "....... . . .. . . ......", "....... . . .. . . ......", "....... . . .. . . ......", "....... . .. . ......", "........ . .... . .......", "........ . .... . .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/1F.xpm0000644006717300001440000000220707005054145015122 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "......... ...............", "....... ... ......", "......... .... ... ......", "......... .... . . ......", "......... .... ........", "......... .... . ........", "......... .... ..........", "......... .... ..........", "....... . ........", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/4C.xpm0000644006717300001440000000220707005052040015112 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".......... ..............", "......... .... .......", "........ . ... ... ......", "........ . ... ... ......", "....... .. ... ..........", "...... ... ... ..........", "...... .. ... ......", ".......... ... ... ......", "......... ... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/7B.xpm0000644006717300001440000000220707005052041015115 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "....... .............", "....... ... . .......", ".......... ... ... ......", ".......... ... ... ......", ".......... ... .......", ".......... ... ... ......", "......... .... ... ......", "......... .... ... ......", "......... ... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/9B.xpm0000644006717300001440000000220707005052041015117 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... . .......", "....... ... .. ... ......", "....... ... .. ... ......", "....... ... .. .......", "........ .. ... ......", "........... .. ... ......", ".......... ... ... ......", "....... ... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/3S.xpm0000644006717300001440000000220707005054145015141 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... ... ......", "........... .. ... ......", "........... .. ..........", "......... ... ........", "........... .... ......", "........... ...... ......", "....... ... .. ... ......", "........ ... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/1B.xpm0000644006717300001440000000220707005052037015114 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "......... ...............", "....... ... .......", "......... .... ... ......", "......... .... ... ......", "......... .... .......", "......... .... ... ......", "......... .... ... ......", "......... .... ... ......", "....... . .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/NW.xpm0000644006717300001440000000220707005054145015200 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "...... .. . .....", "....... .. .. ... ......", "....... .. .. . . ......", "....... . . .. . . ......", "....... . . .. . . ......", "....... .. .. . ......", "....... .. ... . .......", "...... .. ... . .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/4S.xpm0000644006717300001440000000220707005054146015143 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".......... ..............", "......... .... ......", "........ . ... ... ......", "........ . ... ..........", "....... .. ... ........", "...... ... ..... ......", "...... ...... ......", ".......... ... ... ......", "......... .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/XX.xpm0000644006717300001440000000217607005112261015212 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 1 1", /* colors */ " c #FF0000", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " }; mj-1.17-src/fallbacktiles/tongN.xpm0000644006717300001440000000270407005053445015745 0ustar jcbusers/* XPM */ static char * tongN_xpm[] = { "35 35 4 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #F0F0F0", " ......... ", " ...+++++++++... ", " ..+++++++++++++++.. ", " .+++++++++++++++++++. ", " .+++++++++++++++++++++. ", " .++++@@@@@@@@@@@@@@@++++. ", " .+++++@@@@@@@@@@@@@@@+++++. ", " .++++++@@@@@@@@@@@@@@@++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@..@@......@...++++++++. ", ".+++++++++@@..@@.@@.@@@.@+++++++++.", ".+++++++++@@..@@.@@.@.@.@+++++++++.", ".+++++++++@@.@.@.@@.@.@.@+++++++++.", ".+++++++++@@.@.@.@@.@.@.@+++++++++.", ".+++++++++@@.@@..@@..@..@+++++++++.", ".+++++++++@@.@@..@@@.@.@@+++++++++.", ".+++++++++@...@@.@@@.@.@@+++++++++.", ".+++++++++@@@@@@@@@@@@@@@+++++++++.", ".+++++++++@@@@@@@@@@@@@@@+++++++++.", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .++++++++@@@@@@@@@@@@@@@++++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .+++++++@@@@@@@@@@@@@@@+++++++. ", " .++++++@@@@@@@@@@@@@@@++++++. ", " .+++++@@@@@@@@@@@@@@@+++++. ", " .++++@@@@@@@@@@@@@@@++++. ", " .+++++++++++++++++++++. ", " .+++++++++++++++++++. ", " ..+++++++++++++++.. ", " ...+++++++++... ", " ......... "}; mj-1.17-src/fallbacktiles/WD.xpm0000644006717300001440000000220707005054145015166 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "...... . .......", "....... ... .. ... ......", "....... . . .. ... ......", "....... . . .. ... ......", "....... . . .. ... ......", "....... . .. ... ......", "........ . ... ... ......", "........ . .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/8C.xpm0000644006717300001440000000220707005052041015117 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... ... .......", "....... ... .. ... ......", "....... ... .. ... ......", "........ ... ..........", "....... ... .. ..........", "....... ... .. ... ......", "....... ... .. ... ......", "........ .... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/3D.xpm0000644006717300001440000000220707005052040015112 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... . .......", "........... .. ... ......", "........... .. ... ......", "......... ... ... ......", "........... .. ... ......", "........... .. ... ......", "....... ... .. ... ......", "........ .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/2B.xpm0000644006717300001440000000220707005052037015115 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... . .......", "....... ... .. ... ......", "....... ... .. ... ......", ".......... ... .......", "......... .... ... ......", "........ ..... ... ......", "....... ... .. ... ......", "....... . .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/9D.xpm0000644006717300001440000000220707005052041015121 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... . .......", "....... ... .. ... ......", "....... ... .. ... ......", "....... ... .. ... ......", "........ .. ... ......", "........... .. ... ......", ".......... ... ... ......", "....... ... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/2S.xpm0000644006717300001440000000220707005054145015140 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... ... ......", "....... ... .. ... ......", "....... ... .. ..........", ".......... ... ........", "......... ...... ......", "........ ......... ......", "....... ... .. ... ......", "....... .. .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/6C.xpm0000644006717300001440000000220707005052040015114 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "......... .............", "........ ...... .......", "....... ...... ... ......", "....... ...... ... ......", "....... ... ..........", "....... ... .. ..........", "....... ... .. ... ......", "....... ... .. ... ......", "........ .... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/fallbacktiles/9C.xpm0000644006717300001440000000220707005052041015120 0ustar jcbusers/* XPM */ static char *noname[] = { /* width height ncolors chars_per_pixel */ "25 35 2 1", /* colors */ " c #000", ". c #FFF", /* pixels */ ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........ ..............", "....... ... ... .......", "....... ... .. ... ......", "....... ... .. ... ......", "....... ... .. ..........", "........ .. ..........", "........... .. ... ......", ".......... ... ... ......", "....... ..... .......", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", ".........................", "........................." }; mj-1.17-src/client.c0000444006717300001440000001503515002771311012746 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/client.c,v 12.2 2020/05/30 17:27:02 jcb Exp $ * client.c * Provides generic client support. It connects to the controller, * and maintains a game data structure in response to the Controller * messages. After updating the game structure, it (will) invokes a callback * installed by its user. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include "sysdep.h" #include #include "client.h" static const char rcs_id[] UNUSED = "$Header: /home/jcb/MahJong/newmj/RCS/client.c,v 12.2 2020/05/30 17:27:02 jcb Exp $"; /* local for below two fns */ static Game *_client_init(char *address, int reinit, int oldfd) { int fd; int i; Game *g; if ( reinit ) { fd = oldfd; } else { if ( strcmp("-",address) == 0 ) { fd = STDOUT_FILENO; } else { fd = connect_to_host(address); if ( fd == (int)INVALID_SOCKET ) { perror("client_init: connect_to_host failed"); return 0; } } } /* alloc the structures */ g = (Game *) malloc(sizeof(Game)); if ( g == NULL ) { warn("Couldn't malloc game structure"); exit(1); } memset((void *)g,0,sizeof(Game)); for ( i=0 ; i < NUM_SEATS ; i++ ) { if ( (g->players[i] = (PlayerP) malloc(sizeof(Player))) == NULL ) { warn("couldn't malloc player structure"); exit(1); } memset((void *)g->players[i],0,sizeof(Player)); } g->fd = fd; if ( ! reinit ) g->cseqno = 0; return g; } /* client_init: establish a connection to a controller */ Game *client_init(char *address) { return _client_init(address,0,0); } /* client_reinit: as above, but use the existing fd or handle passed as an argument */ Game *client_reinit(int fd) { return _client_init(NULL,1,fd); } /* client_connect: take an id and a name, and send a connect message. Return 1 on success, or 0 on failure. */ int client_connect(Game *g, int id, char *name) { PMsgConnectMsg cm; cm.type = PMsgConnect; cm.pvers = PROTOCOL_VERSION; cm.last_id = id; cm.name = name; client_send_packet(g,(PMsgMsg *)&cm); /* stash info in our own player record */ assert(g->players[0]); initialize_player(g->players[0]); set_player_id(g->players[0],id); set_player_name(g->players[0],name); return 1; } /* internal for below */ static Game *_client_close(Game *g,int close) { int i; if ( close ) close_socket(g->fd); for ( i = 0; i < NUM_SEATS; i++ ) { set_player_name(g->players[i],NULL); free((void *)(g->players[i])); } free(g); return NULL; } /* close connection and free storage */ Game *client_close(Game *g) { return _client_close(g,1); } /* free storage, but don't actually close connection */ Game *client_close_keepconnection(Game *g) { return _client_close(g,0); } /* client_send_packet: send the given packet out on the game fd. Return sequence number of packet. */ int client_send_packet(Game *g, PMsgMsg *m) { char *l; if ( ! g ) { warn("client_send_packet: null game"); return 0; } l = encode_pmsg(m); if ( l == NULL ) { /* this shouldn't happen */ warn("client_send_packet: protocol conversion failed"); return 0; } if ( put_line(g->fd,l) < 0 ) { warn("client_send_packet: write failed"); /* maybe we should shutdown the descriptor here? */ return 0; } return ++g->cseqno; } /* see client.h for spec. */ TileSet *client_find_sets(PlayerP p, Tile d, int mj, PlayerP *pcopies, MJSpecialHandFlags flags) { static TileSet ts[14]; /* should be safe, but FIXME */ static Player copies[14]; TileSet *tsp = ts; Tile t; PlayerP cp = copies; char seen[MaxTile]; /* to note tile denominations already done */ int j; /* check the discard */ if ( d != HiddenTile ) { /* Can we kong? */ if ( !mj && player_can_kong(p,d) ) { tsp->type = Kong; tsp->tile = d; tsp++; cp++; } /* Can we pung it? */ copy_player(cp,p); if ( player_pungs(cp,d) && (!mj || player_can_mah_jong(cp,HiddenTile,flags)) ) { tsp->type = Pung; tsp->tile = d; tsp++; cp++; } /* Can we pair it? */ copy_player(cp,p); if ( player_pairs(cp,d) && (!mj || player_can_mah_jong(cp,HiddenTile,flags)) ) { tsp->type = Pair; tsp->tile = d; tsp++; cp++; } /* Can we chow it? */ for ( j = Lower; j <= Upper; j++ ) { copy_player(cp,p); if ( player_chows(cp,d,j) && (!mj || player_can_mah_jong(cp,HiddenTile,flags))) { tsp->type = Chow; tsp->tile = make_tile(suit_of(d),value_of(d)-j); tsp++; cp++; } } } else { /* no discard. Look for closed sets */ /* Because we should return pungs, then chows, then pairs, we have to go through the tiles three times, which is tedious. */ for (j=0; j < MaxTile; j++) seen[j] = 0; for (j=0; j < p->num_concealed; j++) { t = p->concealed[j]; if ( seen[t]++ ) continue; /* don't enter sets twice */ copy_player(cp,p); if ( player_forms_closed_pung(cp,t) && (!mj || player_can_mah_jong(cp,HiddenTile,flags))) { tsp->type = ClosedPung; tsp->tile = t; tsp++; cp++; } } for (j=0; j < MaxTile; j++) seen[j] = 0; for (j=0; j < p->num_concealed; j++) { t = p->concealed[j]; if ( seen[t]++ ) continue; /* don't enter sets twice */ copy_player(cp,p); if ( player_forms_closed_chow(cp,t,Lower) && (!mj || player_can_mah_jong(cp,HiddenTile,flags))) { tsp->type = ClosedChow; tsp->tile = t; tsp++; cp++; } } for (j=0; j < MaxTile; j++) seen[j] = 0; for (j=0; j < p->num_concealed; j++) { t = p->concealed[j]; if ( seen[t]++ ) continue; /* don't enter sets twice */ copy_player(cp,p); if ( player_forms_closed_pair(cp,t) && (!mj || player_can_mah_jong(cp,HiddenTile,flags))) { tsp->type = ClosedPair; tsp->tile = t; tsp++; cp++; } } } /* have we found anything? */ if ( tsp == ts ) return (TileSet *)0; tsp->type = Empty; if ( pcopies ) *pcopies = copies; return ts; } mj-1.17-src/mj-server.man0000444006717300001440000000015315002771311013726 0ustar jcbusers.\" $Header: /home/jcb/MahJong/newmj/RCS/mj-server.man,v 12.0 2009/06/28 20:43:12 jcb Rel $ .so man1/xmj.1 mj-1.17-src/Makefile.in0000444006717300001440000003435215002771311013374 0ustar jcbusers# $Header: /home/jcb/MahJong/newmj/RCS/Makefile.in,v 12.5 2025/04/23 20:22:44 jcb Exp $ # Master makefile for the xmj suite. ### NOTE NOTE NOTE ### This makefile uses some features specific to GNU make. ### If you do not have GNU make, you will need to adjust it. ### In particular, example Windows specific settings are included here ### by means of ifdef Win32 conditionals (where Win32 is a Makefile ### variable that can be set on the command line, for example). ### It should be easy to edit these out. ### As far as I know, the only other non-portable feature is the ### use of %-pattern rules, but they occur in many makes. # Options can be passed by setting variables from the command line: # pass Win32=1 to build with MinGW on Win32 without MSYS # pass Win32=2 to build with MinGW and MSYS # pass Win32=3 to cross-compile on Linux for MinGW # pass Warnings=1 to switch on almost all compiler warnings # The things you are most likely to change come first! # first target: don't change! all:: ### Local configuration options. Read and edit as necessary. # Gtk version - 2, or maybe one day 3 # if you're using GTK+ 2.n, for ANY n, set Gtk to be 2.0 . Gtk = 2.0 # This is not available in current public releases. # Uncomment the following line to build the table manager. # TableManager=1 # The directory in which to find the tile pixmaps that are # compiled into the program. Beware of licensing considerations. # (The tiles-v1 pixmaps may only be linked into GPLed programs.) FALLBACKTILES=./tiles-v1 # The directory in which to find tile pixmaps. # Defaults to NULL, causing the compiled-in tiles to be used. TILESET=NULL # The search path for finding tile pixmap directories. # Defaults to NULL, meaning current directory. TILESETPATH=NULL # Where to install the programs. # (Don't bother with this on Windows; I don't have an install target # for Windows.) # The binaries go into $(DESTDIR)$(BINDIR) DESTDIR = /usr/local/ BINDIR = bin # The man pages go into $(DESTDIR)$(MANDIR) MANDIR = man/man1 # and the appropriate suffix is 1 MANSUFFIX = 1 # if you are cross-compiling from Linux to Windows, # MGW should be the root of your MinGW hierarchy for the GTK libraries MGW=/home/jcb/MinGW ### End of local configuration. ### System dependent settings. # This section contains compiler settings etc. # If you think this reminds of you something, you're right: this # makefile was made by stripping out most of an imake-generated file. # It's best to use gcc if you can. CC = gcc # C debugging and optimization flags. # In development, we turn on all reasonable warnings. # However, in the production releases, we don't because other people's # code (e.g. glib) may provoke warnings. # Also, we do NOT enable optimization by default, because RedHat 6 # Linux distributions have a seriously buggy C compiler. # I thought the preceding comment should be deleted...but in 2009, # SuSE ships with a seriously buggy C compiler (gcc 4.3). So I'll # just leave optimization switched off. ifdef Warnings CDEBUGFLAGS = -g -Wall -W -Wstrict-prototypes -Wmissing-prototypes else CDEBUGFLAGS = -g endif # The -Wconversion flag is also useful to detect (more than usual) # abuse of enums, but it generates many superfluous warnings, so # is not on by default. # To check for non-ANSI usages, use -ansi; # to get all the ANSI-mandated warnings, use -pedantic, but # this is so pedantic it's not worth it. # Defines for the compilation. ifdef Win32 WINDEFINES = -DWIN32=1 endif ifeq ($(Win32),2) # This doesn't work if one tries to set these. Does anybody know # how to get a double quote through DOS or whatever to gcc ? DEFINES = -DTILESET=$(TILESET) -DTILESETPATH=$(TILESETPATH) $(WINDEFINES) else DEFINES = -DTILESET='$(TILESET)' -DTILESETPATH='$(TILESETPATH)' $(WINDEFINES) endif GTKDEFINES = -DGTK2=1 # Options for compiling. ifdef Win32 # this is suitable for recent mingw releases (the equivalent # of the old -fnative-struct) CCOPTIONS = -mms-bitfields endif # Extra libraries needed above whatever the system provides you with. # Things like -lnsl on Solaris are the most likely examples. # -lm now needed for greedy only ifdef Win32 # We need the winsock libraries LDLIBS = -lwsock32 -lm else # Red Hat and Mandrake (and probably most other) Linux systems # need nothing, as any needed libraries are brought in for gtk anyway. LDLIBS = -lm # If you are on Solaris, you may need the following: # LDLIBS = -lsocket -lnsl # If you are on HP-UX, you may need # LDLIBS = -lm endif # some systems have buggy malloc/realloc etc, so we may use # Doug Lea's implementation. This is switched on for Windows # at the moment ifdef Win32 MALLOCSRC = malloc.c MALLOCOBJ = malloc.o endif # The xmj program is built with gtk+, so it needs to know where the # libraries and headers are. ifdef Win32 ifeq ($(Win32),3) EXTRA_DEFINES=-DXCOMPILE=1 CC=i686-w64-mingw32-gcc EXTRA_INCLUDES=-I$(MGW)/include/glib-2.0 -I$(MGW)/include/gtk-2.0 -I$(MGW)/include/pango-1.0 -I$(MGW)/include/cairo -I$(MGW)/lib/glib-2.0/include -I$(MGW)/lib/gtk-2.0/include -I$(MGW)/include/gdk-pixbuf-2.0 -I$(MGW)/include/atk-1.0 GUILIBS=-L$(MGW)/lib -lgtk-win32-2.0 -lgdk-win32-2.0 -lgdk_pixbuf-2.0 -lglib-2.0 -lgobject-2.0 -mwindows else # should be the same as unix, if it can find pkg-config EXTRA_INCLUDES=$(shell pkg-config --cflags gtk+-$(Gtk)) # We also add the flag that makes xmj.exe be a GUI program GUILIBS=$(shell pkg-config --libs gtk+-$(Gtk)) -mwindows endif else # Not Windows. If gtk+ is properly installed, this is all that's needed. # except that gtk no longer pulls in -lm EXTRA_INCLUDES=`pkg-config --cflags gtk+-$(Gtk)` GUILIBS=`pkg-config --libs gtk+-$(Gtk)` -lm endif # We use gcc to link as well CCLINK = $(CC) # don't mess with these ALLINCLUDES = $(INCLUDES) $(EXTRA_INCLUDES) ALLDEFINES = $(ALLINCLUDES) $(EXTRA_DEFINES) $(DEFINES) $(GTKDEFINES) CFLAGS = $(EXTRA_CFLAGS) $(CDEBUGFLAGS) $(CCOPTIONS) $(ALLDEFINES) LDOPTIONS = $(CDEBUGFLAGS) $(CCOPTIONS) $(EXTRA_LDOPTIONS) # various auxiliary program and settings. ifeq ($(Win32),) # GNU make, we hope MAKE = make CP = cp -f RM = rm -f # makedep is a gcc-based makedepend that doesn't look at the # standard files. DEPEND = ./makedep DEPENDFLAGS = # perl is needed for generating several files PERL = perl # suffix of executables EXE = # programs used in installation MKDIRHIER = mkdir -p INSTALL = install INSTALLFLAGS = -c INSTPGMFLAGS = -s INSTMANFLAGS = -m 0444 else ifeq ($(Win32),1) # Windows with mingw but not msyste # GNU make, we hope MAKE = make CP = xcopy # suppress errors, since del complains if file not there to remove RM = -del /f # we don't have a makedepend on windows ... DEPEND = DEPENDFLAGS = # perl is needed for generating several files PERL = perl # suffix of executables EXE = .exe # programs used in installation: not in windows MKDIRHIER = INSTALL = INSTALLFLAGS = INSTPGMFLAGS = INSTMANFLAGS = else # windows with msys MAKE = make CP = cp -f RM = rm -f # we don't have a makedepend on windows ... DEPEND = DEPENDFLAGS = # perl is needed for generating several files PERL = perl # suffix of executables EXE = .exe # programs used in installation: not in windows MKDIRHIER = INSTALL = INSTALLFLAGS = INSTPGMFLAGS = INSTMANFLAGS = endif endif ### There should be no need to edit after this point, ### unless you're using a non-GNU make. all:: Makefile Makefile: Makefile.in $(RM) $@ $(CP) $< $@ make depend # Autogenerated files to do with the table manager ifdef TableManager TMAUTOGENS = enc_mcmsg.c enc_mpmsg.c dec_mcmsg.c dec_mpmsg.c mcmsg_union.h mcmsg_size.c mpmsg_union.h mpmsg_size.c mprotocol-enums.c mprotocol-enums.h else TMAUTOGENS = endif # This variable lists all the auto-generated program files. AUTOGENS = enc_cmsg.c enc_pmsg.c dec_cmsg.c dec_pmsg.c cmsg_union.h cmsg_size.c pmsg_union.h pmsg_size.c game-enums.c game-enums.h player-enums.c player-enums.h protocol-enums.c protocol-enums.h tiles-enums.c tiles-enums.h fbtiles.c version.h $(TMAUTOGENS) # This is a trick to make the auto-generated files be created # by make depend, if they're not already there. Otherwise the # make depend won't put them in the dependencies. depend:: $(AUTOGENS) echo Remade some auto-generated files # The programs ifdef TableManager PROGRAMS = xmj$(EXE) mj-server$(EXE) mj-player$(EXE) mj-manager$(EXE) else PROGRAMS = xmj$(EXE) mj-server$(EXE) mj-player$(EXE) endif all:: $(PROGRAMS) # The controller target. Requires player, tiles, protocol, sysdep SRCS1 = controller.c player.c tiles.c protocol.c game.c scoring.c sysdep.c $(MALLOCSRC) OBJS1 = controller.o player.o tiles.o protocol.o game.o scoring.o sysdep.o $(MALLOCOBJ) OBJS = $(OBJS1) $(OBJS2) $(OBJS3) $(OBJS4) SRCS = $(SRCS1) $(SRCS2) $(SRCS3) $(SRCS4) mj-server$(EXE): $(OBJS1) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) $(OBJS1) $(LDLIBS) $(EXTRA_LOAD_FLAGS) ifndef Win32 install:: mj-server$(EXE) @if [ -d $(DESTDIR)$(BINDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(BINDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTPGMFLAGS) mj-server$(EXE) $(DESTDIR)$(BINDIR)/mj-server install.man:: mj-server.man @if [ -d $(DESTDIR)$(MANDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(MANDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTMANFLAGS) mj-server.man $(DESTDIR)$(MANDIR)/mj-server.$(MANSUFFIX) endif # Win32 ifdef TableManager # The table manager. Requires mprotocol and sysdep SRCS4 = manager.c mprotocol.c sysdep.c $(MALLOCSRC) OBJS4 = manager.o mprotocol.o sysdep.o $(MALLOCOBJ) mj-manager$(EXE): $(OBJS4) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) $(OBJS4) $(LDLIBS) $(EXTRA_LOAD_FLAGS) ifndef Win32 install:: mj-manager$(EXE) @if [ -d $(DESTDIR)$(BINDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(BINDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTPGMFLAGS) mj-manager$(EXE) $(DESTDIR)$(BINDIR)/mj-manager install.man:: mj-manager.man @if [ -d $(DESTDIR)$(MANDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(MANDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTMANFLAGS) mj-manager.man $(DESTDIR)$(MANDIR)/mj-manager.$(MANSUFFIX) endif # Win32 endif # TableManager # the greedy player SRCS2 = greedy.c client.c player.c tiles.c protocol.c game.c sysdep.c $(MALLOCSRC) SRCS2A = client.c player.c tiles.c protocol.c game.c sysdep.c $(MALLOCSRC) OBJS2 = greedy.o client.o player.o tiles.o protocol.o game.o sysdep.o $(MALLOCOBJ) OBJS2A = client.o player.o tiles.o protocol.o game.o sysdep.o $(MALLOCOBJ) mj-player$(EXE): $(OBJS2) $(DEPLIBS2) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) $(OBJS2) $(LDLIBS) $(EXTRA_LOAD_FLAGS) ga: ga.o $(OBJS2A) $(DEPLIBS2) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) ga.o $(OBJS2A) $(LDLIBS) $(EXTRA_LOAD_FLAGS) # generic greedy variants greedy-%.o: greedy-%.c $(SRCS2A) greedy-%: greedy-%.o $(OBJS2A) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) $< $(OBJS2A) $(LDLIBS) $(EXTRA_LOAD_FLAGS) ifndef Win32 install:: mj-player$(EXE) @if [ -d $(DESTDIR)$(BINDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(BINDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTPGMFLAGS) mj-player$(EXE) $(DESTDIR)$(BINDIR)/mj-player install.man:: mj-player.man @if [ -d $(DESTDIR)$(MANDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(MANDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTMANFLAGS) mj-player.man $(DESTDIR)$(MANDIR)/mj-player.$(MANSUFFIX) endif # Win32 # the gui ifdef Win32 ifeq ($(Win32),3) WR = i686-w64-mingw32-windres else WR = windres endif # the icon resource file and object ICONOBJ = iconres.o iconres.o: iconres.rs icon.ico $(WR) iconres.rs iconres.o else ICONOBJ = endif SRCS3 = gui.c gui-dial.c lazyfixed.c vlazyfixed.c client.c player.c tiles.c fbtiles.c protocol.c game.c sysdep.c $(MALLOCSRC) OBJS3 = gui.o gui-dial.o client.o lazyfixed.o vlazyfixed.o player.o tiles.o fbtiles.o protocol.o game.o sysdep.o $(MALLOCOBJ) $(ICONOBJ) xmj$(EXE): $(OBJS3) $(DEPLIBS3) $(RM) $@ $(CCLINK) -o $@ $(LDOPTIONS) $(OBJS3) $(GUILIBS) $(LDLIBS) $(EXTRA_LOAD_FLAGS) install:: xmj$(EXE) @if [ -d $(DESTDIR)$(BINDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(BINDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTPGMFLAGS) xmj$(EXE) $(DESTDIR)$(BINDIR)/xmj install.man:: xmj.man @if [ -d $(DESTDIR)$(MANDIR) ]; then set +x; \ else (set -x; $(MKDIRHIER) $(DESTDIR)$(MANDIR)); fi $(INSTALL) $(INSTALLFLAGS) $(INSTMANFLAGS) xmj.man $(DESTDIR)$(MANDIR)/xmj.$(MANSUFFIX) # version.h is now made from version.h.in by the release script. # so if we are not making a release, we'd better provide it if # it isn't there. version.h: cp version.h.in $@ chmod u+w $@ # rule to generate the fallback tiles fbtiles.c: $(FALLBACKTILES) makefallbacktiles Makefile $(PERL) makefallbacktiles $(FALLBACKTILES) >fbtiles.c # Rules for the auto generated files enc_cmsg.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -cmsg enc_pmsg.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -pmsg dec_cmsg.c: protocol.h proto-decode-msg.pl $(PERL) proto-decode-msg.pl -cmsg dec_pmsg.c: protocol.h proto-decode-msg.pl $(PERL) proto-decode-msg.pl -pmsg # rules for the other auxiliary files # the commands will be executed twice, but never mind. cmsg_union.h cmsg_size.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -cmsg pmsg_union.h pmsg_size.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -pmsg enc_mcmsg.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -mcmsg enc_mpmsg.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -mpmsg dec_mcmsg.c: protocol.h proto-decode-msg.pl $(PERL) proto-decode-msg.pl -mcmsg dec_mpmsg.c: protocol.h proto-decode-msg.pl $(PERL) proto-decode-msg.pl -mpmsg # rules for the other auxiliary files # the commands will be executed twice, but never mind. mcmsg_union.h mcmsg_size.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -mcmsg mpmsg_union.h mpmsg_size.c: protocol.h proto-encode-msg.pl $(PERL) proto-encode-msg.pl -mpmsg # Rule for the enum parsing files: %-enums.h: %.h make-enums.pl $(PERL) make-enums.pl $< %-enums.c: %.h make-enums.pl $(PERL) make-enums.pl $< # distclean also removes the auto-generated files and the Makefile distclean:: clean $(RM) Makefile $(AUTOGENS) # used to build a source release textdocs:: use.txt rules.txt use.txt rules.txt: xmj.man $(RM) use.txt rules.txt $(PERL) maketxt xmj.man chmod 444 use.txt rules.txt depend:: $(DEPEND) $(DEPENDFLAGS) -- $(ALLDEFINES) $(DEPEND_DEFINES) -- $(SRCS) clean:: $(RM) $(PROGRAMS) clean:: $(RM) *.o core errs *~ mj-1.17-src/makefallbacktiles0000444006717300001440000000427215002771311014706 0ustar jcbusers#!/usr/local/bin/perl # $Header: /home/jcb/MahJong/newmj/RCS/makefallbacktiles,v 12.1 2025/04/25 20:16:15 jcb Exp $ #****************** COPYRIGHT STATEMENT ********************** #* This file is Copyright (c) 2000 by J. C. Bradfield. * #* Distribution and use is governed by the LICENCE file that * #* accompanies this file. * #* The moral rights of the author are asserted. * #* * #***************** DISCLAIMER OF WARRANTY ******************** #* This code is not warranted fit for any purpose. See the * #* LICENCE file for further information. * #* * #************************************************************* # take a directory of tiles, and # generate on stdout a module that exports a single symbol # fallbackpixmaps, of type **char[]. # the xpm pixmap for tile t is in entry t; # the ErrorTile is in entry 99; # the tong pixmaps are in entries 10[1234]. $dir = $ARGV[0]; if ( ! defined($dir) ) { print "char ***fallbackpixmaps = 0;\n"; exit; } if ( ! -d $dir ) { die("Not a directory."); } # read an xpm file and spit it out, changing the variable name # to pm_$i sub readit { my($file,$i) = @_; open(STDIN,"<$dir/$file.xpm") || die("Can't open $dir/$file"); while ( ) { s/\*\s*(\w*)\s*\[\]/\*pm_$i\[\]/; # change the type s/static char /static const char/; print; } # note it $vars[$i] = 1; } &readit('XX',99); &readit('--',0); $j = 1; foreach $s ( 'B', 'C', 'D' ) { for ( $i = 1; $i < 10; $i++ ) { &readit("$i$s",$j*10+$i); } $j++; } $i = 1; foreach $t ( 'E', 'S', 'W', 'N' ) { &readit("${t}W",40+$i); $i++; } $i = 1; foreach $t ( 'R', 'W', 'G' ) { &readit("${t}D",50+$i); $i++; } $j = 6; foreach $s ( 'F', 'S' ) { for ( $i = 1; $i < 5; $i++ ) { &readit("$i$s",$j*10+$i); } $j++; } &readit('tongE',101); &readit('tongS',102); &readit('tongW',103); &readit('tongN',104); # now define the external symbol print "const char **fallbackpixmaps[] = {\n"; for ( $i = 0 ; $i <= $#vars ; $i++ ) { print +($vars[$i] ? "pm_$i" : 0),", "; } print "\n};\n"; mj-1.17-src/gtkrc-plain0000444006717300001440000000265015002771311013461 0ustar jcbusersgtk-font-name = "Verdana Condensed 12px" style "default" { bg[NORMAL] = "#CCC" fg[NORMAL] = "#000" bg[PRELIGHT] = "#EEE" fg[PRELIGHT] = "#000" bg[ACTIVE] = "#AAA" fg[ACTIVE] = "#000" bg[SELECTED] = "#228" fg[SELECTED] = "#FFF" bg[INSENSITIVE] = "#888" fg[INSENSITIVE] = "#444" xthickness = 2 ythickness = 2 } widget "*" style "default" style "table" { bg[NORMAL] = "darkgreen" } style "playerlabel" { fg[NORMAL] = "white" } style "tile" { bg[NORMAL] = "white" fg[NORMAL] = "black" xthickness = 1 ythickness = 1 GtkButton::inner-border = { 0, 0, 0, 0 } } style "mytile" { bg[NORMAL] = "white" bg[PRELIGHT] = "yellow" bg[ACTIVE] = "magenta" GtkButton::inner-border = { 0, 0, 0, 0 } xthickness = 1 ythickness = 1 } style "claim" { bg[NORMAL] = "yellow" font_name = "Sans Bold 20px" } style "thinking" { fg[NORMAL] = "yellow" font_name = "Sans Bold 20px" } binding "topwindow" { bind "Left" { "selectleft"() } bind "Right" { "selectright"() } bind "Left" { "moveleft"() } bind "Right" { "moveright"() } } style "text" { font_name = "Courier New 16px" } widget "*.table" style "table" widget "*.tile" style "tile" widget "*.mytile" style "mytile" widget "*.claim" style "claim" widget "*.think" style "thinking" widget "topwindow" binding "topwindow" widget "*.GtkTextView*" style "text" widget "*.GtkEntry*" style "text" widget "*.playerlabel" style "playerlabel" mj-1.17-src/gui.c0000444006717300001440000050054215002771311012256 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/gui.c,v 12.34 2025/04/25 20:09:31 jcb Exp $ * gui.c * A graphical interface, using gtk. */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #include "gui.h" /* for a debugging hack */ #ifndef WIN32 #include #endif /* string constants for gtk setup */ #include "gtkrc.h" /* local forward declarations */ static AnimInfo *adjust_wall_loose(int tile_drawn); static void playerdisp_popup_claim_window(PlayerDisp *pd, const char *lab,int thinking); static gint check_claim_window(gpointer data); static void create_wall(void); static void clear_wall(void); static int get_relative_posn(GtkWidget *w, GtkWidget *z, int *x, int *y); static AnimInfo *playerdisp_discard(PlayerDisp *pd,Tile t); static void playerdisp_init(PlayerDisp *pd, int ori); static void playerdisp_update(PlayerDisp *pd, CMsgUnion *m); static int playerdisp_update_concealed(PlayerDisp *pd,Tile t); static void playerdisp_clear_discards(PlayerDisp *pd); static void iconify_window(GtkWidget *w); static void uniconify_window(GtkWidget *w); static int read_rcfile(char *rcfile); static void rotate_pixmap(GdkPixmap *east, GdkPixmap **south, GdkPixmap **west, GdkPixmap **north); static void change_tile_selection(GtkWidget *w,gpointer data); static void move_selected_tile(GtkWidget *w,gpointer data); static void server_input_callback(gpointer data, gint source, GdkInputCondition condition); static void start_animation(AnimInfo *source,AnimInfo *target); static void stdin_input_callback(gpointer data, gint source, GdkInputCondition condition); /* currently used only in this file */ void button_set_tile_without_tiletip(GtkWidget *b, Tile t, int ori); /* This global variable contains XPM data for fallback tiles. It is defined in fbtiles.c */ extern const char **fallbackpixmaps[]; /********************* GLOBAL VARIABLES *********************/ /* The search path for tile sets */ /* Confusing terminology: this is nothing to do with tilesets as in pungs. */ #ifndef TILESETPATH #define TILESETPATH "." #endif /* And the name of the tile set */ #ifndef TILESET #define TILESET NULL #endif /* separator character in search paths */ #ifdef WIN32 #define PATHSEP ';' #else #define PATHSEP ':' #endif /* the tileset search path given on the command line or via options. */ char *tileset_path = TILESETPATH; /* ditto for the tile set */ char *tileset = TILESET; static char *tilepixmapdir; /* where to find tiles */ int debug = 0; int server_pversion; PlayerP our_player; int our_id; seats our_seat; /* our seat in the game */ /* convert from a game seat to a position on our display board */ #define game_to_board(s) ((s+NUM_SEATS-our_seat)%NUM_SEATS) /* and conversely */ #define board_to_game(s) ((s+our_seat)%NUM_SEATS) /* convert from game wall position to board wall position */ #define wall_game_to_board(i) ((i + wallstart) % WALL_SIZE) /* and the other way */ #define wall_board_to_game(i) ((i + WALL_SIZE - wallstart)%WALL_SIZE) Game *the_game; int selected_button; /* the index of the user's selected tile, or -1 if none */ int monitor = 0; /* are we playing normally, or instrumenting somebody else? */ int game_over = 0; int closed_set_in_progress = 0; static int do_connect = 0; /* --connect given? */ /* tag for the server input callback */ static gint server_callback_tag = 0; static gint stdin_callback_tag = 0; /* and stdin */ /* This is a list of windows (i.e. GTK windows) that should be iconified along with the main window. It's null-terminated, for simplicity. The second element of each struct records whether the window is visible at the time the main window is iconified. */ static struct { GtkWidget **window; int visible; } windows_following_main[] = { {&textwindow,0}, {&messagewindow,0}, {&status_window,0}, /* these will only be relevant when they're standalone */ {&discard_dialog->widget,0}, {&chow_dialog,0}, {&ds_dialog,0}, {&continue_dialog,0}, {&turn_dialog,0}, {&scoring_dialog,0}, /* these should always be relevant */ {&open_dialog,0}, {&nag_window,0}, {&display_option_dialog,0}, {&game_option_dialog,0}, {&game_prefs_dialog,0}, {&playing_prefs_dialog,0}, {NULL,0} }; /* callback attached to [un]map_event */ static void vis_callback(GtkWidget *w UNUSED, GdkEvent *ev, gpointer data UNUSED) { if ( ev->type == GDK_MAP ) { int i; /* map previously open subwindows */ if ( iconify_dialogs_with_main ) { /* unfortunately, if we have a wm that raises on map, but waits for the map before raising, we will find that the main window is raised over the subwindows, which is probably not intended. So we sleep a little to try to wait for it. */ usleep(50000); for ( i = 0; windows_following_main[i].window ; i++ ) { GtkWidget *ww = *windows_following_main[i].window; if ( ww == NULL ) continue; /* not completely sure why this is needed, but it is */ if ( ! GDK_IS_WINDOW((gtk_widget_get_window(ww) )) ) continue; if ( windows_following_main[i].visible ) uniconify_window(ww); } } } else if ( ev->type == GDK_UNMAP ) { int i = 0; /* unmap any auxiliary windows that are open, and keep state */ if ( iconify_dialogs_with_main ) { for ( i = 0; windows_following_main[i].window ; i++ ) { GtkWidget *ww = *windows_following_main[i].window; if ( ww == NULL ) continue; if ( ! GDK_IS_WINDOW((gtk_widget_get_window(ww) )) ) continue; if ( ! (gdk_window_get_state(gtk_widget_get_window(ww)) && GDK_WINDOW_STATE_ICONIFIED) ) { iconify_window(ww); windows_following_main[i].visible = 1; } else { windows_following_main[i].visible = 0; } } } } else { warn("vis_callback called on unexpected event"); } } /* These are used as time stamps. now_time() is the time since program start measured in milliseconds. start_tv is the start time. */ static struct timeval start_tv; static int now_time(void) { struct timeval now_tv; gettimeofday(&now_tv,NULL); return (now_tv.tv_sec-start_tv.tv_sec)*1000 +(now_tv.tv_usec-start_tv.tv_usec)/1000; } int ptimeout = 15000; /* claim timeout time in milliseconds [global] */ int local_timeouts = 0; /* should we handle timeouts ourselves? */ /* gtk2 rc file to use instead of default */ char gtk2_rcfile[512]; /* whether to use system gtkrc */ int use_system_gtkrc = 0; /* These are the height and width of a tile (including the button border. The spacing is currently 1/4 the width of an actual tile pixmap. */ static int tile_height = 0; static int tile_width = 0; static int tile_spacing = 0; /* variable concerned with the display of the wall */ int showwall = -1; /* preference value for this */ int pref_showwall = -1; /* do the message and game info windows get wrapped into the main ? */ int info_windows_in_main = 0; /* preference values for robot names */ char robot_names[3][128]; /* and robot options */ char robot_options[3][128]; /* address of server */ char address[256] = "localhost:5000"; /* hack: we don't want to save in the rc file the address if we've been redirected */ static int redirected = 0; static char origaddress[256]; /* name of player */ char name[256] = "" ; /* robot difficulty level (0 .. 10) */ int difficulty = 10; /* name of the main system font (empty if use default) */ char main_font_name[256] = ""; /* and the "fixed" font used for text display */ char text_font_name[256] = ""; /* one that we believe to work */ char fallback_text_font_name[256] = ""; /* name of the colour of the table */ char table_colour_name[256] = ""; /* playing options */ int playing_auto_declare_specials = 0; int playing_auto_declare_losing = 0; int playing_auto_declare_winning = 0; /* local variable to note when we are in the middle of an auto-declare */ static int auto_declaring_special = 0; static int auto_declaring_hand = 0; /* to override the suppression of declaring specials dialog when we have a kong in hand */ static int may_declare_kong = 0; /* a count of completed games, for nagging purposes */ int completed_games = 0; int nag_state = 0; /* nag state */ time_t nag_date = 0; /* time of last nag, as a time(). We won't worry about the rollover! */ /* array of buttons for the tiles in the wall. button 0 is the top rightmost tile in our wall, and we count clockwise as the tiles are dealt. */ static GtkWidget *wall[MAX_WALL_SIZE]; /* This gives the wall size with a sensible value when there's no game or when there's no wall established */ #define WALL_SIZE ((the_game && the_game->wall.size) ? the_game->wall.size : MAX_WALL_SIZE) static int wallstart; /* where did the live wall start ? */ /* given a tile number in the wall, what orientation shd it have? */ #define wall_ori(n) (n < WALL_SIZE/4 ? 0 : n < 2*WALL_SIZE/4 ? 3 : n < 3*WALL_SIZE/4 ? 2 : 1) /* Pixmaps for tiles. We need one for each orientation. The reason for this slightly sick procedure is so that the error tile can also have a pixmap here, with the indexing working as expected */ static GdkPixmap *_tilepixmaps[4][MaxTile+1]; /* this is global */ GdkPixmap **tilepixmaps[4] = { &_tilepixmaps[0][1], &_tilepixmaps[1][1], &_tilepixmaps[2][1], &_tilepixmaps[3][1] }; /* pixmaps for tong box, and mask */ static GdkPixmap *tongpixmaps[4][4]; /* [ori,wind-1] */ static GdkBitmap *tongmask; GtkWidget *topwindow; /* main window */ /* used to remember the position of the top level window when recreating it */ static gint top_x, top_y, top_pos_set; GtkWidget *menubar; /* menubar */ GtkWidget *info_box; /* box to hold info windows */ GtkWidget *board; /* the table area itself */ GtkWidget *boardfixed; /* fixed widget enclosing boardframe */ GtkWidget *boardframe; /* event box widget wrapping the board */ GtkWidget *outerframe; /* outermost frame */ static GdkColor highlightcolor; GtkWidget *highlittile = NULL ; /* the unique highlighted discard */ GtkWidget *highlitinhand = NULL; /* the highlighted tile to show that that it's a player's turn */ GtkWidget *dialoglowerbox = NULL; /* encloses dialogs when dialogs are below */ GdkFont *main_font; GdkFont *fixed_font; /* a fixed width font */ GdkFont *big_font; /* big font for claim windows */ static GtkStyle *original_default_style; /* option table for our game preferences */ GameOptionTable prefs_table; /* This gives the width of a player display in units of tiles */ int pdispwidth = 0; int display_size = 0; int square_aspect = 0; /* force a square table */ int animate = 0; /* do fancy animation */ /* This variable determines whether animated tiles are done by moving a tile within the board, which is preferable since it means tiles don't mysteriously float above other higher windows; or by moving top-level popup windows. The only reason for the latter is that it seems to make the animation smoother under Windows. This variable is local and read-only now; it may become an option later. */ #ifdef WIN32 static int animate_with_popups = 1; #else static int animate_with_popups = 0; #endif int sort_tiles = SortAlways; /* when to sort tiles in our hand */ int iconify_dialogs_with_main = 1; int nopopups = 0; /* suppress popups */ int tiletips = 0; /* show tile tips all the time */ int rotatelabels = 1; /* rotate the player info labels on the table */ int thinking_claim = 0; /* show ... before players have noclaimed */ int alert_mahjong = 0; /* pop up notice when can mahjong */ DebugReports debug_reports = DebugReportsUnspecified; /* send debugging reports? */ /* record to keep copy of server input to add to debugging reports */ struct databuffer { char *data; /* malloced buffer */ int alloced; /* length of malloced buffer */ int length; /* length used (including termination null of strings) */ } server_history; static void databuffer_clear(struct databuffer *b) { if ( b->data ) free(b->data); b->data = NULL; b->alloced = 0; b->length = 0; } /* extend a databuffer to at least the given length. Returns 1 on success, 0 on failure */ static int databuffer_extend(struct databuffer *b,int new) { char *bnew; int anew; anew = b->alloced; if ( new <= anew ) return 1; if ( anew == 0 ) anew = 1024; while ( anew < new ) anew *= 2; bnew = realloc(b->data,anew); if ( bnew == NULL ) { warn("databuffer_extend: out of memory"); return 0; } b->data = bnew; b->alloced = anew; return 1; } /* returns 1 on success, 0 on failure. Assuming the databuffer is a null-terminated string (which is not checked), extend it by the argument string */ static int databuffer_add_string(struct databuffer *b, char *new) { int l; l = strlen(new); if ( ! databuffer_extend(b,b->length+l) ) return 0; if ( b->length == 0 ) { b->data[0] = 0; b->length = 1; } strcpy(b->data + b->length - 1, new); b->length += l; return 1; } /* the player display areas */ PlayerDisp pdisps[NUM_SEATS]; int calling = 0; /* disgusting global flag */ /* the widget in which we put discards */ GtkWidget *discard_area; /* This is an allocation structure in which we note its allocated size, at some point when we know the allocation is valid. */ GtkAllocation discard_area_alloc; /* This is used to keep a history of the discard actions in the current hand, so we can reposition the discards if we change size etc. in mid hand */ struct { int count; struct { PlayerDisp *pd ; Tile t ; } hist[2*MAX_WALL_SIZE]; } discard_history; GtkWidget *just_doubleclicked = NULL; /* yech yech yech. See doubleclicked */ /* space round edge of dialog boxes */ const gint dialog_border_width = 10; /* horiz space between buttons */ const gint dialog_button_spacing = 10; /* vert space between text and buttons etc */ const gint dialog_vert_spacing = 10; /* space around player boxes. N.B. this should only apply to the outermost boxes */ const gint player_border_width = 10; DialogPosition dialogs_position = DialogsUnspecified; int pass_stdin = 0; /* 1 if commands are to read from stdin and passed to the server. */ void usage(char *pname,char *msg) { fprintf(stderr,"%s: %s\nUsage: %s [ --id N ]\n\ [ --server ADDR ]\n\ [ --name NAME ]\n\ [ --connect ]\n\ [ --[no-]show-wall ]\n\ [ --size N ]\n\ [ --animate ]\n\ [ --tile-pixmap-dir DIR ]\n\ [ --dialogs-popup | --dialogs-below | --dialogs-central ]\n" " [ --gtk2-rcfile FILE ]\n" " [ --monitor ]\n\ [ --echo-server ]\n\ [ --pass-stdin ]\n", pname,msg,pname); } #ifdef WIN32 /* In Windows, we can't use sockets as they come, but have to convert them to glib channels. The following functions are passed to the sysdep.c initialize_sockets routine to do this. */ static void *skt_open_transform(SOCKET s) { GIOChannel *chan; chan = g_io_channel_unix_new(s); /* don't know what do if this fails */ if ( ! chan ) { warn("Couldn't convert socket to channel"); return NULL; } return (void *)chan; } static int skt_closer(void *chan) { g_io_channel_close((GIOChannel *)chan); g_io_channel_unref((GIOChannel *)chan); /* unref the ref at creation */ return 1; } static int skt_reader(void *chan,void *buf, size_t n) { int len = 0; int e; while ( 1 ) { e = g_io_channel_read((GIOChannel *)chan,buf,n,&len); if ( e == G_IO_ERROR_NONE ) /* fprintf(stderr,"g_io_channel_read returned %d bytes\r\n",len); */ return len; if ( e != G_IO_ERROR_AGAIN ) return -1; } } static int skt_writer(void *chan, const void *buf, size_t n) { int len = 0; if ( g_io_channel_write((GIOChannel *)chan, (void *)buf,n,&len) == G_IO_ERROR_NONE ) return len; else return -1; } /* a glib level wrapper for the server input callback */ static gboolean gcallback(GIOChannel *source UNUSED, GIOCondition condition UNUSED, gpointer data UNUSED) { server_input_callback(NULL,0,GDK_INPUT_READ); return 1; } #endif /* WIN32 */ /* this is a little hack to make it easy to provoke errors for testing */ #ifndef WIN32 static int signalled = 0; static void sighandler(int n UNUSED) { signalled = 1; } #endif /* used to keep the last line received from controller */ static char last_line[1024]; static char *log_msg_dump_state(LogLevel l) { static struct databuffer r; if ( l > LogWarning ) { int bytes_left; static char head[] = "Last cmsg: "; static char head1[] = "Server history:\n"; char *ret; ret = game_print_state(the_game,&bytes_left); databuffer_clear(&r); if ( ! databuffer_extend(&r,strlen(head) + strlen(last_line) + 2 + strlen(ret) + strlen(head1) + server_history.length + 1) ) { warn("Unable to build fault report"); return NULL; } databuffer_add_string(&r,head); databuffer_add_string(&r,last_line); databuffer_add_string(&r,"\n\n"); databuffer_add_string(&r,ret); databuffer_add_string(&r,head1); databuffer_add_string(&r,server_history.data); return r.data; } else { return NULL; } } int main (int argc, char *argv[]) { int i; /* we could potentially get SIGPIPE if we're passing server commands to stdout. We don't want to fail in that case. */ ignore_sigpipe(); /* install the warning hook for gui users */ log_msg_hook = log_msg_add; /* add a dump of the game state to errors */ log_msg_add_note_hook = log_msg_dump_state; /* we have to set up the default preferences table before reading the rcfile */ game_copy_option_table(&prefs_table,&game_default_optiontable); /* read rc file. We should have an option to suppress this */ read_rcfile(NULL); showwall = pref_showwall; /* options. I should use getopt ... */ for (i=1;ifd); strmcpy(last_line,(l ? l : "NULL LINE\n"),sizeof(last_line)); databuffer_add_string(&server_history,l ? l : "NULL LINE\n"); if ( debug && l) { put_line(1,l); } if ( ! l ) { if ( game_over ) { /* not an error - just stop listening */ control_server_processing(0); } else { fprintf(stderr,"receive error from controller\n"); error_dialog_popup("Lost connection to server"); close_connection(0); } return; } m = (CMsgUnion *)decode_cmsg(l); if ( ! m ) { warn("protocol error from controller: %s\n",l); return; } if ( m->type == CMsgConnectReply ) { our_id = ((CMsgConnectReplyMsg *)m)->id; /* need to set this again if this is a second connectreply in response to loadstate */ our_player = the_game->players[0]; if ( our_id == 0 ) { char s[1024]; char refmsg[] = "Server refused connection because: "; close_connection(0); strcpy(s,refmsg); strmcat(s,((CMsgConnectReplyMsg *)m)->reason,sizeof(s)-sizeof(refmsg)); error_dialog_popup(s); return; } else { PMsgSetPlayerOptionMsg spom; /* stash the server's protocol version */ server_pversion = m->connectreply.pvers; /* set our name again, in case we got deleted! */ set_player_name(our_player,name); if ( !monitor ) { /* since we have a human driving us, ask the controller to slow down a bit; especially if we're animating */ spom.type = PMsgSetPlayerOption; spom.option = PODelayTime; spom.ack = 0; spom.value = animate ? 10 : 5; /* 0.5 or 1 second min delay */ spom.text = NULL; send_packet(&spom); /* if the controller is likely to understand it, request that we may handle timeouts locally */ if ( m->connectreply.pvers >= 1036 ) { spom.option = POLocalTimeouts; spom.value = 1; send_packet(&spom); } } } } if ( m->type == CMsgReconnect ) { int fd = the_game->fd; close_connection(1); open_connection(0,(gpointer)3,fd,NULL); return; } if ( m->type == CMsgRedirect ) { char buf[256]; int id = our_id; close_connection(0); our_id = id; open_connection(0,(gpointer)4,0,((CMsgRedirectMsg *)m)->dest); sprintf(buf,"Server has redirected us to %.220s",address); info_dialog_popup(buf); return; } if ( m->type == CMsgAuthReqd ) { password_showraise(); return; } /* skip InfoTiles */ if ( m->type == CMsgInfoTiles ) return; /* error messages */ if ( m->type == CMsgError ) { error_dialog_popup(m->error.error); closed_set_in_progress = 0; /* better set up dialogs here */ setup_dialogs(); return; } /* player options */ if ( m->type == CMsgPlayerOptionSet ) { /* the only player option that is significant for us is LocalTimeouts */ if ( m->playeroptionset.option == POLocalTimeouts ) { local_timeouts = m->playeroptionset.value; if ( monitor ) return; if ( ! m->playeroptionset.ack ) { /* this was a command from the server -- acknowledge it */ PMsgSetPlayerOptionMsg spom; spom.type = PMsgSetPlayerOption; spom.option = POLocalTimeouts; spom.value = local_timeouts; spom.text = NULL; spom.ack = 1; send_packet(&spom); } } /* any other option we just ignore, since we have no corresponding state */ return; } id = game_handle_cmsg(the_game,(CMsgMsg *)m); /* it should not, I think be possible to get an error here. But if we do, let's report it and handle it */ if ( id < 0 ) { error("game_handle_cmsg returned %d; ignoring it",id); return; } calling = 0; /* awful global flag to detect calling */ /* This is the place where we take intelligent action in response to the game. As a matter of principle, we don't do this, but there is an exception: if there is a kong to be robbed, we will check to see whether we can actually go out with the tile, and if we can't, we'll send in NoClaim directly, rather than asking the user */ /* We can't assume that we know whether we can go mahjong. The server might allow special winning hands that we don't know about. So we have to ask the server whether we can go MJ. */ /* we might get the kong message as part of the replay. So we have to respond the first time we get a message after a kong and the game is active */ if ( !monitor && the_game->active && id != our_id && ! querykong && (the_game->state == Discarding || the_game->state == DeclaringSpecials) && the_game->konging && the_game->claims[our_seat] == UnknownClaim ) { PMsgQueryMahJongMsg qmjm; qmjm.type = PMsgQueryMahJong; qmjm.tile = the_game->tile; send_packet(&qmjm); querykong = 1; } /* and if this is the reply, send no claim if appropriate */ if ( !monitor && the_game->active && (the_game->state == Discarding || the_game->state == DeclaringSpecials) && the_game->konging && m->type == CMsgCanMahJong && ! m->canmahjong.answer) { disc_callback(NULL,(gpointer) NoClaim); /* sends the message */ the_game->claims[our_seat] = NoClaim; /* hack to stop the window coming up */ } if ( m->type == CMsgCanMahJong ) querykong = 0; status_update(m->type == CMsgGameOver); if ( id == our_id ) { /* do we want to sort the tiles? */ if ( sort_tiles == SortAlways || (sort_tiles == SortDeal && (the_game->state == Dealing || the_game->state == DeclaringSpecials ))) player_sort_tiles(our_player); } /* some more "intelligent action" comes now */ /* if we should be auto-declaring specials, deal with it */ if ( !monitor && playing_auto_declare_specials ) { if ( m->type == CMsgPlayerDeclaresSpecial && id == our_id ) { auto_declaring_special = 0; may_declare_kong = 0; } if ( the_game->active && ! the_game->paused && ( the_game->state == DeclaringSpecials || the_game->state == Discarding ) && the_game->needs == FromNone && the_game->player == our_seat && !auto_declaring_special ) { PMsgDeclareSpecialMsg pdsm; pdsm.type = PMsgDeclareSpecial; /* if we have a special, declare it. Now that we allow non-sorting, we have to look for specials */ /* if we're declaring specials, but have none, send the finished message */ assert(our_player->num_concealed > 0); int j; for (j=0;jnum_concealed && !is_special(our_player->concealed[j]);j++); if ( j < our_player->num_concealed ) { pdsm.tile = our_player->concealed[j]; send_packet(&pdsm); auto_declaring_special = 1; } else if ( the_game->state == DeclaringSpecials ) { /* First, we have to check whether we have a kong in hand, since then the human must have the option to declare it */ int i; for ( i = 0; i < our_player->num_concealed; i++ ) { if ( player_can_declare_closed_kong(our_player, our_player->concealed[i]) ) { may_declare_kong = 1; break; } } if ( ! may_declare_kong ) { pdsm.tile = HiddenTile; send_packet(&pdsm); auto_declaring_special = 1; } else { /* select the kong we found */ selected_button = i; conc_callback(pdisps[0].conc[i],(gpointer)(intptr_t)(i | 128)); } } } } /* if we should be auto-declaring hands, deal with it */ if ( !monitor && (playing_auto_declare_losing || playing_auto_declare_winning) ) { /* this is mind-numbingly tedious. There has to be a better way. */ if ( (m->type == CMsgPlayerPungs || m->type == CMsgPlayerFormsClosedPung || m->type == CMsgPlayerKongs || m->type == CMsgPlayerDeclaresClosedKong || m->type == CMsgPlayerChows || m->type == CMsgPlayerFormsClosedChow || m->type == CMsgPlayerPairs || m->type == CMsgPlayerFormsClosedPair || m->type == CMsgPlayerSpecialSet || m->type == CMsgPlayerFormsClosedSpecialSet ) && id == our_id ) auto_declaring_hand = 0; /* and to make sure it does get cleared */ if ( pflag(our_player,HandDeclared) ) auto_declaring_hand = 0; if ( !auto_declaring_hand && the_game->active && ! the_game->paused && ( the_game->state == MahJonging ) && !(the_game->mjpending && the_game->player != our_seat) && !pflag(our_player,HandDeclared) && ((the_game->player == our_seat && playing_auto_declare_winning) || (pflag(the_game->players[the_game->player],HandDeclared) && playing_auto_declare_losing) ) ) { TileSet *tsp; MJSpecialHandFlags mjspecflags; PMsgUnion m; mjspecflags = 0; if ( game_get_option_value(the_game,GOSevenPairs,NULL).optbool ) mjspecflags |= MJSevenPairs; /* OK, let's find a concealed set to declare */ auto_declaring_hand = 1; /* following code nicked from greedy.c */ /* get the list of possible decls */ tsp = client_find_sets(our_player, (the_game->player == our_seat && the_game->mjpending) ? the_game->tile : HiddenTile, the_game->player == our_seat, (PlayerP *)0,mjspecflags); if ( !tsp && our_player->num_concealed > 0 ) { /* Can't find a set to declare, and still have concealed tiles left. Either we're a loser, or we have Thirteen Orphans (or some other special hand). */ if ( the_game->player == our_seat ) { if ( the_game->mjpending ) { m.type = PMsgSpecialSet; } else { m.type = PMsgFormClosedSpecialSet; } } else { m.type = PMsgShowTiles; } send_packet(&m); } else { /* just do the first one */ switch ( tsp->type ) { case Kong: /* we can't declare a kong now, so declare the pung instead */ case Pung: m.type = PMsgPung; m.pung.discard = 0; break; case Chow: m.type = PMsgChow; m.chow.discard = 0; m.chow.cpos = the_game->tile - tsp->tile; break; case Pair: m.type = PMsgPair; break; case ClosedPung: m.type = PMsgFormClosedPung; m.formclosedpung.tile = tsp->tile; break; case ClosedChow: m.type = PMsgFormClosedChow; m.formclosedchow.tile = tsp->tile; break; case ClosedPair: m.type = PMsgFormClosedPair; m.formclosedpair.tile = tsp->tile; break; case Empty: /* can't happen, just to suppress warning */ case ClosedKong: /* ditto */ ; } send_packet(&m); } } } /* now deal with the display. There are basically two sorts of things to do: some triggered by a message, such as displaying draws, claims, etc; and some depending only on game state, such as making sure the right dialogs are up. We'll deal with message-dependent things first. */ switch ( m->type ) { case CMsgError: /* this has been dealt with above, and cannot happen */ warn("Error message in impossible place"); break; case CMsgAuthReqd: /* this has been dealt with above, and cannot happen */ warn("AuthReqd message in impossible place"); break; case CMsgRedirect: /* this has been dealt with above, and cannot happen */ warn("Redirect message in impossible place"); break; case CMsgPlayerOptionSet: /* this has been dealt with above, and cannot happen */ warn("PlayerOptionSet message in impossible place"); break; case CMsgStopPlay: { char buf[512]; char suspmsg[] = "Server has suspended play because:\n"; strcpy(buf,suspmsg); strmcat(buf,(m->stopplay.reason),sizeof(buf)-sizeof(suspmsg)); error_dialog_popup(buf); } break; case CMsgGameOption: /* update the timeout value */ if ( m->gameoption.optentry.option == GOTimeout ) { ptimeout = 1000*m->gameoption.optentry.value.optint; } /* if this is the Flowers option, recreate the declaring specials dialog */ if ( m->gameoption.optentry.option == GOFlowers ) { ds_dialog_init(); } /* and refresh the game option panel */ game_option_panel = build_or_refresh_option_panel(&the_game->option_table,game_option_panel); break; case CMsgGame: /* now we know the seating order: make the userdata field of each player point to the appropriate pdisp */ our_seat = game_id_to_seat(the_game,our_id); for ( i = 0; i < NUM_SEATS; i++ ) { set_player_userdata(the_game->players[i], (void *)&pdisps[game_to_board(i)]); /* and make the pdisp point to the player */ pdisps[game_to_board(i)].player = the_game->players[i]; } /* zap the game option dialog, because the option table has been reset */ game_option_panel = build_or_refresh_option_panel(NULL,game_option_panel); /* ask what the current options are */ if ( ! monitor ) { PMsgListGameOptionsMsg plgom; plgom.type = PMsgListGameOptions; plgom.include_disabled = 0; send_packet(&plgom); } /* and enable the menu item */ gtk_widget_set_sensitive(gameoptionsmenuentry,1); /* set the info on the scoring window */ for ( i=0; i < 4; i++ ) { gtk_label_set_text(GTK_LABEL(textlabels[i]), the_game->players[board_to_game(i)]->name); } break; case CMsgNewRound: /* update seat when it might change */ our_seat = game_id_to_seat(the_game,our_id); break; case CMsgNewHand: /* update seat when it might change */ our_seat = game_id_to_seat(the_game,our_id); /* After a new hand message, clear the display */ for ( i=0; i < 4; i++ ) { playerdisp_update(&pdisps[i],m); } discard_history.count = 0; /* need to set wallstart even if not showing wall, in case we change options */ wallstart = m->newhand.start_wall*(the_game->wall.size/4) + m->newhand.start_stack*2; gextras(the_game)->orig_live_end = the_game->wall.live_end; if ( showwall ) { create_wall(); adjust_wall_loose(the_game->wall.dead_end); /* set the loose tiles in place */ } break; case CMsgPlayerDiscards: /* if calling declaration, set flag */ if ( m->playerdiscards.calling ) calling = 1; /* and then update */ // fall through case CMsgPlayerFormsClosedPung: case CMsgPlayerDeclaresClosedKong: case CMsgPlayerAddsToPung: case CMsgPlayerFormsClosedChow: case CMsgPlayerFormsClosedPair: case CMsgPlayerShowsTiles: case CMsgPlayerFormsClosedSpecialSet: case CMsgPlayerDraws: case CMsgPlayerDrawsLoose: case CMsgPlayerDeclaresSpecial: case CMsgPlayerPairs: case CMsgPlayerChows: case CMsgPlayerPungs: case CMsgPlayerKongs: case CMsgPlayerSpecialSet: case CMsgPlayerClaimsPung: case CMsgPlayerClaimsKong: case CMsgPlayerClaimsChow: case CMsgPlayerClaimsMahJong: case CMsgSwapTile: { PlayerP p; p = game_id_to_player(the_game,id); if ( p == NULL ) { error("gui.c error 1: no player found for id %d\n",id); } else { if ( p->userdata == NULL ) { error("gui.c error 2: no player disp for player id %d\n",id); } else { playerdisp_update((PlayerDisp *)p->userdata,m); } } } break; case CMsgGameOver: /* we don't close the connection, we pop up a box to do it */ game_over = 1; end_dialog_popup(); status_showraise(); /* increment and save the game count */ if ( completed_games >= 0 ) { completed_games++; read_or_update_rcfile(NULL,XmjrcNone,XmjrcAll); } if ( nag_state == 0 && completed_games % 10 == 0 && time(NULL) - nag_date >= 30*24*3600) { nag_date = time(NULL); nag_popup(); } return; /* in the following case, we need do nothing (in this switch) */ case CMsgInfoTiles: case CMsgConnectReply: case CMsgReconnect: case CMsgPlayer: case CMsgPause: case CMsgPlayerReady: case CMsgClaimDenied: case CMsgDangerousDiscard: case CMsgCanMahJong: case CMsgHandScore: case CMsgSettlement: case CMsgChangeManager: case CMsgWall: case CMsgComment: break; case CMsgPlayerDoesntClaim: /* there may be a thinking window up, so hide it */ { PlayerP p; p = game_id_to_player(the_game,id); if ( p == NULL ) { error("gui.c error 1: no player found for id %d\n",id); } else { if ( p->userdata == NULL ) { error("gui.c error 2: no player disp for player id %d\n",id); } else { PlayerDisp *pd = (PlayerDisp *)p->userdata; if ( pd->thinkbox ) gtk_widget_hide(pd->thinkbox); } } } break; case CMsgStateSaved: { static char buf[512]; char savmsg[] = "Game state saved in "; strcpy(buf,savmsg); strmcat(buf,m->statesaved.filename,sizeof(buf)-sizeof(savmsg)-2); info_dialog_popup(buf); break; } case CMsgStartPlay: /* If this is a new game, try to apply our option preferences */ if ( the_game->state == HandComplete && the_game->round == EastWind && the_game->players[east]->id == the_game->firsteast && the_game->hands_as_east == 0 && (the_game->manager == 0 || the_game->manager == our_id ) ) apply_game_prefs(); break; case CMsgMessage: { GtkTextBuffer *messagetextbuf; GtkTextIter messagetextiter; GtkTextMark *messagemark; messagetextbuf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (messagetext)); gtk_text_buffer_get_end_iter(messagetextbuf, &messagetextiter); messagemark = gtk_text_buffer_create_mark(messagetextbuf, "end",&messagetextiter,FALSE); gtk_text_buffer_insert(messagetextbuf,&messagetextiter, "\n",-1); gtk_text_buffer_get_end_iter(messagetextbuf, &messagetextiter); gtk_text_buffer_insert(messagetextbuf,&messagetextiter, m->message.sender == 0 ? "CONTROLLER" : game_id_to_player(the_game,m->message.sender)->name,-1); gtk_text_buffer_get_end_iter(messagetextbuf, &messagetextiter); gtk_text_buffer_insert(messagetextbuf,&messagetextiter, "> ",-1); gtk_text_buffer_get_end_iter(messagetextbuf, &messagetextiter); if ( m->message.text ) { gtk_text_buffer_insert(messagetextbuf,&messagetextiter, m->message.text,-1); } gtk_text_buffer_get_end_iter(messagetextbuf, &messagetextiter); gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(messagetext), messagemark); gtk_text_buffer_delete_mark(messagetextbuf,messagemark); if ( !nopopups && !info_windows_in_main) { showraise(messagewindow); } } break; case CMsgPlayerRobsKong: /* we need to update the victim */ playerdisp_update(&pdisps[game_to_board(the_game->supplier)],m); /* and now fall through and do the mah-jong stuff, since the protocol does not issue a MahJongs in addition */ // fall through case CMsgPlayerMahJongs: /* this could be from hand. Clear all the texts in the scoring displays. Unselect tiles (if we're about to claim the discard, it's visually confusing seeing the first tile get selected; so instead, we'll select the first tile when we pop up the "declare closed sets" dialog. */ { int i; if ( the_game->player == our_seat ) conc_callback(NULL,NULL); for (i=0;i<5;i++) gtk_text_buffer_set_text(GTK_TEXT_BUFFER(gtk_text_view_get_buffer(GTK_TEXT_VIEW(textpages[i]))),"",0); } playerdisp_update((PlayerDisp *)game_id_to_player(the_game,id)->userdata,m); break; case CMsgWashOut: /* display a message in every window of the scoring info */ { CMsgWashOutMsg *wom = (CMsgWashOutMsg *)m; int i; char text[1024] = "This hand is a WASH-OUT:\n"; text[1023] = 0; if ( wom->reason ) { strmcat(text,wom->reason,1023-strlen(text)); } for (i=0;i<5;i++) { GtkTextBuffer *g = GTK_TEXT_BUFFER(gtk_text_view_get_buffer(GTK_TEXT_VIEW(textpages[i]))); gtk_text_buffer_set_text(g,"",0); gtk_text_buffer_insert_at_cursor(g, text,strlen(text)); } if (!nopopups) { showraise(textwindow); //gdk_window_show(textwindow->window); /* uniconify */ uniconify_window(textwindow); } } } /* if the message was an error, or a closed set formation message about us, clear the closed_set_in_progress flag. The error case is dealt with above, since our error handling returns immediately. */ { int mid = -99; switch ( m->type ) { case CMsgPlayerFormsClosedSpecialSet: mid = ((CMsgPlayerFormsClosedSpecialSetMsg *)m)->id; break; case CMsgPlayerFormsClosedPair: mid = ((CMsgPlayerFormsClosedPairMsg *)m)->id; break; case CMsgPlayerFormsClosedPung: mid = ((CMsgPlayerFormsClosedPungMsg *)m)->id; break; case CMsgPlayerFormsClosedChow: mid = ((CMsgPlayerFormsClosedChowMsg *)m)->id; break; case CMsgPlayerShowsTiles: mid = ((CMsgPlayerShowsTilesMsg *)m)->id; break; default: ; } if ( mid == our_id ) { closed_set_in_progress = 0; } } /* check the state of all the dialogs */ /* As a special case, if the message was a Message, we won't check the dialog state, since that forces focus to the control dialogs. If we send a message from a chat window incorporated into the main table, we probably do not expect to lose focus when our message arrives */ if ( m->type != CMsgMessage ) { setup_dialogs(); } /* after a HandScore message, stick the text in the display */ if ( m->type == CMsgHandScore || m->type == CMsgSettlement ) { static char bigbuf[8192]; CMsgHandScoreMsg *hsm = (CMsgHandScoreMsg *)m; CMsgSettlementMsg *sm = (CMsgSettlementMsg *)m; int pos; char *text; if ( m->type == CMsgHandScore ) { pos = (game_id_to_seat(the_game,hsm->id)+NUM_SEATS-our_seat)%NUM_SEATS; text = hsm->explanation; } else { pos = 4; text = sm->explanation; } expand_protocol_text(bigbuf,text,8192); gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(gtk_text_view_get_buffer(GTK_TEXT_VIEW(textpages[pos]))), bigbuf,strlen(bigbuf)); if ( m->type == CMsgSettlement ) { if ( ! nopopups ) { /* Since we have our own player selected in the notebook, when this pop up, it takes its size from the first page. This is usually wrong. So find the size requests of each page, and make page 0 ask for the maximum */ GtkRequisition r; int i; int w=-1,h=-1; GtkAllocation a = { 0, 0, 2000, 2000 }; gtk_widget_size_allocate(scoring_notebook,&a); for (i=0; i < 5; i++) { gtk_widget_size_request(textpages[i],&r); if ( r.width > w && r.width > 0 ) w = r.width; if ( r.height > h && r.height > 0 ) h = r.height; } if ( h > 0 ) h += 10; /* something not quite right */ gtk_widget_set_size_request(textpages[0],w,h); gtk_notebook_set_current_page(GTK_NOTEBOOK(scoring_notebook),0); if ( ! nopopups ) { showraise(textwindow); uniconify_window(textwindow); } } } } /* update the scoring history after a Game or Settlement message */ if ( m->type == CMsgGame || m->type == CMsgSettlement ) { GtkTextBuffer *g = GTK_TEXT_BUFFER(gtk_text_view_get_buffer(GTK_TEXT_VIEW(scorehistorytext))); static GtkTextTag *tag = NULL; if ( tag == NULL ) { tag = gtk_text_buffer_create_tag(g, "underline","underline",PANGO_UNDERLINE_SINGLE,NULL); } GtkTextIter iter; gtk_text_buffer_get_end_iter(g,&iter); gtk_text_buffer_place_cursor(g,&iter); char buf[80]; int i; static TileWind lastround = UnknownWind; if ( m->type == CMsgGame ) { /* clear it */ gtk_text_buffer_set_text(g,"",0); /* build a line with the player names (in board order), marking original east with @ */ strcpy(buf," "); for (i=0; i < NUM_SEATS; i++) { PlayerP p = the_game->players[board_to_game(i)]; sprintf(1+buf+16*i,"%13.13s%c%s",p->name, (p->id == the_game->firsteast ? '@' : ' '), (i == NUM_SEATS-1 ? "\n" : " ")); } #define ADDBUF gtk_text_buffer_insert_at_cursor(g,buf,strlen(buf)) ADDBUF; /* now the initial scores */ strcpy(buf," "); for (i=0; i < NUM_SEATS; i++) { PlayerP p = the_game->players[board_to_game(i)]; sprintf(1+buf+16*i,"%13d %s",p->cumulative_score, (i == NUM_SEATS-1 ? "\n" : " ")); } ADDBUF; /* and state the round */ sprintf(buf,"%s round\n",windnames[lastround = the_game->round]); ADDBUF; } else { /* for settlement, display the handscores and the change, and then the scores. Underline the changes. */ int chg[NUM_SEATS]; CMsgSettlementMsg *sm = (CMsgSettlementMsg *)m; chg[east] = sm->east; chg[south] = sm->south; chg[west] = sm->west; chg[north] = sm->north; if ( lastround != the_game->round ) { sprintf(buf,"%s round\n",windnames[lastround = the_game->round]); ADDBUF; } for (i=0; i < NUM_SEATS; i++) { PlayerP p = the_game->players[board_to_game(i)]; sprintf(buf,"%s%s%c", (i == 0 ? " " : ""), shortwindnames[p->wind], (the_game->player == board_to_game(i) ? '*' : ' ')); ADDBUF; int len; sprintf(buf,"%d",p->hand_score); len = strlen(buf); sprintf(buf,"%*s(%d)",4-len,"",p->hand_score); ADDBUF; sprintf(buf,"%+5d",chg[board_to_game(i)]); GtkTextIter iter; gtk_text_buffer_get_end_iter(g,&iter); gtk_text_buffer_insert_with_tags(g,&iter, buf,strlen(buf),tag,NULL); gtk_text_buffer_get_end_iter(g,&iter); gtk_text_buffer_place_cursor(g,&iter); strcpy(buf,(i < NUM_SEATS-1 ? " " : " \n")); ADDBUF; } /* and now the cumulative scores again */ strcpy(buf," "); for (i=0; i < NUM_SEATS; i++) { PlayerP p = the_game->players[board_to_game(i)]; sprintf(1+buf+16*i,"%13d %s",p->cumulative_score, (i == NUM_SEATS-1 ? "\n" : " ")); } ADDBUF; } /* force window to scroll to end */ static GtkTextMark *mark = NULL; if ( mark == NULL ) { GtkTextIter eiter; gtk_text_buffer_get_end_iter(g,&eiter); mark = gtk_text_buffer_create_mark(g,"end",&eiter,FALSE); } gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(scorehistorytext), mark, 0.0, TRUE, 0.0, 0.0); } } /* setup the dialogs appropriate to the game state */ void setup_dialogs(void) { if ( ! the_game ) { gtk_widget_hide(discard_dialog->widget); /* that might have been left up */ return; } if ( game_over ) { end_dialog_popup(); return; } /* in the discarded state, the dialog box should be up if somebody else discarded and we haven't claimed. If we've claimed, it should be down. Further, if we have a chowclaim in and the chowpending flag is set, the chow dialog should be up. */ if ( the_game->state == Discarded && the_game->player != our_seat ) { int s; /* which player display is it? */ s = the_game->player; /* seat */ s = (s+NUM_SEATS-our_seat)%NUM_SEATS; /* posn relative to us */ if ( the_game->active && !the_game->paused && the_game->claims[our_seat] == UnknownClaim ) discard_dialog_popup(the_game->tile,s,0); else gtk_widget_hide(discard_dialog->widget); if ( the_game->active && !the_game->paused && the_game->claims[our_seat] == ChowClaim && the_game->chowpending ) do_chow(NULL,(gpointer)AnyPos); } /* check whether to pop down the claim windows */ { int i; for (i=0;i<4;i++) { if ( ! pdisps[i].claimw || (! GTK_WIDGET_VISIBLE(pdisps[i].claimw) && ! GTK_WIDGET_VISIBLE(pdisps[i].thinkbox) ) ) continue; check_claim_window(&pdisps[i]); } } /* in discarded or mahjonging states, the turn dialog should be down */ if ( the_game->state == Discarded || the_game->state == MahJonging ) gtk_widget_hide(turn_dialog); /* in the discarding state, make sure the chow and ds dialogs are down. If it's our turn, the turn dialog should be up (unless we are in a konging state, when we're waiting for other claims), otherwise down. If there's a kong in progress, and we haven't claimed, the discard dialog should be up */ if ( the_game->state == Discarding ) { gtk_widget_hide(chow_dialog); gtk_widget_hide(ds_dialog); if ( the_game->active && !the_game->paused && the_game->player == our_seat && ! the_game->konging ) turn_dialog_popup(); else gtk_widget_hide(turn_dialog); if ( the_game->active && !the_game->paused && the_game->player != our_seat && the_game->konging && ! querykong /* wait till we know whether we can claim */ && the_game->claims[our_seat] == UnknownClaim ) { seats s; /* which player display is it? */ s = the_game->player; /* seat */ s = (s+NUM_SEATS-our_seat)%NUM_SEATS; /* posn relative to us */ discard_dialog_popup(the_game->tile,s,2); } else gtk_widget_hide(discard_dialog->widget); } /* If it is declaring specials time, and our turn, make sure the ds dialog is up. If it's not our turn, make sure it's down. Also make sure the turn and discard dialogs are down If somebody else has a kong in progress, and we haven't claimed, then the claim box should be up. */ if ( the_game->active && !the_game->paused && the_game->state == DeclaringSpecials ) { if ( the_game->player == our_seat ) { if ( !playing_auto_declare_specials || may_declare_kong ) ds_dialog_popup(); } else { gtk_widget_hide(ds_dialog); } gtk_widget_hide(turn_dialog); if ( the_game->konging && the_game->player != our_seat && the_game->claims[our_seat] == UnknownClaim ) { seats s; /* which player display is it? */ s = the_game->player; /* seat */ s = (s+NUM_SEATS-our_seat)%NUM_SEATS; /* posn relative to us */ discard_dialog_popup(the_game->tile,s,2); } else gtk_widget_hide(discard_dialog->widget); } else gtk_widget_hide(ds_dialog); /* In the mahjonging state, if we're the winner: If the pending flag is set, pop up the discard dialog if the chowpending is not set, otherwise the chowdialog. Otherwise, pop down both it and the chow dialog. Pop down the turn dialog. */ if ( the_game->state == MahJonging && the_game->player == our_seat ) { gtk_widget_hide(turn_dialog); if ( the_game->active && !the_game->paused && the_game->mjpending ) { int s; /* which player display is it? */ s = the_game->supplier; s = (s+NUM_SEATS-our_seat)%NUM_SEATS; /* posn relative to us */ if ( the_game->chowpending ) { do_chow(NULL,(gpointer)AnyPos); } else { discard_dialog_popup(the_game->tile,s,1); } } else { gtk_widget_hide(discard_dialog->widget); gtk_widget_hide(chow_dialog); } } /* In the mahjonging state, if our hand is not declared, and the pending flag is not set, and if either we are the winner or the winner's hand is declared popup the scoring dialog, otherwise down */ if ( !auto_declaring_hand && the_game->active && !the_game->paused && the_game->state == MahJonging && !the_game->mjpending && !pflag(our_player,HandDeclared) && (the_game->player == our_seat || pflag(the_game->players[the_game->player],HandDeclared) ) ) { /* if we don't have a tile selected and we're the winner, select the first */ if ( selected_button < 0 && the_game->player == our_seat ) conc_callback(pdisps[0].conc[0],(gpointer)(0 | 128)); scoring_dialog_popup(); /* to reduce confusing, make it insensitive if we've made a request and are waiting for the reply */ gtk_widget_set_sensitive(scoring_dialog,!closed_set_in_progress); } else { gtk_widget_hide(scoring_dialog); } /* If the game is not active, or active but paused, pop up the continue dialog */ if ( (!the_game->active) || the_game->paused ) continue_dialog_popup(); else gtk_widget_hide(continue_dialog); /* make sure the discard dialog (or turn) is down */ if ( the_game->state == HandComplete ) { gtk_widget_hide(discard_dialog->widget); gtk_widget_hide(turn_dialog); } } static void stdin_input_callback(gpointer data UNUSED, gint source, GdkInputCondition condition UNUSED) { char *l; l = get_line(source); if ( l ) { put_line(the_game->fd,l); /* we're bypassing the client routines, so we must update the sequence number ourselves */ the_game->cseqno++; } } /* functions to achieve moving tiles around by dragging */ static int drag_tile = -1; /* index of tile being dragged */ /* call back when a drag starts */ static void drag_begin_callback(GtkWidget *w UNUSED, GdkDragContext *c UNUSED, gpointer u) { drag_tile = (int)(intptr_t)u; } /* motion callback */ static gboolean drag_motion_callback(GtkWidget *w UNUSED, GdkDragContext *c, gint x UNUSED, gint y UNUSED, guint ts, gpointer u UNUSED) { gdk_drag_status(c,c->suggested_action,ts); return TRUE; } /* call back when a drop occurs */ static gboolean drag_drop_callback(GtkWidget *w UNUSED, GdkDragContext *c, gint x, gint y UNUSED, guint ts, gpointer u) { int new = (int)(intptr_t)u; /* if the tile is moving right, and the drop is in the left half, the new position is one less than here, otherwise it's here. If the tile is moving left, and the drop is in the left half, the new position is here, otherwise one more than here */ if ( new == drag_tile ) return FALSE; gtk_drag_finish(c,TRUE,FALSE,ts); if ( new > drag_tile ) { if ( x < tile_width/2 ) new--; assert(new >= 0); } else { if ( x > tile_width/2 ) new++; assert(new < our_player->num_concealed); } player_reorder_tile(our_player,drag_tile,new); playerdisp_update_concealed(&pdisps[0],HiddenTile); /* clear selection to avoid confusion */ conc_callback(NULL,NULL); return TRUE; } /* given an already allocated PlayerDisp* and an orientation, initialize a player display. The player widget is NOT shown, but everything else (appropriate) is */ static void playerdisp_init(PlayerDisp *pd, int ori) { /* here is the widget hierarchy we want: w -- the parent widget concealed -- the concealed row csets[MAX_TILESETS] -- five boxes which will have tiles packed in them conc -- the concealed & special tile subbox, with... conc[14] -- 14 tile buttons extras[8] -- more spaces for specials if we run out. pdispwidth-14 are initially set, to get the spacing. exposed -- the exposed row esets[MAX_TILESETS] -- with sets, each of which has tile buttons as appropriate. specbox -- spec[8] -- places for specials (Not all of these can always be used; so we may need to balance dynamically between this and extras.) tongbox -- the tong box pixmap widget */ GtkWidget *concealed, *exposed, *conc, *c, *b, *pm, *w,*specbox; int i; int tb,br,bl; int eori; /* orientation of exposed tiles, if different */ /* pd->player = NULL; */ /* don't do this: playerdisp_init is also called when rebuilding the display, in which case we shouldn't null the player. The player field should be nulled when the PlayerDisp is created */ pd->orientation = ori; eori = flipori(ori); /* sometimes it's enough to know whether the player is top/bottom, or the left/right */ tb = ! (ori%2); /* and sometimes the bottom and right players are similar (they are to the increasing x/y of their tiles */ br = (ori < 2); /* and sometimes the bottom and left are similar (their right is at the positive and of the box */ bl = (ori == 0 || ori == 3); /* homogeneous to force exposed row to right height */ if ( tb ) w = gtk_vbox_new(1,tile_spacing); else w = gtk_hbox_new(1,tile_spacing); gtk_widget_set_name(w,"table"); pd->widget = w; gtk_container_set_border_width(GTK_CONTAINER(w),player_border_width); if ( tb ) concealed = gtk_hbox_new(0,tile_spacing); else concealed = gtk_vbox_new(0,tile_spacing); gtk_widget_set_name(concealed,"table"); gtk_widget_show(concealed); /* the concealed box is towards the player */ /* This box needs to stay expanded to the width of the surrounding, which should be fixed at start up.*/ if ( br ) gtk_box_pack_end(GTK_BOX(w),concealed,1,1,0); else gtk_box_pack_start(GTK_BOX(w),concealed,1,1,0); /* players put closed (nonkong) tilesets in to the left of their concealed tiles */ for ( i=0; i < MAX_TILESETS; i++) { if ( tb ) c = gtk_hbox_new(0,0); else c = gtk_vbox_new(0,0); if ( bl ) gtk_box_pack_start(GTK_BOX(concealed),c,0,0,0); else gtk_box_pack_end(GTK_BOX(concealed),c,0,0,0); pd->csets[i].widget = c; tilesetbox_init(&pd->csets[i],ori,NULL,0); } if ( tb ) conc = gtk_hbox_new(0,0); else conc = gtk_vbox_new(0,0); gtk_widget_set_name(conc,"table"); gtk_widget_show(conc); /* the concealed tiles are to the right of the concealed sets */ /* This also needs to stay expanded */ if ( bl ) gtk_box_pack_start(GTK_BOX(concealed),conc,1,1,0); else gtk_box_pack_end(GTK_BOX(concealed),conc,1,1,0); for ( i=0; i < 14; i++ ) { /* normally, just buttons, but for us toggle buttons */ if ( ori == 0 ) b = gtk_toggle_button_new(); else b = gtk_button_new(); /* for gtk2 style info */ gtk_widget_set_name(b, (ori == 0) ? "mytile" : "tile"); /* we don't want any of the buttons taking the focus */ GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS); gtk_widget_show(b); pm = gtk_pixmap_new(tilepixmaps[ori][HiddenTile],NULL); gtk_widget_show(pm); gtk_container_add(GTK_CONTAINER(b),pm); /* the concealed tiles are placed from the player's left */ if ( bl ) gtk_box_pack_start(GTK_BOX(conc),b,0,0,0); else gtk_box_pack_end(GTK_BOX(conc),b,0,0,0); pd->conc[i] = b; /* if this is our player (ori = 0), attach a callback */ if ( ori == 0 ) { gtk_signal_connect(GTK_OBJECT(b),"toggled", GTK_SIGNAL_FUNC(conc_callback),(gpointer)(intptr_t)i); gtk_signal_connect(GTK_OBJECT(b),"button_press_event", GTK_SIGNAL_FUNC(doubleclicked),0); /* and set as a drag source */ /* Why do I have to use ACTION_MOVE ? Why can't I use ACTION_PRIVATE, which just doesn't work ? */ gtk_drag_source_set(b,GDK_BUTTON1_MASK,NULL,0,GDK_ACTION_MOVE); /* and a drag destination, or at least an emitter of events */ gtk_drag_dest_set(b,0,NULL,0,0); gtk_drag_dest_set_track_motion(b,TRUE); gtk_signal_connect(GTK_OBJECT(b),"drag_begin", GTK_SIGNAL_FUNC(drag_begin_callback),(gpointer)(intptr_t)i); gtk_signal_connect(GTK_OBJECT(b),"drag_motion", GTK_SIGNAL_FUNC(drag_motion_callback),(gpointer)(intptr_t)i); gtk_signal_connect(GTK_OBJECT(b),"drag_drop", GTK_SIGNAL_FUNC(drag_drop_callback),(gpointer)(intptr_t)i); } } /* to the right of the concealed tiles, we will place up to 8 other tiles. These will be used for flowers and seasons when we run out of room in the exposed row. By setting pdispwidth-14 (default 5) of them initially, it also serves the purpose of fixing the row to be the desired 19 (currently) tiles wide */ for ( i=0; i < 8; i++ ) { b = gtk_button_new(); gtk_widget_set_name(b,"tile"); /* we don't want any of the buttons taking the focus */ GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS); if ( i < pdispwidth-14 ) gtk_widget_show(b); pm = gtk_pixmap_new(tilepixmaps[ori][HiddenTile],NULL); gtk_widget_show(pm); gtk_container_add(GTK_CONTAINER(b),pm); /* these extra tiles are placed from the player's right */ if ( bl ) gtk_box_pack_end(GTK_BOX(conc),b,0,0,0); else gtk_box_pack_start(GTK_BOX(conc),b,0,0,0); pd->extras[i] = b; } if ( tb ) exposed = gtk_hbox_new(0,tile_spacing); else exposed = gtk_vbox_new(0,tile_spacing); gtk_widget_set_name(exposed,"table"); gtk_widget_show(exposed); /* the exposed box is away from the player */ if ( br ) gtk_box_pack_start(GTK_BOX(w),exposed,0,0,0); else gtk_box_pack_end(GTK_BOX(w),exposed,0,0,0); /* the exposed tiles will be in front of the player, from the left */ for ( i=0; i < MAX_TILESETS; i++ ) { if ( tb ) c = gtk_hbox_new(0,0); else c = gtk_vbox_new(0,0); gtk_widget_set_name(c,"table"); gtk_widget_show(c); if ( bl ) gtk_box_pack_start(GTK_BOX(exposed),c,0,0,0); else gtk_box_pack_end(GTK_BOX(exposed),c,0,0,0); pd->esets[i].widget = c; tilesetbox_init(&pd->esets[i],eori,NULL,0); } /* the special tiles are at the right of the exposed row */ if ( tb ) specbox = gtk_hbox_new(0,0); else specbox = gtk_vbox_new(0,0); gtk_widget_set_name(specbox,"table"); gtk_widget_show(specbox); if ( bl ) gtk_box_pack_end(GTK_BOX(exposed),specbox,0,0,0); else gtk_box_pack_start(GTK_BOX(exposed),specbox,0,0,0); /* at the right of the spec box, we place a tongbox pixmap. This is not initially shown */ pd->tongbox = gtk_pixmap_new(tongpixmaps[ori][east],tongmask); if ( bl ) gtk_box_pack_end(GTK_BOX(specbox),pd->tongbox,0,0,0); else gtk_box_pack_start(GTK_BOX(specbox),pd->tongbox,0,0,0); for ( i=0 ; i < 8; i++ ) { b = gtk_button_new(); gtk_widget_set_name(b,"tile"); /* we don't want any of the buttons taking the focus */ GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS); pm = gtk_pixmap_new(tilepixmaps[eori][HiddenTile],NULL); gtk_widget_show(pm); gtk_container_add(GTK_CONTAINER(b),pm); if ( bl ) gtk_box_pack_end(GTK_BOX(specbox),b,0,0,0); else gtk_box_pack_start(GTK_BOX(specbox),b,0,0,0); pd->spec[i] = b; } /* the player's discard buttons are created as required, so just zero them */ for ( i=0; i<32; i++ ) pd->discards[i] = NULL; pd->num_discards = 0; /* also initialize the info kept by the discard routine */ pd->x = pd->y = 0; pd->row = 0; pd->plane = 0; for (i=0;i<5;i++) { pd->xmin[i] = 10000; pd->xmax[i] = 0; } pd->claimw = NULL; /* the claim window is initialized on first popup, because it needs to have all the rest of the board set out to get the size right */ } /* event callback used below. The widget is a tile button, and the callback data is its tiletip window. For use below, a NULL event means always pop it up */ static gint maybe_popup_tiletip(GtkWidget *w, GdkEvent *ev, gpointer data) { GtkWidget *tiletip = (GtkWidget *)data; /* popup if this is an enternotify with the right button already down, or if this is a button press with the right button */ if ( ev == NULL || ( ev->type == GDK_ENTER_NOTIFY && ( (((GdkEventCrossing *)ev)->state & GDK_BUTTON3_MASK) || tiletips ) ) || ( ev->type == GDK_BUTTON_PRESS && ((GdkEventButton *)ev)->button == 3 ) ) { /* pop it up just above, right, left, below the tile */ int ori = 0; gint x, y; gint ttx = 0, tty = 0; GtkRequisition r; get_relative_posn(boardfixed,w,&x,&y); gtk_widget_size_request(tiletip,&r); ori = (int)(intptr_t)gtk_object_get_data(GTK_OBJECT(w),"ori"); switch ( ori ) { case 0: ttx = x; tty = y-r.height; break; case 1: ttx = x - r.width; tty = y; break; case 2: ttx = x; tty = y + tile_height; break; case 3: ttx = x + tile_height; tty = y; break; default: warn("impossible ori in maybe_popup_tiletip"); } vlazy_fixed_move(VLAZY_FIXED(boardfixed),tiletip,ttx,tty); gtk_widget_show(tiletip); } /* if it's a leavenotify, and the tile is not selected, pop down, unless the tile is a selected button */ if ( ev && ev->type == GDK_LEAVE_NOTIFY ) { if ( tiletips && GTK_WIDGET_VISIBLE(w) && GTK_IS_TOGGLE_BUTTON(w) && GTK_TOGGLE_BUTTON(w)->active ) { ; // leave it up } else { gtk_widget_hide(tiletip); } } return 0; } /* signal callback used to popup tiletips on selecting a tile */ static gint maybe_popup_tiletip_when_selected(GtkWidget *w, gpointer data) { GtkWidget *tiletip = (GtkWidget *)data; if ( tiletips && GTK_IS_TOGGLE_BUTTON(w) && GTK_TOGGLE_BUTTON(w)->active && GTK_WIDGET_VISIBLE(w) ) { maybe_popup_tiletip(w,NULL,data); } else { gtk_widget_hide(tiletip); } return 0; } /* given a button, tile, and orientation, set the pixmap (and tooltip when we have them. Stash the tile in the user_data field. Stash the ori as ori. Last arg is true if a tiletip should be installed */ static void button_set_tile_aux(GtkWidget *b, Tile t, int ori,int use_tiletip) { intptr_t ti; GtkWidget *tiletip, *lab; gtk_pixmap_set(GTK_PIXMAP(GTK_BIN(b)->child), tilepixmaps[ori][t],NULL); ti = t; gtk_object_set_user_data(GTK_OBJECT(b),(gpointer)ti); gtk_object_set_data(GTK_OBJECT(b),"ori",(gpointer)(intptr_t)ori); if ( use_tiletip ) { /* update, create or delete the tiletip */ tiletip = (GtkWidget *)gtk_object_get_data(GTK_OBJECT(b),"tiletip"); if ( t == HiddenTile ) { if ( tiletip ) gtk_widget_destroy(tiletip); gtk_object_set_data(GTK_OBJECT(b),"tiletip",0); } else { if ( tiletip ) { /* update label */ gtk_label_set_text(GTK_LABEL(GTK_BIN(tiletip)->child), tile_name(t)); } else { /* create tiletip */ tiletip = gtk_event_box_new(); /* it will be a child of board fixed, but its window will be moved around, and its widget position ignored */ vlazy_fixed_put(VLAZY_FIXED(boardfixed),tiletip,0,0); gtk_object_set_data(GTK_OBJECT(b),"tiletip",tiletip); lab = gtk_label_new(tile_name(t)); gtk_widget_show(lab); gtk_container_add(GTK_CONTAINER(tiletip),lab); /* install callbacks on the button, which will die when the popup is killed */ /* callback on enter */ gtk_signal_connect_while_alive(GTK_OBJECT(b),"enter_notify_event", (GtkSignalFunc)maybe_popup_tiletip, tiletip, GTK_OBJECT(tiletip)); /* callback on button press */ gtk_signal_connect_while_alive(GTK_OBJECT(b),"button_press_event", (GtkSignalFunc)maybe_popup_tiletip, tiletip, GTK_OBJECT(tiletip)); /* leaving may pop down the window */ gtk_signal_connect_while_alive(GTK_OBJECT(b),"leave_notify_event", (GtkSignalFunc)maybe_popup_tiletip, tiletip, GTK_OBJECT(tiletip)); /* toggling may require tile tips */ if ( GTK_IS_TOGGLE_BUTTON(b) ) { gtk_signal_connect_while_alive(GTK_OBJECT(b),"toggled", (GtkSignalFunc)maybe_popup_tiletip_when_selected, tiletip, GTK_OBJECT(tiletip)); } /* also want to make sure tip goes away on being hidden. */ gtk_signal_connect_object_while_alive(GTK_OBJECT(b),"hide", (GtkSignalFunc)gtk_widget_hide,GTK_OBJECT(tiletip)); /* and if the tile is destroyed, the tip had better be */ gtk_signal_connect_object_while_alive(GTK_OBJECT(b),"destroy", (GtkSignalFunc)gtk_widget_destroy,GTK_OBJECT(tiletip)); } } } } void button_set_tile(GtkWidget *b, Tile t, int ori) { button_set_tile_aux(b,t,ori,1); } void button_set_tile_without_tiletip(GtkWidget *b, Tile t, int ori) { button_set_tile_aux(b,t,ori,0); } /* given a pointer to a TileSetBox, and an orientation, and a callback function and data initialize the box by creating the widgets and attaching the signal function. */ void tilesetbox_init(TileSetBox *tb, int ori, GtkSignalFunc func,gpointer func_data) { int i; GtkWidget *b, *pm; for (i=0; i<4; i++) { /* make a button */ b = gtk_button_new(); gtk_widget_set_name(b,"tile"); tb->tiles[i] = b; if ( func ) gtk_signal_connect(GTK_OBJECT(b),"clicked", func,func_data); /* we don't want any of the tiles taking the focus */ GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS); pm = gtk_pixmap_new(tilepixmaps[ori][HiddenTile],NULL); gtk_widget_show(pm); gtk_container_add(GTK_CONTAINER(b),pm); /* we want chows to grow from top and left for left and bottom */ if ( ori == 0 || ori == 3 ) gtk_box_pack_start(GTK_BOX(tb->widget),b,0,0,0); else gtk_box_pack_end(GTK_BOX(tb->widget),b,0,0,0); } tb->set.type = Empty; } /* given a pointer to a TileSetBox, and a pointer to a TileSet, and an orientation, make the box display the tileset. */ void tilesetbox_set(TileSetBox *tb, const TileSet *ts, int ori) { int n, i, ck, millingkong; Tile t[4]; if (tb->set.type == ts->type && (ts->type == Empty || tb->set.tile == ts->tile) ) { /* nothing to do, except show for safety */ if ( ts->type == Empty ) gtk_widget_hide(tb->widget); else gtk_widget_show(tb->widget); return; } tb->set = *ts; n = 2; /* for pairs etc */ ck = 0; /* closed kong */ millingkong = 0; /* playing Millington's rules, and the kong is claimed rather than annexed */ switch (ts->type) { case ClosedKong: ck = 1; // fall through case Kong: n++; if ( !ck && ! ts->annexed && the_game && game_get_option_value(the_game,GOKongHas3Types,NULL).optbool ) millingkong = 1; // fall through case Pung: case ClosedPung: n++; // fall through case Pair: case ClosedPair: for ( i=0 ; i < n ; i++ ) { t[i] = ts->tile; if ( ck && ( i == 0 || i == 3 ) ) t[i] = HiddenTile; if ( millingkong && i == 3 ) t[i] = HiddenTile; } break; case Chow: case ClosedChow: n = 3; for (i=0; itile+i; } break; case Empty: n = 0; } for (i=0; itiles[i],t[i],ori); gtk_widget_show(tb->tiles[i]); } for ( ; i < 4; i++ ) if ( tb->tiles[i] ) gtk_widget_hide(tb->tiles[i]); if ( ts->type == Empty ) gtk_widget_hide(tb->widget); else gtk_widget_show(tb->widget); } /* utility used by the chow functions. Given a TileSetBox, highlight the nth tile by setting the others insensitive. (n counts from zero). */ void tilesetbox_highlight_nth(TileSetBox *tb,int n) { int i; for (i=0; i<4; i++) if ( tb->tiles[i] ) gtk_widget_set_sensitive(tb->tiles[i],i==n); } /* two routines used internally */ /* update the concealed tiles of the playerdisp. Returns the *index* of the last tile matching the third argument. */ static int playerdisp_update_concealed(PlayerDisp *pd,Tile t) { PlayerP p = pd->player; int tindex = -1; int i; int cori,eori; cori = pd->orientation; eori = flipori(cori); if ( p && pflag(p,HandDeclared) ) cori = eori; for ( i=0 ; p && i < p->num_concealed ; i++ ) { button_set_tile(pd->conc[i],p->concealed[i],cori); gtk_widget_show(pd->conc[i]); if ( p->concealed[i] == t ) tindex = i; } for ( ; i < MAX_CONCEALED ; i++ ) { gtk_widget_hide(pd->conc[i]); } /* if it's the player's turn, other than ours, highlight their rightmost tile to indicate this */ if ( the_game != NULL ) { if ( the_game->player != our_seat && p == the_game->players[the_game->player] ) { if ( the_game->state == DeclaringSpecials || the_game->state == Discarding ) { GtkWidget *t = pd->conc[p->num_concealed-1]; if ( t != highlitinhand && highlitinhand != NULL ) { gtk_widget_modify_bg(highlitinhand,GTK_STATE_NORMAL,NULL); highlitinhand = NULL; } gtk_widget_modify_bg(highlitinhand = t,GTK_STATE_NORMAL,&highlightcolor); } else { if ( highlitinhand != NULL ) { gtk_widget_modify_bg(highlitinhand,GTK_STATE_NORMAL,NULL); highlitinhand = NULL; } } } else if ( the_game->player == our_seat ) { /* clear any highlighting */ if ( highlitinhand != NULL ) { gtk_widget_modify_bg(highlitinhand,GTK_STATE_NORMAL,NULL); highlitinhand = NULL; } } } return tindex; } /* update the exposed tiles of the playerdisp. Returns the _button widget_ of the last tile that matched the second argument. The third argument causes special treatment: an exposed pung matching the tile is displayed as a kong. */ static GtkWidget *playerdisp_update_exposed(PlayerDisp *pd,Tile t,int robbingkong){ int i,scount,excount,ccount,ecount,qtiles; GtkWidget *w; TileSet emptyset; PlayerP p = pd->player; int cori, eori; /* orientations of concealed and exposed tiles */ cori = pd->orientation; eori = flipori(cori); emptyset.type = Empty; if ( p && pflag(p,HandDeclared) ) cori = eori; w = NULL; /* destination widget */ /* We need to count the space taken up by the exposed tilesets, in order to know where to put the specials */ qtiles = 0; /* number of quarter tile widths */ ccount = ecount = 0; for ( i=0; p && i < MAX_TILESETS; i++) { switch (p->tilesets[i].type) { case ClosedPair: case ClosedChow: /* in the concealed row, so no space */ /* NB, these are declared, they are oriented as exposed sets, despite the name */ tilesetbox_set(&pd->csets[ccount++],&p->tilesets[i],eori); break; case ClosedPung: /* in the special case that we are in the middle of robbing a kong (13 wonders), and this is the robbed set, we keep it in the exposed row displayed as a kong */ if ( robbingkong && t == p->tilesets[i].tile) { TileSet ts = p->tilesets[i]; ts.type = ClosedKong; tilesetbox_set(&pd->esets[ecount],&ts,eori); w = pd->esets[ecount].tiles[3]; qtiles += 16; /* four tiles */ ecount++; } else { tilesetbox_set(&pd->csets[ccount++],&p->tilesets[i],eori); } break; case ClosedKong: case Kong: tilesetbox_set(&pd->esets[ecount],&p->tilesets[i],eori); if ( t == p->tilesets[i].tile ) w = pd->esets[ecount].tiles[3]; qtiles += 16; /* four tiles */ ecount++; break; case Pung: if ( robbingkong && t == p->tilesets[i].tile) { TileSet ts = p->tilesets[i]; ts.type = Kong; tilesetbox_set(&pd->esets[ecount],&ts,eori); w = pd->esets[ecount].tiles[3]; qtiles += 16; /* four tiles */ } else { tilesetbox_set(&pd->esets[ecount],&p->tilesets[i],eori); if ( t == p->tilesets[i].tile ) w = pd->esets[ecount].tiles[2]; qtiles += 12; } ecount++; break; case Chow: tilesetbox_set(&pd->esets[ecount],&p->tilesets[i],eori); if ( t >= p->tilesets[i].tile && t <= p->tilesets[i].tile+2 ) w = pd->esets[ecount].tiles[t-p->tilesets[i].tile]; qtiles += 12; ecount++; break; case Pair: tilesetbox_set(&pd->esets[ecount],&p->tilesets[i],eori); if ( t == p->tilesets[i].tile ) w = pd->esets[ecount].tiles[1]; qtiles += 9; /* for the two tiles and space */ ecount++; break; case Empty: ; } } for ( ; ccount < MAX_TILESETS; ) tilesetbox_set(&pd->csets[ccount++],&emptyset,eori); for ( ; ecount < MAX_TILESETS; ) tilesetbox_set(&pd->esets[ecount++],&emptyset,eori); /* if we are the dealer, the tongbox takes up about 1.5 tiles */ if ( p && p->wind == EastWind ) { gtk_pixmap_set(GTK_PIXMAP(pd->tongbox),tongpixmaps[cori][the_game->round-1], tongmask); gtk_widget_show(pd->tongbox); qtiles += 6; } else { gtk_widget_hide(pd->tongbox); } /* for the special tiles, we put as many as possible in the exposed row (specs), and then overflow into the concealed row */ qtiles = (qtiles+3)/4; /* turn quarter tiles into tiles */ scount = excount = 0; for ( i = 0; p && i < p->num_specials && i <= pdispwidth - qtiles ; i++ ) { button_set_tile(pd->spec[i],p->specials[i],eori); gtk_widget_show(pd->spec[i]); if ( t == p->specials[i] ) w = pd->spec[i]; scount++; } for ( ; p && i < p->num_specials ; i++ ) { button_set_tile(pd->extras[i-scount],p->specials[i],eori); gtk_widget_show(pd->extras[i-scount]); if ( t == p->specials[i] ) w = pd->extras[i-scount]; excount++; } for ( ; scount < 8; scount ++ ) gtk_widget_hide(pd->spec[scount]); for ( ; excount < 8; excount ++ ) gtk_widget_hide(pd->extras[excount]); return w; } /* given a playerdisp, update the hand. The second argument gives the message prompting this. Animation is handled entirely in here. */ static void playerdisp_update(PlayerDisp *pd, CMsgUnion *m) { Tile t; int tindex; static AnimInfo srcs[4], dests[4]; int numanims = 0; static GtkWidget *robbedfromkong; /* yech */ PlayerP p = pd->player; int deftile = -1; /* default tile to be discarded */ int cori, eori; /* orientations of concealed and exposed tiles */ int updated = 0; /* flag to say we've done our stuff */ cori = pd->orientation; eori = flipori(cori); if ( p && pflag(p,HandDeclared) ) cori = eori; if ( m == NULL ) { /* just update */ playerdisp_update_concealed(pd,HiddenTile); playerdisp_update_exposed(pd,HiddenTile,0); return; } if ( m->type == CMsgNewHand && p) { playerdisp_clear_discards(pd); playerdisp_update_concealed(pd,HiddenTile); playerdisp_update_exposed(pd,HiddenTile,0); return; } if ( m->type == CMsgPlayerDraws || m->type == CMsgPlayerDrawsLoose) { updated = 1; t = (m->type == CMsgPlayerDraws) ? m->playerdraws.tile : m->playerdrawsloose.tile; tindex = playerdisp_update_concealed(pd,t); assert(tindex >= 0); /* do we want to select a tile? Usually, but in declaring specials state, we want to select a special if there is one, and otherwise not */ if ( p == our_player ) { if ( the_game->state == DeclaringSpecials ) { int i; for (i=0; inum_concealed && !is_special(p->concealed[i]);i++); if ( i < p->num_concealed ) { deftile = i; conc_callback(pd->conc[deftile],(gpointer)(intptr_t)(deftile | 128)); } else { conc_callback(NULL,NULL); } } else { deftile = tindex; conc_callback(pd->conc[deftile],(gpointer)(intptr_t)(deftile | 128)); } } dests[numanims].target = pd->conc[tindex]; dests[numanims].t = t; get_relative_posn(boardframe,dests[numanims].target,&dests[numanims].x,&dests[numanims].y); /* that was the destination, now the source */ if ( showwall ) { if ( m->type == CMsgPlayerDraws ) { int i; /* wall has already been updated, so it's -1 */ i = wall_game_to_board(the_game->wall.live_used-1); srcs[numanims].target = NULL; srcs[numanims].t = m->playerdraws.tile; /* we may as well see our tile now */ srcs[numanims].ori = wall_ori(i); get_relative_posn(boardframe,wall[i],&srcs[numanims].x,&srcs[numanims].y); gtk_widget_destroy(wall[i]); wall[i] = NULL; } else { /* draws loose */ srcs[numanims] = *adjust_wall_loose(the_game->wall.dead_end); /* in fact, we may as well see the tile if we're drawing */ srcs[numanims].t = t; } } else { /* if we're animating, we'll just have the tile spring up from nowhere! */ srcs[numanims].target = NULL; srcs[numanims].t = t; /* we may as well see our tile now */ srcs[numanims].ori = cori; /* these should be corrected for orientation */ srcs[numanims].x = boardframe->allocation.width/2 - tile_width/2; srcs[numanims].y = boardframe->allocation.height/2 - tile_height/2; } if ( p == our_player && alert_mahjong && player_can_mah_jong(our_player,HiddenTile, game_get_option_value(the_game,GOSevenPairs,NULL).optbool) ) { playerdisp_popup_claim_window(&pdisps[0],"Mah-Jong possible!",0); } numanims++; } /* end of Draws and DrawsLoose */ if ( m->type == CMsgPlayerDiscards ) { updated = 1; /* update the concealed tiles */ playerdisp_update_concealed(pd,HiddenTile); /* the source for animation is the previous rightmost tile for other players, or the selected tile, for us. Note that selected button may not be set, if we're replaying history. In that case, we won't bother to set the animation position, in the knowledge that it won't actually be animated. This is far too convoluted. */ /* FIXME: actually the selected button could be wrong, as the user might have moved it between sending the discard request and getting this controller message */ tindex = (p == our_player) ? selected_button : p->num_concealed; srcs[numanims].t = m->playerdiscards.tile; srcs[numanims].ori = eori; if ( tindex >= 0 ) { get_relative_posn(boardframe,pd->conc[tindex],&srcs[numanims].x, &srcs[numanims].y); } /* now display the new discard */ dests[numanims] = *playerdisp_discard(pd,m->playerdiscards.tile); discard_history.hist[discard_history.count].pd = pd; discard_history.hist[discard_history.count].t = m->playerdiscards.tile; discard_history.count++; /* if we're not animating, highlight the tile */ if ( ! (animate && the_game->active) ) { gtk_widget_modify_bg(dests[numanims].target,GTK_STATE_NORMAL,&highlightcolor); highlittile = dests[numanims].target; } /* if we're discarding, clear the selected tile */ if ( p == our_player ) conc_callback(NULL,NULL); numanims++; } /* end of CMsgPlayerDiscards */ if ( m->type == CMsgPlayerDeclaresSpecial ) { updated = 1; if ( m->playerdeclaresspecial.tile != HiddenTile ) { GtkWidget *w = NULL; /* the source tile in this case is either the tile after the rightmost concealed tile (for other players) or the selected tile (for us) */ /* FIXME: again, the selected tile is not necessarily right */ if ( p == our_player ) { if ( selected_button >= 0 ) w = pd->conc[selected_button]; /* else we're probably replaying history */ } else { w = pd->conc[p->num_concealed]; } if ( w ) get_relative_posn(boardframe,w,&srcs[numanims].x,&srcs[numanims].y); srcs[numanims].t = m->playerdeclaresspecial.tile; srcs[numanims].ori = cori; /* now update the special tiles, noting the destination */ w = playerdisp_update_exposed(pd,m->playerdeclaresspecial.tile,0); assert(w); /* this forces a resize calculation so that the special tiles are in place */ gtk_widget_size_request(pd->widget,NULL); gtk_widget_size_allocate(pd->widget,&pd->widget->allocation); dests[numanims].target = w; dests[numanims].ori = eori; get_relative_posn(boardframe,w,&dests[numanims].x,&dests[numanims].y); numanims++; /* that's it */ } else { /* blank tile. nothing to do except select a tile if it's now our turn to declare */ if ( the_game->state == DeclaringSpecials && the_game->player == our_seat ) { if ( is_special(our_player->concealed[our_player->num_concealed-1]) ) { deftile = our_player->num_concealed-1; conc_callback(pdisps[0].conc[deftile],(gpointer)(intptr_t)(deftile | 128)); } else { conc_callback(NULL,NULL); } } /* update the concealed tiles of the *next* player, because it's their turn - this gets highlighting correct */ playerdisp_update_concealed(&pdisps[game_to_board(the_game->player)],HiddenTile); } } /* end of CMsgPlayerDeclaresSpecial */ if ( m->type == CMsgPlayerClaimsPung || m->type == CMsgPlayerClaimsKong || m->type == CMsgPlayerClaimsChow || m->type == CMsgPlayerClaimsMahJong || m->type == CMsgPlayerMahJongs || (m->type == CMsgPlayerDiscards ) ) { char *lab = NULL; switch ( m->type ) { case CMsgPlayerClaimsPung: lab = "Pung!"; break; case CMsgPlayerClaimsKong: lab = "Kong!"; break; case CMsgPlayerClaimsChow: lab = "Chow!"; break; case CMsgPlayerMahJongs: case CMsgPlayerClaimsMahJong: lab = "Mah Jong!"; break; case CMsgPlayerDiscards: lab = m->playerdiscards.calling ? "Calling!" : NULL; break; default: assert(0); } updated = 1; if ( lab ) { playerdisp_popup_claim_window(pd,lab,0); } if ( thinking_claim && server_pversion < 1111 ) { static int warned = 0; if ( ! warned ) { warned = 1; warn("`Thinking' cannot be shown with this server."); } } if ( m->type == CMsgPlayerDiscards && thinking_claim /* before 1111, the server did not send doesntclaims to all players */ && server_pversion >= 1111 ) { // put up thinking labels for the other players for (int i=0; i < 4;i++) { PlayerDisp *pd = &pdisps[i]; if ( pd->player->id == our_id ) continue; if ( m->playerdiscards.id == pd->player->id ) continue; playerdisp_popup_claim_window(pd,"...",1); } } if ( m->type == CMsgPlayerDiscards && m->playerdiscards.id != our_id && alert_mahjong && player_can_mah_jong(our_player,m->playerdiscards.tile, game_get_option_value(the_game,GOSevenPairs,NULL).optbool) ) { playerdisp_popup_claim_window(&pdisps[0],"Mah-Jong possible!",0); } } /* end of the claims */ /* On receiving a successful claim, withdraw the last discard. And if mahjonging, select the first tile if this is us */ if ( (m->type == CMsgPlayerChows && ((CMsgPlayerChowsMsg *)m)->cpos != AnyPos) || m->type == CMsgPlayerPungs || m->type == CMsgPlayerKongs || m->type == CMsgPlayerPairs || m->type == CMsgPlayerSpecialSet ) { GtkWidget *w; updated = 1; /* withdraw discard, or use the saved widget if a kong was robbed */ if ( the_game->whence == FromRobbedKong ) { srcs[numanims].t = the_game->tile; get_relative_posn(boardframe,robbedfromkong, &srcs[numanims].x,&srcs[numanims].y); playerdisp_update_exposed(&pdisps[game_to_board(the_game->supplier)],HiddenTile,0); } else { srcs[numanims] = *playerdisp_discard(&pdisps[game_to_board(the_game->supplier)],HiddenTile); discard_history.hist[discard_history.count].pd = &pdisps[game_to_board(the_game->supplier)]; discard_history.hist[discard_history.count].t = HiddenTile; discard_history.count++; } /* update exposed tiles, and get the destination widget */ w = playerdisp_update_exposed(pd,the_game->tile,0); /* In the case of a special set, the destination tile will be in the concealed tiles */ /* update the concealed tiles. At present we don't animate the movement of tiles from hand to exposed sets, so just update */ if ( m->type == CMsgPlayerSpecialSet ) { w = pd->conc[playerdisp_update_concealed(pd,the_game->tile)]; } else { playerdisp_update_concealed(pd,HiddenTile); } assert(w); /* this should force a resize calculation so that the declared tiles are in place. It seems to work. */ gtk_widget_size_request(pd->widget,NULL); gtk_widget_size_allocate(pd->widget,&pd->widget->allocation); dests[numanims].target = w; srcs[numanims].ori = dests[numanims].ori = eori; get_relative_posn(boardframe,w,&dests[numanims].x,&dests[numanims].y); numanims++; /* that's it */ if ( the_game->state == MahJonging && the_game->player == our_seat && p == our_player && p->num_concealed > 0 ) conc_callback(pd->conc[0],(gpointer)(intptr_t)(0 | 128)); } /* end of the successful claim cases */ else if ( m->type == CMsgPlayerRobsKong ) { GtkWidget *t; /* This should be called on the ROBBED player, so we can stash away the tile that will be robbed */ t = playerdisp_update_exposed(pd,m->playerrobskong.tile,1); /* if we found the tile, then this is it. Otherwise, we were looking at somebody else! */ if ( t ) robbedfromkong = t; updated = 1; } /* end of robbing kong */ /* if not yet handled, just update the tiles */ if ( ! updated ) { playerdisp_update_concealed(pd,HiddenTile); playerdisp_update_exposed(pd,HiddenTile,0); } /* now kick off the animations (or not) */ while ( numanims-- > 0 ) { if ( animate && the_game->active && the_game->state != Dealing ) { gtk_widget_unmap(dests[numanims].target); start_animation(&srcs[numanims],&dests[numanims]); } } } /* utility function: given a gdkpixmap, create rotated versions and put them in the last three arguments. */ static void rotate_pixmap(GdkPixmap *east, GdkPixmap **south, GdkPixmap **west, GdkPixmap **north) { static GdkVisual *v = NULL; int w=-1, h=-1; int x, y; GdkImage *im; GdkPixmap *p1; GdkImage *im1; GdkGC *gc; if ( v == NULL ) { v = gdk_window_get_visual(topwindow->window); } gdk_window_get_size(east,&w,&h); /* remember to destroy these */ im = gdk_image_get(east,0,0,w,h); gc = gdk_gc_new(east); /* do the south */ im1 = gdk_image_new(GDK_IMAGE_NORMAL,v,h,w); for ( x=0; x < w; x++ ) for ( y=0; y < h; y++ ) gdk_image_put_pixel(im1,y,w-1-x,gdk_image_get_pixel(im,x,y)); p1 = gdk_pixmap_new(NULL,h,w,im->depth); gdk_draw_image(p1,gc,im1,0,0,0,0,h,w); *south = p1; /* do the north */ for ( x=0; x < w; x++ ) for ( y=0; y < h; y++ ) gdk_image_put_pixel(im1,h-1-y,x,gdk_image_get_pixel(im,x,y)); p1 = gdk_pixmap_new(NULL,h,w,im->depth); gdk_draw_image(p1,gc,im1,0,0,0,0,h,w); *north = p1; /* and the west */ gdk_image_destroy(im1); im1 = gdk_image_new(GDK_IMAGE_NORMAL,v,w,h); for ( x=0; x < w; x++ ) for ( y=0; y < h; y++ ) gdk_image_put_pixel(im1,w-1-x,h-1-y,gdk_image_get_pixel(im,x,y)); p1 = gdk_pixmap_new(NULL,w,h,im->depth); gdk_draw_image(p1,gc,im1,0,0,0,0,w,h); *west = p1; /* clean up */ gdk_image_destroy(im1); gdk_image_destroy(im); gdk_gc_destroy(gc); } /* new animation routines. The previous way was all very object-oriented, but not very good: having separate timeouts for every animated tile tends to slow things done. So we will instead maintain a global list of animations in progress (of which there will be at most five!), and have a single timeout that deals with all of them */ /* struct for recording information about an animation in progress */ struct AnimProg { GtkWidget *floater; /* the moving window. Set to NULL if this record is not in use */ gint x1,y1,x2,y2; /* start and end posns relative to either boardframe or root, depending on animate_with_popups */ gint steps, n; /* total number of steps, number completed */ GtkWidget *target; /* target widget */ }; struct AnimProg animlist[5]; int animator_running = 0; /* timeout function that moves all running animations by one step */ static gint discard_animator(gpointer data UNUSED) { struct AnimProg *animp; int i; int numinprogress = 0; for ( i = 0 ; i < 5 ; i++ ) { animp = &animlist[i]; if ( ! animp->floater ) continue; if ( animp->n == 0 ) { gtk_widget_destroy(animp->floater); animp->floater = NULL; gtk_widget_show(animp->target); gtk_widget_map(animp->target); } else { numinprogress++; animp->n--; /* Here we move the widget, *and* we move the window. Why? If we move the widget, gtk doesn't actually move its window, as we've arranged for vlazy_fixed_move not to call queue_resize, so relayout doesn't get done. Thus we have to move the window. However, if we don't move the widget, then GTK uses its original position for calculating event deliveries, resulting in total confusion and leave/enter loop if the pointer is in the button. But if we're using popup windows, we only move the window. */ if ( ! animate_with_popups ) { vlazy_fixed_move(VLAZY_FIXED(boardfixed),animp->floater, (animp->n*animp->x1 + (animp->steps-animp->n)*animp->x2) /animp->steps, (animp->n*animp->y1 + (animp->steps-animp->n)*animp->y2) /animp->steps); } gdk_window_move(animp->floater->window, (animp->n*animp->x1 + (animp->steps-animp->n)*animp->x2) /animp->steps, (animp->n*animp->y1 + (animp->steps-animp->n)*animp->y2) /animp->steps); } } if ( numinprogress > 0 ) { return TRUE ; } else { animator_running = 0; return FALSE; } } /* kick off an animation from the given source and target info */ static void start_animation(AnimInfo *source,AnimInfo *target) { GtkWidget *floater,*b; int i; struct AnimProg *animp; GtkWidget *p; /* find a free slot in the animation list */ for ( i = 0 ; animlist[i].floater && i < 5 ; i++ ) ; if ( i == 5 ) { warn("no room in animation list; not animating"); gtk_widget_map(target->target); return; } animp = &animlist[i]; /* create the floating button */ b = gtk_button_new(); gtk_widget_set_name(b,"tile"); gtk_widget_show(b); GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS); p = gtk_pixmap_new(tilepixmaps[0][HiddenTile],NULL); gtk_widget_show(p); gtk_container_add(GTK_CONTAINER(b),p); button_set_tile_without_tiletip(b,source->t,source->ori); if ( animate_with_popups ) { floater = gtk_window_new(GTK_WINDOW_POPUP); gtk_container_add(GTK_CONTAINER(floater),b); } else { /* in GTK2, buttons don't have windows */ floater = gtk_event_box_new(); gtk_container_add(GTK_CONTAINER(floater),b); } animp->x1 = source->x; animp->x2 = target->x; animp->y1 = source->y; animp->y2 = target->y; if ( animate_with_popups ) { int x,y; gdk_window_get_deskrelative_origin(boardframe->window,&x,&y); animp->x1 += x; animp->x2 += x; animp->y1 += y; animp->y2 += y; } /* how many steps? the frame interval is 20ms. We want it to move across the screen in about a second max. So say 1600 pixels in 1 second, i.e. 50 frames. However, we don't want it to move instantaneously over short distances, so let's say a minimum of 10 frames. Hence numframes = 10 + 40 * dist/1600 */ animp->steps = 10 + floor(hypot(source->x-target->x,source->y-target->y))*40/1600; animp->n = animp->steps; animp->target = target->target; if ( animate_with_popups ) { gtk_widget_set_uposition(floater,animp->x1,animp->y1); } else { vlazy_fixed_put(VLAZY_FIXED(boardfixed),floater,animp->x1,animp->y1); } gtk_widget_show(floater); animp->floater = floater; if ( ! animator_running ) { animator_running = 1; gtk_timeout_add(/* 1000 */ 20 ,discard_animator,(gpointer)NULL); } } /* local variable used in next two. Tells playerdisp_discard whether to compute some information */ static int playerdisp_discard_recompute = 1; /* playerdisp_clear_discards: clear the discard area for the player */ static void playerdisp_clear_discards(PlayerDisp *pd) { int i; /* destroy ALL widgets, since there might be widgets after the current last discard (and will be, if the last mah jong was from the wall */ for (i=0; i < 32; i++) { if ( pd->discards[i] ) { gtk_widget_destroy(pd->discards[i]); pd->discards[i] = NULL; } } highlittile = NULL; pd->num_discards = 0; pd->x = pd->y = pd->row = pd->plane = 0; for (i=0;i<5;i++) { pd->xmin[i] = 10000; pd->xmax[i] = 0; } playerdisp_discard_recompute = 1; } /* Given a player disp and a tile, display the tile as the player's next discard. If HiddenTile, then withdraw the last discard. Also, if pd is NULL: assume pd is the last player on which we were called. Buttons are created as required. Tiles are packed in rows, putting a 1/4 tile spacing between them horizontally and vertically. In the spirit of Mah Jong, each player grabs the next available space, working away from it; a little cleverness detects clashes. Returns an animinfo which, in the normal case, is the position of the new discard. In the withdrawing case, it's the discard being withdrawn. */ static AnimInfo *playerdisp_discard(PlayerDisp *pd,Tile t) { static PlayerDisp *last_pd = NULL; static gint16 dw,dh; /* width and height of discard area */ static gint16 bw,bh; /* width and height of tile button in ori 0 */ static gint16 spacing; gint16 w,h; /* width and height of discard area viewed by this player */ int row,plane; /* which row and plane are we in */ int i; gint16 x,y,xx,yy; PlayerDisp *left,*right; /* our left and right neighbours */ GtkWidget *b; int ori, eori; static AnimInfo anim; if ( pd == NULL ) pd = last_pd; last_pd = pd; ori = pd->orientation; eori = flipori(ori); if ( highlittile ) { gtk_widget_modify_bg(highlittile,GTK_STATE_NORMAL,NULL); highlittile = NULL; } if ( t == HiddenTile ) { if ( pd == NULL ) { warn("Internal error: playerdisp_discard called to clear null discard"); return NULL; } b = pd->discards[--pd->num_discards]; if ( b == NULL ) { warn("Internal error: playerdisp_discard clearing nonexistent button"); return NULL; } anim.t = (int)(intptr_t) gtk_object_get_user_data(GTK_OBJECT(b)); anim.target = b; anim.ori = eori; get_relative_posn(boardframe,b,&anim.x,&anim.y); gtk_widget_hide(b); return &anim; } ori = pd->orientation; eori = flipori(ori); b = pd->discards[pd->num_discards]; left = &pdisps[(ori+3)%4]; right = &pdisps[(ori+1)%4]; if ( b == NULL ) { GtkWidget *p; b = pd->discards[pd->num_discards] = gtk_button_new(); gtk_widget_set_name(b,"tile"); GTK_WIDGET_UNSET_FLAGS(b,GTK_CAN_FOCUS); p = gtk_pixmap_new(tilepixmaps[0][HiddenTile],NULL); gtk_widget_show(p); gtk_container_add(GTK_CONTAINER(b),p); /* if this is the first time we've ever been called, we need to compute some information */ if ( playerdisp_discard_recompute ) { playerdisp_discard_recompute = 0; /* This discard area alloc was computed at a known safe time in the main routine */ dw = discard_area_alloc.width - 2 * GTK_CONTAINER(discard_area)->border_width; dh = discard_area_alloc.height - 2 * GTK_CONTAINER(discard_area)->border_width; bw = tile_width; bh = tile_height; spacing = tile_spacing; /* if we're showing the wall, the effective dimensions are is reduced by 2*(tileheight+3*tilespacing) -- each wall occupies tile_height+2*tile_spacing, and we also want some space */ if ( showwall ) { dw -= 2*(tile_height+3*tile_spacing); dh -= 2*(tile_height+3*tile_spacing); /* moreover, the wall might be smaller than the max wall we used to set the discard area */ dw -= ((MAX_WALL_SIZE-WALL_SIZE)/8)*tile_width; dh -= ((MAX_WALL_SIZE-WALL_SIZE)/8)*tile_width; } } /* Now we need to work out where to put this button. To make life easier, we will always look at it as if we were at the bottom, and then rotate the answer later */ /* we can't assume the discard area is square, so swap w and h if we're working for a side player */ if ( ori == 0 || ori == 2 ) { w = dw; h = dh; } else { w = dh; h = dw; } /* get the positions from last time */ x = pd->x; y = pd->y; row = pd->row; plane = pd->plane; /* working in the X coordinate system is too painful. We work in a normal Cartesian system and convert only at the last moment */ /* x,y is the pos of the bottom left corner */ /* repeat until success */ while ( 1 ) { /* Does the current position intrude into the opposite area? If so, we're stuffed, and need to think what to do */ /* also if we've run out of rows */ if ( row >= 5 || y+bh > h/2 ) { /* start stacking tiles */ plane++; row = 0; x = 0 + plane*(bw+spacing)/2; /* two planes is enough, surely! */ y = 0 + plane*(bh+spacing)/2; continue; } /* Does the current position intrude into the left hand neighbour? The left hand neighbour has stored in its xmax array the rightmost points it's used in each row. So for each of its rows, we see if either our top left or top right intrude on it, and if so we move along */ for (i=0; i<5; i++) { if ( ( /* our top left is in the part occupied by the tile */ (x >= i*(bh+spacing) && x < i*(bh+spacing)+bh) /* ditto for top right */ || (x+bw >= i*(bh+spacing) && x+bw < i*(bh+spacing)+bh) ) /* and our top intrudes */ && y+bh > h - left->xmax[i] ) x = i*(bh+spacing)+bh+spacing/2; /* half space all that's needed, I think */ } /* Now see if the current position interferes with the right neighbour similarly */ for (i=0; i<5; i++) { if ( ( /* our top left is in the column */ (x >= w-(i*(bh+spacing)+bh) && x < w - i*(bh+spacing)) /* ditto for top right */ || (x+bw >= w-(i*(bh+spacing)+bh) && x+bw < w - i*(bh+spacing))) /* and our top intrudes */ && y+bh > right->xmin[i] ) { /* we have to move up to the next row */ row++; x = 0; y += (bh+spacing); i=-1; /* signal */ break; } } /* and restart the loop if we moved */ if ( i < 0 ) continue; /* Phew, we've found it, if we get here */ /* store our information for next time */ pd->row = row; pd->plane = plane; pd->x = x+bw+spacing; pd->y = y; if ( x < pd->xmin[row] ) pd->xmin[row] = x; if ( x+bw > pd->xmax[row] ) pd->xmax[row] = x+bw; break; } /* Now we have to rotate for the appropriate player. This means giving a point for the top left corner. And switching y round */ xx = x; yy = y; switch ( ori ) { case 0: x = xx; y = dh-yy-bh; break; case 1: y = dh - xx - bw; x = dw - yy - bh; break; case 2: x = dw - xx - bw; y = yy; break; case 3: y = xx; x = yy; break; } /* and if we are showing the wall, we have to shift everything down and right by the wall space */ if ( showwall ) { x += (tile_height+3*tile_spacing); y += (tile_height+3*tile_spacing); /* and adjust for wall size */ x += ((MAX_WALL_SIZE-WALL_SIZE)/8)*tile_width/2; y += ((MAX_WALL_SIZE-WALL_SIZE)/8)*tile_width/2; } gtk_fixed_put(GTK_FIXED(discard_area),b,x,y); pd->dx[pd->num_discards] = x; pd->dy[pd->num_discards] = y; } else { x = pd->dx[pd->num_discards]; y = pd->dy[pd->num_discards]; } /* now we have a button created and in place: set it */ button_set_tile(b,t,eori); /* now we need to compute the animation info to return */ /* position is that of the button we've just created */ /* unfortunately, if we've just created the button, it won't actually be in position (since it hasn't been realized), so we have to use our calculated position relative to the discard area */ get_relative_posn(boardframe,discard_area,&anim.x,&anim.y); anim.x += x; anim.y += y; /* and don't forget the border of the discard area */ anim.x += GTK_CONTAINER(discard_area)->border_width; anim.y += GTK_CONTAINER(discard_area)->border_width; anim.target = b; anim.t = t; anim.ori = eori; gtk_widget_show(b); pd->num_discards++; return &anim; } /* open_connection: connect to a server and announce, using values from the open dialog box where available. If data is one, start local server and players. If data is 2, load game from file in opengamefiletext. If data is 3, reconnect using existing fd in third arg. If data is 4, open to host:port in fourth arg, where :NNNN means new port on same host. */ void open_connection(GtkWidget *w UNUSED, gpointer data, int oldfd, char *newaddr) { char buf[256], buf2[256]; int i; int mode = (int)(intptr_t)data; int toserver, fromserver; FILE *serverstream; databuffer_clear(&server_history); if ( mode < 3 || mode == 4 ) { if ( GTK_WIDGET_SENSITIVE(openfile) ) { sprintf(address,"%s",gtk_entry_get_text(GTK_ENTRY(openfiletext))); /* strip any initial spaces */ while ( address[0] == ' ' ) { int jj; int ll = strlen(address); for (jj=0;jj 0 && mode < 4) { char cmd[1024]; char ibuf[10]; strcpy(cmd,"mj-server --id-order-seats --server "); strmcat(cmd,address,sizeof(cmd)-strlen(cmd)); if ( ! GTK_TOGGLE_BUTTON(openallowdisconnectbutton)->active ) { strmcat(cmd, " --exit-on-disconnect",sizeof(cmd)-strlen(cmd)); } if ( GTK_TOGGLE_BUTTON(opensaveonexitbutton)->active ) { strmcat(cmd, " --save-on-exit",sizeof(cmd)-strlen(cmd)); } if ( GTK_TOGGLE_BUTTON(openrandomseatsbutton)->active ) { strmcat(cmd, " --random-seats",sizeof(cmd)-strlen(cmd)); } if ( mode == 1 ) { /* if resuming a game, timeout was set initially */ gtk_spin_button_update(GTK_SPIN_BUTTON(opentimeoutspinbutton)); strcat(cmd, " --timeout "); sprintf(ibuf,"%d", gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(opentimeoutspinbutton))); strmcat(cmd,ibuf,sizeof(cmd)-strlen(cmd)); } if ( mode == 2 ) { const char *gfile; FILE *foo; /* if we're restarting a game, allow the robots to take over the previous players */ strmcat(cmd, " --no-id-required",sizeof(cmd)-strlen(cmd)); gfile = gtk_entry_get_text(GTK_ENTRY(opengamefiletext)); /* check that we can actually read the file */ if ( !gfile || !(foo = fopen(gfile,"r")) ) { char rdmsg[] = "Can't read game file: "; strcpy(buf,rdmsg); strmcat(buf,strerror(errno),sizeof(buf)-sizeof(rdmsg)); error_dialog_popup(buf); return; } fclose(foo); strmcat(cmd, " --load-game ",sizeof(cmd)-strlen(cmd)); strmcat(cmd, gfile,sizeof(cmd)-strlen(cmd)); } if ( ! start_background_program_with_io(cmd,&toserver,&fromserver) ) { error_dialog_popup("Couldn't start server; unexpected failure"); return; } close(toserver); serverstream = fdopen(fromserver,"r"); fgets(buf2,255,serverstream); /* if the server managed to open its socket, the first thing it writes to stdout is OK: server-address */ if ( strncmp(buf2,"OK",2) != 0 ) { error_dialog_popup("server failed to start"); return; } } } if ( mode == 3 ) { the_game = client_reinit(oldfd); } else { the_game = client_init(address); } if ( the_game == NULL ) { sprintf(buf,"Couldn't connect to server:\n%s",strerror(errno)); if ( data ) { strcat(buf,"\nPerhaps server didn't start -- check error messages"); } error_dialog_popup(buf); return; } the_game->userdata = malloc(sizeof(GameExtras)); if ( the_game->userdata == NULL ) { warn("Fatal error: couldn't malloc game extra data"); exit(1); } our_player = the_game->players[0]; gtk_widget_set_sensitive(openmenuentry,0); gtk_widget_set_sensitive(newgamemenuentry,0); gtk_widget_set_sensitive(resumegamemenuentry,0); gtk_widget_set_sensitive(savemenuentry,1); gtk_widget_set_sensitive(saveasmenuentry,1); gtk_widget_set_sensitive(closemenuentry,1); close_saving_posn(open_dialog); control_server_processing(1); if ( monitor ) { // do everything from client_connect except send packet. Yech. initialize_player(the_game->players[0]); set_player_id(the_game->players[0],our_id); set_player_name(the_game->players[0],name); } else { client_connect(the_game,our_id,name); } if ( mode == 1 || mode == 2 ) { for ( i = 0 ; i < 3 ; i++ ) { if ( GTK_TOGGLE_BUTTON(openplayercheckboxes[i])->active ) { char cmd[1024]; strcpy(cmd,"mj-player --server "); strmcat(cmd,address,sizeof(cmd)-strlen(cmd)); snprintf(cmd+strlen(cmd),100," --id %d ",i+2); strmcat(cmd," ",sizeof(cmd)-strlen(cmd)); if ( robot_names[i][0] ) { strmcat(cmd," --name ",sizeof(cmd)-strlen(cmd)); strmcat(cmd,robot_names[i],sizeof(cmd)-strlen(cmd)); } if ( difficulty != 10 ) { char s[100]; snprintf(s,100," --randomness %.1f ",(10-difficulty)/10.0); strmcat(cmd,s,sizeof(cmd)-strlen(cmd)); } if ( robot_options[i][0] ) { strmcat(cmd," ",sizeof(cmd)-strlen(cmd)); /* options are not quoted, just added as is */ strmcat(cmd,robot_options[i],sizeof(cmd)-strlen(cmd)); } if ( ! start_background_program(cmd) ) { error_dialog_popup("Unexpected error starting player"); return; } if ( i < 2 ) usleep(250*1000); } } } } /* shut down the connection - unless arg is given, in which case do everything except actually closing the connection */ void close_connection(int keepalive) { int i; game_over = 0; /* cos there is now no game to be over */ control_server_processing(0); free(the_game->userdata); if ( keepalive ) client_close_keepconnection(the_game); else client_close(the_game); the_game = NULL; gtk_widget_set_sensitive(openmenuentry,1); gtk_widget_set_sensitive(newgamemenuentry,1); gtk_widget_set_sensitive(resumegamemenuentry,1); gtk_widget_set_sensitive(savemenuentry,0); gtk_widget_set_sensitive(saveasmenuentry,0); gtk_widget_set_sensitive(closemenuentry,0); /* clear display */ for ( i=0; i < 4; i++ ) { pdisps[i].player = NULL; playerdisp_update(&pdisps[i],0); playerdisp_clear_discards(&pdisps[i]); } clear_wall(); /* and pop down irrelevant dialogs that are up */ gtk_widget_hide(chow_dialog); gtk_widget_hide(ds_dialog); gtk_widget_hide(discard_dialog->widget); gtk_widget_hide(continue_dialog); gtk_widget_hide(turn_dialog); gtk_widget_hide(scoring_dialog); /* zap the game option dialog */ game_option_panel = build_or_refresh_option_panel(NULL,game_option_panel); gtk_widget_set_sensitive(gameoptionsmenuentry,0); } static int get_relative_posn(GtkWidget *w, GtkWidget *z, int *x, int *y) { return gtk_widget_translate_coordinates(z,w,0,0,x,y); } /* utility function for wall display: given the tile in wall ori, stack j, and top layer (k = 0), bottom layer (k = 1) or loose tile (k = -1), return its x and y coordinates in the discard area. If the isdead argument is one, the tile is to be displaced as in the dead wall. This assumes that the board widget has a border into which we can shift the wall a little bit. */ static void wall_posn(int ori, int j, int k, int isdead, int *xp, int *yp) { int x=0, y=0; /* calculate basic position with respect to discard area */ /* because there are possibly three stacks (the two layers and the loose tiles), which we want to display in a formalized perspective, by displacing layers by tile_spacing from each other, each wall occupies tile_height + 2*tile_spacing in the forward direction */ switch (ori) { case 0: x = ((WALL_SIZE/4)/2 - (j+1))*tile_width +tile_spacing; y = (WALL_SIZE/4)/2 * tile_width + (k+1)*tile_spacing ; break; case 1: x = (WALL_SIZE/4)/2 * tile_width + (-k)*tile_spacing + 2*tile_spacing; y = tile_height + 2*tile_spacing + j*tile_width; break; case 2: x = tile_height + 2*tile_spacing + j*tile_width; y = (k+1)*tile_spacing; break; case 3: x = (k+1)*tile_spacing; y = ((WALL_SIZE/4)/2 - (j+1))*tile_width + tile_spacing; break; } /* the board has been sized assuming MAX_WALL_SIZE; so if we are playing with a smaller wall, we should centre the walls */ x += tile_width*((MAX_WALL_SIZE - WALL_SIZE)/8)/2; y += tile_width*((MAX_WALL_SIZE - WALL_SIZE)/8)/2; if ( isdead ) { switch ( wall_ori(wall_game_to_board(gextras(the_game)->orig_live_end)) ) { case 0: x -= tile_spacing; break; case 1: y += tile_spacing; break; case 2: x += tile_spacing; break; case 3: y -= tile_spacing; break; } } *xp = x; *yp = y; } static void clear_wall(void) { int i; /* destroy all existing widgets */ for ( i=0; i= 0 ; k-- ) { n = i*(WALL_SIZE/4)+(2*j)+k; ori = wall_ori(n); wall_posn(ori,j,k,the_game && wall_board_to_game(n) >= the_game->wall.live_end,&x,&y); w = gtk_button_new(); gtk_widget_set_name(w,"tile"); GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_FOCUS); pm = gtk_pixmap_new(tilepixmaps[ori][HiddenTile],NULL); gtk_widget_show(pm); gtk_container_add(GTK_CONTAINER(w),pm); button_set_tile(w,HiddenTile,ori); gtk_widget_show(w); gtk_fixed_put(GTK_FIXED(discard_area),w,x,y); wall[n] = w; } } } } /* adjust the loose tiles. The argument is the tile that *has just* been drawn, which is normally the_game->wall.dead_end */ static AnimInfo *adjust_wall_loose (int tile_drawn) { int i,n,m,ori,j,x,y; AnimInfo *retval = NULL; static AnimInfo anim; i = wall_game_to_board(tile_drawn); if ( tile_drawn < the_game->wall.size ) { /* which tile gets drawn? If dead_end was even before (i.e. it's now odd), the tile drawn was the 2nd to last; otherwise it's the last */ if ( tile_drawn%2 == 1 ) { /* tile drawn was 2nd to last */ i = wall_game_to_board(tile_drawn-1); } else { /* tile drawn was actually beyond the then dead end */ i = wall_game_to_board(tile_drawn+1); } assert(i < the_game->wall.size && wall[i]); anim.target = NULL; anim.t = HiddenTile; anim.ori = wall_ori(i); get_relative_posn(boardframe,wall[i], &anim.x,&anim.y); retval = &anim; gtk_widget_destroy(wall[i]); wall[i] = NULL; /* if we are playing with a 14 tile wall, and the dead wall is now 14 again, then move two tiles from the live wall into the dead wall. */ if ( !game_get_option_value(the_game,GODeadWall16,NULL).optbool && game_get_option_value(the_game,GODeadWall,NULL).optbool && (tile_drawn-the_game->wall.live_end == 14) ) { /* the bottom tile that gets moved: now second tile in dead wall */ n = wall_game_to_board(the_game->wall.live_end+1); /* if tile exists, reposition */ if ( the_game->wall.live_used < the_game->wall.live_end+1 ) { ori = wall_ori(n); j = (n - (the_game->wall.size/4)*(n/(the_game->wall.size/4)))/2; wall_posn(ori,j,1,1,&x,&y); gtk_fixed_move(GTK_FIXED(discard_area),wall[n],x,y); } /* and the top tile */ n = wall_game_to_board(the_game->wall.live_end); /* if tile exists, reposition */ if ( the_game->wall.live_used < the_game->wall.live_end ) { ori = wall_ori(n); j = (n - (the_game->wall.size/4)*(n/(the_game->wall.size/4)))/2; wall_posn(ori,j,0,1,&x,&y); gtk_fixed_move(GTK_FIXED(discard_area),wall[n],x,y); } } } /* if the number of loose tiles remaining is even, move the last two into position */ if ( tile_drawn%2 == 0 ) { /* number of the top loose tile */ n = wall_game_to_board(tile_drawn-2); /* it gets moved back three stacks */ m = ((n-6)+the_game->wall.size)%the_game->wall.size; ori = wall_ori(m); j = (m - (the_game->wall.size/4)*(m/(the_game->wall.size/4)))/2; wall_posn(ori,j,-1,game_get_option_value(the_game,GODeadWall,NULL).optbool && 1,&x,&y); gtk_fixed_move(GTK_FIXED(discard_area),wall[n],x,y); gdk_window_raise(wall[n]->window); button_set_tile(wall[n],HiddenTile,ori); /* and now the bottom tile, gets moved back one stack */ n = wall_game_to_board(tile_drawn-1); m = ((n-2)+the_game->wall.size)%the_game->wall.size; ori = wall_ori(m); j = (m - (the_game->wall.size/4)*(m/(the_game->wall.size/4)))/2; wall_posn(ori,j,-1,game_get_option_value(the_game,GODeadWall,NULL).optbool && 1,&x,&y); gtk_fixed_move(GTK_FIXED(discard_area),wall[n],x,y); gdk_window_raise(wall[n]->window); button_set_tile(wall[n],HiddenTile,ori); } return retval; } /* popup a claim window. This is a real pain, cos we have to work out where to put it. thinking means uses the transparent thinking box To keep it simple, we'll put it half way along the appropriate edge, centered two tileheights in. */ static void playerdisp_popup_claim_window(PlayerDisp *pd, const char *lab, int thinking) { GtkRequisition r; gint x,y,w,h; int cori = pd->orientation; /* first of all, create the claim window if not already done */ if ( pd->claimw == NULL ) { GtkWidget *box; // all this should be handled by styles pd->claimw = gtk_event_box_new(); gtk_widget_set_name(GTK_WIDGET(pd->claimw),"claim"); box = gtk_hbox_new(0,0); gtk_widget_show(box); gtk_widget_set_name(box,"claim"); gtk_container_add(GTK_CONTAINER(pd->claimw),box); /* should parametrize this some time */ gtk_container_set_border_width(GTK_CONTAINER(box),7); pd->claimlab = gtk_label_new(""); gtk_widget_set_name(pd->claimlab,"claim"); pd->claim_serial = -1; pd->claim_time = -10000; gtk_widget_show(pd->claimlab); gtk_box_pack_start(GTK_BOX(box),pd->claimlab,0,0,0); vlazy_fixed_put(VLAZY_FIXED(boardfixed),pd->claimw,0,0); // now do the same for thinkbox - we no longer support gtk1 box = pd->thinkbox = gtk_hbox_new(0,0); gtk_widget_show(box); gtk_widget_set_name(box,"think"); GtkWidget *tlab = gtk_label_new("..."); gtk_widget_show(tlab); gtk_widget_set_name(tlab,"think"); gtk_box_pack_start(GTK_BOX(box),tlab,0,0,0); vlazy_fixed_put(VLAZY_FIXED(boardfixed),box,0,0); } GtkWidget *thisw = thinking ? pd->thinkbox : pd->claimw; /* set the label */ if ( ! thinking ) gtk_label_set_text(GTK_LABEL(pd->claimlab),lab); pd->claim_serial = the_game->serial; pd->claim_time = now_time(); /* calculate the position given the current label */ gtk_widget_size_request(board,&r); w = r.width; h = r.height; gtk_widget_size_request(thisw,&r); switch ( cori ) { case 0: x = w/2 - r.width/2; y = h - r.height/2 - 2*tile_height; break; case 1: x = w - r.width/2 - 2*tile_height; y = h/2 - r.height/2; break; case 2: x = w/2 - r.width/2; y = - r.height/2 + 2*tile_height; break; case 3: x = - r.width/2 + 2*tile_height; y = h/2 - r.height/2; break; default: /* to shut up warnings */ x = 0; y = 0; error("bad value of cori in switch in playerdisp_update"); } vlazy_fixed_move(VLAZY_FIXED(boardfixed),thisw,x,y); /* if not already popped up */ if ( !GTK_WIDGET_VISIBLE(thisw) ) { gtk_widget_show(thisw); } } /* check_claim_window: see whether a claim window should be popped down. This calls itself as a timeout, hence the prototype. If a claim window refers to an old discard, or we are in state handcomplete, pop it down unconditionally. Otherwise, if it's more than two seconds old, and we're not in the state Discarded, pop it down; if we're not in the discarded state, but it's less than two seconds old, check again later. */ static gint check_claim_window(gpointer data) { PlayerDisp *pd = data; /* Unconditional popdown */ if ( !the_game || pd->claim_serial < the_game->serial || the_game->state == HandComplete ) { gtk_widget_hide(pd->claimw); gtk_widget_hide(pd->thinkbox); pd->claim_serial = -1; pd->claim_time = -10000; } else if ( the_game->state != Discarded ) { int interval = now_time() - pd->claim_time; if ( interval > 2000 ) { /* pop down now */ gtk_widget_hide(pd->claimw); gtk_widget_hide(pd->thinkbox); pd->claim_serial = -1; pd->claim_time = -10000; } else { /* schedule a time out to check this window again when the two seconds have expired */ gtk_timeout_add(interval+100,check_claim_window,(gpointer)pd); } } return FALSE; /* don't run *this* timeout again! */ } /* read_rcfile: given a file name, parse it as an rcfile */ /* at the moment, this just copies options, but doesn't act on them. This should be fixed for the display options at least. The arguments say which options are to be read or updated, using groups defined by the XmjrcGroup enum flag bits. It is an error both to read and update a group. */ int read_or_update_rcfile(char *rcfile, XmjrcGroup read_groups, XmjrcGroup update_groups) { char inbuf[1024]; char rcfbuf[1024]; char nrcfile[1024]; char *inptr; int i; int usingrc = 0; FILE *rcf = NULL; FILE *ncf = NULL; # ifndef WIN32 const char *nl = "\n"; # else const char *nl = "\r\n"; # endif /* sanity check */ if ( read_groups & update_groups ) { warn("read_or_update_rcfile called with simultaneous read and update"); return 0; } if ( ! rcfile ) { char *hd = get_homedir(); if ( hd == NULL ) { # ifdef WIN32 // no home dir. If we can write into the execution directory // use that. Otherwise, use root. if ( access(".",W_OK) ) { info("no home directory, using execution directory"); hd = "."; } else { info("no home directory, can't write exec directory, using root"); hd = "C:"; } # else // no homedir, just use tmp info("no home diretory, using /tmp"); hd = "/tmp"; # endif } strmcpy(rcfbuf,hd,sizeof(rcfbuf)); // $HOME can trigger overflow... # ifdef WIN32 strmcat(rcfbuf,"/xmj.ini",sizeof(rcfbuf)-strlen(rcfbuf)); # else strmcat(rcfbuf,"/.xmjrc",sizeof(rcfbuf)-strlen(rcfbuf)); # endif rcfile = rcfbuf; usingrc = 1; /* not an error not to find the standard rc file */ } rcf = fopen(rcfile,"r"); // on Windows, if this failed on the standard file, // look in the root, in case there's an old file there. // If that fails, look in the exec directory, in case there's // an ini file shipped with the distribution. # ifdef WIN32 if ( !rcf && usingrc ) { rcf = fopen("C:/xmj.ini","r"); if ( rcf ) { info("Found old xmj.ini in root directory"); } else if ( (rcf = fopen("./xmj.ini","r")) ) { info("Found old/system xmj.ini in exec directory"); } } # endif if ( ! rcf && ! usingrc ) { warn("Couldn't open rcfile %s: %s",rcfile,strerror(errno)); return 0; } if ( update_groups ) { strmcpy(nrcfile,rcfile,1020); # ifdef WIN32 /* I don't know whether this helps on Windows ME */ if ( strcmp(nrcfile + strlen(nrcfile) - 4, ".ini") == 0 ) { strcpy(nrcfile + strlen(nrcfile) - 4,".new"); } else { strcat(nrcfile,".new"); } # else strcat(nrcfile,".new"); # endif ncf = fopen(nrcfile,"w"); if ( ! ncf ) { warn("Couldn't open new rcfile %s: %s",nrcfile,strerror(errno)); if ( rcf ) fclose(rcf); return 0; } } while ( rcf && ! feof(rcf) ) { if ( ! fgets(inbuf,1024,rcf) ) { if ( feof(rcf) ) break; warn("Unexpected error from fgets: %s",strerror(errno)); fclose(rcf); return 0; } inptr = inbuf; while ( isspace(*inptr) ) inptr++; if ( *inptr == '#' ) { ; /* comment line */ } else if ( ( (strncmp(inptr,"XmjOption",9) == 0) && (inptr += 9) ) || ( (strncmp(inptr,"Display",7) == 0) && (inptr += 7) ) ) { /* if updating display, don't even copy this, since we'll be writing it from UI later */ if ( update_groups & XmjrcDisplay ) continue; /* if not reading display, skip */ if ( read_groups & XmjrcDisplay ) { while ( isspace(*inptr) ) inptr++; if ( strncmp(inptr,"Animate",7) == 0 ) { inptr += 7; sscanf(inptr,"%d",&animate); } else if ( strncmp(inptr,"DialogPosition",14) == 0 ) { inptr += 14; while ( isspace(*inptr) ) inptr++; if ( strncmp(inptr,"central",7) == 0 ) { dialogs_position = DialogsCentral; } else if ( strncmp(inptr,"below",5) == 0 ) { dialogs_position = DialogsBelow; } else if ( strncmp(inptr,"popup",5) == 0 ) { dialogs_position = DialogsPopup; } else { warn("unknown argument for Display DialogPosition: %s", inptr); } } else if ( strncmp(inptr,"NoPopups",8) == 0 ) { inptr += 8; sscanf(inptr,"%d",&nopopups); } else if ( strncmp(inptr,"Tiletips",8) == 0 ) { inptr += 8; sscanf(inptr,"%d",&tiletips); } else if ( strncmp(inptr,"RotateLabels",12) == 0 ) { inptr += 12; sscanf(inptr,"%d",&rotatelabels); } else if ( strncmp(inptr,"ThinkingClaim",13) == 0 ) { inptr += 13; sscanf(inptr,"%d",&thinking_claim); } else if ( strncmp(inptr,"WarnMahjong",11) == 0 ) { inptr += 11; sscanf(inptr,"%d",&alert_mahjong); } else if ( strncmp(inptr,"IconifyDialogs",14) == 0 ) { inptr += 14; sscanf(inptr,"%d",&iconify_dialogs_with_main); } else if ( strncmp(inptr,"InfoInMain",10) == 0 ) { inptr += 10; sscanf(inptr,"%d",&info_windows_in_main); } else if ( strncmp(inptr,"MainFont",8) == 0 ) { inptr += 8; while ( isspace(*inptr) ) inptr++; sscanf(inptr,"%255[^\r\n]",main_font_name); } else if ( strncmp(inptr,"TextFont",8) == 0 ) { inptr += 8; while ( isspace(*inptr) ) inptr++; sscanf(inptr,"%255[^\r\n]",text_font_name); } else if ( strncmp(inptr,"TableColour",11) == 0 ) { inptr += 11; while ( isspace(*inptr) ) inptr++; sscanf(inptr,"%255[^\r\n]",table_colour_name); /* handle colour name formats from gtk1 versions, but not vice versa */ if ( strncmp("rgb:",table_colour_name,4) == 0 ) { char* p = table_colour_name+4; char buf[256]; buf[0] = '#'; int j=1; while ( *p && (*p != '/' || p++)) buf[j++] = *(p++); buf[j] = 0; strcpy(table_colour_name,buf); } } else if ( strncmp(inptr,"ShowWall",8) == 0 ) { inptr += 8; while ( isspace(*inptr) ) inptr++; if ( strncmp(inptr,"always",6) == 0 ) { pref_showwall = 1; } else if ( strncmp(inptr,"when-room",9) == 0 ) { pref_showwall = -1; } else if ( strncmp(inptr,"never",5) == 0 ) { pref_showwall = 0; } else { warn("unknown argument for Display ShowWall: %s", inptr); } } else if ( strncmp(inptr,"Size",4) == 0) { inptr += 4; sscanf(inptr,"%d",&display_size); } else if ( strncmp(inptr,"SortTiles",9) == 0) { inptr += 9; while ( isspace(*inptr) ) inptr++; if ( strncmp(inptr,"always",6) == 0 ) { sort_tiles = SortAlways; } else if ( strncmp(inptr,"deal",4) == 0 ) { sort_tiles = SortDeal; } else if ( strncmp(inptr,"never",5) == 0 ) { sort_tiles = SortNever; } else { warn("unknown argument for Display SortTiles: %s", inptr); } /* N.B. This has to be tested before Tileset ! */ } else if ( strncmp(inptr,"TilesetPath",11) == 0 ) { inptr += 11; while ( isspace(*inptr) ) inptr++; tileset_path = malloc(strlen(inptr+1)); /* FIXME: memory leak */ if ( ! tileset_path ) { warn("out of memory"); exit(1); } sscanf(inptr,"%[^\r\n]",tileset_path); } else if ( strncmp(inptr,"Tileset",7) == 0 ) { inptr += 7; while ( isspace(*inptr) ) inptr++; tileset = malloc(strlen(inptr+1)); /* FIXME: memory leak */ if ( ! tileset ) { warn("out of memory"); exit(1); } sscanf(inptr,"%[^\r\n]",tileset); } else if ( strncmp(inptr,"RobotName",9) == 0 ) { /* This is obsoleted -- it's been moved to the OpenDialog settings. We won't take any notice of it, but we won't complain about it either. */ } else if ( strncmp(inptr,"Gtk2Rcfile",10) == 0 ) { inptr += 10; while ( isspace(*inptr) ) inptr++; char *buf = malloc(strlen(inptr+1)); sscanf(inptr,"%[^\r\n]",buf); strmcpy(gtk2_rcfile,buf,sizeof(gtk2_rcfile)); free(buf); } else if ( strncmp(inptr,"UseSystemGtkrc",14) == 0 ) { inptr += 14; sscanf(inptr,"%d",&use_system_gtkrc); } else { warn("unknown Display: %s",inptr); } } } else if ( strncmp(inptr,"GameOption",10) == 0 ) { CMsgGameOptionMsg *gom; if ( update_groups & XmjrcGame ) continue; if ( read_groups & XmjrcGame ) { gom = (CMsgGameOptionMsg *)decode_cmsg(inptr); if ( gom ) { game_set_option_in_table(&prefs_table,&gom->optentry); } else { warn("Error reading game option from rcfile; deleting option"); } } } else if ( strncmp(inptr,"CompletedGames",14) == 0 ) { if ( update_groups & XmjrcMisc ) continue; if ( read_groups & XmjrcMisc ) { inptr += 14; sscanf(inptr,"%d",&completed_games); } } else if ( strncmp(inptr,"NagDate",7) == 0 ) { if ( update_groups & XmjrcMisc ) continue; if ( read_groups & XmjrcMisc ) { inptr += 7; /* there's no portable way to scan a time_t ... */ long long ttt; sscanf(inptr,"%lld",&ttt); nag_date = (time_t)ttt; } } else if ( strncmp(inptr,"NagState",8) == 0 ) { if ( update_groups & XmjrcMisc ) continue; if ( read_groups & XmjrcMisc ) { inptr += 8; sscanf(inptr,"%d",&nag_state); } } else if ( strncmp(inptr,"DebugReports",12) == 0 ) { if ( update_groups & XmjrcMisc ) continue; if ( read_groups & XmjrcMisc ) { inptr += 12; while ( isspace(*inptr) ) inptr++; if ( strncmp(inptr,"never",5) == 0 ) { debug_reports = DebugReportsNever; } else if ( strncmp(inptr,"ask",3) == 0 ) { debug_reports = DebugReportsAsk; } else if ( strncmp(inptr,"always",6) == 0 ) { debug_reports = DebugReportsAlways; } else { warn("unknown DebugReports value %s",inptr); } } } else if ( strncmp(inptr,"OpenDialog",10) == 0 ) { inptr += 10; if ( update_groups & XmjrcOpen ) continue; if ( read_groups & XmjrcOpen ) { while ( isspace(*inptr) ) inptr++; if ( strncmp(inptr,"Address",7) == 0 ) { inptr += 7; sscanf(inptr,"%255s",address); } else if ( strncmp(inptr,"Name",4) == 0 ) { inptr += 4; sscanf(inptr," %255[^\r\n]",name); } else if ( strncmp(inptr,"Difficulty",10) == 0 ) { inptr += 10; sscanf(inptr," %d",&difficulty); } else if ( strncmp(inptr,"RobotName",9) == 0 ) { int i; char buf[128]; buf[127] = 0; inptr += 9; sscanf(inptr,"%d %127[^\r\n]",&i,buf); if ( i < 2 || i > 4 ) { warn("bad robot argument to OpenDialog RobotName"); } else { strcpy(robot_names[i-2],buf); } } else if ( strncmp(inptr,"RobotOptions",12) == 0 ) { int i; char buf[128]; buf[127] = 0; inptr += 12; sscanf(inptr,"%d %127[^\r\n]",&i,buf); if ( i < 2 || i > 4 ) { warn("bad robot argument to OpenDialog RobotOptions"); } else { strcpy(robot_options[i-2],buf); } } } } else if ( strncmp(inptr,"Playing",7) == 0 ) { inptr += 7; if ( update_groups & XmjrcPlaying ) continue; if ( read_groups & XmjrcPlaying ) { while ( isspace(*inptr) ) inptr++; if ( strncmp(inptr,"AutoSpecials",12) == 0 ) { inptr += 12; sscanf(inptr,"%d",&playing_auto_declare_specials); } else if ( strncmp(inptr,"AutoLosing",10) == 0 ) { inptr += 10; sscanf(inptr,"%d",&playing_auto_declare_losing); } else if ( strncmp(inptr,"AutoWinning",11) == 0 ) { inptr += 11; sscanf(inptr,"%d",&playing_auto_declare_winning); } } } else { warn("unknown setting in rcfile %s: %s",rcfile,inptr); } if ( update_groups ) { if ( fputs(inbuf,ncf) == EOF ) { warn("Error writing to new rcfile %s: %s",nrcfile,strerror(errno)); fclose(rcf); fclose(ncf); return 0; } } } if ( rcf ) fclose(rcf); if ( update_groups & XmjrcDisplay ) { fprintf(ncf,"Display Animate %d%s",animate,nl); fprintf(ncf,"Display DialogPosition %s%s", (dialogs_position == DialogsBelow) ? "below" : (dialogs_position == DialogsPopup) ? "popup" : "central", nl); fprintf(ncf,"Display NoPopups %d%s",nopopups,nl); fprintf(ncf,"Display Tiletips %d%s",tiletips,nl); fprintf(ncf,"Display RotateLabels %d%s",rotatelabels,nl); fprintf(ncf,"Display ThinkingClaim %d%s",thinking_claim,nl); fprintf(ncf,"Display WarnMahjong %d%s",alert_mahjong,nl); fprintf(ncf,"Display IconifyDialogs %d%s",iconify_dialogs_with_main,nl); fprintf(ncf,"Display InfoInMain %d%s",info_windows_in_main,nl); if ( main_font_name[0] ) { fprintf(ncf,"Display MainFont %s%s",main_font_name,nl); } if ( text_font_name[0] ) { fprintf(ncf,"Display TextFont %s%s",text_font_name,nl); } if ( table_colour_name[0] ) { fprintf(ncf,"Display TableColour %s%s",table_colour_name,nl); } fprintf(ncf,"Display ShowWall %s%s", pref_showwall == 1 ? "always" : pref_showwall == 0 ? "never" : "when-room", nl); if ( display_size ) { fprintf(ncf,"Display Size %d%s",display_size,nl); } static const char *stlist[] = { "always", "deal", "never" }; fprintf(ncf,"Display SortTiles %s%s",stlist[sort_tiles],nl); if ( tileset && tileset[0] ) fprintf(ncf,"Display Tileset %s%s",tileset,nl); if ( tileset_path && tileset_path[0] ) fprintf(ncf,"Display TilesetPath %s%s",tileset_path,nl); if ( gtk2_rcfile[0] ) { fprintf(ncf,"Display Gtk2Rcfile %s%s",gtk2_rcfile,nl); } if ( use_system_gtkrc ) { fprintf(ncf,"Display UseSystemGtkrc %d%s",use_system_gtkrc,nl); } } if ( update_groups & XmjrcGame ) { char *pline; CMsgGameOptionMsg gom; gom.type = CMsgGameOption; gom.id = 0; for ( i = 0 ; i < prefs_table.numoptions ; i++ ) { if ( ! prefs_table.options[i].enabled ) continue; gom.optentry = prefs_table.options[i]; pline = encode_cmsg((CMsgMsg *)&gom); # ifndef WIN32 pline[strlen(pline)-1] = 0; pline[strlen(pline)-1] = '\n'; # endif fprintf(ncf,"%s",pline); } } if ( update_groups & XmjrcMisc ) { fprintf(ncf,"CompletedGames %d%s",completed_games,nl); fprintf(ncf,"NagState %d%s",nag_state,nl); /* no portable way to deal with time_t */ long long ttt = nag_date; fprintf(ncf,"NagDate %lld%s",ttt,nl); fprintf(ncf,"DebugReports %s%s", debug_reports == DebugReportsNever ? "never" : debug_reports == DebugReportsAsk ? "ask" : debug_reports == DebugReportsAlways ? "always" : "unspecified",nl); } if ( update_groups & XmjrcOpen ) { /* don't save if it's the default */ if ( strcmp(redirected ? origaddress : address,"localhost:5000") != 0 ) { fprintf(ncf,"OpenDialog Address %s%s",redirected ? origaddress : address,nl); } /* don't save name if it's empty */ if ( name[0] ) { fprintf(ncf,"OpenDialog Name %s%s",name,nl); } /* don't save difficulty if it's 10 (default) */ if ( difficulty < 10 ) { fprintf(ncf,"OpenDialog Difficulty %d%s",difficulty,nl); } for ( i = 0; i < 3; i++ ) { if ( robot_names[i][0] ) fprintf(ncf,"OpenDialog RobotName %d %s%s",i+2,robot_names[i],nl); if ( robot_options[i][0] ) fprintf(ncf,"OpenDialog RobotOptions %d %s%s",i+2,robot_options[i],nl); } } if ( update_groups & XmjrcPlaying ) { fprintf(ncf,"Playing AutoSpecials %d%s",playing_auto_declare_specials,nl); fprintf(ncf,"Playing AutoLosing %d%s",playing_auto_declare_losing,nl); fprintf(ncf,"Playing AutoWinning %d%s",playing_auto_declare_winning,nl); } if ( update_groups ) { fclose(ncf); ncf = fopen(nrcfile,"r"); if ( ! ncf ) { warn("Couldn't re-open new rc file %s: %s",nrcfile,strerror(errno)); return 0; } rcf = fopen(rcfile,"w"); if ( ! rcf ) { warn("couldn't open rcfile %s for update: %s",rcfile,strerror(errno)); fclose(ncf); return 0; } while ( ! feof(ncf) ) { fgets(inbuf,1024,ncf); if ( feof(ncf) ) continue; if ( fputs(inbuf,rcf) == EOF ) { warn("Error writing to rcfile %s: %s",rcfile,strerror(errno)); fclose(rcf); fclose(ncf); return 0; } } fclose(rcf); fclose(ncf); unlink(nrcfile); } return 1; } static int read_rcfile(char *rcfile) { return read_or_update_rcfile(rcfile,XmjrcAll,XmjrcNone); } /* Apply the game options from the preferences to the current game */ void apply_game_prefs(void) { int i; GameOptionEntry *goe; PMsgSetGameOptionMsg psgom; char buf[256]; if ( ! the_game ) return; psgom.type = PMsgSetGameOption; for ( i = 0; i < prefs_table.numoptions; i++ ) { goe = &prefs_table.options[i]; if ( ! goe->enabled ) continue; /* not in preferences */ if ( goe->protversion > the_game->protversion ) continue; /* can't use it */ strmcpy(psgom.optname,goe->name,16); switch ( goe->type ) { case GOTInt: sprintf(buf,"%d",goe->value.optint); break; case GOTNat: sprintf(buf,"%d",goe->value.optnat); break; case GOTBool: sprintf(buf,"%d",goe->value.optbool); break; case GOTScore: sprintf(buf,"%d",goe->value.optscore); break; case GOTString: sprintf(buf,"%s",goe->value.optstring); break; } psgom.optvalue = buf; send_packet(&psgom); } } void create_display(void) { GtkWidget *tmpw, *inboard; int i; if ( main_font_name[0] ) { char c[300]; strcpy(c,"gtk-font-name = \""); char *d = c + strlen(c); strmcpy(d,main_font_name,256); strcat(c,"\""); gtk_rc_parse_string(c); } if ( text_font_name[0] ) { char c[300]; strcpy(c,"style \"text\" { font_name = \""); char *d = c + strlen(c); strmcpy(d,text_font_name,256); strcat(c,"\" }"); gtk_rc_parse_string(c); /* having redefined the styles, we have to recompute them */ gtk_rc_reset_styles(gtk_settings_get_default()); } if ( table_colour_name[0] ) { char c[300]; strcpy(c,"style \"table\" { bg[NORMAL] = \""); char *d = c + strlen(c); strmcpy(d,table_colour_name,256); strcat(c,"\" }"); gtk_rc_parse_string(c); /* having redefined the styles, we have to recompute them */ gtk_rc_reset_styles(gtk_settings_get_default()); } gdk_color_parse("red",&highlightcolor); /* set the size if not given by user */ pdispwidth = display_size; if ( pdispwidth == 0 ) { pdispwidth = 19; #ifdef WIN32 if ( gdk_screen_width() <= 800 ) { pdispwidth = 16; if (info_windows_in_main) pdispwidth = 14; } #else if ( gdk_screen_width() <= 800 ) pdispwidth = 17; #endif } /* choose a sensible value for show-wall */ if ( showwall < 0 ) { showwall = ( gdk_screen_height() >= ((dialogs_position == DialogsBelow) ? 1000 : 900)); } if ( dialogs_position == DialogsUnspecified ) dialogs_position = DialogsCentral; topwindow = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_name(topwindow,"topwindow"); gtk_signal_connect (GTK_OBJECT (topwindow), "delete_event", GTK_SIGNAL_FUNC (gtk_main_quit), NULL); /* (Un)iconify our auxiliary windows when we are */ gtk_signal_connect (GTK_OBJECT (topwindow), "map_event", GTK_SIGNAL_FUNC (vis_callback), NULL); gtk_signal_connect (GTK_OBJECT (topwindow), "unmap_event", GTK_SIGNAL_FUNC (vis_callback), NULL); /* do we have a position remembered ? There will some downward drift because of the window manager decorations, but I really can't be bothered to work round that. */ if ( top_pos_set ) { gtk_widget_set_uposition(topwindow,top_x,top_y); } /* Why do we realize the window? Because we want to use it in creating pixmaps. */ gtk_widget_realize(topwindow); /* create the tile pixmaps */ { char pmfile[512]; const char *code; const char *wl[] = { "E", "S", "W", "N" }; GtkRequisition r; GtkWidget *b,*p; Tile i; int numfailures = 0; /* specifying tileset as the empty string is equivalent to having it null, meaning don't look for tiles */ if ( tileset && tileset[0] == 0 ) tileset = 0; /* Now try to find a tile set: we look for a directory named tileset in the tileset_path search path. If it has a file called 1B.xpm (to choose at random) we'll assume it's a tileset directory */ tilepixmapdir = 0; if ( tileset ) { char *start, *end, *tpend; int lastcmpt = 0; int nopath = 0; static char tp[1024]; if ( tileset_path ) start = tileset_path; else start = ""; /* if the tileset is given as an absolute path, we shouldn't use the tileset-path at all */ # ifdef WIN32 if ( tileset[0] == '/' || tileset[0] == '\\' || tileset[1] == ':' ) { start = ""; nopath = 1; } # else if ( tileset[0] == '/' ) { start = ""; nopath = 1; } # endif while ( ! lastcmpt ) { end = strchr(start,PATHSEP); if ( end == 0 ) { end = start+strlen(start); lastcmpt = 1; } if ( start == end && ! nopath ) { strcpy(tp,"."); } else { strmcpy(tp,start,end-start+1); } strcat(tp,"/"); strcat(tp,tileset); tpend = tp + strlen(tp); strcat(tp,"/1B.xpm"); if ( access(tp,R_OK) == 0 ) { *tpend = 0; /* blank out the /1B.xpm */ tilepixmapdir = tp; break; } /* failed; next component */ start = end+1; } if ( ! tilepixmapdir ) { warn("Can't find tileset called %s - using fallback",tileset); } } for ( i = -1; i < MaxTile; i++ ) { if ( tilepixmapdir ) { strmcpy(pmfile,tilepixmapdir,sizeof(pmfile)-1); strcat(pmfile,"/"); } code = tile_code(i); /* only create the error pixmaps once! */ if ( i >= 0 && strcmp(code,"XX") == 0 ) { tilepixmaps[0][i] = tilepixmaps[0][-1]; tilepixmaps[1][i] = tilepixmaps[1][-1]; tilepixmaps[2][i] = tilepixmaps[2][-1]; tilepixmaps[3][i] = tilepixmaps[3][-1]; } else { GdkPixbuf *pix = NULL, *pixr, *pixl, *pixu; if ( tilepixmapdir ) { strmcat(pmfile,code,sizeof(pmfile)-strlen(pmfile)); strmcat(pmfile,".xpm",sizeof(pmfile)-strlen(pmfile)); pix = gdk_pixbuf_new_from_file(pmfile,NULL); } /* If this fails, use the fallback data. The error tile is in position 99, the others are in their natural position */ if ( pix == NULL ) { int j; if ( i < 0 ) j = 99; else j = i; pix = gdk_pixbuf_new_from_xpm_data(fallbackpixmaps[j]); numfailures++; } int h,w; h = gdk_pixbuf_get_height(pix); w = gdk_pixbuf_get_width(pix); tilepixmaps[0][i] = gdk_pixmap_new(topwindow->window,w,h,-1); gdk_draw_pixbuf(tilepixmaps[0][i],NULL,pix, 0,0,0,0,w,h,GDK_RGB_DITHER_NONE,0,0); tilepixmaps[2][i] = gdk_pixmap_new(topwindow->window,w,h,-1); pixu = gdk_pixbuf_rotate_simple(pix,180); gdk_draw_pixbuf(tilepixmaps[2][i],NULL,pixu, 0,0,0,0,w,h,GDK_RGB_DITHER_NONE,0,0); pixr = gdk_pixbuf_rotate_simple(pix,90); tilepixmaps[1][i] = gdk_pixmap_new(topwindow->window,h,w,-1); gdk_draw_pixbuf(tilepixmaps[1][i],NULL,pixr, 0,0,0,0,h,w,GDK_RGB_DITHER_NONE,0,0); pixl = gdk_pixbuf_rotate_simple(pix,270); tilepixmaps[3][i] = gdk_pixmap_new(topwindow->window,h,w,-1); gdk_draw_pixbuf(tilepixmaps[3][i],NULL,pixl, 0,0,0,0,h,w,GDK_RGB_DITHER_NONE,0,0); g_object_unref((GObject *)pix); g_object_unref((GObject *)pixr); g_object_unref((GObject *)pixl); g_object_unref((GObject *)pixu); } } /* and the tong pixmaps */ for (i=0; i<4; i++) { if ( tilepixmapdir ) { strmcpy(pmfile,tilepixmapdir,sizeof(pmfile)-1); strcat(pmfile,"/"); strmcat(pmfile,"tong",sizeof(pmfile)-strlen(pmfile)); strmcat(pmfile,wl[i],sizeof(pmfile)-strlen(pmfile)); strmcat(pmfile,".xpm",sizeof(pmfile)-strlen(pmfile)); tongpixmaps[0][i] = gdk_pixmap_create_from_xpm(topwindow->window, i>0 ? NULL : &tongmask, NULL, pmfile); } if ( tongpixmaps[0][i] == NULL ) { tongpixmaps[0][i] = gdk_pixmap_create_from_xpm_d(topwindow->window, i>0 ? NULL : &tongmask, NULL,(gchar **)fallbackpixmaps[101+i]); numfailures++; } rotate_pixmap(tongpixmaps[0][i], &tongpixmaps[1][i], &tongpixmaps[2][i], &tongpixmaps[3][i]); } if ( tilepixmapdir && numfailures > 0 ) { char buf[50]; sprintf(buf,"%d tile pixmaps not found: using fallbacks",numfailures); warn(buf); } /* and compute the tile spacing */ gdk_window_get_size(tilepixmaps[0][0],&tile_spacing,NULL); tile_spacing /= 4; b = gtk_button_new(); gtk_widget_set_name(b,"tile"); p = gtk_pixmap_new(tilepixmaps[0][HiddenTile],NULL); gtk_widget_show(p); gtk_container_add(GTK_CONTAINER(b),p); /* we have to ensure that when we get the size of a tile button, it has the style that will be used on the table */ gtk_container_add(GTK_CONTAINER(topwindow),b); gtk_widget_ensure_style(p); gtk_widget_size_request(b,&r); tile_width = r.width; tile_height = r.height; gtk_widget_destroy(b); } /* the outerframe contains the menubar and the board, and if dialogs are at the bottom, a box for them */ /* between the menubar and the board, there is a box for the message and info windows, if info_windows_in_main is set */ outerframe = gtk_vbox_new(0,0); gtk_container_add(GTK_CONTAINER(topwindow),outerframe); gtk_widget_show(outerframe); menubar = menubar_create(); gtk_box_pack_start(GTK_BOX(outerframe),menubar,0,0,0); if ( info_windows_in_main ) { info_box = gtk_hbox_new(0,0); gtk_widget_show(info_box); gtk_box_pack_start(GTK_BOX(outerframe),info_box,0,0,0); } else { info_box = NULL; } /* The structure of the whole board is (at present) an hbox containing leftplayer, vbox, rightplayer with the vbox containing topplayer discardarea bottomplayer (us). The left/right players are actually sandwiched inside vboxes so that they will centre if we force a square aspect ratio. If the wall is shown, player info labels are inserted into the vboxes containing the left and right players. */ /* 2009-06-17. There was something very odd here. Why was the boardframe event box inside the boardfixed rather than the other way round? */ boardfixed = vlazy_fixed_new(); gtk_widget_set_name(boardfixed,"table"); gtk_widget_show(boardfixed); boardframe = gtk_event_box_new(); gtk_widget_show(boardframe); gtk_widget_set_name(boardframe,"table"); gtk_box_pack_start(GTK_BOX(outerframe),boardframe,0,0,0); gtk_container_add(GTK_CONTAINER(boardframe),boardfixed); gtk_widget_set_name(boardframe,"table"); board = gtk_hbox_new(0,0); gtk_widget_set_name(board,"table"); gtk_widget_show(board); vlazy_fixed_put(VLAZY_FIXED(boardfixed),board,0,0); /* the left player */ playerdisp_init(&pdisps[3],3); gtk_widget_show(pdisps[3].widget); tmpw = gtk_vbox_new(0,0); gtk_widget_set_name(tmpw,"table"); gtk_widget_show(tmpw); gtk_box_pack_start(GTK_BOX(board),tmpw,0,0,0); /* label for top player */ if ( showwall ) { GtkWidget *l = gtk_label_new("Player 2"); gtk_widget_set_name(l,"playerlabel"); gtk_label_set_justify(GTK_LABEL(l),GTK_JUSTIFY_CENTER); gtk_label_set_ellipsize(GTK_LABEL(l),PANGO_ELLIPSIZE_END); pdisps[2].infolab = GTK_LABEL(l); gtk_widget_show(l); gtk_box_pack_start(GTK_BOX(tmpw),l,1,1,0); } gtk_box_pack_start(GTK_BOX(tmpw),pdisps[3].widget,1,0,0); /* label for left player */ if ( showwall ) { GtkWidget *l = gtk_label_new("Player 3"); gtk_widget_set_name(l,"playerlabel"); gtk_label_set_justify(GTK_LABEL(l),GTK_JUSTIFY_CENTER); // gtk_label_set_ellipsize(GTK_LABEL(l),PANGO_ELLIPSIZE_END); pdisps[3].infolab = GTK_LABEL(l); gtk_widget_show(l); if ( rotatelabels ) gtk_label_set_angle(GTK_LABEL(l),270); gtk_box_pack_start(GTK_BOX(tmpw),l,1,1,0); } /* the inner box */ inboard = gtk_vbox_new(0,0); gtk_widget_set_name(inboard,"table"); gtk_widget_show(inboard); /* The inner box needs to expand to fit the outer, or else it will shrink to fit the top and bottom players */ gtk_box_pack_start(GTK_BOX(board),inboard,1,1,0); /* the right player */ playerdisp_init(&pdisps[1],1); gtk_widget_show(pdisps[1].widget); tmpw = gtk_vbox_new(0,0); gtk_widget_set_name(tmpw,"table"); gtk_widget_show(tmpw); gtk_box_pack_start(GTK_BOX(board),tmpw,0,0,0); /* label for right player */ if ( showwall ) { GtkWidget *l = gtk_label_new("Player 1"); gtk_widget_set_name(l,"playerlabel"); gtk_label_set_justify(GTK_LABEL(l),GTK_JUSTIFY_CENTER); // gtk_label_set_ellipsize(GTK_LABEL(l),PANGO_ELLIPSIZE_END); pdisps[1].infolab = GTK_LABEL(l); gtk_widget_show(l); if ( rotatelabels ) gtk_label_set_angle(GTK_LABEL(l),90); gtk_box_pack_start(GTK_BOX(tmpw),l,1,1,0); } gtk_box_pack_start(GTK_BOX(tmpw),pdisps[1].widget,1,0,0); /* label for bottom player */ if ( showwall ) { GtkWidget *l = gtk_label_new("Player 0"); gtk_widget_set_name(l,"playerlabel"); gtk_label_set_justify(GTK_LABEL(l),GTK_JUSTIFY_CENTER); gtk_label_set_ellipsize(GTK_LABEL(l),PANGO_ELLIPSIZE_END); pdisps[0].infolab = GTK_LABEL(l); gtk_widget_show(l); gtk_box_pack_start(GTK_BOX(tmpw),l,1,1,0); } /* the top player */ playerdisp_init(&pdisps[2],2); gtk_widget_show(pdisps[2].widget); gtk_box_pack_start(GTK_BOX(inboard),pdisps[2].widget,0,0,0); /* the discard area */ discard_area = lazy_fixed_new(); gtk_widget_set_name(discard_area,"table"); gtk_container_set_border_width(GTK_CONTAINER(discard_area),dialog_border_width); gtk_widget_show(discard_area); gtk_box_pack_start(GTK_BOX(inboard),discard_area,1,1,0); /* the bottom player (us) */ playerdisp_init(&pdisps[0],0); gtk_widget_show(pdisps[0].widget); gtk_box_pack_end(GTK_BOX(inboard),pdisps[0].widget,0,0,0); /* we'll install two signals on the player widget, which deal with changing the tile selection, and likewise to move */ /* install on the top window */ gtk_signal_connect(GTK_OBJECT(topwindow),"selectleft", (GtkSignalFunc)change_tile_selection,(gpointer)0); gtk_signal_connect(GTK_OBJECT(topwindow),"selectright", (GtkSignalFunc)change_tile_selection,(gpointer)1); gtk_signal_connect(GTK_OBJECT(topwindow),"moveleft", (GtkSignalFunc)move_selected_tile,(gpointer)0); gtk_signal_connect(GTK_OBJECT(topwindow),"moveright", (GtkSignalFunc)move_selected_tile,(gpointer)1); /* At this point, all the boxes are where we want them. So to prevent everything being completely mucked up, we now lock the sizes of the player boxes. This works, without showing the window, because we're asking the widgets what they want. */ for (i=0;i<4;i++) { GtkRequisition r; GtkWidget *w; w = pdisps[i].widget; gtk_widget_size_request(w,&r); gtk_widget_set_usize(w,r.width,r.height); } /* if we are showing the wall, create it */ if ( showwall ) create_wall(); /* now we can force square aspect ratio */ if ( square_aspect ) { GtkRequisition r; GtkWidget *w; w = board; gtk_widget_size_request(w,&r); gtk_widget_set_usize(w,r.width,r.width); } /* these dialogs may be part of the top level window, so they get created now */ create_dialogs(); /* create the other dialogs */ /* the scoring message thing */ textwindow_init(); /* and the message window */ messagewindow_init(); /* and the scoring history window */ scorehistory_init(); /* and the warning window */ warningwindow_init(); /* and the status window */ status_init(); /* and the open dialog */ { char idt[10]; idt[0] = 0; sprintf(idt,"%d",our_id); open_dialog_init(idt,name); } /* It's essential that we map the main window now, since if we are resuming an interrupted game, messages will arrive quickly; and the playerdisp_discard needs to know the allocation of the discard area. If we look at the wrong time, before it's mapped, we get -1: the discard_area won't get its allocation until after the topwindow has got its real size from the WM */ /* Note. In GTK1, it was harmless, but not effective, to do gtk_widget_show_now. In GTK2, not only doesn't it work as desired, it messes things up. So we only show, and then process events.*/ gtk_widget_show(topwindow); /* Now we have to wait until stuff is mapped and placed, and I don't know how that happens. So we'll just iterate through the main loop until we find some sensible values in here. It seems that the non sensible values include 1. */ while ( discard_area->allocation.width <= 1 ) gtk_main_iteration(); discard_area_alloc = discard_area->allocation; /* dissuade the discard area from shrinking */ gtk_widget_set_usize(discard_area,discard_area_alloc.width,discard_area_alloc.height); /* now expand the left and right players to match the bottom */ /* now we set the vertical size of the left and right players to match the horizontal size of the top and bottom */ gtk_widget_set_usize(pdisps[1].widget, pdisps[1].widget->allocation.width, pdisps[0].widget->allocation.width); gtk_widget_set_usize(pdisps[3].widget, pdisps[3].widget->allocation.width, pdisps[0].widget->allocation.width); /* The window is now built, and sizes calculated We now clear everything. I don't quite know what the order of events is here, but it seems to work with no flashing. */ for (i=0;i<4;i++) playerdisp_update(&pdisps[i],NULL); /* if there's a wall, and a game, make the wall match reality. We should check that there is a non-zero wall! */ if ( the_game && showwall && the_game->wall.size > 0 ) { int i; for ( i = 0; i < the_game->wall.live_used; i++ ) { gtk_widget_destroy(wall[wall_game_to_board(i)]); wall[wall_game_to_board(i)] = NULL; } for ( i = the_game->wall.size; i >= the_game->wall.dead_end; i-- ) adjust_wall_loose(i); } /* if there's a game, remake the discards */ playerdisp_discard_recompute = 1; if ( the_game ) { int i; for ( i = 0; i < discard_history.count; i++ ) playerdisp_discard(discard_history.hist[i].pd,discard_history.hist[i].t); } /* set up appropriate dialogs */ setup_dialogs(); /* add the callbacks. It's important that we do this after doing main loop processing to fix the discard area! */ /* and if there's a current connection, set callbacks (also on stdin if passing stdin) */ control_server_processing(1); } /* This function destroys the display and frees everything created for it, preparatory to creating it again with new parameters */ void destroy_display(void) { int i,ori; const char *code; /* first of all remove the input callbacks, since we don't want to process them with no display */ control_server_processing(0); if ( pass_stdin ) { gtk_input_remove(stdin_callback_tag); stdin_callback_tag = 0; } /* destroy and zero all the dialogs known publicly */ destroy_dialogs(); /* destroy and clear the wall */ clear_wall(); /* save the position of the top level window */ gdk_window_get_deskrelative_origin(topwindow->window,&top_x,&top_y); top_pos_set = 1; /* destroy the message, warning, status and scoring windows (which may be top-level, or may be in the topwindow */ if ( messagewindow ) gtk_widget_destroy(messagewindow); messagewindow = NULL; if ( status_window ) gtk_widget_destroy(status_window); status_window = NULL; if ( textwindow ) gtk_widget_destroy(textwindow); textwindow = NULL; if ( warningwindow ) gtk_widget_destroy(warningwindow); warningwindow = NULL; gtk_widget_destroy(topwindow); /* kills most everything else */ topwindow = NULL; /* get rid of the tile pixmaps */ for ( i = -1 ; i < MaxTile; i++ ) { code = tile_code(i); /* only destroy the error pixmaps once ... */ if ( tilepixmaps[0][i] && (i < 0 || strcmp(code,"XX") != 0) ) { for ( ori = 0; ori < 4; ori++ ) { gdk_pixmap_unref(tilepixmaps[ori][i]); tilepixmaps[ori][i] = 0; } } } for ( i = 0; i < 4 ; i++ ) { for ( ori = 0; ori < 4; ori++ ) { gdk_pixmap_unref(tongpixmaps[ori][i]); tongpixmaps[ori][i] = 0; } } /* not necessary to destroy dialogs, as create_dialogs does that */ } /* control server processing: disables or enables the processing of input from the game server */ void control_server_processing(int on) { if ( on && the_game && the_game->fd != (int)INVALID_SOCKET ) { #ifdef WIN32 if ( the_game->fd == STDOUT_FILENO ) { server_callback_tag = gdk_input_add(STDIN_FILENO,GDK_INPUT_READ,server_input_callback,NULL); } else { /* the socket routines have already converted the socket into a GIOChannel *. */ server_callback_tag = g_io_add_watch((GIOChannel *)the_game->fd, G_IO_IN|G_IO_ERR,gcallback,NULL); } #else if ( the_game->fd == STDOUT_FILENO ) { server_callback_tag = gdk_input_add(STDIN_FILENO,GDK_INPUT_READ,server_input_callback,NULL); } else { server_callback_tag = gdk_input_add(the_game->fd,GDK_INPUT_READ,server_input_callback,NULL); } #endif if ( pass_stdin ) { stdin_callback_tag = gdk_input_add(0,GDK_INPUT_READ,stdin_input_callback,NULL); } } else { if ( server_callback_tag ) { gdk_input_remove(server_callback_tag); server_callback_tag = 0; } if ( stdin_callback_tag ) { gdk_input_remove(stdin_callback_tag); stdin_callback_tag = 0; } } } /* This function moves the tile selection for our player left (if data = 0) or right. If no tile is selected, it selects the leftmost or rightmost (not sure whether this is the right way round...) */ static void change_tile_selection(GtkWidget *w UNUSED,gpointer right) { int i = selected_button; if ( !our_player || our_player->num_concealed == 0 ) return; if ( i < 0 ) { i = right ? 0 : our_player->num_concealed - 1 ; } else { if ( !right ) i = (i == 0) ? our_player->num_concealed - 1 : i - 1; else i = (i >= our_player->num_concealed - 1) ? 0 : i + 1; } conc_callback(pdisps[0].conc[i],(gpointer)(intptr_t)(i | 128)); } /* This function moves the selected tile left or right in the hand */ static void move_selected_tile(GtkWidget *w UNUSED,gpointer right) { int i = selected_button; if ( !our_player || our_player->num_concealed == 0 ) return; if ( i < 0 ) return; if ( right ) { player_reorder_tile(our_player,i, (i == our_player->num_concealed-1) ? 0 : i+1); } else { player_reorder_tile(our_player,i, (i == 0) ? our_player->num_concealed-1 : i-1); } playerdisp_update_concealed(&pdisps[0],HiddenTile); change_tile_selection(w,right); } static void iconify_window(GtkWidget *w) { if ( ! GTK_IS_WINDOW(w) ) return; return gtk_window_iconify(GTK_WINDOW(w)); } static void uniconify_window(GtkWidget *w) { if ( ! GTK_IS_WINDOW(w) ) return; /* if the widget is not visible in the gtk sense, somebody's closed it while we thought it should be iconified. So don't raise it. */ if ( ! GTK_WIDGET_VISIBLE(w) ) return; gdk_window_show(w->window); } mj-1.17-src/make-enums.pl0000444006717300001440000000572015002771311013723 0ustar jcbusers# $Header: /home/jcb/MahJong/newmj/RCS/make-enums.pl,v 12.0 2009/06/28 20:43:12 jcb Rel $ # make-enums.pl #****************** COPYRIGHT STATEMENT ********************** #* This file is Copyright (c) 2000 by J. C. Bradfield. * #* Distribution and use is governed by the LICENCE file that * #* accompanies this file. * #* The moral rights of the author are asserted. * #* * #***************** DISCLAIMER OF WARRANTY ******************** #* This code is not warranted fit for any purpose. See the * #* LICENCE file for further information. * #* * #*************************************************************/ # This is a utility script. It takes one argument, which should # be a C header file foo.h . # It then rips through it, and generates functions to convert # between strings and all the enumerated types in the file: these # functions are placed in foo-enums.c , and have names # foo_print_Enum and foo_scan_Enum, where Enum is the type name. # Header definitions are put in foo-enums.h . # It is assumed that definitions start # typedef enum { # and end with # } Enum ; # # If the body of the definition (between the braces) contains # a single line C comment of the form # /* make-enums sub { ... } */ # then the defined function will be applied to the C names # of the type values to convert them to strings. N.B. # the function should modify its argument in place. # $file = $ARGV[0]; open(STDIN,"<$file") || die("opening $file"); $stem = $file; $stem =~ s/\.h//; open(STDOUT,">$stem-enums.c") || die("opening stdout"); open(H,">$stem-enums.h") || die("opening stdout"); undef $/; $text = ; # extract a typedef while ( $text =~ s/typedef\s+enum\s*\{(.*?)\}\s*(\w+)\s*;//s ) { $tdef = $1; $tname = $2; eval "sub modifier { }"; # is there a modifier? if ( $tdef =~ m[/\*\s+make-enums\s*sub (.*\})\s+\*/] ) { eval "sub modifier $1"; } # strip comments: see man perlop $tdef =~ s { /\* # Match the opening delimiter. .*? # Match a minimal number of characters. \*/ # Match the closing delimiter. } []gsx; $pfun = "char *${stem}_print_${tname}(const $tname t) {\n"; $sfun = "$tname ${stem}_scan_${tname}(const char *s) {\n"; print H "char *${stem}_print_${tname}(const $tname t); $tname ${stem}_scan_${tname}(const char *s);\n\n"; # eliminate any casts to unsigned $tdef =~ s/\(\s*unsigned\s*\)//g; # we never need to use the initial values, so strip them $tdef =~ s/=[^,\}]*//g; while ( $tdef =~ s/(\w+)\s*// ) { $value = $1; $name = $value; &modifier($name); $pfun .= " if ( t == $value ) return \"$name\";\n"; $sfun .= " if ( strcmp(s,\"$name\") == 0 ) return $value;\n"; } $pfun .= " return (char *)0;\n}\n"; $sfun .= " return -1;\n}\n"; print $pfun; print "\n"; print $sfun; print "\n\n"; } mj-1.17-src/protocol.h0000444006717300001440000012265215002771311013342 0ustar jcbusers/* $Header: /home/jcb/MahJong/newmj/RCS/protocol.h,v 12.3 2020/05/16 12:49:46 jcb Exp $ * protocol.h * defines the messages passed between players and controller */ /****************** COPYRIGHT STATEMENT ********************** * This file is Copyright (c) 2000 by J. C. Bradfield. * * Distribution and use is governed by the LICENCE file that * * accompanies this file. * * The moral rights of the author are asserted. * * * ***************** DISCLAIMER OF WARRANTY ******************** * This code is not warranted fit for any purpose. See the * * LICENCE file for further information. * * * *************************************************************/ #ifndef PROTOCOL_H_INCLUDED #define PROTOCOL_H_INCLUDED #include "tiles.h" #include "player.h" /* required for ChowPosition */ /* some constants */ /* This is the largest wall that this level of the protocol supports */ #define MAX_WALL_SIZE 144 /* a bool is an integer constrained to be 0 or 1 */ typedef int bool; /* a word16 is a word of at most 15 (to allow for final null) characters, without white space */ typedef char word16[16]; /* First we have the definitions for options. These are logically part of the game module, but they are also needed by the this module. Unfortunately, protocol.h can't just include game.h, since game.h depends on this, and I can't see how to remove the circularity. */ /* here are the names of the options. Option 0 for unknown. The End option also has special reserved meaning. Names (omitting GO) shd be 15 chars or less */ typedef enum { /* make-enums sub { $_[0] =~ s/^GO//; } */ GOUnknown=0, GOTimeout, /* the timeout in seconds for claims. Default: 15 */ GOTimeoutGrace, /* grace period when clients handle timeouts locally */ GOScoreLimit, /* limit for score. Default 1000 */ GONoLimit, /* true if no limit on scores. Then ScoreLimit is notional value of limit hand */ GOMahJongScore, /* base points for going out */ GOSevenPairs, /* seven pairs hand allowed? */ GOSevenPairsVal, /* value of a seven pair hand */ /* the following group of options concerns flowers, by which is meant flowers and seasons as in the classical rules */ GOFlowers, /* are flowers to be dealt? */ GOFlowersLoose, /* flowers replaced from dead wall? */ GOFlowersOwnEach, /* score for each own flower or season */ GOFlowersOwnBoth, /* score for own flower and season. N.B. In this case there will also be two scores of FlowersOwnEach */ GOFlowersBouquet, /* all four flowers or all four seasons */ GODeadWall, /* is there a dead wall, or do we play to the end? */ GODeadWall16, /* is the dead wall 16 tiles unreplenished (per Millington) or the normal 14 replenished. */ GOConcealedFully, /* score for a fully concealed hand */ GOConcealedAlmost, /* score for hand concealed up to mah jong, or to end of game (if next option applies) */ GOLosersPurity, /* do losers score the doubles for pure and almost concealed hand? */ GOKongHas3Types, /* do we use Millington's 3-way kong distinction? */ GOEastDoubles, /* does East pay and receive double (as in Chinese Classical)? */ GOLosersSettle, /* do losers pay among themselves? */ GODiscDoubles, /* does the discarder pay a double score? */ GOShowOnWashout, /* are tiles revealed after a washout? */ GONumRounds, /* how many rounds in a game? */ GOEnd } GameOption; #define GONumOptions (GOEnd+1) /* and the types of options: printed as bool, int, string etc. The string option is limited to 127 characters, and may not contain white space. (I can't remember why I thought it might be wanted.) */ typedef enum { /* make-enums sub { $_[0] =~ s/^GOT// ; $_[0] =~ y/A-Z/a-z/; } */ GOTBool, GOTInt, GOTNat, /* non-negative integer -- introduced at proto 1020 */ GOTScore, /* a score is an int, but with a special interpretation: if < 10000, it's a number of points; if a multiple of 10000, it's that number of doubles (up to 100); and a fractional (/100) multiple of 100000000 indicates so many limits. It may combine a limit with a number of doubles, in which case the number of doubles is used only in a no limit game. */ GOTString } GameOptionType; typedef struct _GameOptionEntry { GameOption option; /* integer option number */ char name[16]; /* short name: shd be same as name of option number */ GameOptionType type; /* type of option value */ int protversion; /* least protocol version required to use this option. N.B. This may well be before the introduction of the option, as with many options clients do not have to know about them, and can use the server provided text to allow the user to set them. */ bool enabled; /* is this option understood in this game? */ union { bool optbool; int optint; unsigned int optnat; int optscore; char optstring[128]; } value; char desc[128]; /* short description */ void *userdata; /* field for client use */ } GameOptionEntry; /* This type is for use externally. It's the same as the union type in the option entry, except that the string is returned as a pointer. */ typedef union _GameOptionValue { bool optbool; int optint; unsigned int optnat; int optscore; char *optstring; } GameOptionValue; /* The fundamental principle is that players cannot change the state of the game---including their own hand. They send a message to the controller declaring what they want to do; the controller then issues the message implementing it. These messages should NOT be seen as request and reply, but simply as event deliveries. */ /* Protocol version is an integer of the form 1000*major+minor. The protocol major version should be incremented only when it is impossible for an earlier client to play with the new protocol. Normally, servers and players should be able to downgrade to the lowest common minor version. Note: in the earlier versions, including the alpha binary release, the version was just 1,2,3, corresponding to our current major. However, we will view all those as major 0, and start again at 1000. Note that this is not really just the protocol version; it's more a version of the protocol plus options etc. */ /* This define gives the protocol version implemented in this file */ #define PROTOCOL_VERSION 1111 /* This global variable, found in protocol.c, is used by many functions to determine the protocol version currently being used. It would be better if this were an argument to every function that used it, or if every function were passed a Game structure, but this would be too inconvenient. So this global variable is used instead. If we ever program with different games in the same process, care will have to be taken to manage this. */ extern int protocol_version; /* These structures are the programmer's view of the messages. The protocol is actually defined by the wire protocol, not by these structs. */ /* NOTE: for bad reasons, these enums should always be less than 1000000 (to allow space for flag bits) */ /* types of message from controller to player. For convenience in debugging, the actual values are widely spaced. */ typedef enum { CMsgError = 0, CMsgInfoTiles = 1, CMsgStateSaved = 2, CMsgConnectReply = 10, CMsgReconnect = 11, CMsgAuthReqd = 12, CMsgRedirect = 13, CMsgPlayer = 20, CMsgNewRound = 29, CMsgGame = 30, CMsgNewHand = 31, CMsgPlayerDeclaresSpecial = 34, CMsgStartPlay = 35, CMsgStopPlay = 36, CMsgPause = 37, CMsgPlayerReady = 38, CMsgPlayerDraws = 40, CMsgPlayerDrawsLoose = 41, CMsgPlayerDiscards = 50, CMsgClaimDenied = 51, CMsgPlayerDoesntClaim = 52, CMsgDangerousDiscard = 55, CMsgPlayerClaimsPung = 60, CMsgPlayerPungs = 61, CMsgPlayerFormsClosedPung = 62, CMsgPlayerClaimsKong = 70, CMsgPlayerKongs = 71, CMsgPlayerDeclaresClosedKong = 80, CMsgPlayerAddsToPung = 81, CMsgPlayerRobsKong = 85, CMsgCanMahJong = 87, CMsgPlayerClaimsChow = 90, CMsgPlayerChows = 91, CMsgPlayerFormsClosedChow = 92, CMsgWashOut = 99, CMsgPlayerClaimsMahJong = 100, CMsgPlayerMahJongs = 101, CMsgPlayerPairs = 102, CMsgPlayerFormsClosedPair = 103, CMsgPlayerShowsTiles = 105, CMsgPlayerSpecialSet = 106, CMsgPlayerFormsClosedSpecialSet = 107, CMsgPlayerOptionSet = 110, CMsgHandScore = 115, CMsgSettlement = 120, CMsgGameOver = 200, CMsgGameOption = 300, CMsgChangeManager = 310, CMsgMessage = 400, CMsgWall = 900, CMsgComment = 999, CMsgSwapTile = 1000, } ControllerMsgType; /* Types of message sent by player. The numbers more or less match the corresponding controller messages. */ typedef enum { PMsgSaveState = 1, PMsgLoadState = 2, PMsgConnect = 10, PMsgRequestReconnect = 11, PMsgAuthInfo = 12, PMsgNewAuthInfo = 13, PMsgDisconnect = 14, PMsgDeclareSpecial = 33, PMsgRequestPause = 36, PMsgReady = 37, PMsgDiscard = 50, PMsgNoClaim = 51, PMsgPung = 60, PMsgFormClosedPung = 62, PMsgKong = 70, PMsgDeclareClosedKong = 80, PMsgAddToPung = 81, PMsgQueryMahJong = 87, PMsgChow = 90, PMsgFormClosedChow = 92, PMsgDeclareWashOut = 99, PMsgMahJong = 100, PMsgPair = 102, PMsgFormClosedPair = 103, PMsgShowTiles = 105, PMsgSpecialSet = 106, PMsgFormClosedSpecialSet = 107, PMsgSetPlayerOption = 110, PMsgSetGameOption = 300, PMsgQueryGameOption = 301, PMsgListGameOptions = 302, PMsgChangeManager = 310, PMsgSendMessage = 400, PMsgSwapTile = 1000, } PlayerMsgType; #define DebugMsgsStart 1000 /* NOTE: it is currently assumed (by the wire protocol) that these structures have at most one (char *) component, and if they do, it's the final element in the structure. If this assumption becomes invalid, work will have to be done on the wire protocol code; especially since much of the conversion routines is automatically generated from this file. For the same reason, when adding new structures, the layout and naming convention should be the same as the existing ones. */ /* message sent by controller to player to signal an error. The string is a human readable description. */ typedef struct _CMsgErrorMsg { ControllerMsgType type; /* CMsgError */ int seqno; /* sequence number of player message provoking error, or 0 */ char *error; /* human readable explanation */ } CMsgErrorMsg; /* This message requests the controller to save the state of the current game. */ /* Note: in protocol versions before 1025, the filename field did not exist. This should require no special handling. */ typedef struct _PMsgSaveStateMsg { PlayerMsgType type; /* PMsgSaveState */ char *filename; /* name of file to save in; subject to interpretation and overriding by server. May be null. */ } PMsgSaveStateMsg; /* This message informs players that the state of the game has been saved. */ /* This message was introduced at pversion 1025. */ typedef struct _CMsgStateSavedMsg { ControllerMsgType type; /* CMsgStateSaved */ int id; /* player who requested save */ char *filename; /* file in which saved. Server dependent. May include full path information in system dependent form */ } CMsgStateSavedMsg; /* This message requests the server to load the game state from a saved file. There is no reply on success; an error is returned on failure. */ /* This message was introduced at pversion 1038. */ typedef struct _PMsgLoadStateMsg { PlayerMsgType type; /* PMsgSaveState */ char *filename; /* name of file to load; subject to interpretation and overriding by server. */ } PMsgLoadStateMsg; /* This message is purely informational, and is mainly useful for debugging or for very dumb clients. It may be sent to a player whenever its hand changes, and contains a human readable presentation of the player's tiles. */ typedef struct _CMsgInfoTilesMsg { ControllerMsgType type; /* CMsgInfoTiles */ int id; /* id of player---in teaching situation might send info on other players too */ char *tileinfo; } CMsgInfoTilesMsg; /* Message sent by player after connecting to controller. */ typedef struct _PMsgConnectMsg { PlayerMsgType type; /* PMsgConnect */ int pvers; /* protocol version used by player */ int last_id; /* (if non-zero) identifier from last session; controller is not bound to keep this, but will try */ char *name; /* player's name */ } PMsgConnectMsg; /* Message sent by controller to player on receipt of ConnectMsg */ typedef struct _CMsgConnectReplyMsg { ControllerMsgType type; /* CMsgConnectReply */ int pvers; /* protocol version of server */ int id; /* id assigned by controller, or zero if connection refused */ char *reason; /* human readable reason for refusal (if refused) */ } CMsgConnectReplyMsg; /* Message sent by controller to request authentication. This message was introduced at pversion 1100. */ typedef struct _CMsgAuthReqdMsg { ControllerMsgType type; /* CMsgAuthReqd */ word16 authtype; /* type of authorization, currenly just: basic */ char *authdata; /* any data to be included */ } CMsgAuthReqdMsg; /* Message sent by player to provide authentication. This message was introduced at pversion 1100. */ typedef struct _PMsgAuthInfoMsg { PlayerMsgType type; /* PMsgAuthInfo */ word16 authtype; /* type of authorization, currenly just: basic */ char *authdata; /* any data to be included */ } PMsgAuthInfoMsg; /* Message sent by player to provide new authentication info. This message was introduced at pversion 1100. */ typedef struct _PMsgNewAuthInfoMsg { PlayerMsgType type; /* PMsgNewAuthInfo */ word16 authtype; /* type of authorization, currenly just: basic */ char *authdata; /* any data to be included */ } PMsgNewAuthInfoMsg; /* Message sent by controller to tell player to close connection and connect to another port. The player should connect with the same id and name that it currently has. This message was introduced at pversion 1100. */ typedef struct _CMsgRedirectMsg { ControllerMsgType type; /* CMsgRedirect */ char *dest; /* new connection, in the standard form host:port. If host is omitted, use the same host. */ } CMsgRedirectMsg; /* This message requests a "reconnection": that is, the equivalent of disconnecting and connection, without actually dropping the network connection. If a positive reply is received, the client's next act should be to resend its connect message. This message was introduced at pversion 1070. */ typedef struct _PMsgRequestReconnectMsg { PlayerMsgType type; /* PMsgRequestReconnect */ } PMsgRequestReconnectMsg; /* This message grants a reconnection request. This message was introduced at pversion 1070. */ typedef struct _CMsgReconnectMsg { ControllerMsgType type; /* CMsgReconnect */ } CMsgReconnectMsg; /* This message is an explicit disconnect to quit the game. This message was introduced at pversion 1100. */ typedef struct _PMsgDisconnectMsg { PlayerMsgType type; /* PMsgDisconnect */ } PMsgDisconnectMsg; /* Message sent by controller to inform players of each other's existence. When a player connects, it receives one such message for every already existing player; and each existing player receives a message for the new player. This message is also sent if a player changes its name. After protocol version 1034, if the name is empty, this means "delete the player". (Empty names were previously permitted, but this was always a mistake.) */ typedef struct _CMsgPlayerMsg { ControllerMsgType type; /* CMsgPlayer */ int id; /* id of player */ char *name; /* name of player */ } CMsgPlayerMsg; /* Message sent by controller to initiate a game (possibly continued from a previous session). Contains the initial wind assignment. */ typedef struct _CMsgGameMsg { ControllerMsgType type; /* CMsgGame */ int east; /* id of player in east */ int south; /* id of south */ int west; /* id of west */ int north; /* id of north */ TileWind round; /* wind of the current round */ int hands_as_east; /* number of hands completed with this dealer */ int firsteast; /* id of player who was east in first hand of game */ int east_score; /* east's score in game */ int south_score; int west_score; int north_score; int protversion; /* protocol version to be used in this game */ int manager; /* id of player owning this game */ } CMsgGameMsg; /* Message to say that the wind of the round has changed */ typedef struct _CMsgNewRoundMsg { ControllerMsgType type; /* CMsgNewRound */ TileWind round; /* wind of the new round */ } CMsgNewRoundMsg; /* Message sent by controller to start new hand. It also tells where the live wall notionally starts; this is completely irrelevant to the actual implementation, but can be used to give players a consistent view of the wall. */ typedef struct _CMsgNewHandMsg { ControllerMsgType type; /* CMsgNewHand */ int east; /* id of east player for this hand: just to check */ int start_wall; /* the wall (as a seat number) in which the live wall starts*/ int start_stack; /* the stack (from right) at which the live wall starts */ } CMsgNewHandMsg; /* Message sent by controller to draw one tile for player. Other players will receive this message with a blank tile. */ typedef struct _CMsgPlayerDrawsMsg { ControllerMsgType type; /* CMsgPlayerDraws */ int id; /* player drawing tile */ Tile tile; /* tile drawn (HiddenTile if not known to us) */ } CMsgPlayerDrawsMsg; /* Message sent by controller to draw a loose tile for player. Other players will receive this message with a blank tile. */ typedef struct _CMsgPlayerDrawsLooseMsg { ControllerMsgType type; /* CMsgPlayerDrawsLoose */ int id; /* player drawing tile */ Tile tile; /* tile drawn (HiddenTile if not known to us) */ } CMsgPlayerDrawsLooseMsg; /* Message sent by the player to declare a special tile. If the tile is blank, player has no more specials. (Of course, the controller knows this anyway.) This message is also used during play. */ typedef struct _PMsgDeclareSpecialMsg { /* alias ds */ PlayerMsgType type; /* PMsgDeclareSpecial */ Tile tile; /* tile being declared, or blank */ } PMsgDeclareSpecialMsg; /* Message sent by controller to implement special declaration. */ typedef struct _CMsgPlayerDeclaresSpecialMsg { ControllerMsgType type; /* CMsgPlayerDeclaresSpecial */ int id; /* player making declaration */ Tile tile; /* tile being declared (blank if player finished) */ } CMsgPlayerDeclaresSpecialMsg; /* This message is sent to all players, to tell them they may start normal play. This happens either when a game starts, or to restart a game that was suspended. */ typedef struct _CMsgStartPlayMsg { ControllerMsgType type; /* CMsgStartPlay */ int id; /* not strictly necessary: id of player due to do something */ } CMsgStartPlayMsg; /* This message is sent to all players to stop play. This may be (for example) because a player has lost its connection. */ typedef struct _CMsgStopPlayMsg { ControllerMsgType type; /* CMsgStopPlay */ char *reason; /* human readable explanation */ } CMsgStopPlayMsg; /* This message is sent by a player to request a pause in play. If granted, all players (including the pausing player) must give permission to continue again. */ typedef struct _PMsgRequestPauseMsg { PlayerMsgType type; /* PMsgRequestPause */ char *reason; /* reason for pause; will be passed to other players */ } PMsgRequestPauseMsg; /* This message is sent by the controller when it wishes to gain the permission of players to proceed. This happens typically at: the end of declaring specials, before play proper starts; at the end of a hand, before proceeding to the next hand. */ typedef struct _CMsgPauseMsg { ControllerMsgType type; /* CMsgPause */ int exempt; /* 0, or the id of the one player who is NOT being asked to confirm readiness (typically because it's them to proceed) */ int requestor; /* 0, or id of player who requested pause */ char *reason; /* reason for pause. This will (it happens) be phrased as an infinitive, e.g. "to start play" */ } CMsgPauseMsg; /* and this is the reply */ typedef struct _PMsgReadyMsg { PlayerMsgType type; /* PMsgReady */ } PMsgReadyMsg; /* and this tells other players who's ready (and bloody emacs gets confused by that quote, so here's another) */ typedef struct _CMsgPlayerReadyMsg { ControllerMsgType type; /* CMsgPlayerReady */ int id; } CMsgPlayerReadyMsg; /* Message sent by player to announce discard */ typedef struct _PMsgDiscardMsg { /* alias d */ PlayerMsgType type; /* PMsgDiscard */ Tile tile; /* tile to be discarded */ bool calling; /* player is making a Calling declaration */ } PMsgDiscardMsg; /* Message sent by controller to implement discard */ typedef struct _CMsgPlayerDiscardsMsg { ControllerMsgType type; /* CMsgPlayerDiscards */ int id; /* player discarding */ Tile tile; /* tile being discarded */ int discard; /* a serial number for this discard */ bool calling; /* player is making a Calling declaration */ } CMsgPlayerDiscardsMsg; /* This message is sent by a player to say that it has no claim on the current discard. It implies a request to draw a tile if it's the player's turn. */ typedef struct _PMsgNoClaimMsg { /* alias n */ PlayerMsgType type; /* PMsgNoClaim */ int discard; /* serial number of discard */ } PMsgNoClaimMsg; /* Message sent by controller to say that a player is making no claim on the current discard. Usually this is sent only to the player concerned. */ typedef struct _CMsgPlayerDoesntClaimMsg { ControllerMsgType type; /* CMsgPlayerDoesntClaim */ int id; /* player */ int discard; /* current discard serial */ bool timeout; /* 1 if this is generated by timeout in controller */ } CMsgPlayerDoesntClaimMsg; /* This message is informational, and is sent by the controller after a claim has been implemented to announce that the discard was dangerous. */ typedef struct _CMsgDangerousDiscardMsg { ControllerMsgType type; /* CMsgDangerousDiscard */ int id; /* player who discarded */ int discard; /* serial number of the dangerous discard */ bool nochoice; /* discard was dangerous, but discarder had no choice */ } CMsgDangerousDiscardMsg; /* Message sent by player to claim a pung. In the usual rules, it is possible to claim a pung up to the time the *next* tile is discarded. At the moment, we don't implement that, but we should in due course. Hence a pung (etc.) claim includes an integer identifying the discard that is being claimed---the integer was assigned by the controller. This provides some protection against delays. */ typedef struct _PMsgPungMsg { /* alias p */ PlayerMsgType type; /* PMsgPung */ int discard; /* id of discard being claimed */ } PMsgPungMsg; /* Message sent by controller to other players to inform them of a pung claim (NOT of its success). */ typedef struct _CMsgPlayerClaimsPungMsg { ControllerMsgType type; /* CMsgPlayerClaimsPung */ int id; /* player claiming */ int discard; /* discard being claimed */ } CMsgPlayerClaimsPungMsg; /* generic refusal message sent by controller to deny a claim. */ typedef struct _CMsgClaimDeniedMsg { ControllerMsgType type; /* CMsgClaimDenied */ int id; /* player who made claim */ char *reason; /* if non-NULL, human readable explanation */ } CMsgClaimDeniedMsg; /* Message sent by controller to implement a successful pung claim. */ typedef struct _CMsgPlayerPungsMsg { ControllerMsgType type; /* CMsgPlayerPungs */ int id; /* player who made claim */ Tile tile; /* tile of pung---redundant, but included for safety */ } CMsgPlayerPungsMsg; /* Message to declare a closed pung during scoring */ typedef struct _PMsgFormClosedPungMsg { PlayerMsgType type; /* PMsgFormClosedPung */ Tile tile; } PMsgFormClosedPungMsg; typedef struct _CMsgPlayerFormsClosedPungMsg { ControllerMsgType type; /* CMsgPlayerFormsClosedPung */ int id; Tile tile; } CMsgPlayerFormsClosedPungMsg; /* now the same for kongs */ typedef struct _PMsgKongMsg { /* alias k */ PlayerMsgType type; /* PMsgKong */ int discard; /* id of discard being claimed */ } PMsgKongMsg; /* Message sent by controller to other players to inform them of a pung claim (NOT of its success). */ typedef struct _CMsgPlayerClaimsKongMsg { ControllerMsgType type; /* CMsgPlayerClaimsKong */ int id; /* player claiming */ int discard; /* discard being claimed */ } CMsgPlayerClaimsKongMsg; /* the generic refusal message applies */ /* Message sent by controller to implement a successful pung claim. */ typedef struct _CMsgPlayerKongsMsg { ControllerMsgType type; /* CMsgPlayerKongs */ int id; /* player who made claim */ Tile tile; /* tile of kong---redundant, but included for safety */ } CMsgPlayerKongsMsg; /* Message sent by player to announce a concealed kong. */ typedef struct _PMsgDeclareClosedKongMsg { /* alias ck */ PlayerMsgType type; /* PMsgDeclareClosedKong */ Tile tile; /* which kong */ } PMsgDeclareClosedKongMsg; /* Message sent by controller to implement a concealed kong Because the kong may possibly be robbed, it gets a discard serial number. */ typedef struct _CMsgPlayerDeclaresClosedKongMsg { ControllerMsgType type; /* CMsgPlayerDeclaresClosedKong */ int id; /* player declaring kong */ Tile tile; /* tile being konged */ int discard; /* serial */ } CMsgPlayerDeclaresClosedKongMsg; /* Message sent by player to add drawn tile to exposed pung */ typedef struct _PMsgAddToPungMsg { PlayerMsgType type; /* PMsgAddToPung */ Tile tile; /* which pung */ } PMsgAddToPungMsg; /* Message sent by controller to implement adding to a pung Because the kong may possibly be robbed, it gets a discard serial number. */ typedef struct _CMsgPlayerAddsToPungMsg { ControllerMsgType type; /* CMsgPlayerAddsToPung */ int id; /* player */ Tile tile; /* the tile being added */ int discard; /* serial */ } CMsgPlayerAddsToPungMsg; /* Message sent by controller to say that the player has robbed the just declared kong */ typedef struct _CMsgPlayerRobsKongMsg { ControllerMsgType type; /* CMsgPlayerRobsKong */ int id; /* player */ Tile tile; /* tile being robbed (redundant) */ } CMsgPlayerRobsKongMsg; /* Message sent by player to ask whether it has a mah-jong hand. Needed because only the server knows fully what hands are allowed: the client code might be an earlier version. */ typedef struct _PMsgQueryMahJongMsg { PlayerMsgType type; /* PMsgQueryMahJong */ Tile tile; /* do I have mahjong with this tile added? HiddenTile means no tile */ } PMsgQueryMahJongMsg; /* And the reply */ typedef struct _CMsgCanMahJongMsg { ControllerMsgType type; /* CMsgCanMahJong */ Tile tile; /* the tile specified */ bool answer; /* yes or no */ } CMsgCanMahJongMsg; /* Message sent by player to claim chow. The player should specify where the chowed tile will go (i.e. lower, upper, middle). However, for added realism, it is permissible to specify "any" (AnyPos) for the position. If this is done, and the chow claim is successful, then the controller will send to the chowing player (and only to the chowing player) a PlayerChows message with the cpos set to AnyPos. The player must then send another Chow message with the position specified. */ typedef struct _PMsgChowMsg { /* alias c */ PlayerMsgType type; /* PMsgChow */ int discard; /* discard being claimed */ ChowPosition cpos; /* where the discard will go */ } PMsgChowMsg; /* Message sent by controller to announce a chow claim */ typedef struct _CMsgPlayerClaimsChowMsg { ControllerMsgType type; /* CMsgPlayerClaimsChow */ int id; /* player claiming */ int discard; /* discard being claimed */ ChowPosition cpos; /* chowposition specified (may be AnyPos) */ } CMsgPlayerClaimsChowMsg; /* the general refusal message applies */ /* Message sent by controller to implement a chow claim. See above for meaning of this message with cpos == AnyPos. */ typedef struct _CMsgPlayerChowsMsg { ControllerMsgType type; /* CMsgPlayerChows */ int id; /* player claiming */ Tile tile; /* tile being claimed (redundant, but...) */ ChowPosition cpos; /* position of claimed tile in chow */ } CMsgPlayerChowsMsg; /* To form a closed chow during scoring. As all tiles are in hand, we require that the chow is specified by the lower tile. */ typedef struct _PMsgFormClosedChowMsg { PlayerMsgType type; /* PMsgFormClosedChow */ Tile tile; } PMsgFormClosedChowMsg; typedef struct _CMsgPlayerFormsClosedChowMsg { ControllerMsgType type; /* CMsgPlayerFormsClosedChow */ int id; Tile tile; } CMsgPlayerFormsClosedChowMsg; /* some rules allow a player to declare a washout under certain circumstances. */ typedef struct _PMsgDeclareWashOutMsg { PlayerMsgType type; /* PMsgDeclareWashOut */ } PMsgDeclareWashOutMsg; /* Message sent by controller to declare a dead hand */ typedef struct _CMsgWashOutMsg { ControllerMsgType type; /* CMsgWashOut */ char *reason; /* reason for washout */ } CMsgWashOutMsg; /* Message sent by player to claim Mah-Jong. Applies to both claiming a discard and mah-jong from wall. */ typedef struct _PMsgMahJongMsg { PlayerMsgType type; /* PMsgMahJong */ int discard; /* discard being claimed, or 0 if from wall */ } PMsgMahJongMsg; /* Message sent by controller to announce mah-jong claim */ typedef struct _CMsgPlayerClaimsMahJongMsg { ControllerMsgType type; /* CMsgPlayerClaimsMahJong */ int id; /* player claiming */ int discard; /* discard being claimed */ } CMsgPlayerClaimsMahJongMsg; /* Message sent by controller to announce successful mah-jong */ typedef struct _CMsgPlayerMahJongsMsg { ControllerMsgType type; /* CMsgPlayerMahJongs */ int id; /* player claiming */ Tile tile; /* If this is HiddenTile, then the mahjong is by claiming the discard (the tile will be named in a later claim). If the mahjong is from the wall, then this is the tile that was drawn; this allows the other players to see the legitimacy of scoring claims. */ } CMsgPlayerMahJongsMsg; /* Take the mah-jonged discard for a pair. Since this can only happen at mah-jong, there is no need to specify a tile or discard number */ typedef struct _PMsgPairMsg { PlayerMsgType type; /* PMsgPair */ } PMsgPairMsg; typedef struct _CMsgPlayerPairsMsg { ControllerMsgType type; /* CMsgPlayerPairs */ int id; Tile tile; /* for convenience of other players */ } CMsgPlayerPairsMsg; /* Form a closed pair during scoring */ typedef struct _PMsgFormClosedPairMsg { PlayerMsgType type; /* PMsgFormClosedPair */ Tile tile; } PMsgFormClosedPairMsg; typedef struct _CMsgPlayerFormsClosedPairMsg { ControllerMsgType type; /* CMsgPlayerFormsClosedPair */ int id; Tile tile; } CMsgPlayerFormsClosedPairMsg; /* These messages handle the case of special hands such as thirteen unique wonders */ /* Claim the discard (during scoring) to form a special hand */ typedef struct _PMsgSpecialSetMsg { PlayerMsgType type; /* PMsgSpecialSet */ } PMsgSpecialSetMsg; /* Player claims the discard to form a special set with all remaining tiles; this is a ShowTiles as well */ typedef struct _CMsgPlayerSpecialSetMsg { ControllerMsgType type ; /* CMsgPlayerSpecialSet */ int id; Tile tile; /* the discard tile */ char *tiles; /* string repn of the concealed tiles */ } CMsgPlayerSpecialSetMsg; /* Form a special set in hand */ typedef struct _PMsgFormClosedSpecialSetMsg { PlayerMsgType type; /* PMsgFormClosedSpecialSet */ } PMsgFormClosedSpecialSetMsg; /* Player forms a special set in hand from the concealed tiles */ typedef struct _CMsgPlayerFormsClosedSpecialSetMsg { ControllerMsgType type ; /* CMsgPlayerFormsClosedSpecialSet */ int id; char *tiles; /* string repn of concealed tiles */ } CMsgPlayerFormsClosedSpecialSetMsg; /* This message is sent by a losing player after it has finished making scoring combinations: it reveals the concealed tiles, and triggers the calculation of a score. */ typedef struct _PMsgShowTilesMsg { PlayerMsgType type; /* PMsgShowTiles */ } PMsgShowTilesMsg; /* This message reveals a player's concealed tiles. It currently just has a string repn, because I'm lazy. This ought to be a variable array of tiles. */ typedef struct _CMsgPlayerShowsTilesMsg { ControllerMsgType type; /* CMsgPlayerShowsTiles */ int id; char *tiles; /* string repn of concealed tiles */ } CMsgPlayerShowsTilesMsg; /* This message gives the calculated score for a hand. */ typedef struct _CMsgHandScoreMsg { ControllerMsgType type; /* CMsgHandScore */ int id; int score; /* score of this hand */ char *explanation; /* human readable calculation of the score */ } CMsgHandScoreMsg; /* This message announces the change in cumulative score for each player. The explanation is text, which should be interpreted cumulatively with any previous settlement explanations. */ typedef struct _CMsgSettlementMsg { ControllerMsgType type; /* CMsgSettlement */ int east; int south; int west; int north; char *explanation; } CMsgSettlementMsg; /* This message requests the controller to set a player option, or acknowledges a server set. */ typedef struct _PMsgSetPlayerOptionMsg { PlayerMsgType type; /* PMsgSetPlayerOption */ PlayerOption option; /* which option is being set */ bool ack; /* 1 if this is acknowledging a server request */ int value; /* value of option if int or bool */ char *text; /* value of option if string */ } PMsgSetPlayerOptionMsg; /* This informs the player that an option has been set. It may be a response to a request; it may also be autonomous, in which case the player should send a SetPlayerOption with the given value to acknowledge.*/ typedef struct _CMsgPlayerOptionSetMsg { ControllerMsgType type; /* CMsgPlayerOptionSet */ PlayerOption option; /* which option is being set */ bool ack; /* 1 if acknowledging a client request */ int value; /* value of option if int */ char *text; /* value of option if string */ } CMsgPlayerOptionSetMsg; /* Message to tell players game is over */ typedef struct _CMsgGameOverMsg { ControllerMsgType type; /* CMsgGameOver */ } CMsgGameOverMsg; /* Message to set a game option */ typedef struct _PMsgSetGameOptionMsg { PlayerMsgType type; /* PMsgSetGameOption */ word16 optname; char *optvalue; /* this *is* a char*, it's a text representation of value */ } PMsgSetGameOptionMsg; /* Message to inform players of a game option value */ typedef struct _CMsgGameOptionMsg { ControllerMsgType type; /* CMsgGameOption */ int id; /* player who set it, if any */ GameOptionEntry optentry; } CMsgGameOptionMsg; /* Message to query a game option. Controller will reply with a GameOptionMsg */ typedef struct _PMsgQueryGameOptionMsg { PlayerMsgType type; /* PMsgQueryGameOption */ word16 optname; /* name of option to query */ } PMsgQueryGameOptionMsg; /* Message to query all game options. Controller will reply with a sequence of GameOptionMsgs, finishing with the End pseudo-option */ typedef struct _PMsgListGameOptionsMsg { PlayerMsgType type; /* PMsgListGameOptions */ bool include_disabled; /* send even disabled options */ } PMsgListGameOptionsMsg; /* Request new manager for the game. */ typedef struct _PMsgChangeManagerMsg { PlayerMsgType type; /* PMsgChangeManager */ int manager; /* new manager, or 0 if none */ } PMsgChangeManagerMsg; /* Give new manager for the game. */ typedef struct _CMsgChangeManagerMsg { ControllerMsgType type; /* CMsgChangeManager */ int id; /* player requesting change */ int manager; /* new manager, or 0 if none */ } CMsgChangeManagerMsg; /* Send a message to other player(s). */ typedef struct _PMsgSendMessageMsg { PlayerMsgType type; /* PMsgSendMessage */ int addressee; /* id of addressee, or 0 for broadcast */ char *text; } PMsgSendMessageMsg; /* Message from controller or other player. */ typedef struct _CMsgMessageMsg { ControllerMsgType type; /* CMsgMessage */ int sender; /* id of sender, or 0 for message from controller */ int addressee; /* 0 for broadcast, id of recipient otherwise */ char *text; } CMsgMessageMsg; /* This message is primarily used by the controller internally. It sets up the wall with the given tiles */ typedef struct _CMsgWallMsg { ControllerMsgType type; /* CMsgWall */ char *wall; /* wall as space separated tile codes */ } CMsgWallMsg; /* This message serves no purpose in the protocol; it allows comments to be included in files of protocol commands. It is specially hacked in the proto-encode scripts so that the message name is a hash sign # rather than the word Comment; for the decode script, we use the undocumented alias capability. Introduced at protocol 1030. */ typedef struct _CMsgCommentMsg { /* alias # */ ControllerMsgType type; /* CMsgComment */ char *comment; } CMsgCommentMsg; /* This message is for debugging and testing. It asks the controller to swap the old tile for the new tile. This is implemented by finding the new tile in the wall and swapping it with the old tile. */ typedef struct _PMsgSwapTileMsg { /* alias st */ PlayerMsgType type; /* PMsgSwapTile */ int id; /* player to be changed */ Tile oldtile; Tile newtile; } PMsgSwapTileMsg; typedef struct _CMsgSwapTileMsg { ControllerMsgType type; /* CMsgSwapTile */ int id; /* player to be changed */ Tile oldtile; Tile newtile; } CMsgSwapTileMsg; /* dummy just to get the type field. This MUST come after all the real types (for auto-generation of printing and parsing) */ typedef struct _CMsgMsg { ControllerMsgType type; } CMsgMsg; typedef struct _PMsgMsg { PlayerMsgType type; } PMsgMsg; /* Two humungous unions for when they're wanted. Auto generated. */ #include "cmsg_union.h" #include "pmsg_union.h" /* after all that, some functions to handle conversion from/to the wire protocol */ /* The application programmer need only view the protocol as a sequence of lines. Lines may be terminated by CRLF, or LF. If the last character before the terminator is \ (backslash), then a newline is to be included in the "line", and the following wire line appended. The protocol is in fact human readable; it is described in protocol.c */ /* This function takes a pointer to a controller message, and returns a string (including terminating CRLF) encoding it. The returned string is in static storage, and will be overwritten the next time this function is called. */ char *encode_cmsg(CMsgMsg *msg); /* This function takes a string, which is expected to be a complete line (including terminators, although their absence is ignored). It returns a pointer to a message structure. Both the message structure and any strings it contains are malloc'd. The argument string will be mutilated by this function. */ CMsgMsg *decode_cmsg(char *arg); /* Text (that is, char *) elements of the protocol may contain special sequences of the form {CODE#TYPE...#TYPE:TEXT}{ARG}...{ARG} These are macros, which can be used to provide marked-up, rather than formatted messages. The CODE identifies the function the text serves (e.g. Pung claim, line of scoring details, etc.); each #TYPE notes an argument, with its type; and then the arguments follow. The TEXT provides a default text which can be used by a client that does not recognize the CODE; any occurrences of #n, for n = 1..9, in the TEXT should be replaced by the corresponding ARG, formatted according to its type; if the client does not recognize the type, arguments should be substituted as is. Each CODE or TYPE should be alphanumeric; they need not start with a letter. They may have a maximum length of 15 characters. Code definitions and type definitions are not strictly part of the protocol, and do not *require* protocol version changes. However, as a convenience, this file includes a function to do expansion with the currently defined codes and types, and the protocol minor version will be incremented when new codes and types are defined. All participants are required always to provide suitable default texts. However, the following function is provided to do basic expansion of macros, recognizing no codes or types. NOTE: currently, no types or codes are defined. As a consequence of the above, any occurrence of {, }, # or \ in text must be escaped with \. Note that substition is NOT recursive, nor are braces balanced: "{CODE: one { two } three }" will expand to " one { two three }", possibly with a warning about an unescaped }. */ /* basic_expand_protocol_text: Perform text expansion as above, recognizing no codes or types. dest is the destination string, n its length (including terminating null). Returns 1 on success, 0 on internal errors (e.g. missing arguments), and -1 if dest is not long enough. dest will always be null-terminated. If src is null, expands to the empty string. */ int basic_expand_protocol_text(char *dest,const char *src,int n); /* expand_protocol_text: Ditto, recognizing all currently defined codes and types. */ int expand_protocol_text(char *dest,const char *src,int n); /* Similar functions for player messages */ char *encode_pmsg(PMsgMsg *msg); PMsgMsg *decode_pmsg(char *arg); /* functions given the size of a message type. If the size is negative, the last field is a char* */ int cmsg_size_of(ControllerMsgType t); int pmsg_size_of(PlayerMsgType t); /* functions to copy messages, mallocing space and also mallocing any internal char stars, and to free */ CMsgMsg *cmsg_deepcopy(CMsgMsg *m); PMsgMsg *pmsg_deepcopy(PMsgMsg *m); void cmsg_deepfree(CMsgMsg *m); void pmsg_deepfree(PMsgMsg *m); /* enum parsing and printing functions (mainly for game options) */ #include "protocol-enums.h" #endif /* PROTOCOL_H_INCLUDED */