擲骰子
**多線程&&觀察者模式
題目要求:《擲骰子》窗體小游戲,在該游戲中,玩家初始擁有1000的金錢,每次輸入押大還是押小,以及下注金額,隨機3個骰子的點數,如果3個骰子的總點數小於等於9,則開小,否則開大,然后判斷玩家是否押對,如果未押對則扣除下注金額,如果押對則獎勵和玩家下注金額相同的金錢。
分析:這個題目要求靈活運用多線程的相關知識,達到點擊開始按鈕時,有3個線程啟動,分別控制3顆骰子的轉動,在3顆骰子全部轉完以后,回到主線程計算游戲結果。
1 //3個線程控制3顆骰子 2 Thread t1 = new Thread(); 3 Thread t2 = new Thread(); 4 Thread t3 = new Thread(); 5 //啟動3個線程 6 t1.start(); 7 t2.start(); 8 t3.start(); 9 //將3個線程加入主線程 10 t1.join(); 11 t2.join(); 12 t3.join();
But,,,寫完代碼以后發現,這樣做雖然能夠保證游戲能夠正確運行,但是當我點擊開始按鈕時,由於3個骰子線程都是直接開在主線程上的,點擊開始按鈕時,按鈕出現下沉情況,子線程一直在后台運行,我窗體中的圖片根本不會發生改變,而是直接顯示最后的結果,意思就是骰子一直在后台轉動,不在前台的窗體中及時更新顯示。后來在網上苦苦找尋,大神們說如果想要通過點擊JButton使窗體中的JLabel/JTextFeild等其他組件及時更新,直接在JButton的監聽事件的實現方法里面直接創建匿名線程,也就是說直接在actionPerformed()方法中修改代碼即可,這樣能保證你的組件中內容的及時變換,實現非常炫酷的效果。
代碼如下:
public void actionPerformed(ActionEvent e) { new Thread(new Runnable() { @Override public void run() { //將外部線程類轉移到窗體內部 } }).start(); }
But,,,But,,, 雖然非常炫酷了,能夠實現圖片的及時更新了,游戲結果卻錯了,每次我的骰子還在轉動呢,我的游戲結果卻早早的就出來了。
原因:3根骰子線程屬於子線程,窗體線程屬於主線程,問題就在於:子線程可以通過變成精靈線程來保持與主線程的同生死,但是主線程卻無法控制子線程何時死亡,只有等待子線程執行完所屬的run()方法,結束線程后才知道。
解決方法:在主線程(main)中開3個子線程(t1,t2,t3),在每個子線程上再開一個子子線程(t11,t21,t31)。
t1,t2,t3只運行一次,負責創建子子線程;t11,t21,t31每個線程運行多次,負責控制窗體中的圖標及時更新。
這樣主線程就不受子線程的影響,開始按鈕也不回出現下沉的情況。
但是同樣在此處使用join方法也是hold不住子線程的,畢竟t1,t2,t3只運行了一次,join對他們來說根本不起作用,想要掌控t11,t21,t31,最容易理解的辦法,就是使用觀察者模式了。
將窗體看做觀察者,子線程看做被觀察者。子線程運行完時,通知觀察者我已經運行完成,當觀察者觀察到子線程全都運行完時,才開始運行后續步驟。
全部代碼:
1.窗體
1 package com.sxt.dice; 2 3 import java.awt.Color; 4 5 public class DiceFrame extends JFrame implements ActionListener, Observer { 6 7 /** 8 * 《擲骰子》控制台小游戲,在該游戲中,玩家初始擁有1000的金錢,每次輸入押大還是押小, 9 * 以及下注金額,隨機3個骰子的點數,如果3個骰子的總點數小於等於9,則開小,否則開大, 10 * 然后判斷玩家是否押對,如果未押對則扣除下注金額,如果押對則獎勵和玩家下注金額相同的金錢。 11 * 12 * 運用觀察者模式 3個子線程分別控制3個骰子,都已經結束時,通知觀察者窗體,窗體觀察到所有子線程都結束時,計算游戲結果 13 * 14 */ 15 16 private static final long serialVersionUID = 1L; 17 private JTextField txtPut; 18 private JButton btnStart; 19 private JLabel labResult; 20 private JComboBox<String> comboBox; 21 private JLabel labBigOrSmall; 22 private JLabel labPut; 23 private JLabel labSumMoney; 24 private JLabel labDice3; 25 private JLabel labDice2; 26 private JLabel labDice1; 27 private JLabel labSum; 28 private JLabel labMes; 29 30 private static List<Icon> imgs = new ArrayList<Icon>(); 31 32 public static void main(String[] args) { 33 new DiceFrame(); 34 } 35 36 public DiceFrame() { 37 this.setLocationRelativeTo(null); 38 this.setBounds(200, 50, 380, 297); 39 this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 40 getContentPane().setLayout(null); 41 this.setResizable(false); 42 43 labDice1 = new JLabel(""); 44 labDice1.setIcon(new ImageIcon("img/dices.jpg")); 45 labDice1.setBounds(30, 50, 96, 96); 46 getContentPane().add(labDice1); 47 48 labSum = new JLabel("\u5269\u4F59\u91D1\u989D\uFF1A"); 49 labSum.setBounds(10, 10, 69, 23); 50 getContentPane().add(labSum); 51 52 labDice2 = new JLabel(""); 53 labDice2.setIcon(new ImageIcon("img/dices.jpg")); 54 labDice2.setBounds(136, 50, 96, 96); 55 getContentPane().add(labDice2); 56 57 labDice3 = new JLabel(""); 58 labDice3.setIcon(new ImageIcon("img/dices.jpg")); 59 labDice3.setBounds(242, 50, 96, 96); 60 getContentPane().add(labDice3); 61 62 labSumMoney = new JLabel("3000"); 63 labSumMoney.setForeground(Color.red); 64 labSumMoney.setBounds(86, 10, 63, 23); 65 getContentPane().add(labSumMoney); 66 67 labPut = new JLabel("\u672C\u6B21\u4E0B\u6CE8\uFF1A"); 68 labPut.setToolTipText("0.0"); 69 labPut.setBounds(10, 199, 69, 23); 70 getContentPane().add(labPut); 71 72 txtPut = new JTextField(); 73 txtPut.setBounds(80, 200, 69, 21); 74 getContentPane().add(txtPut); 75 txtPut.setColumns(10); 76 77 labBigOrSmall = new JLabel("\u62BC\uFF1A"); 78 labBigOrSmall.setBounds(45, 232, 34, 27); 79 getContentPane().add(labBigOrSmall); 80 81 comboBox = new JComboBox<String>(); 82 comboBox.setBounds(80, 234, 69, 23); 83 getContentPane().add(comboBox); 84 comboBox.addItem("大"); 85 comboBox.addItem("小"); 86 87 labResult = new JLabel(""); 88 labResult.setBounds(136, 156, 126, 27); 89 getContentPane().add(labResult); 90 91 btnStart = new JButton("START"); 92 btnStart.setBounds(263, 199, 88, 58); 93 getContentPane().add(btnStart); 94 95 labMes = new JLabel("<html><font size=5 color=red>*</font></html>"); 96 labMes.setBounds(152, 203, 101, 15); 97 getContentPane().add(labMes); 98 99 this.setVisible(true); 100 101 imgs.add(new ImageIcon("img/1.png")); 102 imgs.add(new ImageIcon("img/2.png")); 103 imgs.add(new ImageIcon("img/3.png")); 104 imgs.add(new ImageIcon("img/4.png")); 105 imgs.add(new ImageIcon("img/5.png")); 106 imgs.add(new ImageIcon("img/6.png")); 107 108 btnStart.addActionListener(this); 109 } 110 111 @Override 112 public void actionPerformed(ActionEvent e) { 113 if (e.getSource() == btnStart) { 114 115 // 清除上次游戲的結果 116 labResult.setText(""); 117 118 // 獲取當前下注金額,用戶余額,用戶押大還是押小 119 String txt = txtPut.getText().trim(); 120 String remain = labSumMoney.getText().trim(); 121 122 // 余額不足,不能開始游戲,提示用戶充值 123 if (Integer.parseInt(remain) <= 0) { 124 JOptionPane.showMessageDialog(null, "當前余額不足,請充值!"); 125 return; 126 } 127 128 // 下注金額合法性檢查 129 if (txt.length() == 0) { 130 // 提示用戶輸入 131 labMes.setText("*請輸入下注金額"); 132 labMes.setForeground(Color.RED); 133 return; 134 } 135 // 檢查用戶下注金額是否在有效范圍內 136 if (Integer.parseInt(txt) <= 0 137 || Integer.parseInt(txt) > Integer.parseInt(remain)) { 138 txtPut.setText(""); 139 labMes.setText("下注金額應在0~" + remain + "之間"); 140 return; 141 } 142 143 // 游戲開始后相關項不可更改 144 txtPut.setEnabled(false); 145 labMes.setText(""); 146 comboBox.setEnabled(false); 147 148 //在主線程上開t1,t2,t3 3個子線程 149 Thread t1 = new Thread() { 150 @Override 151 public void run() { 152 //每個子線程上再開子子線程,控制圖標變換 153 IconThread t11 = new IconThread(labDice1, imgs); 154 //給t11添加觀察者,即當前窗體 155 t11.addObserver(DiceFrame.this); 156 new Thread(t11).start(); 157 } 158 }; 159 160 Thread t2 = new Thread() { 161 @Override 162 public void run() { 163 IconThread t21 = new IconThread(labDice2, imgs); 164 t21.addObserver(DiceFrame.this); 165 new Thread(t21).start(); 166 } 167 }; 168 169 Thread t3 = new Thread() { 170 @Override 171 public void run() { 172 IconThread t31 = new IconThread(labDice3, imgs); 173 t31.addObserver(DiceFrame.this); 174 new Thread(t31).start(); 175 } 176 }; 177 178 t1.start(); 179 t2.start(); 180 t3.start(); 181 } 182 183 } 184 185 /** 186 * 獲取骰子點數和 187 * 188 * @param lab 189 * @return sum 190 */ 191 private int result(JLabel lab) { 192 // 獲取當前骰子圖片 193 Icon icon = lab.getIcon(); 194 int sum = 0; 195 for (int i = 0; i < imgs.size(); i++) { 196 if (icon.equals(imgs.get(i))) { 197 sum += (i + 1); 198 break; 199 } 200 } 201 return sum; 202 } 203 204 // 構建所有被觀察者的集合 205 Vector<Observable> allObservables = new Vector<Observable>(); 206 207 @Override 208 public void update(Observable o, Object arg) { 209 System.out.println(o + "................."); 210 // 如果集合中不包含當前被觀察者,將此被觀察者加入集合 211 if (allObservables.contains(o) == false) { 212 allObservables.add(o); 213 } 214 215 // 如果集合中被觀察者個數為3,說明3個骰子線程已經全部結束 216 if (allObservables.size() == 3) { 217 // 獲取當前下注金額,用戶余額,用戶押大還是押小 218 String txt = txtPut.getText().trim(); 219 String remain = labSumMoney.getText().trim(); 220 String bigOrSmall = comboBox.getSelectedItem().toString(); 221 // 獲取每個骰子點數 222 int sum1 = result(labDice1); 223 int sum2 = result(labDice2); 224 int sum3 = result(labDice3); 225 System.out.println(sum1 + "-" + sum2 + "-" + sum3); 226 int sum = sum1 + sum2 + sum3; 227 System.out.println(sum); 228 229 if (sum > 9 && "大".equals(bigOrSmall) || sum <= 9 230 && "小".equals(bigOrSmall)) { 231 232 // 獎勵玩家相應金額 233 remain = String.valueOf(Integer.parseInt(remain) 234 + Integer.parseInt(txt)); 235 labSumMoney.setText(remain); 236 237 // 顯示游戲結果 238 labResult.setText("WIN"); 239 labResult.setForeground(Color.GREEN); 240 labResult.setFont(new Font("宋體", Font.BOLD, 40)); 241 242 } else { 243 // 扣除玩家相應金額 244 remain = String.valueOf(Integer.parseInt(remain) 245 - Integer.parseInt(txt)); 246 labSumMoney.setText(remain); 247 248 labResult.setText("FAIL"); 249 labResult.setForeground(Color.red); 250 labResult.setFont(new Font("宋體", Font.BOLD, 40)); 251 252 } 253 txtPut.setEnabled(true); 254 comboBox.setEnabled(true); 255 // 本次游戲結束后移除集合中所有線程 256 allObservables.removeAll(allObservables); 257 } 258 } 259 260 }
2.線程
1 package com.sxt.dice; 2 3 import java.util.List; 4 import java.util.Observable; 5 import java.util.Random; 6 7 import javax.swing.Icon; 8 import javax.swing.JLabel; 9 10 public class IconThread extends Observable implements Runnable { 11 /** 12 * 運用觀察者模式,將子線程作為被觀察對象,一旦子線程運行完,發生改變,通知觀察者 13 */ 14 JLabel lab; 15 16 Random random = new Random(); 17 List<Icon> imgs; 18 19 public IconThread(JLabel lab, List<Icon> imgs) { 20 this.lab = lab; 21 this.imgs = imgs; 22 23 } 24 25 @Override 26 public void run() { 27 //設置每顆骰子轉動30次 28 int count = 30; 29 while (count > 0) { 30 31 //獲取一個隨機數[0~6) 32 int index = random.nextInt(6); 33 //從imgs集合中取相應圖片放入lab中 34 lab.setIcon(imgs.get(index)); 35 count--; 36 37 try { 38 Thread.sleep(50); 39 } catch (InterruptedException e) { 40 // TODO Auto-generated catch block 41 e.printStackTrace(); 42 } 43 } 44 45 this.setChanged();// 子線程運行完,發生改變 46 this.notifyObservers();// 通知觀察者 47 } 48 }