小游戏(贪食蛇)
线程
面向对象
文件操作
内部类
数据库
步骤:
1–了解swing相关知识和小游戏设计
通过main方法启动初始化界面
逐步添加界面元素
蛇的构成(多个关节组成)和运动方式(确认好方向,去尾加头)
食物的构成怎么出现(随机,位置与界面宽度和高度保持整除关系(余数为龙))
蛇如何吃豆子(头部元素与豆子元素坐标重合)
确定使用哪一种容器(LinkedList)
2--配置界面
宽度、高度
3--画出蛇
准备数据(一个关节就是一个对象 一条蛇是一个容器)
初始多个关节数据(关联着)
4--蛇运动(按照方向运动) 可以自定义线程、Timer
方向控制(键盘方向键控制方向 、 snake按照要求的方向继续运动)
360、撞墙、咬自己、调速、暂停
5--蛇吃豆子(不能出现在蛇身上 、 不能出界)
Bean
初始化豆子
画出豆子
吃豆子(循环)
身体长长
6--记录每次游戏的成绩
初始化数据通过配置文件配置(.properties、txt)
7 关卡设计
3关 (速度、分数、时间)
开始代码编程:
所需类: SnakeEle.java(蛇的类) , Bean.java(豆子类) , Constant(常量类) , Main(实现代码类) , config.properties配置文件
----------SnakeEle.java------------
package com.java20180917.snake;
import java.io.Serializable;
/*
蛇身体关节元素 实体类
*/
public class SnakeEle implements Serializable{
private static final long serialVersionUID = -8508352375136668149L;
private int se_size;
private int se_x;
private int se_y;
public SnakeEle() {
super();
}
public SnakeEle(int se_size, int se_x, int se_y) {
super();
this.se_size = se_size;
this.se_x = se_x;
this.se_y = se_y;
}
public int getSe_size() {
return se_size;
}
public void setSe_size(int se_size) {
this.se_size = se_size;
}
public int getSe_x() {
return se_x;
}
public void setSe_x(int se_x) {
this.se_x = se_x;
}
public int getSe_y() {
return se_y;
}
public void setSe_y(int se_y) {
this.se_y = se_y;
}
@Override
public String toString() {
return “SnakeEle [se_size=” + se_size + “, se_x=” + se_x + “, se_y=” + se_y + “]”;
}
}
---------- Bean.java------------
package com.java20180917.snake;
import java.io.Serializable;
public class Bean implements Serializable{
private static final long serialVersionUID = 1894645424910104002L;
private int be_x;
private int be_y;
private int be_size;
public Bean() {
super();
// TODO Auto-generated constructor stub
}
public Bean(int be_x, int be_y, int be_size) {
super();
this.be_x = be_x;
this.be_y = be_y;
this.be_size = be_size;
}
public int getBe_x() {
return be_x;
}
public void setBe_x(int be_x) {
this.be_x = be_x;
}
public int getBe_y() {
return be_y;
}
public void setBe_y(int be_y) {
this.be_y = be_y;
}
public int getBe_size() {
return be_size;
}
public void setBe_size(int be_size) {
this.be_size = be_size;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
@Override
public String toString() {
return "Bean [be_x=" + be_x + ", be_y=" + be_y + ", be_size=" + be_size + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + be_size;
result = prime * result + be_x;
result = prime * result + be_y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Bean other = (Bean) obj;
if (be_size != other.be_size)
return false;
if (be_x != other.be_x)
return false;
if (be_y != other.be_y)
return false;
return true;
}
}
----------Constant.java------------
package com.java20180917.snake;
/*
常亮接口
/
public interface Constant {
/
public static final String TIP = “GAME OVER!”;
public static final String STOP = “stop” ;
public static final String PAUSE = “pause” ;
}
----------Main.java------------
package com.java20180917.snake;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.Properties;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/**
*/
public class Main extends JFrame implements KeyListener {
private static final long serialVersionUID = -1803858577233572276L;
private JPanel jp = null;
// 窗口的宽度
private int fw = 600;
// 窗口的高度
private int fh = 600;
// 蛇身初始长度
private int count = 10;
// 蛇身元素,每个方块大小20
private int se_size = 20;
// 蛇的横坐标
private int se_x = 100;
// 蛇的纵坐标
private int se_y = 200;
// 豆子的大小
private int bean_size = 20;
// 豆子计数的初始化
private int bean_count = 0;
// 豆子集合
private LinkedList bean = new LinkedList();
// 蛇运动的默认方向为UP,向上运动
private String direction = Constant.UP;
// 弹框提示语
private String tip = "";
// 蛇的运动状态
private String status = "";
// 蛇运动的速度
private long speed = 200;
// 关卡初始化,第一关
private int guanqia = 1;
// 蛇运动的开始时间
private long snake_start;
// 蛇结束运动的时间
private long snake_end;
// 按下暂停键的开始时间
private long snake_pause_start;
// 松开暂停键的结束时间
private long snake_pause_end;
// 记录暂停的时间差
private long snake_pause_time;
// 蛇的集合
private LinkedList snake = new LinkedList();
// 总初识化方法
private void init() {
// 文件的读写方法
initProperties();
// 初始化蛇的方法
initSnake();
// 初始化豆子的方法
initBean();
// 初始化面板的方法
initPanel();
// 初始化窗口的方法
initFrame();
// 线程
initThread();
}
// 文件的读写方法
private void initProperties() {
try {
Properties pps = new Properties();
/*
* load(InputStream inStream) 从输入流中读取属性列表,通过对指定的文件,进行装载来获取该文件中的所有键 - 值对
*/
pps.load(new FileInputStream("D:/A工具/java20180917/src/config.properties"));
Enumeration enu = pps.propertyNames();// 获取配置文件的名字
// 要修改的变量
fw = Integer.parseInt(pps.getProperty("fw"));
fh = Integer.parseInt(pps.getProperty("fh"));
speed = Integer.parseInt(pps.getProperty("speed"));
while (enu.hasMoreElements()) {
String strKey = (String) enu.nextElement();
String strValue = pps.getProperty(strKey);
System.out.println(strKey + "=" + strValue);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 豆子的初始化方法
private void initBean() {
// 随机生成豆子,使其范围在窗口的范围里,窗口的宽高是600,所以豆子也在宽高为600的范围中
// bean_x 豆子的横坐标,bean_y 豆子的纵坐标
int bean_x = 20 * Math.round((new Random().nextInt(30)));
int bean_y = 20 * Math.round((new Random().nextInt(30)));
// 判断,豆子随机的产生不能出现在蛇身上
if (!snake.contains(new SnakeEle(bean_size, bean_x, bean_y))) {
bean.add(new Bean(bean_x, bean_y, bean_size));
}
}
// 线程
private void initThread() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
// 如果蛇的头部,即第一个元素的横纵坐标与豆子的横纵坐标相等,重合
if (snake.getFirst().getSe_x() == bean.getFirst().getBe_x()
&& snake.getFirst().getSe_y() == bean.getFirst().getBe_y()) {
// 先将豆子清除
bean.clear();
// 再调用初始化豆子方法
initBean();
// 对豆子进行计数
bean_count++;
// 同时再蛇的尾部添加元素
snake.add(new SnakeEle(se_size, snake.getLast().getSe_x(), snake.getLast().getSe_y()));
// 豆子的数量可以对3整除,就进入下一个关卡,关卡自增,速度变快
if (bean_count % 3 == 0 && bean_count < 10) {
// 关卡自增
guanqia++;
// 重画
jp.repaint();
// 弹框
int op_result = JOptionPane.showConfirmDialog(jp, "下一关!", "恭喜你!",
JOptionPane.OK_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE);
// 弹框有两个按钮,继续和取消,值分别为0和2
if (op_result == 0) {
// 重新开始游戏
speed -= 50;
}
if (op_result == 2) {
System.exit(0);
}
}
}
if ("".equalsIgnoreCase(status)) {
// 判断是否咬到自己(即 头部坐标与身体上的任何一个元素的坐标重合)
for (int i = 1; i < snake.size(); i++) {
if (snake.getFirst().getSe_x() == snake.get(i).getSe_x()
&& snake.getFirst().getSe_y() == snake.get(i).getSe_y()) {
fangfa();
}
}
// 操作snake容器中的元素
// 移除最后一个
snake.removeLast();
// 判断头部向上是否出界
if (direction.equalsIgnoreCase(Constant.UP)) {
if (snake.getFirst().getSe_y() - se_size < 0) {
// 如果新的头部y坐标出界 提示game over调用方法,弹出信息框
fangfa();
} else
snake.addFirst(new SnakeEle(se_size, snake.getFirst().getSe_x(),
snake.getFirst().getSe_y() - se_size));
}
// 判断头部向右是否出界
if (direction.equalsIgnoreCase(Constant.RIGHT)) {
// 如果新的头部y坐标出界 提示game over
if (snake.getFirst().getSe_x() + se_size > fw) {
// 如果新的头部y坐标出界 提示game over调用方法,弹出信息框
fangfa();
} else
snake.addFirst(new SnakeEle(se_size, snake.getFirst().getSe_x() + se_size,
snake.getFirst().getSe_y()));
}
// 判断头部向左是否出界
if (direction.equalsIgnoreCase(Constant.LEFT)) {
if (snake.getFirst().getSe_x() - se_size < 0) {
// 如果新的头部y坐标出界 提示game over调用方法,弹出信息框
fangfa();
} else
snake.addFirst(new SnakeEle(se_size, snake.getFirst().getSe_x() - se_size,
snake.getFirst().getSe_y()));
}
// 判断头部向下是否出界
if (direction.equalsIgnoreCase(Constant.DOWN)) {
if (snake.getFirst().getSe_y() + se_size > fh) {
// 如果新的头部y坐标出界 提示game over调用方法,弹出信息框
fangfa();
} else
snake.addFirst(new SnakeEle(se_size, snake.getFirst().getSe_x(),
snake.getFirst().getSe_y() + se_size));
}
// 重画
jp.repaint();
try {
Thread.sleep(speed);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
// 初始化蛇的方法
private void initSnake() {
// 创建多个有关联着的SankeEle对象
// 添加到snake容器中
for (int i = 0; i < count; i++) {
snake.add(new SnakeEle(se_size, se_x, se_y + se_size * i));
}
// 记录蛇运动的开始时间
snake_start = System.nanoTime();
}
// 初始化面板的方法
private void initPanel() {
jp = new JPanel() {
private static final long serialVersionUID = 4376420223914299841L;
@Override
public void paint(Graphics g) {
super.paint(g);
if (!"".equals(tip)) {
g.setColor(Color.RED);
g.setFont(new Font("", Font.BOLD, 40));
g.drawString(tip, 160, 160);
}
for (int i = 0; i < snake.size(); i++) {
if (i == 0) {
g.setColor(Color.RED);
} else {
g.setColor(Color.white);
}
g.fill3DRect(snake.get(i).getSe_x(), snake.get(i).getSe_y(), snake.get(i).getSe_size(),
snake.get(i).getSe_size(), true);
}
for (int i = 0; i < bean.size(); i++) {
g.setColor(Color.orange);
g.fill3DRect(bean.get(i).getBe_x(), bean.get(i).getBe_y(), bean.get(i).getBe_size(),
bean.get(i).getBe_size(), true);
}
Integer bc = new Integer(bean_count);
String s = bc.toString();
g.setFont(new Font("", Font.BOLD, 30));
g.setColor(Color.white);
g.drawString("总长:", 50, 50);
g.setColor(Color.red);
g.drawString(s, 140, 50);
Long sp = new Long(speed);
String spe = Long.toString(sp);
g.setColor(Color.white);
g.drawString("速度:", 50, 80);
g.setColor(Color.red);
g.drawString(spe, 130, 80);
g.setColor(Color.white);
g.drawString("关卡:", 420, 50);
Integer gq = new Integer(guanqia);
String gg = gq.toString();
g.setColor(Color.red);
g.drawString(gg, 500, 50);
g.setColor(Color.white);
snake_end = System.nanoTime();
g.drawString("时长:" + (snake_end - snake_start - snake_pause_time) / 1000000000, 50, 110);
}
};
jp.setOpaque(false);
}
// 弹出提示信息框的方法
private void fangfa() {
tip = Constant.TIP;
status = Constant.STOP;
int op_result = JOptionPane.showConfirmDialog(jp, "GAME OVER!", "你挂了!", JOptionPane.OK_CANCEL_OPTION,
JOptionPane.ERROR_MESSAGE);
if (op_result == 0) {
// 重新开始游戏
tip = "";
direction = Constant.UP;
status = "";
snake.clear();
initSnake();
bean_count = 0;
guanqia = 1;
speed = 200;
}
if (op_result == 2) {
System.exit(0);
}
}
// 初始化窗口的方法
private void initFrame() {
// this.setTitle(“测试!”);
this.setSize(fw, fh);
this.getContentPane().setBackground(Color.BLACK);
this.setUndecorated(true);
this.add(jp);
this.setAlwaysOnTop(true);
this.setLocationRelativeTo(null);
this.setVisible(true);
// 当前对象具有多重省份 (这是由继承结构决定的)
// 第一个this -- 窗口对象
// 第二个this -- 键盘监听器
this.addKeyListener(this);
}
public Main() {
// 调用初始化的总方法
init();
}
public static void main(String[] args) {
new Main();
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyPressed(KeyEvent e) {
// 按下哪一个按键 停止虚拟机运行
// System.out.println(e.getKeyCode());
// 按下esc键 关停虚拟机
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
System.exit(0);
}
// 如果当前方向的值不是左键并且按下的键为右键,即蛇在向右运动时,按左键无效,不能立马掉头
if (!this.direction.equalsIgnoreCase(Constant.LEFT) && e.getKeyCode() == KeyEvent.VK_RIGHT) {
// 则当前方向向右
this.direction = Constant.RIGHT;
}
// 同上,即相对立的两个方向值不能同时存在
if (!this.direction.equalsIgnoreCase(Constant.RIGHT) && e.getKeyCode() == KeyEvent.VK_LEFT) {
this.direction = Constant.LEFT;
}
if (!this.direction.equalsIgnoreCase(Constant.UP) && e.getKeyCode() == KeyEvent.VK_DOWN) {
this.direction = Constant.DOWN;
}
if (!this.direction.equalsIgnoreCase(Constant.DOWN) && e.getKeyCode() == KeyEvent.VK_UP) {
this.direction = Constant.UP;
}
// 松开暂停键
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
// 先检查status的值 如果是 "" 就修改成 pause 如果是 pause 就改成 ""
switch (status) {// 重启
case Constant.PAUSE:
this.status = "";
// 记录松开暂停键的结束时间
snake_pause_end = System.nanoTime();
// 暂停的时间差
snake_pause_time += (snake_pause_end - snake_pause_start);
break;
case "":// 按下暂停键
// 当前状态为按下暂停键
this.status = Constant.PAUSE;
// 记录按下暂停键的开始时间
snake_pause_start = System.nanoTime();
break;
}
}
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}