最近闲来无事,迟迟不分配任务,看完并发编程实战后脑子彻底乱成浆糊了

无聊之余动手写一点小游戏打发一下时间

贪吃蛇游戏分析

贪吃蛇游戏主要表现为蛇吃食物,吃到食物即身体边长,碰到墙壁或吃到自己即死亡

主要的难点如下:

1:控制蛇转向,不能与当前方向相反,即不能首尾互换

2:蛇头转向后蛇身也应当跟随蛇头的路径

3:蛇吃食物后身体的增长

4:蛇死亡条件的判断

游戏截图

java编写简易贪吃蛇 java贪吃蛇设计思路_贪吃蛇

基础类设计

贪吃蛇游戏的类设计很简单,应当主要包括一个地图类,一个节点类,一个蛇类,一个食物类

地图类存储所有点的信息(但是此处笔者没有直接建立地图类,在界面生成时存储地图信息,这是一种不好的习惯,大家不要学~)

节点类记录蛇身体的节点的信息,食物类与节点类类似,只记录当前坐标

蛇类则比较复杂,要求包含蛇的移动,长度增加等方法

基础类方法设计


节点类和食物类

节点和食物类都是普通的POJO类,主要存储节点的信息和食物的信息

主要区别在于蛇的链表实现要求节点类中需要额外存储节点前后节点的引用

代码如下

package edu.game.snake.model;

public class SnakeNode {
	private int x;
	private int y;
	private SnakeNode priv;
	private SnakeNode next;
	private boolean isTurn;
	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
	public SnakeNode getPriv() {
		return priv;
	}
	public void setPriv(SnakeNode priv) {
		this.priv = priv;
	}
	public SnakeNode getNext() {
		return next;
	}
	public void setNext(SnakeNode next) {
		this.next = next;
	}
	public boolean isTurn() {
		return isTurn;
	}
	public void setTurn(boolean isTurn) {
		this.isTurn = isTurn;
	}
}



package edu.game.snake.model;

import java.util.Random;

public class Food {
	private int x;
	private int y;
	public Food() {
		// TODO Auto-generated constructor stub
		x=new Random().nextInt(46);
		y=new Random().nextInt(46);
	}
	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
}



考虑到两个类的共同特征,可以将Food类作为抽象基类然后允许蛇的节点类进行继承



蛇类

蛇类的设计是最复杂的

由于蛇的结构符合链表的数据结构

所以蛇类的设计应当参照链表的结构

又由于蛇吃食物会增加长度,所以使用双向链表进行实现会比较简单


不要忘记实现迭代器方法


在这里讲解一下吃食物方法的判断

先通过按键响应判断蛇头的下一步位置,与食物位置进行比对,相同则调用增长方法,不同则进行移动

源代码如下

package edu.game.snake.model;

import java.util.*;

public class Snake {
	public static final int goLeft=0;
	public static final int goRight=1;
	public static final int goUp=2;
	public static final int goDown=3;
	private SnakeNode head;
	private SnakeNode tail;
	private Iterator<SnakeNode> iterator;
	public Snake() {
		// TODO Auto-generated constructor stub
		head=new SnakeNode();
		tail=new SnakeNode();
		head.setX(new Random().nextInt(30)+5);
		tail.setX(head.getX());
		head.setY(new Random().nextInt(30)+5);
		tail.setY(head.getY());
		head.setNext(tail);
		tail.setPriv(head);
	}
	public int getLength(){
		int length=0;
		iterator = getIterator();
		if (iterator.hasNext()) {
			length++;
		}
		return length;
	}
	
