贪吃蛇的Java实现
第一次写贪吃蛇游戏,重要在算法和联系效果,界面调用了swing包的绘图方法。这里记录下来,不足的地方请大家批评指正。
首先是需求分析,对于贪吃蛇,总共有如下几点需求:
- 蛇会在舞台上定时自动运行
- 用键盘光标键控制蛇的运行
- 蛇不能反向运行,反向运行没有动作发生
- 蛇吃到食物后长度会增加一节
- 食物被吃掉之后会随机生成新的食物
- 蛇碰到边界或吃到自己会死亡
具体分析如下图:

结合以上分析,可以将需求提取成3个类
- 食物节点类:充当食物,组成蛇的身体
- 蛇类:主要角色,承载众多属性和方法
- 舞台类:用于绘制舍、食物、定时及键盘响应等
从以上3个类出发,进一步梳理确定他们的属性及方法,如下:
其中,test类是为了测试写好的代码片。具体的方法是我写完程序之后再添加上去的,在编程之前只能确定好大体的方向。
运行效果:
下面直接贴上代码片:
Cell类
package com.centerm.snake;
/**
*
* 创建Cell类,作为蛇身体的组成部分,也用作充当食物
*
*/
public class Cell {
private int x;
private int y;
public Cell(){
}
public Cell(int x, int y){
this.x = x;
this.y = y;
}
public int getX(){
return this.x;
}
public int getY(){
return this.y;
}
public String toString(){
return "["+this.x+","+this.y+"]";`
}
}
-------------------
Snake类
package com.centerm.snake;
import java.util.Arrays;
/**
*
* Snake类,作为蛇体,包含了众多元素和方法
*
*/
public class Snake {
public static final int DEFAULT_LENGTH = ;
public static final int SNAKE_UP = ;
public static final int SNAKE_DOWN = -;
public static final int SNAKE_LEFT = ;
public static final int SNAKE_RIGHT = -;
private Cell[] cells;
private int currentDirection;
/** 在构造函数中初始化一条默认的蛇 ,长度为12,位于左上方,默认方向向下*/
public Snake(){
cells = new Cell[DEFAULT_LENGTH];
for(int i = ; i<cells.length; i++){
cells[i] = new Cell(i,);
}
currentDirection = SNAKE_DOWN;
}
/** 包含方法 */
public boolean contains(int x, int y){
for(int i = ; i<cells.length; i++){
if(cells[i].getX() == x && cells[i].getY() == y){
return true;
}
}
return false;
}
/** 创建预测节点,在还没有吃时,创建某方向上即将吃到的节点,服务于其它方法 */
private Cell createHead(int direction){
int x = cells[].getX();
int y = cells[].getY();
switch (direction) {
case SNAKE_UP:
y -- ; break;
case SNAKE_DOWN:
y ++ ; break;
case SNAKE_LEFT:
x -- ; break;
case SNAKE_RIGHT:
x ++ ; break;
default:
break;
}
return new Cell(x,y);
}
/** 沿某个方向爬行,并判断有没有吃到食物,若有,则增长一节 */
/** 先创建方向上的预测节点,若与食物不重合,则移动,若与食物重合
* 则增加长度,且预测节点可以当作食物增加到节点0中 */
public boolean creep(int direction, Cell food){
if(currentDirection+direction == ){
return false;
}
Cell cell = createHead(direction);
boolean eaten = cell.getX() == food.getX() &&
cell.getY() == food.getY();
if(eaten){
cells = Arrays.copyOf(cells, cells.length+);
}
for(int i = cells.length-; i>=; i--){
cells[i] = cells[i-];
}
cells[] = cell;
currentDirection = direction;
return eaten;
}
/** 沿当前方向爬行,自动运行时可以调用 */
public boolean creep(Cell food){
return creep(currentDirection, food);
}
/** 撞击判断,预测下一步是否越界或者吃到自己 */
public boolean hit(int direction){
if(currentDirection+direction == ){
return false;
}
Cell cell = createHead(direction);
boolean wrong = cell.getX() < || cell.getX() >= SnakeStage.COLS ||
cell.getY() < || cell.getY() >= SnakeStage.ROWS;
if(wrong){
return true;
}
for(int i = ; i<cells.length-; i++){
if(cell.getX() == cells[i].getX() &&
cell.getY() == cells[i].getY()){
return true;
}
}
return false;
}
/** 判断在当前方向上走会不会撞到边界,自动运行时用 */
public boolean hit(){
return hit(currentDirection);
}
public Cell[] getCells() {
return cells;
}
/** 测试用,获取当前方向 */
public int getCurrentDirection(){
return currentDirection;
}
/
** 测试用,用于打印当前蛇体 */
public String toString(){
return Arrays.toString(cells);
}
}
-------------------
SnakeStage类
package com.centerm.snake;
/**
*
* SnakeStage类,舞台,实现绘制和管理作用
*
*/
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
public class SnakeStage extends JPanel{
public static final int COLS = ;
public static final int ROWS = ;
private static final int CELL_SIZE = ;
private Snake snake;
private Cell food;
/** 构造函数 ,初始化蛇体和食物 */
public SnakeStage(){
snake = new Snake();
food = createFood();
}
/** 创建食物,随机创建一个与蛇体不重复的食物 */
private Cell createFood(){
Random random = new Random();
int x ;
int y ;
do{
x = random.nextInt(COLS);
y = random.nextInt(ROWS);
}while(snake.contains(x, y));
return new Cell(x, y);
}
/** 重写JPanel中的paint()函数,画出蛇和食物,之后后台会一直调用这个函数 */
public void paint(Graphics g){
//填充背景色
g.setColor(Color.darkGray);
g.fillRect(, , this.getWidth(), this.getHeight());
//绘制食物
g.setColor(Color.red);
g.fill3DRect(CELL_SIZE*food.getX(), CELL_SIZE*food.getY(),
CELL_SIZE, CELL_SIZE, true);
//绘制蛇
g.setColor(Color.yellow);
Cell[] cells = snake.getCells();
for(int i = ; i<cells.length; i++){
g.fill3DRect(CELL_SIZE*cells[i].getX(),
CELL_SIZE*cells[i].getY(),
CELL_SIZE, CELL_SIZE, true);
}
}
/** 开启贪吃蛇,使用定时器实现自动爬行,使用键盘响应方法实现控制方向爬行 */
public void action(){
/* 使用定时器实现自动爬行 */
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
if(snake.hit()){
snake = new Snake();
food = createFood();
}else{
boolean eaten = snake.creep(food);
if(eaten){
food = createFood();
}
}
//每更新一次就要重绘一次,下面这个方法会尽快调用paint()
repaint();
}
}, , /);
/* 使用JPanel的键盘响应实现自动爬行 */
this.requestFocus(); //焦点来到面板
this.addKeyListener(new KeyAdapter(){ //创建键盘监听器
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()){
case KeyEvent.VK_UP:
creepTo(Snake.SNAKE_UP);
break;
case KeyEvent.VK_DOWN:
creepTo(Snake.SNAKE_DOWN);
break;
case KeyEvent.VK_LEFT:
creepTo(Snake.SNAKE_LEFT);
break;
case KeyEvent.VK_RIGHT:
creepTo(Snake.SNAKE_RIGHT);
break;
default:
break;
}
}
});
}
/** 这个方法本来应该写在键盘判断里面,但是写在每个case都要写一遍,这里就提出来 */
private void creepTo(int direction){
if(snake.hit(direction)){
snake = new Snake();
food = createFood();
}else{
if(snake.creep(direction,food)){
food = createFood();
}
}
//响应完按键马上重绘
repaint();
}
/** 程序入口 */
public static void main(String[] args){
JFrame frame = new JFrame("贪吃蛇"); //创建一个框架
SnakeStage panel = new SnakeStage();//创建一个面板
frame.setLayout(null); //取消框架默认设置
frame.add(panel); //在框架上添加面板
panel.setSize(CELL_SIZE*COLS, CELL_SIZE*ROWS);//设置尺寸
panel.setLocation(, ); //设置面板在框架中的问题
frame.setSize(CELL_SIZE*COLS+, CELL_SIZE*ROWS+);//设置尺寸
panel.setBorder(new LineBorder(Color.black)); //设置面板边界
frame.setLocationRelativeTo(null); //使框架在屏幕居中显示
frame.setVisible(true); //将框架和面板显示出来
frame.setDefaultCloseOperation(JFrame.
EXIT_ON_CLOSE);//退出使能
panel.action(); //启动贪吃蛇
}
/** 调试用 */
public String toString(){
return "{"+snake+"}"+"\n"+food;
}
}