制作使用Java语言编写贪吃蛇小游戏,实现基本贪吃蛇功能。更深刻体会Java语言的应用。
package snake.entities;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import snake.listener.SnakeListener;
import snake.util.Global;
public class Snake {
//定义方向变量,用来控制蛇的方向
public static final int UP = -1;
public static final int DOWN = 1;
public static final int LEFT = 2;
public static final int RIGHT = -2;
/*
* 定义一个旧方向,和新方向。用来在改变方向时
* 判断新方向与旧方向是否相同,如果相同则说明
* 是无效方向,忽略。如果不同方向改变
*/
private int oldDirection, newDirection;
Ground ground = new Ground();
//定义一个坐标,用来存放食物坐标
public Point point = null;
//存放蛇身体总长度,占用坐标个数
public int snakeBodyCount;
private Point oldTail;//存放尾巴的坐标
private boolean life; //判断蛇是否活着
private boolean pause; //蛇是否暂停
private boolean isPause; //每次开开局蛇为暂停状态
public boolean isDie; //蛇是否死亡
public int speed = 500; //初始化蛇速度: 500ms/格
//存放蛇身体节点坐标
private LinkedList body =
new LinkedList();
//定义蛇监听列表
private Set listener =
new HashSet();
//构造方法,进行蛇的初始化
public Snake() {
init();
}
/*
* 初始化蛇的位置,让蛇头出现在游戏界面中心,
*/
public void init() {
int x = Global.WIDTH/ 2 - 3;
int y = Global.HEIGHT / 2 ;
//初始化蛇,给蛇添加三个节点
for(int i = 0; i < 3; i++) {
body.addLast(new Point(x--, y));
}
//初始化方向,向右
oldDirection = newDirection = RIGHT;
life = true;
pause = false;
isPause = true;
}
/*
* 蛇移动,先判断新旧方向是否相同,相同则忽略
* 不同,进行改变方向。蛇移动,通过添加一个头节点,
* 去除一个最后一个节点,达到移动的目的
*/
public void move() {
if (!(oldDirection + newDirection == 0)) {
oldDirection = newDirection;
}
//去尾
oldTail = body.removeLast();
int x = body.getFirst().x;
int y = body.getFirst().y;
switch(oldDirection) {
case UP: //向上移动
y--;
//到边上了可以从另一边出现
if (y < 0) {
y = Global.HEIGHT - 1;
}
break;
case DOWN:
y++;
//到边上了可以从另一边出现
if (y >= Global.HEIGHT) {
y = 0;
}
break;
case LEFT:
x--;
if (x < 0) {
x = Global.WIDTH - 1;
}
break;
case RIGHT:
x++;
if (x >= Global.WIDTH) {
x = 0;
}
break;
}
//记录蛇头的坐标
Point newHead = new Point(x, y);
//加头
body.addFirst(newHead);
}
//蛇改变方向
public void chanceDirection(int direction) {
newDirection = direction;
}
//蛇吃食物
public void eatFood() {
//通过添加删去的最后的尾节点,达到吃食物的目的
body.addLast(oldTail);
}
//判断蛇是否吃到身体
public boolean isEatBody() {
//body.get(0)存放的为蛇头的坐标,
//所有要排除蛇头,从i=1开始比较
for (int i = 1; i < body.size(); i++) {
if (body.get(i).equals(getHead())) {
return true;
}
}
return false;
}
/**
* 获取蛇的snakeBody链表,让食物与蛇身不重叠
* body 表示蛇身体的链表
* 返回与蛇身体坐标不重复的坐标
*/
public Point getFood(LinkedList body) {
//获得与石头不重叠的坐标
point = ground.getPoint();
while (checkPoints(body)) {
point = ground.getPoint();
}
// 如果发现食物的位置和蛇身体重叠,则重新随机食物的位置
return point;
// 返回这个对象本身,为创建实例时带来方便
}
//获得食物坐标
public Point getFoodPoint() {
return getFood(body);
}
/**
* 检查蛇身体链表中是否有一块与当前食物坐标相同
* @return 如果有重复返回true
* 否则返回 false
*/
public boolean checkPoints(LinkedList body) {
for (Point p : body)
if (p.getX() == point.getX() && p.getY() == point.getY())
return true;
// 循环遍历是否有重复
return false;
}
//画蛇
public void drawMe(Graphics g) {
for(Point p : body) {
g.setColor(Color.PINK);//设置身体颜色
g.fill3DRect(p.x * Global.CELL_SIZE, p.y * Global.CELL_SIZE,
Global.CELL_SIZE, Global.CELL_SIZE, true);
//最后一个参数,raised 是否凸起的,true为是。
}
//画蛇头,覆盖蛇头位置
g.setColor(Color.RED);
g.fill3DRect(getHead().x * Global.CELL_SIZE, getHead().y * Global.CELL_SIZE,
Global.CELL_SIZE, Global.CELL_SIZE, true);
}
//获得蛇头的坐标
public Point getHead() {
return body.getFirst();
}
//蛇死亡,生命改为false
public void die() {
life = false;
isDie = true;
}
//一个内部类, 驱动蛇定时移动
public class SnakerDriver implements Runnable{
public void run() {
//当蛇活着的时候才进行循环
while(life) {
//入伙蛇没有暂停才能移动
if (!pause) {
move();
//蛇每次移动后,获得蛇身体总长度
getSnakeBodyCount();
//触发 SnakeListener 的状态改变事件
for(SnakeListener l : listener) {
l.snakeMove(Snake.this);
}
//让蛇开开始时为暂停状态
if (isPause) {
pause = true;
isPause = false;
}
}
try {
//定时移动
Thread.sleep(speed);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//让蛇开始运动, 开启一个新的线程
public void start() {
new Thread(new SnakerDriver()).start();
}
//添加监听器
public void addSnakeListener(SnakeListener l) {
if(l != null) {
this.listener.add(l);
}
}
public void getSnakeBodyCount() {
snakeBodyCount = body.size();
}
//改变蛇暂停状态
public void changePause() {
pause = !pause;
}
//清除身体所有节点
public void bodyClear() {
body.clear();
}
}
package snake.entities;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;
import snake.util.Global;
public class Ground {
/*
*定义存放石头坐标的数组,1为石头,0为空白区域。
*一定要用全局静态变量,始终占用空间。否则会在产生食物时出错
*/
private static final int rocks[][] =
new int[Global.WIDTH][Global.HEIGHT];
//存放石头的个数
public int rocksCount = 0;
//是否画网格
private boolean isDrawGriding;
//选择地图是使用
public int MAP = 1;
//构造方法,初始化地图
public Ground() {
init();
}
//清除所有石头
public void clear() {
for (int x = 0; x < Global.WIDTH; x++)
for (int y = 0; y < Global.HEIGHT; y++)
rocks[x][y] = 0;
}
//初始化石头位置
public void init() {
//清除所有石头
clear();
//选择地图
switch(MAP) {
case 1:
map1(); //地图1
//获得石头个数
getScoksCount();
break;
case 2:
map2(); //地图2
getScoksCount();
break;
case 3:
map3(); //随机地图
getScoksCount();
break;
default :
map1(); //默认地图1
getScoksCount();
break;
}
}
//第一组默认地图石头坐标
public void map1() {
for(int x = 0; x < Global.WIDTH; x++) {
rocks[x][0] = 1;
rocks[x][Global.HEIGHT-1] = 1;
}
for(int y = 0; y < Global.HEIGHT; y++) {
rocks[0][y] = 1;
rocks[Global.WIDTH-1][y] = 1;
}
}
//第二个地图
public void map2() {
for(int x = 5; x < Global.WIDTH-5; x++) {
rocks[x][5] = 1;
rocks[x][Global.HEIGHT-4] = 1;
}
for(int y = 9; y < Global.HEIGHT-8; y++) {
rocks[9][y] = 1;
rocks[Global.WIDTH-9][y] = 1;
}
}
//随机地图,随机获得40个坐标座位石头
public void map3() {
Random random = new Random();
int x = 0,y = 0;
for(int i = 0; i < 40; i++) {
x = random.nextInt(Global.WIDTH);
y = random.nextInt(Global.HEIGHT);
rocks[x][y] = 1;
}
}
//获得石头总共数目
public void getScoksCount() {
//每次更换地图时清零,重新获得
rocksCount = 0;
for (int x = 0; x < Global.WIDTH; x++)
for (int y = 0; y < Global.HEIGHT; y++)
if (rocks[x][y] == 1) {
rocksCount++;
}
}
//判断蛇是否吃到石头
//把蛇的所有节点与石头坐标进行比较如果想等则证明吃到石头
public boolean isSnakeEatRock(Snake snake) {
for(int x = 0; x < Global.WIDTH; x++) {
for (int y = 0; y < Global.HEIGHT; y++) {
if (rocks[x][y] == 1
&& x == snake.getHead().x
&& y == snake.getHead().y) {
return true;
}
}
}
return false;
}
//获得不会与石头重叠的随机坐标
public Point getPoint() {
Random random = new Random();
int x = 0, y = 0;
do{
x = random.nextInt(Global.WIDTH);
y = random.nextInt(Global.HEIGHT);
}while(rocks[x][y] == 1);
return new Point(x, y);
}
//画石头和网格
public void drawMe(Graphics g) {
drawRocks(g);
if (isDrawGriding) {
drawGriding(g);
}
}
//画石头
public void drawRocks(Graphics g) {
for(int x = 0; x < Global.WIDTH; x++) {
for (int y = 0; y < Global.HEIGHT; y++) {
if (rocks[x][y] == 1) {
g.setColor(Color.DARK_GRAY);
g.fill3DRect(x * Global.CELL_SIZE, y * Global.CELL_SIZE,
Global.CELL_SIZE, Global.CELL_SIZE, true);
}
}
}
}
//画网格
public void drawGriding(Graphics g) {
for(int x = 0; x < Global.WIDTH; x++) {
for (int y = 0; y < Global.HEIGHT; y++) {
g.setColor(Color.GRAY);
g.fillRect(x * Global.CELL_SIZE , y * Global.CELL_SIZE,
1 , Global.HEIGHT * Global.CELL_SIZE);
g.fillRect(x * Global.CELL_SIZE , y * Global.CELL_SIZE,
Global.HEIGHT * Global.CELL_SIZE, 1 );
}
}
}
//需要要画网格
public void drawGriding() {
isDrawGriding = true;
}
//不需要画网格
public void notDrawGriding() {
isDrawGriding = false;
}
}
package snake.entities;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import snake.util.Global;
public class Food extends Point{
Point point = null;
//设置食物位置坐标
public void newFood(Point p) {
this.point = p;
this.setLocation(p);
}
//判断蛇是否吃到食物
public boolean isSnakeEatFood(Snake snake) {
return this.equals(snake.getHead());
}
//显示食物
public void drawMe(Graphics g) {
g.setColor(Color.GREEN);
g.fill3DRect(point.x * Global.CELL_SIZE, point.y * Global.CELL_SIZE,
Global.CELL_SIZE, Global.CELL_SIZE, true);
}
}
游戏界面GamePanel代码:
package snake.view;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
import snake.entities.Food;
import snake.entities.Ground;
import snake.entities.Snake;
import snake.util.Global;
//游戏的显示界面
@SuppressWarnings("serial")
public class GamePanel extends JPanel{
private Snake snake;
private Food food;
private Ground ground;
//显示画面
public void display(Snake snake, Food food, Ground ground) {
this.snake = snake;
this.food = food;
this.ground = ground;
//会重新显示,此方法会调用下面的方法
this.repaint();
}
@Override
protected void paintComponent(Graphics g) {
//重新显示
//设置背景颜色
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, Global.WIDTH * Global.CELL_SIZE,
Global.HEIGHT * Global.CELL_SIZE);
if(ground != null && snake != null && food != null ) {
this.ground.drawMe(g);
this.snake.drawMe(g);
this.food.drawMe(g);
}
}
}
package snake.listener;
import snake.entities.Snake;
public interface SnakeListener{
//蛇移动的监听
void snakeMove(Snake snake);
}
package snake.controller;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import javax.swing.JOptionPane;
import snake.entities.Food;
import snake.entities.Ground;
import snake.entities.Snake;
import snake.listener.SnakeListener;
import snake.util.Global;
import snake.view.GamePanel;
/*控制器
* 控制Ground, Snake, Food
* 负责游戏的逻辑
* 处理按键事件
* 实现了SnakeListener接口, 可以处理Snake 触发的事件
*/
public class Controller extends KeyAdapter implements SnakeListener {
private Snake snake;
private Food food;
private Ground ground;
private GamePanel gamePanel;
//存放当局游戏得分
public int score = 0;
//存放历史最高得分,这个数据通过读取文件来赋值
public int maxScore;
public Thread thread;
//构造方法,初始化
public Controller(Snake snake, Food food, Ground ground, GamePanel gamePanel) {
super();
this.snake = snake;
this.food = food;
this.ground = ground;
this.gamePanel = gamePanel;
//每次开始游戏读取文件,给maxScore赋值
readFile();
}
@Override
//处理按键事件
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP://向上
snake.chanceDirection(Snake.UP);
break;
case KeyEvent.VK_DOWN://向下
snake.chanceDirection(Snake.DOWN);
break;
case KeyEvent.VK_LEFT://向左
snake.chanceDirection(Snake.LEFT);
break;
case KeyEvent.VK_RIGHT://向右
snake.chanceDirection(Snake.RIGHT);
break;
case KeyEvent.VK_SPACE://空格键,实现游戏暂停
snake.changePause();
break;
case KeyEvent.VK_SHIFT://Shift键,实现开始新游戏
newGame();
break;
}
}
//处理Snake 触发的 snakeMoved 事件
@Override
public void snakeMove(Snake snake){
/*
* 判断是否还可以放下食物
* 当身体占满全部空位,没有地方再可以放食物时
* 游戏结束
* Global.count : 全局游戏界面总坐标,默认1000
* this.snake.snakeBodyCount : 蛇的身体总长度
* ground.rocksCount : 石头总数
*
*/
if (Global.count - this.snake.snakeBodyCount - ground.rocksCount < 3) {
snake.die();
writeMaxScore();
//弹出消息框,提示游戏结束,并显示得分
JOptionPane.showMessageDialog(gamePanel, "您已获得最高分,游戏结束!\n 游戏得分:"+ score);
}
//如果蛇吃到食物,,处理蛇吃到食物的方法,并获得新的食物
if (food.isSnakeEatFood(snake)) {
snake.eatFood();
food.newFood(snake.getFoodPoint());
this.score +=10;
}
//判断是否吃到石头,如果吃到石头,蛇死亡。
if (ground.isSnakeEatRock(snake)) {
snake.die();
//如果游戏得分大于历史记录最高分,把当前得分赋给最高分,并写入文件
writeMaxScore();
//弹出消息框,提示游戏结束,并显示得分
JOptionPane.showMessageDialog(gamePanel, "蛇撞墙死亡,游戏结束!\n 游戏得分:"+ score);
}
//如果蛇吃到身体也死亡
if(snake.isEatBody()) {
snake.die();
writeMaxScore();
JOptionPane.showMessageDialog(gamePanel, "蛇咬到自己死亡,游戏结束!\n 游戏得分:"+ score);
}
//如果蛇死亡,最后一次不刷新画面,如果刷新,蛇头会与石头重叠
if (!(ground.isSnakeEatRock(snake) | snake.isEatBody())) {
gamePanel.display(snake, food, ground);
}
}
//开始游戏
public void beginGame() {
//开始游戏时,得分归零
score = 0;
//每次开始游戏是读取文件,获得历史最高分
readFile();
//获得新的食物坐标
food.newFood(snake.getFoodPoint());
//开始蛇驱动的线程
snake.start();
//开启主窗体界面刷新的线程,用来更新分数
new Thread(thread).start();
}
//开始新游戏
public void newGame() {
//开始新游戏后,清除蛇的身体
snake.bodyClear();
//重新初始化蛇
snake.init();
//得分归零
score = 0;
//获得新食物坐标
food.newFood(snake.getFoodPoint());
/*
* 判断蛇是否处于死亡状态,如果是,
* 则在蛇驱动中已经跳出循环,不会触发蛇的监听
* 此时再开始调用开始游戏,重新初始化游戏,重新监听蛇运动
*
* 如果蛇不是死亡状态,则不执行开始游戏初始化,此时蛇处于正常监听状态
* 只重新初始化蛇和食物,分数即可开始新游戏。
*/
if (snake.isDie) {
beginGame();
snake.isDie = false;
}
}
//读文件,获取历史最高分
public void readFile(){
File file = new File("MaxScore.txt");
//如果文件不存在,文件输出流会自动创建文件
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//读取文件
BufferedReader br;
try {
br = new BufferedReader(
new InputStreamReader(
new FileInputStream(file), "UTF-8"));
maxScore = br.read();
br.close();
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void writeMaxScore() {
if (score > maxScore) {
maxScore = score;
writeFile();
}
}
public void writeFile() {
File file = new File("MaxScore.txt");
//如果文件不存在,文件输出流会自动创建文件
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//写文件
try {
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(file), "UTF-8"));
bw.write(maxScore);//向文件写入最高分
bw.close();//关闭流
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//接收主窗体中刷新界面的线程
public Thread startRefresh(Thread thread) {
this.thread = thread;
return this.thread;
}
}
package snake.util;
public class Global {
//定义格子大小
public static final int CELL_SIZE = 20;
//定义边界的宽度
public static final int WIDTH = 40;
//定义边界的高度
public static final int HEIGHT = 25;
//记录游戏界面总共有多少坐标
public static final int count = WIDTH * HEIGHT;
}
package snake.view;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.ButtonGroup;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import snake.controller.Controller;
import snake.entities.Food;
import snake.entities.Ground;
import snake.entities.Snake;
import snake.util.Global;
/*
* 这些代码基本都在窗体直接涉及而成,所有代码基本可以忽略不看。
* 只看有注释的关键地方即可。
* 在设计中,除了让GamePanel获得焦点,其他组件都不能获得焦点。
*/
@SuppressWarnings("serial")
public class MainWindow extends JFrame{
protected static final Object SnakeListener = null;
private JPanel contentPane;
Snake snake = new Snake();
Food food = new Food();
Ground ground = new Ground();
public JTextField txt_score;
private JTextField txt_speed;
private JTextField txt_maxScore;
GamePanel gamePanel = new GamePanel();
Controller controller = new Controller(snake, food, ground, gamePanel);
public MainWindow() {
setResizable(false);
setTitle("\u8D2A\u5403\u86C7");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//让窗体居中
setLocation(getToolkit().getScreenSize().width / 2 - Global.CELL_SIZE * Global.WIDTH / 2,
getToolkit().getScreenSize().height / 2 - Global.CELL_SIZE * Global.WIDTH / 2);
setSize(821, 760);
addKeyListener(controller);
contentPane = new JPanel();
contentPane.setFocusCycleRoot(true);
contentPane.setFocusTraversalPolicyProvider(true);
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
JPanel panel = new JPanel();
panel.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
panel.setFocusCycleRoot(true);
panel.setFocusTraversalPolicyProvider(true);
gamePanel.setFocusTraversalPolicyProvider(true);
gamePanel.setFocusCycleRoot(true);
gamePanel.setSize(Global.CELL_SIZE * Global.WIDTH, Global.CELL_SIZE * Global.HEIGHT);
gamePanel.setLayout(new BorderLayout(0, 0));
GroupLayout gl_panel = new GroupLayout(panel);
gl_panel.setHorizontalGroup(
gl_panel.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel.createSequentialGroup()
.addComponent(gamePanel, GroupLayout.PREFERRED_SIZE, 800, GroupLayout.PREFERRED_SIZE)
.addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
gl_panel.setVerticalGroup(
gl_panel.createParallelGroup(Alignment.LEADING)
.addComponent(gamePanel, GroupLayout.DEFAULT_SIZE, 505, Short.MAX_VALUE)
);
panel.setLayout(gl_panel);
JPanel panel_1 = new JPanel();
panel_1.setFocusable(false);
GroupLayout gl_contentPane = new GroupLayout(contentPane);
gl_contentPane.setHorizontalGroup(
gl_contentPane.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_contentPane.createSequentialGroup()
.addComponent(panel, GroupLayout.PREFERRED_SIZE, 801, Short.MAX_VALUE)
.addGap(10))
.addGroup(Alignment.LEADING, gl_contentPane.createSequentialGroup()
.addComponent(panel_1, GroupLayout.PREFERRED_SIZE, 795, Short.MAX_VALUE)
.addContainerGap())
);
gl_contentPane.setVerticalGroup(
gl_contentPane.createParallelGroup(Alignment.LEADING)
.addGroup(gl_contentPane.createSequentialGroup()
.addComponent(panel, GroupLayout.PREFERRED_SIZE, 505, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(panel_1, GroupLayout.PREFERRED_SIZE, 205, Short.MAX_VALUE)
.addContainerGap())
);
JPanel lable = new JPanel();
lable.setFocusable(false);
lable.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
JPanel panel_control = new JPanel();
panel_control.setFocusable(false);
panel_control.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
JPanel panel_set = new JPanel();
panel_set.setFocusable(false);
panel_set.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
JPanel panel_display = new JPanel();
panel_display.setFocusable(false);
panel_display.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
GroupLayout gl_panel_1 = new GroupLayout(panel_1);
gl_panel_1.setHorizontalGroup(
gl_panel_1.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_1.createSequentialGroup()
.addComponent(panel_set, GroupLayout.PREFERRED_SIZE,
302, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(gl_panel_1.createParallelGroup(Alignment.TRAILING)
.addComponent(panel_display, GroupLayout.PREFERRED_SIZE,
216, GroupLayout.PREFERRED_SIZE)
.addComponent(panel_control, 0, 0, Short.MAX_VALUE))
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(lable, GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addGap(19))
);
gl_panel_1.setVerticalGroup(
gl_panel_1.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_panel_1.createSequentialGroup()
.addGroup(gl_panel_1.createParallelGroup(Alignment.LEADING)
.addComponent(panel_set,
GroupLayout.DEFAULT_SIZE, 197, Short.MAX_VALUE)
.addGroup(gl_panel_1.createSequentialGroup()
.addComponent(panel_display,
GroupLayout.PREFERRED_SIZE, 123, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(panel_control,
GroupLayout.DEFAULT_SIZE, 68, Short.MAX_VALUE))
.addComponent(lable, GroupLayout.DEFAULT_SIZE, 195, Short.MAX_VALUE))
.addContainerGap())
);
JLabel lable_score = new JLabel("\u5F53\u524D\u5F97\u5206\uFF1A");
lable_score.setFocusable(false);
lable_score.setHorizontalAlignment(SwingConstants.LEFT);
lable_score.setHorizontalTextPosition(SwingConstants.CENTER);
lable_score.setAlignmentX(Component.CENTER_ALIGNMENT);
txt_score = new JTextField();
txt_score.setText("0 分");
txt_score.setEditable(false);
txt_score.setFocusable(false);
txt_score.setColumns(10);
JLabel label_maxScore = new JLabel("\u5386\u53F2\u6700\u9AD8\u5206\uFF1A");
label_maxScore.setFocusable(false);
txt_maxScore = new JTextField();
txt_maxScore.setText(controller.maxScore + " 分");
txt_maxScore.setEditable(false);
txt_maxScore.setFocusable(false);
txt_maxScore.setColumns(10);
JLabel label_speed = new JLabel("\u5F53\u524D\u901F\u5EA6\uFF1A");
label_speed.setFocusable(false);
txt_speed = new JTextField();
txt_speed.setText(snake.speed + " 毫秒 / 格");
txt_speed.setEditable(false);
txt_speed.setFocusable(false);
lable_score.setLabelFor(txt_speed);
txt_speed.setColumns(10);
GroupLayout gl_panel_display = new GroupLayout(panel_display);
gl_panel_display.setHorizontalGroup(
gl_panel_display.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_display.createSequentialGroup()
.addGap(6)
.addGroup(gl_panel_display.createParallelGroup(Alignment.LEADING)
.addComponent(lable_score,
GroupLayout.PREFERRED_SIZE, 75, GroupLayout.PREFERRED_SIZE)
.addComponent(label_maxScore,
GroupLayout.PREFERRED_SIZE, 78, GroupLayout.PREFERRED_SIZE)
.addComponent(label_speed,
GroupLayout.DEFAULT_SIZE, 83, Short.MAX_VALUE))
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(gl_panel_display.createParallelGroup(Alignment.LEADING, false)
.addComponent(txt_maxScore,
GroupLayout.DEFAULT_SIZE, 111, Short.MAX_VALUE)
.addComponent(txt_speed,
GroupLayout.DEFAULT_SIZE, 111, Short.MAX_VALUE)
.addComponent(txt_score))
.addContainerGap(26, Short.MAX_VALUE))
);
gl_panel_display.setVerticalGroup(
gl_panel_display.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_display.createSequentialGroup()
.addContainerGap(24, Short.MAX_VALUE)
.addGroup(gl_panel_display.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_panel_display.createSequentialGroup()
.addComponent(txt_score, GroupLayout.PREFERRED_SIZE,
21, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.UNRELATED)
.addComponent(txt_maxScore, GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(txt_speed, GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addGap(16))
.addGroup(gl_panel_display.createSequentialGroup()
.addComponent(lable_score, GroupLayout.PREFERRED_SIZE,
18, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.UNRELATED)
.addComponent(label_maxScore, GroupLayout.PREFERRED_SIZE,
25, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(label_speed)
.addGap(21))))
);
panel_display.setLayout(gl_panel_display);
JLabel label_set = new JLabel("\u8BBE\u7F6E\u9879\uFF1A");
label_set.setFocusable(false);
JSeparator separator = new JSeparator();
JCheckBox checkBox_isGriding = new JCheckBox("\u663E\u793A\u7F51\u683C");
checkBox_isGriding.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (checkBox_isGriding.isSelected()) {
ground.drawGriding();
}else {
ground.notDrawGriding();
}
}
});
checkBox_isGriding.setFocusable(false);
JSeparator separator_1 = new JSeparator();
JLabel label_isGriding = new JLabel("\u662F\u5426\u663E\u793A\u7F51\u683C\uFF1A");
label_isGriding.setFocusable(false);
JSeparator separator_2 = new JSeparator();
JPanel panel_setMap = new JPanel();
panel_setMap.setFocusable(false);
JPanel panel_setSpeed = new JPanel();
panel_setSpeed.setFocusable(false);
panel_setSpeed.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
GroupLayout gl_panel_set = new GroupLayout(panel_set);
gl_panel_set.setHorizontalGroup(
gl_panel_set.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_set.createSequentialGroup()
.addGroup(gl_panel_set.createParallelGroup(Alignment.LEADING, false)
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(10)
.addComponent(label_set))
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(20)
.addComponent(label_isGriding)
.addPreferredGap(ComponentPlacement.UNRELATED)
.addComponent(checkBox_isGriding))
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(20)
.addComponent(separator_1, GroupLayout.PREFERRED_SIZE,
222, GroupLayout.PREFERRED_SIZE))
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(20)
.addComponent(separator_2, GroupLayout.PREFERRED_SIZE,
224, GroupLayout.PREFERRED_SIZE))
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(14)
.addComponent(panel_setSpeed, GroupLayout.PREFERRED_SIZE,
264, GroupLayout.PREFERRED_SIZE))
.addGroup(gl_panel_set.createSequentialGroup()
.addContainerGap()
.addComponent(panel_setMap, GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
.addGap(10))
.addGroup(gl_panel_set.createSequentialGroup()
.addContainerGap()
.addComponent(separator, GroupLayout.DEFAULT_SIZE, 272, Short.MAX_VALUE)
.addContainerGap())
);
gl_panel_set.setVerticalGroup(
gl_panel_set.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_set.createSequentialGroup()
.addGap(10)
.addComponent(label_set)
.addGap(10)
.addComponent(separator, GroupLayout.PREFERRED_SIZE, 8, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(gl_panel_set.createParallelGroup(Alignment.BASELINE)
.addComponent(label_isGriding)
.addComponent(checkBox_isGriding))
.addGap(12)
.addComponent(separator_2, GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(panel_setMap, GroupLayout.PREFERRED_SIZE,
37, GroupLayout.PREFERRED_SIZE)
.addGap(12)
.addComponent(separator_1, GroupLayout.PREFERRED_SIZE,
2, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(panel_setSpeed, GroupLayout.DEFAULT_SIZE,
34, Short.MAX_VALUE)
.addGap(14))
);
JLabel label_5 = new JLabel("\u9009\u62E9\u96BE\u5EA6\uFF1A");
label_5.setFocusable(false);
JRadioButton radioButton_speed1 = new JRadioButton("\u521D\u7EA7");
radioButton_speed1.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
snake.speed = 500;
txt_speed.setText(snake.speed + " 毫秒 / 格");
}
});
radioButton_speed1.setSelected(true);
radioButton_speed1.setFocusable(false);
JRadioButton radioButton_speed2 = new JRadioButton("\u4E2D\u7EA7");
radioButton_speed2.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
snake.speed = 300;
txt_speed.setText(snake.speed + " 毫秒 / 格");
}
});
radioButton_speed2.setFocusable(false);
JRadioButton radioButton_speed3 = new JRadioButton("\u9AD8\u7EA7");
radioButton_speed3.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
snake.speed = 100;
txt_speed.setText(snake.speed + " 毫秒 / 格");
}
});
radioButton_speed3.setFocusable(false);
ButtonGroup groupSpeed = new ButtonGroup();
groupSpeed.add(radioButton_speed1);
groupSpeed.add(radioButton_speed2);
groupSpeed.add(radioButton_speed3);
panel_setSpeed.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
panel_setSpeed.add(label_5);
panel_setSpeed.add(radioButton_speed1);
panel_setSpeed.add(radioButton_speed2);
panel_setSpeed.add(radioButton_speed3);
JLabel label_setMap = new JLabel("\u9009\u62E9\u5730\u56FE\uFF1A");
label_setMap.setFocusable(false);
JRadioButton radioButton_map1 = new JRadioButton("\u5730\u56FE1");
radioButton_map1.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
ground.MAP = 1;
ground.init();
}
});
radioButton_map1.setSelected(true);
radioButton_map1.setFocusable(false);
JRadioButton radioButton_map2 = new JRadioButton("\u5730\u56FE2");
radioButton_map2.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
//点击鼠标后,设置为地图2
ground.MAP = 2;
//重新初始化地图
ground.init();
}
});
radioButton_map2.setFocusable(false);
JRadioButton radioButton_map3 = new JRadioButton("\u968F\u673A\u5730\u56FE");
radioButton_map3.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
ground.MAP = 3;
ground.init();
}
});
radioButton_map3.setFocusable(false);
ButtonGroup groupMap = new ButtonGroup();
groupMap.add(radioButton_map1);
groupMap.add(radioButton_map2);
groupMap.add(radioButton_map3);
panel_setMap.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
panel_setMap.add(label_setMap);
panel_setMap.add(radioButton_map1);
panel_setMap.add(radioButton_map2);
panel_setMap.add(radioButton_map3);
panel_set.setLayout(gl_panel_set);
JButton button_pause = new JButton("\u5F00\u59CB/\u6682\u505C");
button_pause.setFocusable(false);
button_pause.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
snake.changePause();
}
});
button_pause.setFocusPainted(false);
JButton button_newGame = new JButton("\u5F00\u59CB\u65B0\u6E38\u620F");
button_newGame.setFocusable(false);
button_newGame.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
controller.newGame();
}
});
button_newGame.setFocusPainted(false);
GroupLayout gl_panel_control = new GroupLayout(panel_control);
gl_panel_control.setHorizontalGroup(
gl_panel_control.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_panel_control.createSequentialGroup()
.addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(button_newGame, GroupLayout.PREFERRED_SIZE,
95, GroupLayout.PREFERRED_SIZE)
.addGap(3)
.addComponent(button_pause, GroupLayout.PREFERRED_SIZE,
94, GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);
gl_panel_control.setVerticalGroup(
gl_panel_control.createParallelGroup(Alignment.TRAILING)
.addGroup(Alignment.LEADING, gl_panel_control.createSequentialGroup()
.addContainerGap()
.addGroup(gl_panel_control.createParallelGroup(Alignment.BASELINE)
.addComponent(button_newGame, GroupLayout.PREFERRED_SIZE,
44, GroupLayout.PREFERRED_SIZE)
.addComponent(button_pause, GroupLayout.PREFERRED_SIZE,
44, GroupLayout.PREFERRED_SIZE))
.addContainerGap(18, Short.MAX_VALUE))
);
panel_control.setLayout(gl_panel_control);
JLabel lblNewLabel = new JLabel("\u8BF4\u660E\uFF1A");
lblNewLabel.setFocusable(false);
lblNewLabel.setHorizontalAlignment(SwingConstants.CENTER);
lblNewLabel.setHorizontalTextPosition(SwingConstants.CENTER);
JLabel label = new JLabel("\u65B9\u5411\u952E\u64CD\u4F5C\u65B9\u5411");
label.setFocusable(false);
JLabel label_1 = new JLabel("\u7A7A\u683C\u952E\u53EF\u5B9E\u73B0\u6682\u505C/\u7EE7\u7EED");
label_1.setFocusable(false);
JLabel lblShift = new JLabel("Shift\u952E \u5F00\u59CB\u65B0\u6E38\u620F");
lblShift.setFocusable(false);
JLabel label_2 = new JLabel("\u968F\u673A\u5730\u56FE\u4F1A"
+ "\u968F\u673A\u83B7\u5F9740\u4E2A\u5750\u6807\u4F5C\u4E3A\u77F3\u5934");
label_2.setHorizontalAlignment(SwingConstants.LEFT);
label_2.setInheritsPopupMenu(false);
label_2.setFocusable(false);
label_2.setFocusTraversalKeysEnabled(false);
label_2.setAlignmentX(Component.CENTER_ALIGNMENT);
GroupLayout gl_lable = new GroupLayout(lable);
gl_lable.setHorizontalGroup(
gl_lable.createParallelGroup(Alignment.TRAILING)
.addGroup(gl_lable.createSequentialGroup()
.addGroup(gl_lable.createParallelGroup(Alignment.LEADING)
.addComponent(lblNewLabel, GroupLayout.PREFERRED_SIZE,
71, GroupLayout.PREFERRED_SIZE)
.addGroup(gl_lable.createSequentialGroup()
.addGap(26)
.addGroup(gl_lable.createParallelGroup(Alignment.LEADING)
.addComponent(label_1,
GroupLayout.DEFAULT_SIZE, 212, Short.MAX_VALUE)
.addComponent(label, GroupLayout.PREFERRED_SIZE,
113, GroupLayout.PREFERRED_SIZE)
.addComponent(lblShift)
.addComponent(label_2,
GroupLayout.DEFAULT_SIZE, 212, Short.MAX_VALUE))))
.addContainerGap())
);
gl_lable.setVerticalGroup(
gl_lable.createParallelGroup(Alignment.LEADING)
.addGroup(gl_lable.createSequentialGroup()
.addGap(8)
.addComponent(lblNewLabel, GroupLayout.PREFERRED_SIZE,
30, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(label, GroupLayout.PREFERRED_SIZE,
26, GroupLayout.PREFERRED_SIZE)
.addGap(2)
.addComponent(label_1, GroupLayout.PREFERRED_SIZE,
26, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(lblShift, GroupLayout.PREFERRED_SIZE,
25, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.UNRELATED)
.addComponent(label_2)
.addContainerGap(39, Short.MAX_VALUE))
);
lable.setLayout(gl_lable);
panel_1.setLayout(gl_panel_1);
contentPane.setLayout(gl_contentPane);
//给游戏面板和蛇添加监听器
gamePanel.addKeyListener(controller);
snake.addSnakeListener(controller);
//开始一个新的线程,用来更新分数
controller.startRefresh(new Thread(new refresh()));
//开始游戏
controller.beginGame();
}
//创建一个线程让一直刷新分数
public class refresh implements Runnable{
@Override
public void run() {
//当蛇活着的时候才进行循环
while(!snake.isDie) {
txt_score.setText(controller.score + " 分");
txt_maxScore.setText(controller.maxScore + " 分");
try {
Thread.sleep(snake.speed);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
package snake.game;
import java.awt.EventQueue;
import snake.view.MainWindow;
/*
* 作为游戏的主方法,启动游戏,通过启动窗体,实现启动程序
*/
public class Game {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MainWindow frame = new MainWindow();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
游戏是否显示网格,可通过是否选中来设置。包括选择地图,选择速度。所有设置后在开始新游戏后生效。速度在游戏过程中也可以随时调节。效果如下图:
在蛇每次吃到食物后,分数会加10分,并实时更新分数。以及有历史最高分。包括蛇的速度也会显示。
在边界没有石头时,蛇可以从一边进去,从另外一边出来。
蛇吃到石头后就会死亡,并弹出提框,同时显示分数。
蛇咬到自己也会死亡,弹出提示框。
可点击下载原代码包。