代码重构-以贪吃蛇为示例(二)-分离入口、内部类,抽离函数
程序员文章站
2022-05-21 14:06:07
...
面对这么乱的代码,第一步就是把想关性不太大的部分抽离出去。具体操作:
- Direction类可以从原来文件中取出,放到同一个包下;
- 将main函数提出,放到GameLauncher.java中;
- 将原来的Game类改为GamePanel,作为游戏的面板;
那么现在我们有三个文件:GameLauncher(启动程序),GamePanel(程序面板),Direction(方向类,作为工具)。
接下来我们要抽离函数。
首先构造函数:
public GamePanel () { keyMap.put(KeyEvent.VK_UP, Direction.UP); keyMap.put(KeyEvent.VK_DOWN, Direction.DOWM); keyMap.put(KeyEvent.VK_LEFT, Direction.LEFT); keyMap.put(KeyEvent.VK_RIGHT, Direction.RIGHT); Point p = new Point(random.nextInt(tableWidth - initsnakeLenght >> 1) + initsnakeLenght, random.nextInt(tableHeight - initsnakeLenght >> 1) + initsnakeLenght); snake.add(p); for (int i = 0; i < initsnakeLenght - 1; ++i) { p = direction.getPreviousPoint(p); snake.add(p); } /** * 游戏主循环线程 */ new Thread() { @Override public void run() { while (true) { if (System.currentTimeMillis() - crrTime > 500 / speed) { synchronized (GamePanel.class) { moveSnake(); if (!checkSnack()) { JOptionPane.showMessageDialog(null, "Game Over!"); return; } } repaint(); crrTime = System.currentTimeMillis(); } } }; }.start(); }
这里做了三个事情:初始化按键和方向的映射,初始化蛇的链表,开启游戏线程。其实还有一件事应该放在这里,就是初始化蛇的方向,我们作为第四件事。按照这个方式我们抽取4个函数:initKeyMap,initSnake,initGameLoop,getRandomDirection。
现在代码是这样:
public GamePanel () { initSnakeDirection(); initKeyMap(); initSnake(); initGameLoop(); } /** * 初始化蛇运行方向 */ private void initSnakeDirection() { direction = getRandomDirection(); } /** * 随机生成方向 * * @return 方向 */ private Direction getRandomDirection() { return da[random.nextInt(4)]; } /** * 初始化游戏线程 */ private void initGameLoop() { /** * 游戏主循环线程 */ new Thread() { @Override public void run() { while (true) { if (System.currentTimeMillis() - crrTime > 500 / speed) { synchronized (GamePanel.class) { moveSnake(); if (!checkSnack()) { JOptionPane.showMessageDialog(null, "Game Over!"); return; } } repaint(); crrTime = System.currentTimeMillis(); } } }; }.start(); } /** * 初始化蛇链表 */ private void initSnake() { Point p = new Point(random.nextInt(tableWidth - initsnakeLenght >> 1) + initsnakeLenght, random.nextInt(tableHeight - initsnakeLenght >> 1) + initsnakeLenght); snake.add(p); for (int i = 0; i < initsnakeLenght - 1; ++i) { p = direction.getPreviousPoint(p); snake.add(p); } } /** * 初始化按键和方向的映射 */ private void initKeyMap() { keyMap.put(KeyEvent.VK_UP, Direction.UP); keyMap.put(KeyEvent.VK_DOWN, Direction.DOWM); keyMap.put(KeyEvent.VK_LEFT, Direction.LEFT); keyMap.put(KeyEvent.VK_RIGHT, Direction.RIGHT); }
接下来是checkSnake
/** * 判断贪吃蛇是否撞墙或撞到自己 * * @return */ protected boolean checkSnack() { Point p = snake.getFirst(); int x = p.x, y = p.y; if (x < 0 || x >= tableWidth || y < 0 || y >= tableHeight) { return false; } Iterator<Point> it = snake.iterator(); it.next(); while (it.hasNext()) { Point pBody = it.next(); if (p.equals(pBody)) { return false; } } return true; }
这个函数做了两件事:检查是否碰到墙壁,检查是否和自己相撞。可以抽取两个函数 isAgainstWall和isAgainstSelf:
/** * 判断贪吃蛇是否撞墙或撞到自己 * * @return */ protected boolean checkSnack() { return !isAgainstWall() && !isAgainstSelf(); } /** * 判断蛇头是否撞到自己的身体,是则返回true,否返回false * @return */ private boolean isAgainstSelf() { Point p = snake.getFirst(); Iterator<Point> it = snake.iterator(); it.next(); while (it.hasNext()) { Point pBody = it.next(); if (p.equals(pBody)) { return true; } } return false; } /** * 判断蛇头是否撞到墙壁,是则返回true,否返回false * @return */ private boolean isAgainstWall() { Point p = snake.getFirst(); int x = p.x, y = p.y; return x < 0 || x >= tableWidth || y < 0 || y >= tableHeight; }
最后看一下paintComponent这复杂的大函数:
/** * 绘制图形 */ @Override protected void paintComponent(Graphics g) { g.setColor(new Color(0x555555)); g.clearRect(0, 0, tableWidth * sellSize, tableHeight * sellSize); for (int i = 0; i < tableWidth; i++) { for (int j = 0; j < tableHeight; ++j) { g.drawRect(i * sellSize, j * sellSize, sellSize, sellSize); } } g.setColor(new Color(0x3399cc)); for (Point p : snake) { g.fillRect(p.x * sellSize, p.y * sellSize, sellSize, sellSize); } g.setColor(new Color(0x115599)); Point p = snake.peek(); g.fillRect(p.x * sellSize, p.y * sellSize, sellSize, sellSize); g.setColor(new Color(0xdd7744)); g.fillRect(target.x * sellSize, target.y * sellSize, sellSize, sellSize); }
首先清空一下画布,然后画了整个的表格(可以说是地图),花蛇身和蛇头,最后画目标点(也就是虫子)。
/** * 绘制图形 */ @Override protected void paintComponent(Graphics g) { g.clearRect(0, 0, tableWidth * sellSize, tableHeight * sellSize); drawMap(g); drawSnake(g); drawTarget(g); } /** * 绘制目标点(虫子) * * @param g * 画布 */ private void drawTarget(Graphics g) { g.setColor(new Color(0xdd7744)); g.fillRect(target.x * sellSize, target.y * sellSize, sellSize, sellSize); } /** * 绘制蛇 * * @param g * 画布 */ private void drawSnake(Graphics g) { drawSnakeBody(g); drawSnakeHead(g); } /** * 绘制蛇头 * * @param g * 画布 */ private void drawSnakeHead(Graphics g) { g.setColor(new Color(0x115599)); Point p = snake.peek(); g.fillRect(p.x * sellSize, p.y * sellSize, sellSize, sellSize); } /** * 绘制蛇身 * * @param g * 画布 */ private void drawSnakeBody(Graphics g) { g.setColor(new Color(0x3399cc)); for (Point p : snake) { g.fillRect(p.x * sellSize, p.y * sellSize, sellSize, sellSize); } } /** * 绘制地图 * * @param g * 画布 */ private void drawMap(Graphics g) { g.setColor(new Color(0x555555)); for (int i = 0; i < tableWidth; i++) { for (int j = 0; j < tableHeight; ++j) { g.drawRect(i * sellSize, j * sellSize, sellSize, sellSize); } } }
总体代码预览:
package snakes; import java.awt.Color; import java.awt.Graphics; import java.awt.Point; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Random; import javax.swing.JOptionPane; import javax.swing.JPanel; public class GamePanel extends JPanel implements KeyListener { private static final long serialVersionUID = -7269846451378790762L; private static final Random random = new Random(); /** * 分数 */ private int score = 0; /** * 每一个单元格的尺寸,像素 */ private final int sellSize = 20; /** * 地图横向包含的单元格数 */ private final int tableWidth = 30; /** * 地图纵向包含的单元格数 */ private final int tableHeight = 20; /** * 贪吃蛇的点链表 */ private final LinkedList<Point> snake = new LinkedList<Point>(); private final Direction[] da = { Direction.UP, Direction.DOWM, Direction.LEFT, Direction.RIGHT }; private Direction direction; /** * 虫子的位置 */ private Point target = new Point(random.nextInt(tableWidth), random.nextInt(tableHeight)); /** * 贪吃蛇初始长度 */ private final int initsnakeLenght = 3; private final Map<Integer, Direction> keyMap = new HashMap<Integer, Direction>(); /** * 移动速度 */ private volatile long speed = 1; private volatile long crrTime = System.currentTimeMillis(); public GamePanel () { initSnakeDirection(); initKeyMap(); initSnake(); initGameLoop(); } /** * 初始化蛇运行方向 */ private void initSnakeDirection() { direction = getRandomDirection(); } /** * 随机生成方向 * * @return 方向 */ private Direction getRandomDirection() { return da[random.nextInt(4)]; } /** * 初始化游戏线程 */ private void initGameLoop() { /** * 游戏主循环线程 */ new Thread() { @Override public void run() { while (true) { if (System.currentTimeMillis() - crrTime > 500 / speed) { synchronized (GamePanel.class) { moveSnake(); if (!checkSnack()) { JOptionPane.showMessageDialog(null, "Game Over!"); return; } } repaint(); crrTime = System.currentTimeMillis(); } } }; }.start(); } /** * 初始化蛇链表 */ private void initSnake() { Point p = new Point(random.nextInt(tableWidth - initsnakeLenght >> 1) + initsnakeLenght, random.nextInt(tableHeight - initsnakeLenght >> 1) + initsnakeLenght); snake.add(p); for (int i = 0; i < initsnakeLenght - 1; ++i) { p = direction.getPreviousPoint(p); snake.add(p); } } /** * 初始化按键和方向的映射 */ private void initKeyMap() { keyMap.put(KeyEvent.VK_UP, Direction.UP); keyMap.put(KeyEvent.VK_DOWN, Direction.DOWM); keyMap.put(KeyEvent.VK_LEFT, Direction.LEFT); keyMap.put(KeyEvent.VK_RIGHT, Direction.RIGHT); } /** * 判断贪吃蛇是否撞墙或撞到自己 * * @return */ protected boolean checkSnack() { return !isAgainstWall() && !isAgainstSelf(); } /** * 判断蛇头是否撞到自己的身体,是则返回true,否返回false * * @return */ private boolean isAgainstSelf() { Point p = snake.getFirst(); Iterator<Point> it = snake.iterator(); it.next(); while (it.hasNext()) { Point pBody = it.next(); if (p.equals(pBody)) { return true; } } return false; } /** * 判断蛇头是否撞到墙壁,是则返回true,否返回false * * @return */ private boolean isAgainstWall() { Point p = snake.getFirst(); int x = p.x, y = p.y; return x < 0 || x >= tableWidth || y < 0 || y >= tableHeight; } @Override public void keyPressed(KeyEvent e) {} @Override public void keyReleased(KeyEvent e) { Direction newd = keyMap.get(e.getKeyCode()); if (newd != null && direction.isAvailable(newd)) { direction = newd; synchronized (GamePanel.class) { moveSnake(); if (!checkSnack()) { JOptionPane.showMessageDialog(null, "Game Over!"); return; } } repaint(); crrTime = System.currentTimeMillis(); } } @Override public void keyTyped(KeyEvent e) {} /** * 移动贪吃蛇,包括吃虫 */ private void moveSnake() { snake.addFirst(direction.getNextPoint(snake.getFirst())); if (snake.getFirst().equals(target)) { target = new Point(random.nextInt(tableWidth), random.nextInt(tableHeight)); ++speed; ++score; } else { snake.removeLast(); } } /** * 绘制图形 */ @Override protected void paintComponent(Graphics g) { g.clearRect(0, 0, tableWidth * sellSize, tableHeight * sellSize); drawMap(g); drawSnake(g); drawTarget(g); } /** * 绘制目标点(虫子) * * @param g * 画布 */ private void drawTarget(Graphics g) { g.setColor(new Color(0xdd7744)); g.fillRect(target.x * sellSize, target.y * sellSize, sellSize, sellSize); } /** * 绘制蛇 * * @param g * 画布 */ private void drawSnake(Graphics g) { drawSnakeBody(g); drawSnakeHead(g); } /** * 绘制蛇头 * * @param g * 画布 */ private void drawSnakeHead(Graphics g) { g.setColor(new Color(0x115599)); Point p = snake.peek(); g.fillRect(p.x * sellSize, p.y * sellSize, sellSize, sellSize); } /** * 绘制蛇身 * * @param g * 画布 */ private void drawSnakeBody(Graphics g) { g.setColor(new Color(0x3399cc)); for (Point p : snake) { g.fillRect(p.x * sellSize, p.y * sellSize, sellSize, sellSize); } } /** * 绘制地图 * * @param g * 画布 */ private void drawMap(Graphics g) { g.setColor(new Color(0x555555)); for (int i = 0; i < tableWidth; i++) { for (int j = 0; j < tableHeight; ++j) { g.drawRect(i * sellSize, j * sellSize, sellSize, sellSize); } } } }
下节预告:抽离Snake类
上一篇: Redis - 发布订阅机制详解
下一篇: 代码重构-以贪吃蛇为示例(一)-重构之前