【计算机系统1】4 Nim游戏

本文详细介绍了使用LC3编程语言实现Nim游戏的过程,包括游戏界面的初始化、玩家交互、输入验证和游戏逻辑。通过子程序DISPLAY和PLAY展示了如何处理界面展示和玩家每轮操作,强调了调用者保护R7寄存器的重要性,以及字符到数字的转换和错误处理。在调试过程中,解决了返回地址保护、无效输入处理和界面更新等问题,最终实现了完整的游戏流程。
摘要由CSDN通过智能技术生成

目录

目的与要求

内容与方法

步骤与过程

程序总体设计

核心数据结构及算法流程

核心代码

调试过程

界面展示子程序DISPLAY(嵌套:球数展示子程序PUTBALL)

游戏子程序GAME(嵌套:单人每轮子程序PLAY)

结论或体会


目的与要求

  在游戏开始时,你应该显示游戏界面的初始化状态。具体包括:在每行石头的前面,你应该先输出行的名称,例如“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.

内容与方法

本次实验对Nim游戏做了一些小的改变,具体如下:游戏界面由三行组成,计数器类型为石头,其中A行包含3个石头,B行包含5个石头,C行包含8个石头。

规则如下:

每个玩家轮流从某一行中移除一个或多个石头。

一个玩家不能在一个回合中从多个行中移除石头。

当某个玩家从游戏界面上移除最后剩余的石头时,此时游戏结束,该玩家获胜。

输入输出的格式必须完全和样例中的格式相一致

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 ----- 

⑴ 记住,程序中所有的输入输出使用ASCII字符,你应该负责进行必要的转换。

⑵ 从键盘中输入字符你应该使用TRAP x20(GETC)指令,同时为了回显输入的字符到屏幕上,你应该使用TRAP x21(OUT)指令,该指令紧跟在TRAP x20指令之后。

⑶ 你应该在适当的时候使用子程序。

⑷ 在你编写的每个子程序中,应该保存并还原所使用的任何寄存器。这将避免你在调试过程中遇到问题。

在一个回合中,玩家的输入必须包含指定为A,B或C(即大写字母)的行,后面紧跟不大于该行仍然存在的石头数量的数字。

提示:

你应该设置程序的开始地址在x3000(如,程序的第一行指令应该为 .ORIG x3000)

源文件命名为nim.asm

步骤与过程

(依照实验内容,逐条撰写实验过程与实验所得结果:包括程序总体设计,核心数据结构及算法流程,调试过程。请附上核心代码,及注意格式排版的美观。实验提交时,以上为评分依据,请不删除本行)

程序总体设计

程序可大致分以下功能模块,可用子程序分别实现:

注意子程序的嵌套调用需要调用者保护R7。

每轮游戏包括2名玩家分别玩(调用PLAY子程序)。R5记录玩家身份,0为玩家1(Player1),1为玩家2(Player2),以便警告后可以正确返回到对应玩家重新开始以及判断胜负。每人每轮结束进行胜负判断,看总球数是否为0判断是否胜出。

    1. 界面展示子程序DISPLAY:先输出提示语句,在嵌套调用PUTBALL子程序输出球,ABC球数存于寄存器R1,R2,R3。
      1. 球数展示子程序PUTBALL:循环输出球,直至球数减为0。(do-while)
    2. 单人每轮子程序PLAY:根据输入的行名和球数判断是否有效,无效就警告并重新开始。注意类型转换以及数字字符(数字下限’0’)的判断。球数判断上限我采用直接取球,结果为负说明无效,改回并报错重来。警告操作包括输出警告并根据玩家身份(R5)跳转回对应起始重新开始。
  • 核心数据结构及算法流程

核心代码

	.ORIG	X3000
	LD	R1,Anum	;R1存A球数,初始3
	LD	R2,Bnum	;R2存B球数,初始5
	LD	R3,Cnum	;R3存C球数,初始7	
;
;主函数游戏部分	
player1	JSR	DISPLAY		;调用展示界面子程序
	AND	R5,R5,0		;R5清0表示为玩家1
