Computer Engineering II
  

Schedule    Lab schedule
Homework    Lab Manual
Machine Problems    Resources
Final Project    Photos
Gradebook    Feedback
Syllabus    Archives
Lectures    Download NASM
Home    Restricted access

Machine Problem 3: Forest Racer

Assigned Monday, February 28, 2006
Due Date Wednesday, March 15, 2006
Purpose To understand interrupt-driven I/O, control of the video display, arrays, and queues.
Points 70

In this MP, you will write a program that implements a vertically scrolling driving game where the object is to navigate your car along the various pathways towards the finish line.

Reading: Lab Notes -- Libraries (Chapter 9), Arrays (Section 7.1),Queues (Section 7.2), Keyboard (Section 10.1), Graphics (Chapter 11).


(03/06/2006) The ScrollMap subroutine mistakenly listed Status as an output. This has been corrected.
(03/06/2006) Important: It has been brought to our attention that occasionally mp3 will give an error before starting and fail to load. This occurs fairly infrequently, however before trying to figure out what it wrong with your MP if this happens, do a make clean and re-make your MP. This seems to solve the problem 99% of the time. Clearing the screen (cls in the command window) before running the MP (after re-making) also seems to help.
(03/06/2006) A new version of the library is available which slows the game down a little. Use this if you want the map and car to move slower. libmp3-slower.zip (Note: We will use this to test your program)

*(03/01/2006) New Map Added! channels.txt. Excel Sheet: channels.xls (Right click and select save as)
*(03/06/2006) New Map Added! short.txt. Excel Sheet: short.xls (Right click and select save as). Use this one for debugging to avoid the "cannot load into memory" error in TD.

Screen Dump

Below is a screenshot of an example run of the program. You can see the number of lives left in the upper right corner, represented by red hearts, as well as the car and various types of terrain.



Program Specification

The car drives along the map following the path represented by green squares. If the car crashes into a tree or water a life is lost and the game is paused until the player respawns. At this point, the car is placed back on the correct path towards the goal. The game responds to the following key presses:

  • Left Arrow: Move the car left by one square
  • Right Arrow: Move the car right by one square
  • P: Pause the game
  • Spacebar: Respawn the car on the correct path
  • Escape: Exit the game
When an arrow key is release, the car should stop moving until another arrow key is pressed. The car may only respawn if the player has crashed into an obstacle, and if the player has more than 0 lives left.

The Game Map

The entire map for the game is contained in an array called GameMap whose size can be up to a 255x80 (It will always have 80 columns). The number of rows in the array is specified by the MapLength variable. Each row in GameMap represents one row on the display. Each value in GameMap is one word in length, and contains the color and character specification (see Section 11.1.2 of the lab manual) for a given pixel on the screen. There are four defined values:

SPAWNBLOCK 6ADBh This block marks the respawn position for the row. There will be exactly one SPAWNBLOCK per row in GameMap
WATER 69B0h Block represents a water square
TREE 6A05H Block represents a tree
GROUND 6AB0h Block represents a driveable part of the map
FINISH 6ADFh Block represents the finish line

The five constants in bold are defined by EQU's at the top of mp3asm so you can and should use these mnemonics rather than the hex values.

Program Organization

The program uses 80x25 text mode for the display.

The map is drawn on rows 1-23 (zero-indexed) and the 0th and 24th rows should be black lines.

The program has the following global variables:

SavKOff     RESW    1                       ; Keyboard interrupt vector
SavKSeg     RESW    1
SavTOff     RESW    1                       ; Timer interrupt vector
SavTSeg     RESW    1

CarState    DB      0                       ; Current state of the car-- 1 = Move Left, -1 = Move Right,
                                            ;  0 = Stopped, -2 = Dead
CarCol      DB      40                      ; Which column your car is in

CarTicks    DB      0                       ; Used to determine frequency of car movement
Scroll      DB      0                       ; Used to determine frequency of scrolling
CurrentRow  DB      24                      ; Current row in GameMap
Status      DB      1                       ; Current game status--0 = Paused, 1 = Playing
                                            ; 2 = Won, -1 = Lost
Lives       DB      3                       ; # of lives left

WinString   DB      'You Win!!!','$'        ;If you win
LoseString  DB      'You Lose :(','$'       ;If you lose
			
  • SavKOff and SavKSeg are the offset and segment words in the original keyboard interrupt vector (type 9). Similarly SavTOff and SavTSeg are the offset and segment words in the original user timer interrupt vector (type 1Ch).
  • CarState specifies the current status of your car, and can be one of the following four values: 1 = Move Left, +1 = Move Right, 0 = Stopped (not moving left or right), -2 = Dead.
  • Status specifies the current game status and can be one of the following 4 values: 0 = Paused, 1 = Playing, 2 = Won (reached the finish line), -1 = Lost (all 3 lives gone)
  • CarTicks counts the number of ticks since the car last moved. When enough ticks have elapsed, the car is moved in the direction specified by CarState. Similarly, Scroll counts the number of ticks since the map last scrolled down. When enough ticks have elapsed, the map is scrolled down by one row.
  • CarCol keeps track of which column in the display the car is on. The car is always on the bottom (23rd) row.
  • CurrentRow keeps track of which row in the GameMap array is going to be at the top (row 1) of the screen when ScrollMap is called. When the game starts this is set to 24, meaning the next row to draw when the map scrolls down is row 24 from GameMap
  • Lives indicates how many lives the player has left. A life is lost when the car hits a tree or water.

The program uses a circular queues to store keyboard scan codes.
QBeg        EQU     0                       ; Beginning offset of queue area
QCap        EQU     2                       ; Capacity of of queue
QFront      EQU     4                       ; Index of front item
QRear       EQU     6                       ; Index of next place to put rear item
QData       EQU     8                       ; BYTE to input or output

KQList      RESB    0                       ; Parameter list for Keyboard Queue
KQBeg       DW      KQArea                  ; Offset of Keyboard Queue area
KQCap       DW      10
KQFront     DW      0
KQRear      DW      0
KQData      RESB    1
KQArea      RESB    10
			

The queue is specified by a parameter list. The first word of the list specifies the offset of the first byte of the queue area, where data are stored. The second word is the capacity of the queue c. The third word of the parameter list is the index of the byte at the front of the queue, and the fourth word is the index of the byte that will be new rear of the queue. These indices run from 0 to the c-1. The byte at the front of the queue is the next to be dequeued. If the front and rear indices are equal, then the queue is empty. If the the rear index is either one less than the front index, or one less than the front index plus c, then the queue is full. (So the maximum number of bytes that can be stored in the queue is actually c-1.) Enq uses the Data byte on the parameter list for the item to be enqueued, and Deq delivers the dequeued item into this byte.

Below is a detailed description of all the functions you are responsible for writing. Document each subroutine with a header. In the Lab Notes, standards for headers appear on page 4, and examples of subroutines on pages 28-33 and 84-88.

You may use libDrawGameEnd WITHOUT PENALTY

Init
  • Initializes interrupt vectors and the video display. Draws the starting portion of the map.
  • Inputs: None
  • Outputs:
    • SavTOff, SavTSeg, SavKOff, SavKSeg
  • Calls: DrawStatus
  • Init saves the old user timer and Keyboard interrupt vectors and changes them to point to the interrupt service routines TimerInt and KbdInt (TimerInt is provided, you will need to write KbdInt). Init uses INT 10h to select 80x25 text video mode and to clear the display area (with black at every location). Init draws the first 23 rows of GameMap to the screen. Init draws the number of lives in the upper right corner of the screen (right aligned), and hides the cursor. Init sets Scroll and CarTicks to 0.
