Model.java
上传用户:jennyfly
上传日期:2021-08-10
资源大小:735k
文件大小:45k
源码类别:

游戏

开发平台:

Java

  1. package model;
  2. import java.awt.Color;
  3. import java.awt.Point;
  4. import java.awt.Rectangle;
  5. import java.util.ArrayList;
  6. import java.util.Observable;
  7. import java.util.Random;
  8. import view.Sound;
  9. /**
  10.  * model控制所有数据操作和逻辑操作
  11.  * 
  12.  * @author 何晓飞
  13.  * @version 2.0
  14.  */
  15. public class Model extends Observable implements Runnable {
  16. public static final int PIC_SUITS=2;
  17. /*统一的背景色*/
  18. public static final Color BG_COLOR=new Color(186,120,168);
  19. /* 要导进游戏界面的图片的对数 */
  20. public static final int CARD_PAIRS = 70;
  21. /* 总关数 */
  22. public static final int TOTAL_LEVEL = 10;
  23. /* 默认的剩余时间 */
  24. private static final int DEFAULT_TIME = 400;
  25. /* 默认的历史记录 */
  26. private static final int DEFAULT_RECORD = 999;
  27. /*默认背景音乐*/
  28. private static final String DEFAULT_MUSIC="我心永恒.mid";
  29. /*默认图片文件夹*/
  30. private static final String DEFAULT_PIC_FILE="fruit";
  31. /*总提示数*/
  32. private static final int TOTAL_REMIND = 8;
  33. /* 每个单元的宽度 */
  34. private final int cellWidth;
  35. /* 每个单元的高度 */
  36. private final int cellHeight;
  37. /* 单元之间的边框距离 */
  38. private final int cellBorder;
  39. /* 总行数 */
  40. private final int rows;
  41. /* 总列数 */
  42. private final int cols;
  43. /* 要导进游戏界面的每一类图片的总数 */
  44. private int pictureNumbers;
  45. /* 关数 */
  46. private int level;
  47. /*给每个关一个个性化的名字*/
  48. private String levelName;
  49. /* 通关时的分数,这个数据作为保存游戏时的一个部分 */
  50. private int lastScore;
  51. /* 当前分数 */
  52. private int currentScore;
  53. /*演示开始时的得分*/
  54. private int demoScore;
  55. /* 当前关数游戏时间 */
  56. private int playTime;
  57. /* 剩余时间 */
  58. private int timeLeft;
  59. /* 每一关对应的给定过关时间 */
  60. private int timeGiven;
  61. /* 被标记为选中单元的点 */
  62. private Point cellSelected;
  63. /* 存储路径总的关键点 */
  64. private ArrayList<Point> keyPoints;
  65. /* 纪录拐弯时的图片所对应的点的信息 */
  66. private Point corner1, corner2;
  67. /* 游戏当前关数的开始时间 */
  68. public long startTime;
  69. /* 暂停时的时间 */
  70. public long pauseTime;
  71. /* 当前关数总共暂停了的时间 */
  72. public int pauseDuration;
  73. /* 剩余的图片数量 */
  74. private int cardLeft;
  75. /* 从文件中读取的历史纪录集 */
  76. private ArrayList<Record> records;
  77. /* 该线程用于定时的对界面进行更新功能 */
  78. private Thread autoTimer;
  79. /* 该线程用于示范功能 */
  80. private Thread demoThread;
  81. /* 是否处在演示模式的标志 */
  82. public boolean IsDemo = false;
  83. /* 游戏是否进行中 */
  84. public boolean IsPlaying = true;
  85. /* 所选图片种类的文件夹名字 */
  86. private String pictureFile;
  87. /* 总提示机会 */
  88. private int reminds;
  89. /*演示开始时的提示机会*/
  90. private int demoReminds;
  91. /* 所选音乐 */
  92. private String music;
  93. /* 点击声音 */
  94. private Sound clickSound;
  95. /* 连时声音 */
  96. private Sound linkSound;
  97. /* 过关声音 */
  98. private Sound passSound;
  99. /* 纪录窗体位置 */
  100. private Point frameLocation = null;
  101. /* 建立一个数组,用于记录每个单元的信息 */
  102. private int[][] data;
  103. /**
  104.  * 初始化各参数
  105.  */
  106. public Model() {
  107. /*
  108.  * 初始化单元长宽,边框,总行数和列数
  109.  */
  110. cellWidth = 40;
  111. cellHeight = 40;
  112. cellBorder = 1;
  113. rows = 12;
  114. cols = 20;
  115. data = new int[rows][cols];
  116. records = new ArrayList<Record>();// 初始化历史记录集
  117. pictureFile = DEFAULT_PIC_FILE; // 设置游戏界面所用图片
  118. music = DEFAULT_MUSIC; // 初始化音乐
  119. pictureNumbers = 30;// 设置游戏界面所用图片数量
  120. clickSound = new Sound("click.wav");// 建立点击声音的对象
  121. linkSound = new Sound("delete.wav");// 建立连时声音的对象
  122. passSound = new Sound("pass.wav");// 建立过关时声音的对象
  123. reminds=TOTAL_REMIND;
  124. level = 1;// 设置关数
  125. lastScore = 0;// 作为记录的得分
  126. currentScore = 0;// 当前得分
  127. pauseDuration = 0;// 初始化暂停总时间
  128. timeLeft = DEFAULT_TIME;// 初始化剩余时间
  129. cellSelected = null;
  130. keyPoints = new ArrayList<Point>();
  131. /*
  132.  * 初始化窗口设置
  133.  */
  134. loadSet();
  135. /*
  136.  * 初始化历史记录集
  137.  */
  138. loadRecord();
  139. }
  140. /**
  141.  * 帮助
  142.  */
  143. public void about() {
  144. notifyAbout();
  145. }
  146. public void back(){
  147. notifyBack();
  148. }
  149. /**
  150.  * 退出
  151.  */
  152. public void cancel() {
  153. notifyCancel();
  154. }
  155. /**
  156.  * 假如取消单击右键的话,调用此方法已取消当前的选择
  157.  */
  158. public void cancelSelect() {
  159. cellSelected = null;
  160. paintPic();
  161. }
  162. /**
  163.  * 判断两个位置的图片是否可以消去
  164.  * 
  165.  * @param p1
  166.  *            第一个位置
  167.  * @param p2
  168.  *            第二个位置
  169.  * @return 是否可以消去的boolean 结果
  170.  */
  171. private boolean canDelete(Point p1, Point p2) {
  172. // 初级的判断是否可连(包括位置一样的不能连,位置上的图片不一样,位置上的图片为空)
  173. // 这些是最基本的判断,要不满足就直接返回False
  174. if (p1.equals(p2) || data[p1.x][p1.y] != data[p2.x][p2.y]
  175. || data[p1.x][p1.y] == 0 || data[p2.x][p2.y] == 0)
  176. return false;
  177. corner1 = null;//
  178. corner2 = null;//
  179. // 高级的判断是否可连(包括直线连,有一个弯连,有两个弯连)
  180. // 可连的判断
  181. if (noCorner(p1, p2) || oneCorner(p1, p2) || twoCorner(p1, p2))
  182. return true;
  183. else
  184. return false;
  185. }
  186. /**
  187.  * 当监听MouseEvent事件的话,进行各种情况的判断以知道是否可连
  188.  * 
  189.  * @param p
  190.  *            所点的图片对应的Point对象
  191.  */
  192. public void clickCell(Point p) {
  193. /*
  194.  * 如果越界或者坐标点所表示的图像为空或者处于演示模式则返回
  195.  */
  196. if (isEmpty(p) || (!withinBound(p))||IsDemo)
  197. return;
  198. else {
  199. /*
  200.  * 判断是否已经有被选中的单元了,若没有则将所给元素设为被选中单元 并且播放点击音乐
  201.  */
  202. if (null == cellSelected) {
  203. cellSelected = p;
  204. clickSound.play(false);
  205. }
  206. /*
  207.  * 如果在此之前被选中单元已经有值了,那么判断给定的图片是否能与该 选中单元相消,如果可以则执行消去方法并被选单元置空以重新被赋值
  208.  */
  209. else if (canDelete(cellSelected, p)) {
  210. delete(cellSelected, p, true);
  211. cellSelected = null;
  212. /*
  213.  * 每一次消去都可能导致图片消完因此要进行判断是否消完
  214.  */
  215. if (cardLeft == 0) {
  216. IsPlaying=false;
  217. passSound.play(false);
  218. // 纪录时间
  219. if (getPlayTime() < getRecord(level)) {//判断是否你打破了纪录,如果你打破了纪录
  220. // ,
  221. // 将会让你只知道
  222. LoadAndSave.saveRecord(getLevel(), getPlayTime()); // ,
  223. // 并将这个新的纪录写入文件
  224. notifyBreakRecord();
  225. }
  226. if (!IsDemo) {
  227. if (level < TOTAL_LEVEL) {
  228. if (getPlayTime() < getTimeGiven()) {
  229. int scoreAdd = 0;
  230. scoreAdd += (getTimeGiven() - getPlayTime()) * 5;
  231. currentScore += scoreAdd;
  232. }
  233. setLastScore(currentScore);
  234. level++;
  235. } else {
  236. notifyAccomplish();
  237. notifyOption();
  238. }
  239. }
  240. notifyLevel();// 刷新关数
  241. notifyRecord();// 刷新分数
  242. startGame(true);// 重新开始
  243. }
  244. /*
  245.  * 也有可能导致死锁
  246.  */
  247. if (cardLeft > 2 && solutionCount() == 0) {
  248. notifyNoSolution();
  249. refresh();
  250. }
  251. }
  252. /**
  253.  * 如果不能消去,那么被选单元的值被给定点替换掉,等待与下一个点匹 配,同时播放点击音乐
  254.  */
  255. else {
  256. cellSelected = p;
  257. clickSound.play(false);
  258. }
  259. paintPic();// 刷新
  260. }
  261. }
  262. /**
  263.  * 点击音乐按钮
  264.  */
  265. public void clickMusic() {
  266. notifyClickMusic();
  267. }
  268. /**
  269.  * 选择图片
  270.  */
  271. public void clickPic() {
  272. notifyClickPic();
  273. }
  274. /**
  275.  * 点击声音开关
  276.  */
  277. public void clickSound() {
  278. notifyClickSound();
  279. }
  280. /**
  281.  * 相消
  282.  * 
  283.  * @param Point
  284.  *            ,Point,boolean p1, p2前后所点的两个点,第三个参数能表明是相消还是提示
  285.  */
  286. private void delete(Point p1, final Point p2, boolean bDelete) {
  287. /*
  288.  * corner1 为空那么corner2必然为空 此时只需要加上两个连接点
  289.  */
  290. if (corner1 == null) {
  291. keyPoints.add(p1);
  292. keyPoints.add(p2);
  293. }
  294. /*
  295.  * corner1不为空而corner2为空这样可以判断为一个弯的情况,这样加上两个连接点和第一个转角
  296.  */
  297. else if (corner1 != null && corner2 == null) {
  298. keyPoints.add(p1);
  299. keyPoints.add(corner1);
  300. keyPoints.add(p2);
  301. }
  302. /*
  303.  * corner2 不为空,则corner1必然不为空,这样的话,连接的时候需要加上两个连接点和两个转角
  304.  */
  305. else if (corner2 != null) {
  306. if (p1.x != corner1.x && p1.y != corner1.y) {
  307. Point pt = corner1;
  308. corner1 = corner2;
  309. corner2 = pt;
  310. }
  311. keyPoints.add(p1);
  312. keyPoints.add(corner1);
  313. keyPoints.add(corner2);
  314. keyPoints.add(p2);
  315. }
  316. notifyLink();
  317. // bDelete其实就是判断它是在给你提示,还是你要真正的连
  318. if (bDelete) {
  319. // 根据level来判断连完图片后其他图片应该做怎样的调整,下面是那10关的调用
  320. switch (level) {
  321. case 1:
  322. level1(p1, p2);
  323. break;
  324. case 2:
  325. level2(p1, p2);
  326. break;
  327. case 3:
  328. level3(p1, p2);
  329. break;
  330. case 4:
  331. level4(p1, p2);
  332. break;
  333. case 5:
  334. level5(p1, p2);
  335. break;
  336. case 6:
  337. level6(p1, p2);
  338. break;
  339. case 7:
  340. level7(p1, p2);
  341. break;
  342. case 8:
  343. level8(p1, p2);
  344. break;
  345. case 9:
  346. level9(p1, p2);
  347. break;
  348. case 10:
  349. level10(p1, p2);
  350. break;
  351. default:
  352. break;
  353. }
  354. }
  355. }
  356. /**
  357.  * 演示
  358.  */
  359. public void demo() {
  360. notifyDemo();
  361. }
  362. /**
  363.  * 进入游戏
  364.  */
  365. public void enter() {
  366. notifyEnter();
  367. }
  368. /**
  369.  * 释放关键点
  370.  */
  371. public void freeKeyPoints() {
  372. keyPoints.clear();
  373. }
  374. /**
  375.  * 获取剩余图片数
  376.  * 
  377.  * @return 剩余图片数目
  378.  */
  379. public int getCardLeft() {
  380. return this.cardLeft;
  381. }
  382. /**
  383.  * 获取单元间距
  384.  * 
  385.  * @return 单元间距
  386.  */
  387. public int getCellBorder() {
  388. return this.cellBorder;
  389. }
  390. /**
  391.  * 获取单元格高度
  392.  * 
  393.  * @return 单元高度
  394.  */
  395. public int getCellHeight() {
  396. return this.cellHeight;
  397. }
  398. /**
  399.  * 获取被设为选中单元的位置
  400.  * 
  401.  * @return 被设为选中单元的位置
  402.  */
  403. public Point getCellSelected() {
  404. return this.cellSelected;
  405. }
  406. /**
  407.  * 获取单元格宽度
  408.  * 
  409.  * @return 单元宽度
  410.  */
  411. public int getCellWidth() {
  412. return this.cellWidth;
  413. }
  414. /**
  415.  * 获取 Rectangle 对象的中心坐标,将其生成一个point返回.
  416.  * 
  417.  * @param rect
  418.  *            指定了坐标空间中的一个区域的Rectangle 对象.
  419.  * 
  420.  * @return Point 由参数Rectangle对象的中心坐标生成的Point对象.
  421.  */
  422. public Point getCenter(Rectangle rect) {
  423. return new Point(rect.x + rect.width / 2, rect.y + rect.height / 2);
  424. }
  425. /**
  426.  * 获取总列数
  427.  * 
  428.  * @return 总列数
  429.  */
  430. public int getCols() {
  431. return this.cols;
  432. }
  433. /**
  434.  * 获取当前分数
  435.  * 
  436.  * @return 当前分数
  437.  */
  438. public int getCurrentScore() {
  439. return this.currentScore;
  440. }
  441. /**
  442.  * 垂直的寻找,返回一个路径(包括这个路径上的所有的点)
  443.  * 
  444.  * @param p
  445.  *            原点
  446.  * @return 纵向空白点(包括与原点等值的点)
  447.  */
  448. private ArrayList<Point> getHSpaces(Point p, Point pg) {
  449. ArrayList<Point> ps = new ArrayList<Point>();
  450. // left
  451. for (int dif = 1;; dif++) {
  452. int col = p.y - dif;
  453. int row = p.x;
  454. // 向左走,如果左边的不为0且它与p2不相等,则下面if中的条件为真,退出,否则加进数祖
  455. if (col < 0
  456. || (data[row][col] != 0 && !pg.equals(new Point(row, col))))
  457. break;
  458. ps.add(new Point(row, col));
  459. }
  460. // right
  461. for (int dif = 1;; dif++) {
  462. int col = p.y + dif;
  463. int row = p.x;
  464. // 向右走,如果右边的不为0且它与p2不相等,则下面if中的条件为真,退出,否则加进数祖
  465. if (col >= cols
  466. || (data[row][col] != 0 && !pg.equals(new Point(row, col))))
  467. break;
  468. ps.add(new Point(row, col));
  469. }
  470. return ps;
  471. }
  472. /**
  473.  * 获取关键点
  474.  * @return
  475.  * 关键点
  476.  */
  477. public ArrayList<Point> getKeyPoints() {
  478. return this.keyPoints;
  479. }
  480. /**
  481.  * 获取过关时的分数
  482.  * 
  483.  * @return
  484.  */
  485. public int getLastScore() {
  486. return this.lastScore;
  487. }
  488. /**
  489.  * 获取当前关数
  490.  * 
  491.  * @return 当前关数
  492.  */
  493. public int getLevel() {
  494. return this.level;
  495. }
  496. /**
  497.  * 获取当前关数的名字
  498.  * 
  499.  * @return 当前关的名字
  500.  */
  501. public String getLevelName() {
  502. switch (level) {
  503. case 1:
  504. levelName = "初入江湖";
  505. break;
  506. case 2:
  507. levelName = "地心引力";
  508. break;
  509. case 3:
  510. levelName = "混沌初开";
  511. break;
  512. case 4:
  513. levelName = "乾坤倒转";
  514. break;
  515. case 5:
  516. levelName = "右路逢源";
  517. break;
  518. case 6:
  519. levelName = "迎风一斩";
  520. break;
  521. case 7:
  522. levelName = "左路逢源";
  523. break;
  524. case 8:
  525. levelName = "各自为政";
  526. break;
  527. case 9:
  528. levelName = "齐心协力";
  529. break;
  530. case 10:
  531. levelName = "左右交错";
  532. break;
  533. default:
  534. levelName = "未命名";
  535. break;
  536. }
  537. return levelName;
  538. }
  539. /**
  540.  * 获取窗体的位置
  541.  * 
  542.  * @return 窗体的系统坐标
  543.  */
  544. public Point getLocation() {
  545. return this.frameLocation;
  546. }
  547. /**
  548.  * @return 返回纪录图片的数组信息
  549.  */
  550. public int[][] getMaps() {
  551. return data;
  552. }
  553. /**
  554.  * 获取用户所选的音乐
  555.  * 
  556.  * @return 用户所选的音乐文件名
  557.  */
  558. public String getMusic() {
  559. return music;
  560. }
  561. /**
  562.  * 获取总暂停时间
  563.  * 
  564.  * @return 总暂停时间
  565.  */
  566. public int getPauseDuration() {
  567. return this.pauseDuration;
  568. }
  569. /**
  570.  * 获取暂停时刻
  571.  * 
  572.  * @return 暂停时刻
  573.  */
  574. public long getPauseTime() {
  575. return this.pauseTime;
  576. }
  577. /**
  578.  * 获取所用图片文件夹
  579.  * 
  580.  * @return 所用图片文件夹名
  581.  */
  582. public String getPictureFile() {
  583. return this.pictureFile;
  584. }
  585. /**
  586.  * 获取当前采用的图片总数
  587.  * 
  588.  * @return 图片总数
  589.  */
  590. public int getPictureNumber() {
  591. return this.pictureNumbers;
  592. }
  593. /**
  594.  * 获取当前关除暂停外的游戏时间
  595.  * 
  596.  * @return 除暂停外的游戏时间
  597.  */
  598. public int getPlayTime() {
  599. this.playTime = (int) (System.currentTimeMillis() - startTime) / 1000
  600. - getPauseDuration();
  601. return this.playTime;
  602. }
  603. /**
  604.  * 查询指定关的历史记录
  605.  * 
  606.  * @param level
  607.  *            记录所在关数
  608.  * @return 如果无历史记录则按默认返回否则读取历史记录返回
  609.  */
  610. public int getRecord(int entry) {
  611. /*
  612.  * 历史记录初始化为零,如果提供的level合法则遍历记录集 寻找该关的记录,如果没找到则返回默认的历史记录
  613.  */
  614. int record = 0;
  615. if (entry >= 1 && entry <= TOTAL_LEVEL) {
  616. for (Record r : records) {
  617. if (r.getLevel() == entry) {
  618. record = r.getSeconds();
  619. break;
  620. }
  621. }
  622. }
  623. if (record == 0) {
  624. record = DEFAULT_RECORD;
  625. }
  626. return record;
  627. }
  628. /**
  629.  * 获取提示次数
  630.  * 
  631.  * @return 提示次数
  632.  */
  633. public int getReminds() {
  634. return reminds;
  635. }
  636. /**
  637.  * 获取总行数
  638.  * 
  639.  * @return 总行数
  640.  */
  641. public int getRows() {
  642. return this.rows;
  643. }
  644. /**
  645.  * 获取当前关对应的给定通关时间
  646.  * 
  647.  * @return 当前关对应的给定通关时间
  648.  */
  649. public int getTimeGiven() {
  650. return this.timeGiven;
  651. }
  652. /**
  653.  * 获取当前关除去暂停后的时间剩余
  654.  * 
  655.  * @return 秒数表示的剩余时间
  656.  */
  657. public int getTimeLeft() {
  658. return this.timeLeft - getPlayTime();
  659. }
  660. /**
  661.  * 水平的寻找,返回一个路径(包括这个路径上的所有的点)
  662.  * 
  663.  * @param p
  664.  *            原点
  665.  * @return 纵向空白点(包括与原点等值的点)
  666.  */
  667. private ArrayList<Point> getVSpaces(Point p, Point pg) {
  668. ArrayList<Point> ps = new ArrayList<Point>();
  669. // left
  670. for (int dif = 1;; dif++) {
  671. int col = p.y;
  672. int row = p.x - dif;
  673. // 向上走,如果左边的不为0且它与p2不相等,则下面if中的条件为真,退出,否则加进数祖
  674. if (row < 0
  675. || (data[row][col] != 0 && !pg.equals(new Point(row, col))))
  676. break;
  677. ps.add(new Point(row, col));
  678. }
  679. // right
  680. for (int dif = 1;; dif++) {
  681. int col = p.y;
  682. int row = p.x + dif;
  683. // 向下走,如果左边的不为0且它与p2不相等,则下面if中的条件为真,退出,否则加进数祖
  684. if (row >= rows
  685. || (data[row][col] != 0 && !pg.equals(new Point(row, col))))
  686. break;
  687. ps.add(new Point(row, col));
  688. }
  689. return ps;
  690. }
  691. /**
  692.  * 初始化数据
  693.  */
  694. private void initData() {
  695. //设置初始化游戏面板所需要的数据
  696. switch (level) {
  697. case 1:
  698. setPictureNumber(30);
  699. setTimeGiven(400);
  700. setTimeLeft(400);
  701. break;
  702. case 2:
  703. setPictureNumber(34);
  704. setTimeGiven(400);
  705. setTimeLeft(400);
  706. break;
  707. case 3:
  708. setPictureNumber(38);
  709. setTimeGiven(450);
  710. setTimeLeft(450);
  711. break;
  712. case 4:
  713. setPictureNumber(42);
  714. setTimeGiven(500);
  715. setTimeLeft(500);
  716. break;
  717. /*
  718.  * 第四,七关容易死锁所以图片数给得少
  719.  */
  720. case 5:
  721. setPictureNumber(38);
  722. setTimeGiven(500);
  723. setTimeLeft(500);
  724. case 7:
  725. setPictureNumber(38);
  726. setTimeGiven(500);
  727. setTimeLeft(500);
  728. break;
  729. default:
  730. setPictureNumber(42);
  731. setTimeGiven(500);
  732. setTimeLeft(500);
  733. break;
  734. }
  735. for (int i = 0; i < rows; i++)
  736. for (int j = 0; j < cols; j++) {
  737. data[i][j] = 0;
  738. }
  739. int numberSelected[] = null;
  740. numberSelected = selectNumbers();
  741. int m, n, selectCount = 0, figure, rowNum, columnNum;
  742. for (m = 0; m < numberSelected.length; m++) {
  743. // 从备选数字数组中顺序取出元素赋给两个由下面算法得出的数据单元
  744. figure = numberSelected[m];
  745. // n=2的目的是为了使具有相同值的数据单元成对出现
  746. for (n = 1; n <= 2; n++) {
  747. // 先选一次行号和列号
  748. rowNum = (int) (Math.random() * 10 + 1);
  749. columnNum = (int) (Math.random() * 14 + 3);
  750. /*
  751.  * 判断若先前选的行号和列号对应的数据不为空则重新选。 因为不为空则说明该位置已经被封配过了(低效 ),
  752.  * 同时保证总共选的次数不超过总单元格
  753.  */
  754. while (data[rowNum][columnNum] != 0 && selectCount < 140) {
  755. rowNum = (int) (Math.random() * 10 + 1);
  756. columnNum = (int) (Math.random() * 14 + 3);
  757. }
  758. /*
  759.  * 如果合适,那么赋值
  760.  */
  761. data[rowNum][columnNum] = figure;
  762. /*
  763.  * 成功的分配一个,计数器加一次
  764.  */
  765. selectCount++;
  766. }
  767. }
  768. // 每次初始化判断该关是否加载过,如果没有加载本关并设置图片对应关系改变了
  769. cardLeft = numberSelected.length * 2;// 初始化cardLeft
  770. pauseDuration = 0;
  771. notifyRemind();
  772. notifyLevel();
  773. notifyRecord();
  774. IsPlaying = true;
  775. }
  776. /**
  777.  * 判断是否为空
  778.  * 
  779.  * @return boolean 图片为空的话就返回true,否则就返回false
  780.  */
  781. private boolean isEmpty(Point p) {
  782. return 0 == data[p.x][p.y];
  783. }
  784. /**
  785.  * 判断给定的数字是否为偶数
  786.  * @param number
  787.  * 需要判断的数字
  788.  * @return
  789.  * boolean型的判断结果
  790.  */
  791. private boolean isEven(int number) {
  792. if (number % 2 == 0)
  793. return true;
  794. return false;
  795. }
  796. /**
  797.  * 第一关直接消
  798.  * 
  799.  * @param p1
  800.  * @param p2
  801.  */
  802. private void level1(Point p1, Point p2) {
  803. data[p1.x][p1.y] = 0;
  804. data[p2.x][p2.y] = 0;
  805. updateData();
  806. }
  807. /**
  808.  * 第十关偶数行右移,奇数行左移
  809.  * 
  810.  * @param p1
  811.  * @param p2
  812.  */
  813. private void level10(Point p1, Point p2) {
  814. /*
  815.  * 如果是偶数行那么向右移
  816.  */
  817. if (isEven(p1.x)) {
  818. for (int i = p1.y; i >= 3; i--) {
  819. data[p1.x][i] = data[p1.x][i - 1];
  820. /*
  821.  * 碰到p2则修改它的引用
  822.  */
  823. if (p1.x == p2.x && (i - 1) == p2.y) {
  824. p2 = new Point(p1.x, i);
  825. }
  826. if (data[p1.x][i - 1] == 0) {
  827. break;
  828. }
  829. }
  830. /*
  831.  * 如果是奇数行那么向左移
  832.  */
  833. else if (!isEven(p1.x)) {
  834. for (int i = p1.y; i <= 16; i++) {
  835. data[p1.x][i] = data[p1.x][i + 1];
  836. /*
  837.  * 碰到p2则修改它的引用
  838.  */
  839. if (p1.x == p2.x && (i + 1) == p2.y) {
  840. p2 = new Point(p1.x, i);
  841. }
  842. if (data[p1.x][i + 1] == 0) {
  843. break;
  844. }
  845. }
  846. }
  847. /*
  848.  * 如果是偶数行那么向右移
  849.  */
  850. if (isEven(p2.x)) {
  851. for (int i = p2.y; i >= 3; i--) {
  852. data[p2.x][i] = data[p2.x][i - 1];
  853. if (data[p2.x][i - 1] == 0) {
  854. break;
  855. }
  856. }
  857. /*
  858.  * 如果是奇数行那么向左移
  859.  */
  860. else if (!isEven(p2.x)) {
  861. for (int i = p2.y; i <= 16; i++) {
  862. data[p2.x][i] = data[p2.x][i + 1];
  863. if (data[p2.x][i + 1] == 0) {
  864. break;
  865. }
  866. }
  867. }
  868. updateData();
  869. }
  870. /**
  871.  * 第二关有垂直重力效应
  872.  */
  873. private void level2(Point p1, Point p2) {
  874. // 当遇到第一个值为0的元素或者到达边界时停止替换
  875. for (int i = p1.x; i > 0; i--) {
  876. data[i][p1.y] = data[i - 1][p1.y];
  877. // 如果碰到p2则修改p2的引用
  878. if (p1.y == p2.y && (i - 1) == p2.x) {
  879. p2 = new Point(i, p1.y);
  880. }
  881. if (data[i - 1][p1.y] == 0)
  882. break;
  883. }
  884. for (int j = p2.x; j > 0; j--) {
  885. data[j][p2.y] = data[j - 1][p2.y];
  886. if (data[j - 1][p2.y] == 0)
  887. break;
  888. }
  889. updateData();
  890. }
  891. /**
  892.  * 第三关有中间横向地裂效应
  893.  * 
  894.  * @param p1
  895.  * @param p2
  896.  */
  897. private void level3(Point p1, Point p2) {
  898. /*
  899.  * 如果在边界线上方则上升,从给定坐标元素开始 下方元素替换上方元素,并将下方元素置0,直到边界线
  900.  */
  901. if (p1.x <= 5) {
  902. for (int i = p1.x; i <= 5; i++) {
  903. if (i == 5) {
  904. data[i][p1.y] = 0;
  905. } else {
  906. data[i][p1.y] = data[i + 1][p1.y];
  907. // 碰到p2则 修改p2的引用
  908. if ((i + 1) == p2.x && p1.y == p2.y) {
  909. p2 = new Point(i, p1.y);
  910. }
  911. }
  912. }
  913. }
  914. /*
  915.  * 如果在边界线下方则下降,从给定坐标开始 上方元素替代下方元素,并将上方元素置0,直到边界线
  916.  */
  917. else if (p1.x > 5) {
  918. for (int i = p1.x; i >= 6; i--) {
  919. if (i == 6) {
  920. data[i][p1.y] = 0;
  921. } else {
  922. data[i][p1.y] = data[i - 1][p1.y];
  923. if ((i - 1) == p2.x && p1.y == p2.y) {
  924. p2 = new Point(i, p1.y);
  925. }
  926. }
  927. }
  928. }
  929. /*
  930.  * 如果在边界线上方则上升,从给定坐标元素开始 下方元素替换上方元素,并将下方元素置0,直到边界线
  931.  */
  932. if (p2.x <= 5) {
  933. for (int i = p2.x; i <= 5; i++) {
  934. if (i == 5) {
  935. data[i][p2.y] = 0;
  936. } else {
  937. data[i][p2.y] = data[i + 1][p2.y];
  938. }
  939. }
  940. }
  941. /*
  942.  * 如果在边界线下方则下降,从给定坐标开始 上方元素替代下方元素,并将上方元素置0,直到边界线
  943.  */
  944. else if (p2.x > 5) {
  945. for (int i = p2.x; i >= 6; i--) {
  946. if (i == 6) {
  947. data[i][p2.y] = 0;
  948. } else {
  949. data[i][p2.y] = data[i - 1][p2.y];
  950. }
  951. }
  952. }
  953. updateData();
  954. }
  955. /**
  956.  * 第四关有向上引力
  957.  * 
  958.  * @param p1
  959.  * @param p2
  960.  */
  961. private void level4(Point p1, Point p2) {
  962. for (int i = p1.x; i <= 10; i++) {
  963. data[i][p1.y] = data[i + 1][p1.y];
  964. // 如果碰到p2则修改p2的引用
  965. if (p1.y == p2.y && (i + 1) == p2.x) {
  966. p2 = new Point(i, p1.y);
  967. }
  968. if (data[i + 1][p1.y] == 0)
  969. break;
  970. }
  971. for (int j = p2.x; j <= 10; j++) {
  972. data[j][p2.y] = data[j + 1][p2.y];
  973. if (data[j + 1][p2.y] == 0)
  974. break;
  975. }
  976. updateData();
  977. }
  978. /**
  979.  * 第五关向右靠拢
  980.  * 
  981.  * @param p1
  982.  * @param p2
  983.  */
  984. private void level5(Point p1, Point p2) {
  985. for (int i = p1.y; i >= 3; i--) {
  986. data[p1.x][i] = data[p1.x][i - 1];
  987. if ((i - 1) == p2.y && p1.x == p2.x) {
  988. p2 = new Point(p1.x, i);
  989. }
  990. if (data[p1.x][i - 1] == 0) {
  991. break;
  992. }
  993. }
  994. for (int j = p2.y; j >= 3; j--) {
  995. data[p2.x][j] = data[p2.x][j - 1];
  996. if (data[p2.x][j - 1] == 0) {
  997. break;
  998. }
  999. }
  1000. updateData();
  1001. }
  1002. /**
  1003.  * 第六关纵向地裂效应
  1004.  * 
  1005.  * @param p1
  1006.  * @param p2
  1007.  */
  1008. private void level6(Point p1, Point p2) {
  1009. if (p1.y <= 9) {
  1010. for (int i = p1.y; i <= 9; i++) {
  1011. if (i == 9) {
  1012. data[p1.x][i] = 0;
  1013. } else {
  1014. data[p1.x][i] = data[p1.x][i + 1];
  1015. // 碰到p2则 修改p2的引用
  1016. if ((i + 1) == p2.y && p1.x == p2.x) {
  1017. p2 = new Point(p1.x, i);
  1018. }
  1019. }
  1020. }
  1021. }
  1022. /*
  1023.  * 如果在边界线下方则下降,从给定坐标开始 上方元素替代下方元素,并将上方元素置0,直到边界线
  1024.  */
  1025. else if (p1.y > 9) {
  1026. for (int i = p1.y; i >= 10; i--) {
  1027. if (i == 10) {
  1028. data[p1.x][i] = 0;
  1029. } else {
  1030. data[p1.x][i] = data[p1.x][i - 1];
  1031. if ((i - 1) == p2.y && p1.x == p2.x) {
  1032. p2 = new Point(p1.x, i);
  1033. }
  1034. }
  1035. }
  1036. }
  1037. /*
  1038.  * 如果在边界线上方则上升,从给定坐标元素开始 下方元素替换上方元素,并将下方元素置0,直到边界线
  1039.  */
  1040. if (p2.y <= 9) {
  1041. for (int i = p2.y; i <= 9; i++) {
  1042. if (i == 9) {
  1043. data[p2.x][i] = 0;
  1044. } else {
  1045. data[p2.x][i] = data[p2.x][i + 1];
  1046. }
  1047. }
  1048. }
  1049. /*
  1050.  * 如果在边界线下方则下降,从给定坐标开始 上方元素替代下方元素,并将上方元素置0,直到边界线
  1051.  */
  1052. else if (p2.y > 9) {
  1053. for (int i = p2.y; i >= 10; i--) {
  1054. if (i == 10) {
  1055. data[p2.x][i] = 0;
  1056. } else {
  1057. data[p2.x][i] = data[p2.x][i - 1];
  1058. }
  1059. }
  1060. }
  1061. updateData();
  1062. }
  1063. /**
  1064.  * 第七关向左靠拢
  1065.  * 
  1066.  * @param p1
  1067.  * @param p2
  1068.  */
  1069. private void level7(Point p1, Point p2) {
  1070. for (int i = p1.y; i <= 16; i++) {
  1071. data[p1.x][i] = data[p1.x][i + 1];
  1072. if ((i + 1) == p2.y && p1.x == p2.x) {
  1073. p2 = new Point(p1.x, i);
  1074. }
  1075. if (data[p1.x][i + 1] == 0) {
  1076. break;
  1077. }
  1078. }
  1079. for (int j = p2.y; j <= 16; j++) {
  1080. data[p2.x][j] = data[p2.x][j + 1];
  1081. if (data[p2.x][j + 1] == 0) {
  1082. break;
  1083. }
  1084. }
  1085. updateData();
  1086. }
  1087. /**
  1088.  * 第八关四散逃离
  1089.  * 
  1090.  * @param p1
  1091.  * @param p2
  1092.  */
  1093. private void level8(Point p1, Point p2) {
  1094. /*
  1095.  * 左上区域的向上移动
  1096.  */
  1097. if (p1.x <= 5 && p1.y <= 9) {
  1098. for (int i = p1.x; i <= 5; i++) {
  1099. if (i == 5) {
  1100. data[i][p1.y] = 0;
  1101. } else {
  1102. data[i][p1.y] = data[i + 1][p1.y];
  1103. // 如果碰到p2则修改p2的引用
  1104. if (p1.y == p2.y && (i + 1) == p2.x) {
  1105. p2 = new Point(i, p1.y);
  1106. }
  1107. }
  1108. }
  1109. }
  1110. /*
  1111.  * 右上区域的向右移动
  1112.  */
  1113. else if (p1.x <= 5 && p1.y > 9) {
  1114. for (int i = p1.y; i >= 10; i--) {
  1115. if (i == 10) {
  1116. data[p1.x][i] = 0;
  1117. } else {
  1118. data[p1.x][i] = data[p1.x][i - 1];
  1119. if ((i - 1) == p2.y && p1.x == p2.x) {
  1120. p2 = new Point(p1.x, i);
  1121. }
  1122. }
  1123. }
  1124. }
  1125. /*
  1126.  * 左下区域的向左移动
  1127.  */
  1128. else if (p1.x > 5 && p1.y <= 9) {
  1129. for (int i = p1.y; i <= 9; i++) {
  1130. if (i == 9) {
  1131. data[p1.x][i] = 0;
  1132. } else {
  1133. data[p1.x][i] = data[p1.x][i + 1];
  1134. // 碰到p2则 修改p2的引用
  1135. if ((i + 1) == p2.y && p1.x == p2.x) {
  1136. p2 = new Point(p1.x, i);
  1137. }
  1138. }
  1139. }
  1140. }
  1141. /*
  1142.  * 右下区域的向下移动
  1143.  */
  1144. else if (p1.x > 5 && p1.y > 9) {
  1145. for (int i = p1.x; i >= 6; i--) {
  1146. if (i == 6) {
  1147. data[i][p1.y] = 0;
  1148. } else {
  1149. data[i][p1.y] = data[i - 1][p1.y];
  1150. if ((i - 1) == p2.x && p1.y == p2.y) {
  1151. p2 = new Point(i, p1.y);
  1152. }
  1153. }
  1154. }
  1155. }
  1156. /*
  1157.  * 左上区域的向上移动
  1158.  */
  1159. if (p2.x <= 5 && p2.y <= 9) {
  1160. for (int i = p2.x; i <= 5; i++) {
  1161. if (i == 5) {
  1162. data[i][p2.y] = 0;
  1163. } else {
  1164. data[i][p2.y] = data[i + 1][p2.y];
  1165. }
  1166. }
  1167. }
  1168. /*
  1169.  * 右上区域的向右移动
  1170.  */
  1171. else if (p2.x <= 5 && p2.y > 9) {
  1172. for (int i = p2.y; i >= 10; i--) {
  1173. if (i == 10) {
  1174. data[p2.x][i] = 0;
  1175. } else {
  1176. data[p2.x][i] = data[p2.x][i - 1];
  1177. }
  1178. }
  1179. }
  1180. /*
  1181.  * 左下区域的向左移动
  1182.  */
  1183. else if (p2.x > 5 && p2.y <= 9) {
  1184. for (int i = p2.y; i <= 9; i++) {
  1185. if (i == 9) {
  1186. data[p2.x][i] = 0;
  1187. } else {
  1188. data[p2.x][i] = data[p2.x][i + 1];
  1189. }
  1190. }
  1191. }
  1192. /*
  1193.  * 右下区域的向下移动
  1194.  */
  1195. else if (p2.x > 5 && p2.y > 9) {
  1196. for (int i = p2.x; i >= 6; i--) {
  1197. if (i == 6) {
  1198. data[i][p2.y] = 0;
  1199. } else {
  1200. data[i][p2.y] = data[i - 1][p2.y];
  1201. }
  1202. }
  1203. }
  1204. updateData();
  1205. }
  1206. /**
  1207.  * 第九关核心引力
  1208.  * 
  1209.  * @param p1
  1210.  * @param p2
  1211.  */
  1212. private void level9(Point p1, Point p2) {
  1213. /*
  1214.  * 左上区域的向下移动
  1215.  */
  1216. if (p1.x <= 5 && p1.y <= 9) {
  1217. for (int i = p1.x; i > 0; i--) {
  1218. data[i][p1.y] = data[i - 1][p1.y];
  1219. if (p1.y == p2.y && (i - 1) == p2.x) {
  1220. p2 = new Point(i, p1.y);
  1221. }
  1222. if (data[i - 1][p1.y] == 0)
  1223. break;
  1224. }
  1225. }
  1226. /*
  1227.  * 右上区域的向左移动
  1228.  */
  1229. else if (p1.x <= 5 && p1.y > 9) {
  1230. for (int i = p1.y; i <= 16; i++) {
  1231. data[p1.x][i] = data[p1.x][i + 1];
  1232. if ((i + 1) == p2.y && p1.x == p2.x) {
  1233. p2 = new Point(p1.x, i);
  1234. }
  1235. if (data[p1.x][i + 1] == 0) {
  1236. break;
  1237. }
  1238. }
  1239. }
  1240. /*
  1241.  * 左下区域的向右移动
  1242.  */
  1243. else if (p1.x > 5 && p1.y <= 9) {
  1244. for (int i = p1.y; i >= 3; i--) {
  1245. data[p1.x][i] = data[p1.x][i - 1];
  1246. if ((i - 1) == p2.y && p1.x == p2.x) {
  1247. p2 = new Point(p1.x, i);
  1248. }
  1249. if (data[p1.x][i - 1] == 0) {
  1250. break;
  1251. }
  1252. }
  1253. }
  1254. /*
  1255.  * 右下区域的向上移动
  1256.  */
  1257. else if (p1.x > 5 && p1.y > 9) {
  1258. for (int i = p1.x; i <= 10; i++) {
  1259. data[i][p1.y] = data[i + 1][p1.y];
  1260. // 如果碰到p2则修改p2的引用
  1261. if (p1.y == p2.y && (i + 1) == p2.x) {
  1262. p2 = new Point(i, p1.y);
  1263. }
  1264. if (data[i + 1][p1.y] == 0)
  1265. break;
  1266. }
  1267. }
  1268. /*
  1269.  * 左上区域的向下移动
  1270.  */
  1271. if (p2.x <= 5 && p2.y <= 9) {
  1272. for (int j = p2.x; j > 0; j--) {
  1273. data[j][p2.y] = data[j - 1][p2.y];
  1274. if (data[j - 1][p2.y] == 0)
  1275. break;
  1276. }
  1277. }
  1278. /*
  1279.  * 右上区域的向左移动
  1280.  */
  1281. else if (p2.x <= 5 && p2.y > 9) {
  1282. for (int j = p2.y; j <= 16; j++) {
  1283. data[p2.x][j] = data[p2.x][j + 1];
  1284. if (data[p2.x][j + 1] == 0) {
  1285. break;
  1286. }
  1287. }
  1288. }
  1289. /*
  1290.  * 左下区域的向左移动
  1291.  */
  1292. else if (p2.x > 5 && p2.y <= 9) {
  1293. for (int j = p2.y; j >= 3; j--) {
  1294. data[p2.x][j] = data[p2.x][j - 1];
  1295. if (data[p2.x][j - 1] == 0) {
  1296. break;
  1297. }
  1298. }
  1299. }
  1300. /*
  1301.  * 右下区域的向下移动
  1302.  */
  1303. else if (p2.x > 5 && p2.y > 9) {
  1304. for (int j = p2.x; j <= 10; j++) {
  1305. data[j][p2.y] = data[j + 1][p2.y];
  1306. if (data[j + 1][p2.y] == 0)
  1307. break;
  1308. }
  1309. }
  1310. updateData();
  1311. }
  1312. /**
  1313.  * 加载
  1314.  */
  1315. public void load() {
  1316. notifyLoad();
  1317. }
  1318. /**
  1319.  * 加载游戏
  1320.  */
  1321. public void loadGame(){
  1322. State s=LoadAndSave.loadSave();
  1323. setCurrentScore(s.getScore());
  1324. setLastScore(s.getScore());
  1325. setLevel(s.getLevel());
  1326. setPictureFile(s.getPictureFile());
  1327. setMusic(s.getGameMusic());
  1328. setReminds(s.getReminds());
  1329. initData();
  1330. timing();// 计时
  1331. synchronized (autoTimer) {
  1332. autoTimer.notify();
  1333. startTime = System.currentTimeMillis();// 重新获取开始时间
  1334. }
  1335. paintPic();
  1336. }
  1337. /**
  1338.  * 载入游戏
  1339.  * 
  1340.  * @return 一条状态
  1341.  */
  1342. public State loadGameData() {
  1343. State s = LoadAndSave.loadSave();
  1344. return s;
  1345. }
  1346. /**
  1347.  * 载入历史记录
  1348.  */
  1349. public void loadRecord() {
  1350. records = LoadAndSave.loadRecord(TOTAL_LEVEL);
  1351. }
  1352. /**
  1353.  * 设置窗体的位置信息
  1354.  */
  1355. private void loadSet() {
  1356. frameLocation = LoadAndSave.loadLocation();
  1357. }
  1358. /**
  1359.  * 由相应的矩阵生成一个矩形对象,其左上角坐标由相应的矩阵的行号和列号以及预设的边框宽度确定 而矩形对象的长宽则匹配预设的单元长宽
  1360.  * 
  1361.  * @param row
  1362.  *            矩阵的行号,按约定从零开始
  1363.  * 
  1364.  * @param col
  1365.  *            矩阵的列号,按约定从零开始
  1366.  * 
  1367.  * @return 矩阵对对应的矩形对象
  1368.  */
  1369. public Rectangle matrixToRect(int row, int col) {
  1370. // x,y 分别表示矩形左上角的x,y 坐标。
  1371. int x = col * cellWidth + cellBorder * (col - 1);
  1372. int y = row * cellHeight + cellBorder * (row - 1);
  1373. // 通过 矩形对象的左上顶点的坐标(x,y)、宽度和高度定义这个区域并返回该 矩形 对象
  1374. return new Rectangle(x, y, cellWidth, cellHeight);
  1375. }
  1376. /**
  1377.  * 直线连
  1378.  * 
  1379.  * @param Point
  1380.  *            ,Point p1, p2前后所点的两个点
  1381.  * @return boolean 返回一个boolean值说明直线是否可连
  1382.  */
  1383. private boolean noCorner(Point p1, Point p2) {
  1384. // 只要getHSpaces(p1, p2).contains(p2)或者getVSpaces(p1,
  1385. // p2).contains(p2)中有一个为真就说明可连
  1386. return getHSpaces(p1, p2).contains(p2)
  1387. || getVSpaces(p1, p2).contains(p2);
  1388. }
  1389. /**
  1390.  * 通知帮助
  1391.  */
  1392. private void notifyAbout() {
  1393. setChanged();
  1394. notifyObservers("about");
  1395. }
  1396. /**
  1397.  * 通知通关
  1398.  */
  1399. private void notifyAccomplish() {
  1400. setChanged();
  1401. notifyObservers("accomplish");
  1402. }
  1403. private void notifyBack(){
  1404. setChanged();
  1405. notifyObservers("back");
  1406. }
  1407. /**
  1408.  * 通知破纪录
  1409.  */
  1410. private void notifyBreakRecord() {
  1411. setChanged();// 设置一个内部标志位注明数据发生了变化
  1412. notifyObservers("breakrecord");// 调用一个列表中所有的Observer的update()方法,
  1413. // 通知它们数据发生了变化
  1414. }
  1415. /**
  1416.  * 通知退出
  1417.  */
  1418. private void notifyCancel() {
  1419. setChanged();
  1420. notifyObservers("cancel");
  1421. }
  1422. /**
  1423.  * 通知系统点击了音乐按钮
  1424.  */
  1425. public void notifyClickMusic() {
  1426. setChanged();
  1427. notifyObservers("music");
  1428. }
  1429. /**
  1430.  * 通知选择了图片
  1431.  */
  1432. private void notifyClickPic() {
  1433. setChanged();
  1434. notifyObservers("pic");
  1435. }
  1436. /**
  1437.  * 通知系统点击了音乐开关
  1438.  */
  1439. private void notifyClickSound() {
  1440. setChanged();
  1441. notifyObservers("sound");
  1442. }
  1443. /**
  1444.  * 通知系统进行演示
  1445.  */
  1446. public void notifyDemo() {
  1447. setChanged();
  1448. notifyObservers("demo");
  1449. }
  1450. /**
  1451.  * 假如结束则自动调用Observer的update()方法.
  1452.  */
  1453. private void notifyDemoFinished() {
  1454. setChanged();
  1455. notifyObservers("demofinished");
  1456. }
  1457. /**
  1458.  * 通知进入游戏
  1459.  */
  1460. private void notifyEnter() {
  1461. setChanged();
  1462. notifyObservers("enter");
  1463. }
  1464. /**
  1465.  * 假如过关则自动调用Observer的update()方法,传参"level"
  1466.  */
  1467. private void notifyLevel() {
  1468. setChanged();
  1469. notifyObservers("level");
  1470. }
  1471. /**
  1472.  * 通知画线
  1473.  */
  1474. private void notifyLink() {
  1475. setChanged();
  1476. notifyObservers("link");
  1477. }
  1478. /**
  1479.  * 通知加载
  1480.  */
  1481. private void notifyLoad() {
  1482. setChanged();
  1483. notifyObservers("load");
  1484. }
  1485. /**
  1486.  * 假如无解且非演示模式则自动调用Observer的update()方法.
  1487.  */
  1488. private void notifyNoSolution() {
  1489. setChanged();
  1490. if(!IsDemo)
  1491. notifyObservers("nosolution");
  1492. }
  1493. /**
  1494.  * 游戏通关后征求玩家意见是否重新开始游戏
  1495.  */
  1496. private void notifyOption() {
  1497. setChanged();
  1498. notifyObservers("option");
  1499. }
  1500. /**
  1501.  * 通知游戏结束
  1502.  */
  1503. private void notifyOver() {
  1504. setChanged();
  1505. notifyObservers("over");
  1506. }
  1507. /**
  1508.  * 通知系统发生了暂停事件
  1509.  */
  1510. private void notifyPause() {
  1511. setChanged();
  1512. notifyObservers("pause");
  1513. }
  1514. /**
  1515.  * 通知更新历史记录
  1516.  */
  1517. private void notifyRecord() {
  1518. setChanged();
  1519. notifyObservers("record");
  1520. }
  1521. /**
  1522.  * 假如要提示则自动调用Observer的update()方法,传参"remind"
  1523.  */
  1524. private void notifyRemind() {
  1525. setChanged();
  1526. notifyObservers("remind");
  1527. }
  1528. /**
  1529.  * 通知重新开始
  1530.  */
  1531. private void notifyRestart() {
  1532. setChanged();
  1533. notifyObservers("restart");
  1534. }
  1535. /**
  1536.  * 通知退出
  1537.  */
  1538. private void notifySave() {
  1539. setChanged();
  1540. notifyObservers("save");
  1541. }
  1542. /**
  1543.  * 更新游戏界面下面的面板包括所用时间,剩余本轮可消
  1544.  */
  1545. private void notifyTimer() {
  1546. setChanged();
  1547. notifyObservers("timer");
  1548. }
  1549. /**
  1550.  * 一个弯连
  1551.  * 
  1552.  * @param Point
  1553.  *            ,Point p1, p2前后所点的两个点
  1554.  * @return boolean 返回一个boolean值说明直线是否可连
  1555.  */
  1556. private boolean oneCorner(Point p1, Point p2) {
  1557. for (Point p : getHSpaces(p1, p2)) {
  1558. // 固定行,上下遍历
  1559. if (getVSpaces(p, p2).contains(p2)) {
  1560. corner1 = p;
  1561. return true;
  1562. }
  1563. }
  1564. for (Point p : getVSpaces(p1, p2)) {
  1565. // 固定列,左右遍历
  1566. if (getHSpaces(p, p2).contains(p2)) {
  1567. corner1 = p;
  1568. return true;
  1569. }
  1570. }
  1571. return false;
  1572. }
  1573. /**
  1574.  * 判断是否超时
  1575.  * 
  1576.  * @return 超时与否
  1577.  */
  1578. private boolean overTime() {
  1579. boolean over = false;
  1580. if (getTimeLeft() == 0)
  1581. over = true;
  1582. return over;
  1583. }
  1584. /**
  1585.  * 更新图片面板
  1586.  */
  1587. public void paintPic() {
  1588. setChanged();
  1589. notifyObservers();
  1590. }
  1591. /**
  1592.  * 暂停事件
  1593.  */
  1594. public void pause() {
  1595. notifyPause();
  1596. }
  1597. public void pauseDuration() {
  1598. pauseDuration += (int) (System.currentTimeMillis() - pauseTime) / 1000;
  1599. }
  1600. /**
  1601.  * 将系统的坐标系转化为以程序定制的行列表示的point,这样做的目的是为了 让一块区域属于一张图片,那么只要你只要在这个区域里点击了,那么就认为
  1602.  * 这张图片被点击了。当然返回的坐标点还要进一步处理。
  1603.  * 
  1604.  * @param Point
  1605.  *            任何鼠标返回的系统坐标点
  1606.  * @return Point 程序定制的以行列表示的坐标点
  1607.  */
  1608. public Point pointToMatrix(Point p) {
  1609. // column 表示列号,它实际上由系统坐标轴的 x 轴确定
  1610. int col = 0;
  1611. // row 表示行号,它实际上有系统坐标轴的 y 轴确定
  1612. int row = 0;
  1613. /*
  1614.  * 这个算法的思想是:除了第一个单元,其他的的单元 不但占有宽度和高度还有一个对象间距,因此可以用除法,
  1615.  * 计算列号时x坐标除以单元宽度和间距的和,x坐标小于 这个和时,计算机结果为零,那么列行从零开始。计算 行号时与此类似
  1616.  */
  1617. col = (p.x) / (cellWidth + cellBorder);
  1618. row = (p.y) / (cellHeight + cellBorder);
  1619. return new Point(row, col);
  1620. }
  1621. /**
  1622.  * 由相应的point对象对应的生成一个矩形对象,其左上角坐标由相应的point对象的x,y坐标确定。
  1623.  * 本方法实际上是调用matrixToRect方法。
  1624.  * 
  1625.  * @param p
  1626.  *            一个point 对象,实际上该对象已经经过内部处理,实际上还是一个矩阵
  1627.  * 
  1628.  * @return Rectangle 返回指定了坐标空间中的一个区域Rectangle 对象
  1629.  */
  1630. public Rectangle pointToRect(Point p) {
  1631. return matrixToRect(p.x, p.y);
  1632. }
  1633. /**
  1634.  * 专门用于随机打乱data中数据,即打乱图片的位置,用于在无解的时候刷新 初始化数据的时候第一行到第十行以及第三列到第十六列以外的所有数据都默认为0
  1635.  */
  1636. private void randomSort() {
  1637. ArrayList<Integer> elements = new ArrayList<Integer>();
  1638. // 寻找所有不为空的元素存放在一个临时的array list里
  1639. for (int row = 1; row < rows - 1; row++) {
  1640. for (int col = 3; col < cols - 3; col++) {
  1641. if (data[row][col] != 0)
  1642. elements.add(new Integer(data[row][col]));
  1643. }
  1644. }
  1645. /*
  1646.  * 从array list里随机删除一个元素并且将该被删除元素赋给 array list 的另一个元素。
  1647.  * 这样下一次操作取随机数的时候不会产生重复,同时保证原先不为空的元素才有资格分配 一个新的元素
  1648.  */
  1649. for (int row = 1; row < rows - 1; row++) {
  1650. for (int col = 3; col < cols - 3; col++) {
  1651. if (data[row][col] != 0)
  1652. data[row][col] = elements.remove(new Random()
  1653. .nextInt(elements.size()));
  1654. ;
  1655. }
  1656. }
  1657. cellSelected = null;
  1658. /*
  1659.  * 这样随机排布后还是可能出现死锁的情况,所以必须保证最后一次 随机排列后的结果不为死锁
  1660.  */
  1661. while (solutionCount() == 0) {
  1662. randomSort();
  1663. }
  1664. }
  1665. /**
  1666.  * 随机分配现有的元素并更新界面
  1667.  */
  1668. private void refresh() {
  1669. randomSort();
  1670. paintPic();
  1671. }
  1672. /**
  1673.  * 单击提示按钮时所引发的事件
  1674.  */
  1675. public void remind() {
  1676. reminds--;
  1677. notifyRemind();
  1678. /* 如果还有提示机会则显示提示 */
  1679. if (reminds >0) {
  1680. showTip();
  1681. }
  1682. }
  1683. /**
  1684.  * 重新开始
  1685.  */
  1686. public void reStart() {
  1687. notifyRestart();
  1688. }
  1689. /**
  1690.  * 转到第一关重新开始游戏,重新初始化所有数据
  1691.  */
  1692. public void restartGame() {
  1693. setCurrentScore(0);
  1694. setLastScore(0);
  1695. setLevel(1);
  1696. setReminds(8);
  1697. initData();// 初始化数据
  1698. timing();// 计时
  1699. synchronized (autoTimer) {
  1700. autoTimer.notify();
  1701. startTime = System.currentTimeMillis();// 重新获取开始时间
  1702. }
  1703. paintPic();
  1704. }
  1705. /*
  1706.  * @see java.lang.Runnable#run() autoTimer线程每隔0.5秒检查并通知一下时间
  1707.  * 如果游戏处于进行状态并且超过时间限定,那么 通知游戏结束
  1708.  */
  1709. public void run() {
  1710. while (true) {
  1711. synchronized (autoTimer) {
  1712. try {
  1713. autoTimer.wait(500);
  1714. } catch (InterruptedException e) {
  1715. e.printStackTrace();
  1716. }
  1717. if (IsPlaying == true) {
  1718. notifyTimer();
  1719. if (overTime()) {
  1720. notifyOver();
  1721. }
  1722. }
  1723. }
  1724. }
  1725. }
  1726. /**
  1727.  * 退出游戏
  1728.  */
  1729. public void save() {
  1730. notifySave();
  1731. }
  1732. /**
  1733.  * 保存游戏的数据
  1734.  */
  1735. public void saveGame() {
  1736. String pictureFile = getPictureFile();
  1737. String musicFile = getMusic();
  1738. int level = getLevel();
  1739. int score = getLastScore();
  1740. int reminds=getReminds();
  1741. LoadAndSave.saveData(pictureFile, musicFile, level, score,reminds);
  1742. }
  1743. /**
  1744.  * 保存一条历史记录
  1745.  */
  1746. public void saveRecord() {
  1747. int playTime = getPlayTime();
  1748. int history = getRecord(level);
  1749. if (history > playTime) {
  1750. LoadAndSave.saveRecord(level, playTime);
  1751. }
  1752. }
  1753. /**
  1754.  * 保存窗体退出时候的位置信息,文件不为空的时候进行加载
  1755.  */
  1756. public void saveSet() {
  1757. if (frameLocation != null) {
  1758. LoadAndSave.saveLocation(frameLocation);
  1759. }
  1760. }
  1761. /**
  1762.  * 在所有图片中选出CARD_PARIS对图片
  1763.  * 
  1764.  * @return 图片所对应的数字集合
  1765.  */
  1766. private int[] selectNumbers() {
  1767. int[] numbers = new int[CARD_PAIRS];
  1768. int pictureNumber = getPictureNumber();
  1769. ArrayList<Integer> candidate = new ArrayList<Integer>();
  1770. for (int i = 1; i <= pictureNumber; i++) {
  1771. candidate.add(i);
  1772. }
  1773. for (int i = 0; i < CARD_PAIRS; i++) {
  1774. numbers[i] = candidate.get(new Random().nextInt(pictureNumber));
  1775. }
  1776. return numbers;
  1777. }
  1778. /**
  1779.  * 设置被选中单元
  1780.  * 
  1781.  * @param cellSelected
  1782.  *            Point 类型的被选单元
  1783.  */
  1784. public void setCellSelected(Point cellSelected) {
  1785. this.cellSelected = cellSelected;
  1786. }
  1787. /**
  1788.  * 设置当前得分
  1789.  * 
  1790.  * @param s
  1791.  *            int 类型的得分
  1792.  */
  1793. public void setCurrentScore(int score) {
  1794. currentScore = score;
  1795. }
  1796. /**
  1797.  * 设置窗体的位置,通过结束时给它传递一个位置点的参数
  1798.  * 
  1799.  * @param location
  1800.  *            点的位置
  1801.  */
  1802. public void setFrameLocation(Point location) {
  1803. frameLocation = location;
  1804. }
  1805. /**
  1806.  * 设置关过时的分数
  1807.  * 
  1808.  * @param score
  1809.  *            int 类型的得分
  1810.  */
  1811. public void setLastScore(int score) {
  1812. lastScore = score;
  1813. }
  1814. /**
  1815.  * 设置级数
  1816.  * 
  1817.  * @param i
  1818.  *            关数
  1819.  */
  1820. public void setLevel(int i) {
  1821. level = i;
  1822. }
  1823. /**
  1824.  * 设置音乐种类
  1825.  * 
  1826.  * @param pFile
  1827.  *            所选音乐种类的文件夹名字
  1828.  */
  1829. public void setMusic(String music) {
  1830. this.music = music;
  1831. }
  1832. /**
  1833.  * 设置暂停时间点
  1834.  * 
  1835.  * @param t
  1836.  *            long类型的时间点
  1837.  */
  1838. public void setPauseTime(long t) {
  1839. this.pauseTime = t;
  1840. }
  1841. /**
  1842.  * 设置图片种类
  1843.  * 
  1844.  * @param pFile
  1845.  *            所选图片种类的文件夹名字
  1846.  */
  1847. public void setPictureFile(String pFile) {
  1848. pictureFile = pFile;
  1849. }
  1850. /**
  1851.  * 设置图片数量
  1852.  * 
  1853.  * @param pn
  1854.  *            int类型的图片数量
  1855.  */
  1856. public void setPictureNumber(int pn) {
  1857. pictureNumbers = pn;
  1858. }
  1859. /**
  1860.  * 设置提示次数
  1861.  * 
  1862.  * @param reminds
  1863.  *            int 类型的提示次数
  1864.  */
  1865. public void setReminds(int reminds) {
  1866. this.reminds = reminds;
  1867. }
  1868. /**
  1869.  * 
  1870.  * @param time
  1871.  *  set timeGiven
  1872.  */
  1873. private void setTimeGiven(int time) {
  1874. timeGiven = time;
  1875. }
  1876. /**
  1877.  * 
  1878.  * @param time
  1879.  *            set timeLeft
  1880.  */
  1881. private void setTimeLeft(int time) {
  1882. timeLeft = time;
  1883. }
  1884. /**
  1885.  * 假提示
  1886.  */
  1887. public boolean showTip() {
  1888. return showTip(false);
  1889. }
  1890. /**
  1891.  * 点击提示时给玩家一条提示线
  1892.  * 
  1893.  * @param b
  1894.  *            返回一个
  1895.  * 
  1896.  * @return boolean 返回一个boolean值说明是否可连
  1897.  */
  1898. private boolean showTip(boolean b) {
  1899. // 从左到右,从上到下
  1900. for (int row1 = 0; row1 < rows; row1++)
  1901. for (int col1 = 0; col1 < cols; col1++) {
  1902. // 建立一个点p1
  1903. Point p1 = new Point(row1, col1);
  1904. // 从左到右,从上到下
  1905. for (int row2 = 0; row2 < rows; row2++)
  1906. for (int col2 = 0; col2 < cols; col2++) {
  1907. // 建立一个点p2
  1908. Point p2 = new Point(row2, col2);
  1909. // 提示
  1910. if (canDelete(p1, p2)) {
  1911. delete(p1, p2, b);
  1912. return true;
  1913. }
  1914. }
  1915. }
  1916. notifyNoSolution();// 否则无解,确定后自动刷新
  1917. refresh();
  1918. return false;
  1919. }
  1920. /**
  1921.  * 计算本轮可以消除的图片对数
  1922.  * 
  1923.  * @return 本轮可消的图片的对数
  1924.  */
  1925. public int solutionCount() {
  1926. int n = 0;
  1927. // 从左到右,从上到下
  1928. for (int row1 = 0; row1 < rows; row1++)
  1929. for (int col1 = 0; col1 < cols; col1++) {
  1930. // 建立一个点p1
  1931. Point p1 = new Point(row1, col1);
  1932. // 从左到右,从上到下
  1933. for (int row2 = 0; row2 < rows; row2++)
  1934. for (int col2 = 0; col2 < cols; col2++) {
  1935. // 建立一个点p2
  1936. Point p2 = new Point(row2, col2);
  1937. // 进行可消性判断
  1938. if (canDelete(p1, p2)) {
  1939. n++;
  1940. }
  1941. }
  1942. }
  1943. return n / 2;// 返回可消的图片的对数
  1944. }
  1945. /**
  1946.  * 开始演示模式
  1947.  */
  1948. public void startDemo() {
  1949. IsPlaying=false;
  1950. demoReminds=reminds;//记录当前提示次数
  1951. demoScore=lastScore;//记录进入这关时的得分
  1952. // 如果线程未启动那么建立一个新线程
  1953. if(demoThread==null){
  1954. demoThread = new Thread(new Runnable() {
  1955. public void run() {
  1956. while(true){
  1957. synchronized(demoThread){
  1958. if (cardLeft > 0&&IsDemo) {
  1959. if (!showTip(false)) {
  1960. }
  1961. try {// 延迟0.5秒
  1962. demoThread.wait(500);
  1963. } catch (InterruptedException e) {
  1964. e.printStackTrace();
  1965. }
  1966. showTip(true);
  1967. try {
  1968. demoThread.wait(500);
  1969. } catch (InterruptedException e) {
  1970. e.printStackTrace();
  1971. }
  1972. paintPic();// 重绘图片面板
  1973. }
  1974. else {
  1975. reminds =demoReminds ;// 演示完成后恢复初始提示机会
  1976. currentScore=demoScore;//演示完成后恢复初始得分
  1977. //如果图片消完了,那么通知演示结束
  1978. if(IsDemo && cardLeft==0){
  1979. notifyDemoFinished();
  1980. }
  1981. //如果中途退出了那么从本关重新开始游戏
  1982. else if(!IsDemo){
  1983. startGame(false);
  1984. }
  1985. //演示线程进入无限等待,直到被唤醒
  1986. try {
  1987. demoThread.wait();
  1988. } catch (InterruptedException e) {
  1989. e.printStackTrace();
  1990. }
  1991. }
  1992. }
  1993. }
  1994. }
  1995. });
  1996. demoThread.start();
  1997. }
  1998. //唤醒演示线程并且进入演示模式
  1999. synchronized(demoThread){
  2000. demoThread.notify();
  2001. IsDemo=true;
  2002. }
  2003. }
  2004. /**
  2005.  * 开始游戏,如果是转到下一关则增加提示机会,如果是转到当前关则不增加
  2006.  * @param nextLevel 
  2007.  * 是否转到下一关
  2008.  */
  2009. public void startGame(boolean nextLevel) {
  2010. if(nextLevel){
  2011. //增加两次提是机会,最多加到8次为止
  2012. if (reminds <=TOTAL_REMIND-2)
  2013. setReminds(reminds + 2);
  2014. else {
  2015. setReminds(TOTAL_REMIND);
  2016. }
  2017. }
  2018. initData();// 初始化数据
  2019. timing();
  2020. synchronized (autoTimer) {
  2021. autoTimer.notify();
  2022. startTime = System.currentTimeMillis();// 得到开始时间
  2023. }
  2024. paintPic();
  2025. }
  2026. /**
  2027.  * 停止演示模式
  2028.  */
  2029. public void stopDemo() {
  2030. IsDemo = false;
  2031. }
  2032. /**
  2033.  * 计时器
  2034.  */
  2035. private void timing() {
  2036. if (autoTimer == null) {
  2037. autoTimer = new Thread(this);
  2038. autoTimer.start();
  2039. }
  2040. }
  2041. /**
  2042.  * 两个弯连
  2043.  * 
  2044.  * @param Point
  2045.  *            ,Point p1, p2前后所点的两个点
  2046.  * @return boolean 返回一个boolean值说明直线是否可连
  2047.  */
  2048. private boolean twoCorner(Point p1, Point p2) {
  2049. for (Point ph : getHSpaces(p1, p2)) {
  2050. // 固定行,上下遍历
  2051. for (Point pv : getVSpaces(ph, p2)) {
  2052. // 再固定列,左右遍历
  2053. if (getHSpaces(pv, p2).contains(p2)) {
  2054. corner1 = ph;
  2055. corner2 = pv;
  2056. return true;
  2057. }
  2058. }
  2059. }
  2060. for (Point pv : getVSpaces(p1, p2)) {
  2061. // 固定列,左右遍历
  2062. for (Point ph : getHSpaces(pv, p2)) {
  2063. // 再固定行,上下遍历
  2064. if (getVSpaces(ph, p2).contains(p2)) {
  2065. corner1 = ph;
  2066. corner2 = pv;
  2067. return true;
  2068. }
  2069. }
  2070. }
  2071. return false;
  2072. }
  2073. /**
  2074.  * 更新数据,用于每次过关
  2075.  */
  2076. private void updateData() {
  2077. cardLeft -= 2;
  2078. timeLeft += 1;
  2079. currentScore += 5;
  2080. linkSound.play(false);
  2081. paintPic();
  2082. }
  2083. private boolean withinBound(Point p) {
  2084. return ((p.x >= 0) && (p.x < rows) && (p.y >= 0) && (p.y < cols));
  2085. }
  2086. }