/*Smiler 2: In the city Version 1.0 Digital Prawn February 2010 Public Domain*/ #include #include #include #include #include #include #include #include //For debugging/playtesting #define STARTING_LEVEL 1 /* Memory Map 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 Chip music player (1617 bytes) 58479 AY Chip music data (1748 bytes) 63488 1K HEAP for malloc() requests. (Could reach max range of 60227-64511) 64512 UDGs Vortex Tracker II - must export with player address of 56321 56321=AY_INIT() 56326=AY_PLAY() 56329=AY_MUTE() */ //If defined then compile code for AY chip music //which must be appended onto the tapefile #define AY_VERSION #define LEVEL_SIZE 128 #define HEAP_ADDR 63488 #define UDG_ADDR 64512 #define WAIT_KEY while(!in_Inkey()) #define NUM_LEVELS 20 typedef unsigned char byte; typedef unsigned int word; //Globals // 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 */ /* 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 "# l # #d #" "# # # ##gSe## #" "#j# h# k #" "# h# k# #d#l#ih#" "# lH#imk n fh# #" "# m l # # #" "################", "################" //level 2 "# n hi m #" "#ih# #d###f### #" "# #S e #egeH# #" "# # # ###d# # #" "#jk#i# #hf#n# #" "# fej# de #" "################", "################" //level 3 "##Higj mde d #" "#S#km# e# i# n#" "# # oh# # m# #" "#mn # #def# # #" "# #md# # # dl#" "# l klf l #" "################", "################" //level 4 "# n m j#m #" "# # #d# f # o#" "# k# lhjk e#n #" "# j# #S#ddd#fnj#" "# id #fklmodom#" "# # ####Hh#" "################", "################" //level 5 "# i j j j jH#" "# #e#n#n#o#f## #" "#nnnodefe l e#" "#i## # # #e# # #" "#hji m jihk o#" "#S# # # # # # #" "################", "################" //level 6 "#S fde n df#" "# # # # # # #" "#jelk #d#i#m# #" "#edfgj#f#m#o# l#" "#domln# # #n#ej#" "#fdegh h h kH#" "################", "################" //level 7 "# knmo oed #" "# ## # # # j#" "#le #kkk # #H#" "#S#f# ilo # # k#" "#n # # ## # #" "#om ifdef ken #" "################", "################" //level 8 "#okokd# jeio S#" "#e#H#j#enem# #" "#hedfm#dgg# nd #" "#oggdh#ki###do##" "#dggge#h# hi #" "#fnmfe dom #" "################", "################" //level 9 "#lndfengdnlnhgi#" "#m#e#n#d#g#d#j #" "#Hfnflfddnfife #" "# #f#d#e#g#h# l#" "#d jeggSdikk in#" "# #o#m#l#o#m# m#" "################", "################" //level 10 "#jnomdef lon#" "#d############d#" "#S j H#" "#e############d#" "#hljhikffelnmle#" "#mnloheddghkkko#" "################", "################" //level 11 "# ji dfe kl #" "# # e#Solmn# e#" "#hji##### #kg #" "# d #H # e#" "# ## #kg#### #" "# efg g jgnlm#" "################", "################" //level 12 "#S g n # #" "#l# e# nm# o#e#" "# #j # #l l#hj#" "#kok# d#m # hf#" "# # n #fi# #" "# dij# #Hd #" "################", "################" //level 13 "# d f g #" "#jlmf#ehi#kj#nl#" "# nn #h S#" "# #o # j# # #" "#hdledfhmnjokhi#" "# Hl# ll j #" "################", "################" //level 14 "# fH# fe n #" "# f#o###g# o #" "#fde#d#S #kgfe#" "#mln# #djim#" "# ni#j###e# l #" "# o j lne m #" "################", "################" //level 15 "# f o g #" "# #d#o#d#o##g#i#" "# f k g #" "# #k##e#h#g#e#o#" "# j #nm f #" "# o S#Hm f #" "################", "################" //level 16 "#H did nkg#" "#######d########" "# gmmo efom #" "############j###" "# jjh gjf enf#" "#S oedf #" "################", "################" //level 17 "# ned fnl #" "# jmnH## leeeod#" "# o#######l # #" "# d #mojlnl # #" "# j ##Smn #" "# lomdeg #" "################", "################" //level 18 "# g dkhe h i #" "# # ######### #" "# d #S e# dH#l #" "# ### # ### #" "##o e #m#n# ej #" "# # gddfef #" "################", "################" //level 19 "#Sijjjjhijkjkjj#" "#jgkd#fm#dn#gdd#" "#ololnlonolomlm#" "#fidk#fm#fo#gdk#" "#eeedeedgegfeed#" "#onmokhihgeedHo#" "################", "################" //level 20 "#HdkjkddendmfgS#" "#noffejikoeoeno#" "#lgoffmifdfkkoh#" "#milfeffeefdgmn#" "#fnjognomfojmfl#" "#deegnomnilidde#" "################" }; char *level_name[]= { "Concrete Bungle", "Neighbourhood Watch", "Backstreet Jazz", "Stalag Heights", "Traffic Jam Doughnut", "National Flippy Day", "Turnip Square", "Adulation Street", "In Deep Shrimp", "Bonce Scratcher", "Cat Day Morning", "Bonbon Boulevard", "Planetary Ephemeris", "Ceiling Hog Day", "Spirit of Muffy", "Conker Squad", "Fancy Pants Piazza", "La Rue de Trevor", "Bun Fight District", "Welcome to Annexia" }; //player x and y and old values byte px,py,ox,oy; /*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); /*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 */ void my_bit_fx(byte n, byte m); //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; 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(); 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 in_WaitForNoKey(); while(!(k=in_Inkey())); if(k=='r') { set_status_line("Restarting level!"); for(i=0;i<1;i++) my_bit_fx(4,3); level_over=1; } #ifdef AY_VERSION if(k=='m') { music_on^=1; if(!music_on) { #asm call 56329; #endasm } } #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; if(ok) { rotate_arrows(current_map,c); rotate_flag=1; } if(c=='H') { redraw_map(current_map); set_status_line("Level Complete!"); for(i=0;i<7;i++) my_bit_fx(1,4); level_over=1; level++; } } //only redraw the map if smiler moved redraw_map(current_map); //play arrow sound fx immeditaley after map redraw if(rotate_flag) my_bit_fx(1,6); } if(level<=NUM_LEVELS) { goto levelstart; } else { set_status_line("Congrats! Entire game completed!"); for(i=0;i<3;i++) my_bit_fx(2,4); printf("PRESS A KEY"); WAIT_KEY; goto roswell; } } } void set_udg(byte n, byte d0, byte d1, byte d2, byte d3, byte d4, byte d5, byte d6, byte d7) { byte *a=(byte *)UDG_ADDR+((n-128)<<3); a[0]=d0;a[1]=d1;a[2]=d2;a[3]=d3; a[4]=d4;a[5]=d5;a[6]=d6;a[7]=d7; } void printstrat64(byte x, byte y, char *s) { putchar(22);putchar(32+y);putchar(32+x); printf("%s",s); } void setcolattr(byte i, byte p, byte f, byte b) { putchar(16); putchar(i|0x30); //ink putchar(17); putchar(p|0x30); //paper putchar(18); putchar(f|0x30); //flash putchar(19); putchar(b|0x30); //bright } void printcolstrat64(byte i, byte p, byte f, byte b, byte x, byte y, char *s) { setcolattr(i,p,f,b); printstrat64(x,y,s); } void print2x2udgat64(byte x, byte y, byte u) { char t[3]; t[2]=0; t[0]=u++;t[1]=u++; putchar(22);putchar(32+y);putchar(32+x); printf("%s",t); putchar(22);putchar(33+y);putchar(32+x); t[0]=u++;t[1]=u++; printf("%s",t); } void printcol2x2udgat64(byte i, byte p, byte f, byte b, byte x, byte y, byte u) { setcolattr(i,p,f,b); print2x2udgat64(x,y,u); } void fast_printcol2x2udgat64(byte i, byte p, byte x, byte y, byte u) { putchar(16); putchar(i|0x30); //ink putchar(17); putchar(p|0x30); //paper putchar(22);putchar(32+y);putchar(32+x);putchar(u++);putchar(u++); putchar(22);putchar(33+y);putchar(32+x);putchar(u++);putchar(u++); } void cls(void) { putchar(12); //send a form feed control } void colcls(byte i, byte p, byte f, byte b) { setcolattr(i,p,f,b); cls(); } void colborcls(byte i, byte p, byte f, byte b, byte o) { colcls(i,p,f,b); zx_border2(o); } void setcolmode(byte m) { putchar(1);putchar(m); } void init_session(void) { //Inititialise the heap mallinit(); // set heap as currently empty sbrk(HEAP_ADDR,1024); // add a 1K heap // Init keyboard routines in_GetKeyReset(); //Init the sound system bit_open(); //Init the IM2 interrupt handler for keyboard handling & AY chip music init_im2_handler(); //Alter UDG address to allow full range of 128 UDGs (above heap) putchar(3);putchar(UDG_ADDR/0x0100);putchar(UDG_ADDR%0x0100); //setup UDGs //house sprite set_udg(128,1,3,7,15,31,63,127,255); set_udg(129,128,192,224,240,248,252,254,255); set_udg(130,195,195,195,195,255,255,255,255); set_udg(131,255,255,15,15,15,15,15,15); //apartment sprite //set_udg(128,0x00,0x7f,0x7f,0x49,0x49,0x7f,0x49,0x49); //set_udg(129,0x00,0xfe,0xfe,0x92,0x92,0xfe,0x92,0x92); //set_udg(130,0x7f,0x49,0x49,0x7f,0x7f,0x7e,0x7e,0x7e); //set_udg(131,0xfe,0x92,0x92,0xfe,0xfe,0x7e,0x7e,0x7e); //up-arrow sprite set_udg(132,0xff,0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0xc0); set_udg(133,0xff,0x7f,0x3f,0x1f,0x0f,0x07,0x03,0x03); set_udg(134,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xff,0xff); set_udg(135,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0xff,0xff); //right-arrow sprite set_udg(136,0xff,0xff,0xff,0xff,0xff,0xff,0xc0,0xc0); set_udg(137,0xff,0xff,0x3f,0x1f,0x0f,0x07,0x03,0x01); set_udg(138,0xc0,0xc0,0xff,0xff,0xff,0xff,0xff,0xff); set_udg(139,0x01,0x03,0x07,0x0f,0x1f,0x3f,0xff,0xff); //down-arrow sprite set_udg(140,0xff,0xff,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc); set_udg(141,0xff,0xff,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f); set_udg(142,0xc0,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff); set_udg(143,0x03,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff); //left-arrow sprite set_udg(144,0xff,0xff,0xfc,0xf8,0xf0,0xe0,0xc0,0x80); set_udg(145,0xff,0xff,0xff,0xff,0xff,0xff,0x03,0x03); set_udg(146,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xff,0xff); set_udg(147,0x03,0x03,0xff,0xff,0xff,0xff,0xff,0xff); //Smiley face set_udg(148,0xff,0xfc,0xe0,0xc0,0xc0,0xcc,0x8c,0x80); set_udg(149,0xff,0x3f,0x07,0x03,0x03,0x33,0x31,0x01); set_udg(150,0x81,0x91,0xd0,0xc8,0xc6,0xe1,0xfc,0xff); set_udg(151,0x81,0x89,0x0b,0x13,0x63,0x87,0x3f,0xff); //blank sprite set_udg(252,0,0,0,0,0,0,0,0); set_udg(253,0,0,0,0,0,0,0,0); set_udg(254,0,0,0,0,0,0,0,0); set_udg(255,0,0,0,0,0,0,0,0); //Set 32 column mode setcolmode(32); // Set the screen to black background and border colborcls(0,0,0,0,0); //Allocate memory for current map if(!(current_map=malloc(LEVEL_SIZE))) { bombout("Insufficient RAM"); } } void init_game(void) { //Set 32 column mode for main game setcolmode(32); } void init_level(byte l) { char s[32]; // Set the screen to black background and black border //and clear the screen colborcls(0,0,0,0,0); memcpy(current_map, map[l-1],LEVEL_SIZE); sprintf(s,"Level %d: %s\0",l,level_name[l-1]); set_status_line(s); draw_map_outline(current_map); } void bombout(char *s) { setcolmode(64); colborcls(1,0,0,1,0); puts("Fatal Error:"); puts(s); exit(0); } void redraw_map(char *m) { byte i,x,y; //skip top and bottom for(i=16;i<112;i++) { x=(i&0x0f)<<1; y=(i>>3)&0x0e; 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,4-((m[i]&0x0f)>>2),x,y,132+((m[i]&0x03)<<2)); //arrow } } } } void draw_map_outline(char *m) { byte i,x,y; for(i=0;i<32;i+=2) { fast_printcol2x2udgat32(2,0,i,0,128); fast_printcol2x2udgat32(2,0,i,14,128); fast_printcol2x2udgat32(2,0,0,(i&0xfc)>>1,128); fast_printcol2x2udgat32(2,0,30,(i&0xfc)>>1,128); } for(i=16;i<112;i++) { x=(i&0x0f)<<1; y=(i>>3)&0x0e; //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; 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 case 'l':case 'm':case 'n':case 'o': for(i=16;i