/* BeerBuilder 2010. magne.saxegaard@gmail.com */ #include #include #include #include /* Define global values */ #define LONGSTR 1024 #define MEDIUMSTR 100 #define SHORTSTR 10 #define MAXLIST 100 #define MALTFILE "data/maltlist.txt" struct beer{ char name[MEDIUMSTR]; char date[SHORTSTR]; float boilvolume; float totalvolume; float totalweight; float totalOG; float totalBG; float estABV; float totalIBU; char color[MEDIUMSTR]; }; struct malt{ char name[MEDIUMSTR]; float SG; float utilization; int EBC; float weight; float BG; float OG; char addlate[SHORTSTR]; }; struct hop{ char name[MEDIUMSTR]; float alpha; float weight; float time; float IBU; }; struct maltdata{ char name[MEDIUMSTR]; float SG; float utilization; int EBC; }; /* Define global variables */ struct maltdata mdata[MAXLIST]; struct beer newbeer; struct malt malts[MAXLIST]; struct hop hops[MAXLIST]; /* Define functions */ void initbeer(); void initmalts(); void inithops(); void rmmalt(int rmline); void rmhop(int rmline); void printmenu(); void savefile(char* filename); float sumMalt(); float sumOG(); float sumBG(); float sumIBU(); float calcIBU(float BG, float time, float alpha, float weight, float totalvolume); float calcOG(float sgmalt, float weight, float utilization , float volume); float setColor(); float abv(float OG); void recalc(); void emptystring(char *string); /* Main function. Handling user input */ int main(int argc,char *argv[]) { /* load maltdata */ int ff = 0, mdatalen = 0; FILE *myfile = fopen(MALTFILE, "r"); if(myfile==NULL) { printf("Error: can't open file %s.\n",MALTFILE); return 1; }else{ while(!feof(myfile)) { fscanf(myfile, "%s %f %f %d", &mdata[ff].name, &mdata[ff].SG, &mdata[ff].utilization, &mdata[ff].EBC); ff++; } } mdatalen = ff; /* Menu control constants */ char ui0[SHORTSTR]; char ui1[MEDIUMSTR]; emptystring(ui0); emptystring(ui1); int end_flag0 = 0, mm = 0, hh = 0, nummalts = 0, numhops = 0, temp = 0; /* Initializing structs */ initbeer(); initmalts(); inithops(); /* Print main menu and handle commands and user input */ while (end_flag0 == 0){ /* Main page */ printmenu(); gets(ui0); /* Stopping program */ if(strcmp(ui0,"x") == 0){ end_flag0 = 1; /* Edit beer name */ }else if(strcmp(ui0,"n") == 0){ printf("Please enter new beer name: "); gets(ui1); strcpy(newbeer.name,ui1); /* Edit brew date */ }else if(strcmp(ui0,"ed") == 0){ printf("Please enter brew date: "); gets(ui1); strcpy(newbeer.date,ui1); /* Edit boil volume */ }else if(strcmp(ui0,"bv") == 0){ printf("Please enter boil volume: "); gets(ui1); newbeer.boilvolume = atof(ui1); /* Edit total volume */ }else if(strcmp(ui0,"tv") == 0){ printf("Please enter total volume: "); gets(ui1); newbeer.totalvolume = atof(ui1); /* Add malt */ }else if(strcmp(ui0,"am") == 0){ /* Print maltdata, loaded from file */ printf("\n"); printf("No\t Name \t \t SG \t Utilz. EBC \n"); for(ff = 0; ff < mdatalen-1; ff++){ printf("%d\t %-15s %.3f \t %.2f \t %d\n",ff,mdata[ff].name, mdata[ff].SG, mdata[ff].utilization, mdata[ff].EBC); } printf("\n"); printf("Please select malt [number] from list, or define custom [c]: "); gets(ui1); if(strcmp(ui1,"c") == 0){ printf("Please enter malt name: "); gets(ui1); strcpy(malts[nummalts].name,ui1); printf("Please enter malt specific gravity (eg. 1.036): "); gets(ui1); malts[nummalts].SG = atof(ui1); printf("Please enter expected utilization (typ. 0.73-0.75): "); gets(ui1); malts[nummalts].utilization = atof(ui1); printf("Please enter malt EBC: "); gets(ui1); malts[nummalts].EBC = atof(ui1); }else{ strcpy(malts[nummalts].name,mdata[atoi(ui1)].name); malts[nummalts].SG = mdata[atoi(ui1)].SG; malts[nummalts].utilization = mdata[atoi(ui1)].utilization; malts[nummalts].EBC = mdata[atoi(ui1)].EBC; } printf("Add to late boil? (partial boil) [y,n]: "); gets(ui1); strcpy(malts[nummalts].addlate,ui1); malts[nummalts].OG = calcOG(malts[nummalts].SG, malts[nummalts].weight, 1, newbeer.totalvolume); if(strcmp(ui1,"n") == 0){ malts[nummalts].BG = calcOG(malts[nummalts].SG, malts[nummalts].weight, 1, newbeer.boilvolume); }else{ malts[nummalts].BG = 1; } printf("Please enter malt weight (gram): "); gets(ui1); malts[nummalts].weight = atof(ui1); nummalts++; /* Add hops */ }else if(strcmp(ui0,"ah") == 0){ printf("Please enter hop name: "); gets(ui1); strcpy(hops[numhops].name,ui1); printf("Please enter alpha acid (\%) : "); gets(ui1); hops[numhops].alpha = atof(ui1); printf("Please enter hop weight (gram): "); gets(ui1); hops[numhops].weight = atof(ui1); printf("Please enter boil time (min): "); gets(ui1); hops[numhops].time = atof(ui1); hops[numhops].IBU = calcIBU(newbeer.totalBG, hops[numhops].time, hops[numhops].alpha, hops[numhops].weight, newbeer.totalvolume); numhops++; /* remove malt */ }else if(strcmp(ui0,"rm") == 0){ printf("Please enter line number for malt to remove: "); gets(ui1); rmmalt(atoi(ui1)); /* remove hop */ }else if(strcmp(ui0,"rh") == 0){ printf("Please enter line number for hop to remove: "); gets(ui1); rmhop(atoi(ui1)); /* edit malt weight */ }else if(strcmp(ui0,"emw") == 0){ printf("Please enter line for malt weight to edit: "); gets(ui1); temp = atoi(ui1); printf("Please enter new malt weight (g): "); gets(ui1); malts[temp].weight = atof(ui1); /* edit hop weight */ }else if(strcmp(ui0,"ehw") == 0){ printf("Please enter line for hop weight to edit: "); gets(ui1); temp = atoi(ui1); printf("Please enter new hop weight (g): "); gets(ui1); hops[temp].weight = atof(ui1); /* print to file */ }else if(strcmp(ui0,"p") == 0){ printf("Please enter filename: "); gets(ui1); savefile(ui1); } printmenu(); } fclose(myfile); return 0; } void initbeer(){ emptystring(newbeer.name); emptystring(newbeer.date); newbeer.boilvolume = 18; newbeer.totalvolume= 25; newbeer.totalweight= 0; newbeer.totalOG = 0; newbeer.totalBG = 0; newbeer.estABV = 0; newbeer.totalIBU = 0; emptystring(newbeer.color); } void initmalts(){ int mm; for(mm = 0; mm < MAXLIST; mm++){ malts[mm].name[MEDIUMSTR]; emptystring(malts[mm].name); malts[mm].SG = 0; malts[mm].EBC = 0; malts[mm].weight = 0; malts[mm].OG = 0; malts[mm].BG = 0; emptystring(malts[mm].addlate); } } void inithops(){ int hh; for(hh = 0; hh < MAXLIST; hh++){ hops[hh].name[MEDIUMSTR]; emptystring(hops[hh].name); hops[hh].alpha = 0; hops[hh].weight = 0; hops[hh].IBU = 0; } } void rmmalt(int rmline){ int mm = 0, mcp = 0; struct malt maltcpy[MAXLIST]; /* free struct members if on rmline, else copy */ for(mm = 0; mm < MAXLIST; mm++){ if(mm == rmline){ emptystring(malts[mm].name); malts[mm].SG = 0; malts[mm].EBC = 0; malts[mm].weight = 0; malts[mm].OG = 0; malts[mm].BG = 0; emptystring(malts[mm].addlate); }else{ sprintf(maltcpy[mcp].name,"%s",malts[mm].name); maltcpy[mcp].SG = malts[mm].SG; maltcpy[mcp].EBC = malts[mm].EBC; maltcpy[mcp].weight = malts[mm].weight; maltcpy[mcp].OG = malts[mm].OG; maltcpy[mcp].BG = malts[mm].BG; sprintf(maltcpy[mcp].addlate,"%s",malts[mm].addlate); mcp = mcp + 1; } } /* copy back to input struct */ for(mm = 0; mm < MAXLIST; mm++){ sprintf(maltcpy[mm].name,"%s",malts[mm].name); maltcpy[mm].SG = malts[mm].SG; maltcpy[mm].EBC = malts[mm].EBC; maltcpy[mm].weight = malts[mm].weight; maltcpy[mm].OG = malts[mm].OG; maltcpy[mm].BG = malts[mm].BG; sprintf(maltcpy[mm].addlate,"%s",malts[mm].addlate); } } void rmhop(int rmline){ int hh = 0, hcp = 0; struct hop hopcpy[MAXLIST]; /* free struct members if on rmline, else copy */ for(hh = 0; hh < MAXLIST; hh++){ if(hh == rmline){ emptystring(hops[hh].name); hops[hh].alpha = 0; hops[hh].weight = 0; hops[hh].time = 0; hops[hh].IBU = 0; }else{ sprintf(hopcpy[hcp].name,"%s",hops[hh].name); hopcpy[hcp].alpha = hops[hh].alpha; hopcpy[hcp].weight = hops[hh].weight; hopcpy[hcp].time = hops[hh].time; hopcpy[hcp].IBU = hops[hh].IBU; hcp = hcp + 1; } } /* copy back to input struct */ for(hh = 0; hh < MAXLIST; hh++){ sprintf(hopcpy[hh].name,"%s",hops[hh].name); hopcpy[hh].alpha = hops[hh].alpha; hopcpy[hh].weight = hops[hh].weight; hopcpy[hh].time = hops[hh].time; hopcpy[hh].IBU = hops[hh].IBU; } } void printmenu(){ int mm = 0, hh = 0; recalc(); system("cls"); printf("\n"); printf("Beer Builder 2010. magne.saxegaard@gmail.com \n"); printf("\n"); printf("Edit field by typing command in [brackets]\n"); printf("Malt data can be added to \"%s\" \n",MALTFILE); printf("\n"); printf("--------------------------------------------------------------------------\n"); printf("\n"); printf("Beer name \t [n] \t: %s \n",newbeer.name); printf("Brew date \t [ed]\t: %s \n",newbeer.date); printf("Boil vol. (L) \t [bv]\t: %.1f \n",newbeer.boilvolume); printf("Total vol. (L) \t [tv]\t: %.1f \n",newbeer.totalvolume); printf("Total weigt (g)\t \t: %.0f\n",newbeer.totalweight); printf("BG (ppg) \t \t: %.3f\n",newbeer.totalBG); printf("OG (ppg) \t \t: %.3f\n",newbeer.totalOG); if(newbeer.estABV < 8){ printf("ABV (pct) \t \t: %.1f\n",newbeer.estABV); }else{ printf("ABV (pct) \t \t: %.1f (rai rai)\n",newbeer.estABV); } if(newbeer.totalIBU <= 50){ printf("IBU \t \t \t: %.0f\n",newbeer.totalIBU); }else if( (newbeer.totalIBU > 50) && (newbeer.totalIBU < 80)){ printf("IBU \t \t \t: %.0f (don't worry, be hoppy)\n",newbeer.totalIBU); }else if(newbeer.totalIBU > 80){ printf("IBU \t \t \t: %.0f (welcome to the green side)\n",newbeer.totalIBU); } printf("Color \t \t \t: %s\n",newbeer.color); printf("\n"); /* Malt list */ printf("Line \t Malt \t \t SG \t EBC \t Wt \t \t OG \t Late \n"); printf("(-) \t (-) \t \t (ppg) \t (-) \t (g)\t (pct) \t (ppg)\t \n"); printf("--------------------------------------------------------------------------\n"); for(mm = 0; mm < MAXLIST; mm++){ if(strcmp(malts[mm].name,"") == 1){ printf("%d \t %-15s %.3f \t %d \t %.0f \t %.0f \t %.3f \t %s\n",mm, malts[mm].name, malts[mm].SG, malts[mm].EBC, malts[mm].weight, malts[mm].weight*100/newbeer.totalweight,malts[mm].OG, malts[mm].addlate); } } printf("\n"); printf("\n"); /* Hop list */ printf("Line \t Hop \t \t Alpha \t Wt \t Time \t IBU\n"); printf("(-) \t (-) \t \t (pct) \t (g) \t (min) \t (-)\n"); printf("--------------------------------------------------------------------------\n"); for(hh = 0; hh < MAXLIST; hh++){ if(strcmp(hops[hh].name,"") == 1){ printf("%d \t %-15s %.1f \t %.0f \t %.0f \t %.0f\n",hh, hops[hh].name, hops[hh].alpha, hops[hh].weight, hops[hh].time, hops[hh].IBU); } } printf("\n"); printf("\n"); /* Rest of commands */ printf("[am] \t add malt\n"); printf("[rm] \t remove malt\n"); printf("[emw] \t edit malt weight\n"); printf("[ah] \t add hop\n"); printf("[rh] \t remove hop\n"); printf("[ehw] \t edit hop wt\n"); printf("[p] \t print to file\n"); printf("[x] \t quit\n"); printf("\n"); } void savefile(char filename[]){ int mm = 0, hh = 0; FILE *myFile = fopen(filename, "w+"); recalc(); /* print to file */ fprintf(myFile,"Name : %s\n", newbeer.name); fprintf(myFile,"Date : %s\n", newbeer.date); fprintf(myFile,"Boil volume (L) : %.1f\n",newbeer.boilvolume); fprintf(myFile,"Total volume (L) : %.1f\n",newbeer.totalvolume); fprintf(myFile,"Total weight (g) : %.0f\n",newbeer.totalweight); fprintf(myFile,"BG (ppg) : %.3f\n",newbeer.totalBG); fprintf(myFile,"OG (ppg): %.3f\n",newbeer.totalOG); fprintf(myFile,"ABV (pct): %.1f\n",newbeer.estABV); fprintf(myFile,"IBU: %.0f\n",newbeer.totalIBU); fprintf(myFile,"Color \t: %s",newbeer.color); fprintf(myFile,"\n"); fprintf(myFile,"Line \t Malt \t \t SG \t EBC \t Wt \t \t OG \t Late \n"); fprintf(myFile,"(-) \t (-) \t \t (ppg) \t (-) \t (g)\t (pct) \t (ppg)\t \n"); fprintf(myFile,"--------------------------------------------------------------------------\n"); for(mm = 0; mm < MAXLIST; mm++){ if(strcmp(malts[mm].name,"") == 1){ /*only add line if it has a name */ fprintf(myFile,"%d \t %-15s %.3f \t %d \t %.0f \t %.0f \t %.3f \t %s\n",mm, malts[mm].name, malts[mm].SG, malts[mm].EBC, malts[mm].weight, malts[mm].weight*100/newbeer.totalweight,malts[mm].OG, malts[mm].addlate); } } fprintf(myFile,"\n"); fprintf(myFile,"Line \t Hop \t \t Alpha \t Wt \t Time \t IBU\n"); fprintf(myFile,"(-) \t (-) \t \t (pct) \t (g) \t (min) \t (-)\n"); fprintf(myFile,"--------------------------------------------------------------------------\n"); for(hh = 0; hh < MAXLIST; hh++){ if(strcmp(hops[hh].name,"") == 1){ fprintf(myFile,"%d \t %-15s %.1f \t %.0f \t %.0f \t %.0f\n",hh, hops[hh].name, hops[hh].alpha, hops[hh].weight, hops[hh].time, hops[hh].IBU); } } fclose(myFile); } float sumMalt(){ int mm = 0; float totalmalt = 0; for(mm = 0; mm < MAXLIST; mm++){ totalmalt = totalmalt + malts[mm].weight; } return totalmalt; } float sumOG(){ int gg = 0; float totalOG = 0; for(gg = 0; gg < MAXLIST; gg++){ totalOG = totalOG + (malts[gg].OG-1); } totalOG = totalOG+1; return totalOG; } float sumBG(){ int gg = 0; float totalBG = 0; for(gg = 0; gg < MAXLIST; gg++){ totalBG = totalBG + (malts[gg].BG-1); } totalBG = totalBG+1; return totalBG; } float sumIBU(){ int hh = 0; float totalIBU = 0; for (hh = 0; hh < MAXLIST; hh++){ totalIBU = totalIBU + hops[hh].IBU; } return totalIBU; } float calcIBU(float BG, float time, float alpha, float weight, float totalvolume){ /* Calculate IBU using Tinset formula, http://realbeer.com/hops/ */ float IBU = 0; IBU = 1.65 * pow(0.000125,BG-1) * (1 - exp(-0.04*time))/4.15 * (alpha/100)*weight*(1000/totalvolume); return IBU; } float calcOG(float sgmalt, float weight, float utilization , float volume){ /* Calculate OG using formula from "Moderne Hjemmebrygging" */ float OG = 0; OG = ((sgmalt-1) * 8.3333 * weight * utilization) / (1000*volume) + 1; return OG; } float setColor(){ /* Calculate beer color from malt color units (MCU) and the Morey equation http://www.beersmith.com/blog/2008/04/29/beer-color-understanding-srm-lovibond-and-ebc/ MCU = (Weight of grain in lbs) * (Color of grain in SRM) / (volume in gallons) SRM color = 1.4922 * (MCU ** 0.6859) 1 pound = 453.59237 grams 1 gallon = 3.78541178 liter 1 EBC = 1.97 SRM (SRM is the same as degrees Lovibond) Set color param directly from values in malts, to newbeer global struct */ int mm = 0; float mcu = 0, srm = 0; char color[MEDIUMSTR]; emptystring(color); /* calculate total srm using total MCUs and the Morey formula */ for(mm = 0; mm < MAXLIST; mm++){ mcu = mcu + (malts[mm].weight/453.59237) * (malts[mm].EBC / 1.97) / (newbeer.totalvolume / 3.78541178); } srm = 1.4922 * pow(mcu,0.6859); /* Look up table for colors, defined by lovibond values */ if( (srm >= 0) && (srm < 7.5) ){ sprintf(color,"%s","yellow"); }else if( (srm >= 7.5) && (srm < 14) ){ sprintf(color,"%s","amber"); }else if( (srm >= 14) && (srm < 25) ){ sprintf(color,"%s","brown"); }else if(srm >= 25){ sprintf(color,"%s","black"); } /* Set color value of global struct */ sprintf(newbeer.color,"%s",color); return srm; } float abv(float OG){ return (OG - 1) * 1000 * 0.75 / 7.6; } void recalc(){ /* recalculate everything that is calculated */ int mm; for(mm = 0; mm