Gofor5 commited on
Commit
a6de768
·
verified ·
1 Parent(s): 112b627

Update 2048的网页实现.py

Browse files
Files changed (1) hide show
  1. 2048的网页实现.py +292 -292
2048的网页实现.py CHANGED
@@ -1,293 +1,293 @@
1
- import gradio as gr
2
- import numpy as np
3
- import random
4
- import torch
5
- import torch.nn as nn
6
- import torch.nn.functional as F
7
- from game2048 import Game2048
8
-
9
- # 创建游戏实例
10
- game = Game2048(size=4)
11
-
12
- # 方块颜色映射(根据数字值)
13
- TILE_COLORS = {
14
- 0: "#cdc1b4", # 空白格子
15
- 2: "#eee4da", # 2
16
- 4: "#ede0c8", # 4
17
- 8: "#f2b179", # 8
18
- 16: "#f59563", # 16
19
- 32: "#f67c5f", # 32
20
- 64: "#f65e3b", # 64
21
- 128: "#edcf72", # 128
22
- 256: "#edcc61", # 256
23
- 512: "#edc850", # 512
24
- 1024: "#edc53f", # 1024
25
- 2048: "#edc22e", # 2048
26
- 4096: "#3c3a32", # 4096+
27
- }
28
-
29
- # 文本颜色映射(根据背景深浅)
30
- TEXT_COLORS = {
31
- 0: "#776e65", # 空白格子
32
- 2: "#776e65", # 2
33
- 4: "#776e65", # 4
34
- 8: "#f9f6f2", # 8+
35
- 16: "#f9f6f2", # 16+
36
- 32: "#f9f6f2", # 32+
37
- 64: "#f9f6f2", # 64+
38
- 128: "#f9f6f2", # 128+
39
- 256: "#f9f6f2", # 256+
40
- 512: "#f9f6f2", # 512+
41
- 1024: "#f9f6f2", # 1024+
42
- 2048: "#f9f6f2", # 2048+
43
- 4096: "#f9f6f2", # 4096+
44
- }
45
-
46
- # 定义DQN网络结构(与训练时相同)
47
- class DQN(nn.Module):
48
- def __init__(self, input_channels, output_size):
49
- super(DQN, self).__init__()
50
- self.input_channels = input_channels
51
-
52
- # 卷积层
53
- self.conv1 = nn.Conv2d(input_channels, 128, kernel_size=3, padding=1)
54
- self.conv2 = nn.Conv2d(128, 128, kernel_size=3, padding=1)
55
- self.conv3 = nn.Conv2d(128, 128, kernel_size=3, padding=1)
56
-
57
- # Dueling DQN架构
58
- # 价值流
59
- self.value_conv = nn.Conv2d(128, 4, kernel_size=1)
60
- self.value_fc1 = nn.Linear(4 * 4 * 4, 128)
61
- self.value_fc2 = nn.Linear(128, 1)
62
-
63
- # 优势流
64
- self.advantage_conv = nn.Conv2d(128, 16, kernel_size=1)
65
- self.advantage_fc1 = nn.Linear(16 * 4 * 4, 128)
66
- self.advantage_fc2 = nn.Linear(128, output_size)
67
-
68
- def forward(self, x):
69
- x = F.relu(self.conv1(x))
70
- x = F.relu(self.conv2(x))
71
- x = F.relu(self.conv3(x))
72
-
73
- # 价值流
74
- value = F.relu(self.value_conv(x))
75
- value = value.view(value.size(0), -1)
76
- value = F.relu(self.value_fc1(value))
77
- value = self.value_fc2(value)
78
-
79
- # 优势流
80
- advantage = F.relu(self.advantage_conv(x))
81
- advantage = advantage.view(advantage.size(0), -1)
82
- advantage = F.relu(self.advantage_fc1(advantage))
83
- advantage = self.advantage_fc2(advantage)
84
-
85
- # 合并价值流和优势流
86
- q_values = value + advantage - advantage.mean(dim=1, keepdim=True)
87
- return q_values
88
-
89
- # 加载模型
90
- def load_model(model_path):
91
- model = DQN(4, 4) # 输入通道4,输出动作4个
92
- try:
93
- checkpoint = torch.load(model_path, map_location=torch.device('cpu'))
94
- model.load_state_dict(checkpoint['policy_net_state_dict'])
95
- model.eval()
96
- print("模型加载成功")
97
- return model
98
- except Exception as e:
99
- print(f"模型加载失败: {e}")
100
- return None
101
-
102
- # 尝试加载模型
103
- model_path = "models/dqn_2048_best_tile.pth"
104
- model = load_model(model_path)
105
-
106
- def render_board(board):
107
- html = "<div style='background-color:#bbada0; padding:10px; border-radius:6px;'>"
108
- html += "<table style='border-spacing:10px; border-collapse:separate;'>"
109
-
110
- for i in range(game.size):
111
- html += "<tr>"
112
- for j in range(game.size):
113
- value = board[i][j]
114
- color = TILE_COLORS.get(value, "#3c3a32") # 默认深色
115
- text_color = TEXT_COLORS.get(value, "#f9f6f2") # 默认浅色
116
- font_size = "36px" if value < 100 else "30px" if value < 1000 else "24px"
117
-
118
- html += f"""
119
- <td style='background-color:{color};
120
- width:80px; height:80px;
121
- border-radius:4px;
122
- text-align:center;
123
- font-weight:bold;
124
- font-size:{font_size};
125
- color:{text_color};'>
126
- {value if value > 0 else ''}
127
- </td>
128
- """
129
- html += "</tr>"
130
-
131
- html += "</table></div>"
132
- return html
133
-
134
- def make_move(direction):
135
- """执行移动操作并更新界面"""
136
- direction_names = ["上", "右", "下", "左"]
137
-
138
- # 执行移动
139
- new_board, game_over = game.move(direction)
140
-
141
- # 渲染棋盘
142
- board_html = render_board(new_board)
143
-
144
- # 更新状态信息
145
- status = f"<b>移动方向:</b> {direction_names[direction]}"
146
- status += f"<br><b>当前分数:</b> {game.score}"
147
- status += f"<br><b>最大方块:</b> {np.max(game.board)}"
148
-
149
- if game.game_over:
150
- status += "<br><br><div style='color:#ff0000; font-weight:bold;'>游戏结束!</div>"
151
- status += f"<br><b>最终分数:</b> {game.score}"
152
-
153
- return board_html, status
154
-
155
- def reset_game():
156
- """重置游戏"""
157
- global game
158
- game = Game2048(size=4)
159
- board = game.reset()
160
-
161
- # 渲染棋盘
162
- board_html = render_board(board)
163
-
164
- # 初始状态信息
165
- status = "<b>游戏已重置!</b>"
166
- status += f"<br><b>当前分数:</b> {game.score}"
167
- status += f"<br><b>最大方块:</b> {np.max(game.board)}"
168
-
169
- return board_html, status
170
-
171
- def ai_move():
172
- """使用AI模型进行一步移动"""
173
- if model is None:
174
- return render_board(game.board), "<b>错误:</b> 未加载AI模型"
175
-
176
- # 获取当前状态
177
- state = game.get_state()
178
-
179
- # 获取有效移动
180
- valid_moves = game.get_valid_moves()
181
- if not valid_moves:
182
- return render_board(game.board), "<b>游戏结束!</b> 没有有效移动"
183
-
184
- # 转换状态为模型输入
185
- state_tensor = torch.tensor(state, dtype=torch.float).unsqueeze(0)
186
-
187
- # 模型预测
188
- with torch.no_grad():
189
- q_values = model(state_tensor).numpy().flatten()
190
-
191
- # 只考虑有效动作
192
- valid_q_values = np.full(4, -np.inf)
193
- for move in valid_moves:
194
- valid_q_values[move] = q_values[move]
195
-
196
- # 选择最佳动作
197
- action = np.argmax(valid_q_values)
198
-
199
- # 执行移动
200
- direction_names = ["上", "右", "下", "左"]
201
- new_board, game_over = game.move(action)
202
-
203
- # 渲染棋盘
204
- board_html = render_board(new_board)
205
-
206
- # 更新状态信息
207
- status = f"<b>AI移动方向:</b> {direction_names[action]}"
208
- status += f"<br><b>当前分数:</b> {game.score}"
209
- status += f"<br><b>最大方块:</b> {np.max(game.board)}"
210
-
211
- if game.game_over:
212
- status += "<br><br><div style='color:#ff0000; font-weight:bold;'>游戏结束!</div>"
213
- status += f"<br><b>最终分数:</b> {game.score}"
214
-
215
- return board_html, status
216
-
217
- # 创建Gradio界面
218
- with gr.Blocks(title="2048游戏", theme="soft") as demo:
219
- gr.Markdown("# 🎮 2048游戏")
220
- gr.Markdown("使用方向键或下方的按钮移动方块,相同数字的方块相撞时会合并!")
221
- with gr.Row():
222
- with gr.Column(scale=2):
223
- board_html = gr.HTML(render_board(game.board))
224
- with gr.Row(visible=False):
225
- status_display = gr.HTML("<b>当前分数:</b> 0<br><b>最大方块:</b> 2")
226
- with gr.Column():
227
- gr.Markdown("## 手动操作")
228
- with gr.Row():
229
- gr.Button("上 ↑", elem_id="up-btn").click(
230
- fn=lambda: make_move(0),
231
- outputs=[board_html, status_display]
232
- )
233
- gr.Button("左 ←", elem_id="left-btn").click(
234
- fn=lambda: make_move(3),
235
- outputs=[board_html, status_display]
236
- )
237
- with gr.Row():
238
- gr.Button("下 ↓", elem_id="down-btn").click(
239
- fn=lambda: make_move(2),
240
- outputs=[board_html, status_display]
241
- )
242
- gr.Button("右 →", elem_id="right-btn").click(
243
- fn=lambda: make_move(1),
244
- outputs=[board_html, status_display]
245
- )
246
- with gr.Row():
247
- gr.Button("🔄 重置游戏", elem_id="reset-btn").click(
248
- fn=reset_game,
249
- outputs=[board_html, status_display]
250
- )
251
- with gr.Row():
252
- status_display = gr.HTML("<b>当前分数:</b> 0<br><b>最大方块:</b> 2")
253
- with gr.Column():
254
- gr.Markdown("## AI操作")
255
- gr.Button("🤖 AI移动一步", elem_id="ai-btn").click(
256
- fn=ai_move,
257
- outputs=[board_html, status_display]
258
- )
259
- gr.Markdown("基于DQN神经网络提供支持")
260
-
261
- # 添加键盘快捷键支持
262
- demo.load(
263
- fn=None,
264
- inputs=None,
265
- outputs=None,
266
- js="""() => {
267
- document.addEventListener('keydown', function(e) {
268
- if (e.key === 'ArrowUp') {
269
- document.getElementById('up-btn').click();
270
- } else if (e.key === 'ArrowRight') {
271
- document.getElementById('right-btn').click();
272
- } else if (e.key === 'ArrowDown') {
273
- document.getElementById('down-btn').click();
274
- } else if (e.key === 'ArrowLeft') {
275
- document.getElementById('left-btn').click();
276
- } else if (e.key === 'r' || e.key === 'R') {
277
- document.getElementById('reset-btn').click();
278
- } else if (e.key === 'a' || e.key === 'A') {
279
- document.getElementById('ai-btn').click();
280
- }
281
- });
282
- }"""
283
- )
284
- gr.Markdown("### 📚 使用说明")
285
- gr.Markdown("1. 使用方向键或下方的按钮移动方块。")
286
- gr.Markdown("2. 相同数字的方块相撞时会合并。")
287
- gr.Markdown("3. 快捷键说明:上/下/左/右键移动方块,R键重置游戏,A键AI移动一步。")
288
- gr.Markdown("4. 点击 '🤖 AI移动一步' 按钮可以使用AI模型进行一步移动。")
289
- gr.Markdown("5. 游戏结束后,会显示最终分数和最大方块。")
290
-
291
- # 启动界面
292
- if __name__ == "__main__":
293
  demo.launch(share=True)
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import random
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ from game2048 import Game2048
8
+
9
+ # 创建游戏实例
10
+ game = Game2048(size=4)
11
+
12
+ # 方块颜色映射(根据数字值)
13
+ TILE_COLORS = {
14
+ 0: "#cdc1b4", # 空白格子
15
+ 2: "#eee4da", # 2
16
+ 4: "#ede0c8", # 4
17
+ 8: "#f2b179", # 8
18
+ 16: "#f59563", # 16
19
+ 32: "#f67c5f", # 32
20
+ 64: "#f65e3b", # 64
21
+ 128: "#edcf72", # 128
22
+ 256: "#edcc61", # 256
23
+ 512: "#edc850", # 512
24
+ 1024: "#edc53f", # 1024
25
+ 2048: "#edc22e", # 2048
26
+ 4096: "#3c3a32", # 4096+
27
+ }
28
+
29
+ # 文本颜色映射(根据背景深浅)
30
+ TEXT_COLORS = {
31
+ 0: "#776e65", # 空白格子
32
+ 2: "#776e65", # 2
33
+ 4: "#776e65", # 4
34
+ 8: "#f9f6f2", # 8+
35
+ 16: "#f9f6f2", # 16+
36
+ 32: "#f9f6f2", # 32+
37
+ 64: "#f9f6f2", # 64+
38
+ 128: "#f9f6f2", # 128+
39
+ 256: "#f9f6f2", # 256+
40
+ 512: "#f9f6f2", # 512+
41
+ 1024: "#f9f6f2", # 1024+
42
+ 2048: "#f9f6f2", # 2048+
43
+ 4096: "#f9f6f2", # 4096+
44
+ }
45
+
46
+ # 定义DQN网络结构(与训练时相同)
47
+ class DQN(nn.Module):
48
+ def __init__(self, input_channels, output_size):
49
+ super(DQN, self).__init__()
50
+ self.input_channels = input_channels
51
+
52
+ # 卷积层
53
+ self.conv1 = nn.Conv2d(input_channels, 128, kernel_size=3, padding=1)
54
+ self.conv2 = nn.Conv2d(128, 128, kernel_size=3, padding=1)
55
+ self.conv3 = nn.Conv2d(128, 128, kernel_size=3, padding=1)
56
+
57
+ # Dueling DQN架构
58
+ # 价值流
59
+ self.value_conv = nn.Conv2d(128, 4, kernel_size=1)
60
+ self.value_fc1 = nn.Linear(4 * 4 * 4, 128)
61
+ self.value_fc2 = nn.Linear(128, 1)
62
+
63
+ # 优势流
64
+ self.advantage_conv = nn.Conv2d(128, 16, kernel_size=1)
65
+ self.advantage_fc1 = nn.Linear(16 * 4 * 4, 128)
66
+ self.advantage_fc2 = nn.Linear(128, output_size)
67
+
68
+ def forward(self, x):
69
+ x = F.relu(self.conv1(x))
70
+ x = F.relu(self.conv2(x))
71
+ x = F.relu(self.conv3(x))
72
+
73
+ # 价值流
74
+ value = F.relu(self.value_conv(x))
75
+ value = value.view(value.size(0), -1)
76
+ value = F.relu(self.value_fc1(value))
77
+ value = self.value_fc2(value)
78
+
79
+ # 优势流
80
+ advantage = F.relu(self.advantage_conv(x))
81
+ advantage = advantage.view(advantage.size(0), -1)
82
+ advantage = F.relu(self.advantage_fc1(advantage))
83
+ advantage = self.advantage_fc2(advantage)
84
+
85
+ # 合并价值流和优势流
86
+ q_values = value + advantage - advantage.mean(dim=1, keepdim=True)
87
+ return q_values
88
+
89
+ # 加载模型
90
+ def load_model(model_path):
91
+ model = DQN(4, 4) # 输入通道4,输出动作4个
92
+ try:
93
+ checkpoint = torch.load(model_path, map_location=torch.device('cpu'))
94
+ model.load_state_dict(checkpoint['policy_net_state_dict'])
95
+ model.eval()
96
+ print("模型加载成功")
97
+ return model
98
+ except Exception as e:
99
+ print(f"模型加载失败: {e}")
100
+ return None
101
+
102
+ # 尝试加载模型
103
+ model_path = "dqn_2048_best_tile.pth"
104
+ model = load_model(model_path)
105
+
106
+ def render_board(board):
107
+ html = "<div style='background-color:#bbada0; padding:10px; border-radius:6px;'>"
108
+ html += "<table style='border-spacing:10px; border-collapse:separate;'>"
109
+
110
+ for i in range(game.size):
111
+ html += "<tr>"
112
+ for j in range(game.size):
113
+ value = board[i][j]
114
+ color = TILE_COLORS.get(value, "#3c3a32") # 默认深色
115
+ text_color = TEXT_COLORS.get(value, "#f9f6f2") # 默认浅色
116
+ font_size = "36px" if value < 100 else "30px" if value < 1000 else "24px"
117
+
118
+ html += f"""
119
+ <td style='background-color:{color};
120
+ width:80px; height:80px;
121
+ border-radius:4px;
122
+ text-align:center;
123
+ font-weight:bold;
124
+ font-size:{font_size};
125
+ color:{text_color};'>
126
+ {value if value > 0 else ''}
127
+ </td>
128
+ """
129
+ html += "</tr>"
130
+
131
+ html += "</table></div>"
132
+ return html
133
+
134
+ def make_move(direction):
135
+ """执行移动操作并更新界面"""
136
+ direction_names = ["上", "右", "下", "左"]
137
+
138
+ # 执行移动
139
+ new_board, game_over = game.move(direction)
140
+
141
+ # 渲染棋盘
142
+ board_html = render_board(new_board)
143
+
144
+ # 更新状态信息
145
+ status = f"<b>移动方向:</b> {direction_names[direction]}"
146
+ status += f"<br><b>当前分数:</b> {game.score}"
147
+ status += f"<br><b>最大方块:</b> {np.max(game.board)}"
148
+
149
+ if game.game_over:
150
+ status += "<br><br><div style='color:#ff0000; font-weight:bold;'>游戏结束!</div>"
151
+ status += f"<br><b>最终分数:</b> {game.score}"
152
+
153
+ return board_html, status
154
+
155
+ def reset_game():
156
+ """重置游戏"""
157
+ global game
158
+ game = Game2048(size=4)
159
+ board = game.reset()
160
+
161
+ # 渲染棋盘
162
+ board_html = render_board(board)
163
+
164
+ # 初始状态信息
165
+ status = "<b>游戏已重置!</b>"
166
+ status += f"<br><b>当前分数:</b> {game.score}"
167
+ status += f"<br><b>最大方块:</b> {np.max(game.board)}"
168
+
169
+ return board_html, status
170
+
171
+ def ai_move():
172
+ """使用AI模型进行一步移动"""
173
+ if model is None:
174
+ return render_board(game.board), "<b>错误:</b> 未加载AI模型"
175
+
176
+ # 获取当前状态
177
+ state = game.get_state()
178
+
179
+ # 获取有效移动
180
+ valid_moves = game.get_valid_moves()
181
+ if not valid_moves:
182
+ return render_board(game.board), "<b>游戏结束!</b> 没有有效移动"
183
+
184
+ # 转换状态为模型输入
185
+ state_tensor = torch.tensor(state, dtype=torch.float).unsqueeze(0)
186
+
187
+ # 模型预测
188
+ with torch.no_grad():
189
+ q_values = model(state_tensor).numpy().flatten()
190
+
191
+ # 只考虑有效动作
192
+ valid_q_values = np.full(4, -np.inf)
193
+ for move in valid_moves:
194
+ valid_q_values[move] = q_values[move]
195
+
196
+ # 选择最佳动作
197
+ action = np.argmax(valid_q_values)
198
+
199
+ # 执行移动
200
+ direction_names = ["上", "右", "下", "左"]
201
+ new_board, game_over = game.move(action)
202
+
203
+ # 渲染棋盘
204
+ board_html = render_board(new_board)
205
+
206
+ # 更新状态信息
207
+ status = f"<b>AI移动方向:</b> {direction_names[action]}"
208
+ status += f"<br><b>当前分数:</b> {game.score}"
209
+ status += f"<br><b>最大方块:</b> {np.max(game.board)}"
210
+
211
+ if game.game_over:
212
+ status += "<br><br><div style='color:#ff0000; font-weight:bold;'>游戏结束!</div>"
213
+ status += f"<br><b>最终分数:</b> {game.score}"
214
+
215
+ return board_html, status
216
+
217
+ # 创建Gradio界面
218
+ with gr.Blocks(title="2048游戏", theme="soft") as demo:
219
+ gr.Markdown("# 🎮 2048游戏")
220
+ gr.Markdown("使用方向键或下方的按钮移动方块,相同数字的方块相撞时会合并!")
221
+ with gr.Row():
222
+ with gr.Column(scale=2):
223
+ board_html = gr.HTML(render_board(game.board))
224
+ with gr.Row(visible=False):
225
+ status_display = gr.HTML("<b>当前分数:</b> 0<br><b>最大方块:</b> 2")
226
+ with gr.Column():
227
+ gr.Markdown("## 手动操作")
228
+ with gr.Row():
229
+ gr.Button("上 ↑", elem_id="up-btn").click(
230
+ fn=lambda: make_move(0),
231
+ outputs=[board_html, status_display]
232
+ )
233
+ gr.Button("左 ←", elem_id="left-btn").click(
234
+ fn=lambda: make_move(3),
235
+ outputs=[board_html, status_display]
236
+ )
237
+ with gr.Row():
238
+ gr.Button("下 ↓", elem_id="down-btn").click(
239
+ fn=lambda: make_move(2),
240
+ outputs=[board_html, status_display]
241
+ )
242
+ gr.Button("右 →", elem_id="right-btn").click(
243
+ fn=lambda: make_move(1),
244
+ outputs=[board_html, status_display]
245
+ )
246
+ with gr.Row():
247
+ gr.Button("🔄 重置游戏", elem_id="reset-btn").click(
248
+ fn=reset_game,
249
+ outputs=[board_html, status_display]
250
+ )
251
+ with gr.Row():
252
+ status_display = gr.HTML("<b>当前分数:</b> 0<br><b>最大方块:</b> 2")
253
+ with gr.Column():
254
+ gr.Markdown("## AI操作")
255
+ gr.Button("🤖 AI移动一步", elem_id="ai-btn").click(
256
+ fn=ai_move,
257
+ outputs=[board_html, status_display]
258
+ )
259
+ gr.Markdown("基于DQN神经网络提供支持")
260
+
261
+ # 添加键盘快捷键支持
262
+ demo.load(
263
+ fn=None,
264
+ inputs=None,
265
+ outputs=None,
266
+ js="""() => {
267
+ document.addEventListener('keydown', function(e) {
268
+ if (e.key === 'ArrowUp') {
269
+ document.getElementById('up-btn').click();
270
+ } else if (e.key === 'ArrowRight') {
271
+ document.getElementById('right-btn').click();
272
+ } else if (e.key === 'ArrowDown') {
273
+ document.getElementById('down-btn').click();
274
+ } else if (e.key === 'ArrowLeft') {
275
+ document.getElementById('left-btn').click();
276
+ } else if (e.key === 'r' || e.key === 'R') {
277
+ document.getElementById('reset-btn').click();
278
+ } else if (e.key === 'a' || e.key === 'A') {
279
+ document.getElementById('ai-btn').click();
280
+ }
281
+ });
282
+ }"""
283
+ )
284
+ gr.Markdown("### 📚 使用说明")
285
+ gr.Markdown("1. 使用方向键或下方的按钮移动方块。")
286
+ gr.Markdown("2. 相同数字的方块相撞时会合并。")
287
+ gr.Markdown("3. 快捷键说明:上/下/左/右键移动方块,R键重置游戏,A键AI移动一步。")
288
+ gr.Markdown("4. 点击 '🤖 AI移动一步' 按钮可以使用AI模型进行一步移动。")
289
+ gr.Markdown("5. 游戏结束后,会显示最终分数和最大方块。")
290
+
291
+ # 启动界面
292
+ if __name__ == "__main__":
293
  demo.launch(share=True)