欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
關(guān)于《連連看》的算法研究和演示Demo

連連看曾經(jīng)是一款非常受歡迎的游戲,同時(shí)它也是一款比較古老的游戲??吹竭@里你千萬(wàn)不要認為本篇文章打算討論《連連看》的歷史以及它取得的豐功偉績(jì)。恰恰相反,在這篇文章中我們打算討論該游戲背后的實(shí)現思想,包括它定義的游戲規則,以及游戲的實(shí)現算法。作為應用,我們還將利用Java代碼實(shí)現一個(gè)通用的《連連看》算法,并使用Java Swing框架創(chuàng )建一個(gè)演示實(shí)例。


1《連連看》的游戲規則是如何定義的?


連連看的游戲界面和游戲規則都非常簡(jiǎn)單。游戲界面可以簡(jiǎn)單看作一個(gè)具有M×N個(gè)單元格的棋盤(pán),每個(gè)單元格內部顯示著(zhù)各種圖片,游戲的最終目的是消除所有圖片。但是在消除的過(guò)程中,我們需要遵守以下規則:


  1. 只有內容相同的圖片才有消除的可能
  2. 每次只能消除兩張圖片,消除時(shí)需要使用鼠標指定(即連接)
  3. 兩張圖片連接時(shí)所經(jīng)過(guò)的路徑(連接路徑)不能超過(guò)兩個(gè)拐點(diǎn)
  4. 連接路徑經(jīng)過(guò)的單元格所包含的圖片必須已經(jīng)消除

直觀(guān)感受,第一條和第二條規則不應該是算法完成的任務(wù),因為這兩條規則實(shí)現起來(lái)比較簡(jiǎn)單,應該盡量放在游戲邏輯中完成,避免算法與游戲邏輯產(chǎn)生強依賴(lài)關(guān)系。實(shí)現第三條和第四條規則有一個(gè)非常經(jīng)典的算法理論,該算法就是接下來(lái)我們要講的分類(lèi)搜索算法。


2 分類(lèi)搜索算法的原理


分類(lèi)搜索算法的基本原理是一種遞歸思想。假設我們要判斷A單元與B單元格是否可以通過(guò)一條具有N個(gè)拐點(diǎn)的路徑相連,該問(wèn)題可以轉化為能否找到一個(gè)C單元格,C與A可以直線(xiàn)連接(0折連接),且C與B可以通過(guò)一條具有N-1個(gè)拐點(diǎn)的路徑連接。下面截圖解釋了這一思想。圖中,白色和淺灰色的單元格表示沒(méi)有內容,可以連通??梢园l(fā)現,A與B連接必須經(jīng)過(guò)①②③④⑤⑥個(gè)拐點(diǎn)。假設我們找到了一個(gè)可以直接與A連接的C點(diǎn),那么只需要搜索C與B連接需要經(jīng)過(guò)的②③④⑤⑥個(gè)拐點(diǎn)即可。




基于連連看要求的拐點(diǎn)數不能超過(guò)2個(gè)的規則,我們可以將上述思想簡(jiǎn)化為三種情況。


1)0折連接

0折連接表示A與B的X坐標或Y坐標相等,可以直線(xiàn)連接,不需要任何拐點(diǎn),且連通的路徑上沒(méi)有任何阻礙,具體可以分為下面兩種情況。



2)1折連接

1折連接與0折連接恰好相反,要求A單元格與B單元格的X軸坐標與Y軸坐標都不能相等。此時(shí)通過(guò)A與B可以畫(huà)出一個(gè)矩形,而A與B位于矩形的對角點(diǎn)上。判斷A與B能否一折連接只需要判斷矩形的另外兩個(gè)對角點(diǎn)是否有一個(gè)能同時(shí)與A和B滿(mǎn)足0折連接。下面截圖說(shuō)明了1折連通的原理:



3)2折連接

根據遞歸的思想,判斷A單元格與B單元格能否經(jīng)過(guò)兩個(gè)拐點(diǎn)連接,可以轉化為判斷能否找到一個(gè)C單元格,該C單元格可以與A單元格0折連接,且C與B可以1折連接。若能找到這樣一個(gè)C單元格,那么A與B就可以2折連接,下面截圖解釋了2折連接的情況:



判斷A單元格和B單元格是否可以2折連接時(shí)需要完成水平和豎直方向上的掃描。觀(guān)察下面兩幅截圖,A與B單元格的連接屬于典型的2折連接,首先我們需要找到圖中的C單元格,然后判斷C與B單元格是否可以1折連接。在搜索C單元格時(shí)我們必須從A單元格開(kāi)始,分別向右、向左掃描,尋找同時(shí)可以滿(mǎn)足與A單元格0折連接,與B單元格1折連接的C單元格。




同樣,如果A與B單元格的位置關(guān)系是下面兩幅截圖展示的那樣。那么我們就需要在垂直方向完成向上、向下搜索,找到符合要求的C單元格。




上面我們討論了分類(lèi)搜索法的實(shí)現原理,接下來(lái)我們使用Java語(yǔ)言實(shí)現一個(gè)通用的分類(lèi)搜索算法。

3 如何實(shí)現通用的分類(lèi)搜索算法


前面多次強調,我們需要實(shí)現一個(gè)通用的分類(lèi)搜索算法。通用意味著(zhù)算法與具體的實(shí)現分離。上面介紹的分類(lèi)搜索算法建立在一個(gè)二維數組的前提下,但是我們應該使用何種類(lèi)型的二維數組呢?為了滿(mǎn)足上述要求,我們應該定義一個(gè)所有希望使用該算法的應用都應該實(shí)現的一個(gè)接口,然后在算法中使用該接口類(lèi)型的二維數組。

那么該接口應該包含些什么方法呢?根據上面對算法的分析,分類(lèi)搜索算法唯一需要判斷的就是每個(gè)單元格的連通性,即單元格是否已經(jīng)填充。理解了這些內容,下面我們創(chuàng )建該接口。

  1. public interface LinkInterface {
  2. public boolean isEmpty();
  3. public void setEmpty();
  4. public void setNonEmpty();
  5. }

上面我們將該接口起名為L(cháng)inkInterface,并且聲明了三個(gè)方法,分別用于設置或判斷單元格的連通性。

為了保證算法的獨立性,我們還應該創(chuàng )建一個(gè)用于表示單元格位置的Point類(lèi):

  1. public class Point {
  2. public int x;
  3. public int y;
  4. public Point(){}
  5. public Point(int x, int y) {
  6. this.x = x;
  7. this.y = y;
  8. }
  9. }

1)0折連通算法

接下來(lái)我們來(lái)實(shí)現0折連通的算法。首先我們需要聲明一個(gè)類(lèi),這里我們將該類(lèi)聲明為L(cháng)inkSerach。下面我們需要思考0折連通需要些什么參數,以及返回值應該是什么?首先,我們必須傳遞一個(gè)實(shí)現了LinkInterface接口的類(lèi)的數組對象。其次我們還必須傳遞A和B的位置坐標。搜索算法的一個(gè)重要功能就是返回搜索的路徑。對于0折連接,即使搜索到可用路徑,我們也不用返回任何路徑,因為整個(gè)連通路徑就是A和B點(diǎn)的連線(xiàn)。但是我們必須返回一個(gè)可以表明搜索是否成功的boolean類(lèi)型值。接下來(lái)創(chuàng )建該方法:

  1. public class LinkSearch {
  2. private static boolean MatchBolck(LinkInterface[][] datas,
  3. final Point srcPt, final Point destPt) {
  4. // 如果不屬于0折連接則返回false
  5. if(srcPt.x != destPt.x && srcPt.y != destPt.y)
  6. return false;
  7. int min, max;
  8. // 如果兩點(diǎn)的x坐標相等,則在水平方向上掃描
  9. if(srcPt.x == destPt.x) {
  10. min = srcPt.y < destPt.y ? srcPt.y : destPt.y;
  11. max = srcPt.y > destPt.y ? srcPt.y : destPt.y;
  12. for(min++; min < max; min++) {
  13. if(!datas[srcPt.x][min].isEmpty())
  14. return false;
  15. }
  16. }
  17. // 如果兩點(diǎn)的y坐標相等,則在豎直方向上掃描
  18. else {
  19. min = srcPt.x < destPt.x ? srcPt.x : destPt.x;
  20. max = srcPt.x > destPt.x ? srcPt.x : destPt.x;
  21. for(min++; min < max; min++) {
  22. if(!datas[min][srcPt.y].isEmpty())
  23. return false;
  24. }
  25. }
  26. return true;
  27. }
  28. }

0折連通算法的核心思想是根據A、B單元格的相對位置將掃描過(guò)程分解為水平和豎直兩個(gè)方向。


2)1折連接

1折連接算法與0折連接算法輸入參數相同,但是1折連接算法應該返回搜索路徑。那么應該如何處理呢?由于1折連接算法最多只有1個(gè)拐點(diǎn),而整個(gè)路徑就是兩個(gè)搜索單元格的位置加上該拐點(diǎn)的位置,需要搜索的單元格位置用戶(hù)已經(jīng)知道,因此我們只需要返回拐點(diǎn)的位置即可,當沒(méi)有搜索到連接路徑時(shí)可以返回null值,下面是1折連接的搜索算法:

  1. private static Point MatchBolckOne(LinkInterface[][] datas,
  2. final Point srcPt, final Point destPt) {
  3. // 如果不屬于1折連接則返回null
  4. if(srcPt.x == destPt.x || srcPt.y == destPt.y)
  5. return null;
  6. // 測試對角點(diǎn)1
  7. Point pt = new Point(srcPt.x,destPt.y);
  8. if(datas[pt.x][pt.y].isEmpty()) {
  9. boolean stMatch = MatchBolck(datas, srcPt, pt);
  10. boolean tdMatch = stMatch ?
  11. MatchBolck(datas, pt, destPt) : stMatch;
  12. if (stMatch && tdMatch) {
  13. return pt;
  14. }
  15. }
  16. // 測試對角點(diǎn)2
  17. pt = new Point(destPt.x, srcPt.y);
  18. if(datas[pt.x][pt.y].isEmpty()) {
  19. boolean stMatch = MatchBolck(datas, srcPt, pt);
  20. boolean tdMatch = stMatch ?
  21. MatchBolck(datas, pt, destPt) : stMatch;
  22. if (stMatch && tdMatch) {
  23. return pt;
  24. }
  25. }
  26. return null;
  27. }

3)2折連接

可以發(fā)現,0折算法和1折算法都是獨立,如果是1折連接的情況,我們是不能使用0折算法進(jìn)行判斷的,同樣,屬于0折的情況,我們也是不能使用1折算法進(jìn)行判斷的。為了建立一種通用的方法,我們必須在2折連接算法里包含上述兩種算法的判斷,這也是為什么我們將上述兩個(gè)方法聲明為private的原因,因為我們只需要為用戶(hù)暴露2折算法的方法即可。還有,2折連接需要返回一個(gè)包含兩個(gè)拐點(diǎn)的路徑,此處我們使用Java基礎庫的LinkedList來(lái)實(shí)現,具體代碼如下:

  1. public static List<Point> MatchBolckTwo(LinkInterface[][] datas,
  2. final Point srcPt, final Point destPt) {
  3. if(datas == null || datas.length == 0)
  4. return null;
  5. if(srcPt.x < 0 || srcPt.x > datas.length)
  6. return null;
  7. if(srcPt.y < 0 || srcPt.y > datas[0].length)
  8. return null;
  9. if(destPt.x < 0 || destPt.x > datas.length)
  10. return null;
  11. if(destPt.y < 0 || destPt.y > datas[0].length)
  12. return null;
  13. // 判斷0折連接
  14. if(MatchBolck(datas, srcPt, destPt)) {
  15. return new LinkedList<>();
  16. }
  17. List<Point> list = new LinkedList<Point>();
  18. Point point;
  19. // 判斷1折連接
  20. if((point = MatchBolckOne(datas, srcPt, destPt)) != null) {
  21. list.add(point);
  22. return list;
  23. }
  24. // 判斷2折連接
  25. int i;
  26. for(i = srcPt.y + 1; i < datas[srcPt.x].length; i++) {
  27. if(datas[srcPt.x][i].isEmpty()) {
  28. Point src = new Point(srcPt.x, i);
  29. Point dest = MatchBolckOne(datas, src, destPt);
  30. if(dest != null) {
  31. list.add(src);
  32. list.add(dest);
  33. return list;
  34. }
  35. } else break;
  36. }
  37. for(i = srcPt.y - 1; i > -1; i--) {
  38. if(datas[srcPt.x][i].isEmpty()) {
  39. Point src = new Point(srcPt.x, i);
  40. Point dest = MatchBolckOne(datas, src, destPt);
  41. if(dest != null) {
  42. list.add(src);
  43. list.add(dest);
  44. return list;
  45. }
  46. } else break;
  47. }
  48. for(i = srcPt.x + 1; i < datas.length; i++) {
  49. if(datas[i][srcPt.y].isEmpty()) {
  50. Point src = new Point(i, srcPt.y);
  51. Point dest = MatchBolckOne(datas, src, destPt);
  52. if(dest != null) {
  53. list.add(src);
  54. list.add(dest);
  55. return list;
  56. }
  57. } else break;
  58. }
  59. for(i = srcPt.x - 1; i > -1; i--) {
  60. if(datas[i][srcPt.y].isEmpty()) {
  61. Point src = new Point(i, srcPt.y);
  62. Point dest = MatchBolckOne(datas, src, destPt);
  63. if(dest != null) {
  64. list.add(src);
  65. list.add(dest);
  66. return list;
  67. }
  68. } else break;
  69. }
  70. return null;
  71. }

