Java多线程之赛跑游戏(含生成exe文件)

在JavaSE中,多线程是一个重要的内容。

我们要了解多线程的概念,就要先了解进程的概念;要了解进程的概念,就离不开操作系统的概念。

在一台正常运行的电脑中,计算机硬件(如CPU、内存、硬盘、网卡、显示器、键盘、鼠标等)提供了基础的硬件环境;在硬件之上,是操作系统,这是系统级的软件程序,用来管理计算机的各项软件资源,是人机交互中的关键环节;在操作系统之上,则是各类应用软件程序,比如QQ、微信、浏览器等。

进程是一个动态的概念,程序则是一个静态的概念。程序在操作系统中运行起来时,需要操作系统为其分配相应的内存、CPU等资源,成为进程。不同的进程,独享各自的内存、CPU等资源。这也是进程安全性所不可缺少的。所以,进程是操作系统分配CPU及各类系统资源的基本单位。

当进程运行起来时,通常会有多个子任务,以游戏为例,里面需要绘制游戏面板,如果游戏里有多个角色的人物进行并发活动,还需要能够获得相应的CPU资源,从而取得CPU的执行权,以进入运行状态。同时,在同一个游戏中,每个角色的有关状态数据,可以为该游戏中的其他角色所获取。由此可见,一个进程中可以有多个子任务,这些子任务需要并发轮流获取CPU的执行权,同时可以共享进程的内存资源。进程中的这些子任务就被称为线程,多个子任务就是多个线程。线程是进程中分配CPU资源的基本单位,线程不可以脱离进程而存在。

Java语言本身就是一种多线程语言。比如,在JavaSE的一个Java工程中,JVM通过main入口函数运行起来时,同时还会有GC(垃圾回收器)运行起来,以等待回收Java堆内存中变为垃圾的对象,以节省堆内存空间。

在做完上述知识铺垫和准备后,我接下来通过一个赛跑游戏,来为大家演示Java多线程的运用。

在这里,我先将该赛跑游戏的运行效果予以演示:

每次登录该游戏,会弹出一个提示框,告知用户是第几次登录,并有温馨提示。

点击“确定”或关闭该提示框,则进入游戏界面。该界面分为上下两部分,上半部分为游戏跑道,4个赛手,共4个跑道;下半部分为游戏控制面板,提示用户选择赛手,输入押注金额,以及点击“开始”按钮开始赛跑,还有“重新开始”按钮以回到比赛准备的状态等。

点击“开始”按钮后,进入赛跑游戏,游戏面板中的4个赛手开始奔跑,对于先跑到终点的赛手,会弹出提示框予以庆祝;如果用户押注失败,则用户本金会予以相应减少;如果用户押注成功,则用户本金会予以相应增加。

好了,演示完上述游戏后,接下来谈下开发思路。

首先,完成游戏前端部分。先绘制一个游戏窗体,窗体上添加面板,面板上添加各类所需的组件。

其次,游戏前端部分完成后,根据面向对象的思想,将赛手抽象为一个类,将与赛手有关的各种属性与功能均由该类实现;同时,主线程负责绘制游戏前端,各个赛手要运行时,则通过开辟子线程来实现。

再次,当赛跑游戏结束时,根据产生的第一名,刷新本金,弹出庆祝框,同时提示用户是成功或者失败;此外,将用户的本金与登录次数存入一个属性文件,用户本金与登录次数的变化会实时更新到属性文件中。

Step 1

请看游戏前端代码:

package runningGame;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Properties;
import java.util.ResourceBundle;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;

/**
 * 游戏 面板设计
 * @author 李章勇
 * 常量分析:
 * 		4个赛手
 * 		赛手编号
 * 		胜利感言
 *		跑道面板
 *			4个跑道,每个跑道一张背景图
 *			每个跑道上各有一个准备状态的赛手
 *			终点线
 *		控制面板
 *			4个单选按钮[表示赛手】
 *			提示选择赛手、输入押注,押注输入框
 *			我的本金提示
 *			开始按钮,重新开始按钮
 *	多个子线程共用同一段代码,赛手类
 */
public class GameJFrame extends JFrame{
	//赛道背景面板
	GamePanel jp_game=new GamePanel();
	//4张跑道背景图
	Image[] img_back=new Image[StaticIntString.NUM];
	//每个跑道上处于准备状态的小人
	Image[] img_ready=new Image[StaticIntString.NUM];
	//每个跑道上奔跑中的小人
	Image[] img_run=new Image[StaticIntString.NUM];
	
