|
Table of Content | Chapter Nineteen
(Part 8) |
| CHAPTER NINETEEN: PROCESSES, COROUTINES AND CONCURRENCY (Part 7) |
| 19.3.1 - AMAZE.ASM |
The best way to learn how to use coroutines is via example. The following program is an interesting piece of code that generates mazes on the PC's display. The maze generation algorithm has one major constraint - there must be no more than one correct solution to the maze (it is possible for there to be no solution). The main program creates a set of background processes called "demons" (actually, daemon is the correct term, but demon sounds more appropriate here). Each demon begins carving out a portion of the maze subject to the main constraint. Each demon gets to dig one cell from the maze and then it passes control to another demon. As it turns out, demons can "dig themselves into a corner" and die (demons live only to dig). When this happens, the demon removes itself from the list of active demons. When all demons die off, the maze is (in theory) complete. Since the demons die off fairly regularly, there must be some mechanism to create new demons. Therefore, this program randomly spawns new demons who start digging their own tunnels perpendicular to their parents. This helps ensure that there is a sufficient supply of demons to dig out the entire maze; the demons all die off only when there are no, or few, cells remaining to dig in the maze.
; AMAZE.ASM
;
; A maze generation/solution program.
;
; This program generates an 80x25 maze and directly draws the maze on the
; video display. It demonstrates the use of coroutines within a program.
.xlist
include stdlib.a
includelib stdlib.lib
.list
byp textequ <byte ptr>
dseg segment para public 'data'
; Constants:
;
; Define the "ToScreen" symbol (to any value) if the maze is 80x25 and you
; want to display it on the video screen.
ToScreen equ 0
; Maximum X and Y coordinates for the maze (matching the display).
MaxXCoord equ 80
MaxYCoord equ 25
; Useful X,Y constants:
WordsPerRow = MaxXCoord+2
BytesPerRow = WordsPerRow*2
StartX equ 1 ;Starting X coordinate for maze
StartY equ 3 ;Starting Y coordinate for maze
EndX equ MaxXCoord ;Ending X coordinate for maze
EndY equ MaxYCoord-1 ;Ending Y coordinate for maze
EndLoc = ( (EndY-1)*MaxXCoord + EndX-1)*2
StartLoc = ( (StartY-1)*MaxXCoord + StartX-1)*2
; Special 16-bit PC character codes for the screen for symbols drawn during
; maze generation. See the chapter on the video display for details.
ifdef mono ;Mono display adapter.
WallChar equ 7dbh ;Solid block character
NoWallChar equ 720h ;space
VisitChar equ 72eh ;Period
PathChar equ 72ah ;Asterisk
else ;Color display adapter.
WallChar equ 1dbh ;Solid block character
NoWallChar equ 0edbh ;space
VisitChar equ 0bdbh ;Period
PathChar equ 4e2ah ;Asterisk
endif
; The following are the constants that may appear in the Maze array:
Wall = 0
NoWall = 1
Visited = 2
; The following are the directions the demons can go in the maze
North = 0
South = 1
East = 2
West = 3
; Some important variables:
; The Maze array must contain an extra row and column around the
; outside edges for our algorithm to work properly e.
pop bp
ret 4
; See if moving to this spot was an illegal move. There will be
; a NoWall value at this cell in the maze if the move is legal.
NotSolved: mov dl, sm_X
mov dh, sm_Y
MazeAdrs
mov bx, ax
cmp Maze[bx], NoWall
je MoveOK
mov ax, 0 ;Return failure
pop bp
ret 4
; Well, it is possible to move to this point, so place an appropriate
; value on the screen and keep searching for the solution.
MoveOK: mov Maze[bx], Visited
ifdef ToScreen
push es ;Write a "VisitChar"
ScrnAdrs ; character to the
mov bx, ax ; screen at this X,Y
mov ax, ScreenSeg ; position.
mov es, ax
mov word ptr es:[bx], VisitChar
pop es
endif
; Recusively call SolveMaze until we get a solution. Just call SolveMaze
; for the four possible directions (up, down, left, right) we could go.
; Since we've left "Visited" values in the Maze, we will not accidentally
; search back through the path we've already travelled. Furthermore, if
; we cannot go in one of the four directions, SolveMaze will catch this
; immediately upon entry (see the code at the start of this routine).
mov ax, sm_X ;Try the path at location
dec ax ; (X-1, Y)
push ax
push sm_Y
call SolveMaze
test ax, ax ;Solution?
jne Solved
push sm_X ;Try the path at location
mov ax, sm_Y ; (X, Y-1)
dec ax
push ax
call SolveMaze
test ax, ax ;Solution?
jne Solved
mov ax, sm_X ;Try the path at location
inc ax ; (X+1, Y)
push ax
push sm_Y
call SolveMaze
test ax, ax ;Solution?
jne Solved
push sm_X ;Try the path at location
mov ax, sm_Y ; (X, Y+1)
inc ax
push ax
call SolveMaze
test ax, ax ;Solution?
jne Solved
pop bp
ret 4
Solved:
ifdef ToScreen ;Draw return path.
push es
mov dl, sm_X
mov dh, sm_Y
ScrnAdrs
mov bx, ax
mov ax, ScreenSeg
mov es, ax
mov word ptr es:[bx], PathChar
pop es
mov ax, 1 ;Return true
endif
pop bp
ret 4
SolveMaze endp
; Here's the main program that drives the whole thing:
Main proc
mov ax, dseg
mov ds, ax
mov es, ax
meminit
call Init ;Initialize maze stuff.
lesi MainPCB ;Initialize coroutine
coinit ; package.
; Create the first demon.
; Set up the stack pointer for this guy:
mov cx, 256
malloc
add di, 248
mov DemonList.regsp, di
mov DemonList.regss, es
; Set up the execution address for this guy:
mov DemonList.regcs, cs
mov DemonList.regip, offset Dig
; Initial coordinates and direction for this guy:
mov cx, East ;Start off going east.
mov dh, StartY
mov dl, StartX
mov DemonList.regcx, cx
mov DemonList.regdx, dx
; Set up other misc junk:
mov DemonList.regds, seg dseg
sti
pushf
pop DemonList.regflags
mov byp DemonList.NextProc, 1 ;Demon is "active".
inc DemonCnt
mov DemonIndex, 0
; Set up the Timer demon:
mov DemonList.regsp+(size pcb), offset EndTimerStk
mov DemonList.regss+(size pcb), ss
; Set up the execution address for this guy:
mov DemonList.regcs+(size pcb), cs
mov DemonList.regip+(size pcb), offset TimerDemon
; Set up other misc junk:
mov DemonList.regds+(size pcb), seg dseg
sti
pushf
pop DemonList.regflags+(size pcb)
mov byp DemonList.NextProc+(size pcb), 1
inc DemonCnt
; Start the ball rolling.
mov ax, ds
mov es, ax
lea di, DemonList
cocall
; Wait for the user to press a key before solving the maze:
getc
mov ax, StartX
push ax
mov ax, StartY
push ax
call SolveMaze
; Wait for another keystroke before quitting:
getc
mov ax, 3 ;Clear screen and reset video mode.
int 10h
Quit: ExitPgm ;DOS macro to quit program.
Main endp
cseg ends
sseg segment para stack 'stack'
; Stack for the timer demon we create (we'll allocate the other
; stacks dynamically).
TimerStk byte 256 dup (?)
EndTimerStk word ?
; Main program's stack:
stk byte 512 dup (?)
sseg ends
zzzzzzseg segment para public 'zzzzzz'
LastBytes db 16 dup (?)
zzzzzzseg ends
end Main
|
Table of Content | Chapter Nineteen (Part 8) |
Chapter Nineteen: Processes,
Coroutines and Concurrency (Part 7)
29 SEP 1996