- 游戏功能需求说明
- 代码编写
- 1 框架搭建
- 2 主要技术难点
- 21 图片面板对应的图片索引获取
- 22 图片面板
- 3 完整代码
- 游戏截图
- 1启动后界面
- 2开始游戏界面
- 3游戏结束界面
1 游戏功能需求说明
该游戏主要模拟常见的翻牌游戏,即找到所有两两相同的牌认为游戏成功,主要需求包含:
- 初始化面板后显示所有图片网格,图片默认显示为背景色;
- 点击图片后显示图片,再次点击后显示背景;
- 点击另一张图片,如果与前一张图片相同,则两张图片一直显示,再次点击不会再显示为背景,表示配对成功;如果与前一张图片不同,则两张图片显示50ms后显示为背景;
- 重复上面两步,直到图片网格全部显示为图片,即所有图片配对成功;
- 游戏开始后显示游戏用时,并在全部配对成功后提示游戏用时。
2 代码编写
2.1 框架搭建
主面板当然是JFrame了,显示图片使用的是JLabel,考虑到点击事件的处理以及能够显示出图片的轮廓来,将JLabel放到了一个自己写的一个JPanel子类PicPanel中。
实现过程中主要的问题就是PicPanel的实现问题,主要是为该类增加了一个MouseListener,需要处理好鼠标点击的事件。因为需要跟主面板进行交互,因此需要将主面板作为构造参数传入到该类中。同时该类是用于显示图片的,自然少不了传入的图片路径了。
2.2 主要技术难点
当然了这里的技术难点是针对个人来讲的,就是比较耗时的功能点,对于大神来讲就不是什么难点了。
2.2.1 图片面板对应的图片索引获取
因为需要在构造每个图片面板的时候传入每个面板对应的图片路径,因此首先需要初始化每个图片面板对应的图片路径,这里假设所有图片都存放在本地一个固定目录下,根据游戏设计,图片面板个数作为已知量使用(本程序使用了两个全局静态变量ROWS和COLUMNS表示图片面板网格的行数和列数,自然两者的乘积必须为偶数了),而初始化的图片索引指的是图片路径下所有图片按文件名从小到大排序后,随机生成的一个由int组成的数组,该数组必须满足以下条件:
- 数组长度与图片面板个数相同;
- 数组中每个值表示图片路径下的图片顺序;
- 数组中的值介于0-picNums之间,不包含picNums(图片总数量);
- 数组中的每个值出现的次数必须为偶数;
- 数组中每个值所在的位置是随机的;
- 图片总数小于图片网格个数一半时必须使用所有图片。
这里针对图片数量和图片面板个数之间的关系使用了两种方法:
- 图片数量大于等于图片面板个数一半时,因为图片数量多,因此只要随机从图片中选取不重复的图片索引即可,使用的是List;
- 图片数量小于图片面板个数一半时,因为图片数量少,需要保证所有的图片都要使用上,因此Map来进行下标存储,并记录每个下标出现的次数;
2.2.2 图片面板
由于需要将图片显示在面板上,这里采用了传统的图片显示方案,即使用JLabel对象,并使用其setIcon()方法为其设置背景图片,为保证图片能够完全铺满整个面板,因此对获取的图像进行了拉伸(有变形的可能)。
2.3 完整代码
这里没有进行详细的拆分,所有代码都写到了一个类中,代码使用的图片放到了D:\pics文件夹下,使用的图片是从网上下载的三个车标,在后面的截图中可以看到,理论上将代码拷贝走,并在D:\pics文件夹下放入图片即可运行。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.CompoundBorder;
import javax.swing.border.LineBorder;
/**
* @author jqs 主要实现记忆翻牌功能
*/
public class RememberCard extends JFrame {
/**
* 初始化游戏的行列数,行列数成绩必须为偶数
*/
private static final int ROWS = 3;
private static final int COLUMNS = 4;
private static final long serialVersionUID = -8908268719780973221L;
private JTextField txt_Time;
private boolean isRunning = false;
/**
* 存放图片的目录,简单起见,存放图片的目录中图片个数为初始化的行列数乘积的一半
*/
private String picDir = "D:\\pics";
private String[] picture;
protected boolean isStart;
private PicPanel preOne = null;
/**
* 用于标示已找到的对数
*/
private int count;
private JPanel panel_Pic;
public RememberCard() {
setTitle("\u5BFB\u627E\u76F8\u540C\u5361\u724C");
JPanel panel_Time = new JPanel();
getContentPane().add(panel_Time, BorderLayout.NORTH);
JLabel lbl_Time = new JLabel("\u7528\u65F6\uFF1A");
panel_Time.add(lbl_Time);
txt_Time = new JTextField();
txt_Time.setEditable(false);
panel_Time.add(txt_Time);
txt_Time.setColumns(10);
JLabel lbl_Unit = new JLabel("\u79D2");
panel_Time.add(lbl_Unit);
JButton btn_Start = new JButton("\u5F00\u59CB");
panel_Time.add(btn_Start);
panel_Pic = new JPanel();
getContentPane().add(panel_Pic, BorderLayout.CENTER);
btn_Start.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (isRunning) {
return;
}
setRunning(true);
startGame();
}
});
initPicPanels();
}
/**
* 初始化图片面板
*/
private void initPicPanels() {
panel_Pic.setLayout(new GridLayout(ROWS, COLUMNS, 5, 5));
initPictureIndex();
for (int i = 0; i < ROWS * COLUMNS; i++) {
PicPanel panel_1 = new PicPanel(this, picture[i]);
panel_Pic.add(panel_1);
}
}
/**
* 开始游戏
*/
protected void startGame() {
new Thread() {
@Override
public void run() {
long startTime = System.currentTimeMillis();
while (count < ROWS * COLUMNS / 2) {
txt_Time.setText(((System.currentTimeMillis() - startTime) / 1000)
+ "");
}
JOptionPane.showMessageDialog(null,
"成功!共耗时" + txt_Time.getText() + "秒。");
// 结束后重新初始化一下面板以便于下一次的运行
count = 0;
panel_Pic.removeAll();
initPicPanels();
txt_Time.setText(null);
panel_Pic.validate();
isRunning = false;
}
}.start();
}
/**
* 初始化图片的索引并赋值每个图片的路径
*/
private void initPictureIndex() {
picture = new String[ROWS * COLUMNS];
// 这里没有检测图片目录中文件的有效性,需要保证都是图片类型。
File file = new File(picDir);
File[] pics = file.listFiles();
// 初始化一个ROWS*COLUMNS的int数组,里面存放每个图片的索引
int[] indexs = getIndexs(picture.length, pics.length);
for (int i = 0; i < indexs.length; i++) {
picture[i] = pics[indexs[i]].getAbsolutePath();
}
}
/**
* 根据提供的图片总数目(假设图片都是互不相同的)得到一个长度为sum的数组用来表示每个图片的索引
*
* @param sum
* 游戏的行列数乘积
* @param picNums
* 给定目录下图片的总数目
* @return
*/
private int[] getIndexs(int sum, int picNums) {
int half = sum / 2;
if (picNums < half) {
return getIndexsByMap(sum, picNums);
}
int[] tmpResult = new int[sum];
Random random = new Random(System.currentTimeMillis());
int temp = 0;
LinkedList<Integer> list = new LinkedList<Integer>();
while (list.size() != half) {
temp = random.nextInt(picNums);
if (!list.contains(temp)) {
list.add(temp);
} else if (picNums < half) {
list.add(temp);
}
}
for (int i = 0; i < tmpResult.length; i++) {
tmpResult[i] = list.get(i >= half ? i % half : i);
}
// 将顺序打乱,否则会出现前半部分和后半部分是完全分开的情况
LinkedList<Integer> _result = new LinkedList<Integer>();
while (_result.size() != sum) {
temp = random.nextInt(sum);
if (!_result.contains(temp)) {
_result.add(temp);
}
}
int[] result = new int[sum];
for (int i = 0; i < result.length; i++) {
result[i] = tmpResult[_result.get(i)];
}
return result;
}
/**
* 当图片数量小于总格子数一半时需要使用下面的方法获取,保证所有的图片都能使用上
*
* @param sum
* @param picNums
* @return
*/
private int[] getIndexsByMap(int sum, int picNums) {
int half = sum / 2;
int[] tmpResult = new int[sum];
Random random = new Random(System.currentTimeMillis());
int temp = 0;
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
// 因为图片的数量小于sum的一半,因此先按顺序将图片索引都添加到map中以保证得到的结果中每个图片都被使用到
for (int i = 0; i < picNums; i++) {
map.put(i, 1);
}
int size = picNums;
while (size != half) {
temp = random.nextInt(picNums);
if (!map.containsKey(temp)) {
map.put(temp, 1);
} else {
map.put(temp, map.get(temp) + 1);
}
size++;
}
List<Integer> list = mapKeyToList(map);
for (int i = 0; i < tmpResult.length; i++) {
tmpResult[i] = list.get(i >= half ? i % half : i);
}
// 将顺序打乱,否则会出现前半部分和后半部分是完全分开的情况
LinkedList<Integer> _result = new LinkedList<Integer>();
while (_result.size() != sum) {
temp = random.nextInt(sum);
if (!_result.contains(temp)) {
_result.add(temp);
}
}
int[] result = new int[sum];
for (int i = 0; i < result.length; i++) {
result[i] = tmpResult[_result.get(i)];
}
return result;
}
/**
* 将map中的key转换成一个list,其中每个key的value表示该key出现的次数,转换中如果次数多于1需要重复添加key到list中
*
* @param map
* @return
*/
private List<Integer> mapKeyToList(HashMap<Integer, Integer> map) {
List<Integer> list = new ArrayList<Integer>();
Iterator<Integer> keyIt = map.keySet().iterator();
Integer key = 0;
while (keyIt.hasNext()) {
key = keyIt.next();
if (map.get(key) == 1) {
list.add(key);
} else {
for (int i = 0; i < map.get(key); i++) {
list.add(key);
}
}
}
return list;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
RememberCard remCard = new RememberCard();
remCard.setSize(400, 300);
remCard.setLocationRelativeTo(null);
remCard.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
remCard.setVisible(true);
}
});
}
public PicPanel getPreOne() {
return preOne;
}
public void setPreOne(PicPanel preOne) {
this.preOne = preOne;
}
public void addCount() {
count++;
}
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
}
/**
* @author jqs
*
* 图片面板,主要实现了图片的显示与图片相同判断
*/
class PicPanel extends JPanel {
private static final long serialVersionUID = 2172162568449349737L;
private String picPath;
private JLabel lbl_Pic = new JLabel();
private ImageIcon bgIcon = null;
private boolean isShow = false;
private RememberCard parent;
private boolean finished = false;
public PicPanel(RememberCard rememberCard, String picPath) {
this.picPath = picPath;
this.parent = rememberCard;
this.setBorder(new CompoundBorder(null, new LineBorder(new Color(0, 0,
0), 2)));
this.setLayout(new BorderLayout());
this.add(lbl_Pic, BorderLayout.CENTER);
this.addMouseListener(mouseAdapter);
}
public String getPicPath() {
return picPath;
}
public void setPicPath(String picPath) {
this.picPath = picPath;
}
/**
* 图片面板的鼠标事件监听,配对过程在此完成
*/
private MouseAdapter mouseAdapter = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
new Thread() {
public void run() {
if (!parent.isRunning() || finished) {
return;
}
isShow = !isShow;
if (isShow) {
if (bgIcon == null) {
initLabelImage();
}
PicPanel curOne = (PicPanel) lbl_Pic.getParent();
PicPanel preOne = parent.getPreOne();
if (preOne == null) {
parent.setPreOne(curOne);
} else {
boolean right = checkRight(curOne, preOne);
if (right) {
parent.setPreOne(null);
curOne.setFinished(true);
preOne.setFinished(true);
parent.addCount();
} else {
lbl_Pic.setIcon(bgIcon);
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
lbl_Pic.setIcon(null);
isShow = !isShow;
repaint();
preOne.getMouseListeners()[0]
.mouseClicked(null);
parent.setPreOne(null);
return;
}
}
lbl_Pic.setIcon(bgIcon);
} else {
lbl_Pic.setIcon(null);
}
repaint();
};
}.start();
}
/**
* 检查两个面板显示的图片是否一致,根据图片的路径来判断,同时要保证两个面板不是同一个面板
*
* @param curOne
* @param preOne
* @return
*/
private boolean checkRight(PicPanel curOne, PicPanel preOne) {
return curOne.getPicPath().equals(preOne.getPicPath())
&& !curOne.equals(preOne);
}
};
/**
* 初始化Label对象的image
*/
private void initLabelImage() {
try {
Image image = ImageIO.read(new File(picPath));
if (image != null) {
int lblWidth = this.getWidth();
int lblHeight = this.getHeight();
bgIcon = new ImageIcon(image.getScaledInstance(lblWidth,
lblHeight, Image.SCALE_DEFAULT));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 当找到配对的图片面板后设置完成状态为true,此时点击图片面板已经无效了。
*
* @param b
*/
protected void setFinished(boolean b) {
finished = b;
}
}