程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> J2ME >> 雙人掃雷1.0 游戲完整實現(二)

雙人掃雷1.0 游戲完整實現(二)

編輯:J2ME

作者:yinowl
2005年2月

幫助界面
幫助界面很簡單,把需要的文字排好寬度放在一個String數組裡,然後繪制在屏幕上,如果一屏放不下就增加按鍵響應來翻屏,其實只是重新畫出數組前面或後面的幾個值,源代碼如下:

在MiningCanvas.Java中添加如下代碼
final String[] strGamehelp =new String[10];
public MiningCanvas(MiningMIDlet miningMIDlet){
    ...
    strGamehelp[0]="此游戲為雙人對戰游戲";
    strGamehelp[1]=",和經典的掃雷不同,這";
    strGamehelp[2]="個游戲中,我們要挖出";
    strGamehelp[3]="雷,挖錯雷則交換玩家.";
    strGamehelp[4]="一共有52顆雷,256個格";
    strGamehelp[5]="子,最先挖到27顆雷者";
    strGamehelp[6]="獲勝.1.上,下,左,右分";
    strGamehelp[7]="別為數字鍵2,8,4,6,挖";
    strGamehelp[8]="雷為5;2.屏幕外框的顏";
    strGamehelp[9]="色為當前下玩家;";
}
private void paintHelpScreen(Graphics g){
    g.setColor(0x00FFFFFF);
    g.fillRect(0,0,canvasW,canvasH);
    g.setFont(lowFont);
    g.setColor(0x00000000);
    for(int i=0;i<strGamehelp.length;i++){
        g.drawString(strGamehelp[i],5,5+(lowFont.getHeight()+3)*i,Graphics.TOP|Graphics.LEFT);
    }
}
在keyPressed方法中的switch結構中添加
case GAMESTATE_HELP://如果游戲現在的狀態為幫助狀態,那個不管玩家按哪個鍵都會跳轉到主菜單狀態
    gamestate=GAMESTATE_MENU;
    break;

游戲主界面
主界面的繪制分成幾個部分,一個是雷區棋盤的繪制,根據雷區二位數組中的每一個Bomb對象中的變量值繪出整個棋盤,如果hasFound值為false,畫出;如果hasFound值為true且isBomb值為false,那麼會畫出這個雷位周圍的雷數;如果hasFound值為true且isBomb值為true,那麼會畫出這個雷位是哪一個玩家所挖出的,分別是。第二個部分是玩家的選擇框。第三個部分是游戲的信息框,也就是兩個玩家目前的分值,還剩幾顆雷,信息框的圖片為。最後一個部分是提示當前輪到哪一個玩家掃雷,在雷區的外框用玩家的顏色提示,和在信息框中畫出玩家的旗幟
這裡有幾個注意點:
1.雷區不能全部在屏幕中畫下,所以會通過卷軸滾動。整個雷區是16x16,我們的屏幕預備畫出10x12,用兩個變量(paintX和paintY)來控制當前要畫出的雷區左上角的雷位坐標,例如這兩個變量的值分別為3和4,那麼會畫出橫軸3-10,縱軸4-13的雷位,選擇框的坐標(selectedX和selectedY)不用擔心,當他超出了屏幕,只需要增加或減少paintX和paintY的值即可,只要保證選擇框的坐標和paintX/Y坐標值得距離不超過9(10-1)和11(12-1)即可。這裡要十分小心的是,我們這裡所有的坐標值和雷區的二位數組bombs中的坐標正好相反,也就是選擇框(5,6)是類位bombs[6][5]。
2.在創建雷區二維Bomb數組時,二位數組的大小是18x18(new Bomb[miningMapGrid+2][miningMapGrid+2]),目的是在大小16x16的真正雷區外加一圈雷位,以避免bombOut()方法和其他需要周邊搜索雷位時不會出現超越邊界異常。
3.因為bombs數組中的索引1-16才是真正代表雷區中坐標(1,1)至(16,16)的雷位,所以在整個程序中,所有有關坐標和數組索引值的地方都要十分小心,不要出現位置錯誤
4.消息框中需要繪制的幾個數值的坐標變量(Player1X/Y,Player2X/Y,bombnowX/Y)的值都是相對於整個消息框的左上角的相對坐標,我們也可以為消息框的坐標單獨定義一對變量,這樣就更清晰了。
源代碼如下:

在MiningCanvas.Java中添加如下代碼
int empty;
static final int miningMapGrid=16;//雷區為16格x16格
static final int bombNum=52;//共有52顆雷
int miningMapLength,miningGridLength;//整個雷區的邊長,每一個雷位格子的邊長
int miningMapX,miningMapY;//雷區的左上角坐標
int selectedX,selectedY;//選擇框的坐標,是在雷區裡的坐標(1-16)
int player1Found,player2Found;//兩個玩家的分值,即已經找到的雷數
int paintX,paintY;//要畫出的區域的左上角坐標,是在雷區裡的坐標(1-16)
int bombLeft;//剩下未掃出的雷數
static final int Player1X=10,Player1Y=30;//信息框中畫出玩家一分數的坐標位置,相對於信息框左上角
static final int Player2X=10,Player2Y=90;//信息框中畫出玩家二分數的坐標位置,相對於信息框左上角
static final int bombnowX=8,bombnowY=52;//信息框中畫出剩余雷數的坐標位置,相對於信息框左上角
static final int bombMapW=10,bombMapH=12;//屏幕上能繪出的雷位的數量
Bomb[][] bombs;
boolean isPlayer1;//當前是否輪到玩家一掃雷
Alert winAlert;//顯示輸贏信息的Alert對象
String winString;//比出輸贏後要輸出的信息
boolean sbWon;//是否得出輸贏
static final Font font  = Font.getFont(Font.FACE_MONOSPACE,Font.STYLE_PLAIN,Font.SIZE_SMALL);
    //消息框中使用的字體
Image infoImg,splashImage;
Image unFoundGroundImg;
Image foundGroundImg;
Image player1BombImg,player2BombImg,player1TurnImg,player2TurnImg;
Image bomb1Img,bomb2Img,bomb3Img,bomb4Img,bomb5Img,bomb6Img,bomb7Img,bomb8Img; 
    //所有要用到的圖片
public MiningCanvas(MiningMIDlet miningMIDlet){
    ...
    try{//實例化游戲中需要的Image對象
        unFoundGroundImg=Image.createImage("/unfoundGroundbig.png");
        foundGroundImg=Image.createImage("/foundGroundbig.png");
        player1BombImg=Image.createImage("/player1bombbig.png");
        player2BombImg=Image.createImage("/player2bombbig.png");
        bomb1Img=Image.createImage("/bomb1big.png");
        bomb2Img=Image.createImage("/bomb2big.png");
        bomb3Img=Image.createImage("/bomb3big.png");
        bomb4Img=Image.createImage("/bomb4big.png");
        bomb5Img=Image.createImage("/bomb5big.png");
        bomb6Img=Image.createImage("/bomb6big.png");
        infoImg=Image.createImage("/info.png");
        splashImage=Image.createImage("/occo.png");
        player1TurnImg=Image.createImage("/player1turn.png");
        player2TurnImg=Image.createImage("/player2turn.png");
    }catch(IOException e){}
    isPlayer1=true;//初始化玩家先後順序
    miningGridLength=14;//初始化每一個雷位的邊長
    miningMapLength=14*12;//整個掃雷棋盤的邊長
    miningMapX=(canvasW-miningMapLength)/2;//屏幕上棋盤左上角的X坐標
    miningMapY=(canvasH-miningMapLength)/2;//屏幕上棋盤左上角的Y坐標
    selectedX=selectedY=miningMapGrid/2;//初始化選擇框的坐標為
    player1Found=player2Found=0;//初始化兩個玩家的得分
    paintX=paintY=3;//初始化整個雷區一開始在屏幕上顯示的范圍
    sbWon=false;//初始化沒有玩家獲勝
    bombLeft=bombNum;//初始化剩余雷數為總雷數
    bombs=new Bomb[miningMapGrid+2][miningMapGrid+2];
    bombInit();//初始化雷區 
}
private void paintGameScreen(Graphics g){
    paintPlayer(g,isPlayer1);
    paintMiningMap(g);
    paintInfo(g);
    paintSelected(g);
}
protected void paintInfo(Graphics g){
    g.drawImage(infoImg,miningMapX+bombMapW*miningGridLength+1,
                miningMapY,Graphics.TOP|Graphics.LEFT);
    g.setFont(font);
    g.setColor(0x00FFFFFF);
    g.drawString(String.valueOf(bombLeft),miningMapX+bombMapW*miningGridLength+bombnowX,
                miningMapY+bombnowY,Graphics.TOP|Graphics.LEFT);
    g.drawString(String.valueOf(player1Found),miningMapX+bombMapW*miningGridLength+Player1X,
                miningMapY+Player1Y,Graphics.TOP|Graphics.LEFT);
    g.drawString(String.valueOf(player2Found),miningMapX+bombMapW*miningGridLength+Player2X,
                miningMapY+Player2Y,Graphics.TOP|Graphics.LEFT);
    //這個方法中接下來的代碼是用來在信息框中畫出小地圖,也就是屏幕上顯示的地雷區域在整個雷區
      中的位置
    g.setColor(0x00777777);
    g.fillRect(miningMapX+bombMapW*miningGridLength+1,miningMapY+8*miningGridLength+1,
                2*miningGridLength-2,2*miningGridLength-1);
    g.setColor(0x00000000);
    g.drawRect(miningMapX+bombMapW*miningGridLength+1,miningMapY+8*miningGridLength+1,
                2*miningGridLength-2,2*miningGridLength-1);
    g.setColor(0x00BBBBBB);
    g.fillRect(miningMapX+bombMapW*miningGridLength+4+2*(paintX-1),
                miningMapY+8*miningGridLength+4+2*(paintY-1),12,17);
    g.setColor(0x00FFFFFF);
    g.drawRect(miningMapX+bombMapW*miningGridLength+4+2*(paintX-1),
                miningMapY+8*miningGridLength+4+2*(paintY-1),12,17);
}
protected void paintPlayer(Graphics g,boolean isPlayer1){
    if(isPlayer1)//在棋盤外圍畫出玩家顏色的外框
        g.setColor(0x000000FF);
    else
        g.setColor(0x00FF0000);
    for(int i=1;i<=5;i++){
        g.drawRect(miningMapX-i,miningMapY-i,miningMapLength+2*i,miningMapLength+2*i);
    }
    if(isPlayer1)//在信息框中畫出代表玩家的旗幟
        g.drawImage(player1TurnImg,miningMapX+(bombMapW+1)*miningGridLength+1,
                miningMapY+11*miningGridLength,Graphics.HCENTER|Graphics.VCENTER);
    else
        g.drawImage(player2TurnImg,miningMapX+(bombMapW+1)*miningGridLength+1,
                miningMapY+11*miningGridLength,Graphics.HCENTER|Graphics.VCENTER);
}
public void paintMiningMap(Graphics g){
    for(int i=0;i<bombMapH;i++){
        for(int j=0;j<bombMapW;j++){//根據每個Bomb對象中的變量值畫出不同的圖片
            if(!bombs[i+paintY+1][j+paintX+1].hasFound){
                g.drawImage(unFoundGroundImg,miningMapX+j*miningGridLength,
                        miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
            }
            else {
                if(!bombs[i+paintY+1][j+paintX+1].isBomb){
                    switch(bombs[i+paintY+1][j+paintX+1].bombaround){
                        case 0:
                            g.drawImage(foundGroundImg,miningMapX+j*miningGridLength,
                                miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
                            break;
                        case 1:
                            g.drawImage(bomb1Img,miningMapX+j*miningGridLength,
                                miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
                            break;
                        case 2:
                            g.drawImage(bomb2Img,miningMapX+j*miningGridLength,
                                miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
                            break;
                        case 3:
                            g.drawImage(bomb3Img,miningMapX+j*miningGridLength,
                                miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
                            break;
                        case 4:
                            g.drawImage(bomb4Img,miningMapX+j*miningGridLength,
                                miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
                            break;
                        case 5:
                            g.drawImage(bomb5Img,miningMapX+j*miningGridLength,
                                miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
                            break;
                        case 6:
                            g.drawImage(bomb6Img,miningMapX+j*miningGridLength,
                                miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
                            break;
                        default:
                            g.drawImage(foundGroundImg,miningMapX+j*miningGridLength,
                                miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
                        break;
                    }
                }
                else {
                    if(bombs[i+paintY+1][j+paintX+1].isPlayer1){
                        g.drawImage(player1BombImg,miningMapX+j*miningGridLength,
                            miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
                    }
                    else{
                        g.drawImage(player2BombImg,miningMapX+j*miningGridLength,
                            miningMapY+i*miningGridLength,Graphics.TOP|Graphics.LEFT);
                    }
                }
            }
        }
    }
}
public void paintSelected(Graphics g){
    g.setColor(0x00FF0000);//畫出選擇框,注意其與總雷區和能在屏幕上顯示出的區域間的關系
    g.drawRoundRect(miningMapX+(selectedX-paintX)*miningGridLength-1,
                miningMapY+(selectedY-paintY)*miningGridLength-1,
                miningGridLength+1,miningGridLength+1,2,2);
}
在keyPressed方法中的switch結構中添加
case GAMESTATE_GAMEING:
{
    if(keyCode==FullCanvas.KEY_SOFTKEY1){
        gamestate=GAMESTATE_GAMEMENU;
    }
    else if (action == FullCanvas.LEFT ) {
        selectedX=(--selectedX+miningMapGrid)%(miningMapGrid);
    }
    else if (action == FullCanvas.RIGHT) {
        selectedX=(++selectedX)%(miningMapGrid);
    }
    else if (action == FullCanvas.UP) {
        selectedY=(--selectedY+miningMapGrid)%(miningMapGrid);
    }
    else if (action == FullCanvas.DOWN) {
        selectedY=(++selectedY)%(miningMapGrid);
    }
    else if (action == FullCanvas.FIRE) {
        if(!bombs[selectedY+1][selectedX+1].hasFound){
            if(bombs[selectedY+1][selectedX+1].isBomb){
                bombs[selectedY+1][selectedX+1].hasFound=true;
                bombs[selectedY+1][selectedX+1].isPlayer1=this.isPlayer1;
                if(isPlayer1)
                    player1Found++;
                else
                    player2Found++;
                bombLeft--;
                checkWin();//每次有玩家挖到了雷就監測其是否勝出
            }
            else{
                bombOut(selectedY+1,selectedX+1);
                //如果此雷位及周圍都無雷,打開所有與其相連的相同情況的雷位及此區域周邊一圈雷位
                isPlayer1=!isPlayer1;
            }
        }
    }
    //以下幾行代碼是調整顯示在屏幕上的區域的坐標,以免選擇框跑出屏幕范圍
    if((selectedX-paintX)<0)
        paintX=selectedX;
    else if((selectedX-paintX)>=bombMapW)
        paintX=selectedX-bombMapW+1;
    if((selectedY-paintY)<0)
        paintY=selectedY;
    else if((selectedY-paintY)>=bombMapH)
        paintY=selectedY-bombMapH+1;
    break;
}

這裡會使用到幾個方法
checkWin()方法:我們把這個方法的調用放在玩家挖到雷的時候,每當有玩家挖到雷的時候,會改變player1Found和player2Found的值,這時候檢測游戲的輸贏,當有玩家的分數大於半數的總雷數的時候,勝負就得出了,這時候用Alert對象顯示出比分和勝負信息,同時要為下一局比賽作准備---游戲初始化(gameInit())。
gameInit()方法:這個方法用來初始化游戲,程序中在兩種情況需要調用初始化游戲,一個是重新開始游戲時,另一個是在一局結束後准備另一局的開始。每一次需要初始化游戲時,需要將所有的變量初始化
bombInit()方法:初始化表示整個雷區的Bomb型的二維表,病隨機的安排52顆雷的位置
bombOut()方法:經典掃雷中,如果我們點到一雷位,本身不是雷,周圍也沒有雷,電腦會自己打開周圍所有相連的類似的雷和這塊區域最外圈的一圈雷位,這個方法就是這個作用。我在這裡用了遞歸,當然我們只需要監測fasFound值為false的雷位,要不然就會都搜一遍

在MiningCanvas.Java中添加如下代碼
public void gameInit(){
    sbWon=false;
    bombInit();
    player1Found=0;player2Found=0;
    selectedX=miningMapGrid/4;selectedY=miningMapGrid/4;
    bombLeft=bombNum;
    System.gc();//手動進行垃圾回收
}
public void bombInit(){
    int bombindex=0;
    int x=0,y=0;
    Random random=new Random();
    for(int i=0;i<miningMapGrid+2;i++){
        for(int j=0;j<miningMapGrid+2;j++){
            bombs[i][j]=new Bomb();
        }
    }
    while(bombindex<bombNum){
        x=Math.abs(random.nextInt()%16+1);
        y=Math.abs(random.nextInt()%16+1);
        if(!bombs[x][y].isBomb && x<=16 && x>=1 && y<=16 && y>=1){
            try{
                bombs[x][y].isBomb=true;
                bombindex++;
                bombs[x-1][y].bombaround++;
                bombs[x-1][y-1].bombaround++;
                bombs[x][y-1].bombaround++;
                bombs[x+1][y-1].bombaround++;
                bombs[x+1][y].bombaround++;
                bombs[x+1][y+1].bombaround++;
                bombs[x][y+1].bombaround++;
                bombs[x-1][y+1].bombaround++;
            }catch(ArrayIndexOutOfBoundsException e){}
        }
    }
}
public void bombOut(int x,int y){
    if(bombs[x][y].hasFound==false && x>=1 && y>=1 && 
            x<=MiningCanvas.miningMapGrid && y<=MiningCanvas.miningMapGrid){
        bombs[x][y].hasFound=true;
        if(bombs[x][y].bombaround==0){
            this.bombOut(x-1,y);
            this.bombOut(x-1,y-1);
            this.bombOut(x,y-1);
            this.bombOut(x+1,y-1);
            this.bombOut(x+1,y);
            this.bombOut(x+1,y+1);
            this.bombOut(x,y+1);
            this.bombOut(x-1,y+1);
        }
    }
}
public void checkWin(){
    if(player1Found>26){
        winString="Player1 has won!";
        sbWon=true;
    }
    if(player2Found>26){
        winString="Player2 has won!";
        sbWon=true;
    }
    if(sbWon){
        winAlert=new Alert("",winString+"\nPlayer1 "+player1Found
                    +" : "+player2Found+" player2",null,AlertType.INFO);
        gameInit();
        winAlert.setTimeout(Alert.FOREVER);
        MiningMIDlet.display.setCurrent(winAlert);
    }
}

游戲時菜單
在游戲進行時,我們需要有幾個功能:重新開始游戲,打開幫助,返回主菜單,退出游戲,當然,進入了菜單就還要能返回正在進行的游戲。這個游戲時菜單和主菜單非常相似,我就不詳細解釋了,只是這裡有一個缺陷,游戲時菜單中選擇幫助後,在幫助界面中選擇返回,會回到主菜單,而不會回到這個游戲時菜單,當然,加一個變量就可以解決這個問題,原代碼如下:

在MiningCanvas.Java中添加如下代碼
static final int GAME_RETURN = 0;
static final int GAME_RESTART = 1;
static final int GAME_HELP = 2;
static final int GAME_MENU = 3;
static final int GAME_EXIT = 4;
static final int GAME_MENU_ITEM_COUNT = 5;
static String[] gameMenu = new String[GAME_MENU_ITEM_COUNT];
static int gamemenuIdx;
public MiningCanvas(MiningMIDlet miningMIDlet){
    ...
    gamemenuIdx=0;
    gameMenu[0] = "RETURN";
    gameMenu[1] = "RESTART";
    gameMenu[2] = "HELP";
    gameMenu[3] = "MENU";
    gameMenu[4] = "EXIT";
}
private void paintGameMenuScreen(Graphics g){
    for(int i=0;i<gameMenu.length;i++){
        if(i==gamemenuIdx){
            g.setColor(highBGColor);
            g.fillRect(0,startHeight+i*(lowFont.getHeight()+spacing)
                    -(highFont.getHeight()-lowFont.getHeight())/2,
                    canvasW,highFont.getHeight());
            g.setFont(highFont);
            g.setColor(highColor);
            g.drawString(gameMenu[i],(canvasW-highFont.stringWidth(gameMenu[i]))/2,
                    startHeight+i*(lowFont.getHeight()+spacing)-(highFont.getHeight()
                    -lowFont.getHeight())/2,Graphics.TOP|Graphics.LEFT);
        } else {
            g.setFont(lowFont);
            g.setColor(lowColor);
            g.drawString(gameMenu[i],(canvasW-lowFont.stringWidth(gameMenu[i]))/2,
                    startHeight+i*(lowFont.getHeight()+spacing),Graphics.TOP|Graphics.LEFT);
        } 
    }
}
在keyPressed方法中的switch結構中添加
case GAMESTATE_GAMEMENU:
{
    if (getGameAction(keyCode) == FullCanvas.UP && gamemenuIdx - 1 >= 0) {
        gamemenuIdx--;
    }
    else if (getGameAction(keyCode) == FullCanvas.DOWN && gamemenuIdx + 1 < gameMenu.length) {
        gamemenuIdx++;
    }
    else if (getGameAction(keyCode) == FullCanvas.FIRE) {
        switch(gamemenuIdx) {
            case GAME_RETURN:
                gamestate=GAMESTATE_GAMEING;
                break;
            case GAME_RESTART:
                gameInit();
                gamestate=GAMESTATE_GAMEING;
                break;
            case GAME_HELP:
                gamestate=GAMESTATE_HELP;
                break;
            case GAME_MENU:
                gamestate=GAMESTATE_MENU;
                break;
            case GAME_EXIT:
                miningMIDlet.destroyApp(false);
                break;
        }
    }
    break;
}

最終代碼補全
到這裡,按照游戲的功能部件,主要的代碼基本上都給出了,在稍微補充一些代碼就是整個游戲的完整程序了,需要補充如下:

修改keyPressed方法中的switch結構
switch(gamestate){
    
    ...
    
    default:
        gamestate=GAMESTATE_MENU;
}
repaint();

總結
整個游戲已經全部完成,大家一定會覺得很簡單吧,但已經是一個完整的游戲了,希望對一些朋友有所幫助。當然我們完全可以進行一些擴展,比如加上聲音,加上藍牙對戰功能,這樣游戲就慢慢的完善,並且具有商業價值。
文章慢慢寫到這裡也差不多該結束,突然間發現整篇文章完全偏離了我原先預計的方式和效果,我原先打算從游戲的設想,到設計,到編碼,到測試除錯整個過程寫成文章,最重要的是腦子裡的想法,包括剛開始錯誤的、不完善的和後來的修改,這樣才能把我做游戲真正的整個過程和經驗告訴大家。沒想到寫到最後還是沒有哪個效果,但願這只是寫文章的經驗問題,下次一定盡量改善,請大家多諒解。
文章寫完了不免會有一些錯誤,而且每次一寫完,總是迫不及待的想貼上來請大家指點,盡管每次都克制自己告訴自己在檢查一下看看有沒有錯誤,偶爾也會小偷懶一下,呵呵。有由於《J2ME-MIDP1.0游戲完整實現-雙人掃雷(一)》幾天前就貼上來了,看一個游戲的代碼總不能一半一半看吧,就趕著把這一半寫出來了,原本打算補充一個界面和變量的標示圖和一個游戲思考流程,就再寫一篇後記吧,順便把原代碼整理一下打包一起貼上來。
最後願大家學J順利,多多交流(MSN:[email protected] QQ:47599318 E-mail:[email protected])。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved