计算机软件实习实验二:贪吃蛇的游戏开发(实验准备)

实验内容

1. 实现贪吃蛇游戏基本功能,屏幕上随机出现一个“食物”,称为豆子,上下左右控制蛇的移动,吃到豆子以后“蛇”的身体加长一点,得分增加,

蛇碰到边界或蛇头与蛇身相撞,蛇死亡,游戏结束。为游戏设计初始欢迎界面,游戏界面,游戏结束界面。


2. 进行交互界面的设计,要有开始键、暂停键和停止退出的选项。对蛇吃到豆子进行分值计算,可以设置游戏速度等拓展元素。


实验思路

1. 有了上一次《简单计算器的实现》的实验基础,此次实验就方便了许多。

鄙人打算继续使用Java来实现程序描述,使用 Eclipse 开发环境编译调试,还是用 Java swing 框架来实现GUI。

 

2. 贪吃蛇游戏主要涉及线性表(List)的构造与使用。蛇身体的每一节都是一个节点(Node),可以用动态数组 ArrayList 存储坐标来实现。

本次实验我使用的是 Java 语言,JDK 中已有现成的 List 包,所以只需要在开头处引入,并在具体方法中调用即可。

 

涉及知识

数据结构(List, Node等),ArrayList 的调用与动态存储;

Java继承类与接口技术,Java swing 框架,Java swing 图形界面布局;

Java事件执行按钮,Java 虚拟键码 KeyEvent 等。

 

具体步骤

1. 学习了解程序需要调用的包:javax.swing,java.awt,java.util.list 等。

import javax.swing.*;   // Java图形界面设计工具包

import java.awt.*;   // GUI设计工具软件包

import java.awt.event.*;   // 提供处理由 awt 组件所激发的各类事件的接口和类

import java.util.*;   // Java集合框架以及各种实用工具类

import java.util.List;   // 有序集合包接口

 

2. get() 和 set() 方法。

class SnakeAct {
	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;
	}
	
}

 

3.这个游戏我打算添加的拓展元素是速度,利用线程休眠时间控制蛇的移动速度。可以设置为吃完一个豆子蛇体移动速度加1。

int speed = 0,eat = 0;
    eat++;
if(eat>=1){
        Speed++;
}                            //可以将这段程序加到蛇体移动的方法的循环体里面

 

4.使用 Thread 类,一是将任务分成多个子任务并行计算,因为这样可以避免多缓冲的使用,减少内存消耗,运行速度更快;二是可以很方便地控制速度。

Thread nthread;
nthread = new Thread(this); //this访问Thread类

nthread.start(); //开始
/*......*/
nthread = null; //异常终止

Thread.sleep(300-30*Speed); //利用线程休眠时间控制蛇的移动速度

 

5.最关键的算法:当蛇吃到豆子时,让豆子坐标获取list最后一个元素的坐标,即蛇头坐标,每个元素使用前一个元素的坐标(相当于依次后移)。

SnakeAct tempAct = new SnakeAct(); //建立tempAct对象

for (int i = 0; i < list.size(); i++) {
			if (i==1) {
				list.get(i).setX(list.get(0).getX());
				list.get(i).setY(list.get(0).getY()); //刚开始迭代不了,单独处理
			}

    else if(i>1){
				tempAct=list.get(i-1);
				list.set(i-1, list.get(i));
				list.set(i, tempAct);                 //迭代法完成list的存储更新
			}
			
		}

 

6.keyEvent:键值与所调用方法的参数对应。

值得注意的是:布局时宽和高是以左上角为 “坐标原点” 画出来的,所以上下对应的键值得换一下。

	public void keyPressed(KeyEvent e) {
		if(start){
			switch (e.getKeyCode()) { //获取键值
			case KeyEvent.VK_UP:
				move(0, -1);   //up和down相反是因为坐标原点在左上角,横坐标向右,纵坐标向下
				temp=1;
				break;
			case KeyEvent.VK_DOWN:
				move(0, 1);
				temp=2;
				break;
			case KeyEvent.VK_LEFT:
				move(-1, 0);
				temp=3;
				break;
			case KeyEvent.VK_RIGHT:
				move(1, 0);
				temp=4;
				break;

			default:
				break;
			}
		}
	}

 

7.关于图形界面的设计还在学习当中,整体布局如下图(虽然简陋但基本功能都是有的)。将在后面验收的代码中体现,实验准备里就不再赘述了。

 

8.最后分享一下作为一个丢三落四者的经历。

写迷糊了,蛇头坐标和身体其他节点坐标重合游戏结束,就这么个方法写了半天。

①刚开始忘了在move()方法里调用,我说为啥吃到自己咋没反应;

②调用完还是没用。一看发现条件语句我居然写出了 if(list.get(0).equals(get(i))) 这么个玩意。get()个锤子呢,,x和y两个变量怎么访问。

