⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 game.c

📁 支持网络和单机的麻将游戏
💻 C
📖 第 1 页 / 共 5 页
字号:
/* $Header: /home/jcb/newmj/RCS/game.c,v 11.25 2003/06/02 16:56:53 jcb Rel $ * 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.                     * *                                                           * *************************************************************/ static const char rcs_id[] = "$Header: /home/jcb/newmj/RCS/game.c,v 11.25 2003/06/02 16:56:53 jcb Rel $";#include "game.h"#include "sysdep.h"/* 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) && 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) ) {    /* 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)	 && ( 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) ) {    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 100000000static 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 },  { 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; i<t->numoptions; 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 */void *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 0;  if ( goe->type == GOTString )     return (void *) &goe->value.optstring;  else    return (void *) 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; i<t->numoptions; 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->idint 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 ( (int)game_get_option_value(g,GOSevenPairs,NULL) )    mjspecflags |= MJSevenPairs;  switch ( cm->type ) {  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;i<NUM_SEATS;i++) g->claims[i] = 0;      g->protversion = m->protversion;      g->manager = m->manager;      game_set_options_from_defaults(g);      /* and games always start inactive */      g->active = 0;    }

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -