1. 程式人生 > 程式設計 >Java實現貪吃蛇遊戲

Java實現貪吃蛇遊戲

最近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,size);
 }else if ( isBody || isRear) {
  g.setColor(black);
  g.fillRect(x,size);
 }else if(isFood){
  g.setColor( blue);
  g.fillRect(x,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);
 }
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。