/* bowling.c by Michael Thorpe 2006-07-16 */ /* Since the bowling world does not have an "errors" stats, I must explain it. * Errors occur on deliveries, and are any one of the following: * 1. Foot fault * 2. Open full frame that wasn't a split * 3. A split where fewer than half the remaining pins were knocked down */ #include #include #include #include #include #define FRAME_SPLIT 1 #define FRAME_FAULT_BALL1 2 #define FRAME_FAULT_BALL2 4 #define FRAME_HALFFRAME 8 struct frame { char ball[2]; char type; /* bitwise OR of FRAME_* #defines */ }; struct game { struct frame frame[12]; unsigned short score[10]; unsigned int numframes; }; struct stats { unsigned int games,frames,deliveries; unsigned int strikes,spares,openframes; unsigned int splits,splitsconverted; unsigned int firstballtotal; unsigned int pinfall,mingame,maxgame; unsigned int footfaults,errors; }; /* Parses a full frame into the provided struct game; returns a line * position on error (1-based), or 0 on success. */ static int parseframe(char *frame,struct game *game,int framenum) { /* Parse split flag */ switch(frame[0]) { case ' ': break; case 's': game->frame[framenum].type |= FRAME_SPLIT; break; default: return(1); } /* Parse first ball */ switch(frame[1]) { case '-': if(game->frame[framenum].type&FRAME_SPLIT) return(2); break; case 'X': game->frame[framenum].ball[0]=10; if(game->frame[framenum].type&FRAME_SPLIT) return(2); break; case 'f': game->frame[framenum].type |= FRAME_FAULT_BALL1; if(game->frame[framenum].type&FRAME_SPLIT) return(2); break; default: if(!isdigit(frame[1])) return(2); game->frame[framenum].ball[0]=frame[1]-'0'; break; } if((10==game->frame[framenum].ball[0]) != (' '==frame[2])) return(3); /* Parse second ball */ switch(frame[2]) { case ' ': break; case '-': break; case '/': game->frame[framenum].ball[1]=10-game->frame[framenum].ball[0]; break; case 'f': game->frame[framenum].type |= FRAME_FAULT_BALL2; break; default: if(!isdigit(frame[2])) return(3); game->frame[framenum].ball[1]=frame[2]-'0'; if(10<=game->frame[framenum].ball[0]+game->frame[framenum].ball[1]) return(3); break; } return(0); } /* Parses a half frame into the provided struct game; returns a line * position on error (1-based), or 0 on success. */ static int parsehalfframe(char *frame,struct game *game,int framenum) { game->frame[framenum].type |= FRAME_HALFFRAME; /* Parse split flag */ switch(frame[0]) { case ' ': break; case 's': game->frame[framenum].type |= FRAME_SPLIT; break; default: return(1); } /* Parse ball */ switch(frame[1]) { case '-': if(game->frame[framenum].type&FRAME_SPLIT) return(2); break; case 'X': game->frame[framenum].ball[0]=10; if(game->frame[framenum].type&FRAME_SPLIT) return(2); break; case 'f': game->frame[framenum].type |= FRAME_FAULT_BALL1; if(game->frame[framenum].type&FRAME_SPLIT) return(2); break; default: if(!isdigit(frame[1])) return(2); game->frame[framenum].ball[0]=frame[1]-'0'; break; } switch(frame[2]) { case '\0': case '\n': case '\r': break; default: return(3); } return(0); } /* Parses a line into the provided struct game; returns a line position on * error (1-based), or 0 on success. */ static int parsegame(char *line,struct game *game) { int f,i,lastchar; /* Zero out the frames */ for(f=0;f<12;f++) { game->frame[f].ball[0]=0; game->frame[f].ball[1]=0; game->frame[f].type=0; } for(f=0;f<10;f++) { i=parseframe(line+3*f,game,f); if(i) return(3*f+i); } lastchar=3*f; if(10==game->frame[9].ball[0]) { lastchar+=3; i=parseframe(line+3*f,game,f); if(i) return(3*f+i); f++; if(10==game->frame[10].ball[0]) { lastchar+=2; i=parsehalfframe(line+3*f,game,f); if(i) return(3*f+i); f++; } } else if(10==game->frame[9].ball[0]+game->frame[9].ball[1]) { lastchar+=2; i=parsehalfframe(line+3*f,game,f); if(i) return(3*f+i); f++; } switch(line[lastchar]) { case '\0': case '\n': case '\r': break; default: return(lastchar+1); } game->numframes=f; /* Calculate scores */ for(f=0;f<10;f++) { game->score[f]=game->frame[f].ball[0]+game->frame[f].ball[1]; if(10==game->score[f]) { game->score[f]+=game->frame[f+1].ball[0]; if(10==game->frame[f].ball[0]) { if(10==game->frame[f+1].ball[0]) game->score[f]+=game->frame[f+2].ball[0]; else game->score[f]+=game->frame[f+1].ball[1]; } } if(f) game->score[f]+=game->score[f-1]; } return(0); } static void printgame(FILE *f,struct game *game) { int i; for(i=0;inumframes;i++) { fputc(game->frame[i].type&FRAME_SPLIT?'s':' ',f); if(10==game->frame[i].ball[0]) { fputc('X',f); fputc(' ',f); } else { if(game->frame[i].type&FRAME_FAULT_BALL1) fputc('f',f); else if(0==game->frame[i].ball[0]) fputc('-',f); else fputc('0'+game->frame[i].ball[0],f); if(game->frame[i].type&FRAME_HALFFRAME) ; else if(game->frame[i].type&FRAME_FAULT_BALL2) fputc('f',f); else if(0==game->frame[i].ball[1]) fputc('-',f); else if(10==game->frame[i].ball[0]+game->frame[i].ball[1]) fputc('/',f); else fputc('0'+game->frame[i].ball[1],f); } fputc(' ',f); } fputc('\n',f); for(i=0;i<10;i++) { if(i) fputc(' ',f); fprintf(f,"%3d",game->score[i]); } fputc('\n',f); } static void printgamehtml(FILE *f,struct game *game) { int i; fputs("",f); for(i=0;inumframes;i++) { if(i<10) fputs("",f); if(game->frame[i].type&FRAME_SPLIT) fputc('s',f); if(10==game->frame[i].ball[0]) { fputc('X',f); if(10==i && 12==game->numframes) { i++; if(game->frame[i].type&FRAME_SPLIT) fputc('s',f); if(game->frame[i].type&FRAME_FAULT_BALL1) fputc('f',f); else if(0==game->frame[i].ball[0]) fputc('-',f); else if(10==game->frame[i].ball[0]) fputc('X',f); else fputc('0'+game->frame[i].ball[0],f); } } else { if(game->frame[i].type&FRAME_FAULT_BALL1) fputc('f',f); else if(0==game->frame[i].ball[0]) fputc('-',f); else fputc('0'+game->frame[i].ball[0],f); if(game->frame[i].type&FRAME_HALFFRAME) ; else if(game->frame[i].type&FRAME_FAULT_BALL2) fputc('f',f); else if(0==game->frame[i].ball[1]) fputc('-',f); else if(10==game->frame[i].ball[0]+game->frame[i].ball[1]) fputc('/',f); else fputc('0'+game->frame[i].ball[1],f); } if(i<9) fputs("",f); } fputs("\n",f); for(i=0;i<9;i++) fprintf(f,"%d",game->score[i]); fprintf(f,"%d",game->score[9]); fputs("\n",f); } static int parsedate(char *date) { int i,d,m,y; static int mdays[12]={31,28,31,30,31,30,31,31,30,31,30,31}; for(i=0;i<8;i++) if(!isdigit(date[i])) return(INT_MIN); if(date[8]) return(INT_MIN); y=1000*(date[0]-'0')+100*(date[1]-'0')+10*(date[2]-'0')+date[3]-'0'; if(y<1970) return(0); m=10*(date[4]-'0')+date[5]-'0'-1; if(0>m || 12<=m) return(0); d=10*(date[6]-'0')+date[7]-'0'-1; if(0>d || mdays[m]<=d) return(0); for(i=m;i;i--) d+=mdays[i]; if(m>1 && !(y%4) && (!(y%100) || !(y%400))) d++; d+=365*(y-1970); /* This works because C's division operator is fucked up (-5/400==0): */ d+=(y-1969)/4-(3*(y-2001)/400); return(d); } void addstats(struct stats *stats,struct game *game) { int i; stats->games++; stats->frames+=game->numframes; for(i=0;inumframes;i++) { stats->firstballtotal+=game->frame[i].ball[0]; if(game->frame[i].type&FRAME_SPLIT) stats->splits++; if(10==game->frame[i].ball[0]) stats->strikes++; else if(10==game->frame[i].ball[0]+game->frame[i].ball[1]) { stats->spares++; if(game->frame[i].type&FRAME_SPLIT) stats->splitsconverted++; } else if(!(game->frame[i].type&FRAME_HALFFRAME)) { stats->openframes++; if(!(game->frame[i].type&FRAME_FAULT_BALL2)) { if(game->frame[i].type&FRAME_SPLIT) { if(game->frame[i].ball[0]+2*game->frame[i].ball[1]<10) stats->errors++; /* didn't pick up half of split leave */ } else stats->errors++; /* foot faults on ball2 will be added later */ } } if(game->frame[i].type&FRAME_FAULT_BALL1) stats->footfaults++; if(game->frame[i].type&FRAME_FAULT_BALL2) stats->footfaults++; if(10==game->frame[i].ball[0]) stats->deliveries+=1; else if(game->frame[i].type&FRAME_HALFFRAME) stats->deliveries+=1; else stats->deliveries+=2; } stats->pinfall+=game->score[9]; if(stats->mingame>game->score[9]) stats->mingame=game->score[9]; if(stats->maxgamescore[9]) stats->maxgame=game->score[9]; stats->errors+=stats->footfaults; } int main(int argc,char **argv) { char line[80]; struct game game; struct stats stats; int i,l; int gamedate=0,verbose=0; char *htmltitle=0; while(EOF != (i=getopt(argc,argv,"+d:h:v"))) { switch(i) { case 'd': gamedate=parsedate(optarg); if(INT_MIN==gamedate) { fprintf(stderr,"Unknown date format: %s (must be YYYYMMDD)\n",optarg); goto badarg; } break; case 'h': htmltitle=optarg; break; case 'v': verbose=1; break; default: goto badarg; } } if(argc != optind) { badarg: fprintf(stderr,"usage: %s [-d ] [-h ] [-v]\n",argv[0]); return(1); } memset(&stats,0,sizeof(stats)); stats.mingame=300; if(htmltitle) printf("%s\n\n",htmltitle); for(l=1;line==fgets(line,sizeof(line),stdin);l++) { if('#'==line[0]) continue; i=parsegame(line,&game); if(i) { fprintf(stderr,"Error at position %d on line %d\n",i,l); fputs(line,stderr); for(i--;i;i--) fputc(' ',stderr); fputs("^\n",stderr); return(1); } addstats(&stats,&game); if(htmltitle) printgamehtml(stdout,&game); else if(verbose) { printgame(stdout,&game); fputc('\n',stdout); } } if(!stats.games) return(0); if(htmltitle) { fputs("