KbdInt
  • Interrupt service routine for keyboard
  • Inputs: None
  • Outputs: None
  • Calls: Enq
  • KbdInt enqueues the scan code from the keyboard onto the Keyboard Queue with a call to Enq. Most of this routine is specified on page 93 of the ECE 390 Lab Notes.
ReadKeyboard
  • Processes one scan code byte
  • Inputs: None
  • Outputs:
    • CarState, Status
  • Calls: FindRespawn, Deq, mp3xit
  • ReadKeyboard first checks that the Keyboard Queue is nonempty. If so, then ReadKeyboard dequeues the byte at the front of the Keyboard Queue.
  • If the byte is the scan code for Spacebar, CarState = DEAD, and Lives > 0, ReadKeyboard finds the respawn point for the row the car died on and updates CarCol, CarState, and Status.
  • If the byte is the scan code for the ESC key, then ReadKeyboard restores the old interrupt vectors and exits to DOS.
  • If the byte is the scan code for the Left or Right Arrow key or a release of the arrow keys, then ReadKeyboard sets CarState accordingly.
  • If the byte is the scan code for the P key, then ReadKeyboard toggles the pause state of the game (in Status).
UpdateMoves
  • Updates the movement of your car on the screen
  • Inputs:
    • Status, CarState, CarTicks, CarCol, CurrentRow
  • Outputs:
    • CarCol, Status
  • Calls: DrawGameEnd
  • UpdateMoves exits if either the game is currently paused or CarTicks indicates that fewer than 2 (the constant CARSPEED) ticks have elapsed. Otherwise, UpdateMoves sets CarTicks = 0, then determines the new column for the car (CarCol).
  • After determining the new value of CarCol, UpdateMoves should draw the car in its new column and replace the old square with its value from GameMap.
  • If the square the car has just moved to is the finish line (the constant FINISH), UpdateMoves should change Status and call DrawGameEnd appropriately.
ScrollMap
  • Scrolls the map vertically down one line
  • Inputs:
    • Status, Scroll, CarCol, CurrentRow
  • Outputs:
    • Scroll, CurrentRow
  • Calls: None
  • ScrollMap exits if either the game is currently paused or Scroll indicates that fewer than 10 (the constant GAMESPEED) ticks have elapsed. Otherwise, ScrollMap sets Scroll = 0, and scrolls the map vertically down by 1 row.
  • When the value of a block is found to be 6ADBh (constant SPAWNBLOCK) it should be drawn as 6AB0h (constant GROUND). The value 6ADBh is a special value used in the FindRespawn function but it should never be drawn to the screen.
  • Note: "Scrolling the map vertically down by 1 row" means that if the screen previously displayed rows 10-33 of GameMap, after this function returns the screen should display rows 11-34 of GameMap.
CollisionDetect
  • Determine if the car has collided with an object, and if so perform the appropriate actions.
  • Inputs:
    • Status, CarCol, CurrentRow
  • Outputs:
    • Status, Lives, CarState
  • Calls: DrawStatus, DrawGameEnd
  • CollisionDetect exits if the game is currently paused. Otherwise, CollisionDetect checks to see if the current square occupied by the car contains an obstacle (TREE or WATER constants).
  • If a collision occurred, Lives should be decremented and Status and CarState should be modified appropriately.
  • CollisionDetect should CALL DrawStatus to update the top status bar (displaying lives) if a collision was detected.
  • If a collision is detected and Lives becomes 0, Status should be updated appropriately and DrawGameEnd should be CALLed to inform the player that the game has ended.
DrawStatus
  • Draw the game status bar to the top row of the display
  • Inputs:
    • Lives
  • Outputs: None
  • Calls: None
  • DrawStatus draws red hearts (constants HEART and RED) equal to the number of Lives to the upper right corner of the screen, right justified.
  • Hint: Erase the last MAXLIVES columns from the top row with black boxes (constants BLOCK and BLACK) before drawing the correct number of hearts. Why? Because if you don't do this, when you lose a life it will still show up on the screen unless you erase it.
FindRespawn
  • Scans a row in GameMap to find the respawn column
  • Inputs:
    • GameMap, CurrentRow
  • Outputs:
    • (DL) - The column containing the respawn block
  • Calls: NONE
  • FindRespawn scans the row in GameMap which your car crashed on for the block whose value is 6ADBh (constant SPAWNBLOCK). It then returns this column number in DL.
  • It is guaranteed that there will be exactly one SPAWNBLOCK per row.
Enq
  • Enqueues a byte on a circular queue
  • Inputs:
    • (BX): offset of queue list structure
    • [BX+QData]: byte to enqueue
  • Outputs: None
  • Calls: None
  • If the queue is not full, then Enq enqueues the byte at [BX+QData] onto the rear of the circular queue. Then Enq updates [BX+QRear].
Deq
  • Dequeues a byte on a circular queue
  • Inputs:
    • (BX): offset of queue list structure
  • Outputs:
    • [BX+QData]: byte dequeued
  • Calls: None
  • If the queue is not empty, then Deq dequeues the byte from the front of the queue, places the byte in [BX+QData], and updates [BX+QFront].

Friendly Advice

  • The libmp3.lib file contains executable library subroutines for each of the routines that you need to implement. The library subroutines enable you to run the program and understand how it works before you implement it. You can test your program with any combination of your own code and library subroutines. You will receive credit only for the subroutines that you implement yourself.
  • You may define new memory variables as needed. Variables associated with a subroutine may be declared between the header and the first instruction of the subroutine.
  • Each subroutine should save and restore any registers that it uses, except for registers that deliver subroutine outputs. The caller may use registers without outputs and expect them to remain unchanged.
  • Be careful when transmitting subroutine outputs via registers. For example, if you use POPA at the end of a subroutine, avoid overwriting a register that delivers an output.
  • You may design and implement additional subroutines. For example, you could write a subroutine that places a character on the screen by writing to the video memory directly (see page 110 in the Lab Notes). You could write a subroutine that reads from the video memory. Be sure to modify the Calls lines in the headers of the subroutines that call these additional subroutines.
  • Remember that the QData is shared among several subroutines including an interrupt. What does this mean?
  • Monitor the course WebBoard for clarifications and help.
  • START EARLY!

Files for MP3 are on the V: drive in the directory V:\ece390\mp3. In this directory are the program framework mp2.asm and a running version of the program mp2lab.exe. Lab versions of subroutines are in libmp3.lib, which contains all subroutines of LIB291 plus versions of the MP3 subroutines (libMain for Main, etc). You will use mp3xit instead of dosxit. You should start by copying these files to your home directory with the following command:
xcopy /s V:\ece390\mp3 W:\mp3
You may download the files from the server as mp3.zip

Demonstration, Documentation, and Grading

Demonstrate your program to an ECE 390 staff member.

As in MP2, keep an MP development log and write a cover memo, which should address the following questions:

  • How much time did you spend on each subroutine, from planning and design through final debugging?
  • What kinds of defects (bugs) did you find during the development of the program? When did you discover these defects (during code review or during testing)? How did you find them?
  • What did you learn about design, coding, testing, and debugging in this MP?
  • What went well in your work on this MP? What did not?
  • What you would do differently for the next MP?

After the demonstration, submit your program and cover memo. Your program will be graded according to the clarity of your design and the quality of your documentation.

Gradesheet:

Function Points
Init  11
KbdInt  2
ReadKeyboard  9
UpdateMoves  10
ScrollMap  10
CollisionDetect  8
DrawStatus  4
FindRespawn  3
Enq  3
Deq  3
Documentation  3
Cover Memo  4

