Java 贪食蛇

概要:

本实例用Applet实现4种级别的玩法,可以通过方向键控制蛇的运动来靠近前面的食物并吃掉食物,当碰到墙壁时作为游戏失败。

| |目录

技术要点

实现贪食蛇的技术要点如下:

  1. 实现4个级别,分别为PRIMARY(初级)、INTERMEDIATE(中级)、SENIOR(高级)和EXTRA(特级),选择级别进入游戏界面。

  2. 方向键用来控制蛇的运动。小蛇向着食物的方向前进,如果不小心碰到墙壁则游戏结束,上方显示本次的成绩(得分)。小蛇本身在直行时由小段组成,碰到拐弯变成两段,每段由黑色的方格组成。

代码实现

package net.xsoftlab.baike;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
class Move {// 小蛇或食物位置(方位)移动
	int move_X;
	int move_Y;
	Move(int move_X, int move_Y) {// 带参数构造方法进行初始化
		this.move_X = move_X;
		this.move_Y = move_Y;
	}
}
class MoveOperate extends Observable implements Runnable {// 小蛇和食物移动操作的类
	public static final int LEFT = 1;// 小蛇向左移动的标识
	public static final int UP = 2;// 小蛇向上运动的标识
	public static final int RIGHT = 3;// 小蛇向右运动的标识
	public static final int DOWN = 4;// 小蛇向下运动的标识
	private boolean[][] isHave; // 指示位置上是否有小蛇或食物
	private LinkedList snake = new LinkedList();// 声明小蛇的双链表集合
	private Move aliment; // 食物
	private int move_Direction = LEFT;// 小蛇移动的方向向左
	private boolean running = false; // 运行状态
	private int timeSpace = 300; // 时间间隔
	private double speedChange = 0.75; // 每次的速度变化率
	private boolean paused = false; // 暂停标志
	private int score = 0; // 得分
	private int moveCount = 0; // 吃到食物前移动的次数
	private int X;// 横坐标
	private int Y;// 纵坐标
	public MoveOperate(int X, int Y) {// 带参数构造方法进行初始化
		this.X = X;
		this.Y = Y;
		resetGame();
	}
	public void run() {// 实现Runnable接口必须实现的方法
		running = true;// 标识设置为真
		while (running) {// 根据标识进行循环
			try {
				Thread.sleep(timeSpace);// 休眠0.3秒
			} catch (Exception e) {
				System.out.println("休眠出错:" + e.getMessage());
				break;// 跳出循环
			}
			if (!paused) {// 小蛇正在移动时
				if (move()) {
					setChanged();// 更新界面数据
					notifyObservers();
				} else {// 弹出对话框显示游戏结束
					JOptionPane.showMessageDialog(null, "你失败了!", "Game Over",
							JOptionPane.INFORMATION_MESSAGE);
					break;// 跳出循环
				}
			}
		}
		if (!running) {// 小蛇暂停或停止
			JOptionPane.showMessageDialog(null, "你停止了游戏", "Game Over",
					JOptionPane.INFORMATION_MESSAGE);
		}
	}
	public void resetGame() {// 重置游戏
		move_Direction = MoveOperate.LEFT;// 小蛇移动的方向向左
		timeSpace = 300;// 时间间隔为0.3秒
		paused = false;// 不暂停
		score = 0;
		moveCount = 0;// 吃到食物前移动的次数为0
		isHave = new boolean[X][Y];// 初始化小蛇活动的范围
		for (int i = 0; i < X; i++) {
			isHave[i] = new boolean[Y];
			Arrays.fill(isHave[i], false);// 填充数据
		}
		int initLength = X > 20 ? 10 : X / 2;// 初始化小蛇,如果横向位置超过20个,长度为10,否则为横向位置的一半
		snake.clear();
		int x = X / 2;// 初始居中显示
		int y = Y / 2;
		for (int i = 0; i < initLength; i++) {
			snake.addLast(new Move(x, y));// 添加蛇移动的位置
			isHave[x][y] = true;
			x++;
		}
		aliment = createAliment();// 创建食物
		isHave[aliment.move_X][aliment.move_Y] = true;// 在指定位置上有蛇或食物
	}
	public boolean move() {// 蛇运行一步
		Move snakeHead = (Move) snake.getFirst();// 获得蛇头的位置
		int headX = snakeHead.move_X;// 获得蛇的横坐标
		int headY = snakeHead.move_Y;// 获得蛇的纵坐标
		switch (move_Direction) {// 分支循环判断蛇的运动
		case UP:// 向上移动
			headY--;// 纵坐标减1
			break;// 跳出支循环
		case DOWN:// 向下移动
			headY++;// 纵坐标加1
			break;
		case LEFT:// 向左移动
			headX--;// 横坐标减1
			break;
		case RIGHT:// 向上移动
			headX++;// 横坐标加1
			break;
		}
		if ((0 <= headX && headX < X) && (0 <= headY && headY < Y)) {
			if (isHave[headX][headY]) {// 如果指定位置有蛇或食物
				if (headX == aliment.move_X && headY == aliment.move_Y) {
					snake.addFirst(aliment);// 蛇头增长表示吃到食物
					int scoreGet = (10000 - 200 * moveCount) / timeSpace;// 分数与改变方向次数和速度有关
					score += scoreGet > 0 ? scoreGet : 10;
					moveCount = 0;
					aliment = createAliment(); // 创建新的食物
					isHave[aliment.move_X][aliment.move_Y] = true;// 设置食物所在位置
					return true;
				} else {
					return false;// 小蛇吃到自己
				}
			} else {
				snake.addFirst(new Move(headX, headY));// 添加小蛇的身体
				isHave[headX][headY] = true;
				snakeHead = (Move) snake.removeLast();// 获得蛇头信息
				isHave[snakeHead.move_X][snakeHead.move_Y] = false;
				moveCount++;
				return true;
			}
		}
		return false;// 碰到外壁失败
	}
	public void changeDirection(int dir) {// 改变蛇运动的方向
		if (move_Direction % 2 != dir % 2) {// 改变的方向不与原方向相同或相反
			move_Direction = dir;
		}
	}
	private Move createAliment() {// 创建食物
		int x = 0;
		int y = 0;
		do {
			Random r = new Random();// 随机获取位置
			x = r.nextInt(X);
			y = r.nextInt(Y);
		} while (isHave[x][y]);
		return new Move(x, y);// 返回食物的新位置
	}
	public void speedUp() {// 加速运行
		timeSpace *= speedChange;
	}
	public void speedDown() {// 减速运行
		timeSpace /= speedChange;
	}
	public void changePauseState() {// 改变暂停状态
		paused = !paused;
	}
	public boolean isRunning() {// 判断是否运动
		return running;
	}
	public void setRunning(boolean running) {// 设置运动标识
		this.running = running;
	}
	public LinkedList getMoveList() {// 获得链表数据(蛇的身体)
		return snake;
	}
	public Move getAliment() {// 获得食物
		return aliment;
	}
	public int getScore() {// 获得得分
		return score;
	}
}
class SnakeFrame extends JFrame implements Observer {// 贪吃蛇的视图,MVC中的View
	public static final int gridWidth = 10;// 格子的宽度
	public static final int gridHeight = 10;// 格子的高度
	private int gameWidth;// 画面的宽度
	private int gameHeight;// 画面的高度
	private int gameX = 0;// 画面左上角横位置
	private int startY = 0;// 画面左上角纵坐标
	JLabel score; // 声明分数标签
	Canvas canvas; // 声明画布
	public SnakeFrame() {// // 默认构造方法调用带参数的构造方法
		this(30, 40, 0, 0);
	}
	public SnakeFrame(int X, int Y) {// 带参数的构造方法进行初始化
		this(X, Y, 0, 0);
	}
	public SnakeFrame(int X, int Y, int startX, int startY) {// 带参数的构造方法进行初始化
		this.gameWidth = X * gridWidth;
		this.gameHeight = Y * gridHeight;
		this.gameX = startX;
		this.startY = startY;
		init();
	}
	private void init() {// 初始化游戏界面
		this.setTitle("贪食蛇");// 设置界面的标题
		this.setLocation(gameX, startY);// 设置界面的位置(方位)
		Container cp = this.getContentPane();// 获得一容器
		score = new JLabel("成绩:");
		cp.add(score, BorderLayout.SOUTH);// 将成绩放在界面的南侧
		canvas = new Canvas();// 创建中间的游戏显示区域
		canvas.setSize(gameWidth + 1, gameHeight + 1);// 设置区域的大小
		cp.add(canvas, BorderLayout.CENTER);// 区域放在界面的中间
		this.pack();// 根据组件的最优尺寸来进行布局
		this.setResizable(false);// 窗体不能最小化
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 设置默认关闭操作
		this.setVisible(true);// 可视
	}
	public void update(Observable observer, Object obj) {// 实现Observer接口定义的update方法
		MoveOperate operate = (MoveOperate) observer; // 获取被监控的模型
		Graphics graphics = canvas.getGraphics();// 获得画笔
		graphics.setColor(Color.WHITE);// 设置颜色为白色
		graphics.fillRect(0, 0, gameWidth, gameHeight);// 填充一个矩形
		graphics.setColor(Color.BLACK);// 重新设置颜色
		LinkedList list = operate.getMoveList();// 获得蛇体
		Iterator it = list.iterator();
		while (it.hasNext()) {
			Move move = (Move) it.next();
			drawMove(graphics, move);// 调用方法画方格
		}
		graphics.setColor(Color.RED);// 设置颜色表示食物
		Move move = operate.getAliment();// 获得食物
		drawMove(graphics, move);
		this.updateScore(operate.getScore());// 更新成绩
	}
	private void drawMove(Graphics g, Move n) {// 根据移动路径画格子
		g.fillRect(n.move_X * gridWidth, n.move_Y * gridHeight, gridWidth - 1,
				gridHeight - 1);// 填充一个矩形
	}
	public void updateScore(int score) {// 更新成绩
		String s = "成绩: " + score;
		this.score.setText(s);// 设置成绩标签的值
	}
}
class ControlSnake implements KeyListener {// 控制蛇的运动
	private MoveOperate snake; // 贪吃的小蛇模型
	private SnakeFrame frame; // 蛇的对象
	private int X;// 蛇移动区域横坐标
	private int Y;// 蛇移动区域纵坐标
	public ControlSnake() {// 默认构造方法进行初始化
		this.X = 30;
		this.Y = 40;
	}
	public ControlSnake(int X, int Y) {// 带参数的构造方法进行初始化
		this();
		if ((10 < X) && (X < 200) && (10 < Y) && (Y < 200)) {// 判断移动区域
			this.X = X;
			this.Y = Y;
		} else {
			System.out.println("初始化参数出错!");
		}
		initSnake();// 调用初始化方法
	}
	private void initSnake() {// 初始化
		this.snake = new MoveOperate(X, Y); // 创建蛇模型
		this.frame = new SnakeFrame(X, Y, 500, 200);
		this.snake.addObserver(this.frame); // 为模型添加对象
		this.frame.addKeyListener(this); // 添加键盘事件
		(new Thread(this.snake)).start();// 启动线程蛇运动
	}
	public void keyPressed(KeyEvent e) {// 键盘按下事件
		int keyCode = e.getKeyCode();
		if (snake.isRunning()) {// 只有在贪吃蛇处于运行状态下,才处理的按键事件
			switch (keyCode) {
			case KeyEvent.VK_ADD:// 按下数字键盘上的+
			case KeyEvent.VK_PAGE_UP:// 按下PageUp键
				snake.speedUp();
				break;
			case KeyEvent.VK_SUBTRACT:// 按下数字键盘上的-
			case KeyEvent.VK_PAGE_DOWN:// 按下PageDown
				snake.speedDown();
				break;
			case KeyEvent.VK_SPACE:// 按下空格键
			case KeyEvent.VK_P:// 按下PauseBreak
				snake.changePauseState();
				break;
			case KeyEvent.VK_UP:// 按下向上键
				snake.changeDirection(MoveOperate.UP);
				break;
			case KeyEvent.VK_DOWN:// 按下向下键
				snake.changeDirection(MoveOperate.DOWN);
				break;
			case KeyEvent.VK_LEFT:// 按下向左键
				snake.changeDirection(MoveOperate.LEFT);
				break;
			case KeyEvent.VK_RIGHT:// 按下向右键
				snake.changeDirection(MoveOperate.RIGHT);
				break;
			default:
			}
		}
		if (keyCode == KeyEvent.VK_F || keyCode == KeyEvent.VK_J
				|| keyCode == KeyEvent.VK_ENTER) {// 按下回车、F、J键开始游戏
			snake.resetGame();
		}
		if (keyCode == KeyEvent.VK_S) {// 按下S键停止游戏
			snake.setRunning(false);
		}
	}
	public void keyReleased(KeyEvent e) {// 键盘弹起事件
	}
	public void keyTyped(KeyEvent e) {// 有字符被输入事件
	}
}
public class EdaciousSnake {// 操作实现小蛇吃食物的操作
	public static void main(String[] args) {// java程序主入口处
		new ControlSnake(40, 30);// 实例化对象
	}
}

