废话不多说了,直接奔主题。
**多线程&&观察者模式
题目要求:《掷骰子》窗体小游戏,在该游戏中,玩家初始拥有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编写掷骰子游戏的全部内容,希望大家喜欢。