运行效果如下:
新建Yard类
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;
/**
* 这个类代表贪吃蛇的活动场所
*
* @author wuranghao
* @version 1.0
*/
public class Yard extends Frame {
public static void main(String[] args) {
Yard y = new Yard();
y.beginTime = System.currentTimeMillis();
y.launch();
}
/*
* 画图的线程
* */
PaintThread paintThread = new PaintThread();
private boolean gameOver = false; //游戏是否结束
/*
* 行数
*/
public static final int ROWS = 30;
/*
* 列数
* */
public static final int COLS = 30;
/*
* 活动区域大小
* */
public static final int BLOCK_SIZE = 15;
//设置显示字属性
private Font fontGameOver = new Font("宋体", Font.BOLD, 50);
//分数
private int score = 0;
/*
* 记录开始时候的时间
* */
private long beginTime = 0;
/*
*实例化Snake和Egg的对象
* */
Snake s = new Snake(this);
Egg e = new Egg();
/*
* 抽象类 Image 是表示图形图像的所有类的超类。
* 必须以特定于平台的方式获取图像。
* */
Image offScreenImage = null;
/*
* 此函数的功能是:设置窗口的大小、位置、可见,以及点击事件和键盘事件,最后开启了绘图线程
* */
public void launch() {
/*
* 指定窗口的位置,窗口的左上角的位置为(90,10).是相对父窗口的相对位置
* */
this.setLocation(90, 10);
/*
* 设定窗口的大小
* 宽度width:COLS*BLOCK_SIZE
* 高度hight:ROWS*BLOCK_SIZE
* */
this.setSize(COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);
/*
* 为窗口添加监听器
* */
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
/*
* 将窗口设置可见
* */
this.setVisible(true);
/*
* 为窗口添加键盘事件
* */
this.addKeyListener(new KeyMonitor());
new Thread(paintThread).start();
}
/*
* 将变量gameOver设置为true,使得在paint()函数中将使得线程停止
* */
public void stop() {
gameOver = true;
}
@Override
public void paint(Graphics g) {
/*
* 获取此图形上下文的颜色
* */
Color c = g.getColor();
/*
* 指定图形上下文的颜色
* */
g.setColor(Color.GRAY);
/*
* 用当前的颜色来填充指定的区域
* */
g.fillRect(0, 0, COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);
/*
* 再一次的指定颜色为:黑灰色???????为什么要再一次的设定???
* 原因在于:我们想将绘图的颜色与文字显示的不一样
* */
g.setColor(Color.DARK_GRAY);
//画出横线
/*
* drawLine(int x1, int y1, int x2, int y2)
* 函数的功能为:
* 在此图形上下文的坐标系中,使用当前颜色在点 (x1, y1) 和 (x2, y2) 之间画一条线。
* 通过下面的两个for循环就会在图形化对象上画出表格
* */
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, BLOCK_SIZE * ROWS);
}
g.setColor(Color.YELLOW);//设定颜色,为下面显示文字信息做准备
/*
* drawString(String str, int x, int y)
*使用此图形上下文的当前字体和颜色绘制由指定 string 给定的文本。
* */
g.drawString("使用说明:使用方向键控制方向,F1--停止,F2--停止后恢复,F5--重新开始", 10, 40);
g.drawString("目前分数:" + score, 10, 60);
g.drawString("加分规则:每吃一个加5分,加油!", 10, 80);
g.drawString("已经使用的时间:" + (System.currentTimeMillis() - beginTime) / 1000, 10, 100);
/*
* 检测游戏是否结束,当游戏结束时,则提示游戏“game over”,而且将界面恢复到初始界面的状态,且终止绘图线程
* */
if (gameOver) {
g.setFont(fontGameOver);
g.drawString("game over!", 90, 170);
g.drawString("在玩一次,请按F5", 10, 250);
g.drawString(" ", 200, 230);//???这个用意何在??
paintThread.pause();
}
if (score > 100) {
g.drawString("好棒!!!", 90, 170);
g.drawString("你已经超过" + score + ",继续加油", 10, 230);
}
/*
* 将图形界面设置为刚开始的颜色
* */
g.setColor(c);
s.eat(e);
e.draw(g);
s.draw(g);
}
@Override
public void update(Graphics g) {
if (offScreenImage == null) {
/*
* public Image createImage(int width,
* int height)
* 创建一幅用于双缓冲的、可在屏幕外绘制的图像
* */
offScreenImage = this.createImage(COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);
}
/*
* getGraphics()
*创建供绘制闭屏图像(off-screen image)使用的图形上下文。
* */
Graphics gOff = offScreenImage.getGraphics();
paint(gOff);
/*
* drawImage(Image img, int x, int y, ImageObserver observer)
* 绘制指定图像中当前可用的图像。
* */
g.drawImage(offScreenImage, 0, 0, null);
}
private class PaintThread implements Runnable {
private boolean running = true;
private boolean pause = false;
public void run() {
while (running) {//线程将一直处于运行当中,只有在游戏结束的时候
if (pause) continue;
else repaint();//如果组件是轻量级组件,则会尽快调用paint()方法或者是调用update()
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void pause() {
this.pause = true;
}
public void reStart() {
this.pause = false;
s = new Snake(Yard.this);
gameOver = false;
score = 0;
}
public void gameOver() {
running = false;
}
}
/*
* 此函数的功能为:检测我们是否按下F2,若按下,则重新启动线程,即重新开始游戏
* */
private class KeyMonitor extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_F5) {
paintThread.reStart();//重新开始游戏
} else if (key == KeyEvent.VK_F1) {
paintThread.pause = true;//暂停
} else if (key == KeyEvent.VK_F2) {
paintThread.pause = false;//从暂停中恢复
}
s.keyPressed(e);
}
}
/**
* 拿到所得的分数
*
* @return 分数
*/
public int getScore() {
return score;
}
/**
* 设置所得的分数
*
* @param score 分数
*/
public void setScore(int score) {
this.score = score;
}
}
新建Snake类
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
/**
* 代表蛇
* @author wuranghao
*
*/
public class Snake {
/*
* 头结点
* */
private Node head = null;
/*
* 尾结点
* */
private Node tail = null;
/*
* 大小
* */
private int size = 0;
/*
* 开始游戏时:
* 初始位置:(20,30)
* 初始运动方向:Dir.L
* */
private Node n = new Node(20, 30, Dir.L);
/*
* Yard的对象属性;
* */
private Yard y;
/*
* 构造函数
* */
public Snake(Yard y) {
/*
* 将初始结点给头结点和尾结点,size初始化为 1,
* */
head = n;
tail = n;
size = 1;
this.y = y;
}
/*
* 节点类
* */
private class Node {
int w = Yard.BLOCK_SIZE;
int h = Yard.BLOCK_SIZE;
int row , col;
Dir dir = Dir.L;
Node next = null;
Node prev = 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 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.prev = 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;
}
head.prev=node;
node.next=head;
head=node;
size ++;
}
public void draw(Graphics g) {
if(size <= 0) return;
move();
for(Node n = head; n != null; n = n.next) {
n.draw(g);
}
}
/*
* 移动过程所要做的操作:在运动方向增加一个节点,在尾部减去一个节点,并且检测是否已经死亡
* */
private void move() {
addToHead();
deleteFromTail();
checkDead();
}
private void checkDead() {
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() {
if(size == 0) return;
tail = tail.prev;
tail.next = null;
}
public void eat(Egg e) {
/*
* boolean intersects(Rectangle r)
* 确定此 Rectangle 是否与指定的 Rectangle 相交。
* 若相交,表示我们吃到了一个点 ,则导致蛇的长度变长并且在出现一个点,并且加5分,否则什么也不做
* */
if(this.getRect().intersects(e.getRect())) {
e.reAppear();
this.addToHead();
//吃了加5分
y.setScore(y.getScore() + 5);
}
}
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类
import 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.PINK;
//运动时的位置
public Egg(int row, int col) {
this.row = row;
this.col = col;
}
//初始时在位置
public Egg() {
this(r.nextInt(Yard.ROWS-2) + 2, r.nextInt(Yard.COLS));
}
public void reAppear() {
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 getCol() {
return col;
}
public void setCol(int col) {
this.col = col;
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
}
新建Dir类
/**
* 代表蛇的运行方向
* @author wuranghao
*
*/
public enum Dir {
L, U, R, D
}