贪吃蛇(java实现)

贪吃蛇java实现

图形界面在搜索引擎搜了许久…看上去依然一脸懵逼,完全不知道该如何修改,无奈只好跑到某站看了一下swing相关的知识,有了大概的了解,再参考搜到的,修改成了如下的样子,还有一些问题没有解决

比如:我限制了键盘操作的间隔时间 200ms,因为我发现当蛇移动的过慢,而操作转向过快时,就会出现逆行的bug; 比如当前正在向上走,我可以在蛇一次移动中,先操作向左,然后向下,出现逆向行走的 错误逻辑;但是限制了操作时间,又会影响到体验,暂时没有想出很好的解决方案

package snake_game;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.LinkedList;
import java.util.Objects;

import javax.swing.JFrame;
import javax.swing.JPanel;
/**
 * 
 * @Description 不足之处 请多多指教
 * @qq 59663479 
 * @E-mail 59663479@qq.com
 * @Date 2019年9月8日 下午1:58:40
 */
public class SnakeGame extends JPanel implements KeyListener {
	/**
	 * 每秒移动(1000/moveSpeed)次
	 */
	public static int moveSpeed = 200;
	/**
	 * 每次操作 间隔 200ms
	 */
	int runTime = 200;

	/**
	 * 主程
	 */
	public static void main(String[] args) throws InterruptedException {
		while (true) {
			run();
			jd.repaint();
			Thread.sleep(300);
		}
	}

	/**
	 * 格子大小 30像素
	 */
	public static final int SIZE = 30;
	/**
	 * 行 (高)
	 */
	public static final int HANG = 15;
	/**
	 * 列 (宽)
	 */
	public static final int LIE = 20;
	/**
	 * 地图
	 */
	public static boolean[][] map = new boolean[HANG][LIE];
	/**
	 * 获取蛇对象,方便操作
	 */
	private static LinkedList<Point> snake = Snake.snake;
	/**
	 * 游戏状态,true表示结束
	 */
	private static boolean isGameOver = false;

	/**
	 * 将游戏 设置为 结束 状态为 true
	 */
	public static void updateIsGameOver() {
		isGameOver = true;
	}

	/**
	 * 游戏状态,true表示结束
	 */
	public static boolean getIsGameOver() {
		return isGameOver;
	}

	/**
	 * 初始化 map,将整个map容器的 四周全部设置为 true
	 */
	static {
		for (int i = 0; i < map.length; i++) {
			map[i][0] = true;
			map[i][map[0].length - 1] = true;
			for (int j = 0; j < map[0].length; j++) {
				if (i == 0) {
					map[0][j] = true;
				}
				if (i == map.length - 1) {
					map[map.length - 1][j] = true;
				}
			}
		}
	}

	/**
	 * 初始化 map,将整个map容器的 四周全部设置为 true,重新开始使用
	 */
	public static void initMap() {
		for (int i = 0; i < map.length; i++) {
			for (int j = 0; j < map[0].length; j++) {
				map[i][j] = false;
			}
		}
		for (int i = 0; i < map.length; i++) {
			map[i][0] = true;
			map[i][map[0].length - 1] = true;
			for (int j = 0; j < map[0].length; j++) {
				if (i == 0) {
					map[0][j] = true;
				}
				if (i == map.length - 1) {
					map[map.length - 1][j] = true;
				}
			}
		}
	}

	/**
	 * 一开始就加载窗口,使JpanelDemo静态化,用来刷新窗口
	 */
	static SnakeGame jd;
	static {
		// 创建主窗口
		JFrame jf = new JFrame("贪吃蛇");
		// 设置窗口的 宽高
		// 注意:前两个参数是 主窗口的位置, 第三个参数是宽,第四个参数是高
		// 而我们使用的二维数组map[][],则是前面的[]表示高,后面的[]表示宽+5和+25是因为 窗口有边界;
		jf.setBounds(300, 300, SIZE * LIE + 5, SIZE * HANG + 25);
		// 创建面板,将 面板添加到窗体容器
		jd = new SnakeGame();
		jf.add(jd);
		// 关闭方式
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		// 窗体大小不可变
		jf.setResizable(false);
		// 添加 按键 监听到窗体容器
		jf.addKeyListener(jd);
		// 使 窗体可见
		jf.setVisible(true);
	}

	/**
	 * 重画 主面板,这个方法不能自己调用,需要调用 当前类对象.repaint();,让java帮我们调用
	 */
	@Override
	public void paint(Graphics g) {
		// 画窗口的矩形
		for (int i = 0; i < LIE; i++) {
			for (int j = 0; j < HANG; j++) {
				if (map[j][i]) {
					// 设置颜色
					g.setColor(Color.DARK_GRAY);
				} else {
					g.setColor(Color.GRAY);
				}
				g.fill3DRect(i * SIZE, j * SIZE, SIZE, SIZE, true);
			}
		}
		// 画蛇的位置,先蛇身,后蛇头;原因:如果蛇头撞到蛇身了,蛇头不会被覆盖
		g.setColor(Color.BLUE);
		for (int i = 1; i < snake.size(); i++) {
			g.fill3DRect(snake.get(i).y * SIZE, snake.get(i).x * SIZE, SIZE, SIZE, true);
		}
		g.setColor(Color.CYAN);
		g.fill3DRect(snake.getFirst().y * SIZE, snake.getFirst().x * SIZE, SIZE, SIZE, true);

		// 画食物
		g.setColor(Color.black);
		g.fill3DRect(Food.food.y * SIZE, Food.food.x * SIZE, SIZE, SIZE, true);

		// 当游戏结束时,画game over,以及提示 回车重新开始
		if (isGameOver) {
			g.setColor(Color.RED);
			g.setFont(new Font("宋体", Font.BOLD, 45));
			g.drawString("Game Over", (SIZE * LIE >> 1) - 100, (SIZE * HANG >> 1));
			g.setFont(new Font("宋体", Font.BOLD, 35));
			g.drawString("回车键重新开始", ((SIZE * LIE >> 1) - 20) - 100, ((SIZE * HANG >> 1)) + 45);
		}
	}

	/**
	 * 蛇开始走
	 */
	public static void run() throws InterruptedException {
		for (;;) {
			Thread.sleep(moveSpeed);
			// 使蛇行走一格
			if (Snake.move()) {
				return;
			}
			// 刷新窗体
			jd.repaint();
		}
	}

	/**
	 * 下面是按键 监听, 默认200ms 只能操作1次 ,修改 runTime 到150或者100,可以更流畅操作,不过也可能出现bug
	 */
	long time = System.currentTimeMillis();

	@Override
	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == KeyEvent.VK_DOWN) {
			// 如果没有频繁操作,且 进行了有效操作,则修改 限定操作的time值
			if (System.currentTimeMillis() - time > runTime && Snake.updateDirection(-1)) {
				time = System.currentTimeMillis();
			}
		} else if (e.getKeyCode() == KeyEvent.VK_UP) {
			if (System.currentTimeMillis() - time > runTime && Snake.updateDirection(1)) {
				time = System.currentTimeMillis();
			}
		} else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
			if (System.currentTimeMillis() - time > runTime && Snake.updateDirection(2)) {
				time = System.currentTimeMillis();
			}
		} else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
			if (System.currentTimeMillis() - time > runTime && Snake.updateDirection(-2)) {
				time = System.currentTimeMillis();
			}
		} else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
			// 当用户按下回车键,判断游戏是否已结束,如果结束,则重置,重新开始
			if (isGameOver == true) {
				// 重置蛇的数量.重置蛇的位置
				Snake.initSnake();
				// 重置地图
				initMap();
				isGameOver = false;
			}
		}
	}

	@Override
	public void keyTyped(KeyEvent e) {
	}

	@Override
	public void keyReleased(KeyEvent e) {
	}
}

/**
 * 贪吃蛇 类
 * 
 * @qq 59663479
 * @E-mail 59663479@qq.com
 * @Date 2019年9月8日 下午1:34:33
 */
class Snake {
	public static LinkedList<Point> snake = new LinkedList<>();
	/**
	 * 蛇的朝向,上1,右2,下-1,左-2,优点:反方向的值 相加==0;
	 */
	public volatile static int toward;

	/**
	 * 拿到地图,方便操作
	 */
	private static boolean map[][] = SnakeGame.map;

	/**
	 * 拿到地图的行和列,方便操作
	 */
	private static int hang = SnakeGame.HANG;
	private static int lie = SnakeGame.LIE;