③改成getX()和getY()后还是不行。因为最后if(Dead)没看清,返回值搞反了,一开始游戏就结束。吐了呀,,差点就真的 Dead() 了。

每次测试做的挺麻烦的,主要是担心线程睡眠对位置蛇头和身体的重合的判断有影响,所以每次都吃到一定长度纵向去吃。长了速度变快还是很考验手速的。

***********************************

下附Dead()方法和调用部分。

public boolean Dead() {
        for(int i = 1; i < list.size(); i++) {
        	if (list.get(0).getX() == list.get(i).getX() 
        			&& list.get(0).getY() == list.get(i).getY()) {
    			return true;
    		}
        }
        return false;   
    }
	
	public void move(int x,int y){
		 if(Dead()) {
			 nThread = null;
				label.setText("您输了!您的分数是"+fenShu);
				dialog.setVisible(true);
		 }

............................
}

 

9.完整源码。

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;

class Node { //构造方法声明
    private int x , y;
    public Node() {}
    public Node(int a , int b){
         x = a;
         y = b;
     }
    public Node(Node tmp) {
        x = tmp.getX(); 
        y = tmp.getY();
    }
    int getX() {
        return x;
    }
    int getY() {
        return y;
    }
    void setX(int a) {
        x = a;
    }
    void setY(int b) {
        y = b;
    }
}

class SnakeOperator extends JPanel implements ActionListener,KeyListener,Runnable{
	/**
	 * 功能函数实现游戏的操作:继承 JPanel 类,装配 ActionListener,KeyListener,Runnable 接口
	 */
	
	private static final long serialVersionUID = 1L; //定义程序序列化ID,将对象转换为字节
	
	//定义变量
	int fenShu=0,Speed=0;
	boolean start=false;
	int rx=0,ry=0;
	int eat=0;
	
	//添加按钮
	JDialog dialog = new JDialog();
	JLabel label = new JLabel("您输了!你的分数是"+fenShu);
	JButton ok = new JButton("确认");
	Random r = new Random();
	JButton newGame,stopGame;
	
	//定义动态数组list,引用snakeAct类中的构造方法
	private List<Node> list = new ArrayList<Node>();
	
	int temp=0;
	Thread nThread; //定义线程nThread
	
	//初步定义界面布局
	public SnakeOperator() {
		newGame = new JButton("开始");
		stopGame = new JButton("结束");
		
		//通过this实现接口,调用父类方法
		this.addKeyListener(this); //添加键盘事件keyListener
		this.setLayout(new FlowLayout(FlowLayout.LEFT)); //组件左对齐
		this.add(newGame);
		this.add(stopGame);
		
		newGame.addActionListener(this); //为newGame添加事件接口
		stopGame.addActionListener(this); //为stopGame添加事件接口
		
		dialog.setLayout(new GridLayout(2, 1)); //定义网状布局
		dialog.add(label);
		dialog.add(ok);
		dialog.setSize(200, 200); //大小
		dialog.setLocation(200, 200); //位置
		dialog.setVisible(false); //设置组件的可见性
		ok.addActionListener(this);
	}
	
