面试题类似国际象棋的棋子移动实现
Posted 娃都会打酱油了
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试题类似国际象棋的棋子移动实现相关的知识,希望对你有一定的参考价值。
都将近年关了,居然还有人找我帮做第一轮的笔试题,与其它第一轮在线做题相比,这边比较新颖的是:居然是给一个Solution,要求按例子实现代码。可能这是外企比较流行的做法吧,题目大致是实现一个8*8的棋盘,实现3个棋子在上面随机移动,当然限制要求也有一些,具体的要求文档如下:
You have been provided with a third-party library "ChessLib" which calculates the legal moves a knight can make given
a position on an 8 by 8 board. The library has been used to create a program which moves a knight randomly around a
board, given an initial starting position and a total number of moves to make.
Problem:
========
Extend this program to set up an 8 by 8 square game board containing several different pieces in predefined positions.
For each move of the game, the program will choose a piece at random, and move it to a randomly selected valid
position.
You are not allowed to change any of the ChessLib code.
Extend the program as required.
Use Object Oriented Design and Modeling appropriately for extensibility.
Please supply all the code for your solution in the file Answer.cs in the SampleProgram project.
Please supply all the tests for your solution in the file TestAnswer.cs in the SampleProgram.Test project.
Game Rules:
-----------
* Only one piece can occupy any position on the board at a given time.
* All pieces can “jump” any occupied position.
Note: Although the game bears a striking resemblance to Chess, this is entirely coincidental. Do not assume other
chess rules apply.
Game Pieces to support:
-----------------------
* Knight – Moves as implemented by ChessLib
* Bishop - Moves diagonally, any distance within board boundaries
* Queen – Moves diagonally, horizontally or vertically, any distance within board boundaries
说实在的,这题目我刚看时,都没看懂啥意思,问求助者是否理解,对方只是回复猎头给的题目,大约花了一个小时的时间,结合Solution自带的Demo,大致琢磨出来这个题目到底是想做啥:实现一个类似国际象棋的游戏,Demo中呢已经提供了Knight
的实现,笔试者不允许修改ChessLib
的代码,然后实现Bishop
和Queen
,并约定同一个时间一个位置上只能有一个棋子,然后就是所以棋子都可以跳过被占的位置,最后就是不要套用其它国际象棋规则!
下面是国际象棋的棋盘图。
先列举下Demo中ChessLib
的代码,代码包含Position
和KnightMove
,功能与类名一致:一个代表棋盘位置信息,一个代表Knight
如何获取后续可以跳到的Position
public struct Position
public readonly int X;
public readonly int Y;
public Position(int x, int y)
X = x;
Y = y;
public override bool Equals(object obj)
if (obj is Position)
var val = (Position) obj;
return val.X==X && val.Y==Y;
return false;
public override int GetHashCode()
return X^Y;
public override string ToString()
return String.Format("0,1", X, Y);
public class KnightMove
public static readonly int[,] Moves = new[,] 1, 2 , 1, -2 , -1, 2 , -1, -2 , 2, 1 , -2, 1 , 2, -1 , -2, -1 ;
public IEnumerable<Position> ValidMovesFor(Position pos)
for(var i=0;i<=Moves.GetUpperBound(0);i++)
var newX = pos.X + Moves[i,0];
var newY = pos.Y + Moves[i,1];
if (newX > 8 || newX < 1 || newY > 8 || newY < 1)
continue;
yield return new Position(newX, newY);
然后是Demo的其它代码
public static class Program
public static void Main()
var game = new Game();
//var game = new ComplexGame();
game.Setup();
game.Play(15);
Console.WriteLine("Press any key ...");
Console.ReadKey();
public class Game
private readonly Random _rnd = new Random();
private Position _startPosition;
public void Play(int moves)
var knight = new KnightMove();
var pos = _startPosition;
Console.WriteLine("0: My position is 0", pos);
for(var move = 1; move <= moves; move++)
var possibleMoves = knight.ValidMovesFor(pos).ToArray();
pos = possibleMoves[_rnd.Next(possibleMoves.Length)];
Console.WriteLine("1: My position is 0", pos, move);
public void Setup()
_startPosition = new Position(3, 3);
Demo运行起来效果后如下
下面开始讲思路,既然题目要求了要考虑可扩展性,那就不可能是简单的一套代码撸下去。既然题目要求要实现三个棋子Knight
、Bishop
、Queue
,那首先就是要有一个棋子的接口设计,既然是棋子,题目也只是要求移动部分,所以设计上该接口只需要关注移动相关的设定,接口定义IChessPiece
如下:
public interface IChessPiece
/// <summary>
/// 棋子当前位置
/// </summary>
Position Current get;
/// <summary>
/// 获取当前位置下所有允许移动的位置
/// </summary>
/// <returns></returns>
IReadOnlyCollection<Position> GetAllPositions();
/// <summary>
/// 移动棋子,注意目标位置必须为允许移动的位置
/// </summary>
/// <param name="targetPosition"></param>
void Move(Position targetPosition);
一开始设计上是三个棋子是互不相干的实现,但做着做着突然发现,棋子的一些共有功能都是一样的,唯一不同的地方,只是获取可以移动的位置有差异,于是BasicChessPiece
抽象实现被提取了出来,因为Move
时肯定也要判断目标位置是否是符合移动规则的位置,所以额外设置了NextPositions
,用于缓存当前位置下后续可以移动的位置点
public abstract class BasicChessPiece : IChessPiece
public BasicChessPiece(Position startPosition)
this.ValidPosition(startPosition);
this.Current = startPosition;
public Position Current get; protected set;
/// <summary>
/// 允许移动的后续位置集合
/// </summary>
protected IDictionary<string, Position> NextPositions get; set;
public virtual IReadOnlyCollection<Position> GetAllPositions()
this.InitNextPositions();
return NextPositions.Values.ToArray();//not allowed to be changed
/// <summary>
/// 获取所有后续位置集合
/// </summary>
/// <returns></returns>
protected abstract IEnumerable<Position> GetPositions();
public virtual void Move(Position targetPosition)
this.ValidPosition(targetPosition);
this.InitNextPositions();
if (this.NextPositions.ContainsKey(targetPosition.GetUnionKey()))
this.NextPositions = null;//棋子已经移动,清空位置
this.Current = targetPosition;
else
throw new ArgumentException("error position");
/// <summary>
/// 判定位置是否符合棋盘规则
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
protected bool IsValidPosition(int x, int y)
return x >= 1 && x <= 8 && y >= 1 && y <= 8;
private void ValidPosition(Position pos)
if (!this.IsValidPosition(pos.X, pos.Y))
throw new ArgumentException("invalid position");
private void InitNextPositions()
if (NextPositions == null) //约定通过null来判断是否已经生成了允许移动的位置
var positions = this.GetPositions();
if (positions == null || !positions.Any())
this.NextPositions = new Dictionary<string, Position>();//如果没有可移动的位置,则设置空字典
else
this.NextPositions = positions.ToDictionary(k => k.GetUnionKey(), v => v);
接下来就是三个棋子的实现,因为Knight
已经有了ChessLib
实现,所以Knight
直接是对KnightMove
做了一层适配,而Bishop
和Queue
可移动的位置比骑士多了很多(Bishop
可以有8*4=32个位置,Queue
可以有8*8=64个位置),所以不考虑KnightMove
的类似实现,而是直接通过棋子的当前位置,向规则允许的方向进行循环计算,另外Queue
其实只比Bishop
多了垂直和水平四个移动方向,所以Queue
直接继承自Bishop
public class Knight : BasicChessPiece
KnightMove knight;
public Knight(Position startPosition)
: base(startPosition)
knight = new KnightMove();
protected override IEnumerable<Position> GetPositions()
return knight.ValidMovesFor(this.Current);
public class Bishop : BasicChessPiece
public Bishop(Position startPosition)
: base(startPosition)
protected override IEnumerable<Position> GetPositions()
for (var h = 0; h < 4; h++)
//四个45度角方向允许移动的位置
var byX = h % 2 == 0 ? 1 : -1;
var byY = h / 2 == 0 ? 1 : -1;
for (var i = 1; i < 8; i++)
var newX = this.Current.X + byX * i;
var newY = this.Current.Y + byY * i;
if (!this.IsValidPosition(newX, newY))
break;
yield return new Position(newX, newY);
public class Queen : Bishop
public Queen(Position startPosition)
: base(startPosition)
protected override IEnumerable<Position> GetPositions()
var positions = base.GetPositions().ToList();
for (var h = 0; h < 4; h++)
//垂直和水平四个方向允许移动的位置
var byX = 0;
var byY = 0;
if (h < 2)
byX = h % 2 == 0 ? 1 : -1;
else
byY = h % 2 == 0 ? 1 : -1;
for (var i = 1; i < 8; i++)
var newX = this.Current.X + byX * i;
var newY = this.Current.Y + byY * i;
if (!this.IsValidPosition(newX, newY))
break;
positions.Add(new Position(newX, newY));
return positions;
棋子定义完了,接下来就是对ComplexGame
的填充,首先是用到了两个扩展方法,设置internal
是限制其作用范围
internal static class RandomHelper
public static int Next(this int maxNumber)
if (maxNumber <= 0)
throw new ArgumentException("'maxNumber' must great than zero");
return Math.Abs(Guid.NewGuid().GetHashCode() % maxNumber);
internal static class PositionExtensions
/// <summary>
/// 获取Position对应的唯一标志
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
public static string GetUnionKey(this Position pos)
return $"pos.X_pos.Y";
接下来是ComplexGame
,代码与例子Game
类似,判断移动的代码都写在了此处,其实棋盘控制部分的代码可以在此进一步提取,即Knight
、Bishop
、Queen
只是最基础的棋子实现,可以设定一个IChess
来进行限定布局、阻塞、吃掉棋子等行为,只是这里没这么做而已
public class ComplexGame
IList<IChessPiece> pieces;
IChessPiece prevPiece;
HashSet<string> occupiedPositions;
public void Setup()
// TODO: Set up the state of the game here
var knight = new Knight(new Position(1, 2));
var bishop = new Bishop(new Position(1, 3));
var queen = new Queen(new Position(1, 4));
occupiedPositions = new HashSet<string>
knight.Current.GetUnionKey(),
bishop.Current.GetUnionKey(),
queen.Current.GetUnionKey(),
;
pieces = new List<IChessPiece>()
knight,
bishop,
queen
;
public void Play(int moves)
// TODO: Play the game moves here
for (var move = 1; move <= moves; move++)
var piece = this.GetRandomChessPiece();
var pieceName = piece.GetType().Name;
Console.WriteLine("0: 0 current position is 1", pieceName, piece.Current);
var possibleMoves = piece.GetAllPositions();
if (possibleMoves != null && possibleMoves.Count > 0)
possibleMoves = this.GetValidPositions(possibleMoves);
if (possibleMoves == null || possibleMoves.Count == 0)
Console.WriteLine("No position for 0 to move", pieceName);
//move--; //注释表示不移动也算过了一步,如果不允许这样要注释此行
continue;
var newPosition = this.GetNextPositon(possibleMoves);
this.MoveTo(piece, newPosition);
this.SetPrevChessPiece(piece);
Console.WriteLine("1: 2 move to position 0", newPosition, move, pieceName);
protected virtu以上是关于面试题类似国际象棋的棋子移动实现的主要内容,如果未能解决你的问题,请参考以下文章