#include #include #include #include #include #include #include #include #include "oq.h" #include "mjp.h" #include "msg.h" #include "csget.h" #include "strings.h" #include "pollloop.h" extern const char *__progname; typedef enum { MT_NONE = 1, MT_CHOW, MT_PUNG, MT_KONG, MT_CKONG } MELDTYPE; typedef enum { PS_WAIT = 1, PS_PLAY, PS_CLAIM, PS_CLAIMED, PS_OUT } PLAYERSTATE; typedef enum { DT_NORMAL = 1, DT_FS, DT_KONG } DRAWTYPE; typedef struct game GAME; typedef struct player PLAYER; typedef struct meld MELD; typedef struct conn CONN; typedef struct pconn PCONN; typedef struct nconn NCONN; typedef struct acc ACC; typedef struct rops ROPS; typedef struct scoring SCORING; typedef struct scelt SCELT; struct acc { ACC *link; int fd; struct addrinfo *ai; int id; } ; struct rops { int (*rd)(void *, unsigned char *, int); void (*kill)(void *); } ; #define OPS(suff) { &rd_##suff, &kill_##suff } struct conn { int fd; OQ oq; unsigned char *rbuf; int rlen; int rfill; int rptr; ROPS *ops; void *arg; int id; } ; struct nconn { CONN *c; CSGET *s; } ; struct pconn { CONN *c; char *name; int id; int inx; PLAYER *player; } ; struct meld { MELDTYPE type; int claimedfrom; int claimx; TILE base; /* tile, for pung/kong; lowest tile, for chow */ unsigned int tiles; /* used only during scoring */ } ; struct player { GAME *game; PCONN *conn; int wind; int riichi; int nhand; TILE hand[MAXHAND]; MELD melds[MAXMELDS]; int nfs; TILE fs[8]; PLAYERSTATE state; TILE claim_hand[3]; } ; struct game { PCONN *conns[NWIND]; PLAYER players[NWIND]; TILE deck[DECKSIZE]; int handwind; int drawloc; DRAWTYPE drawtype; int replloc; int nextdeck; int wallleft; int replleft; int ndora; TILE dora[5]; int ndraws; int turn; int cur_discard; PLAYER *cur_discarder; int claimid; SCORING *best; SCORING *cur; TILE scorehand[2+(MAXMELDS*4)]; } ; struct scelt { SCELT *link; const char *what; int pts; } ; struct scoring { SCORING *link; int totpts; SCELT *elts; SCELT **eltt; } ; static const char *portstr = 0; static ACC *accs; static struct addrinfo *acc_ai0; static GAME g; static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! portstr) { portstr = *av; } else { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs ++; } continue; } fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static int free_conn(void *cv) { remove_block_id(((CONN *)cv)->id); free(cv); return(BLOCK_LOOP); } static void kill_conn(CONN *c) { if (c->id == PL_NOID) return; remove_poll_id(c->id); close(c->fd); oq_flush(&c->oq); c->id = PL_NOID; free(c->rbuf); (*c->ops->kill)(c->arg); c->id = add_block_fn(&free_conn,c); } static int rtest_conn(void *cv) { CONN *c; c = cv; if (c->id == PL_NOID) return(0); if (! c->ops) return(0); return(1); } static int wtest_conn(void *cv) { CONN *c; c = cv; if (c->id == PL_NOID) return(0); return(oq_nonempty(&c->oq)); } static void rd_conn(void *cv) { CONN *c; int n; int r; c = cv; if (c->id == PL_NOID) return; if (! c->rbuf) abort(); if (c->rfill < c->rlen) { r = read(c->fd,c->rbuf+c->rfill,c->rlen-c->rfill); if (r < 0) { fprintf(stderr,"%s: read: %s\n",__progname,strerror(errno)); kill_conn(c); return; } if (r == 0) { fprintf(stderr,"%s: read: unexpected EOF\n",__progname); kill_conn(c); return; } c->rfill += r; } while ((c->rptr < c->rfill) && c->ops) { n = (*c->ops->rd)(c->arg,c->rbuf+c->rptr,c->rfill-c->rptr); if (c->id == PL_NOID) return; if (n > 0) c->rptr += n; else break; } if (c->rptr >= c->rfill) { c->rfill = 0; c->rptr = 0; } else if (c->rptr >= (c->rlen>>1)) { bcopy(c->rbuf+c->rptr,c->rbuf,c->rfill-c->rptr); c->rfill -= c->rptr; c->rptr = 0; } } static void wr_conn(void *cv) { CONN *c; int w; c = cv; if (c->id == PL_NOID) return; w = oq_writev(&c->oq,c->fd); if (w < 0) { fprintf(stderr,"%s: writev: %s\n",__progname,strerror(errno)); kill_conn(c); return; } if (w > 0) oq_dropdata(&c->oq,w); } static int rnd(int n) { return(random()%n); } static void report_draw(GAME *g, PLACE from, TILE t, int tohand) { int px; for (px=NWIND-1;px>=0;px--) { msg_write( &g->players[px].conn->c->oq, S_LITERAL, "move", S_TILE, (px == tohand) ? t : TILE_UNKTILE, S_PLACE, from, S_PLACE, makeplace(PT_HAND,g->players[tohand].conn->inx), S_END ); } } static void report_open_move(GAME *g, PLACE from, TILE t, PLACE to) { int px; for (px=NWIND-1;px>=0;px--) { msg_write( &g->players[px].conn->c->oq, S_LITERAL, "move", S_TILE, t, S_PLACE, from, S_PLACE, to, S_END ); } } static void draw_to_hand(GAME *g, int hand) { TILE t; PLAYER *p; p = &g->players[hand]; g->wallleft --; t = g->deck[g->nextdeck--]; p->hand[p->nhand++] = t; report_draw(g,makeplace(PT_WALL,g->drawloc),t,hand); g->drawtype = DT_NORMAL; g->drawloc = (g->drawloc + 1) % WALLSIZE; } static void replace_replaceables(GAME *g, int hand) { PLAYER *p; int i; p = &g->players[hand]; for (i=p->nhand-1;i>=0;i--) { while (tileflags(p->hand[i]) & TF_FS) { report_open_move( g, makeplace(PT_HAND,p->conn->inx), p->hand[i], makeplace(PT_FS,p->conn->inx) ); p->hand[i] = g->deck[g->nextdeck--]; report_draw(g,makeplace(PT_REPL,g->replloc++),p->hand[i],hand); g->drawtype = DT_FS; g->replleft --; } } } static void next_dora(GAME *g) { TILE t; PLACE p; t = g->deck[g->nextdeck--]; p = makeplace(PT_DORA,g->ndora*2); g->dora[g->ndora++] = t; report_open_move(g,p,t,p); } static void begin_hand(GAME *g) { int i; int d1; int d2; int d; int j; int k; int h; OQ *oq; d1 = rnd(6) + 1; d2 = rnd(6) + 1; for (i=NWIND-1;i>=0;i--) { g->players[i].game = g; g->players[i].wind = i; g->players[i].riichi = 0; g->players[i].nhand = 0; g->players[i].nfs = 0; for (j=MAXMELDS-1;j>=0;j--) g->players[i].melds[j].type = MT_NONE; } for (i=NWIND-1;i>=0;i--) { oq = &g->players[i].conn->c->oq; msg_write( oq, S_LITERAL, "newhand", S_ONECHAR, windchar(g->handwind), S_ONECHAR, windchar(g->conns[0]->player->wind), S_ONECHAR, windchar(g->conns[1]->player->wind), S_ONECHAR, windchar(g->conns[2]->player->wind), S_ONECHAR, windchar(g->conns[3]->player->wind), S_END ); msg_write(oq,S_LITERAL,"break",S_ONECHAR,'0'+d1,S_ONECHAR,'0'+d2,S_END); } bcopy(&unshuffled_deck[0],&g->deck[0],DECKSIZE); for (i=DECKSIZE-1;i>=0;i--) { TILE t; j = rnd(DECKSIZE); if (i != j) { t = g->deck[j]; g->deck[j] = g->deck[i]; g->deck[i] = t; } } g->nextdeck = DECKSIZE - 1; g->wallleft = DECKSIZE - REPLSIZE; g->replleft = REPLSIZE; g->replloc = 0; g->ndraws = 0; for (i=WALLSIZE-1;i>=0;i--) { report_open_move(g,makeplace(PT_DECK),TILE_UNKTILE,makeplace(PT_WALL,i)); } d = d1 + d2; d = ((((d+3) % 4) * 17) + d-1) * 2; h = WIND_E; g->drawloc = d; { PLACE nextloc(void) { if (k < DORASIZE) return(makeplace(PT_DORA,k++)); return(makeplace(PT_REPL,k++-DORASIZE)); } k = 0; for (j=DECKSIZE-WALLSIZE;j>0;j--) { report_open_move(g,makeplace(PT_DECK),TILE_UNKTILE,nextloc()); } d ++; for (j=DORASIZE+REPLSIZE-(DECKSIZE-WALLSIZE);j>0;j--) { if (d & 1) d += WALLSIZE-3; else d ++; d %= WALLSIZE; report_open_move(g,makeplace(PT_WALL,d),TILE_UNKTILE,nextloc()); } if (k != DORASIZE+REPLSIZE) abort(); } for (i=3;i>0;i--) { for (j=NWIND;j>0;j--) { for (k=4;k>0;k--) { draw_to_hand(g,h); } h = WIND_NEXT(h); } } for (j=NWIND;j>0;j--) { draw_to_hand(g,h); h = WIND_NEXT(h); } if (h != WIND_E) abort(); draw_to_hand(g,WIND_E); for (j=NWIND;j>0;j--) { replace_replaceables(g,h); h = WIND_NEXT(h); } g->turn = WIND_E; g->cur_discard = TILE_NOTILE; g->cur_discarder = 0; g->ndora = 0; next_dora(g); for (i=NWIND;i>0;i--) g->players[i].state = PS_WAIT; msg_write(&g->players[WIND_E].conn->c->oq,S_LITERAL,"play",S_END); g->players[WIND_E].state = PS_PLAY; } static void fail(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void fail(const char *fmt, ...) { va_list ap; fprintf(stderr,"FAILURE: "); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); exit(1); } static void send_chats(PCONN *src, unsigned int destmask, const char *msg) { GAME *g; int i; int j; g = src->player->game; for (i=NWIND-1;i>=0;i--) { if (destmask & (1 << i)) { msg_write( &g->conns[i]->c->oq, S_LITERAL, "chat", S_ONECHAR, pxid(src->inx), S_END ); for (j=NWIND-1;j>=0;j--) { if ((i != j) && (destmask & (1 << j))) { msg_write(&g->conns[i]->c->oq,S_ONECHAR,pxid(j),S_END); } } msg_write( &g->conns[i]->c->oq, S_ONECHAR, '.', S_CS_COPY, msg, S_STRLEN, S_END ); } } } typedef struct chat_priv CHAT_PRIV; struct chat_priv { ROPS *prevops; void *prevarg; PCONN *pc; CSGET *s; unsigned int dests; } ; static void free_chat(CHAT_PRIV *p) { if (p->s) csget_abort(p->s); free(p); } static void unstack_free_chat(CHAT_PRIV *p) { p->pc->c->ops = p->prevops; p->pc->c->arg = p->prevarg; free_chat(p); } static int rd_chat(void *pv, unsigned char *buf, int len) { CHAT_PRIV *p; int i; int x; p = pv; for (i=0;is) { if (csget_input(p->s,buf[i])) { char *msg; if (csget_error(p->s)) fail("bad chat text string"); msg = csget_result_nul(p->s); p->s = 0; send_chats(p->pc,p->dests,msg); free(msg); unstack_free_chat(p); return(i+1); } } else { if (whitespace(buf[i])) continue; x = pidx(buf[i]); if (x >= 0) { p->dests |= 1 << x; continue; } if (buf[i] != '.') fail("bad chat dest char %02x",buf[i]); p->s = csget_start(MJP_MAXCHATLEN); } } return(len); } static void kill_chat(void *pv) { CHAT_PRIV *p; p = pv; unstack_free_chat(p); (*p->prevops->kill)(p->prevarg); } static ROPS ops_chat = OPS(chat); static int pcmd_chat(PCONN *pc) { CHAT_PRIV *priv; priv = malloc(sizeof(CHAT_PRIV)); priv->prevops = pc->c->ops; priv->prevarg = pc->c->arg; priv->pc = pc; priv->s = 0; priv->dests = 0; pc->c->ops = &ops_chat; pc->c->arg = priv; return(4); } static int tile_in_hand(TILE t, PLAYER *pl, unsigned int mask) { int i; unsigned int b; for (i=pl->nhand-1,b=1U<=0;i--,b>>=1) { if (mask & b) continue; if (pl->hand[i] == t) return(i); } return(-1); } static void kong_replacement(PLAYER *pl) { GAME *g; TILE t; g = pl->game; t = g->deck[g->nextdeck--]; pl->hand[pl->nhand++] = t; report_draw(g,makeplace(PT_REPL,g->replloc++),t,pl->wind); next_dora(g); g->drawtype = DT_KONG; replace_replaceables(g,pl->wind); } static void reject_it(PLAYER *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void reject_it(PLAYER *p, const char *fmt, ...) { char *msg; int len; va_list ap; va_start(ap,fmt); len = vasprintf(&msg,fmt,ap); va_end(ap); msg_write(&p->conn->c->oq,S_LITERAL,"reject",S_CS_FREE,msg,len,S_END); } static const char *tile_name(TILE t) { static char qbuf[64]; switch (t) { case TILE_UNKTILE: return("?""?"); break; case TILE_NOTILE: return(".."); break; } if ((t >= 0) && (t < TILE__N)) return(tilenames[t]); sprintf(&qbuf[0],"?%d",(int)t); return(&qbuf[0]); } static void sort_tiles(TILE *v, int n) { int i; int j; TILE t; for (i=n-1;i>0;i--) { for (j=i-1;j>=0;j--) { if (v[j] > v[i]) { t = v[i]; v[i] = v[j]; v[j] = t; } } } } static void sort_ints(int *v, int n) { int i; int j; int t; for (i=n-1;i>0;i--) { for (j=i-1;j>=0;j--) { if (v[j] > v[i]) { t = v[i]; v[i] = v[j]; v[j] = t; } } } } #if (MAXHAND != 14) || (MAXMELDS != 4) #error going-out/scoring code needs attention #endif static int seven_pairs(TILE *h) { return( (h[0] == h[1]) && (h[2] == h[3]) && (h[4] == h[5]) && (h[6] == h[7]) && (h[8] == h[9]) && (h[10] == h[11]) && (h[12] == h[13]) ); } static int seven_shifted_pairs(TILE *h) { static TILE init[3][9] = { { TILE_1_BAMBOO, TILE_2_BAMBOO, TILE_3_BAMBOO, TILE_4_BAMBOO, TILE_5_BAMBOO, TILE_6_BAMBOO, TILE_7_BAMBOO, TILE_8_BAMBOO, TILE_9_BAMBOO }, { TILE_1_CHARACTER, TILE_2_CHARACTER, TILE_3_CHARACTER, TILE_4_CHARACTER, TILE_5_CHARACTER, TILE_6_CHARACTER, TILE_7_CHARACTER, TILE_8_CHARACTER, TILE_9_CHARACTER }, { TILE_1_DOT, TILE_2_DOT, TILE_3_DOT, TILE_4_DOT, TILE_5_DOT, TILE_6_DOT, TILE_7_DOT, TILE_8_DOT, TILE_9_DOT } }; static int didinit = 0; static TILE hands[9][14]; int i; int j; int hx; if (! didinit) { hx = 0; for (i=3-1;i>=0;i--) { for (j=3-1;j>=0;j--) { bcopy(&init[j][i],&hands[hx][0],7); bcopy(&hands[hx][0],&hands[hx][7],7); sort_tiles(&hands[hx][0],14); hx ++; } } didinit = 1; } for (i=9-1;i>=0;i--) { if (! bcmp(h,&hands[i][0],14)) return(1); } return(0); } static int nine_gates(TILE *h, TILE last) { static TILE in_b[13] = { TILE_1_BAMBOO, TILE_1_BAMBOO, TILE_1_BAMBOO, TILE_2_BAMBOO, TILE_3_BAMBOO, TILE_4_BAMBOO, TILE_5_BAMBOO, TILE_6_BAMBOO, TILE_7_BAMBOO, TILE_8_BAMBOO, TILE_9_BAMBOO, TILE_9_BAMBOO, TILE_9_BAMBOO }; static TILE in_c[13] = { TILE_1_CHARACTER, TILE_1_CHARACTER, TILE_1_CHARACTER, TILE_2_CHARACTER, TILE_3_CHARACTER, TILE_4_CHARACTER, TILE_5_CHARACTER, TILE_6_CHARACTER, TILE_7_CHARACTER, TILE_8_CHARACTER, TILE_9_CHARACTER, TILE_9_CHARACTER, TILE_9_CHARACTER }; static TILE in_d[13] = { TILE_1_DOT, TILE_1_DOT, TILE_1_DOT, TILE_2_DOT, TILE_3_DOT, TILE_4_DOT, TILE_5_DOT, TILE_6_DOT, TILE_7_DOT, TILE_8_DOT, TILE_9_DOT, TILE_9_DOT, TILE_9_DOT }; static int didinit = 0; if (! didinit) { sort_tiles(&in_b[0],13); sort_tiles(&in_c[0],13); sort_tiles(&in_d[0],13); didinit = 1; } return( same_suit(h[0],last) && ( !bcmp(&in_b[0],h,13*sizeof(TILE)) || !bcmp(&in_c[0],h,13*sizeof(TILE)) || !bcmp(&in_d[0],h,13*sizeof(TILE)) ) ); } /* XXX How is Thirteen Orphans affected by haku as a dragon? */ static int thirteen_orphans(TILE *h, TILE last) { static TILE want[13] = { TILE_1_BAMBOO, TILE_1_CHARACTER, TILE_1_DOT, TILE_9_BAMBOO, TILE_9_CHARACTER, TILE_9_DOT, TILE_WIND_E, TILE_WIND_S, TILE_WIND_W, TILE_WIND_N, TILE_DRAGON_R, TILE_DRAGON_G, TILE_DRAGON_W }; static int didinit = 0; if (! didinit) { sort_tiles(&want[0],13); didinit = 1; } return( !bcmp(h,&want[0],13*sizeof(TILE)) && ({ int i; for (i=13-1;i>=0;i--) if (last == want[i]) break; (i>=0); }) ); } static void enum_elts(TILE *v, unsigned int m, int nt, void (*got)(TILE *, unsigned int *, unsigned int)) { unsigned int triples[4]; unsigned int head; void check(TILE *v, unsigned int m) { unsigned int im; int ix; for (im=0x2000,ix=14-1;im;im>>=1,ix--) { if ((m & im) && ((v[ix] < 0) || (v[ix] >= TILE__N))) abort(); } } void search(unsigned int m, int nt, int hd, int tx) { unsigned int im; int ix; unsigned int jm; int jx; unsigned int km; int kx; if ((nt < 0) || (nt > 4) || (hd < 0) || (hd > 1)) abort(); do <"found"> { for (im=0x2000,ix=14-1;im;im>>=1,ix--) if (m & im) break <"found">; abort(); } while (0); if ((nt == 0) && !hd) { (*got)(v,&triples[0],head); return; } for (jm=im>>1,jx=ix-1;jm;jm>>=1,jx--) { if ((m & jm) && (v[jx] == v[ix])) { if (nt > 0) { for (km=jm>>1,kx=jx-1;km;km>>=1,kx--) { if ((m & km) && (v[kx] == v[ix])) { triples[tx] = im | jm | km; search(m&~(im|jm|km),nt-1,hd,tx+1); } } } if (hd) { head = im | jm; search(m&~(im|jm),nt,0,tx); } } } if (tileflags(v[ix]) & TF_SUIT) { TILE u; TILE uu; TILE d; TILE dd; u = tile_nextup(v[ix]); uu = (u == TILE_NOTILE) ? TILE_NOTILE : tile_nextup(u); d = tile_nextdn(v[ix]); dd = (d == TILE_NOTILE) ? TILE_NOTILE : tile_nextdn(d); for (jm=im>>1,jx=ix-1;jm;jm>>=1,jx--) { if ((m & jm) && (v[jx] == u)) { for (km=jm>>1,kx=jx-1;km;km>>=1,kx--) { if ((m & km) && ((v[kx] == uu) || (v[kx] == d))) { triples[tx] = im | jm | km; search(m&~(im|jm|km),nt-1,hd,tx+1); } } } if ((m & jm) && (v[jx] == uu)) { for (km=jm>>1,kx=jx-1;km;km>>=1,kx--) { if ((m & km) && (v[kx] == u)) { triples[tx] = im | jm | km; search(m&~(im|jm|km),nt-1,hd,tx+1); } } } if ((m & jm) && (v[jx] == d)) { for (km=jm>>1,kx=jx-1;km;km>>=1,kx--) { if ((m & km) && ((v[kx] == dd) || (v[kx] == u))) { triples[tx] = im | jm | km; search(m&~(im|jm|km),nt-1,hd,tx+1); } } } if ((m & jm) && (v[jx] == dd)) { for (km=jm>>1,kx=jx-1;km;km>>=1,kx--) { if ((m & km) && (v[kx] == d)) { triples[tx] = im | jm | km; search(m&~(im|jm|km),nt-1,hd,tx+1); } } } } } } check(v,m); search(m,nt,1,0); } static int can_go_out(PLAYER *p, TILE last) { TILE h[MAXHAND]; int found; void got_elts(TILE *h __attribute__((__unused__)), unsigned int *triples __attribute__((__unused__)), unsigned int head __attribute__((__unused__))) { found = 1; } found = 0; switch (p->nhand % 3) { case 2: if (last != TILE_NOTILE) abort(); break; case 1: if (last == TILE_NOTILE) abort(); p->hand[p->nhand] = last; break; default: abort(); break; } switch (p->nhand) { default: abort(); break; case 14: case 13: bcopy(&p->hand[0],&h[0],13*sizeof(TILE)); sort_tiles(&h[0],13); if (nine_gates(&h[0],p->hand[13])) return(1); if (thirteen_orphans(&h[0],p->hand[13])) return(1); h[13] = p->hand[13]; sort_tiles(&h[0],14); if (seven_pairs(&h[0])) return(1); enum_elts(&p->hand[0],0x3fff,4,&got_elts); break; case 11: case 10: case 8: case 7: case 5: case 4: case 2: case 1: enum_elts(&p->hand[0],~((~3U)<<(3*(p->nhand/3))),p->nhand/3,&got_elts); break; } return(found); } static void scoring_init(GAME *g) { g->best = 0; g->cur = 0; } static void scoring_add(GAME *g, const char *what, int pts) { SCELT *e; if (! g->cur) { g->cur = malloc(sizeof(SCORING)); g->cur->link = 0; g->cur->totpts = 0; g->cur->elts = 0; g->cur->eltt = &g->cur->elts; } e = malloc(sizeof(SCELT)); e->what = what; e->pts = pts; *g->cur->eltt = e; g->cur->eltt = &e->link; e->link = 0; g->cur->totpts += pts; } static void free_scoring(SCORING *sc) { SCELT *e; while ((e=sc->elts)) { sc->elts = e->link; free(e); } free(sc); } static void free_scoring_chain(SCORING *sc) { SCORING *sl; while (sc) { sl = sc->link; free_scoring(sc); sc = sl; } } static void scoring_more(GAME *g) { if (g->cur || !g->best) abort(); g->cur = g->best; g->best = 0; } static void scoring_close(GAME *g) { if (! g->best) { g->best = g->cur; } else if (g->best->totpts < g->cur->totpts) { free_scoring_chain(g->best); g->best = g->cur; } else if (g->best->totpts > g->cur->totpts) { free_scoring(g->cur); } else { g->cur->link = g->best; g->best = g->cur; } g->cur = 0; } typedef struct score SCORE; struct score { int pts; void (*try)(PLAYER *, MELD *, TILE, int); #define SCORE_TRY_PROTO PLAYER *, MELD *, TILE, int #define SCORE_TRY_ARGS \ PLAYER *pl __attribute__((__unused__)), \ MELD *elts __attribute__((__unused__)), \ TILE head __attribute__((__unused__)), \ int claimed __attribute__((__unused__)) unsigned int flags; #define SF_ENABLED 0x00000001 #define SF_SEVENPAIR 0x00000002 } ; static SCORE scores[]; /* forward */ static const int nscores; /* forward */ static int green_tile(TILE t) { return((tileflags(t)&TF_GREEN)?1:0); } static int simple_tile(TILE t) { return((tileflags(t)&TF_SIMPLE)?1:0); } static int reversible_tile(TILE t) { return((tileflags(t)&TF_REVERSIBLE)?1:0); } static int upper_four_tile(TILE t) { return((tileflags(t)&TF_UPPER4)?1:0); } static int lower_four_tile(TILE t) { return((tileflags(t)&TF_LOWER4)?1:0); } static int even_tile(TILE t) { return((tileflags(t)&TF_EVEN)?1:0); } static int bamboo_tile(TILE t) { return(same_suit(t,TILE_1_BAMBOO)); } static int character_tile(TILE t) { return(same_suit(t,TILE_1_CHARACTER)); } static int dot_tile(TILE t) { return(same_suit(t,TILE_1_DOT)); } static int upper_tile(TILE t) { return((tileflags(t)&TF_UPPER3)?1:0); } static int middle_tile(TILE t) { return((tileflags(t)&TF_MIDDLE3)?1:0); } static int lower_tile(TILE t) { return((tileflags(t)&TF_LOWER3)?1:0); } static int terminal_or_honour_tile(TILE t) { return((tileflags(t)&(TF_TERMINAL|TF_HONOUR))?1:0); } static int terminal_tile(TILE t) { return((tileflags(t)&TF_TERMINAL)?1:0); } static int honour_tile(TILE t) { return((tileflags(t)&TF_HONOUR)?1:0); } static void map_tiles(PLAYER *pl, void (*fn)(TILE)) { int mx; MELD *m; TILE tv[MAXMELDS*4]; int i; i = 0; for (mx=MAXMELDS-1;mx>=0;mx--) { m = &pl->melds[mx]; switch (m->type) { case MT_NONE: break; case MT_CHOW: tv[i++] = m->base; tv[i++] = tile_nextup(m->base); tv[i++] = tile_nextup(tile_nextup(m->base)); break; case MT_KONG: case MT_CKONG: tv[i++] = m->base; /* fall through */ case MT_PUNG: tv[i++] = m->base; tv[i++] = m->base; tv[i++] = m->base; break; default: abort(); break; } } if (i > sizeof(tv)/sizeof(tv[0])) abort(); for (i--;i>=0;i--) (*fn)(tv[i]); for (i=pl->nhand-1;i>=0;i--) (*fn)(pl->hand[i]); } static int all_tiles_are(PLAYER *pl, int (*tst)(TILE)) { int bad; void chk(TILE t) { if (! (*tst)(t)) bad = 1; } bad = 0; map_tiles(pl,&chk); return(!bad); } static void disable_score(void (*sfn)(SCORE_TRY_PROTO)) { int i; for (i=nscores-1;i>=0;i--) if (scores[i].try == sfn) scores[i].flags &= ~SF_ENABLED; } static int meld_is_xxng(MELD *m) { switch (m->type) { case MT_PUNG: case MT_KONG: case MT_CKONG: return(1); break; default: return(0); break; } } static int meld_is_kong(MELD *m) { switch (m->type) { case MT_KONG: case MT_CKONG: return(1); break; default: return(0); break; } } static int meld_is_chow(MELD *m) { switch (m->type) { case MT_CHOW: return(1); break; default: return(0); break; } } static void score_pure_double_chow(SCORE_TRY_ARGS) { int i; int j; for (i=4-1;i>0;i--) { if (meld_is_chow(&elts[i])) { for (j=i-1;j>=0;j--) { if ( meld_is_chow(&elts[j]) && (elts[j].base == elts[i].base) ) { scoring_add(pl->game,"Pure Double Chow",1); } } } } } static void score_mixed_double_chow(SCORE_TRY_ARGS) { int i; int j; for (i=4-1;i>0;i--) { if (meld_is_chow(&elts[i])) { for (j=i-1;j>=0;j--) { if ( meld_is_chow(&elts[j]) && !same_suit(elts[j].base,elts[i].base) && (tile_num(elts[j].base) == tile_num(elts[i].base)) ) { scoring_add(pl->game,"Mixed Double Chow",1); } } } } } static void score_short_straight(SCORE_TRY_ARGS) { int i; int j; for (i=4-1;i>0;i--) { if (meld_is_chow(&elts[i])) { for (j=i-1;j>=0;j--) { if ( meld_is_chow(&elts[j]) && same_suit(elts[j].base,elts[i].base) && ( (tile_num(elts[j].base) == tile_num(elts[i].base)+3) || (tile_num(elts[i].base) == tile_num(elts[j].base)+3) ) ) { scoring_add(pl->game,"Short Straight",1); } } } } } /* XXX does 123 123 789 in suit score Two Terminal Chows twice? */ /* This code assumes not, that no elt can participate in more than one Two Terminal Chows. */ static void score_two_terminal_chows(SCORE_TRY_ARGS) { unsigned int dis; int i; int j; dis = 0; for (i=4-1;i>0;i--) { if (dis & (1U << i)) continue; if (meld_is_chow(&elts[i])) { for (j=i-1;j>=0;j--) { if (dis & (1U << j)) continue; if ( meld_is_chow(&elts[j]) && same_suit(elts[j].base,elts[i].base) && ( ( (tile_num(elts[i].base) == 1) && (tile_num(elts[j].base) == 7) ) || ( (tile_num(elts[i].base) == 7) && (tile_num(elts[j].base) == 1) ) ) ) { scoring_add(pl->game,"Two Terminal Chows",1); dis |= (1U << i) | (1U << j); } } } } } static void score_pung_of_terminals_or_honours(SCORE_TRY_ARGS) { int i; for (i=4-1;i>0;i--) { if ( meld_is_xxng(&elts[i]) && (tileflags(elts[i].base) & (TF_TERMINAL|TF_HONOUR)) ) { scoring_add(pl->game,"Pung of Terminals or Honours",1); } } } static void score_melded_kong(SCORE_TRY_ARGS) { int i; for (i=4-1;i>0;i--) { if (elts[i].type == MT_KONG) { scoring_add(pl->game,"Melded Kong",1); } } } static void score_voided_suit(SCORE_TRY_ARGS) /* SEVENPAIR */ { static const TILE proto[3] = { TILE_1_BAMBOO, TILE_1_CHARACTER, TILE_1_DOT }; int have[3]; void chk(TILE t) { int i; for (i=3-1;i>=0;i--) if (same_suit(proto[i],t)) have[i] = 1; } have[0] = 0; have[1] = 0; have[2] = 0; map_tiles(pl,&chk); if (have[0]+have[1]+have[2] != 3) scoring_add(pl->game,"Voided Suit",1); } static void score_no_honours(SCORE_TRY_ARGS) /* SEVENPAIR */ { int any; any = 0; map_tiles(pl,({ void chk(TILE t) { if (tileflags(t) & TF_HONOUR) any = 1; } &chk; })); if (! any) scoring_add(pl->game,"No Honours",1); } static void score_edge_wait(SCORE_TRY_ARGS) { int i; for (i=4-1;i>0;i--) { if ( meld_is_chow(&elts[i]) && (elts[i].tiles & (1U << (pl->nhand-1))) && ( ( (tile_num(elts[i].base) == 1) && (tile_num(pl->hand[pl->nhand-1]) == 3) ) || ( (tile_num(elts[i].base) == 7) && (pl->hand[pl->nhand-1] == elts[i].base) ) ) ) { scoring_add(pl->game,"Edge Wait",1); return; } } } static void score_closed_wait(SCORE_TRY_ARGS) { int i; for (i=4-1;i>0;i--) { if ( meld_is_chow(&elts[i]) && (elts[i].tiles & (1U << (pl->nhand-1))) && (pl->hand[pl->nhand-1] == tile_nextup(elts[i].base)) ) { scoring_add(pl->game,"Closed Wait",1); return; } } } static void score_single_wait(SCORE_TRY_ARGS) { int i; for (i=4-1;i>0;i--) { if (elts[i].tiles & (1U << (pl->nhand-1))) return; } scoring_add(pl->game,"Single Wait",1); } /* XXX Do kong/flower replacement tiles count as self-drawn? */ static void score_self_draw(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (! claimed) scoring_add(pl->game,"Self Draw",1); } static void score_dragon_pung(SCORE_TRY_ARGS) { int i; for (i=4-1;i>0;i--) { if ( meld_is_xxng(&elts[i]) && (tileflags(elts[i].base) & TF_DRAGON) ) { scoring_add(pl->game,"Dragon Pung",2); } } } static void score_prevalent_wind(SCORE_TRY_ARGS) { int i; for (i=4-1;i>0;i--) { if ( meld_is_xxng(&elts[i]) && (elts[i].base == wind_to_tile(pl->game->handwind)) ) { scoring_add(pl->game,"Prevalent Wind",2); } } } static void score_seat_wind(SCORE_TRY_ARGS) { int i; for (i=4-1;i>0;i--) { if ( meld_is_xxng(&elts[i]) && (elts[i].base == wind_to_tile(pl->wind)) ) { scoring_add(pl->game,"Seat Wind",2); } } } static void score_concealed_hand(SCORE_TRY_ARGS) { int i; for (i=MAXMELDS-1;i>=0;i--) if (pl->melds[i].type != MT_NONE) return; scoring_add(pl->game,"Concealed Hand",2); } /* XXX does it matter whether the head is a suit tile or not? */ static void score_all_chows(SCORE_TRY_ARGS) { int i; for (i=4-1;i>=0;i--) if (! meld_is_chow(&elts[i])) return; scoring_add(pl->game,"All Chows",2); } static void score_tile_hog(SCORE_TRY_ARGS) /* SEVENPAIR */ { int c[TILE__N]; int i; void chk(TILE t) { c[t] ++; } for (i=TILE__N-1;i>=0;i--) c[i] = 0; map_tiles(pl,&chk); for (i=MAXMELDS-1;i>=0;i--) { if (meld_is_kong(&pl->melds[i])) { c[pl->melds[i].base] = 0; /* anything but 4 */ } } for (i=TILE__N-1;i>=0;i--) { if (c[i] == 4) { if (! (tileflags(i) & TF_SUIT)) abort(); scoring_add(pl->game,"Tile Hog",2); } } } /* XXX Can we score Double Pung twice in the same hand? Assume not. */ static void score_double_pung(SCORE_TRY_ARGS) { int i; int j; for (i=4-1;i>0;i--) { if ( meld_is_xxng(&elts[i]) && (tileflags(elts[i].base) & TF_SUIT) ) { for (j=i-1;j>=0;j--) { if ( meld_is_xxng(&elts[j]) && (tileflags(elts[j].base) & TF_SUIT) && !same_suit(elts[i].base,elts[j].base) && (tile_num(elts[i].base) == tile_num(elts[j].base)) ) { scoring_add(pl->game,"Double Pung",2); return; } } } } } /* Cannot score twice - Four Concealed Pungs trips first */ /* XXX do concealed-kong melds count? */ static void score_two_concealed_pungs(SCORE_TRY_ARGS) { int i; int n; n = 0; for (i=4-1;i>0;i--) { switch (elts[i].type) { case MT_PUNG: if (elts[i].tiles <= ((1U<nhand)-1)) n ++; break; case MT_CKONG: n ++; break; default: break; } } if (n < 2) return; if (n > 2) abort(); scoring_add(pl->game,"Two Concealed Pungs",2); } static void score_concealed_kong(SCORE_TRY_ARGS) { int i; for (i=4-1;i>0;i--) if (elts[i].type == MT_CKONG) scoring_add(pl->game,"Concealed Kong",2); } static void score_all_simples(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (all_tiles_are(pl,&simple_tile)) { scoring_add(pl->game,"All Simples",2); } } static void score_outside_hand(SCORE_TRY_ARGS) { int i; if (! (tileflags(head) & (TF_TERMINAL|TF_HONOUR))) return; for (i=4-1;i>0;i--) { switch (elts[i].type) { case MT_CHOW: break; switch (tile_num(elts[i].base)) { case 1: case 7: break; default: return; break; } break; case MT_PUNG: case MT_KONG: case MT_CKONG: if (! (tileflags(elts[i].base) & (TF_TERMINAL|TF_HONOUR))) return; break; default: abort(); break; } } scoring_add(pl->game,"Outside Hand",4); } static void score_fully_concealed_hand(SCORE_TRY_ARGS) /* SEVENPAIR */ { int i; for (i=MAXMELDS-1;i>=0;i--) if (pl->melds[i].type != MT_NONE) return; if (claimed) return; scoring_add(pl->game,"Fully Concealed Hand",4); disable_score(&score_concealed_hand); } static void score_two_melded_kongs(SCORE_TRY_ARGS) { int i; int n; n = 0; for (i=4-1;i>0;i--) if (elts[i].type == MT_KONG) n ++; for (n/=2;n>0;n--) scoring_add(pl->game,"Two Melded Kongs",4); } static void score_last_tile(SCORE_TRY_ARGS) { /* XXX 4 Last Tile Other three of winning tile are openly visible */ } static void score_all_pungs(SCORE_TRY_ARGS) { int i; for (i=4-1;i>0;i--) if (meld_is_chow(&elts[i])) return; scoring_add(pl->game,"All Pungs",6); } /* Don't check for honours; Full Flush disables this */ static void score_half_flush(SCORE_TRY_ARGS) /* SEVENPAIR */ { static const TILE proto[3] = { TILE_1_BAMBOO, TILE_1_CHARACTER, TILE_1_DOT }; int have[3]; void chk(TILE t) { int i; for (i=3-1;i>=0;i--) if (same_suit(proto[i],t)) have[i] = 1; } have[0] = 0; have[1] = 0; have[2] = 0; map_tiles(pl,&chk); if (have[0]+have[1]+have[2] == 1) scoring_add(pl->game,"Half Flush",1); disable_score(&score_voided_suit); } /* XXX Can we score Mixed Shifted Chowns twice in the same hand? (eg, 234b 345c 456d 567b, or 234b 345c 345c 456d.) Assume not. */ static void score_mixed_shifted_chows(SCORE_TRY_ARGS) { int i; int j; int k; for (i=4-1;i>=0;i--) { if (! meld_is_chow(&elts[i])) continue; for (j=4-1;j>=0;j--) { if (j == i) continue; if (! meld_is_chow(&elts[j])) continue; if (same_suit(elts[i].base,elts[j].base)) continue; for (k=4-1;k>=0;k--) { if ((k == i) || (k == j)) continue; if (! meld_is_chow(&elts[k])) continue; if ( same_suit(elts[i].base,elts[k].base) || same_suit(elts[j].base,elts[k].base) ) continue; if ( (tile_num(elts[i].base) == tile_num(elts[j].base)-1) && (tile_num(elts[j].base) == tile_num(elts[k].base)-1) ) { scoring_add(pl->game,"Mixed Shifted Chows",6); return; } } } } } static void score_all_types(SCORE_TRY_ARGS) /* SEVENPAIR */ { unsigned int have; void chk(TILE t) { if (same_suit(TILE_1_BAMBOO,t)) have |= 1; if (same_suit(TILE_1_CHARACTER,t)) have |= 2; if (same_suit(TILE_1_DOT,t)) have |= 4; if (tileflags(t) & TF_DRAGON) have |= 8; if (tileflags(t) & TF_WIND) have |= 16; } have = 0; map_tiles(pl,&chk); if (have == 31) scoring_add(pl->game,"All Types",6); } static void score_melded_hand(SCORE_TRY_ARGS) { if ((pl->nhand == 2) && claimed) scoring_add(pl->game,"Melded Hand",6); } static void score_two_dragon_pungs(SCORE_TRY_ARGS) { int i; int n; n = 0; for (i=4-1;i>0;i--) { if ( meld_is_xxng(&elts[i]) && (tileflags(elts[i].base) & TF_DRAGON) ) n ++; } for (n/=2;n>0;n--) scoring_add(pl->game,"Two Dragon Pungs",6); } static void score_one_melded_and_one_concealed_kong(SCORE_TRY_ARGS) { int i; int nm; int nc; nm = 0; nc = 0; for (i=4-1;i>0;i--) { switch (elts[i].type) { case MT_KONG: nm ++; break; case MT_CKONG: nc ++; break; default: break; } } for (i=(nm0;i--) scoring_add(pl->game,"One melded and one concealed kong",6); } static void score_mixed_triple_chow(SCORE_TRY_ARGS) { int i; int j; int k; for (i=4-1;i>=0;i--) { if (! meld_is_chow(&elts[i])) continue; for (j=4-1;j>=0;j--) { if (j == i) continue; if (! meld_is_chow(&elts[j])) continue; if (same_suit(elts[i].base,elts[j].base)) continue; for (k=4-1;k>=0;k--) { if ((k == i) || (k == j)) continue; if (! meld_is_chow(&elts[k])) continue; if ( same_suit(elts[i].base,elts[k].base) || same_suit(elts[j].base,elts[k].base) ) continue; if ( (tile_num(elts[i].base) == tile_num(elts[j].base)) && (tile_num(elts[j].base) == tile_num(elts[k].base)) ) { scoring_add(pl->game,"Mixed Triple Chow",8); disable_score(&score_mixed_double_chow); return; } } } } } static void score_mixed_straight(SCORE_TRY_ARGS) { int i; int j; int k; for (i=4-1;i>=0;i--) { if (! meld_is_chow(&elts[i])) continue; if (tile_num(elts[i].base) != 1) continue; for (j=4-1;j>=0;j--) { if (j == i) continue; if (! meld_is_chow(&elts[j])) continue; if (same_suit(elts[i].base,elts[j].base)) continue; if (tile_num(elts[j].base) != 4) continue; for (k=4-1;k>=0;k--) { if ((k == i) || (k == j)) continue; if (! meld_is_chow(&elts[k])) continue; if ( same_suit(elts[i].base,elts[k].base) || same_suit(elts[j].base,elts[k].base) ) continue; if (tile_num(elts[k].base) != 7) continue; scoring_add(pl->game,"Mixed Straight",8); return; } } } } static void score_mixed_shifted_pungs(SCORE_TRY_ARGS) { int i; int j; int k; for (i=4-1;i>=0;i--) { if (! meld_is_xxng(&elts[i])) continue; for (j=4-1;j>=0;j--) { if (j == i) continue; if (! meld_is_xxng(&elts[j])) continue; if (same_suit(elts[i].base,elts[j].base)) continue; for (k=4-1;k>=0;k--) { if ((k == i) || (k == j)) continue; if (! meld_is_xxng(&elts[k])) continue; if ( same_suit(elts[i].base,elts[k].base) || same_suit(elts[j].base,elts[k].base) ) continue; if ( (tile_num(elts[i].base) == tile_num(elts[j].base)-1) && (tile_num(elts[j].base) == tile_num(elts[k].base)-1) ) { scoring_add(pl->game,"Mixed Shifted Pungs",8); return; } } } } } static void score_two_concealed_kongs(SCORE_TRY_ARGS) { int i; int n; n = 0; for (i=4-1;i>0;i--) if (elts[i].type == MT_CKONG) n ++; for (n/=2;n>0;n--) scoring_add(pl->game,"Two Concealed Kongs",8); } /* XXX does a flower replacement tile count? does a kong replacement when the kong results from a last tile draw? */ static void score_last_tile_draw(SCORE_TRY_ARGS) /* SEVENPAIR */ { if ((pl->game->wallleft == 0) && !claimed) { scoring_add(pl->game,"Last Tile Draw",8); disable_score(&score_self_draw); } } static void score_last_tile_claim(SCORE_TRY_ARGS) /* SEVENPAIR */ { if ((pl->game->wallleft == 0) && claimed) { scoring_add(pl->game,"Last Tile Claim",8); disable_score(&score_self_draw); } } static void score_out_with_replacement_tile(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (!claimed && (pl->game->drawtype == DT_KONG)) { scoring_add(pl->game,"Out With Replacement Tile",8); } } static void score_robbing_the_kong(SCORE_TRY_ARGS) { /* XXX 8 Robbing the Kong Going out on a tile that would have promoted someone else's exposed pung to a kong */ } static void score_reversible_tiles(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (all_tiles_are(pl,&reversible_tile)) { scoring_add(pl->game,"Reversible Tiles",8); } } static void score_upper_four(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (all_tiles_are(pl,&upper_four_tile)) { scoring_add(pl->game,"Upper Four",12); } } static void score_lower_four(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (all_tiles_are(pl,&lower_four_tile)) { scoring_add(pl->game,"Lower Four",12); } } static void score_big_three_winds(SCORE_TRY_ARGS) { int i; int n; n = 0; for (i=4-1;i>0;i--) { if ( meld_is_xxng(&elts[i]) && (tileflags(elts[i].base) & TF_WIND) ) n ++; } if (n >= 3) scoring_add(pl->game,"Big Three Winds",12); } /* XXX Is it possible to score Pure Straight twice in the same hand? (eg, 123 456 456 789.) This code assumes not. */ static void score_pure_straight(SCORE_TRY_ARGS) { int i; int j; int k; for (i=4-1;i>=0;i--) { if (! meld_is_chow(&elts[i])) continue; if (tile_num(elts[i].base) != 1) continue; for (j=4-1;j>=0;j--) { if (j == i) continue; if (! meld_is_chow(&elts[j])) continue; if (! same_suit(elts[i].base,elts[j].base)) continue; if (tile_num(elts[j].base) != 4) continue; for (k=4-1;k>=0;k--) { if ((k == i) || (k == j)) continue; if (! meld_is_chow(&elts[k])) continue; if (! same_suit(elts[i].base,elts[k].base)) continue; if (tile_num(elts[k].base) != 7) continue; scoring_add(pl->game,"Pure Straight",16); disable_score(&score_short_straight); return; } } } } /* XXX disable All Chows here? */ static void score_three_suited_terminal_chows(SCORE_TRY_ARGS) { int i; TILE suit0; int got[2][2]; int sx; int nx; if (!(tileflags(head) & TF_SUIT) || (tile_num(head) != 5)) return; suit0 = TILE_NOTILE; got[0][0] = 0; got[0][1] = 0; got[1][0] = 0; got[1][1] = 0; for (i=4-1;i>=0;i--) { if (! meld_is_chow(&elts[i])) return; if (same_suit(elts[i].base,head)) return; switch (tile_num(elts[i].base)) { case 1: nx = 0; break; case 7: nx = 1; break; default: return; break; } if (suit0 == TILE_NOTILE) { suit0 = elts[i].base; sx = 0; } else if (same_suit(suit0,elts[i].base)) { sx = 0; } else { sx = 1; } if (got[sx][nx]) return; got[sx][nx] = 1; } scoring_add(pl->game,"Three Suited Terminal Chows",16); } /* XXX is it possible to score two Pure Shifted Chows in a hand? eg, 123 345 345 567? Assume not. */ static void score_pure_shifted_chows(SCORE_TRY_ARGS) { int i; int j; int k; int ni; int nj; int nk; for (i=4-1;i>=0;i--) { if (! meld_is_chow(&elts[i])) continue; ni = tile_num(elts[i].base); for (j=4-1;j>=0;j--) { if (j == i) continue; if (! meld_is_chow(&elts[j])) continue; if (! same_suit(elts[i].base,elts[j].base)) continue; nj = tile_num(elts[j].base); for (k=4-1;k>=0;k--) { if ((k == i) || (k == j)) continue; if (! meld_is_chow(&elts[k])) continue; if (! same_suit(elts[i].base,elts[k].base)) continue; nk = tile_num(elts[k].base); if ( ((ni+1 == nj) && (nj+1 == nk)) || ((ni+1 == nk) && (nk+1 == nj)) || ((nj+1 == nk) && (nk+1 == ni)) || ((nj+1 == ni) && (ni+1 == nk)) || ((nk+1 == ni) && (ni+1 == nj)) || ((nk+1 == nj) && (nj+1 == ni)) || ((ni+2 == nj) && (nj+2 == nk)) || ((ni+2 == nk) && (nk+2 == nj)) || ((nj+2 == nk) && (nk+2 == ni)) || ((nj+2 == ni) && (ni+2 == nk)) || ((nk+2 == ni) && (ni+2 == nj)) || ((nk+2 == nj) && (nj+2 == ni)) ) { scoring_add(pl->game,"Pure Shifted Chows",16); return; } } } } } static void score_all_fives(SCORE_TRY_ARGS) { int i; if (!(tileflags(head)&TF_SUIT) || (tile_num(head) != 5)) return; for (i=4-1;i>=0;i--) { switch (elts[i].type) { case MT_CHOW: switch (tile_num(elts[i].base)) { case 3: case 4: case 5: break; default: return; break; } break; case MT_PUNG: case MT_KONG: case MT_CKONG: if ( !(tileflags(elts[i].base) & TF_SUIT) || (tile_num(elts[i].base) != 5) ) return; break; default: break; } } scoring_add(pl->game,"All Fives",16); } /* XXX is it possible to score two Triple Pungs in a hand? eg, 234b 234c 234c 234d? Assume not. */ /* XXX is it right to completely disable Double Pung here? */ static void score_triple_pung(SCORE_TRY_ARGS) { int i; int j; int k; for (i=4-1;i>=0;i--) { if (! meld_is_xxng(&elts[i])) continue; for (j=4-1;j>=0;j--) { if (j == i) continue; if (! meld_is_xxng(&elts[j])) continue; if (same_suit(elts[i].base,elts[j].base)) continue; if (tile_num(elts[i].base) != tile_num(elts[j].base)) continue; for (k=4-1;k>=0;k--) { if ((k == i) || (k == j)) continue; if (! meld_is_xxng(&elts[k])) continue; if ( same_suit(elts[i].base,elts[k].base) || same_suit(elts[j].base,elts[k].base) ) continue; if (tile_num(elts[i].base) != tile_num(elts[k].base)) continue; scoring_add(pl->game,"Triple Pung",16); disable_score(&score_double_pung); return; } } } } static void score_three_concealed_pungs(SCORE_TRY_ARGS) { int i; int n; n = 0; for (i=4-1;i>0;i--) if (elts[i].type == MT_CKONG) n ++; for (i=4-1;i>0;i--) { if ( (elts[i].type == MT_PUNG) && (elts[i].tiles <= ((1U<nhand)-1)) ) n ++; } if (n >= 3) { scoring_add(pl->game,"Three Concealed Pungs",16); disable_score(&score_two_concealed_pungs); } } static void score_all_even(SCORE_TRY_ARGS) { if (all_tiles_are(pl,&even_tile)) { scoring_add(pl->game,"All Even",24); disable_score(&score_all_pungs); disable_score(&score_all_simples); } } static void score_full_flush(SCORE_TRY_ARGS) /* SEVENPAIR */ { if ( all_tiles_are(pl,&bamboo_tile) || all_tiles_are(pl,&character_tile) || all_tiles_are(pl,&dot_tile) ) { scoring_add(pl->game,"Full Flush",24); disable_score(&score_half_flush); disable_score(&score_voided_suit); } } static void score_pure_triple_chow(SCORE_TRY_ARGS) { int i; int j; int k; for (i=4-1;i>=0;i--) { if (! meld_is_chow(&elts[i])) continue; for (j=4-1;j>=0;j--) { if (j == i) continue; if (! meld_is_chow(&elts[j])) continue; if (elts[j].base != elts[i].base) continue; for (k=4-1;k>=0;k--) { if ((k == i) || (k == j)) continue; if (! meld_is_chow(&elts[k])) continue; if (elts[k].base != elts[i].base) continue; scoring_add(pl->game,"Pure Triple Chow",16); disable_score(&score_pure_double_chow); return; } } } } static void score_pure_shifted_pungs(SCORE_TRY_ARGS) { int i; int j; int k; int ni; int nj; int nk; for (i=4-1;i>=0;i--) { if (! meld_is_xxng(&elts[i])) continue; if (! (tileflags(elts[i].base) & TF_SUIT)) continue; ni = tile_num(elts[i].base); for (j=4-1;j>=0;j--) { if (j == i) continue; if (! meld_is_xxng(&elts[j])) continue; if (! same_suit(elts[i].base,elts[j].base)) continue; nj = tile_num(elts[j].base); for (k=4-1;k>=0;k--) { if ((k == i) || (k == j)) continue; if (! meld_is_xxng(&elts[k])) continue; if (! same_suit(elts[i].base,elts[k].base)) continue; nk = tile_num(elts[k].base); if ( ((ni+1 == nj) && (nj+1 == nk)) || ((nj+1 == nk) && (nk+1 == ni)) || ((nk+1 == ni) && (ni+1 == nj)) ) { scoring_add(pl->game,"Pure Shifted Pungs",24); return; } } } } } static void score_upper_tiles(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (all_tiles_are(pl,&upper_tile)) { scoring_add(pl->game,"Upper Tiles",32); disable_score(&score_upper_four); } } static void score_middle_tiles(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (all_tiles_are(pl,&middle_tile)) { scoring_add(pl->game,"Middle Tiles",32); } } static void score_lower_tiles(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (all_tiles_are(pl,&lower_tile)) { scoring_add(pl->game,"Lower Tiles",32); disable_score(&score_lower_four); } } static void score_four_pure_shifted_chows(SCORE_TRY_ARGS) { int i; int n[4]; for (i=4-1;i>=0;i--) { if (! meld_is_chow(&elts[i])) return; n[i] = tile_num(elts[i].base); } if ( !same_suit(elts[0].base,elts[1].base) || !same_suit(elts[0].base,elts[2].base) || !same_suit(elts[0].base,elts[3].base) ) return; sort_ints(&n[0],4); if ( ( (n[1] == n[0]+1) && (n[2] == n[1]+1) && (n[3] == n[2]+1) ) || ( (n[1] == n[0]+2) && (n[2] == n[1]+2) && (n[3] == n[2]+2) ) ) { scoring_add(pl->game,"Four Pure Shifted Chows",32); disable_score(&score_pure_shifted_chows); } } static void score_all_terminals_and_honours(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (all_tiles_are(pl,&terminal_or_honour_tile)) { scoring_add(pl->game,"All Terminals And Honours",32); } } static void score_three_kongs(SCORE_TRY_ARGS) { int i; int n; n = 0; for (i=4-1;i>0;i--) if (meld_is_kong(&elts[i])) n ++; if (n >= 3) scoring_add(pl->game,"Three Kongs",32); } static void score_quadruple_chow(SCORE_TRY_ARGS) { int i; for (i=4-1;i>=0;i--) if (! meld_is_chow(&elts[i])) return; if (elts[1].base != elts[0].base) return; if (elts[2].base != elts[0].base) return; if (elts[3].base != elts[0].base) return; scoring_add(pl->game,"Quadruple Chow",48); disable_score(&score_pure_triple_chow); disable_score(&score_pure_double_chow); } static void score_four_pure_shifted_pungs(SCORE_TRY_ARGS) { int i; int n[4]; for (i=4-1;i>0;i--) { if (! meld_is_xxng(&elts[i])) return; if (! (tileflags(elts[i].base) & TF_SUIT)) return; n[i] = tile_num(elts[i].base); } if ( !same_suit(elts[0].base,elts[1].base) || !same_suit(elts[0].base,elts[2].base) || !same_suit(elts[0].base,elts[3].base) ) return; sort_ints(&n[0],4); if ( (n[0]+1 == n[1]) && (n[1]+1 == n[2]) && (n[2]+1 == n[3]) ) { scoring_add(pl->game,"Four Pure Shifted Pungs",48); disable_score(&score_pure_shifted_pungs); } } static void score_all_terminals(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (all_tiles_are(pl,&terminal_tile)) { scoring_add(pl->game,"All Terminals",64); disable_score(&score_double_pung); disable_score(&score_no_honours); disable_score(&score_all_terminals_and_honours); } } static void score_little_four_winds(SCORE_TRY_ARGS) { int i; int n; if (! (tileflags(head) & TF_WIND)) return; n = 0; for (i=4-1;i>=0;i--) { if ( meld_is_xxng(&elts[i]) && (tileflags(elts[i].base) & TF_WIND) ) n ++; } if (n >= 3) { scoring_add(pl->game,"Little Four Winds",64); disable_score(&score_big_three_winds); } } static void score_little_three_dragons(SCORE_TRY_ARGS) { /* XXX how does this interact with haku as a fourth dragon? 64 Little Three Dragons Two dragon pungs and a head of the remaining dragon Does not score for individual dragon pungs */ } static void score_all_honours(SCORE_TRY_ARGS) /* SEVENPAIR */ { if (all_tiles_are(pl,&honour_tile)) { scoring_add(pl->game,"All Honours",64); disable_score(&score_all_pungs); } } /* XXX Why does this disable Fully Concealed Hand? */ static void score_four_concealed_pungs(SCORE_TRY_ARGS) { int i; int n; n = 0; for (i=4-1;i>0;i--) if (elts[i].type == MT_CKONG) n ++; for (i=4-1;i>0;i--) { if ( (elts[i].type == MT_PUNG) && (elts[i].tiles <= ((1U<nhand)-1)) ) n ++; } if (n >= 4) { scoring_add(pl->game,"Four Concealed Pungs",64); disable_score(&score_two_concealed_pungs); disable_score(&score_three_concealed_pungs); disable_score(&score_all_pungs); disable_score(&score_fully_concealed_hand); } } /* XXX is the list of disabled scores right? */ static void score_pure_terminal_chows(SCORE_TRY_ARGS) { int i; int j; if (!(tileflags(head)&TF_SUIT) || (tile_num(head) != 5)) return; j = 0; for (i=4-1;i>=0;i--) { if (! meld_is_chow(&elts[i])) return; if (! same_suit(elts[i].base,head)) return; switch (tile_num(elts[i].base)) { case 1: j += 1; break; case 7: j += 8; break; } } if (j != 1+1+8+8) return; scoring_add(pl->game,"Pure Terminal Chows",64); disable_score(&score_full_flush); disable_score(&score_half_flush); disable_score(&score_voided_suit); disable_score(&score_two_terminal_chows); disable_score(&score_all_chows); } static void score_big_four_winds(SCORE_TRY_ARGS) { int i; int n; n = 0; for (i=4-1;i>=0;i--) { if ( meld_is_xxng(&elts[i]) && (tileflags(elts[i].base) & TF_WIND) ) n ++; } if (n == 4) { scoring_add(pl->game,"Big Four Winds",88); disable_score(&score_little_four_winds); disable_score(&score_big_three_winds); disable_score(&score_all_pungs); } } static void score_big_three_dragons(SCORE_TRY_ARGS) { /* XXX how does this interact with haku as a fourth dragon? 88 Pungs/kongs of all three dragons Does not score Dragon Pung */ } static void score_all_green(SCORE_TRY_ARGS) { if (all_tiles_are(pl,&green_tile)) { scoring_add(pl->game,"All Green",88); } } static void score_four_kongs(SCORE_TRY_ARGS) { if ( meld_is_kong(&elts[0]) && meld_is_kong(&elts[1]) && meld_is_kong(&elts[2]) && meld_is_kong(&elts[3]) ) { scoring_add(pl->game,"Four Kongs",88); disable_score(&score_all_pungs); disable_score(&score_three_kongs); } } static void score_heaven_and_earth(SCORE_TRY_ARGS) { if (pl->game->ndraws == 0) { scoring_add(pl->game,"Heaven and Earth",88); } } static SCORE scores[] = { { 1, &score_pure_double_chow }, { 1, &score_mixed_double_chow }, { 1, &score_short_straight }, { 1, &score_two_terminal_chows }, { 1, &score_pung_of_terminals_or_honours }, { 1, &score_melded_kong }, { 1, &score_voided_suit, SF_SEVENPAIR }, { 1, &score_no_honours, SF_SEVENPAIR }, { 1, &score_edge_wait }, { 1, &score_closed_wait }, { 1, &score_single_wait }, { 1, &score_self_draw, SF_SEVENPAIR }, /* Flower Tiles */ { 2, &score_dragon_pung }, { 2, &score_prevalent_wind }, { 2, &score_seat_wind }, { 2, &score_concealed_hand }, { 2, &score_all_chows }, { 2, &score_tile_hog, SF_SEVENPAIR }, { 2, &score_double_pung }, { 2, &score_two_concealed_pungs }, { 2, &score_concealed_kong }, { 2, &score_all_simples, SF_SEVENPAIR }, { 4, &score_outside_hand }, { 4, &score_fully_concealed_hand, SF_SEVENPAIR }, { 4, &score_two_melded_kongs }, { 4, &score_last_tile }, { 6, &score_all_pungs }, { 6, &score_half_flush, SF_SEVENPAIR }, { 6, &score_mixed_shifted_chows }, { 6, &score_all_types, SF_SEVENPAIR }, { 6, &score_melded_hand }, { 6, &score_two_dragon_pungs }, { 6, &score_one_melded_and_one_concealed_kong }, { 8, &score_mixed_triple_chow }, { 8, &score_mixed_straight }, { 8, &score_mixed_shifted_pungs }, { 8, &score_two_concealed_kongs }, { 8, &score_last_tile_draw, SF_SEVENPAIR }, { 8, &score_last_tile_claim, SF_SEVENPAIR }, { 8, &score_out_with_replacement_tile, SF_SEVENPAIR }, { 8, &score_robbing_the_kong }, { 8, &score_reversible_tiles, SF_SEVENPAIR }, /* Chicken Hand */ { 12, &score_upper_four, SF_SEVENPAIR }, { 12, &score_lower_four, SF_SEVENPAIR }, { 12, &score_big_three_winds }, { 16, &score_pure_straight }, { 16, &score_three_suited_terminal_chows }, { 16, &score_pure_shifted_chows }, { 16, &score_all_fives }, { 16, &score_triple_pung }, { 16, &score_three_concealed_pungs }, /* Seven Pairs */ { 24, &score_all_even }, { 24, &score_full_flush, SF_SEVENPAIR }, { 24, &score_pure_triple_chow }, { 24, &score_pure_shifted_pungs }, { 24, &score_upper_tiles, SF_SEVENPAIR }, { 24, &score_middle_tiles, SF_SEVENPAIR }, { 24, &score_lower_tiles, SF_SEVENPAIR }, { 32, &score_four_pure_shifted_chows }, { 32, &score_all_terminals_and_honours, SF_SEVENPAIR }, { 32, &score_three_kongs }, { 48, &score_quadruple_chow }, { 48, &score_four_pure_shifted_pungs }, { 64, &score_all_terminals, SF_SEVENPAIR }, { 64, &score_little_four_winds }, { 64, &score_little_three_dragons }, { 64, &score_all_honours, SF_SEVENPAIR }, { 64, &score_four_concealed_pungs }, { 64, &score_pure_terminal_chows }, { 88, &score_big_four_winds }, { 88, &score_big_three_dragons }, { 88, &score_all_green, SF_SEVENPAIR }, /* Nine Gates */ { 88, &score_four_kongs }, /* Seven Shifted Pairs */ /* Thirteen Orphans */ { 88, &score_heaven_and_earth, SF_SEVENPAIR } }; static const int nscores = sizeof(scores) / sizeof(scores[0]); #if MAXMELDS != 4 #error scoring code needs work #endif static void scoring_alternative(PLAYER *pl, unsigned int *triples, unsigned int head, int claimed) { MELD elts[4]; TILE ht; int eltx; int i; TILE t[3]; MELD m; void select_tiles(unsigned int m, int n) { int tx; unsigned int jm; int jx; tx = 0; for (jx=14-1,jm=0x2000;jm;jm>>=1,jx--) { if (m & jm) { if (tx >= 3) abort(); if ((pl->hand[jx] < 0) || (pl->hand[jx] >= TILE__N)) abort(); t[tx++] = pl->hand[jx]; } } if (tx != n) abort(); } eltx = 0; for (i=MAXMELDS-1;i>=0;i--) { if (pl->melds[i].type == MT_NONE) continue; if (eltx >= 4) abort(); if (pl->melds[i].tiles & ((1U<nhand)-1)) abort(); elts[eltx++] = pl->melds[i]; } if (pl->nhand/3 != 4-eltx) abort(); for (i=0;eltx<4;eltx++,i++) { select_tiles(triples[i],3); m.tiles = triples[i]; if ((t[0] == t[1]) && (t[1] == t[2])) { m.type = MT_PUNG; m.base = t[0]; } else if ( ( (t[1] == tile_nextup(t[0])) && (t[2] == tile_nextup(t[1])) ) || ( (t[2] == tile_nextup(t[0])) && (t[1] == tile_nextup(t[2])) ) ) { m.type = MT_CHOW; m.base = t[0]; } else if ( ( (t[2] == tile_nextup(t[1])) && (t[0] == tile_nextup(t[2])) ) || ( (t[0] == tile_nextup(t[1])) && (t[2] == tile_nextup(t[0])) ) ) { m.type = MT_CHOW; m.base = t[1]; } else if ( ( (t[0] == tile_nextup(t[2])) && (t[1] == tile_nextup(t[0])) ) || ( (t[1] == tile_nextup(t[2])) && (t[0] == tile_nextup(t[1])) ) ) { m.type = MT_CHOW; m.base = t[2]; } else { abort(); } m.claimedfrom = -1; elts[eltx++] = m; } if (eltx != 4) abort(); select_tiles(head,2); if (t[0] != t[1]) abort(); ht = t[0]; for (i=nscores-1;i>=0;i--) scores[i].flags |= SF_ENABLED; for (i=nscores-1;i>=0;i--) { if (scores[i].flags & SF_ENABLED) (*scores[i].try)(pl,&elts[0],ht,claimed); } if (! pl->game->cur) { scoring_add(pl->game,"Chicken Hand",12); /* XXX 8? */ } scoring_close(pl->game); } static void won_hand(GAME *g, int winner, int claimed) { PCONN *pc; PLAYER *pl; TILE h[MAXHAND]; int i; int x; void got_elts(TILE *h, unsigned int *triples, unsigned int head) { if (h != &pl->hand[0]) abort(); scoring_alternative(pl,triples,head,claimed); } pc = g->conns[winner]; pl = pc->player; scoring_init(g); x = pl->nhand; for (i=MAXMELDS-1;i>=0;i--) { switch (pl->melds[i].type) { default: abort(); break; case MT_NONE: break; case MT_CHOW: pl->melds[i].tiles = 7U << x; g->scorehand[x++] = pl->melds[i].base; g->scorehand[x++] = tile_nextup(pl->melds[i].base); g->scorehand[x++] = tile_nextup(tile_nextup(pl->melds[i].base)); break; case MT_KONG: case MT_CKONG: pl->melds[i].tiles = 15U << x; g->scorehand[x++] = pl->melds[i].base; if (0) { case MT_PUNG: pl->melds[i].tiles = 7U << x; } g->scorehand[x++] = pl->melds[i].base; g->scorehand[x++] = pl->melds[i].base; g->scorehand[x++] = pl->melds[i].base; break; } } switch (pl->nhand) { default: abort(); break; case 14: bcopy(&pl->hand[0],&h[0],13*sizeof(TILE)); sort_tiles(&h[0],13); if (nine_gates(&h[0],pl->hand[13])) { scoring_add(g,"Nine Gates",88); scoring_close(g); break; } if (thirteen_orphans(&h[0],pl->hand[13])) { scoring_add(g,"Thirteen Orphans",88); scoring_close(g); break; } h[13] = pl->hand[13]; sort_tiles(&h[0],14); if (seven_pairs(&h[0])) { if (seven_shifted_pairs(&h[0])) { scoring_add(g,"Seven Shifted Pairs",88); scoring_close(g); break; } else { int i; scoring_add(g,"Seven Pairs",24); scoring_close(g); for (i=nscores-1;i>=0;i--) scores[i].flags |= SF_ENABLED; for (i=nscores-1;i>=0;i--) { if ( (scores[i].flags & (SF_ENABLED|SF_SEVENPAIR)) == (SF_ENABLED|SF_SEVENPAIR) ) { (*scores[i].try)(pl,0,TILE_NOTILE,claimed); } } break; } } enum_elts(&pl->hand[0],0x3fff,4,&got_elts); break; case 11: case 8: case 5: case 2: enum_elts(&pl->hand[0],~((~3U)<<(3*(pl->nhand/3))),pl->nhand/3,&got_elts); break; } scoring_more(pl->game); for (i=pl->nfs-1;i>=0;i--) scoring_add(pl->game,"Flower/Season",1); scoring_close(pl->game); for (i=NWIND-1;i>=0;i--) { msg_write( &g->players[i].conn->c->oq, S_LITERAL, "win", S_ONECHAR, pxid(pl->conn->inx), S_END ); } { SCORING *sc; SCELT *e; for (sc=g->best;sc;sc=sc->link) { printf("\n"); for (e=sc->elts;e;e=e->link) { printf("%3d %s\n",e->pts,e->what); } printf("%3d (total)\n",sc->totpts); } } exit(0); /* XXX */ } static void drawn_hand(GAME *g) { int i; for (i=NWIND-1;i>=0;i--) { msg_write( &g->players[i].conn->c->oq, S_LITERAL, "draw", S_END ); } exit(0); /* XXX */ } static void delete_tile_from_hand(PLAYER *p, TILE t) { int i; if (p->nhand < 1) abort(); p->nhand --; for (i=p->nhand;i>=0;i--) { if (p->hand[i] == t) { if (i != p->nhand) p->hand[i] = p->hand[p->nhand]; return; } } abort(); } static int check_claimed(void *gv) { GAME *g; int i; int j; int k; int win; PLAYER *pl; MELDTYPE mt[NWIND]; TILE base[NWIND]; g = gv; for (i=NWIND-1;i>=0;i--) { switch (g->players[i].state) { case PS_WAIT: if (&g->players[i] != g->cur_discarder) abort(); break; default: if (&g->players[i] == g->cur_discarder) abort(); break; } } win = 0; for (i=NWIND-1;i>=0;i--) { pl = g->conns[i]->player; if (pl->state == PS_OUT) { if (can_go_out(pl,g->cur_discard)) { win = 1; } else { reject_it(pl,"Hand isn't out"); pl->state = PS_CLAIM; return(BLOCK_LOOP); } } } for (i=NWIND-1;i>=0;i--) { unsigned int mask; mt[i] = MT_NONE; pl = g->conns[i]->player; if (pl->state != PS_CLAIMED) continue; if ( (pl->claim_hand[0] == TILE_NOTILE) && (pl->claim_hand[1] == TILE_NOTILE) && (pl->claim_hand[2] == TILE_NOTILE) ) continue; j = tile_in_hand(pl->claim_hand[0],pl,0); if (j < 0) { reject_it(pl,"Claim tile 0 %s not in hand",tile_name(pl->claim_hand[0])); pl->state = PS_CLAIM; return(BLOCK_LOOP); } mask = 1U << j; j = tile_in_hand(pl->claim_hand[1],pl,mask); if (j < 0) { reject_it(pl,"Claim tile 1 %s not in hand",tile_name(pl->claim_hand[1])); pl->state = PS_CLAIM; return(BLOCK_LOOP); } mask |= 1U << j; if (pl->claim_hand[2] != TILE_NOTILE) { j = tile_in_hand(pl->claim_hand[2],pl,mask); if (j < 0) { reject_it(pl,"Claim tile 2 %s not in hand",tile_name(pl->claim_hand[2])); pl->state = PS_CLAIM; return(BLOCK_LOOP); } } if ( (pl->claim_hand[0] == g->cur_discard) && (pl->claim_hand[1] == g->cur_discard) ) { if (pl->claim_hand[2] == g->cur_discard) { mt[i] = MT_KONG; base[i] = g->cur_discard; } else if (pl->claim_hand[2] == TILE_NOTILE) { mt[i] = MT_PUNG; base[i] = g->cur_discard; } } else if ( (tileflags(g->cur_discard) & TF_SUIT) && (pl->claim_hand[2] == TILE_NOTILE) && (pl->claim_hand[1] != TILE_NOTILE) && (pl->claim_hand[0] != TILE_NOTILE) ) { if ( ( (pl->claim_hand[0] == tile_nextup(g->cur_discard)) && (pl->claim_hand[1] == tile_nextup(pl->claim_hand[0])) ) || ( (pl->claim_hand[1] == tile_nextup(g->cur_discard)) && (pl->claim_hand[0] == tile_nextup(pl->claim_hand[1])) ) ) { mt[i] = MT_CHOW; base[i] = g->cur_discard; } else if ( ( (pl->claim_hand[1] == tile_nextdn(g->cur_discard)) && (pl->claim_hand[0] == tile_nextdn(pl->claim_hand[1])) ) || ( (pl->claim_hand[0] == tile_nextdn(g->cur_discard)) && (pl->claim_hand[1] == tile_nextup(g->cur_discard)) ) ) { mt[i] = MT_CHOW; base[i] = pl->claim_hand[0]; } else if ( ( (pl->claim_hand[0] == tile_nextdn(g->cur_discard)) && (pl->claim_hand[1] == tile_nextdn(pl->claim_hand[0])) ) || ( (pl->claim_hand[1] == tile_nextdn(g->cur_discard)) && (pl->claim_hand[0] == tile_nextup(g->cur_discard)) ) ) { mt[i] = MT_CHOW; base[i] = pl->claim_hand[1]; } } if (mt[i] == MT_NONE) { reject_it(pl,"Claim tiles don't form a valid meld"); pl->state = PS_CLAIM; return(BLOCK_LOOP); } } j = -1; for (i=NWIND-1;i>=0;i--) { pl = g->conns[i]->player; if (pl->state != PS_CLAIMED) continue; switch (mt[i]) { default: abort(); break; case MT_NONE: continue; break; case MT_CHOW: if (pl->wind != WIND_NEXT(g->cur_discarder->wind)) { reject_it(pl,"Chow claim by non-next player"); pl->state = PS_CLAIM; return(BLOCK_LOOP); } if (j < 0) j = i; break; case MT_PUNG: case MT_KONG: if ((j < 0) || (mt[j] == MT_CHOW)) { j = i; } else { abort(); } break; } } if (win) { win = -1; k = g->cur_discarder->wind; for (i=NWIND-2;i>=0;i--) { k = WIND_NEXT(k); if (g->players[k].state == PS_OUT) { win = g->players[k].conn->inx; break; } } } else { win = -1; k = 0; for (i=NWIND-1;i>=0;i--) { pl = g->conns[i]->player; switch (pl->state) { default: abort(); break; case PS_WAIT: break; case PS_CLAIM: return(BLOCK_NIL); break; case PS_CLAIMED: k ++; break; } } if (k != NWIND-1) abort(); } remove_block_id(g->claimid); g->claimid = PL_NOID; if (win >= 0) { pl = g->conns[win]->player; if (pl->nhand >= MAXHAND) abort(); pl->hand[pl->nhand++] = g->cur_discard; won_hand(g,win,1); return(BLOCK_LOOP); } else if (j < 0) { g->turn = WIND_NEXT(g->turn); pl = &g->players[g->turn]; if (g->wallleft < 1) { drawn_hand(g); return(BLOCK_LOOP); } draw_to_hand(g,pl->wind); g->ndraws ++; replace_replaceables(g,pl->wind); } else { pl = g->conns[j]->player; for (k=0;kmelds[k].type == MT_NONE) break; if (k >= MAXMELDS) fail("claim can't find a meld slot"); pl->melds[k].type = mt[j]; pl->melds[k].claimedfrom = g->cur_discarder->conn->inx; pl->melds[k].base = base[j]; switch (mt[j]) { case MT_CHOW: if (g->cur_discard == base[j]) { pl->melds[k].claimx = 0; } else if (g->cur_discard == tile_nextup(base[j])) { pl->melds[k].claimx = 1; } else if (g->cur_discard == tile_nextup(tile_nextup(base[j]))) { pl->melds[k].claimx = 2; } else { abort(); } break; case MT_PUNG: case MT_KONG: case MT_CKONG: pl->melds[k].claimx = 0; break; default: abort(); break; } report_open_move(g,makeplace(PT_DISC,g->cur_discarder->conn->inx),g->cur_discard,makeplace(PT_MELD,j,k)); report_open_move(g,makeplace(PT_HAND,j),pl->claim_hand[0],makeplace(PT_MELD,j,k)); delete_tile_from_hand(pl,pl->claim_hand[0]); report_open_move(g,makeplace(PT_HAND,j),pl->claim_hand[1],makeplace(PT_MELD,j,k)); delete_tile_from_hand(pl,pl->claim_hand[1]); if (pl->claim_hand[2] != TILE_NOTILE) { report_open_move(g,makeplace(PT_HAND,j),pl->claim_hand[2],makeplace(PT_MELD,j,k)); kong_replacement(pl); } g->turn = pl->wind; } for (i=NWIND-1;i>=0;i--) g->players[i].state = PS_WAIT; pl->state = PS_PLAY; msg_write(&pl->conn->c->oq,S_LITERAL,"play",S_END); return(BLOCK_LOOP); } static void do_discard(PCONN *pc, TILE t, int riichi) { int i; PLAYER *pl; GAME *g; pl = pc->player; g = pl->game; if (pl->state != PS_PLAY) { reject_it(pl,"Discard by non-PS_PLAY player"); return; } if (riichi && pl->riichi) { reject_it(pl,"Riichi discard when riichi in effect"); return; } do <"found"> { for (i=pl->nhand-1;i>=0;i--) if (t == pl->hand[i]) break <"found">; reject_it(pl,"Discarded tile not in hand"); return; } while (0); pl->nhand --; if (i != pl->nhand) { if (pl->riichi) { reject_it(pl,"Discarding wrong tile while under riichi"); return; } pl->hand[i] = pl->hand[pl->nhand]; } g->cur_discard = t; g->cur_discarder = pl; if (riichi) for (i=NWIND-1;i>=0;i--) msg_write(&g->conns[i]->c->oq,S_LITERAL,"riichi",S_END); report_open_move(g,makeplace(PT_HAND,pc->inx),t,makeplace(PT_DISC,pc->inx)); pl->state = PS_WAIT; for (i=NWIND-1;i>=0;i--) { if (g->conns[i] != pc) { msg_write(&g->conns[i]->c->oq,S_LITERAL,"claim",S_END); g->conns[i]->player->state = PS_CLAIM; } } g->claimid = add_block_fn(&check_claimed,g); if (riichi) pl->riichi = 1; } static void pcpost_discard(PCONN *pc, TILE t) { do_discard(pc,t,0); } static void pcpost_riichi(PCONN *pc, TILE t) { do_discard(pc,t,1); } static void pcpost_ckong(PCONN *pc, TILE t) { int i; int j; PLAYER *pl; GAME *g; int n; int m; pl = pc->player; g = pl->game; if (pl->state != PS_PLAY) { reject_it(pl,"Melding concealed kong without the move"); return; } n = 0; for (i=0;inhand;i++) if (pl->hand[i] == t) n ++; if (n < 4) { reject_it(pl,"Melding concealed kong with too few tiles"); return; } if (n > 4) fail("concealed kong matching tile count = %d",n); j = 0; for (i=0;inhand;i++) { if (pl->hand[i] != t) { if (j != i) pl->hand[j] = pl->hand[i]; j ++; } } pl->nhand -= 4; for (m=0;mmelds[m].type == MT_NONE) break; if (m >= MAXMELDS) fail("concealed kong can't find a meld slot"); pl->melds[m].type = MT_CKONG; pl->melds[m].claimedfrom = -1; pl->melds[m].claimx = -1; pl->melds[m].base = t; for (i=4;i>0;i--) msg_write(&pc->c->oq, S_LITERAL, "move", S_TILE, t, S_PLACE, makeplace(PT_HAND,pc->inx), S_PLACE, makeplace(PT_MELD,pc->inx,m), S_END ); msg_write(&pc->c->oq, S_LITERAL, "move", S_TILE, TILE_UNKTILE, S_PLACE, makeplace(PT_MELD,pc->inx,m), S_PLACE, makeplace(PT_MELD,pc->inx,m), S_END ); for (i=NWIND-1;i>=0;i--) { if (g->conns[i] != pc) { for (j=4;j>0;j--) msg_write(&pc->c->oq, S_LITERAL, "move", S_TILE, ((j==4)||(j==1))?t:TILE_UNKTILE, S_PLACE, makeplace(PT_HAND,pc->inx), S_PLACE, makeplace(PT_MELD,pc->inx,m), S_END ); } } kong_replacement(pl); } static void pcpost_pkong(PCONN *pc, TILE t) { PLAYER *pl; GAME *g; int i; int n; int m; pl = pc->player; g = pl->game; if (pl->state != PS_PLAY) { reject_it(pl,"Upgrading pung to kong without the move"); return; } do <"found"> { for (m=MAXMELDS-1;m>=0;m--) { if ((pl->melds[m].type == MT_PUNG) && (pl->melds[m].base == t)) { break <"found">; } } reject_it(pl,"Upgrading nonexistent pung"); return; } while (0); n = 0; do <"found"> { for (i=pl->nhand-1;i>=0;i--) if (t == pl->hand[i]) break <"found">; reject_it(pl,"Pung upgrade tile not in hand"); return; } while (0); pl->nhand --; if (i != pl->nhand) pl->hand[i] = pl->hand[pl->nhand]; pl->melds[m].type = MT_KONG; report_open_move(g,makeplace(PT_HAND,pc->inx),t,makeplace(PT_MELD,pc->inx,m)); kong_replacement(pl); } typedef struct tilec_priv TILEC_PRIV; struct tilec_priv { ROPS *prevops; void *prevarg; PCONN *pc; void (*postfn)(PCONN *, TILE); int noneok; } ; static void free_tilec(TILEC_PRIV *p) { free(p); } static void unstack_tilec(TILEC_PRIV *p) { p->pc->c->ops = p->prevops; p->pc->c->arg = p->prevarg; } static int rd_tilec(void *pv, unsigned char *buf, int len) { TILEC_PRIV *p; int n; static int l[TILE__N] = { -1 }; int i; int any; p = pv; n = 0; while ((len > 0) && whitespace(buf[0])) { n ++; len --; buf ++; } if (len < 1) return(n); if (p->noneok && (buf[0] == '?')) { unstack_tilec(p); (*p->postfn)(p->pc,TILE_UNKTILE); free_tilec(p); return(n+1); } if (l[0] < 0) for (i=TILE__N-1;i>=0;i--) l[i] = strlen(tilenames[i]); for (i=TILE__N-1;i>=0;i--) { if (len < l[i]) { if (! bcmp(tilenames[i],buf,len)) any = 1; } else { if (! bcmp(tilenames[i],buf,l[i])) { unstack_tilec(p); (*p->postfn)(p->pc,i); free_tilec(p); return(n+l[i]); } } } if (! any) fail("bad tile name"); return(n); } static void kill_tilec(void *pv) { TILEC_PRIV *p; p = pv; unstack_tilec(p); free_tilec(p); (*p->prevops->kill)(p->prevarg); } static ROPS ops_tilec = OPS(tilec); static void pcmd_tilec(PCONN *pc, void (*pfn)(PCONN *, TILE), int noneok) { TILEC_PRIV *priv; priv = malloc(sizeof(TILEC_PRIV)); priv->prevops = pc->c->ops; priv->prevarg = pc->c->arg; priv->pc = pc; priv->postfn = pfn; priv->noneok = noneok; pc->c->ops = &ops_tilec; pc->c->arg = priv; } static int pcmd_discard(PCONN *pc) { pcmd_tilec(pc,&pcpost_discard,0); return(7); } static int pcmd_riichi(PCONN *pc) { pcmd_tilec(pc,&pcpost_riichi,0); return(6); } static int pcmd_ckong(PCONN *pc) { pcmd_tilec(pc,&pcpost_ckong,0); return(5); } static int pcmd_pkong(PCONN *pc) { pcmd_tilec(pc,&pcpost_pkong,0); return(5); } static void pcpost_claim2(PCONN *pc, TILE t) { pc->player->claim_hand[2] = ((t == TILE_UNKTILE) ? TILE_NOTILE : t); pc->player->state = PS_CLAIMED; } static void pcpost_claim1(PCONN *pc, TILE t) { pc->player->claim_hand[1] = ((t == TILE_UNKTILE) ? TILE_NOTILE : t); pcmd_tilec(pc,&pcpost_claim2,1); } static void pcpost_claim0(PCONN *pc, TILE t) { pc->player->claim_hand[0] = ((t == TILE_UNKTILE) ? TILE_NOTILE : t); pcmd_tilec(pc,&pcpost_claim1,0); } static int pcmd_claim(PCONN *pc) { pcmd_tilec(pc,&pcpost_claim0,0); return(5); } static int pcmd_pass(PCONN *pc) { pc->player->state = PS_CLAIMED; pc->player->claim_hand[0] = TILE_NOTILE; pc->player->claim_hand[1] = TILE_NOTILE; pc->player->claim_hand[2] = TILE_NOTILE; return(4); } static int pcmd_out(PCONN *pc) { if (! can_go_out(pc->player,TILE_NOTILE)) { reject_it(pc->player,"Hand isn't out"); } else { won_hand(pc->player->game,pc->inx,0); } return(3); } static int pcmd_ron(PCONN *pc) { pc->player->state = PS_OUT; return(3); } static void pcpost_swap(PCONN *pc, TILE t) { t=t; reject_it(pc->player,"Swap is unimplemented"); } static int pcmd_swap(PCONN *pc) { pcmd_tilec(pc,&pcpost_swap,0); return(4); } static int rd_pconn(void *pv, unsigned char *buf, int len) { static struct { const char *cmd; int (*impl)(PCONN *); int len; } cmds[] = { { "chat", &pcmd_chat }, { "discard", &pcmd_discard }, { "riichi", &pcmd_riichi }, { "out", &pcmd_out }, { "ckong", &pcmd_ckong }, { "pkong", &pcmd_pkong }, { "claim", &pcmd_claim }, { "ron", &pcmd_ron }, { "pass", &pcmd_pass }, { "swap", &pcmd_swap } }; PCONN *pc; PLAYER *pl; int i; int any; pc = pv; pl = pc->player; if (cmds[0].len == 0) { for (i=(sizeof(cmds)/sizeof(cmds[0]))-1;i>=0;i--) { cmds[i].len = strlen(cmds[i].cmd); } } any = 0; for (i=(sizeof(cmds)/sizeof(cmds[0]))-1;i>=0;i--) { if (len < cmds[i].len) { if (! bcmp(cmds[i].cmd,buf,len)) any = 1; } else { if (! bcmp(cmds[i].cmd,buf,cmds[i].len)) { return((*cmds[i].impl)(pc)); } } } if (! any) fail("unrecognized command"); return(0); } static void kill_pconn(void *pv) { PCONN *p; p = pv; fail("killing pconn"); } static int rd_ipconn(void *pv __attribute__((__unused__)), unsigned char *buf __attribute__((__unused__)), int len __attribute__((__unused__))) { return(0); } static void kill_ipconn(void *pv) { kill_pconn(pv); } static ROPS ops_pconn = OPS(pconn); static ROPS ops_ipconn = OPS(ipconn); static int handck(void *gv) { GAME *g; int px; PLAYER *p; int n; int i; g = gv; for (px=NWIND-1;px>=0;px--) { p = &g->players[px]; n = p->nhand; for (i=MAXMELDS-1;i>=0;i--) if (p->melds[i].type != MT_NONE) n += 3; switch (p->state) { case PS_WAIT: case PS_CLAIM: case PS_CLAIMED: case PS_OUT: if (n != 13) abort(); break; case PS_PLAY: if (n != 14) abort(); break; default: abort(); break; } } return(BLOCK_NIL); } static void start_game(GAME *g) { int cx[NWIND]; int i; int j; for (i=NWIND-1;i>=0;i--) { g->conns[i]->c->ops = &ops_pconn; g->conns[i]->inx = i; } for (i=NWIND-1;i>=0;i--) { msg_write( &g->conns[i]->c->oq, S_LITERAL, "game", S_ONECHAR, pxid(i), S_CS_POINT, g->conns[0]->name, S_STRLEN, S_CS_POINT, g->conns[1]->name, S_STRLEN, S_CS_POINT, g->conns[2]->name, S_STRLEN, S_CS_POINT, g->conns[3]->name, S_STRLEN, S_END ); } for (i=NWIND-1;i>=0;i--) cx[i] = i; for (i=NWIND-1;i>=0;i--) { j = i ? rnd(i+1) : 0; g->players[i].conn = g->conns[cx[j]]; g->players[i].conn->player = &g->players[i]; if (j != i) cx[j] = cx[i]; } g->claimid = PL_NOID; add_block_fn(&handck,g); begin_hand(g); } static void kill_accs(void) { ACC *a; while ((a=accs)) { accs = a->link; remove_poll_id(a->id); close(a->fd); free(a); } freeaddrinfo(acc_ai0); } static void nconn_to_pconn(NCONN *n) { char *s; PCONN *p; int i; s = csget_result_nul(n->s); printf("Got name %s\n",s); p = malloc(sizeof(PCONN)); p->c = n->c; free(n); p->name = s; p->c->ops = &ops_ipconn; p->c->arg = p; for (i=NWIND-1;i>=0;i--) { if (g.conns[i] == 0) { g.conns[i] = p; if (i == 0) { kill_accs(); start_game(&g); } break; } } } static int rd_nconn(void *nv, unsigned char *buf, int len) { NCONN *n; int i; n = nv; if (n->s) { for (i=0;is,buf[i])) { if (csget_error(n->s)) { csget_abort(n->s); n->s = 0; kill_conn(n->c); return(0); } nconn_to_pconn(n); return(i+1); } } return(len); } else { if (len < MJP_MAGICLEN) return(0); if (! bcmp(buf,&magic_client[0],MJP_MAGICLEN)) { n->s = csget_start(256); return(MJP_MAGICLEN); } kill_conn(n->c); return(0); } } static void kill_nconn(void *nv) { NCONN *n; n = nv; if (n->s) csget_abort(n->s); free(n); } static ROPS ops_nconn = OPS(nconn); static void acc_accept(void *av) { ACC *a; int fd; struct sockaddr_storage from; socklen_t fromlen; CONN *c; NCONN *n; a = av; if (! accs) return; fromlen = sizeof(from); fd = accept(a->fd,(void *)&from,&fromlen); if (fd < 0) { fprintf(stderr,"%s: accept: %s\n",__progname,strerror(errno)); return; } c = malloc(sizeof(CONN)); n = malloc(sizeof(NCONN)); n->c = c; n->s = 0; c->fd = fd; oq_init(&c->oq); c->rbuf = malloc(512); c->rlen = 512; c->rfill = 0; c->rptr = 0; c->ops = &ops_nconn; c->arg = n; c->id = add_poll_fd(fd,&rtest_conn,&wtest_conn,&rd_conn,&wr_conn,c); oq_queue_point(&c->oq,&magic_server[0],MJP_MAGICLEN); } static void setup_accept(void) { struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int err; int s; ACC *a; int on; hints.ai_flags = AI_PASSIVE; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; if (portstr == 0) { char *s; asprintf(&s,"%d",MJP_DEFAULT_PORT); portstr = s; hints.ai_flags |= AI_NUMERICSERV; } err = getaddrinfo(0,portstr,&hints,&ai0); if (err) { fprintf(stderr,"%s: %s: %s\n",__progname,portstr,gai_strerror(err)); exit(1); } accs = 0; for (ai=ai0;ai;ai=ai->ai_next) { s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(errno)); continue; } on = 1; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); if (bind(s,(void *)ai->ai_addr,ai->ai_addrlen) < 0) { fprintf(stderr,"%s: bind: %s\n",__progname,strerror(errno)); close(s); continue; } if (listen(s,10) < 0) { fprintf(stderr,"%s: listen: %s\n",__progname,strerror(errno)); close(s); continue; } a = malloc(sizeof(ACC)); a->fd = s; a->ai = ai; a->id = add_poll_fd(s,&rwtest_always,&rwtest_never,&acc_accept,0,a); a->link = accs; accs = a; } if (! accs) exit(1); acc_ai0 = ai0; } static void init_game(GAME *g) { int i; for (i=NWIND-1;i>=0;i--) g->players[i].conn = 0; g->handwind = WIND_E; } static void setup_random(void) { srandom(time(0)); } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); init_polling(); init_game(&g); setup_accept(); setup_random(); while (1) { pre_poll(); if (do_poll() < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } post_poll(); } }