面对这么乱的代码,第一步就是把想关性不太大的部分抽离出去。具体操作:
- 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类