1. 程式人生 > >Java遊戲開發——華容道

Java遊戲開發——華容道

遊戲介紹:

“華容道”是一款比較古老的遊戲,其源於三國時期著名的歷史故事。華容道作為一個經典遊戲,各部分的設計都恰到好處,非常巧妙,因此成為世界遊戲界的三大不可思議。

“華容道”遊戲初始時曹操被圍在華容道最裡層,玩家需要移動其他角色,使曹操順利到達出口。玩家先選擇需要移動的角色,然後拖動滑鼠,被選中的角色就會向滑鼠拖動的方向移動。最後,當成功地將曹操移動到出口時,遊戲結束。

本次開發的“華容道”執行效果如下圖所示:

 

 

 

 

使用到的素材資料夾:

 

素材及完整原始碼連結: https://pan.baidu.com/s/1_vOhAYk07h9dXBaez_lW3g 提取碼: sni6

遊戲設計思路:

“華容道”整體可以看成5*4的網格,其中張飛、趙雲、關羽、馬超、黃忠各佔兩個格子,兵佔一個格子,曹操最大佔4個格子。在這裡玩家滑鼠的操作可以看成對帶圖示的JButton的拖動。宣告變數W表示一個格子的邊長,自定義資料結構Node類,用於儲存每個關卡按鈕的初始位置,使用繼承自JButton的Person類用於每個帶人物圖示按鈕的顯示。

在這裡我約定Node的x,y下標等同於圖片按鈕左上角對應的二維陣列的x,y下標,id表示每個按鈕的人物ID,direction為true時橫放,direction為false時豎放。比如Node(0,0,0,true),表示曹操按鈕位於左上角。

Node類:

package 華容道;

public class Node {
	private int id;
	private boolean direction;//true為橫放,false為豎放
	private int x;//左上角陣列x下標
	private int y;//左上角陣列y下標
	
	public Node(int id,int x,int y,boolean direction){
		this.id = id;
		this.x = x;
		this.y = y;
		this.direction = direction;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public boolean getDirection() {
		return direction;
	}

	public void setDirection(boolean direction) {
		this.direction = direction;
	}

	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;
	}
	
	
}

Person類是帶人物ID的JButton

Person類:

package 華容道;

import java.awt.Color;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;

import javax.swing.JButton;

public class Person extends JButton{

	int id;//編號
	String name;

	
	public Person(int id,String str){
		super("");
		name = str;
		this.id = id;
	}
	
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
	
	
}

 MapFactory是儲存遊戲關卡資料的工廠類,MapFactory同時也支援歷史記錄的讀寫

MapFactory類:

package 華容道;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;

public class MapFactory {
		
