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

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

  惯例发包,还是加了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
posted @ 2014-02-02 23:42  Chihane  阅读(133)  评论(0编辑  收藏  举报