#include "ptoc.h" /* ÛßÜ ÛßÜ ÜßßÜ ßÛß Ûßß Üß ßÛß ÜßÛßÜ ßÛß ÛÜ Û ÜÛßß ßÛß ÛßÜ Ûßß Û Ûß ÛÛ Û Û Ü Û Ûßß ßÜ Û Û Û Û Û Û ÛÜÛ ßßßÜ Û ÛÛ Ûßß Û ß ß ß ßß ßß ßßß ß ß ß ß ß ßßß ß ßß ßßß ß ß ß ßßß ßßß NIM UNIT A unit version of the pub game (Nim). */ #define __nimunit_implementation__ #include "nimunit.h" const array > names = {{"Avalot","Dogfood"}}; array<1,3,byte> old,stones; array<0,3,0,22,1,7,byte> stonepic; /* picture of Nimstone */ byte turns; boolean dogfoodsturn; byte fv; byte stonesleft; boolean clicked; byte row,number; boolean squeak; shortint mnum,mrow; void chalk(integer x,integer y, string z) { const array<0,3,byte> greys = {{0,8,7,15}}; byte fv; for( fv=0; fv <= 3; fv ++) { setcolor(greys[fv]); outtextxy(x-fv,y,z); sound(fv*100*length(z)); delay(3); nosound; delay(30); } } void setup() { const integer page3 = 0xac00; byte gd,gm; untyped_file f; byte bit; setactivepage(3); setvisualpage(3); cleardevice(); dawn(); assign(f,"nim.avd"); reset(f,1); seek(f,41); for( gm=0; gm <= 22; gm ++) for( bit=0; bit <= 3; bit ++) { port[0x3c4]=2; port[0x3ce]=4; port[0x3c5]=1 << bit; port[0x3cf]=bit; blockread(f,stonepic[bit][gm],7); } for( gd=1; gd <= 3; gd ++) for( gm=0; gm <= 22; gm ++) for( bit=0; bit <= 3; bit ++) { port[0x3c4]=2; port[0x3ce]=4; port[0x3c5]=1 << bit; port[0x3cf]=bit; blockread(f,mem[page3*3200+gd*2800+gm*80],7); } for( gm=0; gm <= 36; gm ++) for( bit=0; bit <= 3; bit ++) { port[0x3c4]=2; port[0x3ce]=4; port[0x3c5]=1 << bit; port[0x3cf]=bit; blockread(f,mem[page3*400+49+gm*80],30); } close(f); gd=getpixel(0,0); /* clear codes */ setcolor(4); rectangle(394,50,634,197); setfillstyle(1,6); bar(395,51,633,196); rectangle(10,5,380,70); bar(11,6,379,69); setcolor(15); outtextxy(475,53,"SCOREBOARD:"); setcolor(14); outtextxy(420,63,"Turn:"); outtextxy(490,63,"Player:"); outtextxy(570,63,"Move:"); for( gd=1; gd <= 3; gd ++) stones[gd]=gd+2; turns=0; dogfoodsturn=true; chalk(27,15,"Take pieces away with:"); chalk(77,25,"1) the mouse (click leftmost)"); chalk(53,35,"or 2) the keyboard:"); chalk(220,35,string('\30')+'/'+'\31'+": choose row,"); chalk(164,45,string("+/- or ")+'\33'+'/'+'\32'+": more/fewer,"); chalk(204,55,"Enter: take stones."); row=1; number=1; fillchar(old,sizeof(old),'\0'); stonesleft=12; /* Set up mouse. */ off_virtual(); oncandopageswap=false; setactivepage(3); setvisualpage(3); } void plotstone(byte x,byte y) { byte fv,bit; word ofs; ofs=3200+y*2800+x*8; for( fv=0; fv <= 22; fv ++) for( bit=0; bit <= 3; bit ++) { port[0x3c4]=2; port[0x3ce]=4; port[0x3c5]=1 << bit; port[0x3cf]=bit; move(stonepic[bit][fv],mem[0xac00*ofs+fv*80],7); } } void board() { byte fv,ff; for( fv=1; fv <= 3; fv ++) for( ff=1; ff <= stones[fv]; ff ++) plotstone(ff,fv); } void startmove() { varying_string<2> tstr; integer ypos; turns += 1; str(turns,2,tstr); ypos=63+turns*10; dogfoodsturn=! dogfoodsturn; chalk(433,ypos,tstr); chalk(493,ypos,names[dogfoodsturn]); old=stones; } void show_changes() { byte fv,ff,fq; varying_string<2> move; move=string(chr(64+row))+chr(48+number); chalk(573,63+turns*10,move); log_aside(names[dogfoodsturn]+" plays "+move+'.'); for( fv=1; fv <= 3; fv ++) if (old[fv]>stones[fv]) for( ff=stones[fv]+1; ff <= old[fv]; ff ++) for( fq=0; fq <= 22; fq ++) fillchar(mem[0xac00*3200+fv*2800+ff*8+fq*80],7,'\0'); stonesleft -= number; } void checkmouse(); static void blip() { note(1771); delay(3); nosound; clicked=false; } void checkmouse() { xycheck(); /* Check the mouse */ clicked=keystatus>0; if (clicked) { void& with = r; /* The mouse was clicked. Where? */ mrow=(my-38) / 35; if ((mrow<1) || (mrow>3)) blip(); mnum=stones[mrow]-(mx / 64)+1; if ((mnum<1) || (mnum>(unsigned char)stones[mrow])) blip(); } } void takesome(); static void less() { if (number>1) number -= 1; } void takesome() { char r; byte sr; number=1; do { do { sr=stones[row]; if (sr==0) { row=row % 3+1; number=1; } } while (!(sr!=0)); if (number>sr) number=sr; setcolor(1); rectangle(63+(sr-number)*64,38+35*row,54+sr*64,63+35*row); /* Wait for choice */ on(); do { checkmouse(); } while (!(keypressed() || clicked)); if (keypressed()) r=upcase(readkey()); off(); setcolor(0); rectangle(63+(sr-number)*64,38+35*row,54+sr*64,63+35*row); if (clicked) { number=mnum; row=mrow; return; } else { switch (r) { case '\0': switch (readkey()) { case 'H': if (row>1) row -= 1; break; /* Up */ case 'P': if (row<3) row += 1; break; /* Down */ case 'K': number += 1; break; case 'M': less(); break; case 'I': row=1; break; /* PgUp */ case 'Q': row=3; break; /* PgDn */ case 'G': number=5; break; /* Home- check routine will knock this down to size */ case 'O': number=1; break; /* End */ } break; case '+': number += 1; break; case '-': less(); break; case RANGE_3('A','C'): row=ord(r)-64; break; case RANGE_5('1','5'): number=ord(r)-48; break; case '\15': return; break; /* Enter was pressed */ } } } while (!false); } void endofgame() { char rr; chalk(595,63+turns*10,"Wins!"); outtextxy(100,190,"- - - Press any key... - - -"); while (keypressed()) rr=readkey(); do { check(); } while (!(mpress==0)); { void& with = r; do { check(); } while (!(keypressed() || (mrelease>0)));} if (keypressed()) rr=readkey(); mousepage(cp); off(); on_virtual(); } void dogfood(); /* AI procedure to play the game */ const matrix<1,3,1,2,byte> other = {{{{2,3}},{{1,3}},{{1,2}}}}; static byte live,fv,ff,matches,thisone,where; static array<1,3,byte> r,sr; static array<1,3,boolean> inap; static boolean lmo; /* Let Me Out! */ static byte ooo; /* Odd one out */ static boolean find(byte x) /* This gives True if there's a pile with x stones in. */ { boolean q; byte p; boolean find_result; q=false; for( p=1; p <= 3; p ++) if (stones[p]==x) { q=true; inap[p]=true; } find_result=q; return find_result; } static void find_ap(byte start,byte stepsize) { byte ff; matches=0; fillchar(inap,sizeof(inap),'\0'); /* blank 'em all */ for( ff=0; ff <= 2; ff ++) if (find(start+ff*stepsize)) matches += 1; else thisone=ff; /* Now.. Matches must be 0, 1, 2, or 3. 0/1 mean there are no A.P.s here, so we'll keep looking, 2 means there is a potential A.P. that we can create (ideal!), and 3 means that we're already in an A.P. (Trouble!). */ switch (matches) { case 2: { for( ff=1; ff <= 3; ff ++) /* find which one didn't fit the A.P. */ if (! inap[ff]) ooo=ff; if (stones[ooo]>(start+thisone*stepsize)) /* check it's possible! */ { /* create an A.P. */ row=ooo; /* already calculated */ /* Start+thisone*stepsize will give the amount we SHOULD have here. */ number=stones[row]-(start+thisone*stepsize); lmo=true; return; } } break; case 3: { /* we're actually IN an A.P! Trouble! Oooh dear. */ row=r[3]; number=1; lmo=true; return; /* take 1 from the largest pile */ } break; } } void dogfood() { boolean sorted; byte temp; live=0; lmo=false; for( fv=1; fv <= 3; fv ++) { if (stones[fv]>0) { live += 1; r[live]=fv; sr[live]=stones[fv]; } } switch (live) { case 1: /* Only one is free- so take 'em all */ { row=r[1]; number=stones[r[1]]; return; } break; case 2: /* Two are free- make them equal */ { if (sr[1]>sr[2]) { row=r[1]; number=sr[1]-sr[2]; return; } else /* T > b */ if (sr[1] t */ { row=r[1]; number=1; return; } /* B = t... oh no, we've lost! */ } break; case 3: /* Ho hum... this'll be difficult! */ { /* There are three possible courses of action when we have 3 lines left: 1) Look for 2 equal lines, then take the odd one out. 2) Look for A.P.s, and capitalise on them. 3) Go any old where. */ for( fv=1; fv <= 3; fv ++) /* Look for 2 equal lines */ if (stones[other[fv][1]]==stones[other[fv][2]]) { row=fv; /* this row */ number=stones[fv]; /* all of 'em */ return; } do { sorted=true; for( fv=1; fv <= 2; fv ++) if (sr[fv]>sr[fv+1]) { temp=sr[fv+1]; sr[fv+1]=sr[fv]; sr[fv]=temp; temp= r[fv+1]; r[fv+1]= r[fv]; r[fv]=temp; sorted=false; } } while (!sorted); /* Now we look for A.P.s ... */ for( fv=1; fv <= 3; fv ++) { find_ap(fv,1); /* there are 3 "1"s */ if (lmo) return; /* cut-out */ } find_ap(1,2); /* only "2" possible */ if (lmo) return; /* A.P. search must have failed- use the default move. */ row=r[3]; number=1; return; } break; } } void play_nim() /* Plays the game. Only procedure in this unit to be declared in the interface section. */ { byte groi; if (dna.wonnim) { /* Already won the game. */ dixi('Q',6); return; } if (! dna.asked_dogfood_about_nim) { dixi('q',84); return; } dixi('Q',3); dna.playednim += 1; dusk(); oncandopageswap=false; copypage(3,1-cp); /* Store old screen. */ groi=getpixel(0,0); off(); setup(); board(); on(); mousepage(3); do { startmove(); if (dogfoodsturn) dogfood(); else takesome(); stones[row] -= number; show_changes(); } while (!(stonesleft==0)); endofgame(); /* Winning sequence is A1, B3, B1, C1, C1, btw. */ dusk(); off(); oncandopageswap=true; copypage(1-cp,3); /* Restore old screen. */ groi=getpixel(0,0); on(); dawn(); if (dogfoodsturn) { /* Dogfood won - as usual */ log_aside("He won."); if (dna.playednim==1) /* Your first game */ dixi('Q',4); /* Goody! Play me again? */ else dixi('Q',5); /* Oh, look at that! I've won again! */ pennycheck(4); /* And you've just lost 4d! */ } else { /* You won - strange! */ log_aside("You won."); dixi('Q',7); /* You won! Give us a lute! */ dna.obj[lute]=true; objectlist(); dna.wonnim=true; show_one(1); /* Show the settle with no lute on it. */ points(7); /* 7 points for winning! */ } if (dna.playednim==1) points(3); /* 3 points for playing your 1st game. */ } /* No init part. */