LC3简易版四子棋

详情移步blog

题目要求

四子棋是一款普遍流行的简易型桌面游戏,据说,虎克船长曾因专注于此游戏而长期隐身在住所,当船员们发现船长的这一专长之后,他们称这个游戏为“船长的情妇”。

       四子棋是个双人游戏,两人轮流下棋,棋盘由行和列组成的网格,每个选手每次下一个子直到两人中有一人的棋子连成一条水平线、垂直线或者是对角线。

       本实验需要在LC-3中实现简易版四子棋的游戏,两位选手通过键盘和输出窗口轮流交互操作,棋盘由6 X 6的网格组成。

  •        两位选手依次轮流落子;
  •        选手不能悔棋;
  •        有子的地方不能继续落子;
  •        直到有一方的四个棋子能够连成一条水平线、垂直线或者是对角线;
  •        如果棋盘已满,无人获胜,则平局。

       游戏最初时应该打印空的棋盘,可以用ASCII码"-" (即ASCII 码 x002D)来表示该处为空,"O"(ASCII 码 x004F)表示第一位选手的棋子,"X" (ASCII 码 x0058)来表示第二位选手的棋子,为了让棋盘更易于观察,在各列间加一个空格,第6列之后不要添加。

选手一始终先下第一步棋,然后两者轮流落子,在每次落子之后,应该打印该选手的信息,提示他落子,以选手一为例,应该打印信息如下:

Player 1, choose a column:

       为了明确选手的落子的位置,该选手应该输入数字1-6,然后回车,数字1-6指示在落子所在的列,从左到右,无需输入行号,程序应默认从行号6到行号1递减的顺序填入该棋子,若前后输入的列号相同,则行号减一。例如,如果选手第一次在左起第二列落子,应该输入2,然后回车,则该棋子落在行6列2处,当后面输入的列号再次为2时,则将棋子落子行5列2处,以此类推,详情见后续示例输出。程序应该确保选手输入的数字对应正确的列的范围,如果输入不合理,应该输出一条错误信息,提示该选手继续输入,例如,如果对于选手一:

    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:

       程序应该一直提示该选手,知道输入正确的数字,当用户输入完成,程序应通过显示回馈给选手,然后通过换行符(ASCII 码 x000A)换行。

       当选手输入成功后,程序应打印更新后的棋盘,并检查是否有人获胜,如果没人获胜,则轮到下一位输入。

       当其中一位获胜或平局时,游戏结束,程序显示最后的棋盘情况并终止(Halt)。例如,如果选手二有四子相连,应该输出:

Player 2 Wins.

如果平局,程序应该输出:

Tie Game.

示例输出

- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
Player 1, choose a column: 1
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
O - - - - - 
Player 2, choose a column: 2
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
O X - - - - 
Player 1, choose a column: 2
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
- O - - - - 
O X - - - - 
Player 2, choose a column: 3
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
- O - - - - 
O X X - - - 
Player 1, choose a column: 3
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
- O O - - - 
O X X - - - 
Player 2, choose a column: 1
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
X O O - - - 
O X X - - - 
Player 1, choose a column: 4
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
X O O - - - 
O X X O - - 
Player 2, choose a column: 4
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
X O O X - - 
O X X O - - 
Player 1, choose a column: 3
- - - - - - 
- - - - - - 
- - - - - - 
- - O - - - 
X O O X - - 
O X X O - - 
Player 2, choose a column: 4
- - - - - - 
- - - - - - 
- - - - - - 
- - O X - - 
X O O X - - 
O X X O - - 
Player 1, choose a column: 4
- - - - - - 
- - - - - - 
- - - O - - 
- - O X - - 
X O O X - - 
O X X O - - 
Player 1 Wins.
----- Halting the processor ----- 

思路

代码流程

  • LOOP
    • 更换玩家
    • 展示地图
    • 输入列号
      • 不是1-6重新输入
      • 这一列没有空位重新输入
    • 修改地图内存实现落子
    • 判断胜利(子程序)
    • 判断平局(子程序)

难点

地图存储

一行加上换行刚好12个字符,所以使用一个内存指针R5访问地图,初始化为MAP的地址,移动到下一行+12,移动到下一列+2

判断是不是1-6

使用两个label存储1和6的ascall码,然后比较即可