	//ID編號對應關係:0曹操,1趙雲,2張飛,3關羽,4馬超,5黃忠,6小兵,7小兵,8小兵,9小兵
	//Node(id,x,y,direction),id對應每個角色,x,y對應左上角陣列下標,direction為true時,橫放,false時,豎放
	static String[] array;
	public static Node[][] map = new Node[][]{

		{//七步成詩
			new Node(0,2,2,true),new Node(1,2,0,false),new Node(2,0,1,false),
			new Node(3,0,3,false),new Node(4,0,0,false),new Node(5,2,1,false),
			new Node(6,4,0,true),new Node(7,4,1,true),new Node(8,4,2,true),new Node(9,4,3,true)
		},

		{//橫刀立馬
			new Node(0,0,1,true),new Node(1,0,3,false),new Node(2,2,3,false),
			new Node(3,2,1,true),new Node(4,0,0,false),new Node(5,2,0,false),
			new Node(6,4,0,true),new Node(7,3,1,true),new Node(8,3,2,true),new Node(9,4,3,true)
		},
		
		{//屯兵東路
			new Node(0,0,0,true),new Node(1,0,3,false),new Node(2,0,2,false),
			new Node(3,2,0,true),new Node(4,3,0,false),new Node(5,3,1,false),
			new Node(6,2,2,true),new Node(7,2,3,true),new Node(8,3,2,true),new Node(9,3,3,true)
		},
		{//插翅難飛
			new Node(0,0,1,true),new Node(1,0,0,false),new Node(2,4,2,true),
			new Node(3,4,0,true),new Node(4,2,1,false),new Node(5,0,3,false),
			new Node(6,2,0,true),new Node(7,3,0,true),new Node(8,2,3,true),new Node(9,3,3,true)
		},
		{//巧過五關
			new Node(0,0,1,true),new Node(1,2,2,true),new Node(2,3,2,true),
			new Node(3,4,1,true),new Node(4,3,0,true),new Node(5,2,0,true),
			new Node(6,0,0,true),new Node(7,1,0,true),new Node(8,0,3,true),new Node(9,1,3,true)
		},
		{//層層設防
			new Node(0,0,1,true),new Node(1,1,3,false),new Node(2,1,0,false),
			new Node(3,4,1,true),new Node(4,2,1,true),new Node(5,3,1,true),
			new Node(6,0,0,true),new Node(7,3,0,true),new Node(8,0,3,true),new Node(9,3,3,true)
		},
		{//近在咫尺
			new Node(0,3,2,true),new Node(1,0,1,false),new Node(2,3,0,true),
			new Node(3,2,0,true),new Node(4,0,3,false),new Node(5,0,2,false),
			new Node(6,0,0,true),new Node(7,1,0,true),new Node(8,2,2,true),new Node(9,2,3,true)
		},
		{//兵臨曹營
			new Node(0,0,1,true),new Node(1,3,1,false),new Node(2,3,2,false),
			new Node(3,2,1,true),new Node(4,2,0,false),new Node(5,2,3,false),
			new Node(6,0,0,true),new Node(7,1,0,true),new Node(8,0,3,true),new Node(9,1,3,true)
		},
		{//眾志成城
			new Node(0,1,1,true),new Node(1,3,1,false),new Node(2,3,3,false),
			new Node(3,0,2,true),new Node(4,0,0,false),new Node(5,2,0,false),
			new Node(6,0,1,true),new Node(7,1,3,true),new Node(8,2,3,true),new Node(9,4,2,true)
		},
		{//佳人梳妝
			new Node(0,1,0,true),new Node(1,3,1,false),new Node(2,3,2,false),
			new Node(3,0,2,true),new Node(4,1,2,false),new Node(5,2,3,false),
			new Node(6,3,0,true),new Node(7,4,0,true),new Node(8,1,3,true),new Node(9,4,3,true)
		}
	};
	
	
	
	//返回指定關卡布局資訊的拷貝(避免直接引用)
	public static Node[] getMap(int level){
		
		Node[] temp = new Node[10];
		for(int i=0;i<10;i++){
			temp[i] = map[level-1][i];
		}
		
		return temp;
	}
	
	public static int getRecord(int level){
		
		FileOutputStream fos = null;
		DataOutputStream dos = null;
		FileInputStream fis = null;
		DataInputStream dis = null;
		
		File file = new File("D://GameRecordAboutSwing");
		
		if(!file.exists()){
			file.mkdirs();
		}
		File record = new File("D://GameRecordAboutSwing/recordKlotskiGame.txt");

		try{
		if(!record.exists()){//如果不存在,新建文字
			record.createNewFile();
			fos = new FileOutputStream(record);
			dos = new DataOutputStream(fos);
			String s = "9999,9999,9999,9999,9999,9999,9999,9999,9999,9999";
			dos.writeBytes(s);
			System.out.println(record.isFile());;
		}
		//讀取記錄
		fis = new FileInputStream(record);
		dis = new DataInputStream(fis);
		String str = dis.readLine();
		array = str.split(",");
		
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			 try {
					if(fis!=null)
					 fis.close();
					if(dis!=null)
					 dis.close();			
					if(fos!=null)
			    	 fos.close();
					if(dos!=null)
					 dos.close();				
			     } catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		}
			
		return Integer.parseInt(array[level-1]);
		
	}
	
	public static void writeRecord(int level,int step){
		
		FileOutputStream fos = null;
		DataOutputStream dos = null;
		File record = new File("D://GameRecordAboutSwing/recordKlotskiGame.txt");
		
		try {
			//清空原有記錄
			FileWriter fileWriter =new FileWriter(record);
	        fileWriter.write("");
			fileWriter.flush();
			fileWriter.close();
	        //重新寫入文字
			fos = new FileOutputStream(record);
			dos = new DataOutputStream(fos);
			array[level-1] = step+"";
			StringBuilder s = new StringBuilder();
			s.append(array[0]);
			for(int i=1;i<array.length;i++){
				s.append(","+array[i]);
			}System.out.println(s.toString());
			dos.writeBytes(s.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
		     try {
				if(fos!=null)
		    	 fos.close();
				if(dos!=null)
				 dos.close();				
		     } catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		       
		}
        

		
	}
	
	
	public static void hello(){
		
	}
	
	
}

MapUtil是地圖工具類,方法直接呼叫工廠類方法

MapUtil類:

package 華容道;

public class MapUtil {

	public MapUtil(){
		
	}
	
	public Node[] getMap(int level){
		return MapFactory.getMap(level);
	}
	
	//獲取關卡歷史記錄
	public int getRecord(int level){	
		return MapFactory.getRecord(level);
	}
	
	//更新指定關卡記錄
	public void updateRecode(int level,int record){
		MapFactory.writeRecord(level, record);
	}
	
	
}

GamePanel是繼承自JPanel類的遊戲面板類,內部包含遊戲的主要邏輯:

①圖片的獲取

	
	//獲取圖片
	private void getIcons() {
				
		for(int i=0;i<15;i++){
			if(i==0){
				pics[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/KlotskiGame/pic"+i+".png").getScaledInstance(2*W, 2*W,Image.SCALE_SMOOTH);
			}else if(i==1||i==2||i==3||i==4||i==5){
				pics[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/KlotskiGame/pic"+i+".png").getScaledInstance(2*W, W,Image.SCALE_SMOOTH);
			}else if(i==6||i==7||i==8||i==9){
				pics[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/KlotskiGame/pic"+i+".png").getScaledInstance(W, W,Image.SCALE_SMOOTH);
			}else{
				pics[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/KlotskiGame/pic"+i+".png").getScaledInstance(W,2*W,Image.SCALE_SMOOTH);				
			}
			icons[i] = new ImageIcon(pics[i]);

			
		}
		repaint();
	}

②按鈕圖片顯示和按鈕位置的初始化

遍歷地圖按鈕位置資訊陣列map,然後根據map陣列元素對應的圖示ID,對相應的人物圖示按鈕陣列persons依次進行圖示和位置的初始化。在這裡只有黃忠、趙雲、關羽、馬超、張飛存在橫放豎放的選擇,豎放的圖示ID = 橫放的圖示ID+9

//對遊戲佈局進行初始化
	private void initialize() {
		
		for(int i=0;i<10;i++){
			switch(map[i].getId()){
			case 0://曹操,佔四格
				persons[i] = new Person(0,"曹操");
				persons[i].setIcon(icons[0]);
				persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, 2*W);
				break;
			case 1://趙雲,佔兩格
				persons[i] = new Person(1,"趙雲");
				if(map[i].getDirection()){//橫放
					persons[i].setIcon(icons[1]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, W);					
				}else{//豎放
					persons[i].setIcon(icons[10]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, 2*W);
				}
				break;
			case 2://張飛,佔兩格
				persons[i] = new Person(2,"張飛");
				if(map[i].getDirection()){//橫放
					persons[i].setIcon(icons[2]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, W);					
				}else{//豎放
					persons[i].setIcon(icons[11]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, 2*W);
				}
				break;
			case 3://關羽,佔兩格
				persons[i] = new Person(3,"關羽");
				if(map[i].getDirection()){//橫放
					persons[i].setIcon(icons[3]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, W);					
				}else{//豎放
					persons[i].setIcon(icons[12]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, 2*W);
				}
				break;
			case 4://馬超,佔兩格
				persons[i] = new Person(4,"馬超");
				if(map[i].getDirection()){//橫放
					persons[i].setIcon(icons[4]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, W);					
				}else{//豎放
					persons[i].setIcon(icons[13]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, 2*W);
				}
				break;
			case 5://黃忠,佔兩格
				persons[i] = new Person(5,"黃忠");
				if(map[i].getDirection()){//橫放
					persons[i].setIcon(icons[5]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, W);					
				}else{//豎放
					persons[i].setIcon(icons[14]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, 2*W);
				}
				break;
			case 6://小兵,佔一格
				persons[i] = new Person(6,"小兵");
				persons[i].setIcon(icons[6]);
				persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, W);									
				break;
			case 7://小兵,佔一格
				persons[i] = new Person(7,"小兵");
				persons[i].setIcon(icons[7]);
				persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, W);													
				break;
			case 8://小兵,佔一格
				persons[i] = new Person(8,"小兵");
				persons[i].setIcon(icons[8]);
				persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, W);									
				break;
			case 9://小兵,佔一格
				persons[i] = new Person(9,"小兵");
				persons[i].setIcon(icons[9]);
				persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, W);									
				break;		
			}
			persons[i].addMouseListener(this);
			persons[i].addKeyListener(this);
			this.add(persons[i]);
		}		
	}

