一个月没有写博客,中间其实学了不少东西,因为是自学,所以进度很慢,同时在看Java核心技术还有李刚的疯狂Java,这两本书讲的很详细,另外也学着看一点源代码。特别是IO流的部分,类太多让人比较晕,一连学了快一个星期,看了一点源代码,最后才缕清了思路。
之前学到list,发现这个ArrayList真是一个好东西,不用像数组一样预先要声明空间大小,可以动态的增长和缩减,可以保存任意类型的引用数据类型,当然基本数据类型可以自动拆装箱,所以也很方便。那一天学着学着突然福至心灵(没错就是这样),随手写了一个贪吃蛇,因为那一天在看内部类,匿名内部类这些知识。
最开始的版本,就是一个控制台程序,思路是创建一个贪吃蛇类,贪吃蛇类里面有一个节点内部类,模拟贪吃蛇是有很多节点组成的,然后有一个食物类,只要保存食物的坐标系就好了。游戏流程是:贪吃蛇在一个范围内游走寻找食物,“吃”到食物后就将自身增加一个节点,当贪吃蛇吃满屏幕或者撞到障碍物(蛇自己和墙壁)游戏结束。
首先要实现蛇的运动,其实就是改变蛇的每一个节点的坐标,蛇头往某一个方向前进一步(上下左右),蛇头后面一个节点的坐标更新为原来蛇头的坐标,第三节坐标更新成第二节的,第n节坐标更新为n-1节的坐标,这样蛇就移动啦,当然你也可以改进这个思路,比如蛇前进就在蛇的头节点前面增加一个节点,尾巴减少一个节点,减少一点运算量,当初没有想到,就用了最野蛮的一种方法,看官老爷见谅。
那么蛇怎么吃东西呢?也很简单,分为三部分:1.检测到食物 2.蛇吃食物 3.刷新食物坐标 具体的实验细节,检测食物其实就是当蛇头部坐标和食物坐标重合的时候,我们认为找到了食物; 而吃到食物就是蛇这个类内部增加一个节点类对象,具体来说就是ArrayList 里面添加一个 节点对象; 刷新食物坐标只要用到random类就好了,但是注意的点是食物不能刷新到蛇身上去。
最后检测蛇有没有撞到自己那就要遍历蛇身每一个坐标了,检测蛇头坐标有没有和蛇身重合,如果有那就是游戏结束,如果
没有游戏继续。以上主要分析给第一次写贪吃蛇的看官老爷听,实现的代码如下:
1.蛇简化版本:
package com.ycs.LinkList;
import java.util.ArrayList;
import java.util.Scanner;
public class Snake {
/**
* 成员变量
* x:当前头部 x 坐标
* y:当前头部 y 坐标
* length:蛇的长度
* len:蛇能活动的区域长度
* width:蛇能够活动的区域宽度
* pace:蛇每一次前进的长度
* snake:模拟一条蛇
*/
private int x;
private int y;
private int length;
private int len;
private int width;
private int pace;
private ArrayList snake = new ArrayList();
/**
* 构造函数,完成蛇的初始化
* @param x [蛇初始x坐标]
* @param y [蛇初始y坐标]
* @param len [蛇活动区域长度]
* @param width [活动区域宽度]
* @return [description]
*/
public Snake(int x, int y, int len, int width) {
super();
this.x = x;
this.y = y;
this.len = len;
this.width = width;
this.pace=1;
snake.add(new Node(x, y));
}
/**
* 内部类,蛇的每一节,仅仅保存每一节的x,y坐标
*/
private class Node {
private int x;
private int y;
public Node(int x, int y) {
super();
length++;
this.x = x;
this.y = y;
}
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;
}
}
/**
* 蛇吃食物的函数
* @param fd [食物对象]
* @return [吃成功true,失败false]
*/
public boolean eat(Food fd) {
if (fd.getX() == this.x && fd.getY() == this.y) {
//添加新的节点,新节点位置不确定,暂时设为-1 -1
snake.add(new Node(-1, -1));
return true;
}
return false;
}
/**
* 检测有没有和i,j坐标重合
* @param i [任意的横坐标]
* @param j [任意的纵坐标]
* @return [重合返回重合的蛇节点编号,不重合返回-1]
*/
public int show(int i, int j) {
int num = 0;
for (Node node : snake) {
if (node.getX() == i && node.getY() == j) {
// System.out.println("冲突检测");
return num;
}
num++;
}
return -1;
}
/**
* 根据字符串控制蛇的前进方向
* @param wasd [键盘输入的字符串,但只有“w”“a”“s”“d”能被识别]
*/
public void run(String wasd) {
switch (wasd.toLowerCase()) {
case "w":
this.direction = wasd;
this.y = this.y <= 0 ? this.width - this.pace : this.y - this.pace;
break;
case "s":
this.direction = wasd;
this.y = this.y >= this.width - this.pace ? 0 : this.y + this.pace;
break;
case "a":
this.direction = wasd;
this.x = this.x <= 0 ? this.len - this.pace : this.x - this.pace;
break;
case "d":
this.direction = wasd;
this.x = this.x >= this.len - this.pace ? 0 : this.x + this.pace;
break;
default:
switch (this.direction) {
case "w":
this.y = this.y <= 0 ? this.width - this.pace : this.y - this.pace;
break;
case "s":
this.y = this.y >= this.width - this.pace ? 0 : this.y + this.pace;
break;
case "a":
this.x = this.x <= 0 ? this.len - this.pace : this.x - this.pace;
break;
case "d":
this.x = this.x >= this.len - this.pace ? 0 : this.x + this.pace;
break;
}
break;
}
moveNode(this.x,this.y);
}
/**
* 移动蛇的函数,可以移动蛇身,并且可以判断有没有撞上自己
* @param nextHeadX [下一步x坐标]
* @param nextHeadY [下一步y坐标]
*/
public void moveNode(int nextHeadX, int nextHeadY) {
this.x = nextHeadX;
this.y = nextHeadY;
int alterX = nextHeadX, alterY = nextHeadY;
for (Node node : snake) {
int tmpX = node.getX();
int tmpY = node.getY();
if (nextHeadX == tmpX && nextHeadY == tmpY) {
System.out.println("游戏结束!你撞到自己了");
System.out.println(tmpX + "," + tmpY + "---" + this.x + "," + this.y);
System.exit(0);
}
node.setX(alterX);
node.setY(alterY);
alterX = tmpX;
alterY = tmpY;
}
}
public int getX() {
return x;
}
public void setWidth(int width) {
this.width = width;
}
public void setLength(int length) {
this.len = length;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
2.食物类
package com.ycs.LinkList;
public class Food {
private int x;
private int y;
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 Food(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
3.游戏运行类
package com.ycs.LinkList;
import java.io.IOException;
import java.util.Random;
import java.util.Scanner;
public class Play {
private static int length =20;
private static int width =20;
public static void show(Snake sn,Food fd){
for (int j = 0; j < length; j++) {
for (int i = 0; i < width; i++) {
if(i==fd.getX()&&j==fd.getY()){
System.out.print("1");
}else if(sn.show(i, j)) {
System.out.print("\1");
}else
System.out.print(" ");
}
System.out.println();
}
}
/**
* 开始游戏类,只能输入w a s d,输完你要按回车,水平有限,我没有实现直接接收键值的功能
* @param args [系统传参,在dos窗口可以把参数传过来,很少用]
*/
public static void main(String[] args) {
try {
Random random = new Random();
Snake snake = new Snake(9,9,20,20);
Food fd = new Food(random.nextInt(length),random.nextInt(width));
Scanner sc = new Scanner(System.in);
String wasd="w";
while(!wasd.equals("e")){
if(System.in.available()>0)
{
wasd = sc.nextLine();
System.out.println(wasd);
}
long st = System.currentTimeMillis();
snake.run(wasd);
if(snake.eat(fd)){
int fx = random.nextInt(length);
int fy = random.nextInt(width);
while(snake.show(fx, fy))
{
fx = random.nextInt(length);
fy = random.nextInt(width);
//System.out.println("那啥子!");
}
fd.setX(fx);
fd.setY(fy);
}
show(snake,fd);
long end = System.currentTimeMillis();
while(end-st<500){
end = System.currentTimeMillis();
}
}
sc.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
若上述代码不能运行,请见谅,后面改动比较大,这里只是一个简单的demo,继续看下一篇,代码将保证能运行,而且会用上图形化界面,其实写贪吃蛇我只是在用一个数据结构,并不是为了游戏,所以界面粗糙点,下一章将会介绍贪吃蛇自动吃食物的算法,有两种算法贪婪算法和A*算法,我会逐步介绍两种算法并且通过一些策略让贪吃蛇实现吃满屏幕。效果如下: