貪吃蛇的JAVA實現
阿新 • • 發佈:2018-12-02
最近JAVA和JSwing上手練習了一下貪吃蛇
先上程式碼 https://github.com/rgzhang2018/snake
歡迎交流和加入新的內容
用到了JSwing,下面是一些具體的思路
實現
* 蛇:
採用單鏈表記錄首尾,整個蛇被分為lattice格子,放在map裡
* 移動:
我在實現的過程中發現最難得反而是蛇的定義和實現。一直想著怎麼樣用單獨的方法表示出蛇來,但是如果將蛇單獨實現,總有些細節實現起來特別麻煩
其實蛇移動並非牽一髮而動全身,其實身子是沒有發生變化的,關鍵是兩點:
a.頭的移動
b.尾巴的移動
實現:
直接把蛇實現在地圖的小格子裡,不再單獨設定子類或者ArrayList等,Map里加上蛇頭的座標,從而使得Map可以根據蛇頭改變蛇的座標(類似於變數交換)。為頭部單獨設定x,y,作為移動的方向(也可以作為靜態變數x和y,不過沒什麼區別),為身子設定next指標,只要next.next不是尾巴,那麼保持不變。如果next是尾巴,就把自己的設定為尾巴,並且改變next,使之成為普通地圖塊。(refresh方法)
* 控制方向:
使用鍵盤事件,目前僅設定了wasd四個
* 視窗設計:
view extends JPanel,控制顯示,然後在Lattice裡呼叫Graphics.draw(...)實現對每個格子的顯示
下面是核心的map部分程式碼(包括自動移動,檢測食物,增加長度等等)
import codes.myGame.snake.cell.Lattice; import java.util.Random; public class Smap { private boolean getFood = false;//如果得到食物,該指標設為true,並且在隨後的autoChange裡增加蛇的長度 private boolean gameOver = false; private boolean directionChange = false;//這裡標誌的作用是保證在一次運動期間只會進行一次轉向,使遊戲更流暢 private int MAP_SIZE; private Lattice[][] map; private int directionX = 0;//下一次頭在當前位置的哪個方向上 private int directionY = 1;//下一次頭在當前位置的哪個方向上 private int[] head = new int[2];//記錄當前頭的位置 private int[] food = new int[2];//記錄當前食物的位置 public Smap(int size) { MAP_SIZE = size; map = new Lattice[MAP_SIZE][MAP_SIZE]; for(int i=0;i<size;i++){ for (int j = 0 ;j<size;j++){ map[i][j] = new Lattice(); } } map[MAP_SIZE/2][MAP_SIZE/2].setHead(true,map[MAP_SIZE/2][MAP_SIZE/2-1]);//初始化設定一個頭結點,以及他的尾節點 head[0] = MAP_SIZE/2; head[1] = MAP_SIZE/2; map[MAP_SIZE/2][MAP_SIZE/2-1].setRear(true,null); this.randFood(); } //模擬蛇的自動移動 public void autoChange(){ this.setHead(); if(food[0]==head[0] && food[1]==head[1]){//如果新的頭部碰觸到了食物,那麼尾部增長 getFood = true; } if(!gameOver)this.setRear(); if(getFood)this.randFood(); directionChange = false; } //根據鍵盤事件,改變頭的下一次移動方向,注意 該移動方向是僅針對頭部的 //setDirection和setHead兩個方法需要互斥進行,這裡單執行緒,用synchronized即可 //(否則,如果當前頭部在邊界位置,連續變幻方向可能導致在setHead裡發生溢位) public synchronized void setDirection(int x,int y){ if(directionY!=y && directionX!=x &&!directionChange){ directionX = x; directionY = y; directionChange = true; } } public boolean gameOver(){ return gameOver;//頭碰到身子,證明gameOver } private synchronized void setHead(){ int i = head[0]; int j = head[1]; head[0] = ( head[0] + directionX + MAP_SIZE)%MAP_SIZE; head[1] = ( head[1] + directionY + MAP_SIZE )%MAP_SIZE; if(map[head[0]][head[1]].isBody())gameOver = true; map[head[0]][head[1]].setHead(true,map[i][j]); map[i][j].setBody(true,null); map[i][j].setHead(false,null); //傳入null表示不改變當前指向 } //設定尾巴由於沒法像頭部那樣直接設定,這裡只能採用連結串列遍歷的方式獲取尾巴 private void setRear(){ if(!getFood){ Lattice temp = map[head[0]][head[1]]; while (!temp.next.isRear())temp = temp.next; temp.next().setRear(false,null); temp.setRear(true,null); temp.setBody(false,null); } } private void randFood(){ getFood = false; map[food[0]][food[1]].setFood(false);//先把當前的食物取消掉 boolean flag = false;//設定下一個食物 Random random = new Random(); int x = random.nextInt(MAP_SIZE); int y = random.nextInt(MAP_SIZE); while (!flag){ x = random.nextInt(MAP_SIZE); y = random.nextInt(MAP_SIZE); if(!map[x][y].isHead() && !map[x][y].isRear() &&!map[x][y].isBody())flag = true; } map[x][y].setFood(true); food[0] = x; food[1] = y; } public Lattice get(int row, int col){ return map[row][col]; } public int getMAP_SIZE() { return MAP_SIZE; } }
下面是顯示部分的程式碼
顯示分為兩部分,一塊是利用Graphics.draw()方法實現單個單元格的繪製,另一塊設定view類繼承自JPanel。負責繪製圖畫顯示
public class Lattice { private boolean isBody = false; private boolean isHead = false; private boolean isFood = false; private boolean isRear = false; public Lattice next = null; public void setHead(boolean bool,Lattice next){ isHead = bool; if(next!=null)this.next = next; } public void setBody(boolean bool,Lattice next){ isBody = bool; if(next!=null)this.next = next; //傳入引數為null時,不改變當前的next } public void setRear(boolean bool,Lattice next){ isRear = bool; this.next = next; } public void setFood(boolean bool){ isFood = bool; } public Lattice next(){ return next; } public boolean isHead(){ return isHead; } public boolean isFood(){ return isFood; } public boolean isRear(){ return isRear; } public boolean isBody(){ return isBody; } public void refresh(){ if(isHead){ isBody = true; isHead = false; // 怎麼設定下一個頭呢?(考慮把DirectionX,Y放到Smap裡,而不是這裡) }else if(isBody){ if(next.isRear){ next.isRear = false; isRear = true; isBody = false; } } } // 在這裡設定細胞可見 public void draw(Graphics g, int x, int y, int size) { g.setColor(black); g.drawRect(x, y, size, size); if ( isHead ) { g.setColor( red); g.fillRect(x, y, size, size); }else if ( isBody || isRear) { g.setColor(black); g.fillRect(x, y, size, size); }else if(isFood){ g.setColor( blue); g.fillRect(x, y, size, size); } } }
view部分:
import codes.myGame.snake.cell.Lattice;
import javax.swing.*;
import java.awt.*;
public class View extends JPanel {
private static final long serialVersionUID = -5258995676212660595L;
private static final int GRID_SIZE = 32; //填充的畫素數量
private Smap thisMap;
public View(Smap map) {
thisMap = map;
}
@Override
public void paint(Graphics g) {
super.paint(g);
int size = thisMap.getMAP_SIZE();
for (int row = 0; row< size; row++ ) {
for (int col = 0; col< size; col++ ) {
Lattice lattice = thisMap.get(row, col);
if ( lattice != null ) {
lattice.draw(g, col*GRID_SIZE, row*GRID_SIZE, GRID_SIZE);//對應的格子的顯示
}
}
}
}
@Override
public Dimension getPreferredSize() {//建立該div大小
return new Dimension(thisMap.getMAP_SIZE()*GRID_SIZE+1, thisMap.getMAP_SIZE()*GRID_SIZE+1);
}
}