BRnzp #2
NUMBER1 .FILL x0031
NUMBER6 .FILL x0036
;判断输入字符是否可行
LD R0,NUMBER1
NOT R0,R0
ADD R0,R0,#1;正数取反
ADD R0,R0,R2
BRn INVAL;小于1违法
LD R0,NUMBER6
NOT R0,R0
ADD R0,R0,#1;正数取反
ADD R0,R0,R2
BRp INVAL;大于6违法

判断是否有空位落子

内存开辟一段6位的空间,用ACCOUNT标记,存放每一列已落棋子个数,落子时从内存中取出对应值,判断是否小于6即可。然后将行存储下来(我赋值给了R3,从0开始)

;判断有没有空位
LEA R0, ACCOUNT 
ADD R0,R0,R2
LDR R3,R0,#0
ADD R3,R3,#-6
BRzp INVAL;没有空位,违法,重新输入
ADD R3,R3,#7
STR R3,R0,#0;更新account记录
NOT R3,R3
ADD R3,R3,#7;R3现在为行坐标(0开始)

修改地图实现落子

采用指针R5来定位,初始化为MAP首地址,然后+12*行+2*列即可定位(PS:我的行和列从0开始),然后LDR R4,R5,#0即可将当前玩家对应的子存到地图中

;修改地图
ADD R0,R3,#1;
LEA R5,MAP;R5为地图指针
LOOP1 
  ADD R0,R0,#-1
  BRnz LOOPEND1
  ADD R5,R5,#12
  BRnzp LOOP1
LOOPEND1
ADD R5,R5,R2;
ADD R5,R5,R2;R5指针定位完毕
STR R4,R5,#0

判断胜利

该实验最大的难点是如何判断胜利,因为LC3的寄存器有限,无法像C++那样多重循环去判断。所以我分成两步进行

  1. 计算八个方向(上下左右,右上右下左上左下)以落子点开始的连续相同棋子个数(不计入当前落子)
    • 每个方向使用循环一直将R3,R2,R5(行,列,map内存指针)往对应方向变,直到出现和当前落子不相等的符号,跳出循环,存储连续个数R0
    • 注意在取R5对应的字符前判断是否越界,越界跳出循环,存储连续个数R0
    • 在每个方向的循环前有个存储R0,加载R2,R3,R5的过程
  2. 按照左右,上下,主对角线,副对角线4个方向计算连续棋子个数是否>=4(eg上+下,左上+右下)
    • 占用8位内存的ACCOUNT2按顺序存储右,左,上,下,右上,右下,左上,左下的连续个数

如果胜利BR到胜利逻辑然后HALT,否则RET(JMP R7简写)

判断平局

这个简单,将ACCOUNT中所有列的落子个数相加然后和36比较就行,注意ADD一次最多加15

如果平局BR到平局逻辑然后HALT,否则RET

一些思考

本来我没用JSR,但是JUDGE_WIN逻辑的代码太多了,导致BR的PC_offset9不够用,只能用11位的JSR了

除此之外要充分利用BR的负数部分,可以吧结尾的.STRINGZ / .FILL / .BLKW放一些在程序开始,用BRNZP跳过即可,这样可以充分利用LD,ST,BR等指令9位offset的范围,防止越界(只在不行还是用子程序吧TAT)

因为我所有的寄存器都是当全局变量使用的,所以子程序也就没有ST和LD恢复的部分了

代码实现

.ORIG x3000
AND R1,R1,#0
ADD R1,R1,#1;R1存储当前玩家

BRNZP LOOP0
NUMBER1 .FILL x0031
NUMBER6 .FILL x0036
ENTER .FILL x000A
OCCUPY_1 .FILL x004F;"O"
OCCUPY_2 .FILL x0058;"X"

LOOP0
NOT R1,R1
ADD R1,R1,#2
BRz #2
LD R4,OCCUPY_2
BRnzp #1
LD R4,OCCUPY_1
;R4存放当前玩家的子

LEA R0, MAP
PUTS;展示地图
INPUT 
  LEA R0,PROMPT_1
  PUTS
  LD R0,NUMBER1;载入字符1的ascall码
  ADD R0,R0,R1
  OUT;显示玩家编号
  LEA R0, PROMPT_2 
  PUTS
GETC
OUT;回显
ADD R2,R0,#0;R2存储输入的字符
LD R0,ENTER
OUT;换行

;判断输入字符是否可行
LD R0,NUMBER1
NOT R0,R0
ADD R0,R0,#1;正数取反
ADD R0,R0,R2
BRn INVAL;小于1违法
LD R0,NUMBER6
NOT R0,R0
ADD R0,R0,#1;正数取反
ADD R0,R0,R2
BRp INVAL;大于6违法

