JAVA記憶翻牌遊戲製作
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;
}
}