-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcolumns.s
More file actions
2661 lines (2542 loc) · 74.7 KB
/
columns.s
File metadata and controls
2661 lines (2542 loc) · 74.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
; COLUMNS for Durango-X
; original idea by SEGA
; (c) 2022-2024 Carlos J. Santisteban
; last modified 20240831-1755
; add -DMAGIC to increase magic jewel chances
; ****************************
; *** hardware definitions ***
; ****************************
screen3 = $6000
IO8attr = $DF80
IO8blk = $DF88
IO9kbd = $DF9B
IO9nes0 = $DF9C
IO9nlat = IO9nes0
IO9nes1 = $DF9D
IO9nclk = IO9nes1
IOAie = $DFA0
IOBeep = $DFB0
IO_PSG = $DFDB ; PSG for optional effects and background music
; ****************************
; *** constant definitions ***
; ****************************
; * controls *
#define PAD_BUTTONS
#define PAD_FIRE %10000000
#define PAD_STRT %01000000
#define PAD_B %00100000
#define PAD_SEL %00010000
#define PAD_UP %00001000
#define PAD_LEFT %00000100
#define PAD_DOWN %00000010
#define PAD_RGHT %00000001
; * direction flag *
#define MOV_LEFT 2
#define MOV_RGHT 0
#define MOV_DOWN 1
#define MOV_ROT 3
#define MOV_NONE 3
; * screen addresses *
#define FIELD_PG $63
#define BANNER_PG $6C
#define RST_BYT 64
; * score display type *
#define DISP_LVL 0
#define DISP_JWL 1
#define DISP_SCO 2
; * compressed pictures indices (always even) *
#define SC_INTRO 0
#define SC_FIELD 2
; * renumbered status indices (best if even) *
#define STAT_OVER 0
#define STAT_LVL 2
#define STAT_PLAY 4
#define STAT_CRSH 6
#define STAT_CHK 8
#define STAT_HCHK 10
#define STAT_VCHK 12
#define STAT_SLCK 14
#define STAT_BSCK 16
#define STAT_BLNK 18
#define STAT_EXPL 20
#define STAT_DROP 22
#define STAT_RLS 24
#define STAT_PAUS 26
#define STAT_DIE 28
; * some game constants *
#define NUM_LVLS 3
#define JWL_COL 3
#define NUM_JWLS 10
#define MAGIC_JWL 7
; number of jewels for next level (1-byte BCD)
#define GOAL $50
; player array offset
#define PLYR_OFF 128
; * tile/column size *
; tile height in pixels
#define TIL_HGT 8
; column height in pixels (actually JWL_COL*TIL_HGT)
#define COL_HGT 24
; tile width in bytes
#define TIL_WDT 4
; banner width in bytes (actually TIL_WDT*ROW_WDT)
#define BAN_WDT 24
; banner height in pixels
#define BAN_HGT 22
; * notable positions on matrix *
; last visible cell
#define LAST_V 118
; bottom left cell (actually LAST_V-5)
#define BOTT_L 113
; first visible row
#define VTOP_L 16
; bottom sentinel row
#define LASTROW 120
; starting column for new pieces
#define INIT_COL 4
; offset between rows
#define ROW_OFF 8
; visible cells in row
#define ROW_WDT 6
; * animation parameters *
; magic jewel animation speed (MUST be one less a power of two!)
#define MJ_UPD 31
; cycles for blink animation and spacing between them
#define BLINKS 8
#define BL_SPC 12
; explosion rate
#define EXP_SPD 16
; column drop rate
#define CDROP_T 2
; die animation period
#define DIE_PER 10
; pause animation period (around 1/4 second)
#define PAUS_SP 32
; * other timings *
; down key repeat rate
#define DTIM 4
; peñonazo cycles and time between pulses
#define P_CYC 5
#define P_PER 4
; *************************
; *** memory allocation ***
; *************************
; player-1 data (player 2 has 128-byte offset)
status = 64 ; player status
speed = status+1 ; 7-bit value between events (127 at level 0, halving after that, but never under 5?)
ev_dly = speed+1 ; 8-bit counter for next event
dr_dly = ev_dly+1 ; 8-bit counter for next drop
s_level = dr_dly+1 ; selected difficulty
pad0mask= s_level+1 ; gamepad masking values
pad0val = pad0mask+1 ; gamepad current status
padlast = pad0val+1 ; last pad status
column = padlast+1 ; current column
next_c = column+3 ; next piece
posit = next_c+3 ; position in 8x16 matrix
oldposit= posit+1 ; old position
bcd_arr = oldposit+1 ; level/jewels/score arrays [LJJSSS] in BCD, big endian
anim = bcd_arr+6 ; base row for death and other animations
phase = anim+1 ; current animation coordinate (formerly Y and die_y)
mag_col = phase+1 ; specific magic jewel colour animation
dr_mj = mag_col+1 ; flag (d7) if magic jewel is dropped / show or hide tile during blink
match_c = dr_mj+1 ; match counter
cycle = match_c+1 ; check round
goal = cycle+1 ; jewel count for next level, big-endian BCD (JJ)
delta = goal+2 ; temporary score (binary)
paus_t = delta+2 ; stored event count after pause
; common data (non-duplicated)
tempx = paus_t+1 ; now another temporary
temp = tempx+1
select = temp+1 ; player index for main loop
bcd_lim = select+1
colour = bcd_lim+1
seed = colour+1 ; PRNG seed
; * these NEED to be on zeropage *
src = seed+2
ptr = src+2
; these save a few bytes and cycles in ZP
; irq_ptr and ticks(h) no longer here
kbd_ok = ptr+2 ; if non-zero, supported keyboard has been detected
col_sel = kbd_ok+1 ; keyboard column counter
; * these probably common, but will need indexing if multi-threaded *
htd_out = col_sel+1 ; 3-byte output
mult = htd_out+3 ; 16-bit temporary multiply
mul8 = mult+2 ; 8-bit factor
; player 2 data for convenience
status2 = status+PLYR_OFF ; player status (this is usually 192)
speed2 = status2+1 ; 7-bit value between events (127 at level 0, halving after that, but never under 5?)
ev_dly2 = speed2+1 ; 8-bit counter for next event
dr_dly2 = ev_dly2+1 ; 8-bit counter for next drop
s_level2= dr_dly2+1 ; selected difficulty
pad1mask= s_level2+1 ; gamepad masking values
pad1val = pad1mask+1 ; gamepad current status
padlast2= pad1val+1 ; last pad status
column2 = padlast2+1 ; current column
next_c2 = column2+3 ; next piece
posit2 = next_c2+3 ; position in 8x16 matrix
oldpos2 = posit2+1 ; old position in 8x16 matrix
bcd_arr2= oldpos2+1 ; level/jewels/score arrays [LJJSSS] in BCD, big endian
anim2 = bcd_arr2+6 ; base row for death animation
phase2 = anim2+1 ; current death animation index (formerly Y)
mag_col2= phase2+1 ; specific magic jewel colour animation
dr_mj2 = mag_col2+1 ; flag (d7) if magic jewel is dropped
match_c2= dr_mj2+1 ; match counter
cycle2 = match_c2+1 ; check round
goal2 = cycle2+1 ; jewel count for next level, big-endian BCD (JJ)
delta2 = goal2+2 ; temporary score (binary)
paus_t2 = delta2+2 ; stored event count after pause
_end_zp = paus_t2+2
; these MUST be outside ZP, change start address accordingly
irq_ptr = $0200 ; for Pocket compatibility
nmi_ptr = $0202
ticks = $0206 ; standard address, although 8-bit only
ticks_l = $0207 ; older timer for compatibility
field = $0400 ; 8x16 (6x13 visible) game status arrays (player2 = +128)
field2 = $0480
mark = $0500 ; tile match register, mimics the game arrays (player2 = +128)
mark2 = $0580
; *****************
; *** main code ***
; *****************
#ifdef POCKET
* = $0800 ; standard pocket address
#else
* = $C000 ; will 16K suffice?
#endif
rom_start:
; header ID
.byt 0 ; [0]=NUL, first magic number
#ifdef POCKET
.asc "pX" ; pocket executable
.word rom_start ; load address
.word reset ; execution address
#else
.asc "dX" ; bootable ROM for Durango-X devCart
.asc "****" ; reserved
#endif
.byt 13 ; [7]=NEWLINE, second magic number
; filename
.asc "Columns", 0 ; C-string with filename @ [8], max 238 chars
.asc "Original idea by Jay Geertsen/SEGA", 0 ; comment with IMPORTANT attribution
; advance to end of header
.dsb rom_start + $E6 - *, $FF
; NEW library commit (user field 2)
.asc "$$$$$$$$"
; NEW main commit (user field 1)
.asc "$$$$$$$$"
; NEW coded version number
.word $1083 ; 1.0RC3 %vvvvrrrr sshhbbbb, where revision = %hhrrrr, ss = %00 (alpha), %01 (beta), %10 (RC), %11 (final)
; date & time in MS-DOS format at byte 248 ($F8)
.word $8F00 ; time, 17.56 1000 1-111 000-0 0000
.word $591F ; date, 2024/8/31 0101 100-1 000-1 1111
; filesize in top 32 bits (@ $FC) now including header ** must be EVEN number of pages because of 512-byte sectors
.word file_end-rom_start ; actual executable size
.word 0 ; 64K space does not use upper 16 bits, [255]=NUL may be third magic number
reset:
SEI ; usual 6502 init
CLD
LDX #$FF
TXS
; Durango-X specifics
STX IOAie ; enable interrupts, as X is an odd value
LDA #$38 ; colour mode, screen 3, RGB
STA IO8attr ; set video mode
; mute PSG, if available
LDA #$FF ; max. attenuation for noise channel
psg_mute:
STA IO_PSG ; send command to PSG (4)
SEC ; used for subtraction (2)
SBC #32 ; next channel (2)
JSR delay24 ; wait for next PSG command (24)
BMI psg_mute ; until all four channels done (3)
; show splash screen
; LDX #SC_INTRO ; actually 0
INX ; was $FF, now 0 is the index of compressed file entry
JSR dispic ; decompress!
; * init game stuff * actually whole ZP
LDX #0 ; reset index, as dispic affects all
rst_loop:
STZ 0, X ; was status, X [CMOS only, use TXA/STA otherwise]
INX
BNE rst_loop
; setup controllers etc (assume minstrel-type kbd)
JSR read_pad ; get initial values (mask variables already reset)
LDX pad0val
LDY pad1val
STX pad0mask ; ...and store them
STY pad1mask
JSR read_pad ; just for clearing the values
; check here for supported keyboard presence (col 6 = $2C)
LDA #%00100000 ; sixth column (set PD5)
STA IO9kbd
LDA IO9kbd ; check ID value
CMP #$2C ; standard 5x8 kbd
BNE no_kbd ; if present...
STA kbd_ok ; ...enable for ISR (otherwise was zero)
no_kbd:
; setup interrupt system
LDY #<isr
LDX #>isr ; ISR address
STY irq_ptr ; standard FW adress
STX irq_ptr+1
LDY #<reset
LDX #>reset ; warm start address
STY nmi_ptr
STX nmi_ptr+1 ; set standard pointer
CLI ; enable interrupts!
; let at least one player start the game
JSR continue ; wait for user action
LDA ticks
STA seed
STY seed+1 ; quite random seed
STX select ; save selected player
; display game field
LDX #SC_FIELD ; set compressed file index
JSR dispic ; decompress!
; then level selection according to player
LDX select ; retrieve selected player
LDA #STAT_LVL
STA status, X ; set new status
JSR sel_ban
; *******************************
; *** *** main event loop *** ***
; *******************************
loop:
LDX select ; check player...
LDY pad0val, X ; ...and its controller status
BNE chk_stat ; some buttons were pressed
STZ padlast, X ; otherwise clear that
chk_stat:
LDA status, X ; check status of current player
; * * STATUS 0, game over * *
; CMP #STAT_OVER ; not needed if STAT_OVER is zero
BNE not_over
TYA ; get this player controller status
BIT #PAD_FIRE|PAD_B|PAD_SEL|PAD_STRT ; start new game
BEQ not_over ; not if not pressed...
CMP padlast, X
BEQ not_over ; ...or not just released
STA padlast, X ; anyway, register this press
LDA #STAT_LVL
STA status, X ; go into selection status
JSR sel_ban ; after drawing level selection menu
not_over:
LDA status, X ; check status of current player EEEEK
; * * LVL STATUS, level selection * *
CMP #STAT_LVL ; selecting level?
BEQ do_lvl
JMP not_lvl
do_lvl:
; selecting level, check up/down and fire/select/start
LDA pad0val, X ; ...and its controller status for proper operation
BIT #PAD_DOWN ; increment level
BEQ not_s1d ; not if not pressed
CMP padlast, X ; still pressing?
BEQ not_lvl ; ignore either!
STA padlast, X ; anyway, register this press
JSR inv_row ; deselect current
INC s_level, X ; increment level
LDY s_level, X
CPY #NUM_LVLS ; three levels only, wrap otherwise
BNE s1_nw
STZ s_level, X
BRA s1_nw ; common ending
not_s1d:
BIT #PAD_UP ; decrement level
BEQ not_s1u
CMP padlast, X ; still pressing?
BEQ not_lvl ; ignore!
STA padlast, X ; anyway, register this press
JSR inv_row ; deselect current
DEC s_level, X ; decrement level
BPL s1_nw ; wrap if negative
LDA #NUM_LVLS-1 ; max level index
STA s_level, X
BRA s1_nw ; common ending
not_s1u:
BIT #PAD_FIRE|PAD_B|PAD_SEL|PAD_STRT ; select current level
BEQ not_lvl
; level is selected, set initial score and display
CMP padlast, X ; still pressing?
BEQ not_lvl ; ignore!
STA padlast, X ; anyway, register this press
; set initial parameters
LDY s_level, X ; selected difficulty
LDA ini_lbin, Y ; get level accordingly
TAY ; level index 0, 5 or 10
LDA ini_spd, Y ; proper speed for this level
STA speed, X ; eeeeek
LDY s_level, X ; selected difficulty again
LDA ini_lev, Y ; level BCD as index for initial value
PHA ; later...
LDA ini_score, Y
STA bcd_arr+3, X ; score counter eeeeek
LDA ini_sc_l, Y
STA bcd_arr+4, X
STZ bcd_arr+5, X ; clear this one too!
PLA
STA bcd_arr, X ; place initial values in adequate array indices
STZ bcd_arr+1, X
STZ bcd_arr+2, X ; reset jewel counter as well
LDA #GOAL
STA goal+1, X ; set jewel goal for next level
STZ goal, X
LDY #DISP_LVL
JSR numdisp
LDY #DISP_JWL
JSR numdisp
LDY #DISP_SCO
JSR numdisp ; display all values
; and go into playing mode
JSR clearfield ; init game matrix and all gameplay status
LDA speed, X ; eeeeek
CLC
ADC ticks
STA ev_dly, X ; compute delay until next event
LDA #STAT_PLAY
STA status, X
BRA not_lvl
s1_nw:
JSR inv_row ; mark new value
not_lvl:
LDA status, X
; * * PLAY STATUS * *
CMP #STAT_PLAY ; playing?
BEQ is_play
JMP not_play
is_play:
LDA pad0val, X ; restore and continue evaluation * must be here
BIT #PAD_STRT|PAD_SEL ; START will make pause
BEQ not_pstart
CMP padlast, X ; still pressing?
BEQ not_pstart
; in order not to help, store whatever time is left for next down event so it could be restored
LDA ev_dly, X ; next event
SEC
SBC ticks_l ; minus current time
STA paus_t, X ; store for later
; should make initial display ** TO DO
LDA #STAT_RLS ; will wait for START key release!
STA status, X
JMP not_play
not_pstart:
BIT #PAD_LEFT ; move to the left?
BEQ not_pleft ; not if not pressed
CMP padlast, X ; still pressing?
BNE is_pleft
JMP not_pfire ; ignore either, but keep going down!
is_pleft:
STA padlast, X ; anyway, register this press
LDY #MOV_LEFT ; otherwise, x is one less
BRA p_end
not_pleft:
BIT #PAD_RGHT ; move to the right?
BEQ not_pright ; not if not pressed
CMP padlast, X ; still pressing?
BNE is_pright
JMP not_pfire ; ignore either, but keep going down!
is_pright:
STA padlast, X ; anyway, register this press
LDY #MOV_RGHT ; otherwise, x is one more
BRA p_end
not_pright:
BIT #PAD_DOWN ; let it drop?
BEQ not_pdown ; not if not pressed
CMP padlast, X ; still pressing?
BEQ cont_down ; do not reset special event
STA padlast, X ; otherwise, register this first press
LDA ticks_l ; from current time...
INC ; ...ASAP!
STA dr_dly, X ; update next event
cont_down:
LDY #MOV_NONE ; default action in most cases
LDA dr_dly, X ; time to drop...
CMP ticks_l ; ...already?
BPL p_end ; note inverse condition!
CLC
ADC #DTIM ; add drop delay
STA dr_dly, X ; update time for next event
LDY #MOV_DOWN ; otherwise, Y is one more
INC delta, X ; extra score!
BRA p_end
not_pdown:
BIT #PAD_FIRE|PAD_B ; flip?
BEQ not_pfire ; not if not pressed
CMP padlast, X ; still pressing?
BNE do_pfire
JMP not_pfire ; ignore either, but keep going down!
do_pfire:
STA padlast, X ; anyway, register this press
; piece rotation
; * might launch PSG effect here *
LDA #1
STA IOBeep ; activate sound...
LDA column+2, X
PHA ; save last piece
LDA column+1, X
STA column+2, X
LDA column, X
STA column+1, X ; rotate the rest
PLA
STA column, X ; and wrap the last one
LDY posit, X ; display recently rotated column!
JSR col_upd
STZ IOBeep ; ...and finish audio pulse
LDY #MOV_ROT ; this was a rotation, thus no change
BRA p_end
not_pfire:
LDY #MOV_NONE ; eeek
do_advance:
; in case of timeout, put piece down... or take another
LDA ticks_l
CMP ev_dly, X
BMI p_end ; if timeout expired... not BCC eeeeeek
CLC
ADC speed, X
STA ev_dly, X ; update time for next event
; will check if possible to move down
LDY #MOV_DOWN
p_end:
; move according to Y-direction, if possible
; should actually update offset, check availability and, if so, update screen, otherwise revert offset (easily done)
CPY #MOV_NONE ; any move?
BNE do_move
JMP not_move
do_move:
LDA posit, X
CLC
ADC ix_dir, Y ; add combined offset
STA posit, X
JSR chkroom ; returns zero if available space
CMP #0
BEQ is_room ; movement is feasible, do not revert
LDA posit, X
SEC
SBC ix_dir, Y ; otherwise revert move
STA posit, X
CPY #MOV_DOWN ; tried to go down and failed?
BNE not_move ; do not update screen... but check if at bottom
; cannot go down any more, update field
; maybe here's the place to check for matches...
; but first check peñonazo's height, must be second row or below
BIT #%01110000 ; at least 16, no matter the player, the row is visible
BNE have_col ; any bit on is OK
; this is done when no room for the new column
LDA #STAT_DIE ; will trigger palmatoria
STA status, X ; eeeeeeeeeeeeeeeeeeeeek
; prepare loops for the new status
TXA ; get player index
ORA #BOTT_L ; 14*8+1 is first column in new coordinates, plus player offset
STA phase, X ; eeeek
LDY #15 ; needs 16 iterations non-visible rows
STY anim, X
; start this animation immediatly, then once every 5 ticks (10 interrupts ~ 2 fields)
LDA ticks
STA ev_dly, X
; now will display the gameover animation concurrently!
BRA not_play
have_col:
PHY ; just in case
LDY posit, X ; final position of first tile (both players)
LDA column, X ; first jewel
STA field, Y
; update magic flag!
CMP #MAGIC_JWL ; was it the magic jewel?
BNE mj_not ; no, leave flag alone
LDA #0
STA field, Y ; otherwise, do NOT store magic jewel
TYA ; get actual position for magic flag
STA dr_mj, X ; store flag
BRA mj_done ; do not bother with the remaining tiles
mj_not:
; continue storing column
LDA column+1, X ; second jewel
STA field+ROW_OFF, Y ; into next row
LDA column+2, X ; last jewel
STA field+ROW_OFF*2, Y
mj_done:
JSR gen_col ; another piece
LDA delta, X ; any drop points?
BEQ no_droppt
JSR addscore ; add possible drop points
LDY #DISP_SCO
JSR numdisp ; display updated score
no_droppt:
PLY
; new piece is stored, let's check for matches!
LDA #STAT_CRSH ; ...but let's go for peñonazo first!
STA status, X ; change status
LDA #P_CYC ; number of peñonazo cliks
STA anim, X ; preload counter
LDA ticks
ADC #P_PER ; time between pulses
STA ev_dly, X ; and also next click
; * might prepare here the PSG initial effect *
is_room:
JSR col_upd ; ...as screen must be updated
not_move:
not_play:
; * * CRSH STATUS, play peñonazo sound * *
LDA status, X
CMP #STAT_CRSH
BNE not_crash
; base sound effect starts here
LDA ticks ; check current time
CMP ev_dly, X ; alternative, safer way
BMI not_crash ; if timeout expired... but not BCC eeeeeek
DEC anim, X ; check sound effect progress
BEQ crash_end ; all done!
ADC #P_PER ; otherwise, add another delay
STA ev_dly, X
JSR pulse ; make brief tick
BRA not_crash ; continue with next thread
crash_end:
; * might alter peñonazo PSG effect from here *
; JSR cl_mark ; before anything else, clear marked tiles matrix
STZ cycle, X ; reset hojalete counter (magic jewel does not count, nor multiplies by level)
LDA #STAT_CHK
STA status, X ; after peñonazo, check for matches
not_crash:
; * * CHK STATUS, check for matching tiles (magic jewel only) * *
LDA status, X
CMP #STAT_CHK
BNE not_check
JSR cl_mark ; before anything else, clear marked tiles matrix
; * check whether magic tile has dropped *
LDY dr_mj, X ; get magic flag
BEQ no_mjwl ; nope, proceed with standard check
; if so, look for whatever tile is under the fallen one and make all of their type disappear
; but first mark it as deletion-pending for the flashing
TYA ; recover index value
STA mark, Y ; only as pending deletion, value is irrelevant
STA mark+ROW_OFF, Y
STA mark+ROW_OFF*2, Y
CLC
ADC #COL_HGT ; go three rows below from first tile
TAY ; index within field
LDX field, Y ; check what was under the magic column
BMI no_mjwl2 ; if sentinel, we are at the very bottom, do nothing... just flashing
; special case, mark every tile of the same type of that just below the magic jewel
LDA select ; player as last position
ORA #LAST_V ; start from last useable cell, backwards
TAY ; index ready
TXA ; pivot element ready
LDX select ; EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEK
mjml:
CMP field, Y ; does it match?
BNE no_mjmt
STA mark, Y ; if so, mark it for deletion
INC match_c, X ; count it as well EEEEEEEEEEK
no_mjmt:
DEY ; one less
CPY select ; already done all of this player's field?
BNE mjml ; if not, continue scanning
BRA do_match
no_mjwl2:
; magic jewel bottomed alone, add 10000 points
LDX select ; needed only when is at the bottom
LDY mj_alone
LDA mj_alone+1 ; get score in binary
STY delta, X
STA delta+1, X ; store as function input
JSR addscore ; display extra points
; *** COMMON entry point to shift from CHK (or BSCK) to BLNK status ***
do_match:
LDA #BLINKS ; usually 8 cycles
STA anim, X ; set counter
LDA ticks ; best update this too
INC
STA ev_dly, X ; will start very soon
LDA #STAT_BLNK
BRA chk_switch ; switch to blink
no_mjwl:
; * magic jewel is handled, now check for any 3 or more matched tiles *
; JSR cl_mark ; should be here eeeek
INC cycle, X ; every cycle multiplies score
LDA #STAT_HCHK ; horizontal check
BRA chk_switch ; will eventually switch thread
; *** common exit from any kind of unsuccessful check ***
not_match:
; JSR gen_col ; is this the correct place?
; JSR col_upd ; might look nicer
LDA ticks_l
CLC
ADC speed, X
STA ev_dly, X ; update time for next event
LDA #STAT_PLAY ; no success, back to play
LDX select ; needed, I'm afraid
; common exit from CHK, switching status
chk_switch:
STA status, X ; EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEK
not_check:
; * * HCHK STATUS, check for matching tiles (horizontal) * *
LDA status, X
CMP #STAT_HCHK
BNE not_hchk
; JSR hchkmatch ; check for horizontal matches (eventually switching to VCHK)
; scan for horizontal matches (now inlined)
STZ temp ; temporary storage for mathch_c
LDA select ; player index
ORA #LAST_V ; last used cell (will add one later)
TAY ; read index
hch_try:
INY ; eeek
LDX #255 ; -1, will be preincremented
hch_skip:
DEY ; scan for blanks
CPY select ; eeek
BEQ hch_fin
INX ; count blanks
LDA field, Y ; get tile in field
BEQ hch_skip ; if blank, keep skipping
CPX #ROW_WDT ; did six consecutive blanks? ** CHECK **
BEQ hch_fin ; all done, then
LDX #0 ; reset match counter
hch_rpt:
INX
DEY ; advance backwards
CPY select ; eeeek
BEQ hch_fin ; eeeeeeeeeeeeek
CMP field, Y ; same as pivot?
BEQ hch_rpt ; keep counting matches
CPX #JWL_COL ; at least 3-in-a-row? ** CHECK **
BCC hch_try ; not enough, try again
; compute score from number of matched tiles, X is run length
PHY ; eeeek
LDY select ; eeeeek
LDA delta, Y ; get accumulated score eeeeeek
CLC
ADC base_sc, X ; add base points for this match
STA delta, Y
LDA delta+1, Y ; propagate carry
ADC #0
STA delta+1, Y
PLY ; eeek
; update match counter as well
LDA temp
CLC ; eeeek
ADC id_table, X ; actually A=A+X
STA temp ; update temporary counter
TYA ; non-zero value, also saves current position
hch_detect:
STA mark+1, Y ; mark them, one 'before' the first mismatch
INY
DEX
BNE hch_detect
TAY ; restore index
BNE hch_try ; and keep trying
hch_fin:
LDX select
LDA temp ; return Z if no matches
STA match_c, X ; set counter (this is the first one, no need to add)
; SEC ; this is run to completion, thus switch thread ASAP
; BCC not_hchk
; prepare things for switching into VCHK status
LDA #BOTT_L ; first column on last visible row
ORA select ; eeeeeeeeeeeeeeeeeeeeeeeeek
STA anim, X ; store as initial position
; switch thread!
LDA #STAT_VCHK
BRA chk_switch
not_hchk:
; * * VCHK STATUS, check for matching tiles (vertical) * *
LDA status, X
CMP #STAT_VCHK
BNE not_vchk
JSR vchkmatch ; check for vertical matches (eventually switching to SLCK)
BCC not_vchk
LDA #LAST_V-2 ; first sensible diagonal, bottom right
ORA select ; eeeeeeeeeeeeeeeeeeeeeeeeek
STA anim, X ; store as initial position
LDA #STAT_SLCK
BRA chk_switch
not_vchk:
; * * SLCK STATUS, check for matching tiles (slash diagonal) * *
LDA status, X
CMP #STAT_SLCK
BNE not_slck
JSR slckmatch ; check for horizontal matches (eventually switching to VCHK)
BCC not_slck
LDA #VTOP_L+ROW_WDT+2*ROW_OFF ; $26 is first sensible diagonal, top-ish right
ORA select ; eeeeeeeeeeeeeeeeeeeeeeeeek
STA anim, X ; store as initial position
LDA #STAT_BSCK
JMP chk_switch
not_slck:
; * * BSCK STATUS, check for matching tiles (backslash diagonal) * *
LDA status, X
CMP #STAT_BSCK
BNE not_bsck
JSR bsckmatch ; check for horizontal matches (eventually switching to BLINK or PLAY)
BCC not_bsck
; all checks are finished, check for any detected matches
LDA match_c, X ; match counter
BNE bs_do_mt
JMP not_match ; no, back to play
bs_do_mt:
JMP do_match ; yes, proceed to blink, explode and drop
not_bsck:
; * * BLNK STATUS, blink matched pieces * *
LDA status, X
CMP #STAT_BLNK
BEQ do_blink
JMP not_blink
do_blink:
LDA ticks ; check current time
CMP ev_dly, X ; alternative, safer way
BMI not_blink ; if timeout expired... but not BCC eeeeeek
ADC #BL_SPC ; ...add another delay for next iteration
STA ev_dly, X
; proceed to hide or show marked tiles
LDA anim, X ; check frame
LSR ; C set means visible
ROR dr_mj, X ; insert C into d7 as flag
; scan all marked tiles, show or hide depending on dr_mj.D7 flag!
TXA ; player index
ORA #LAST_V ; last position
TAY ; index ready (as array index)
mk_cl:
LDA mark, Y ; marked for deletion?
BEQ not_mark
LDA #0 ; hidden by default
BIT dr_mj, X ; time to display or hide?
BPL bl_hide
LDA field, Y ; if display, get tile index
BNE bl_hide ; if marked, but field is zero, it's magic jewel
LDA #MAGIC_JWL ; magic jewel is not stored, thus provide constant index
bl_hide:
PHY
JSR tiledis ; update tile on screen
PLY
LDX select ; eeeeeek * worth doing on tiledis?
not_mark:
DEY
CPY select ; all done?
BNE mk_cl
; anything else?
DEC anim, X ; one less step
BPL not_blink ; still to do, keep this status
; after animation, update jewel counter
LDA #LAST_V ; last visible tile
ORA select ; plus player index
TAX ; use as scanning index
LDY #0 ; reset jewel counter
jwl_ct:
LDA mark, X ; check whether tile was deleted eeeek
BEQ no_jct
INY ; if so, count it!
no_jct:
DEX ; previous tile
CPX select ; discarding player bit
BNE jwl_ct
; in case of magic jewel, this counts is three more, subtract if needed
LDA cycle, X ; this is zero when doing magic jewel
BNE no_3mj
TYA ; temporary count storage
SEC
SBC #JWL_COL ; subtract magic jewel tiles
TAY
no_3mj:
; Y has updated jewel count, convert to BCD
SED ; eeeeek
LDA bcd_id, Y ; jewel count in BCD
CLC
ADC bcd_arr+2, X ; add jewel LSB eeeeek
STA bcd_arr+2, X
LDA bcd_arr+1, X ; MSB
ADC #0 ; propagate carry
STA bcd_arr+1, X
BCC jw_bcd_cc
LDA #$99 ; special overflow case
STA bcd_arr+1, X
STA bcd_arr+2, X ; keep at '9999'
jw_bcd_cc:
CLD
; print updated jewel count
LDY #DISP_JWL ; selects jewel display
JSR numdisp ; update display (reloads X as select)
; finally, turn into EXPLode status
LDA #MAGIC_JWL ; index before first explosion tile
STA anim, X ; store for next thread
LDA ticks
INC
STA ev_dly, X ; execute on next interrupt
STZ dr_mj, X ; eeeek
LDA #STAT_EXPL
STA status, X
not_blink:
; * * EXPLode STATUS * *
LDA status, X
CMP #STAT_EXPL
BEQ do_explode
JMP not_explode
do_explode:
LDA ticks
CMP ev_dly, X ; is it time?
BPL upd_explode
JMP not_explode
upd_explode:
CLC
ADC #EXP_SPD ; frame rate
STA ev_dly, X ; ready for next time
; could do brief tone here IF goal has been reached
LDA bcd_arr+1, X ; jewel count MSB
CMP goal, X ; reached goal?
BCC no_warn
LDA bcd_arr+2, X ; if so, check LSB afterwards
CMP goal+1, X
BCC no_warn ; nope, stay in current level
; sound effect for level change
LDA #88
JSR tone
LDX select ; eeek
no_warn:
; check above could be unified, using a flag
INC anim, X ; preincrement step
LDA anim, X ; which step?
CMP #NUM_JWLS+2 ; over last of explosion
BEQ end_expl ; it's over
; do explode animation
TAX ; keep current frame
LDA select ; get player index
ORA #LAST_V ; last visible tile
TAY ; use as index
TXA ; frame to be saved
ex_loop:
LDX mark, Y ; was this one marked?
BEQ tile_noexp
PHA
PHY
JSR tiledis ; display frame
PLY
PLA
tile_noexp:
DEY
CPY select ; until topmost tile
BNE ex_loop
LDX select ; restore this!
JMP not_explode ; leave thread for now
; delete marked pieces
end_expl:
TXA ; get player index
ORA #LAST_V ; last visible piece
TAX ; use as index, STZ-savvy
exp_cl:
LDA mark, X ; is this marked?
BEQ not_fd ; nope, leave it
STZ field, X ; otherwise, clear it
not_fd:
DEX ; next cell
CPX select ; until the top
BNE exp_cl
; * after animation is ended, may display updated score ** perhaps after drop **
; should apply factors here
LDY bcd_arr, X ; this is current level in BCD
LDA bcd2bin, Y ; binary equivalent
INC ; zero-based!
JSR multiply ; level applied
LDA cycle, X ; check hojalete's shot
JSR multiply ; applied as well
; might also check magic jewel score (no factors)
LDA delta, X
ORA delta+1, X ; any non-magic points?
BNE do_score ; if so, proceed directly
LDA match_c, X ; otherwise, score is number of tiles, times 15
STZ delta+1, X ; clear MSB
ASL
; ROL delta+1, X ; max. match_c is 78, thus delta < 156
ASL
ROL delta+1, X
ASL
ROL delta+1, X
ASL
ROL delta+1, X ; times 16
SEC
SBC match_c, X ; minus one, is times 15
STA delta, X
; more efficient code to check borrow
BCS do_score ; no borrow is OK
DEC delta+1, X
; LDA delta+1, X ; propagate borrow