	//布局完善及动态实现
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		g.drawRect(10, 40, 400, 300); //画出矩形范围,原点在左上角
		g.drawString("分数:"+fenShu, 150, 15);
		g.drawString("速度:"+Speed, 150, 35);
		g.setColor(new Color(255, 0, 0)); //设置豆子的颜色为红色
		if(start){
			g.fillRect(10+rx*10, 40+ry*10, 10, 10); //红色填充格子
			
			for (int i = 0; i < list.size(); i++) {
				
				//游戏开始后,蛇吃下豆子,豆子变为绿色添加到蛇身上
				g.setColor(new Color(0, 255, 0));
				g.fillRect(10+list.get(i).getX()*10, 40+list.get(i).getY()*10, 10, 10);
			}
		}
	}
	
	//事件描述及处理
	public void actionPerformed(ActionEvent e) {
		
		//重新开始游戏
		if(e.getSource()==newGame){
			newGame.setEnabled(false);
			start = true;
			rx=r.nextInt(40);
			ry=r.nextInt(30);
			
			Node tempAct = new Node();
			tempAct.setX(20);
			tempAct.setY(15);
			list.add(tempAct);
			this.requestFocus();
			nThread = new Thread(this); //初始化线程
			nThread.start();
			repaint(); //调用paint(),刷新界面(重绘构件)
		}
		
		if(e.getSource()==stopGame){ //结束事件按钮停止游戏
			System.exit(0);
		}
		
		//点击确认返回初始界面
		if(e.getSource()==ok){
			list.clear();
			start=false;
			newGame.setEnabled(true);
			dialog.setVisible(false);
			fenShu=0;
			Speed=0;
			repaint();
		}
	}
	
	private void eat() {
		
		//游戏开始在界面内生成随机位置
		if (rx==list.get(0).getX()&&ry==list.get(0).getY()) {
			rx = r.nextInt(40);
			ry = r.nextInt(30);
			Node tempAct = new Node();
			
			//this指针指向list的尾元素的坐标
			tempAct.setX(list.get(list.size()-1).getX());
			tempAct.setY(list.get(list.size()-1).getY());
			list.add(tempAct);
			
			fenShu = 10*Speed+10; //加分数
			eat++;
			if(eat>=1){
				Speed++;
			}
		}
	}
	
	public void otherMove(){
		Node tempAct = new Node(); //为对象重新分配内存
		
		/*关键算法,迭代法,tempAct获取list最后一个元素的坐标,也就是蛇头坐标,
		 * 每个元素使用前一个元素的坐标,第一个使用表头坐标
		 */
		for (int i = 0; i < list.size(); i++) {
			if (i==1) {
				list.get(i).setX(list.get(0).getX());
				list.get(i).setY(list.get(0).getY());
			}else if(i>1){
				tempAct=list.get(i-1);
				list.set(i-1, list.get(i));
				list.set(i, tempAct);
			}
			
		}
	}
	
	public void move(int x,int y){
		
		if (minYes(x, y)) {
			otherMove(); 								//调用otherMove()方法
			list.get(0).setX(list.get(0).getX()+x);
			list.get(0).setY(list.get(0).getY()+y);
			eat(); 										//调用eat()方法
			repaint(); 								//实现list的更新
		}else {
			nThread = null;
			label.setText("您输了!您的分数是"+fenShu);
			dialog.setVisible(true); 				//显示窗口
		}
	}
	
	public boolean minYes(int x,int y){
		if (!maxYes(list.get(0).getX()+x,list.get(0).getY()+ y)) {
			return false;
		}
		return true;
	}
	public boolean maxYes(int x,int y){
		if (x<0||x>=40||y<0||y>=30) {
			return false;
		}
		
		/*蛇头坐标和身体其他节点坐标重合游戏结束。
		 * 这地方真的是改死我了,专治丢三落四系列。
		 * 一开始忘了在move()方法里调用,我说为啥吃到自己咋没反应呢;
		 *调用完还是没用。一看发现条件语句if(list.get(0).equals(get(i))),get()个锤子呢,,x和y两个变量怎么访问;
		 * 改成getX()和getY()后还是不行,if(Dead)没看清,返回值搞反了,一开始游戏就结束。真是吐了呀,,差点猝!!!
		 */
		for (int i = 0; i < list.size(); i++) {
			if (i>1&&list.get(0).getX()==list.get(i).getX()
					&&list.get(0).getY()==list.get(i).getY()) {
				return false;
			}
		}
		return true;
	}
	
	//键值与方法的参数对应
	public void keyPressed(KeyEvent e) {
		if(start){
			switch (e.getKeyCode()) { //获取键值
			case KeyEvent.VK_UP:
				move(0, -1);   //up和down相反是因为坐标原点在左上角,横坐标向右,纵坐标向下
				temp=1;
				break;
			case KeyEvent.VK_DOWN:
				move(0, 1);
				temp=2;
				break;
			case KeyEvent.VK_LEFT:
				move(-1, 0);
				temp=3;
				break;
			case KeyEvent.VK_RIGHT:
				move(1, 0);
				temp=4;
				break;

			default:
				break;
			}
		}
	}
	
	public void keyReleased(KeyEvent e) {}
	public void keyTyped(KeyEvent e) {}
	
	public void run() {
		
		//move()方法与参数值对应
		while (start) {
			switch (temp) {
			case 1:
				move(0, -1);
				break;
			case 2:
				move(0, 1);
				break;
			case 3:
				move(-1, 0);
				break;
			case 4:
				move(1, 0);
				break;
			default:
				break;
			}
			repaint(); //调用paint(),刷新界面(重绘构件)
			
			try {
				
				//通过线程休眠时间控制蛇的移动速度
				Thread.sleep(300-15*Speed);
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}	
	}
}

public class SnakeGame extends JFrame {
	/**
	 * 继承JFrame类
	 */
	private static final long serialVersionUID = 1L; //定义程序序列化ID,将对象转换为字节
	public SnakeGame() {
		SnakeOperator win = new SnakeOperator(); //创建SnakeOperator的对象win
		add(win); //将win添加到SnakeGame类中
		
		setTitle("̰贪吃蛇");
		setSize(435,390);
		setLocation(200, 200);
		setVisible(true); //显示布局
	}
	public static void main(String[] args) {
		new SnakeGame(); //调用类运行程序
	}
}

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值