4 接下來(lái)我們利用Java Swing框架創(chuàng )建一個(gè)演示實(shí)例

除了上面創(chuàng )建的兩個(gè)類(lèi)之外,我們還需要創(chuàng )建一個(gè)表示每個(gè)單元格的LinkItem類(lèi),以及一個(gè)創(chuàng )建框架的主類(lèi)LinkGame。創(chuàng )建類(lèi)之前,我們還需尋找一些示例圖片。這里我們使用GitHub上的一個(gè)開(kāi)源項目freegemas的資源圖片,下面截圖顯示了我們的即將使用的七張圖片資源:




創(chuàng )建LinkItem類(lèi):
  1. public class LinkItem extends JComponent implements LinkInterface {
  2. private static LinkItem selectedItem;
  3. private static LinkItem targetItem;
  4. private int rowId = -1;
  5. private int colId = -1;
  6. private boolean empty = true;
  7. private Image image;
  8. private Stroke defaultStroke;
  9. public LinkItem() {
  10. setLayout(new FlowLayout());
  11. defaultStroke = new BasicStroke(2, BasicStroke.CAP_BUTT,
  12. BasicStroke.JOIN_ROUND, 1f);
  13. }
  14. @Override
  15. protected void paintComponent(Graphics g) {
  16. Graphics2D g2 = (Graphics2D)g;
  17. int width = getWidth();
  18. int height = getHeight();
  19. // 激活時(shí)才填充并顯示內容
  20. if(!empty && image != null) {
  21. g2.drawImage(image.getScaledInstance(width - 8, height - 8,
  22. Image.SCALE_SMOOTH), 4, 4, null);
  23. }
  24. // 繪制邊框的顏色
  25. if(selectedItem == this) {
  26. g2.setColor(Color.RED);
  27. g2.setStroke(defaultStroke);
  28. }
  29. else if(targetItem == this) {
  30. g2.setColor(Color.ORANGE);
  31. g2.setStroke(defaultStroke);
  32. } else {
  33. g2.setColor(Color.PINK);
  34. }
  35. g2.drawRect(1, 1, width - 2, height - 2);
  36. }
  37. public static LinkItem getSelectedItem() {
  38. return selectedItem;
  39. }
  40. public static void setSelectedItem(LinkItem selectedComponent) {
  41. LinkItem.selectedItem = selectedComponent;
  42. }
  43. public static LinkItem getTargetItem() {
  44. return targetItem;
  45. }
  46. public static void setTargetItem(LinkItem targetComponent) {
  47. LinkItem.targetItem = targetComponent;
  48. }
  49. @Override
  50. public boolean equals(Object obj) {
  51. if(!(obj instanceof LinkItem))return false;
  52. else {
  53. LinkItem item = (LinkItem)obj;
  54. if(image == null || item.getImage() == null)
  55. return false;
  56. return (this.image == item.image);
  57. }
  58. }
  59. public void setRow(int row) {
  60. this.rowId = row;
  61. }
  62. public void setCol(int col) {
  63. this.colId = col;
  64. }
  65. public int getRow() {
  66. return rowId;
  67. }
  68. public int getCol() {
  69. return colId;
  70. }
  71. public Image getImage() {
  72. return image;
  73. }
  74. public void setImage(Image image) {
  75. this.image = image;
  76. }
  77. @Override
  78. public boolean isEmpty() {
  79. return empty;
  80. }
  81. @Override
  82. public void setEmpty() {
  83. empty = true;
  84. }
  85. @Override
  86. public void setNonEmpty() {
  87. empty = false;
  88. }
  89. }