\n",stdout); printf("Frames: %u in %u games
\n",stats.frames,stats.games); printf("Strikes: %u (%.2f%%)
\n",stats.strikes,100*stats.strikes/(double)stats.frames); printf("Spares: %u (%.2f%%)
\n",stats.spares,100*stats.spares/(double)stats.frames); printf("Open frames: %u (%.2f%%)
\n",stats.openframes,100*stats.openframes/(double)stats.frames); printf("Splits: %u of %u converted (%.2f%%)
\n",stats.splitsconverted,stats.splits,stats.splits?100*stats.splitsconverted/(double)stats.splits:100); printf("Errors: %u on %u deliveries (%.4f%%)
\n",stats.errors,stats.deliveries,100*stats.errors/(double)stats.deliveries); printf("First ball average: %.2f
\n",stats.firstballtotal/(double)stats.frames); printf("Pinfall: %u (%.1f average, %u low, %u high)\n",stats.pinfall,stats.pinfall/(double)stats.games,stats.mingame,stats.maxgame); fputs("\n",stdout); } else if(gamedate) { printf("<>\n",stats.pinfall,stats.pinfall/(double)stats.games,stats.mingame,stats.maxgame); } else { printf("Frames: %u in %u games\n",stats.frames,stats.games); printf("Strikes: %u (%.2f%%)\n",stats.strikes,100*stats.strikes/(double)stats.frames); printf("Spares: %u (%.2f%%)\n",stats.spares,100*stats.spares/(double)stats.frames); printf("Open frames: %u (%.2f%%)\n",stats.openframes,100*stats.openframes/(double)stats.frames); printf("Splits: %u of %u converted (%.2f%%)\n",stats.splitsconverted,stats.splits,stats.splits?100*stats.splitsconverted/(double)stats.splits:100); printf("Errors: %u on %u deliveries (%.4f%%)\n",stats.errors,stats.deliveries,100*stats.errors/(double)stats.deliveries); printf("First ball average: %.2f\n",stats.firstballtotal/(double)stats.frames); printf("Pinfall: %u (%.1f average, %u low, %u high)\n",stats.pinfall,stats.pinfall/(double)stats.games,stats.mingame,stats.maxgame); } return(0); }