mp3.asm (program framework)

  1  ; MP3 - Forest Racer
  2  ; This program implements a vertically scrolling driving game
  3  ;
  4  ; Your name
  5  ; Date
  6
  7         BITS    16
  8
  9  ;====== SECTION 1: Define constants =======================================
 10
 11         KVEC        EQU     0024h           ; Location of Keyboard Vector
 12         TVEC        EQU     0070h           ; Location of User Timer Vector
 13         ESCSCAN     EQU     1               ; Scan code for ESC key
 14         SPSCAN      EQU     57              ; Scan code for spacebar
 15         LEFT        EQU     75              ; Scan code for left arrow
 16         LEFT_UP     EQU     0CBh            ; Scan code for left arrow release
 17         RIGHT       EQU     77              ; Scan code for right arrow
 18         RIGHT_UP    EQU     0CDh            ; Scan code for right arrow up
 19
 20         CHAR_P      EQU     25              ; Scan code for 'P'
 21         ROWS        EQU     25              ; Number of rows in entire display
 22         COLS        EQU     80              ; Number of columns in entire display
 23
 24         MAXLIVES    EQU     3
 25         GAMESPEED   EQU     02              ; Number of ticks between scrolling
 26         CARSPEED    EQU     1               ; Speed of the car
 27         WON         EQU     2               ; The game has been won
 28         LOST        EQU     -1              ; The game has been lost
 29         PLAYING     EQU     1               ; The game is still being played
 30         PAUSED      EQU     0               ; The game is paused
 31         DEAD        EQU     -2              ; The car hit something
 32
 33         NULL        EQU     0
 34         SPAWNBLOCK  EQU     6ADBh           ; The spawn block
 35         FINISH      EQU     0DFh            ; The finish Line
 36         WATER       EQU     69B0h           ; Water
 37         TREE        EQU     6A05h           ; A tree
 38         GROUND      EQU     6AB0h          ; The light block shape extended ASCII
 39         BLOCK       EQU     0DBh            ; The block shape extended ASCII
 40         HEART       EQU     03h             ; The heart shape
 41         CAR         EQU     06h             ; Your Car (Spade)
 42         BROWN       EQU     06h             ; Color attribute for brown
 43         WHITE       EQU     0Fh             ; Color attribute for white
 44         BLACK       EQU     00h             ; Color attribute for black
 45         RED         EQU     0Ch             ; Color attribute for red
 46
 47  ;====== SECTION 2: Declare external procedures ============================
 48
 49  EXTERN  ascbin, binasc, kbdin, dspout, dspmsg, dosxit
 50
 51  GLOBAL SavKOff, SavKSeg, SavTOff, SavTSeg
 52  GLOBAL KQList, KQBeg, KQCap, KQFront, KQRear, KQData, KQArea
 53  GLOBAL CarState, CarCol, CarTicks, Scroll, CurrentRow, Status, Lives
 54  GLOBAL WinString, LoseString, MapLength, GameMap
 55  GLOBAL Init, KbdInt, ReadKeyboard, UpdateMoves
 56  GLOBAL ScrollMap, CollisionDetect, DrawStatus, DrawGameEnd
 57  GLOBAL FindRespawn, Enq, Deq
 58
 59
 60  EXTERN libInit, libKbdInt, libReadKeyboard, libUpdateMoves
 61  EXTERN libScrollMap, libCollisionDetect, libDrawStatus, libDrawGameEnd
 62  EXTERN libFindRespawn, libEnq, libDeq
 63  EXTERN TimerInt, mp3xit
 64
 65  ;====== SECTION 4: Define stack segment ===================================
 66
 67  SEGMENT stkseg STACK                    ; *** STACK SEGMENT ***
 68         RESB    64*8
 69  stacktop:
 70         RESB    0                       ; NASM bug workaround
 71
 72  ;====== SECTION 5: Define code segment ====================================
 73
 74  SEGMENT code                            ; *** CODE SEGMENT ***
 75
 76  ;====== SECTION 6: Declare variables for main procedure ===================
 77  SavKOff     RESW    1                       ; Keyboard interrupt vector
 78  SavKSeg     RESW    1
 79  SavTOff     RESW    1                       ; Timer interrupt vector
 80  SavTSeg     RESW    1
 81
 82  QBeg        EQU     0                       ; Beginning offset of queue area
 83  QCap        EQU     2                       ; Capacity of of queue
 84  QFront      EQU     4                       ; Index of front item
 85  QRear       EQU     6                       ; Index of next place to put rear item
 86  QData       EQU     8                       ; BYTE to input or output
 87
 88  KQList      RESB    0                       ; Parameter list for Keyboard Queue
 89  KQBeg       DW      KQArea                  ; Offset of Keyboard Queue area
 90  KQCap       DW      10
 91  KQFront     DW      0
 92  KQRear      DW      0
 93  KQData      RESB    1
 94  KQArea      RESB    10
 95
 96  CarState    DB      0                       ; Current state of the car-- 1 = Move Left, -1 = Move Right,
 97                                              ;  0 = Stopped, -2 = Dead
 98  CarCol      DB      40                      ; Which column your car is in
 99
100  CarTicks    DB      0                       ; Used to determine frequency of car movement
101  Scroll      DB      0                       ; Used to determine frequency of scrolling
102  CurrentRow  DB      24                      ; Current row in GameMap
103  Status      DB      1                       ; Current game status--0 = Paused, 1 = Playing
104                                              ; 2 = Won, -1 = Lost
105  Lives       DB      3                       ; # of lives left
106
107  WinString   DB      'You Win!!!','$'        ;If you win
108  LoseString  DB      'You Lose :(','$'       ;If you lose
109
110  ;The game map. An XXx80 array of words, each specifying a specific part
111  ;of the map. First byte is the attributes, 2nd is the character.
112  %include "gamemap.txt"
113
114
115  ; You may declare additional variables here
116
117  ;====== SECTION 7: Program initialization =================================
118
119  ..start:
120         MOV     AX, CS                  ; Initialize Data Segment register
121         MOV     DS, AX
122         MOV     AX, stkseg              ; Initialize Stack Segment register
123         MOV     SS, AX
124         MOV     SP, stacktop            ; Initialize Stack Pointer register
125
126  ;====== SECTION 8: Main procedure =========================================
127
128  main:
129         MOV      AH, 2Ch                 ; Get current time of day
130         INT      21h                     ;   (DH) = secs, (DL) = hundredths of secs
131         CALL     Init                    ; Initialization
132  .mainLoop:
133         CALL     ReadKeyboard
134         CALL     UpdateMoves
135         CALL     ScrollMap
136         CALL     CollisionDetect
137         JMP      .mainLoop
138
139
140  ;=============== Your Subroutines =========================================
141
142  KbdInt:
143         JMP     libKbdInt
144         IRET
145
146  Init:
147         CALL     libInit
148         RET
149
150  ReadKeyboard:
151         CALL     libReadKeyboard
152         RET
153
154  UpdateMoves:
155         CALL     libUpdateMoves
156         RET
157
158  ScrollMap:
159         CALL     libScrollMap
160         RET
161
162  CollisionDetect:
163          CALL    libCollisionDetect
164          RET
165
166  DrawStatus:
167         CALL     libDrawStatus
168         RET
169
170  DrawGameEnd:
171          CALL    libDrawGameEnd
172          RET
173
174  FindRespawn:
175          CALL    libFindRespawn
176          RET
177
178  Enq:
179         CALL     libEnq
180         RET
181
182  Deq:
183         CALL     libDeq
184         RET

Makefile

 1  MPNAME=mp3
 2
 3  all: $(MPNAME).exe
 4
 5  clean: 
 6      rm -f $(MPNAME).exe $(MPNAME).obj $(MPNAME).lst $(MPNAME).map
 7
 8  %.exe: %.obj
 9      tlink /c /v $<, $*.exe, $*.map, lib291.lib libmp3.lib
10
11  %.obj: %.asm
12      nasm -g -f obj -o $*.obj $< -l $*.lst
13
Return to ECE390 Home Page Spring 2006