上述代碼很簡(jiǎn)單,如果你對Java Swing不是很了解,那先去看看《Java核心編程》第一卷的第七和第八章。需要解釋的一點(diǎn)是,selectedItem和targetItem都是靜態(tài)成員變量,用于保存當前選中的對象以及需要配對的目標對象,即A和B單元格。image.getScaledInstance(,,Image.SCALE_SMOOTH)調用非常重要,如果你直接使用g2.drawImage()方法繪制圖片的話(huà),最終界面看起來(lái)非常糟糕,不信你可以試試,這是由于圖片鋸齒化造成的,而getScaledInstance方法配合SCALE_SMOOTH參數可以返回抗鋸齒的圖片對象。還有,該類(lèi)繼承于JCompont是為了利用Java Swing的GridLayout布局管理器將整個(gè)棋盤(pán)排列起來(lái)。在構造器中必須調用setLayout(new FlowLayout())代碼,如果不調用,Item將不會(huì )顯示。

創(chuàng )建主類(lèi)LinkGame:
  1. public class LinkGame {
  2. public static void main(String[] args) {
  3. EventQueue.invokeLater(new Runnable() {
  4. @Override
  5. public void run() {
  6. // 創(chuàng )建并啟動(dòng)框架
  7. JFrame frame = new LinkFrame();
  8. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  9. frame.setVisible(true);
  10. }
  11. });
  12. }
  13. }
  14. class LinkFrame extends JFrame {
  15. private static final long serialVersionUID = 1L;
  16. private static final int DEFAULT_WIDTH = 500;
  17. private static final int DEFAULT_HEIGHT = 500;
  18. // 棋盤(pán)格數 (rows * cols) % 2必須等于0
  19. private static final int rows = 8;
  20. private static final int cols = 8;
  21. // 所有單元格
  22. private final LinkItem[][] items;
  23. // 棋子可以選的顯示內容圖片
  24. private static Image[] optImgs;
  25. private static int optCount = 7;
  26. // 選中對象的位置
  27. private int selRow = -1;
  28. private int selCol = -1;
  29. // 是否已經(jīng)選中一個(gè)對象
  30. private boolean isSelected;
  31. // 結果路徑
  32. private List<Point> pathList;
  33. // 窗口邊框和標題欄的尺寸
  34. private Insets insets;
  35. // 繪制路徑時(shí)使用的默認線(xiàn)性
  36. private Stroke defaultStroke;
  37. public LinkFrame() {
  38. setTitle("LinkGame");
  39. // 設置為網(wǎng)格布局管理器
  40. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
  41. setLayout(new GridLayout(rows, cols));
  42. defaultStroke = new
  43. BasicStroke(5, BasicStroke.CAP_ROUND,
  44. BasicStroke.JOIN_BEVEL, 1f);
  45. // 初始沒(méi)有選中對象
  46. isSelected = false;
  47. // 為Item創(chuàng )建鼠標事件處理器
  48. MouseHandler handler = new MouseHandler();
  49. // 加載圖片
  50. optImgs = new Image[optCount];
  51. for(int i = 0; i < optImgs.length; i++) {
  52. String path = "assets/images/"+ (i + 1) + ".png";
  53. File file;
  54. try {
  55. file = new File(path);
  56. optImgs[i] = ImageIO.read(file);
  57. } catch (IOException e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. // 創(chuàng )建棋盤(pán)并初始化
  62. items = new LinkItem[rows][cols];
  63. LinkItem comp;
  64. for(int i = 0; i < items.length; i++) {
  65. for(int j = 0; j < items[i].length; j++) {
  66. comp = items[i][j] = new LinkItem();
  67. comp.addMouseListener(handler);
  68. comp.setImage(optImgs[(int)(Math.random() * optImgs.length)]);
  69. <span style="white-space:pre">comp.setNonEmpty();</span>
  70. comp.setRow(i);
  71. comp.setCol(j);
  72. add(comp);
  73. }
  74. }
  75. }
  76. @Override
  77. public void paint(Graphics g) {
  78. super.paint(g);
  79. Graphics2D g2 = (Graphics2D)g;
  80. // 抗鋸齒
  81. g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
  82. RenderingHints.VALUE_ANTIALIAS_ON);
  83. // 更新窗口邊框尺寸
  84. insets = getInsets();
  85. // 設置線(xiàn)性和顏色
  86. g2.setStroke(defaultStroke);
  87. g2.setColor(Color.CYAN);
  88. // 如果存在路徑則繪制
  89. if(pathList != null) {
  90. Point pre = pathList.get(0); // 前一點(diǎn)
  91. for(int i = 1; i < pathList.size(); i++) {
  92. Point next = pathList.get(i); // 下一點(diǎn)
  93. // 獲得兩點(diǎn)對應的對象
  94. LinkItem a = items[pre.x][pre.y];
  95. LinkItem b = items[next.x][next.y];
  96. int x1 = insets.left + a.getX() + a.getWidth() / 2;
  97. int x2 = insets.left + b.getX() + b.getWidth() / 2;
  98. int y1 = insets.top + a.getY() + a.getHeight() / 2;
  99. int y2 = insets.top + b.getY() + b.getHeight() / 2;
  100. g2.drawLine(x1, y1, x2, y2);
  101. // 在最后一個(gè)點(diǎn)處填充一個(gè)圓
  102. if(i == pathList.size() - 1) {
  103. g2.draw(new Ellipse2D.Float(x2 - 2, y2 - 2, 4, 4));
  104. }
  105. pre = next;
  106. }
  107. }
  108. }
  109. private class MouseHandler extends MouseAdapter {
  110. @Override
  111. public void mouseReleased(MouseEvent e) {
  112. LinkItem curComp = (LinkItem) e.getSource();
  113. // 刷新邊框
  114. curComp.repaint();
  115. if(!isSelected) {
  116. // 設置選中對象并取消目標對象
  117. LinkItem.setSelectedItem(curComp);
  118. LinkItem.setTargetItem(null);
  119. selRow = curComp.getRow();
  120. selCol = curComp.getCol();
  121. } else {
  122. // 設置目標對象并取消選中對象
  123. LinkItem.setSelectedItem(null);
  124. LinkItem.setTargetItem(curComp);
  125. // 判斷是否可以連接
  126. LinkItem srcComp = items[selRow][selCol];
  127. if(curComp.equals(srcComp) && curComp != srcComp
  128. && !curComp.isEmpty() && !srcComp.isEmpty()) {
  129. Point srcPt = new Point(selRow, selCol);
  130. Point destPt = new Point(curComp.getRow(), curComp.getCol());
  131. // 搜索路徑
  132. pathList = LinkSearch.MatchBolckTwo(items, srcPt, destPt);
  133. // 如果存在鏈接路徑則消除單元格內容
  134. // 并為搜索路徑添加起止單元格
  135. if(pathList != null) {
  136. srcComp.setEmpty();
  137. curComp.setEmpty();
  138. srcComp.repaint();
  139. curComp.repaint();
  140. pathList.add(0, srcPt);
  141. pathList.add(destPt);
  142. LinkFrame.this.repaint();
  143. }
  144. }
  145. }
  146. // 轉換選中狀態(tài)
  147. isSelected = !isSelected;
  148. }
  149. }
  150. }
關(guān)于上述代碼,沒(méi)有什么難點(diǎn),著(zhù)重觀(guān)察一下paint()方法和MouseHander內部類(lèi)的處理邏輯。要讓上面代碼運行,你還必須創(chuàng )建一個(gè)assets文件夾,并將上述七張圖片資源分別命名為1.png、2.png...7.png,然后將其拷貝到assets下的images文件夾。

運行游戲
接下來(lái)運行游戲,最終結果如下圖所示:


下面是運行動(dòng)畫(huà):



5 上述代碼的缺陷

可能你還沒(méi)有發(fā)現,上述代碼具有致命缺陷。我們在初始化棋盤(pán)的時(shí)候是這樣做的:
  1. // 創(chuàng )建棋盤(pán)并初始化
  2. items = new LinkItem[rows][cols];
  3. LinkItem comp;
  4. for(int i = 0; i < items.length; i++) {
  5. for(int j = 0; j < items[i].length; j++) {
  6. comp = items[i][j] = new LinkItem();
  7. comp.addMouseListener(handler);
  8. comp.setImage(optImgs[(int)(Math.random() * optImgs.length)]);
  9. comp.setNonEmpty();
  10. comp.setRow(i);
  11. comp.setCol(j);
  12. add(comp);
  13. }
  14. }
上述代碼為每個(gè)單元格隨機分配了一張圖片。試想,上述這種方法如何保證每種圖片都出現了偶數次?比如說(shuō)當游戲進(jìn)行到最后出現下面情況該怎么辦:


上述情況違反了連連看要求的必須將所有單元格消除的基本規則。

其實(shí)上述問(wèn)題很容解決的。我們只需要保證每次為兩個(gè)單元格設置同一個(gè)圖片對象即可。深入思考一下,該任務(wù)應該屬于算法的份內責任,所以我們應該將初始化棋盤(pán)的任務(wù)交給算法類(lèi)處理。算法的初始化方法在填充棋盤(pán)時(shí),至少應該知道填充的內容的類(lèi)型吧!但是為了避免算法與實(shí)現產(chǎn)生強依賴(lài),我們應該使用一種通用的類(lèi)型表示填充對象的類(lèi)型,這里可以使用Java的Object類(lèi),但是有一個(gè)更好的方法,那就是泛型編程。下面我們來(lái)改造上述算法,添加初始化方法。

首先修改LinkInterface接口,修改完成后的代碼如下:
  1. public interface LinkInterface<T> {
  2. public boolean isEmpty();
  3. public void setEmpty();
  4. public void setNonEmpty();
  5. public T getContent();
  6. public void setContent(T content);
  7. }
上述代碼T表示需要填充的內容數據類(lèi)型。我們還添加了兩個(gè)用于獲取或設置內容的方法。

接下來(lái)修改LinkItem類(lèi):
  1. public class LinkItem extends JComponent implements LinkInterface<Image> {
  2. private boolean empty = true;
  3. private Image image;
  4. ...
  5. @Override
  6. public boolean isEmpty() {
  7. return empty;
  8. }
  9. @Override
  10. public void setEmpty() {
  11. empty = true;
  12. }
  13. @Override
  14. public void setNonEmpty() {
  15. empty = false;
  16. }
  17. @Override
  18. public Image getContent() {
  19. return image;
  20. }
  21. @Override
  22. public void setContent(Image content) {
  23. this.image = content;
  24. }
  25. }
刪除getImage和setImage方法,實(shí)現getContent和setContent方法,將泛型T設置為Image。

接下來(lái)按照下面代碼修改LinkSearch算法類(lèi):
  1. public class LinkSearch {
  2. private static <T> boolean MatchBolck(LinkInterface<T>[][] datas,
  3. final Point srcPt, final Point destPt) {
  4. ...
  5. }
  6. private static <T> Point MatchBolckOne(LinkInterface<T>[][] datas,
  7. final Point srcPt, final Point destPt) {
  8. ...
  9. }
  10. public static <T> List<Point> MatchBolckTwo(LinkInterface<T>[][] datas,
  11. final Point srcPt, final Point destPt) {
  12. ...
  13. }
  14. public static <T> void generateBoard(LinkInterface<T>[][] datas, T[] optConts) {
  15. List<Point> list = new LinkedList<>();
  16. for(int i = 0; i < datas.length; i++) {
  17. for(int j = 0; j < datas[i].length; j++) {
  18. list.add(new Point(i, j));
  19. }
  20. }
  21. while (list.size() != 0) {
  22. Point srcPt = list.remove((int)(Math.random() * list.size()));
  23. Point destPt = list.remove((int)(Math.random() * list.size()));
  24. LinkInterface<T> src = datas[srcPt.x][srcPt.y];
  25. LinkInterface<T> dest = datas[destPt.x][destPt.y];
  26. src.setNonEmpty();
  27. dest.setNonEmpty();
  28. T t = optConts[(int)(Math.random() * optConts.length)];
  29. src.setContent(t);
  30. dest.setContent(t);
  31. }
  32. }
  33. }
首先 我們需要為前面實(shí)現的三個(gè)方添加泛型參數,接著(zhù)創(chuàng )建一個(gè)generateBoard()方法,該方法需要一個(gè)LinkInterface類(lèi)型的二維數組參數,該參數表示即將填充的棋盤(pán),還有,我們需要傳遞一個(gè)T[]類(lèi)型的數組對象,該數組包含了棋盤(pán)可以填充的所有選擇。在方法內部,我們首先使用所有位置創(chuàng )建了一個(gè)列表,然后通過(guò)循環(huán),每次從列表中隨機刪除并返回兩個(gè)位置,然后對返回的這兩個(gè)位置上的單元格填充相同的內容,并設置為非空,當列表中的所有元素被刪除完之后跳出循環(huán),結束初始化。

最后修改LinkFrame類(lèi)的初始化代碼,最終結果如下:
  1. // 創(chuàng )建棋盤(pán)并初始化
  2. items = new LinkItem[rows][cols];
  3. LinkItem comp;
  4. for(int i = 0; i < items.length; i++) {
  5. for(int j = 0; j < items[i].length; j++) {
  6. comp = items[i][j] = new LinkItem();
  7. comp.addMouseListener(handler);
  8. comp.setRow(i);
  9. comp.setCol(j);
  10. add(comp);
  11. }
  12. }
  13. LinkSearch.generateBoard(items, optImgs);
  14. }
將棋盤(pán)修改為4*4格,運行并測試游戲:



從上面截圖可以看到,每種圖形都是偶數個(gè),無(wú)論游戲進(jìn)行到何時(shí),都能保證總是存在可以連接的對象。上述規則就一定能將所有圖片消除嗎?如果出現下面情況,該怎么辦?




其實(shí)解決上面問(wèn)題有許多種方法,比如說(shuō)使用拉斯維加斯算法和回溯法創(chuàng )建有效棋盤(pán)。該方法的基本思想是,隨機選取兩個(gè)位置判斷是否可以連通,如果可以則繼續從剩余的單元格隨機選取兩點(diǎn),再進(jìn)行判斷。當判斷兩個(gè)單元格不能連通時(shí),回溯到上一次隨機選取的狀態(tài)之前,重新嘗試。該算法的優(yōu)點(diǎn)是總能找到有效的棋盤(pán)。但是,該算法有一個(gè)致命的缺點(diǎn),就是效率低,因此大多數時(shí)候我們都不應該使用該算法產(chǎn)生棋盤(pán)。

還有一種解決上述問(wèn)題的方法是,將整個(gè)棋盤(pán)看作是一個(gè)以中心點(diǎn)為圓心的靶子,半徑就是棋盤(pán)的一半寬度,然后從內到外或從外到內,逐步填充每環(huán)所經(jīng)過(guò)的單元格。該方法
效率較高,但容易產(chǎn)生具有規則的棋盤(pán)。

