因爲課程的關係,要交作業就寫了這個小遊戲。
下面先看看架構
對於程序的難點:
- 對於拼圖是否存在解的處理: (查到大佬結論)N×N的棋盤,N爲奇數時,與八數碼問題相同。逆序奇偶同性可互達
N爲偶數時,空格每上下移動一次,奇偶性改變。稱空格位置所在的行到目標空格所在的行步數爲空格的距離(不計左右距離),若兩個狀態的可相互到達,則有,兩個狀態的逆序奇偶性相同且空格距離爲偶數,或者,逆序奇偶性不同且空格距離爲奇數數。否則不能。
也就是說,當此表達式成立時,兩個狀態可相互到達:(狀態1奇偶性狀態2奇偶性)(空格距離%2==0)。 - 切圖:因爲一開始不知道可以通過JAVA實現切圖(JAVA弱渣)一直搗鼓用PPT,PS各種瞎搞,後來知道後大罵自己一句傻逼。
- 自動尋路算法:原本設想是速學一波啓發式搜索趕在交作業前完成的,結果還是高估了自己的智商,到現在依舊沒有掌握該算法的精髓。簡單寫了廣搜實現的復原,不過還是得抓緊時間將啓發式搜索補好,廣搜處理複雜度過不了3階以上的拼圖。
剩下的就只要基本掌握某一門語言基礎語法,隨便搞搞應該就可以完成了。
下面上代碼:
Puzzle窗體類
package demo;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import javax.swing.*;
public class Puzzle extends JFrame {
static HashMap<Integer, Puzzle> hash = new HashMap<Integer, Puzzle>(); //哈希表存儲遊戲窗口
static GamePanel gamePanel; //遊戲面板
static TopPanel topPanel; //原圖展示面板+功能面板
static int _n; //n階拼圖
public static void main(String[] args) {
// TODO Auto-generated method stub
init();
}
public Puzzle(int n, boolean isMove) {
super();
this.setTitle("Puzzle"); //設置遊戲名稱
this.setSize(426,760); //設置窗體大小
this.setResizable(false); //設置窗體大小無法修改
this.setLocationRelativeTo(null); //將遊戲窗口設置在中間
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //關閉遊戲窗口設置
this._n = n;
gamePanel = new GamePanel(this._n, isMove); //建立遊戲面板傳遞N階拼圖
topPanel = new TopPanel(hash); //建立遊戲功能面板並傳遞遊戲窗體
this.add(gamePanel,BorderLayout.CENTER); //將面板加入窗體
this.add(topPanel,BorderLayout.NORTH);
this.setVisible(true); //將窗體可視
hash.put(0, this);
}
public static void init() {
Puzzle Game = new Puzzle(3, true); //創建遊戲窗體
}
}
TopPanel面板類
package demo;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.HashMap;
import javax.swing.JPanel;
import javax.swing.border.TitledBorder;
public class TopPanel extends JPanel{
final String[] src = {"img\\Original.jpg"};
public TopPanel(HashMap<Integer, Puzzle> hash) {
// TODO Auto-generated constructor stub
super();
this.setBorder(new TitledBorder(null, "",TitledBorder.DEFAULT_JUSTIFICATION,TitledBorder.DEFAULT_POSITION, null, null));
ImgPanel panel1 = new ImgPanel(src[0]);
FuncPanel panel2 = new FuncPanel(hash);
this.add(panel1);
this.add(panel2);
}
}
ImgPanel面板類
package demo;
import java.awt.Dimension;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
//鍘熷浘闈㈡澘
public class ImgPanel extends JPanel {
public ImgPanel(String src) {
// TODO Auto-generated constructor stub
super();
this.setPreferredSize(new Dimension(300,300));
final JLabel img = new JLabel(new ImageIcon(src));
this.add(img);
}
}
FuncPanel面板類
package demo;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.util.HashMap;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
//功能面板
public class FuncPanel extends JPanel {
final String[] names = {"開始遊戲", "下一關卡", "重置遊戲", "復原拼圖"}; //遊戲功能項
public FuncPanel(HashMap<Integer, Puzzle> hash) {
// TODO Auto-generated constructor stub
super();
this.setPreferredSize(new Dimension(100,300)); //設置面板大小
final FlowLayout flowLayout = new FlowLayout(FlowLayout.CENTER, 0, 36); //設置流佈局
this.setLayout(flowLayout);
JButton start = new JButton();
start.setText(this.names[0]);
this.add(start);
start.addActionListener(new ActionListener() { //重新開始遊戲,起點爲3階拼圖
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
JFrame newGame = new JFrame();
newGame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
final FlowLayout Flow = new FlowLayout();
Flow.setAlignment(FlowLayout.CENTER);
Flow.setHgap(30);
newGame.getContentPane().setLayout(Flow);
newGame.setResizable(false);
newGame.setSize(200,100);
JLabel text = new JLabel();
text.setText("NewGame Start!!!");
text.setSize(100,30);
newGame.getContentPane().add(text);
JButton label = new JButton("確定");
label.setBounds(10, 10, 100, 23);
newGame.getContentPane().add(label);
label.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
newGame.dispatchEvent(new WindowEvent(newGame,WindowEvent.WINDOW_CLOSING) );
Puzzle Game = hash.get(0);
Game.setVisible(false);
Game = new Puzzle(3, true);
}
});
newGame.setLocationRelativeTo(null);
newGame.setVisible(true);
}
});
JButton nextGame = new JButton();
nextGame.setText(this.names[1]);
this.add(nextGame);
nextGame.addActionListener(new ActionListener() { //進入下一卡關,當通關5階拼時將不再進入下一卡關並給予遊戲者提醒
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
Puzzle Game = hash.get(0);
if(Game.gamePanel.chack()) {
if(Game._n == 5) {
JFrame tipGame = new JFrame();
tipGame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
final FlowLayout Flow = new FlowLayout();
Flow.setAlignment(FlowLayout.CENTER);
Flow.setHgap(30);
tipGame.getContentPane().setLayout(Flow);
tipGame.setResizable(false);
tipGame.setSize(200,100);
JLabel text = new JLabel();
text.setText("抱歉,您已經通過所有關卡!!!");
text.setSize(100,30);
tipGame.getContentPane().add(text);
JButton label = new JButton("確定");
label.setBounds(10, 10, 100, 23);
tipGame.getContentPane().add(label);
label.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tipGame.dispatchEvent(new WindowEvent(tipGame,WindowEvent.WINDOW_CLOSING) );
}
});
tipGame.setLocationRelativeTo(null);
tipGame.setVisible(true);
}
else {
Game.setVisible(false);
Game = new Puzzle(Game._n+1, true);
}
}
else {
JFrame rtGame = new JFrame();
rtGame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
final FlowLayout Flow = new FlowLayout();
Flow.setAlignment(FlowLayout.CENTER);
Flow.setHgap(30);
rtGame.getContentPane().setLayout(Flow);
rtGame.setResizable(false);
rtGame.setSize(200,100);
JLabel lowerTip = new JLabel();
lowerTip.setText("抱歉,您暫未通過本關卡!!!");
lowerTip.setSize(100,30);
rtGame.getContentPane().add(lowerTip);
JButton label = new JButton("確定");
label.setBounds(10, 10, 100, 23);
rtGame.getContentPane().add(label);
label.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
rtGame.dispatchEvent(new WindowEvent(rtGame,WindowEvent.WINDOW_CLOSING) );
}
});
rtGame.setLocationRelativeTo(null);
rtGame.setVisible(true);
}
}
});
JButton reset = new JButton();
reset.setText(names[2]);
this.add(reset);
reset.addActionListener(new ActionListener() { //重置遊戲
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
Puzzle Game = hash.get(0);
Game.gamePanel.reSet( Game.gamePanel._n);
}
});
JButton repair = new JButton();
repair.setText(names[3]);
this.add(repair);
repair.addActionListener(new ActionListener() { //復原拼圖,自動尋路
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
//------------------------------------自動尋路算法-------------------------------------------
Puzzle Game = hash.get(0);
if(Game.gamePanel._n==3) {
int[][] dir = {{1,0},{0,1},{-1, 0},{0,-1}};
boolean[] vis = new boolean[362881];
for(int i = 0; i < 362881; i++) vis[i] = false;
int head = 0, tail = 1;
AiArr[] array = new AiArr[370000];
AiArr[] res = new AiArr[370000];
int idx = 0, n = Game.gamePanel._n;
for(int i = 0; i < n*n; i++) {
if(Game.gamePanel.rondomArr[i] == n*n-1) {
idx = i;
break;
}
}
array[0] = new AiArr(Game.gamePanel.rondomArr, idx, -1, -1);
vis[array[0].calc()] = true;
if(vis[1] == true) {
Game.gamePanel.winGame();
}
else {
while(head!=tail) {
AiArr k = array[head];
int x = (k._idx)/n, y = (k._idx)%n;
for(int i = 0; i < 4; i++) {
int tx = x+dir[i][0], ty = y+dir[i][1];
if(tx < 0 || tx >= n || ty < 0 || ty >= n) continue;
AiArr tmp = k.move(i, head);
if(vis[tmp.calc()]==true) continue;
array[tail] = tmp;
vis[array[tail].calc()] = true;
if(vis[1] == true) {
break;
}
tail+=1;
}
if(vis[1] == true) break;
head++;
}
int t = 0;
res[t] = array[tail];
while(res[t]._node!=-1) {
t+=1;
res[t] = array[res[t-1]._node];
}
final int gbk = t;
new Thread(new Runnable(){
@Override
public void run() {
try {
for(int i = gbk-1; i >=0; i--) {
Game.gamePanel.moveSet(res[i]._dir, res[i+1]._idx);
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
else {
JFrame tipWin = new JFrame();
tipWin.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
final FlowLayout Flow = new FlowLayout();
Flow.setAlignment(FlowLayout.CENTER);
Flow.setHgap(30);
tipWin.getContentPane().setLayout(Flow);
tipWin.setResizable(false);
tipWin.setSize(200,100);
JLabel text = new JLabel();
text.setText("功能暫未實現!!!");
text.setSize(100,30);
tipWin.getContentPane().add(text);
JButton label = new JButton("確定");
label.setBounds(10, 10, 100, 23);
tipWin.getContentPane().add(label);
label.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tipWin.dispatchEvent(new WindowEvent(tipWin,WindowEvent.WINDOW_CLOSING) );
}
});
tipWin.setLocationRelativeTo(null);
tipWin.setVisible(true);
}
}
});
}
}
尋路算法結構類
package demo;
public class AiArr {
int[] _arr;
int _idx;
int _dir;
int _node;
private int[][] __dir = {{1,0},{0,1},{-1, 0},{0,-1}};
public AiArr() {
}
public AiArr(int[] arr, int idx, int dir, int node) {
_arr = arr;
_idx = idx;
_dir = dir;
_node = node;
}
public AiArr move(int dir, int node) {
int x = _idx/3, y = _idx%3;
int tx = x+__dir[dir][0], ty = y+__dir[dir][1];
int t = _arr[x*3+y];
int[] newArr = new int[9];
for(int i = 0; i < 9; i++) newArr[i] = _arr[i];
newArr[x*3+y] = newArr[tx*3+ty];
newArr[tx*3+ty] = t;
return new AiArr(newArr, tx*3+ty, dir, node);
}
public int fac(int n) {
int x = 1;
for(int i = 1; i <= n; i++) x*=i;
return x;
}
public int calc() {
int len = 9, ans = 0;
for(int i = 0; i < len; i++) {
int Count = 0;
for(int j = i+1; j < len; j++) {
if(_arr[i] > _arr[j]) {
Count+=1;
}
}
ans+=(Count*fac(len-i-1));
}
return ans+1;
}
}
GamePanel面板類
package demo;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class GamePanel extends JPanel{
final String[] srcs = {"img\\easy\\", "img\\mediun\\", "img\\hard\\"};
String src;
JButton[] icons;
JButton[] TMP;
final JButton[] buttons;
int _n;
int[] rondomArr;
int[] rondomTmpArr;
int[][] dir = {{1,0},{0,1},{-1, 0},{0,-1}};
public GamePanel(int n, boolean vis) {
// TODO Auto-generated constructor stub
_n = n;
this.removeAll();
final GridLayout gridLayout = new GridLayout(n, n);
gridLayout.setHgap(0);
gridLayout.setVgap(0);
this.setLayout(gridLayout);
src = srcs[n-3];
buttons = new JButton[n*n];
for(int row = 0; row < n; row++) {
for(int col = 0; col < n; col++) {
String s = src+row+col+".jpg";
buttons[row*n+col] = new JButton(new ImageIcon(s));
}
}
icons = new JButton[n*n];
TMP = new JButton[n*n];
rondomArr = rondom(n*n);
rondomTmpArr = new int[n*n];
for(int i = 0; i < n*n; i++) {
rondomTmpArr[i] = rondomArr[i];
icons[i] = buttons[rondomArr[i]];
icons[i].setName(""+i);
TMP[i] = new JButton(icons[i].getIcon());
this.add(icons[i]);
icons[i].addActionListener(new MoveButtonAction());
}
}
public static int[] rondom(int n) {
Random rondom = new Random();
int[] rondomArrT = new int[n];
boolean[] vis = new boolean[n];
int Count = -1, its = 1;
while(Count%2!=0 && (((int)Math.sqrt(n)-its)%2 == 1 || n%2==1)) {
for(int i = 0; i < n; i++) vis[i] = false;
for(int i = 0; i < n; i++) {
rondomArrT[i] = rondom.nextInt(n);
while(vis[rondomArrT[i]] == true) {
rondomArrT[i] = rondom.nextInt(n);
if(vis[rondomArrT[i]] == false) {
break;
}
}
vis[rondomArrT[i]] = true;
}
Count = 0;
for(int i = 0; i < n; i++) {
if(rondomArrT[i] == n-1) {
its = i/(int)Math.sqrt(n);
continue;
}
for(int j = 0; j < i; j++) {
if(rondomArrT[i] < rondomArrT[j] && rondomArrT[j]!=n-1) {
Count+=1;
}
}
}
}
return rondomArrT;
}
public void reSet(int n) {
for(int i = 0; i < n*n; i++) {
icons[i].setIcon(TMP[i].getIcon());
rondomArr[i] = rondomTmpArr[i];
}
}
public void moveSet(int xdir, int idx) { //移動設置
JButton tmp = new JButton(icons[idx].getIcon());
int tx = dir[xdir][0]+idx/_n, ty = dir[xdir][1]+idx%_n;
icons[idx].setIcon(icons[tx*_n+ty].getIcon());
icons[tx*_n+ty].setIcon(tmp.getIcon());
int TMP = rondomArr[idx];
rondomArr[idx] = rondomArr[tx*_n+ty];
rondomArr[tx*_n+ty] = TMP;
winGame();
}
class MoveButtonAction implements ActionListener { //移動判斷
public void actionPerformed(ActionEvent e) {
if(chack()) return;
JButton moveButton = (JButton)e.getSource();
int idx = Integer.parseInt(moveButton.getName());
int x = idx/_n, y = idx%_n;
String target = src+(_n-1)+(_n-1)+".jpg";
for(int i = 0; i < 4; i++) {
int tx = x+dir[i][0], ty = y+dir[i][1];
if(tx < 0 || tx >= _n || ty < 0 || ty >= _n) continue;
String original = new String(icons[tx*_n+ty].getIcon()+"");
if(original.equals(target)) {
moveSet(i, idx);
winGame();
break;
}
}
}
}
public boolean chack() { //檢測是否通關
// TODO Auto-generated method stub
for(int i = 0; i < _n*_n; i++) {
String s = src+(i/_n)+(i%_n)+".jpg";
if(s.equals(icons[i].getIcon()+"")==false) {
return false;
}
}
return true;
}
public void winGame() { //通關提示通知窗口
if(chack()) {
JFrame newGame = new JFrame();
newGame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
final FlowLayout Flow = new FlowLayout();
Flow.setAlignment(FlowLayout.CENTER);
Flow.setHgap(30);
newGame.getContentPane().setLayout(Flow);
newGame.setResizable(false);
newGame.setSize(200,100);
JLabel text = new JLabel();
text.setText("恭喜你,通關成功!!!");
text.setSize(100,30);
newGame.getContentPane().add(text);
JButton label = new JButton("確定");
label.setBounds(10, 10, 100, 23);
newGame.getContentPane().add(label);
label.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
newGame.dispatchEvent(new WindowEvent(newGame,WindowEvent.WINDOW_CLOSING) );
System.out.println();
}
});
newGame.setLocationRelativeTo(null);
newGame.setVisible(true);
}
}
}
展示部分: