Java編寫擲骰子游戲
廢話不多說了,直接奔主題。
**多線程&&觀察者模式
題目要求:《擲骰子》窗體小游戲,在該游戲中,玩家初始擁有1000的金錢,每次輸入押大還是押小,以及下注金額,隨機(jī)3個(gè)骰子的點(diǎn)數(shù),如果3個(gè)骰子的總點(diǎn)數(shù)小于等于9,則開小,否則開大,然后判斷玩家是否押對(duì),如果未押對(duì)則扣除下注金額,如果押對(duì)則獎(jiǎng)勵(lì)和玩家下注金額相同的金錢。
分析:這個(gè)題目要求靈活運(yùn)用多線程的相關(guān)知識(shí),達(dá)到點(diǎn)擊開始按鈕時(shí),有3個(gè)線程啟動(dòng),分別控制3顆骰子的轉(zhuǎn)動(dòng),在3顆骰子全部轉(zhuǎn)完以后,回到主線程計(jì)算游戲結(jié)果。
//個(gè)線程控制顆骰子 Thread t = new Thread(); Thread t = new Thread(); Thread t = new Thread(); //啟動(dòng)個(gè)線程 t.start(); t.start(); t.start(); //將個(gè)線程加入主線程 t.join(); t.join(); t.join();
But,,,寫完代碼以后發(fā)現(xiàn),這樣做雖然能夠保證游戲能夠正確運(yùn)行,但是當(dāng)我點(diǎn)擊開始按鈕時(shí),由于3個(gè)骰子線程都是直接開在主線程上的,點(diǎn)擊開始按鈕時(shí),按鈕出現(xiàn)下沉情況,子線程一直在后臺(tái)運(yùn)行,我窗體中的圖片根本不會(huì)發(fā)生改變,而是直接顯示最后的結(jié)果,意思就是骰子一直在后臺(tái)轉(zhuǎn)動(dòng),不在前臺(tái)的窗體中及時(shí)更新顯示。后來在網(wǎng)上苦苦找尋,大神們說如果想要通過點(diǎn)擊JButton使窗體中的JLabel/JTextFeild等其他組件及時(shí)更新,直接在JButton的監(jiān)聽事件的實(shí)現(xiàn)方法里面直接創(chuàng)建匿名線程,也就是說直接在actionPerformed()方法中修改代碼即可,這樣能保證你的組件中內(nèi)容的及時(shí)變換,實(shí)現(xiàn)非常炫酷的效果。
代碼如下:
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
@Override
public void run() {
//將外部線程類轉(zhuǎn)移到窗體內(nèi)部
}
}).start();
}
But,,,But,,, 雖然非常炫酷了,能夠?qū)崿F(xiàn)圖片的及時(shí)更新了,游戲結(jié)果卻錯(cuò)了,每次我的骰子還在轉(zhuǎn)動(dòng)呢,我的游戲結(jié)果卻早早的就出來了。
原因:3根骰子線程屬于子線程,窗體線程屬于主線程,問題就在于:子線程可以通過變成精靈線程來保持與主線程的同生死,但是主線程卻無法控制子線程何時(shí)死亡,只有等待子線程執(zhí)行完所屬的run()方法,結(jié)束線程后才知道。
解決方法:在主線程(main)中開3個(gè)子線程(t1,t2,t3),在每個(gè)子線程上再開一個(gè)子子線程(t11,t21,t31)。
t1,t2,t3只運(yùn)行一次,負(fù)責(zé)創(chuàng)建子子線程;t11,t21,t31每個(gè)線程運(yùn)行多次,負(fù)責(zé)控制窗體中的圖標(biāo)及時(shí)更新。

這樣主線程就不受子線程的影響,開始按鈕也不回出現(xiàn)下沉的情況。
但是同樣在此處使用join方法也是hold不住子線程的,畢竟t1,t2,t3只運(yùn)行了一次,join對(duì)他們來說根本不起作用,想要掌控t11,t21,t31,最容易理解的辦法,就是使用觀察者模式了。
將窗體看做觀察者,子線程看做被觀察者。子線程運(yùn)行完時(shí),通知觀察者我已經(jīng)運(yùn)行完成,當(dāng)觀察者觀察到子線程全都運(yùn)行完時(shí),才開始運(yùn)行后續(xù)步驟。
全部代碼:
1.窗體
package com.sxt.dice;
import java.awt.Color;
public class DiceFrame extends JFrame implements ActionListener, Observer {
/**
* 《擲骰子》控制臺(tái)小游戲,在該游戲中,玩家初始擁有的金錢,每次輸入押大還是押小,
* 以及下注金額,隨機(jī)個(gè)骰子的點(diǎn)數(shù),如果個(gè)骰子的總點(diǎn)數(shù)小于等于,則開小,否則開大,
* 然后判斷玩家是否押對(duì),如果未押對(duì)則扣除下注金額,如果押對(duì)則獎(jiǎng)勵(lì)和玩家下注金額相同的金錢。
*
* 運(yùn)用觀察者模式 個(gè)子線程分別控制個(gè)骰子,都已經(jīng)結(jié)束時(shí),通知觀察者窗體,窗體觀察到所有子線程都結(jié)束時(shí),計(jì)算游戲結(jié)果
*
*/
private static final long serialVersionUID = L;
private JTextField txtPut;
private JButton btnStart;
private JLabel labResult;
private JComboBox<String> comboBox;
private JLabel labBigOrSmall;
private JLabel labPut;
private JLabel labSumMoney;
private JLabel labDice;
private JLabel labDice;
private JLabel labDice;
private JLabel labSum;
private JLabel labMes;
private static List<Icon> imgs = new ArrayList<Icon>();
public static void main(String[] args) {
new DiceFrame();
}
public DiceFrame() {
this.setLocationRelativeTo(null);
this.setBounds(, , , );
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
getContentPane().setLayout(null);
this.setResizable(false);
labDice = new JLabel("");
labDice.setIcon(new ImageIcon("img/dices.jpg"));
labDice.setBounds(, , , );
getContentPane().add(labDice);
labSum = new JLabel("\u\uF\uD\uD\uFFA");
labSum.setBounds(, , , );
getContentPane().add(labSum);
labDice = new JLabel("");
labDice.setIcon(new ImageIcon("img/dices.jpg"));
labDice.setBounds(, , , );
getContentPane().add(labDice);
labDice = new JLabel("");
labDice.setIcon(new ImageIcon("img/dices.jpg"));
labDice.setBounds(, , , );
getContentPane().add(labDice);
labSumMoney = new JLabel("");
labSumMoney.setForeground(Color.red);
labSumMoney.setBounds(, , , );
getContentPane().add(labSumMoney);
labPut = new JLabel("\uC\uB\uEB\uCE\uFFA");
labPut.setToolTipText(".");
labPut.setBounds(, , , );
getContentPane().add(labPut);
txtPut = new JTextField();
txtPut.setBounds(, , , );
getContentPane().add(txtPut);
txtPut.setColumns();
labBigOrSmall = new JLabel("\uBC\uFFA");
labBigOrSmall.setBounds(, , , );
getContentPane().add(labBigOrSmall);
comboBox = new JComboBox<String>();
comboBox.setBounds(, , , );
getContentPane().add(comboBox);
comboBox.addItem("大");
comboBox.addItem("小");
labResult = new JLabel("");
labResult.setBounds(, , , );
getContentPane().add(labResult);
btnStart = new JButton("START");
btnStart.setBounds(, , , );
getContentPane().add(btnStart);
labMes = new JLabel("<html><font size= color=red>*</font></html>");
labMes.setBounds(, , , );
getContentPane().add(labMes);
this.setVisible(true);
imgs.add(new ImageIcon("img/.png"));
imgs.add(new ImageIcon("img/.png"));
imgs.add(new ImageIcon("img/.png"));
imgs.add(new ImageIcon("img/.png"));
imgs.add(new ImageIcon("img/.png"));
imgs.add(new ImageIcon("img/.png"));
btnStart.addActionListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == btnStart) {
// 清除上次游戲的結(jié)果
labResult.setText("");
// 獲取當(dāng)前下注金額,用戶余額,用戶押大還是押小
String txt = txtPut.getText().trim();
String remain = labSumMoney.getText().trim();
// 余額不足,不能開始游戲,提示用戶充值
if (Integer.parseInt(remain) <= ) {
JOptionPane.showMessageDialog(null, "當(dāng)前余額不足,請(qǐng)充值!");
return;
}
// 下注金額合法性檢查
if (txt.length() == ) {
// 提示用戶輸入
labMes.setText("*請(qǐng)輸入下注金額");
labMes.setForeground(Color.RED);
return;
}
// 檢查用戶下注金額是否在有效范圍內(nèi)
if (Integer.parseInt(txt) <=
|| Integer.parseInt(txt) > Integer.parseInt(remain)) {
txtPut.setText("");
labMes.setText("下注金額應(yīng)在~" + remain + "之間");
return;
}
// 游戲開始后相關(guān)項(xiàng)不可更改
txtPut.setEnabled(false);
labMes.setText("");
comboBox.setEnabled(false);
//在主線程上開t,t,t 個(gè)子線程
Thread t = new Thread() {
@Override
public void run() {
//每個(gè)子線程上再開子子線程,控制圖標(biāo)變換
IconThread t = new IconThread(labDice, imgs);
//給t添加觀察者,即當(dāng)前窗體
t.addObserver(DiceFrame.this);
new Thread(t).start();
}
};
Thread t = new Thread() {
@Override
public void run() {
IconThread t = new IconThread(labDice, imgs);
t.addObserver(DiceFrame.this);
new Thread(t).start();
}
};
Thread t = new Thread() {
@Override
public void run() {
IconThread t = new IconThread(labDice, imgs);
t.addObserver(DiceFrame.this);
new Thread(t).start();
}
};
t.start();
t.start();
t.start();
}
}
/**
* 獲取骰子點(diǎn)數(shù)和
*
* @param lab
* @return sum
*/
private int result(JLabel lab) {
// 獲取當(dāng)前骰子圖片
Icon icon = lab.getIcon();
int sum = ;
for (int i = ; i < imgs.size(); i++) {
if (icon.equals(imgs.get(i))) {
sum += (i + );
break;
}
}
return sum;
}
// 構(gòu)建所有被觀察者的集合
Vector<Observable> allObservables = new Vector<Observable>();
@Override
public void update(Observable o, Object arg) {
System.out.println(o + ".................");
// 如果集合中不包含當(dāng)前被觀察者,將此被觀察者加入集合
if (allObservables.contains(o) == false) {
allObservables.add(o);
}
// 如果集合中被觀察者個(gè)數(shù)為,說明個(gè)骰子線程已經(jīng)全部結(jié)束
if (allObservables.size() == ) {
// 獲取當(dāng)前下注金額,用戶余額,用戶押大還是押小
String txt = txtPut.getText().trim();
String remain = labSumMoney.getText().trim();
String bigOrSmall = comboBox.getSelectedItem().toString();
// 獲取每個(gè)骰子點(diǎn)數(shù)
int sum = result(labDice);
int sum = result(labDice);
int sum = result(labDice);
System.out.println(sum + "-" + sum + "-" + sum);
int sum = sum + sum + sum;
System.out.println(sum);
if (sum > && "大".equals(bigOrSmall) || sum <=
&& "小".equals(bigOrSmall)) {
// 獎(jiǎng)勵(lì)玩家相應(yīng)金額
remain = String.valueOf(Integer.parseInt(remain)
+ Integer.parseInt(txt));
labSumMoney.setText(remain);
// 顯示游戲結(jié)果
labResult.setText("WIN");
labResult.setForeground(Color.GREEN);
labResult.setFont(new Font("宋體", Font.BOLD, ));
} else {
// 扣除玩家相應(yīng)金額
remain = String.valueOf(Integer.parseInt(remain)
- Integer.parseInt(txt));
labSumMoney.setText(remain);
labResult.setText("FAIL");
labResult.setForeground(Color.red);
labResult.setFont(new Font("宋體", Font.BOLD, ));
}
txtPut.setEnabled(true);
comboBox.setEnabled(true);
// 本次游戲結(jié)束后移除集合中所有線程
allObservables.removeAll(allObservables);
}
}
}
2.線程
package com.sxt.dice;
import java.util.List;
import java.util.Observable;
import java.util.Random;
import javax.swing.Icon;
import javax.swing.JLabel;
public class IconThread extends Observable implements Runnable {
/**
* 運(yùn)用觀察者模式,將子線程作為被觀察對(duì)象,一旦子線程運(yùn)行完,發(fā)生改變,通知觀察者
*/
JLabel lab;
Random random = new Random();
List<Icon> imgs;
public IconThread(JLabel lab, List<Icon> imgs) {
this.lab = lab;
this.imgs = imgs;
}
@Override
public void run() {
//設(shè)置每顆骰子轉(zhuǎn)動(dòng)次
int count = ;
while (count > ) {
//獲取一個(gè)隨機(jī)數(shù)[~)
int index = random.nextInt();
//從imgs集合中取相應(yīng)圖片放入lab中
lab.setIcon(imgs.get(index));
count--;
try {
Thread.sleep();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.setChanged();// 子線程運(yùn)行完,發(fā)生改變
this.notifyObservers();// 通知觀察者
}
}
以上所述就是關(guān)于Java編寫擲骰子游戲的全部內(nèi)容,希望大家喜歡。
相關(guān)文章
簡單了解java標(biāo)識(shí)符的作用和命名規(guī)則
這篇文章主要介紹了簡單了解java標(biāo)識(shí)符的作用和命名規(guī)則,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
java實(shí)現(xiàn)操作系統(tǒng)中的最佳置換Optimal算法
這篇文章主要介紹了java實(shí)現(xiàn)操作系統(tǒng)中的最佳置換Optimal算法 ,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Java實(shí)現(xiàn)Excel導(dǎo)入導(dǎo)出數(shù)據(jù)庫的方法示例
這篇文章主要介紹了Java實(shí)現(xiàn)Excel導(dǎo)入導(dǎo)出數(shù)據(jù)庫的方法,結(jié)合實(shí)例形式分析了java針對(duì)Excel的讀寫及數(shù)據(jù)庫操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-08-08
Spring Boot 驗(yàn)證碼的生成和驗(yàn)證詳解
我們?cè)谧鲇脩舻卿浀臅r(shí)候,為了安全性考慮,會(huì)增加驗(yàn)證碼的功能,下面這篇文章主要給大家介紹了關(guān)于Spring Boot中驗(yàn)證碼的生成和驗(yàn)證的相關(guān)資料,文中給出了詳細(xì)的示例代碼供大家參考學(xué)習(xí),需要的朋友們下面來一起看看吧。2017-06-06
SpringBoot任務(wù)調(diào)度器的實(shí)現(xiàn)代碼
SpringBoot自帶了任務(wù)調(diào)度器,通過注解的方式使用。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12
intelliJ idea 2023 配置Tomcat 8圖文教程
這篇文章主要介紹了intelliJ idea 2023 配置Tomcat 8教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06
Java使用新浪微博API開發(fā)微博應(yīng)用的基本方法
這篇文章主要介紹了Java使用新浪微博API開發(fā)微博應(yīng)用的基本方法,文中還給出了一個(gè)不使用任何SDK實(shí)現(xiàn)Oauth授權(quán)并實(shí)現(xiàn)簡單的發(fā)布微博功能的實(shí)現(xiàn)方法,需要的朋友可以參考下2015-11-11