	//游戏控制面板
	JPanel jp_control=new JPanel();
	//4个单选按钮
	static JRadioButton[] jrb_mans=new JRadioButton[StaticIntString.NUM];
	//按钮组
	ButtonGroup bg=new ButtonGroup();
	//提示选择姓名并输入金额
	JLabel jl_inputTips=new JLabel(StaticIntString.INPUT_TIPS1);
	//输入押注金额
	JTextField jtf_cost=new JTextField(10);
	JLabel jl_winFailTips=new JLabel("本次输赢:");
	JLabel jl_winFail=new JLabel(StaticIntString.WIN_FAIL);
	//本金提示
	JLabel jl_ownMoneyTips=new JLabel("我的本金:");
	static Properties pro=new Properties();
	//从属性文件里读取本金
	static ResourceBundle resource=ResourceBundle.getBundle("runningGame.infos");
	String ownMoney=resource.getString("ownMoney");
	BigInteger bi_times=new BigInteger(resource.getString("times"));
	static BigInteger bi_bankruptcy=new BigInteger(resource.getString("bankruptcy"));
	String ownMoney_System=resource.getString("ownMoney_System");
	//本金
	JLabel jl_ownMoney=new JLabel(ownMoney);
	//开始按钮
	JButton jbt_start=new JButton("开始");
	//重新开始按钮
	JButton jbt_restart=new JButton("重新开始");
	static String selectedName;
	RunningMan[] mans=new RunningMan[StaticIntString.NUM];
	
