用java實現貪吃蛇小遊戲
阿新 • • 發佈:2021-01-20
在本類中目的是做一個背景和畫板,實現貪吃蛇遊戲的一個動態效果
package Snake; import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Background { //容器物件 private JFrame sanke_jf = new JFrame("貪吃蛇小遊戲"); private JPanel jPanel = new JPanel(); private SnakeClass snake;//蛇 private GetFood.Food food;//食物 public Background( SnakeClass snake, GetFood.Food food) { //構造器 this.food = food; this.snake = snake; } //背景的大小 public static final int BACK_WIDTH = 1210; public static final int BACK_HEIGHT = 858; //遊戲相關資訊的畫布和畫筆 private class DrawArea extends Canvas { @Overridepublic void paint(Graphics g) { if (Is.isOver) { g.setColor(Color.black); g.setFont(new Font("Times", Font.BOLD, 45)); g.drawString("遊戲結束!", 600, 350); g.drawString("你的分數:"+food.count, 600, 400); g.drawString("歷史最高分:"+SaveMaxCount.read(), 600, 450); }else { if (food.color==5||food.color==10||food.color==7){ g.setColor(Color.orange); g.fillOval(food.foodx, food.foody, Node.NODE_DIA, Node.NODE_DIA); }else if(food.color==4||food.color==8){ g.setColor(Color.black); g.fillOval(food.foodx, food.foody, Node.NODE_DIA, Node.NODE_DIA); }else { g.setColor(Color.GREEN); g.fillOval(food.foodx, food.foody, Node.NODE_DIA, Node.NODE_DIA); } //蛇頭(黃色) g.setColor(Color.orange); g.fillOval(snake.head.nodex, snake.head.nodey, Node.NODE_DIA, Node.NODE_DIA); //蛇的身體(藍色) g.setColor(Color.BLUE); Node temp = snake.head; while (true) { if (temp.next == null) { g.fillOval(temp.nodex, temp.nodey, Node.NODE_DIA, Node.NODE_DIA); break; } temp = temp.next; g.fillOval(temp.nodex, temp.nodey, Node.NODE_DIA, Node.NODE_DIA); } } } } private Timer timer;//多久自動執行一次相關程式碼的物件 //建立繪畫區域物件 DrawArea drawArea = new DrawArea(); //組裝顯示介面的方法 public void draw_back() { drawArea.setPreferredSize(new Dimension(BACK_WIDTH, BACK_HEIGHT));//為繪畫區域設定大小 //鍵盤監聽器,用鍵盤控制蛇的移動 KeyListener listener = new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode(); snake.move(keyCode); } }; //為相關區域新增監聽器 jPanel.addKeyListener(listener); sanke_jf.addKeyListener(listener); drawArea.addKeyListener(listener); //設定JFrame容器的大小 sanke_jf.setSize(BACK_WIDTH, BACK_HEIGHT); //每過指定時間自動執行的任務程式碼 ActionListener tack = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { drawArea.repaint();//重畫,實現視覺效果 snake.autmove(); if (Is.isOver) timer.stop(); } }; timer = new Timer(230, tack); timer.start(); //將繪畫區域新增到容器中 jPanel.add(drawArea); sanke_jf.add(jPanel); sanke_jf.setResizable(false);//設定JFrame的大小不可變 sanke_jf.setVisible(true);//設定視窗可見 //點選右上角“x”號結束程式 sanke_jf.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { System.exit(0); } }); }
//該類是食物相關類,實現同一時間有且僅有一個食物存在,主要利用多執行緒的通訊機制
package Snake; import java.util.Random; public class GetFood { //食物消失與生成的類 //每一個食物也是一個節點 Random rx = new Random();//隨機生成食物的x座標 Random ry = new Random();//隨機生成食物的y座標 Random rc = new Random(); //將背景按節點大小劃分為一定的格子,食物只能產生在格子中以保證蛇一定可以吃到 int rowy = Background.BACK_WIDTH/Node.NODE_DIA; int rowx = Background.BACK_HEIGHT/Node.NODE_DIA; //食物類 class Food{ //座標 public int foodx =-1; public int foody =-1; public int color = 1; //是否需要產生食物 public boolean isGetFood = true; //計算分數 int count=0; } //用多執行緒實現食物的消亡與產生,利用等待喚醒機制保證食物的同步 class CreateFood extends Thread { //提供鎖物件 private Food food; public CreateFood(Food food){ this.food=food; } @Override public void run() { while(true){ synchronized (food){ if(!food.isGetFood){ try { food.wait();//如果有食物進入等待 } catch (InterruptedException e) { e.printStackTrace(); } }else{ //如果沒有食物隨機生成一個食物(座標) if(Is.is_Strength){ food.color=(int)(Math.random()*10); } food.foodx=Node.NODE_DIA*(rx.nextInt(rowy-2)+1); food.foody=Node.NODE_DIA*(ry.nextInt(rowx-3));//設定一定範圍,增強遊戲體驗 food.isGetFood=false;//產生食物以後修改食物的狀態 food.notify();//喚醒吃食物的執行緒 } } } } } class EatFood extends Thread{ private Food food;//提供鎖物件 private SnakeClass snake;//蛇(與食物發生碰撞) public EatFood(Food food,SnakeClass snake){ //構造器 this.food=food; this.snake=snake; } @Override public void run() { while(true){ synchronized (food){ if(food.isGetFood){ try { food.wait();//如果沒有食物,進入等待 } catch (InterruptedException e) { e.printStackTrace(); } }else{ //如果有食物進 if (snake.head.nodex==food.foodx&&snake.head.nodey==food.foody){ //如果蛇頭食物發生碰撞 if(food.color==5||food.color==10||food.color==7){ snake.add_Tail(new Node(snake.get_Tail().nodex,snake.get_Tail().nodey)); snake.add_Tail(new Node(snake.get_Tail().nodex,snake.get_Tail().nodey)); food.foodx=-1; food.foody=-1; food.count+=2; }else if(food.color==4||food.color==8){ snake.deleteTail(); food.foodx=-1; food.foody=-1; food.count--; try { Thread.sleep(3500); } catch (InterruptedException e) { e.printStackTrace(); }if(food.count>=SaveMaxCount.read()){ SaveMaxCount.save(food.count); } food.isGetFood=true;//修改食物狀態 food.notify();//喚醒產生食物的執行緒 } else{ snake.add_Tail(new Node(snake.get_Tail().nodex,snake.get_Tail().nodey)); food.foodx=-1; food.foody=-1; food.count++; } // System.out.println(food.color);(在測試時檢視隨機數) //如果當前分數大於歷史最大分數,則將當前分數存入檔案 if(!food.isGetFood){ food.isGetFood=true;//修改食物狀態 food.notify();//喚醒產生食物的執行緒 } } } } } } } }
//遊戲入口
public class Inner { public static void main(String[] args) { new StartFrame().start_window(); } }
//遊戲模式和遊戲是否結束的開關
package Snake; public class Is { public static boolean isOver = false;//表示遊戲是否結束 public static boolean is_nodie=false; public static boolean is_Strength=false; }
package Snake; //蛇的沒一個節點的基本單位,因為在本遊戲中不斷地進行刪除與增加,所以選擇使用連結串列 public class Node { //表示蛇的每一個節點(連結串列結構) public Node next;//指向下一個 public int nodex;//節點的x座標 public int nodey;//節點的y座標 public static final int NODE_DIA = 22;//節點的大小 public Node(int nodex,int nodey){ //構造器 this.nodex=nodex; this.nodey=nodey; } }
package Snake; //遊戲的初始化資訊和模式設定 public class RunGameMethod { public static void method1(){//標準模式 Is.is_nodie=false; Is.is_Strength=false; original(); } public static void method2(){//無盡模式 Is.is_Strength=false; Is.is_nodie=true; original(); } public static void method3(){//無盡模式 Is.is_nodie=false; Is.is_Strength=true; original(); } private static void original(){ SaveMaxCount filecount = new SaveMaxCount();//建立儲存最高分數的物件 //初始化一條小蛇 Node head = new Node(2 * Node.NODE_DIA, 0); Node body1 = new Node(Node.NODE_DIA, 0); Node body2 = new Node(0, 0); SnakeClass snake = new SnakeClass(head); snake.add_Tail(body1); snake.add_Tail(body2); //建立食物的物件 GetFood.Food food = new GetFood().new Food(); //顯示遊戲執行介面 Background back = new Background(snake, food); back.draw_back(); //開啟產生食物和吃食物的兩個執行緒,並將食物物件作為鎖物件 new GetFood().new EatFood(food, snake).start(); new GetFood().new CreateFood(food).start(); } }
package Snake;
//利用檔案儲存遊戲的最高分 import java.io.*; public class SaveMaxCount { //儲存最高分數的檔案位置 private static File file_normal = new File("C:\\Users\\lenovo\\IdeaProjects\\MyJava\\src\\Snake\\savecount.txt"); private static File file_Strength= new File("C:\\Users\\lenovo\\IdeaProjects\\MyJava\\src\\Snake\\save_strength.txt"); private static File file = file_normal; private static File getFile() { if(Is.is_Strength){ return file_Strength; } return file; } //儲存分數的方法 public static void save(int count){ FileOutputStream fos = null; try{ fos = new FileOutputStream(SaveMaxCount.getFile()); fos.write(count); }catch(IOException e){ e.getMessage(); }finally { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } //得到歷史最高分數的方法 public static int read(){ FileInputStream fis = null; int count = 0; try { fis=new FileInputStream(SaveMaxCount.getFile()); count=fis.read(); } catch (IOException e) { e.printStackTrace(); }finally{ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } return count; } }
package Snake; //實現蛇的構造、移動、遊戲結束邏輯的控制。 public class SnakeClass { public SnakeClass(){ } //蛇的構造器,重點是頭節點 IS是為了在整個程式中保證判斷結束語句的物件的一致性 public SnakeClass( Node head) { this.head = head; } public Node head;//頭結點 public Node tail;//尾節點 //增加蛇的長度(向尾部新增一個節點) public void add_Tail(Node node) { Node temp = head; while (true) { if (temp.next == null) { //此時temp為當前的尾節點 break; } temp = temp.next; } temp.next = node; } //在頭結點的後面和原頭結點的下一個節點中間插入一個節點(移動的第一步) public void add_head(Node node) { Node temp = head.next; head.next = node; node.next = temp; } //得到當前的尾節點(在得分和移動時都需要用到) public Node get_Tail() { Node temp = head; while (true) { if (temp.next == null) { tail = temp; return tail; } temp = temp.next; } } //刪除尾節點(移動的第二步) public void deleteTail() { Node temp = head; Node temp2 = temp; while (true) { if (temp.next == null) { temp2.next = null; return; } temp2 = temp; temp = temp.next; } } //蛇頭撞到了自身,遊戲結束邏輯的一方面 private boolean isOver_Self(){ boolean b = false; Node temp = head.next; while(true){ if(head.nodey==temp.nodey&&head.nodex==temp.nodex){ b=true; break; }else if(temp.next==null){ break; } temp=temp.next; } return b; } //遊戲結束的邏輯 public boolean isOver(){ return head.nodex < Node.NODE_DIA || head.nodex > (Background.BACK_WIDTH - 2 * Node.NODE_DIA) || head.nodey < -0 || head.nodey > (Background.BACK_HEIGHT - 4 * Node.NODE_DIA)||isOver_Self(); } //移動的方法(改變方向)(將頭結點按指定方法移動一個單位,在其後新增加一個單位,刪除尾節點 //判斷蛇是否可以向右移動,正在向左方向移動 //條件成立不能該方向移動 private boolean can_moveRight(){ return head.nodey==head.next.nodey&&head.nodex<head.next.nodex; } //判斷蛇是否可以向左移動,正在向右方向移動 //條件成立不能該方向移動 private boolean can_moveLeft(){ return head.nodey==head.next.nodey&&head.nodex>head.next.nodex; } //判斷蛇是否可以向上移動,正在向下方向移動 //條件成立不能該方向移動 private boolean can_moveUp(){ return head.nodex==head.next.nodex&&head.nodey<head.next.nodey; } //判斷蛇是否可以向下移動,正在向上方向移動 //條件成立不能該方向移動 private boolean can_moveDown(){ return head.nodex==head.next.nodex&&head.nodey>head.next.nodey; } public void move(int ch) { boolean b=isOver(); //撞到邊界的判斷邏輯,滿足條件遊戲結束 int tempx = head.nodex; int tempy = head.nodey; //新增節點的x和y座標 switch (ch) { case 37: if(can_moveLeft()) break; if(!Is.is_nodie){ if (b) Is.isOver = true; } head.nodex -= Node.NODE_DIA; add_head(new Node(tempx, tempy)); deleteTail(); break; case 40: if(can_moveUp()) break; if(!Is.is_nodie){ if (b) Is.isOver = true; } head.nodey += Node.NODE_DIA; add_head(new Node(tempx, tempy)); deleteTail(); break; case 39: if(can_moveRight()) break; if(!Is.is_nodie){ if (b) Is.isOver = true; } head.nodex += Node.NODE_DIA; add_head(new Node(tempx, tempy)); deleteTail(); break; case 38: if(can_moveDown()) break; if(!Is.is_nodie){ if (b) Is.isOver = true; } head.nodey -= Node.NODE_DIA; add_head(new Node(tempx, tempy)); deleteTail(); break; default: break; } } //實現蛇的自動移動(與第一種移動方式有相似的邏輯,但不會改變方向) public void autmove(){ int tempx = head.nodex; int tempy = head.nodey; boolean b=isOver(); if(can_moveRight()){ if(!Is.is_nodie){ if (b) Is.isOver = true; } head.nodex -= Node.NODE_DIA; add_head(new Node(tempx, tempy)); deleteTail(); }else if(can_moveLeft()){ if(!Is.is_nodie){ if (b) Is.isOver = true; } head.nodex += Node.NODE_DIA; add_head(new Node(tempx, tempy)); deleteTail(); }else if (can_moveUp()){ if(!Is.is_nodie){ if (b) Is.isOver = true; } head.nodey -= Node.NODE_DIA; add_head(new Node(tempx, tempy)); deleteTail(); }else{ if(!Is.is_nodie){ if (b) Is.isOver = true; } head.nodey += Node.NODE_DIA; add_head(new Node(tempx, tempy)); deleteTail(); } } }
package Snake; //遊戲開始介面 import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class StartFrame implements ActionListener{ private JFrame start_trame = new JFrame("遊戲模式選擇"); private JPanel start_panle = new JPanel(); private JButton but1 = new JButton("標準模式"); private JButton but2 = new JButton("無盡模式"); private JButton but3 = new JButton("強化模式"); private JButton but4 = new JButton("遊戲資訊"); private String str; public void start_window(){ but1.addActionListener(this); but2.addActionListener(this); but3.addActionListener(this); but4.addActionListener(this); start_panle.add(but1); start_panle.add(but2); start_panle.add(but3); start_panle.add(but4); start_panle.setLayout(new GridLayout(4,1)); start_trame.add(start_panle); start_trame.setSize(400,500); start_trame.setVisible(true); start_trame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } @Override public void actionPerformed(ActionEvent e) { Is.isOver=false; if(e.getSource()==but1){ RunGameMethod.method1(); }else if(e.getSource()==but2){ RunGameMethod.method2(); }else if(e.getSource()==but3){ RunGameMethod.method3(); }else{ str="你可以通過“↑”“↓”“←”“→”控制一條小蛇去吃食物,注意不要撞到邊界和自己的身體!\n" + "在無盡模式中你可以任意移動而不擔心死亡,在強化模式中有三種不同的食物各有不同的效果!\n" + "快點開始你的遊戲之旅吧!\n" + "\n\n" + JOptionPane.showMessageDialog(null,str); } } }