├── .github └── funding.yml ├── .gitignore ├── blend.s ├── build.bat ├── build_data.py ├── build_runtime.bat ├── dgolf.c ├── dgolf.cfg ├── dgolf.s ├── fmult.py ├── layer1.png ├── layer1te.png ├── layer2.png ├── layers.chr ├── layerste.chr ├── readme.md ├── sounds.ftm ├── splash.ase ├── sprite.chr ├── sprite.png └── temp ├── runtime.lib └── slopes.inc /.github/funding.yml: -------------------------------------------------------------------------------- 1 | patreon: rainwarrior -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cc65/ 2 | libsrc/ 3 | temp/runtime/ 4 | temp/*.o 5 | temp/*.s 6 | temp/*.dbg 7 | temp/*.map 8 | temp/*.lst 9 | temp/*.nes 10 | *.opt 11 | cmd.exe.lnk 12 | -------------------------------------------------------------------------------- /blend.s: -------------------------------------------------------------------------------- 1 | ; 2 | ; blend.s 3 | ; arbitrary 5-step colour blend for NES palettes 4 | ; http://rainwarrior.ca 5 | ; 6 | 7 | ; uint8 blend50(uint8 a, uint8 b); // 50% a, 50% b 8 | ; uint8 blend25(uint8 a, uint8 b); // 75% a, 25% b 9 | .export _blend50 10 | .export _blend25 11 | 12 | ; Intended for a 4-step fade from one colour to any other: 13 | ; 1. a 14 | ; 2. blend25(a,b) 15 | ; 3. blend50(a,b) 16 | ; 4. blend25(b,a) 17 | ; 5. b 18 | ; 19 | ; blends from colour to colour will take the shortest path of hue rotation 20 | ; blends against white or black will progress vertically without hue change 21 | ; blends from grey to a colour will immediately take on the colour's hue 22 | ; 23 | ; a,b must be 0-63, input is not sanitized 24 | ; fastcall (default) calling convention is required 25 | 26 | ; 27 | ; Implementation: 28 | ; 29 | 30 | .import popa ; used to retrieve argument from CC65 C-stack (A=result, Y=0, sp+=1) 31 | .importzp tmp1, tmp2, tmp3, tmp4 ; CC65 ZP temporaries 32 | hue0 = tmp1 33 | hue1 = tmp2 34 | val0 = tmp3 35 | val1 = tmp4 36 | 37 | ; "normalized" colour palette 38 | ; low nybble 1-12 is hue, 0 for greys 39 | ; high nybble is intensity 40 | ; for colours: intensity is shifted down a row as 1-4, 0 becomes black, and 5 becomes white 41 | ; for greys: intensity 0-5 is monotonically increasing from 0F black to 30 white (including 2D/3D) 42 | palette_normalize: 43 | .byte $20, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1A, $1B, $1C, $00, $00, $00 44 | .byte $30, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $00, $00, $00 45 | .byte $50, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $10, $00, $00 46 | .byte $50, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $40, $00, $00 47 | 48 | ; reverse of normalize, returns to original colours 49 | palette_denormalize: 50 | .byte $0F, $0F, $0F, $0F, $0F, $0F, $0F, $0F, $0F, $0F, $0F, $0F, $0F, $0F, $0F, $0F 51 | .byte $2D, $01, $02, $03, $04, $05, $06, $07, $08, $09, $0A, $0B, $0C, $2D, $2D, $2D 52 | .byte $00, $11, $12, $13, $14, $15, $16, $17, $18, $19, $1A, $1B, $1C, $00, $00, $00 53 | .byte $10, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $10, $10, $10 54 | .byte $3D, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $3D, $3D 55 | .byte $30, $30, $30, $30, $30, $30, $30, $30, $30, $30, $30, $30, $30, $30, $30, $30 56 | 57 | blend_common: 58 | ; A = color 1 59 | ; stack = color 0 60 | tax 61 | lda palette_normalize, X 62 | and #$F 63 | sta hue1 64 | lda palette_normalize, X 65 | and #$F0 66 | sta val1 67 | jsr popa 68 | tax 69 | lda palette_normalize, X 70 | and #$F 71 | sta hue0 72 | lda palette_normalize, X 73 | and #$F0 74 | sta val0 75 | rts 76 | 77 | .proc _blend50 78 | ; A = color 1 79 | ; stack = color 0 80 | jsr blend_common 81 | ; blend value 50% 82 | lda val0 83 | clc 84 | adc val1 85 | lsr 86 | and #$F0 87 | sta val0 88 | ; blend hue 89 | lda hue0 90 | beq grey0 91 | lda hue1 92 | beq grey1 93 | colours: ; both are colours, blend using shortest path around hue ring 94 | sec 95 | sbc hue0 96 | bcc @hue0_high 97 | @hue1_high: 98 | cmp #7 99 | bcs @hue_blend_wrapped 100 | @hue_blend_straight: 101 | lda hue0 102 | clc 103 | adc hue1 104 | lsr 105 | jmp hue 106 | @hue_blend_wrapped: 107 | lda #12 108 | clc 109 | adc hue0 110 | ;clc 111 | adc hue1 112 | lsr 113 | cmp #13 114 | bcc hue 115 | ;sec 116 | sbc #12 117 | jmp hue 118 | ; 119 | @hue0_high: 120 | cmp #(256-6) 121 | bcs @hue_blend_straight 122 | jmp @hue_blend_wrapped 123 | grey0: ; if either is grey, favour keeping colour 124 | lda hue1 125 | jmp hue 126 | grey1: 127 | lda hue0 128 | ;jmp @hue 129 | hue: 130 | ; val0 = val << 4 131 | ; A = hue 132 | ora val0 133 | tax 134 | lda palette_denormalize, X 135 | rts 136 | .endproc 137 | 138 | .proc _blend25 139 | ; A = color 1 140 | ; stack = color 0 141 | jsr blend_common 142 | ; blend value 75% vs 25% 143 | lda val0 144 | lsr val0 145 | lsr val1 146 | ;clc 147 | adc val0 148 | ;clc 149 | adc val1 150 | lsr 151 | ;clc 152 | adc #$07 ; round to nearest 153 | and #$F0 154 | sta val0 155 | ; blend hue 156 | lda hue0 157 | beq grey0 158 | lda hue1 159 | beq grey1 160 | colours: ; both are colours, blend using shortest path around hue ring 161 | sec 162 | sbc hue0 163 | bcc @hue0_high 164 | @hue1_high: 165 | cmp #7 166 | bcs @hue0_blend_wrapped 167 | @hue_blend_straight: 168 | lda hue0 169 | asl 170 | ;clc 171 | adc hue0 172 | ; unoptimized: 173 | ;clc 174 | ;adc hue1 175 | ;clc 176 | ;adc #1 ; round 177 | ; optimized: 178 | sec ; round 179 | adc hue1 180 | ; 181 | lsr 182 | lsr ; ((hue0*3)+hue1)/4 183 | jmp hue 184 | @hue0_blend_wrapped: 185 | lda hue0 186 | asl 187 | ;clc 188 | adc hue0 189 | ;clc 190 | adc #(12*3) + 1 191 | ;clc 192 | adc hue1 193 | lsr 194 | lsr ;(((hue0+12)*3)+hue1)/4 195 | cmp #13 196 | bcc hue 197 | ;sec 198 | sbc #12 199 | jmp hue 200 | ; 201 | @hue0_high: 202 | cmp #(256-6) 203 | bcs @hue_blend_straight 204 | @hue1_blend_wrapped: 205 | lda hue0 206 | asl 207 | ;clc 208 | adc hue0 209 | ;clc 210 | adc hue1 211 | ;clc 212 | adc #12 + 1 213 | lsr 214 | lsr ;((huge0*3)+(hue1+12))/4 215 | cmp #13 216 | bcc hue 217 | ;sec 218 | sbc #12 219 | jmp hue 220 | ; 221 | grey0: ; if either is grey, favour keeping colour 222 | lda hue1 223 | jmp hue 224 | grey1: 225 | lda hue0 226 | ;jmp @hue 227 | hue: 228 | ; val0 = val << 4 229 | ; A = hue 230 | ora val0 231 | tax 232 | lda palette_denormalize, X 233 | rts 234 | .endproc 235 | 236 | ; 237 | ; http://rainwarrior.ca 238 | ; 239 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @del temp\*.s 2 | @del temp\*.o 3 | @del temp\dgolf.nes 4 | @del temp\dgolf.dbg 5 | @del temp\dgolf.map 6 | 7 | cc65\bin\cc65 -o temp\dgolf.c.s -O -T -g dgolf.c 8 | @IF ERRORLEVEL 1 GOTO error 9 | 10 | cc65\bin\ca65 -o temp\dgolf.c.o -g temp\dgolf.c.s 11 | @IF ERRORLEVEL 1 GOTO error 12 | 13 | cc65\bin\ca65 -o temp\dgolf.o -g dgolf.s 14 | @IF ERRORLEVEL 1 GOTO error 15 | 16 | cc65\bin\ca65 -o temp\blend.o -g blend.s 17 | @IF ERRORLEVEL 1 GOTO error 18 | 19 | cc65\bin\ld65 -o temp\dgolf.nes -m temp\dgolf.map --dbgfile temp\dgolf.dbg -C dgolf.cfg temp\blend.o temp\dgolf.o temp\dgolf.c.o temp\runtime.lib 20 | @IF ERRORLEVEL 1 GOTO error 21 | 22 | @echo. 23 | @echo. 24 | @echo Build successful! 25 | @pause 26 | @GOTO end 27 | :error 28 | @echo. 29 | @echo. 30 | @echo Build error! 31 | @pause 32 | :end -------------------------------------------------------------------------------- /build_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import PIL.Image 4 | import math 5 | 6 | # 7 | # processes some image files into NES CHR data 8 | # 9 | 10 | def index_image(filename, palette): 11 | # load and convert image to indexed palette 12 | src = PIL.Image.open(filename) 13 | dst = PIL.Image.new("P",src.size,color=0) 14 | for y in range(src.size[1]): 15 | for x in range(src.size[0]): 16 | p = src.getpixel((x,y)) 17 | mag = ((255**2)*3)+1 18 | mat = 0 19 | for i in range(len(palette)): 20 | m = sum([(a-b)**2 for (a,b) in zip(p,palette[i])]) 21 | if m < mag: # better match 22 | mat = i 23 | mag = m 24 | if m == 0: # perfect match 25 | break 26 | dst.putpixel((x,y),mat) 27 | return dst 28 | 29 | def print_img(im): 30 | # print indexed image (for debugging) 31 | print(im) 32 | for y in range(im.size[1]): 33 | s = "%3d:" % y 34 | for x in range(im.size[0]): 35 | s += "" + str(im.getpixel((x,y))) 36 | print(s) 37 | 38 | def make_chr(im, horizontal=True): 39 | # convert indexed (0-3) image to CHR, as 8x8 tiles 40 | c = [] 41 | wx, wy = im.size[0]//8, im.size[1]//8 42 | if not horizontal: 43 | wy, wx = wx, wy 44 | for ty in range(wy): 45 | for tx in range(wx): 46 | ox = tx*8 47 | oy = ty*8 48 | if not horizontal: 49 | oy, ox = ox, oy 50 | c0 = [] 51 | c1 = [] 52 | for y in range(8): 53 | r0 = 0 54 | r1 = 0 55 | for x in range(8): 56 | p = im.getpixel((x+ox,y+oy)) 57 | r0 = (p & 1) | (r0 << 1) 58 | r1 = ((p & 2) >> 1) | (r1 << 1) 59 | c0.append(r0) 60 | c1.append(r1) 61 | c = c + c0 + c1 62 | return c 63 | 64 | def layer_chr(c1,c2): 65 | # take two CHR itmes and swap planes, 66 | # creating two new CHR items: 67 | # the first with both plane 0s 68 | # the second with both plane 1s 69 | l1 = [] 70 | l2 = [] 71 | assert(len(c1) == len(c2)) 72 | for i in range(0,len(c1),16): 73 | l1 = l1 + c1[i :i+8 ] + c2[i :i+8 ] 74 | l2 = l2 + c1[i+8:i+16] + c2[i+8:i+16] 75 | return (l1,l2) 76 | 77 | def chr_to_rgb(c, palette, width=16, horizontal=True): 78 | # converts CHR data into an RGB image 79 | # using the given 4-colour palette. 80 | tiles = len(c) // 16 81 | columns = width 82 | rows = (tiles + (width-1)) // width 83 | if not horizontal: 84 | rows, columns = columns, rows 85 | img = PIL.Image.new("RGB",(width*8,rows*8),palette[0]) 86 | for t in range(0,tiles): 87 | xo = (t % width) * 8 88 | yo = (t // width) * 8 89 | if not horizontal: 90 | yo, xo = xo, yo 91 | to = t * 16 92 | for y in range(8): 93 | p0 = c[to + 0 + y] 94 | p1 = c[to + 8 + y] 95 | for x in range(8): 96 | p = ((p0 >> 7) & 1) | ((p1 >> 6) & 2) 97 | img.putpixel((xo+x, yo+y), palette[p]) 98 | p0 <<= 1 99 | p1 <<= 1 100 | return img 101 | 102 | def byte_table(t, width = 16): 103 | s = "" 104 | while (len(t) > 0): 105 | s += ".byte " 106 | r = t[0:width] 107 | t = t[width:] 108 | s += "$%02X" % r[0] 109 | r = r[1:] 110 | for e in r: 111 | s += ", $%02X" % e 112 | s += "\n" 113 | return s 114 | 115 | # 116 | # output filenames 117 | # 118 | 119 | layersout = "layers.chr" 120 | layersteout = "layerste.chr" 121 | spriteout = "sprite.chr" 122 | slopeout = "temp\\slopes.inc" 123 | 124 | # 125 | # specific CHR data 126 | # 127 | 128 | pal1bit = [(0,0,0),(255,255,255)] 129 | pal2bit = [(0,0,0),(0x65,0x66,0x65),(0xb0,0xb1,0xb0),(255,255,255)] 130 | 131 | layer1 = index_image("layer1.png",pal1bit) 132 | layer2 = index_image("layer2.png",pal1bit) 133 | sprite = index_image("sprite.png",pal2bit) 134 | layer1te = index_image("layer1te.png",pal1bit) 135 | 136 | # Test of print_img 137 | #print_img(layer1) 138 | #print_img(layer2) 139 | #print_img(sprite) 140 | 141 | layers_chr = layer_chr(make_chr(layer1),make_chr(layer2))[0] 142 | open(layersout,"wb").write(bytes(layers_chr)) 143 | print(layersout + " -> %d bytes" % len(layers_chr)) 144 | 145 | layerste_chr = layer_chr(make_chr(layer1te),make_chr(layer2))[0] 146 | open(layersteout,"wb").write(bytes(layerste_chr)) 147 | print(layersteout + " -> %d bytes" % len(layerste_chr)) 148 | 149 | sprite_chr = make_chr(sprite) 150 | open(spriteout,"wb").write(bytes(sprite_chr)) 151 | print(spriteout + " -> %d bytes" % len(sprite_chr)) 152 | 153 | # Test of chr_to_rgb 154 | #chr_to_rgb(layer_chr, pal2bit).save("layer_rev.png") 155 | #chr_to_rgb(sprite_chr, pal2bit, 8, False).save("sprite_rev.png") 156 | 157 | # 158 | # slope and normal table generator 159 | # 160 | 161 | slopes = [0,1,2,3,4] + ([0]*8) + ([1]*8) # special values, frequent values 162 | slopes_curve = 4 # increasing this number favours shallower slopes 163 | slopes_remain = 128 - len(slopes) 164 | for i in range(0,slopes_remain): 165 | slopes.append(4 * math.pow(i / (slopes_remain-1),slopes_curve)) 166 | slopes = slopes + [-x for x in slopes] # second half is negative 167 | 168 | # convert to fixed point .8 169 | # normal is perpendicular to the slope, always facing up 170 | norms = [] 171 | for i in range(len(slopes)): 172 | s = slopes[i] 173 | mag = math.sqrt((s*s) + (-1*-1)) 174 | norms.append((int(s * 256 / mag), int(-256 / mag))) 175 | slopes[i] = int(s * 256) 176 | 177 | # build text 178 | s = "; generated slopes\n" 179 | s += "\n" 180 | s += "slope_y0:\n" + byte_table([((e>>0)&255) for e in slopes]) + "\n" 181 | s += "slope_y1:\n" + byte_table([((e>>8)&255) for e in slopes]) + "\n" 182 | s += "norm_x0:\n" + byte_table([((e[0]>>0)&255) for e in norms]) + "\n" 183 | s += "norm_x1:\n" + byte_table([((e[0]>>8)&255) for e in norms]) + "\n" 184 | s += "norm_y0:\n" + byte_table([((e[1]>>0)&255) for e in norms]) + "\n" 185 | s += "norm_y1:\n" + byte_table([((e[1]>>8)&255) for e in norms]) + "\n" 186 | s += "; end\n" 187 | open(slopeout,"wt").write(s) 188 | print(slopeout + " -> %d characters" % len(s)) 189 | -------------------------------------------------------------------------------- /build_runtime.bat: -------------------------------------------------------------------------------- 1 | @REM Generates temp\runtime.lib 2 | @REM 3 | @REM cc65\ should contain latest cc65 build (cc65\bin) 4 | @REM libsrc\ should contain latest cc65 libsrc folder 5 | @REM 6 | @REM Download CC65 source here: 7 | @REM https://github.com/cc65/cc65 8 | @REM 9 | @REM contents: 10 | @REM libsrc\runtime 11 | @REM -condes.s (don't need constructor/destructor feature) 12 | @REM -callirq.s (IRQ management not needed, uses .constructor) 13 | @REM -callmain.s (main() argument generation not needed for NES) 14 | @REM -stkchk.s (not needed, uses .constructor) 15 | @REM libsrc\common\copydata.s (used by crt0.s for copying DATA segment to RAM) 16 | @REM 17 | if not exist libsrc goto needlibsrc 18 | md temp\runtime 19 | del temp\runtime\*.o 20 | del temp\runtime.lib 21 | for %%X in (libsrc\runtime\*.s) do cc65\bin\ca65.exe %%X -g -o temp\runtime\%%~nX.o 22 | cc65\bin\ca65.exe libsrc\common\copydata.s -g -o temp\runtime\copydata.o 23 | del temp\runtime\condes.o 24 | del temp\runtime\callirq.o 25 | del temp\runtime\callmain.o 26 | del temp\runtime\stkchk.o 27 | for %%X in (temp\runtime\*.o) do cc65\bin\ar65.exe a temp\runtime.lib %%X 28 | @echo. 29 | @echo. 30 | @echo temp\runtime.lib build complete, review the log above in case of errors. 31 | @goto end 32 | 33 | :needlibsrc 34 | @echo. 35 | @echo. 36 | @echo temp\runtime.lib not rebuilt! 37 | @echo Download CC65 source and place libsrc\ folder here. 38 | @echo Edit this batch file for more information. 39 | 40 | :end 41 | @echo. 42 | @pause 43 | -------------------------------------------------------------------------------- /dgolf.c: -------------------------------------------------------------------------------- 1 | // 2 | // dgolf.c 3 | // NESert Golfing, by Brad Smith 2019-2022 4 | // http://rainwarrior.ca 5 | // 6 | 7 | #define VERSION_STRING "1.5" 8 | const char rom_version[] = " ################ " 9 | "NESert Golfing version 1.5 by Brad Smith, 2022" 10 | " ################ "; 11 | 12 | // for debugging performance 13 | // (turns screen greyscale/green at end of frame when SELECT is held) 14 | //#define PROFILE() { if (gamepad & PAD_SELECT) ppu_profile(0x41); } 15 | //#define PROFILE() { ppu_profile(0x41); } 16 | #define PROFILE() {} 17 | 18 | // a few checks for safety, triggers infinite loop 19 | #define ASSERT(__c__) {} 20 | //#define ASSERT(__c__) { debug_assert(__c__); } 21 | 22 | // for debugging graphics in left column 23 | //#define SHOW_LEFT_COLUMN 1 24 | #define SHOW_LEFT_COLUMN 0 25 | 26 | // for testing the last hole 27 | //#define LAST_HOLE_TEST 3 28 | #define LAST_HOLE_TEST 0 29 | 30 | // for debug hole skip with START button 31 | //#define HOLE_SKIP 1 32 | #define HOLE_SKIP 0 33 | 34 | // 35 | // common library stuff 36 | // 37 | 38 | typedef unsigned char uint8; 39 | typedef signed char sint8; 40 | typedef unsigned short int uint16; 41 | typedef signed short int sint16; 42 | typedef unsigned long int uint32; 43 | typedef signed long int sint32; 44 | 45 | extern uint8* ptr; 46 | extern uint32 seed; // 24-bit random seed, don't set to 0 47 | extern uint32 seedw; // TE 48 | extern uint32 seedf; // TE 49 | extern uint8 i, j, k, l; 50 | extern uint16 mx,nx,ox,px; 51 | extern sint16 ux,vx; 52 | extern uint8 te; // TE non-zero if tournament edition active 53 | #pragma zpsym("ptr") 54 | #pragma zpsym("seed") 55 | #pragma zpsym("seedw") 56 | #pragma zpsym("seedf") 57 | #pragma zpsym("i") 58 | #pragma zpsym("j") 59 | #pragma zpsym("k") 60 | #pragma zpsym("l") 61 | #pragma zpsym("mx") 62 | #pragma zpsym("nx") 63 | #pragma zpsym("ox") 64 | #pragma zpsym("px") 65 | #pragma zpsym("ux") 66 | #pragma zpsym("vx") 67 | #pragma zpsym("te") 68 | 69 | extern uint8 input[16]; 70 | extern uint8 input_flags; 71 | extern uint8 gamepad; 72 | extern uint8 mouse1; 73 | extern sint8 mouse2; 74 | extern sint8 mouse3; 75 | #pragma zpsym("input") 76 | #pragma zpsym("input_flags") 77 | #pragma zpsym("gamepad") 78 | #pragma zpsym("mouse1") 79 | #pragma zpsym("mouse2") 80 | #pragma zpsym("mouse3") 81 | 82 | extern uint8 prng(); // 8-bit random value 83 | extern uint8 prng1(); // "fast" random value, only bit 0 is truly random (bits 1-7 have increasing entropy) 84 | extern uint8 prngw(); // TE dependent random generator for ongoing wind adjustment only 85 | extern uint8 prngw1(); // TE 86 | extern uint8 prngf(); // TE independent random generator for things that should still be random 87 | extern uint8 prngf1(); // TE 88 | extern void mouse_sense(); // cycles sensitivity setting (doesn't work on Hyperkin clone) 89 | extern void input_setup(); 90 | extern void input_poll(); 91 | extern void sound_play(const uint8* addr); // play a sound effect 92 | 93 | extern void te_switch(); // rendering off, reset and switch mode 94 | extern void soft_reset(); 95 | 96 | extern void ppu_latch(uint16 addr); // write address to PPU latch 97 | extern void ppu_direction(uint8 vertical); // set write increment direction 98 | extern void ppu_write(uint8 value); // write value to $2007 99 | extern void ppu_load(uint16 count); // uploads bytes from ptr to $2007 (clobbers ptr) 100 | extern void ppu_fill(uint8 value, uint16 count); // uploads single value to $2007 101 | extern void ppu_ctrl(uint8 v); // $2000, only bits 4-6 count (tile pages, sprite height), applies at next post 102 | extern void ppu_mask(uint8 v); // $2001, applies at next post 103 | extern void ppu_scroll_x(uint16 x); 104 | extern void ppu_scroll_y(uint16 y); 105 | extern void ppu_post(uint8 mode); // waits for next frame and posts PPU update 106 | extern void ppu_profile(uint8 emphasis); // immediate $2001 write, OR with current mask (use bit 0 for greyscale) 107 | extern void ppu_apply_direction(uint8 vertical); // immediately set write increment direction 108 | extern void ppu_apply(); // immediately uploads ppu_send to $2007, resets ppu_send_count to 0 109 | 110 | // POST_OFF turn off rendering 111 | // POST_NONE turn on, no other updates 112 | // POST_UPDATE turn on, palette, send 113 | // POST_DOUBLE turn on, palette, send 64 bytes across 2 nametables 114 | #define POST_OFF 1 115 | #define POST_NONE 2 116 | #define POST_UPDATE 3 117 | #define POST_DOUBLE 4 118 | 119 | #define PAD_A 0x80 120 | #define PAD_B 0x40 121 | #define PAD_SELECT 0x20 122 | #define PAD_START 0x10 123 | #define PAD_UP 0x08 124 | #define PAD_DOWN 0x04 125 | #define PAD_LEFT 0x02 126 | #define PAD_RIGHT 0x01 127 | #define MOUSE_L 0x40 128 | #define MOUSE_R 0x80 129 | 130 | extern uint16 ppu_send_addr; 131 | extern uint8 ppu_send_count; 132 | extern uint8 ppu_send[64]; 133 | 134 | extern uint8 palette[32]; 135 | extern uint8 oam[256]; 136 | 137 | // 138 | // simple color blending library 139 | // 140 | 141 | extern uint8 blend50(uint8 a, uint8 b); // best palette match of 50% a, 50% b 142 | extern uint8 blend25(uint8 a, uint8 b); // best palette match of 75% a, 25% b 143 | 144 | // 145 | // desert golfing stuff in assembly 146 | // 147 | 148 | extern uint8 layers_chr[]; 149 | extern uint8 sprite_chr[]; 150 | extern uint8 layerste_chr[]; 151 | extern const uint16 LAYERS_CHR_SIZE; 152 | extern const uint16 SPRITE_CHR_SIZE; 153 | extern const uint16 LAYERSTE_CHR_SIZE; 154 | 155 | extern uint8 floor_column; 156 | extern uint8 weather_tile; 157 | extern uint8 weather_attribute; 158 | extern sint8 weather_wind_dir; 159 | extern uint8 weather_wind_p; 160 | extern uint8 weather_rate_min; 161 | extern uint8 weather_rate_mask; 162 | extern uint8 hole; 163 | extern uint8 ocean_attribute; 164 | extern uint16 tx; 165 | extern uint16 ty; 166 | extern sint16 tsx; 167 | extern sint16 tsy; 168 | extern uint32 ball_x; 169 | extern uint32 ball_y; 170 | extern sint32 ball_vx; 171 | extern sint32 ball_vy; 172 | extern volatile uint32 balls_x[4]; // 16:8 fixed point + 8 bits of padding for convenience 173 | extern volatile uint8 balls_fx[16]; // byte access to balls_x+0 (0,4,8,12), fixed 8 174 | extern volatile uint8 balls_lx[15]; // byte access to balls_x+1 (0,4,8,12), low 8 175 | extern volatile uint8 balls_hx[14]; // byte access to balls_x+2 (0,4,8,12), high 8 176 | extern volatile uint16 balls_wx[7]; // word access to balls_x+1 (0,2,4,6), low,high 16 177 | extern uint8 balls_y[4]; 178 | extern sint16 norm_x; 179 | extern sint16 norm_y; 180 | // note: while "volatile" is semantically important because of the type-punned aliases for balls_x 181 | // it was actually a bad idea, because in cc65 it ends up disabling all optimizations in any function 182 | // that uses volatile variables. In hindsight it would have been better to do this without the 183 | // alias access. 184 | 185 | #pragma zpsym("floor_column") 186 | #pragma zpsym("weather_tile") 187 | #pragma zpsym("weather_attribute") 188 | #pragma zpsym("weather_wind_dir") 189 | #pragma zpsym("weather_wind_p") 190 | #pragma zpsym("weather_rate_min") 191 | #pragma zpsym("weather_rate_mask") 192 | #pragma zpsym("hole") 193 | #pragma zpsym("ocean_attribute") 194 | #pragma zpsym("tx") 195 | #pragma zpsym("ty") 196 | #pragma zpsym("tsx") 197 | #pragma zpsym("tsy") 198 | #pragma zpsym("ball_x") 199 | #pragma zpsym("ball_y") 200 | #pragma zpsym("ball_vx") 201 | #pragma zpsym("ball_vy") 202 | #pragma zpsym("balls_x") 203 | #pragma zpsym("balls_fx") 204 | #pragma zpsym("balls_lx") 205 | #pragma zpsym("balls_hx") 206 | #pragma zpsym("balls_wx") 207 | #pragma zpsym("balls_y") 208 | #pragma zpsym("norm_x") 209 | #pragma zpsym("norm_y") 210 | 211 | extern uint8 floor_render[64]; 212 | extern uint8 floor_y[512]; 213 | extern uint8 floor_a[512]; 214 | 215 | extern uint16 read_slope(uint8 index); 216 | extern sint16 read_norm_x(uint8 index); 217 | extern sint16 read_norm_y(uint8 index); 218 | extern void floor_render_prepare(uint8 phase); // 0 = build, 1-5 = send, 6+ nothing (~6500, 300 x 4, 600, 0... cy) 219 | 220 | extern void weather_animate(); // <~4300 cy 221 | extern void weather_shift(uint8 shift); // ~800 cy 222 | 223 | // fixed point multiply: (a * b) / 256 224 | extern sint16 fmult(sint16 a, sint16 b); // ~545-594 cy 225 | // used this to replace basis transforms like: ((a*b)+(c*d))/256 226 | // (they are about 4x as fast, the original C code is left in comments for comparison) 227 | 228 | // 229 | // allocations and other common stuff 230 | // 231 | 232 | // fixed point for swing_x/y 233 | #define SWING_FIX 16 234 | // minimum radius for swing (to allow cancel) 235 | #define SWING_MIN (SWING_FIX*3) 236 | // maximum radius of swing 237 | #define SWING_MAX (SWING_FIX*256) 238 | 239 | // GLOBAL_FLOOR must be at least 2 pixels above OCEAN_FLOOR 240 | // OCEAN_FLOOR must match definition in dgolf.s 241 | #define GLOBAL_FLOOR 222 242 | #define OCEAN_FLOOR 224 243 | #define WATER_COLOUR 0x11 244 | const uint8 BALL_COLOUR[5] = { 0x06, 0x02, 0x08, 0x0A, 0x00 }; 245 | 246 | // conventions: 247 | // sx/sy = screen x,y (s = sprite) 248 | // cx = circular x (mod 512) 249 | 250 | #pragma bss-name (push, "ZEROPAGE") // was out of BSS space, but had plenty of ZEROPAGE 251 | 252 | uint16 tee_cx; 253 | uint16 hole_cx; 254 | uint16 scroll_cx; 255 | 256 | // render positions of things to draw 257 | 258 | uint16 tee_sx; 259 | uint16 hole_sx; 260 | uint16 status_sx; 261 | 262 | uint8 tee_sy; 263 | uint8 hole_sy; 264 | 265 | uint8 tee_s; 266 | uint8 ball_s; 267 | uint8 splash_s; 268 | 269 | uint8 balls_draw[4]; // which player is in which draw-slot 270 | 271 | uint8 hole_digits[3]; 272 | uint8 flag_remove; 273 | 274 | uint8 players; 275 | uint8 player; 276 | 277 | uint8 swinging; 278 | sint16 swing_x; 279 | sint16 swing_y; 280 | 281 | uint8 strokes[5*4]; 282 | uint8 cleared; // bit 0,1,2,3 = player in hole 283 | 284 | uint8 status_w; 285 | uint8 ring_glow; 286 | uint8 roll_sx, roll_sy; // last frame's position 287 | uint8 soll_sx, soll_sy; // two frames ago position 288 | uint16 timeout; // frame timeout 289 | uint8 rollout; // visual timeout (if ball is in same place for too long) 290 | uint8 valley; 291 | uint8 old_lip[2]; 292 | uint8 first_stroke; 293 | uint8 next_player; 294 | 295 | #pragma bss-name (pop) 296 | 297 | // sprite building 298 | 299 | uint8 oam_pos; 300 | 301 | // floor generation 302 | 303 | // control parameters 304 | uint16 fg_min; // floor should not go below min 305 | uint16 fg_max; // floor should not go below max 306 | uint8 fg_hold_mask; // power of 2 - 1, controls maximum length of slopes 307 | uint8 fg_angle_mask; // $80 | power of 2 - 1, lower values favour shallower slopes 308 | 309 | uint16 fg_cx; // next floor pixel index to build 310 | uint16 fg_last; // last floor value 311 | 312 | uint16 fg_min_soft; // used to guide more toward centre 313 | uint16 fg_max_soft; 314 | uint16 fg_next; // next floor value 315 | uint8 fg_angle; // last floor angle 316 | uint8 fg_hold; // pixels to continue current angle 317 | 318 | // animation of scenery etc. 319 | 320 | uint8 frame_count; 321 | uint8 transition; 322 | uint8 transition_time; 323 | uint8 day; 324 | sint8 weather_wind_fade_dir; 325 | uint8 weather_wind_fade_p; 326 | uint16 weather_wind_timeout; 327 | 328 | // hole generation 329 | 330 | uint8 pal_floor; 331 | uint8 pal_sky; 332 | uint8 pal_text; 333 | 334 | uint8 course; 335 | uint8 field_set; 336 | 337 | // title menu 338 | 339 | uint8 title_menu; 340 | uint8 gamepad_last; 341 | uint8 gamepad_new; 342 | uint8 mouse_last; 343 | uint8 mouse_new; 344 | uint8 mouse_steady; 345 | sint8 mouse_sx; 346 | sint8 mouse_sy; 347 | 348 | // TE tournament edition 349 | 350 | uint16 holes; 351 | uint8 seed_pos; 352 | uint8 holes_pos; 353 | uint32 seed_start; 354 | extern uint16 holes; 355 | extern uint32 seed_start; 356 | 357 | // 358 | // sound effects 359 | // 360 | 361 | // 0xFF = end 362 | // 0xFE = all notes off 363 | // 0xFD = next frame 364 | // 0xXX 0xYY = write YY to register $4000+XX 365 | // - start all sounds with $FE 366 | // - end all sounds with $FE $FF 367 | 368 | #define SFX_BEGIN 0xFE 369 | #define SFX_END 0xFE, 0xFF 370 | #define SFX_FRAME 0xFD 371 | #define SFX(a_,b_) a_, b_ 372 | 373 | const uint8 SFX_STROKE[] = { SFX_BEGIN, 374 | SFX(0xC,0x3F), SFX(0xE,0x0C), SFX_FRAME, 375 | SFX(0xC,0x3D), SFX(0xE,0x00), SFX_FRAME, 376 | SFX(0xC,0x32), SFX_FRAME, 377 | SFX(0xC,0x31), SFX_FRAME, SFX_END }; 378 | 379 | const uint8 SFX_FLAG[] = { SFX_BEGIN, 380 | SFX(0x0,0xB2), SFX(0x2,0x93), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 381 | SFX(0x0,0xB3), SFX(0x2,0x3F), SFX_FRAME, SFX_FRAME, 382 | SFX(0x0,0xB4), SFX(0x2,0xEF), SFX(0x3,0x00), SFX_FRAME, SFX_FRAME, 383 | SFX(0x0,0xB5), SFX(0x2,0xBD), SFX_FRAME, SFX_FRAME, 384 | SFX(0x0,0xB4), SFX(0x2,0x9F), SFX_FRAME, SFX_FRAME, 385 | SFX(0x0,0xB3), SFX(0x2,0x7E), SFX_FRAME, SFX_FRAME, 386 | SFX(0x0,0xB2), SFX(0x2,0x64), SFX_FRAME, SFX_FRAME, 387 | SFX(0x0,0xB1), SFX(0x2,0x54), SFX_FRAME, SFX_FRAME, 388 | SFX(0x0,0xB1), SFX(0x2,0x3F), SFX_FRAME, SFX_FRAME, SFX_END }; 389 | 390 | const uint8 SFX_RESPAWN[] = { SFX_BEGIN, 391 | SFX(0x0,0x71), SFX(0x2,0x59), SFX_FRAME, SFX_FRAME, 392 | SFX(0x0,0x72), SFX(0x2,0x54), SFX_FRAME, SFX_FRAME, 393 | SFX(0x0,0x73), SFX(0x2,0x4F), SFX_FRAME, SFX_FRAME, 394 | SFX(0x0,0x74), SFX(0x2,0x4B), SFX_FRAME, SFX_FRAME, 395 | SFX(0x0,0x73), SFX(0x2,0x59), SFX_FRAME, SFX_FRAME, 396 | SFX(0x2,0x54), SFX_FRAME, SFX_FRAME, 397 | SFX(0x2,0x4F), SFX_FRAME, SFX_FRAME, 398 | SFX(0x2,0x4B), SFX_FRAME, SFX_FRAME, 399 | SFX(0x0,0x71), SFX(0x2,0x59), SFX_FRAME, SFX_FRAME, 400 | SFX(0x2,0x54), SFX_FRAME, SFX_FRAME, 401 | SFX(0x2,0x4F), SFX_FRAME, SFX_FRAME, 402 | SFX(0x2,0x4B), SFX_FRAME, SFX_FRAME, SFX_END }; 403 | 404 | const uint8 SFX_BOUNCE0[] = { SFX_BEGIN, 405 | SFX(0xC,0x31), SFX(0xE,0x0D), SFX_FRAME, 406 | SFX(0xC,0x32), SFX_FRAME, 407 | SFX(0xC,0x31), SFX_FRAME, 408 | SFX_FRAME, SFX_FRAME, SFX_END }; 409 | 410 | const uint8 SFX_BOUNCE1[] = { SFX_BEGIN, 411 | SFX(0xC,0x31), SFX(0xE,0x0E), SFX_FRAME, 412 | SFX(0xC,0x35), SFX_FRAME, 413 | SFX(0xC,0x33), SFX_FRAME, 414 | SFX(0xC,0x32), SFX_FRAME, 415 | SFX(0xC,0x31), SFX_FRAME, SFX_END }; 416 | 417 | const uint8 SFX_BOUNCE2[] = { SFX_BEGIN, 418 | SFX(0xC,0x32), SFX(0xE,0x0C), SFX_FRAME, 419 | SFX(0xC,0x35), SFX_FRAME, 420 | SFX(0xC,0x34), SFX_FRAME, 421 | SFX(0xC,0x32), SFX_FRAME, 422 | SFX(0xC,0x31), SFX_FRAME, SFX_END }; 423 | 424 | const uint8 SFX_SPLASH[] = { SFX_BEGIN, 425 | SFX(0xC,0x36), SFX(0xE,0x0F), SFX_FRAME, 426 | SFX(0xC,0x36), SFX(0xE,0x09), SFX_FRAME, SFX_FRAME, SFX_FRAME, 427 | SFX(0xC,0x38), SFX(0xE,0x06), SFX_FRAME, 428 | SFX(0xC,0x3C), SFX_FRAME, 429 | SFX(0xC,0x39), SFX_FRAME, 430 | SFX(0xC,0x38), SFX_FRAME, 431 | SFX(0xC,0x37), SFX_FRAME, 432 | SFX(0xC,0x36), SFX_FRAME, 433 | SFX(0xC,0x35), SFX_FRAME, 434 | SFX(0xC,0x34), SFX_FRAME, 435 | SFX(0xC,0x33), SFX_FRAME, 436 | SFX(0xC,0x32), SFX_FRAME, SFX_FRAME, SFX_FRAME, SFX_FRAME, 437 | SFX(0xC,0x31), SFX_FRAME, SFX_FRAME, SFX_FRAME, SFX_FRAME, 438 | SFX_FRAME, SFX_FRAME, SFX_END }; 439 | 440 | const uint8 SFX_TEE[] = { SFX_BEGIN, 441 | SFX(0x0,0xB2), SFX(0x2,0x9D), SFX(0x3,0x05), SFX_FRAME, 442 | SFX(0x2,0xF9), SFX(0x3,0x02), SFX_FRAME, 443 | SFX(0x2,0x9D), SFX(0x3,0x05), SFX_FRAME, 444 | SFX(0x2,0x4C), SFX(0x3,0x05), SFX_FRAME, 445 | SFX(0x0,0xB3), SFX(0x2,0x80), SFX(0x3,0x02), SFX_FRAME, 446 | SFX(0x2,0xB8), SFX(0x3,0x04), SFX_FRAME, 447 | SFX(0x2,0x00), SFX(0x3,0x05), SFX_FRAME, 448 | SFX(0x2,0x5C), SFX(0x3,0x02), SFX_FRAME, 449 | SFX(0x2,0x74), SFX(0x3,0x04), SFX_FRAME, 450 | SFX(0x2,0x34), SFX(0x3,0x04), SFX_FRAME, 451 | SFX(0x2,0xFB), SFX(0x3,0x01), SFX_FRAME, 452 | SFX(0x2,0xBF), SFX(0x3,0x03), SFX_FRAME, 453 | SFX(0x2,0x89), SFX(0x3,0x03), SFX_FRAME, 454 | SFX(0x2,0xAB), SFX(0x3,0x01), SFX_FRAME, 455 | SFX(0x2,0x89), SFX(0x3,0x03), SFX_FRAME, 456 | SFX(0x2,0xCE), SFX(0x3,0x02), SFX_FRAME, 457 | SFX(0x0,0xB1), SFX(0x2,0x89), SFX(0x3,0x03), SFX_FRAME, 458 | SFX(0x2,0xAB), SFX(0x3,0x01), SFX_FRAME, 459 | SFX(0x2,0x89), SFX(0x3,0x03), SFX_FRAME, 460 | SFX(0x2,0xCE), SFX(0x3,0x02), SFX_FRAME, SFX_END }; 461 | 462 | const uint8 SFX_PROMPT0[] = { SFX_BEGIN, 463 | SFX(0x0, 0xB3), SFX(0x2,0xFB), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 464 | SFX(0x2,0xC4), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 465 | SFX(0x2,0x52), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 466 | SFX(0x0, 0xB1), SFX_FRAME, SFX_FRAME, 467 | SFX_FRAME, SFX_FRAME, SFX_END }; 468 | 469 | const uint8 SFX_PROMPT1[] = { SFX_BEGIN, 470 | SFX(0x0, 0x73), SFX(0x2,0x7C), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 471 | SFX(0x2,0xAB), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 472 | SFX(0x2,0x3A), SFX(0x3,0x02), SFX_FRAME, SFX_FRAME, 473 | SFX(0x0, 0x71), SFX_FRAME, SFX_FRAME, 474 | SFX_FRAME, SFX_FRAME, SFX_END }; 475 | 476 | const uint8 SFX_PROMPT2[] = { SFX_BEGIN, 477 | SFX(0x0, 0xB3), SFX(0x2,0xAB), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 478 | SFX(0x2,0x52), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 479 | SFX(0x2,0x1C), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 480 | SFX(0x0, 0xB1), SFX_FRAME, SFX_FRAME, 481 | SFX_FRAME, SFX_FRAME, SFX_END }; 482 | 483 | const uint8 SFX_PROMPT3[] = { SFX_BEGIN, 484 | SFX(0x0, 0x33), SFX(0x2,0xFD), SFX(0x3,0x00), SFX_FRAME, SFX_FRAME, 485 | SFX(0x2,0x2D), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 486 | SFX(0x2,0x7C), SFX(0x3,0x01), SFX_FRAME, SFX_FRAME, 487 | SFX(0x0, 0x31), SFX_FRAME, SFX_FRAME, 488 | SFX_FRAME, SFX_FRAME, SFX_END }; 489 | 490 | const uint8* const SFX_PROMPTS[4] = { 491 | SFX_PROMPT0, 492 | SFX_PROMPT1, 493 | SFX_PROMPT2, 494 | SFX_PROMPT3, 495 | }; 496 | 497 | // 498 | // misc 499 | // 500 | 501 | void debug_assert(int v) 502 | { 503 | if (!v) 504 | { 505 | ppu_profile(0x81); // red tinted grey 506 | while(1); // infinite loop 507 | } 508 | } 509 | 510 | uint16 mag_squared_s8(sint8 x, sint8 y) 511 | { 512 | if (x < 0) x = -x; 513 | if (y < 0) y = -y; 514 | return ((uint16)x * x) + ((uint16)y * y); 515 | } 516 | 517 | // 518 | // graphical stuff 519 | // 520 | 521 | void palette_generate(uint8 sky, uint8 ground, uint8 text) 522 | { 523 | palette[ 0] = palette[16] = 524 | palette[ 2] = palette[10] = 525 | palette[ 5] = palette[13] = sky; 526 | palette[26] = 527 | palette[ 9] = palette[11] = 528 | palette[14] = palette[15] = ground; 529 | palette[ 1] = palette[ 3] = 530 | palette[ 6] = palette[ 7] = text; 531 | //palette[26] = 0x21; // for debugging tee 532 | 533 | if (ocean_attribute != 255) 534 | { 535 | palette[3] = WATER_COLOUR; 536 | } 537 | } 538 | 539 | void ball_draw_setup() 540 | { 541 | // colour for current player 542 | i = BALL_COLOUR[player]; 543 | palette[17] = i | 0x10; 544 | palette[18] = i | 0x20; 545 | palette[19] = i | 0x30; 546 | 547 | // 3 remaining players packed into one palette 548 | balls_draw[0] = player; 549 | for (i=1; i<4; ++i) 550 | { 551 | if (i >= players) balls_draw[i] = 4; // hidden 552 | else balls_draw[i] = (player + i) % players; 553 | } 554 | 555 | for (i=1; i<4; ++i) 556 | { 557 | palette[20+i] = BALL_COLOUR[balls_draw[i]] | 0x10; 558 | } 559 | } 560 | 561 | uint8 attribute(tl,tr,bl,br) 562 | { 563 | return tl|(tr<<2)|(bl<<4)|(br<<6); 564 | } 565 | 566 | void ppu_text(const char* text, uint16 addr) 567 | { 568 | ptr = (uint8*)text; 569 | nx = addr; 570 | ppu_latch(nx); 571 | i = *ptr; 572 | while (i) 573 | { 574 | if (i == '\n') 575 | { 576 | nx += 32; 577 | ppu_latch(nx); 578 | } 579 | else ppu_write(i); 580 | ++ptr; 581 | i = *ptr; 582 | } 583 | } 584 | 585 | void cls() // erase nametables 586 | { 587 | ppu_latch(0x2000); 588 | ppu_fill(0x00,0x1000); 589 | } 590 | 591 | void sprite_begin() 592 | { 593 | oam_pos = 4; 594 | // sprite 0 always untouched 595 | } 596 | 597 | void sprite_end() 598 | { 599 | //while (oam_pos != 0) 600 | while (oam_pos < (32*4)) // leaves last 32 for weather 601 | { 602 | oam[oam_pos] = 0xFF; 603 | oam_pos += 4; 604 | } 605 | } 606 | 607 | void sprite_add(uint8 tile, uint8 x, uint8 y, uint8 attrib) 608 | { 609 | (oam+2)[oam_pos] = attrib; 610 | (oam+0)[oam_pos] = (uint8)(y-1); 611 | (oam+3)[oam_pos] = x; 612 | (oam+1)[oam_pos] = tile; 613 | oam_pos += 4; 614 | } 615 | 616 | // 617 | // floor generation 618 | // 619 | 620 | void floor_build_pixel_calculate_range() 621 | { 622 | fg_min_soft = (fg_min/4) + (fg_max/4) + (fg_min/2); 623 | fg_max_soft = (fg_min/4) + (fg_max/4) + (fg_max/2); 624 | } 625 | 626 | void floor_build_pixel_angle() 627 | { 628 | fg_hold = (prng() & fg_hold_mask) + 8; 629 | fg_angle = prng() & fg_angle_mask; 630 | 631 | // drive toward centre 632 | if (fg_last < fg_min_soft) fg_angle &= 0x7F; 633 | else if (fg_last > fg_max_soft) fg_angle |= 0x80; 634 | } 635 | 636 | void floor_build_pixel_next() 637 | { 638 | fg_next = fg_last + read_slope(fg_angle); 639 | } 640 | 641 | const uint8 HOLE_ANGLE[8] = { 4, 4, 0, 0, 0x80+0, 0x80+0, 0x80+4, 0x80+4 }; 642 | 643 | // builds one pixel worth of floor 644 | // note that this should always be 8 pixels ahead of floor_column, 645 | // so that floor_render_prepare has 8 pixels worth of stuff to build, 646 | // (and floor_column should be another 8 pixels ahead of the scroll) 647 | void floor_build_pixel() 648 | { 649 | uint16 h; 650 | 651 | if (ocean_attribute != 255) goto build; 652 | 653 | h = fg_cx - hole_cx; 654 | if (h < 8) 655 | { 656 | if (hole == 0) 657 | { 658 | ocean_attribute = ((floor_column + 1) / 4) & 15; 659 | fg_last = fg_next = OCEAN_FLOOR * 256U; 660 | fg_angle = 0; 661 | palette[3] = WATER_COLOUR; 662 | goto build; 663 | } 664 | 665 | fg_hold = 0; 666 | fg_angle = HOLE_ANGLE[(uint8)h]; 667 | floor_build_pixel_next(); 668 | goto build; 669 | } 670 | 671 | if (fg_hold) --fg_hold; 672 | else floor_build_pixel_angle(); 673 | 674 | floor_build_pixel_next(); 675 | if (fg_next < fg_min) 676 | { 677 | floor_build_pixel_angle(); 678 | fg_angle &= 0x7F; // force positive 679 | floor_build_pixel_next(); 680 | } 681 | else if (fg_next > fg_max) 682 | { 683 | floor_build_pixel_angle(); 684 | fg_angle |= 0x80; // force negative 685 | floor_build_pixel_next(); 686 | } 687 | 688 | build: 689 | floor_y[fg_cx] = ((fg_last/2) + (fg_next/2)) / 256; 690 | floor_a[fg_cx] = fg_angle; 691 | 692 | fg_last = fg_next; 693 | fg_cx = (fg_cx + 1) & 511; 694 | } 695 | 696 | // 697 | // hole generation 698 | // 699 | 700 | // snow rain brwn pink dbrn gren purp yell, night... 701 | const uint8 set_f[16] = { 0x30, 0x1A, 0x17, 0x25, 0x07, 0x19, 0x13, 0x27, 0x30, 0x1A, 0x17, 0x15, 0x07, 0x19, 0x13, 0x37 }; 702 | const uint8 set_s[16] = { 0x21, 0x2B, 0x27, 0x35, 0x17, 0x21, 0x23, 0x37, 0x0F, 0x0B, 0x0F, 0x05, 0x0F, 0x09, 0x03, 0x01 }; 703 | const uint8 set_t[16] = { 0x0F, 0x30, 0x0F, 0x0F, 0x30, 0x0F, 0x0F, 0x0F, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 }; 704 | const uint8 set_w[16] = { 0x39, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 705 | 706 | // 18 hole repeating course terrain structure 707 | 708 | // angle mask: 709 | // 0 artificial angular, mostly flat 710 | // 1 favour moderate slopes 711 | // 2 all slopes 712 | #define AM0 0x9F 713 | #define AM1 0xBF 714 | #define AM2 0xFF 715 | 716 | // hold mask: 717 | // 0 short segments 718 | // 1 medium segments 719 | // 2 long segments 720 | #define HM0 15 721 | #define HM1 31 722 | #define HM2 63 723 | 724 | const uint8 course_hm[18] = { HM1,HM0,HM1,HM0,HM1,HM1,HM2,HM1,HM1, HM1,HM1,HM2,HM1,HM0,HM2,HM1,HM2,HM1 }; 725 | const uint8 course_am[18] = { AM0,AM0,AM1,AM1,AM1,AM2,AM1,AM1,AM2, AM1,AM2,AM1,AM2,AM2,AM2,AM1,AM2,AM1 }; 726 | 727 | void hole_next() 728 | { 729 | ++hole; 730 | for (i=0;i<3;++i) 731 | { 732 | ++hole_digits[i]; 733 | if (hole_digits[i] < 10) break; 734 | hole_digits[i] = 0; 735 | } 736 | 737 | #if LAST_HOLE_TEST 738 | if (hole == LAST_HOLE_TEST) hole = 0; 739 | #endif 740 | 741 | if (te) // TE configurable number of holes 742 | { 743 | if (hole == (holes & 0xFF)) hole = 0; 744 | } 745 | 746 | fg_hold_mask = course_hm[course]; 747 | fg_angle_mask = course_hm[course]; 748 | ++course; if (course >= 18) course = 0; 749 | 750 | mx = hole_cx - scroll_cx; 751 | if (hole_cx < scroll_cx) mx += 512; 752 | i = (mx / 8) + 7; // min screen column of next hole 753 | j = i + 13; // max screen column of next hole 754 | if (!hole) j = i+1; // "last hole" should be close 755 | if (i < 34) i = 34; // must be at least 2 tiles offscreen 756 | k = j - i; // allowed range 757 | if (k == 0 || k >= 16) k = 1; // prevent invalid ranges (shouldn't occur?) 758 | l = i + (prng() % k); // new hole column 759 | 760 | ASSERT((scroll_cx & 7) == 0); 761 | if (!hole) 762 | { 763 | // final hole must fall on 32-pixel attribute boundary 764 | while (((scroll_cx + (l*8)) & 24) != 0) ++l; 765 | // force terrain to go down toward ocean 766 | fg_min = (GLOBAL_FLOOR - 8) * 256U; 767 | floor_build_pixel_calculate_range(); 768 | } 769 | 770 | tee_cx = hole_cx; // last hole becomes new tee 771 | hole_cx = (scroll_cx + (l * 8)) & 511; // place new hole 772 | hole_sx = l * 8; // screen position of new hole 773 | 774 | if (!hole) hole_sx = 2048; // place it offscreen for the ocean 775 | } 776 | 777 | // 778 | // common animation 779 | // 780 | 781 | #define TRANSITION_TIME 16 782 | #define DAY_TIME 8 783 | 784 | void transition_animate() 785 | { 786 | uint8 s, f; 787 | 788 | --transition; 789 | if (transition == ((TRANSITION_TIME*3)/4)) 790 | { 791 | s = blend25(pal_sky, set_s[field_set]); 792 | f = blend25(pal_floor, set_f[field_set]); 793 | palette_generate(s,f,s); 794 | } 795 | else if (transition == ((TRANSITION_TIME*2)/4)) 796 | { 797 | s = blend50(pal_sky, set_s[field_set]); 798 | f = blend50(pal_floor, set_f[field_set]); 799 | palette_generate(s,f,s); 800 | } 801 | else if (transition == ((TRANSITION_TIME*1)/4)) 802 | { 803 | s = blend25(set_s[field_set], pal_sky); 804 | f = blend25(set_f[field_set], pal_floor); 805 | palette_generate(s,f,s); 806 | } 807 | else if (transition == 0) 808 | { 809 | pal_sky = set_s[field_set]; 810 | pal_floor = set_f[field_set]; 811 | palette_generate(pal_sky, pal_floor, pal_sky); 812 | } 813 | } 814 | 815 | void weather_fade_automask() 816 | { 817 | // scale back the randomness at each power of 2 818 | if ((weather_rate_min & (weather_rate_min-1)) == 0) 819 | { 820 | weather_rate_mask = weather_rate_min-1; 821 | } 822 | } 823 | 824 | void weather_wind_random() 825 | { 826 | if (!te) 827 | { 828 | weather_wind_fade_dir = 1 - ((prng1() & 1) * 2); // 1 or -1, wind direction 829 | weather_wind_fade_p = prng(); // wind strength 830 | weather_wind_timeout = ((prng() & 15) + 32) * 64; // 30-50 seconds before wind changes 831 | } 832 | else 833 | { 834 | weather_wind_fade_dir = 1 - ((prngw1() & 1) * 2); // 1 or -1, wind direction 835 | weather_wind_fade_p = prngw(); // wind strength 836 | weather_wind_timeout = ((prngw() & 15) + 32) * 64; // 30-50 seconds before wind changes 837 | } 838 | } 839 | 840 | void weather_fade() 841 | { 842 | if (weather_wind_timeout == 0) 843 | weather_wind_random(); 844 | else 845 | --weather_wind_timeout; 846 | 847 | // smoothly change wind 848 | if (weather_wind_dir != weather_wind_fade_dir) 849 | { 850 | if (weather_wind_p == 0) weather_wind_dir = weather_wind_fade_dir; 851 | else --weather_wind_p; 852 | } 853 | else 854 | { 855 | if (weather_wind_p < weather_wind_fade_p) ++weather_wind_p; 856 | else if (weather_wind_p > weather_wind_fade_p) --weather_wind_p; 857 | } 858 | 859 | // smoothly change particles drop rate 860 | 861 | if (frame_count & 15) return; 862 | 863 | if (set_w[field_set]) 864 | { 865 | if (weather_rate_min == 0) 866 | { 867 | weather_rate_min = 64; 868 | weather_fade_automask(); 869 | } 870 | else if (weather_rate_min > 4) 871 | { 872 | --weather_rate_min; 873 | weather_fade_automask(); 874 | } 875 | } 876 | else 877 | { 878 | if (weather_rate_min != 0) 879 | { 880 | ++weather_rate_min; 881 | weather_fade_automask(); 882 | if (weather_rate_min >= 65) weather_rate_min = 0; 883 | } 884 | } 885 | } 886 | 887 | void weather_attribute_set() 888 | { 889 | // sets speed and palette via whether tile is rain or snow 890 | // the unused bits of the OAM attribute byte are used to control particle fall speed 891 | weather_attribute = (weather_tile == 0x38) ? ((4<<2)|3) : ((1<<2)|2); 892 | } 893 | 894 | void flag_animate() 895 | { 896 | uint8 t; 897 | 898 | if (flag_remove == 0) return; 899 | t = hole_sy - 32; 900 | if (t > hole_sy) 901 | { 902 | hole_sy = 255; 903 | flag_remove = 0; 904 | return; 905 | } 906 | hole_sy = t; 907 | } 908 | 909 | void frame() 910 | { 911 | if (transition) transition_animate(); 912 | weather_fade(); 913 | weather_animate(); 914 | flag_animate(); 915 | PROFILE(); 916 | ppu_post(POST_UPDATE); 917 | ++frame_count; 918 | } 919 | 920 | void frame_double() 921 | { 922 | weather_fade(); 923 | weather_animate(); 924 | flag_animate(); 925 | PROFILE(); 926 | ppu_post(POST_DOUBLE); 927 | ++frame_count; 928 | } 929 | 930 | void frames(uint8 count) 931 | { 932 | while (count--) frame(); 933 | } 934 | 935 | void delay(uint8 frames) 936 | { 937 | while (frames--) frame(); 938 | } 939 | 940 | // 941 | // main menu 942 | // 943 | 944 | const char help_text[] = 945 | " NESert Golfing\n" 946 | " Brad Smith, 2019\n" 947 | " http://rainwarrior.ca\n" 948 | "\n" 949 | "GAMEPAD CONTROL:\n" 950 | " Hold A to begin swing,\n" 951 | " use directions to aim,\n" 952 | " release to stroke.\n" 953 | " Hold B for fine control.\n" 954 | "\n" 955 | "MOUSE CONTROL:\n" 956 | " Hold left button to begin\n" 957 | " swing, drag to aim,\n" 958 | " release to stroke.\n" 959 | " Hold right button for\n" 960 | " fine control.\n" 961 | "\n" 962 | " NESert Golfing was derived\n" 963 | " from the original game\n" 964 | " Desert Golfing\n" 965 | " by Justin Smith, 2014."; 966 | 967 | const char title_text[] = 968 | " Play\n" 969 | "\n" 970 | "How many? 1 2 3 4\n" 971 | "\n" 972 | " Help"; 973 | 974 | const char help_text_te[] = 975 | " NESert Golfing " VERSION_STRING "\n" 976 | " Brad Smith, 2019-2022\n" 977 | " http://rainwarrior.ca\n" 978 | "\n" 979 | "GAMEPAD CONTROL:\n" 980 | " Hold A to begin swing,\n" 981 | " use directions to aim,\n" 982 | " release to stroke.\n" 983 | " Hold B for fine control.\n" 984 | "\n" 985 | "MOUSE CONTROL:\n" 986 | " Hold left button to begin\n" 987 | " swing, drag to aim,\n" 988 | " release to stroke.\n" 989 | " Hold right button for\n" 990 | " fine control.\n" 991 | "\n" 992 | " NESert Golfing was derived\n" 993 | " from the original game\n" 994 | " Desert Golfing\n" 995 | " by Justin Smith, 2014."; 996 | 997 | const char title_text_te[] = 998 | " Play\n" 999 | "\n" 1000 | "How many? 1 2 3 4\n" 1001 | "\n" 1002 | " Seed: \n" 1003 | "\n" 1004 | " Holes: \n" 1005 | "\n" 1006 | " Help"; 1007 | 1008 | #define MOUSE_STEADY_TIME 24 1009 | #define MOUSE_STEADY_MOVE 12 1010 | 1011 | void new_poll() 1012 | { 1013 | input_poll(); 1014 | gamepad_new = gamepad & (gamepad ^ gamepad_last); 1015 | mouse_new = mouse1 & (mouse1 ^ mouse_last); 1016 | gamepad_last = gamepad; 1017 | mouse_last = mouse1; 1018 | 1019 | // filter out new mouse motions until it's been stationary for a few frames 1020 | mouse_sx = 0; 1021 | mouse_sy = 0; 1022 | if (mouse_steady) 1023 | { 1024 | --mouse_steady; 1025 | } 1026 | else 1027 | { 1028 | if (mouse3 >= MOUSE_STEADY_MOVE || mouse3 <= -MOUSE_STEADY_MOVE) 1029 | { 1030 | mouse_sx = mouse3; 1031 | mouse_steady = MOUSE_STEADY_TIME; 1032 | } 1033 | if (mouse2 >= MOUSE_STEADY_MOVE || mouse2 <= -MOUSE_STEADY_MOVE ) 1034 | { 1035 | mouse_sy = mouse2; 1036 | mouse_steady = MOUSE_STEADY_TIME; 1037 | } 1038 | } 1039 | } 1040 | 1041 | void help() 1042 | { 1043 | #define HELP_SCROLL_RATE 16 1044 | 1045 | sprite_begin(); 1046 | sprite_end(); 1047 | 1048 | // scroll in 1049 | nx = 512; 1050 | for (i = 0; i < (256 / HELP_SCROLL_RATE); ++i) 1051 | { 1052 | nx -= HELP_SCROLL_RATE; 1053 | ppu_scroll_x(nx); 1054 | weather_shift(HELP_SCROLL_RATE); 1055 | frame(); 1056 | new_poll(); 1057 | } 1058 | 1059 | while (1) // wait for button 1060 | { 1061 | ppu_scroll_x(256); 1062 | frame(); 1063 | new_poll(); 1064 | 1065 | // TE hold A, B or START when entering HELP screen and press SELECT to switch mode 1066 | if ((gamepad_new & PAD_SELECT) && (gamepad & (PAD_A | PAD_B | PAD_START))) te_switch(); 1067 | 1068 | if (gamepad_new || mouse_new) break; 1069 | } 1070 | 1071 | // scroll out 1072 | nx = 256; 1073 | for (i=0; i < (256 / HELP_SCROLL_RATE); ++i) 1074 | { 1075 | nx += HELP_SCROLL_RATE; 1076 | ppu_scroll_x(nx); 1077 | weather_shift(-HELP_SCROLL_RATE); 1078 | frame(); 1079 | new_poll(); 1080 | } 1081 | 1082 | return; 1083 | } 1084 | 1085 | const uint8 TITLE_NMT_TE[16*8] = { // TE 1086 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x00, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 1087 | 0x20, 0x21, 0x22, 0x33, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 1088 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x00, 0x3E, 0x3F, 1089 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x00, 0x4E, 0x4F, 1090 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 1091 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 1092 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 1093 | 0x19, 0x23, 0x3D, 0x4D, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 1094 | }; 1095 | 1096 | void menu_te(void); // forward 1097 | 1098 | void title() 1099 | { 1100 | cls(); 1101 | 1102 | palette[25] = 0x2D; // grey tee / flagpole 1103 | // 26 // floor (palette_generate) 1104 | palette[27] = 0x30; // white snow 1105 | palette[29] = 0x00; // unused 1106 | palette[30] = 0x00; // unused 1107 | palette[31] = WATER_COLOUR; // rain 1108 | 1109 | gamepad_last = gamepad; 1110 | mouse_last = mouse1; 1111 | mouse_steady = 0; 1112 | mouse_sx = 0; 1113 | mouse_sy = 0; 1114 | 1115 | players = 1; 1116 | frame_count = 0; 1117 | ocean_attribute = 255; 1118 | flag_remove = 0; 1119 | course = 0; 1120 | field_set = prng() & 15; 1121 | pal_floor = set_f[field_set]; 1122 | pal_sky = set_s[field_set]; 1123 | pal_text = set_t[field_set]; 1124 | weather_tile = set_w[field_set]; 1125 | weather_rate_mask = 3; 1126 | weather_rate_min = weather_tile ? 4 : 0; 1127 | weather_wind_random(); 1128 | weather_wind_p = weather_wind_fade_p; 1129 | weather_wind_dir = weather_wind_fade_dir; 1130 | weather_attribute_set(); 1131 | transition = 0; 1132 | transition_time = (prng() & 7) + 3; 1133 | day = prng() & 7; 1134 | 1135 | // keep hole and start off the field for menu 1136 | hole_cx = 512; 1137 | tee_cx = 512; 1138 | 1139 | // build initial floor 1140 | fg_cx = 256; 1141 | fg_max = GLOBAL_FLOOR * 256U; // global maximum 1142 | fg_min = (192+3) * 256U; // 3 pixels below help text 1143 | fg_angle_mask = 0x80 | 0x3F; // shallow hills 1144 | floor_build_pixel_calculate_range(); 1145 | fg_hold_mask = 7; // short hills 1146 | fg_last = fg_min + (prng() % (fg_max - fg_min)); 1147 | fg_hold = 0; 1148 | for (mx = 0; mx < 256; ++mx) floor_build_pixel(); 1149 | fg_min = (176+3) * 256U; // 3 pixels below menu 1150 | floor_build_pixel_calculate_range(); 1151 | for (mx = 0; mx < 256; ++mx) floor_build_pixel(); 1152 | fg_min = 48 * 256U; // global minimum 1153 | floor_build_pixel_calculate_range(); 1154 | 1155 | // render initial floor 1156 | floor_column = 32; 1157 | for (mx = 0; mx < 64; ++mx) 1158 | { 1159 | for (j=0;j<6;++j) 1160 | { 1161 | floor_render_prepare(j); 1162 | if (j>0) ppu_apply(); 1163 | } 1164 | } 1165 | ppu_apply_direction(0); 1166 | 1167 | // title 1168 | if (!te) 1169 | { 1170 | i = 16; 1171 | for (j=0; j<7; ++j) 1172 | { 1173 | ppu_latch(0x2000 + 8 + ((4+j) * 32)); 1174 | for (k=0; k<16; ++k) { ppu_write(i); ++i; } 1175 | } 1176 | ppu_latch(0x2000 + (8+13) + ((4+7) * 32)); 1177 | ppu_write(13); 1178 | ppu_write(14); 1179 | ppu_write(15); 1180 | } 1181 | else 1182 | { 1183 | i = 0; 1184 | for (j=0; j<8; ++j) 1185 | { 1186 | ppu_latch(0x2000 + 8 + ((4+j) * 32)); 1187 | for (k=0; k<16; ++k) { ppu_write(TITLE_NMT_TE[i]); ++i; } 1188 | } 1189 | } 1190 | 1191 | // menu 1192 | ppu_latch(0x23C0 + 0 + (12*2)); 1193 | ppu_fill(attribute(1,1,1,1),16); 1194 | if (!te) 1195 | { 1196 | ppu_fill(attribute(2,2,2,2),24); 1197 | ppu_text(title_text, 0x2000 + 5 + (15 * 32)); 1198 | } 1199 | else 1200 | { 1201 | ppu_fill(attribute(1,1,2,2),8); 1202 | ppu_fill(attribute(2,2,2,2),16); 1203 | ppu_text(title_text_te, 0x2000 + 5 + (13 * 32)); 1204 | } 1205 | 1206 | // help 1207 | ppu_latch(0x27C0 + 0 + (0*2)); 1208 | ppu_fill(attribute(1,1,1,1),48); 1209 | ppu_fill(attribute(3,3,3,3),16); 1210 | if (!te) 1211 | ppu_text(help_text, 0x2400 + 2 + (3 * 32)); 1212 | else 1213 | ppu_text(help_text_te, 0x2400 + 2 + (2 * 32)); 1214 | 1215 | palette_generate(pal_sky, pal_floor, pal_text); 1216 | 1217 | title_menu = 0; 1218 | 1219 | ppu_scroll_x(0); 1220 | ppu_scroll_y(0); 1221 | palette[18] = 0x24; 1222 | 1223 | if (te) 1224 | { 1225 | seed_pos = 0; 1226 | holes_pos = 2; 1227 | menu_te(); 1228 | } 1229 | else while (1) 1230 | { 1231 | #define MENU_Y0 (15*8) 1232 | #define MENU_Y1 (17*8) 1233 | #define MENU_Y2 (19*8) 1234 | #define MENU_X0 (12*8) 1235 | #define MENU_X1 (19*8) 1236 | #define MENU_XP1 ((16-3)*8) 1237 | 1238 | palette[22] = BALL_COLOUR[players-1]; 1239 | i = (frame_count / 8) & 3; 1240 | if (i == 0) i = 2; 1241 | palette[22] |= i << 4; 1242 | 1243 | sprite_begin(); 1244 | if (title_menu != 1) 1245 | { 1246 | uint8 i = title_menu==0 ? MENU_Y0 : MENU_Y2; 1247 | sprite_add(0x3A, MENU_X0,i,0x00); 1248 | sprite_add(0x3A, MENU_X1,i,0x40); 1249 | palette[22] = (palette[22] & 0x0F) | 0x10; // darken, not selected 1250 | } 1251 | sprite_add(0x3A, MENU_XP1+(players*24), MENU_Y1, 0x01); 1252 | sprite_add(0x3A, MENU_XP1+16+(players*24), MENU_Y1, 0x41); 1253 | sprite_end(); 1254 | 1255 | frame(); 1256 | new_poll(); 1257 | 1258 | if ((mouse_new & MOUSE_L) || (gamepad_new & PAD_START)) // START or LMB starts game unless help is selected 1259 | { 1260 | if (title_menu != 2) break; 1261 | else help(); 1262 | } 1263 | else if (mouse_new & MOUSE_R) // RMB cycles players 1264 | { 1265 | players = (players & 3) + 1; 1266 | } 1267 | else if ((title_menu != 1) && (gamepad_new & (PAD_A | PAD_B))) // A/B can also start game or help 1268 | { 1269 | if (title_menu != 2) break; 1270 | else help(); 1271 | } 1272 | else if ((gamepad_new & (PAD_DOWN | PAD_SELECT)) || mouse_sy > 0) // SELECT or down on pad/mouse to switch selection 1273 | { 1274 | ++title_menu; 1275 | if (title_menu > 2) title_menu = 0; 1276 | } 1277 | else if ((gamepad_new & PAD_UP) || mouse_sy < 0) // up on pad/mouse to switch selection (reverse) 1278 | { 1279 | --title_menu; 1280 | if (title_menu > 2) title_menu = 2; 1281 | } 1282 | else if (title_menu == 1) // players selection 1283 | { 1284 | if ((gamepad_new & (PAD_A | PAD_RIGHT)) || mouse_sx > 0) // A or right on pad/mouse to increase players 1285 | { 1286 | players = (players & 3) + 1; 1287 | } 1288 | else if ((gamepad_new & PAD_LEFT) || mouse_sx < 0) // left on pad/mouse to decrease players 1289 | { 1290 | players = ((players + 2) & 3) + 1; 1291 | } 1292 | } 1293 | 1294 | prng(); // build up entropy 1295 | 1296 | // colour cycle the cursor 1297 | if (!(frame_count & 7)) 1298 | { 1299 | i = (palette[18] & 0x0F) + 1; 1300 | if (i >= 0xD) i = 1; 1301 | palette[18] = 0x20 | i; 1302 | } 1303 | }; 1304 | 1305 | // prepare for game 1306 | 1307 | // clear sprites 1308 | sprite_begin(); 1309 | sprite_end(); 1310 | 1311 | // fade out text 1312 | i = pal_text; 1313 | palette[1] = palette[3] = palette[6] = palette[7] = blend25(i,pal_sky); frames(2); 1314 | palette[1] = palette[3] = palette[6] = palette[7] = blend50(i,pal_sky); frames(2); 1315 | palette[1] = palette[3] = palette[6] = palette[7] = blend25(pal_sky,i); frames(2); 1316 | palette[1] = palette[3] = palette[6] = palette[7] = pal_sky; 1317 | 1318 | // wipe existing text 1319 | for (i=0; i<64; ++i) ppu_send[i] = 0; 1320 | for (i=3; i<(te ? 22 : 20); ++i) 1321 | { 1322 | ppu_send_addr = 0x2000 + (32 * i); 1323 | frame_double(); 1324 | } 1325 | for (i=20; i<24; ++i) 1326 | { 1327 | ppu_send_addr = 0x2400 + (32 * i); 1328 | ppu_send_count = 32; 1329 | frame(); 1330 | } 1331 | 1332 | // replace attributes 1333 | j = attribute(1,1,1,1); for (i=0; i< 8; ++i) ppu_send[i] = j; 1334 | j = attribute(2,2,2,2); for (i=8; i<64; ++i) ppu_send[i] = j; 1335 | ppu_send_addr = 0x23C0; 1336 | ppu_send_count = 64; 1337 | frame(); 1338 | j = attribute(3,3,3,3); for (i=8; i<64; ++i) ppu_send[i] = j; 1339 | ppu_send_addr = 0x27C0; 1340 | ppu_send_count = 64; 1341 | frame(); 1342 | 1343 | // TE apply starting seed 1344 | if (te) 1345 | { 1346 | seed = seed_start; 1347 | for (i=0; i<8; ++i) prng(); // prime the RNG a little to increase "distance" from starting seed 1348 | seedw = seed; 1349 | transition_time = prng() & 3; // give it a slightly low value to encourage the seed to affect the landscape quickly 1350 | day = prng() & 7; 1351 | } 1352 | 1353 | // create first tee (flat ground) 1354 | hole_cx = 256; 1355 | tee_sx = 256; 1356 | tee_sy = fg_last / 256; 1357 | tee_s = 7; 1358 | for (i=0; i<8; ++i) 1359 | { 1360 | floor_y[fg_cx+i] = tee_sy; 1361 | floor_a[fg_cx+i] = 0; 1362 | } 1363 | fg_cx += 8; 1364 | scroll_cx = 0; 1365 | hole = 0; 1366 | hole_digits[0] = hole_digits[1] = hole_digits[2] = 0; 1367 | hole_next(); // hole_cx converts to tee_cx, new hole_cx is created 1368 | 1369 | // render the tee, and keep the floor build 8 pixels ahead of it 1370 | for (i=0; i<8; ++i) 1371 | { 1372 | floor_build_pixel(); 1373 | floor_render_prepare(i); 1374 | frame(); 1375 | } 1376 | 1377 | // place players on tee, reset other variables 1378 | status_sx = 256; 1379 | balls_x[0] = 1380 | balls_x[1] = 1381 | balls_x[2] = 1382 | balls_x[3] = 256 * 256UL; 1383 | balls_y[0] = 1384 | balls_y[1] = 1385 | balls_y[2] = 1386 | balls_y[3] = tee_sy - 6; 1387 | 1388 | ball_s = 0; 1389 | splash_s = 0; 1390 | player = 0; 1391 | next_player = 0; 1392 | swinging = 0; 1393 | status_w = 0; 1394 | ring_glow = 0; 1395 | first_stroke = (players > 1) ? 0 : 1; 1396 | status_w = (16 - (((7 * players) - 2) / 2)); // position to start the strokes display 1397 | ball_draw_setup(); 1398 | 1399 | return; // hole_play() 1400 | } 1401 | 1402 | void seed_start_up() // TE 1403 | { 1404 | j = 20 - (seed_pos * 4); 1405 | i = (seed_start >> j) & 0xF; 1406 | seed_start &= (0xFFFFFFFFUL ^ (0xFUL << j)); 1407 | i = (i+1) & 0xF; 1408 | seed_start |= ((uint32)i) << j; 1409 | } 1410 | 1411 | void seed_start_down() // TE 1412 | { 1413 | j = 20 - (seed_pos * 4); 1414 | i = (seed_start >> j) & 0xF; 1415 | seed_start &= (0xFFFFFFFFUL ^ (0xFUL << j)); 1416 | i = (i-1) & 0xF; 1417 | seed_start |= ((uint32)i) << j; 1418 | } 1419 | 1420 | const uint16 HOLES_INC[3] = { 100, 10, 1 }; // TE 1421 | 1422 | void menu_te() // TE has different menu 1423 | { 1424 | while (1) 1425 | { 1426 | #undef MENU_Y0 1427 | #undef MENU_Y1 1428 | #undef MENU_Y2 1429 | #define MENU_Y0 (13*8) 1430 | #define MENU_Y1 (15*8) 1431 | #define MENU_Y2 (17*8) 1432 | #define MENU_Y3 (19*8) 1433 | #define MENU_Y4 (21*8) 1434 | #define MENU_X0 (12*8) 1435 | #define MENU_X1 (19*8) 1436 | #define MENU_XP1 ((16-3)*8) 1437 | #define MENU_XS0 (16*8) 1438 | #define MENU_XH0 (18*8) 1439 | #define TITLE_OPTS 5 1440 | 1441 | palette[22] = BALL_COLOUR[players-1]; 1442 | i = (frame_count / 8) & 3; 1443 | if (i == 0) i = 2; 1444 | palette[22] |= i << 4; 1445 | 1446 | palette[23] = palette[1]; // use text color for seed/holes number sprites 1447 | 1448 | sprite_begin(); 1449 | 1450 | if (title_menu == 0 || title_menu == 4) 1451 | { 1452 | uint8 i = title_menu==0 ? MENU_Y0 : MENU_Y4; 1453 | sprite_add(0x3A, MENU_X0,i,0x00); 1454 | sprite_add(0x3A, MENU_X1,i,0x40); 1455 | } 1456 | else if (title_menu == 2) 1457 | { 1458 | sprite_add(0x3B, MENU_XS0+(seed_pos*8), MENU_Y2-8,0x00); 1459 | sprite_add(0x3B, MENU_XS0+(seed_pos*8), MENU_Y2+7,0x80); 1460 | } 1461 | else if (title_menu == 3) 1462 | { 1463 | sprite_add(0x3B, MENU_XH0+(holes_pos*8), MENU_Y3-8,0x00); 1464 | sprite_add(0x3B, MENU_XH0+(holes_pos*8), MENU_Y3+7,0x80); 1465 | } 1466 | if (title_menu != 1) 1467 | { 1468 | palette[22] = (palette[22] & 0x0F) | 0x10; // darken, not selected 1469 | } 1470 | sprite_add(0x3A, MENU_XP1+(players*24), MENU_Y1, 0x01); 1471 | sprite_add(0x3A, MENU_XP1+16+(players*24), MENU_Y1, 0x41); 1472 | 1473 | // TE: numbers 1474 | // hex dump of seed 1475 | for (i=0; i<24; i+=4) 1476 | { 1477 | j = (seed_start >> i) & 0x0F; 1478 | sprite_add(j, MENU_XS0+(5*8)-(i*2), MENU_Y2, 0x01); 1479 | } 1480 | // decimal holes count 1481 | mx = holes; 1482 | if (holes >= 200) { sprite_add(2, MENU_XH0+0, MENU_Y3, 0x01); mx -= 200; } 1483 | else if (holes >= 100) { sprite_add(1, MENU_XH0+0, MENU_Y3, 0x01); mx -= 100; } 1484 | j = 0; 1485 | i = mx & 0xFF; // guaranteed to be < 100 1486 | while (i >= 10) 1487 | { 1488 | ++j; 1489 | i -= 10; 1490 | } // j = 10s digit 1491 | if (holes >= 100 || j > 0) { sprite_add(j, MENU_XH0+8, MENU_Y3, 0x01); }; 1492 | sprite_add(i, MENU_XH0+16, MENU_Y3, 0x01); 1493 | 1494 | sprite_end(); 1495 | 1496 | frame(); 1497 | new_poll(); 1498 | 1499 | switch (title_menu) 1500 | { 1501 | default: 1502 | title_menu=0; // failsafe? 1503 | case 0: 1504 | if ((mouse_new & MOUSE_L) || (gamepad_new & (PAD_START | PAD_A | PAD_B))) return; 1505 | if (mouse_new & MOUSE_R) players = (players & 3) + 1; 1506 | if ((gamepad_new & (PAD_DOWN | PAD_SELECT)) || mouse_sy > 0) ++title_menu; 1507 | if ((gamepad_new & PAD_UP) || mouse_sy < 0) title_menu = TITLE_OPTS-1; 1508 | break; 1509 | case 1: 1510 | if (gamepad_new & PAD_START) return; 1511 | if ((mouse_new & (MOUSE_L | MOUSE_R)) || (gamepad_new & (PAD_A | PAD_RIGHT)) || mouse_sx > 0) players = (players & 3) + 1; 1512 | if ((gamepad_new & PAD_LEFT) || mouse_sx < 0) players = ((players + 2) & 3) + 1; 1513 | if ((gamepad_new & (PAD_DOWN | PAD_SELECT)) || mouse_sy > 0) ++title_menu; 1514 | if ((gamepad_new & PAD_UP) || mouse_sy < 0) --title_menu; 1515 | break; 1516 | case 2: 1517 | if (gamepad_new & PAD_START) return; 1518 | if ((mouse1 & (MOUSE_L | MOUSE_R)) || (gamepad & (PAD_A | PAD_B))) 1519 | { 1520 | if ((gamepad_new & PAD_UP) || mouse_sy < 0) seed_start_up(); 1521 | if ((gamepad_new & PAD_DOWN) || mouse_sy > 0) seed_start_down(); 1522 | } 1523 | else 1524 | { 1525 | if ((gamepad_new & PAD_RIGHT) || mouse_sx > 0) { seed_pos += 1; if (seed_pos >= 6) seed_pos = 0; } 1526 | if ((gamepad_new & PAD_LEFT) || mouse_sx < 0) { if (seed_pos > 0) --seed_pos; else seed_pos = 5; } 1527 | if ((gamepad_new & (PAD_DOWN | PAD_SELECT)) || mouse_sy > 0) ++title_menu; 1528 | if ((gamepad_new & PAD_UP) || mouse_sy < 0) --title_menu; 1529 | } 1530 | break; 1531 | case 3: 1532 | if (gamepad_new & PAD_START) return; 1533 | if ((mouse1 & (MOUSE_L | MOUSE_R)) || (gamepad & (PAD_A | PAD_B))) 1534 | { 1535 | if ((gamepad_new & PAD_UP) || mouse_sy < 0) 1536 | { 1537 | holes += HOLES_INC[holes_pos]; 1538 | if (holes > 256) holes = 256; 1539 | } 1540 | if ((gamepad_new & PAD_DOWN) || mouse_sy > 0) 1541 | { 1542 | if (holes >= HOLES_INC[holes_pos]) holes -= HOLES_INC[holes_pos]; 1543 | if (holes < 2) holes = 2; 1544 | } 1545 | } 1546 | else 1547 | { 1548 | if ((gamepad_new & PAD_RIGHT) || mouse_sx > 0) { holes_pos += 1; if (holes_pos >= 3) holes_pos = 0; } 1549 | if ((gamepad_new & PAD_LEFT) || mouse_sx < 0) { if (holes_pos > 0) --holes_pos; else holes_pos = 2; } 1550 | if ((gamepad_new & (PAD_DOWN | PAD_SELECT)) || mouse_sy > 0) ++title_menu; 1551 | if ((gamepad_new & PAD_UP) || mouse_sy < 0) --title_menu; 1552 | } 1553 | break; 1554 | case 4: 1555 | if ((mouse_new & MOUSE_L) || (gamepad_new & (PAD_START | PAD_A | PAD_B))) help(); 1556 | if (mouse_new & MOUSE_R) players = (players & 3) + 1; 1557 | if ((gamepad_new & (PAD_DOWN | PAD_SELECT)) || mouse_sy > 0) title_menu=0; 1558 | if ((gamepad_new & PAD_UP) || mouse_sy < 0) --title_menu; 1559 | break; 1560 | } 1561 | 1562 | //prng(); // build up entropy, not desired for TE 1563 | 1564 | // colour cycle the cursor 1565 | if (!(frame_count & 7)) 1566 | { 1567 | i = (palette[18] & 0x0F) + 1; 1568 | if (i >= 0xD) i = 1; 1569 | palette[18] = 0x20 | i; 1570 | } 1571 | } 1572 | } 1573 | 1574 | // 1575 | // play loop 1576 | // 1577 | 1578 | void hole_shift() 1579 | { 1580 | --tee_sx; 1581 | --hole_sx; 1582 | balls_wx[0] -= 1; 1583 | balls_wx[2] -= 1; 1584 | balls_wx[4] -= 1; 1585 | balls_wx[6] -= 1; 1586 | } 1587 | 1588 | void hole_draw_flag() 1589 | { 1590 | if (hole_sy == 255) return; 1591 | 1592 | j = (uint8)hole_sx; 1593 | i = j + 2; 1594 | if (i < j) return; 1595 | 1596 | sprite_add(0x3C, i, hole_sy- 1, 0x02); // flagpole bottom 1597 | sprite_add(0x2C, i, hole_sy- 9, 0x02); // flagpole mid 1598 | sprite_add(0x1C, i, hole_sy-17, 0x02); // flagpole top 1599 | 1600 | i += 8; 1601 | k = hole_sy-16; 1602 | l = hole_sy-24; 1603 | if (i < j) return; 1604 | 1605 | if (hole >= 100) 1606 | { 1607 | sprite_add(0x10 | hole_digits[2], i, k, 0x02); 1608 | sprite_add(0x1B , i, l, 0x02); 1609 | i += 8; 1610 | if (i < j) return; 1611 | } 1612 | 1613 | if (hole >= 10) 1614 | { 1615 | sprite_add(0x10 | hole_digits[1], i, k, 0x02); 1616 | sprite_add(0x1B , i, l, 0x02); 1617 | i += 8; 1618 | if (i < j) return; 1619 | } 1620 | 1621 | sprite_add(0x10 | hole_digits[0], i, k, 0x02); // numeral 1622 | sprite_add(0x1B , i, l, 0x02); // top line 1623 | i += 8; 1624 | if (i < j) return; 1625 | 1626 | sprite_add(0x1A, i, k, 0x02); // flag tip 1627 | } 1628 | 1629 | const uint8 RING_CYCLE[4] = { 0x00, 0x80, 0xC0, 0x40 }; // rotating ring for sprite 3B 1630 | const uint8 RING_OFFX [4] = { 0, 0, 1, 1 }; 1631 | const uint8 RING_OFFY [4] = { 0, 1, 1, 0 }; 1632 | const uint8 RING_GLOW [4] = { 0x3D, 0x3E, 0x3F, 0x3F }; // glowing sprites for motion indicator 1633 | 1634 | // clobbers i,j,k,l,mx,nx,ox,px 1635 | void hole_draw() 1636 | { 1637 | static uint8 order; // needed internal value because i,j,k,l are clobbered by hole_draw_flag 1638 | 1639 | ox = balls_wx[player*2]; 1640 | px = balls_y[player]; 1641 | 1642 | sprite_begin(); 1643 | if (swinging) 1644 | { 1645 | sprite_add(0x2A, ox, px, 0x00); // highlighted ball 1646 | 1647 | if ( 1648 | swing_x >= SWING_MIN || 1649 | swing_x <= -SWING_MIN || 1650 | swing_y >= SWING_MIN || 1651 | swing_y <= -SWING_MIN ) 1652 | { 1653 | // note: the +1/2 on swing offsets is a rounding adjustment to keep negative/positive visually symmetrical 1654 | tsx = (swing_x + (SWING_FIX/2)) / SWING_FIX; 1655 | tsy = (swing_y + (SWING_FIX/2)) / SWING_FIX; 1656 | 1657 | // rotating ring in direction of shot 1658 | order = ((swing_x + swing_y) / (SWING_FIX / 4)) & 3; 1659 | tx = ox - tsx - RING_OFFX[order]; 1660 | ty = px - tsy - RING_OFFY[order]; 1661 | if (tx < 256 && ty < 256) sprite_add(0x2B, (uint8)tx, (uint8)ty, RING_CYCLE[order]); 1662 | 1663 | // solid dark ring direction of pull-back for swing 1664 | tx = ox + tsx; 1665 | ty = px + tsy; 1666 | if (tx < 256 && ty < 256) sprite_add(0x29, (uint8)tx, (uint8)ty, 0x00); 1667 | 1668 | // separated from frame count so it's not synchronized with it 1669 | // (want to avoid some bad colour against the background always 1670 | // appearing in the same phase in this visualization of direction) 1671 | ring_glow += 13; 1672 | 1673 | #define SWING_FRAMES (1<<4) 1674 | order = frame_count & (SWING_FRAMES-1); 1675 | mx = (tsx * (sint16)order) / (SWING_FRAMES-1); 1676 | nx = (tsy * (sint16)order) / (SWING_FRAMES-1); 1677 | tx = ox - mx; 1678 | ty = px - nx; 1679 | if (tx < 256 && ty < 256) sprite_add(RING_GLOW[(ring_glow/32)&3], (uint8)tx, (uint8)ty, 0x00); 1680 | 1681 | tx += tsx; 1682 | ty += tsy; 1683 | if (tx < 256 && ty < 256) sprite_add(RING_GLOW[(ring_glow/32)&3], (uint8)tx, (uint8)ty, 0x00); 1684 | } 1685 | } 1686 | else if (ox < 256) 1687 | { 1688 | if (splash_s) // ball splashing into water 1689 | { 1690 | sprite_add(0x1F + splash_s, ox, px, 0x03); 1691 | if (splash_s < 4) // ball behind splash 1692 | sprite_add(0x1D + (ball_s/2), ox, px + splash_s - 1, 0x00); 1693 | } 1694 | else // ball by itself 1695 | sprite_add(0x1D + (ball_s/2), ox, px, 0x00); 1696 | } 1697 | if (tee_sx < 256) sprite_add(0x30 | tee_s, (uint8)tee_sx, tee_sy, 0x02); 1698 | if (status_sx < 256) sprite_add(0x3A, (uint8)status_sx, 16, 0x00); 1699 | 1700 | // cycling order of remaining sprites 1701 | for (order=4; order!=0; --order) 1702 | { 1703 | i = (frame_count + order) & 3; 1704 | switch(i) 1705 | { 1706 | case 0: 1707 | if (hole_sx < 256) hole_draw_flag(); 1708 | break; 1709 | case 1: 1710 | case 2: 1711 | case 3: 1712 | j = balls_draw[i]; 1713 | k = j * 4; 1714 | if (j < players && !balls_hx[k]) 1715 | sprite_add(0x2C + i, balls_lx[k], balls_y[j], 0x01); 1716 | break; 1717 | } 1718 | } 1719 | sprite_end(); 1720 | } 1721 | 1722 | void stroke_add() 1723 | { 1724 | i = player * 5; 1725 | for (j=0; j<5; ++j) 1726 | { 1727 | ++strokes[i]; 1728 | if(strokes[i] < 10) return; 1729 | strokes[i] = 0; 1730 | ++i; 1731 | } 1732 | // maxed out 1733 | i -= 5; 1734 | strokes[i] = 1735 | strokes[i+1] = 1736 | strokes[i+2] = 1737 | strokes[i+3] = 1738 | strokes[i+4] = 9; 1739 | } 1740 | 1741 | void status_draw() 1742 | { 1743 | uint8 ws; 1744 | uint8 w; 1745 | 1746 | for (i=0; i<64; ++i) ppu_send[i] = 0; // blank the line 1747 | 1748 | ASSERT((scroll_cx & 7) == 0); 1749 | w = (status_w + (scroll_cx / 8)) & 63; // adjust for scroll 1750 | 1751 | ppu_send_addr = 0x2000 + (2 * 32); 1752 | 1753 | // draw stroke counter for each player 1754 | k = 4; 1755 | for (i=0; i 1) 1777 | status_sx = ((status_w + (player * 7)) - 1) * 8; // player indicator 1778 | } 1779 | 1780 | #define status_player_hide() { status_sx = 256; } 1781 | 1782 | void hole_splash() 1783 | { 1784 | uint8 t; 1785 | sound_play(SFX_SPLASH); 1786 | for (t=0; t<18; ++t) 1787 | { 1788 | splash_s = (t/2)+1; 1789 | balls_y[player] = OCEAN_FLOOR-6; 1790 | hole_draw(); 1791 | frame(); 1792 | } 1793 | } 1794 | 1795 | // collision priority per-pixel overlapping the bottom half of the ball 1796 | // favours closeness to the centre, and right side before left 1797 | // (lowest wins, 13=miss) 1798 | const uint8 COLLIDE_PRIORITY[5*4] = { 1799 | 8, 12, 13, 13, // left column 1800 | 3, 5, 11, 13, 1801 | 0, 1, 6, 13, // centre column 1802 | 2, 4, 9, 13, 1803 | 7, 10, 13, 13, // right column 1804 | }; 1805 | 1806 | void status_bar_fade_in() 1807 | { 1808 | pal_text = set_t[field_set]; 1809 | status_draw(); 1810 | hole_draw(); frame_double(); 1811 | palette_generate(pal_sky, pal_floor, blend25(pal_sky, pal_text)); hole_draw(); frames(2); 1812 | palette_generate(pal_sky, pal_floor, blend50(pal_sky, pal_text)); hole_draw(); frames(2); 1813 | palette_generate(pal_sky, pal_floor, blend25(pal_text, pal_sky)); hole_draw(); frame(); 1814 | input_poll(); hole_draw(); frame(); // clear pending input 1815 | palette_generate(pal_sky, pal_floor, pal_text); 1816 | } 1817 | 1818 | void hole_play() 1819 | { 1820 | uint16 t = (tee_cx - (6*8)) & 511; // target scroll position 1821 | while (scroll_cx != t) 1822 | { 1823 | // build one more pixel of floor 1824 | floor_build_pixel(); 1825 | floor_render_prepare(scroll_cx & 7); 1826 | 1827 | // scroll one pixel to the right 1828 | weather_shift(-1); 1829 | hole_shift(); 1830 | scroll_cx = (scroll_cx + 1) & 511; 1831 | ppu_scroll_x(scroll_cx); 1832 | 1833 | if (hole_sx < 256) hole_sy = floor_y[hole_cx + 4] - 8; 1834 | 1835 | hole_draw(); 1836 | frame(); 1837 | } 1838 | 1839 | // 3. raise the tee 1840 | if (hole != 1) 1841 | { 1842 | sound_play(SFX_TEE); 1843 | tee_sy = floor_y[tee_cx + 4] - 8; 1844 | tee_sx = (tee_cx - scroll_cx) & 511; 1845 | for (t = 0; t < 8; ++t) 1846 | { 1847 | tee_s = t; 1848 | i = tee_sy + 1 - t; 1849 | for (j=0; j<4; ++j) 1850 | if (balls_y[j] > i) balls_y[j] = i; 1851 | hole_draw(); 1852 | frames(2); 1853 | } 1854 | for (i = 0; i < 8; ++i) 1855 | { 1856 | floor_a[tee_cx+i] = 0; // flat 1857 | floor_y[tee_cx+i] = tee_sy; 1858 | } 1859 | // restore the lip 1860 | floor_a[(tee_cx-1)&511] = old_lip[0]; 1861 | floor_a[(tee_cx+8)&511] = old_lip[1]; 1862 | } 1863 | // add a little "lip" for the hole to help balls fall in 1864 | if (hole != 0) 1865 | { 1866 | old_lip[0] = floor_a[(hole_cx-1)&511]; 1867 | old_lip[1] = floor_a[(hole_cx+8)&511]; 1868 | floor_a[(hole_cx-1)&511] = 0x01; 1869 | floor_a[(hole_cx+8)&511] = 0x81; 1870 | } 1871 | 1872 | while (transition) { hole_draw(); frame(); } // finish any pending colour transition 1873 | 1874 | // 4. fade in status bar 1875 | if (!first_stroke) // delay this on first stroke in 1-player mode 1876 | status_bar_fade_in(); 1877 | 1878 | // 5. play hole 1879 | 1880 | // find leading player (after the one who played first last) 1881 | for (t=1; t<4; ++t) 1882 | { 1883 | j = (next_player + t) & 3; 1884 | if (j >= players) continue; 1885 | for (i = 0; i<5; ++i) 1886 | { 1887 | k = strokes[(j*5)+4-i]; 1888 | l = strokes[(next_player)*5+4-i]; 1889 | if (k > l) break; 1890 | if (k < l) 1891 | { 1892 | next_player = j; 1893 | break; 1894 | } 1895 | } 1896 | } 1897 | player = next_player; 1898 | next_player = (next_player + 1) % players; // favour the next one if tied to keep it cycling 1899 | 1900 | cleared = 0; 1901 | for (i=0; i<4; ++i) 1902 | if (i >= players) cleared |= (1 << i); 1903 | ball_draw_setup(); 1904 | 1905 | // play sound to indicate current player 1906 | if (players > 1) 1907 | { 1908 | hole_draw(); frames(2); 1909 | sound_play(SFX_PROMPTS[player]); 1910 | } 1911 | 1912 | 1913 | while (cleared < 0x10) 1914 | { 1915 | // if centre of ball is offscreen, put it back on the tee 1916 | if (balls_x[player] >= (253*256UL) || balls_x[player] < (5*256UL)) 1917 | { 1918 | if (players > 1) // extra time to keep prompt jingle from cutting rudely 1919 | { 1920 | hole_draw(); frames(2); 1921 | hole_draw(); frames(2); 1922 | hole_draw(); frames(2); 1923 | hole_draw(); frames(2); 1924 | } 1925 | sound_play(SFX_RESPAWN); 1926 | balls_x[player] = tee_sx * 256UL; 1927 | balls_y[player] = tee_sy - 6; 1928 | } 1929 | 1930 | stroke_wait: 1931 | status_player(); // indicate whose turn it is, and that input is now accepted 1932 | do 1933 | { 1934 | input_poll(); 1935 | if (!te) prng1(); // entropy 1936 | hole_draw(); 1937 | frame(); 1938 | #if HOLE_SKIP 1939 | if (gamepad & PAD_START) goto hole_skip; 1940 | #endif 1941 | } 1942 | while (!(gamepad & (PAD_B | PAD_A)) && !(mouse1 & (MOUSE_L | MOUSE_R))); 1943 | 1944 | //stroke_swing: 1945 | swinging = 1; 1946 | swing_x = 0; 1947 | swing_y = 0; 1948 | do 1949 | { 1950 | input_poll(); 1951 | 1952 | #define PSWING_COARSE 32 1953 | #define PSWING_FINE 1 1954 | #define MSWING_COARSE 8 1955 | #define MSWING_FINE 1 1956 | 1957 | if (gamepad & PAD_B) 1958 | { 1959 | if (gamepad & PAD_LEFT ) swing_x -= PSWING_FINE; 1960 | if (gamepad & PAD_RIGHT) swing_x += PSWING_FINE; 1961 | if (gamepad & PAD_UP ) swing_y -= PSWING_FINE; 1962 | if (gamepad & PAD_DOWN ) swing_y += PSWING_FINE; 1963 | } 1964 | else 1965 | { 1966 | if (gamepad & PAD_LEFT ) swing_x -= PSWING_COARSE; 1967 | if (gamepad & PAD_RIGHT) swing_x += PSWING_COARSE; 1968 | if (gamepad & PAD_UP ) swing_y -= PSWING_COARSE; 1969 | if (gamepad & PAD_DOWN ) swing_y += PSWING_COARSE; 1970 | } 1971 | 1972 | if (mouse1 & MOUSE_R) 1973 | { 1974 | // note: preventing overflow by testing that direction of motion has same sign 1975 | swing_x += mouse3 * MSWING_FINE; 1976 | swing_y += mouse2 * MSWING_FINE; 1977 | } 1978 | else 1979 | { 1980 | swing_x += mouse3 * MSWING_COARSE; 1981 | swing_y += mouse2 * MSWING_COARSE; 1982 | } 1983 | 1984 | if (swing_x > SWING_MAX) swing_x = SWING_MAX; 1985 | if (swing_x < -SWING_MAX) swing_x = -SWING_MAX; 1986 | if (swing_y > SWING_MAX) swing_y = SWING_MAX; 1987 | if (swing_y < -SWING_MAX) swing_y = -SWING_MAX; 1988 | 1989 | if (!te) prng1(); // entropy 1990 | hole_draw(); 1991 | frame(); 1992 | } 1993 | while ((gamepad & (PAD_B | PAD_A)) || (mouse1 & (MOUSE_L | MOUSE_R))); 1994 | 1995 | swinging = 0; 1996 | if ( 1997 | swing_x < SWING_MIN && 1998 | swing_x > -SWING_MIN && 1999 | swing_y < SWING_MIN && 2000 | swing_y > -SWING_MIN ) 2001 | goto stroke_wait; 2002 | 2003 | stroke_add(); 2004 | if (!first_stroke) status_draw(); 2005 | status_player_hide(); // no longer taking input 2006 | 2007 | hole_draw(); 2008 | if (!first_stroke) frame_double(); // frame_double is for status_draw only 2009 | else frame(); 2010 | 2011 | //ball_fly: 2012 | sound_play(SFX_STROKE); 2013 | ball_x = balls_x[player]; 2014 | ball_y = balls_y[player] * 256UL; 2015 | ball_vx = -swing_x / 2; // divider of swing controls max velocity 2016 | ball_vy = -swing_y / 2; 2017 | timeout = 0; 2018 | rollout = 0; 2019 | 2020 | // some motion constants, higher gravity makes a "faster" game 2021 | // DRAG should be probably be greater than WIND so that the ball will stop? 2022 | // STICK is how fast a bounce is required to escape the floor 2023 | #define GRAVITY 12 2024 | #define WIND 3 2025 | #define DRAG 6 2026 | #define STICK 128 2027 | // radius should be at least 2 pixels: 2028 | // 2 pixels -> lifts ball completely off ground 2029 | // sqrt(4.5) pixels -> touches inner corners of all ball pixels 2030 | // 2.5 pixels -> touches outer 4 faces 2031 | // sqrt(8.5) pixels -> touches outer corners of all pixels 2032 | // chose the first, because smaller = easier to get in hole 2033 | #define BALL_RADIUS (2*256) 2034 | // limit amount of ejection per-frame to avoid "pop" 2035 | // (hard hits will tend to self-correct with the bounce anyway) 2036 | #define EJECT_MAX (BALL_RADIUS*2) 2037 | // to keep bad physics from lasting forever, just drop the ball straight down after this many frames 2038 | #define TIMEOUT (20*60) 2039 | // additional timeout: drop the ball after not visually moving for this many frames 2040 | #define ROLLOUT 16 2041 | 2042 | do 2043 | { 2044 | soll_sx = roll_sx; 2045 | soll_sy = roll_sy; 2046 | roll_sx = balls_lx[player*4]; 2047 | roll_sy = balls_y[player]; 2048 | 2049 | ball_x += ball_vx; 2050 | ball_y += ball_vy; 2051 | ball_vy += GRAVITY; 2052 | 2053 | balls_x[player] = ball_x; 2054 | balls_y[player] = ball_y / 256; 2055 | 2056 | if (ball_y >= (256*256UL)) 2057 | { 2058 | balls_hx[player*4] = 1; // offscreen 2059 | goto collide_skip; 2060 | } 2061 | 2062 | // final hole water test 2063 | if (hole == 0 && ball_y >= ((OCEAN_FLOOR-6)*256U)) 2064 | { 2065 | hole_splash(); // pause for a little splash animation 2066 | cleared |= (1 << player); 2067 | splash_s = 0; // clear splash 2068 | ball_x = 256*256UL; 2069 | balls_hx[player*4] = 1; // move ball offscreen 2070 | break; 2071 | } 2072 | 2073 | // check 5 columns of the ball for any pixel overlap with the field 2074 | mx = (ball_x / 256) + 1; // start at left column of ball 2075 | l = (ball_y / 256) + 5; // bottom row of ball 2076 | px = 0; // best priority column 2077 | k = 13; // best priority 2078 | valley = 0; 2079 | for (i=0; i<(5*4); i+=4) 2080 | { 2081 | nx = mx; 2082 | if (mx >= 0x8000) nx = 0; // off left side, use leftmost column 2083 | else if (mx >= 256) nx = 255; // off right side, use rightmost column 2084 | nx = (nx + scroll_cx) & 511; 2085 | ++mx; 2086 | j = floor_y[nx]; 2087 | if (l >= j) // possible overlap 2088 | { 2089 | j = l-j; 2090 | if (j >= 2) { j = COLLIDE_PRIORITY[i]; } // centre row or above 2091 | else { j = COLLIDE_PRIORITY[i+(2-j)]; } // bottom two rows 2092 | 2093 | if (j < 13) // detect simultaneous collision on both sides 2094 | { 2095 | if (i < (2*4)) valley |= 1; 2096 | else if (i >= (3*4)) valley |= 2; 2097 | } 2098 | 2099 | // replace j with priority 2100 | if (j < k) // store best priority column 2101 | { 2102 | k = j; 2103 | px = nx; 2104 | } 2105 | } 2106 | } 2107 | if (k >= 13) goto collide_skip; // no pixel collision detected 2108 | 2109 | // if ball lands offscreen, don't try to collide 2110 | // (this speeds up recovery for the next shot, but also avoids 2111 | // a problem calculating tsx/tsy below that causes it to slip past 2112 | // the radius of ejection and ending up in free fall) 2113 | if (ball_x >= (256*256UL)) break; 2114 | 2115 | // px = index of floor 2116 | i = floor_a[px]; // i = angle lookup for floor slope 2117 | norm_x = read_norm_x(i); 2118 | norm_y = read_norm_y(i); 2119 | mx = (((px - scroll_cx) & 255) * 256) + 128; // midpoint of floor column 2120 | nx = floor_y[px] * 256U; 2121 | 2122 | // 1. eject ball from overlap with the floor plane 2123 | 2124 | // tsx/tsy = vector pointing from midpoint of floor tile to ball centre (sprite corner + 3.5 pixels) 2125 | //tsx = (uint16)ball_x + 896 - mx; // 3.5 pixels seems to have horizontal bias on slopes 2126 | tsx = (uint16)ball_x + 768 - mx; // 3.0 pixels instead? maybe corrects the visual bias of rounding down? unsure 2127 | tsy = (uint16)ball_y + 896 - nx; 2128 | 2129 | // ux = distance to eject from floor plane 2130 | //ux = BALL_RADIUS-(((norm_x * (sint32)tsx) + (norm_y * (sint32)tsy)) / 256); 2131 | ux = BALL_RADIUS-(fmult(norm_x,tsx) + fmult(norm_y,tsy)); 2132 | if (ux > 0) 2133 | { 2134 | if (ux > EJECT_MAX) ux = EJECT_MAX; 2135 | tsx = (norm_x * (sint32)ux) / 256; 2136 | tsy = (norm_y * (sint32)ux) / 256; 2137 | ball_x += tsx; 2138 | ball_y += tsy; 2139 | } 2140 | 2141 | balls_x[player] = ball_x; 2142 | balls_y[player] = ball_y / 256; 2143 | 2144 | // 2. reflect ball velocity if against the floor plane, roll along plane if hit is too weak 2145 | 2146 | //tsy = ((norm_x * (sint32)ball_vx) + (norm_y * (sint32)ball_vy)) / 256; // velocity against the normal 2147 | //tsx = ((norm_y * (sint32)ball_vx) - (norm_x * (sint32)ball_vy)) / 256; // velocity perpendicular to the normal 2148 | tsy = fmult(norm_x,ball_vx) + fmult(norm_y,ball_vy); // velocity against the normal 2149 | tsx = fmult(norm_y,ball_vx) - fmult(norm_x,ball_vy); // velocity perpendicular to the normal 2150 | 2151 | // stick to floor if bounce isn't strong enough 2152 | if (tsy > -STICK && tsy < STICK ) 2153 | { 2154 | if (tsy < 0 ) tsx -= tsx / 8; // milder attenuation of horizontal 2155 | if (tsx > DRAG) tsx -= DRAG; 2156 | else if (tsx < -DRAG) tsx += DRAG; 2157 | else tsx = 0; 2158 | tsy = 0; 2159 | } 2160 | else if (tsy < 0) // bounce if we're not already heading out 2161 | { 2162 | if (tsy < -400) sound_play(SFX_BOUNCE2); 2163 | else if (tsy < -200) sound_play(SFX_BOUNCE1); 2164 | else sound_play(SFX_BOUNCE0); 2165 | tsy /= -2; 2166 | tsx /= 2; 2167 | } 2168 | 2169 | if (tsx == 0 && tsy == 0) break; // ball has stopped 2170 | 2171 | //ball_vx = ((norm_x * (sint32)tsy) + (norm_y * (sint32)tsx)) / 256; 2172 | //ball_vy = ((norm_y * (sint32)tsy) - (norm_x * (sint32)tsx)) / 256; 2173 | ball_vx = fmult(norm_x,tsy) + fmult(norm_y,tsx); 2174 | ball_vy = fmult(norm_y,tsy) - fmult(norm_x,tsx); 2175 | 2176 | // stop ball from gaining gravity indefinitely in a valley 2177 | if (valley >= 3) 2178 | { 2179 | if (ball_vy > 0) ball_vy = 0; 2180 | } 2181 | 2182 | goto collide_done; 2183 | 2184 | collide_skip: 2185 | if (!te) 2186 | { 2187 | // apply wind only if not colliding or on ground 2188 | if (weather_rate_min && (prng1() < weather_wind_p)) 2189 | ball_vx += weather_wind_dir * WIND; 2190 | } 2191 | else 2192 | { 2193 | // TE: uses "free" prngf because this is supposed to be a statistical chance of wind curving the ball, 2194 | // and prngw is only for deciding wind patterns at a higher level 2195 | if (weather_rate_min && (prngf1() < weather_wind_p)) 2196 | ball_vx += weather_wind_dir * WIND; 2197 | } 2198 | 2199 | collide_done: 2200 | // rolling/wobble, tick it whenever the ball moves a pixel 2201 | if (balls_lx[player*4] > roll_sx) // -x = rolling backward 2202 | { 2203 | ball_s -= 1; if (ball_s >= 6) ball_s = 5; 2204 | if (balls_lx[player*4] != soll_sx || balls_y[player] != soll_sy) // secondary check for 2-frame cycle before clearing rollout 2205 | rollout = 0; 2206 | else 2207 | ++rollout; 2208 | } 2209 | else if (balls_lx[player*4] != roll_sx || balls_y[player] != roll_sy) // moved and not -x = rolling forward 2210 | { 2211 | ball_s += 1; if (ball_s >= 6) ball_s = 0; 2212 | if (balls_lx[player*4] != soll_sx || balls_y[player] != soll_sy) 2213 | rollout = 0; 2214 | else 2215 | ++rollout; 2216 | } 2217 | else 2218 | { 2219 | ++rollout; // ball is visually still? 2220 | } 2221 | 2222 | ++timeout; 2223 | if (timeout >= TIMEOUT || rollout >= ROLLOUT) 2224 | { 2225 | if ((balls_hx[player*4] != 0) || (ball_y >= (256*256UL))) 2226 | { 2227 | balls_x[player] = 256*256UL; // force offscreen, will reload position next swing 2228 | break; // just immediately end 2229 | } 2230 | // onscreen: slide down 1 pixel per frame until we're done 2231 | t = (floor_y[(balls_wx[player*2] + scroll_cx + 3) & 511] - 6) & 255; 2232 | while (balls_y[player] < t) 2233 | { 2234 | ball_y += 256; 2235 | ++balls_y[player]; 2236 | hole_draw(); 2237 | frame(); 2238 | } 2239 | break; 2240 | } 2241 | 2242 | hole_draw(); 2243 | frame(); 2244 | 2245 | } while(1); 2246 | 2247 | //ball_landed: 2248 | // stop the ball 2249 | ball_s = 0; 2250 | hole_draw(); 2251 | frame(); 2252 | hole_draw(); frames(2); // just a little extra time for last bounce sound to die down 2253 | 2254 | if ((hole != 0) && ((ball_x/256) > (hole_sx-3)) && ((ball_x/256) < (hole_sx+5))) 2255 | { 2256 | cleared |= (1 << player); // landed in hole! 2257 | } 2258 | balls_x[player] = ball_x & 0x00FFFFFF; // sanitize the 4th "padding" byte 2259 | balls_y[player] = ball_y / 256; 2260 | 2261 | // next player 2262 | for (i=0; i<4; ++i) 2263 | { 2264 | player = (player+1) & 3; 2265 | if (!(cleared & (1 << player))) break; 2266 | } 2267 | if (i >= 4) break; // should be same as cleared >= 0x10 2268 | 2269 | // player prompt sound if mutliplayer 2270 | if (players > 1) sound_play(SFX_PROMPTS[player]); 2271 | 2272 | if (first_stroke) 2273 | { 2274 | status_bar_fade_in(); 2275 | first_stroke = 0; 2276 | } 2277 | 2278 | ball_draw_setup(); 2279 | hole_draw(); 2280 | input_poll(); // clear pending input 2281 | frame(); 2282 | } 2283 | 2284 | #if HOLE_SKIP 2285 | hole_skip: 2286 | #endif 2287 | 2288 | // end of game 2289 | while (hole == 0) 2290 | { 2291 | hole_draw(); 2292 | frame(); 2293 | input_poll(); // allow reset 2294 | } 2295 | 2296 | // 6. fade out status bar, remove flag 2297 | sound_play(SFX_FLAG); 2298 | flag_remove = 1; 2299 | if (!first_stroke) // if they got a hole in 1, stroke status hasn't been faded in, so skip this fadeout 2300 | { 2301 | palette_generate(pal_sky, pal_floor, blend25(pal_text, pal_sky)); hole_draw(); frame(); hole_draw(); frame(); 2302 | palette_generate(pal_sky, pal_floor, blend50(pal_sky, pal_text)); hole_draw(); frame(); hole_draw(); frame(); 2303 | palette_generate(pal_sky, pal_floor, blend25(pal_sky, pal_text)); hole_draw(); frame(); hole_draw(); frame(); 2304 | palette_generate(pal_sky, pal_floor, pal_sky ); 2305 | } 2306 | else first_stroke = 0; 2307 | // wait for flag to finish leaving 2308 | while (flag_remove) { hole_draw(); frame(); } 2309 | 2310 | // 7. prepare next hole 2311 | hole_next(); 2312 | 2313 | if (te) seedw = seed; // TE: re-synchronize the wind RNG at every hole 2314 | 2315 | if (day) --day; 2316 | 2317 | if (day == 0) 2318 | { 2319 | day = DAY_TIME; 2320 | field_set ^= 8; // day/night only 2321 | goto field_change; 2322 | } 2323 | else if (transition_time == 0) 2324 | { 2325 | field_set = (prng() & 7) | (field_set & 8); // random but don't switch day/night 2326 | transition_time = (prng() & 7) + 3; 2327 | field_change: 2328 | transition = TRANSITION_TIME; 2329 | i = set_w[field_set]; 2330 | if (i) 2331 | { 2332 | weather_tile = i; 2333 | weather_attribute_set(); 2334 | } 2335 | } 2336 | else 2337 | { 2338 | --transition_time; 2339 | } 2340 | weather_wind_random(); 2341 | if (hole == 0 && weather_wind_fade_dir < 0) weather_wind_fade_dir = 1; // no left wind allowed on last hole 2342 | 2343 | return; // hole_play() again 2344 | } 2345 | 2346 | void fmult_test() 2347 | { 2348 | // needed this test to verify that fmult works 2349 | for (mx=1; mx!=0; ++mx) 2350 | { 2351 | tsx = (sint32)(prng() | ((uint16)prng()<<8)); 2352 | tsy = (sint32)(prng() | ((uint16)prng()<<8)); 2353 | ux = (sint32)(((sint32)tsx*tsy)/256); 2354 | vx = fmult(tsx,tsy); 2355 | ASSERT(ux==vx); 2356 | } 2357 | } 2358 | 2359 | // 2360 | // main` 2361 | // 2362 | 2363 | void main() 2364 | { 2365 | //fmult_test(); 2366 | 2367 | if (!te) 2368 | { 2369 | // replace the common "all 0s" or "all 1s" emulator RAM initialization seed 2370 | // with two hand-picked cases to make the title screen look nice. 2371 | // (further entropy for subsequent holes is gathered while waiting on the 2372 | // title screen, but I have to generate at least the title screen before user input.) 2373 | if (seed == 0x00800000) seed = 0x00654321; // field set 7 (yellow day), 4 holes to night 2374 | if (seed == 0x00FFFFFF) seed = 0x000D7755; // field set F (yellow night), 4 holes to day 2375 | if (seed == 0x00FF0000) seed = 0x00654399; // field set 7 (yellow day), 5 holes to night 2376 | } 2377 | else // TE 2378 | { 2379 | seed = 0x00456321; // yellow, day 2380 | seedw = seed; // weather PRNG will be reloaded from main PRNG at sync points 2381 | seedf = seed; // any nonzero value is find for free PRNG 2382 | 2383 | // settings should persist across reset 2384 | if (holes < 2 || holes > 256 || ((seed_start >> 24) != 42)) 2385 | { 2386 | holes = 100; 2387 | seed_start = 0x00123456 | (42UL << 24); 2388 | } 2389 | } 2390 | 2391 | input_setup(); 2392 | 2393 | ppu_latch(0x1000); 2394 | ppu_fill(0x55,8*1024); 2395 | 2396 | ptr = te ? layerste_chr : layers_chr; 2397 | ppu_latch(0x0000); 2398 | ppu_load(te ? LAYERSTE_CHR_SIZE : LAYERS_CHR_SIZE); 2399 | 2400 | ptr = sprite_chr; 2401 | ppu_latch(0x1000); 2402 | ppu_load(SPRITE_CHR_SIZE); 2403 | 2404 | #if !SHOW_LEFT_COLUMN 2405 | ppu_mask(0x18); // hide left column 2406 | #endif 2407 | 2408 | title(); 2409 | while (1) hole_play(); 2410 | return; // never reached 2411 | } 2412 | 2413 | // end of file 2414 | -------------------------------------------------------------------------------- /dgolf.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ZEROPAGE: start = $0000, size = $100, type = rw, file = ""; 3 | STACK: start = $0100, size = $100, type = rw, file = ""; 4 | RAM: start = $0200, size = $600, type = rw, file = ""; 5 | HEADER: start = $0000, size = $10, type = ro, file = %O, fill = yes, fillval = 0; 6 | PRG: start = $8000, size = $8000, type = ro, file = %O, fill = yes, fillval = 0; 7 | } 8 | 9 | SEGMENTS { 10 | ZEROPAGE: load = ZEROPAGE, type = zp; 11 | STACK: load = STACK, type = bss, optional = yes; 12 | OAM: load = RAM, type = bss, align = $100, optional = yes; 13 | BSS: load = RAM, type = bss, align = $100, define = yes; 14 | 15 | ALIGN: load = PRG, type = ro, align = $100, optional = yes; 16 | DATA: load = PRG, run = RAM, type = rw, define = yes, optional = yes; 17 | RODATA: load = PRG, type = ro, optional = yes; 18 | CODE: load = PRG, type = ro, optional = yes; 19 | 20 | HEADER: load = HEADER, type = ro; 21 | STUB: load = PRG, type = ro, start = $FFED; 22 | VECTORS: load = PRG, type = ro, start = $FFFA; 23 | } 24 | -------------------------------------------------------------------------------- /dgolf.s: -------------------------------------------------------------------------------- 1 | ; 2 | ; dgolf.s 3 | ; NESert Golfing, by Brad Smith 2019 4 | ; http://rainwarrior.ca 5 | ; 6 | 7 | .macpack longbranch 8 | 9 | ; === 10 | ; RAM 11 | ; === 12 | 13 | .segment "ZEROPAGE" 14 | _ptr: .res 2 ; shared pointer for C interface 15 | _seed: .res 4 ; random number seed (only using low 3 bytes) 16 | ppu_post_mode: .res 1 17 | _seedw: .res 4 ; TE dependent seed for wind changes only 18 | _seedf: .res 4 ; TE independent seed for things that can still be random (e.g. rain/snow animation)ppu_post_mode: .res 1 19 | sound_ptr: .res 2 20 | _input: .res 16 21 | mouse_index: .res 1 22 | fourscore_off: .res 1 23 | _gamepad: .res 1 24 | _mouse1: .res 1 25 | _mouse2: .res 1 26 | _mouse3: .res 1 27 | 28 | ; convenient index/temporary values 29 | _i: .res 1 30 | _j: .res 1 31 | _k: .res 1 32 | _l: .res 1 33 | _mx: .res 2 34 | _nx: .res 2 35 | _ox: .res 2 36 | _px: .res 2 37 | _ux: .res 2 38 | _vx: .res 2 39 | 40 | _te: .res 4 ; TE signature for selecting TE mode (first byte is 0/nonzero for TE) 41 | 42 | ; nesert golfing 43 | 44 | _floor_column: .res 1 45 | _weather_tile: .res 1 46 | _weather_attribute: .res 1 ; bits 2-4 are speed of descent 47 | _weather_wind_dir: .res 1 ; wind direction: 0, 1, or -1 48 | _weather_wind_p: .res 1 ; wind probability 0 = never, 255 = almost 1 per frame 49 | _weather_rate_min: .res 1 ; minimum wait until next spawn 50 | _weather_rate_mask: .res 1 ; prng mask add until next spawn 51 | weather_wait: .res 1 ; current wait until next spawn 52 | weather_next: .res 1 ; next spawn index 53 | _hole: .res 1 ; current hole (0 for ending) 54 | _ocean_attribute: .res 1 ; starting byte of ocean attribute (255 before last hole is built) 55 | _tx: .res 2 ; temporary X 56 | _ty: .res 2 ; temporary Y 57 | _tsx: .res 2 ; signed temporary X 58 | _tsy: .res 2 ; signed temporary Y 59 | 60 | _ball_x: .res 4 61 | _ball_y: .res 4 62 | _ball_vx: .res 4 63 | _ball_vy: .res 4 64 | 65 | _balls_x: .res 16 66 | _balls_y: .res 4 67 | _balls_fx = _balls_x+0 68 | _balls_lx = _balls_x+1 69 | _balls_hx = _balls_x+2 70 | _balls_wx = _balls_x+1 71 | 72 | _norm_x: .res 2 73 | _norm_y: .res 2 74 | 75 | ; sound 76 | 77 | apu_out: .res 16 ; last values written to APU 78 | apu_sqh: .res 2 ; last written values to $4003/$4007 79 | sound_pos: .res 1 80 | sound_seed: .res 2 81 | snow_pitch: .res 1 82 | snow_time: .res 1 83 | 84 | .segment "BSS" 85 | 86 | CSTACK_SIZE = 128 87 | 88 | .align 256 89 | _floor_y: .res 512 ; floor height 90 | _floor_a: .res 512 ; floor angle 91 | cstack: .res 128 ; CC65 internal C stack 92 | 93 | _floor_render: .res 64 ; floor render buffer 94 | 95 | temp: .res 4 96 | 97 | .segment "STACK" 98 | sound: .res 2 99 | ppu_2000: .res 1 100 | ppu_2001: .res 1 101 | ppu_2005x: .res 2 ; second byte is high bit (redundant in $2000) 102 | ppu_2005y: .res 2 ; second byte is high bit (redundant in $2000) 103 | _ppu_send_addr: .res 2 104 | _ppu_send_count: .res 1 105 | _ppu_send: .res 64 106 | _palette: .res 32 107 | 108 | .segment "OAM" 109 | .align 256 110 | _oam: .res 256 111 | 112 | .exportzp _ptr 113 | .exportzp _seed 114 | .exportzp _seedw, _seedf 115 | .exportzp _input, _gamepad, _mouse1, _mouse2, _mouse3 116 | .exportzp _i,_j,_k,_l 117 | .exportzp _mx,_nx,_ox,_px 118 | .exportzp _ux,_vx 119 | .exportzp _te 120 | 121 | .exportzp _floor_column 122 | .exportzp _weather_tile 123 | .exportzp _weather_attribute 124 | .exportzp _weather_wind_dir 125 | .exportzp _weather_wind_p 126 | .exportzp _weather_rate_min 127 | .exportzp _weather_rate_mask 128 | .exportzp _hole 129 | .exportzp _ocean_attribute 130 | .exportzp _tx 131 | .exportzp _ty 132 | .exportzp _tsx 133 | .exportzp _tsy 134 | .exportzp _ball_x 135 | .exportzp _ball_y 136 | .exportzp _ball_vx 137 | .exportzp _ball_vy 138 | .exportzp _balls_x 139 | .exportzp _balls_fx 140 | .exportzp _balls_lx 141 | .exportzp _balls_hx 142 | .exportzp _balls_wx 143 | .exportzp _balls_y 144 | .exportzp _norm_x 145 | .exportzp _norm_y 146 | 147 | .export _floor_render 148 | .export _floor_y 149 | .export _floor_a 150 | 151 | .export _ppu_send_addr 152 | .export _ppu_send_count 153 | .export _ppu_send 154 | .export _palette 155 | .export _oam 156 | 157 | .enum 158 | POST_OFF = 1 159 | POST_NONE = 2 160 | POST_UPDATE = 3 161 | POST_DOUBLE = 4 162 | .endenum 163 | 164 | ; cc65 temporaries 165 | .importzp sp, sreg, regsave 166 | .importzp ptr1, ptr2, ptr3, ptr4 167 | .importzp tmp1, tmp2, tmp3, tmp4 168 | ; cc65 library functions 169 | .import popa ; A = byte from cstack, Y=0, sp+=1 170 | .import popax ; A:X = two bytes from cstack, Y=0, sp+=2 171 | 172 | .import _seed_start ; TE: from dgolf.c, it should persist on restart 173 | .import _holes 174 | 175 | ; ============== 176 | ; NESert Golfing 177 | ; ============== 178 | 179 | OCEAN_FLOOR = 224 ; must be multiple of 8 and match parallel definition in dgolf.c 180 | 181 | .export _layers_chr 182 | .export _sprite_chr 183 | .export _layerste_chr 184 | .export _LAYERS_CHR_SIZE 185 | .export _SPRITE_CHR_SIZE 186 | .export _LAYERSTE_CHR_SIZE 187 | 188 | .export _read_slope 189 | .export _read_norm_x 190 | .export _read_norm_y 191 | .export _floor_render_prepare 192 | .export _weather_shift 193 | .export _weather_animate 194 | .export _fmult 195 | 196 | .export _hole 197 | 198 | .export _te_switch 199 | .export _soft_reset 200 | 201 | .segment "RODATA" 202 | 203 | _layers_chr: .incbin "layers.chr" 204 | _LAYERS_CHR_SIZE: .word * - _layers_chr 205 | 206 | _sprite_chr: .incbin "sprite.chr" 207 | _SPRITE_CHR_SIZE: .word * - _sprite_chr 208 | 209 | _layerste_chr: .incbin "layerste.chr" 210 | _LAYERSTE_CHR_SIZE: .word * - _layerste_chr 211 | 212 | .include "temp/slopes.inc" 213 | ;slope_y0/1 214 | ;norm_x0/1 215 | ;norm_y0/1 216 | 217 | .segment "CODE" 218 | 219 | _read_slope: 220 | tay 221 | ldx slope_y1, Y 222 | lda slope_y0, Y 223 | rts 224 | _read_norm_x: 225 | tay 226 | ldx norm_x1, Y 227 | lda norm_x0, Y 228 | rts 229 | _read_norm_y: 230 | tay 231 | ldx norm_y1, Y 232 | lda norm_y0, Y 233 | rts 234 | 235 | .proc _floor_render_prepare 236 | ; A = 0,1,2,3,4,5, 6+ 237 | ; phases: 238 | ; 0 - build render data (32 bytes of CHR tile, 30 bytes of nametable) 239 | ; 1-4 - prepare CHR tiles for send 240 | ; 5 - prepare nametable column for send, increment column 241 | ; 6-7 ocean attributes on final hill 242 | cmp #6 243 | jcs floor_send_ocean 244 | cmp #0 245 | beq floor_build 246 | cmp #5 247 | bcs floor_send_nmt 248 | floor_send_chr: ; A = 1-4 249 | tay 250 | ; ppu_send_addr = 251 | ; vertical: ((A-1) * 256) + 0x800 + 252 | ; horizontal: ((column & 15) * 16) + 253 | ; slice: (column & 16) * (16 * 4) 254 | ; plane: (column & 32) / 4 255 | clc ; vertical 256 | adc #$07 257 | sta _ppu_send_addr+1 258 | lda _floor_column ; slice 259 | and #16 260 | lsr 261 | lsr 262 | clc 263 | adc _ppu_send_addr+1 264 | sta _ppu_send_addr+1 265 | lda _floor_column ; horizontal 266 | ;and #15 ; implicit in << 4 267 | asl 268 | asl 269 | asl 270 | asl 271 | sta _ppu_send_addr+0 272 | lda _floor_column ; plane 273 | and #32 274 | lsr 275 | lsr 276 | ora _ppu_send_addr+0 277 | sta _ppu_send_addr+0 278 | ; read position = (A-1) * 8 279 | dey 280 | tya 281 | asl 282 | asl 283 | asl 284 | tax 285 | ldy #0 286 | : 287 | lda _floor_render, X 288 | sta _ppu_send, Y 289 | inx 290 | iny 291 | cpy #8 292 | bcc :- 293 | ;ldy #8 294 | sty _ppu_send_count 295 | lda #0 ; horizontal update 296 | jsr _ppu_direction 297 | rts 298 | floor_send_nmt: ; A = 5 299 | lda _floor_column 300 | and #31 301 | sta _ppu_send_addr+0 302 | lda _floor_column 303 | and #32 304 | lsr 305 | lsr 306 | lsr 307 | ora #$20 308 | sta _ppu_send_addr+1 309 | ; copy nametable data 310 | ldx #0 311 | : 312 | lda _floor_render+32, X 313 | sta _ppu_send, X 314 | inx 315 | cpx #30 316 | bcc :- 317 | ;ldx #30 318 | stx _ppu_send_count 319 | lda #1 ; vertical update 320 | jsr _ppu_direction 321 | ; increment column 322 | inc _floor_column 323 | lda _floor_column 324 | and #63 325 | sta _floor_column 326 | rts 327 | floor_build: ; A = 0 328 | src = ptr1 329 | min = tmp1 330 | mint = tmp2 331 | tile = tmp3 332 | pass = tmp4 333 | ; src = floor_y + (column * 8) 334 | ;lda #0 335 | sta src+1 336 | lda _floor_column 337 | .repeat 3 338 | asl 339 | rol src+1 340 | .endrepeat 341 | clc 342 | adc #<_floor_y 343 | sta src+0 344 | lda #>_floor_y 345 | adc src+1 346 | sta src+1 347 | ; find minimum 348 | ldy #0 349 | lda #$FF 350 | sta min 351 | @min_loop: 352 | lda (src), Y 353 | cmp min 354 | bcs :+ 355 | sta min 356 | : 357 | iny 358 | cpy #8 359 | bcc @min_loop 360 | ; min = OCEAN_FLOOR is interpreted as ocean 361 | lda min 362 | cmp #OCEAN_FLOOR 363 | jeq floor_build_ocean 364 | ; round down to nearest tile above the minimum (max 28 tiles down) 365 | ;lda min 366 | and #%11111000 367 | cmp #(28*8) 368 | bcc :+ 369 | lda #(28*8) 370 | : 371 | sta min 372 | lsr 373 | lsr 374 | lsr 375 | sta mint 376 | ; generate CHR plane 377 | ldy #0 378 | @chr_loop: 379 | ldx min 380 | lda (src), Y 381 | sta pass 382 | .repeat 64, I 383 | cpx pass 384 | rol _floor_render + I 385 | inx 386 | .endrepeat 387 | iny 388 | cpy #8 389 | jcc @chr_loop 390 | ; calculate first tile to use 391 | lda _floor_column 392 | and #15 393 | ora #$80 394 | sta tile 395 | lda _floor_column 396 | and #16 397 | asl 398 | asl 399 | ora tile 400 | sta tile 401 | ; generate nametable strip 402 | lda #0 ; blank until mint 403 | ldx #0 404 | : 405 | cpx mint 406 | bcs :+ 407 | sta _floor_render+32, X 408 | inx 409 | jmp :- 410 | : 411 | ldy #4 ; 4 tiles 412 | lda tile 413 | : 414 | sta _floor_render+32, X 415 | inx 416 | clc 417 | adc #16 418 | dey 419 | bne :- 420 | lda #$03 ; filled until bottom 421 | : 422 | cpx #32 423 | bcs :+ 424 | sta _floor_render+32, X 425 | inx 426 | jmp :- 427 | : 428 | rts 429 | floor_build_ocean: 430 | ; the ocean is just tiles of $03 at the bottom 431 | lda #0 432 | tax 433 | : 434 | sta _floor_render, X 435 | inx 436 | cpx #(32+(OCEAN_FLOOR/8)) 437 | bcc :- 438 | lda #$03 439 | : 440 | sta _floor_render, X 441 | inx 442 | cpx #64 443 | bcc :- 444 | rts 445 | floor_send_ocean: 446 | ; fills the bottom row of attributes to colour the ocean 447 | tax 448 | lda _ocean_attribute 449 | cmp #255 450 | bne :+ 451 | rts 452 | : 453 | tay ; Y = _ocean_attribute 454 | lda #8 455 | sta _ppu_send_count 456 | lda #<$23F8 457 | sta _ppu_send_addr+0 458 | cpx #7 459 | bcs :+ 460 | lda #>$23F8 ; screen 1 461 | sta _ppu_send_addr+1 462 | lda #%10101010 ; base attribute 463 | jmp :++ 464 | : 465 | lda #>$27F8 ; screen 2 466 | sta _ppu_send_addr+1 467 | tya 468 | sec 469 | sbc #8 470 | and #15 471 | tay ; shift ocean coordinate by -8 bytes 472 | lda #%11111111 ; base attribute 473 | : 474 | ; fill with base attribute 475 | ldx #0 476 | : 477 | sta _ppu_send, X 478 | inx 479 | cpx #8 480 | bcc :- 481 | ; write 6 attribute bytes with ocean colour 482 | ldx #0 483 | : 484 | lda #%00000000 ; ocean attribute 485 | sta _ppu_send, Y 486 | iny 487 | tya 488 | and #15 ; wrap at 16 bytes (the extra 8 bytes is a dummy for other screen) 489 | tay 490 | inx 491 | cpx #6 492 | bcc :- 493 | lda #0 ; horizontal update 494 | jsr _ppu_direction 495 | rts 496 | .endproc 497 | 498 | _weather_shift: 499 | ; A = value to add to all weather particle X coordinates 500 | sta tmp1 501 | ldx #(32*4) 502 | : 503 | lda tmp1 504 | clc 505 | adc _oam + 3, X 506 | sta _oam + 3, X 507 | inx 508 | inx 509 | inx 510 | inx 511 | bne :- 512 | rts 513 | 514 | .proc _weather_animate 515 | ; spawn a tile if appropriate 516 | lda _weather_rate_min ; min rate of 0 = no weather 517 | beq animate 518 | lda weather_wait 519 | beq spawn 520 | dec weather_wait 521 | jmp animate 522 | spawn: 523 | ldy weather_next 524 | cpy #(32*4) 525 | bcs :+ 526 | ldy #(32*4) 527 | : 528 | lda _oam + 0, Y 529 | cmp #240 530 | bcc spawn_defer ; particle active, try again next frame 531 | lda #0 ;Y 532 | sta _oam + 0, Y 533 | lda _weather_tile 534 | sta _oam + 1, Y 535 | lda _weather_attribute 536 | sta _oam + 2, Y 537 | lda _te 538 | bne :+ 539 | jsr _prng ; X 540 | jmp :++ 541 | : ; TE uses separated rain PRNG 542 | jsr _prngf 543 | : 544 | sta _oam + 3, Y 545 | ; set time until next spawn 546 | lda _te 547 | bne :+ 548 | jsr _prng 549 | jmp :++ 550 | : ; TE uses separated rain PRNG 551 | jsr _prngf 552 | : 553 | and _weather_rate_mask 554 | clc 555 | adc _weather_rate_min 556 | sta weather_wait 557 | spawn_defer: 558 | ; set next spawn index 559 | iny 560 | iny 561 | iny 562 | iny 563 | sty weather_next 564 | animate: 565 | ldx #(32*4) 566 | ; iterate through particles 567 | animate_loop: 568 | lda _oam + 0, X 569 | cmp #240 570 | bcs animate_next ; inactive 571 | ; vertical fall (add speed stored in attribute bits) 572 | lda _oam + 2, X 573 | and #%00011100 574 | lsr 575 | lsr 576 | tay ; becomes a multiplier for wind 577 | adc _oam + 0, X 578 | sta _oam + 0, X 579 | ; wind 580 | lda _te 581 | bne @wind_te ; TE 582 | @wind: ; non-TE uses shared PRNG 583 | jsr prng1 584 | cmp _weather_wind_p 585 | bcs :+ 586 | lda _oam + 3, X 587 | clc 588 | adc _weather_wind_dir 589 | sta _oam + 3, X 590 | : 591 | dey ; multiply wind by fall velocity 592 | bne @wind 593 | jmp @collide 594 | @wind_te: ; TE uses separated rain PRNG 595 | jsr prngf1 596 | cmp _weather_wind_p 597 | bcs :+ 598 | lda _oam + 3, X 599 | clc 600 | adc _weather_wind_dir 601 | sta _oam + 3, X 602 | : 603 | dey ; multiply wind by fall velocity 604 | bne @wind_te 605 | ;jmp @collide 606 | @collide: 607 | ; collide with floor 608 | lda _oam + 3, X 609 | clc 610 | adc ppu_2005x+0 611 | sta ptr1+0 612 | lda #0 613 | adc ppu_2005x+1 614 | and #1 615 | .assert (<_floor_y) = 0, error, "_floor_y must be page aligned." 616 | clc 617 | adc #>_floor_y 618 | sta ptr1+1 ; ptr1 = _floor_y + ((particle X + scroll X) & 511) 619 | ;ldy #0 620 | lda (ptr1), Y 621 | cmp _oam + 0, X 622 | bcc @hit 623 | bne @miss 624 | @hit: 625 | lda #240 626 | sta _oam + 0, X 627 | @miss: 628 | animate_next: 629 | inx 630 | inx 631 | inx 632 | inx 633 | bne animate_loop 634 | rts 635 | .endproc 636 | 637 | .proc _fmult 638 | ; A:X operand a (16-bit) 639 | ; stack = operand b (16-bit) 640 | ba = ptr1+0 641 | dc = ptr1+1 642 | fe = ptr2+0 643 | hg = ptr2+1 644 | out0 = tmp1 645 | out1 = tmp2 646 | out2 = tmp3 647 | mix = tmp4 648 | sign = ptr3 649 | ; retrieve operands, negate and remember is signed 650 | sta ba 651 | stx dc 652 | lda #0 653 | sta sign 654 | cpx #$80 655 | bcs :+ 656 | lda #0 657 | jmp :++ 658 | : 659 | lda #0 660 | sec 661 | sbc ba 662 | sta ba 663 | lda #0 664 | sbc dc 665 | sta dc 666 | lda #1 667 | : 668 | sta sign 669 | jsr popax 670 | sta fe 671 | stx hg 672 | cpx #$80 673 | bcc :+ 674 | lda #0 675 | sec 676 | sbc fe 677 | sta fe 678 | lda #0 679 | sbc hg 680 | sta hg 681 | inc sign 682 | lda fe 683 | : 684 | ; unsigned nibble long-multiplcation 685 | ; d c b a 686 | ; * h g f e 687 | ; ------------------- 688 | ; e--c e--a 689 | ; e--d e--b 690 | ; f--c f--a 691 | ; f--d f--b 692 | ; g--d g--b 693 | ; g--c g--a 694 | ; h--c h--a 695 | ; h--b 696 | ; |<-keep->| 16 bit result 697 | ; out2 out1 out0 698 | ; 699 | ; 1. e x dcba 700 | tax ; A = fe 701 | lda asl4, X 702 | sta mix ; e0 703 | lda ba 704 | and #$0F 705 | ora mix 706 | tax 707 | lda nmult0, X 708 | sta out0 ; e*a 709 | lda dc 710 | and #$0F 711 | ora mix 712 | tax 713 | lda nmult0, X 714 | sta out1 ; e*c 715 | lda fe 716 | and #$0F 717 | sta mix ; 0e 718 | lda ba 719 | and #$F0 720 | ora mix 721 | tax 722 | lda nmult4, X 723 | clc 724 | adc out0 725 | sta out0 726 | lda nmult8, X 727 | adc out1 728 | sta out1 ; e*b (note: carry to out2 isn't possible) 729 | lda dc 730 | and #$F0 731 | ora mix 732 | tax 733 | lda nmult4, X 734 | ;clc 735 | adc out1 736 | sta out1 737 | lda nmult8, X 738 | adc #0 739 | sta out2 ; e*d 740 | ; 2. f x dcba 741 | lda fe 742 | and #$F0 743 | sta mix ; f0 744 | lda ba 745 | and #$0F 746 | ora mix 747 | tax 748 | lda nmult4, X 749 | ;clc 750 | adc out0 751 | sta out0 752 | lda nmult8, X 753 | adc out1 754 | sta out1 755 | lda out2 756 | adc #0 757 | sta out2 ; f*a 758 | lda dc 759 | and #$0F 760 | ora mix 761 | tax 762 | lda nmult4, X 763 | ;clc 764 | adc out1 765 | sta out1 766 | lda nmult8, X 767 | adc out2 768 | sta out2 ; f*c 769 | ldx fe 770 | lda lsr4, X 771 | sta mix ; 0f 772 | lda ba 773 | and #$F0 774 | ora mix 775 | tax 776 | lda nmult0, X 777 | clc 778 | adc out1 779 | sta out1 ; f*b + carry 780 | lda dc 781 | and #$F0 782 | ora mix 783 | tax 784 | lda nmult0, X 785 | adc out2 786 | sta out2 ; f*d 787 | ; 3. g x dcba 788 | lda hg 789 | and #$0F 790 | sta mix ;0g 791 | lda ba 792 | and #$F0 793 | ora mix 794 | tax 795 | lda nmult4, X 796 | clc 797 | adc out1 798 | sta out1 799 | lda nmult8, X 800 | adc out2 801 | sta out2 ; g*b 802 | lda dc 803 | and #$F0 804 | ora mix 805 | tax 806 | lda nmult4, X 807 | clc 808 | adc out2 809 | sta out2 ; g*d 810 | ldx hg 811 | lda asl4, X 812 | sta mix ; g0 813 | lda ba 814 | and #$0F 815 | ora mix 816 | tax 817 | lda nmult0, X 818 | clc 819 | adc out1 820 | sta out1 ; g*a + carry 821 | lda dc 822 | and #$0F 823 | ora mix 824 | tax 825 | lda nmult0, X 826 | adc out2 827 | sta out2 ; g*c 828 | ; 4. h x dcba 829 | lda hg 830 | and #$F0 831 | sta mix ; h0 832 | lda ba 833 | and #$0F 834 | ora mix 835 | tax 836 | lda nmult4, X 837 | clc 838 | adc out1 839 | sta out1 840 | lda nmult8, X 841 | adc out2 842 | sta out2 ; h*a 843 | lda dc 844 | and #$0F 845 | ora mix 846 | tax 847 | lda nmult4, X 848 | clc 849 | adc out2 850 | sta out2 ; h*c 851 | ldx hg 852 | lda lsr4, X 853 | sta mix ; 0h 854 | lda ba 855 | and #$F0 856 | ora mix 857 | tax 858 | lda nmult0, X 859 | clc 860 | adc out2 861 | ;sta out2 ; h*b 862 | ldy sign 863 | cpy #1 864 | beq :+ 865 | tax ; A = out2 866 | lda out1 867 | rts 868 | : 869 | ; negate the result if sign parity did not match 870 | sta out2 871 | lda #0 872 | sec 873 | sbc out0 874 | ;sta out0 875 | lda #0 876 | sbc out1 877 | sta out1 878 | lda #0 879 | sbc out2 880 | tax 881 | lda out1 882 | rts 883 | ; 884 | 885 | .segment "ALIGN" 886 | .align 256 887 | nmult0: ; ab = 4-bit a * b 888 | .repeat 256, I 889 | .byte (I&15) * (I>>4) 890 | .endrepeat 891 | nmult4: ; ab = 4-bit a * b << 4 892 | .repeat 256, I 893 | .byte <(((I&15) * (I>>4)) << 4) 894 | .endrepeat 895 | nmult8: ; ab = 4-bit a * b >> 4 896 | .repeat 256, I 897 | .byte (((I&15) * (I>>4)) >> 4) 898 | .endrepeat 899 | asl4: ; arithmetic shift left 4 bits 900 | .repeat 256, I 901 | .byte <(I<<4) 902 | .endrepeat 903 | lsr4: ; logical shift right 4 bits 904 | .repeat 256, I 905 | .byte (I>>4) 906 | .endrepeat 907 | .endproc 908 | 909 | ; ========= 910 | ; Utilities 911 | ; ========= 912 | 913 | .export _prng 914 | .export _prng1 915 | .export _prngw ; TE 916 | .export _prngw1 ; TE 917 | .export _prngf ; TE 918 | .export _prngf1 ; TE 919 | .export _mouse_sense 920 | .export _input_setup 921 | .export _input_poll 922 | 923 | .segment "CODE" 924 | 925 | _prng: 926 | ldx #8 927 | prngx: 928 | lda _seed+0 929 | : 930 | asl 931 | rol _seed+1 932 | rol _seed+2 933 | bcc :+ 934 | eor #$1B 935 | : 936 | dex 937 | bne :-- 938 | sta _seed+0 939 | ;ldx #0 ; clear high bits of return value 940 | rts 941 | 942 | _prng1: 943 | ldx #0 944 | prng1: 945 | lda _seed+0 946 | asl 947 | rol _seed+1 948 | rol _seed+2 949 | bcc :+ 950 | eor #$1B 951 | : 952 | sta _seed+0 953 | rts 954 | 955 | _prngw: ; TE 956 | ldx #8 957 | prngwx: 958 | lda _seedw+0 959 | : 960 | asl 961 | rol _seedw+1 962 | rol _seedw+2 963 | bcc :+ 964 | eor #$1B 965 | : 966 | dex 967 | bne :-- 968 | sta _seedw+0 969 | ;ldx #0 ; clear high bits of return value 970 | rts 971 | 972 | _prngw1: ; TE 973 | ldx #0 974 | prngw1: 975 | lda _seedw+0 976 | asl 977 | rol _seedw+1 978 | rol _seedw+2 979 | bcc :+ 980 | eor #$1B 981 | : 982 | sta _seedw+0 983 | rts 984 | 985 | _prngf: ; TE 986 | ldx #8 987 | prngfx: 988 | lda _seedf+0 989 | : 990 | asl 991 | rol _seedf+1 992 | rol _seedf+2 993 | bcc :+ 994 | eor #$1B 995 | : 996 | dex 997 | bne :-- 998 | sta _seedf+0 999 | ;ldx #0 ; clear high bits of return value 1000 | rts 1001 | 1002 | _prngf1: ; TE 1003 | ldx #0 1004 | prngf1: 1005 | lda _seedf+0 1006 | asl 1007 | rol _seedf+1 1008 | rol _seedf+2 1009 | bcc :+ 1010 | eor #$1B 1011 | : 1012 | sta _seedf+0 1013 | rts 1014 | 1015 | input_poll_raw: 1016 | ; strobe 1017 | ldy #1 1018 | sty $4016 1019 | dey 1020 | sty $4016 1021 | ; read 4 bytes each from 4 data lines 1022 | input_poll_raw_strobed: 1023 | ldx #0 1024 | @poll_byte: 1025 | ldy #8 1026 | : 1027 | lda $4016 1028 | ror 1029 | rol _input+0, X 1030 | ror 1031 | rol _input+2, X 1032 | lda $4017 1033 | ror 1034 | rol _input+1, X 1035 | ror 1036 | rol _input+3, X 1037 | dey 1038 | bne :- 1039 | inx 1040 | inx 1041 | inx 1042 | inx 1043 | cpx #16 1044 | bcc @poll_byte 1045 | rts 1046 | 1047 | _mouse_sense: 1048 | lda #1 1049 | sta $4016 1050 | lda $4016 1051 | lda $4017 1052 | lda #0 1053 | sta $4016 1054 | rts 1055 | 1056 | _input_setup: 1057 | ; if present, mouse needs to be initialized by cycling the sensitivity 1058 | jsr _mouse_sense 1059 | ; detect four-score: 1060 | ; There are signatures in the 3rd byte, but I'm ignoring this for a simpler test: 1061 | ; as long as the device reports $00 when not in use, assume no conflict. 1062 | ; An unknown device might be able to affect the controls in a weird, but benign way, 1063 | ; but conveniently this overlay works very well with the extra buttons on an SNES Pad. 1064 | lda #$FF 1065 | sta fourscore_off 1066 | jsr input_poll_raw 1067 | ;lda _input+8 1068 | ;cmp #$10 ; signature 1069 | ;bne :+ 1070 | lda _input+4 1071 | bne :+ ; no buttons pressed 1072 | lda #$80 1073 | eor fourscore_off 1074 | sta fourscore_off 1075 | : 1076 | ;lda _input+9 1077 | ;cmp #$20 1078 | ;bne :+ 1079 | lda _input+5 1080 | bne :+ 1081 | lda #$40 1082 | eor fourscore_off 1083 | sta fourscore_off 1084 | : 1085 | ; detect for mouse on all 4 lines: 1086 | lda #4 1087 | sta mouse_index ; 4 = no mouse detected 1088 | ldx #3 ; count down from 3-0 so that lowest index mouse is used 1089 | @mouse_detect: 1090 | lda #0 1091 | sta temp, X 1092 | stx tmp1 1093 | jsr input_poll_raw 1094 | ldx tmp1 1095 | ;lda _input+0, X 1096 | ;bne @fail ; first byte is 0 1097 | lda _input+4, X 1098 | and #$0F 1099 | cmp #1 1100 | bne @fail ; missing signature 1101 | lda _input+4, X 1102 | lsr 1103 | lsr 1104 | lsr 1105 | lsr 1106 | and #3 1107 | cmp #3 1108 | bcs @fail ; invalid sensitivity value 1109 | @pass: 1110 | lda #1 1111 | sta temp, X ; mouse detected 1112 | stx mouse_index ; mouse selected 1113 | @fail: 1114 | dex 1115 | cpx #4 1116 | bcc @mouse_detect 1117 | ; attempt to cycle to medium sensitivity setting 1118 | lda #4 ; maximum 4 attempts (3 should be sufficient, the 4th is for luck) 1119 | sta tmp1 1120 | : 1121 | jsr _mouse_sense 1122 | jsr _input_poll 1123 | lda _mouse1 1124 | and #$30 1125 | cmp #$10 ; medium setting 1126 | beq :+ 1127 | dec tmp1 1128 | bne :- 1129 | : 1130 | rts 1131 | 1132 | _input_poll: 1133 | jsr input_poll_raw 1134 | ; combine gamepad inputs 1135 | ldx #0 1136 | stx _gamepad 1137 | : 1138 | lda _input, X 1139 | cmp #$FF ; unplugged? 1140 | beq :+ 1141 | ora _gamepad 1142 | sta _gamepad 1143 | : 1144 | inx 1145 | cpx #4 1146 | bcc :-- 1147 | lda mouse_index 1148 | cmp #4 1149 | bcc @mouse 1150 | @no_mouse: 1151 | ; no mouse leaves the possibility of four-score 1152 | ; (treated as if duplicates of ports 2/3 if not $FF) 1153 | bit fourscore_off 1154 | bmi :+ 1155 | lda _input+4 1156 | cmp #$FF 1157 | beq :+ 1158 | ora _gamepad 1159 | sta _gamepad 1160 | : 1161 | bit fourscore_off 1162 | bvs :+ 1163 | lda _input+5 1164 | cmp #$FF 1165 | beq :+ 1166 | ora _gamepad 1167 | sta _gamepad 1168 | : 1169 | lda #0 1170 | sta _mouse1 1171 | sta _mouse2 1172 | sta _mouse3 1173 | jmp @finish 1174 | @mouse: 1175 | ;lda mouse_index 1176 | tax 1177 | lda _input+4, X 1178 | sta _mouse1 1179 | lda _input+8, X 1180 | bpl :+ ; convert signed magnitude to two's complement 1181 | eor #$7F 1182 | clc 1183 | adc #1 1184 | : 1185 | sta _mouse2 1186 | lda _input+12, X 1187 | bpl :+ 1188 | eor #$7F 1189 | clc 1190 | adc #1 1191 | : 1192 | sta _mouse3 1193 | ;jmp @finish 1194 | @finish: 1195 | ; A+B+SELECT+START soft reset 1196 | lda _gamepad 1197 | and #$F0 1198 | cmp #$F0 1199 | bne :+ 1200 | jmp _soft_reset 1201 | : 1202 | rts 1203 | 1204 | ; ===== 1205 | ; Sound 1206 | ; ===== 1207 | 1208 | .export _sound_play 1209 | 1210 | .segment "RODATA" 1211 | sound_silent: .byte $FE, $FF 1212 | 1213 | rain_table: 1214 | .byte $F, $F, $F, $E, $E, $E, $E, $D 1215 | 1216 | apu_init: 1217 | .byte %00110000 ; $4000 SQ0 volume/duty 0, disable length counter 1218 | .byte %01111111 ; $4001 SQ0 sweep disable 1219 | .byte %00000000 ; $4002 SQ0 frequency low 1220 | .byte %00001000 ; $4003 SQ0 frequency high, length counter 1221 | .byte %00110000 ; $4004 SQ1 volume/duty 0, disable length counter 1222 | .byte %01111111 ; $4005 SQ1 sweep disable 1223 | .byte %00000000 ; $4006 SQ1 frequency low 1224 | .byte %00001000 ; $4007 SQ1 frequency high, length counter 1225 | .byte %10000000 ; $4008 TRI halt 1226 | .byte %00000000 ; $4009 unused 1227 | .byte %00000000 ; $400A TRI frequency low 1228 | .byte %00001000 ; $400B TRI frequency high, length counter 1229 | .byte %00110000 ; $400C NSE volume 0, disable length counter 1230 | .byte %00000000 ; $400D unused 1231 | .byte %00000000 ; $400E NSE frequency, period 1232 | .byte %00001000 ; $400F NSE length counter 1233 | 1234 | .macro APU_INIT addr 1235 | .assert (addr >= $4000 && addr < $4010), error, "APU_INIT parameter range: $4000-$400F" 1236 | lda apu_init + (addr - $4000) 1237 | sta addr 1238 | .endmacro 1239 | 1240 | .macro APU_COPY addr 1241 | .assert (addr >= $4000 && addr < $4010), error, "APU_COPY parameter range: $4000-$400F" 1242 | lda apu_out + (addr - $4000) 1243 | sta addr 1244 | .endmacro 1245 | 1246 | .macro APU_OUT_RESET addr 1247 | .assert (addr >= $4000 && addr < $4010), error, "APU_OUT_RESET parameter range: $4000-$400F" 1248 | lda apu_init + (addr - $4000) 1249 | sta apu_out + (addr - $4000) 1250 | .endmacro 1251 | 1252 | .segment "CODE" 1253 | 1254 | sound_prng: 1255 | lda sound_seed+0 1256 | asl 1257 | rol sound_seed+1 1258 | bcc :+ 1259 | eor #$D7 1260 | : 1261 | sta sound_seed+0 1262 | rts 1263 | 1264 | rain_freq: 1265 | jsr sound_prng 1266 | and #7 1267 | tax 1268 | lda #$F 1269 | sec 1270 | sbc rain_table, X 1271 | rts 1272 | 1273 | snow_freq: 1274 | ldx snow_time 1275 | bne @finish 1276 | ; drunk walk from pitches $9-$F 1277 | jsr sound_prng 1278 | and #31 1279 | clc 1280 | adc #32 1281 | tax ; X = snow_time 1282 | ldy snow_pitch 1283 | jsr sound_prng 1284 | and #1 1285 | beq @dec_pitch 1286 | @inc_pitch: 1287 | iny 1288 | cpy #$10 1289 | bcc :+ 1290 | ldy #$0E 1291 | : 1292 | jmp @end_pitch 1293 | @dec_pitch: 1294 | dey 1295 | cpy #$09 1296 | bcs :+ 1297 | ldy #$0A 1298 | : 1299 | @end_pitch: 1300 | sty snow_pitch 1301 | @finish: 1302 | dex 1303 | stx snow_time 1304 | lda #$F 1305 | sec 1306 | sbc snow_pitch 1307 | rts 1308 | 1309 | _sound_play: 1310 | ; A:X = pointer to sound 1311 | ; called from main thread 1312 | ldy sound+1 1313 | bne :+ ; sound is already pending, not safe to rewrite 1314 | sta sound+0 1315 | stx sound+1 1316 | : 1317 | rts 1318 | 1319 | sound_init: 1320 | ; initialize APU in a specific order 1321 | lda #0 1322 | sta $4015 ; silence/reset all channels 1323 | APU_INIT $4000 ; constant volume 0 / halt all channels before enabling any 1324 | APU_INIT $4004 1325 | APU_INIT $400C 1326 | APU_INIT $4008 1327 | lda #%00001111 1328 | sta $4015 ; turn on 4 channels (not DMC) 1329 | APU_INIT $4001 ; disable sweep 1330 | APU_INIT $4005 1331 | APU_INIT $4002 ; low frequency 1332 | APU_INIT $4006 1333 | APU_INIT $400A 1334 | APU_INIT $400E 1335 | APU_INIT $4003 ; high frequency, length counter reload 1336 | APU_INIT $4007 1337 | APU_INIT $400B 1338 | APU_INIT $400F 1339 | ldx #0 1340 | : 1341 | lda apu_init, X 1342 | sta apu_out, X ; store last-written value to apu_out 1343 | inx 1344 | cpx #$10 1345 | bcc :- 1346 | lda #0 1347 | : 1348 | sta $4000, X ; 0 to DMC registers 1349 | inx 1350 | cpx #$14 1351 | bcc :- 1352 | sta apu_sqh+0 1353 | sta apu_sqh+1 1354 | ; initialize other variables 1355 | sta snow_pitch 1356 | sta snow_time 1357 | lda #1 1358 | sta sound_seed+0 1359 | sta sound_seed+1 1360 | lda #sound_silent 1363 | sta sound_ptr+1 1364 | rts 1365 | 1366 | sound_update: 1367 | ; called from NMI 1368 | ; thread safe transfer of "sound" to "sound_ptr" 1369 | lda sound+1 1370 | beq :+ 1371 | sta sound_ptr+1 1372 | lda sound+0 1373 | sta sound_ptr+0 1374 | lda #0 ; 0 in sound+1 signals safe to write again 1375 | sta sound+0 1376 | sta sound+1 1377 | : 1378 | ; play sound 1379 | ldy #0 1380 | @sound_read: 1381 | lda (sound_ptr), Y 1382 | iny 1383 | cmp #$FF ; end 1384 | beq @sound_end 1385 | cmp #$FE ; note-off 1386 | beq @sound_note_off 1387 | cmp #$FD ; next frame 1388 | beq @sound_increment 1389 | @sound_register: 1390 | tax 1391 | lda (sound_ptr), Y 1392 | iny 1393 | sta apu_out, X 1394 | jmp @sound_read 1395 | @sound_note_off: 1396 | APU_OUT_RESET $4000 1397 | APU_OUT_RESET $4004 1398 | APU_OUT_RESET $4008 1399 | APU_OUT_RESET $400C 1400 | APU_OUT_RESET $4002 1401 | APU_OUT_RESET $4006 1402 | APU_OUT_RESET $400A 1403 | APU_OUT_RESET $400E 1404 | APU_OUT_RESET $4003 1405 | APU_OUT_RESET $4007 1406 | APU_OUT_RESET $400B 1407 | APU_OUT_RESET $400F 1408 | jmp @sound_read 1409 | @sound_end: 1410 | dey 1411 | @sound_increment: 1412 | tya 1413 | clc 1414 | adc sound_ptr+0 1415 | sta sound_ptr+0 1416 | bcc :+ 1417 | inc sound_ptr+1 1418 | : 1419 | ; weather overlay 1420 | lda _weather_rate_min 1421 | beq sound_deliver_apu ; particles actively falling 1422 | lda apu_out + $C 1423 | and #$0F ; if existing SFX is using noise, skip this 1424 | bne sound_deliver_apu 1425 | lda apu_out + $E 1426 | pha 1427 | lda apu_out + $C 1428 | pha 1429 | lda #%00110001 1430 | sta apu_out + $C ; volume 1 1431 | lda _weather_tile 1432 | cmp #$38 ; rain 1433 | beq @rain 1434 | @snow: 1435 | ;jsr snow_freq 1436 | ;jmp @apply_weather 1437 | lda #%00110000 1438 | sta apu_out + $C ; didn't like snow sounds, maybe silent is better 1439 | jmp @apply_weather 1440 | @rain: 1441 | jsr sound_prng 1442 | and #63 1443 | clc 1444 | adc #4 1445 | cmp _weather_rate_min 1446 | bcs :+ 1447 | lda #%00110000 1448 | sta apu_out + $C 1449 | : 1450 | jsr rain_freq 1451 | @apply_weather: 1452 | sta apu_out + $E ; weather frequency 1453 | jsr sound_deliver_apu 1454 | pla 1455 | sta apu_out + $C 1456 | pla 1457 | sta apu_out + $E 1458 | rts 1459 | ; 1460 | sound_deliver_apu: 1461 | ;APU_COPY $400F 1462 | APU_COPY $400E 1463 | ;APU_COPY $400D 1464 | APU_COPY $400C 1465 | APU_COPY $400B 1466 | APU_COPY $400A 1467 | ;APU_COPY $4009 1468 | APU_COPY $4008 1469 | lda apu_out+7 1470 | cmp apu_sqh+1 ; prevent phase reset 1471 | beq :+ 1472 | sta $4007 1473 | sta apu_sqh+1 1474 | : 1475 | APU_COPY $4006 1476 | ;APU_COPY $4005 1477 | APU_COPY $4004 1478 | lda apu_out+3 1479 | cmp apu_sqh+0 ; prevent phase reset 1480 | beq :+ 1481 | sta $4003 1482 | sta apu_sqh+0 1483 | : 1484 | APU_COPY $4002 1485 | ;APU_COPY $4001 1486 | APU_COPY $4000 1487 | rts 1488 | 1489 | ; ===================== 1490 | ; NES hardware handling 1491 | ; ===================== 1492 | 1493 | .export _ppu_latch 1494 | .export _ppu_direction 1495 | .export _ppu_write 1496 | .export _ppu_load 1497 | .export _ppu_fill 1498 | .export _ppu_ctrl 1499 | .export _ppu_mask 1500 | .export _ppu_scroll_x 1501 | .export _ppu_scroll_y 1502 | .export _ppu_post 1503 | .export _ppu_profile 1504 | .export _ppu_apply_direction 1505 | .export _ppu_apply 1506 | 1507 | .import _main 1508 | .import popa 1509 | 1510 | .segment "CODE" 1511 | 1512 | _ppu_latch: 1513 | bit $2002 1514 | stx $2006 1515 | sta $2006 1516 | rts 1517 | 1518 | _ppu_direction: 1519 | asl 1520 | asl 1521 | and #%00000100 1522 | pha 1523 | lda ppu_2000 1524 | and #%11111011 1525 | sta ppu_2000 1526 | pla 1527 | ora ppu_2000 1528 | sta ppu_2000 1529 | rts 1530 | 1531 | _ppu_write: 1532 | sta $2007 1533 | rts 1534 | 1535 | _ppu_load: 1536 | ; A:X = count 1537 | stx tmp1 1538 | tax 1539 | cpx #0 1540 | beq @page 1541 | ; finish incomplete page first 1542 | ldy #0 1543 | : 1544 | lda (_ptr), Y 1545 | sta $2007 1546 | iny 1547 | dex 1548 | bne :- 1549 | tya 1550 | clc 1551 | adc _ptr+0 1552 | sta _ptr+0 1553 | lda #0 1554 | adc _ptr+1 1555 | sta _ptr+1 1556 | @page: ; remaining 256 byte pages 1557 | ; X = 0 1558 | txa ; A = 0 1559 | cmp tmp1 1560 | beq @done 1561 | tay ; Y = 0, tmp1 = remaining page count 1562 | : 1563 | lda (_ptr), Y 1564 | sta $2007 1565 | iny 1566 | bne :- 1567 | inc _ptr+1 1568 | dec tmp1 1569 | jmp @page 1570 | @done: 1571 | rts 1572 | 1573 | _ppu_fill: 1574 | ; A:X = count 1575 | ; stack = fill value 1576 | sta tmp1 1577 | lda ppu_2000 ; set increment by 1 mode 1578 | and #%11111011 1579 | sta $2000 1580 | jsr popa 1581 | ldy tmp1 1582 | cpy #0 1583 | beq @no_partial 1584 | @page: 1585 | : 1586 | sta $2007 1587 | dey 1588 | bne :- 1589 | @no_partial: 1590 | cpx #0 1591 | beq @done 1592 | dex 1593 | ldy #0 1594 | jmp @page 1595 | @done: 1596 | rts 1597 | 1598 | _ppu_ctrl: 1599 | and #%00111000 1600 | pha 1601 | lda ppu_2000 1602 | and #%11000111 1603 | sta ppu_2000 1604 | pla 1605 | ora ppu_2000 1606 | sta ppu_2000 1607 | rts 1608 | 1609 | _ppu_mask: 1610 | sta ppu_2001 1611 | rts 1612 | 1613 | _ppu_scroll_x: 1614 | ; A:X = scroll 1615 | sta ppu_2005x+0 1616 | txa 1617 | and #1 1618 | sta ppu_2005x+1 1619 | pha 1620 | lda ppu_2000 1621 | and #%11111110 1622 | sta ppu_2000 1623 | pla 1624 | ora ppu_2000 1625 | sta ppu_2000 1626 | rts 1627 | 1628 | _ppu_scroll_y: 1629 | ; A:X = scroll 1630 | sta ppu_2005y+0 1631 | txa 1632 | and #1 1633 | sta ppu_2005y+1 1634 | asl 1635 | pha 1636 | lda ppu_2000 1637 | and #%11111101 1638 | sta ppu_2000 1639 | pla 1640 | ora ppu_2000 1641 | sta ppu_2000 1642 | rts 1643 | 1644 | _ppu_post: 1645 | sta ppu_post_mode 1646 | : 1647 | lda ppu_post_mode 1648 | bne :- 1649 | rts 1650 | 1651 | _ppu_profile: 1652 | ora ppu_2001 1653 | sta $2001 1654 | rts 1655 | 1656 | _ppu_apply_direction: 1657 | jsr _ppu_direction 1658 | lda ppu_2000 1659 | sta $2000 1660 | rts 1661 | 1662 | _ppu_apply: 1663 | bit $2002 1664 | lda _ppu_send_addr+1 1665 | sta $2006 1666 | lda _ppu_send_addr+0 1667 | sta $2006 1668 | lda ppu_2000 1669 | sta $2000 ; set direction 1670 | ldx #0 1671 | cpx _ppu_send_count 1672 | bcs :++ 1673 | : 1674 | lda _ppu_send, X 1675 | sta $2007 1676 | inx 1677 | cpx _ppu_send_count 1678 | bcc :- 1679 | : 1680 | ldx #0 1681 | stx _ppu_send_count 1682 | rts 1683 | 1684 | nmi: 1685 | pha 1686 | txa 1687 | pha 1688 | tya 1689 | pha 1690 | ldx #0 1691 | lda ppu_post_mode 1692 | stx ppu_post_mode ; signal the post is complete (after RTI) 1693 | jeq @end 1694 | cmp #POST_NONE 1695 | jeq @post_none 1696 | cmp #POST_UPDATE 1697 | beq @post_update 1698 | cmp #POST_DOUBLE 1699 | beq @post_double 1700 | ; otherwise POST_OFF 1701 | @post_off: 1702 | lda ppu_2001 1703 | and #%11100001 1704 | sta $2001 1705 | jmp @end 1706 | @post_update: 1707 | jsr @post_common 1708 | lda ppu_2000 1709 | sta $2000 ; set direction 1710 | ldx #0 1711 | cpx _ppu_send_count 1712 | bcs :++ 1713 | : 1714 | lda _ppu_send, X 1715 | sta $2007 1716 | inx 1717 | cpx _ppu_send_count 1718 | bcc :- 1719 | : 1720 | ldx #0 1721 | stx _ppu_send_count 1722 | jmp @post_on 1723 | @post_double: 1724 | jsr @post_common ; direction remains horizontal 1725 | ldx #0 1726 | stx _ppu_send_count 1727 | : 1728 | lda _ppu_send, X 1729 | sta $2007 1730 | inx 1731 | cpx #32 1732 | bcc :- 1733 | lda _ppu_send_addr+1 1734 | eor #$04 ; flip horizontal nametable 1735 | sta $2006 1736 | lda _ppu_send_addr+0 1737 | sta $2006 1738 | : 1739 | lda _ppu_send, X 1740 | sta $2007 1741 | inx 1742 | cpx #64 1743 | bcc :- 1744 | jmp @post_on 1745 | @post_common: 1746 | ; OAM 1747 | lda #0 1748 | sta $2003 1749 | lda #>_oam 1750 | sta $4014 1751 | ; palettes 1752 | lda #0 1753 | sta $2000 ; set horizontal increment 1754 | bit $2002 1755 | ldx #>$3F00 1756 | stx $2006 1757 | ;lda #<$3F00 1758 | sta $2006 1759 | ldx #0 1760 | : 1761 | lda _palette, X 1762 | sta $2007 1763 | inx 1764 | cpx #32 1765 | bcc :- 1766 | ; prepare address for send 1767 | lda _ppu_send_addr+1 1768 | sta $2006 1769 | lda _ppu_send_addr+0 1770 | sta $2006 1771 | rts 1772 | @post_none: 1773 | @post_on: 1774 | lda ppu_2000 1775 | sta $2000 1776 | lda ppu_2005x+0 1777 | sta $2005 1778 | lda ppu_2005y+0 1779 | sta $2005 1780 | lda ppu_2001 1781 | sta $2001 1782 | @end: 1783 | jsr sound_update 1784 | pla 1785 | tay 1786 | pla 1787 | tax 1788 | pla 1789 | rti 1790 | 1791 | irq: 1792 | rti 1793 | 1794 | reset: 1795 | ; already done in reset_stub 1796 | ;sei ; disable maskable interrupts 1797 | ;lda #0 1798 | ;sta $2000 ; disable non-maskable interrupt 1799 | lda #0 1800 | sta $2001 ; rendering off 1801 | sta $4010 ; disable DMC IRQ 1802 | sta $4015 ; disable APU sound 1803 | lda #$40 1804 | sta $4017 ; disable APU IRQ 1805 | cld ; disable decimal mode 1806 | ldx #$FF 1807 | txs ; setup stack 1808 | ; wait for vblank #1 1809 | bit $2002 1810 | : 1811 | bit $2002 1812 | bpl :- 1813 | ; TE decide at reset whether to use tournament edition 1814 | ; if signature is present: keep/flip te, otherwise te = 0 1815 | TE_SIG1 = $19 1816 | TE_SIG2 = $01 ; 01 = keep, (+1) 02 = flip 1817 | TE_SIG3 = $31 1818 | lda _te+0 1819 | cmp #2 1820 | bcs @te0 ; if it wasn't 0 or 1, is invalid, so reset 1821 | lda _te+1 1822 | cmp #TE_SIG1 1823 | bne @te0 1824 | lda _te+3 1825 | cmp #TE_SIG3 1826 | bne @te0 1827 | lda _te+2 1828 | cmp #TE_SIG2 ; 01 = keep 1829 | beq @te_ready 1830 | cmp #TE_SIG2+1 ; (+1) 02 = flip 1831 | bne @te0 ; unknown = reset 1832 | @te_flip: 1833 | lda _te+0 1834 | bne @te0 1835 | lda #1 1836 | sta _te+0 1837 | jmp @te_ready 1838 | @te0: 1839 | lda #0 1840 | sta _te+0 1841 | @te_ready: 1842 | ; preserve values across reset 1843 | lda _te+0 1844 | bne @preserve_te 1845 | @preserve: 1846 | ; preserve PRNG seed (keeps randomness across reset) 1847 | lda _seed+2 1848 | pha 1849 | lda _seed+1 1850 | pha 1851 | lda _seed+0 1852 | pha 1853 | jmp @clear_ram 1854 | @preserve_te: 1855 | ; TE: preserve starting seed choice across reset, and holes 1856 | lda _seed_start+3 1857 | pha 1858 | lda _seed_start+2 1859 | pha 1860 | lda _seed_start+1 1861 | pha 1862 | lda _seed_start+0 1863 | pha 1864 | lda _holes+1 1865 | pha 1866 | lda _holes+0 1867 | pha 1868 | ;jmp @clear_ram 1869 | @clear_ram: 1870 | lda _te+0 1871 | pha ; always preserve te 1872 | lda #0 ; clear RAM 1873 | tax 1874 | : 1875 | sta $0000, X 1876 | ;sta $0100, X ; don't clear stack yet 1877 | sta $0200, X 1878 | sta $0300, X 1879 | sta $0400, X 1880 | sta $0500, X 1881 | sta $0600, X 1882 | sta $0700, X 1883 | inx 1884 | bne :- 1885 | pla ; restore te 1886 | sta _te+0 1887 | bne @restore_te 1888 | @restore: 1889 | ; restore PRNG seed 1890 | pla 1891 | sta _seed+0 1892 | pla 1893 | sta _seed+1 1894 | pla 1895 | ora #$80 ; make sure at least 1 bit of seed is set 1896 | sta _seed+2 1897 | jmp @clear_stack 1898 | @restore_te: 1899 | pla 1900 | sta _holes+0 1901 | pla 1902 | sta _holes+1 1903 | pla 1904 | sta _seed_start+0 1905 | pla 1906 | sta _seed_start+1 1907 | pla 1908 | sta _seed_start+2 1909 | pla 1910 | sta _seed_start+3 1911 | ;jmp @clear_stack 1912 | @clear_stack: 1913 | ; set valid TE signature 1914 | lda #TE_SIG1 1915 | sta _te+1 1916 | lda #TE_SIG2 1917 | sta _te+2 1918 | lda #TE_SIG3 1919 | sta _te+3 1920 | ; clear stack, separately wipe OAM 1921 | lda #0 1922 | : 1923 | sta $0100, X 1924 | inx 1925 | bne :- 1926 | lda #$FF 1927 | : 1928 | sta _oam, X 1929 | inx 1930 | bne :- 1931 | ; wait for vblank #2 1932 | : 1933 | bit $2002 1934 | bpl :- 1935 | ; initialize internal variables 1936 | jsr sound_init 1937 | lda #%00011110 ; No emphasis, no greyscale, BG and sprite shown, no hidden column 1938 | sta ppu_2001 1939 | lda #%10001000 ; NMI on, 8-pixel sprites, BG page 0, Sprite page 1 1940 | sta ppu_2000 1941 | sta $2000 ; turn NMI on permanently 1942 | ; initialize CC65 and enter main() 1943 | jsr cc65_init 1944 | jsr _main 1945 | jmp _soft_reset 1946 | 1947 | _te_switch: ; void te_switch() 1948 | lda #POST_OFF 1949 | jsr _ppu_post 1950 | lda #TE_SIG2+1 ; (+1) 02 = flip 1951 | sta _te+2 1952 | _soft_reset: 1953 | jmp reset_stub 1954 | 1955 | ; ========== 1956 | ; CC65 setup 1957 | ; ========== 1958 | 1959 | ; simplified version of cc65 libsrc/nes/crt0.s 1960 | 1961 | .import copydata ; cc65 "DATA" segment setup 1962 | .importzp sp ; cc65 C stack pointer 1963 | .export __STARTUP__: absolute = 1 1964 | 1965 | .segment "CODE" 1966 | cc65_init: 1967 | jsr copydata 1968 | lda #<(cstack + CSTACK_SIZE) 1969 | sta sp+0 1970 | lda #>(cstack + CSTACK_SIZE) 1971 | sta sp+1 1972 | rts 1973 | 1974 | ; ======= 1975 | ; NES ROM 1976 | ; ======= 1977 | 1978 | .segment "HEADER" 1979 | 1980 | INES_MAPPER = 2 ; UNROM 1981 | INES_MIRROR = 1 ; horizontal nametables 1982 | INES_PRG_16K = 2 ; 32K 1983 | INES_CHR_8K = 0 1984 | INES_BATTERY = 0 1985 | INES2 = %00001000 ; NES 2.0 flag for bit 7 1986 | INES2_SUBMAPPER = 0 1987 | INES2_PRGRAM = 0 1988 | INES2_PRGBAT = 0 1989 | INES2_CHRRAM = 7 ; 8K 1990 | INES2_CHRBAT = 0 1991 | INES2_REGION = 2 ; 0=NTSC, 1=PAL, 2=Dual 1992 | 1993 | ; iNES 1 header 1994 | .byte 'N', 'E', 'S', $1A ; ID 1995 | .byte >8) 2001 | .byte ((INES_CHR_8K >> 8) << 4) | (INES_PRG_16K >> 8) 2002 | .byte (INES2_PRGBAT << 4) | INES2_PRGRAM 2003 | .byte (INES2_CHRBAT << 4) | INES2_CHRRAM 2004 | .byte INES2_REGION 2005 | .byte $00 ; VS system 2006 | .byte $00, $00 ; padding/reserved 2007 | .assert * = 16, error, "NES header must be 16 bytes." 2008 | 2009 | .segment "STUB" 2010 | reset_stub: 2011 | sei ; disable maskable interrupts 2012 | lda #0 2013 | sta $2000 ; disable non-maskable interrupt 2014 | sta @zero ; setup up UxROM bank 0 2015 | jmp reset 2016 | @zero: .byte 0 2017 | 2018 | .segment "VECTORS" 2019 | .word nmi 2020 | .word reset_stub 2021 | .word irq 2022 | 2023 | ; end of file 2024 | -------------------------------------------------------------------------------- /fmult.py: -------------------------------------------------------------------------------- 1 | # python test for verifying and debugging fmult 2 | 3 | import random 4 | 5 | demo = False 6 | 7 | def sint16(x): 8 | if x < 0x8000: 9 | return x 10 | else: 11 | return x - 0x10000 12 | 13 | def fmult(b,a): 14 | def fmult_row(la,lb): 15 | print ("%c*%c: %08X [%02X %02X %02X]" % (la,lb,out,out&0xFF,(out>>8)&0xFF,(out>>16)&0xFF)) 16 | oa = a 17 | ob = b 18 | print ("fmult %04X * %04X = dcba * hgfe" % (a,b)) 19 | flip = False 20 | flipped = False 21 | if (a & 0x8000): 22 | a = (-a) & 0xFFFF 23 | flip = not flip 24 | flipped = True 25 | if (b & 0x8000): 26 | b = (-b) & 0xFFFF 27 | flip = not flip 28 | flipped = True 29 | if (flipped): 30 | print ("flip %04X * %04X" % (a,b)) 31 | out = 0; 32 | out += ((a & 0x000F) * (b & 0x000F)) 33 | fmult_row('e','a') 34 | out += ((a & 0x0F00) * (b & 0x000F)) 35 | fmult_row('e','c') 36 | out += ((a & 0x00F0) * (b & 0x000F)) 37 | fmult_row('e','b') 38 | out += ((a & 0xF000) * (b & 0x000F)) 39 | fmult_row('e','d') 40 | out += ((a & 0x000F) * (b & 0x00F0)) 41 | fmult_row('f','a') 42 | out += ((a & 0x0F00) * (b & 0x00F0)) 43 | fmult_row('f','c') 44 | out += ((a & 0x00F0) * (b & 0x00F0)) 45 | fmult_row('f','b') 46 | out += ((a & 0xF000) * (b & 0x00F0)) 47 | fmult_row('f','d') 48 | out += ((a & 0x00F0) * (b & 0x0F00)) 49 | fmult_row('g','b') 50 | out += ((a & 0xF000) * (b & 0x0F00)) 51 | fmult_row('g','d') 52 | out += ((a & 0x000F) * (b & 0x0F00)) 53 | fmult_row('g','a') 54 | out += ((a & 0x0F00) * (b & 0x0F00)) 55 | fmult_row('g','c') 56 | out += ((a & 0x000F) * (b & 0xF000)) 57 | fmult_row('h','a') 58 | out += ((a & 0x0F00) * (b & 0xF000)) 59 | fmult_row('h','c') 60 | out += ((a & 0x00F0) * (b & 0xF000)) 61 | fmult_row('h','b') 62 | out += ((a & 0xF000) * (b & 0xF000)) 63 | fmult_row('h','d') 64 | out = out & 0x00FFFFFF 65 | if (flip): 66 | print ("flip %04X" % out) 67 | out = -out 68 | out = (out & 0x00FFFFFF) >> 8 69 | verify = ((sint16(oa)*sint16(ob))&0xFFFF00)//256 70 | print ("out: %04X = %04X" % (out,verify)) 71 | assert (out == verify), "verify fail!" 72 | 73 | if demo: 74 | for i in range(20000): 75 | a = random.getrandbits(16) 76 | b = random.getrandbits(16) 77 | fmult(a,b) 78 | -------------------------------------------------------------------------------- /layer1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbbradsmith/NESertGolfing/1947a966403a9b0d05641a316df7d7bb209429e6/layer1.png -------------------------------------------------------------------------------- /layer1te.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbbradsmith/NESertGolfing/1947a966403a9b0d05641a316df7d7bb209429e6/layer1te.png -------------------------------------------------------------------------------- /layer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbbradsmith/NESertGolfing/1947a966403a9b0d05641a316df7d7bb209429e6/layer2.png -------------------------------------------------------------------------------- /layers.chr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbbradsmith/NESertGolfing/1947a966403a9b0d05641a316df7d7bb209429e6/layers.chr -------------------------------------------------------------------------------- /layerste.chr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbbradsmith/NESertGolfing/1947a966403a9b0d05641a316df7d7bb209429e6/layerste.chr -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # NESert Golfing 2 | 3 | A 2D golf game for the NES, directly inspired by [Desert Golfing](https://captaingames.itch.io/desert-golfing). 4 | 5 | Brad Smith, 2019. 6 | 7 | Official website: 8 | https://rainwarrior.itch.io/nesert-golfing 9 | 10 | Author's website: 11 | http://rainwarrior.ca 12 | 13 | ## License 14 | 15 | This source code is provided under the Creative Commons Attribution license. (CC BY 4.0) 16 | 17 | Full details of this license are available here: 18 | https://creativecommons.org/licenses/by/4.0/ 19 | 20 | This approximately means that you are free to reuse this source code for your own purposes, 21 | provided that you include an attribution to me (Brad Smith) in documentation and 22 | accessible credits for the work it is used in. 23 | 24 | ## Prerequisites 25 | 26 | The only required tool for building this game is CC65: 27 | https://cc65.github.io/ 28 | 29 | The specific version of CC65 used: 30 | **cc65 V2.17 - Git 80a43d7** 31 | 32 | Optional: 33 | - [Mesen](https://www.mesen.ca/) - An NES emulator and debugger. 34 | - [FCEUX](http://www.fceux.com/) - An alternative NES emulator and debugger. 35 | - [Python 3.7](https://www.python.org/) - Used for some included build scripts. 36 | - [Aseprite](https://www.aseprite.org/) - Used to prototype animated sprites. 37 | - [FamiTracker](http://famitracker.com/) - Used to prototype sounds. 38 | 39 | The provided build script (*build.bat*) is a Windows batch file. 40 | If you wish to build this program on another platform, 41 | you will have to recreate a similar script for yourself in a suitable format. 42 | 43 | ## Guide 44 | 45 | Place CC65 in a *cc65/* directory in the root of this project. 46 | The CC65 toolchain executables should be in *cc65/bin/*. 47 | 48 | The main code for this game is contained in *dgolf.c* and *dgolf.s*. 49 | These are two separate but complementary halves of the source code. 50 | The C side contains most of the high level logic, 51 | and the assembly (*.s*) side contains low level hardware access 52 | and some other routines that needed to be written 53 | directly in more efficient assembly. 54 | 55 | The output NES ROM *temp/dgolf.nes* is built by running *build.bat*. 56 | 57 | The build will also create a debug symbols file *temp/dgolf.deb*, 58 | which can be used with the Mesen emulator. 59 | 60 | Some additional assembly code is contained in *blend.s*. 61 | I made this separately as a generic routine to blend between any 62 | two colours in the NES palette in 4 steps. 63 | 64 | The graphics tiles are contained in *layers.chr* and *sprite.chr*, 65 | but these files were built from the correspoding *.png* images. 66 | You can use *build_data.py* to rebuild the *.chr* files from 67 | the source images. 68 | 69 | A subset of the CC65 runtime libraries have been compiled 70 | into *temp/runtime.lib*, but this may be rebuilt if you wish 71 | to use a later version of its libraries. 72 | Read the comments in *build_runtime.bat* for details. 73 | 74 | A few other files have been included that aren't directly 75 | used as part of the build. See the file list below for details. 76 | 77 | ## Files 78 | 79 | - *build.bat* - Batch file to build *temp/dgolf.nes*. 80 | - *build_runtime.bat* - Batch file to rebuild *temp/runtime.lib*. 81 | - *build_data.py* - Python script to build *layers.chr*, *layerste.chr*, *sprite.chr*, and *temp/slopes.inc*. 82 | - *fmult.py* - Python script for prototyping *_fmult* subroutine found in *dgolf.s*. 83 | - *dgolf.c* - C code for NESert Golfing. 84 | - *dgolf.s* - Assembly code for NESert Golfing. 85 | - *blend.s* - Assembly code generic 4-step NES colour blend. 86 | - *dgolf.cfg* - ROM build layout for CC65 linker. 87 | - *layer1.png* - Title image tiles, monochrome. 88 | - *layer1te.png* - Tournament Edition title image tiles, monochrome. 89 | - *layer2.png* - Font set tiles, monochrome. 90 | - *sprite.png* - Sprite tiles, 3 colour. 91 | - *layers.chr* - Compiled from *layer1.png* and *layer2.png*. 92 | - *layerste.chr* - Compiled from *layer1te.png* and *layer2.png*. 93 | - *sprite.chr* - Compiled from *sprite.png*. 94 | - *sounds.ftm* - FamiTracker prototype of the sound effects used in the game. 95 | - *splash.ase* - Aseprite animation prototype of the ball splashing into the sea. 96 | - *temp/dgolf.nes* - Provided game ROM, already built. 97 | - *temp/runtime.lib* - Pre-compiled CC65 runtime library. 98 | - *temp/slopes.inc* - Generated slope tables. 99 | 100 | ## Other Versions 101 | 102 | SNES: 103 | - https://github.com/bbbradsmith/NESertGolfing/tree/snes 104 | 105 | Famicom Disk System: 106 | - https://github.com/bbbradsmith/NESertGolfing/tree/fds 107 | 108 | Tournament Edition: 109 | - In version 1.5 this was merged into the regular game. Select HELP from the menu, press and hold A, then press SELECT to switch to Tournament Edition. (A+B+SELECT+START will also reset the game.) 110 | 111 | History: 112 | - 1.5 - 2022-10-15 - SNES version. Tournament Edition now in regular version via secret code. Soft reset button combo. 113 | - 1.4 FDS - 2021-06-05 - Famicom Disk System version. 114 | - 1.4 TE - 2019-11-24 - Tournament Edition allows chosen random seed. 115 | - 1.4 - 2019-10-05 - Optimization. 116 | - 1.3 - 2019-02-08 - Multiplayer first place player goes first. 117 | - 1.2 - 2019-02-02 - Flag sound effect tweak. 118 | - 1.1 - 2019-02-02 - Multiplayer rotates which player starts. 119 | - 1.0 - 2019-01-31 - Initial release. 120 | -------------------------------------------------------------------------------- /sounds.ftm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbbradsmith/NESertGolfing/1947a966403a9b0d05641a316df7d7bb209429e6/sounds.ftm -------------------------------------------------------------------------------- /splash.ase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbbradsmith/NESertGolfing/1947a966403a9b0d05641a316df7d7bb209429e6/splash.ase -------------------------------------------------------------------------------- /sprite.chr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbbradsmith/NESertGolfing/1947a966403a9b0d05641a316df7d7bb209429e6/sprite.chr -------------------------------------------------------------------------------- /sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbbradsmith/NESertGolfing/1947a966403a9b0d05641a316df7d7bb209429e6/sprite.png -------------------------------------------------------------------------------- /temp/runtime.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbbradsmith/NESertGolfing/1947a966403a9b0d05641a316df7d7bb209429e6/temp/runtime.lib -------------------------------------------------------------------------------- /temp/slopes.inc: -------------------------------------------------------------------------------- 1 | ; generated slopes 2 | 3 | slope_y0: 4 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 5 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 6 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01, $01, $02, $02, $03, $03 7 | .byte $04, $04, $05, $06, $07, $08, $09, $0A, $0C, $0D, $0F, $10, $12, $14, $16, $19 8 | .byte $1B, $1E, $21, $24, $27, $2B, $2E, $32, $36, $3B, $40, $44, $4A, $4F, $55, $5B 9 | .byte $62, $69, $70, $77, $7F, $88, $90, $99, $A3, $AD, $B7, $C2, $CE, $D9, $E6, $F3 10 | .byte $00, $0E, $1D, $2C, $3B, $4C, $5D, $6E, $80, $93, $A7, $BB, $D0, $E6, $FC, $14 11 | .byte $2C, $45, $5E, $79, $94, $B0, $CE, $EC, $0B, $2B, $4C, $6D, $90, $B4, $D9, $00 12 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 13 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 14 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $FF, $FF, $FF, $FF, $FE, $FE, $FD, $FD 15 | .byte $FC, $FC, $FB, $FA, $F9, $F8, $F7, $F6, $F4, $F3, $F1, $F0, $EE, $EC, $EA, $E7 16 | .byte $E5, $E2, $DF, $DC, $D9, $D5, $D2, $CE, $CA, $C5, $C0, $BC, $B6, $B1, $AB, $A5 17 | .byte $9E, $97, $90, $89, $81, $78, $70, $67, $5D, $53, $49, $3E, $32, $27, $1A, $0D 18 | .byte $00, $F2, $E3, $D4, $C5, $B4, $A3, $92, $80, $6D, $59, $45, $30, $1A, $04, $EC 19 | .byte $D4, $BB, $A2, $87, $6C, $50, $32, $14, $F5, $D5, $B4, $93, $70, $4C, $27, $00 20 | 21 | slope_y1: 22 | .byte $00, $01, $02, $03, $04, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01 23 | .byte $01, $01, $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 24 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 25 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 26 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 27 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 28 | .byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $02 29 | .byte $02, $02, $02, $02, $02, $02, $02, $02, $03, $03, $03, $03, $03, $03, $03, $04 30 | .byte $00, $FF, $FE, $FD, $FC, $00, $00, $00, $00, $00, $00, $00, $00, $FF, $FF, $FF 31 | .byte $FF, $FF, $FF, $FF, $FF, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 32 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 33 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 34 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 35 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 36 | .byte $FF, $FE, $FE, $FE, $FE, $FE, $FE, $FE, $FE, $FE, $FE, $FE, $FE, $FE, $FE, $FD 37 | .byte $FD, $FD, $FD, $FD, $FD, $FD, $FD, $FD, $FC, $FC, $FC, $FC, $FC, $FC, $FC, $FC 38 | 39 | norm_x0: 40 | .byte $00, $B5, $E4, $F2, $F8, $00, $00, $00, $00, $00, $00, $00, $00, $B5, $B5, $B5 41 | .byte $B5, $B5, $B5, $B5, $B5, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 42 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01, $01, $02, $02, $03, $03 43 | .byte $04, $04, $05, $06, $07, $08, $09, $0A, $0C, $0D, $0F, $10, $12, $14, $16, $19 44 | .byte $1B, $1E, $20, $23, $27, $2A, $2D, $31, $35, $39, $3E, $42, $47, $4C, $51, $56 45 | .byte $5B, $61, $66, $6C, $72, $78, $7E, $83, $89, $8F, $95, $9A, $A0, $A5, $AB, $B0 46 | .byte $B5, $B9, $BE, $C2, $C6, $CA, $CE, $D1, $D5, $D8, $DB, $DD, $E0, $E2, $E4, $E6 47 | .byte $E8, $EA, $EB, $ED, $EE, $EF, $F1, $F2, $F3, $F4, $F4, $F5, $F6, $F7, $F7, $F8 48 | .byte $00, $4B, $1C, $0E, $08, $00, $00, $00, $00, $00, $00, $00, $00, $4B, $4B, $4B 49 | .byte $4B, $4B, $4B, $4B, $4B, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 50 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $FF, $FF, $FF, $FF, $FE, $FE, $FD, $FD 51 | .byte $FC, $FC, $FB, $FA, $F9, $F8, $F7, $F6, $F4, $F3, $F1, $F0, $EE, $EC, $EA, $E7 52 | .byte $E5, $E2, $E0, $DD, $D9, $D6, $D3, $CF, $CB, $C7, $C2, $BE, $B9, $B4, $AF, $AA 53 | .byte $A5, $9F, $9A, $94, $8E, $88, $82, $7D, $77, $71, $6B, $66, $60, $5B, $55, $50 54 | .byte $4B, $47, $42, $3E, $3A, $36, $32, $2F, $2B, $28, $25, $23, $20, $1E, $1C, $1A 55 | .byte $18, $16, $15, $13, $12, $11, $0F, $0E, $0D, $0C, $0C, $0B, $0A, $09, $09, $08 56 | 57 | norm_x1: 58 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 59 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 60 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 61 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 62 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 63 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 64 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 65 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 66 | .byte $00, $FF, $FF, $FF, $FF, $00, $00, $00, $00, $00, $00, $00, $00, $FF, $FF, $FF 67 | .byte $FF, $FF, $FF, $FF, $FF, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 68 | .byte $00, $00, $00, $00, $00, $00, $00, $00, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 69 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 70 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 71 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 72 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 73 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 74 | 75 | norm_y0: 76 | .byte $00, $4B, $8E, $B0, $C2, $00, $00, $00, $00, $00, $00, $00, $00, $4B, $4B, $4B 77 | .byte $4B, $4B, $4B, $4B, $4B, $00, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01 78 | .byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01 79 | .byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $02, $02 80 | .byte $02, $02, $03, $03, $04, $04, $05, $05, $06, $07, $08, $09, $0B, $0C, $0E, $10 81 | .byte $12, $14, $16, $19, $1B, $1E, $22, $25, $29, $2D, $31, $35, $39, $3E, $42, $47 82 | .byte $4C, $51, $55, $5A, $5F, $64, $69, $6E, $73, $77, $7C, $81, $85, $89, $8D, $92 83 | .byte $95, $99, $9D, $A1, $A4, $A7, $AB, $AE, $B1, $B3, $B6, $B9, $BB, $BE, $C0, $C2 84 | .byte $00, $4B, $8E, $B0, $C2, $00, $00, $00, $00, $00, $00, $00, $00, $4B, $4B, $4B 85 | .byte $4B, $4B, $4B, $4B, $4B, $00, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01 86 | .byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01 87 | .byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $02, $02 88 | .byte $02, $02, $03, $03, $04, $04, $05, $05, $06, $07, $08, $09, $0B, $0C, $0E, $10 89 | .byte $12, $14, $16, $19, $1B, $1E, $22, $25, $29, $2D, $31, $35, $39, $3E, $42, $47 90 | .byte $4C, $51, $55, $5A, $5F, $64, $69, $6E, $73, $77, $7C, $81, $85, $89, $8D, $92 91 | .byte $95, $99, $9D, $A1, $A4, $A7, $AB, $AE, $B1, $B3, $B6, $B9, $BB, $BE, $C0, $C2 92 | 93 | norm_y1: 94 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 95 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 96 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 97 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 98 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 99 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 100 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 101 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 102 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 103 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 104 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 105 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 106 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 107 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 108 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 109 | .byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF 110 | 111 | ; end 112 | --------------------------------------------------------------------------------