13_窗口使用,华容道

华容道小游戏

绘制界面

设置窗体大小,居中显示,始终显示在顶层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void initFrame() {
// 设置窗体大小
setSize(590,635);
// 设置窗体关闭模式
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 设置窗体标题
setTitle("华容道");
// 设置窗体置顶
setAlwaysOnTop(true);
// 设置窗体居中
setLocationRelativeTo(null);
// 取消默认布局
setLayout(null);
}

加载图片:两层循环,图片用二维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int data[][] = {
{1,2,3},
{4,5,6},
{7,8,9}
};

for(int i = 0 ; i < 3 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
JLabel jLabel = new JLabel(new ImageIcon("E:\\Desktop\\pic\\"+data[i][j]+".png"));
jLabel.setBounds(30+175*j,60+175*i,175,175);
jFrame.getContentPane().add(jLabel);
}
}

使用窗体,但是增加新功能:

继承改进
  1. 定义 MainFrame 类继承 JFrame
  2. 将代码抽取到一个单独的方法 initFrame()
  3. 将绘制界面的代码, 抽取为一个单独的方法 paintView()
  4. 将二维数组提取到成员变量的位置
  5. JFrame 类中方法的调用方式, 更换为super或省略
  6. 在构造方法中, 调用 initFrame() 和 paintView()
  7. 在构造方法的最后调用 setVisible(true);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public MyFrame() {
// 窗体对象.addKeyListener(KeyListener实现类对象);
// this : 当前类对象代表
// 1) 窗体对象
// 2) KeyListener实现类对象
this.addKeyListener(this);
// 初始化窗体
initFrame();
// 初始化数据
initData();
// 绘制游戏界面
paintView();
// 设置窗体可见
setVisible(true);
}
初始化九宫格

核心思路:遍历二维数组获取每一个元素,和其他元素随机交换

注:二维数组数据覆盖不允许,需要重新new {}

  1. 遍历二维数组, 获取到每一个元素
  2. 在遍历的过程中, 产生两个随机的索引
  3. 让当前元素, 和随机索引所指向的元素进行交换
1
2
3
4
5
6
7
8
9
10
11
12
private void initFrame() {
Random random = new Random();
for (int i = 0; i < data.length; i++) { //遍历二维数组
for (int j = 0; j < data[i].length; j++) {
int x = random.nextInt(3); //产生随机索引值,交换数据
int y = random.nextInt(3);
int temp = data[i][j];
data[i][j] = data[x][y];
data[x][y] = temp;
}
}
}
上下左右变位—键盘监听
1
2
3
4
5
6
7
8
9
10
if(keyCode == 37 ){                     //左按键
if(column == 2){
return; //在右边界时,无法移动
}
int temp = data[row][column]; //空白和右边的交换
data[row][column] = data[row][column + 1];
data[row][column + 1] = temp;
column++; //交换后,列值加1
count++; //步数+1
}

注:数据在数组中改变之后,界面需要重新刷新

  1. 每一次移动之后,调用paintView重新绘制界面

  2. paintView方法中,加载图片资源之前,需要将现有的组件移除

    1
    getContentPane().removeAll();
  3. 加载后,需要刷新界面

    1
    getContentPane().repaint();
1
2
3
4
5
//到达边界不会再移动的情况
column == 3 不允许左移动
row == 3 不允许上移动
column == 0 不允许右移动
row == 0 不允许下移动
游戏判定胜利
  1. 定义新的二维数组, 存储游戏胜利的数据
  2. 使用现有数组中的元素, 和胜利数组中元素逐个进行比对
  3. 每一次移动后, 都需要调用绘制界面的方法,在该方法中判断是否胜利

注意:胜利界面出现后,游戏将进入不可移动的状态

在移动的方法中,再次调用 victory方法,如果为胜利,直接return

1
2
3
4
5
6
7
8
9
10
private boolean victory() {
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
if (data[i][j] != victory[i][j]) {
return false; //数据不一致,还没有胜利
}
}
}
return true;
}
统计步数

添加统计变量 count
每次上下左右移动,count ++

重新游戏

绑定动作监听 ActionListenter

  1. 步数归零
  2. 重新调用initData()方法
  3. 重新调用paintView()方法
1
2
3
4
5
6
7
8
9
JButton btn = new JButton("重新开始");
btn.setBounds(350,20,100,20);
getContentPane().add(btn);
btn.setFocusable(false); //取消按钮焦点
btn.addActionListener(e -> { //初始化游戏数据
count = 0;
initData();
paintView();
});

完整代码

Run.java

1
2
3
4
5
public class Run {
public static void main(String[] args) {
new MyFrame(); //调用构造方法即可
}
}

MyFrame.java

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
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;

public class MyFrame extends JFrame implements KeyListener {
int[][] data = {
{1,2,3},
{4,5,6},
{7,8,9}
};

int[][] victory = {
{1,2,3},
{4,5,6},
{7,8,9}
};

int row; // 空白块 行
int column; // 空白块 列
int count; // 步数

public MyFrame() {
// 窗体对象.addKeyListener(KeyListener实现类对象);
// this : 当前类对象代表
// 1) 窗体对象
// 2) KeyListener实现类对象
this.addKeyListener(this);
// 初始化窗体
initFrame();
// 初始化数据
initData();
// 绘制游戏界面
paintView();
// 设置窗体可见
setVisible(true);
}

/**
* 初始化窗体
*/
public void initFrame() {
// 设置窗体大小
setSize(590,635);
// 设置窗体关闭模式
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 设置窗体标题
setTitle("华容道");
// 设置窗体置顶
setAlwaysOnTop(true);
// 设置窗体居中
setLocationRelativeTo(null);
// 取消默认布局
setLayout(null);
}


/**
* 初始化九宫格图片
*/
private void initData() {
Random random = new Random();
for (int i = 0; i < data.length; i++) { //遍历二维数组
for (int j = 0; j < data[i].length; j++) {
int x = random.nextInt(3); //产生随机索引值,交换数据
int y = random.nextInt(3);
int temp = data[i][j];
data[i][j] = data[x][y];
data[x][y] = temp;
}
}

for (int i = 0; i < data.length; i++) { //找到空白元素所在位置
for (int j = 0; j < data[i].length; j++) {
if (data[i][j] == 9) {
row = i;
column = j;
}
}
}
}

/**
* 绘制华容道界面
*/
public void paintView() {
getContentPane().removeAll(); //清空原始配置

if(victory()){ //胜利弹窗
JLabel win = new JLabel(new ImageIcon("E:\\Desktop\\pic\\win.png"));
win.setBounds(120,220,270,90);
getContentPane().add(win);
}

JButton btn = new JButton("重新开始");
btn.setBounds(350,20,100,20);
getContentPane().add(btn);
btn.setFocusable(false); //取消按钮焦点
btn.addActionListener(e -> { //初始化游戏数据
count = 0;
initData();
paintView();
});

JLabel jLabel = new JLabel("步数:" + count );
jLabel.setBounds(50,20,100,20);
getContentPane().add(jLabel);

for(int i = 0 ; i < 3 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
JLabel image = new JLabel(new ImageIcon("E:\\Desktop\\pic\\"+data[i][j]+".png"));
image.setBounds(30+175*j,60+175*i,175,175);
getContentPane().add(image);
}
}

getContentPane().repaint(); //重新安排布局
}

/**
* 判断胜利
*/
private boolean victory() {
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
if (data[i][j] != victory[i][j]) {
return false; //数据不一致,还没有胜利
}
}
}
return true;
}

/**
* 上下左右移动
*/
private void move(int keyCode){
//System.out.println(keyCode);
if(victory()){
return; //游戏胜利,停止移动
}
if(keyCode == 37 ){ //左按键
if(column == 2){
return; //在右边界时,无法移动
}
int temp = data[row][column]; //空白和右边的交换
data[row][column] = data[row][column + 1];
data[row][column + 1] = temp;
column++; //交换后,列值加1
count++; //步数+1
}else if(keyCode == 38 ){ //上按键
if(row == 2){
return; //在下边界时,无法移动
}
int temp = data[row][column]; //空白和下边的交换
data[row][column] = data[row + 1][column];
data[row + 1][column] = temp;
row++; //交换后,行值加1
count++; //步数+1
}else if(keyCode == 39 ){ //右按键
if(column == 0){
return; //在左边界时,无法移动
}
int temp = data[row][column]; //空白和左边的交换
data[row][column] = data[row][column - 1];
data[row][column - 1] = temp;
column--; //交换后,列值-1
count++; //步数+1
}else if(keyCode == 40 ){ //下按键
if(row == 0){
return; //在下边界时,无法移动
}
int temp = data[row][column]; //空白和上边的交换
data[row][column] = data[row - 1][column];
data[row - 1][column] = temp;
row--; //交换后,行值-1
count++; //步数+1
}else if (keyCode == 32) {
// 测试:如果按空格,直接胜利
data = new int[][]{
{1,2,3},
{4,5,6},
{7,8,9}
};
}

}

@Override
public void keyTyped(KeyEvent e) {
}

@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
move(keyCode);
// 每一次移动之后, 都重新绘制游戏界面
paintView();
}

@Override
public void keyReleased(KeyEvent e) {
}
}

main函数中仅有new MyFarme语句,此时调用构造方法,实现类中的各个方法,简化代码;同时,因为继承Farme类,所以调用其中的方法为super.xxx(),而子类也没有重写过该方法,所以super可以省略,直接方法名调用

窗体对象.addKeyListener(KeyListener实现类对象);
this.addKeyListener(this);
this : 当前类对象代表
1) 窗体对象
2) KeyListener实现类对象
所以可以这样用this;