	// 蛇 初始化
	static {
		snake.add(new Point(hang >> 1, (lie >> 1) - 2));
		snake.add(new Point(hang >> 1, (lie >> 1) - 3));
		snake.add(new Point(hang >> 1, (lie >> 1) - 4));
		// 位置确定之后,将 蛇身 设置为true,当舌头 碰撞到蛇身则 game over
		for (int i = 1; i < snake.size(); i++) {
			Point p = snake.get(i);
			map[p.x][p.y] = true;
		}

		// 一开始就向右移动
		toward = 2;
		// 生成食物
		Food.generateFood(snake.getFirst());
	}

	// 蛇 重新初始化,为重新开始准备
	public static void initSnake() {
		for (;;) {
			snake.remove();
			if (snake.isEmpty()) {
				break;
			}
		}
		snake.add(new Point(hang >> 1, (lie >> 1) - 2));
		snake.add(new Point(hang >> 1, (lie >> 1) - 3));
		snake.add(new Point(hang >> 1, (lie >> 1) - 4));
		// 位置确定之后,将 蛇身 设置为true,当舌头 碰撞到蛇身则 game over
		for (int i = 1; i < snake.size(); i++) {
			Point p = snake.get(i);
			map[p.x][p.y] = true;
		}

		// 一开始就向右移动
		toward = 2;
		// 生成食物
		Food.generateFood(snake.getFirst());
	}

	/**
	 * 蛇前进
	 * 
	 * @Description
	 * @Date 2019年9月8日 上午3:09:53
	 */
	public static boolean move() {
		// 如果游戏已结束,就不继续走了
		if (SnakeGame.getIsGameOver()) {
			return true;
		}
		// 1上,2右,-1下,-2左
		if (toward == -2) {
			snake.addFirst(new Point(snake.getFirst().x, snake.getFirst().y - 1));
		} else if (toward == -1) {
			snake.addFirst(new Point(snake.getFirst().x + 1, snake.getFirst().y));
		} else if (toward == 2) {
			snake.addFirst(new Point(snake.getFirst().x, snake.getFirst().y + 1));
		} else if (toward == 1) {
			snake.addFirst(new Point(snake.getFirst().x - 1, snake.getFirst().y));
		}
		// 这里过多无谓操作,下面已优化
		// for (int i = 1; i < snake.size(); i++) {
		// Point p = snake.get(i);
		// map[p.x][p.y] = true;
		// }
		// 位置确定之后,将 蛇头后的蛇身(下标1,之前的蛇头) 设置为true,当舌头 碰撞到蛇身则 game over
		Point p = snake.get(1);
		map[p.x][p.y] = true;

		int x = snake.getFirst().x;
		int y = snake.getFirst().y;
		// 每走一步,判断一次是否 触发结束条件
		if (map[x][y]) {
			SnakeGame.updateIsGameOver();
			return true;
		}
		// 判断是否吃到食物,吃到则生成新的食物,且不需要移除蛇尾.
		if (Food.eatFood(snake.getFirst())) {
			Food.generateFood(snake.getFirst());
		} else {
			// 没吃到,将蛇尾移除,并设为false
			Point removeLast = snake.removeLast();
			map[removeLast.x][removeLast.y] = false;
		}
		return false;
	}

	/**
	 * 贪吃蛇 转向,如果转向成功返回true
	 * 
	 * @Description
	 * @Date 2019年9月8日 上午3:30:12
	 * @param dir
	 */
	public static boolean updateDirection(int dir) {
		if (toward + dir != 0) {
			toward = dir;
			return true;
		}
		return false;
	}

}

/**
 * 食物 类
 */
class Food {
	/**
	 * 为贪吃蛇 提供食物,需要有x,y坐标
	 */
	public static Point food;

	private static boolean map[][] = SnakeGame.map;

	/**
	 * 生成食物,需要当前蛇头的位置,如果刷新在蛇头,则重新计算
	 */
	public static void generateFood(Point p) {
		while (true) {
			int x = (int) (Math.random() * (map.length - 2)) + 1;
			int y = (int) (Math.random() * (map[0].length - 2)) + 1;
			// 判断当前位置不是 地图障碍物
			if (map[x][y] != true) {
				food = new Point(x, y);
				// 如果和 蛇头重合,重新随机
				if (Objects.equals(food, p)) {
					continue;
				}
				return;
			}
		}
	}

	/**
	 * 判断是否吃掉了食物
	 */
	public static boolean eatFood(Point p) {
		if (Objects.equals(food, p)) {
			return true;
		}
		return false;
	}
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值