ECE 291
Computer Engineering II
Lockwood, Spring 1998
 

Machine Problem 3: The "15-Puzzle"

 
Assigned:
Tuesday, February 24, 1998
Due Date:
Tuesday, March 10, 1998
Purpose:
Text-mode video, Mouse control, Stack usage, GUI programming
Points:
50
 
Figure 1. The ECE 291 15-Puzzle
 

About this Machine Problem

In the 1870's a man by the name of Samuel Lloyd shocked and amazed a simple-minded world with an equally simple-minded puzzle.  His puzzle consisted of a wooden box in which there were 15 numbered blocks.  The blocks were placed at random into the box, and the goal was to shift the blocks around until the blocks lined up sequentially with the '1' block in the upper left hand corner of the board.  This puzzle has become what is known as the "15-Puzzle."  After taking the world by storm a mere century ago, the classic 15-Puzzle now lives out its mundane life as a cheap corporate giveaway at the Engineering EXPO.  In this machine problem, we will attempt to restore the 15-Puzzle to its former glory.

How the Puzzle Works

The operation of the puzzel is best explained using the concepts your learned in ECE340 (yes, really, the semiconductor class). Fundamentally, Our 15-puzzle works not by moving numbered tiles around, but by moving the hole around.  When the user clicks on the 15 tile (Figure 2), the tile slides to the right to fill in the gap, right?  Wrong.  Think of this as the hole sliding over to the left to fill in the space previously occupied by the 15 tile. 

Figure 2.
 
By thinking of the problem as "moving the hole around" instead of "moving the tiles around," we simplify the coding experience.  The core of this functionality is the MoveHole subroutine.  This routine accepts a direction as input, and attempts to move the hole in the specified direction.  If the direction is invalid, MoveHole returns an error code.  An example of an invalid move would be attempting to move the hole down on the puzzle in figure 2.

Although we know that it is the hole that is moving, the user sees the tile sliding into the place previously occupied by the hole.  MoveHole accomplishes this by repeated calls to the DrawTile subroutine.  DrawTile simply draws the specified tile at the specified location.  The "animated" effect is achieved by repeatedly drawing the tile, each time moving it over by one character until it reaches its destination. Calling the Delay subroutine each time the tile is drawn slows the animation down.  Delay simply burns clock cycles so that the animation appears at human-detectable speed.  Without some sort of delay, the animation would be instantaneous to the human eye.

The state of the game board is stored in the Board variable.  This is simply an array of bytes indicating which tiles are in each of the 16 possible positions (numbered 0...15).  The hole is represented as tile 0.  The variable HolePtr always contains the position of the hole.  In addition, the Moves variable always contains the number of moves the user has made. 

The mouse control is implemented in the MouseControl subroutine.  Using the mouse as an input device is straightforward, and the details are contained in the lab manual.  MouseControl returns a value indicating which tile or button was clicked.  The Main routine (given) takes this value and decides what action to take.  If a tile is clicked, Main calls the CheckMove routine, which checks if the click was valid, and if so, calls MoveHole to effect the desired move.  Note that the user should be able to click on any tile in line with the hole.  In figure 2 (farthest to right), clicking on the 3 tile should cause the 11, 7, and 3 tiles to move downward.  The full details of these subroutines are given below.

The Autosolve "Algorithm"

When complete, your machine problem will amaze your non-engineering friends with its ability to automagically solve itself. Of course, we engineers know that there is no magic involved.  Rather, this puzzle will use the stack to perform the amazing autosolve feat. Whenever a move is made, the opposite move is pushed onto the stack.  The number of moves currently residing on the stack is kept track of using the MovesPushed variable.

To solve the puzzle, the AutoSolve subroutine simply pops the moves off of the stack and calls MoveHole. The Shuffle routine resets the puzzle and then moves the hole in random directions.  The number of times the hole is moved is specified by ShuffAmount.  Your Shuffle routine should take care not to move the hole in one direction and then follow that by moving the hole right back to where it started again.  That sort of behavior is not very impressive and it is annoying to watch. 

The randomness is achieved by generating random numbers with the Random routine.  The method for generating random numbers is outlined below.  Each time Shuffle moves the hole, the opposite move is pushed onto the stack for later retrieval by the AutoSolve routine.  In this manner, the puzzle can solve itself after being shuffled and then played with.  Be careful when using the stack to store moves.  You will be using the same stack that the CPU uses to store return addresses for subroutine calls.  Review your lecture notes on stack usage.

A Few Other Things

Play With It

The best way to get the feel for how the game should operate is to play with the library version of the game.  Your version must implement all the features of the library version.  If you have a question as to how your program should behave, look at the library code and have your version do what it does.  You may use some degree of artistic license in your version, but be aware that if you change the locations of buttons or tiles, your code will not interoperate properly with the library code.  The best strategy is to place all of your screen elements in the same places as the library code.

Efficiency

Many of the operations of this Machine Problem can be implemented most efficiently with loops, lookup tables, and bit-tricks.  In this case, efficient = simple. Many of those same operations can be very tedious and time-consuming to program if you do not choose efficient ways to implement them.  We will not explicitly test the speed of your code for this MP, but the benefit derived from efficient coding is that you will have many more free-time hours to pursue leisure activities such as playing with the 15-puzzle that some company gave out free at the last EXPO.

80x50 Text Mode Video

You will be using text-mode video graphics to display the game on the screen.  You will be using direct video memory writes as described in the lecture notes.  Using 80x50 text mode is a simple extension of what you know about 80x25 text mode.  You simply double the size of the memory page to which you will write data.

Data Structures

Procedures

Scoring

Points are earned by replacing the library code with your own code. The point values for each of the subroutines is listed below:
 

 Preliminary Procedure

Final Steps

  1. Print a copy of the MP3 grading sheet.
  2. Demonstrate MP3.EXE to a TA or to the instructor.
  3. Handin in your program by running:

  4. A:\Handin YourWindowsLogin
  5. Print MP3.ASM

  6. Staple the MP3 grading sheet to the front of your MP3.ASM file and give both to the same TA that approved your demonstration.

MP3.ASM (program framework)

Study the given program framework carefully.  There are many hints in the given source code.