	public GameJFrame() {
		if(bi_times.toString().equals("1")){			
			JOptionPane.showMessageDialog(this, "首次登陆,欢迎光临!\n系统赠予您10000元,\n祝您玩得开心愉快!\n只是玩玩,不要沉溺赌博哦");	
		}else{
				JOptionPane.showMessageDialog(this, "您这是第 "+bi_times.toString()+" 次登陆,欢迎光临!\n祝您玩得开心愉快!\n只是玩玩,不要沉溺赌博哦");			
		}
		bi_times=bi_times.add(new BigInteger("1"));
		try {
			BufferedInputStream fis=new BufferedInputStream(new FileInputStream("src//runningGame//infos.properties"));
			BufferedOutputStream fos=new BufferedOutputStream(new FileOutputStream("src//runningGame//infos.properties"));							
			String ownMoney_System=resource.getString("ownMoney_System");;
			try {
				pro.load(fis);
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			pro.setProperty("times", bi_times.toString());
			pro.setProperty("ownMoney", ownMoney);
			pro.setProperty("bankruptcy", bi_bankruptcy.toString());
			pro.setProperty("ownMoney_System", ownMoney_System);			
			try {
				pro.store(fos, "");
				fis.close();
				fos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		
		for(int i=0;i<StaticIntString.NUM;i++){
			img_back[i]=Toolkit.getDefaultToolkit().createImage("imgBack//"+(i+1)+".jpg");
			img_ready[i]=Toolkit.getDefaultToolkit().createImage("imgReady//"+(i+1)+"2.gif");
			img_run[i]=Toolkit.getDefaultToolkit().createImage("imgRun//"+(i+1)+"1.gif");
			jrb_mans[i]=new JRadioButton(StaticIntString.NAMES[i]);
			bg.add(jrb_mans[i]);
			jp_control.add(jrb_mans[i]);
		}
		jbt_start.addActionListener(new ActionListener(){
			boolean isSelected=false;
			public boolean judgeSelected(){
				for(JRadioButton j:jrb_mans){
					if(j.isSelected()){
						isSelected=true;
					}
				}
				return isSelected;
			}
			
			@Override
			public void actionPerformed(ActionEvent arg0) {
				if(!judgeSelected()){
					JOptionPane.showMessageDialog(GameJFrame.this, "您还没选人呢", 
							"看着点", JOptionPane.WARNING_MESSAGE);
					return;
				}
				if(jtf_cost.getText().trim().length()==0){
					JOptionPane.showMessageDialog(GameJFrame.this, "您还没下注呢", 
							"看着点", JOptionPane.WARNING_MESSAGE);
					return;
				}
				char[] cs=jtf_cost.getText().toCharArray();
				for(int i=0;i<cs.length;i++){
					if(cs[i]<'0' || cs[i]>'9'){
						JOptionPane.showMessageDialog(GameJFrame.this, "您输入的不是数字", 
								"看着点", JOptionPane.WARNING_MESSAGE);
						jtf_cost.setText("");
						return;
					}
					if(cs[0]=='0'){
						JOptionPane.showMessageDialog(GameJFrame.this, "数字首位不能是0", 
								"看着点", JOptionPane.WARNING_MESSAGE);
						jtf_cost.setText("");
						return;
					}
				}
				BigInteger bi_jtf_cost=new BigInteger(jtf_cost.getText());
				BigInteger bi_jl_ownMoney=new BigInteger(jl_ownMoney.getText());
				
				if((bi_jtf_cost.compareTo(bi_jl_ownMoney))>0){
					JOptionPane.showMessageDialog(GameJFrame.this, "您的下注金额不能超过本金哦", 
							"看着点", JOptionPane.WARNING_MESSAGE);
					jtf_cost.setText("");
					return;
				}
				
				for(JRadioButton j:jrb_mans){
					if(j.isSelected()){
						selectedName=j.getText();
						break;
					}
				}
				for(JRadioButton j:jrb_mans){
					j.setEnabled(false);
				}
				
				jtf_cost.setEditable(false);
				jl_inputTips.setText(StaticIntString.INPUT_TIPS2);
				jbt_restart.setEnabled(true);
				jbt_start.setEnabled(false);
				RunningMan.isEnd=false;
				RunningMan.canRun=true;
				RunningMan.num=0;
				for(int i=0;i<StaticIntString.NUM;i++){
					mans[i].img=img_run[i];
					mans[i].start();
				}				
			}			
		});
		jbt_restart.addActionListener(new ActionListener(){

			@Override
			public void actionPerformed(ActionEvent arg0) {
				
				for(int i=0;i<StaticIntString.NUM;i++){
					jrb_mans[i].setText(StaticIntString.NAMES[i]);
					jrb_mans[i].setEnabled(true);
				}
				jl_inputTips.setText(StaticIntString.INPUT_TIPS1);
				jtf_cost.setText("");
				jtf_cost.setEditable(true);
				jl_winFail.setForeground(Color.BLACK);
				jl_winFail.setText(StaticIntString.WIN_FAIL);
				jbt_start.setEnabled(true);
				jbt_restart.setEnabled(false);
				RunningMan.isEnd=true;
				RunningMan.canRun=false;
				bg.clearSelection();
				for(int i=0;i<StaticIntString.NUM;i++){
					mans[i]=new RunningMan(StaticIntString.NAMES[i], 0, (jp_game.getHeight()*i)/4+30, img_ready[i], GameJFrame.this);
				}
			}
		});
		
		jp_control.setBorder(BorderFactory.createTitledBorder("游戏控制系统"));
		jp_control.add(jl_inputTips);
		jp_control.add(jtf_cost);
		jp_control.add(jl_winFailTips);
		jp_control.add(jl_winFail);
		jp_control.add(jl_ownMoneyTips);
		jp_control.add(jl_ownMoney);		
		jp_control.add(jbt_start);
		jp_control.add(jbt_restart);
		this.add(jp_game,BorderLayout.CENTER);
		this.add(jp_control, BorderLayout.SOUTH);
		setResizable(false);
		setTitle("赛跑游戏   ---作者:李章勇");
		setBounds(200, 100, 1000, 600);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);
		for(int i=0;i<StaticIntString.NUM;i++){
			mans[i]=new RunningMan(StaticIntString.NAMES[i], 0, (jp_game.getHeight()*i)/4+30, img_ready[i], this);
		}
	}
	
	class GamePanel extends JPanel{		
		@Override
		public void paint(Graphics g) {
			super.paint(g);
			for(int i=0;i<StaticIntString.NUM;i++){
				g.drawImage(img_back[i], 0, (this.getHeight()*i)/4,this.getWidth(),this.getHeight()/4+30,this);
				mans[i].drawSelf(g, this);
				
			}
			g.setColor(Color.RED);
			g.fillRect(850, 0, 5, this.getHeight());
		}
	}
	
	public static void main(String[] args) {
		new GameJFrame();
	}
}

Step 2

 将赛手抽象为一个类,同时创建一个接口工具,存储常数,便于对数据的统一管理;再创建一个名为“infos.properties”的属性文件,将用户本金、登录次数等信息存储到属性文件里,以备更新。

package runningGame;

public interface StaticIntString {
	public static final int NUM=4;
	public static final String[] NAMES={ "1号选手", "2号选手", "3号选手", "4号选手" };
	public static final String[] WINS={ "预测未来的最好方法,就是创造未来.", "人生重要的不是所站的位置,而是所朝的方向.", 
			"如你想要拥有完美无暇的友谊,可能一辈子找不到兄弟姐妹","正因年轻咱们一无所有,也正正因年轻咱们将拥有一切." };	
	public static final String INPUT_TIPS1="请选择赛手并输入金额:";
	public static final String INPUT_TIPS2="本次下注金额:";
	public static final String GET_WIN="[获胜]";
	public static final String WIN_FAIL="[Win/Fail]";
	public static final String WIN_STATE="[Win]";
	public static final String FAIL_STATE="[Fail]";
} 
属性文件infos.properties: 
#破产次数,登录次数,系统默认赠予的本金,用户的实际本金;只要用户本金小于等于0,系统会自动为用户赠予初始本金。
#Fri Dec 01 09:34:34 CST 2017
bankruptcy=0
times=1
ownMoney_System=10000
ownMoney=10000  
赛手类RunningMan: 
package runningGame;

import java.awt.Graphics;
import java.awt.Image;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Random;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;

/**
 * 赛手的属性:姓名,坐标(x/y),自身图像,准备状态的图像,关联的窗体
 * 		位移所需的随机数Random,
 * 		第一名NO1
 * 		是否能跑的状态canRun
 * 		比赛是否结束isEnd
 * 		每个赛手跑完时计数num
 *  			
 * 	赛手画自己的方法drawSelf
 * 	赛手跑步位移的方法move
 * 	子线程运行的run方法
 * 		
 * @author 李章勇
 *
 */
public class RunningMan extends Thread {
	public String name;
	private int x_point,y_poinit;
	public Image img;
	private Image imgReady;
	private GameJFrame jf_game;
	static Random rand=new Random();
	static RunningMan NO1;
	static boolean canRun=true;
	static boolean isEnd=false;
	static int num=0;
	static BigInteger bi_ownMoney;
	
	public RunningMan(String name,int x_point,int y_point,Image img,GameJFrame jf_game) {
		this.name=name;
		this.x_point=x_point;
		this.y_poinit=y_point;
		this.img=img;
		this.imgReady=img;
		this.jf_game=jf_game;
	}
	
	public void setName2(String name){
		this.name=name;
	}
	public String getName2(){
		return name;
	}
	
	public void drawSelf(Graphics g,JPanel jp){
		g.drawImage(img, x_point, y_poinit, jp);
	}
	
	public void move(){
		x_point+=10+rand.nextInt(30);
	}
	
	@Override
	public void run() {
		while(x_point<=850 && canRun){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			move();
		}
		synchronized(String.class){
			if(canRun){
				x_point=850;
				img=imgReady;
				num++;
			}
			if(!isEnd){
				NO1=this;
				isEnd=true;
			}
			if(num==4){
				System.out.println("本次比赛第一名:"+NO1.name);
				//根据比赛结果,修改金额 类
				bi_ownMoney=new GetResult(jf_game).getRightMoney();
				new WinDialog();
				try {
					BufferedInputStream fis = new BufferedInputStream(new FileInputStream("src//runningGame//infos.properties"));
					BufferedOutputStream fos=new BufferedOutputStream(new FileOutputStream("src//runningGame//infos.properties"));
					try {
						GameJFrame.pro.load(fis);
						if((bi_ownMoney.compareTo(new BigInteger("0"))<=0)){
							JOptionPane.showMessageDialog(jf_game, "您已没有本金,\n系统不差钱儿,再次赠予您 10000 元!\n祝您玩得开心哦!", 
									"看着点", JOptionPane.WARNING_MESSAGE);
							jf_game.jl_ownMoney.setText(GameJFrame.resource.getString("ownMoney_System"));
							GameJFrame.pro.setProperty("ownMoney", GameJFrame.resource.getString("ownMoney_System"));
							BigInteger bi_bankruptcy=new BigInteger(GameJFrame.resource.getString("bankruptcy")).add(new BigInteger("1"));
							GameJFrame.pro.setProperty("bankruptcy", bi_bankruptcy.toString());
							
						}else{
							jf_game.jl_ownMoney.setText(bi_ownMoney.toString());
							GameJFrame.pro.setProperty("ownMoney", bi_ownMoney.toString());
						}
						GameJFrame.pro.store(fos, "");
						fis.close();
						fos.close();	
					} catch (IOException e1) {
						e1.printStackTrace();
					}					
				} catch (FileNotFoundException e1) {
					e1.printStackTrace();
				}
				for(JRadioButton j:GameJFrame.jrb_mans){
					if(NO1.name.equals(j.getText())){
						j.setText(j.getText()+StaticIntString.GET_WIN);
					}
				}
			}
		}		
	}
}

 刷新用户本金的类: 

package runningGame;

import java.awt.Color;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JRadioButton;
import javax.swing.JTextField;

/**
 * 需要的属性
 * 		代表四个赛手的按钮
 * 		输入的金额
 * 		输赢状态
 * 		本金
 * 		关联的窗体
 * 获得比赛后、发生变化的钱数getRightMoney
 * 设置本金新的数值setNewMoney
 * @author 李章勇
 *
 */
public class GetResult {
	private JRadioButton[] jrb_mans;
	private JTextField jtf_cost;
	private JLabel jl_winFail;
	private JLabel jl_ownMoney;
	
	public GetResult(GameJFrame jf_game) {
		this.jrb_mans=jf_game.jrb_mans;
		this.jtf_cost=jf_game.jtf_cost;
		this.jl_winFail=jf_game.jl_winFail;
		this.jl_ownMoney=jf_game.jl_ownMoney;
	}
	
	public BigInteger getRightMoney(){
		BigInteger bi_ownMoney=new BigInteger(jl_ownMoney.getText());
		BigInteger bi_cost=new BigInteger(jtf_cost.getText());
		if(GameJFrame.selectedName!=null && GameJFrame.selectedName.equals(RunningMan.NO1.name)){
			jl_winFail.setForeground(Color.GREEN);
			jl_winFail.setText(StaticIntString.WIN_STATE);
			bi_ownMoney=bi_ownMoney.add(bi_cost);
			jl_ownMoney.setText(bi_ownMoney.toString());
		}else{
			jl_winFail.setForeground(Color.RED);
			jl_winFail.setText(StaticIntString.FAIL_STATE);
			bi_ownMoney=bi_ownMoney.subtract(bi_cost);	
			jl_ownMoney.setText(bi_ownMoney.toString());
		}
		return bi_ownMoney;
	}	
}

  弹出庆祝框的类WinDialog:

package runningGame;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.util.Random;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
 * 		对话框背景图
 * 		4个赛手获胜时的图片
 * 		自定义面板
 * 		随机数,随机获取胜利宣言
 * @author 李章勇
 *
 */
public class WinDialog extends JDialog{
	Image img_back=Toolkit.getDefaultToolkit().createImage("imgWin//winbg.jpg");
	Image[] img_wins=new Image[StaticIntString.NUM];
	WinJPanel wp=new WinJPanel();
	Random rand=new Random();
	int num=0;
	
	public WinDialog() {
		for(int i=0;i<img_wins.length;i++){
			img_wins[i]=Toolkit.getDefaultToolkit().createImage("imgWin//"+(i+1)+"3.gif");
		}
		wp.setBounds(100, 100, 400, 300);
		add(wp);
		setTitle("胜利墙");
		setBounds(300, 300, 400, 300);
		setResizable(false);
		setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		setVisible(true);
		num=rand.nextInt(4);
	}
	
	class WinJPanel extends JPanel{
		
		@Override
		public void paint(Graphics g) {
			super.paint(g);
			g.drawImage(img_back, 0, 0, this.getWidth(), this.getHeight(), this);
			
			switch(RunningMan.NO1.name){
				case "1号选手":
					g.drawImage(img_wins[0], 50, 100, this);
					break;
				case "2号选手":
					g.drawImage(img_wins[1], 50, 100, this);
					break;
				case "3号选手":
					g.drawImage(img_wins[2], 50, 100, this);
					break;
				case "4号选手":
					g.drawImage(img_wins[3], 50, 100, this);
					break;
				default:
					break;
			}
			g.setColor(Color.RED);
			g.drawString("本次获胜选手:"+RunningMan.NO1.name, 20, 30);
			g.drawString(StaticIntString.WINS[num], 20, 50);
		}
	}
}

  到此,代码部分就结束了。至于代码中用到的图片,读者参考下述对图片的命名规则,收集相应的图片即可。

   到此,整个单机赛跑游戏是完成了。然而,目前在运行时,我们需要在Eclipse里运行项目才可以打开游戏,显然这种方式并不符合普通用户。为此,我们来看如何把项目打包成jar包后,再生成exe可执行文件,这样的话,用户只要双击运行该exe可执行文件,就可以打开游戏。

  在此,介绍一款将jar文件生成exe文件时用到的第三方软件:exe4j(下载地址:http://www.ej-technologies.com/download/exe4j/files):

  至于接下来应该怎样把jar包生成exe文件,有一篇博客《将java项目打包成jar包并生成可独立执行的exe文件》中已经介绍得很详细,建议读者参考。

  现在,一个完整的exe版单机赛跑游戏就制作好了。

posted @ 2017-12-11 12:05  奔跑在梦想的道路上  阅读(1812)  评论(0编辑  收藏  举报