📄 model.java
字号:
package model;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Random;
import javax.swing.JOptionPane;
import login.*;
/**
* 这个类实现了该游戏的核心算法,这是游戏的最重要的部分,就是判断图像的信息,在满足设定好的条件下相消.
*
* @author : 李强,何明,何晓飞
* @version : 2.0
*/
public class Model extends Observable {
/* 要导进游戏界面的图片的对数 */
public static final int CARD_NUMBERS = 70;
/* 要导进游戏界面的每一类图片的总数 */
private int PICTURE_NUMBERS ;
/* 关数 */
private int level;
/*分数*/
private int score;
/* 剩余时间*/
private int timeLeft;
/* 将摆置图片的panel设置成12行20列,共240个格子 */
private int rows = 12, cols = 20;// 改过
/* 建立一个数组,用于记录每个格子的信息 */
private int[][] data;
/* 每个图片的导进去的位置信息存放在一个Point对象中,sltMatrix就是当前选中的图片Point */
private Point sltMatrix;
/* 纪录拐弯时的图片所对应的点的信息 */
private Point corner1, corner2;
/* 游戏的开始时间 */
public long startTime;
/*暂停时的时间*/
public long pauseTime;
/*总共暂停了的时间*/
public int pauseDuration=0;
/* 剩余的图片数量 */
private int cardLeft;
/* 历史纪录 */
private int historyRecord;
/* 该线程用于定时的对界面进行更新功能 */
private Thread t;
/* 该线程用于示范功能 */
private Thread demoThread;
/* 是否处在演示模式的标志 */
private boolean bdemo = false;
/* 用于暂停的时候,作为是否处于暂停状态的标志 */
public boolean bPlaying = true;
/* 所选图片种类的文件夹名字 */
private static String pictureFile;
/* 已提示次数 */
private int count;
/*记录游戏开始时已使用的提示次数*/
private int initCount;
/* 所选音乐种类的文件夹名字 */
private String musicFile;
/* 点击声音 */
private Sound clickSound;
/* 连时声音 */
private Sound linkSound;
/*过关声音*/
private Sound passSound;
/* 纪录窗体位置 */
private Point frameLocation;
/**
* Model (构造函数)
*/
public Model() {
pictureFile = "fruit"; // 设置游戏界面所用图片
PICTURE_NUMBERS = 30;//设置游戏界面所用图片数量
clickSound = new Sound(1);// 建立点击声音的对象
linkSound = new Sound(2);// 建立连时声音的对象
passSound=new Sound(3);//建立过关时声音的对象
level = 1;//设置关数
score =0;//设置分数
timeLeft = 350;// 初始化剩余时间
loadSet();
// 建立了一个新的线程用于刷新游戏界面下面的状态栏,包括时间,所剩图片数量
t = new Thread(new Runnable() {
public void run() {
while (true) {
synchronized (t) {
// 每隔一秒刷一次屏
try {
t.wait(500);//
} catch (InterruptedException e) {
e.printStackTrace();
}
if (bPlaying == true) {
notifyTimer();
}
}
// 假如时间超过剩余时间的话,重新开始或者退出游戏
if (getTimeLeft()==getSeconds()) {
int response = JOptionPane.showConfirmDialog(null,
"你已经超过规定时间,是否重新开始游戏 ?\n点否直接退出游戏!",
" 友情提示", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (response == JOptionPane.YES_OPTION) {
restartGame();
} else if(response==JOptionPane.NO_OPTION){
System.exit(0);
}
}
}
}
});
t.start();// 线程开始
gameStart();// 游戏开始
}
/**
* Model (构造函数) 无参构造函数,只用于构造一个简单的Model对象以调用它的loadSet方法初始化窗体的位置
*/
//public Model() {
// 初始化frameLocation
// }
/**
* 设置图片种类
*
* @param pFile
* 所选图片种类的文件夹名字
*/
public int setCount(int count) {
this.count = count;
return this.count;
}
/**
*
* @return 提示次数
*/
public int getCount() {
return count;
}
/**
*
* @return 游戏开始时使用过的提示次数
*/
public int getInitCount(){
return initCount;
}
/**
* 设置图片种类
*
* @param pFile
* 所选图片种类的文件夹名字
*/
public void setPictureFile(String pFile) {
pictureFile = pFile;
}
/**
* 得到图片种类
*
* @return 所选图片种类的文件夹名字
*/
public String getPictureFile() {
return pictureFile;
}
/**
* 设置音乐种类
*
* @param pFile
* 所选音乐种类的文件夹名字
*/
public void setMusicFile(String mFile) {
musicFile = mFile;
}
/**
* 得到音乐种类
*
*
* @return 所选音乐种类的文件夹名字
*/
public String getMusicFile() {
return musicFile;
}
/**
* 设置级数
*
* @param i
* 关数
*/
public void setLevel(int i) {
level = i;
}
/**
* 得到关数
*
* @return 关数
*/
public int getLevel() {
return level;
}
public int getScore(){
return score;
}
public void setScore(int s){
score=s;
}
/**
*
* @return 图片数量
*/
public int getPictureNumber(){
return PICTURE_NUMBERS;
}
/**
*
* @param pn 图片数量
*/
public void setPictureNumber(int pn){
PICTURE_NUMBERS=pn;
}
/**
* 设置窗体的位置信息
*/
private void loadSet() {
frameLocation = LoadAndSave.loadLocation();
}
/**
* 假如取消单击右键的话,调用此方法已取消当前的选择
*/
public void cancelSelect() {
sltMatrix = null;
updateAll();
}
/**
* 判断两个图片是否能连
*
* @param Point,
* Point 每个图片位置信息由一个Point存放 p1, p2
* @return boolean 返回一个boolean值说明是否可消
*/
private boolean canDelete(Point p1, Point p2) {
// 初级的判断是否可连(包括位置一样的不能连,位置上的图片不一样,位置上的图片为空)
// 这些是最基本的判断,要不满足就直接返回False
if (p1.equals(p2) || data[p1.x][p1.y] != data[p2.x][p2.y]
|| data[p1.x][p1.y] == 0 || data[p2.x][p2.y] == 0)
return false;
corner1 = null;//
corner2 = null;//
// 高级的判断是否可连(包括直线连,有一个弯连,有两个弯连)
// 可连的判断
if (noCorner(p1, p2) || oneCorner(p1, p2) || twoCorner(p1, p2))
return true;
else
return false;
}
/**
* 当监听MouseEvent事件的话,进行各种情况的判断以知道是否可连
*
* @param p
* 所点的图片对应的Point对象
*/
public void clickMatrix(Point p) {
// 初级的判断(包括越界,是否在游戏中和所点位置是否有图片,即data数组中队应的0点)
if (p.x < 0 || p.y < 0 || p.x >= rows || p.y >= cols)
return;
// 假如图片为空,退出
if (isEmpty(p))
return;
// 根据前后两次点的进行可消性判断
if (null == sltMatrix) {// 看在点击此图片的时候,前面是否已经点了一个图片了,如果没有那么就把刚点的这个图赋给纪录前一次点击图片的点
sltMatrix = p;
clickSound.play(1);// 这要加上音乐
} else if (canDelete(sltMatrix, p)) {
delete(sltMatrix, p, true);// 如果前面已经点了一个图片的话,那么就进行可消性判断
sltMatrix = null;// 如果能消了就复原
} else {
sltMatrix = p;// 如果消不了的话,将当前所点的图片换成第一次所点的图片
clickSound.play(1);// 加上音乐
}
// 假如所剩余的图片的为0个,进行相应的处理
if (cardLeft == 0) {
LoadAndSave.saveRecord(getSeconds(),getLevel());// 纪录时间
if (getSeconds() < historyRecord) {// 判断是否你打破了纪录,如果你打破了纪录,将会让你只知道,并将这个新的纪录写入文件
notifyBreakRecord();
historyRecord = getSeconds();
}
if (!bdemo) {// 只让它演示本关
passSound.play(1);
level++;
}
notifyLevel();// 刷新关数
gameStart();// 重新开始
}
// 判断是否有解,无解自动刷新
if (solutionCount() == 0) {
refresh();
}
updateAll();// 刷新
}
/**
* 这里特别说明一下,我们用的是java设计模式-监听模式,来搭起整个程序的大的框架
*/
private void notifyBreakRecord() {
setChanged();// 设置一个内部标志位注明数据发生了变化
notifyObservers("breakrecord");// 调用一个列表中所有的Observer的update()方法,通知它们数据发生了变化
}
/**
* 假如无解则自动调用Observer的update()方法,传参"nosolution"
*/
private void notifyNoSolution() {
setChanged();
notifyObservers("nosolution");
}
/**
* 假如结束则自动调用Observer的update()方法,传参"demofinished"
*/
private void notifyDemoFinished() {
setChanged();
notifyObservers("demofinished");
}
/**
* 假如过关则自动调用Observer的update()方法,传参"level"
*/
public void notifyLevel() {
setChanged();
notifyObservers("level");
}
/**
* 消一个图片
*
* @param Point
* 要消的图片
*/
private void delete(Point p) {
data[p.x][p.y] = 0;
cardLeft--;
timeLeft++;//消一张图片奖励1秒
score+=5;
linkSound.play(1);// 连时加上声音
updateAll();// 更新整个
}
/**
* 相消
*
* @param Point,Point,boolean
* p1, p2前后所点的两个点,第三个参数能表明是相消还是提示
*/
private void delete(Point p1, final Point p2, boolean bDelete) {
// 建立一个ArrayList将showpath()方法的所需参数加进去
ArrayList<Point> points = new ArrayList<Point>();
// 没有拐弯,就是直线连,直接将两个图片加紧去就行了
if (corner1 == null) {
points.add(p1);
points.add(p2);
} else if (corner2 == null) {// 拐了一个弯,除了要将两个要连的图片加进去,还要将第一个拐弯点加进去
points.add(p1);
points.add(corner1);
points.add(p2);
} else {// 拐两个弯的情况,将拐弯点和所连的信息全加进去
if (p1.x != corner1.x && p1.y != corner1.y) {
Point pt = corner1;
corner1 = corner2;
corner2 = pt;
}
points.add(p1);
points.add(corner1);
points.add(corner2);
points.add(p2);
}
notifyEffect(points);
// bDelete其实就是判断它是在给你提示,还是你要真正的连
if (bDelete) {
delete(p1);
delete(p2);
// 根据level来判断连完图片后其他图片应该做怎样的调整,下面是那4关的调用
switch (level) {
case 1:
level1();
break;
case 2:
level2(p1, p2);
break;
case 3:
level3(p1, p2);
break;
case 4:
level4(p1, p2);
break;
case 5:
level5(p1,p2);
break;
default:
break;
}
}
}
public int getCols() {
return cols;
}
/**
* 垂直的寻找,返回一个路径(包括这个路径上的所有的点)
*
* @param p
* 原点
* @return 纵向空白点(包括与原点等值的点)
*/
private ArrayList<Point> getHSpaces(Point p, Point pg) {
ArrayList<Point> ps = new ArrayList<Point>();
// left
for (int dif = 1;; dif++) {
int col = p.y - dif;
int row = p.x;
// 向左走,如果左边的不为0且它与p2不相等,则下面if中的条件为真,退出,否则加进数祖
if (col < 0
|| (data[row][col] != 0 && !pg.equals(new Point(row, col))))
break;
ps.add(new Point(row, col));
}
// right
for (int dif = 1;; dif++) {
int col = p.y + dif;
int row = p.x;
// 向右走,如果右边的不为0且它与p2不相等,则下面if中的条件为真,退出,否则加进数祖
if (col >= cols
|| (data[row][col] != 0 && !pg.equals(new Point(row, col))))
break;
ps.add(new Point(row, col));
}
return ps;
}
/**
* @return 返回纪录图片的数组信息
*/
public int[][] getMaps() {
return data;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -