/* * SMALL.c * * Source code for the 'tinygames' project. * * Created: 11/22/2016 5:30:05 PM * Author: Mark Sherman * Copyright (c) 2016/2017 Mark W. Sherman, GPL v2 or higher * exceptions: function 'wait_until' is Copyright (c) 2010 Myles Metzer */ /* Timing CRAP */ /* I am using a 10.7Mhz crystal, beacuse that's what I had sitting around. If you change F_CPU, you need to scale the following constants proportionally */ #define F_CPU 10752000L /* How many clocks make about 1 microsecond */ #define U_SECOND_CLOCKS 11 /* How many clocks for 1 line of video (about 15 khz) */ #define LINE_CLOCKS 682 /* Half of LINE_CLOCKS*/ #define HALF_LINE_CLOCKS 341 /* Bring in standard stuff */ #include #include #include #define NTSC_PORT PORTA #define NTSC_PIN PINA #define NTSC_DDR DDRA #define NTSC_LUM 0b00000010 #define NTSC_SYNC 0b10000000 #define NTSC_DDR_MASK (NTSC_LUM | NTSC_SYNC) /* Setting NTSC_PORT to NTSC_BLACK or NTSC_WHITE will also leave the sync line high */ /* These are so you can set the output color with OUT instead of SBI/CBI and saves 1 clock cycle*/ #define NTSC_BLACK NTSC_SYNC #define NTSC_WHITE (NTSC_LUM | NTSC_SYNC) /* Which line is currently being drawn. */ unsigned int line; /* Taken from CAT-644 VGA routines and hacked up a bit */ inline void ntsc_init() { TIMSK1=_BV(OCIE1A); /* use timer interrupt */ TCCR1B = (1<=6 && line <12 ) { OCR1A = HALF_LINE_CLOCKS; /* vsync lines are 1/2 length*/ wait_until(HSYNCSTART- ( ( U_SECOND_CLOCKS * 440L ) /100 ) ); NTSC_PORT |= NTSC_SYNC; //sync high wait_until(HSYNCSTART); NTSC_PORT &= ~NTSC_SYNC; //sync low vsync is normally low durring verical serrations . This is timed so the low transition is the same as when it does for hsync goto end; //line++; //return; //scync was easy, maybe we can go back to the main program } if (line==12) { OCR1A = LINE_CLOCKS ; //back to regular sized lines scrpos=0; //reset screen pos counter } wait_until(HSYNCSTART); NTSC_PORT &= ~NTSC_SYNC; /* sync low */ wait_until(HSYNCSTART + ( ( U_SECOND_CLOCKS * 425L ) /100 ) ); /* 4.25 microseconds */ NTSC_PORT |= NTSC_SYNC; /* sync high */ wait_until(HSYNCSTART + ( ( U_SECOND_CLOCKS * (425+800L) ) /100 ) ); /* 8 microseconds */ /* On the line of a bullet, we actually don't draw anything else */ if (line == bullety) { for (i=-5;i!= bulletx;i++) { /* Delay to bullet position. -5 determined by experimentation to match player position */ NTSC_PORT=NTSC_BLACK; } NTSC_PORT=NTSC_WHITE; NTSC_PORT=NTSC_WHITE; NTSC_PORT=NTSC_WHITE; NTSC_PORT=NTSC_WHITE; NTSC_PORT=NTSC_BLACK; goto end; /* jumping to end to line++ and return is less code than doing it here */ //line++; //return; } if (line == 262 + 3 ) { /* we are on the 263nd line. 262 because 1st line is 0. Then +3 because vsync lines are double speed*/ line=0; /*next up is vsync*/ field^=1; /*switch fields*/ /* On odd frames we draw mushrooms, on even frame we draw bugs */ if (field){ drawpos=&mushrooms[0]; } else{ drawpos=&bugs[0]; } return; /* note we don't line++, because we just reset it to 0 */ } if ((line>64)&&(line <224)) { /*Active video lines */ scrposo=scrpos; /*in case we need to reset*/ drawposold = drawpos; /*in case we need to reset*/ if (field){ sprite = mushpxp[ line&7]; } else{ sprite = bugpxp[ line&7]; } for (i=0;i>1; NTSC_PORT=shifter; shifter=shifter>>1; NTSC_PORT=shifter; shifter=shifter>>1; NTSC_PORT=shifter; shifter=shifter>>1; NTSC_PORT=shifter; shifter=shifter>>1; NTSC_PORT=shifter; NTSC_PORT=NTSC_BLACK; /* to keep last pixel from being stuck on */ scrpos++; } if ((line & 7 )!= 7) { //all but the 7th line of a sprite, reset this scrpos=scrposo; drawpos=drawposold; } } if (line >240) { for (i=0;i< playerx;i++) { //delay NTSC_PORT=NTSC_BLACK; } for (i=0;i<4;i++) NTSC_PORT|=NTSC_WHITE; NTSC_PORT=NTSC_BLACK; } end: line++; } /* joystick inputs */ #define JOYSTICK PINA #define FIRE 0b00100000 #define LEFT 0b00001000 #define RIGHT 0b00010000 void main(void) { unsigned int i; int j; unsigned char t; unsigned char* bugp; unsigned char* mushp; unsigned char* nmushp; unsigned char bulletpos; unsigned char abug=0; unsigned char tick=0; unsigned char insmush; volatile unsigned char* bx = &bulletx; volatile unsigned char* by = &bullety; /* volatile unsigned char* px = &playerx; //Use if necessary */ //scrpos=0; ntsc_init(); while(1){ for (i=58000;i;i++) { __asm__ __volatile__ ( ""); /* This makes the delay loop actually happen */ } tick = (tick +1) & 0xF; //tick counter 0 to 15 /* advance the player */ if (! (JOYSTICK&LEFT)) playerx--; // *((volatile char*)(&playerx)) -= 1; //force update to interrupt handler if (! (JOYSTICK&RIGHT)) playerx++; // *((volatile char*)(&playerx)) += 1; //force update to interrupt handler /* restrict player motion */ playerx&=31; bulletpos = INVALID; if (bullety< 64 ) { /* reset bullet position */ bulletx=playerx; /* So bullet lines up with where the player was. Determined by experimentation */ bullety=2; //off the screen if (!(JOYSTICK&FIRE)) /* If the fire button is pressed, shoot off a bullet */ bullety=240; } else { /* move bullet up */ bullety-=2; // very important we never interrupt ODD scanlines (every 7th line updates the sprite counters, and drawing the bullet suppresses a row of pixels) bulletpos = (bullety - 64) / 8 * SCRCOLS + (bulletx/4); /*turn bullet x/y into approximate screen position number */ if (bulletx>20) /* Correction factor because integer division sucks */ bulletpos++; /* //For calibratation purposes These mushrooms are the leftmost, rightmost, and bullet positions. mushrooms[0] = bulletpos % SCRCOLS; //show the column we shoot in mushrooms[1] = SCRCOLS; //show the column we shoot in mushrooms[2] = 2*SCRCOLS -1; */ } /* Update bullet via the volatile pointers */ *bx=bulletx; *by=bullety; /* gcc optimizes by keeping bx and by in registers. But we need the interrupt routine to see it. Normally, you would just make bulletx/y as volatile, but that adds extra instructions where I don't need it. I just need to 'poke' these vars at least once per main loop */ //*px=playerx; /* NOTE: for some reason we don't need to update playerx through a volatile. I guess gcc spills this var to memory at least once per main loop If the player position becomes stuck, you have to re-volatilize-it */ resort: /* Yes, there is a goto down below, deal with it */ /* go through all bugs */ for (bugp=bugs; *bugp != ENDOFLINE; bugp++) { if (tick==0) { /* we are on an update tick: move all the bugs */ if ((*bugp/SCRCOLS)&1) { /*bugs move left on odd rows*/ if ((*bugp % SCRCOLS) ==0) *bugp += (SCRCOLS+1); /* +1 is because we un-advance in next statement. */ /* we could easily not do this and instead do an else, but that adds an extra jump statement to get around the else clause */ (*bugp)--; } else { /*bugs move left on even rows*/ if ((*bugp % SCRCOLS) ==(SCRCOLS-1)) *bugp += (SCRCOLS-1); /* -1 is because we advance in next statement. */ /* we could easily not do this and instead do an else, but that adds an extra jump statement to get around the else clause */ (*bugp)++; } } else { /* if we are not on an update tick, we can safely move around the bug ordering to repair the sort */ if ( (*bugp) >= (*(bugp+1)) ) { t=*bugp; if ( (*bugp) == (*(bugp+1))) t--; /*on conflict move one bug back*/ *bugp=*(bugp+1); *(bugp+1) = t; goto resort; /* We use the goto to force a re-sorting. If we don't it takes too many main loop iterations to fix the order, and the display looks bad. We can repeat these gotos as many times as we need because we are NOT in an update tick. */ } } /* scan thru all mushrooms */ for (mushp=mushrooms;*mushp!=ENDOFLINE;mushp++) { if (tick==0) { /* if on updatetick jump down 1 row when bug hits mushroom */ if (*mushp == *bugp) (*bugp)+=SCRCOLS; } if (bulletpos == *mushp) { bullety=0; *mushp = ERASED; /* erase a mushroom if touched by bullet */ } /* if we have a free mushroom slot, insert pending mushroom*/ if (*mushp==ERASED){ *mushp=insmush; insmush = ERASED; } /* swap if we find out of order */ /* note, we don't need a goto, as mushrooms are scanned much more often than bugs because its the inner loop */ if ( *(mushp) > *(mushp+1) ) { t=*mushp; *mushp=*(mushp+1); *(mushp+1) = t; } } /* end mushroom loop */ /* Check if bug is shot */ if (bulletpos == *bugp) { bullety=0; insmush = bulletpos; //set mushroom insert *bugp = 0; //when shot a new bug appears } } /* end bug loop */ }// end main looop } //end main