*看了GUI,虽然java的GUI被淘汰了,我还是做了一个贪吃蛇练习下链表和逻辑思路,重点是面向对象的知识点。
思路:
三个对象,地图(背景),蛇,果实
三个事件,蛇碰壁,碰自身,吃到果实
一个线程,间隔固定的时间移动蛇身并刷新界面
先上效果图
游戏开始:
撞墙:
撞自身:
/**
* 盒子类
* 构成蛇的基本单位
* */
public class Box {
private int x; //横坐标
private int y; //纵坐标 从上往下变大
private Box up; //上一个box
private Box down; //下一个box
public Box() {
}
public Box(int x,int y) {
this.x = x;
this.y = y;
}
...
}
这个box类,主要记录的是一个坐标,并没有继承GUI里的组件,只要获得坐标,再给组件上色就行了。
/**
* 蛇类
* */
public class Snake {
private Box first; //蛇头
private Box last; //蛇尾
private int len; //蛇长度
private int direction; // 方向 0up 1down 2right 3left
public Snake(Box b) {
init(b);
}
private void init(Box b) {
this.first = b;
this.last = b;
this.len = 1;
direction = (int)(Math.random()*4); //随机一个方向
}
....
}
//向上移动
public void up() {
Box newFirst = new Box(first.getX(),first.getY()-1);
newSnake(newFirst);
}
private void newSnake(Box newFirst) {
newFirst.setDown(first);
first.setUp(newFirst);
this.first = newFirst;
Box newLast = last.getUp();
newLast.setDown(null);
last.setUp(null);
this.last = newLast;
}
//吃食物方法
public void eatBox(Box box) {
first.setUp(box);
box.setDown(first);
this.first = box;
len+=1;
}
蛇类,移动的思路是,在移动方向前面加上一个Box对象,再去掉尾部的last,就实现了移动,而不用把整条蛇的每个box都移动。吃食物也很简单,把食物加在头first的上一个box,然后食物作为新头。这个思路来自集合容器LinkedList。
因为蛇是固定间隔时间移动的,按键上下左右只是改变方向,所以这里设置了一个方向direction属性。
public class SnakeFrame extends JFrame{
private Snake snake; //蛇
private Box food; //食物
private boolean haveFood = false; //食物是否存在
private JPanel panel;
private JTextField[][] texts;
public SnakeFrame() {
initFrame(); // 初始化Frame
initComponent(); // 初始化组件
initComponentData(); // 初始化组件数据
addComponent(); // 添加组件
this.setVisible(true); //frame可见
new SnakeThread().start(); // 线程start
}
背景类,有个haveFood属性,根据这个属性知道当前页面有没有食物food存在,没有再生成一个食物。
格子我用的文本框JTextField,创建20x20个文本框并用网格布局,根据蛇每个box和食物的坐标再给对应的JTextField上色。
//给蛇和食物上色
//给蛇和食物上色
for(int j = 0;j < 20;j++) { //纵坐标
for(int k = 0;k < 20;k++) { //横坐标
//蛇上色
boolean flag = true;
Box temp = snake.getFirst();
for(int i = 0;i < snake.getLen();i++) {
if(j == temp.getY() && k == temp.getX()) {
texts[temp.getY()][temp.getX()].setBackground(Color.BLACK);
flag = false;
break;
}
if(temp.getDown() != null) {
temp = temp.getDown();
}
}
//食物上色
if(flag) {
if(k == food.getX() && j == food.getY()) {
texts[j][k].setBackground(Color.BLUE);
}else {
texts[j][k].setBackground(Color.WHITE);
}
}
}
}
在线程里每次遍历所有坐标,如果是当前坐标是蛇的身体的坐标,便给对应的文本框texts[][]上黑色,根据食物坐标给对应texts[][]上蓝色,其他上白色。
//不断移动
if(snake.getDirection() == 0) { //方向向上
if(snake.getFirst().getX() == food.getX() && snake.getFirst().getY()-1 == food.getY()) {
snake.eatBox(food); //上方有食物 吃食物
SnakeFrame.this.haveFood = false;
}else {
snake.up(); //没有直接向上移动
}
}
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_UP) { // 当按下的是向上键
if(snake.getDirection() != 1) { //如果方向不是向下,就把方向调整为向上
snake.setDirection(0);
}
}
...
}
给自定义背景Frame添加按键监听并集中在frame上,当按上,如果当时方向是朝下就不能改变方向,就是不能掉头。
不断移动和食物刷新都在刷新页面的线程里,按键监听只是改变方向。
撞墙和撞自身添加在线程里,不断移动之后,撞墙我的判断方法是蛇头的坐标超出范围,撞自身是遍历蛇所有box的坐标,如果和蛇头坐标相同就是撞上了,便把蛇头上红色。
源码连接:
https://pan.baidu.com/s/1eUO3LWZ9G7DIBQ95i-DuCw 提取码: 4d6n