这是MVC模式的完整Java项目,编译运行SnakeApp.java即可开始游戏。
可扩展功能:
- 积分功能:可以创建得分规则的类(模型类的一部分), 在GameController的run()方法中计算得分
- 变速功能:比如加速功能,减速功能,可以在GameController的keyPressed()方法中针对特定的按键设置每一次移动之间的时间间隔,将Thread.sleep(Settings.DEFAULT_MOVE_INTERVAL);替换为动态的时间间隔即可
- 更漂亮的游戏界面:修改GameView中的drawXXX方法,比如可以将食物渲染为一张图片,Graphics有drawImage方法
View
public class SnakeApp {
public void init() {
JFrame window = new JFrame("一只长不大的蛇");
Grid grid = new Grid(50*Settings.DEFAULT_NODE_SIZE,50*Settings.DEFAULT_NODE_SIZE);
GameView gameView = new GameView(grid);
gameView.initCanvas();
GameController gameController = new GameController(grid);
window.setPreferredSize(new Dimension(526,548));
window.add(gameView.getCanvas(),BorderLayout.CENTER);
GameView.draw();
window.addKeyListener((KeyListener)gameController);
new Thread(gameController).start();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.pack();
window.setVisible(true);
}
public static void main(String[] args) {
SnakeApp snakeApp = new SnakeApp();
snakeApp.init();
}
}
public class GameView {
private final Grid grid;
private static JPanel canvas;
public GameView(Grid grid) {
this.grid = grid;
}
public static void draw() {
canvas.repaint();
}
public JPanel getCanvas() {
return canvas;
}
public void initCanvas() {
canvas = new JPanel() {
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
drawGridBackground(graphics);
drawSnake(graphics, grid.getSnake());
drawFood(graphics, grid.getFood());
}
};
}
public void drawSnake(Graphics graphics, Snake snake) {
for(Iterator<Node> i = snake.body.iterator();i.hasNext();) {
Node bodyNode = (Node)i.next();
drawSquare(graphics, bodyNode,Color.BLUE);
}
}
public void drawFood(Graphics graphics, Node food) {
drawCircle(graphics,food,Color.ORANGE);
}
public void drawGridBackground(Graphics graphics) {
graphics.setColor(Color.GRAY);
canvas.setBackground(Color.BLACK);
for(int i=0 ; i < 50 ; i++) {
graphics.drawLine(0, i*Settings.DEFAULT_NODE_SIZE, this.grid.getWidth(), i*Settings.DEFAULT_NODE_SIZE);
}
for(int i=0 ; i <50 ; i++) {
graphics.drawLine(i*Settings.DEFAULT_NODE_SIZE, 0, i*Settings.DEFAULT_NODE_SIZE , this.grid.getHeight());
}
graphics.setColor(Color.red);
graphics.fillRect(0, 0, this.grid.width, Settings.DEFAULT_NODE_SIZE);
graphics.fillRect(0, 0, Settings.DEFAULT_NODE_SIZE, this.grid.height);
graphics.fillRect(this.grid.width, 0, Settings.DEFAULT_NODE_SIZE,this.grid.height);
graphics.fillRect(0, this.grid.height, this.grid.width+10,Settings.DEFAULT_NODE_SIZE);
}
public static void showGameOverMessage() {
JOptionPane.showMessageDialog(null,"游戏结束","短暂的蛇生到此结束", JOptionPane.INFORMATION_MESSAGE);
}
private void drawSquare(Graphics graphics, Node squareArea, Color color) {
graphics.setColor(color);
int size = Settings.DEFAULT_NODE_SIZE;
graphics.fillRect(squareArea.getX(), squareArea.getY(), size - 1, size - 1);
}
private void drawCircle(Graphics graphics, Node squareArea, Color color) {
graphics.setColor(color);
int size = Settings.DEFAULT_NODE_SIZE;
graphics.fillOval(squareArea.getX(), squareArea.getY(), size, size);
}
}
Controller
public class GameController implements KeyListener, Runnable{
private Grid grid;
private boolean running;
public GameController(Grid grid){
this.grid = grid;
this.running = true;
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
switch(keyCode) {
case KeyEvent.VK_UP:
grid.changeDirection(Direction.UP);
break;
case KeyEvent.VK_DOWN:
grid.changeDirection(Direction.DOWN);
break;
case KeyEvent.VK_LEFT:
grid.changeDirection(Direction.LEFT);
break;
case KeyEvent.VK_RIGHT:
grid.changeDirection(Direction.RIGHT);
break;
}
isOver(grid.nextRound());
GameView.draw();
}
private void isOver(boolean flag) {
if(!flag) {
this.running = false;
GameView.showGameOverMessage();
System.exit(0);
}
}
@Override
public void run() {
while(running) {
try {
Thread.sleep(Settings.DEFAULT_MOVE_INTERVAL);
isOver(grid.nextRound());
GameView.draw();
} catch (InterruptedException e) {
break;
}
}
running = false;
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
}
Model
public class Grid {
private Snake snake;
int width;
int height;
Node food;
private Direction snakeDirection =Direction.LEFT;
public Grid(int length, int high) {
super();
this.width = length;
this.height = high;
initSnake();
food = creatFood();
}
private void initSnake() {
snake = new Snake();
int x = width/2;
int y = height/2;
for(int i = 0;i<5;i++) {
snake.addTail(new Node(x, y));
x = x+Settings.DEFAULT_NODE_SIZE;
}
}
private Node creatFood() {
int x,y;
do {
x =(int)(Math.random()*100)+10;
y =(int)(Math.random()*100)+10;
System.out.println(x);
System.out.println(y);
System.out.println(this.width);
System.out.println(this.height);
}while(x>=this.width-10 || y>=this.height-10 || snake.hasNode(new Node(x,y)));
food = new Node(x,y);
return food;
}
public boolean nextRound() {
Node trail = snake.move(snakeDirection);
Node snakeHead = snake.getBody().removeFirst();
if(snakeHead.getX()<=width-10 && snakeHead.getX()>=10
&& snakeHead.getY()<=height-10 && snakeHead.getY()>=10
&& !snake.hasNode(snakeHead)) {
if(snakeHead.equals(food)) {
snake.addTail(trail);
food = creatFood();
}
snake.getBody().addFirst(snakeHead);
return true;
}
return false;
}
public Node getFood() {
return food;
}
public Snake getSnake() {
return snake;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void changeDirection(Direction newDirection){
snakeDirection = newDirection;
}
}
public class Snake implements Cloneable{
public LinkedList<Node> body = new LinkedList<>();
public Node move(Direction direction) {
Node n;
switch (direction) {
case UP:
n = new Node(this.getHead().getX(),this.getHead().getY()-Settings.DEFAULT_NODE_SIZE);
break;
case DOWN:
n = new Node(this.getHead().getX(),this.getHead().getY()+Settings.DEFAULT_NODE_SIZE);
break;
case RIGHT:
n = new Node(this.getHead().getX()+Settings.DEFAULT_NODE_SIZE,this.getHead().getY());
break;
default:
n = new Node(this.getHead().getX()-Settings.DEFAULT_NODE_SIZE,this.getHead().getY());
}
Node temp = this.body.getLast();
this.body.addFirst(n);
this.body.removeLast();
return temp;
}
public Node getHead() {
return body.getFirst();
}
public Node getTail() {
return body.getLast();
}
public Node addTail(Node area) {
this.body.addLast(area);
return area;
}
public LinkedList<Node> getBody(){
return body;
}
public boolean hasNode(Node node) {
Iterator<Node> it = body.iterator();
Node n = new Node(0,0);
while(it.hasNext()) {
n = it.next();
if(n.getX() == node.getX() && n.getY() == node.getY()) {
return true;
}
}
return false;
}
}
public enum Direction {
UP(0),
DOWN(1),
LEFT(2),
RIGHT(3);
private final int directionCode;
public int directionCode() {
return directionCode;
}
Direction(int directionCode){
this.directionCode = directionCode;
}
}
public class Node {
private int x;
private int y;
public Node(int x, int y) {
this.x = ((int)(x/10))*10;
this.y = ((int)(y/10))*10;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public boolean equals(Object n) {
Node temp;
if(n instanceof Node) {
temp = (Node)n;
if(temp.getX()==this.getX() && temp.getY()==this.getY())
return true;
}
return false;
}
}
public class Settings {
public static int DEFAULT_NODE_SIZE = 10;
public static int DEFAULT_MOVE_INTERVAL = 200;
}