-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvgamaze.asm
709 lines (601 loc) · 19.7 KB
/
vgamaze.asm
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
; ======================================================================================================================
; Title: VGA-Maze
; Description: A randomized, recursive maze generator featuring ground-breaking VGA graphics.
; Author: Federico Dal Pio Luogo (July 2022)
; ======================================================================================================================
org 100h ; code offset in the current segment
SCREEN_WIDTH equ 320
SCREEN_HEIGHT equ 200
SCREEN_PIXELS equ SCREEN_WIDTH*SCREEN_HEIGHT
CELL_SIZE equ 20 ; size of a square cell in pixels
MAZE_COLS equ SCREEN_WIDTH/CELL_SIZE
MAZE_ROWS equ SCREEN_HEIGHT/CELL_SIZE
WALK_DELAY equ 50000 ; time to wait between visits in microseconds; can range from 0 to 65535
START_COLOR equ 0x36 ; azure
WALL_COLOR equ 0x13 ; gray
BORDER_COLOR equ 0x13 ; gray
BACKGROUND_COLOR equ 0x0f ; white
VISITED_COLOR equ 0x41 ; light red
DISCOVERED_COLOR equ 0x1d ; very light gray
section .data
Visited times MAZE_ROWS*MAZE_COLS db 0 ; keeps track of visited cells
Neighbors dw -1, MAZE_COLS, 1, -MAZE_COLS ; offsets for cardinal directions; WEST, SOUTH, EAST, NORTH respectively
section .bss
Color resb 1 ; value corresponding to a color in the VGA palette
Seed resw 1 ; state of the PRNG
section .text
main: ; entry point (offset 100h)
; load ES with the video screen segment's base address
mov ax, 0xA000
mov es, ax
; enter video mode (AH = 0) in graphical mode (AL = 0x13)
mov ax, 0x0013
int 0x10
; get system time (AH = 0) to initialize the RNG state
xor ax, ax
int 0x1a ; BIOS stores number of ticks since midnight in CX:DX
mov word [Seed], dx ; the lower two bytes will suffice
Restart:
; pick a random cell from where to begin walking the pattern
push MAZE_ROWS*MAZE_COLS-MAZE_COLS-2 ; cell at (24;38)
push MAZE_ROWS+1 ; cell at (1;1)
call RandInt
push ax
call DrawMaze
GetChar:
; read a keystroke from the keyboard (AH = 0x0)
xor ax, ax
int 16h ; BIOS stores ASCII character in AL; waits if buffer is empty
; q / Q: quit the program
; r / R: run the generator again
cmp al, 'q'
je ResetMode
cmp al, 'Q'
je ResetMode
cmp al, 'r'
je Restart
cmp al, 'R'
je Restart
jmp GetChar ; consume another character from the keyboard buffer if none matches
ResetMode:
; set the video mode back to text mode (AL = 0x03)
mov ax, 0x0003
int 0x10
ret
; ----------------------------------------------------------------------------------------------------------------------
; Draws a grid pattern of MAZE_COLS*MAZE_ROWS square cells of side CELL_SIZE representing the labyrinth walls.
; Walls (a cell's sides) are one-pixel-thick vertical and horizontal lines sharing the color specified in the WALL_COLOR
; constant. Cells at the border of the screen are filled with the color specified in the BORDER_COLOR constant.
;
; Stack at start of useful work:
; RET ADDR BP+2
; SP --> BP BP+0
; ----------------------------------------------------------------------------------------------------------------------
DrawWalls:
push bp
mov bp, sp
mov byte [Color], WALL_COLOR
; draw MAZE_ROWS rows of length SCREEN_WIDTH spaced CELL_SIZE pixels each
xor dx, dx
mov cx, word MAZE_ROWS
RowLoop:
push dx ; y
push word SCREEN_WIDTH-1 ; x2
push word 0 ; x1
call HLine
add dx, CELL_SIZE
loop RowLoop
; draw MAZE_COLS columns of length SCREEN_HEIGHT spaced CELL_SIZE pixels each
xor dx, dx
mov cx, word MAZE_COLS
ColLoop:
push word SCREEN_HEIGHT-1 ; y2
push word 0; y1
push dx ; x
call VLine
add dx, CELL_SIZE
loop ColLoop
mov byte [Color], BORDER_COLOR
; fill in the border cells with a single color by drawing 8 adjacent columns/rows for each side
xor dx, dx
mov cx, word CELL_SIZE
FillBorder:
; top rows
push dx ; y
push SCREEN_WIDTH-1 ; x2
push 0 ; x1
call HLine
; bottom rows
mov ax, dx
add ax, SCREEN_HEIGHT-CELL_SIZE
push ax ; y
push SCREEN_WIDTH-1 ; x2
push 0 ; x1
call HLine
; left columns
push SCREEN_HEIGHT-1 ; y2
push 0 ; y1
push dx ; x
call VLine
; right columns
mov ax, dx
add ax, SCREEN_WIDTH-CELL_SIZE
push SCREEN_HEIGHT-1
push 0
push ax
call VLine
inc dx
loop FillBorder
; close-off the grid pattern in case WALL_COLOR and BORDER_COLOR are different values
mov byte [Color], WALL_COLOR
push SCREEN_HEIGHT-CELL_SIZE ; y
push SCREEN_WIDTH-CELL_SIZE ; x2
push CELL_SIZE ; x1
call HLine
push SCREEN_HEIGHT-CELL_SIZE ; y2
push CELL_SIZE ; y1
push SCREEN_WIDTH-CELL_SIZE ; x
call VLine
pop bp
ret
; ----------------------------------------------------------------------------------------------------------------------
; Explores the grid of cells using randomized Depth First Search. The function accepts the starting cell in the form of
; its linear offset in the maze grid (that is, if the starting cell is identified by the couple (x,y), its linear offset
; is y*40 + x, where y lies in [0;MAZE_ROWS] and x lies in [0;MAZE_COLS]). The starting cell is colored START_COLOR.
;
; Stack at start of useful work:
; start_cell BP+4 (cell's linear offset; can be any value in [0;MAZE_ROWS*MAZE_COLS])
; RET ADDR BP+2
; SP --> BP BP+0
; ----------------------------------------------------------------------------------------------------------------------
DrawMaze:
push bp
mov bp, sp
; flood the screen with a solid color, then draw a grid pattern
mov byte [Color], BACKGROUND_COLOR
call FillScreen
call DrawWalls
; set Visited array so border cells cant be walked on
call InitVisited
; color-in the starting cell
push word [bp+4]
call GetScreenCoords
mov byte [Color], START_COLOR
push si ; y
push di ; x
call FillCell
mov byte [Color], WALL_COLOR
push word [bp+4]
call Walk
; recolor the starting cell as it's been covered up by the previous call
mov byte [Color], START_COLOR
push si ; y
push di ; x
call FillCell
pop bp
ret 2 ; add the specified offset to SP to clear the parameters
; ----------------------------------------------------------------------------------------------------------------------
; Visits the cell given as parameter and discovers all paths that can be walked from it recursively. Each neighbor that
; is visited from the parameter is colored DISCOVERED_COLOR and the wall that separates them is colored VISITED_COLOR.
; Dead-ends (cells whose neighbors have all been visited) are colored VISITED_COLOR. Delay has been implemented between
; visits to animate the search progression.
;
; Stack at start of useful work:
; visitee BP+6 (cell's linear offset; can be any value in [0;MAZE_ROWS*MAZE_COLS])
; RET ADDR BP+4
; CX BP+2
; BP BP+0
; SP --> neighbor_id BP-2
; ----------------------------------------------------------------------------------------------------------------------
Walk:
push cx ; save caller's iteration number
push bp
mov bp, sp
sub sp, 2 ; make space for next neighbor's index
; visit the current cell
mov bx, word [bp+6]
mov byte [Visited + bx], 1
; get a random starting point for the scan of the neightbors' list
push 3
push 0
call RandInt
mov word [bp-2], ax
; each cell waits a few milliseconds before visiting its neighbors
mov ah, 0x86
xor cx, cx
mov dx, WALK_DELAY
int 0x15
mov cx, 4 ; number of neighbors to check
VisitNeighbors:
; compute the linear offset of the neighbor's cell
mov bx, word [bp-2]
add bx, bx
mov ax, word [bp+6]
add ax, word [Neighbors + bx]
mov bx, ax
cmp byte [Visited + bx], 1
je Continue ; neighbor has already been visited and must be skipped
; get the coordinates of the neighbor's cell top-left corner
push bx
call GetScreenCoords
mov byte [Color], DISCOVERED_COLOR
push si ; y
push di ; x
call FillCell
mov byte [Color], VISITED_COLOR ; wall's color blends in with visited cells
; destroy the wall separating current cell and the visited neighbor
cmp word [bp-2], 0
je DestroyRightWall ; when visiting the western neighbor, destroy its eastern wall
cmp word [bp-2], 1
je DestroyTopWall ; when visiting the southern neighbor, destroy its northern wall
cmp word [bp-2], 2
je DestroyLeftWall ; when visiting the eastern neighbor, destroy its western wall
cmp word [bp-2], 3
je DestroyBottomWall ; when visiting the northern neighbor, destory its southern wall
; cancel only the inner pixels so walls don't intersect
DestroyLeftWall:
; VLine(x, y+1, y+7)
mov ax, si
add ax, CELL_SIZE-1
push ax ; y2
mov ax, si
inc ax
push ax ; y1
push di ; x
call VLine
jmp Recurse
DestroyBottomWall:
; HLine(x+1, x+7, y+8)
add si, CELL_SIZE
push si ; y
mov ax, di
add ax, CELL_SIZE-1
push ax ; x2
mov ax, di
inc ax
push ax ; x1
call HLine
jmp Recurse
DestroyRightWall:
; VLine(x+8, y+1, y+7)
mov ax, si
add ax, CELL_SIZE-1
push ax ; y2
mov ax, si
inc ax
push ax ; y1
add di, CELL_SIZE
push di ; x
call VLine
jmp Recurse
DestroyTopWall:
; HLine(x+1, x+7, y)
push si ; y
mov ax, di
add ax, CELL_SIZE-1
push ax ; x2
mov ax, di
inc ax
push ax ; x1
call HLine
jmp Recurse
Recurse:
push bx
call Walk
Continue:
; next <- (next+1) % 4
mov ax, word [bp-2]
inc ax
mov dl, 4
div dl
mov al, ah
mov ah, 0
mov word [bp-2], ax
dec cx
jnz VisitNeighbors
; cell and all of its neighbors have been visited; mark accordingly
push word [bp+6]
call GetScreenCoords
mov byte [Color], VISITED_COLOR
push si ; y
push di ; x
call FillCell
; each cell waits a few milliseconds before backtracking
mov ah, 0x86
xor cx, cx
mov dx, WALK_DELAY
int 0x15
Exit:
mov sp, bp
pop bp
pop cx ; restore caller's iteration number
ret 2
; ----------------------------------------------------------------------------------------------------------------------
; Initializes the visit array to zeros and marks the cells at the screen border as visited to prevent the walking
; algorithm going past the screen boundary.
;
; Stack at start of useful work:
; RET ADDR BP+2
; SP --> BP BP+0
; ----------------------------------------------------------------------------------------------------------------------
InitVisited:
push bp
mov bp, sp
; set all cells to unvisited
xor bx, bx
mov cx, MAZE_COLS*MAZE_ROWS
SetZero:
mov byte [Visited + bx], 0
inc bx
loop SetZero
; mark cells at top and bottom border as visited
xor bx, bx
mov cx, MAZE_COLS
VisitHorzBorder:
mov byte [Visited + bx], 1
mov byte [Visited + bx + MAZE_COLS*MAZE_ROWS-MAZE_COLS], 1
inc bx
loop VisitHorzBorder
; mark cells at left and right border as visited
xor bx, bx
mov cx, MAZE_ROWS
VisitVertBorder:
mov byte [Visited + bx-1], 1
mov byte [Visited + bx], 1
add bx, MAZE_COLS
loop VisitVertBorder
pop bp
ret
; ----------------------------------------------------------------------------------------------------------------------
; Get the screen coordinates (x,y) of a maze cell's top-left corner from its offset in the maze array. This is done by
; obtaining the cell coordinates from the equation cell_offset = cell_y*MAZE_COLS + cell_x and then scaling them down by
; a factor of CELL_SIZE.
; Returns x in DI and y in SI.
;
; Stack at start of useful work:
; cell_offset BP+4 (cell's linear offset; can be any value in [0;MAZE_ROWS*MAZE_COLS])
; RET ADDR BP+2
; SP --> BP BP+0
; ----------------------------------------------------------------------------------------------------------------------
GetScreenCoords:
push bp
mov bp, sp
; convert the linear offset to cell coordinates
; cell_x := cell_offset % MAZE_COLS
; cell_y := cell_offset / MAZE_COLS
mov ax, [bp+4]
mov dl, MAZE_COLS
div dl
; DI <- cell_x
; SI <- cell_y
xor dx, dx
mov dl, ah
mov di, dx
mov dl, al
mov si, dx
; convert cell coordinates to screen coordinates
; DI <- DI*CELL_SIZE
; SI <- SI*CELL_SIZE
mov ax, di
mov dl, CELL_SIZE
imul dl
mov di, ax
mov ax, si
mov dl, CELL_SIZE
imul dl
mov si, ax
pop bp
ret 2
; ----------------------------------------------------------------------------------------------------------------------
; Generate a random number on the interval [min;max] using a simple xor-shift generator.
; Algorithm source: http://www.retroprogramming.com/2017/07/xorshift-pseudorandom-numbers-in-z80.html
;
; Stack at start of useful work:
; max BP+6
; min BP+4
; RET ADDR BP+2
; SP --> BP BP+0
; ----------------------------------------------------------------------------------------------------------------------
RandInt:
push bp
mov bp, sp
; seed := time()
; seed ^= seed << 7
; seed ^= seed >> 9
; seed ^= seed << 8
mov ax, word [Seed]
mov dx, ax
shl ax, 7
xor dx, ax
mov ax, dx
shr ax, 9
xor dx, ax
mov ax, dx
shl ax, 8
xor dx, ax
mov ax, dx
mov word [Seed], ax ; updatedSeed
; return min + updatedSeed % (max - min + 1)
xor dx, dx
mov cx, word [bp+6]
sub cx, word [bp+4]
inc cx
div cx
add dx, [bp+4]
mov ax, dx
pop bp
ret 4
; ----------------------------------------------------------------------------------------------------------------------
; Floods the entire screen with a single color specified in the global variable Color.
;
; Stack at start of useful work:
; RET ADDR BP+2
; SP --> BP BP+0
; ----------------------------------------------------------------------------------------------------------------------
FillScreen:
push bp
mov bp, sp
mov cx, SCREEN_PIXELS
xor bx, bx
mov al, [Color]
FillScreenLoop:
mov [es:bx], al
inc bx
loop FillScreenLoop
pop bp
ret
; ----------------------------------------------------------------------------------------------------------------------
; Fills the inside of a maze cell of coordinates (x,y) with a solid color.
; Caller may specify the desired color in the global variable Color.
;
; Stack at start of useful work:
; y BP+8 (cell's row number)
; x BP+6 (cell's column number)
; RET ADDR BP+4
; CX BP+2 (caller's iteration counter)
; SP --> BP BP+0
; ----------------------------------------------------------------------------------------------------------------------
FillCell:
push cx
push bp
mov bp, sp
mov cx, CELL_SIZE-1
Fill:
; HLine(x+1, x+7, y+1)
inc word [bp+8]
push word [bp+8]
mov ax, word [bp+6]
add ax, CELL_SIZE-1
push ax
mov ax, word [bp+6]
inc ax
push ax
call HLine
loop Fill
pop bp
pop cx
ret 4
; ----------------------------------------------------------------------------------------------------------------------
; Plots a horizontal line connecting the end points x1 and x2 within the raster row y.
; Color is specified by the caller.
;
; Stack at start of useful work:
; y BP+18 (raster row)
; x2 BP+16 (x coordinate of the right end of the line)
; x1 BP+14 (x coordinate of the left end of the line)
; RET ADDR BP+12
; AX BP+10
; BX BP+8
; CX BP+6
; DX BP+4
; DI BP+2
; SP --> BP BP+0
; ----------------------------------------------------------------------------------------------------------------------
HLine:
push ax
push bx
push cx
push dx
push di
push bp
mov bp, sp
; validate the coordinates
; x2 < SCREEN_WIDTH
cmp word [bp+16], SCREEN_WIDTH
jge EndHLine
; x1 <= x2
mov ax, word [bp+16]
cmp word [bp+14], ax
jg EndHLine
; 0 <= x1
cmp word [bp+14], 0
jl EndHLine
; 0 <= y < SCREEN_HEIGHT
cmp word [bp+18], 0
jl EndHLine
cmp word [bp+18], SCREEN_HEIGHT
jge EndHLine
; line starts at offset 320*y + x1
mov ax, SCREEN_WIDTH
imul word [bp+18]
add ax, word [bp+14]
mov bx, ax
; set the next x2 - x1 + 1 pixels
mov cx, word [bp+16]
sub cx, word [bp+14]
inc cx
mov al, [Color]
HLineLoop:
mov [es:bx], al
inc bx
loop HLineLoop
EndHLine:
pop bp
pop di
pop dx
pop cx
pop bx
pop ax
ret 6
; ----------------------------------------------------------------------------------------------------------------------
; Plots a vertical line connecting the end points y1 and y2 within the raster column x.
; Color is specified by the caller.
;
; Stack at start of useful work:
; y2 BP+18 (y coordinate of the higher end of the line)
; y1 BP+16 (y coordinate of the lower end of the line)
; x BP+14 (raster column)
; RET ADDR BP+12
; AX BP+10
; BX BP+8
; CX BP+6
; DX BP+4
; DI BP+2
; SP --> BP BP+0
; ----------------------------------------------------------------------------------------------------------------------
VLine:
push ax
push bx
push cx
push dx
push di
push bp
mov bp, sp
; validate the coordinates
; y2 < SCREEN_HEIGHT
cmp word [bp+18], SCREEN_HEIGHT
jge EndVLine
; y1 <= y2
mov ax, word [bp+18]
cmp word [bp+16], ax
jg EndVLine
; 0 <= y1
cmp word [bp+16], 0
jl EndVLine
; 0 <= x < SCREEN_WIDTH
cmp word [bp+14], 0
jl EndVLine
cmp word [bp+14], SCREEN_WIDTH
jge EndVLine
; line starts at offset 320*y1 + x
mov ax, SCREEN_WIDTH
imul word [bp+16]
add ax, word [bp+14]
mov bx, ax
; set the next y2 - y1 + 1 pixels
mov cx, word [bp+18]
sub cx, word [bp+16]
inc cx
mov al, [Color]
VLineLoop:
mov [es:bx], al
add bx, SCREEN_WIDTH ; step between raster lines (320 bytes apart)
loop VLineLoop
EndVLine:
pop bp
pop di
pop dx
pop cx
pop bx
pop ax
ret 6