begin1	LEA	R0,Prompt1	;传player1串
	PUTS			;输出提示语句
	JSR	PLAY		;开始游戏
	LD	R0,NewLine	;/n
	OUT
	LD	R0,NewLine	;/n
	OUT
	ADD	R4,R1,R2
	ADD	R4,R4,R3
	BRnp	player2		;球数不说明游戏继续
	LEA	R0,Win1		;总球数为0玩家1胜利
	PUTS
	BRnzp	exit		;游戏结束
	
player2	JSR	DISPLAY		;调用展示界面子程序
	ADD	R5,R5,1		;玩家2
begin2	LEA	R0,Prompt2	;传player2串
	PUTS			;输出提示语句
	JSR	PLAY		;开始游戏
	LD	R0,NewLine	;/n
	OUT
	LD	R0,NewLine	;/n
	OUT
	ADD	R4,R1,R2
	ADD	R4,R4,R3
	BRnp	player1		;球数不说明游戏继续
	LEA	R0,Win2		;总球数为0玩家2胜利
	PUTS

exit	HALT
;
;--main数据区------------
Anum	.FILL		3
Bnum	.FILL		5
Cnum	.FILL		8
NewLine	.FILL		10		;/n
Prompt1	.STRINGZ	"Player 1, choose a row and number of rocks: "	;玩家1提示语句
Prompt2	.STRINGZ	"Player 2, choose a row and number of rocks: "	;玩家2提示语句
Win1	.STRINGZ	"Player 1 Wins.";玩家1胜利语句
Win2	.STRINGZ	"Player 2 Wins.";玩家2胜利语句
;******************************************************************************************


;*******展示界面子程序**************************************
DISPLAY	ST	R7,DISPLAYR7

	LEA	R0,PromptA	;传A串
	PUTS			;输出A提示语句
	ADD	R4,R1,0	;传A球数
	JSR	PUTBALL		;调用展示球数子程序

	LEA	R0,PromptB	;传B串
	PUTS			;输出B提示语句
	ADD	R4,R2,0	;传B球数
	JSR	PUTBALL		;调用展示球数子程序

	LEA	R0,PromptC	;传C串
	PUTS			;输出C提示语句
	ADD	R4,R3,0	;传C球数
	JSR	PUTBALL		;调用展示球数子程序

	LD	R7,DISPLAYR7
	RET
;
;--DISPLAY数据区----------------------------------
DISPLAYR7 .FILL		0
PromptA	.STRINGZ	"ROW A: "	;A提示语句
PromptB	.STRINGZ	"ROW B: "	;B提示语句
PromptC	.STRINGZ	"ROW C: "	;C提示语句
;********************************************************


;********展示球数子程序****************************
PUTBALL	ST	R7,PUTBALLR7	

	LD	R0,o		;'o'

output	ADD	R4,R4,-1	;计数--
	BRn	finish		;输出完毕跳到换行	
	OUT
	BRnzp	output

finish	LD	R0,NewLine	;/n
	OUT

	LD	R7,PUTBALLR7
	RET
;
;--PUTBALL数据区-----------------------------
PUTBALLR7 .FILL		0
o	.FILL		111		;'o'	
;*******************************************************


;************每人每轮游戏子程序*************
PLAY	ST	R5,SaveR5
	ST	R7,PLAYR7	

	GETC			;获得行名
	OUT			;回显
	ADD	R6,R0,0		;将行名存于R6

	GETC			;获得取球数
	OUT			;回显	
	LD	R5,Term		;-'0'=-48
	ADD	R5,R0,R5
	BRnz	warn		

	LD	R4,nA
	ADD	R4,R6,R4	;=?'A'
	BRnp	testb		;不是就跳转查B
	NOT	R4,R5
	ADD	R4,R4,1		;-取球数
	ADD	R1,R1,R4	;取球
	BRzp	return		;取球有效返回
	ADD	R1,R1,R5	;取球无效改回
	BRnzp	warn		;警告重来

testb	LD	R4,nB
	ADD	R4,R6,R4	;=?'B'
	BRnp	testc		;不是就跳转查C
	NOT	R4,R5
	ADD	R4,R4,1		;-取球数
	ADD	R2,R2,R4	;取球
	BRzp	return		;取球有效返回
	ADD	R2,R2,R5	;取球无效改回
	BRnzp	warn		;警告重来

testc	LD	R4,nC
	ADD	R4,R6,R4	;=?'C'
	BRnp	warn		;不是就表明输入有误跳至警告
	NOT	R4,R5
	ADD	R4,R4,1		;-取球数
	ADD	R3,R3,R4	;取球
	BRzp	return		;取球有效返回
	ADD	R3,R3,R5	;取球无效改回

warn	LD	R0,NewLine	;/n
	OUT
	LEA	R0,Warning
	PUTS			;输出警告
	LD	R0,NewLine	;/n
	OUT
	LD	R5,SaveR5	;获得玩家序号
	BRz	begin1		;为0跳回玩家1
	BRp	begin2		;为1跳回玩家2
	
return	LD	R5,SaveR5
	LD	R7,PLAYR7
	RET
;
;--PLAY数据区----------------------------------------
SaveR5	.FILL		0
PLAYR7 	.FILL		0
Term	.FILL		-48		;数值ASCII转换
nA	.FILL		-65		;-'A'
nB	.FILL		-66		;-'B'
nC	.FILL		-67		;-'C'
Warning	.STRINGZ	"Invalid move. Try again.";输入无效警告
;**************************************************************

调试过程

界面展示子程序DISPLAY(嵌套:球数展示子程序PUTBALL

  • 问题1:子程序无法正确返回(图1

球数输出出错,一直输出,经调试是输出球的子程序返回报错,无法正常返回。它返回到该子程序调用的OUT的下一行代码,说明R7返回地址在嵌套调用TRAP服务程序时已被修改破坏,无法正常返回。这说明我应当做到调用者保护R7,调用前应当保护R7原值。 

图表 1 地址返回有误

于是在2个子程序都加入调用者保护R7。

ST	R7,SaveR7
LD	R7,SaveR7

  • 问题2:嵌套子程序破坏R7(图2

修改后仍然无法正确返回,这一次返回到的是嵌套调用的子程序PUTBALL的下一条代码,说明在嵌套调用子程序时破坏了R7。

图表 2 DISPALY子程序调用者保护失效

         多次嵌套使用子程序却用同一内存SaveR7保护,会导致进入嵌套子程序时SaveR7就被破坏。所以我们改变策略对每个子程序的调用者保护返回地址,都用新内存存储。

ST	R7,DISPLAYR7
LD	R7,DISPLAYR7

  • 修改成功(图3

         另外发现需要添加换行。修改后如下,结果正确:

图表 3 界面展示子程序DISPLAY和球数展示子程序PUTBALL

游戏子程序GAME嵌套:单人每轮子程序PLAY

  • 问题1:字符未转换为数字(Figure 1

运行报错,经调试发现是所取数字没有做类型转换(-48【‘0’】)。(图)

Figure 1 字符未转换为数字

  • 问题2:函数未返回Figure 2

忘加ret。

Figure 2 函数未返回

  • 问题3:2人间应当显示界面Figure 3

Figure 3 2人间应当显示界面

  • 问题4:换行破坏球数寄存器Figure 4

Figure 4 换行破坏球数寄存器

  • 问题5:无效输入后没有新的提示语句(Figure 5

Figure 5 无效输入后没有新的提示语句

  • 问题6:0球还输出(Figure 6

Figure 6

    • 问题7:缺乏判断是否是数字(Figure 7

Figure 7

  1. 运行结果正确(Figure 8 9

Figure 8 运行结果正确

Figure 9 运行结果正确

结论或体会

通过本次实验尝试运用LC3实现Nim游戏,首次尝试运用子程序结构进行代码编写,同时完成了界面实现。在该过程中我意识到对返回地址的保护(调用者保护)的重要性,尤其在嵌套调用子程序时。另外也要注意字符和数字的类型转换。以及do-while的运用避免0时仍输出。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jennie佳妮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值