• 我写了个”不贪吃蛇“小游戏


    前言

    我写的这个”贪吃蛇“和小时候玩的”贪吃蛇“有点不一样,以往的”贪吃蛇“吃了食物蛇身就会变长,而我写的这个吃了“食物”蛇身会变短,并且胜利条件就是“把蛇变没”,嘻嘻~

    这里的“食物”其实是“药丸”,初始时,蛇身很长,你要通过食用“药丸”,来让自己的身体变短,直到自己消失不见,你就获胜了。

    “药丸”共有三种,分别为“红色药丸、蓝色药丸、绿色药丸”,对应分值“5分、2分、1分”,蛇吃了“药丸”会减掉对应分值数量的身体,并累计分值。

    代码

    蛇、药丸的抽象

    坐标 Point.java

    记录横纵坐标值。

    1. package cn.xeblog.snake.model;
    2. import java.util.Objects;
    3. /**
    4. * 坐标
    5. *
    6. * @author anlingyi
    7. * @date 2022/8/2 3:35 PM
    8. */
    9. public class Point {
    10. public int x;
    11. public int y;
    12. public Point(int x, int y) {
    13. this.x = x;
    14. this.y = y;
    15. }
    16. @Override
    17. public boolean equals(Object o) {
    18. if (this == o) return true;
    19. if (o == null || getClass() != o.getClass()) return false;
    20. Point point = (Point) o;
    21. return x == point.x && y == point.y;
    22. }
    23. @Override
    24. public int hashCode() {
    25. return Objects.hash(x, y);
    26. }
    27. @Override
    28. public String toString() {
    29. return "Point{" +
    30. "x=" + x +
    31. ", y=" + y +
    32. '}';
    33. }
    34. }
    35. 复制代码

    移动方向 Direction.java

    提供上、下、左、右四个移动方向的枚举。

    1. package cn.xeblog.snake.model;
    2. /**
    3. * @author anlingyi
    4. * @date 2022/8/2 5:25 PM
    5. */
    6. public enum Direction {
    7. UP,
    8. DOWN,
    9. LEFT,
    10. RIGHT
    11. }
    12. 复制代码

    蛇 Snake.java

    存储蛇身坐标信息,提供蛇身移动、移除蛇尾坐标、获取蛇头、蛇尾坐标、蛇身长度等方法。

    这里讲一下蛇移动的实现原理:游戏开始时,会固定一个移动方向,蛇会一直朝着这个方向移动,我们可以通过方向键改变蛇的移动方向,蛇的移动其实就是将蛇身的坐标移动一下位置,比如蛇身长度(不包含蛇头)为6,移动时,只需将蛇身位置为5的坐标移到位置为6的坐标去,位置为4的坐标移动到位置为5的坐标去,简单来说就是将前一个的坐标换到它后面一个去,这是蛇身的移动,蛇头的移动需要单独计算,如果是上下方向移动,那就是对y坐标的加减操作,向上运动需要减一个蛇身的高度,向下则与之相反,需要加一个蛇身的高度,左右运动同理。

    1. package cn.xeblog.snake.model;
    2. import java.util.List;
    3. /**
    4. * 蛇
    5. *
    6. * @author anlingyi
    7. * @date 2022/8/2 3:32 PM
    8. */
    9. public class Snake {
    10. public static int width = 10;
    11. public static int height = 10;
    12. /**
    13. * 蛇身坐标列表
    14. */
    15. public List body;
    16. public Snake(List body) {
    17. this.body = body;
    18. }
    19. /**
    20. * 添加蛇身坐标
    21. *
    22. * @param x
    23. * @param y
    24. */
    25. public void add(int x, int y) {
    26. this.body.add(new Point(x * width, y * height));
    27. }
    28. /**
    29. * 移除蛇尾坐标
    30. */
    31. public void removeLast() {
    32. int size = size();
    33. if (size == 0) {
    34. return;
    35. }
    36. this.body.remove(size - 1);
    37. }
    38. /**
    39. * 获取蛇头坐标
    40. *
    41. * @return
    42. */
    43. public Point getHead() {
    44. if (size() > 0) {
    45. return this.body.get(0);
    46. }
    47. return null;
    48. }
    49. /**
    50. * 获取蛇尾坐标
    51. *
    52. * @return
    53. */
    54. public Point getTail() {
    55. int size = size();
    56. if (size > 0) {
    57. return this.body.get(size - 1);
    58. }
    59. return null;
    60. }
    61. /**
    62. * 蛇身长度
    63. *
    64. * @return
    65. */
    66. public int size() {
    67. return this.body.size();
    68. }
    69. /**
    70. * 蛇移动
    71. *
    72. * @param direction 移动方向
    73. */
    74. public void move(Direction direction) {
    75. if (size() == 0) {
    76. return;
    77. }
    78. for (int i = this.size() - 1; i > 0; i--) {
    79. // 从蛇尾开始向前移动
    80. Point point = this.body.get(i);
    81. Point nextPoint = this.body.get(i - 1);
    82. point.x = nextPoint.x;
    83. point.y = nextPoint.y;
    84. }
    85. // 蛇头移动
    86. Point head = getHead();
    87. switch (direction) {
    88. case UP:
    89. head.y -= height;
    90. break;
    91. case DOWN:
    92. head.y += height;
    93. break;
    94. case LEFT:
    95. head.x -= width;
    96. break;
    97. case RIGHT:
    98. head.x += width;
    99. break;
    100. }
    101. }
    102. }
    103. 复制代码

    药丸 Pill.java

    存储“药丸“的坐标、类型信息。

    1. package cn.xeblog.snake.model;
    2. /**
    3. * 药丸
    4. *
    5. * @author anlingyi
    6. * @date 2022/8/2 4:49 PM
    7. */
    8. public class Pill {
    9. public static int width = 10;
    10. public static int height = 10;
    11. /**
    12. * 坐标
    13. */
    14. public Point point;
    15. /**
    16. * 药丸类型
    17. */
    18. public PillType pillType;
    19. public enum PillType {
    20. /**
    21. * 红色药丸
    22. */
    23. RED(5),
    24. /**
    25. * 蓝色药丸
    26. */
    27. BLUE(2),
    28. /**
    29. * 绿色药丸
    30. */
    31. GREEN(1),
    32. ;
    33. /**
    34. * 分数
    35. */
    36. public int score;
    37. PillType(int score) {
    38. this.score = score;
    39. }
    40. }
    41. public Pill(int x, int y, PillType pillType) {
    42. this.point = new Point(x * width, y * height);
    43. this.pillType = pillType;
    44. }
    45. }
    46. 复制代码

    游戏界面

    初始化一些信息,比如游戏界面的宽高、定时器、一些状态标识(是否停止游戏、游戏是否胜利)、监听一些按键事件(空格键开始/暂停游戏、四个方向键控制蛇移动的方向),绘制游戏画面。

    当检测到游戏开始时,初始化蛇、“药丸”的位置信息,然后启动定时器每隔一段时间重新绘制游戏画面,让蛇可以动起来。

    当检测到蛇咬到自己或者是蛇撞墙时,游戏状态将会被标记为“游戏失败”,绘制游戏结束画面,并且定时器停止,如果蛇身为0,则游戏结束,游戏状态标记为“游戏胜利”。

    1. package cn.xeblog.snake.ui;
    2. import cn.xeblog.snake.model.Direction;
    3. import cn.xeblog.snake.model.Pill;
    4. import cn.xeblog.snake.model.Point;
    5. import cn.xeblog.snake.model.Snake;
    6. import javax.swing.*;
    7. import java.awt.*;
    8. import java.awt.event.*;
    9. import java.util.ArrayList;
    10. import java.util.Collections;
    11. import java.util.Random;
    12. /**
    13. * @author anlingyi
    14. * @date 2022/8/2 3:51 PM
    15. */
    16. public class SnakeGameUI extends JPanel implements ActionListener {
    17. /**
    18. * 宽度
    19. */
    20. private int width;
    21. /**
    22. * 高度
    23. */
    24. private int height;
    25. /**
    26. * 蛇
    27. */
    28. private Snake snake;
    29. /**
    30. * 药丸
    31. */
    32. private Pill pill;
    33. /**
    34. * 移动方向
    35. */
    36. private Direction direction;
    37. /**
    38. * 停止游戏标记
    39. */
    40. private boolean stop;
    41. /**
    42. * 游戏状态 0.初始化 1.游戏胜利 2.游戏失败
    43. */
    44. private int state = -1;
    45. /**
    46. * 定时器
    47. */
    48. private Timer timer;
    49. /**
    50. * 移动速度
    51. */
    52. private int speed = 100;
    53. /**
    54. * 分数
    55. */
    56. private int score;
    57. /**
    58. * 特殊药丸列表
    59. */
    60. private ArrayList specialPill;
    61. public SnakeGameUI(int width, int height) {
    62. this.width = width;
    63. this.height = height;
    64. this.timer = new Timer(speed, this);
    65. this.stop = true;
    66. initPanel();
    67. }
    68. /**
    69. * 初始化
    70. */
    71. private void init() {
    72. this.score = 0;
    73. this.state = 0;
    74. this.stop = true;
    75. this.timer.setDelay(speed);
    76. initSnake();
    77. initPill();
    78. generatePill();
    79. repaint();
    80. }
    81. /**
    82. * 初始化游戏面板
    83. */
    84. private void initPanel() {
    85. this.setPreferredSize(new Dimension(this.width, this.height));
    86. this.addKeyListener(new KeyAdapter() {
    87. @Override
    88. public void keyPressed(KeyEvent e) {
    89. if (stop && e.getKeyCode() != KeyEvent.VK_SPACE) {
    90. return;
    91. }
    92. switch (e.getKeyCode()) {
    93. case KeyEvent.VK_UP:
    94. if (direction == Direction.DOWN) {
    95. break;
    96. }
    97. direction = Direction.UP;
    98. break;
    99. case KeyEvent.VK_DOWN:
    100. if (direction == Direction.UP) {
    101. break;
    102. }
    103. direction = Direction.DOWN;
    104. break;
    105. case KeyEvent.VK_LEFT:
    106. if (direction == Direction.RIGHT) {
    107. break;
    108. }
    109. direction = Direction.LEFT;
    110. break;
    111. case KeyEvent.VK_RIGHT:
    112. if (direction == Direction.LEFT) {
    113. break;
    114. }
    115. direction = Direction.RIGHT;
    116. break;
    117. case KeyEvent.VK_SPACE:
    118. if (state != 0) {
    119. init();
    120. }
    121. stop = !stop;
    122. if (!stop) {
    123. timer.start();
    124. }
    125. break;
    126. }
    127. }
    128. });
    129. }
    130. /**
    131. * 初始化蛇
    132. */
    133. private void initSnake() {
    134. this.direction = Direction.LEFT;
    135. int maxX = this.width / Snake.width;
    136. int maxY = this.height / Snake.height;
    137. this.snake = new Snake(new ArrayList<>());
    138. this.snake.add(maxX - 2, 3);
    139. this.snake.add(maxX - 1, 3);
    140. this.snake.add(maxX - 1, 2);
    141. this.snake.add(maxX - 1, 1);
    142. for (int i = maxX - 1; i > 0; i--) {
    143. this.snake.add(i, 1);
    144. }
    145. for (int i = 1; i < maxY - 1; i++) {
    146. this.snake.add(1, i);
    147. }
    148. for (int i = 1; i < maxX - 1; i++) {
    149. this.snake.add(i, maxY - 2);
    150. }
    151. }
    152. /**
    153. * 初始化药丸
    154. */
    155. private void initPill() {
    156. this.specialPill = new ArrayList<>();
    157. for (int i = 0; i < 5; i++) {
    158. this.specialPill.add(Pill.PillType.RED);
    159. }
    160. for (int i = 0; i < 10; i++) {
    161. this.specialPill.add(Pill.PillType.BLUE);
    162. }
    163. Collections.shuffle(specialPill);
    164. }
    165. /**
    166. * 生成药丸
    167. */
    168. private void generatePill() {
    169. // 是否获取特殊药丸
    170. boolean getSpecialPill = new Random().nextInt(6) == 3;
    171. Pill.PillType pillType;
    172. if (getSpecialPill && this.specialPill.size() > 0) {
    173. // 生成特殊药丸
    174. int index = new Random().nextInt(this.specialPill.size());
    175. pillType = this.specialPill.get(index);
    176. this.specialPill.remove(index);
    177. } else {
    178. // 生成绿色药丸
    179. pillType = Pill.PillType.GREEN;
    180. }
    181. // 随机坐标
    182. int x = new Random().nextInt(this.width / Pill.width - 1);
    183. int y = new Random().nextInt(this.height / Pill.height - 1);
    184. this.pill = new Pill(x, y, pillType);
    185. }
    186. @Override
    187. protected void paintComponent(Graphics g) {
    188. super.paintComponent(g);
    189. Graphics2D g2 = (Graphics2D) g;
    190. g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    191. g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    192. g2.setColor(new Color(66, 66, 66));
    193. g2.fillRect(0, 0, this.width, this.height);
    194. if (this.snake != null) {
    195. // 画蛇
    196. g2.setColor(new Color(255, 255, 255));
    197. for (int i = this.snake.size() - 1; i >= 0; i--) {
    198. Point point = this.snake.body.get(i);
    199. if (i == 0) {
    200. // 蛇头
    201. g2.setColor(new Color(255, 92, 92));
    202. } else {
    203. g2.setColor(new Color(215, 173, 173));
    204. }
    205. g2.fillRect(point.x, point.y, Snake.width, Snake.height);
    206. }
    207. }
    208. if (this.pill != null) {
    209. // 画药丸
    210. Color pillColor;
    211. switch (this.pill.pillType) {
    212. case RED:
    213. pillColor = new Color(255, 41, 41);
    214. break;
    215. case BLUE:
    216. pillColor = new Color(20, 250, 243);
    217. break;
    218. default:
    219. pillColor = new Color(97, 255, 113);
    220. break;
    221. }
    222. g2.setColor(pillColor);
    223. g2.fillOval(pill.point.x, pill.point.y, Pill.width, Pill.height);
    224. }
    225. if (state > 0) {
    226. // 显示游戏结果
    227. String tips = "游戏失败!";
    228. if (state == 1) {
    229. tips = "游戏胜利!";
    230. }
    231. g2.setFont(new Font("", Font.BOLD, 20));
    232. g2.setColor(new Color(208, 74, 74));
    233. g2.drawString(tips, this.width / 3, this.height / 3);
    234. g2.setFont(new Font("", Font.PLAIN, 18));
    235. g2.setColor(Color.WHITE);
    236. g2.drawString("得分:" + this.score, this.width / 2, this.height / 3 + 50);
    237. }
    238. if (stop) {
    239. g2.setFont(new Font("", Font.PLAIN, 18));
    240. g2.setColor(Color.WHITE);
    241. g2.drawString("按空格键开始/暂停游戏!", this.width / 4, this.height - 50);
    242. }
    243. }
    244. @Override
    245. public void actionPerformed(ActionEvent e) {
    246. // 是否吃药
    247. boolean isAte = false;
    248. if (!this.stop) {
    249. // 移动蛇
    250. this.snake.move(this.direction);
    251. Point head = this.snake.getHead();
    252. if (head.equals(this.pill.point)) {
    253. // 吃药了
    254. isAte = true;
    255. // 药丸分数
    256. int getScore = this.pill.pillType.score;
    257. // 累计分数
    258. this.score += getScore;
    259. for (int i = 0; i < getScore; i++) {
    260. // 移除蛇尾
    261. this.snake.removeLast();
    262. if (this.snake.size() == 0) {
    263. // 游戏胜利
    264. this.state = 1;
    265. this.stop = true;
    266. break;
    267. }
    268. }
    269. pill = null;
    270. if (this.score % 10 == 0) {
    271. int curSpeed = this.timer.getDelay();
    272. if (curSpeed > 30) {
    273. // 加速
    274. this.timer.setDelay(curSpeed - 10);
    275. }
    276. }
    277. }
    278. if (state == 0) {
    279. // 判断蛇有没有咬到自己或是撞墙
    280. int maxWidth = this.width - this.snake.width;
    281. int maxHeight = this.height - this.snake.height;
    282. boolean isHitWall = head.x > maxWidth || head.x < 0 || head.y > maxHeight || head.y < 0;
    283. boolean isBiting = false;
    284. for (int i = this.snake.size() - 1; i > 0; i--) {
    285. if (head.equals(this.snake.body.get(i))) {
    286. isBiting = true;
    287. break;
    288. }
    289. }
    290. if (isHitWall || isBiting) {
    291. // 游戏失败
    292. this.state = 2;
    293. this.stop = true;
    294. }
    295. }
    296. }
    297. if (this.stop) {
    298. this.timer.stop();
    299. } else if (isAte) {
    300. // 重新生成药丸
    301. generatePill();
    302. }
    303. repaint();
    304. }
    305. }
    306. 复制代码

    启动类

    游戏启动入口。

    1. package cn.xeblog.snake;
    2. import cn.xeblog.snake.ui.SnakeGameUI;
    3. import javax.swing.*;
    4. /**
    5. * 启动游戏
    6. *
    7. * @author anlingyi
    8. * @date 2022/8/2 3:41 PM
    9. */
    10. public class StartGame {
    11. public static void main(String[] args) {
    12. JFrame frame = new JFrame();
    13. frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    14. frame.setVisible(true);
    15. frame.setResizable(false);
    16. frame.setTitle("不贪吃蛇");
    17. frame.setSize(400, 320);
    18. frame.setLocationRelativeTo(null);
    19. JPanel gamePanel = new SnakeGameUI(400, 300);
    20. frame.add(gamePanel);
    21. gamePanel.requestFocus();
    22. }
    23. }
    24. 复制代码

    游戏演示

     

     

    最后

    刚开始蛇身很长,蛇身移动缓慢,后面会随着得分的越来越高,蛇身会移动的越来越快,看着挺容易,真玩起来,还真有“亿”点难,目前我玩了好几把,没有赢过,最开始那张图就是我目前的最高分了,嘻嘻~

  • 相关阅读:
    硬件设计哪些事-PCB设计那些事
    Jmeter接口测试简易步骤
    Socks5代理与网络安全:保护隐私、绕过限制与爬虫应用
    web基础与HTTP协议
    docker部署elk
    Golang基本命令操作
    Vue前端项目安装及相关问题解决
    指针变量和地址
    SpringBoot原理-自动配置-案例(自定义starter分析)
    数据卷(Data Volumes)&dockerfile
  • 原文地址:https://blog.csdn.net/YYniannian/article/details/126134577