/*Smiler 4: Grinner on Tour Version 1.0 Digital Prawn 28th March 2010 Public Domain*/ #include #include #include #include #include #include #include #include //For debugging/playtesting #define STARTING_LEVEL 1 /* Memory Map 23755 ZX BASIC loader 24576 Bank of dormant AY tunes in contended RAM 32600 (approx) potential lower limit of stack 32768 Compiled C code and data (can reach up to 56025 MAX) 56026 (0xDADA) 3-byte JP instruction to (compiled C) ISR routine 56064 (0xDB00) 257-byte IM2 vector table filled with 0xDADA 56321 vtplayer Chip music player (1617 bytes) 58479 Currently selected AY Chip tune (typically less than 2K bytes) 63488 1K HEAP for malloc() requests. (Could reach max range of 60527-64511) 64512 UDGs Vortex Tracker II - must export with player address of 56321 56321=AY_INIT() 56326=AY_PLAY() 56329=AY_MUTE() Extra AY chip music tunes -> low RAM. Copied into high RAM before playing. */ //If defined then compile code for AY chip music //which must be appended onto the tapefile #define AY_VERSION #ifdef AY_VERSION #define NUM_TUNES 6 //number of AY tunes loaded #endif #define LEVEL_SIZE 176 #define HEAP_ADDR 63488 #define UDG_ADDR 64512 #define WAIT_KEY while(!in_Inkey()) #define NUM_LEVELS 20 //For single-level demo //#define NUM_LEVELS 1 //Maximum number of antagonists per level #define MAX_ANTAGONISTS 16 //MACROS #define SGN(x) ((x)<0?-1:((x)>0?1:0)) typedef unsigned char byte; typedef unsigned int word; //Globals //Colours of arrows byte arrow_colour[4]={7,3,2,1}; //addresses and lengths of all loaded tunes #ifdef AY_VERSION word tune_addr[NUM_TUNES]={28975,29986,24576,26157,27167,27843}; word tune_len[NUM_TUNES]={1011,1400,1581,1010,676,1132}; #endif //level passcodes char *passcode[]= { "563087", "421558", "328807", "533731", "632581", "759050", "714169", "903705", "770063", "149519", "884881", "083499", "601101", "770996", "503484", "563963", "063512", "575885", "274293", "145403" }; // LAST_K system variable used by IM2 keyboard handling routine extern char LAST_K(23560); // variables used by in_GetKey() byte in_KeyDebounce = 1; byte in_KeyStartRepeat = 20; byte in_KeyRepeatPeriod = 10; word in_KbdState; byte music_on=0; long heap; char *current_map; /* level map data format (case sensitive) ' ' = open space '#' = red house obstruction 'S' = smiler starting point 'G' = grinner starting point (Smiler's Cousin) 'H' = Green house, the object of each level (All arrows are listed in the sequence North, East, South, West) 'd','e','f','g' = Magenta arrows 'h','i','j','k' = Red arrows 'l','m','n','o' = Blue arrows 'p','q','r','s' = White arrows 't','u','v',w' = Reserved for future arrows 'x','y','z','{' = Reserved for future arrows a = antagonist (Frowner) */ /* Arrow behaviour Magenta arrows - same as first Smiler game - rotate all arrows on the level 90 degrees clockwise Red arrows - rotate all arrows on the level 90 degrees anti-clockwise Blue arrows - rotate all blue arrows 180 degrees. Rotate all magenta arrows 90 degrees clockwise. Rotate all red arrows 90 degrees anti-clockwise. Do not rotate white arrows. White arrows - do not rotate any arrows - but kill frowners when they step onto them */ /* level map data */ char *map[]= { "################" //level 1 "#Siilmnojkldefg#" "#e#j#k#l#m#i#jh#" "#gkl j m e fajk#" "#am i e g #" "# h k f h #" "# j l g j #" "#kldegjikjmlijk#" "#h#g#n#k#h#n#hd#" "#HkfhmodefgfglG#" "################", "################" //level 2 "#G a a S#" "# ## #" "#de#lgi##kjo#ig#" "# pq ## rs #" "# ## #" "#he#dfn##lde#fg#" "# a ## a #" "#jkildh##fkhedo#" "# iH g #" "################", "################" //level 3 "# d k ai H#" "# rl f h #" "#liffghedomjkil#" "# k pj af d #" "#jikloghfdejiln#" "# g f sf ae #" "#dhfedlkkdghdfj#" "# k m e e #" "#GS e qi ia#" "################", "################" //level 4 "#a k a#" "# ff####gg #" "# #G mh# #" "# # oi # #" "#goi# lj #hlk#" "# # nk S# #" "# dd####ee #" "#a d #" "#H f a#" "################", "################" //level 5 "#SG# er # ks #" "# # # # # #" "# # # # # #" "#df#kn#li#om#gr#" "# # a# a# a# a#" "#gm#ni#mn#fi#ed#" "# # # # # #" "# j # # # #" "# dq # fp # H#" "################", "################" //level 6 "#dded k gilj#" "#G# h # #" "# # j # #" "#kljo# i # #" "# r#nk#li#dhk#" "# df qg # #" "#dfgk # #of#" "#aaae # #g #" "#aaai S# Hoef#" "################", "################" //level 7 "# if ad #" "# ###### i# S#" "#ok p # hiloh#" "# n# i#ke# #" "#jno# #ks# #df#" "# #jk# # #" "#add# f # #" "# hl #### #gjl#" "#gjk H # G#" "################", "################" //level 8 "# ne eo #" "#go#### ####en#" "# #s r# #" "#df# d###kh # #" "# # #H# #ld#" "# #a l m a# #" "#dh##########mo#" "# g S G q ea #" "# aep n g #" "################", "################" //level 9 "# kli gkj #" "# jSg qGd #" "# eno hml #" "# #" "# #aaaaaa# #" "# #" "# omn #" "# lHk #" "# edf #" "################", "################" //level 10 "# j k l e d ef#" "#H#h#f#d#e#m#lo#" "#a g e l k ja #" "#e#f#i#j#l#e#dg#" "# g h d f #" "# # # # # ###dl#" "# o m #nS i#" "# # # # # #G# g#" "# e g k medf#" "################", "################" //level 11 "# sedris d #" "#a#hijk#afe#H #" "# # # a#ge#" "# d #lon#djk e#" "# f S# d #qdl #" "# g # f # e #" "#me h# g #meds#" "# li#omen#sdgh#" "#G j k fh #" "################", "################" //level 12 "# l j j hi #" "# ### d#j ####h#" "# e #a#a# j i#" "# d G###H m m#" "# ############l#" "# f S###a j g#" "# g #a# # k q#" "# ### k#ld####j#" "# e md #" "################", "################" //level 13 "# l f f m #" "#gdh ### ikg#" "# k ### d #" "# r S#G e #" "# j####### j #" "# n # f #" "# ino#H#ejid #" "# i # # g #" "#fgedadgoagekf #" "################", "################" //level 14 "# f m d l #" "# # h #eerqff#" "# #G#S# k #" "#df# #nog####hi#" "#lifkjl #aa# #" "#aa###mlk####gk#" "# fgih k #" "# # lpok nde#" "#H### m l h #" "################", "################" //level 15 "#d j f i aaH#" "# ###e g h###a#" "# ### ### ###o#" "#d dlf### d#" "#o e g gs gea#" "# ### fd ###dh#" "#n###n ### #" "#d d###hk e #" "#SG i### g l#" "################", "################" //level 16 "# f i g S#" "#h#h#ggg###kkkk#" "# # # G #" "#d# #gig###lefl#" "# e d gmdgm#" "#a###gqr# # #" "# d # #ikjh#" "#a###fff# #jjkl#" "#Ha a e e #" "################", "################" //level 17 "# ikljhefgm S#" "# # # # #g #" "# #oa#ea#f #ed#" "#g g i k j #" "#h # # # # #" "#jf #da#ea#m lj#" "#k n d f q #" "#l#de#fg#hj#dj #" "#nH#jm#kl#fg# G#" "################", "################" //level 18 "# d e m k j #" "#g# #dg###kn#jl#" "# #H# a#a # #" "#h###l # e#dk#" "# # #gia#aefe #" "#i# # ### #df#" "# e d f g k #" "#h############o#" "#S i i l m e kG#" "################", "################" //level 19 "#deS kj hk G#" "#q### ##e ##g#" "# #" "# d e f g #" "#i# ####### #d#" "# h i j k #" "# # # m #" "#g### ## ###r#" "# aHag mj fe#" "################", "################" //level 20 "#Hg i a j l g k#" "#h l i j g o f #" "# k m p r d h j#" "#g h # n o #" "# e g ### g l#" "#l g # l i #" "# h k p f k h e#" "#p s l j d m h #" "# a l l d k dSG#" "################" }; char *level_name[]= { "Double Decker", "The Giggle Brothers", "Twin Grin", "The Piglet Folder", "Also Sprach Grinner", "Chicken Chase", "Round the Turnip Tree", "Pepper Sprout Route", "Hypnagogic Jerks", "Spam Fritter Titter", "Mind the Sideboard", "An Echo in Here?", "Fun House", "Philosopher's Bricks", "Streets of Rhodium", "Handbags at Dawn", "Quest for Scollops", "Government Warning", "Detachable Sleeves", "The Eridian Gambit" }; //Which player character, smiler or grinner? //0=Smiler, 1=Grinner byte current_p; //player x and y and old values (Smiler & Grinner) byte px[2],py[2],ox[2],oy[2],is_home[2]; //anatagonist x and y and old values //if ax[i] is set to zero then it means antagonist is dead (not drawn) byte ax[MAX_ANTAGONISTS],ay[MAX_ANTAGONISTS], oax[MAX_ANTAGONISTS],oay[MAX_ANTAGONISTS], num_antagonists,ant_motion; /*Set up a UDG (numbered from 128-255) */ void set_udg(byte n,byte d0, byte d1, byte d2, byte d3, byte d4, byte d5, byte d6, byte d7); /*Print a string at x,y */ void printstrat64(byte x, byte y, char *s); #define printstrat32(x,y,s) printstrat64((x)<<1,(y),(s)) /*Set the current colour attributes */ void setcolattr(byte i, byte p, byte f, byte b); /* Print a colour string at x,y */ void printcolstrat64(byte i, byte p, byte f, byte b, byte x, byte y, char *s); #define printcolstrat32(i,p,f,b,x,y,s) printcolstrat64((i),(p),(f),(b),(x)<<1,(y),(s)) /* print a 2x2 UDG number u at x,y */ void print2x2udgat64(byte x, byte y, byte u); #define print2x2udgat32(x,y,u) print2x2udgat64((x)<<1,(y),(u)) /* print a colour 2x2 UDG number u at x,y */ void printcol2x2udgat64(byte i, byte p, byte f, byte b, byte x, byte y, byte u); #define printcol2x2udgat32(i,p,f,b,x,y,u) printcol2x2udgat64((i),(p),(f),(b),(x)<<1,(y),(u)) /* clear the screen */ void cls(void); /* clear the screen to a specific colour */ void colcls(byte i, byte p, byte f, byte b); /* clear the screen and set the BORDER to the specified colour o*/ void colborcls(byte i, byte p, byte f, byte b, byte o); /* set mode to either 32 or 64 columns */ void setcolmode(byte m); /* init the session */ void init_session(void); /* init the game */ void init_game(void); /*init the level */ void init_level(byte l); /*called in the event of a fatal error */ void bombout(char *s); /* redraw the map pointed to by m*/ void redraw_map(char *m); /* draw the outine of a map */ void draw_map_outline(char *m); /* move antagonists around map. Return true if an antagonist catches the player */ byte move_antagonists(char *m); /*semi-optimised version of above function which is called many times in the main game loop*/ void fast_printcol2x2udgat64(byte i, byte p, byte x, byte y, byte u); #define fast_printcol2x2udgat32(i,p,x,y,u) fast_printcol2x2udgat64((i),(p),(x)<<1,(y),(u)); /*rotate the arrows on the board */ void rotate_arrows(char *m, byte rotator); /*Print the intro screen*/ void intro_screen(void); /* Set the status line */ void set_status_line(char *s); /* Wrapper around zx_border() library function. zx_border2() also sets BORDCR system variable which is needed if sounds are played when the BORDER is non-white */ void zx_border2(byte b); /*Slight delay in main loop to reduce flicker*/ void main_loop_delay(byte n); /*Initiate the IM2 mode interrupt handler*/ void init_im2_handler(void); /*Play sound effects but only if music is off. Also sleep for s seconds only if music is on. (to provide equivalent delay to that of a sound effect - so status line can be read) */ void my_bit_fx(byte n, byte m, byte s); //Screen for entering level passcodes byte passcode_screen(void); #ifdef AY_VERSION void select_ay_tune(byte t); #endif //Interrupt service routine for AY chip music M_BEGIN_ISR(isr) { //char c; // if ((c = in_GetKey()) != 0) // LAST_K = c; #ifdef AY_VERSION if(music_on) { #asm call 56326 #endasm } #endif } M_END_ISR main() { byte i,level_over, session_over,level,killed; byte rotate_flag=0; init_session(); roswell: session_over=0; level=STARTING_LEVEL; //for playtesting //level=NUM_LEVELS; while(!session_over) { intro_screen(); init_game(); passenter: level=passcode_screen(); levelstart: init_level(level); level_over=0; redraw_map(current_map); // main game loop while(!level_over) { byte k,nx,ny,c,ok; char temp[32]; //Delays not needed with new smiler/map draw sequence //Slight delay //main_loop_delay(16); //halt to syn with screen raster //#asm //halt //#endasm //keep this commented out to enable keyboard auto-repeat //in_WaitForNoKey(); while(!(k=in_Inkey())); //'x' key quits the game if(k=='x') goto roswell; if(k=='r') { set_status_line("Restarting level!"); my_bit_fx(4,3,2); level_over=1; } //Switch between Smiler and Grinner if(k=='s') { //update old-coordinates to prevent them erasing //smiler being switched to ox[current_p]=px[current_p]; oy[current_p]=py[current_p]; current_p^=1; redraw_map(current_map); my_bit_fx(1,5,0); } #ifdef AY_VERSION if(k=='0') { music_on++; if(music_on>NUM_TUNES) music_on=0; if(!music_on) { #asm call 56329; #endasm } if(music_on) { select_ay_tune(music_on); } } #endif //store old position values for erasing old sprite in draw routine ox[current_p]=px[current_p];oy[current_p]=py[current_p]; nx=px[current_p]+(((k=='p')-(k=='o'))<<1); ny=py[current_p]+(((k=='a')-(k=='q'))<<1); c=current_map[(ny<<3)+(nx>>1)]; //Debugging //sprintf(temp,"%d %d %d %d %d \0",px,py,nx,ny,(int)c); //printstrat32(0,18,temp) ok=0; if(c>='a'&&c<='z') { byte d=c&0x03; ok=((d==0)&&(k=='q'))||((d==1)&&(k=='p'))|| ((d==2)&&(k=='a'))||((d==3)&&(k=='o')); } rotate_flag=0; //if can move into square and have actually moved on this turn if((c==32||c=='H'||ok)&&(px[current_p]!=nx||py[current_p]!=ny)) { //set new value px[current_p]=nx;py[current_p]=ny; //Check if stepped onto a frowner for(i=0,killed=0;i>3)&0xfe; of=0; if(x==px[0]&&y==py[0]) { //Erase old smiler if moved off a space if((ox[0]!=x||oy[0]!=y)&&(m[(oy[0]<<3)+(ox[0]>>1)]==32)) { //check for other smiler if(ox[0]==px[1]&&oy[0]==py[1]) { fast_printcol2x2udgat32(0,1,ox[0],oy[0],152); } else { fast_printcol2x2udgat32(0,0,ox[0],oy[0],252); //blank space } } //Draw Smiler fast_printcol2x2udgat32(0,(current_p?1:6),x,y,(current_p?152:148)); of=1; } if(x==px[1]&&y==py[1]) { //Erase old grinner if moved off a space if((ox[1]!=x||oy[1]!=y)&&(m[(oy[1]<<3)+(ox[1]>>1)]==32)) { //check for other smiler if(ox[1]==px[0]&&oy[1]==py[0]) { fast_printcol2x2udgat32(0,1,ox[1],oy[1],152); } else { fast_printcol2x2udgat32(0,0,ox[1],oy[1],252); //blank space } } //Draw Grinner fast_printcol2x2udgat32(0,(current_p?4:1),x,y,(current_p?148:152)); of=1; } if(!of) { //print a map tile or home //Probably not much faster to skip edges //if((x>0x00)&&(x<0x1e) //All lower case letters are arrows if(m[i]>='a'&&m[i]<='z') { fast_printcol2x2udgat32(5,arrow_colour[(m[i]&0x0f)>>2],x,y,132+((m[i]&0x03)<<2)); //arrow } else if(m[i]=='H') { fast_printcol2x2udgat32(4,0,x,y,128); } } } //draw antagonists for(i=0;i>1)]==32)) fast_printcol2x2udgat32(0,0,oax[i],oay[i],252); //blank space //Draw antagonist fast_printcol2x2udgat32(0,2+(i&1),ax[i],ay[i],156); } } } void draw_map_outline(char *m) { byte i,x,y; num_antagonists=0; for(i=0;i<32;i+=2) { fast_printcol2x2udgat32(2,0,i,0,128); fast_printcol2x2udgat32(2,0,i,20,128); fast_printcol2x2udgat32(2,0,0,(i<20?i:0),128); fast_printcol2x2udgat32(2,0,30,(i<20?i:0),128); } for(i=16;i<160;i++) { x=(i&0x0f)<<1; y=(i>>3)&0xfe; //print just the houses/buildings here //skip map edges for speedup if((x>0x00)&&(x<0x1e)) { switch(m[i]) { case '#': fast_printcol2x2udgat32(2,0,x,y,128); //house break; case 'S': //smiley man ox[0]=px[0]=x; //set starting position oy[0]=py[0]=y; m[i]=' '; //blank the smiley off the current map break; case 'G': //Grinner ox[1]=px[1]=x; //set starting position oy[1]=py[1]=y; m[i]=' '; //blank Grinner off the current map break; // now printed in redraw_map()as it is possible to step off "home" // in this game //case 'H': // //home // fast_printcol2x2udgat32(4,0,x,y,128); // break; case 'a': //antagonist oax[num_antagonists]=ax[num_antagonists]=x; //set starting position oay[num_antagonists]=ay[num_antagonists]=y; m[i]=' '; //blank antagonist off current map num_antagonists++; //next antagonist if(num_antagonists>MAX_ANTAGONISTS) bombout("Too many antagonists on level - increase MAX_ANTAGONISTS"); break; default: break; } } } } void rotate_arrows(char *m, byte rotator) { byte i; switch(rotator) { //the clockwise rotators case 'd':case 'e':case 'f':case 'g': for(i=16;i='a'&&m[i]<='z') m[i]=(m[i]&0xfc)+((m[i]+1)&3); break; //the anti-clockwise rotators case 'h':case 'i':case 'j':case 'k': for(i=16;i='a'&&m[i]<='z') m[i]=(m[i]&0xfc)+((m[i]-1)&3); break; //these rotators turn themselves 180deg and also // turn defg clockwise and hijk anti-clockwise //leave pqrs untouched case 'l':case 'm':case 'n':case 'o': for(i=16;i>1)]; //Check for arrow move ok=0; if(c>='a'&&c<='z') { byte d=c&0x03; ok=((d==0)&&(dy==-2))||((d==1)&&(dx==2))|| ((d==2)&&(dy==2))||((d==3)&&(dx==-2)); } //check for no other antagonists on target square noa=1; for(j=0;j='p'&&c<='s') { redraw_map(m); ax[i]=0; //de-activate antagonist my_bit_fx(1,1,1); } } } return killed; } byte passcode_screen(void) { char buf[7]; byte i,k,match; setcolmode(32); colborcls(7,0,0,0,0); //clear screen and set border printstrat32(4,10,"Enter level Passcode:"); printstrat32(1,14,"or press ENTER for new game"); for(i=0;i<6;i++) printstrat32(9+(i<<1),12,"*"); i=0; while(i<6) { in_WaitForNoKey(); k=0; while(!k) { k=in_Inkey(); //if ENTER pressed then just start on level 1 if(k==13) return 1; //allow only numeric keys and delete if(((k<'0')||(k>'9'))&&(k!=12)) k=0; } //debugging //printf("%d",k); if(k==12) { if(i) i--; printstrat32(9+(i<<1),12,"*"); //my_bit_fx(1,1,0); } else { buf[i]=k;buf[i+1]=0; printstrat32(9+(i<<1),12,buf+i); //my_bit_fx(1,6,0); i++; } } match=0; i=0; while(i