|
// GreedSnake.java /** * 程序名称:贪食蛇 * 原作者:BigF * 修改者:algo * 说明:我以前也用C写过这个程序,现在看到BigF用Java写的这个,发现虽然作者自称是Java的初学者, * 但是明显编写程序的素养不错,程序结构写得很清晰,有些细微得地方也写得很简洁,一时兴起之 * 下,我认真解读了这个程序,发现数据和表现分开得很好,而我近日正在学习MVC设计模式, * 因此尝试把程序得结构改了一下,用MVC模式来实现,对源程序得改动不多。 * 我同时也为程序增加了一些自己理解得注释,希望对大家阅读有帮助。 */ public class GreedSnake { public static void main(String[] args) { SnakeModel model = new SnakeModel(20,30); SnakeControl control = new SnakeControl(model); SnakeView view = new SnakeView(model,control); model.addObserver(view); (new Thread(model)).start(); } }
//---------------------------------------------------------------------- // SnakeModel.java import javax.swing.*; import java.util.Arrays; import java.util.LinkedList; import java.util.Observable; import java.util.Random;
/** * 游戏的Model类,负责所有游戏相关数据及运行 */ class SnakeModel extends Observable implements Runnable { boolean[][] matrix; // 指示位置上有没蛇体或食物 LinkedList nodeArray = new LinkedList(); // 蛇体 Node food; int maxX; int maxY; int direction = 2; // 蛇运行的方向 boolean running = false; // 运行状态
int timeInterval = 200; // 时间间隔,毫秒 double speedChangeRate = 0.75; // 每次得速度变化率 boolean paused = false; // 暂停标志
int score = 0; // 得分 int countMove = 0; // 吃到食物前移动的次数
// UP and DOWN should be even // RIGHT and LEFT should be odd public static final int UP = 2; public static final int DOWN = 4; public static final int LEFT = 1; public static final int RIGHT = 3;
public SnakeModel( int maxX, int maxY) { this.maxX = maxX; this.maxY = maxY;
reset(); }
public void reset(){ direction = SnakeModel.UP; // 蛇运行的方向 timeInterval = 200; // 时间间隔,毫秒 paused = false; // 暂停标志 score = 0; // 得分 countMove = 0; // 吃到食物前移动的次数
// initial matirx, 全部清0 matrix = new boolean[maxX][]; for (int i = 0; i < maxX; ++i) { matrix[i] = new boolean[maxY]; Arrays.fill(matrix[i], false); }
// initial the snake // 初始化蛇体,如果横向位置超过20个,长度为10,否则为横向位置的一半 int initArrayLength = maxX > 20 ? 10 : maxX / 2; nodeArray.clear(); for (int i = 0; i < initArrayLength; ++i) { int x = maxX / 2 + i; int y = maxY / 2; nodeArray.addLast(new Node(x, y)); matrix[x][y] = true; }
// 创建食物 food = createFood(); matrix[food.x][food.y] = true; }
public void changeDirection(int newDirection) { // 改变的方向不能与原来方向同向或反向 if (direction % 2 != newDirection % 2) { direction = newDirection; } }
/** * 运行一次 * @return */ public boolean moveOn() { Node n = (Node) nodeArray.getFirst(); int x = n.x; int y = n.y;
// 根据方向增减坐标值 switch (direction) { case UP: y--; break; case DOWN: y++; break; case LEFT: x--; break; case RIGHT: x++; break; }
// 如果新坐标落在有效范围内,则进行处理 if ((0 <= x && x < maxX) && (0 <= y && y < maxY)) { if (matrix[x][y]) { // 如果新坐标的点上有东西(蛇体或者食物) if (x == food.x && y == food.y) { // 吃到食物,成功 nodeArray.addFirst(food); // 从蛇头赠长
// 分数规则,与移动改变方向的次数和速度两个元素有关 int scoreGet = (10000 - 200 * countMove) / timeInterval; score += scoreGet > 0 ? scoreGet : 10; countMove = 0;
food = createFood(); // 创建新的食物 matrix[food.x][food.y] = true; // 设置食物所在位置 return true; } else // 吃到蛇体自身,失败 return false; } else { // 如果新坐标的点上没有东西(蛇体),移动蛇体 nodeArray.addFirst(new Node(x, y)); matrix[x][y] = true; n = (Node) nodeArray.removeLast(); matrix[n.x][n.y] = false; countMove++; return true; } } return false; // 触到边线,失败 }
public void run() { running = true; while (running) { try { Thread.sleep(timeInterval); } catch (Exception e) { break; }
if (!paused) { if (moveOn()) { setChanged(); // Model通知View数据已经更新 notifyObservers(); } else { JOptionPane.showMessageDialog(null, "you failed", "Game Over", JOptionPane.INFORMATION_MESSAGE); break; } } } running = false; }
private Node createFood() { int x = 0; int y = 0; // 随机获取一个有效区域内的与蛇体和食物不重叠的位置 do { Random r = new Random(); x = r.nextInt(maxX); y = r.nextInt(maxY); } while (matrix[x][y]);
return new Node(x, y); }
public void speedUp() { timeInterval *= speedChangeRate; }
public void speedDown() { timeInterval /= speedChangeRate; }
public void changePauseState() { paused = !paused; }
public String toString() { String result = ""; for (int i = 0; i < nodeArray.size(); ++i) { Node n = (Node) nodeArray.get(i); result += "[" + n.x + "," + n.y + "]"; } return result; } }
class Node { int x; int y;
Node(int x, int y) { this.x = x; this.y = y; } }
//--------------------------------------------------------------------- // SnakeControl.java import java.awt.event.KeyEvent; import java.awt.event.KeyListener;
/** * MVC中的Controler,负责接收用户的操作,并把用户操作通知Model */ public class SnakeControl implements KeyListener{ SnakeModel model;
public SnakeControl(SnakeModel model){ this.model = model; }
public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode(); if (model.running){ // 运行状态下,处理的按键 switch (keyCode) { case KeyEvent.VK_UP: model.changeDirection(SnakeModel.UP); break; case KeyEvent.VK_DOWN: model.changeDirection(SnakeModel.DOWN); break; case KeyEvent.VK_LEFT: model.changeDirection(SnakeModel.LEFT); break; case KeyEvent.VK_RIGHT: model.changeDirection(SnakeModel.RIGHT); break; case KeyEvent.VK_ADD: case KeyEvent.VK_PAGE_UP: model.speedUp(); break; case KeyEvent.VK_SUBTRACT: case KeyEvent.VK_PAGE_DOWN: model.speedDown(); break; case KeyEvent.VK_SPACE: case KeyEvent.VK_P: model.changePauseState(); break; default: } }
// 任何情况下处理的按键,按键导致重新启动游戏 if (keyCode == KeyEvent.VK_R || keyCode == KeyEvent.VK_S || keyCode == KeyEvent.VK_ENTER) { model.reset(); } }
public void keyReleased(KeyEvent e) { }
public void keyTyped(KeyEvent e) { } }
//---------------------------------------------------------------------- // SnakeView.java import javax.swing.*; import java.awt.*; import java.util.Iterator; import java.util.LinkedList; import java.util.Observable; import java.util.Observer;
/** * MVC模式中得Viewer,只负责对数据的显示,而不用理会游戏的控制逻辑 */ public class SnakeView implements Observer { SnakeControl control = null; SnakeModel model = null;
JFrame mainFrame; Canvas paintCanvas; JLabel labelScore;
public static final int canvasWidth = 200; public static final int canvasHeight = 300;
public static final int nodeWidth = 10; public static final int nodeHeight = 10;
public SnakeView(SnakeModel model, SnakeControl control) { this.model = model; this.control = control;
mainFrame = new JFrame("GreedSnake");
Container cp = mainFrame.getContentPane();
// 创建顶部的分数显示 labelScore = new JLabel("Score:"); cp.add(labelScore, BorderLayout.NORTH);
// 创建中间的游戏显示区域 paintCanvas = new Canvas(); paintCanvas.setSize(canvasWidth + 1, canvasHeight + 1); paintCanvas.addKeyListener(control); cp.add(paintCanvas, BorderLayout.CENTER);
// 创建底下的帮助栏 JPanel panelButtom = new JPanel(); panelButtom.setLayout(new BorderLayout()); JLabel labelHelp; labelHelp = new JLabel("PageUp, PageDown for speed;", JLabel.CENTER); panelButtom.add(labelHelp, BorderLayout.NORTH); labelHelp = new JLabel("ENTER or R or S for start;", JLabel.CENTER); panelButtom.add(labelHelp, BorderLayout.CENTER); labelHelp = new JLabel("SPACE or P for pause", JLabel.CENTER); panelButtom.add(labelHelp, BorderLayout.SOUTH); cp.add(panelButtom, BorderLayout.SOUTH);
mainFrame.addKeyListener(control); mainFrame.pack(); mainFrame.setResizable(false); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainFrame.setVisible(true); }
void repaint() { Graphics g = paintCanvas.getGraphics();
//draw background g.setColor(Color.WHITE); g.fillRect(0, 0, canvasWidth, canvasHeight);
// draw the snake g.setColor(Color.BLACK); LinkedList na = model.nodeArray; Iterator it = na.iterator(); while (it.hasNext()) { Node n = (Node) it.next(); drawNode(g, n); }
// draw the food g.setColor(Color.RED); Node n = model.food; drawNode(g, n);
updateScore(); }
private void drawNode(Graphics g, Node n) { g.fillRect(n.x * nodeWidth, n.y * nodeHeight, nodeWidth - 1, nodeHeight - 1); }
public void updateScore() { String s = "Score: " + model.score; labelScore.setText(s); }
public void update(Observable o, Object arg) { repaint(); } }
|