很多游戲在解決上述問(wèn)題都使用了模板的方法,即在游戲中保存幾百或數千種游戲棋盤(pán)排列方式,然后再游戲中隨機選取,隨機填充不同類(lèi)型的圖片。

從概率的角度講,實(shí)現一種生成有效棋盤(pán)的算法沒(méi)有多大意義,因為即使產(chǎn)生的棋盤(pán)有效,我們在游戲時(shí),最后也有可能將游戲的棋盤(pán)玩死。比如下面兩幅截圖:

        


本來(lái)整個(gè)棋盤(pán)是可以完全消除所有單元格的,但是由于我們操作的順序發(fā)生了變化,最終導致棋盤(pán)無(wú)解。

還有,我們上面提到的一開(kāi)始就無(wú)解的情況隨著(zhù)棋盤(pán)格子數的遞增,出現的概率將越來(lái)越小,這一問(wèn)題可以通過(guò)在生成棋盤(pán)時(shí)至少創(chuàng )建一對位置相鄰的單元格來(lái)解決。


6  結束語(yǔ)

上面我們介紹了連連看的路徑搜索算法和產(chǎn)生游戲棋盤(pán)的算法。如果從算法的角度思考的話(huà),上面內容還有許多不足的地方,例如優(yōu)化搜索算法、創(chuàng )建有效的棋盤(pán)。關(guān)于搜索算法還有很多種,由于篇幅有限,本文將不再介紹。
最后再提出兩個(gè)未完成的問(wèn)題,關(guān)于上面介紹的搜素算法我們是否可以擴展到N個(gè)拐點(diǎn)的遞歸算法呢?還有生成有效棋盤(pán)的拉斯維加斯算法和回溯法應該如何實(shí)現?如果你有更好的見(jiàn)解,請不吝賜教。



本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
2 Add Two Numbers
js笛卡爾積算法
簡(jiǎn)單的畫(huà)圖程序
對java中equals和hashCode函數的一些理解
C#代碼精簡(jiǎn)優(yōu)化技巧(中)
equals()方法的重寫(xiě)
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久