程序解读

  1. Move类设置小蛇或食物的位置,其中move_X表示小蛇或食物所在的横坐标,move_Y表示小蛇或食物所在的纵坐标。

  2. MoveOperate类实现Runnable接口,实现接口必须实现接口的run()方法,继承Observable类表时该类是一个被观察的对象,当它有任何修改操作时都在别人的观察控制中。声明isHave二维布尔数组来存储游戏区域各个位置的状态,如果数值为真则表明在该位置上有小蛇的身体或食物,否则没有。声明snake来存储蛇身体的各个部分,由于蛇身和蛇尾都要操作,而LinkedList是双向链表,在操作数据的头和尾时速度相对其他的集合都是比较快的。声明move_Direction表明小蛇初始是向左移动。声明timeSpace来控制小蛇移动的间隔时间,时间越小,小蛇移动的越快。声明running标识小蛇是否在移动。声明moveCount来获得小蛇在吃到食物前移动的次数,移动的次数越小,则分数越高。声明四个静态变量来操作小蛇移动的方向。

  3. MoveOperate类的run()方法根据标识为真进行循环判断小蛇的状态,如果小蛇正在移动,那么调用Observable类的setChanged()方法标识被观察者对象发生改变,调用Observable类的notifyObservers()方法通知该对象的观察者,否则弹出显示游戏结束的对话框。如果小蛇暂停或停止,则弹出停止游戏的对话框。

  4. MoveOperate类的resetGame()方法重置游戏,初始化小蛇移动的方向、时间间隔、在吃到食物之前移动的次数以及初始化蛇的身体和食物等。循环中Array.fill()方法将数组的元素统一赋值为false。move()方法是小蛇移动一步。通过双向链表集合LinkedList的getFirst()方法取得小蛇的蛇头的信息,包括其所在的横坐标和纵坐标。可以用来修改小蛇头的坐标,如果修改后的坐标落在游戏区域内,而且该位置上没有蛇的身体,则允许移动,将小蛇的头的新坐标构建一个新的对象,通过LinkedList类的addFirst()方法可以将新小蛇头添加到蛇的身体中,通过LinkedList类的removeLast()方法去掉小蛇的尾部,小蛇的长度保持不变,整体前进一步。如果修改后的蛇头位置上有食物,则吃掉食物,把食物位置添加到小蛇的头部,小蛇的尾部保持不变,可以看作是小蛇的身体长度加1,同时增加成绩值。成绩值与小蛇移动的次数和移动的频率有关。在计算成绩时,随机创建一个新的食物。

  5. MoveOperate类的changeDirection()方法改变小蛇运动的方向,如果小蛇的前进方向与被改变的方向不在同一直线上,才改变小蛇前进的方向。createAliment()方法在游戏区域随机生成一个食物类,Random类的nextInt()方法生成一个随机的整数作为坐标,如果坐标上没有小蛇的身体,则将该坐标上放食物,否则继续生成新的坐标,直到新的食物生成。

  6. SnakeFrame类继承JFrame类实现Observer接口,实现该接口须实现update()方法,表明该类是一个观察者对象,可以观察别人的一切变化情况。SnakeFrame类的构造方法中初始化格子的宽度和高度、画面的宽度和高度。init()方法初始化一个游戏界面。设置界面的标题和初始位置,创建一个显示分数的标签,创建一个容器用来放置小蛇和食物。

  7. SnakeFrame类的update()方法实现了Observer接口,作为一个观察者对象,当观察的对象发生变化或改变时,会调用该方法。即当被观察者MoveOperate对象调用notifyObservers()方法时,观察者SnakeFrame对象将会收到信息,并且调用update()方法做相应的修改处理。其从方法的参数中获得被观察者对象,转换成MoveOperate对象。通过Canvas类的getGraphics()方法获得画板的上下文环境Graphics,Graphics的fillRect()方法画一个矩形,表示游戏的区域,通过MoveOperate类的getMoveList()和getAliment方法获得小蛇和食物的数据,实质上是一组Move对象,调用drawNode()方法画Move。

  8. SnakeFrame类的drawMove()方法画Move对象,一个Move表示一个坐标,赋一定宽度和高度可以画一个矩形,使用Graphics类的fillRect()方法可以画一个矩形。

  9. ControlSnake类实现KeyListener接口来处理键盘事件必须实现keyPressed()方法、keyReleased()方法和keyTyped()方法。initSnake()方法初始化游戏界面和小蛇以及食物。创建MoveOperate对象和SnakeFrame对象,再通过addObserver()方法将对象SnakeFrame添加到MoveOperate的观察者中。调用addKeyListener()方法为SnakeFrame添加键盘监听器,用于处理键盘事件。启动小蛇游戏的线程使小蛇移动起来。

  10. ControlSnake类的keyPressed()方法根据参数KeyEvent获得被按下键盘的码,不同的码调用不同的方法实现不同的操作。


评论关闭
评论 还能输入200
评论关闭
评论 还能输入200
  • 全部评论(0)
资料加载中...
已关注 , 取消