• 贪吃蛇之于程序员就像巴大蝴之于小智


      对的,出了新手村就不用了的那种。

      惯例发包,还是加了rr3,明明根本没人下。

      里面三个文件,一个是纯打包的jar,一个是能能运行的jar,一个是能运行的exe,我测试没问题,运行不了请检查环境变量和人品。

      不过按照习惯,最后还是会把所有代码直接发出来的。

      第一次用了下枚举类,还是挺简单的,百度随便搜两篇博客就懂了,用起来也挺智能,最重要的是用1234来表示方向太low了。

      像这样:

    public enum Orientation {UP, DOWN, LEFT, RIGHT}

      之后用起来直接当常量引用就行了(比如Orientation.UP),不过数据类型是Orientation要注意。

      我之前一直不敢写这玩意是因为我不知道怎么用格子来表示坐标,直到今天才知道我想多了,格子的左上角就是坐标,剩下的是格子边长的事。

      

      我觉得自己的代码已经挺好看的了,反正比网上某些一个java文件搞定一切的要好一点。

      还有一个bug,之前也看到别人有。

      描述一下的话就是我模拟贪吃蛇移动的延迟使用的线程睡眠方法,像这样:

    Thread.sleep(100)

      这带来了一个什么问题呢,就是,虽然我在程序里做了判断,不允许玩家往后退,但是如果你手够快,能在线程睡着的这段时间里连续改两次方向,从结果来说就是往后退了。

      举个例子:

        蛇正在往右走,你按了方向左,这没有任何作用,因为我加了判断,阻止你这么做。

        但是你在100毫秒内先按上,方向就变成了上,但是因为线程还在睡,所以蛇的位置没有更新,这时你又按了左,这样是允许的,因为你是从上变成左,并不是掉头。

        结果就是蛇开始往左跑,如果蛇身只有两节,那就是自由掉头,如果大于二,蛇会咬到自己的第三节身体导致游戏失败。

      ……我也不知道该怎么解决这个bug……以后再说……

      就敲着这段字的时间里我发现了一个问题,我是直接实现KeyListener接口的,这个是我抄人家的我错了。

      我的做法是做成内部类然后继承KeyAdapter的,直接用接口会多出来两个用不着的方法,太难看了。

      但是懒得重新打包,也不想更新版本号了,一会下边发的代码会是新版本的代码。

      一个下午加上一个晚上搞定一个贪吃蛇,我还是有点进步的。

     1 package snake;
     2 
     3 /** 
     4  * 枚举类。
     5  * 定义了四个方向。
     6  * 
     7  * @author mlxy
     8  * @version 1.0
     9  */
    10 public enum Orientation {
    11     UP, DOWN, LEFT, RIGHT
    12 }
    Orientation.java
     1 package snake;
     2 
     3 /**
     4  * 运行类。
     5  * 内容很简单,初始化框架。
     6  * 
     7  * @author mlxy
     8  * @version 1.0
     9  */
    10 public class Run {
    11     public static void main(String[] args) {
    12         new MainFrame();
    13     }
    14 }
    Run.java
     1 package snake;
     2 
     3 import javax.swing.JFrame;
     4 
     5 /**
     6  * 主框架类。
     7  * 初始化框架内容和相关属性。
     8  * 
     9  * @author mlxy
    10  * @version 1.0
    11  */
    12 public class MainFrame extends JFrame {
    13     private static final long serialVersionUID = 3693516591938222507L;
    14     GamePad gamePad;
    15     
    16     MainFrame() {
    17         this.gamePad = new GamePad();
    18         this.add(gamePad);
    19         this.setResizable(false);
    20         this.pack();
    21         
    22         this.setTitle("Snake");
    23         this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    24         this.setLocationRelativeTo(null);
    25         this.setVisible(true);
    26     }
    27 }
    MainFrame.java
      1 package snake;
      2 
      3 import java.awt.Color;
      4 import java.awt.Dimension;
      5 import java.awt.Graphics;
      6 import java.awt.event.KeyAdapter;
      7 import java.awt.event.KeyEvent;
      8 
      9 import javax.swing.JPanel;
     10 
     11 /** 
     12  * 游戏面板类。
     13  * 用变量存储了游戏分数,蛇的引用,食物的引用。
     14  * 还包括了游戏进行中所需要的所有方法。
     15  * 实现了Runnable接口,用以将自身作为一个线程启动。
     16  * 包含了一个内部类作为监听器,用以监听玩家的键盘输入。
     17  * 
     18  * @author mlxy
     19  * @version 1.0
     20  */
     21 public class GamePad extends JPanel implements Runnable {
     22     private static final long serialVersionUID = -6102074989461150615L;
     23     int point;
     24     Snake snake;
     25     Food food;
     26     
     27     /** 常量,游戏面板的边长。*/
     28     static int SIDELEN_OF_PAD = 500;
     29     /** 常量,一个格子的边长。*/
     30     static int SIDELEN_OF_GRID = 10;
     31     
     32     GamePad() {
     33         // 设置偏好大小,+1是为了能看到边界线,因为边界线宽1像素。
     34         this.setPreferredSize(new Dimension(SIDELEN_OF_PAD + 1, SIDELEN_OF_PAD + 1));
     35         
     36         this.setBackground(Color.WHITE);
     37         this.addKeyListener(new PlayerInputListener());
     38         
     39         // 取得焦点,否则无法读取键盘输入。
     40         this.setFocusable(true);
     41         this.requestFocus();
     42         
     43         // 初始化三个变量。
     44         point = 0;
     45         snake = new Snake();
     46         food = new Food(snake);
     47         
     48         // 启动线程。
     49         new Thread(this).start();
     50     }
     51     
     52     // 绘制面板内容。
     53     @Override
     54     public void paintComponent(Graphics g) {
     55         super.paintComponent(g);
     56         drawBorder(g);
     57         drawPoints(g);
     58         drawFood(g);
     59         drawSnake(g);
     60     }
     61     
     62     /** 绘制边界线。*/
     63     void drawBorder(Graphics g) {
     64         g.setColor(Color.GREEN);
     65         g.drawRect(0, 0, SIDELEN_OF_PAD, SIDELEN_OF_PAD);
     66         g.setColor(Color.BLACK);
     67     }
     68     
     69     /** 绘制分数字符。*/
     70     void drawPoints(Graphics g) {
     71         g.setColor(Color.RED);
     72         g.drawString("Points: " + point, 10, 20);
     73         g.setColor(Color.BLACK);
     74     }
     75     
     76     /** 绘制蛇身。*/
     77     void drawSnake(Graphics g) {
     78         g.setColor(Color.BLUE);
     79         
     80         // 以蛇身各节所在格子数乘以每格边长求得坐标并依次绘制。
     81         for (int i = 0; i < snake.getLength(); i++) {
     82             g.fillRect(snake.getX(i) * SIDELEN_OF_GRID, snake.getY(i) * SIDELEN_OF_GRID,
     83                     SIDELEN_OF_GRID, SIDELEN_OF_GRID);
     84         }
     85         g.setColor(Color.BLACK);
     86     }
     87     
     88     /** 绘制食物。*/
     89     void drawFood(Graphics g) {
     90         g.setColor(Color.MAGENTA);
     91         
     92         // 以食物所在格子数乘以每格边长求得坐标并绘制。
     93         g.fillOval(food.getX() * SIDELEN_OF_GRID, food.getY() * SIDELEN_OF_GRID,
     94                 SIDELEN_OF_GRID, SIDELEN_OF_GRID);
     95     }
     96     
     97     /** 向前方移动蛇身。*/
     98     void moveSnake() {
     99         //先根据蛇身长度将蛇身顺次移一格。
    100         for (int i = snake.getLength() - 1; i > 0; i--) {
    101             snake.setX(i, snake.getX(i-1));
    102             snake.setY(i, snake.getY(i-1));
    103         }
    104         
    105         // 根据蛇头朝向决定蛇头出现在哪个方向的格子里。
    106         switch (snake.getOrientation()) {
    107         case UP:
    108             snake.setY(0, snake.getY(0) - 1);
    109             break;
    110         case DOWN:
    111             snake.setY(0, snake.getY(0) + 1);
    112             break;
    113         case LEFT:
    114             snake.setX(0, snake.getX(0) - 1);
    115             break;
    116         case RIGHT:
    117             snake.setX(0, snake.getX(0) + 1);
    118             break;
    119         default:
    120             break;
    121         }
    122     }
    123     
    124     /** 接收一个方向参数,改变方向。*/
    125     void turn(Orientation orientation) {
    126         // 如果输入的方向是后方,则直接跳出方法,蛇不应该后退。
    127         switch (orientation) {
    128         case UP:
    129             if (snake.getOrientation() == Orientation.DOWN) {return;}
    130             break;
    131         case DOWN:
    132             if (snake.getOrientation() == Orientation.UP) {return;}
    133             break;
    134         case LEFT:
    135             if (snake.getOrientation() == Orientation.RIGHT) {return;}
    136             break;
    137         case RIGHT:
    138             if (snake.getOrientation() == Orientation.LEFT) {return;}
    139             break;
    140         }
    141         
    142         snake.setOrientation(orientation);
    143     }
    144     
    145     
    146     
    147     /** 进食并改变蛇身长度,之后重新建一个食物。*/
    148     void eat() {
    149         snake.setLength(snake.getLength() + 1);
    150         point += 100;
    151         
    152         food = new Food(snake);
    153     }
    154     
    155     /** 运行函数。*/
    156     public void run() {
    157         while (true) {
    158             // 进行各项判断之后移动。
    159             if (isOutOfBound()) {System.exit(0);}
    160             if (isSelfBiting()) {System.exit(0);}
    161             if (isOnFood()) {eat();}
    162             moveSnake();
    163             
    164             // 重绘面板并睡眠,睡眠时长随分数增加而缩短,最小20毫秒。
    165             repaint();
    166             int sleepTime = 200 - point / 10;
    167             if (sleepTime <= 20) {sleepTime = 20;}
    168             try {
    169                 Thread.sleep(sleepTime);
    170             } catch (InterruptedException e) {
    171                 e.printStackTrace();
    172             }
    173         }
    174     }
    175 
    176     /** 出边界检查。*/
    177     boolean isOutOfBound() {
    178         int gridsOneSide = GamePad.SIDELEN_OF_PAD / GamePad.SIDELEN_OF_GRID;
    179         if (snake.getX(0) < 0 || snake.getX(0) >= gridsOneSide) {
    180             return true;
    181         }
    182         if (snake.getY(0) < 0 || snake.getY(0) >= gridsOneSide) {
    183             return true;
    184         }
    185         return false;
    186     }
    187     
    188     /** 自噬检查。*/
    189     boolean isSelfBiting() {
    190         for (int i = 1; i < snake.getLength(); i++) {
    191             if (snake.getX(i) == snake.getX(0) && snake.getY(i) == snake.getY(0)) {
    192                 return true;
    193             }
    194         }
    195         return false;
    196     }
    197     
    198     /** 进食检查。*/
    199     boolean isOnFood() {
    200         if (snake.getX(0) == food.getX() && snake.getY(0) == food.getY()) {
    201             return true;
    202         }
    203         return false;
    204     }
    205     
    206     /** 
    207      * 按键监听器。
    208      * 处理玩家的键盘输入。
    209      * 
    210      * @author mlxy
    211      * @version 1.0
    212      */
    213     class PlayerInputListener extends KeyAdapter {
    214         @Override
    215         public void keyPressed(KeyEvent e) {
    216             switch (e.getKeyCode()) {
    217             case KeyEvent.VK_UP:
    218                 turn(Orientation.UP);
    219                 break;
    220             case KeyEvent.VK_DOWN:
    221                 turn(Orientation.DOWN);
    222                 break;
    223             case KeyEvent.VK_LEFT:
    224                 turn(Orientation.LEFT);
    225                 break;
    226             case KeyEvent.VK_RIGHT:
    227                 turn(Orientation.RIGHT);
    228                 break;
    229             case KeyEvent.VK_ESCAPE:
    230                 System.exit(0);
    231                 break;
    232             default:
    233                 break;
    234             }
    235         }
    236     }
    237 }
    GamePad.java
     1 package snake;
     2 
     3 /** 
     4  * 食物类。
     5  * 定义了食物的坐标,内部存储了对蛇的引用以判断生成位置是否在蛇身上。
     6  * 
     7  * @author mlxy
     8  * @version 1.0
     9  */
    10 public class Food {
    11     Snake snake;
    12     
    13     // 食物所在格子数,并非坐标。
    14     private int x;
    15     private int y;
    16     
    17     Food(Snake snake) {
    18         this.snake = snake;
    19         
    20         // 随机初始化食物坐标。
    21         int gridsOneSide = GamePad.SIDELEN_OF_PAD / GamePad.SIDELEN_OF_GRID;
    22         
    23         outerWhile: while (true) {
    24             x = 1 + (int) (Math.random() * (gridsOneSide - 2));
    25             y = 1 + (int) (Math.random() * (gridsOneSide - 2));
    26             
    27             // 如果和蛇身重合就重新生成。
    28             for (int i = 0; i < snake.getLength(); i++) {
    29                 if (snake.getX(i) == x && snake.getY(i) == y) {
    30                     continue outerWhile;
    31                 }
    32             }
    33             
    34             // 生成完成,跳出。
    35             break;
    36         }
    37     }
    38 
    39     public int getX() {return x;}
    40     public int getY() {return y;}
    41 }
    Food.java
     1 package snake;
     2 
     3 /** 
     4  * 蛇类。
     5  * 定义了蛇头的朝向,蛇的长度,以及蛇身各节所在的坐标。
     6  * 
     7  * @author mlxy
     8  * @version 1.0
     9  */
    10 public class Snake {
    11     private Orientation orientation;
    12     
    13     private int length;
    14     
    15     // 蛇身各节所在的格子数,并非坐标。
    16     private int[] x;
    17     private int[] y;
    18     
    19     Snake() {
    20         // 初始化朝向和长度。
    21         orientation = Orientation.RIGHT;
    22         length = 2;
    23         
    24         // 用游戏面板总格子数初始化数组大小。
    25         int numberOfGrid = (int) Math.pow(GamePad.SIDELEN_OF_PAD / GamePad.SIDELEN_OF_GRID, 2);
    26         x = new int[numberOfGrid];
    27         y = new int[numberOfGrid];
    28         
    29         // 初始化最初的蛇身位置。
    30         int gridsOneSide = GamePad.SIDELEN_OF_PAD / GamePad.SIDELEN_OF_GRID;
    31         x[1] = gridsOneSide / 2 + 1;
    32         y[1] = gridsOneSide / 2;
    33         x[0] = gridsOneSide / 2;
    34         y[0] = gridsOneSide / 2;
    35     }
    36     
    37     int getLength() {return length;}
    38     void setLength(int newLength) {this.length = newLength;}
    39     
    40     int getX(int index) {return x[index];}
    41     int getY(int index) {return y[index];}
    42     
    43     public void setX(int index, int x) {this.x[index] = x;}
    44     public void setY(int index, int y) {this.y[index] = y;}
    45     
    46     public Orientation getOrientation() {return orientation;}
    47     public void setOrientation(Orientation orientation) {this.orientation = orientation;}
    48 }
    Snake.java
  • 相关阅读:
    3.6 符号表的应用
    将博客搬至CSDN
    webpack打包vue项目IE报错,“对象不支持“use”属性或方法”
    移动端解决input被输入法挡住的问题
    javascript中对象的深复制的几种方法
    如何随机洗牌一个数组
    setInterval中this指向的问题
    css中的各种常见布局写法
    vue设置全局变量或函数
    【nodejs爬虫】使用async控制并发写一个小说爬虫
  • 原文地址:https://www.cnblogs.com/chihane/p/3537416.html
Copyright © 2020-2023  润新知