一道面试题--设计炸船互动游戏
Posted 於之
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一道面试题--设计炸船互动游戏相关的知识,希望对你有一定的参考价值。
游戏规则
游戏分为甲乙双方两人。游戏规则如下
- 游戏双方各拥有一个 10*10 的地图中部署战舰进行初始化分布。布局完成后,玩家只能
看到自己战舰,看不到对方战舰。 - 战舰有三种形状, 每个玩家分别拥有每个模型的一条战舰。三个模型分别为:
三种战舰可以自由旋转。
例:甲为红色,已为蓝色,初始布局可以如下:
乙方地图(战舰只对乙方可见)
甲方地图(战舰只对甲方可见)
- 游戏开始,甲乙两人轮流挑选对方坐标进行炸弹盲投,如果炸弹击中对方战舰,则标记
击中,如果没有击中,标记 miss。如果一个战舰的所有位置都被击中,该战舰标记位沉
没。 - 战舰全部沉没者为失败方,游戏结束。
要求
- 设计游戏数据结构,流程以及相关对象 Class. 无需设计 UI 界面。
- 描述如何启动游戏,初始化游戏,进行游戏,以及游戏结束。
- 描述如何对该游戏测试
附加要求
- 如果船只有多种不规则形状,如何改进现有系统?
例如引入新型战舰模型:
- 如果双方在游戏中各有一次移动一只战舰坐标的机会,如何添加该功能?
以下是我自己的一些解题思路,仅供参考:
1.相关对象(包含代码注释)
通过分析游戏设计需求,定义游戏界面为横向 X 轴坐标,纵向 Y 轴,
不同战舰形状均是以多个 xy 轴坐标上的点假设为 node 对象构成,
利用享元模式,定义 node 对象,而不同的战舰均是以 node 对象基
于组合模式构成,通一个 ship 中的所有 node 基于双向链表数据结构
组成,ship 中只需要存储该战舰 ship 的 firstNode 即可(链表的查询
速度最优,便于查询 node 并做炮弹击中等逻辑判断时对 node 的遍
历查询),其中 firstNode 为战舰的左上脚第一个 node;ship 的初始
化是基于工厂模式实现,01,02,03 分别对应 a\\b\\c 三类战舰;
战舰相关对象及含义
public class Node {
/**
* 每个节点的颜⾊, --如果被轰炸之后,那节点颜⾊就是灰⾊
*/
private NodeColor nodeColor;
private int x;
private int y;
private Node preNode;
private Node nextNode;
public NodeColor getNodeColor() {
return nodeColor;
}
public void setNodeColor(NodeColor nodeColor) {
this.nodeColor = nodeColor;
}public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public Node getPreNode() {
return preNode;
}
public void setPreNode(Node preNode) {
this.preNode = preNode;
}
public Node getNextNode() {
return nextNode;
}
public void setNextNode(Node nextNode) {this.nextNode = nextNode;
}
public boolean changeColor(NodeColor color){
this.nodeColor = color;
return true;
} }
NodeColor 枚举类型用于标识 node 的不同颜色,当 node 被击中,则修改当前 node 的颜
色--如置灰
nodeColor 如下:
public boolean changeColor(NodeColor color){
this.nodeColor = color;
return true;
}
通过工厂类创建战舰,工厂类的代码如下:
public class ShipFactory {
public static Map<NodeColor,int[][]> allMap = new ConcurrentHashMap<>(3);
static {
/**
* 虚拟地图-编号 0-9 ,⽽实际 UI 中是 1-10
*/
int[][] shipNodeMap4Red = new int[10][10];//红⽅地图
int[][] shipNodeMap4Blue = new int[10][10];//蓝⽅地图allMap.put(NodeColor.RED,shipNodeMap4Red);
allMap.put(NodeColor.BLUE,shipNodeMap4Blue);
}
public static Ship getInstance(String shipType,NodeColor color,int x,int y){
if (0 < x && x <= 10 && 0 < y && y <= 10){
if("01".equals(shipType)){
/**
* 横向三点战舰
*/
if(x -2 > 0 && x < 11 && y > 0 && y < 11 ){
Node firstNode = new Node(color,x , y, null );//左侧 node
Node midNode = new Node(color,x- 1, y , firstNode);
Node endNode = new Node(color,x - 2, y, midNode);
if(checkCanBeCreate(firstNode,midNode,endNode)){//判断 node 是否存在重
复
firstNode.setNextNode(midNode);
midNode.setNextNode(endNode);
endNode.setNextNode(null);
addNodes2Map(firstNode,midNode,endNode);//创建成功之后,记录创建
记录return new Ship(true,firstNode);
}else{
firstNode = null;
midNode = null;
endNode = null;
clearNode(firstNode,midNode,endNode);
}
return new Ship(false);
}
return new Ship(false);
}else if("02".equals(shipType)){
/**
* 四点战舰
*/
if(x -1 > 0 && y - 1 > 0 && x + 1 < 11 && y + 1 < 11){
Node firstNode = new Node(color,x , y, null );//左上⻆ node
Node secondNode = new Node(color,x + 1, y , firstNode);
Node thirdNode = new Node(color,x , y -1 , secondNode);
Node endNode = new Node(color,x + 1 , y -1 , thirdNode);
if(checkCanBeCreate(firstNode,secondNode,thirdNode,endNode)){//判断node 是否存在重复
firstNode.setNextNode(secondNode);
secondNode.setNextNode(thirdNode);
thirdNode.setNextNode(endNode);
endNode.setNextNode(null);
addNodes2Map(firstNode,secondNode,thirdNode,endNode);//创建成功之
后,记录创建记录
return new Ship(true,firstNode);
}else{
firstNode = null;
secondNode = null;
thirdNode = null;
endNode = null;
clearNode(firstNode,secondNode,thirdNode,endNode);
}
return new Ship(false);
}
return new Ship(false);
}else {
/*** 纵向两点战舰
*/
if(y -1 > 0 && y< 11 && x >0 && x < 11){
Node firstNode = new Node(color,x , y, null );//左侧 node
Node endNode = new Node(color,x - 1, y, firstNode);
if(checkCanBeCreate(firstNode,endNode)){//判断 node 是否存在重复
firstNode.setNextNode(endNode);
endNode.setNextNode(null);
addNodes2Map(firstNode,endNode);//创建成功之后,记录创建记录
return new Ship(true,firstNode);
}else{
firstNode = null;
endNode = null;
clearNode(firstNode,endNode);
}
return new Ship(false);
}
return new Ship(false);
}
}return null;
}
/**
* 当前您地图中是否已经存在相关 node--防⽌两个 node 重复
* @param nodes
* @return
*/
private static boolean checkCanBeCreate(Node... nodes){
for (Node node:nodes ) {
int[][] shipNodeMap = allMap.get(node.getNodeColor());
if(shipNodeMap[node.getX() - 1][node.getY() - 1 ] == 1 ){
return false;
}
}
return true;
}/**
* 将创建过的 node 记录在内存地图中
* @param nodes
* @return
*/
private static boolean addNodes2Map(Node... nodes){
for (Node node:nodes ) {
int[][] shipNodeMap = allMap.get(node.getNodeColor());
shipNodeMap[node.getX() - 1][node.getY() - 1 ] = 1;
}
return true;
}
/**
* 清除垃圾对象,防⽌内存泄漏
* @param nodes
*/
private static void clearNode(Node ... nodes){
for (Node node:nodes ) {
node.setNextNode(null);
node.setNextNode(null);
}}
}
以上对象为⼀个战舰所需要的所有构成要素相关的核⼼类
炸弹相关对象及含义
public class Bomb {
private int killNodeNum;//当前⼦弹的威⼒,默认为 1,即默认只能炸毁⼀个节点所在的船舰--可
以增加多种模式
private int speed ; //炸弹运⾏速度,默认为 2, --炸弹每秒移动两个 node 距离()
private NodeColor selfColor;// 豁免标识,防⽌炸弹误炸⼰⽅
/**
* 轰炸⽬标点的坐标,当炸弹击中当前坐标时,通过判断当前点在地⽅地图中是否存在 node,如果
存在则将该敌⽅
* 的 node 对象查找到并修改其颜⾊为"NodeColor.GRAY", ⽽当前 node 所属的 ship,执⾏战损评
估逻辑,如果当前
* ship 的所有 node 颜⾊都为"NodeColor.GRAY",即当前 ship 被击毁
*/
private int destXValue;
private int destYValue;/**
* 炸弹构造⽅法
* @param killNodeNum
* @param destXValue
* @param destYValue
*/
public Bomb(int killNodeNum, int destXValue, int destYValue) {
this.killNodeNum = killNodeNum;
this.destXValue = destXValue;
this.destYValue = destYValue;
}
public NodeColor getSelfColor() {
return selfColor;
}
public void setSelfColor(NodeColor selfColor) {
this.selfColor = selfColor;
}
public int getSpeed() {return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getKillNodeNum() {
return killNodeNum;
}
public void setKillNodeNum(int killNodeNum) {
this.killNodeNum = killNodeNum;
}
public int getDestXValue() {
return destXValue;
}
public void setDestXValue(int destXValue) {
this.destXValue = destXValue;
}public int getDestYValue() {
return destYValue;
}
public void setDestYValue(int destYValue) {
this.destYValue = destYValue;
}
}
2 游戏过程
- 游戏采用联网对战模式,双方需要在规定时间内尽可能多的击毁敌方战舰;用户可以自由创建自己的对战房间,然后等待对手进入房间发起挑战,对战双方分为红蓝颜色标识,红方具有先行发射炮弹的权利,每方发射一发炮弹之后必须等待对方发射炮弹之后才能进行下一发炮弹的发射操作,战舰位置由系统随机生成;
- 对战过程中,任意一方可以在海图中选择炮弹的打击坐标,选中并击发之后,根据炮弹的威力参数攻击敌舰,目前默认为 1,即只能攻击坐标中的战舰;炮弹的运行轨迹和颜色根据本次发射炮弹的己方战舰以及炮弹的豁免颜色决定(豁免颜色与本方的战舰颜色一致);当炮弹击中目标区域之后,判断是否击中敌方战舰,并在游戏界面显示战果信息,同时敌方显示战损信息
- 当游戏规定时间到了却没有任何一方战舰全部被击毁时,根据双方击中的次数和战果分数,得分高者获胜,饭制失败;如果在游戏规定时间内任意一方战舰全部被击毁,则另一方获胜!
3. 游戏测试
- 使用 junit 测试框架,对游戏进行单元测试,单元测试需要在开发阶段完成
- 功能测试,部署测试环境,按照用户的各项功能分别进行测试
- 压力测试,根据游戏设计指标,在特定环境(带宽、硬件资源等)下,对游戏的运行能力进行压力测试
4. 添加其它形状战舰
- 方法一,在工厂类中增加新的战舰类型,如同战舰 a\\b\\c 三种类型一样,基于Node 对象生成新的战舰即可
- 方法二、理由现有战舰类型组合,要求总希望增加的战舰 D,正好可以是战舰 a\\c两种类型组合可以得到,由于才有了双向链表方式,只需要将两个类型的链表连接起来即可实现
5. 如何添加战舰移动功能
由于对战双方只有一次移动任何一艘战舰的机会,则
- 第一步,则游戏的介绍中声明添加此功能,该功能允许用户在对战过程中上下左右任意方向移动一次战舰,且只允许使用一次,
- 第二步,在游戏界面的右下角显示上下左右操作虚拟按键,当用户已经使用过此功能时,则应该立即置灰,表示已经无法再次使用,目前代码中我增加了相应限制,即不允许同一个点存在两个 node,也就是说战舰的移动不能覆盖到另一个战舰(可通过代码修改),
以上是关于一道面试题--设计炸船互动游戏的主要内容,如果未能解决你的问题,请参考以下文章