NCL 409: Refactoring the Game
At the end of NCL 310, we had a complete game of Noughts and Crosses.
It accepts keyboard input, moves a selection around the board, places pieces, detects wins and draws, and gives Nought a simple computer player.
It also contains quite a lot of code.
That's expected. We built the game using the tools we understood at the time. Since then, we've learned about the value stack, subroutine arguments and return values, preserving state, coalescing, execution cost, and indirect registers.
Now we can return to the same program and make different choices about how it is organized.
This is refactoring: changing the structure of a program while preserving its behavior.
We'll start with the parts of the game whose purpose we already understand well.
Organizing Our Registers
The original game used registers for several different kinds of state:
#selection r0
#column r1
#row r2
#x r3
#y r4
#draw_cell r6
#value r7
#selected r8
#player r9
#line r10
#highest r11
#lowest r12
#moves r13
#strength r14
#opportunity r15
#danger r16
These values don't all have the same lifetime.
#selection, #player, and #moves are persistent game state. Their values need to survive while many different parts of the program execute.
#column, #row, #x, and #y are temporary calculations.
#draw_cell, #value, and #selected are really values being passed into or used by drawing code.
We can make those differences visible in the register map.
For this program, we'll use the convention from NCL 405:
| Registers | Purpose |
|---|---|
r0–r7 |
Temporary integer scratch |
r8–r19 |
Longer-lived scalar state |
r20–r28 |
Board cells |
r29–r30 |
Spare |
r31 |
Stack-pointer saver |
Our persistent scalar state becomes:
#selection r8
#player r9
#moves r10
#highest r11
#lowest r12
#line r13
#strength r14
#opportunity r15
#danger r16
The board remains in r20 through r28:
#cell0 r20
#cell1 r21
#cell2 r22
#cell3 r23
#cell4 r24
#cell5 r25
#cell6 r26
#cell7 r27
#cell8 r28
We'll also name the beginning and end of that range:
#board_base 20
#board_limit 29
#board_base is the register number of the first board cell.
#board_limit is the first register number after the board.
The individual #cell0 through #cell8 aliases are still useful when we mean a particular fixed cell. #board_base is useful when the cell number is chosen at runtime.
Our string registers remain:
#key s0
#glyph s1
The important change is what has disappeared.
There is no longer a permanent alias for every temporary coordinate or subroutine input. When a routine needs temporary integer values, it can use r0 through r7.
Those registers are scratch. A caller should assume that a subroutine may change them.
Clearing the Board
The original program cleared every board cell individually:
MOVE #cell0 0
MOVE #cell1 0
MOVE #cell2 0
MOVE #cell3 0
MOVE #cell4 0
MOVE #cell5 0
MOVE #cell6 0
MOVE #cell7 0
MOVE #cell8 0
We now know that the board occupies nine consecutive integer registers.
Indirect access lets us walk through them:
MOVE r0 #board_base
$clear_board
MOVE rr0 0
INC r0
BLT $clear_board r0 #board_limit
r0 begins at #board_base, which is 20.
Each pass clears the integer register selected by r0, increments the register number, and continues until r0 reaches #board_limit.
The representation hasn't changed. We've simply taken advantage of something that was already true about it: the board cells are consecutive.
Starting from zero
When a program is invoked, NCL clears all registers and the value stack. The board cells therefore already contain 0 when this program begins.
We still initialize them explicitly. Initialization code makes the program's intended starting state clear instead of requiring the reader to rely on the machine's startup behavior.
The rest of our initial state stays explicit too:
MOVE #selection 0
MOVE #player 1
MOVE #moves 0
Placing a Piece
The original $place code turned #selection into a large branch tree:
$place
BEQ $place0 #selection 0
BEQ $place1 #selection 1
BEQ $place2 #selection 2
BEQ $place3 #selection 3
BEQ $place4 #selection 4
BEQ $place5 #selection 5
BEQ $place6 #selection 6
BEQ $place7 #selection 7
JUMP $place8
Each destination then performed the same operation on a different board register:
$place0
BNEQ $occupied #cell0 0
MOVE #cell0 #player
JUMP $placed
$place1
BNEQ $occupied #cell1 0
MOVE #cell1 #player
JUMP $placed
and so on.
The program already knows the cell number.
What it couldn't do in NCL 310 was use that number to select one of the board registers directly.
Now it can.
If cell 0 is stored in register 20, cell 1 in register 21, and so on, then the selected board register number is:
board_base + selection
In NCL:
ADD r0 #selection #board_base
and the selected cell can then be accessed as:
rr0
We could place the piece directly inside $place, but placement is also a useful operation in its own right:
Try to place this value in this board cell, and tell me whether it succeeded.
That gives us a natural subroutine interface.
$try_place
We'll define $try_place to:
- consume a cell index;
- consume a player value;
- produce
1if the piece was placed; - produce
0if the cell was occupied.
The caller supplies the values on the value stack:
PUSH #selection
PUSH #player
CALL $try_place
POP r0
Since the player value was pushed last, the subroutine pops it first:
$try_place
POP r1
POP r0
Now:
r0contains the cell index;r1contains the player value.
We turn the cell index into a register number:
ADD r0 r0 #board_base
Then inspect the selected board register:
BNEQ $place_failed rr0 0
If it is empty, place the value and return success:
MOVE rr0 r1
PUSH 1
RET
Otherwise:
$place_failed
PUSH 0
RET
The complete routine is:
-- Try to place a value in a board cell.
-- Consumes: cell index, player value.
-- Produces: 1 if placed, 0 if occupied.
$try_place
POP r1
POP r0
ADD r0 r0 #board_base
BNEQ $place_failed rr0 0
MOVE rr0 r1
PUSH 1
RET
$place_failed
PUSH 0
RET
The main game now asks only whether placement succeeded:
$place
PUSH #selection
PUSH #player
CALL $try_place
POP r0
BEQ $input r0 0
JUMP $placed
The nine placement branches and nine nearly identical placement blocks are gone.
The main game flow no longer needs to know how a cell index becomes a board register.
Giving Drawing an Interface
Drawing has a similar opportunity.
The original program passed information into $draw_cell through shared registers:
#draw_cell r6
#value r7
#selected r8
Other routines prepared those registers before making the call:
$draw_normal
MOVE #draw_cell #selection
MOVE #selected 0
CALL $draw_cell
RET
and:
$draw_selected
MOVE #draw_cell #selection
MOVE #selected 1
CALL $draw_cell
RET
These values aren't persistent game state.
They're arguments.
Let's make that explicit.
We'll give $draw_cell this interface:
- consume a cell index;
- consume a selected state;
- draw the cell;
- return no value.
To draw the current cell normally:
PUSH #selection
PUSH 0
CALL $draw_cell
To draw it inverted:
PUSH #selection
PUSH 1
CALL $draw_cell
Inside the routine:
$draw_cell
POP r1
POP r0
Now:
r0contains the cell index;r1contains the selected state.
The rest of the scratch registers are available for temporary calculations.
Reading the Cell
The old drawing routine contained another nine-way dispatcher:
BEQ $draw0 #draw_cell 0
BEQ $draw1 #draw_cell 1
BEQ $draw2 #draw_cell 2
BEQ $draw3 #draw_cell 3
BEQ $draw4 #draw_cell 4
BEQ $draw5 #draw_cell 5
BEQ $draw6 #draw_cell 6
BEQ $draw7 #draw_cell 7
JUMP $draw8
Each destination then copied one fixed board register into #value.
We no longer need that control flow.
With the cell index in r0:
ADD r2 r0 #board_base
MOVE r3 rr2
Now:
| Register | Meaning |
|---|---|
r0 |
Cell index |
r1 |
Selected state |
r2 |
Board register number |
r3 |
Board value |
The runtime cell number selects the register directly.
Drawing with Scratch Registers
The rest of $draw_cell can use r4 through r7 for temporary work:
| Register | Temporary meaning |
|---|---|
r4 |
Column |
r5 |
Row |
r6 |
X coordinate |
r7 |
Y coordinate |
First choose the glyph stored in the cell:
SMOVE #glyph #empty
BEQ $use_cross r3 1
BEQ $use_nought r3 -1
JUMP $paint_position
$use_cross
SMOVE #glyph #cross
JUMP $paint_position
$use_nought
SMOVE #glyph #nought
Then convert the cell index into screen coordinates:
$paint_position
MOD r4 r0 3
DIV r5 r0 3
MUL r6 r4 4
ADD r6 r6 11
MUL r7 r5 2
ADD r7 r7 2
Then choose the display style:
BEQ $paint_selected r1 1
D.COL #D.COL.WHITE #D.TXT.NORMAL
JUMP $paint_glyph
$paint_selected
D.COL #D.COL.WHITE #D.TXT.INVERT
And draw:
$paint_glyph
D.CUR r6 r7
D.CHR #glyph
D.COL #D.COL.WHITE #D.TXT.NORMAL
RET
$draw_cell now gets everything it needs through its interface and scratch registers.
The old #draw_cell, #value, and #selected aliases are gone.
So are $draw0 through $draw8.
Moving the Selection
There is another repeated operation in the four movement handlers.
After checking whether movement is allowed, each one:
- draws the old selection normally;
- changes
#selection; - draws the new selection inverted;
- returns to input.
Only the amount of movement differs.
| Direction | Change |
|---|---|
| Left | -1 |
| Right | 1 |
| Up | -3 |
| Down | 3 |
The boundary checks still belong to each direction.
For example:
$move_left
MOD r0 #selection 3
BEQ $input r0 0
PUSH -1
CALL $move_selection
JUMP $input
Right:
$move_right
MOD r0 #selection 3
BEQ $input r0 2
PUSH 1
CALL $move_selection
JUMP $input
Up:
$move_up
BLT $input #selection 3
PUSH -3
CALL $move_selection
JUMP $input
Down:
$move_down
BGE $input #selection 6
PUSH 3
CALL $move_selection
JUMP $input
Now $move_selection needs only one argument: the amount to add to #selection.
$move_selection
POP r0
Before changing the selection, however, it needs to call $draw_cell.
r0 is a scratch register. $draw_cell is allowed to change it.
Our movement amount needs to survive that call.
So $move_selection, acting as the caller, preserves the scratch value it cares about:
$move_selection
POP r0
-- r0 now holds the movement amount.
-- Save it because $draw_cell may change scratch registers.
PUSH r0
PUSH #selection
PUSH 0
CALL $draw_cell
POP r0
ADD #selection #selection r0
PUSH #selection
PUSH 1
CALL $draw_cell
RET
At first glance:
POP r0
PUSH r0
may look redundant.
The two operations have different jobs.
The first POP consumes the argument supplied to $move_selection and makes it available in a scratch register.
The following PUSH preserves that scratch value across a call to another subroutine.
This is the caller-saved convention from NCL 405 appearing in a real program.
$move_selection is a subroutine, but while it calls $draw_cell, it becomes a caller too.
The Main Flow Gets Simpler
Several places in the program can now call $draw_cell directly.
After drawing the initial board:
PUSH #selection
PUSH 1
CALL $draw_cell
After a move is placed:
$placed
INC #moves
PUSH #selection
PUSH 1
CALL $draw_cell
CALL $check_win
When the computer begins thinking and we want to remove the visible selection:
$computer_turn
PUSH #selection
PUSH 0
CALL $draw_cell
We no longer need $draw_normal and $draw_selected merely to prepare shared registers before calling another routine.
The values travel with the call instead.
What We Haven't Changed
We've removed quite a lot of machinery:
- nine separate board-clearing instructions became a loop;
- nine-way placement dispatch became indirect access;
- placement became a subroutine with arguments and a return value;
- drawing no longer uses shared argument registers;
- nine-way drawing dispatch became indirect access;
- repeated movement work became one argument-taking subroutine;
- temporary calculations now live in an explicit scratch-register range.
But the computer player is still enormous.
It still contains sections like:
BNEQ $scan1 #cell0 0
CALL $check_cell0
and nine routines named:
$check_cell0
through:
$check_cell8
Indirect registers can answer:
Which board register contains cell 5?
They don't answer:
Which winning lines contain cell 5?
Right now, those relationships are encoded in the structure of the program itself.
For example, $check_cell0 says that cell 0 belongs to:
- the top row;
- the left column;
- the first diagonal.
$check_cell4 says that cell 4 belongs to:
- the middle row;
- the center column;
- both diagonals.
The program knows these facts because we wrote different instructions into nine different subroutines.
That's a different kind of repetition.
Before we address it, let's look at the complete refactored game so far.
The Refactored Program
This version behaves like the game from NCL 310, but its state and repeated operations are now organized using the tools from the 400-level lessons.
-- Noughts and Crosses
-- Cross is controlled by the keyboard.
-- Nought is controlled by the computer.
-- Persistent game state.
#selection r8
#player r9
#moves r10
#highest r11
#lowest r12
#line r13
#strength r14
#opportunity r15
#danger r16
-- String scratch.
#key s0
#glyph s1
-- Board layout.
#board_base 20
#board_limit 29
#cell0 r20
#cell1 r21
#cell2 r22
#cell3 r23
#cell4 r24
#cell5 r25
#cell6 r26
#cell7 r27
#cell8 r28
-- Visible board values.
#empty "\u3000"
#cross "\uE573"
#nought "\uE5CB"
-- Starting state.
MOVE #selection 0
MOVE #player 1
MOVE #moves 0
-- Clear the board.
MOVE r0 #board_base
$clear_board
MOVE rr0 0
INC r0
BLT $clear_board r0 #board_limit
-- Draw the static board.
D.PALRST
D.COL #D.COL.WHITE #D.TXT.NORMAL
D.FIL " "
D.CUR 11 2
D.TXT #empty
D.TXT "\uE502"
D.TXT #empty
D.TXT "\uE502"
D.TXT #empty
D.CUR 11 3
D.TXT "\uE500\uE53C\uE500\uE53C\uE500"
D.CUR 11 4
D.TXT #empty
D.TXT "\uE502"
D.TXT #empty
D.TXT "\uE502"
D.TXT #empty
D.CUR 11 5
D.TXT "\uE500\uE53C\uE500\uE53C\uE500"
D.CUR 11 6
D.TXT #empty
D.TXT "\uE502"
D.TXT #empty
D.TXT "\uE502"
D.TXT #empty
D.CUR 9 8
D.TXT "ARROWS MOVE"
D.CUR 9 9
D.TXT "ENTER PLACE"
D.CUR 9 10
D.TXT "ESC TO EXIT"
D.BLT
-- Draw the initial selection.
PUSH #selection
PUSH 1
CALL $draw_cell
-- Wait for Cross to move.
$input
SYS.AKEY #key
BSEQ $move_left #key "LEFT"
BSEQ $move_right #key "RIGHT"
BSEQ $move_up #key "UP"
BSEQ $move_down #key "DOWN"
BSEQ $place #key "ENTER"
BSEQ $exit #key "ESC"
JUMP $input
-- Move left.
$move_left
MOD r0 #selection 3
BEQ $input r0 0
PUSH -1
CALL $move_selection
JUMP $input
-- Move right.
$move_right
MOD r0 #selection 3
BEQ $input r0 2
PUSH 1
CALL $move_selection
JUMP $input
-- Move up.
$move_up
BLT $input #selection 3
PUSH -3
CALL $move_selection
JUMP $input
-- Move down.
$move_down
BGE $input #selection 6
PUSH 3
CALL $move_selection
JUMP $input
-- Try to place the current player's piece.
$place
PUSH #selection
PUSH #player
CALL $try_place
POP r0
BEQ $input r0 0
JUMP $placed
-- A piece was placed.
$placed
INC #moves
PUSH #selection
PUSH 1
CALL $draw_cell
CALL $check_win
ABS #strength #lowest
MAX #strength #highest #strength
BNEQ $no_winner #strength 3
BEQ $cross_wins #highest 3
JUMP $nought_wins
$no_winner
BEQ $draw #moves 9
NEG #player #player
BEQ $input #player 1
JUMP $computer_turn
-- Nought's turn.
$computer_turn
-- Remove the selection while the computer thinks.
PUSH #selection
PUSH 0
CALL $draw_cell
-- -1 means that nothing has been found yet.
MOVE #opportunity -1
MOVE #danger -1
-- Inspect cell 0.
BNEQ $scan1 #cell0 0
CALL $check_cell0
BNEQ $danger0 #lowest -2
BNEQ $danger0 #opportunity -1
MOVE #opportunity 0
$danger0
BNEQ $scan1 #highest 2
BNEQ $scan1 #danger -1
MOVE #danger 0
-- Inspect cell 1.
$scan1
BNEQ $scan2 #cell1 0
CALL $check_cell1
BNEQ $danger1 #lowest -2
BNEQ $danger1 #opportunity -1
MOVE #opportunity 1
$danger1
BNEQ $scan2 #highest 2
BNEQ $scan2 #danger -1
MOVE #danger 1
-- Inspect cell 2.
$scan2
BNEQ $scan3 #cell2 0
CALL $check_cell2
BNEQ $danger2 #lowest -2
BNEQ $danger2 #opportunity -1
MOVE #opportunity 2
$danger2
BNEQ $scan3 #highest 2
BNEQ $scan3 #danger -1
MOVE #danger 2
-- Inspect cell 3.
$scan3
BNEQ $scan4 #cell3 0
CALL $check_cell3
BNEQ $danger3 #lowest -2
BNEQ $danger3 #opportunity -1
MOVE #opportunity 3
$danger3
BNEQ $scan4 #highest 2
BNEQ $scan4 #danger -1
MOVE #danger 3
-- Inspect cell 4.
$scan4
BNEQ $scan5 #cell4 0
CALL $check_cell4
BNEQ $danger4 #lowest -2
BNEQ $danger4 #opportunity -1
MOVE #opportunity 4
$danger4
BNEQ $scan5 #highest 2
BNEQ $scan5 #danger -1
MOVE #danger 4
-- Inspect cell 5.
$scan5
BNEQ $scan6 #cell5 0
CALL $check_cell5
BNEQ $danger5 #lowest -2
BNEQ $danger5 #opportunity -1
MOVE #opportunity 5
$danger5
BNEQ $scan6 #highest 2
BNEQ $scan6 #danger -1
MOVE #danger 5
-- Inspect cell 6.
$scan6
BNEQ $scan7 #cell6 0
CALL $check_cell6
BNEQ $danger6 #lowest -2
BNEQ $danger6 #opportunity -1
MOVE #opportunity 6
$danger6
BNEQ $scan7 #highest 2
BNEQ $scan7 #danger -1
MOVE #danger 6
-- Inspect cell 7.
$scan7
BNEQ $scan8 #cell7 0
CALL $check_cell7
BNEQ $danger7 #lowest -2
BNEQ $danger7 #opportunity -1
MOVE #opportunity 7
$danger7
BNEQ $scan8 #highest 2
BNEQ $scan8 #danger -1
MOVE #danger 7
-- Inspect cell 8.
$scan8
BNEQ $choose_move #cell8 0
CALL $check_cell8
BNEQ $danger8 #lowest -2
BNEQ $danger8 #opportunity -1
MOVE #opportunity 8
$danger8
BNEQ $choose_move #highest 2
BNEQ $choose_move #danger -1
MOVE #danger 8
-- Prefer a winning move, then a blocking move.
$choose_move
BEQ $no_opportunity #opportunity -1
MOVE #selection #opportunity
JUMP $place
$no_opportunity
BEQ $no_danger #danger -1
MOVE #selection #danger
JUMP $place
-- Nothing is urgent.
-- Prefer the center, then corners, then edges.
$no_danger
BEQ $choose4 #cell4 0
BEQ $choose0 #cell0 0
BEQ $choose2 #cell2 0
BEQ $choose6 #cell6 0
BEQ $choose8 #cell8 0
BEQ $choose1 #cell1 0
BEQ $choose3 #cell3 0
BEQ $choose5 #cell5 0
JUMP $choose7
$choose0
MOVE #selection 0
JUMP $place
$choose1
MOVE #selection 1
JUMP $place
$choose2
MOVE #selection 2
JUMP $place
$choose3
MOVE #selection 3
JUMP $place
$choose4
MOVE #selection 4
JUMP $place
$choose5
MOVE #selection 5
JUMP $place
$choose6
MOVE #selection 6
JUMP $place
$choose7
MOVE #selection 7
JUMP $place
$choose8
MOVE #selection 8
JUMP $place
-- Move the current selection.
-- Consumes: movement amount.
$move_selection
POP r0
-- r0 now holds the movement amount.
-- Save it because $draw_cell may change scratch registers.
PUSH r0
PUSH #selection
PUSH 0
CALL $draw_cell
POP r0
ADD #selection #selection r0
PUSH #selection
PUSH 1
CALL $draw_cell
RET
-- Try to place a value in a board cell.
-- Consumes: cell index, player value.
-- Produces: 1 if placed, 0 if occupied.
$try_place
POP r1
POP r0
ADD r0 r0 #board_base
BNEQ $place_failed rr0 0
MOVE rr0 r1
PUSH 1
RET
$place_failed
PUSH 0
RET
-- Draw one board cell.
-- Consumes: cell index, selected state.
$draw_cell
POP r1
POP r0
-- Read the selected board cell.
ADD r2 r0 #board_base
MOVE r3 rr2
-- Choose the glyph.
SMOVE #glyph #empty
BEQ $use_cross r3 1
BEQ $use_nought r3 -1
JUMP $paint_position
$use_cross
SMOVE #glyph #cross
JUMP $paint_position
$use_nought
SMOVE #glyph #nought
-- Convert the cell number into screen coordinates.
$paint_position
MOD r4 r0 3
DIV r5 r0 3
MUL r6 r4 4
ADD r6 r6 11
MUL r7 r5 2
ADD r7 r7 2
-- Draw normally or inverted.
BEQ $paint_selected r1 1
D.COL #D.COL.WHITE #D.TXT.NORMAL
JUMP $paint_glyph
$paint_selected
D.COL #D.COL.WHITE #D.TXT.INVERT
$paint_glyph
D.CUR r6 r7
D.CHR #glyph
D.COL #D.COL.WHITE #D.TXT.NORMAL
RET
-- Calculate the eight winning lines.
$check_top
ADD #line #cell0 #cell1
ADD #line #line #cell2
RET
$check_middle
ADD #line #cell3 #cell4
ADD #line #line #cell5
RET
$check_bottom
ADD #line #cell6 #cell7
ADD #line #line #cell8
RET
$check_left
ADD #line #cell0 #cell3
ADD #line #line #cell6
RET
$check_center
ADD #line #cell1 #cell4
ADD #line #line #cell7
RET
$check_right
ADD #line #cell2 #cell5
ADD #line #line #cell8
RET
$check_diagonal1
ADD #line #cell0 #cell4
ADD #line #line #cell8
RET
$check_diagonal2
ADD #line #cell2 #cell4
ADD #line #line #cell6
RET
-- Find the strongest line for either player.
$check_win
MOVE #highest -3
MOVE #lowest 3
CALL $check_top
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_middle
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_bottom
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_left
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_center
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_right
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_diagonal1
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_diagonal2
MAX #highest #highest #line
MIN #lowest #lowest #line
RET
-- Find the strongest lines passing through cell 0.
$check_cell0
MOVE #highest -3
MOVE #lowest 3
CALL $check_top
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_left
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_diagonal1
MAX #highest #highest #line
MIN #lowest #lowest #line
RET
-- Find the strongest lines passing through cell 1.
$check_cell1
MOVE #highest -3
MOVE #lowest 3
CALL $check_top
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_center
MAX #highest #highest #line
MIN #lowest #lowest #line
RET
-- Find the strongest lines passing through cell 2.
$check_cell2
MOVE #highest -3
MOVE #lowest 3
CALL $check_top
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_right
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_diagonal2
MAX #highest #highest #line
MIN #lowest #lowest #line
RET
-- Find the strongest lines passing through cell 3.
$check_cell3
MOVE #highest -3
MOVE #lowest 3
CALL $check_middle
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_left
MAX #highest #highest #line
MIN #lowest #lowest #line
RET
-- Find the strongest lines passing through cell 4.
$check_cell4
MOVE #highest -3
MOVE #lowest 3
CALL $check_middle
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_center
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_diagonal1
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_diagonal2
MAX #highest #highest #line
MIN #lowest #lowest #line
RET
-- Find the strongest lines passing through cell 5.
$check_cell5
MOVE #highest -3
MOVE #lowest 3
CALL $check_middle
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_right
MAX #highest #highest #line
MIN #lowest #lowest #line
RET
-- Find the strongest lines passing through cell 6.
$check_cell6
MOVE #highest -3
MOVE #lowest 3
CALL $check_bottom
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_left
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_diagonal2
MAX #highest #highest #line
MIN #lowest #lowest #line
RET
-- Find the strongest lines passing through cell 7.
$check_cell7
MOVE #highest -3
MOVE #lowest 3
CALL $check_bottom
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_center
MAX #highest #highest #line
MIN #lowest #lowest #line
RET
-- Find the strongest lines passing through cell 8.
$check_cell8
MOVE #highest -3
MOVE #lowest 3
CALL $check_bottom
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_right
MAX #highest #highest #line
MIN #lowest #lowest #line
CALL $check_diagonal1
MAX #highest #highest #line
MIN #lowest #lowest #line
RET
-- Game endings.
$cross_wins
D.CUR 9 9
D.TXT "CROSS WINS! "
D.BLT
JUMP 0
$nought_wins
D.CUR 9 9
D.TXT "NOUGHT WINS! "
D.BLT
JUMP 0
$draw
D.CUR 9 9
D.TXT "DRAW! "
D.BLT
JUMP 0
$exit
JUMP 0
Try It
Look through the refactored program and compare it with the version from NCL 310.
Find places where a register used to act as an informal subroutine argument.
For each one, ask:
Does this value need a permanent home in the register file, or does it only need to travel into a subroutine?
Then find places where a chain of branches disappeared because the program can now select a board register indirectly.
Ask:
Is the program making a decision here, or was it using control flow to perform an indexed lookup?
Finally, look at the parts of the computer player that remain repetitive.
Notice that they describe a different problem.
The board is now easy to access by index, and repeated operations have clearer interfaces.
The largest repetition left describes relationships between cells and winning lines. Those relationships are still encoded as different branches and different subroutines.
In the next lesson, NCL 410: Refactoring the Computer, we'll give those relationships a representation of their own.