首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

J2ME 連連看核心邏輯實現:從地圖生成到多轉彎連線演算法完整解析

     編輯:J2ME

前言:經典消除遊戲的 J2ME 移植精髓

「連連看」規則簡單卻極具深度:在 10×10 棋盤上成對消除相同圖案,連線不可超過兩個轉彎且不穿越其他方塊。本文完整拆解一個採用 MVC 分層 的 J2ME 實現:LinkCanvas(視圖/控制)+ GameEngine(模型/邏輯),重點剖析地圖打亂、直線/一轉/兩轉連線判定、連線路徑記錄三大核心演算法。

架構概覽

LinkCanvas (extends Canvas, implements Runnable)
├── paint()          → 呼叫 engine.paintMap / paintSelectArea / paintLinkLine
├── keyPressed()     → 方向鍵移動游標,FIRE 鍵呼叫 engine.fire()
├── run()            → 固定 100ms 幀率,呼叫 engine.action() + repaint()
└── GameEngine
    ├── map[10][10]  → 0=空,1-10=圖案類型
    ├── 選取狀態     → firstRow/Col, secondRow/Col, cRow/cCol (游標)
    ├── 連線類型     → NO_LINK / H_LINK / V_LINK / ONE_CORNER_X/Y / TWO_CORNER
    └── 核心演算法   → isEmptyRow/Col/Cell, isOneCornerLink, isTwoCornerLink, logic()

地圖初始化與打亂:確保可解性

private void initMap() {
    // 1. 依行填入 1~10,每行相同 → 保證每種圖案出現偶數次
    for (int r=0; r<MAX_ROW; r++)
        for (int c=0; c<MAX_COL; c++)
            map[r][c] = r + 1;

    // 2. 重複 10 次隨機交換兩個非相同格子
    for (int i=0; i<10; i++)
        for (int r=0; r<MAX_ROW; r++)
            for (int c=0; c<MAX_COL; c++) {
                int tr = Math.abs(ran.nextInt() % 10);
                int tc = Math.abs(ran.nextInt() % 10);
                if (tr != r || tc != c) swap(map[r][c], map[tr][tc]);
            }
}

關鍵點: 初始按行填充確保每種圖案恰好 10 個(偶數),隨機交換保持總數不變,理論上必有解(但未檢查連通性,極少數情況可能無解,商業版需加入「可解性驗證 + 重洗」邏輯)。

渲染管線:三層疊加

protected void paint(Graphics g) {
    clearScreen(g);                    // 白底
    engine.paintMap(g);                // 方塊與數字
    engine.paintSelectArea(g);         // 綠框游標 + 紅框已選
    engine.paintLinkLine(g);           // 消除連線動畫(閃現後清除)
}
  • paintMap:雙層迴圈繪製非零格子,drawRect + drawString。
  • paintSelectArea:依 selectTileNum (0/1/2) 繪製游標與已選高亮。
  • paintLinkLine:依 linkType 繪製直線/折線,繪製完立即重置 linkType = NO_LINK,形成「閃一下」視覺回饋。

核心演算法深度解析

1. 基礎檢測:同行/同列是否為空

// 檢查 (r1,c1) 到 (r2,c2) 之間(不含端點)同行是否全為 0
private boolean isEmptyRow(int r1, int c1, int r2, int c2) {
    if (r1 != r2) return false;
    if (c1 > c2) for (int c=c1-1; c>c2; c--) if (map[r1][c]!=0) return false;
    else         for (int c=c2-1; c>c1; c--) if (map[r1][c]!=0) return false;
    return true;
}
// isEmptyCol 同理,檢查同列
private boolean isEmptyCell(int r, int c) { return map[r][c] == 0; }

2. 直線連線(水平/垂直)

在 logic() 中優先判斷:

if (isEmptyRow(fr, fc, sr, sc)) return H_LINK;
if (isEmptyCol(fr, fc, sr, sc)) return V_LINK;

3. 一轉彎連線:枚舉轉折點

轉折點必須是 (fr, sc) 或 (sr, fc) 且為空,且兩段皆為直線通路:

