java實(shí)現(xiàn)貪吃蛇小游戲
本文實(shí)例為大家分享了java實(shí)現(xiàn)貪吃蛇小游戲的具體代碼,供大家參考,具體內(nèi)容如下

這是MVC模式的完整Java項(xiàng)目,編譯運(yùn)行SnakeApp.java即可開始游戲。
可擴(kuò)展功能:
1、積分功能:可以創(chuàng)建得分規(guī)則的類(模型類的一部分), 在GameController的run()方法中計(jì)算得分
2、變速功能:比如加速功能,減速功能,可以在GameController的keyPressed()方法中針對(duì)特定的按鍵設(shè)置每一次移動(dòng)之間的時(shí)間間隔,將Thread.sleep(Settings.DEFAULT_MOVE_INTERVAL);替換為動(dòng)態(tài)的時(shí)間間隔即可
3、更漂亮的游戲界面:修改GameView中的drawXXX方法,比如可以將食物渲染為一張圖片,Graphics有drawImage方法
View
SnakeApp.java
/*
* 屬于View,用來根據(jù)相應(yīng)的類展示出對(duì)應(yīng)的游戲主界面,也是接收控制信息的第一線。
*/
public class SnakeApp {
public void init() {
//創(chuàng)建游戲窗體
JFrame window = new JFrame("一只長不大的蛇");
//初始化500X500的棋盤,用來維持各種游戲元素的狀態(tài),游戲的主要邏輯部分
Grid grid = new Grid(50*Settings.DEFAULT_NODE_SIZE,50*Settings.DEFAULT_NODE_SIZE);
//傳入grid參數(shù),新建界面元素對(duì)象
GameView gameView = new GameView(grid);//繪制游戲元素的對(duì)象
//初始化面板
gameView.initCanvas();
//根據(jù)棋盤信息建立控制器對(duì)象
GameController gameController = new GameController(grid);
//設(shè)置窗口大小
window.setPreferredSize(new Dimension(526,548));
//往窗口中添加元素,面板對(duì)象被加入到窗口時(shí),自動(dòng)調(diào)用其中的paintComponent方法。
window.add(gameView.getCanvas(),BorderLayout.CENTER);
//畫出蛇和棋盤和食物
GameView.draw();
//注冊窗口監(jiān)聽器
window.addKeyListener((KeyListener)gameController);
//啟動(dòng)線程
new Thread(gameController).start();
//窗口關(guān)閉的行為
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//設(shè)置窗口大小不可變化
window.setResizable(false);
//渲染和顯示窗口
window.pack();
window.setVisible(true);
}
//可以忽略,以后每個(gè)類中都有這么一個(gè)測試模塊
public static void main(String[] args) {
SnakeApp snakeApp = new SnakeApp();
snakeApp.init();
}
}
GameView.java
/*
* 屬于View,用于繪制地圖、蛇、食物
*/
/* Graphics 相當(dāng)于一個(gè)畫筆。對(duì)象封裝了 Java 支持的基本呈現(xiàn)操作所需的狀態(tài)信息。此狀態(tài)信息包括以下屬性:
要在其上繪制的 Component 對(duì)象。
呈現(xiàn)和剪貼坐標(biāo)的轉(zhuǎn)換原點(diǎn)。
當(dāng)前剪貼區(qū)。
當(dāng)前顏色。
當(dāng)前字體。
當(dāng)前邏輯像素操作函數(shù)(XOR 或 Paint)。
當(dāng)前 XOR 交替顏色
*/
/* java.awt.Component的repaint()方法
作用:更新組件。
如果此組件不是輕量級(jí)組件,則為了響應(yīng)對(duì) repaint 的調(diào)用,AWT 調(diào)用 update 方法??梢约俣ㄎ辞宄尘?。
Component 的 update 方法調(diào)用此組件的 paint 方法來重繪此組件。為響應(yīng)對(duì) repaint 的調(diào)用而需要其他工作的子類通常重寫此方法。重寫此方法的 Component 子類應(yīng)該調(diào)用 super.update(g),或者直接從其 update 方法中調(diào)用 paint(g)。
圖形上下文的原點(diǎn),即它的(0,0)坐標(biāo)點(diǎn)是此組件的左上角。圖形上下文的剪貼區(qū)域是此組件的邊界矩形。
*/
public class GameView {
private final Grid grid;
private static JPanel canvas;//畫板,用于在這上面制作畫面,然后返回。
public GameView(Grid grid) {
this.grid = grid;
}
//重新繪制游戲界面元素,不斷重新調(diào)用paintComponent方法覆蓋原本的面板。
public static void draw() {
canvas.repaint();
}
//獲取畫板對(duì)象的接口
public JPanel getCanvas() {
return canvas;
}
//對(duì)畫板進(jìn)行初始化
public void initCanvas() {
canvas = new JPanel() {
//指向一個(gè)方法被覆蓋的新面板子類對(duì)象
//paintComponent()繪制此容器中的每個(gè)組件,Swing會(huì)在合適的時(shí)機(jī)去調(diào)用這個(gè)方法,展示出合適的界面,這就是典型的回調(diào)(callback)的概念。
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics); //這里必須調(diào)用一下父類 也就是 container的重繪方法,否則表現(xiàn)為之前的繪圖不會(huì)覆蓋
drawGridBackground(graphics);//畫出背景網(wǎng)格線
drawSnake(graphics, grid.getSnake());//畫蛇
drawFood(graphics, grid.getFood());//畫食物
}
};
}
//畫蛇
public void drawSnake(Graphics graphics, Snake snake) {
for(Iterator<Node> i = snake.body.iterator();i.hasNext();) {
Node bodyNode = (Node)i.next();
drawSquare(graphics, bodyNode,Color.BLUE);
}
}
//畫食物
public void drawFood(Graphics graphics, Node food) {
drawCircle(graphics,food,Color.ORANGE);
}
//畫格子背景,方便定位Snake運(yùn)動(dòng)軌跡,橫豎各以10為單位的50個(gè)線。
public void drawGridBackground(Graphics graphics) {
graphics.setColor(Color.GRAY);
canvas.setBackground(Color.BLACK);
for(int i=0 ; i < 50 ; i++) {
graphics.drawLine(0, i*Settings.DEFAULT_NODE_SIZE, this.grid.getWidth(), i*Settings.DEFAULT_NODE_SIZE);
}
for(int i=0 ; i <50 ; i++) {
graphics.drawLine(i*Settings.DEFAULT_NODE_SIZE, 0, i*Settings.DEFAULT_NODE_SIZE , this.grid.getHeight());
}
graphics.setColor(Color.red);
graphics.fillRect(0, 0, this.grid.width, Settings.DEFAULT_NODE_SIZE);
graphics.fillRect(0, 0, Settings.DEFAULT_NODE_SIZE, this.grid.height);
graphics.fillRect(this.grid.width, 0, Settings.DEFAULT_NODE_SIZE,this.grid.height);
graphics.fillRect(0, this.grid.height, this.grid.width+10,Settings.DEFAULT_NODE_SIZE);
}
/*
* public abstract void drawLine(int x1,int y1,int x2,int y2)
在此圖形上下文的坐標(biāo)系中,使用當(dāng)前顏色在點(diǎn) (x1, y1) 和 (x2, y2) 之間畫一條線。
參數(shù):
x1 - 第一個(gè)點(diǎn)的 x 坐標(biāo)。
y1 - 第一個(gè)點(diǎn)的 y 坐標(biāo)。
x2 - 第二個(gè)點(diǎn)的 x 坐標(biāo)。
y2 - 第二個(gè)點(diǎn)的 y 坐標(biāo)。
*/
//提供直接出現(xiàn)游戲結(jié)束的選項(xiàng)框的功能。
public static void showGameOverMessage() {
JOptionPane.showMessageDialog(null,"游戲結(jié)束","短暫的蛇生到此結(jié)束", JOptionPane.INFORMATION_MESSAGE);
}
//畫圖形的具體方法:
private void drawSquare(Graphics graphics, Node squareArea, Color color) {
graphics.setColor(color);
int size = Settings.DEFAULT_NODE_SIZE;
graphics.fillRect(squareArea.getX(), squareArea.getY(), size - 1, size - 1);
}
private void drawCircle(Graphics graphics, Node squareArea, Color color) {
graphics.setColor(color);
int size = Settings.DEFAULT_NODE_SIZE;
graphics.fillOval(squareArea.getX(), squareArea.getY(), size, size);
}
}
Controller
GameController
/*
* 接收窗體SnakeApp傳遞過來的有意義的事件,然后傳遞給Grid,讓Grid即時(shí)的更新狀態(tài)。
* 同時(shí)根據(jù)最新狀態(tài)渲染出游戲界面讓SnakeApp顯示
*
*/
public class GameController implements KeyListener, Runnable{
private Grid grid;
private boolean running;
public GameController(Grid grid){
this.grid = grid;
this.running = true;
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
switch(keyCode) {
case KeyEvent.VK_UP:
grid.changeDirection(Direction.UP);
break;
case KeyEvent.VK_DOWN:
grid.changeDirection(Direction.DOWN);
break;
case KeyEvent.VK_LEFT:
grid.changeDirection(Direction.LEFT);
break;
case KeyEvent.VK_RIGHT:
grid.changeDirection(Direction.RIGHT);
break;
}
isOver(grid.nextRound());
GameView.draw();
}
private void isOver(boolean flag) {
if(!flag) {//如果下一步更新棋盤時(shí),出現(xiàn)游戲結(jié)束返回值(如果flag為假)則
this.running = false;
GameView.showGameOverMessage();
System.exit(0);
}
}
@Override
/*run()函數(shù)中的核心邏輯是典型的控制器(Controller)邏輯:
修改模型(Model):調(diào)用Grid的方法使游戲進(jìn)入下一步
更新視圖(View):調(diào)用GameView的方法刷新頁面*/
public void run() {
while(running) {
try {
Thread.sleep(Settings.DEFAULT_MOVE_INTERVAL);
isOver(grid.nextRound());
GameView.draw();
} catch (InterruptedException e) {
break;
}
// 進(jìn)入游戲下一步
// 如果結(jié)束,則退出游戲
// 如果繼續(xù),則繪制新的游戲頁面
}
running = false;
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
}
Model
Grid
/*
* 隨機(jī)生成食物,維持貪吃蛇的狀態(tài),根據(jù)SnakeApp中的用戶交互來控制游戲狀態(tài)。
*/
public class Grid {
private Snake snake;
int width;
int height;
Node food;
private Direction snakeDirection =Direction.LEFT;
public Grid(int length, int high) {
super();
this.width = length;
this.height = high;
initSnake();
food = creatFood();
}
//在棋盤上初始化一個(gè)蛇
private void initSnake() {
snake = new Snake();
int x = width/2;
int y = height/2;
for(int i = 0;i<5;i++) {
snake.addTail(new Node(x, y));
x = x+Settings.DEFAULT_NODE_SIZE;
}
}
//棋盤上隨機(jī)制造食物的功能。
//一直循環(huán)獲取隨機(jī)值,直到三個(gè)條件都不滿足。
private Node creatFood() {
int x,y;
do {
x =(int)(Math.random()*100)+10;
y =(int)(Math.random()*100)+10;
System.out.println(x);
System.out.println(y);
System.out.println(this.width);
System.out.println(this.height);
}while(x>=this.width-10 || y>=this.height-10 || snake.hasNode(new Node(x,y)));
food = new Node(x,y);
return food;
}
//提供下一步更新棋盤的功能,移動(dòng)后更新游戲和蛇的狀態(tài)。
public boolean nextRound() {
Node trail = snake.move(snakeDirection);
Node snakeHead = snake.getBody().removeFirst();//將頭部暫時(shí)去掉,拿出來判斷是否身體和頭部有重合的點(diǎn)
if(snakeHead.getX()<=width-10 && snakeHead.getX()>=10
&& snakeHead.getY()<=height-10 && snakeHead.getY()>=10
&& !snake.hasNode(snakeHead)) {//判斷吃到自己和撞到邊界
if(snakeHead.equals(food)) {
//原本頭部是食物的話,將move操作刪除的尾部添加回來
snake.addTail(trail);
food = creatFood();
}
snake.getBody().addFirst(snakeHead);
return true;//更新棋盤狀態(tài)并返回游戲是否結(jié)束的標(biāo)志
}
return false;
}
public Node getFood() {
return food;
}
public Snake getSnake() {
return snake;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
//提供一個(gè)更改貪吃蛇前進(jìn)方向的方法
public void changeDirection(Direction newDirection){
snakeDirection = newDirection;
}
}
Snake
/*
* 蛇類,實(shí)現(xiàn)了自身數(shù)據(jù)結(jié)構(gòu),以及移動(dòng)的功能
*/
public class Snake implements Cloneable{
public LinkedList<Node> body = new LinkedList<>();
public Node move(Direction direction) {
//根據(jù)方向更新貪吃蛇的body
//返回移動(dòng)之前的尾部Node(為了吃到時(shí)候后增加尾部長度做準(zhǔn)備)
Node n;//臨時(shí)存儲(chǔ)新頭部移動(dòng)方向的結(jié)點(diǎn)
switch (direction) {
case UP:
n = new Node(this.getHead().getX(),this.getHead().getY()-Settings.DEFAULT_NODE_SIZE);
break;
case DOWN:
n = new Node(this.getHead().getX(),this.getHead().getY()+Settings.DEFAULT_NODE_SIZE);
break;
case RIGHT:
n = new Node(this.getHead().getX()+Settings.DEFAULT_NODE_SIZE,this.getHead().getY());
break;
default:
n = new Node(this.getHead().getX()-Settings.DEFAULT_NODE_SIZE,this.getHead().getY());
}
Node temp = this.body.getLast();
this.body.addFirst(n);
this.body.removeLast();
return temp;
}
public Node getHead() {
return body.getFirst();
}
public Node getTail() {
return body.getLast();
}
public Node addTail(Node area) {
this.body.addLast(area);
return area;
}
public LinkedList<Node> getBody(){
return body;
}
//判斷參數(shù)結(jié)點(diǎn)是否在蛇身上
public boolean hasNode(Node node) {
Iterator<Node> it = body.iterator();
Node n = new Node(0,0);
while(it.hasNext()) {
n = it.next();
if(n.getX() == node.getX() && n.getY() == node.getY()) {
return true;
}
}
return false;
}
}
Direction
/*
* 用來控制蛇的移動(dòng)方向
*/
public enum Direction {
UP(0),
DOWN(1),
LEFT(2),
RIGHT(3); //調(diào)用構(gòu)造方法對(duì)方向枚舉實(shí)例進(jìn)行代碼初始化
//成員變量
private final int directionCode;
//成員方法
public int directionCode() {
return directionCode;
}
Direction(int directionCode){
this.directionCode = directionCode;
}
}
Node
public class Node {
private int x;
private int y;
public Node(int x, int y) {
this.x = ((int)(x/10))*10;
this.y = ((int)(y/10))*10;
}//使用這種方法可以使得節(jié)點(diǎn)坐標(biāo)不會(huì)出現(xiàn)個(gè)位數(shù)
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
//判斷兩個(gè)Node是否相同
public boolean equals(Object n) {
Node temp;
if(n instanceof Node) {
temp = (Node)n;
if(temp.getX()==this.getX() && temp.getY()==this.getY())
return true;
}
return false;
}
}
Settings
public class Settings {
public static int DEFAULT_NODE_SIZE = 10;//每一個(gè)節(jié)點(diǎn)方塊的單位
public static int DEFAULT_MOVE_INTERVAL = 200;//蛇移動(dòng)時(shí)間間隔
}
更多有趣的經(jīng)典小游戲?qū)崿F(xiàn)專題,分享給大家:
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Boot集成tablesaw插件快速入門示例代碼
Tablesaw是一款Java的數(shù)據(jù)可視化庫,數(shù)據(jù)解析庫,主要用于加載數(shù)據(jù),對(duì)數(shù)據(jù)進(jìn)行操作(轉(zhuǎn)化,過濾,匯總等),類比Python中的Pandas庫,本文介紹Spring Boot集成tablesaw插件快速入門Demo,感興趣的朋友一起看看吧2024-06-06
Java對(duì)象進(jìn)行深拷貝的五種方法實(shí)例代碼
這篇文章主要介紹了Java對(duì)象進(jìn)行深拷貝的五種方法,分別是構(gòu)造函數(shù)、重載clone()方法、Apache?Commons?Lang序列化、Gson序列化和Jackson序列化,每種方法都給出了實(shí)例代碼,需要的朋友可以參考下2025-04-04
Java實(shí)現(xiàn)MapStruct對(duì)象轉(zhuǎn)換的示例代碼
本文主要介紹了MapStruct在Java中的對(duì)象轉(zhuǎn)換使用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
Spring如何基于aop實(shí)現(xiàn)事務(wù)控制
這篇文章主要介紹了Spring如何基于aop實(shí)現(xiàn)事務(wù)控制,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
Spring Boot利用Docker快速部署項(xiàng)目的完整步驟
這篇文章主要給大家介紹了關(guān)于Spring Boot利用Docker快速部署項(xiàng)目的完整步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
關(guān)于Process的waitFor死鎖問題及解決方案
這篇文章主要介紹了關(guān)于Process的waitFor死鎖問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12

