1. Purpose of the experiment
- Analyze and understand assigned problems to be solved.
- Use the assembly code of LC-3 to design and realize related programs.
- Debug and run related programs through LC-3 emulator and get correct results.
2. Experimental content
Connect Four is a popular simple table game. It is said that Captain Hook has been hiding in the residence for a long time because of his concentration on this game. When the crew discovered the captain's specialty, they called this game "Captain's mistress".
Connect four is a two-player game in which two players take turns playing on a grid of rows and columns where each player plays one piece at a time until one of the two pieces connects in a horizontal, vertical, or diagonal line Wire.
This experiment needs to implement a simple version of Connect Four in LC-3. Two players interact with each other through the keyboard and output window in turn. The chessboard is composed of a 6 X 6 grid.
The rules of the game are as follows:
- The two players take turns to play;
- Players cannot regret a move;
- Where there is a child, the child cannot continue to be placed;
- Until the four pieces of one side can be connected into a horizontal line, vertical line or diagonal line;
- If the board is full and no one wins, there is a draw.
At the beginning of the game, an empty board should be printed. You can use the ASCII code "-" (ASCII code x002D) to indicate that the place is empty, "O" (ASCII code x004F) to indicate the first player's piece, and "X" (ASCII code x0058) to represent the chess piece of the second player. In order to make the chessboard easier to observe, add a space between each column, do not add after the 6th column, the initial chessboard should be as follows:
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
Player 1 always plays the first move first, and then the two take turns to make moves. After each move, the player's information should be printed to remind him to make a move. Taking player 1 as an example, the printed information should be as follows:
Player 1, choose a column:
In order to clarify the position of the player's move, the player should enter the number 1-6, and then press Enter, the number 1-6 indicates the column where the move is located, from left to right, no need to enter the line number, the program should default from line number 6 to Fill in the pieces in descending order of row number 1. If the column numbers entered before and after are the same, the row number will be reduced by one. For example, if the player places a piece in the second column from the left for the first time, he should enter 2, and then press Enter, then the piece will be placed in row 6, column 2, and when the column number entered later is 2 again, the piece will be placed in the row 5 column 2, and so on, see the following example output for details. The program should ensure that the number entered by the player corresponds to the correct column range, and if the input is unreasonable, an error message should be output, prompting the player to continue typing, for example, if for player one:
Player 1, choose a column: D
Invalid move. Try again.
Player 1, choose a column: 7
Invalid move. Try again.
Player 1, choose a column:
The program should always prompt the player to know the correct number to enter, and when the user completes the input, the program should give feedback to the player through display, and then use a newline character (ASCII code x000A) to break the line.
When a player enters successfully, the program should print the updated board and check if anyone has won, and if no one has won, it's the next player's turn to enter.
When one of them wins or draws, the game ends, and the program displays the final board situation and terminates (Halt). For example, if player two has four connected, it should output:
Player 2 Wins.
In case of a tie, the program should output:
Tie Game.
3. Experimental procedures and results
Overall process (main function)
The overall process of my tentatively designed experiment is shown in the figure below. The flowchart is the work of the main function in the code.
Representation of chessboard matrices and round parties
A 6×6 two-dimensional matrix is used to represent the chessboard. The position on the board with no pieces is represented by 0, the position of Player 1’s position is represented by 1, and the position of Play 2’s position is represented by -1. Matrix subscripts start at 0.
The memory addresses in the computer are continuous, so it is necessary to continuously allocate a one-dimensional memory space of 36 units. The specific operation is as follows:
;
;矩阵建立及其初始化
;
ARRAY .BLKW #36
There is a one-to-one correspondence between the coordinates on the two-dimensional matrix and the coordinates on the one-dimensional matrix. For example, on the memory corresponding to the two-dimensional coordinates (i, j), the (6 × i + j)th position counted from the entry of the matrix. The index position on the matrix memory corresponds to the two-dimensional matrix coordinates (index // 6, index % 6). Among them, "//" means divisibility, and "%" means taking the remainder.
Because in this experiment, the coordinates are frequently converted from one-dimensional to two-dimensional, I implemented the multi function and the div function. The function of the multi function is to multiply the incoming parameter by 6 and return, and the function of the div function is to return the quotient and remainder of the incoming parameter.
Use a register R0 to store the current mover. When the register stores 1, the current round is played by Player 1; when -1 is stored, Player 2 plays.
The specific implementation method of each module in the main flow chart is discussed in detail below, and a flow chart is attached.
Implement the print function: print the current situation
To implement the printing function, you only need to traverse the values in the matrix memory in sequence. If the value of the element in the matrix is 0, output "-"; if it is 1, output "O"; if it is -1, output "X". It should be noted that each output of "-" must be judged. Under normal circumstances, a space is output, but when a line break is required, the line is changed directly. The flow chart is as follows:
Implement the isTie function: determine whether the current situation is a tie
It is worth noting that if there is a tie, then all 6 elements in row 0 of the chessboard matrix must be non-zero. It also corresponds to the 6 locations from ARRAY to ARRAY + 5 on the memory. Therefore, it is only necessary to judge whether all the six positions are not 0. If all are not 0, the situation is a tie; if there are non-zeros, the situation is not a tie. The process is as follows:
Implement the Play function: the player enters the coordinates and returns the actual drop position
The coordinates entered by the player are an ordinate, and this function needs to find the legal drop position of this column and return its actual coordinates (one-dimensional). If the coordinate does not exist (the column is full or the input is invalid), output an error message and return -1.
According to the current round, this function is divided into Play1 and Play2. Its function is the same.
Algorithm idea: Assume that the input ordinate is j (starting from 0) and the entry address of the last line is ARRAY + 30 + j. Then start from this address, continuously subtract 6 (that is, go back one line), look for the position with a value of 0, and return the subscript if it is found; if it has not been found, return -1.
The flow chart is as follows:
Implement the isOver function: determine whether a party wins
This function has two parameters: move coordinate index and move value value. Its meaning is, when one party changes the value of ARRAY[index] to value, can the game be distinguished from the winner?
This function is the core of this experiment, and four kinds of judgments are required: horizontal judgment, vertical judgment, main diagonal direction judgment and sub-diagonal direction judgment.
To realize the judgment of the four-part line in the above four situations, the brute force algorithm is not considered, because the efficiency is low, and the design of multiple loops is also relatively difficult and prone to confusion. Therefore, I adopt the following optimized algorithm.
Whenever a chess piece falls, the situation is bound to change, and there may be a new situation where four pieces form a line. There may not be such a situation, but if there is, this new move will definitely become one of the four in the line. Then we take this new chess piece as the center and expand in the seven directions shown in the figure below:
For example, to determine whether four pieces form a line in the horizontal direction, it is necessary to count the number of consecutive identical pieces on the left side of the drop point. As shown in the figure below, the red circular chess piece is a new chess piece, so when making a horizontal judgment, you can use this chess piece as the center and extend to the left and right. There are two consecutive identical chess pieces on its left side, and one consecutive identical chess piece on its right side, plus itself, just four pieces form a line, so it is judged that Yuanfang wins.
The judgments of the vertical direction, the main diagonal direction, and the subdiagonal direction are similar to this. The algorithm designed in this way is more efficient and more concise in the process of coding and implementation.
The above is the idea realized by this experiment. The next section will test the correctness and robustness of this program from several angles.
Test 1: Horizontal Victory
Test 2: Vertical Victory
Test 3: The main diagonal direction wins
Test 4: Subdiagonal direction wins
Test 5: Draw
Test 6: Illegal input
Illegal input with top overflow is tested in the left image, and incorrect input with illegal characters is tested in the right image. For these two types of wrong input, the program pops up an error message and reminds the player to re-enter. It shows that the robustness of the program is good.
4. Complete code
Below is the complete Connect Four LC-3 code.
;
; author: Cao-Yixuan 2019282129
; date:2021.6.8
; function: a game called Connect Four
;
;
.ORIG x3000
;
JSR main
;
;函数:打印矩阵
;
print ST R0 SAVE_R0 ;被调用者保存
ST R1 SAVE_R1
ST R2 SAVE_R2
ST R3 SAVE_R3
ST R4 SAVE_R4
ST R5 SAVE_R5
ST R6 SAVE_R6
ST R7 SAVE_R7
;
;R1 = 36,控制循环
;R2 = 6,控制换行
;R3 = ARRAY
;
LEA R3 ARRAY
AND R1 R1 #0
ADD R2 R1 #6
ADD R1 R1 #15
ADD R1 R1 #15
ADD R1 R1 R2
;
PRINT_LOOP LDR R0 R3 #0
BRp PRINT_1_CH
BRn PRINT_2_CH
LD R0 BLANK
BRnzp PRINT_CH
PRINT_1_CH LD R0 P_1_CH
BRnzp PRINT_CH
PRINT_2_CH LD R0 P_2_CH
BRnzp PRINT_CH
PRINT_CH TRAP x21
ADD R3 R3 #1
ADD R2 R2 #-1
BRp PRINT_BLANK
ADD R2 R2 #6
LD R0 ENDLINE
TRAP x21
BRnzp PRINT_L_END
PRINT_BLANK LD R0 BLANK1
TRAP x21
PRINT_L_END ADD R1 R1 #-1
BRp PRINT_LOOP
;
;
;
LD R0 SAVE_R0 ; 被调用者恢复
LD R1 SAVE_R1
LD R2 SAVE_R2
LD R3 SAVE_R3
LD R4 SAVE_R4
LD R5 SAVE_R5
LD R6 SAVE_R6
LD R7 SAVE_R7
RET
;
P_1_CH .FILL x004F
P_2_CH .FILL x0058
ENDLINE .FILL x0D
BLANK .FILL x002D
BLANK1 .FILL x20
;
;函数:PLAY1落子
;输入:TEMP0
;输出:TEMP0
;如果落子失败,R6处返回-1
;如果落子成功,R6处返回落子位置的下标
;
play1 ST R0 SAVE_R0 ;被调用者保存
ST R1 SAVE_R1
ST R2 SAVE_R2
ST R3 SAVE_R3
ST R4 SAVE_R4
ST R5 SAVE_R5
ST R6 SAVE_R6
ST R7 SAVE_R7
;
;初始化返回值R6 = -1
;
AND R6 R6 #0
ADD R6 R6 #-1
;
;边界检查
;
LD R0 TEMP0 ;传入参数j
ADD R1 R0 #-6
BRzp P_1_ERROR
ADD R1 R0 #0
BRn P_1_ERROR
;
;循环
;R2 = ARRAY+30+j,入口
;R3 = ARRAY+j,出口
;
LEA R2 ARRAY
ADD R3 R2 R0
ADD R2 R3 #15
ADD R2 R2 #15
NOT R0 R3
ADD R0 R0 #1
P_1_LOOP LDR R3 R2 #0
ADD R3 R3 #0
BRz P_1_RIGHT
ADD R2 R2 #-6
ADD R4 R2 R0
BRzp P_1_LOOP
P_1_ERROR LEA R0 ERROR
TRAP x22
BRnzp PLAY_1_END
P_1_RIGHT AND R6 R2 R2
AND R4 R4 #0
ADD R4 R4 #1
STR R4 R6 #0
LEA R7 ARRAY
NOT R7 R7
ADD R7 R7 #1
ADD R6 R6 R7
BRnzp PLAY_1_END
PLAY_1_END ST R6 TEMP0 ; 输出
LD R0 SAVE_R0 ; 被调用者恢复
LD R1 SAVE_R1
LD R2 SAVE_R2
LD R3 SAVE_R3
LD R4 SAVE_R4
LD R5 SAVE_R5
LD R6 SAVE_R6
LD R7 SAVE_R7
RET
;
;函数:PLAY2落子
;输入:TEMP0
;输出:TEMP0
;如果落子失败,R6处返回-1
;如果落子成功,R6处返回落子位置的下标
;
play2 ST R0 SAVE_R0 ;被调用者保存
ST R1 SAVE_R1
ST R2 SAVE_R2
ST R3 SAVE_R3
ST R4 SAVE_R4
ST R5 SAVE_R5
ST R6 SAVE_R6
ST R7 SAVE_R7
;
;初始化返回值R6 = -1
;
AND R6 R6 #0
ADD R6 R6 #-1
;
;边界检查
;
LD R0 TEMP0 ;传入参数j
ADD R1 R0 #-6
BRzp P_2_ERROR
ADD R1 R0 #0
BRn P_2_ERROR
;
;循环
;R2 = ARRAY + 30 + j, 作为入口
;R3 = ARRAY + j, 作为出口
;
LEA R2 ARRAY
ADD R3 R2 R0
ADD R2 R3 #15
ADD R2 R2 #15
NOT R0 R3
ADD R0 R0 #1
P_2_LOOP LDR R3 R2 #0
ADD R3 R3 #0
BRz P_2_RIGHT
ADD R2 R2 #-6
ADD R4 R2 R0
BRzp P_2_LOOP
P_2_ERROR LEA R0 ERROR
TRAP x22
BRnzp PLAY_2_END
P_2_RIGHT AND R6 R2 R2
AND R4 R4 #0
ADD R4 R4 #-1
STR R4 R6 #0
LEA R7 ARRAY
NOT R7 R7
ADD R7 R7 #1
ADD R6 R6 R7
BRnzp PLAY_2_END
PLAY_2_END ST R6 TEMP0 ; 输出
LD R0 SAVE_R0 ; 被调用者恢复
LD R1 SAVE_R1
LD R2 SAVE_R2
LD R3 SAVE_R3
LD R4 SAVE_R4
LD R5 SAVE_R5
LD R6 SAVE_R6
LD R7 SAVE_R7
RET
ERROR .STRINGZ "Invalid move. Try again.\n"
;
;函数:判断是否是平局
;output:TEMP1
;
isTie ST R0 SAVE_R0 ;被调用者保存
ST R1 SAVE_R1
ST R2 SAVE_R2
ST R3 SAVE_R3
ST R4 SAVE_R4
ST R5 SAVE_R5
ST R6 SAVE_R6
ST R7 SAVE_R7
LEA R0 ARRAY
AND R1 R1 #0 ;index = 5 to 0
ADD R1 R1 #5
AND R2 R2 #0 ;返回0
;
TIE_LOOP ADD R4 R0 R1
LDR R3 R4 #0
ADD R3 R3 #0
BRz TIE_RET
ADD R1 R1 #-1
BRzp TIE_LOOP
ADD R2 R2 #1 ;返回1
TIE_RET ST R2 TEMP1
LD R0 SAVE_R0 ; 被调用者恢复
LD R1 SAVE_R1
LD R2 SAVE_R2
LD R3 SAVE_R3
LD R4 SAVE_R4
LD R5 SAVE_R5
LD R6 SAVE_R6
LD R7 SAVE_R7
RET
;
;矩阵建立及其初始化
;
ARRAY .BLKW #36
;
;保存寄存器
;
SAVE_R0 .FILL #0
SAVE_R1 .FILL #0
SAVE_R2 .FILL #0
SAVE_R3 .FILL #0
SAVE_R4 .FILL #0
SAVE_R5 .FILL #0
SAVE_R6 .FILL #0
SAVE_R7 .FILL #0
;
;临时存储用寄存器
;
TEMP0 .FILL #0
TEMP1 .FILL #0
TEMP2 .FILL #0
TEMP3 .FILL #0
TEMP4 .FILL #0
TEMP5 .FILL #0
TEMP6 .FILL #0
TEMP7 .FILL #0
TEMP8 .FILL #0
TEMP9 .FILL #0
;
;The key function: is it over?
;if return 1, play1 wins
;if return -1, play2 wins
;if return 0, it is not over
;INPUT1: TEMP0, an index of the array
;INPUT2: TEMP1, the value of array[TEMP0]
;OUTPUT: TEMP0, a bool value
;
isOver ST R0 SAVE_R0 ;被调用者保存
ST R1 SAVE_R1
ST R2 SAVE_R2
ST R3 SAVE_R3
ST R4 SAVE_R4
ST R5 SAVE_R5
ST R6 SAVE_R6
ST R7 SAVE_R7
;
;行判断
;
JUDGE1 LD R0 TEMP0 ;R0 = index
LD R1 TEMP1 ;R1 = -value
NOT R1 R1
ADD R1 R1 #1
JSR div
LD R2 TEMP1 ;R2 = i
LD R3 TEMP2 ;R3 = j
AND R4 R4 #0 ;R4 = cnt
LEA R5 ARRAY ;R5 元素指针,从行首开始
ADD R5 R5 R0
NOT R6 R3
ADD R6 R6 #1
ADD R5 R5 R6
ST R5 TEMP3 ;行首指针存起来
;
;Loop: jump-tp-middle
;
BRnzp J_MIDD_1
J_LOOP_1 LDR R6 R5 #0
ADD R6 R6 R1
BRnp J_NOT_1
ADD R4 R4 #1
ADD R5 R5 #1
ADD R6 R4 #-4
BRz J_RET_TRUE
BRnzp J_MIDD_1
J_NOT_1 AND R4 R4 #0
ADD R5 R5 #1
J_MIDD_1 LD R6 TEMP3
ADD R6 R6 #5
NOT R6 R6
ADD R6 R6 #1
ADD R6 R6 R5
BRn J_LOOP_1
;
;列判断
;
JUDGE2 ADD R4 R0 #-16
ADD R4 R4 #-2
BRzp JUDGE3
LEA R5 ARRAY
ADD R5 R5 R0
LDR R6 R5 #6
ADD R6 R6 R1
BRnp JUDGE3
LDR R6 R5 #12
ADD R6 R6 R1
BRnp JUDGE3
LDR R6 R5 #18
ADD R6 R6 R1
BRnp JUDGE3
BRnzp J_RET_TRUE
;
;主对角线方向判断
;
JUDGE3 AND R4 R4 #0
ADD R4 R4 #1 ;cnt=1
ST R2 TEMP3 ;存储i
ST R3 TEMP4 ;存储j
J_3_LOOP_1 ADD R2 R2 #-1
BRn J_3_END_1
ADD R3 R3 #-1
BRn J_3_END_1
ST R2 TEMP0
JSR multi
LD R5 TEMP1
ADD R5 R5 R3
LEA R6 ARRAY
ADD R5 R5 R6
LDR R5 R5 #0
ADD R5 R5 R1
BRnp J_3_END_1
ADD R4 R4 #1
BRnzp J_3_LOOP_1
J_3_END_1 LD R2 TEMP3
LD R3 TEMP4
J_3_LOOP_2 ADD R2 R2 #1
ADD R5 R2 #-6
BRzp J_3_END_2
ADD R3 R3 #1
ADD R5 R3 #-6
BRzp J_3_END_2
ST R2 TEMP0
JSR multi
LD R5 TEMP1
ADD R5 R5 R3
LEA R6 ARRAY
ADD R5 R5 R6
LDR R5 R5 #0
ADD R5 R5 R1
BRnp J_3_END_2
ADD R4 R4 #1
BRnzp J_3_LOOP_2
J_3_END_2 ADD R4 R4 #-4
BRzp J_RET_TRUE
;
;副对角线方向判断
;
JUDGE4 AND R4 R4 #0
ADD R4 R4 #1 ;cnt=1
LD R2 TEMP3
LD R3 TEMP4
J_4_LOOP_1 ADD R2 R2 #-1
BRn J_4_END_1
ADD R3 R3 #1
ADD R5 R3 #-6
BRzp J_4_END_1
ST R2 TEMP0
JSR multi
LD R5 TEMP1
ADD R5 R5 R3
LEA R6 ARRAY
ADD R5 R5 R6
LDR R5 R5 #0
ADD R5 R5 R1
BRnp J_4_END_1
ADD R4 R4 #1
BRnzp J_4_LOOP_1
J_4_END_1 LD R2 TEMP3
LD R3 TEMP4
J_4_LOOP_2 ADD R2 R2 #1
ADD R5 R2 #-6
BRzp J_4_END_2
ADD R3 R3 #-1
BRn J_4_END_2
ST R2 TEMP0
JSR multi
LD R5 TEMP1
ADD R5 R5 R3
LEA R6 ARRAY
ADD R5 R5 R6
LDR R5 R5 #0
ADD R5 R5 R1
BRnp J_4_END_2
ADD R4 R4 #1
BRnzp J_4_LOOP_2
J_4_END_2 ADD R4 R4 #-4
BRzp J_RET_TRUE
AND R1 R1 #0
ST R1 TEMP0
BRnzp J_RET_FALSE
J_RET_TRUE NOT R1 R1
ADD R1 R1 #1 ;恢复value, return
ST R1 TEMP0
J_RET_FALSE LD R0 SAVE_R0 ; 被调用者恢复
LD R1 SAVE_R1
LD R2 SAVE_R2
LD R3 SAVE_R3
LD R4 SAVE_R4
LD R5 SAVE_R5
LD R6 SAVE_R6
LD R7 SAVE_R7
RET
;
;----------------------------------------------
;函数: 实现对6执行乘法的结果
;input: TEMP0
;output: TEMP1
;这里,单独设计一群寄存器保存multi和div函数,
;防止函数在嵌套时出现内存被篡改的现象
;
SAVE_R0_ .FILL #0
SAVE_R1_ .FILL #0
SAVE_R2_ .FILL #0
;
multi ST R0 SAVE_R0_ ;被调用者保存
ST R1 SAVE_R1_
ST R2 SAVE_R2_
LD R0 TEMP0
AND R1 R1 #0 ;返回值
BRnzp MUL_MIDDLE
MUL_LOOP ADD R1 R1 #6
ADD R0 R0 #-1
MUL_MIDDLE ADD R0 R0 #0
BRp MUL_LOOP
ST R1 TEMP1 ; 函数返回值
LD R0 SAVE_R0_ ; 被调用者恢复
LD R1 SAVE_R1_
LD R2 SAVE_R2_
RET
;
;函数:除法,获得非负数对6的积和余数
;input: TEMP0
;output: TEMP1, TEMP2
;
div ST R0 SAVE_R0_ ; 被调用者保存
ST R1 SAVE_R1_
ST R2 SAVE_R2_
LD R0 TEMP0 ;余数
AND R1 R1 #0 ;积
DIV_LOOP ADD R2 R0 #-6
BRn DIV_END
ADD R1 R1 #1
AND R0 R2 R2
BRp DIV_LOOP
DIV_END ST R1 TEMP1
ST R0 TEMP2
LD R0 SAVE_R0_ ; 被调用者恢复
LD R1 SAVE_R1_
LD R2 SAVE_R2_
RET
ENDLINE1 .FILL 0x0D
;
;main function
;
main AND R1 R1 #0
ADD R1 R1 #1 ;落子方标记,1=player1,-1=player2
MAIN_LOOP JSR print
JSR isTie
LD R2 TEMP1 ;R2用于胜负判断
ADD R2 R2 #0
BRnp tie
ADD R1 R1 #0
BRn P2_DO
P1_DO LEA R0 PLAY_1
TRAP x22
BRnzp MAIN_JUDGE
P2_DO LEA R0 PLAY_2
TRAP x22
MAIN_JUDGE TRAP x20
TRAP x21
ADD R0 R0 #-16
ADD R0 R0 #-16
ADD R0 R0 #-16 ;index = ascii_code - 48
ADD R3 R0 #-1 ;index in function is from 0 to 5 in every line
LD R0 ENDLINE1
TRAP x21
ST R3 TEMP0
ST R1 TEMP1 ;we need a input
ADD R1 R1 #0
BRn P2_DO_1
P1_DO_1 JSR play1
BRnzp P_DO_END1
P2_DO_1 JSR play2
P_DO_END1 LD R3 TEMP0
ADD R3 R3 #0
BRzp MAIN_RIGHT
MAIN_ERROR ;LEA R0 ERROR
;TRAP x22
BRnzp MAIN_LOOP
MAIN_RIGHT ST R1 TEMP1
ST R3 TEMP0
JSR isOver
LD R4 TEMP0
ADD R4 R4 #0
BRnp GAME_OVER
NOT R1 R1
ADD R1 R1 #1
BRnzp MAIN_LOOP
GAME_OVER JSR print
ADD R1 R1 #0
BRp p1_win
BRn p2_win
;
;三种结局,最终分别调用这三种函数
;
tie LEA R0 TIE
TRAP x22
HALT
p1_win LEA R0 P_1_WIN
TRAP x22
HALT
p2_win LEA R0 P_2_WIN
TRAP x22
HALT
;
;常用字符串和常量
;
PLAY_1 .STRINGZ "Player 1, choose a column: "
PLAY_2 .STRINGZ "Player 2, choose a column: "
P_1_WIN .STRINGZ "Player 1 Wins.\n"
P_2_WIN .STRINGZ "Player 2 Wins.\n"
TIE .STRINGZ "Tie Game.\n"
.END