实验目的
(1)分析和理解指定的需解决问题。
(2)利用LC-3的汇编语言设计实现相关程序。
(3)通过LC-3仿真器调试和运行相关程序并得到正确的结果。
实验内容
实现Nim游戏,具体规则和要求如下:
游戏界面由三行组成,计数器类型为石子,其中A行包含3个石子,B行包含5个石子,C行包含8个石子。规则如下:
(1)每个玩家轮流从某一行中移除一个或多个石子。
(2)一个玩家不能在一个回合中从多个行中移除石子。
(3)当某个玩家从游戏界面上移除最后一个石子时,此时游戏结束,该玩家战败。
实验要求
(1)在游戏开始时,你应该显示游戏界面的初始化状态。具体包括:在每行石子的前面,你应该先输出行的名称,例如“ROW A”。你应该使用ASCII字符小写字母“o”(ASCII码 x006F)来表示石子。游戏界面的初始化状态应该如下:
ROW A: ooo
ROW B: ooooo
ROW C: oooooooo
(2) 游戏总是从玩家1先开始,之后玩家1和玩家2轮流进行。在每一个回合开始时,你应该输出轮到哪一个玩家开始,并提示玩家进行操作。例如,对于玩家1,应该有如下显示:
Player 1,choose a row and number of rocks:
(3)为了指定要移除哪一行中的多少石子,玩家应该输入一个字母后跟一个数字(输入结束后不需要按Enter键),其中字母(A,B或C)指定行,数字(从1到所选行中石子的数量)指定要移除的石子的数量。你的程序必须要确保玩家从有效的行中移除有效数量的石子,如果玩家输入无效,你应该输出错误提示信息并提示该玩家再次进行输入。例如,如果轮到玩家1:
Player 1, choose a row and number of rocks: D4
Invalid move. Try again.
Player 1, choose a row and number of rocks: A9
Invalid move. Try again.
Player 1, choose a row and number of rocks: A*
Invalid move. Try again.
Player 1, choose a row and number of rocks: &4
Invalid move. Try again.
Player 1, choose a row and number of rocks:
你的程序应保持提示玩家,直到玩家选择有效的输入为止。确保你的程序能够回显玩家的输入到屏幕上,当回显玩家的输入后,此时应该输出一个换行符(ASCII码x000A)使光标指向下一行。
(4)玩家选择有效的输入后,你应该检查获胜者。如果有一个玩家获胜,你应该显示相应的输出来表明该玩家获胜。如果没有胜利者,你的程序应该更新游戏界面中每行石子的数量,重新显示更新的游戏界面,并轮到下一个玩家继续。
(5)当某个玩家从游戏界面上移除最后一个石子时,游戏结束。此时,你的程序应该显示获胜者然后停止。例如,如果玩家2移除了最后一个石子,你的程序应该输出一下内容:
Player 1 Wins.
实验步骤
一、实验分析:
1、本游戏中涉及到了多次输入,输出操作,可以用trap调用完成多次输入输出的操作,并将输入的值存在相应寄存器(或再存到内存)中。
2、由于游戏中用到了很多次的输入和判断,可以考虑采用子程序的方式,简化代码,并能使代码结构更清晰
3、由于用到了调用子程序,要记得储存和恢复R7的值,避免由于对R7进行编辑而造成主程序指针损坏而无法从子程序跳回主程序。
4、在程序运行中,要输出多次提示语,可以考虑先把提示语加载到内存中,再在运行时直接调用。
5、由于要一直存储三行的石子数量,可以考虑将一二三号寄存器的值分别代表第一二三行中的石子数。
二、程序实现:
考虑到要多次进行输入操作,判断操作,回显操作,将相应的全部写为子程序将方便很多。因此只在主程序中完成对各个子程序的调用,在主程序之外对各个子程序进行实现,将大大缩减代码量也能使代码可读性提高。
下面按照代码实现的顺序进行逐个分析讲解:
主程序:
1、完成对各个寄存器的初始化:
先将R1~R3三个寄存器清零,并将三个寄存器作为计数器用于对ABC三行石头数的计数。
2、游戏时对棋盘的显示和对各个操作的读入:
游戏时先输出一次棋盘,然后P1读入字母和数字,并判断合法性,P1读入数字之后对P1是否获胜进行判断,如果P1胜利则直接跳转至游戏结束,并输出P1胜利提示语,若P1未胜利,则继续进行P2的读写和判断。
3、胜利提示:
如果胜负已分,则程序将跳转至此处,输出相应的提示语,并跳转至程序结束。此处将加载完的字符串的首地址赋给R0直接用PUTS进行输出。
4、游戏结束:
如果胜负已分,且相应的提示语输出完毕,则将跳转至这里,并输出游戏结束语。
5、数据区:
为了方便对P1和P2获胜提示语的输出和游戏结束语的输出,将相应的提示语存入对应的内存中,加载时加载至相应的LABLE,并存入R0进而便于输出
子程序
1、输出棋盘:
游戏中经常会对棋盘进行输出,要依次对ABC三行进行输出,此处仅展示对A列石头的输出,对B列和C列石头的输出与A大致相同
再对ABC三行石头输出完毕后,跳转回主程序
数据区:
2、每次读入输入进来的字母和数字:
每次需要读入数字和字母并对数字和字母和合法性进行检查再检查是否有玩家胜利,先完成对字符和数字的读入(此处仅对玩家1的操作进行展示,玩家2的与1的大致相同):
判断字母合法性:
判断数值合法性,在判断数值合法性时,需要分成ABC三种情况进行分开判断,并检查是否移除的石子数小于该行有的石子数且大于0此时将对应字母的ASCII值减去B的ASCII值,此时可以直接用BR进行判断三个字母,比较方便
此处将分别对输入的字母是ABC进行判断,如果合法则继续判断是否分出胜负,若分出胜负则将胜利玩家标记,并跳回主程序输出对应的胜利提示语并结束程序。若未分出胜负则继续输入,继续进行下一次读写和判断。
如果输入不合法,则跳回输入程序并给出对应的错误提示语,并准备进行下一次的输入和合法性及输赢的判断。
此处以输入A为例进行展示,对B、C的输入与A的大致相同:
再进行胜负判断
如果输入时不合法,则将跳转至ERROR2处,进行错误信息的输出,在错误信息输出后,恢复R7的值并跳回主程序
数据区:
三、测试游戏:
1、在simulator中加载对应的obj文件:
2、测试对非法字母的判断
3、测试对越界数值的判断
4、测试合法数据的操作
5、测试胜负的判断
经过对各个情况的测试,游戏均能正常无误运行,本次编程正确
实验结论
经过本次实验,我感受颇多,与以往较短代码汇编实验不同,本次汇编实验代码较长(300行+,2000字+)因此对思维能力有较高的要求,经过实验有以下感悟:
1、如果代码较长,要特别注意LD等指令的作用范围,如果超过256bit则要对代码进行缩减或者调换代码位置,避免由于代码过长造成标签超过范围
2、当遇到需要反复执行的操作(如本实验的读字符和数字,显示棋盘等)可以考虑使用子程序的方式,不仅可以大大缩减代码量,也可以使代码可读性提高,但需要尤其注意对R7中值的储存与恢复
3、使用一些技巧可以减少代码量,并使程序更清晰,如在区分ABC三个字母时,分别加上B的ASCII码的相反数,分别将三个数转换为-1,0,1对应正零负,从而可以直接使用BR进行条件跳转。
实验源码(仅供参考)
.ORIG x3000; ;主程序 ;初始化寄存器 AND R1,R1,#0; A行的石头数 AND R2,R2,#0; B行的石头数 AND R3,R3,#0; C行的石头数 ADD R1,R1,#3 ADD R2,R2,#5 ADD R3,R3,#8 LD R5,ASCB;储存B的ASCII码 LD R6,NUM;储存数字1 ; LOOP JSR PRINT;输出棋盘 JSR INP1;P1输入字母及数字 LD R4,WINNERA ADD R4,R4,#-1 BRz WINNER1;判断玩家1是否胜利 JSR UPD;更新棋盘 JSR PRINT;输出棋盘 JSR INP2;P2输入字母及数字 LD R4,WINNERB; ADD R4,R4,#-2 BRz WINNER2; 判断玩家2是否胜利利 JSR UPD;更新棋盘 BRnzp LOOP;若胜负未分继续游戏 ; WINNER1 LD R0,ENDL OUT;换行 LEA R0,P1WIN PUTS;输出“Player 1 Wins.” BRnzp Over ; WINNER2 LD R0,ENDL OUT;换行 LEA R0,P2WIN PUTS;输出“Player 2 Wins.” BRnzp Over ; Over LD R0,ENDL OUT ;换行 LEA R0,OVER PUTS ;输出结束语 HALT ASCB .FILL x42 NUM .FILL #0 P1WIN .STRINGZ "Player 1 Wins." P2WIN .STRINGZ "Player 2 Wins." WINNERA .BLKW 1 OVER .STRINGZ "----- Halting the processor -----" ;以下为子程序 UPD ADD R1,R1,#0 BRnp RETURN;若A行的石头数不为0,则跳出子程序 ADD R2,R2,#0 BRnp RETURN;若B行的石头数不为0,则跳出子程序 ADD R3,R3,#0 BRnp RETURN;若C行的石头数不为0,则跳出子程序 ADD R1,R1,#3 ADD R2,R2,#5 ADD R3,R3,#8;将棋盘初始化 RETURN RET ; ;打印棋盘 PRINT NOT R6,R6 ADD R6,R6,#1;将字符型转为整型 LD R4,FB ADD R5,R5,R4;将字母A,B,C对应为-1,0,1 ST R7,SAVER7;保存主程序指针 LD R0,ENDL OUT;换行 LEA R4,ROWA ADD R0,R4,#0 PUTS;输出“ROW A: ” LD R0,STONE ADD R4,R1,#0;将R4作为A的计数器输出石头 RAA BRz RA OUT ADD R4,R4,#-1 BRnzp RAA ; RA LD R0,ENDL OUT LEA R4,ROWB ADD R0,R4,#0 PUTS;输出“ROW B: ” LD R0,STONE ADD R4,R2,#0;将R4作为B的计数器输出石头 RBB BRz RB OUT ADD R4,R4,#-1 BRnzp RBB ; RB LD R0,ENDL OUT LEA R4,ROWC ADD R0,R4,#0 PUTS;输出“ROW C: ” LD R0,STONE ADD R4,R3,#0;将R4作为C的计数器输出石头 RCC BRz RC OUT ADD R4,R4,#-1 BRnzp RCC ; RC LD R0,ENDL OUT;换行 LD R7,SAVER7;将主程序的地址传给R7 RET; FA .FILL xFFBF FB .FILL xFFBE STONE .FILL x006F ENDL .FILL x000A SAVER7 .BLKW 1 ROWA .STRINGZ "ROW A: " ROWB .STRINGZ "ROW B: " ROWC .STRINGZ "ROW C: " WINNERB .BLKW 1 INP1 ST R7,SAVER7;保存R7 AGAIN1 ;输出提示信息 LEA R0,Player1 PUTS;输出P1的提示语 GETC;输入字母 OUT;回显 ADD R5,R0,#0;将输入的字母的ASCII码赋给R5 GETC;输入数字 OUT;回显 LD R6,NUMASC;将'0'的ASCII码的相反数赋给R6 ADD R6,R0,R6;将输入数字的ASCII码转化为整型 LD R0,ENDL OUT;换行 ADD R6,R6,#0 BRn ERROR2;若输入的ASCII码小于'0'的ASCII码,则输入错误 ;判断字母合法性 LD R4,FA;将'A'的ASCII码的相反数赋给R4 ADD R4,R5,R4;将A,B,C对应为0,1,2 BRn ERROR1;若R4为负,则输入的字符出错 ADD R4,R4,#-2;将A,B,C对应为-2,-1,0 BRp ERROR1;若R4大于0,则输入的字符出错 BRnzp RIGHT1;输入正确 ERROR1 LEA R0,Tagain PUTS;输出错误信息 LD R0,ENDL OUT;换行 BRnzp AGAIN1;若字符输入错误,则重新输入 ;判断数字合法性 RIGHT1 ADD R4,R6,#0;将输入的数字赋给R4 NOT R4,R4 ADD R4,R4,#1;取相反反数 LD R0,FB ADD R0,R5,R0;将A,B,C对应为-1,0,1 ;若输入A BRzp A1;若输入的字母为A ADD R7,R4,R1 BRn ERROR2;若输入的数字大于A的石头数,则输入错误 ADD R1,R7,#0;若输入正确,则更新A的石头数 AND R7,R7,#0; ADD R7,R7,R1; ADD R7,R7,R2; ADD R7,R7,R3;判断石头总数 BRz WIN111;若更新后的石头数为0 BRnzp JUMP2 ;判断胜负 WIN111 AND R4,R4,#0 ADD R4,R4,#1;玩家1胜利,将R4标记为1 ST R4,WINNERA;将R4存储在内存中 BRnzp JUMP2 ;若输入B A1 ADD R0,R0,#0 BRp A2; ADD R7,R4,R2 BRn ERROR2;若输入的数字大于B的石头数,则输入错误 ADD R2,R7,#0;若输入正确,则更新B的石头数 AND R7,R7,#0; ADD R7,R7,R1; ADD R7,R7,R2; ADD R7,R7,R3;判断石头总数 BRz WIN112;该子程序判断玩家1是否胜利;若更新后的石头数为0 BRnzp JUMP2 WIN112 AND R4,R4,#0 ADD R4,R4,#1;若输入数字为1,且石头数为2,则该玩家1胜利,并将R4标记为1 ST R4,WINNERA;将R4存储在内存中 BRnzp JUMP2 ;若输入C' A2 ADD R7,R4,R3 BRn ERROR2;若输入的数字大于C的石头数,则输入错误 ADD R3,R7,#0;若输入正确,则更新C的石头数 AND R7,R7,#0; ADD R7,R7,R1; ADD R7,R7,R2; ADD R7,R7,R3; BRz WIN113;该子程序判断玩家1是否胜利;若更新后的石头数为0 BRnzp JUMP2 WIN113 AND R4,R4,#0 ADD R4,R4,#1;若输入数字为1,且石头数为2,则该玩家1胜利,并将R4标记为1 ST R4,WINNERA;将R4存储在内存中 BRnzp JUMP2 ERROR2 LEA R0,Tagain PUTS;输出“Invalid move. Try again.” LD R0,ENDL OUT;换行 BRnzp AGAIN1;重新输入 JUMP2 LD R7,SAVER7;恢复R7 RET INP2 ST R7,SAVER7 AGAIN2 LEA R0,Player2 PUTS; 输出P1的提示语 GETC;输入字母 OUT ADD R5,R0,#0;将输入的字母的ASCII码赋给R5 GETC;输入数字 OUT;回显 LD R6,NUMASC;将'0'的ASCII码的相反数赋给R6 ADD R6,R0,R6;将字符型转为整型 LD R0,ENDL OUT;换行 ADD R6,R6,#0 BRn ERROR4;若输入的ASCII码小于'0'的ASCII码,则输入错误 ;判断字母合法性 LD R4,FA;将'A'的ASCII码的相反数赋给R4 ADD R4,R5,R4;将A,B,C对应为0,1,2 BRn ERROR3;若R4为负,则输入的字符出错 ADD R4,R4,#-2;将A,B,C对应为-2,-1,0 BRp ERROR3;若R4大于0,则输入的字符出错 BRnzp RIGHT2;若不满足以上两个条件,则输入的字母为A,B,C中的其中一个,输入正确 ERROR3 LEA R0,Tagain PUTS;若字符输入错误,则输出“Invalid move. Try again.” LD R0,ENDL OUT;换行 BRnzp AGAIN2;若字符输入错误,则重新输入 ;判断数字合法性 RIGHT2 ADD R4,R6,#0;将输入的数字赋给R4 NOT R4,R4 ADD R4,R4,#1;取输入的数字的相反数并赋给R4 LD R0,FB ADD R0,R5,R0;将A,B,C对应的为-1,0,1 BRzp BB1;若输入的字母为A ADD R7,R4,R1 BRn ERROR4;若输入的数字大于A的石头数,则输入错误 ADD R1,R7,#0;若输入正确,则更新A的石头数 AND R7,R7,#0; ADD R7,R7,R1; ADD R7,R7,R2; ADD R7,R7,R3;判断石头总数 BRz WIN211;若更新后的石头数为0 BRnzp JUMP3 ;判断是否胜利 WIN211 AND R4,R4,#0 ADD R4,R4,#2;若玩家2胜利 ST R4,WINNERB;将R4存储在内存中 BRnzp JUMP3 ; BB1 ADD R0,R0,#0 BRp B2;若输入的字母为B ADD R7,R4,R2 BRn ERROR2;若输入的数字大于B的石头数,则输入错误 ADD R2,R7,#0;若输入正确,则更新B的石头数 AND R7,R7,#0; ADD R7,R7,R1; ADD R7,R7,R2; ADD R7,R7,R3;判断石头总数 BRz WIN212;若更新后的石头数为0 BRnzp JUMP3 ; WIN212 AND R4,R4,#0 ADD R4,R4,#2;若输入数字为1,且石头数为0,则该玩家2胜利,并将WINNER为在的内存空间标记为2 ST R4,WINNERB;将R4存储在内存中 BRnzp JUMP3 ;若输入C B2 ADD R7,R4,R3 BRn ERROR4;若输入的数字大于C的石头数,则输入错误 ADD R3,R7,#0;若输入正确,则更新C的石头数 AND R7,R7,#0; ADD R7,R7,R1; ADD R7,R7,R2; ADD R7,R7,R3;判断石头总数 BRz WIN213;若更新后的石头数为0 BRnzp JUMP3 WIN213 AND R4,R4,#0 ADD R4,R4,#2;若输入数字为1,且石头数为0,则该玩家2胜利,并将WINNER为在的内存空间标记为2 ST R4,WINNERB;将R4存储在内存中 BRnzp JUMP3 ERROR4 LEA R0,Tagain PUTS;输出“Invalid move. Try again.” LD R0,ENDL OUT;换行 BRnzp AGAIN2 JUMP3 LD R7,SAVER7;恢复R7 RET ; NUMASC .FILL xFFD0 Tagain .STRINGZ "Invalid move. Try again." Player1 .STRINGZ "Player 1, choose a row and number of rocks: " Player2 .STRINGZ "Player 2, choose a row and number of rocks: " .END