回溯法
基本思想:
- 对一个包括有很多结点,每个结点有若干个搜索分支的问题,把原问题分解为对若干个子问题求解的算法。
- 当搜索到某个结点、发现无法再继续搜索下去时,就让搜索过程回溯(即退回)到该结点的前一结点,继续搜索这个结点的其他尚未搜索过的分支;
- 如果发现这个结点也无法再继续搜索下去时,就让搜索过程回溯到这个结点的前一结点继续这样的搜索过程;
- 这样的搜索过程一直进行到搜索到问题的解或搜索完了全部可搜索分支没有解存在为止。
回溯的处理思想,有点类似枚举搜索。我们枚举所有的解,找到满足期望的解。为了有规律地枚举所有可能的解,避免遗漏和重复。
回溯算法的思想非常简单,大部分情况下,都是用来解决广义的搜索问题,也就是,从一组可能的解中,选择出一个满足要求的解。回溯算法非常适合用递归来实现,在实现的过程中,剪枝操作是提高回溯效率的一种技巧。利用剪枝,我们并不需要穷举搜索所有的情况,从而提高搜索效率。
正则表达式里的回溯法
正则表达式中,最重要的就是通配符,通配符结合在一起,可以表达非常丰富的语义。为了方便讲解,我假设正表达式中只包含 “” 和 “?” 这两种通配符,并且对这
两个通配符的语义稍微做些改变,其中, “” 匹配任意多个(大于等于 0 个)任意字符, “?” 匹配零个或者一个任意字符。基于以上背景假设,我们看下,如何用回
溯算法,判断一个给定的文本,能否跟给定的正则表达式匹配?
我们依次考察正则表达式中的每个字符,当是非通配符时,我们就直接跟文本的字符进行匹配,如果相同,则继续往下处理;如果不同,则回溯。
如果遇到特殊字符的时候,我们就有多种处理方式了,也就是所谓的岔路口,比如 “*” 有多种匹配方案,可以匹配任意个文本串中的字符,我们就先随意的选择一
种匹配方案,然后继续考察剩下的字符。如果中途发现无法继续匹配下去了,我们就回到这个岔路口,重新选择一种匹配方案,然后再继续匹配剩下的字符
public class Pattern {
private boolean matched = false;
private char[] pattern; // 正则表达式
private int plen; // 正则表达式长度
public Pattern(char[] pattern, int plen) {
this.pattern = pattern;
this.plen = plen;
}
public boolean match(char[] text, int tlen) { // 文本串及长度
matched = false;
rmatch(0, 0, text, tlen);
return matched;
}
private void rmatch(int ti, int pj, char[] text, int tlen) {
if (matched) return; // 如果已经匹配了,就不要继续递归了
if (pj == plen) { // 正则表达式到结尾了
if (ti == tlen) matched = true; // 文本串也到结尾了
return;
}
if (pattern[pj] == '*') { // *匹配任意个字符
for (int k = 0; k <= tlen - ti; ++k) {
rmatch(ti + k, pj + 1, text, tlen);
}
} else if (pattern[pj] == '?') { // ?匹配0个或者1个字符
rmatch(ti, pj + 1, text, tlen);
rmatch(ti + 1, pj + 1, text, tlen);
} else if (ti < tlen && pattern[pj] == text[ti]) { // 纯字符匹配才行
rmatch(ti + 1, pj + 1, text, tlen);
}
}
}
回溯法解决马踏棋盘问题
马踏棋盘算法也被称为骑士周游问题
将马随机放在国际象棋的8×8棋盘Board[0~7][0~7]的某个方格中,马按走棋规则(马走日字)进行移动。要求每个方格只进入一次,走遍棋盘上全部64个方格
马踏棋盘问题(骑士周游问题)实际上是图的深度优先搜索(DFS)的应用。
如果使用回溯(就是深度优先搜索)来解决,假如马儿踏了53个点,如图:走到了第53个,坐标(1,0),发现已经走到尽头,没办法,那就只能回退了,查看其他的路径,就在棋盘上不停的回溯……
解决步骤
- 定义棋盘二维数组及访问数组
- 设置当前位置已经访问,根据当前位置计算马儿下一步需要走的位置集合ArrayList–
根据马走“日”字可以看出马儿可走8个方向的位置.每走一步,步数step+1 - 遍历马儿的走的位置集合ArrayList,判断哪条路可以走通,如果走通就继续递归,如果走不通就回溯
- 判断马儿是否完成任务(step == 数组长度*数组长度);
制作马踏棋盘游戏
根据以上思想可以制作个游戏来解决马踏棋盘问题
我们需要的功能:
1 自动执行–即自动寻找合适的马儿所走位置
2 用户点击棋盘位置指定放马儿的位置
3 在用户点击的基础上自动执行寻找合适位置
4 重新开始
5 统计马儿步数
6 游戏成功与失败的判定
1 首先我们需要定义棋盘得大小和马儿的图片及该位置已经访问的图片
private static final int FRAME_WIDTH = 600;//窗口长
private static final int FRAME_HEIGHT = 600;//窗口宽
private static final int KNIGHT_LENGTH = 6;//棋盘大小
private static final String HORSE_SRC = "Algorithm\\src\\Algorithm\\KnightTour\\horse.png";
private static final String HORSE_IS_VISITED_SRC = "Algorithm\\src\\Algorithm\\KnightTour\\isVisited.png";
2 定义KnightTourFrame 棋盘类继承JFrame :基本属性如下:
private final KnightTourMap[][] map = new KnightTourMap[KNIGHT_LENGTH][KNIGHT_LENGTH];//棋盘数组
private final boolean[] isVisited; //棋盘是否访问数组
private JLabel jLabel;//显示统计步数控件
private int totalStep; //总步数
private boolean isAutoMove = false; //自动运行标志
private final Point initialPoint;//初始化位置索引坐标
private boolean isFinished = false;//结束标志
3 定义马儿类 HorseJPanel 继承 JPanel 基本属性如下
Image icon; //图标
private final int maxHeight; //图片最大高度
private final int maxWidth;//图片最大宽度
private int scalingWidth;//缩放图片宽度
private int scalingHeight;//缩放图片高度
4 初始化马儿图片(加载图片)
/*
* @param: [srcWidth]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:28
* @description: 缩放图片长度和宽度,使之自适应棋盘网格
*/
private void initialImage(int srcWidth){
float scalingFactor;
if (scalingWidth > maxWidth/2) {
scalingFactor = (float) srcWidth/(float) maxWidth;
scalingWidth = (int) ((scalingWidth-maxWidth/2)/scalingFactor);
scalingHeight = Math.round((float)scalingHeight/ scalingFactor);
}
if (scalingWidth < maxWidth/2) scalingWidth = maxWidth/2;
int srcHeight = scalingHeight;
if (scalingHeight > maxHeight/2) {
scalingFactor = (float) srcHeight /(float) maxHeight;
scalingHeight = (int) ((scalingHeight-maxHeight/2)/scalingFactor);
scalingWidth = Math.round((float)scalingWidth/scalingFactor);
}
if (scalingHeight < maxHeight / 2) scalingHeight = maxHeight/2;
}
5 初始化控件
/*
* @param: [topPanel, panel]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 20:16
* @description:初始化JPanel控件
*/
private void initialJPanel(JPanel topPanel,JPanel panel){
topPanel.setLayout(new GridLayout(1,3,10,10));
topPanel.setSize(new Dimension(FRAME_WIDTH,40));
jLabel = new JLabel("",JLabel.CENTER);
JButton button = new JButton("重来");
button.setVerticalTextPosition(JButton.CENTER);
button.setPreferredSize(new Dimension(50,40));
button.setMargin(new Insets(5,5,5,5));
button.setRequestFocusEnabled(false);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
JButton autoButton = new JButton("自动运行");
autoButton.setVerticalTextPosition(JButton.CENTER);
button.setPreferredSize(new Dimension(50,40));
button.setMargin(new Insets(5,5,5,5));
autoButton.setRequestFocusEnabled(false);
autoButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
jLabel.setForeground(Color.red);
jLabel.setPreferredSize(new Dimension(50,40));
topPanel.add(autoButton);
topPanel.add(jLabel);
topPanel.add(button);
panel.setLayout(new GridLayout(KNIGHT_LENGTH, KNIGHT_LENGTH));
add(topPanel,BorderLayout.BEFORE_FIRST_LINE);
add(panel, BorderLayout.CENTER);
button.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
clearKnightTourMap();
}
});
autoButton.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (isFinished) return;
isAutoMove = true;
int selected = getStepOfHorse(initialPoint.x, initialPoint.y, totalStep = Math.max(totalStep, 1));
exitGame(selected ==1 ?1:-1);
}
});
}
完成以上5步则可以加载游戏地图
6 获取马儿下一步左走位置的集合
/*
* @param: [i, j]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:24
* @description: 获取马儿下一步所需要走的位置集合
*/
private ArrayList<Point> nextSteps(int i, int j){
ArrayList<Point> horseStepList = new ArrayList<>();//为每个递归栈开辟存储马儿下一步位置的集合
//八个方向
nextSteps(j - 2,i - 1, j - 2 >= 0 && i - 1 >= 0,horseStepList);
nextSteps(j - 1,i - 2, j - 1 >= 0 && i - 2 >= 0,horseStepList);
nextSteps(j + 1,i - 2, j + 1 < KNIGHT_LENGTH && i - 2 >= 0,horseStepList);
nextSteps(j + 2,i - 1, j + 2 < KNIGHT_LENGTH && i - 1 >= 0,horseStepList);
nextSteps(j + 2,i + 1, j + 2 < KNIGHT_LENGTH && i + 1 < KNIGHT_LENGTH,horseStepList);
nextSteps(j + 1,i + 2, j + 1 < KNIGHT_LENGTH && i + 2 < KNIGHT_LENGTH,horseStepList);
nextSteps(j - 1,i + 2, j - 1 >= 0 && i + 2 < KNIGHT_LENGTH,horseStepList);
nextSteps(j - 2,i + 1, j - 2 >= 0 && i + 1 < KNIGHT_LENGTH,horseStepList);
return horseStepList;
}
/*
* @param: [nextJ, nextI, isSelected,horseStepList]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:26
* @description: 获取下一步集合
*/
private void nextSteps(int nextJ, int nextI, boolean isSelected, ArrayList<Point> horseStepList){
if (isSelected) {
if (!isVisited[nextI * KNIGHT_LENGTH + nextJ]){
horseStepList.add(new Point(nextI, nextJ));
}
}
}
7 为每个棋格控件绑定鼠标事件方法
/*
* @param: [key, i, j, step, point,horseStepList]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:23
* @description: 给马儿下一步将要走的位置绑定鼠标事件
*/
private void addHorseMouseListener(KnightTourMap key, int i, int j, Point point, ArrayList<Point> horseStepList){
KnightTourMap component = map[point.x][point.y];
key.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent event) {
if (!isFinished) {
removeOldKeyMouseListeners(horseStepList);
horseStepList.remove(point);
removeOldHorse(i,j, totalStep);
int selected = -1;
if (component.getBackground() == Color.BLUE &&!isVisited[point.x * KNIGHT_LENGTH + point.y]){
initialPoint.setLocation(point.x, point.y);//记录当前走的位置
selected = getStepOfHorse(point.x,point.y, totalStep+1);
}
clearCurrentKnightMapColor(horseStepList,component);
if ( selected == 1 || selected == 0) { //回溯结束并满足退出条件
exitGame(selected);
}
}
}
public void mouseEntered(MouseEvent e) {
if (component.getBackground() == Color.BLUE && !isVisited[point.x*KNIGHT_LENGTH + point.y]){
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
else setCursor(Cursor.getDefaultCursor());
}
public void mouseExited(MouseEvent e) {
setCursor(Cursor.getDefaultCursor());
}
});
}
8 马踏棋盘核心方法,放置马儿,绑定鼠标事件,自动运行,自动退出
/*
* @param: [i, j, step]
* @return: int
* @author: Jerssy
* @dateTime: 2021/5/25 13:18
* @description: 马踏棋盘核心方法,放置马儿,绑定鼠标事件,自动运行
*/
private int getStepOfHorse(int i, int j, int step) {
ArrayList<Point> horseStepList = nextSteps(i, j);
isVisited[i * KNIGHT_LENGTH + j] = true; //标记该位置已经访问
putHorse(map[i][j],i,j);
jLabel.setText("Step: "+(totalStep = step));
if (!isFinished){
if (isAutoMove) {//如果是自动执行则对马儿下一步所走位置进行升序排序
optimalNextStepSort(horseStepList);
if (step < KNIGHT_LENGTH*KNIGHT_LENGTH) removeOldHorse(i,j, step);
}
for (Iterator<Point> iterator = horseStepList.iterator();iterator.hasNext();) {
Point point = iterator.next();
if (!isVisited[point.x * KNIGHT_LENGTH + point.y]) {
if (!isAutoMove) {
map[point.x][point.y].setBackground(Color.BLUE);
addHorseMouseListener(map[point.x][point.y], i, j, point,horseStepList);
} else {
iterator.remove(); //删除已经走过的位置
int selected = getStepOfHorse(point.x, point.y, step + 1);
if (!isFinished && step < KNIGHT_LENGTH * KNIGHT_LENGTH){//回溯中
isVisited[i * KNIGHT_LENGTH + j] = false;
}
return selected;
}
}
}
if (step == KNIGHT_LENGTH * KNIGHT_LENGTH) return 1;
if (horseStepList.isEmpty()) return 0;
}
return -1;
}
9 清空棋盘
/*
* @param: []
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:24
* @description: 清除棋盘信息
*/
private void clearKnightTourMap(){
if (totalStep == 1) return;
for (int i = 0; i < KNIGHT_LENGTH; i++) {
for (int j = 0; j < KNIGHT_LENGTH; j++) {
if (map[i][j].getComponents().length !=0) {
map[i][j].removeAll();
}
setBackgroundColor(i,j, map[i][j]);
isVisited[i * KNIGHT_LENGTH + j] = false;
}
}
isAutoMove = false;
isFinished = false;
getStepOfHorse(initialPoint.x = KNIGHT_LENGTH/2, initialPoint.y = KNIGHT_LENGTH/2, totalStep = 1);
}
10 退出游戏
```c
/*
* @param: [selected]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:24
* @description: 退出游戏
*/
private void exitGame(int selected){
if (!isFinished){
isFinished = true;
if (selected == 0){
JOptionPane.showMessageDialog(KnightTourFrame.this,"很遗憾,你输了,一共走了" + totalStep + "步","Game Over",JOptionPane.INFORMATION_MESSAGE);
}
else if (selected == 1){
int exitNum = JOptionPane.showConfirmDialog(KnightTourFrame.this, "恭喜,你赢了!是否在来一次?", "Winner",JOptionPane.YES_NO_OPTION);
if (exitNum == 0 ) clearKnightTourMap();
}
else {
JOptionPane.showMessageDialog(KnightTourFrame.this,"此行走路线无法走通","错误路线",JOptionPane.ERROR_MESSAGE);
clearKnightTourMap();
}
}
}
游戏运行
1 自动运行
自动运行可能执行失败即找不到合适的行走路线
1 棋盘格局过小,无法行走
2 用户使用鼠标点击了马儿行走位置然后在使用自动执行,但从用户点击的位置开始无法找到合适的行走路线导致失败
数字代表其走过的位置路线
星星代码该位置已走过;
最后马儿的位置即马儿最后所停在的位置
如 :用户点击了错误路线,从此位置开始无法找到合适路线,点击确定会自动重新开始
棋盘格局太小
2 用户点击鼠标控制马儿行走
行走失败的
成功一种的路线
用户先控制马儿走几步然后自动执行:此种方式会按用户最后点击的位置开始执行,可能存在失败即路线无法走通的情况
完整代码
package Algorithm.KnightTour;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.ImagingOpException;
import java.util.*;
/**
* @author: Jerssy
* @create: 2021-05-22 18:02
* @version: V1.0
* @slogan: 业精于勤, 荒于嬉;行成于思,毁于随。
* @description: 骑士周游问题(马踏棋盘问题)
*
*
* 马踏棋盘游戏代码实现
*
* 马踏棋盘问题(骑士周游问题)实际上是图的深度优先搜索(DFS)的应用。
*
* 如果使用回溯(就是深度优先搜索)来解决,假如马儿踏了53个点,如图:走到了第53个,坐标(1,0),发现已经走到尽头,没办法,那就只能回退了,查看其他的路径,就在棋盘上不停的回溯…… ,思路分析+代码实现
*
*
* 使用前面的游戏来验证算法是否正确。
*
* 心得:递归栈里如果有对引用数据类型的操作如删除,添加可能会造成ConcurrentModificationException 异常,解决:为每一个递归栈开辟空间避免冲突
*
*/
public class KnightTour {
private static final int FRAME_WIDTH = 600;//窗口长
private static final int FRAME_HEIGHT = 600;//窗口宽
private static final int KNIGHT_LENGTH = 6;//棋盘大小
private static final String HORSE_SRC = "Algorithm\\src\\Algorithm\\KnightTour\\horse.png";
private static final String HORSE_IS_VISITED_SRC = "Algorithm\\src\\Algorithm\\KnightTour\\isVisited.png";
public static void main(String[] args) {
new KnightTourFrame();
}
/*
* @param: [i, j, horseJPanel]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:47
* @description: 恢复棋盘最初的颜色
*/
private static void setBackgroundColor(int i, int j, JPanel horseJPanel){
if ((i + j) % 2 == 0) {
horseJPanel. setBackground(Color.lightGray);
} else {
horseJPanel. setBackground(Color.WHITE);
}
horseJPanel.repaint();
}
private static class KnightTourFrame extends JFrame {
private final KnightTourMap[][] map = new KnightTourMap[KNIGHT_LENGTH][KNIGHT_LENGTH];//棋盘数组
private final boolean[] isVisited; //棋盘是否访问数组
private JLabel jLabel;//显示统计步数控件
private int totalStep; //总步数
private boolean isAutoMove = false; //自动运行标志
private final Point initialPoint;//初始化位置索引坐标
private boolean isFinished = false;//结束标志
public KnightTourFrame() {
setSize(FRAME_WIDTH, FRAME_HEIGHT);
setLocationRelativeTo(null);
JPanel panel = new JPanel();
JPanel topPanel = new JPanel();
setResizable(false);
setVisible(true);
initialJPanel(topPanel,panel);
isVisited = new boolean[KNIGHT_LENGTH * KNIGHT_LENGTH];
initialPoint = new Point(KNIGHT_LENGTH/2,KNIGHT_LENGTH/2); //默认在棋盘中间放入马儿
initialization(panel);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
/*
* @param: [topPanel, panel]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 20:16
* @description:初始化JPanel控件
*/
private void initialJPanel(JPanel topPanel,JPanel panel){
topPanel.setLayout(new GridLayout(1,3,10,10));
topPanel.setSize(new Dimension(FRAME_WIDTH,40));
jLabel = new JLabel("",JLabel.CENTER);
JButton button = new JButton("重来");
button.setVerticalTextPosition(JButton.CENTER);
button.setPreferredSize(new Dimension(50,40));
button.setMargin(new Insets(5,5,5,5));
button.setRequestFocusEnabled(false);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
JButton autoButton = new JButton("自动运行");
autoButton.setVerticalTextPosition(JButton.CENTER);
button.setPreferredSize(new Dimension(50,40));
button.setMargin(new Insets(5,5,5,5));
autoButton.setRequestFocusEnabled(false);
autoButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
jLabel.setForeground(Color.red);
jLabel.setPreferredSize(new Dimension(50,40));
topPanel.add(autoButton);
topPanel.add(jLabel);
topPanel.add(button);
panel.setLayout(new GridLayout(KNIGHT_LENGTH, KNIGHT_LENGTH));
add(topPanel,BorderLayout.BEFORE_FIRST_LINE);
add(panel, BorderLayout.CENTER);
button.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
clearKnightTourMap();
}
});
autoButton.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (isFinished) return;
isAutoMove = true;
int selected = getStepOfHorse(initialPoint.x, initialPoint.y, totalStep = Math.max(totalStep, 1));
exitGame(selected ==1 ?1:-1);
}
});
}
/*
* @param: [panel]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:22
* @description:初始化棋盘
*/
private void initialization(JPanel panel) {
for (int i = 0; i < KNIGHT_LENGTH; i++) {
for (int j = 0; j < KNIGHT_LENGTH; j++) {
map[i][j] = new KnightTourMap(i, j);
panel.add(map[i][j]);
}
}
if (KNIGHT_LENGTH < 5) {
JOptionPane.showMessageDialog(KnightTourFrame.this,"棋盘格局太小,是无法找到合适的行走路线滴","警告",JOptionPane.WARNING_MESSAGE);
isFinished = true;
}
getStepOfHorse(initialPoint.x, initialPoint.y, totalStep = 1);
}
/*
* @param: [i, j, step]
* @return: int
* @author: Jerssy
* @dateTime: 2021/5/25 13:18
* @description: 马踏棋盘核心方法,放置马儿,绑定鼠标事件,自动运行
*/
private int getStepOfHorse(int i, int j, int step) {
ArrayList<Point> horseStepList = nextSteps(i, j);
isVisited[i * KNIGHT_LENGTH + j] = true; //标记该位置已经访问
putHorse(map[i][j],i,j);
jLabel.setText("Step: "+(totalStep = step));
if (!isFinished){
if (isAutoMove) {
optimalNextStepSort(horseStepList);
if (step < KNIGHT_LENGTH*KNIGHT_LENGTH) removeOldHorse(i,j, step);
}
for (Iterator<Point> iterator = horseStepList.iterator();iterator.hasNext();) {
Point point = iterator.next();
if (!isVisited[point.x * KNIGHT_LENGTH + point.y]) {
if (!isAutoMove) {
map[point.x][point.y].setBackground(Color.BLUE);
addHorseMouseListener(map[point.x][point.y], i, j, point,horseStepList);
} else {
iterator.remove();
int selected = getStepOfHorse(point.x, point.y, step + 1);
if (!isFinished && step < KNIGHT_LENGTH * KNIGHT_LENGTH){//回溯中
isVisited[i * KNIGHT_LENGTH + j] = false;
}
return selected;
}
}
}
if (step == KNIGHT_LENGTH * KNIGHT_LENGTH) return 1;
if (horseStepList.isEmpty()) return 0;
}
return -1;
}
/*
* @param: [horseStepList]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 20:12
* @description: 排序,获取该位置的下一步的下一步最少路径集合,减少回溯次数,使每次选择的都是最优路线
*/
private void optimalNextStepSort(ArrayList<Point> horseStepList){
horseStepList.sort(Comparator.comparingInt(o -> nextSteps(o.x, o.y).size()));
}
/*
* @param: [key, i, j, step, point,horseStepList]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:23
* @description: 给马儿下一步将要走的位置绑定鼠标事件
*/
private void addHorseMouseListener(KnightTourMap key, int i, int j, Point point, ArrayList<Point> horseStepList){
KnightTourMap component = map[point.x][point.y];
key.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent event) {
if (!isFinished) {
removeOldKeyMouseListeners(horseStepList);
horseStepList.remove(point);
removeOldHorse(i,j, totalStep);
int selected = -1;
if (component.getBackground() == Color.BLUE &&!isVisited[point.x * KNIGHT_LENGTH + point.y]){
initialPoint.setLocation(point.x, point.y);//记录当前走的位置
selected = getStepOfHorse(point.x,point.y, totalStep+1);
}
clearCurrentKnightMapColor(horseStepList,component);
if ( selected == 1 || selected == 0) { //回溯结束并满足退出条件
exitGame(selected);
}
}
}
public void mouseEntered(MouseEvent e) {
if (component.getBackground() == Color.BLUE && !isVisited[point.x*KNIGHT_LENGTH + point.y]){
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
else setCursor(Cursor.getDefaultCursor());
}
public void mouseExited(MouseEvent e) {
setCursor(Cursor.getDefaultCursor());
}
});
}
/*
* @param: []
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:24
* @description: 清除棋盘信息
*/
private void clearKnightTourMap(){
if (totalStep == 1) return;
for (int i = 0; i < KNIGHT_LENGTH; i++) {
for (int j = 0; j < KNIGHT_LENGTH; j++) {
if (map[i][j].getComponents().length !=0) {
map[i][j].removeAll();
}
setBackgroundColor(i,j, map[i][j]);
isVisited[i * KNIGHT_LENGTH + j] = false;
}
}
isAutoMove = false;
isFinished = false;
getStepOfHorse(initialPoint.x = KNIGHT_LENGTH/2, initialPoint.y = KNIGHT_LENGTH/2, totalStep = 1);
}
/*
* @param: [selected]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:24
* @description: 退出游戏
*/
private void exitGame(int selected){
if (!isFinished){
isFinished = true;
if (selected == 0){
JOptionPane.showMessageDialog(KnightTourFrame.this,"很遗憾,你输了,一共走了" + totalStep + "步","Game Over",JOptionPane.INFORMATION_MESSAGE);
}
else if (selected == 1){
int exitNum = JOptionPane.showConfirmDialog(KnightTourFrame.this, "恭喜,你赢了!是否在来一次?", "Winner",JOptionPane.YES_NO_OPTION);
if (exitNum == 0 ) clearKnightTourMap();
}
else {
JOptionPane.showMessageDialog(KnightTourFrame.this,"此行走路线无法走通","错误路线",JOptionPane.ERROR_MESSAGE);
clearKnightTourMap();
}
}
}
/*
* @param: [i, j]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:24
* @description: 获取马儿下一步所需要走的位置集合
*/
private ArrayList<Point> nextSteps(int i, int j){
ArrayList<Point> horseStepList = new ArrayList<>();//为每个递归栈开辟存储马儿下一步位置的集合
//八个方向
nextSteps(j - 2,i - 1, j - 2 >= 0 && i - 1 >= 0,horseStepList);
nextSteps(j - 1,i - 2, j - 1 >= 0 && i - 2 >= 0,horseStepList);
nextSteps(j + 1,i - 2, j + 1 < KNIGHT_LENGTH && i - 2 >= 0,horseStepList);
nextSteps(j + 2,i - 1, j + 2 < KNIGHT_LENGTH && i - 1 >= 0,horseStepList);
nextSteps(j + 2,i + 1, j + 2 < KNIGHT_LENGTH && i + 1 < KNIGHT_LENGTH,horseStepList);
nextSteps(j + 1,i + 2, j + 1 < KNIGHT_LENGTH && i + 2 < KNIGHT_LENGTH,horseStepList);
nextSteps(j - 1,i + 2, j - 1 >= 0 && i + 2 < KNIGHT_LENGTH,horseStepList);
nextSteps(j - 2,i + 1, j - 2 >= 0 && i + 1 < KNIGHT_LENGTH,horseStepList);
return horseStepList;
}
/*
* @param: [horseStepList]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:25
* @description: 清除已经访问的位置上绑定的鼠标事件
*/
private void removeOldKeyMouseListeners(ArrayList<Point> horseStepList){
for (Point key: horseStepList) {
KnightTourMap knightTourMap;
if ((knightTourMap = map[key.x][key.y]).getMouseListeners().length > 0){
knightTourMap.removeMouseListener(knightTourMap.getMouseListeners()[0]);
setCursor(Cursor.getDefaultCursor());
}
}
}
/*
* @param: [nextJ, nextI, isSelected,horseStepList]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:26
* @description: 获取下一步集合
*/
private void nextSteps(int nextJ, int nextI, boolean isSelected, ArrayList<Point> horseStepList){
if (isSelected) {
if (!isVisited[nextI * KNIGHT_LENGTH + nextJ]){
horseStepList.add(new Point(nextI, nextJ));
}
}
}
/*
* @param: [horseStepList,component]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:26
* @description: 点击下一步时候恢复当前被染蓝色位置棋盘的原来背景颜色
*/
private void clearCurrentKnightMapColor(ArrayList<Point> horseStepList, KnightTourMap component) {
if (horseStepList.size() > 0 ){
for ( Point key : horseStepList) {
map[key.x][key.y].setBackground(component.getBackground());
map[key.x][key.y].setBounds(0,0,0,0);
}
}
}
/*
* @param: [panel, i, j]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:27
* @description: 棋盘网格上放置马儿
*/
private void putHorse(KnightTourMap panel,int i,int j){
if (panel.getComponents().length == 0){
HorseJPanel horseJPanel = new HorseJPanel(HORSE_SRC);
panel.add(horseJPanel, BorderLayout.CENTER);
panel.setBounds(0, 0, 0, 0);
setBackgroundColor(i, j, horseJPanel);
panel.setBackground(horseJPanel.getBackground());
}
}
/*
* @param: [i,j, step]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:27
* @description: 清除已经放置的马儿并设置该位置放入已访问图片,加入记录步数控件
*/
private void removeOldHorse(int i,int j, int step){
KnightTourMap key = map[i][j];
if (key.getComponents().length == 1 ){
key.remove(0);
HorseJPanel isVisitedJPanel = new HorseJPanel(HORSE_IS_VISITED_SRC);
JLabel label = new JLabel(String.valueOf(step));
label.setForeground(Color.DARK_GRAY);
label.setFont(new Font("宋体",Font.BOLD,13));
key.add(isVisitedJPanel);
key.add(label);
setBackgroundColor(i,j,key);
isVisitedJPanel.setBackground(key.getBackground());
}
}
}
private static class KnightTourMap extends JPanel {
public KnightTourMap(int i, int j) {
setBackgroundColor(i,j,this);
setBounds(0,0,0,0);
}
}
private static class HorseJPanel extends JPanel{
Image icon;
private final int maxHeight;
private final int maxWidth;
private int scalingWidth;
private int scalingHeight;
public HorseJPanel(String src){
icon = new ImageIcon(src).getImage();
int srcWidth = icon.getWidth(null);
if (srcWidth < 0) throw new ImagingOpException("图片加载失败") ;
int srcHeight = icon.getHeight(null);
maxWidth = FRAME_WIDTH / KNIGHT_LENGTH;
maxHeight = FRAME_HEIGHT / KNIGHT_LENGTH;
scalingWidth = srcWidth;
scalingHeight = srcHeight;
initialImage(srcWidth);
}
/*
* @param: [srcWidth]
* @return: void
* @author: Jerssy
* @dateTime: 2021/5/25 13:28
* @description: 缩放图片长度和宽度,使之自适应棋盘网格
*/
private void initialImage(int srcWidth){
float scalingFactor;
if (scalingWidth > maxWidth/2) {
scalingFactor = (float) srcWidth/(float) maxWidth;
scalingWidth = (int) ((scalingWidth-maxWidth/2)/scalingFactor);
scalingHeight = Math.round((float)scalingHeight/ scalingFactor);
}
if (scalingWidth < maxWidth/2) scalingWidth = maxWidth/2;
int srcHeight = scalingHeight;
if (scalingHeight > maxHeight/2) {
scalingFactor = (float) srcHeight /(float) maxHeight;
scalingHeight = (int) ((scalingHeight-maxHeight/2)/scalingFactor);
scalingWidth = Math.round((float)scalingWidth/scalingFactor);
}
if (scalingHeight < maxHeight / 2) scalingHeight = maxHeight/2;
}
protected void paintComponent(Graphics g ) {
super.paintComponent(g);
setBounds((maxWidth-scalingWidth)/2 ,(maxHeight-scalingHeight)/2 ,scalingWidth,scalingHeight);
g.drawImage(icon,0,0,scalingWidth , scalingHeight, this);
}
}
}