③判斷滑鼠拖拽方向

記錄下滑鼠按下的畫素點p1,再記錄滑鼠鬆開時的畫素點p2,用p2去減去p1,得到x的位移量和y的位移量,如果x的位移量大於y的位移量,說明是水平方向拖拽,然後再判斷p2.x-p1.x是正數還是負數,如果是正數,說明是水平向右拖拽,否則是水平向左拖拽。

	private String getDirection(){
		int dx,dy;
		String direction;
		dx = p2.x - p1.x;
		dy = p2.y - p1.y;
		if(dx==0&&dy==0){
			return "no move";
		}
		if(Math.abs(dx)>Math.abs(dy)){//水平方向的偏移大於豎直方向的偏移,即水平方向運動
			if(dx>0){//釋放點在按壓點右邊
				direction =  "right";
			}else{
				direction =  "left";				
			}
			
		}else{//豎直方向的偏移大於水平方向的偏移,即豎直方向運動
			if(dy>0){//釋放點在按壓點下邊
				direction =  "down";
			}else{
				direction =  "up";				
			}
			
		}
		return direction;
	}
	

	@Override
	public void mousePressed(MouseEvent e) {
		if(e.getSource()==null)//沒有選中任何按鈕,直接返回
			return ;
		p1.x = e.getX();
		p1.y = e.getY();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		if(e.getSource()==null)
			return ;
		Person man = (Person) e.getSource();
		p2.x = e.getX();
		p2.y = e.getY();
		String direction = getDirection();
		boolean canMoveFlag;
		if(direction.equals("up")){
			canMoveFlag = getCanMoveFlag(man,UP);
			if(canMoveFlag)
				move(man,UP);
		}else if(direction.equals("down")){
			canMoveFlag = getCanMoveFlag(man,DOWN);
			if(canMoveFlag)
				move(man,DOWN);			
		}else if(direction.equals("left")){
			canMoveFlag = getCanMoveFlag(man,LEFT);
			if(canMoveFlag)
				move(man,LEFT);
		}else if(direction.equals("right")){
			canMoveFlag = getCanMoveFlag(man,RIGHT);
			if(canMoveFlag)
				move(man,RIGHT);			
		}
	}

④判斷是否可以向某個方向拖拽

getCanMoveFlag(Person man,int direction)根據傳入的JButton物件和拖拽方向,獲取是否可以移動的標記。

先獲取傳入的按鈕對應的矩形物件,然後根據不同方向對該矩形移動一個單位格,然後對每個人物按鈕對應的矩形物件判斷是否相交,如果跟每個按鈕對應的矩形都不相交,再跟邊界的四個矩形進行是否相交的判斷,最後返回canMoveFlag;

	Rectangle leftBoundary = new Rectangle(leftX-10, leftY, 10, 5*W);
	Rectangle rightBoundary = new Rectangle(leftX+4*W, leftY, 10, 5*W);
	Rectangle upBoundary = new Rectangle(leftX, leftY-10, 4*W, 10);
	Rectangle downBoundary = new Rectangle(leftX, leftY+5*W, 4*W, 10);

private boolean getCanMoveFlag(Person man, int direction) {

		boolean canMoveFlag = true;
		Rectangle manRect = man.getBounds();//返回點選的人物按鈕對應的矩形物件
		int x = manRect.x;
		int y = manRect.y;		
		if(direction ==UP){
			y -= W;
		}else if(direction == DOWN){
			y += W;
		}else if(direction == LEFT){
			x -= W;
		}else if(direction == RIGHT){
			x += W;
		}
		manRect.setLocation(x, y);//矩形進行了移動
		for(int i=0;i<10;i++){
			if(persons[i].getId()!=man.getId()){//位移後的矩形與其他人物圖塊對應的矩形進行碰撞檢測
				Rectangle personRect = persons[i].getBounds();
				if(personRect.intersects(manRect)){//如果兩矩形相交,說明不能移動
					canMoveFlag = false;
				}
			}
		}
		
		//檢測是否超出遊戲區域
		if(manRect.intersects(upBoundary)||manRect.intersects(downBoundary)||manRect.intersects(leftBoundary)||manRect.intersects(rightBoundary)){
			canMoveFlag = false;
		}
		
		
		return canMoveFlag;
	}

⑤移動按鈕並判斷是否過關 

如果可以向某個方向移動按鈕,則改變根據方向改變按鈕的位置,step++,然後判斷曹操是否位於終點

private void move(Person man, int direction) {
		switch(direction){
		case UP:man.setLocation(man.getX(), man.getY()-W);break;
		case DOWN:man.setLocation(man.getX(), man.getY()+W);break;
		case LEFT:man.setLocation(man.getX()-W, man.getY());break;
		case RIGHT:man.setLocation(man.getX()+W, man.getY());break;		
		}
		step++;
		GameClient.helpPanel.nowStep.setText(""+step);
		if(isWin(man)){
			if(level==10){JOptionPane.showMessageDialog(this, "恭喜通過最後一關");}
			else{	
				String msg;
				if(step<mapUtil.getRecord(level)){
					msg = "恭喜你通過第"+level+"關!!!\n通關步數是"+step+"\n重新整理了歷史記錄"+mapUtil.getRecord(level)+"步\n是否要進入下一關?";
					mapUtil.updateRecode(level, step);
				}else{
					msg = "恭喜你通過第"+level+"關!!!\n通關步數是"+step+"\n"+"是否要進入下一關?";				
				}
				int type = JOptionPane.YES_NO_OPTION;
				String title = "過關";
				int choice = 0;
				choice = JOptionPane.showConfirmDialog(null, msg,title,type);
				if(choice==1){
					System.exit(0);
				}else if(choice == 0){
					level++;
					step = 0;
					for(int i=0;i<10;i++){
						this.remove(persons[i]);
					}
					repaint();
					map = mapUtil.getMap(level);
					GameClient.panel.remove(GameClient.helpPanel);
					GameClient.helpPanel = new HelpPanel(level);
					GameClient.panel.add(GameClient.helpPanel,BorderLayout.EAST);
					GameClient.helpPanel.validate();
					GameClient.gamePanel.validate();
					GameClient.panel.validate();
					initialize();
					setVisible(true);
					this.requestFocus();
				}
			}
		}
	}


	private boolean isWin(Person man) {
		if(man.getId()==0&&man.getX()==leftX+W&&man.getY()==leftY+3*W){//如果移動的圖塊是曹操,並且曹操位於終點
			return true;
		}
		
		return false;
	}

GamePanel類

package 華容道;

import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class GamePanel extends JPanel implements ActionListener,MouseListener,KeyListener{
	int leftX=50,leftY=50;
	int level,W=100;
	int step = 0;//儲存當前步數
	int record;//儲存當前關卡記錄
	Icon[] icons;
	Image[] pics;
	Node[] map;
	Person[] persons;
	Point p1 = new Point(0,0);//滑鼠按下時點選的畫素座標
	Point p2 = new Point(0,0);//滑鼠釋放時點選的畫素座標
	Rectangle leftBoundary = new Rectangle(leftX-10, leftY, 10, 5*W);
	Rectangle rightBoundary = new Rectangle(leftX+4*W, leftY, 10, 5*W);
	Rectangle upBoundary = new Rectangle(leftX, leftY-10, 4*W, 10);
	Rectangle downBoundary = new Rectangle(leftX, leftY+5*W, 4*W, 10);

	public static final int UP=1,DOWN=2,LEFT=3,RIGHT=4;

	MapUtil mapUtil = new MapUtil();
	
	public GamePanel(int level){
		this.level = level;
		map	= mapUtil.getMap(level);
		persons = new Person[10];
		icons = new Icon[15];
		pics = new Image[15];
		setLayout(null);
		setSize(400, 500);
		getIcons();
		initialize();
		HelpPanel.restart.addMouseListener(new MouseAdapter(){
			public void mouseClicked(MouseEvent e){
				step = 0;
				for(int i=0;i<10;i++){
					GameClient.gamePanel.remove(persons[i]);
				}
				repaint();
				map = mapUtil.getMap(level);
				GameClient.panel.remove(GameClient.helpPanel);
				GameClient.helpPanel = new HelpPanel(level);
				GameClient.panel.add(GameClient.helpPanel,BorderLayout.EAST);
				GameClient.helpPanel.validate();
				GameClient.gamePanel.validate();
				GameClient.panel.validate();
				initialize();
				setVisible(true);
				GameClient.gamePanel.requestFocus();
				repaint();
			}
		});;
	}

	
	public void paint(Graphics g){
		setLayout(null);
		g.clearRect(0, 0, getWidth(), getHeight());
		for(int i=0;i<10;i++){
			persons[i].requestFocus();
			persons[i].paintComponents(g);
		}
	}
	
	
	//獲取圖片
	private void getIcons() {
				
		for(int i=0;i<15;i++){
			if(i==0){
				pics[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/KlotskiGame/pic"+i+".png").getScaledInstance(2*W, 2*W,Image.SCALE_SMOOTH);
			}else if(i==1||i==2||i==3||i==4||i==5){
				pics[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/KlotskiGame/pic"+i+".png").getScaledInstance(2*W, W,Image.SCALE_SMOOTH);
			}else if(i==6||i==7||i==8||i==9){
				pics[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/KlotskiGame/pic"+i+".png").getScaledInstance(W, W,Image.SCALE_SMOOTH);
			}else{
				pics[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/KlotskiGame/pic"+i+".png").getScaledInstance(W,2*W,Image.SCALE_SMOOTH);				
			}
			icons[i] = new ImageIcon(pics[i]);

			
		}
		repaint();
	}


	
	//對遊戲佈局進行初始化
	private void initialize() {
		
		for(int i=0;i<10;i++){
			switch(map[i].getId()){
			case 0://曹操,佔四格
				persons[i] = new Person(0,"曹操");
				persons[i].setIcon(icons[0]);
				persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, 2*W);
				break;
			case 1://趙雲,佔兩格
				persons[i] = new Person(1,"趙雲");
				if(map[i].getDirection()){//橫放
					persons[i].setIcon(icons[1]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, W);					
				}else{//豎放
					persons[i].setIcon(icons[10]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, 2*W);
				}
				break;
			case 2://張飛,佔兩格
				persons[i] = new Person(2,"張飛");
				if(map[i].getDirection()){//橫放
					persons[i].setIcon(icons[2]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, W);					
				}else{//豎放
					persons[i].setIcon(icons[11]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, 2*W);
				}
				break;
			case 3://關羽,佔兩格
				persons[i] = new Person(3,"關羽");
				if(map[i].getDirection()){//橫放
					persons[i].setIcon(icons[3]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, W);					
				}else{//豎放
					persons[i].setIcon(icons[12]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, 2*W);
				}
				break;
			case 4://馬超,佔兩格
				persons[i] = new Person(4,"馬超");
				if(map[i].getDirection()){//橫放
					persons[i].setIcon(icons[4]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, W);					
				}else{//豎放
					persons[i].setIcon(icons[13]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, 2*W);
				}
				break;
			case 5://黃忠,佔兩格
				persons[i] = new Person(5,"黃忠");
				if(map[i].getDirection()){//橫放
					persons[i].setIcon(icons[5]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, 2*W, W);					
				}else{//豎放
					persons[i].setIcon(icons[14]);
					persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, 2*W);
				}
				break;
			case 6://小兵,佔一格
				persons[i] = new Person(6,"小兵");
				persons[i].setIcon(icons[6]);
				persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, W);									
				break;
			case 7://小兵,佔一格
				persons[i] = new Person(7,"小兵");
				persons[i].setIcon(icons[7]);
				persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, W);													
				break;
			case 8://小兵,佔一格
				persons[i] = new Person(8,"小兵");
				persons[i].setIcon(icons[8]);
				persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, W);									
				break;
			case 9://小兵,佔一格
				persons[i] = new Person(9,"小兵");
				persons[i].setIcon(icons[9]);
				persons[i].setBounds(leftX+map[i].getY()*W, leftY+map[i].getX()*W, W, W);									
				break;		
			}
			persons[i].addMouseListener(this);
			persons[i].addKeyListener(this);
			this.add(persons[i]);
		}		
	}
	
	private String getDirection(){
		int dx,dy;
		String direction;
		dx = p2.x - p1.x;
		dy = p2.y - p1.y;
		if(dx==0&&dy==0){
			return "no move";
		}
		if(Math.abs(dx)>Math.abs(dy)){//水平方向的偏移大於豎直方向的偏移,即水平方向運動
			if(dx>0){//釋放點在按壓點右邊
				direction =  "right";
			}else{
				direction =  "left";				
			}
			
		}else{//豎直方向的偏移大於水平方向的偏移,即豎直方向運動
			if(dy>0){//釋放點在按壓點下邊
				direction =  "down";
			}else{
				direction =  "up";				
			}
			
		}
		return direction;
	}
	

	@Override
	public void mousePressed(MouseEvent e) {
		if(e.getSource()==null)//沒有選中任何按鈕,直接返回
			return ;
		p1.x = e.getX();
		p1.y = e.getY();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		if(e.getSource()==null)
			return ;
		Person man = (Person) e.getSource();
		p2.x = e.getX();
		p2.y = e.getY();
		String direction = getDirection();
		boolean canMoveFlag;
		if(direction.equals("up")){
			canMoveFlag = getCanMoveFlag(man,UP);
			if(canMoveFlag)
				move(man,UP);
		}else if(direction.equals("down")){
			canMoveFlag = getCanMoveFlag(man,DOWN);
			if(canMoveFlag)
				move(man,DOWN);			
		}else if(direction.equals("left")){
			canMoveFlag = getCanMoveFlag(man,LEFT);
			if(canMoveFlag)
				move(man,LEFT);
		}else if(direction.equals("right")){
			canMoveFlag = getCanMoveFlag(man,RIGHT);
			if(canMoveFlag)
				move(man,RIGHT);			
		}
	}
	


	private void move(Person man, int direction) {
		switch(direction){
		case UP:man.setLocation(man.getX(), man.getY()-W);break;
		case DOWN:man.setLocation(man.getX(), man.getY()+W);break;
		case LEFT:man.setLocation(man.getX()-W, man.getY());break;
		case RIGHT:man.setLocation(man.getX()+W, man.getY());break;		
		}
		step++;
		GameClient.helpPanel.nowStep.setText(""+step);
		if(isWin(man)){
			if(level==10){JOptionPane.showMessageDialog(this, "恭喜通過最後一關");}
			else{	
				String msg;
				if(step<mapUtil.getRecord(level)){
					msg = "恭喜你通過第"+level+"關!!!\n通關步數是"+step+"\n重新整理了歷史記錄"+mapUtil.getRecord(level)+"步\n是否要進入下一關?";
					mapUtil.updateRecode(level, step);
				}else{
					msg = "恭喜你通過第"+level+"關!!!\n通關步數是"+step+"\n"+"是否要進入下一關?";				
				}
				int type = JOptionPane.YES_NO_OPTION;
				String title = "過關";
				int choice = 0;
				choice = JOptionPane.showConfirmDialog(null, msg,title,type);
				if(choice==1){
					System.exit(0);
				}else if(choice == 0){
					level++;
					step = 0;
					for(int i=0;i<10;i++){
						this.remove(persons[i]);
					}
					repaint();
					map = mapUtil.getMap(level);
					GameClient.panel.remove(GameClient.helpPanel);
					GameClient.helpPanel = new HelpPanel(level);
					GameClient.panel.add(GameClient.helpPanel,BorderLayout.EAST);
					GameClient.helpPanel.validate();
					GameClient.gamePanel.validate();
					GameClient.panel.validate();
					initialize();
					setVisible(true);
					this.requestFocus();
				}
			}
		}
	}

	
	private boolean isWin(Person man) {
		if(man.getId()==0&&man.getX()==leftX+W&&man.getY()==leftY+3*W){//如果移動的圖塊是曹操,並且曹操位於終點
			return true;
		}
		
		return false;
	}

	private boolean getCanMoveFlag(Person man, int direction) {

		boolean canMoveFlag = true;
		Rectangle manRect = man.getBounds();//返回點選的人物按鈕對應的矩形物件
		int x = manRect.x;
		int y = manRect.y;		
		if(direction ==UP){
			y -= W;
		}else if(direction == DOWN){
			y += W;
		}else if(direction == LEFT){
			x -= W;
		}else if(direction == RIGHT){
			x += W;
		}
		manRect.setLocation(x, y);//矩形進行了移動
		for(int i=0;i<10;i++){
			if(persons[i].getId()!=man.getId()){//位移後的矩形與其他人物圖塊對應的矩形進行碰撞檢測
				Rectangle personRect = persons[i].getBounds();
				if(personRect.intersects(manRect)){//如果兩矩形相交,說明不能移動
					canMoveFlag = false;
				}
			}
		}
		
		//檢測是否超出遊戲區域
		if(manRect.intersects(upBoundary)||manRect.intersects(downBoundary)||manRect.intersects(leftBoundary)||manRect.intersects(rightBoundary)){
			canMoveFlag = false;
		}
		
		
		return canMoveFlag;
	}

	@Override
	public void keyPressed(KeyEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void keyReleased(KeyEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void keyTyped(KeyEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseClicked(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}



	@Override
	public void actionPerformed(ActionEvent arg0) {
		// TODO Auto-generated method stub
		
	}
	
	
	
	
}

HelpPanel類是繼承自JPanel的輔助面板類,負責顯示關卡名字和步數

HelpPanel類:

package 華容道;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class HelpPanel extends JPanel{

	String[] names = new String[]{"七步成詩","橫刀立馬","屯兵東路","插翅難飛","巧過五關","層層設防","近在咫尺","兵臨曹營","眾志成城","佳人梳妝"};//十個關卡的名字
	int level;
	JPanel panelNorth = new JPanel(new GridLayout(4,2,10,30));
	static JLabel nowStep = new JLabel("0");
	JLabel nowName = new JLabel("");
	JLabel record = new JLabel("9999");
	static	JButton restart = new JButton("重置");
                 	
	public HelpPanel(int level){
		this.level = level;
		this.setVisible(true);
		this.setLayout(new BorderLayout());//當前面板
		panelNorth.add(new JLabel("關卡名字:"));
		panelNorth.add(nowName);
		panelNorth.add(new JLabel("當前步數:"));
		panelNorth.add(nowStep);
		panelNorth.add(new JLabel("歷史記錄:"));
		panelNorth.add(record);//歷史記錄
		panelNorth.add(restart);
		
		this.add(panelNorth,BorderLayout.NORTH);
		initialize();
	}

	public void setLevel(int level){
		this.level = level;
		initialize();
	}
	
	private void initialize() {
//		System.out.println("level is "+level);
//		System.out.println(names[level-1]);
		nowName.setText(names[level-1]+"");
		nowStep.setText("0");
		record.setText(""+MapFactory.getRecord(level));	
	}
	
}

遊戲視窗類GameClient類用於裝載遊戲面板和輔助面板。

GameClient類:

package 華容道;
import java.awt.BorderLayout;
import java.awt.Container;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class GameClient extends JFrame{

	static GamePanel gamePanel;
	static HelpPanel helpPanel;
	static Container panel;//視窗容器
	
	public GameClient(){

		helpPanel = new HelpPanel(1);
		gamePanel = new GamePanel(1);//設定關卡
		gamePanel.setLayout(new BorderLayout());
		panel = this.getContentPane();
		panel.setLayout(new BorderLayout());
		panel.add(helpPanel,BorderLayout.EAST);
		panel.add(gamePanel,BorderLayout.CENTER);
		this.setSize(650,650);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setTitle("華容道");
		this.setVisible(true);
		this.setResizable(false);	
	}
	
	
	public static void main(String[] args) {
			new GameClient();
	}

}

這次遊戲開發中途遇到了一些顯示問題的bug(第二關開始最後一個人物按鈕變成全屏),導致出現了很多複雜的靜態變數引用,不過目前已經解決,實現略繁瑣,就不細說了。

ps:根據關卡名字均可以在百度上找到解密過程,(#^.^#)

素材及完整原始碼連結: https://pan.baidu.com/s/1_vOhAYk07h9dXBaez_lW3g 提取碼: sni6