PAGE 75, 132 TITLE MP3:15-Puzzle Your_Name_Here Date COMMENT * The 15-Puzzle ECE291: Machine Problem 3 Spring 1998 / Lockwood Guest MP Author: Daniel Restelli University of Illinois, Dept. of Electrical & Computer Engineering Ver 1.0 * ; =================== External Library Procedures ======================= ; LIB291 Routines -- Use these for free EXTRN DOSXIT:NEAR EXTRN RSAVE:NEAR EXTRN RREST:NEAR EXTRN KBDIN:NEAR EXTRN BINASC:NEAR EXTRN MP3XIT:NEAR ; LIBMP3 routines -- Comment out and replace with your own code! EXTRN MouseControl:NEAR EXTRN ResetBoard:NEAR EXTRN Shuffle:NEAR EXTRN MoveHole:NEAR EXTRN DrawBoard:NEAR EXTRN UpdateScreen:NEAR EXTRN Delay:NEAR EXTRN Random:NEAR EXTRN AutoSolve:NEAR EXTRN DrawTile:NEAR EXTRN CheckMove:NEAR ; ============================ Stack Segment ============================ stkseg segment stack db 128 dup ('STACK ') stkseg ends ; ============================ Program Data ============================= CSEG segment public 'CODE' assume cs:CSEG, ds:CSEG, ss:stkseg, es:nothing ; ============================= Variables =============================== TEXTVIDSEG EQU 0b800h ; == Use these constants in your code, if you change them, ; == your code will not work properly with the library code BOARDSTART EQU 980 ; Starting offset of the playing board TILEWIDTH EQU 10 ; Width of tiles TILEHEIGHT EQU 9 ; Height of tiles BOARDDIM EQU 4 ; Dimension of board (4x4) SHUFFAMOUNT EQU 200 ; When shuffling board, how many moves to make ButtonWidth EQU 16 ; The width of the control panel buttons MAXDELAY EQU 20 MINDELAY EQU 1 ; == These are the values corresponding to the different buttons TileClicked EQU 0 ; A tile was clicked ShuffleClk EQU 1 ; The Shuffle button was clicked SolveClk EQU 2 ; The Solve button was clicked SolveOnceClk EQU 3 ; The Solve Once button was clicked DelayUpClk EQU 4 ; The > Delay button was clicked DelayDownClk EQU 5 ; The < Delay button was clicked QuitClk EQU 6 ; The Quit button was clicked ResetClk EQU 7 ; The Reset button was clicked ; == These are the starting offsets of the different buttons ShuffleLoc EQU 2034 SolveLoc EQU 2514 SolveOnceLoc EQU 2994 DelayUpLoc EQU 3480 DelayDownLoc EQU 3474 QuitLoc EQU 4274 ResetLoc EQU 1554 PUBLIC Board, HolePtr, randval, delayc, Moves, MovesPushed Board db 01,02,03,04 ; This simple array keeps track of the state db 05,06,07,08 ; of the board db 09,10,11,12 db 13,14,15,00 HolePtr dw 15 ; The location of the hole ; == This is a lookup table of the starting offsets of each tile ; == location in the board. BoardLoc dw BOARDSTART+0*160*TILEHEIGHT+0*2*TILEWIDTH dw BOARDSTART+0*160*TILEHEIGHT+1*2*TILEWIDTH dw BOARDSTART+0*160*TILEHEIGHT+2*2*TILEWIDTH dw BOARDSTART+0*160*TILEHEIGHT+3*2*TILEWIDTH dw BOARDSTART+1*160*TILEHEIGHT+0*2*TILEWIDTH dw BOARDSTART+1*160*TILEHEIGHT+1*2*TILEWIDTH dw BOARDSTART+1*160*TILEHEIGHT+2*2*TILEWIDTH dw BOARDSTART+1*160*TILEHEIGHT+3*2*TILEWIDTH dw BOARDSTART+2*160*TILEHEIGHT+0*2*TILEWIDTH dw BOARDSTART+2*160*TILEHEIGHT+1*2*TILEWIDTH dw BOARDSTART+2*160*TILEHEIGHT+2*2*TILEWIDTH dw BOARDSTART+2*160*TILEHEIGHT+3*2*TILEWIDTH dw BOARDSTART+3*160*TILEHEIGHT+0*2*TILEWIDTH dw BOARDSTART+3*160*TILEHEIGHT+1*2*TILEWIDTH dw BOARDSTART+3*160*TILEHEIGHT+2*2*TILEWIDTH dw BOARDSTART+3*160*TILEHEIGHT+3*2*TILEWIDTH ; == How might you use these tables to simplify your code? SlideDir dw -160, 2, 160, -2 SlideLen dw TILEHEIGHT, TILEWIDTH, TILEHEIGHT, TILEWIDTH PointDir dw -4, 1, 4, -1 randval dw 3 ; random # generator seed delayc db 3 ; delay constant Moves dw 0 ; # of moves made MovesPushed dw 0 ; # of moves currently on the stack ; ========================== Your Subroutines ========================== ; Uncomment and add your own code here ;MouseControl PROC NEAR ; ; RET ;MouseControl ENDP ;ResetBoard PROC NEAR ; ; RET ;ResetBoard ENDP ;Shuffle PROC NEAR ; ; RET ;Shuffle ENDP ;MoveHole PROC NEAR ; ; RET ;MoveHole ENDP ;DrawBoard PROC NEAR ; ; RET ;DrawBoard ENDP ;UpdateScreen PROC NEAR ; ; RET ;UpdateScreen ENDP ;Delay PROC NEAR ; ; RET ;Delay ENDP ;Random PROC NEAR ; ; RET ;random endp ;AutoSolve PROC NEAR ; ; RET ;AutoSolve ENDP ;DrawTile Proc NEAR ; ; RET ;DrawTile ENDP ;CheckMove PROC NEAR ; ; RET ;CheckMove ENDP ; == Main ================================================================ MAIN Proc FAR ; Initialize DS register MOV AX, CSEG MOV DS, AX ; Put display into 80x50 text mode MOV AX, 1202h ; Sets to 400 line scan mode MOV BL, 30h int 10h MOV AX, 3 ; Sets to 8x8 font INT 10h MOV AX, 1112h ; Enter text mode MOV BL, 0 INT 10h ; Initialize the mouse hardware, Function 0000h MOV AX, 0000h INT 33h ; Display the mouse cursor, Function 0001h MOV AX, 0001h INT 33h ; The Main Program Code CALL DrawBoard CALL UpdateScreen MainLoop: CALL MouseControl CMP DX, TileClicked JNE @F CALL CheckMove JMP MainLoop @@: CMP DX, ResetClk JNE @F CALL ResetBoard JMP MainLoop @@: CMP DX, ShuffleClk JNE @F CALL ResetBoard CALL Shuffle JMP MainLoop @@: CMP DX, SolveClk JNE @F MOV AX, 0 CALL Autosolve JMP MainLoop @@: CMP DX, SolveOnceClk JNE @F MOV AX, 1 CALL AutoSolve JMP MainLoop @@: CMP DX, DelayUpClk JNE @F CMP DelayC, MAXDELAY JNL MainLoop INC DelayC CALL UpdateScreen JMP MainLoop @@: CMP DX, DelayDownClk JNE @F CMP DelayC, MINDELAY JNG MainLoop DEC DelayC CALL UpdateScreen JMP MainLoop @@: CMP DX, QuitClk JNE MainLoop ; Put display into 80x50 text mode (to reset the screen) EndIt: MOV AX, 1202h MOV BL, 30h int 10h MOV AX, 3 INT 10h MOV AX, 1112h MOV BL, 0 INT 10h CALL MP3Xit MAIN ENDP ; ===================== End of Proceures & Data ========================== CSEG ends end main </FONT>