	public boolean move(int keyPressed,SnakeNode newHead){
		switch (keyPressed) {
		case goLeft:
			if (head.getX()<=head.getNext().getX()) {
				newHead.setNext(head);
				head.setPriv(newHead);
				head=newHead;
				tail=tail.getPriv();
				tail.setNext(null);
				if (!isAlive()) {
					return false;
				}
			}
			break;
		case goRight:
			if (head.getX()>=head.getNext().getX()) {
				newHead.setNext(head);
				head.setPriv(newHead);
				head=newHead;
				tail=tail.getPriv();
				tail.setNext(null);
				if (!isAlive()) {
					return false;
				}
			}
			break;
		case goUp:
			if (head.getY()<=head.getNext().getY()) {
				newHead.setNext(head);
				head.setPriv(newHead);
				head=newHead;
				tail=tail.getPriv();
				tail.setNext(null);
				if (!isAlive()) {
					return false;
				}
			}
			break;
		case goDown:
			if (head.getY()>=head.getNext().getY()) {
				newHead.setNext(head);
				head.setPriv(newHead);
				head=newHead;
				tail=tail.getPriv();
				tail.setNext(null);
				if (!isAlive()) {
					return false;
				}
			}
			break;

		default:
			break;
		}
		return true;
	}
	public void add(SnakeNode newHead){
		newHead.setNext(head);
		head.setPriv(newHead);
		head=newHead;
	}
	public void add(){
		SnakeNode newTail=new SnakeNode();
		SnakeNode priv = tail.getPriv();
		if (tail.getX()>priv.getX()) {
			newTail.setX(tail.getX()+1);
			newTail.setY(tail.getY());
		}else if (tail.getX()<priv.getX()) {
			newTail.setX(tail.getX()-1);
			newTail.setY(tail.getY());
		}else if (tail.getY()>priv.getY()) {
			newTail.setX(tail.getX());
			newTail.setY(tail.getY()+1);
		}else if (tail.getY()<priv.getY()) {
			newTail.setX(tail.getX());
			newTail.setY(tail.getY()-1);
		}
		tail.setNext(newTail);
		newTail.setPriv(tail);
		tail=newTail;
	}
	public boolean isAlive(){
		if (head.getX()<0||head.getY()<0||head.getX()>=46||head.getY()>=46) {
			return false;
		}
		SnakeNode node=head;
		iterator=getIterator();
		while (iterator.hasNext()) {
			node=iterator.next();
			if (head.getX()==node.getX()&&head.getY()==node.getY()) {
				return false;
			}
		}
		return true;
	}
	public Iterator<SnakeNode> getIterator(){
		return new Iterator<SnakeNode>() {
			SnakeNode node=head;
			@Override
			public SnakeNode next() {
				if (node.getNext()!=null) {
					node=node.getNext();
					return node;
				}
				return null;
			}
			
			@Override
			public boolean hasNext() {
				if (node.getNext()!=null) {
					return true;
				}
				return false;
			}
			
		};
	}
	public SnakeNode getHead() {
		return head;
	}
	public void setHead(SnakeNode head) {
		this.head = head;
	}
	public SnakeNode getTail() {
		return tail;
	}
	public void setTail(SnakeNode tail) {
		this.tail = tail;
	}
}



主要操作实现以及面板类

这个类其实设计的很有问题,因为他混杂了许多其他方法,没有实现“高内聚、低耦合”有兴趣的读者可以自己进行拆分或重构


主要思路是利用JButton二维数组来生成地图

通过JButton背景色的不同来表示食物、蛇身、蛇头已经背景的区别

通过添加全局的keyListener来监听用户按键事件并相应

出于游戏性的的考虑我们设计了一个线程类,来使蛇进行不间断的移动,而不是每次移动都需要用户按键操作


其中的主要方法还有每次操作后对界面的刷新,以及判断是否吃到食物和吃到食物后重新生成食物的方法

代码如下

package edu.game.snake.show;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Iterator;

import javax.swing.*;

import edu.game.snake.model.Food;
import edu.game.snake.model.Snake;
import edu.game.snake.model.SnakeNode;

public class MainPanel extends JFrame {
	private static final long serialVersionUID = 548886555237508754L;
	private JButton[][] snakeButton = new JButton[46][46];
	private static Snake snake;
	private Iterator<SnakeNode> iterator;
	private static MainPanel m;
	public static int keyPressd = 0;
	private Food food = new Food();

	public static void main(String[] args) {
		m = new MainPanel();
		m.init();
		m.snake();
		m.displayFood();
		new keepMoving().start();
	}

