拼图游戏是一款经典的益智游戏,游戏难度分为 简单、正常、困难 三种难度,分别对应3*3,4*4,5*5布局,游戏开始前图片被随机打乱,空块位于最右下角,玩家通过点击空块周围图片或者按键方式对图片和空块进行相互交换,直到所有图片都回到原位即为游戏胜利。
本次制作的拼图游戏运行界面如下:
对拼图界面的图像信息可以采用二维数组map进行存储,数组存储的是图片ID,拼图完成的map数组存储的内容应该为从左到右,从上到下,图片ID顺序为1~8,最右下角的数 组元素存储的图片ID为-1(BLANK_STATE)。所有的移动操作可以简化为对map的移动操作,每次移动完成调用repaint()对图片按ID进行绘画即可。使用文本存储历史记录,每次过关对当前步数和历史记录进行比较和更新。考虑到数组map要求打乱后可以通过移动被还原,所以对数组的打乱必须有所讲究,这里我们采用系统对原有图片执行10000次上下左右按键事件来对图片进行打乱,最后再将空块移动到最右下角,这样图片就顺利地做到了随机打乱。
先根据关卡level来获取关卡图片,不同的关卡将分成不同数量的小块,假如是简单难度3*3地图,小图片的宽w是大图片的宽的三分之一,小图片的高h是大图片的高的三分之一,第i张图片的左上角起点的x值为图片所在列数*小图片宽(i%3*w),y值为图片所在行数*小图片高(i/3*h):
private void getPics() {
BufferedImage bufferedImage = null;
int width = 0,height = 0;
try {
bufferedImage = ImageIO.read(new File("D:/Game/JigsawGame/pic"+level+".png"));
width = bufferedImage.getWidth();
height = bufferedImage.getHeight();
} catch (IOException e) {
e.printStackTrace();
}
if(level==EASY){
pics = new Image[9];
int w = width/3;
int h = height/3;
for(int i=0;i<8;i++){
int x = i%3*w;
int y = i/3*h;
pics[i] = bufferedImage.getSubimage(x, y, w, h);
}
}else if(level==NORMAL){
pics = new Image[16];
int w = width/4;
int h = height/4;
for(int i=0;i<15;i++){
int x = i%4*w;
int y = i/4*h;
pics[i] = bufferedImage.getSubimage(x, y, w, h);
}
}else{
pics = new Image[25];
int w = width/5;
int h = height/5;
for(int i=0;i<24;i++){
int x = i%5*w;
int y = i/5*h;
pics[i] = bufferedImage.getSubimage(x, y, w, h);
}
}
}
public void paint(Graphics g){
g.clearRect(0, 0, this.getWidth(), this.getHeight());
for(int i=0;i
对玩家执行不同的操作进行分别处理:
①当玩家鼠标点击
当玩家鼠标点击的小图片位于空块上下左右方时,点击的小图片与空块交换位置,否则点击无效。
②当玩家按键移动
当空块位于第一行时,moveDown()无效;
当空块位于最后一行时,moveUp()无效;
当空块位于第一列时,moveRight()无效;
当空块位于最后一列时,moveLeft()无效;
此处的moveLeft()指的是小图片移动的方向,也就是空块移动的反方向。鼠标点击事件的移动可以复用按键事件的移动方法,只需要将点击的小图片与数组下标进行转换,再判断下调用哪个方法即可。
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
int x = e.getX()-leftX;
int y = e.getY()-leftY;
if(x<0||y<0||x>600||y>600){//超出地图范围
return ;
}
//存储数组行列下标
int i = y/W;
int j = x/W;
if(blank_x == i-1&&blank_y == j){//空块在当前点击块的上侧
moveUp();
}else if(blank_x == i+1&&blank_y == j){//空块在当前点击块的下侧
moveDown();
}else if(blank_x == i&&blank_y == j-1){//空块在当前点击块的左侧
moveLeft();
}else if(blank_x == i&&blank_y == j+1){//空块在当前点击块的右侧
moveRight();
}else{
return ;
}
repaint();
isWin();
}
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if(e.getKeyCode() == KeyEvent.VK_UP&&blank_x!=level+1){//上
moveUp();
}else if(e.getKeyCode() == KeyEvent.VK_DOWN&&blank_x!=0){//下
moveDown();
}else if(e.getKeyCode() == KeyEvent.VK_LEFT&&blank_y!=level+1){//左
moveLeft();
}else if(e.getKeyCode() == KeyEvent.VK_RIGHT&&blank_y!=0){//右
moveRight();
}else{
return ;
}
repaint();
isWin();
}
小图片的移动
交换小图片与空块的值,并对空块的数组下标重新赋值:
//空块与上一格交换
private void moveDown(){
map[blank_x][blank_y] = map[blank_x-1][blank_y];
map[blank_x-1][blank_y] = BLANK_STATE;
blank_x = blank_x-1;
step++;
HelpPanel.nowStep.setText(step+"");
}
//空块与下一格交换
private void moveUp(){
map[blank_x][blank_y] = map[blank_x+1][blank_y];
map[blank_x+1][blank_y] = BLANK_STATE;
blank_x = blank_x+1;
step++;
HelpPanel.nowStep.setText(step+"");
}
//空块与左一格交换
private void moveRight(){
map[blank_x][blank_y] = map[blank_x][blank_y-1];
map[blank_x][blank_y-1] = BLANK_STATE;
blank_y = blank_y-1;
step++;
HelpPanel.nowStep.setText(step+"");
}
//空块与右一格交换
private void moveLeft(){
map[blank_x][blank_y] = map[blank_x][blank_y+1];
map[blank_x][blank_y+1] = BLANK_STATE;
blank_y = blank_y+1;
step++;
HelpPanel.nowStep.setText(step+"");
}
打乱地图信息
使用Math.Random()获取随机值,各个方向按键事件的概率是1/4,当符合条件时执行移动方法并且移动次数+1,移动10000次实现随机打乱地图。
private void RandomTheMap() {
int i =0;
//尝试移动一万次
while(i<10000){
int rate = (int) (Math.random()*100);
if(rate<25&&blank_x!=level+1){
moveUp();
i++;
}else if(rate<50&&blank_x!=0){
moveDown();
i++;
}else if(rate<75&&blank_y!=level+1){
moveLeft();
i++;
}else if(rate<100&&blank_y!=0){
moveRight();
i++;
}
}
//再将空块移动到最右下角
while(blank_x
判断输赢
将现有map信息与正确的map信息一一对比,如果都相等,过关,比较当前记录和历史记录,如果刷新历史记录进行记录的更新,然后询问是否开始下一关。如果开始下一关,进行level++并对地图及面板信息进行初始化:
private void isWin() {
boolean flag = true;
for(int i=0;i
历史记录的读写
采用文本形式对记录进行保存,初始情况下,读取记录的时候如果没有记录文件进行文件的新建并初始所有历史记录为9999步;记录的保存采用两个逗号对三个关卡的历史记录进行分割,读取后进行array.split(",")分割操作;写入时,先清空原有文本信息,再将记录合成字符串写入文件。
//获取关卡历史记录
public int getRecord(int level){
File file = new File("D://GameRecordAboutSwing");
if(!file.exists()){
file.mkdirs();
}
File record = new File("D://GameRecordAboutSwing/recordJigsawGame.txt");
try{
if(!record.exists()){//如果不存在,新建文本
record.createNewFile();
fos = new FileOutputStream(record);
dos = new DataOutputStream(fos);
String s = "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 void writeRecord(int level,int step){
File record = new File("D://GameRecordAboutSwing/recordJigsawGame.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+"";
String s = array[0]+","+array[1]+","+array[2];
dos.writeBytes(s);
} 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();
}
}
}
到这里核心的游戏逻辑及游戏面板的相关内容已经说明完了,涉及辅助面板类的东西会有些错综复杂,这里就不详细介绍了。
完整源码如下:
MapUtil类:
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 MapUtil {
int[][] map;
FileInputStream fis = null;
FileOutputStream fos = null;
DataInputStream dis = null;
DataOutputStream dos = null;
String array[] = null;
int level = 1;
public static final int EASY = 1,NORMAL = 2,HARD = 3,BLANK_STATE = -1;
public int[][] getMap(int level){
switch(level){
case EASY:
map = new int[3][3];
for(int i=0;i<3;i++)
for(int j=0;j<3;j++){
map[i][j] = 3*i+j+1;
}
map[2][2] = BLANK_STATE;
;break;
case NORMAL:
map=new int[4][4];
for(int i=0;i<4;i++)
for(int j=0;j<4;j++){
map[i][j] = 4*i+j+1;
}
map[3][3] = BLANK_STATE;
break;
case HARD:
map=new int[5][5];
for(int i=0;i<5;i++)
for(int j=0;j<5;j++){
map[i][j] = 5*i+j+1;
}
map[4][4] = BLANK_STATE;
break;
}
return map;
}
//获取关卡历史记录
public int getRecord(int level){
File file = new File("D://GameRecordAboutSwing");
if(!file.exists()){
file.mkdirs();
}
File record = new File("D://GameRecordAboutSwing/recordJigsawGame.txt");
try{
if(!record.exists()){//如果不存在,新建文本
record.createNewFile();
fos = new FileOutputStream(record);
dos = new DataOutputStream(fos);
String s = "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 void writeRecord(int level,int step){
File record = new File("D://GameRecordAboutSwing/recordJigsawGame.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+"";
String s = array[0]+","+array[1]+","+array[2];
dos.writeBytes(s);
} 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();
}
}
}
}
MapUtil类主要用做对地图数组信息的初始化以及历史记录的读写。
GamePanel类
package 人物拼图;
import java.awt.Graphics;
import java.awt.Image;
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.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class GamePanel extends JPanel implements ActionListener ,MouseListener,KeyListener{
Image pics[];
int leftX=50,leftY=50;
int level,W;
int[][] map;
int[][] successMap;//最后结果
int blank_x,blank_y;//记录空白块的数组下标
int step = 0;
MapUtil mapUtil = new MapUtil();
int record;//存储当前关卡记录
public static final int EASY = 1,NORMAL = 2,HARD = 3,BLANK_STATE = -1;
public GamePanel(int level){
setSize(600, 600);
this.level = level;
map = mapUtil.getMap(level);
successMap = mapUtil.getMap(level);
initializeMap();//初始化地图相关变量信息
RandomTheMap();//打乱图案顺序
this.setVisible(true);
this.addKeyListener(this);
this.addMouseListener(this);
this.setFocusable(true);
getPics();
record = mapUtil.getRecord(level);
HelpPanel.histotyStep.setText(record+"");
HelpPanel.nowStep.setText(step+"");
repaint();
}
public void paint(Graphics g){
g.clearRect(0, 0, this.getWidth(), this.getHeight());
for(int i=0;i600||y>600){//超出地图范围
return ;
}
//存储数组行列下标
int i = y/W;
int j = x/W;
if(blank_x == i-1&&blank_y == j){//空块在当前点击块的上侧
moveUp();
}else if(blank_x == i+1&&blank_y == j){//空块在当前点击块的下侧
moveDown();
}else if(blank_x == i&&blank_y == j-1){//空块在当前点击块的左侧
moveLeft();
}else if(blank_x == i&&blank_y == j+1){//空块在当前点击块的右侧
moveRight();
}else{
return ;
}
repaint();
isWin();
}
private void isWin() {
boolean flag = true;
for(int i=0;i
GamePanel类主要涉及游戏面板的小图片的读取和显示,以及全部游戏的逻辑。
SuccessPanel类:
package 人物拼图;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class SuccessPanel extends JPanel{
int level;
Image[] pics;
int[][] map;
int W ;
MapUtil mapUtil;
private int leftX=10;
private int leftY=150;
public static final int EASY=1,NORMAL=2,HARD=3,BLANK_STATE=-1;
public SuccessPanel(int level){
setSuccessPic(level);
}
public void setSuccessPic(int level) {
mapUtil = new MapUtil();
this.level = level;
BufferedImage bufferedImage = null;
int width = 0,height = 0;
try {
bufferedImage = ImageIO.read(new File("D:/Game/JigsawGame/pic"+level+".png"));
width = bufferedImage.getWidth();
height = bufferedImage.getHeight();
} catch (IOException e) {
e.printStackTrace();
}
if(level == EASY){
pics = new Image[9];
map = mapUtil.getMap(level);
W = 40;
int w = width/3;
int h = height/3;
for(int i=0;i<8;i++){
int x = i%3*w;
int y = i/3*h;
pics[i] = bufferedImage.getSubimage(x, y, w, h);
}
}else if(level == NORMAL){
pics = new Image[16];
map = mapUtil.getMap(level);
W = 30;
int w = width/4;
int h = height/4;
for(int i=0;i<15;i++){
int x = i%4*w;
int y = i/4*h;
pics[i] = bufferedImage.getSubimage(x, y, w, h);
}
}else{
pics = new Image[25];
map = mapUtil.getMap(level);
W = 24;
int w = width/5;
int h = height/5;
for(int i=0;i<24;i++){
int x = i%5*w;
int y = i/5*h;
pics[i] = bufferedImage.getSubimage(x, y, w, h);
}
}
repaint();
}
public void paint(Graphics g){
for(int i=0;i
SuccessPanel类负责游戏运行界面右下角完整拼图顺序的小图片显示;
HelpPanel类:
package 人物拼图;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class HelpPanel extends JPanel {
private int level = 1;
public static final int EASY = 1,NORMAL = 2,HARD = 3,BLANK_STATE = -1;
static JLabel nowStep = new JLabel("0");
static JLabel histotyStep = new JLabel("9999");
static JLabel nowLevel = new JLabel();
JLabel levelLabel = new JLabel("当前难度:");
JLabel nowStepLabel = new JLabel("当前步数:");
JLabel historyStepLabel = new JLabel("历史记录:");
JButton button1 = new JButton("退出");
BorderLayout panel1 = new BorderLayout();//右侧
JPanel panelNorth = new JPanel(new GridLayout(4,2,10,30));
static SuccessPanel panelCenter = new SuccessPanel(1);
public HelpPanel(int level){
setLevel(level);
this.setVisible(true);
this.setLayout(new BorderLayout());//当前面板
panelNorth.add(levelLabel);
panelNorth.add(nowLevel);
panelNorth.add(nowStepLabel);
panelNorth.add(nowStep);
panelNorth.add(historyStepLabel);
panelNorth.add(histotyStep);//历史记录
panelNorth.add(button1);
this.add(panelNorth,BorderLayout.NORTH);
this.add(panelCenter,BorderLayout.CENTER);
button1.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
System.exit(1);
}
});;
}
public void setLevel(int level){
this.level = level;
panelCenter.setSuccessPic(level);
if(level == EASY){
nowLevel.setText("简单");
}else if(level == NORMAL){
nowLevel.setText("正常");
}else{
nowLevel.setText("困难");
}
}
}
HelpPanel类负责游戏运行界面右边辅助面板的显示,其中装载了SuccessPanel;
GameClient类:
package 人物拼图;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class GameClient extends JFrame{
GamePanel panel2;
public GameClient(){
HelpPanel panel = new HelpPanel(1);
panel2 = new GamePanel(1);//难度简单
panel2.setLayout(new BorderLayout());
this.getContentPane().setLayout(new BorderLayout());
this.getContentPane().add(panel,BorderLayout.EAST);
this.getContentPane().add(panel2,BorderLayout.CENTER);
this.setSize(850,750);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle("人物拼图游戏");
this.setVisible(true);
}
public static void main(String[] args) {
new GameClient();
}
}
GameClient类是游戏窗口类,装载了游戏面板和辅助面板,用来开始游戏。
源码已经粘贴完毕,最后再展示两张游戏运行截图: