廢話不多說了,直接奔主題。
**多線程&&觀察者模式
題目要求:《擲骰子》窗體小游戲,在該游戲中,玩家初始擁有1000的金錢,每次輸入押大還是押小,以及下注金額,隨機3個骰子的點數,如果3個骰子的總點數小于等于9,則開小,否則開大,然后判斷玩家是否押對,如果未押對則扣除下注金額,如果押對則獎勵和玩家下注金額相同的金錢。
分析:這個題目要求靈活運用多線程的相關知識,達到點擊開始按鈕時,有3個線程啟動,分別控制3顆骰子的轉動,在3顆骰子全部轉完以后,回到主線程計算游戲結果。
1
2
3
4
5
6
7
8
9
10
11
12
|
//個線程控制顆骰子 Thread t = new Thread(); Thread t = new Thread(); Thread t = new Thread(); //啟動個線程 t.start(); t.start(); t.start(); //將個線程加入主線程 t.join(); t.join(); t.join(); |
But,,,寫完代碼以后發現,這樣做雖然能夠保證游戲能夠正確運行,但是當我點擊開始按鈕時,由于3個骰子線程都是直接開在主線程上的,點擊開始按鈕時,按鈕出現下沉情況,子線程一直在后臺運行,我窗體中的圖片根本不會發生改變,而是直接顯示最后的結果,意思就是骰子一直在后臺轉動,不在前臺的窗體中及時更新顯示。后來在網上苦苦找尋,大神們說如果想要通過點擊JButton使窗體中的JLabel/JTextFeild等其他組件及時更新,直接在JButton的監聽事件的實現方法里面直接創建匿名線程,也就是說直接在actionPerformed()方法中修改代碼即可,這樣能保證你的組件中內容的及時變換,實現非常炫酷的效果。
代碼如下:
1
2
3
4
5
6
7
8
|
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
package com.sxt.dice; import java.awt.Color; public class DiceFrame extends JFrame implements ActionListener, Observer { /** * 《擲骰子》控制臺小游戲,在該游戲中,玩家初始擁有的金錢,每次輸入押大還是押小, * 以及下注金額,隨機個骰子的點數,如果個骰子的總點數小于等于,則開小,否則開大, * 然后判斷玩家是否押對,如果未押對則扣除下注金額,如果押對則獎勵和玩家下注金額相同的金錢。 * * 運用觀察者模式 個子線程分別控制個骰子,都已經結束時,通知觀察者窗體,窗體觀察到所有子線程都結束時,計算游戲結果 * */ 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) { // 清除上次游戲的結果 labResult.setText( "" ); // 獲取當前下注金額,用戶余額,用戶押大還是押小 String txt = txtPut.getText().trim(); String remain = labSumMoney.getText().trim(); // 余額不足,不能開始游戲,提示用戶充值 if (Integer.parseInt(remain) <= ) { JOptionPane.showMessageDialog( null , "當前余額不足,請充值!" ); return ; } // 下注金額合法性檢查 if (txt.length() == ) { // 提示用戶輸入 labMes.setText( "*請輸入下注金額" ); labMes.setForeground(Color.RED); return ; } // 檢查用戶下注金額是否在有效范圍內 if (Integer.parseInt(txt) <= || Integer.parseInt(txt) > Integer.parseInt(remain)) { txtPut.setText( "" ); labMes.setText( "下注金額應在~" + remain + "之間" ); return ; } // 游戲開始后相關項不可更改 txtPut.setEnabled( false ); labMes.setText( "" ); comboBox.setEnabled( false ); //在主線程上開t,t,t 個子線程 Thread t = new Thread() { @Override public void run() { //每個子線程上再開子子線程,控制圖標變換 IconThread t = new IconThread(labDice, imgs); //給t添加觀察者,即當前窗體 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(); } } /** * 獲取骰子點數和 * * @param lab * @return sum */ private int result(JLabel lab) { // 獲取當前骰子圖片 Icon icon = lab.getIcon(); int sum = ; for ( int i = ; i < imgs.size(); i++) { if (icon.equals(imgs.get(i))) { sum += (i + ); break ; } } return sum; } // 構建所有被觀察者的集合 Vector<Observable> allObservables = new Vector<Observable>(); @Override public void update(Observable o, Object arg) { System.out.println(o + "................." ); // 如果集合中不包含當前被觀察者,將此被觀察者加入集合 if (allObservables.contains(o) == false ) { allObservables.add(o); } // 如果集合中被觀察者個數為,說明個骰子線程已經全部結束 if (allObservables.size() == ) { // 獲取當前下注金額,用戶余額,用戶押大還是押小 String txt = txtPut.getText().trim(); String remain = labSumMoney.getText().trim(); String bigOrSmall = comboBox.getSelectedItem().toString(); // 獲取每個骰子點數 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)) { // 獎勵玩家相應金額 remain = String.valueOf(Integer.parseInt(remain) + Integer.parseInt(txt)); labSumMoney.setText(remain); // 顯示游戲結果 labResult.setText( "WIN" ); labResult.setForeground(Color.GREEN); labResult.setFont( new Font( "宋體" , Font.BOLD, )); } else { // 扣除玩家相應金額 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 ); // 本次游戲結束后移除集合中所有線程 allObservables.removeAll(allObservables); } } } |
2.線程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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 { /** * 運用觀察者模式,將子線程作為被觀察對象,一旦子線程運行完,發生改變,通知觀察者 */ 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() { //設置每顆骰子轉動次 int count = ; while (count > ) { //獲取一個隨機數[~) int index = random.nextInt(); //從imgs集合中取相應圖片放入lab中 lab.setIcon(imgs.get(index)); count--; try { Thread.sleep(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this .setChanged(); // 子線程運行完,發生改變 this .notifyObservers(); // 通知觀察者 } } |
以上所述就是關于Java編寫擲骰子游戲的全部內容,希望大家喜歡。