private int isOneCornerLink(int r1, int c1, int r2, int c2) {
    // 情況 A:先橫後豎,轉折點 (r1, c2)
    if (isEmptyCell(r1, c2) && isEmptyRow(r1, c1, r1, c2) && isEmptyCol(r1, c2, r2, c2))
        return ONE_CORNER_FIRSTX; // 先 X(橫)
    // 情況 B:先豎後橫,轉折點 (r2, c1)
    if (isEmptyCell(r2, c1) && isEmptyCol(r1, c1, r2, c1) && isEmptyRow(r2, c1, r2, c2))
        return ONE_CORNER_FIRSTY; // 先 Y(豎)
    return NO_LINK;
}

4. 兩轉彎連線:從首格向四方向延伸尋找「可一轉到達終點」的中繼點

private boolean isTwoCornerLink(int r1, int c1, int r2, int c2) {
    // 1. 向上掃描
    for (int r=r1-1; r>=0; r--) {
        if (map[r][c1]!=0) break; // 撞牆停止
        p[0]=r; p[1]=c1; // 記錄第一轉折點
        if (isOneCornerLink(r, c1, r2, c2) != NO_LINK) { // 能一轉到達終點?
            // 記錄第二轉折點
            switch(result) { case ONE_CORNER_FIRSTX: p[2]=r; p[3]=c2; break; ... }
            return true;
        }
    }
    // 2. 向下、向左、向右 同理...
    // 3. 特例:繞過邊界(視棋盤外圍為空)
    if (isEmptyCol(r1,c1,-1,c1) && isEmptyCol(r2,c2,-1,c2)) { p[0]=-1; p[1]=c1; p[2]=-1; p[3]=c2; return true; }
    // 左/下/右 邊界同理...
    return false;
}

演算法複雜度: O(Rows + Cols) 級,10×10 棋盤極快,無效能壓力。

5. 邏輯總入口 logic() 與 action()

private int logic() {
    if (map[fr][fc] != map[sr][sc]) return NO_LINK; // 圖案不同
    if (isEmptyRow(...)) return H_LINK;
    if (isEmptyCol(...)) return V_LINK;
    int r = isOneCornerLink(...); if (r != NO_LINK) return r;
    if (isTwoCornerLink(...)) return TWO_CORNER;
    return NO_LINK;
}

public boolean action() {
    if (selectTileNum != 2) return false;
    linkType = logic();
    if (linkType != NO_LINK) {
        map[fr][fc] = map[sr][sc] = 0; // 消除
        selectTileNum = 0; return true;
    }
    selectTileNum = 0; return false;
}

按鍵處理與游標移動

public void keyPressed(int keyCode) {
    int action = getGameAction(keyCode); // MIDP 內建映射
    switch(action) {
        case UP:    engine.moveUP(); break;
        case DOWN:  engine.moveDown(); break;
        case LEFT:  engine.moveLeft(); break;
        case RIGHT: engine.moveRight(); break;
        case FIRE:  engine.fire(); break; // 選取/確認
    }
}

游標移動邊界檢查在 GameEngine.moveXxx() 內完成,cRow/cCol 限制在 [0, MAX-1]。

擴展建議:從教學版到商業版

模組教學版現狀商業級增強
地圖生成隨機交換 10 次演算法生成保證可解 + 難度分級 + 關卡編輯器匯入
圖形資源drawString 數字佔位載入 PNG 圖塊,支援動畫方塊、特效粒子
連線動畫單幀閃現漸顯/漸隱、粒子流、聲音同步、連擊加分
狀態管理單一 Canvas狀態機:選單/遊戲/暫停/設定/成就/排行榜
存檔無RMS 存檔:進度、最高分、音樂開關、自訂鍵位
效能全量重繪髒矩形更新、雙緩衝(GameCanvas)、物件池

總結

本實現以 約 300 行核心代碼 完整呈現連連看核心邏輯,展示了在 MIDP 1.0 受限環境下:

  1. 資料驅動渲染:map[][] 單一真實來源,繪製邏輯純函數化。
  2. 演算法分層:基礎檢測 → 直線 → 一轉 → 兩轉,優先級遞減,邊界特例統一處理。
  3. 狀態最小化:僅用 selectTileNum、first/second、cRow/cCol、linkType 驅動整個遊戲流程。

此代碼可直接作為 J2ME 遊戲開發教學範例,或作為現代平台(Android/LibGDX/Unity)移植時的演算法參考實現。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved