; ; VCR PONG ; Copyright 1998 David B. Thomas ; dt@dt.prohosting.com ; ; Code may be freely redistributed and modified for educational purposes. ; ; With this program, a PIC16C711 can act as a pong game, generating ; genuine NTSC video, with only the smallest handful of external parts. ; ; The code is written in the form of a generic NTSC video "engine", ; with a "user" application portion that implements pong. It is hoped ; that others can rip the pong game out and insert their own video ; applications. ; ; At the heart of this hack is a one-transistor driver circuit ; connected to a single PIC pin. The driver circuit pulls the video ; output to 1V (white level) when the PIC pin is high, 0V (sync level) ; when the PIC pin is low, and .3V (black level) when the PIC pin ; is tristated (configured as an input). By flipping between those ; three states with just the right timings, the PIC actually spews ; out properly-timed black-and-white NTSC video! ; ; This code uses the Parallax pseudo-ops and must be assembled with ; Parallax's SPASM.EXE, available for free from Parallax's web site ; http://www.parallaxinc.com/ or Tech-Tools's CVASM16, available ; for evaluation from Tech-Tools's web site ; http://www.tech-tools.com/ While you must use one of these ; assemblers to produce the object file, you may use any device ; programmer, including Microchip's PICSTART or PRO-MATE. ; ; For schematic, parts list and any other information about ; this project, see http://dt.prohosting.com/pic/pong.html ; DEVICE PIC16C711,WDT_OFF,PROTECT_OFF,HS_OSC,PWRT_ON ;***************************** ; USER PORT DIRECTION SETTINGS ;***************************** ; note: bit 7 of TRIS_PORTB must be 0. TRIS_PORTA equ 00000011b ; paddle inputs TRIS_PORTB equ 0 ; next user-serviceable code after engine code ;************************* ; ENGINE CODE: DON'T TOUCH ;************************* ; port/tris assignments VPORT equ PORTB VTRIS equ TRISB|80h ; 2nd reg page video_out equ PORTB.7 vport_tris equ INDIRECT ; (careful with FSR) video_tris equ INDIRECT.7 TRIS_WHITE equ TRIS_PORTB TRIS_BLACK equ TRIS_PORTB|80h TRIS_SYNC equ TRIS_PORTB ; for white: video_out=1; video_tris=0 or vport_tris=TRIS_WHITE ; for sync: video_out=0; video_tris=0 or vport_tris=TRIS_SYNC ; for black: video_tris=1 or vport_tris=TRIS_BLACK ; constants PRE_EQ_HALFLINES equ 6 ; pre eq halflines POST_EQ_HALFLINES equ 6 ; post eq halflines (7 for field 1) VERTICAL_SYNC_HALFLINES equ 6 ; v sync halflines SCAN_LINES equ 252 ; vert int plus active thru L261 ; general register assignments t0 equ 0ch ; loop counter preeq_count equ 0dh ; counts pre-eq pulses vsync_count equ 0eh ; counts vert sync pulses posteq_count equ 0fh ; counts post-eq pulses field equ 10h ; 0 or ~0: field indicator line equ 11h ; counts lines down from 252 to 1 org 0 jmp Init org 4 Init ;; set INDIRECT to point to VTRIS, so the video_out pin ;; can be tristated in one machine cycle. mov FSR,#VTRIS ;; set initial port directions setb RP0 mov TRISA,#TRIS_PORTA mov TRISB,#TRIS_PORTB clrb RP0 clrb video_out ; sync when !tris mov field,#-1 ; field is flipped right away call UserInit ; start application ; ; FieldTop ; ; Vertical blanking happens 60 times a second, once at the beginning ; of each field. At the beginning of field 0, vertical blanking ; occurs just after the last line of the previous field, coincident ; with the next horizontal sync pulse. At the beginning of field 1, ; vertical blanking begins right in the middle of a line. (The ; reason is that the total number of scan lines is odd. This also ; lets the TV know which field is which.) ; FieldTop ; before jumping here: ; video_out must be low (sync when !tris) ; video_tris must be high ; move on to next field not field ; 124/251/-3 ; The first 3 lines of a field consist of 6 ; equalizing pulses, two to a line. ; The pulses are 2.5 uS wide (10 machine steps), ; spaced 31.75 uS (half a line, 127 machine steps) ; apart. mov W,#PRE_EQ_HALFLINES ; 125/252/-2 mov preeq_count,W ; 126/253/-1 PreEQ clrb video_tris ; sync now! ; 127/254/0! ; while killing time, get ready for next segment mov W,#VERTICAL_SYNC_HALFLINES ; 1 mov vsync_count,W ; 2 nop ; 3 nop ; 4 nop ; 5 nop ; 6 nop ; 7 nop ; 8 nop ; 9 setb video_tris ; black now! ; 10! ; next pulse comes 117 machine steps later nop ; 11 mov W,#37 ; 12 mov t0,W ; 13 :preloop decsz t0 ; 122 (last) jmp :preloop ; 123 decsz preeq_count ; next pulse of 6 ; 124 jmp PreEQ ; skipped cycle due to jmp ; 126/-1 ; fallthru case nop ; 126/-1 ; all 6 pre-equalizing pulses have been generated. ; now generate the actual vertical sync, which is ; 3 lines' worth of pulses that stay at sync ; level for 27.25 uS (109 machine steps), then ; rise to black level for 4.5 uS (18 steps). VSync clrb video_tris ; sync now! ; 127/0! ; while killing time, get ready for next segment mov W,#POST_EQ_HALFLINES ; 1 mov posteq_count,W ; 2 btfsc field.0 ; 3 inc posteq_count; 7 pulses if field 1; 4 mov W,#34 ; 5 mov t0,W ; 6 :syncloop0 decsz t0 ; 106 (last) jmp :syncloop0 ; 107 nop ; 108 setb video_tris ; black now! ; 109! nop ; 110 mov W,#4 ; 111 mov t0,W ; 112 :syncloop1 decsz t0 ; 122 (last) jmp :syncloop1 ; 123 decsz vsync_count ; 124 jmp VSync ; 125/-2 ; skipped cycle due to jmp ; 126/-1 ; fallthru case nop ; 126/-1 ; now we need to generate the post-equalizing ; pulses. Like the pre-equalizing pulses, these ; are pulses 2.5 uS wide (10 machine steps), ; spaced 31.75 uS (half a line, 127 machine steps) ; apart. On odd fields, we need an extra post-eq ; pulse to get back in phase with the horizontal ; sync. PostEQ clrb video_tris ; sync now! ; 127/0! ; while killing time, get ready for next segment mov W,#SCAN_LINES ; 1 mov line,W ; 2 nop ; 3 nop ; 4 nop ; 5 nop ; 6 nop ; 7 nop ; 8 nop ; 9 setb video_tris ; black now! ; 10! ; next pulse comes 117 machine steps later nop ; 11 mov W,#37 ; 12 mov t0,W ; 13 :postloop decsz t0 ; 122 (last) jmp :postloop ; 123 decsz posteq_count ; next pulse of 6 or 7; 124 jmp PostEQ ; 125/-2 ; skipped cycle due to jmp ; 126/-1 ; fallthru case nop ; 126/-1 ; Lines 10 to 261 are treated the same, though ; they aren't exactly. Lines 10-21 are part of ; the vertical interval and should be blank. ; Lines 22-261 may contain picture information. ScanLine ; horizontal sync lasts 4.75 uS (19 steps) clrb video_tris ; 254/0! call UserPreLine ; 1,2,[3-18] setb video_tris ; black now! ; 19! setb video_out ; white when !TRIS ; 20 ; back porch lasts 4.75 uS (19 steps), then ; active portion of video line starts. Callee ; at routine "UserLine" must leave video at ; black level at least until cycle 38. ; Video must return to black level by cycle ; 248, for the front porch. call UserLine ; 21,22,[23-249] clrb video_out ; sync when !tris ; 250 decsz line ; 251 jmp ScanLine ; 252/-2 ; cycle skipped due to jmp ; 253/-1 ; fallthru case nop ; 253/-1 ; now we've reached the bottom of the screen. If this ; is field 0, we need to generate one full blank line ; (line 262), then a half a line, and jump to the vertical ; interval (and thus field 1) right in the middle. Field 0 ; contains 262.5 lines. If this is field 1, we are ; already half a line ahead of the game due to the extra ; post-eq pulse at the top. Thus, we only need to ; generate line 262 and jump to vertical blanking ; right where the next line would have begun. Line262 clrb video_tris ; sync now! ; 254/0! ; sync pulse lasts 4.75 uS (19 steps) mov W,#5 ; 1 mov t0,W ; 2 :loop262s decsz t0 ; 15 (last) jmp :loop262s ; 16 nop ; 17 nop ; 18 setb video_tris ; black now! ; 19! ; remainder of line (active part plus front porch) ; lasts 58.75 uS (235 steps). We leave the output ; at black level throughout, thus flattening the ; front porch. call UserField ; 20,21,[22-247] btfsc field.0 ; 248 jmp FieldTop ; end fld 1: goto fld0 ; 249/-5 ; skipped cycle due to jmp (field 1) ; 250/-4 ; fallthru case ; this is the end of field 0. Generate the ; first half of line 263, then hyperspace ; to vertical blanking right in the middle. ; The sync pulse is 4.75 uS (19 steps) as always. ; The entire half-line lasts 31.75 uS (half of ; 63.5 uS). That half line is 127 machine steps, ; which means that after the 19 step sync pulse, ; we'll need to spend 108 cycles at black level. nop ; 250/-4 nop ; 251/-3 nop ; 252/-2 nop ; 253/-1 clrb video_tris ; 254/0! call UserPreFrame ; 1,2,[3-18] setb video_tris ; black now! ; 19! call UserFrame ; 20,21,[22-121] ; end of field 0: go to field 1. jmp FieldTop ; 122/-5 ; skipped cycle due to jmp ; 123/-4 ;********************** ; USER SERVICEABLE CODE ;********************** ; port assignments pdl1_in equ PORTA.0 ; paddle 1 input pdl2_in equ PORTA.1 ; paddle 2 input ; A/D ADCON0 settings ADCON0_PDL1 equ 10000001b ; settings for paddle 1 ADCON0_PDL2 equ 10001001b ; settings for paddle 2 ADGO equ ADCON0.2 ; the GO/DONE bit ; constants SCORE_TOP equ 225 ; top line of score display PLAYING_FIELD_TOP equ 205 ; top line of "court" PADDLE_HEIGHT equ 24 ; height of paddle HIT_REGION equ 6 ; hit regions for paddle BALLX_MIN equ 1 ; min. ball x coord BALLX_MAX equ 50 ; max. ball x coord BALLY_MIN equ 10 ; lowest ball y coord BALLY_MAX equ 199 ; highest ball y coord BALLX_MIDDLE equ 25 ; serve location for ball BALLY_MIDDLE equ 95 ; serve location for ball BALL_HEIGHT equ 6 ; how tall is the ball SERVE_DELAY equ 120 ; 2 seconds before serve PADDLE_MIN equ 10 ; lowest line paddle may touch PADDLE_MAX equ 205 ; highest line paddle may touch ; general register assignments t1 equ 12h t2 equ 13h bitflags equ 14h ballflag equ bitflags.0 pdl1flag equ bitflags.1 pdl2flag equ bitflags.2 endgame equ bitflags.3 paddle1 equ 15h ; location of paddle 1 paddle2 equ 16h ; location of paddle 2 ballx equ 17h ; ball x location (1-50 ONLY) ballxcomp equ 18h ; always 51-ballx bally equ 19h ; ball y location ballxdir equ 1ah ; ball x direction -1 or 1 ballydir equ 1bh ; ball y dir -2 thru 2 score1 equ 1ch ; player 1 score score2 equ 1dh ; player 2 score servetimer equ 1eh ; timer for serve temppaddle equ 1fh ; ; CharTable ; ; Translates a digit and row into three bits for the left ; middle a right pixels on that row. Call with the digit ; 0-7 times 5 plus the row 0-4. bits 2 thru 0 are left ; middle and right respectively. Row 0 is the top. ; I did it this way so the table would look like the numerals ; in the source code. ; ; Cost of call: 6 cycles: CALL, (skip), JMP, (skip), RET, (skip). ; CharTable jmp PC+W ; 1 ; skipped cycle for jmp ; 2 retw 111b ;0 ; 3... retw 101b retw 101b retw 101b retw 111b retw 001b ;1 retw 001b retw 001b retw 001b retw 001b retw 111b ;2 retw 001b retw 111b retw 100b retw 111b retw 111b ;3 retw 001b retw 111b retw 001b retw 111b retw 101b ;4 retw 101b retw 111b retw 001b retw 001b retw 111b ; 5 retw 100b retw 111b retw 001b retw 111b retw 111b ; 6 retw 100b retw 111b retw 101b retw 111b retw 111b ; 7 retw 001b retw 001b retw 001b retw 001b ; skipped cycle after ret ; 4 ; ; UserInit ; ; called once at start. timing not important. ; UserInit setb RP0 clr ADCON1 ; all RA inputs analog mov OPTION,#11011000b ; T0CS internal clrb RP0 ; initial paddle information (must be in range) mov W,#50 mov paddle1,W mov paddle2,W ; initial ball info (prepare to serve) mov ballx,#BALLX_MIDDLE mov bally,#BALLY_MIDDLE mov ballxcomp,#51 sub ballxcomp,ballx clr ballxdir ; don't move till serve clr ballydir mov servetimer,#SERVE_DELAY ; initial score info clr score1 clr score2 clrb endgame ; not over yet! ; do first a/d conversion to read paddle 1 mov ADCON0,#ADCON0_PDL1 ; ready to read paddle 1 setb ADGO ; start conversion ret ; ; CAUTION: ; these subroutines may be changed but must ; take the **exact** number of cycles required! ; ; ; UserPreLine ; ; called during the horizontal sync of lines 10 thru 261 ; ; occupies cycles 3 thru 18 of the scan line. ; UserPreLine ; see if the ball will be seen on this line setb ballflag ; 3 mov W,line ; if bally > line... ; 4 mov t0,W ; then no ball this line ; 5 mov W,bally ; 6 sub t0,W ; 7 sc ; borrow means no ball ; 8 clrb ballflag ; 9 mov W,#BALL_HEIGHT ; 10 sub t0,W ; 11 snc ; borrow means ball ; 12 clrb ballflag ; 13 nop ; 14 nop ; 15 nop ; 16 ret ; 17 ; skipped cycle ; 18 ; ; UserLine ; ; Called during the active video of lines 10 thru 261. ; Lines 10-21 must be blank (vertical interval). ; 'line' counts down from 252 to 1. Therefore the ; vertical interval occupies counts 252 thru 241, ; and active video from 240 to 1. On most TVs, ; lines 37-247 (210 lines, counted as 225-15) ; are visible. 'field' alternates 0 and -1. ; ; Occupies cycles 23-249 of the scan line. Video must ; remain at black level until the end of the back porch ; (cycle 38) and return to black level by the time the ; front porch begins (cycle 248). On most TVs, the ; scan line is visible from cycle 58 to cycle 228. ; ; To make white: ; clrb video_tris ; -or- ; mov W,#TRIS_WHITE ; mov vport_tris,W ; For black: ; setb video_tris ; -or- ; mov W,#TRIS_BLACK ; mov vport_tris,W ; UserLine ; determine whether we are above the score lines, ; in the score lines, or in the playing field. mov W,#PLAYING_FIELD_TOP+1 ; 23 subwf line,0 ; 24 sc ; 25 jmp :playline ; 26 ; jmp taken ; 27 mov W,#SCORE_TOP+1 ; 27 subwf line,0 ; 28 sc ; 29 jmp :scoreline ; 30 ; jmp taken ; 31 ;***** BLANK LINE ; if we get here, this is one of the blank lines at the ; top (lines 10 thru 36). ; just kill time, then return at the right moment. mov W,#72 ; 31 mov t0,W ; 32 :blanklineloop decsz t0 ; 246 (last) jmp :blanklineloop ; 247 ret ; 248 ; skipped cycle ; 249 ;***** SCORE LINE :scoreline ; This is one of the lines used for displaying the ; score numerals at the top of the screen. mov W,#15 ; 32 mov t0,W ; 33 :score1delay decsz t0 ; 76 (last) jmp :score1delay ; 77 nop ; 78 nop ; 79 ; compute pixels for score1 mov W,score1 ; t0 = score1 * 5 ; 80 mov t0,W ; 81 clc ; 82 rl t0 ; 83 clc ; 84 rl t0 ; 85 add t0,W ; 86 ; 20 score lines are divided into 5 rows mov W,#SCORE_TOP ; t1=row ; 87 mov t1,W ; 88 mov W,line ; 89 sub t1,W ; 90 clc ; 91 rr t1 ; 92 clc ; 93 rr t1 ; 94 ; look up pixels mov W,t0 ; score1*5 ; 95 add W,t1 ; +row ; 96 call CharTable ; 97,98,99,100,101,102 mov t0,W ; pixels ; 103 ; plot pixels for first player's score mov W,#TRIS_BLACK ; 104 btfsc t0.2 ; 105 mov W,#TRIS_WHITE ; 106 mov vport_tris,W ; white if left on ; 107 mov W,#TRIS_BLACK ; 108 btfsc t0.1 ; 109 mov W,#TRIS_WHITE ; 110 mov vport_tris,W ; white if mid on ; 111 mov W,#TRIS_BLACK ; 112 btfsc t0.0 ; 113! center 75 pix in mov W,#TRIS_WHITE ; 114 mov vport_tris,W ; white if right on; 115 nop ; 116 nop ; 117 nop ; 118 setb video_tris ; black now ; 119 mov W,#6 ; 120 mov t0,W ; 121 :score2delay decsz t0 ; 137 (last) jmp :score2delay ; 138 nop ; 139 ; compute pixels for score2 mov W,score2 ; t0 = score2 * 5 ; 140 mov t0,W ; 141 clc ; 142 rl t0 ; 143 clc ; 144 rl t0 ; 145 add t0,W ; 146 ; 20 score lines are divided into 5 rows mov W,#SCORE_TOP ; t1 = row ; 147 mov t1,W ; 148 mov W,line ; 149 sub t1,W ; 150 clc ; 151 rr t1 ; 152 clc ; 153 rr t1 ; 154 ; look up pixels mov W,t0 ; score2 * 5 ; 155 add W,t1 ; + row ; 156 call CharTable ; 157,158,159,160,161,162 mov t0,W ; pixels ; 163 ; plot pixels for second player's score mov W,#TRIS_BLACK ; 164 btfsc t0.2 ; 165 mov W,#TRIS_WHITE ; 166 mov vport_tris,W ; white if left on ; 167 mov W,#TRIS_BLACK ; 168 btfsc t0.1 ; 169 mov W,#TRIS_WHITE ; 170 mov vport_tris,W ; white if mid on ; 171 mov W,#TRIS_BLACK ; 172 btfsc t0.0 ; 173! center 135 in mov W,#TRIS_WHITE ; 174 mov vport_tris,W ; white if right on; 175 nop ; 176 nop ; 177 nop ; 178 setb video_tris ; black now ; 179 ; rest of line is blank mov W,#22 ; 180 mov t0,W ; 181 :scoreend decsz t0 ; 245 (last) jmp :scoreend ; 246 nop ; 247 ret ; 248 ; skipped cycle ; 249 ;***** PLAY LINE :playline ; this line of video is part of the "playing field" ; under the scores where the ball and paddles appear. ; set pdl1flag if paddle 1 is visible on this line ; note: cycle 38 is technically the end of the back ; porch, but it doesn't matter here. setb pdl1flag ; plan on paddle1 ; 28 mov W,line ; if paddle1 > line... ; 29 mov t0,W ; then no paddle this line ; 30 mov W,paddle1 ; 31 sub t0,W ; 32 sc ; borrow means no paddle ; 33 clrb pdl1flag ; 34 mov W,#PADDLE_HEIGHT ; check height ; 35 sub t0,W ; 36 snc ; borrow means paddle ; 37 clrb pdl1flag ; 38! ; set pdl2flag if paddle 2 is visible on this line setb pdl2flag ; plan on paddle2 ; 39 mov W,line ; if paddle2 > line... ; 40 mov t0,W ; then no paddle this line ; 41 mov W,paddle2 ; 42 sub t0,W ; 43 sc ; borrow means no paddle ; 44 clrb pdl2flag ; 45 mov W,#PADDLE_HEIGHT ; check height ; 46 sub t0,W ; 47 snc ; borrow means paddle ; 48 clrb pdl2flag ; 49 nop ; 50 nop ; 51 nop ; 52 nop ; 53 nop ; 54 nop ; 55 nop ; 56 ; paddle 1, if shown, occupies cycles 58-61 btfsc pdl1flag ; 57 clrb video_tris ; white if paddle ; 58! mov W,ballx ; preload loop counter ; 59 mov t0,W ; in spare cycles ; 60 nop ; 61 setb video_tris ; black ; 62 ; show ball if it touches this line. ; valid values for ballx are 1 thru 50 ONLY! ; also, ballxcomp must be set to 51-ballx always. :ball1 decsz t0 ; 63+3*(ballx-1) (last) jmp :ball1 ; 64+3*(ballx-1) nop ; 65+3*(ballx-1) btfsc ballflag ; 66+3*(ballx-1) clrb video_tris ; white if ball ; 67+3*(ballx-1)! mov W,ballxcomp ; preload loop ctr ; 68+3*(ballx-1) mov t0,W ; in spare cycles ; 69+3*(ballx-1) nop ; 70+3*(ballx-1) setb video_tris ; black ; 71+3*(ballx-1) :ball2 decsz t0 ; [72+3*(ballx-1)] + ; 3 * (51-ballx-1) ; Yuck! Algebra!! ; 219 (last) jmp :ball2 ; 220 ; paddle 2, if shown, occupies cycles 223-226 nop ; 221 btfsc pdl2flag ; 222 clrb video_tris ; white now if pdl ; 223! nop ; 224 nop ; 225 nop ; 226 setb video_tris ; black ; 227 mov W,#6 ; 228 mov t0,W ; 229 :fploop decsz t0 ; 245 (last) jmp :fploop ; 246 nop ; 247 ret ; 248 ; skipped cycle ; 249 ; ; UserField ; ; called once each field, 60 times a second. ; ; occupies cycles 22-247 of line 262. ; ; 'field' alternates 0 and -1. ; UserField nop ; 22 nop ; 23 nop ; 24 ; on field 0, acquire paddle 1's location. ; on field 1, do paddle 2. Each time, ; start the conversion for next time. mov W,ADRES ; get a/d result ; 25 mov t0,W ; 26 mov W,#PADDLE_MIN ; force in range ; 27 subwf t0,0 ; 28 mov W,#PADDLE_MIN ; 29 sc ; skip if paddle in range ; 30 mov t0,W ; 31 mov W,#PADDLE_MAX ; get max in t1 ; 32 mov t1,W ; 33 mov W,#PADDLE_HEIGHT ; 34 sub t1,W ; 35 mov W,t1 ; 36 subwf t0,0 ; 37 mov W,t1 ; 38 snc ; skip if paddle in range ; 39 mov t0,W ; t0 now in range ; 40 nop ; 41 nop ; 42 ; copy to appropriate paddle value mov W,t0 ; 43 btfss field.0 ; 44 mov paddle1,W ; 45 btfsc field.0 ; 46 mov paddle2,W ; 47 ; prepare for next acquisition (but don't ; actually start it for at least 20 uS, ; to allow holding capacitor to charge). btfss field.0 ; 48 mov W,#ADCON0_PDL2 ; 49 btfsc field.0 ; 50 mov W,#ADCON0_PDL1 ; 51 mov ADCON0,W ; 52 nop ; 53 ; handle any pending serve. For easier code timing, ; a random serve direction is generated every time, ; even if it's not time to serve! ; xdir is -1 or 1: put it in t1 mov W,#-1 ; 54 btfsc TMR0.7 ; 55 mov W,#1 ; 56 mov t1,W ; 57 ; ydir is -2, -1, 1 or 2 (no straight serves) ; put that value in t2 mov W,#-1 ; 58 btfsc TMR0.6 ; 59 mov W,#1 ; 60 mov t2,W ; 61 clc ; 62 btfsc TMR0.5 ; 63 rl t2 ; 64 ; see if it's time to release the ball after a serve. ; servetimer must be nonzero on arrival and decrement ; to zero this time through. Also, the game has to ; not be over. setb t0.0 ; assume serving ; 65 test servetimer ; 66 snz ; 67 clrb t0.0 ; timer is 0, not serving ; 68 sz ; 69 dec servetimer ; 70 sz ; 71 clrb t0.0 ; not time yet ; 72 btfsc endgame ; 73 clrb t0.0 ; game over, man! ; 74 ; if it's serve time, replace ball directions with ; random ones generated earlier. mov W,t1 ; 75 btfsc t0.0 ; 76 mov ballxdir,W ; 77 mov W,t2 ; 78 btfsc t0.0 ; 79 mov ballydir,W ; 80 nop ; 81 nop ; 82 nop ; 83 ; we have more stuff to do 30 times a second than will ; fit in "UserFrame", so we do some of it here, every ; other time through. test field ; 84 snz ; 85 jmp :field0 ; 86 ; skipped cycle on jmp ; 87 mov W,#51 ; 87 mov t0,W ; 88 :fieldloop decsz t0 ; 239 (last) jmp :fieldloop ; 240 nop ; 241 nop ; 242 jmp :fieldbottom ; 243 ; skipped cycle on jmp ; 244 :field0 ; This code executes 30 times a second ; move the ball (Y axis) ; speed along the Y axis can be -2, -1, 0, 1 or 2, ; depending on how theball strikes the paddle. clr t0 ; opposite dir in t0 ; 88 mov W,ballydir ; 89 sub t0,W ; 90 mov W,ballydir ; increment ; 91 add bally,W ; 92 mov W,#BALLY_MIN ; range check ; 93 subwf bally,0 ; 94 mov W,t0 ; 95 sc ; 96 mov ballydir,W ; bounce ; 97 mov W,#BALLY_MAX ; 98 subwf bally,0 ; 99 mov W,t0 ; 100 snc ; 101 mov ballydir,W ; bounce ; 102 ; move the ball (X axis) ; speed along the X axis is 1 ot -1. It can also be 0, ; but only when not in play. mov W,ballxdir ; increment ; 103 add ballx,W ; 104 mov W,ballxdir ; 105 sub ballxcomp,W ; 106 mov W,ballx ; 107 xor W,#BALLX_MIN ; 108 snz ; 109 jmp :bounceleft ; 110 ; skipped cycle on jmp ; 111 mov W,ballx ; 111 xor W,#BALLX_MAX ; 112 snz ; 113 jmp :bounceright ; 114 ; skipped cycle on jmp ; 115 ; no bounce: even out the timing, then merge code paths mov W,#42 ; 115 mov t0,W ; 116 :nobounceloop decsz t0 ; 240 (last) jmp :nobounceloop ; 241 nop ; 242 jmp :fieldbottom ; 243 ; skipped cycle on jmp ; 244 :bounceleft ; ball has just struck left side. ; set common variables, then jump to ; common code for bounce. mov W,paddle1 ; 112 mov temppaddle,W ; 113 nop ; 114 nop ; 115 jmp :bouncecommon ; 116 ; skipped cycle on jmp ; 117 :bounceright mov W,paddle2 ; 116 mov temppaddle,W ; 117 ; fallthru to bouncecommon :bouncecommon not ballxdir ; reverse travel ; 118 inc ballxdir ; 119 clrb t1.0 ; clear "miss" flag ; 120 mov W,bally ; 121 add W,#BALL_HEIGHT ; 122 subwf temppaddle,0 ; 123 btfsc C ; 124 setb t1.0 ; definitely missed ; 125 mov W,temppaddle ; 126 add W,#PADDLE_HEIGHT ; 127 mov t0,W ; 128 mov W,bally ; 129 sub t0,W ; 130 btfss C ; 131 setb t1.0 ; definitely missed ; 132 ; if ball has hit, t0 says where. ; t0 is now somewhere between 0 ; and PADDLE_HEIGHT+BALL_HEIGHT ; compute hit region in t2. Assuming ; the ball didn't miss, exactly ; one of the following subtractions ; will borrow. That marks the region. ; load t2 with -2, -1, 0, 1 or 2 ; depending on where ball hit paddle. mov W,#HIT_REGION ; 133 sub t0,W ; 134 mov W,#2 ; 135 btfss C ; 136 mov t2,W ; bottom ; 137 mov W,#HIT_REGION ; 138 sub t0,W ; 139 mov W,#1 ; 140 btfss C ; 141 mov t2,W ; mid-bottom ; 142 mov W,#HIT_REGION ; 143 sub t0,W ; 144 mov W,#0 ; 145 btfss C ; 146 mov t2,W ; middle ; 147 mov W,#HIT_REGION ; 148 sub t0,W ; 149 mov W,#-1 ; 150 btfss C ; 151 mov t2,W ; mid-top ; 152 mov W,#HIT_REGION ; 153 sub t0,W ; 154 mov W,#-2 ; 155 btfss C ; 156 mov t2,W ; top ; 157 ; at this point we either have a complete ; miss (t1.0 is set), or we have a hit and ; t2 contains a new ball direction, based ; on where the ball struck. mov W,t2 ; new dir ready ; 158 btfss t1.0 ; skip if miss ; 159 mov ballydir,W ; pong! ; 160 ; In the event of a miss, increment the other ; player's score. mov W,ballx ; which player missed? ; 161 xor W,#BALLX_MIN ; 162 mov W,#score2 ; 163 sz ; 164 mov W,#score1 ; 165 mov FSR,W ; 166 btfsc t1.0 ; 167 inc INDIRECT ; 168 mov W,#7 ; 169 and INDIRECT,W ; 170 xorwf INDIRECT,0 ; game over? ; 171 snz ; 172 setb endgame ; 173 mov W,#VTRIS ; restore FSR ; 174 mov FSR,W ; 175 ; Also in the event of a miss, set up next serve. mov W,#SERVE_DELAY ; 176 btfsc t1.0 ; 177 mov servetimer,W ; 178 btfsc t1.0 ; 179 clr ballxdir ; 180 btfsc t1.0 ; 181 clr ballydir ; 182 mov W,#BALLX_MIDDLE ; 183 btfsc t1.0 ; 184 mov ballx,W ; 185 mov W,#BALLY_MIDDLE ; 186 btfsc t1.0 ; 187 mov bally,W ; 188 mov W,#51 ; 189 mov ballxcomp,W ; 190 mov W,ballx ; 191 sub ballxcomp,W ; 192 mov W,#17 ; 193 mov t0,W ; 194 :bounceloop decsz t0 ; 243 (last) jmp :bounceloop ; 244 nop ; 245 nop ; 246 :fieldbottom setb ADGO ; read next paddle ; 247 ret ; 248 ; skipped cycle ; 249 ; ; UserPreFrame ; ; called once each frame, 30 times a second ; ; occupies cycles 3-18 of incomplete line 263 ; UserPreFrame mov W,#4 ; 3 mov t0,W ; 4 :preframeloop decsz t0 ; 14 (last) jmp :preframeloop ; 15 nop ; 16 ret ; 17 ; skipped cycle ; 18 ; ; UserFrame ; ; called once each frame, 30 times a second ; ; occupies cycles 22-121 of incomplete line 263 ; UserFrame mov W,#32 ; 22 mov t0,W ; 23 :frameloop decsz t0 ; 117 (last) jmp :frameloop ; 118 nop ; 119 ret ; 120 ; skipped cycle ; 121