最近突然想做一个自动寻路的贪吃蛇,然而这个基础是先做一个贪吃蛇,然后再做修改,下面是花一天按照教程(尚学堂马士兵的教程)撸的一个贪吃蛇的代码,我尽量注释得清楚,以便日后再看方便。
Yard.java
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Yard extends Frame {
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
PaintThread paintThread = new PaintThread();
private boolean gameOver = false;
//院子有几个小格子 25*25
public static final int rows = 30;
public static final int cols = 30;
//每个小格子的大小5*5
public static final int BLOCK_SIZE = 15;
//得分
private int score = 0;
//创建一条蛇,传进this
Snake s = new Snake(this);
Egg e = new Egg();
//加入双缓冲,这个不清楚是什么东西,缓冲解决闪烁的问题
Image offScreenImage = null;
//展示一下小格子,使用launch(运行)
public void launch() {
//设定院子出现的位置
this.setLocation(200,200);
//设置院子的尺寸
this.setSize(rows * BLOCK_SIZE, cols*BLOCK_SIZE);
//关闭窗口
this.addWindowListener(new WindowAdapter() {
//重写 右键source --> overide -->windowclosing
@Override
public void windowClosing(WindowEvent e) {
/* // TODO Auto-generated method stub
super.windowClosing(e);
*/
//重写上面注释掉的部分,下面是简单处理
System.exit(0);
}
});
//显示可见
this.setVisible(true);
//有了这一行蛇才会动,之前一直少这一行
this.addKeyListener(new KeyMonitor());
//让下面定义的线程跑起来
// 原版 new Thread(new PaintThread()).start();
new Thread(paintThread).start();
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//运行一下
new Yard().launch();
}
public void stop() {
gameOver = true;
}
//画出小格子
public void paint(Graphics g){
Color c = g.getColor();
//背景色
g.setColor(Color.GRAY);
g.fillRect(0, 0, rows * BLOCK_SIZE, cols*BLOCK_SIZE);
g.setColor(Color.DARK_GRAY);
//画出横线,原理是两个定点确定一条直线
for(int i = 1; i < rows ; i++){
g.drawLine(0, BLOCK_SIZE*i, cols*BLOCK_SIZE, BLOCK_SIZE * i);
}
//同理,画出竖线
for(int i = 1; i < cols ; i++){
g.drawLine(BLOCK_SIZE*i,0 ,BLOCK_SIZE * i, rows*BLOCK_SIZE);
}
//画出分数线
g.setColor(Color.YELLOW);
g.drawString("分数:" + score, 10, 60);
//第一版的展示分数,之前的那一版因为在线程结束后刷新界面,无法显示
/*if(flag == false){
//构造方法可以在api手册里面找,字体可以在notepad++里面找一个好看的
g.setFont(new Font("华文彩云",Font.BOLD,50));
g.drawString("HaHa,you Died", 10, 80);
}*/
if(gameOver){
g.setFont(new Font("华文彩云",Font.BOLD,50));
g.drawString("HaHa,you Died", 30, 180);
g.setFont(new Font("楷体",Font.BOLD,20));
g.drawString("code by mz", 300, 220);
paintThread.gameOver();
}
g.setColor(c);
//判断是否吃到蛇,画出蛇,画出蛋
s.eat(e);
s.draw(g);
e.draw(g);
}
//这个方法是利用双缓冲解决闪烁现象的,虽然原理我还不太懂
@Override
public void update(Graphics g) {
if(offScreenImage == null){
offScreenImage = this.createImage(rows * BLOCK_SIZE, cols*BLOCK_SIZE);
}
Graphics gOff = offScreenImage.getGraphics();
paint(gOff);
g.drawImage(offScreenImage,0,0 ,null);
}
//定义一个线程让蛇移动起来
private class PaintThread implements Runnable{
private boolean runing = true;
@Override
public void run() {
// TODO Auto-generated method stub
//下面的格式比较固定
while(runing){
repaint();
try{
//停顿50毫秒
Thread.sleep(100);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
public void gameOver() {
runing = false;
}
}
//键盘监听,来操作蛇的移动
private class KeyMonitor extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
//让蛇去处理
// int key = e.getKeyCode();
// if(key == KeyEvent.VK_F2){
// //paitThread.reStart;
// }
s.keyPressed(e);
}
}
}
Snake.java
import java.awt.Color; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.KeyEvent; public class Snake { private Node head = null; private Node tail = null; private int size = 0; //先定义一个节点用来创建蛇,作为蛇的初始坐标 Node n = new Node(20, 30, Dir.L); private Yard y; //构造函数,一个节点构造的蛇 /*public Snake(Node node){ head = tail = node; size = 1; }*/ //重新写构造函数,使其有坐标 public Snake(Yard y){ head = tail = n; size = 1; this.y = y; } //把节点加在尾巴上 public void addToTail(){ // Node node = null; //判定方向,并由此确定尾巴加在什么上 switch(tail.dir){ case L: node = new Node(tail.row,tail.col+1,tail.dir); break; case U: node = new Node(tail.row + 1,tail.col,tail.dir); break; case R: node = new Node(tail.row,tail.col - 1,tail.dir); break; case D: node = new Node(tail.row - 1,tail.col,tail.dir); break; } tail.next = node; node.prior = tail; tail = node; size ++; } //吃一个加在头上 public void addToHead(){ Node node = null; //判定方向,并由此确定尾巴加在什么上 switch(head.dir){ case L: node = new Node(head.row,head.col-1,head.dir); break; case U: node = new Node(head.row - 1,head.col,head.dir); break; case R: node = new Node(head.row,head.col + 1,head.dir); break; case D: node = new Node(head.row + 1,head.col,head.dir); break; } node.next = head; head.prior = node; head = node; size ++; } //画出蛇,draw方法,区别与下面那个draw方法,这个是可以外部访问的 public void draw(Graphics g){ if(size <= 0){return;} //move放在打印之前更加灵敏 move(); //循环打印节点 for(Node n = head; n != null; n = n.next) { n.draw(g); } } //根据蛇头的方向来进行移动,基本思想是把尾巴add到头,同时删除尾巴,比如最新版的贪吃蛇头尾不一样,也可以加到head.next private void move() { // TODO Auto-generated method stub addToHead(); deleteFromTail(); //移动完成之后判断是否符合死亡的条件 checkDead(); } private void checkDead() { // TODO Auto-generated method stub //判断是否撞墙 //之所以是head.col < 2 是因为上面的标题框盖住了两行 if(head.row < 2 || head.col < 0 || head.row > Yard.rows || head.col > Yard.cols) { y.stop(); } //判断是否和自己的身体相撞 for(Node n = head.next; n != null; n = n.next){ if(head.row == n.row && head.col == n.col){ y.stop(); } } } // private void deleteFromTail() { // TODO Auto-generated method stub if(size == 0)return; tail = tail.prior; tail.next = null; } private class Node{ //节点的宽高 int w = Yard.BLOCK_SIZE; int h = Yard.BLOCK_SIZE; //节点的坐标(第几行第几列 ) int row ,col; Dir dir = Dir.L; //下一个节点是谁 Node next = null; Node prior = null; //构造函数 Node(int row, int col,Dir dir) { this.row = row; this.col = col; this.dir = dir; } //画出节点 void draw(Graphics g) { Color c = g.getColor(); g.setColor(Color.BLACK); g.fillRect(Yard.BLOCK_SIZE * col, Yard.BLOCK_SIZE * row, w, h); g.setColor(c); } } public void eat(Egg e){ //判断是否碰撞 if(this.getRect().intersects(e.getRect())){ //碰上的话egg就随机出现在另一个地方 e.reAppear(); //蛇长一个节点 this.addToHead(); y.setScore(y.getScore()+5); } } //辅助性方法,蛇头所在方块,由此可以想到egg里也有一个这样的方块 private Rectangle getRect(){ return new Rectangle(Yard.BLOCK_SIZE * head.col, Yard.BLOCK_SIZE * head.row, head.w, head.h); } //键盘控制蛇头的方向 public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); switch(key) { case KeyEvent.VK_LEFT : if(head.dir != Dir.R) head.dir = Dir.L; break; case KeyEvent.VK_UP : if(head.dir != Dir.D) head.dir = Dir.U; break; case KeyEvent.VK_RIGHT : if(head.dir != Dir.L) head.dir = Dir.R; break; case KeyEvent.VK_DOWN : if(head.dir != Dir.U) head.dir = Dir.D; break; } } }
Egg.javaimport java.awt.Color; import java.awt.Graphics; import java.awt.Rectangle; import java.util.Random; public class Egg { int row , col; int w = Yard.BLOCK_SIZE; int h = Yard.BLOCK_SIZE; //生成一个随机数 private static Random r = new Random(); private Color color = Color.GREEN; public Egg(int row, int col) { super(); this.row = row; this.col = col; } //随机生成一个蛋 public Egg(){ //注意这种写法,很重要 this(r.nextInt(Yard.rows-2)+2,r.nextInt(Yard.cols)); } //egg被吃掉后重新出现 public void reAppear(){ //之所以要减去2再加上2是为了避免出现在我们看不到的边界地带 this.row = r.nextInt(Yard.rows-2)+2; this.col = r.nextInt(Yard.cols); } //画出蛋所在的 public Rectangle getRect(){ return new Rectangle(Yard.BLOCK_SIZE * col, Yard.BLOCK_SIZE * row, w, h); } public void draw(Graphics g) { Color c = g.getColor(); g.setColor(color); g.fillOval(Yard.BLOCK_SIZE * col, Yard.BLOCK_SIZE * row, w, h); g.setColor(c); if(color == Color.GREEN){ color = Color.RED; }else{ color = Color.GREEN; } } public int getRow() { return row; } public void setRow(int row) { this.row = row; } public int getCol() { return col; } public void setCol(int col) { this.col = col; } }
Dir.java
public enum Dir { L,U,R,D }
今天就先敲到这里,有时间再把这个代码改进一下,从今天的敲代码的过程,我发现我对有ava GUI还不熟悉,线程还用的不熟练等问题,日后加强。