J2ME连连看基础功能源代码(含详细注释)
作者:陈跃峰

//界面类代码 

import javax.microedition.lcdui.Canvas; 

import javax.microedition.lcdui.Graphics; 


/** 

 * 连连看游戏界面 

 */ 

public class LinkCanvas extends Canvas implements Runnable{ 

    /**游戏逻辑类*/ 

    GameEngine engine;    

    /**屏幕宽度*/ 

    int width; 

    /**屏幕高度*/ 

    int height; 

    public LinkCanvas(){ 

        //创建对象 

        engine = new GameEngine(); 

        //获得屏幕的高度和宽度 

        width = getWidth(); 

        height = getHeight(); 

        //启动线程 

        Thread t = new Thread(this); 

        t.start(); 

    } 

    

    /** 

     * 绘制方法 

     */ 

    protected void paint(Graphics g) { 

        //清屏 

        clearScreen(g); 

        //绘制地图 

        engine.paintMap(g); 

        //绘制选择框 

        engine.paintSelectArea(g); 

        //绘制连线 

        engine.paintLinkLine(g); 

    } 

    

    /** 

     * 清屏方法 

     * @param g 画笔 

     */ 

    private void clearScreen(Graphics g){ 

        g.setColor(0xffffff); 

        g.fillRect(0, 0, width, height); 

        g.setColor(0); 

    } 

    

    public void keyPressed(int keyCode){ 

        int action = getGameAction(keyCode); 

        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; 

        } 

    } 


    public void run() { 

        try{ 

            while(true){ 

                //延时 

                Thread.sleep(100); 

                //每次判断逻辑 

                engine.action(); 

                repaint(); 

            } 

        }catch(Exception e){ 

            e.printStackTrace(); 

        } 

    } 

} 


//逻辑类源代码 

import java.util.*; 

import javax.microedition.lcdui.*; 

/** 

 * 游戏数据和逻辑类 

 */ 

public class GameEngine { 

    /**选中块的个数*/ 

    private int selectTileNum = 0; 

    //第一个选择块的行号和列号 

    /**行号*/ 

    private int firstRow; 

    /**列号*/ 

    private int firstCol; 

    //第二个选择块的行号和列号 

    /**行号*/ 

    private int secondRow; 

    /**列号*/ 

    private int secondCol; 

    

    //当前选择框,默认在左上角 

    /**当前选择框的行号*/ 

    private int cRow; 

    /**当前选择框的列号*/ 

    private int cCol; 

    

    /**最大行数*/ 

    private final int MAX_ROW = 10; 

    /**最大列数*/ 

    private final int MAX_COL = 10; 

    

    /**地图数据,0代表空,数据1-10分别代表十种不同的结构*/ 

    private int[][] map = new int[MAX_ROW][MAX_COL]; 

    /**随机数对象*/ 

    private Random ran = new Random(); 

    

    //地图区域左上角的坐标 

    private final int LEFTX = 20; 

    private final int LEFTY = 50; 

    

    /**每个单元格的宽度*/ 

    private final int TILE_WIDTH = 20; 

    /**每个单元格的高度*/ 

    private final int TILE_HEIGHT = 20; 

    

    /**连线类型*/ 

    private int linkType; 

    

    /**无法连线*/ 

    private final int NO_LINK = 0; 

    /**水平连线*/ 

    private final int H_LINK = 1; 

    /**垂直联系*/ 

    private final int V_LINK = 2; 

    /**一个拐点,先移动x*/ 

    private final int ONE_CORNER_FIRSTX = 3; 

    /**一个拐点,先移动y*/ 

    private final int ONE_CORNER_FIRSTY = 4; 

    /**两个拐点,待完善*/ 

    private final int TWO_CORNER = 5; 

    

    /** 

     * 两次拐弯的行号和列号 

     * 数据格式为: 

     *   第一个拐点的行号,第一个拐点的列号,第二个拐点的行号,第二个拐点的列号 

     */ 

    int[] p = new int[4]; 

    

    public GameEngine(){ 

        //初始化地图数据 

        initMap(); 

    } 

    

    /** 

     * 初始化地图数据 

     */ 

    private void initMap(){ 

        for(int row = 0;row < map.length;row++){ 

            for(int col = 0;col < map[row].length;col++){ 

                map[row][col] = row + 1; 

            } 

        } 

        //循环打乱10次 

        int tempRow; 

        int tempCol; 

        int temp; 

        for(int i = 0;i < 10;i++){ 

            for(int row = 0;row < map.length;row++){ 

                for(int col = 0;col < map[row].length;col++){ 

                    //随机行号 

                    tempRow = Math.abs(ran.nextInt() % 10); 

                    //随机列号 

                    tempCol = Math.abs(ran.nextInt() % 10); 

                    //如果不是同一个单元格,则交换数据 

                    if(!((tempRow == row) && (tempCol == col))){ 

                        temp = map[row][col]; 

                        map[row][col] = map[tempRow][tempCol]; 

                        map[tempRow][tempCol] = temp; 

                    } 

                } 

            } 

        } 

    } 

    

    /** 

     * 绘制地图数据 

     * @param g 画笔 

     */ 

    public void paintMap(Graphics g){ 

        for(int row = 0;row < map.length;row++){ 

            for(int col = 0;col < map[row].length;col++){ 

                //如果没有数据,则跳过 

                if(map[row][col] == 0){ 

                    continue; 

                }else{//绘制方块 

                    //绘制方框 

                    g.drawRect(LEFTX + col * TILE_WIDTH, LEFTY + row * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT); 

                    //绘制数字 

                    g.drawString(String.valueOf(map[row][col]), LEFTX + col * TILE_WIDTH + 5, 

                            LEFTY + row * TILE_HEIGHT + 4, 

                            Graphics.TOP | Graphics.LEFT); 

                } 

            } 

        } 

    } 

    

    /** 

     * 绘制选择框 

     * @param g 画笔 

     */ 

    public void paintSelectArea(Graphics g){ 

        //绘制当前选择框 

        g.setColor(0xff00); 

        g.drawRect(LEFTX + cCol * TILE_WIDTH, LEFTY + cRow * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT); 

        g.setColor(0); 

        

        //绘制选中项 

        switch(selectTileNum){ 

        case 1:  //选择一个 

            g.setColor(0xff0000); 

            g.drawRect(LEFTX + firstCol * TILE_WIDTH, LEFTY + firstRow * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT); 

            g.setColor(0); 

            break; 

        case 2:  //选中两个 

            g.setColor(0xff0000); 

            g.drawRect(LEFTX + firstCol * TILE_WIDTH, LEFTY + firstRow * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT); 

            g.drawRect(LEFTX + secondCol * TILE_WIDTH, LEFTY + secondRow * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT); 

            g.setColor(0); 

            break; 

        } 

    } 

    

    /** 

     * 绘制方块连线 

     * @param g 画笔 

     */ 

    public void paintLinkLine(Graphics g){ 

        //如果无连线,则直接返回 

        if(linkType == NO_LINK){ 

            return; 

        } 

        //根据连线类型实现绘制 

        //绘制到方块的中心点 

        switch(linkType){ 

        case H_LINK://水平 

        case V_LINK://垂直 

            paintLine(g,firstRow,firstCol,secondRow,secondCol); 

            break; 

        case ONE_CORNER_FIRSTX://一个拐弯,先移动X 

            //水平线 

            paintLine(g,firstRow,firstCol,firstRow,secondCol); 

            //垂直线 

            paintLine(g,firstRow,secondCol,secondRow,secondCol); 

            break; 

        case ONE_CORNER_FIRSTY://一个拐弯,先移动Y 

            //水平线 

            paintLine(g,firstRow,firstCol,secondRow,firstCol); 

            //垂直线 

            paintLine(g,secondRow,firstCol,secondRow,secondCol); 

            break; 

        case TWO_CORNER: 

            //块1到第一个拐点的连线 

            paintLine(g,firstRow,firstCol,p[0],p[1]); 

            //两个拐点之间的连线 

            paintLine(g,p[0],p[1],p[2],p[3]); 

            //第二个拐点到块2的连线 

            paintLine(g,p[2],p[3],secondRow,secondCol); 

            break; 

        } 

        //逻辑代码,清除连接类型 

        linkType = NO_LINK; 

    } 

    

    /** 

     * 绘制两个方块中心点的连线 

     * @param g  画笔 

     * @param r1 方块1的行号 

     * @param c1 方块1的列号 

     * @param r2 方块2的行号 

     * @param c2 方块2的列号 

     */ 

    private void paintLine(Graphics g,int r1,int c1,int r2,int c2){ 

        g.drawLine(LEFTX + c1 * TILE_WIDTH + TILE_WIDTH/2, 

                LEFTY + r1 * TILE_HEIGHT + TILE_HEIGHT/2, 

                LEFTX + c2 * TILE_WIDTH + TILE_WIDTH/2, 

                LEFTY + r2 * TILE_HEIGHT + TILE_HEIGHT/2); 

        

    } 

    

    /** 

     * 向左移动选择框 

     */ 

    public void moveLeft(){ 

        if(cCol > 0){ 

            cCol--; 

        } 

    } 

    

    /** 

     * 向右移动选择框 

     */ 

    public void moveRight(){ 

        if(cCol < MAX_COL -1){ 

            cCol++; 

        } 

    } 

    

    /** 

     * 向上移动选择框 

     */ 

    public void moveUP(){ 

        if(cRow > 0){ 

            cRow--; 

        } 

    } 

    

    /** 

     * 向下移动选择框 

     */ 

    public void moveDown(){ 

        if(cRow < MAX_ROW - 1){ 

            cRow++; 

        } 

    } 

    

    /** 

     * 确定键逻辑处理 

     */ 

    public void fire(){ 

        //如果选择的块为空,则直接返回 

        if(map[cRow][cCol] == 0){ 

            return; 

        } 

        //选中的块的数量增加1 

        selectTileNum++; 

        //判别存储位置 

        switch(selectTileNum){ 

        case 1: //第一次选择 

            firstRow = cRow; 

            firstCol = cCol; 

            break; 

        case 2: //第二次选择 

            //选择同一个块,2个选择块都失去选中 

            if((firstRow == cRow) && (firstCol == cCol)){ 

                selectTileNum = 0; 

                return; 

            } 

            secondRow = cRow; 

            secondCol = cCol; 

            break; 

        } 

    } 

    

    /** 

     * 判断(r1,c1)块和(r2,c2)块中间是否为空行 

     * 不包含这两个块 

     * @param r1 块1的行号 

     * @param c1 块1的列号 

     * @param r2 块2的行号 

     * @param c2 块2的列号 

     * @return true代表为空,false代表不为空 

     */      

    private boolean isEmptyRow(int r1,int c1,int r2,int c2){ 

        //判断是否位于同一行 

        if(r1 != r2){ 

            return false; 

        } 

        //判断两个块的相对位置 

        if(c1 > c2){ //第一块位于右侧 

            for(int col = c1 - 1;col > c2;col--){ 

                //如果有非空块 

                if(map[r1][col] != 0){ 

                    return false; 

                } 

            } 

        }else{ //第一块位于左侧 

            for(int col = c2 - 1;col > c1;col--){ 

                //如果有非空块 

                if(map[r1][col] != 0){ 

                    return false; 

                } 

            } 

        } 

        return true; 

    } 

    

    /** 

     * 判断块(r1,c1)和块(r2,c2)之间是否是空列 

     * 不包含这两个块 

     * @param r1 块1的行号 

     * @param c1 块1的列号 

     * @param r2 块2的行号 

     * @param c2 块2的列号 

     * @return true代表为空,false代表不为空 

     */ 

