计算机系统1 实验 LC-3 Nim游戏 子程序

写在前面

今天找时间把LC-3的Nim游戏(实验四、五)给做了,浅浅地分享一下吧。

一、游戏背景和规则

背景:Nim是一个简单的双人游戏,可能起源于中国。游戏中使用的计数器类型有很多种类,如石头、火柴、苹果等。游戏界面被划分为很多行,每行中有数量不等的计数器,如表所示:

行号计数器数量
1○○○
2○○○○○○
······
n○○○○○○○○○

本次实验对Nim游戏做了一些小的改变,具体如下:
游戏界面由三行组成,计数器类型为石头,其中A行包含3个石头,B行包含5个石头,C行包含8个石头。
规则如下:
⑴ 每个玩家轮流从某一行中移除一个或多个石头。
⑵ 一个玩家不能在一个回合中从多个行中移除石头。
⑶ 当某个玩家从游戏界面上移除最后剩余的石头时,此时游戏结束,该玩家获胜。

二、实验要求

⑴ 在游戏开始时,你应该显示游戏界面的初始化状态。具体包括:在每行石头的前面,你应该先输出行的名称,例如“ROW A”。你应该使用ASCII字符小写字母“o”(ASCII码 x006F)来表示石头。游戏界面的初始化状态应该如下:

ROW A: ooo
ROW B: ooooo
ROW C: oooooooo

⑵ 游戏总是从玩家1先开始,之后玩家1和玩家2轮流进行。在每一个回合开始时,你应该输出轮到哪一个玩家开始,并提示玩家进行操作。例如,对于玩家1,应该有如下显示:

Player 1,choose a row and number of rocks:

⑶ 为了指定要移除哪一行中的多少石头,玩家应该输入一个字母后跟一个数字(输入结束后不需要按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)使光标指向下一行。
⑷ 玩家选择有效的输入后,你应该检查获胜者。如果有一个玩家获胜,你应该显示相应的输出来表明该玩家获胜。如果没有胜利者,你的程序应该更新游戏界面中每行石头的数量,重新显示更新的游戏界面,并轮到下一个玩家继续。
⑸ 当某个玩家从游戏界面上移除最后的石头时,游戏结束。此时,你的程序应该显示获胜者然后停止。例如,如果玩家2移除了最后的石头,你的程序应该输出一下内容:

Player 2 Wins.

三、样例输入/输出

据说要和样例输出一模一样

ROW A: ooo
ROW B: ooooo
ROW C: oooooooo
Player 1, choose a row and number of rocks: B2

ROW A: ooo
ROW B: ooo
ROW C: oooooooo
Player 2, choose a row and number of rocks: A1

ROW A: oo
ROW B: ooo
ROW C: oooooooo
Player 1, choose a row and number of rocks: C6

ROW A: oo
ROW B: ooo
ROW C: oo
Player 2, choose a row and number of rocks: G1
Invalid move. Try again.
Player 2, choose a row and number of rocks: B3

ROW A: oo
ROW B: 
ROW C: oo
Player 1, choose a row and number of rocks: A3
Invalid move. Try again.
Player 1, choose a row and number of rocks: C2

ROW A: oo
ROW B: 
ROW C: 
Player 2, choose a row and number of rocks: A1

ROW A: o
ROW B: 
ROW C: 
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: A1

Player 1 Wins.
----- Halting the processor ----- 

四、代码编写

数据存放

1、程序从x3000开始存放
2、每行石头的数量从x3200开始存放,且以-1结尾作为结束标志,以支持非题目要求的D~Z行(属于是自找麻烦了)

编写代码

注意:
以下代码片段并非代码实际顺序。
并且子程序没有严格地保存和恢复寄存器状态,这是不对滴。

1、石头状态输出子程序:

;石头状态输出
;R1-石头数量地址,R2-数量
FUNC_DRAW		ST  R7,SAVER7		;保存R7的值,因为PUTS和OUT会对其进行修改
				LD  R1,PTR_STONES	;石头堆首地址
				LD  R3,CHAR_ROWBASE	;'A'的ASCII码
  ;遍历石头堆
LOOP_1			LDR R2,R1,#0		;读取当前行石头数量
				BRn END_DRAW		;若数字为-1,结束输出
				LEA R0,STR_ROW
				PUTS
				AND R0,R0,#0
				ADD R0,R3,#0
				OUT
				LEA R0,STR_ROW2
				PUTS
				LD  R0,CHAR_SYMBOL	;
				ADD R2,R2,#0		;将CC恢复为R2
    ;循环输出o
LOOP_2			BRz OUT_2			;若数字R2降到0,结束当前循环
				OUT	
				ADD R2,R2,#-1
				BRnzp LOOP_2
OUT_2			LD  R0,CHAR_LF
				OUT
				ADD R1,R1,#1
				ADD R3,R3,#1		
				BRnzp LOOP_1
END_DRAW		LD R7,SAVER7
				RET

2、检查石头是否被取完:

;检查石头是否被取完了
;Output(R6):没取完(0),取完了(1)
FUN_CHECK		LD  R1, PTR_STONES	;R1指向石头
				AND R6, R6, #0
				ADD R6, R6, #1		;初始化R6为1
SUB_CHECK_LOOP	LDR R2, R1, #0		;读取当前行石头数
				BRp SUB_CHECK_FALSE	;任意行非零则返回0
				BRn SUB_CHECK_END	;遇到-1,说明石头数全为0,返回1
				ADD R1, R1, #1
				BRnzp SUB_CHECK_LOOP	

SUB_CHECK_FALSE ADD R6, R6, #-1		;R6置为0
SUB_CHECK_END	RET

3、判断当前输入的合法性并取走石头:

;判断当前的输入是否还有剩余,若无剩余输出错误信息
;Input(PTR_ROW,PTR_CNT)  Output(R6):非法输入(0),取走前一切正常(n)
FUN_HASLEFT		ST  R7, SAVER7		;保存R7,因为PUTS
				LD  R1, PTR_STONES	;R1指向首堆石头
				LD  R2, PTR_ROW
				LD  R3, NCHAR_A
				ADD R2, R2, R3		;行距离A的偏移
				BRn SUB_HAS_ERROR	;行号非法
				ADD R1, R1, R2		;指向输入指定的行
				AND R6, R6, #0
				LDR R2, R1, #0
				BRnz SUB_HAS_ERROR	;剩下的石头<=0,可同时判断行号非法和当前行为0
				ADD R6, R6, R2		;R6 = R2
SUB_HAS_SUB		LD  R2, PTR_CNT		;读取输入的数字
				LD  R3, NCHAR_0
				ADD R2, R2, R3		;ASCII -> num
				BRnz SUB_HAS_ERROR	;数字非法
				NOT R2, R2
				ADD R2, R2, #1		;R2 = -R2
				ADD R4, R6, R2		;R4 = 该行剩余 - 取走数量
				BRn SUB_HAS_ERROR	;该行剩下的石头不够
				STR R4, R1, #0		;更新石头数量
				BRnzp SUB_HAS_END
SUB_HAS_ERROR	AND R6, R6, #0		;需要返回的0
				LD  R0, CHAR_LF
				OUT
				LEA R0, STR_INVALID
				PUTS
				LD  R0, CHAR_LF
				OUT
SUB_HAS_END		LD  R7, SAVER7		;载入R7
				RET

4、行号及数量输入:

;输入行号(ABCDEFGHIJKLMNOPQRSTUVWXYZ)
INPUT_ROW		ST  R7, SAVER7
				GETC
				OUT
				ST  R0, PTR_ROW
				LD  R7, SAVER7
				RET

;输入个数(123456789)
INPUT_CNT		ST  R7, SAVER7
				GETC
				OUT
				ST  R0, PTR_CNT
				LD  R7, SAVER7
				RET

;此处用PTR命名是因为原来是打算用LDI和STI的,后来改用LD和ST懒得改了哈哈哈
PTR_ROW			.BLKW 1			;储存输入的行号(ASCII码A~Z)
PTR_CNT			.BLKW 1			;储存输入的数字(ASCII码0~9)
PTR_PLAYER		.FILL x0000		;储存当前玩家,0为P1,1为P2

5、主程序及玩家控制:

;主程序
;FUNC_MAIN
LOOP_PLAY		JSR FUNC_DRAW		;开局先输出一次石堆
				LD  R6, PTR_PLAYER
				BRz FUNC_P1			;为0就是玩家1
				BRp FUNC_P2			;为1就是玩家2
PLAY_AFTER		LD  R0, CHAR_LF
				OUT					;输出换行
				JSR FUNC_CHANGE		;切换玩家
				BRnzp LOOP_PLAY		;循环就是了,跳出在玩家控制中

;玩家1控制
FUNC_P1			LEA R0, STR_PLAYER1	;输出游戏提示
				PUTS
				JSR INPUT_ROW		;输入行号
				JSR INPUT_CNT		;输入数量
				JSR FUN_HASLEFT		;进行一个的判断和取石头
				ADD R6, R6, #0		;刷新CC
				BRnz FUNC_P1		;非法输入,再来
				LD  R0, CHAR_LF
				OUT					;为了让输出和样例一模一样
				JSR FUN_CHECK
				ADD R6, R6, #0		;刷新CC
				BRnz PLAY_AFTER		;R6=0,还没赢
				LD  R0, CHAR_LF		;R6=1,赢了
				OUT
				LEA R0, STR_WIN_1
				PUTS
				BRnzp _END
		
;玩家2控制(结构同玩家1)
FUNC_P2			LEA R0, STR_PLAYER2
				PUTS
				JSR INPUT_ROW		;输入行号
				JSR INPUT_CNT		;输入数量
				JSR FUN_HASLEFT		;进行一个的判断和取石头
				ADD R6, R6, #0		;刷新CC
				BRnz FUNC_P2		;非法输入,再来
				LD  R0, CHAR_LF
				OUT					;为了让输出和样例一模一样
				JSR FUN_CHECK
				ADD R6, R6, #0		;刷新CC
				BRnz PLAY_AFTER		;R6=0,还没赢
				LD  R0, CHAR_LF		;R6=1,赢了
				OUT
				LEA R0, STR_WIN_2
				PUTS
				BRnzp _END		

;玩家切换子程序(没有用到TRAP故)
FUNC_CHANGE	LD  R6, PTR_PLAYER
				BRz SUB_ZERO
				ADD R6, R6, #-1		;如果是1就切0
				ST  R6, PTR_PLAYER
				BRnzp SUB_CHANGE_RET
SUB_ZERO		ADD R6, R6, #1		;如果是0就切1
				ST  R6, PTR_PLAYER
SUB_CHANGE_RET	RET

完整代码如下(仅供参考,建议独立思考,所以我删掉了完整代码中的很多注释哈哈哈哈):

				.ORIG x3000

;主程序
;FUNC_MAIN	
LOOP_PLAY		JSR FUNC_DRAW
		
				LD  R6, PTR_PLAYER
				BRz FUNC_P1
				BRp FUNC_P2
PLAY_AFTER		LD  R0, CHAR_LF
				OUT			;LF
				JSR FUNC_CHANGE
				BRnzp LOOP_PLAY

;玩家1
FUNC_P1			LEA R0, STR_PLAYER1
				PUTS
				JSR INPUT_ROW
				JSR INPUT_CNT
				JSR FUN_HASLEFT
				ADD R6, R6, #0
				BRnz FUNC_P1
				LD  R0, CHAR_LF
				OUT
				JSR FUN_CHECK
				ADD R6, R6, #0
				BRnz PLAY_AFTER
				LD  R0, CHAR_LF
				OUT
				LEA R0, STR_WIN_1
				PUTS
				BRnzp _END
		
;玩家2
FUNC_P2			LEA R0, STR_PLAYER2
				PUTS
				JSR INPUT_ROW
				JSR INPUT_CNT
				JSR FUN_HASLEFT
				ADD R6, R6, #0
				BRnz FUNC_P2
				LD  R0, CHAR_LF
				OUT
				JSR FUN_CHECK
				ADD R6, R6, #0
				BRnz PLAY_AFTER
				LD  R0, CHAR_LF
				OUT
				LEA R0, STR_WIN_2
				PUTS
				BRnzp _END		

;玩家切换子程序	
FUNC_CHANGE	LD  R6, PTR_PLAYER
				BRz SUB_ZERO
				ADD R6, R6, #-1
				ST  R6, PTR_PLAYER
				BRnzp SUB_CHANGE_RET
SUB_ZERO		ADD R6, R6, #1
				ST  R6, PTR_PLAYER
SUB_CHANGE_RET	RET

;输入行号(ABCDEFGHIJKLMNOPQRSTUVWXYZ)
INPUT_ROW		ST  R7, SAVER7
				GETC
				OUT
				ST  R0, PTR_ROW
				LD  R7, SAVER7
				RET

;输入个数
INPUT_CNT		ST  R7, SAVER7
				GETC
				OUT
				ST  R0, PTR_CNT
				LD  R7, SAVER7
				RET

PTR_ROW			.BLKW 1
PTR_CNT			.BLKW 1
PTR_PLAYER		.FILL x0000

;石头状态输出
;R1-石头数量地址,R2-数量
FUNC_DRAW		ST  R7,SAVER7
				LD  R1,PTR_STONES
				LD  R3,CHAR_ROWBASE
  ;遍历石头堆
LOOP_1			LDR R2,R1,#0
				BRn END_DRAW
				LEA R0,STR_ROW
				PUTS
				AND R0,R0,#0
				ADD R0,R3,#0
				OUT
				LEA R0,STR_ROW2
				PUTS
				LD  R0,CHAR_SYMBOL
				ADD R2,R2,#0
    ;循环输出o
LOOP_2			BRz OUT_2
				OUT	
				ADD R2,R2,#-1
				BRnzp LOOP_2
OUT_2			LD  R0,CHAR_LF
				OUT
				ADD R1,R1,#1
				ADD R3,R3,#1		
				BRnzp LOOP_1
END_DRAW		LD R7,SAVER7
				RET
		
		
;检查石头是否完了
;Output(R6):false(0),true(1)
FUN_CHECK		LD  R1, PTR_STONES
				AND R6, R6, #0
				ADD R6, R6, #1
SUB_CHECK_LOOP	LDR R2, R1, #0
				BRp SUB_CHECK_FALSE
				BRn SUB_CHECK_END
				ADD R1, R1, #1
				BRnzp SUB_CHECK_LOOP	

SUB_CHECK_FALSE ADD R6, R6, #-1
SUB_CHECK_END	RET

;判断当前的输入是否还有剩余,若无剩余输出错误信息
;Input(x5001)  Output(R6):false(0),p-left(n)
FUN_HASLEFT		ST  R7, SAVER7
				LD  R1, PTR_STONES
				LD  R2, PTR_ROW
				LD  R3, NCHAR_A
				ADD R2, R2, R3
				BRn SUB_HAS_ERROR
				ADD R1, R1, R2
				AND R6, R6, #0
				LDR R2, R1, #0
				BRnz SUB_HAS_ERROR
				ADD R6, R6, R2
SUB_HAS_SUB		LD  R2, PTR_CNT
				LD  R3, NCHAR_0
				ADD R2, R2, R3
				BRnz SUB_HAS_ERROR
				NOT R2, R2
				ADD R2, R2, #1
				ADD R4, R6, R2
				BRn SUB_HAS_ERROR
				STR R4, R1, #0
				BRnzp SUB_HAS_END
SUB_HAS_ERROR	AND R6, R6, #0
				LD  R0, CHAR_LF
				OUT
				LEA R0, STR_INVALID
				PUTS
				LD  R0, CHAR_LF
				OUT
SUB_HAS_END		LD  R7, SAVER7
				RET

_END			HALT

CHAR_ROWBASE	.FILL x0041	;'A'的ASCII码
NCHAR_A			.FILL xFFBF	;'A'的负值
NCHAR_0			.FILL xFFD0	;'0'的负值
CHAR_SYMBOL		.FILL x006f	;'o'的ASCII码
CHAR_LF			.FILL x000A	;换行符的ASCII
NCHAR_LF		.FILL xFFF6	;LF的负值
PTR_STONES		.FILL x3200	;石头数量信息储存起点

STR_ROW			.STRINGZ "ROW "
STR_ROW2		.STRINGZ ": "
STR_PLAYER1		.STRINGZ "Player 1, choose a row and number of rocks:"
STR_PLAYER2		.STRINGZ "Player 2, choose a row and number of rocks:"
STR_WIN_1		.STRINGZ "Player 1 Wins."
STR_WIN_2		.STRINGZ "Player 2 Wins."
STR_INVALID		.STRINGZ "Invalid move. Try again."

SAVER7			.BLKW 1		;保存R7

.END

五、小小总结

这次的实验长度(难度)肯定是比上次要翻倍的,不过有了子程序思想的加持,以及对LC-3汇编指令理解的加深,感觉也不是特别的难。
以上代码可能仍存在一些问题,但毕竟只是个实验嘛,凑合着做做(逃)~

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值