这几天一直在写贪吃蛇小游戏,ACM都没时间去做,但是收获还是很大的。程序中包函了线程、监听、界面的应用,最大的困难的如何实现动画,经过反复修改,感觉采用线程的方法是最好控制的。动画是通过JPanel.updateUI()函数来重画,每各一定时间重画一下,就形成了动画效果。很假O(∩_∩)O哈哈~….
//package 课后练习.d1104;
import java.awt.Color;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class Snake extends JFrame implements KeyListener{
private final int JPS_WIDTH = 400;//蛇活动范围的宽度
private final int JPS_HEIGHT = 400;//蛇活动范围的高度
private JLabel lbScore;//显示得分的标签
private SnakeJPanel snakePanel;//蛇活动的面板
private SnakeList S_List;//实现Runnable接口成动画的关键
//存放蛇身体每个节点的链表
private LinkedList<Point> snakeList = new LinkedList<Point>();
Point food;//食物
public Snake() {
setTitle("贪吃蛇");
setBounds(450, 150, 410, 485);//窗口大小需要自己调...
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);//设置不可改变大小
addKeyListener(this);//添加键盘监听
Container contentPane = getContentPane();//获取内容窗格
contentPane.setLayout(null);//设置空布局
//从新开始
JMenuBar bar = new JMenuBar();//创建菜单栏
setJMenuBar(bar);//给框架添加菜单栏
bar.setLayout(new FlowLayout(FlowLayout.LEFT));
JMenuItem afresh = new JMenuItem("从新开始");//创建菜单项
afresh.addActionListener(new ActionListener() {//添加监听
@Override
public void actionPerformed(ActionEvent e) {
S_List.running=false;
snakeList = new LinkedList<Point>();
begin();
}
});
bar.add(afresh);//菜单栏添加菜单项
//帮助
JMenuItem help = new JMenuItem("帮助");//创建菜单项
help.addActionListener(new ActionListener() {//添加监听
@Override
public void actionPerformed(ActionEvent e) {
S_List.changeCondition();
JOptionPane.showMessageDialog(Snake.this, "向上按 ↑ 键\n向下按 ↓ 键\n"
+ "向左按 ← 键\n向右按 → 键\n按空格键暂停");
S_List.changeCondition();
}
});
bar.add(help);//菜单栏添加菜单项
JPanel scorePanel = new JPanel();//创建存放得分标签的面板
scorePanel.setSize(JPS_WIDTH, 30);//设置位置和大小
lbScore = new JLabel("得分:0");//创建得分标签
scorePanel.add(lbScore);//添加得分标签到面板上
contentPane.add(scorePanel);//得分面板添加到内容窗格
snakePanel = new SnakeJPanel();//创建蛇活动面板
snakePanel.setBounds(0, 30 , JPS_WIDTH, JPS_HEIGHT);//设置位置和大小
contentPane.add(snakePanel);//蛇活动面板添加到内容窗格
setVisible(true);
begin();//实现动画的关键
}
public JLabel getLbScore() {
return lbScore;
}
public SnakeJPanel getSnakePanel() {
return snakePanel;
}
public LinkedList<Point> getSnakeList() {
return snakeList;
}
public int getJPS_WIDTH() {
return JPS_WIDTH;
}
public int getJPS_HEIGHT() {
return JPS_HEIGHT;
}
//实现动画:采用线程,让线程sleep一定的时间,然后更新面板从而实现动画
private void begin() {
if (S_List==null||S_List.running==false) {
S_List = new SnakeList(this);
new Thread(S_List).start();
}
}
//画每一瞬间的蛇与食物
class SnakeJPanel extends JPanel {
@Override
public void paint(Graphics g) {
g.setColor(Color.WHITE);//设置画笔颜色
g.fillRect(0, 0, JPS_WIDTH, JPS_HEIGHT);//设置背景颜色
g.setColor(Color.GREEN);//设置画笔颜色
if (snakeList == null) {//卫条件
return;
}
Iterator<Point> it = snakeList.iterator();//通过迭代器获取链表中的每个节点
while (it.hasNext()) {
Point p = it.next();
g.fillRect(p.x*10, p.y*10, 9, 9);
}
if (food == null) {//卫条件
return;
}
g.setColor(Color.black);
g.fillRect(food.x*10, food.y*10, 9, 9);
}
}
@Override
public void keyTyped(KeyEvent e) {
}
//实现键盘监听
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
switch (keyCode) {
case KeyEvent.VK_UP:
S_List.changDirection(SnakeList.UP);
break;
case KeyEvent.VK_DOWN:
S_List.changDirection(SnakeList.DOWN);
break;
case KeyEvent.VK_LEFT:
S_List.changDirection(SnakeList.LEFT);
break;
case KeyEvent.VK_RIGHT:
S_List.changDirection(SnakeList.RIGHT);
break;
case KeyEvent.VK_SPACE:
S_List.changeCondition();
break;
default:
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
}
//主函数
public static void main(String[] args) {
Snake snake = new Snake();
}
}
//实现Runnable接口,动态改变蛇的位置和食物的位置,以便实现动画
class SnakeList implements Runnable {
private int w;// 活动区间的宽度
private int h;// 活动区间的高度
private boolean[][] matrix;// 活动区间 matrix[i][j]为真说明该点有东西(食物/蛇身节点)
private Snake snake;// 框架
private LinkedList<Point> list;// 蛇身
private int direction;// 方向
// UP和DOWN是偶数,RIGHT和LEFT是奇数,避免冲突,例如向上跑时按向上或者向下是无法改变蛇的跑动方向
public static final int UP = 2;
public static final int DOWN = 4;
public static final int LEFT = 1;
public static final int RIGHT = 3;
boolean running;//判断游戏是否结束
boolean isPause = true;//判断游戏是否暂停
private long time = 200;//sleep时间(单位是毫米)
private int score;//得分
public SnakeList(Snake snake) {
//构造传参
this.snake = snake;
list = snake.getSnakeList();
w = snake.getJPS_WIDTH();
h = snake.getJPS_HEIGHT();
matrix = new boolean[w / 10][h / 10];
direction = 1;// 初始方向向左
running = true;
isPause = false;
initlist();//初始化蛇
}
//初始化蛇的函数
public void initlist() {
int x = h / 20;
int y = w / 20;
for (int i = 0; i < 10; i++) {
Point point = new Point((x + i) , y );
matrix[x + i][y] = true;
list.add(point);
}
createFood();
}
//创建食物:通过调用Random是食物随机出现
public void createFood() {
int x = 0;
int y = 0;
do {
Random random = new Random();
x = random.nextInt(w / 10);//随机数的范围0 ~ w/10
y = random.nextInt(h / 10);
} while (matrix[x][y]);// 如果matrix[x][y]为真说明这是蛇身不能从新创建食物
snake.food = new Point(x, y);
matrix[x][y] = true;
}
//关键函数,控制蛇位置函数
public boolean move() {
Point first = list.getFirst();//通过头节点来控制蛇运动方向
int x = first.x;
int y = first.y;
switch (direction) {
case LEFT:
x--;
break;
case RIGHT:
x++;
break;
case UP:
y--;
break;
case DOWN:
y++;
}
if (0 <= x && x < w / 10 && 0 <= y && y < h / 10) {//如果在活动区间说明蛇不是撞墙死的
if(matrix[x][y]){//matrix[x][y]为真说明这个位置要么是食物,要么是自己身体的某个节点
if(x==snake.food.x&&y==snake.food.y){//能进来说明是食物
score += (int)1000/time+list.size();//结算得分
list.addFirst(snake.food);
createFood();//创建新的食物
return true;//蛇没死
}else{
//System.out.println("撞自己");
return false;
}
}
//能到这里说明matrix[x][y]值为假,此时该点应该是蛇头到达的点
Point point = new Point(x, y);
matrix[x][y] = true;
list.addFirst(point);
point=list.removeLast();//同时蛇尾应该变成它的上一个节点
matrix[point.x][point.y] = false;
return true;//蛇没死
}
//System.out.println("撞墙");
return false;
}
//不能180度转弯
public void changDirection(int dcn){
if(dcn%2!=direction%2){
direction = dcn;
}
}
// public void up(){
// direction = UP;
// }
// public void down(){
// direction = DOWN;
// }
// public void left(){
// direction = LEFT;
// }
// public void right(){
// direction = RIGHT;
// }
//改变蛇状态函数
public void changeCondition(){
isPause = !isPause;
}
//重写run方法
@Override
public void run() {
while(running){
try {//这里只是控制蛇的移动速度,当分数达到一定值时,蛇移速变快
if (score>222) {
Thread.sleep(time/2);
}else if(score>666){
Thread.sleep(time/3);
}else{
Thread.sleep(time);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!isPause){
if (move()) {
snake.getSnakePanel().updateUI();
snake.getLbScore().setText("得分:"+score);
continue;
}
JOptionPane.showMessageDialog(null, "游戏结束!");
running = false;
}
}
}
}
class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}