;'1'对应0,将ascall转成int
LD R0,NUMBER1
NOT R0,R0
ADD R0,R0,#1;
ADD R2,R0,R2;R2存放列

;判断有没有空位
LEA R0, ACCOUNT 
ADD R0,R0,R2
LDR R3,R0,#0
ADD R3,R3,#-6
BRzp INVAL;没有空位,违法,重新输入
ADD R3,R3,#7
STR R3,R0,#0;更新account记录
NOT R3,R3
ADD R3,R3,#7;R3现在为行坐标(0开始)

;修改地图
ADD R0,R3,#1;
LEA R5,MAP;R5为地图指针
LOOP1 
  ADD R0,R0,#-1
  BRnz LOOPEND1
  ADD R5,R5,#12
  BRnzp LOOP1
LOOPEND1
ADD R5,R5,R2;
ADD R5,R5,R2;R5指针定位完毕
STR R4,R5,#0

;连线判断输赢
JSR JUDGE_WIN
JSR JUDGE_TIE

BRnzp LOOP0;while循环

INVAL
LEA R0,WARNING 
PUTS
BRNZP INPUT;违法,重新输入

MAP .STRINGZ "- - - - - -\n- - - - - -\n- - - - - -\n- - - - - -\n- - - - - -\n- - - - - -\n"
PROMPT_1 .STRINGZ "Player "
PROMPT_2 .STRINGZ ", choose a column: "
PROMPT_WIN .STRINGZ " Wins."
PROMPT_TIE .STRINGZ "Tie Game."
WARNING .STRINGZ "Invalid move. Try again.\n"
ACCOUNT .STRINGZ "\0\0\0\0\0\0\0";计数器,记录每列已落棋子个数

JUDGE_TIE
AND R5,R5,#0
LEA R0,ACCOUNT
LDR R6,R0,#0
ADD R5,R5,R6
LDR R6,R0,#1
ADD R5,R5,R6
LDR R6,R0,#2
ADD R5,R5,R6
LDR R6,R0,#3
ADD R5,R5,R6
LDR R6,R0,#4
ADD R5,R5,R6
LDR R6,R0,#5
ADD R5,R5,R6
ADD R5,R5,#-10
ADD R5,R5,#-10
ADD R5,R5,#-10
ADD R5,R5,#-6
BRz TIE
RET

;平局逻辑
TIE
  LEA R0,MAP
  PUTS
  LEA R0,PROMPT_TIE 
  PUTS
  HALT

;胜利逻辑
WIN
  LEA R0,MAP
  PUTS
  LEA R0,PROMPT_1
  PUTS
  LD R0,NUMBER1;载入字符1的ascall码
  ADD R0,R0,R1
  OUT;显示玩家编号
  LEA R0, PROMPT_WIN
  PUTS
  HALT;结束程序

;判断胜利子程序
JUDGE_WIN
ST R2,Y
ST R3,X
ST R5,POINTER;

;右
RIGHT_BEFORE
AND R0,R0,#0
LD R2,Y
LD R3,X
LD R5,POINTER

RIGHT
ADD R2,R2,#1
ADD R6,R2,#-6
BRzp LEFT_BEFORE;超边界跳出
ADD R5,R5,#2
LDR R6,R5,#0
NOT R6,R6;
ADD R6,R6,#1
ADD R6,R6,R4
BRnp #2
ADD R0,R0,#1
BRNZP RIGHT

;左
LEFT_BEFORE
LEA R6,ACCOUNT2
STR R0,R6,#0
AND R0,R0,#0
LD R2,Y
LD R3,X
LD R5,POINTER

LEFT
ADD R2,R2,#-1
BRn UP_BEFORE;超边界跳出
ADD R5,R5,#-2
LDR R6,R5,#0
NOT R6,R6;
ADD R6,R6,#1
ADD R6,R6,R4
BRnp #2
ADD R0,R0,#1
BRNZP LEFT


;上
UP_BEFORE
LEA R6,ACCOUNT2
STR R0,R6,#1
AND R0,R0,#0
LD R2,Y
LD R3,X
LD R5,POINTER

UP
ADD R3,R3,#-1
BRn DOWN_BEFORE;超边界跳出
ADD R5,R5,#-12
LDR R6,R5,#0
NOT R6,R6;
ADD R6,R6,#1
ADD R6,R6,R4
BRnp #2
ADD R0,R0,#1
BRNZP UP


;下
DOWN_BEFORE
LEA R6,ACCOUNT2
STR R0,R6,#2
AND R0,R0,#0
LD R2,Y
LD R3,X
LD R5,POINTER

DOWN
ADD R3,R3,#1
ADD R6,R3,#-6
BRzp RIGHTUP_BEFORE;超边界跳出
ADD R5,R5,#12
LDR R6,R5,#0
NOT R6,R6;
ADD R6,R6,#1
ADD R6,R6,R4
BRnp #2
ADD R0,R0,#1
BRNZP DOWN


;右上
RIGHTUP_BEFORE
LEA R6,ACCOUNT2
STR R0,R6,#3
AND R0,R0,#0
LD R2,Y
LD R3,X
LD R5,POINTER

RIGHTUP
ADD R2,R2,#1
ADD R6,R2,#-6
BRzp RIGHTDOWN_BEFORE;超边界跳出
ADD R3,R3,#-1
BRn RIGHTDOWN_BEFORE;超边界跳出
ADD R5,R5,#-10;-12+2
LDR R6,R5,#0
NOT R6,R6;
ADD R6,R6,#1
ADD R6,R6,R4
BRnp #2
ADD R0,R0,#1
BRNZP RIGHTUP



;右下
RIGHTDOWN_BEFORE
LEA R6,ACCOUNT2
STR R0,R6,#4
AND R0,R0,#0
LD R2,Y
LD R3,X
LD R5,POINTER

RIGHTDOWN
ADD R2,R2,#1
ADD R6,R2,#-6
BRzp LEFTUP_BEFORE;超边界跳出
ADD R3,R3,#1
ADD R6,R3,#-6
BRzp LEFTUP_BEFORE;超边界跳出
ADD R5,R5,#14
LDR R6,R5,#0
NOT R6,R6;
ADD R6,R6,#1
ADD R6,R6,R4
BRnp #2
ADD R0,R0,#1
BRNZP RIGHTDOWN

;左上
LEFTUP_BEFORE
LEA R6,ACCOUNT2
STR R0,R6,#5
AND R0,R0,#0
LD R2,Y
LD R3,X
LD R5,POINTER

LEFTUP
ADD R2,R2,#-1
BRn LEFTDOWN_BEFORE;超边界跳出
ADD R3,R3,#-1
BRn LEFTDOWN_BEFORE;超边界跳出
ADD R5,R5,#-14
LDR R6,R5,#0
NOT R6,R6;
ADD R6,R6,#1
ADD R6,R6,R4
BRnp #2
ADD R0,R0,#1
BRNZP LEFTUP


;左下
LEFTDOWN_BEFORE
LEA R6,ACCOUNT2
STR R0,R6,#6
AND R0,R0,#0
LD R2,Y
LD R3,X
LD R5,POINTER

LEFTDOWN
ADD R2,R2,#-1
BRn PRE_OP_END;超边界跳出
ADD R3,R3,#1
ADD R6,R3,#-6
BRzp PRE_OP_END;超边界跳出
ADD R5,R5,#10
LDR R6,R5,#0
NOT R6,R6;
ADD R6,R6,#1
ADD R6,R6,R4
BRnp #2
ADD R0,R0,#1
BRNZP LEFTDOWN

PRE_OP_END
LEA R6,ACCOUNT2
STR R0,R6,#7


;左右、上下、主副对角线求和,判断有没有>=3个子
LEA R6,ACCOUNT2
;左右
LDR R2,R6,#0
LDR R3,R6,#1
ADD R2,R2,R3
ADD R2,R2,#-3
BRzp WIN
;上下
LDR R2,R6,#2
LDR R3,R6,#3
ADD R2,R2,R3
ADD R2,R2,#-3
BRzp WIN
;主对角线
LDR R2,R6,#5
LDR R3,R6,#6
ADD R2,R2,R3
ADD R2,R2,#-3
BRzp WIN
;副对角线
LDR R2,R6,#4
LDR R3,R6,#7
ADD R2,R2,R3
ADD R2,R2,#-3
BRzp WIN
RET;返回循环

ACCOUNT2 .STRINGZ "\0\0\0\0\0\0\0\0"
X .BLKW 1
Y .BLKW 1
POINTER .BLKW 1

.END

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值