	public void init() {
		JFrame j = new JFrame("贪吃蛇");
		j.setBounds(700, 240, 800, 800);
		j.setTitle("贪吃蛇");
		j.setLayout(new GridLayout(46, 46));
		JButton button;
		for (int i = 0; i < 46 * 46; i++) {
			button = new JButton();
			snakeButton[i / 46][i % 46] = button;
			button.setBackground(Color.BLACK);
			button.setEnabled(false);
			button.setBorder(null);
			j.add(button);
		}
		j.setVisible(true);
		j.addKeyListener(new KeyListener() {

			@Override
			public void keyTyped(KeyEvent e) {
				// TODO Auto-generated method stub

			}

			@Override
			public void keyReleased(KeyEvent e) {
				// TODO Auto-generated method stub

			}

			@Override
			public void keyPressed(KeyEvent e) {
				// TODO Auto-generated method stub
				synchronized (snake) {
					SnakeNode newHead;
					switch (e.getKeyCode()) {
					case KeyEvent.VK_LEFT:
						keyPressd = 0;
						newHead = new SnakeNode();
						newHead.setX(snake.getHead().getX() - 1);
						newHead.setY(snake.getHead().getY());
						m.judge(newHead);
						if (!snake.move(Snake.goLeft, newHead)) {
							System.exit(0);
						}
						m.refresh();
						break;
					case KeyEvent.VK_RIGHT:
						keyPressd = 1;
						newHead = new SnakeNode();
						newHead.setX(snake.getHead().getX() + 1);
						newHead.setY(snake.getHead().getY());
						m.judge(newHead);
						if (!snake.move(Snake.goRight, newHead)) {
							System.exit(0);
						}
						m.refresh();
						break;
					case KeyEvent.VK_UP:
						keyPressd = 2;
						newHead = new SnakeNode();
						newHead.setX(snake.getHead().getX());
						newHead.setY(snake.getHead().getY() - 1);
						m.judge(newHead);
						if (!snake.move(Snake.goUp, newHead)) {
							System.exit(0);
						}
						m.refresh();
						break;
					case KeyEvent.VK_DOWN:
						keyPressd = 3;
						newHead = new SnakeNode();
						newHead.setX(snake.getHead().getX());
						newHead.setY(snake.getHead().getY() + 1);
						m.judge(newHead);
						if (!snake.move(Snake.goDown, newHead)) {
							System.exit(0);
						}
						m.refresh();
						break;

					default:
						break;
					}
				}
			}

		});
	}

	public static class keepMoving extends Thread {
		SnakeNode newHead;

		@Override
		public void run() {
			// TODO Auto-generated method stub
			while (true) {
				switch (keyPressd) {
				case 0:
					newHead = new SnakeNode();
					newHead.setX(snake.getHead().getX() - 1);
					newHead.setY(snake.getHead().getY());
					m.judge(newHead);
					if (!snake.move(Snake.goLeft, newHead)) {
						System.exit(0);
					}
					m.refresh();
					break;
				case 1:
					newHead = new SnakeNode();
					newHead.setX(snake.getHead().getX() + 1);
					newHead.setY(snake.getHead().getY());
					m.judge(newHead);
					if (!snake.move(Snake.goRight, newHead)) {
						System.exit(0);
					}
					m.refresh();
					break;
				case 2:
					newHead = new SnakeNode();
					newHead.setX(snake.getHead().getX());
					newHead.setY(snake.getHead().getY() - 1);
					m.judge(newHead);
					if (!snake.move(Snake.goUp, newHead)) {
						System.exit(0);
					}
					m.refresh();
					break;
				case 3:
					newHead = new SnakeNode();
					newHead.setX(snake.getHead().getX());
					newHead.setY(snake.getHead().getY() + 1);
					m.judge(newHead);
					if (!snake.move(Snake.goDown, newHead)) {
						System.exit(0);
					}
					m.refresh();
					break;

				default:
					break;
				}
				try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}

	public void snake() {
		snake = new Snake();
		iterator = snake.getIterator();
		SnakeNode node = snake.getHead();
		snakeButton[node.getY()][node.getX()].setBackground(Color.GRAY);
		while (iterator.hasNext()) {
			node = iterator.next();
			snakeButton[node.getY()][node.getX()].setBackground(Color.WHITE);
		}
	}

	public void displayFood() {
		snakeButton[food.getY()][food.getX()].setBackground(Color.YELLOW);
	}

	public boolean judge(SnakeNode newHead) {
		if (newHead.getX() == food.getX() && newHead.getY() == food.getY()) {
			snake.add();
			food = new Food();
			m.displayFood();
			return true;
		}
		return false;
	}

	public void refresh() {
		for (int i = 0; i < snakeButton.length; i++) {
			for (int j = 0; j < snakeButton[i].length; j++) {
				snakeButton[i][j].setBackground(Color.BLACK);
			}
		}
		iterator = snake.getIterator();
		SnakeNode node = snake.getHead();
		snakeButton[node.getY()][node.getX()].setBackground(Color.GRAY);
		while (iterator.hasNext()) {
			node = iterator.next();
			snakeButton[node.getY()][node.getX()].setBackground(Color.WHITE);
		}
		m.displayFood();
	}

}



思考

贪吃蛇游戏是一个十分简单且易学的游戏

希望读者能够在专注于实现时能够注意同时注意类的设计等问题

比如引入观察者模式等

有兴趣的读者可以尝试一下贪吃蛇自动吃食物算法的实现