As they say, anything that works is better than anything that doesn't. Still, there are possibly useful changes that could be made in spots. Consider this:
Code:
00265 10AC title_screen_loop
00266 10AC 20 42 F1 jsr read_key_buffer
00267 10AF F0 FB beq title_screen_loop
00268 10B1 C9 58 cmp #$58 ; 'x'
00269 10B3 F0 0B beq go_x
00270 10B5 C9 4F cmp #$4f ; 'o'
00271 10B7 F0 0D beq go_o
00272 10B9 C9 51 cmp #$51
00273 10BB F0 9C beq end
00274 10BD 4C AC 10 jmp title_screen_loop
00275 10C0
00276 10C0 go_x
00277 10C0 A9 58 lda #$58
00278 10C2 8D 0C 11 sta playingx$
00279 10C5 60 rts
00280 10C6
00281 10C6 go_o
00282 10C6 A9 4F lda #$4f
00283 10C8 8D 0C 11 sta playingx$
00284 10CB 60 rts
and later this:
Code:
00324 110C 00 playingx$ byte 0
The first thing I notice is that you are using numbers for things that conceptually are not numbers. You might make the code easier to read by writing something like this:
Code:
00265 10AC title_screen_loop
00266 10AC 20 42 F1 jsr read_key_buffer
00267 10AF F0 FB beq title_screen_loop
00268 10B1 C9 58 cmp #'X'
00269 10B3 F0 0B beq go_x
00270 10B5 C9 4F cmp #'O'
00271 10B7 F0 0D beq go_o
00272 10B9 C9 51 cmp #'Q'
00273 10BB F0 9C beq end
00274 10BD 4C AC 10 jmp title_screen_loop
00275 10C0
00276 10C0 go_x
00277 10C0 A9 58 lda #$58
00278 10C2 8D 0C 11 sta playingx$
00279 10C5 60 rts
00280 10C6
00281 10C6 go_o
00282 10C6 A9 4F lda #$4f
00283 10C8 8D 0C 11 sta playingx$
00284 10CB 60 rts
Or you might use an equate somewhere to attach a label to the keys you're using:
Code:
CH_X equ $58
and then use that label wherever you've been using a hard-coded number. That would make it both easier to read and easier to change (not that that's very likely in tic-tac-toe - but what if you port the game to system that doesn't use ASCII?).
Second, taking a branch of any sort does not change the contents of any register you can easily get at (it does change the program counter, but that's harder to read - not impossible, but not especially easy). So when you branch after comparing the contents of the accumulator, when the program gets to where the branch leads, the accumulator still has that same value. You don't need to re-load it. And since you store the contents to the same location, you don't really need two branch destinations either:
Code:
00265 10AC title_screen_loop
00266 10AC 20 42 F1 jsr read_key_buffer
00267 10AF F0 FB beq title_screen_loop
00268 10B1 C9 58 cmp #CH_X
00269 10B3 F0 0B beq go_move
00270 10B5 C9 4F cmp #CH_O
00271 10B7 F0 0D beq go_move
00272 10B9 C9 51 cmp #CH_Q
00273 10BB F0 9C beq end
00274 10BD 4C AC 10 jmp title_screen_loop
00275 10C0
00276 10C0 go_move
00278 10C2 8D 0C 11 sta playingx$
00279 10C5 60 rts
For this bit:
Code:
00324 110C 00 playingx$ byte 0
I have an old book by Adam Osborne that says one of the defining characteristics of microcomputer programs is blatant and promiscuous intermixing of code and data. Maybe so, but I've personally never been comfortable with it. Here you've got a variable buried in the midst of your program. You already know that you can use zero page to store things. You can also use anywhere in memory that isn't already used by something else. The memory directly following your program, for instance. Doing would not change the size, speed or functionality of your program, but it would be cleaner, IMHO.