    private boolean isEmptyCol(int r1,int c1,int r2,int c2){ 

        //判断是否位于同一列 

        if(c1 != c2){ 

            return false; 

        } 

        //判断两个块的相对位置 

        if(r2 > r1){//第一个块在上方 

            for(int row = r1 + 1;row < r2;row++){ 

                //如果有非空块 

                if(map[row][c1] != 0){ 

                    return false; 

                } 

            } 

        }else{//第二个块在上方 

            for(int row = r2 + 1;row < r1;row++){ 

                //如果有非空块 

                if(map[row][c1] != 0){ 

                    return false; 

                } 

            } 

        } 

        return true; 

    } 

    

    /** 

     * 判断一个块是否为空 

     * @param r 块的行号 

     * @param c 块的列号 

     * @return true代表为空,false代表不空 

     */ 

    private boolean isEmptyCell(int r,int c){ 

        return map[r][c] == 0; 

    }    

        

    /** 

     * 是否是一次转弯实现连线 

     * @return NO_LINK代表没有连线,其他值代表对应的连线类型 

     */ 

    private int isOneCornerLink(int r1,int c1,int r2,int c2){ 

        //先移动行,再移动列 

        if(isEmptyCell(r1,c2)){ //转折点为空 

            if(isEmptyRow(r1,c1,r1,c2) & isEmptyCol(r1,c2,r2,c2)){ 

                return ONE_CORNER_FIRSTX; 

            } 

        } 

        //先移动列,再移动行 

        if(isEmptyCell(r2,c1)){//转折点为空 

            if(isEmptyCol(r1,c1,r2,c1) & isEmptyRow(r2,c1,r2,c2)) { 

                return ONE_CORNER_FIRSTY; 

            } 

        } 

        //无连接 

        return NO_LINK; 

    } 

    

    /** 

     * 是否经过2次转折实现连接 

     * @param r1 块1的行号 

     * @param c1 块1的列号 

     * @param r2 块2的行号 

     * @param c2 块2的列号 

     * @return true代表可以连接,false代表不能 

     */ 

    private boolean isTwoCornerLink(int r1,int c1,int r2,int c2){ 

        int result; 

        //正常情况,划分成4个方向 

        //块1向上 

        for(int row = r1 -1;row >= 0;row--){ 

            //如果有数据不为空,则直接结束该方向的尝试 

            if(map[row][c1] != 0){ 

                break; 

            } 

            //存储第一个拐点的坐标 

            p[0] = row; 

            p[1] = c1; 

            //每次都尝试转折,则变成一个转点的操作 

            result = isOneCornerLink(row,c1,r2,c2); 

            //如果可以连接 

            if(result != NO_LINK){ 

                //存储第二个拐点的位置 

                switch(result){ 

                case ONE_CORNER_FIRSTX: 

                    p[2] = row; 

                    p[3] = c2; 

                    break; 

                case ONE_CORNER_FIRSTY: 

                    p[2] = r2; 

                    p[3] = c1; 

                    break; 

                } 

                return true; 

            } 

        } 

        //块1向下 

        for(int row = r1 + 1;row < MAX_ROW;row++){ 

            //如果有数据不为空,则直接结束该方向的尝试 

            if(map[row][c1] != 0){ 

                break; 

            } 

            //存储第一个拐点的坐标 

            p[0] = row; 

            p[1] = c1; 

            //每次都尝试转折,则变成一个转点的操作 

            result = isOneCornerLink(row,c1,r2,c2); 

            //如果可以连接 

            if(result != NO_LINK){ 

                //存储第二个拐点的位置 

                switch(result){ 

                case ONE_CORNER_FIRSTX: 

                    p[2] = row; 

                    p[3] = c2; 

                    break; 

                case ONE_CORNER_FIRSTY: 

                    p[2] = r2; 

                    p[3] = c1; 

                    break; 

                } 

                return true; 

            } 

        } 

        //块1向左 

        for(int col = c1 -1;col >= 0;col--){ 

            //如果有数据不为空,则直接结束该方向的尝试 

            if(map[r1][col] != 0){ 

                break; 

            } 

            //存储第一个拐点的坐标 

            p[0] = r1; 

            p[1] = col; 

            //每次都尝试转折,则变成一个转点的操作 

            result = isOneCornerLink(r1,col,r2,c2); 

            //如果可以连接 

            if(result != NO_LINK){ 

                //存储第二个拐点的位置 

                switch(result){ 

                case ONE_CORNER_FIRSTX: 

                    p[2] = r1; 

                    p[3] = c2; 

                    break; 

                case ONE_CORNER_FIRSTY: 

                    p[2] = r2; 

                    p[3] = col; 

                    break; 

                } 

                return true; 

            } 

        } 

        //块1向右 

        for(int col = c1  + 1;col < MAX_COL;col++){ 

            //如果有数据不为空,则直接结束该方向的尝试 

            if(map[r1][col] != 0){ 

                break; 

            } 

            //存储第一个拐点的坐标 

            p[0] = r1; 

            p[1] = col; 

            //每次都尝试转折,则变成一个转点的操作 

            result = isOneCornerLink(r1,col,r2,c2); 

            //如果可以连接 

            if(result != NO_LINK){ 

                //存储第二个拐点的位置 

                switch(result){ 

                case ONE_CORNER_FIRSTX: 

                    p[2] = r1; 

                    p[3] = c2; 

                    break; 

                case ONE_CORNER_FIRSTY: 

                    p[2] = r2; 

                    p[3] = col; 

                    break; 

                } 

                return true; 

            } 

        } 

        

        //四个特例,也就是超出地图区域的连接 

        //实现地图区域上侧的连接,也就是到上侧是一个空列 

        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; 

        } 

        //左侧 

        if((isEmptyRow(r1,c1,r1,-1)) & (isEmptyRow(r2,c2,r2,-1))){ 

            p[0] = r1; 

            p[1] = -1; 

            p[2] = r2; 

            p[3] = -1; 

            return true; 

        } 

        //下侧 

        if((isEmptyCol(r1,c1,MAX_ROW,c1)) & (isEmptyCol(r2,c2,MAX_ROW,c2))){ 

            p[0] = MAX_ROW; 

            p[1] = c1; 

            p[2] = MAX_ROW; 

            p[3] = c2; 

            return true; 

        } 

        //右侧 

        if((isEmptyRow(r1,c1,r1,MAX_COL)) & (isEmptyRow(r2,c2,r2,MAX_COL))){ 

            p[0] = r1; 

            p[1] = MAX_COL; 

            p[2] = r2; 

            p[3] = MAX_COL; 

            return true; 

        }        

        return false; 

    } 

    

    /** 

     * 逻辑判断是否有连线 

     * @return NO_LINK代表无连线,其它数据代表有连线 

     */ 

    private int logic(){ 

        //如果数值不同 

        if(map[firstRow][firstCol] != map[secondRow][secondCol]){ 

            return NO_LINK; 

        } 

        //判断连接方式 

        if(isEmptyRow(firstRow,firstCol,secondRow,secondCol)){ //水平连线 

            return H_LINK; 

        } 

        if(isEmptyCol(firstRow,firstCol,secondRow,secondCol)){//垂直连线 

            return V_LINK; 

        } 

        //一个转点的连接 

        int result = isOneCornerLink(firstRow,firstCol,secondRow,secondCol); 

        if(result != NO_LINK){ 

            return result; 

        } 

        //两个转点的连接 

        if(isTwoCornerLink(firstRow,firstCol,secondRow,secondCol)){ 

            return TWO_CORNER; 

        }        

        //返回无连接 

        return NO_LINK; 

    } 

    

    /** 

     * 逻辑判别和逻辑处理 

     */ 

    public boolean action(){ 

        //判断是否选择两个方块 

        if(selectTileNum != 2){ 

            return false; 

        } 

        boolean b = false; 

        //判断是否有连线 

        linkType = logic(); 

        //如果有连线,则消失 

        if(linkType != NO_LINK){ 

            map[firstRow][firstCol] = 0; 

            map[secondRow][secondCol] = 0; 

            b = true; 

        } 

        //选择的块数初始化 

        selectTileNum = 0; 

        return b; 

    } 

}