孔明棋,是法国跳棋
独立钻石在中国的别称,也有人叫它跳弹珠,或者叫它「Pegged」。一般流传的玩法是先取去中央的那个棋子,便可以展开游戏。游戏时,是将棋子跳过邻近的棋子,到达一个旁边空着的位置,被跳过的棋子则从棋盘上取开;跳的路径可以前,后,左,右,但不可对角跳,直到剩下最后的一颗棋子,游戏便结束了。这是一种流传很广的益智游戏,也有很多种变形的棋盘摆法,而本文要讲的是在三角形棋盘上的玩法,它是自17世纪末就在欧洲开始被人们玩起来了。当各大餐厅为每一个餐桌上等待食物的顾客都放置一个棋盘以供他们娱乐的时候,这类益智游戏变得越来越流行。
本文实际上通过一段简单的回溯算法DFS去搜索找出一个解决给定棋盘上的出发点的解决方案。
背景
这个方案的主要目的是陈述一个递归函数的概念,本质上就是递归问题和回溯。它还提供了一个面向对象的思维,在一个可重用的类里来保存所有回溯逻辑形式。解决这个难题,需要使用被称为回溯的技术来搜索所有可能的移动路径,这是很容易使用递归实现的。
基本的步骤
三角之谜是15孔组织成一个三角形的集合,在其中放置钉,留下至少一个空孔。通常,该益智游戏开始只有一个空孔,如在结构(a)所示,其中实心圆圈代表钉,而空的圆圈代表孔。解谜的目的是通过一系列合法的移动,每个移动造成一个钉被删除,直到最后只剩下一个钉。一个钉被删除仅仅是通过跳过它的其他钉。因此,一个合法的移动只能是由一个位于孔和其他钉的侧面的钉移动,它们排列在一行。例如,结构(b)所示的结构是由结构(a)经过一次跳跃获得,然后(c)又由(b)再次一次跳跃得到。
代码
GameBoard.java:此类是为了生成三角形状的棋盘
package com.game.board;
import java.util.List;
import java.util.ArrayList;
public class GameBoard {
// board consists of pegs and holes in a 5 x 5, but only uses triangular pyramid
boolean[][] pins = new boolean[5][5];
public GameBoard(int row, int col) {
for (int i = 0; i < 5; ++i)
for (int j = 0; j <= i; ++j)
pins[i][j] = true;
pins[row][col] = false;
}
public GameBoard(int board) {
for (int i = 4; i >= 0; --i)
for (int j = i; j >= 0; --j) {
if ((board & 1 ) == 1)
pins[i][j] = true;
else
pins[i][j] = false;
board /= 2;
}
}
// copy constructor
public GameBoard(GameBoard that) {
for (int i = 0; i < 5; ++i)
for (int j = 0; j <= i; ++j)
pins[i][j] = that.pins[i][j];
}
public List<GameBoard> possibleBoards() {
List<GameBoard> boards = new ArrayList<GameBoard>();
for (int i = 0; i < 5; ++i)
for (int j = 0; j <= i; ++j) {
Position start = new Position(i,j);
List<Move> possibleMoves = Moves.getMoves(start);
for (Move move : possibleMoves) {
if (validMove(move))
boards.add(jump(move));
}
}
return boards;
}
public boolean validMove(Move move) {
if (!pins[move.getStart().getRow()][move.getStart().getCol()]) // empty start
return false;
if (!pins[move.getJump().getRow()][move.getJump().getCol()]) // empty jump over
return false;
if (pins[move.getEnd().getRow()][move.getEnd().getCol()]) // filled in end
return false;
return true;
}
public GameBoard jump(Move move) {
GameBoard gb = new GameBoard(this);
gb.pins[move.getStart().getRow()][move.getStart().getCol()] = false;
gb.pins[move.getJump().getRow()][move.getJump().getCol()] = false;
gb.pins[move.getEnd().getRow()][move.getEnd().getCol()] = true;
return gb;
}
public boolean finalBoard() {
int remainingPins = 0;
for (int i = 0; i < 5; ++i)
for (int j = 0; j <= i; ++j)
if (pins[i][j]) {
remainingPins++;
if (remainingPins > 1) // optimize, early out, more than 1 pin is not final board
return false;
}
return remainingPins == 1;
}
public int toInt() {
int ret = 0;
for (int i = 0; i < 5; ++i)
for (int j = 0; j <= i; ++j) {
ret *= 2;
if (pins[i][j]) {
ret |= 1;
}
}
return ret;
}
public String toString() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 5; ++i) {
for (int s = 4-i; s > 0; --s)
sb.append(" ");
for (int j = 0; j <= i; ++j) {
sb.append(pins[i][j] ? 'X' : 'O').append(" ");
}
sb.append("\n");
}
// sb.append(toInt()+ "\n");
return sb.toString();
}
}
Move.java:移动的坐标指向类
package com.game.board;
public class Move {
private Position start;
private Position jump;
private Position end;
public Move(Position start, Position jump, Position end) {
this.start = start;
this.jump = jump;
this.end = end;
}
public Position getStart() { return start; }
public Position getJump() { return jump; }
public Position getEnd() { return end; }
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("{"+start);
sb.append(","+jump);
sb.append(","+end+ "}");
return sb.toString();
}
}
Moves.java:将所有起始点可以移动的方向全部列举并放入map集合中
package com.game.board;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
public class Moves {
private static Map<Position,List<Move>> validMoves = new HashMap<Position,List<Move>>();
static {
/*
* 0,0
* 1,0 1,1
* 2,0 2,1 2,2
* 3,0 3,1 3,2 3,3
* 4,0 4,1 4,2 4,3 4,4
*
*/
Position start;
start = new Position(0,0);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(1,0), new Position(2,0)));
validMoves.get(start).add(new Move(start, new Position(1,1), new Position(2,2)));
start = new Position(1,0);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(2,0), new Position(3,0)));
validMoves.get(start).add(new Move(start, new Position(2,1), new Position(3,2)));
start = new Position(1,1);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(2,1), new Position(3,1)));
validMoves.get(start).add(new Move(start, new Position(2,2), new Position(3,3)));
/*
* 0,0
* 1,0 1,1
* 2,0 2,1 2,2
* 3,0 3,1 3,2 3,3
* 4,0 4,1 4,2 4,3 4,4
*
*/
start = new Position(2,0);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(1,0), new Position(0,0)));
validMoves.get(start).add(new Move(start, new Position(2,1), new Position(2,2)));
validMoves.get(start).add(new Move(start, new Position(3,0), new Position(4,0)));
validMoves.get(start).add(new Move(start, new Position(3,1), new Position(4,2)));
start = new Position(2,1);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(3,1), new Position(4,1)));
validMoves.get(start).add(new Move(start, new Position(3,2), new Position(4,3)));
start = new Position(2,2);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(1,1), new Position(0,0)));
validMoves.get(start).add(new Move(start, new Position(2,1), new Position(2,0)));
validMoves.get(start).add(new Move(start, new Position(3,2), new Position(4,2)));
validMoves.get(start).add(new Move(start, new Position(3,3), new Position(4,4)));
/*
* 0,0
* 1,0 1,1
* 2,0 2,1 2,2
* 3,0 3,1 3,2 3,3
* 4,0 4,1 4,2 4,3 4,4
*
*/
start = new Position(3,0);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(2,0), new Position(1,0)));
validMoves.get(start).add(new Move(start, new Position(3,1), new Position(3,2)));
start = new Position(3,1);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(2,1), new Position(1,1)));
validMoves.get(start).add(new Move(start, new Position(3,2), new Position(3,3)));
start = new Position(3,2);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(2,1), new Position(1,0)));
validMoves.get(start).add(new Move(start, new Position(3,1), new Position(3,0)));
start = new Position(3,3);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(2,2), new Position(1,1)));
validMoves.get(start).add(new Move(start, new Position(3,2), new Position(3,1)));
/*
* 0,0
* 1,0 1,1
* 2,0 2,1 2,2
* 3,0 3,1 3,2 3,3
* 4,0 4,1 4,2 4,3 4,4
*
*/
start = new Position(4,0);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(3,0), new Position(2,0)));
validMoves.get(start).add(new Move(start, new Position(4,1), new Position(4,2)));
start = new Position(4,1);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(3,1), new Position(2,1)));
validMoves.get(start).add(new Move(start, new Position(4,2), new Position(4,3)));
start = new Position(4,2);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(3,1), new Position(2,0)));
validMoves.get(start).add(new Move(start, new Position(3,2), new Position(2,2)));
validMoves.get(start).add(new Move(start, new Position(4,1), new Position(4,0)));
validMoves.get(start).add(new Move(start, new Position(4,3), new Position(4,4)));
start = new Position(4,3);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(3,2), new Position(2,1)));
validMoves.get(start).add(new Move(start, new Position(4,2), new Position(4,1)));
start = new Position(4,4);
validMoves.put(start, new ArrayList<Move>());
validMoves.get(start).add(new Move(start, new Position(3,3), new Position(2,2)));
validMoves.get(start).add(new Move(start, new Position(4,3), new Position(4,2)));
}
public static List<Move> getMoves(Position position) {
if (!validMoves.containsKey(position))
throw new RuntimeException("Invalid position: " + position);
return validMoves.get(position);
}
public String toString() {
return validMoves.toString();
}
}
position.java:坐标类
package com.game.board;
public class Position {
int row;
int col;
public Position(int row, int col) {
this.row = row;
this.col = col;
}
public int getRow() { return row; }
public int getCol() { return col; }
public String toString() {
return "[" + row + "," + col + "]";
}
public int hashCode() {
int result = 17;
result = 37*result + row;
result = 37*result + col;
return result;
}
public boolean equals(Object other) {
if (!(other instanceof Position))
return false;
Position that = (Position) other;
if (this.row != that.row)
return false;
return this.col == that.col;
}
}
GameTree.java:以树的形式保存遍历的所有路径
package com.game.play;
import java.util.List;
import java.util.ArrayList;
import com.game.board.GameBoard;
public class GameTree {
GameTree level;
GameBoard gb;
List<GameTree> children = new ArrayList<GameTree>();
public GameTree(GameBoard gb) {
this.gb = gb;
}
public void addChild(GameTree child) {
children.add(child);
}
public GameBoard getGameBoard() { return gb; }
public boolean hasChildren() {
return children.size() > 0;
}
public GameTree getFirstChild() {
return children.get(0);
}
public void removeFirstChild() {
children.remove(0);
}
public int numChildren() {
return children.size();
}
}
Play.java:通过DFS搜索所有可能的路径
package com.game.play;
import java.io.IOException;
import java.util.List;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import com.game.board.GameBoard;
public class Play {
GameBoard startBoard;
public Play(String[] args) throws IOException {
if (args.length == 0) {
int n1,n2;
BufferedReader stdin = new BufferedReader (new InputStreamReader(System.in));
System.out.println("Name: samudra ");
System.out.println("......................................");
System.out.println("Enter the position of the hole:");
n1=Integer.parseInt( stdin.readLine());
n2=Integer.parseInt( stdin.readLine());
init(n1,n2);
}
else
init(Integer.parseInt( args[0]),Integer.parseInt( args[1]));
}
private void init(int row, int col) {
startBoard = new GameBoard(row, col);
}
//DFS function
public void DFS() {
GameTree root = new GameTree(startBoard);
for (GameBoard nextBoard : startBoard.possibleBoards()) {
GameTree nextNode = new GameTree(nextBoard);
if (play(nextBoard, nextNode))
root.addChild(nextNode);
}
printWinningGame(root);
}
// to iterate is human, to recurse divine
// print game board at each node on the way down, removing the printed child on the way up
private void printWinningGame(GameTree parent) {
System.out.println(parent.getGameBoard());
if (parent.numChildren() > 0) {
GameTree nextNode = parent.getFirstChild();
printWinningGame(nextNode); // recurse
if (nextNode.numChildren() == 0)
parent.removeFirstChild();
} else {
System.out.println("===============================================");
}
}
// chase all possible boards
private boolean play(GameBoard gb, GameTree parent) {
if (gb.finalBoard()) // remember this path was a winning path
return true;
List<GameBoard> nextBoards = gb.possibleBoards();
boolean found = false;
for (GameBoard nextBoard : nextBoards) {
GameTree nextNode = new GameTree(nextBoard);
if (play(nextBoard, nextNode)) { // recurse
found = true;
parent.addChild(nextNode);
}
}
return found;
}
}
PegBord.java:主方法
package com.game.main;
import java.io.IOException;
import com.game.play.Play;
public class PegBoard {
/**
* @param args
*/
public static void main(String[] args) throws IOException {
Play play = new Play(args);
play.DFS();
}
}
运行结果展示:
Name: samudra
......................................
Enter the position of the hole:
2
1
X
X X
X O X
X X X X
X X X X X
X
X X
X X X
X O X X
X O X X X
X
X X
X X X
X X O O
X O X X X
X
O X
X O X
X X X O
X O X X X
X
O O
X O O
X X X X
X O X X X
X
X O
O O O
O X X X
X O X X X
O
O O
X O O
O X X X
X O X X X
O
O O
X O O
O X X X
X X O O X
O
O O
O O O
O O X X
X X X O X
O
O O
O O O
O O X X
X O O X X
O
O O
O O X
O O X O
X O O X O
O
O O
O O O
O O O O
X O X X O
O
O O
O O O
O O O O
X X O O O
O
O O
O O O
O O O O
O O X O O
===============================================
......................................
Enter the position of the hole:
2
1
X
X X
X O X
X X X X
X X X X X
X
X X
X X X
X O X X
X O X X X
X
X X
X X X
X X O O
X O X X X
X
O X
X O X
X X X O
X O X X X
X
O O
X O O
X X X X
X O X X X
X
X O
O O O
O X X X
X O X X X
O
O O
X O O
O X X X
X O X X X
O
O O
X O O
O X X X
X X O O X
O
O O
O O O
O O X X
X X X O X
O
O O
O O O
O O X X
X O O X X
O
O O
O O X
O O X O
X O O X O
O
O O
O O O
O O O O
X O X X O
O
O O
O O O
O O O O
X X O O O
O
O O
O O O
O O O O
O O X O O
===============================================