/*Smiler 3: Travels around Blighty Version 1.0 Digital Prawn 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 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 4 //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 //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 legths of all loaded tunes #ifdef AY_VERSION word tune_addr[NUM_TUNES]={24576,26157,27167,27843}; word tune_len[NUM_TUNES]={1581,1010,676,1132}; #endif //level passcodes char *passcode[]= { "035248", "664091", "253469", "732124", "037135", "231975", "733364", "409745", "631075", "778691", "136425", "918476", "816013", "796803", "593352", "687431", "922899", "693396", "675477", "036383" }; // 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 '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' = Green arrows 't','u','v',w' = Reserved for future arrows 'x','y','z','{' = Reserved for future arrows a = antagonist */ /* 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 */ /* level map data */ char *map[]= { "################" //level 1 "#S #" "#d#e#f#g#h#i#jk#" "# l m n o p q #" "#d#e#f#g#h#i#rs#" "# jak l m nao #" "#r#s#d#e#f#g#pq#" "# hai j k lam #" "#p#q#r#s#d#e#no#" "# f g h i j k H#" "################", "################" //level 2 "#S #" "# d a a #" "# h #" "# #####jki##" "# l # e #" "# iq eidefg#" "# a hr d fa a#" "#a l hp h fa a#" "# # k g H#" "################", "################" //level 3 "# j oa f #" "# f# #H# n#ddd#" "#ieg#dd# # l #" "# g # # a #" "# g # # #" "#ef#lop# #jik#" "# # # jik#" "#hjln#ddf#ijjnk#" "# f g S#" "################", "################" //level 4 "# gade okjgS#" "#l## l##d ##f #" "# jki#m i# o #" "# ndf#k e# p #" "# m gf## de lj#" "#fl## nnl ## e#" "#kg a#hl # h #f#" "#hfed#dmlk#m #m#" "#Heieio n n#" "################", "################" //level 5 "#Hifdke gekghk #" "#kj#dli#l# #m# #" "# defeg s dodk#" "#a #j#l#f# # #f#" "#############n #" "# j# # # # # #j#" "#d dkekfji g l#" "#m ee pk hg #" "#S #n#l#n#g# #a#" "################", "################" //level 6 "#a hm glnh edka#" "#d# d## #dee#e#" "#ef# #qs# #gd#" "#qe daH k#nhs#" "#fdd ##### ejlo#" "# ##edS d# fl#" "#e jgl#a #jm#dh#" "# # kq# d#pi #" "#g d feg i k#" "################", "################" //level 7 "##lninl o n#" "# jgil#ed# la m#" "# io S# # q #" "# #####fl#####k#" "#kk j p a o d#" "# #####mg#####j#" "# edj # #H #k #" "# gfh #km#a po #" "#edf oe ejgjef #" "################", "################" //level 8 "# lnlada jil #" "# r # s #" "#df#ied#djk#gki#" "# # g#f # #" "# kjl##Hi#lkh #" "#a eehgd a#" "#l###########mn#" "#pjk # kgq#" "# fd S ih #" "################", "################" //level 9 "#h # k # H#" "#Sl # j # #" "#hkg#fje#kle#fd#" "#ddf#ikn#mfn#mi#" "#r a#a r#a s#aq#" "#eof#fgg#oig#eo#" "#ifm#rnd#kji#he#" "# d # en #" "# g # f #" "################", "################" //level 10 "# o i j h #" "#fd#S##i jl##kj#" "# # ## ## #" "#eg#n qff e# #" "# ## a ## #a #" "# a##gijh##d# #" "#hnko r dg fe#" "# ########## #" "# e H h ke #" "################", "################" //level 11 "# e g H#" "#ljkm######nfgo#" "# g dejfg# #" "#ej galak#mkgi#" "# # foSmi# # #" "# # hanae#e#jd#" "# #sredjfk# # #" "# r i #" "# q n #" "################", "################" //level 12 "#H e o kl d #" "########### g #" "# fj mei#" "# ############" "#snlk a jmog #" "# jk a feq #" "############ #" "# de pfn #" "#S nol kofj #" "################", "################" //level 13 "#S dd a#" "# fghk q ##" "#jekl## lnom####" "# # # j gh#" "# r###### haj #" "#jikl fijoledf#" "# i # kg #" "# fed s###hkg#" "# a jikg hH#" "################", "################" //level 14 "# k S#" "# # #q#q# # # #" "# a a a a a #" "# # # # # # # #" "# a a a a af#" "# # # # # # # #" "# a a a apa #" "# # # # # # # #" "# # H#" "################", "################" //level 15 "#S# a # #" "#je#noddi#dlg ##" "# # # #a#" "#nolm# # hj # #" "## q #dee#" "#a#jdfgkil r #" "# # # #" "# p kk #fgi#" "#nfid# lm # H#" "################", "################" //level 16 "#Sda####H n #" "#nij# #dofid#" "#kkfoo## #f a#" "# # leed## gje #" "#j# joek## i h#" "# # # jfk #" "#d # deklom #" "#klm#### gd #" "# a# ej #" "################", "################" //level 17 "#d a k f d#" "# f h g #" "# ag ag ak #" "# h g f #" "# j h j S#" "# ai aq am #" "# k m o #" "#ne l j l #" "#Hd m m k d#" "################", "################" //level 18 "# dijon dj#" "#ale ###### #" "# # ag jked#" "#llll#H ef #" "#joffa#### ilk#" "# eek S#mnol#" "# kdk # #" "# ##### e #" "#fdi fddi #" "################", "################" //level 19 "#S # H#" "# n # ddg #" "#rnoiej#deekjim#" "# #lomjaris#" "#delfon# qio #" "#a # def a#" "#lmonki#diffjop#" "# #" "#hello nipples #" "################", "################" //level 20 "#kdijhgfagmikol#" "#djfhdilaedfgih#" "#jhiionkanmfedi#" "#lmnoklqafddgkk#" "#SmgfeklaihljmH#" "#jnedgkoajjhilk#" "#geedghiageglom#" "#fffgjkladfejie#" "#podfeghajegnkl#" "################" }; char *level_name[]= { "Wolverhampton", "Colwyn Bay", "Wigan", "Morecambe", "Barrow-in-Furness", "Sellafield", "Cumbernauld", "Glenrothes", "Middlesbrough", "Hull", "Dove Holes", "Lowestoft", "Bracknell", "Slough", "Leatherhead", "Uckfield", "Swanage", "Camborne", "Maesteg", "Bromsgrove" }; //player x and y and old values byte px,py,ox,oy; //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; } #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=px;oy=py; nx=px+(((k=='p')-(k=='o'))<<1); ny=py+(((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!=nx||py!=ny)) { //set new value px=nx;py=ny; //Check if stepped onto a frowner for(i=0,killed=0;i>3)&0xfe; if(x==px&&y==py) { //Erase old smiler if moved off a space if((ox!=x||oy!=y)&&(m[(oy<<3)+(ox>>1)]==32)) fast_printcol2x2udgat32(0,0,ox,oy,252); //blank space //Draw smiler fast_printcol2x2udgat32(0,6,x,y,148); } else { //print a map tile //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 } } } //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,ax[i],ay[i],152); } } } 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=px=x; //set starting position oy=py=y; m[i]=' '; //blank the smiley off the current map break; 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