• 简易gui斗地主摸鱼小游戏;启动服务端和三个客户端即可使用

服务端

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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 文件名:server.py

import socket
import json
import random
import threading
import time

Client_Number = 0 # 客户端数
FLAG = 0
FLAG1 = 0
FLAG2 = 0 # 抢地主判定符
# 牌型全局
type = 'init'
value = 0
seq_num = 0
jumpCounter = 0

POKER = [0 for i in range(54)]
for i in range(1, 55):
POKER[i - 1] = i
random.shuffle(POKER) # 洗牌
# print(POKER)
s = socket.socket() # 创建 socket 对象
#host = socket.gethostname() # 获取本地主机名
host = '0.0.0.0'
port = 12345 # 设置端口
s.bind((host, port)) # 绑定端口
s.listen(3) # 等待客户端连接


def Turner(num):
if num == 0:
return 1
if num == 1:
return 2
if num == 2:
return 0


def send_message(socket, string):
json = {
'status': 200,
'Operation': 'message',
'Card': [0],
'message': ''
}
json['message'] = string
socket.sendall(str.encode(str(json)))


def ask_select(socket, socket1, socket2):
json = {
'Status': 200,
'Operation': 'AskS'
}
socket.sendall(str.encode(str(json)))
socket1.sendall(str.encode(str(json)))
socket2.sendall(str.encode(str(json)))


def set_turn(socket, Type, value, seq_num):
json = {
'Status': 200,
'Operation': 'SetTurn',
'type': Type,
'value': value
}
print('sent:type=', Type, ' value=', value, ' seq_num=', seq_num)
socket.sendall(str.encode(str(json)))


def json_prase(js, socket=s, socket1=s, socket2=s):
recjs = eval(js)
global type
global value
global seq_num
global jumpCounter

if recjs['Operation'] == 'AnsS':
return recjs['message']
elif recjs['Operation'] == 'AnsTurn':
print(recjs)
# 这里会收到回牌
json = {
'Status': 200,
'Operation': 'Announce',
'message': recjs['message']
}
socket.sendall(str.encode(str(json)))
socket1.sendall(str.encode(str(json)))
socket2.sendall(str.encode(str(json)))
if recjs['type'] == 'Jump':

if jumpCounter == 1:
type = 'init'
value = 0
seq_num = 0
jumpCounter = 0
else:
jumpCounter += 1
else:
jumpCounter = 0
type = recjs['type']
value = recjs['value']
seq_num = recjs['seq_num']
print('JC=', jumpCounter)
time.sleep(0.5)

elif recjs['Operation'] == 'Clear':
send_message(socket, 'Game Over!')
send_message(socket1, 'Game Over!')
send_message(socket2, 'Game Over!')
socket.close()
socket1.close()
socket2.close()
else:
print('Unknown json')
return -1


def send_card(socket):
ADD = [0 for i in range(3)]
ADD[0] = POKER[53]
ADD[1] = POKER[52]
ADD[2] = POKER[51]
json = {
'status': 200,
'Operation': 'Add',
'message': ''
}
json['message'] = ADD
print(json)
socket.sendall(str.encode(str(json)))


def init_card(socket, socket1, socket2):
SET = [0 for i in range(20)]
SET1 = [0 for i in range(20)]
SET2 = [0 for i in range(20)]

for i in range(0, 17):
SET[i] = POKER[i] # poker的0到16号
SET1[i] = POKER[i + 17] # poker的17到34
SET2[i] = POKER[i + 34] # poker的34到51
print('SET:')
print(sorted(SET))
print('SET1')
print(sorted(SET1))
print('SET2')
print(sorted(SET2))
json = {
'status': 200,
'Operation': 'init',
'Card': [0],
'message': SET
}
json1 = {
'status': 200,
'Operation': 'init',
'Card': [0],
'message': SET1
}
json2 = {
'status': 200,
'Operation': 'init',
'Card': [0],
'message': SET2
}

socket.sendall(str.encode(str(json)))
socket1.sendall(str.encode(str(json1)))
socket2.sendall(str.encode(str(json2)))


# def receive_card(socket,socket1,socket2):
# START REGISTERRING
while True:
if Client_Number == 9: # 暂存

c, addr = s.accept() # 建立客户端连接。 c是本连接的socket
Client_Number = Client_Number + 1
print('Connected by', addr) # 输出客户端的IP地址
data = c.recv(1024) # 把接收的数据实例化

if len(data.strip()) == 0:
c.sendall(b"Done")
else:
recData = eval(data) # str 转 Dict
string = bytes.decode(data) # byte to str
print(string)
print(recData['massage'])
c.sendall(b'successfully connected')

elif Client_Number == 0:
c, addr = s.accept()
Client_Number = Client_Number + 1
print('Connected by', addr)
# c.sendall(str.encode('successfully connected from'+addr.__str__()))
send_message(c, 'hello!')


elif Client_Number == 1:
c1, addr1 = s.accept()
Client_Number = Client_Number + 1
print('Connected by', addr1)
# c1.sendall(str.encode('successfully connected from'+addr1.__str__()))
send_message(c1, 'hello!')

else:
c2, addr2 = s.accept()
Client_Number = Client_Number + 1
print('Connected By', addr2)
# c2.sendall(str.encode('successfully connected from'+addr1.__str__()))
send_message(c2, 'hello!')

if Client_Number == 3:
print('Players all connected')
time.sleep(2)
send_message(c, 'Players all connected')
send_message(c1, 'Players all connected')
send_message(c2, 'Players all connected')
break
# START PLAYING
time.sleep(2)
init_card(c, c1, c2) # 发牌
# START APPLICATING
TURN = 0
while True:
ask_select(c, c1, c2) # 要求客户端回复抢地主结果
Client_Number = 0 ##收到回应数
time.sleep(1)
# Waiting for Client 0
receive = c.recv(1024)
if len(receive.strip()) == 0:
continue
else:
FLAG = json_prase(receive)
Client_Number = Client_Number + 1
# Waiting for Client 1
time.sleep(1) # 等待buffer
receive1 = c1.recv(1024)
if len(receive1.strip()) == 0:
continue
else:
FLAG1 = json_prase(receive1)
Client_Number = Client_Number + 1
# Waiting for Client 2
time.sleep(1)
receive2 = c2.recv(1024)
if len(receive.strip()) == 0:
continue
else:
FLAG2 = json_prase(receive2)
Client_Number = Client_Number + 1
if Client_Number == 3:
# print('FLAG=',FLAG)
# print('FLAG1=',FLAG1)
# print('FLAG2=',FLAG2)
if FLAG + FLAG1 + FLAG2 == 0:
continue
elif FLAG + FLAG1 + FLAG2 != 1:
continue # 这里之后要有个加倍积分的函数
else:
if FLAG == 1:
send_message(c, 'You are the king!')
send_message(c1, 'Player0 is the king!')
send_message(c2, 'Player0 is the king!')
time.sleep(2)
send_card(c)
if FLAG1 == 1:
send_message(c1, 'You are the king!')
send_message(c, 'Player1 is the king!')
send_message(c2, 'Player1 is the king!')
time.sleep(2)
send_card(c1)
TURN = 1
if FLAG2 == 1:
send_message(c2, 'You are the king!')
send_message(c, 'Player2 is the king!')
send_message(c1, 'Player2 is the king!')
time.sleep(2)
send_card(c2)
TURN = 2
break
# GAME START!
while True:
time.sleep(2)
if TURN == 0:
set_turn(c, type, value, seq_num)
while True:
time.sleep(0.1)
receive = c.recv(1024)
if len(receive.strip()) == 0:
continue
else:
json_prase(receive, c, c1, c2)
TURN = Turner(TURN)
break
elif TURN == 1:
set_turn(c1, type, value, seq_num)
while True:
time.sleep(0.1)
receive = c1.recv(1024)
if len(receive.strip()) == 0:
continue
else:
json_prase(receive, c, c1, c2)
TURN = Turner(TURN)
break
elif TURN == 2:
set_turn(c2, type, value, seq_num)
while True:
time.sleep(0.1)
receive = c2.recv(1024)
if len(receive.strip()) == 0:
continue
else:
json_prase(receive, c, c1, c2)
TURN = Turner(TURN)
break
print('Shutting down server...')
c.close()
c1.close()
c2.close()

客户端

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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
#!/usr/bin/python
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import ttk, messagebox
import socket
import json
import threading
import time


class DouDiZhuGUI:
def __init__(self, root):
self.root = root
self.root.title("斗地主游戏")
self.root.geometry("800x600")

# 游戏状态变量
self.CARD = [0 for i in range(20)]
self.Card_num = 17
self.CURRENT = []
self.selected_cards = [] # 当前选中的牌

# 当前牌型要求
self.required_type = 'init'
self.required_value = 0
self.required_count = 0

# 玩家信息
self.player_id = 0 # 默认玩家ID
self.current_player = 0 # 当前出牌玩家
self.landlord = -1 # 地主玩家ID

# 游戏状态
self.game_over = False

# Socket连接
self.s = None
self.connected = False

# 扑克牌映射
self.A = ['红桃', '黑桃', '方片', '梅花']
self.B = ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2']
self.POKERS = []
n = 1
for i in self.A:
for j in self.B:
self.POKERS.append(((i + j + '(' + str(n) + ')')))
n += 1
self.POKERS.append('小王(53)')
self.POKERS.append('大王(54)')

# 创建UI界面
self.create_widgets()

# 启动接收消息的线程
self.receive_thread = None

def map_card(self, Cno):
"""将牌编号映射为牌面"""
if Cno == 0:
return None
else:
return self.POKERS[Cno - 1]

def get_card_color(self, card_num):
"""获取牌的颜色"""
if card_num >= 1 and card_num <= 13: # 红桃
return "red"
elif card_num >= 14 and card_num <= 26: # 黑桃
return "black"
elif card_num >= 27 and card_num <= 39: # 方片
return "red"
elif card_num >= 40 and card_num <= 52: # 梅花
return "black"
else: # 王
return "gold"

def get_card_display_info(self, card_num):
"""获取牌的显示信息(文本和颜色)"""
card_text = self.map_card(card_num)
card_color = self.get_card_color(card_num)
return card_text, card_color

def sort_cards_for_display(self, cards):
"""按牌面大小排序,相同大小的牌放在一起"""
if not cards:
return []

# 按照牌面值分组
card_groups = {}
for card in cards:
card_value = self.get_card_value(card)
if card_value not in card_groups:
card_groups[card_value] = []
card_groups[card_value].append(card)

# 对组进行排序(按牌值大小)
sorted_groups = sorted(card_groups.items(), key=lambda x: x[0])

# 合并排序后的牌
sorted_cards = []
for value, group in sorted_groups:
# 组内按花色排序
group.sort()
sorted_cards.extend(group)

return sorted_cards

def get_card_value(self, card_num):
"""获取牌的数值用于比较大小"""
if card_num == 53: # 小王
return 16
elif card_num == 54: # 大王
return 17
else:
return (card_num - 1) % 13 + 3 # 3到17的值,其中3最小,2最大(15)

def parse_cards_type(self, cards):
"""分析牌型"""
if not cards:
return None, 0, 0

sorted_cards = sorted(cards)
card_values = [self.get_card_value(card) for card in sorted_cards]

# 特殊牌型:王炸
if sorted_cards == [53, 54]:
return 'DualKing', 17, 2 # 王炸是最大的牌型

# 获取牌的点数分布
value_counts = {}
for value in card_values:
value_counts[value] = value_counts.get(value, 0) + 1

unique_values = sorted(value_counts.keys())
counts = sorted(value_counts.values(), reverse=True)
card_count = len(cards)

# 分析牌型
if card_count == 1:
# 单张
return 'Single', card_values[0], 1
elif card_count == 2:
if len(value_counts) == 1:
# 对子
return 'Dual', card_values[0], 2
elif card_count == 3:
if len(value_counts) == 1:
# 三张
return 'Tri', card_values[0], 3
elif card_count == 4:
if len(value_counts) == 1:
# 炸弹
return 'Quad', card_values[0], 4
elif len(value_counts) == 2 and 3 in counts:
# 三带一
tri_value = [v for v, c in value_counts.items() if c == 3][0]
return '3+1', tri_value, 4
elif card_count == 5:
if len(value_counts) == 2 and 3 in counts:
# 三带二
tri_value = [v for v, c in value_counts.items() if c == 3][0]
return '3+2', tri_value, 5
elif len(value_counts) == 5:
# 顺子(5张)
if unique_values[-1] - unique_values[0] == 4 and len(unique_values) == 5:
return 'Sequ', unique_values[0], 5
elif card_count >= 5 and len(value_counts) == card_count:
# 检查是否为顺子(所有牌点数连续,且无重复)
if unique_values[-1] - unique_values[0] == card_count - 1:
# 还需要检查不能包含2(除非是最后一张)
if 15 not in unique_values or unique_values[-1] == 15:
return 'Sequ', unique_values[0], card_count
elif card_count % 2 == 0 and card_count >= 6:
# 双顺子检查
if self._is_double_sequence(value_counts):
return 'doubleSequ', unique_values[0], card_count
elif card_count % 3 == 0 and card_count >= 6:
# 三顺子检查
if self._is_triple_sequence(value_counts):
return 'triSequ', unique_values[0], card_count

# 其他牌型
return None, 0, 0

def _is_double_sequence(self, value_counts):
"""检查是否为双顺子"""
# 每个点数都必须出现2次,且点数连续
values = sorted(value_counts.keys())
counts = [value_counts[v] for v in values]

# 检查每个点数是否都出现2次
if not all(c == 2 for c in counts):
return False

# 检查点数是否连续(不能包含2)
if 15 in values:
return False

# 检查是否连续
for i in range(1, len(values)):
if values[i] - values[i - 1] != 1:
return False

return True

def _is_triple_sequence(self, value_counts):
"""检查是否为三顺子"""
# 每个点数都必须出现3次,且点数连续
values = sorted(value_counts.keys())
counts = [value_counts[v] for v in values]

# 检查每个点数是否都出现3次
if not all(c == 3 for c in counts):
return False

# 检查点数是否连续(不能包含2)
if 15 in values:
return False

# 检查是否连续
for i in range(1, len(values)):
if values[i] - values[i - 1] != 1:
return False

return True

def is_valid_play(self, selected_cards, required_type, required_value, required_count):
"""验证出牌是否符合规则"""
if not selected_cards:
return False, "没有选择牌"

# 分析当前出牌的牌型
card_type, card_value, card_count = self.parse_cards_type(selected_cards)

if card_type is None:
return False, "无效的牌型"

# 如果是跳过,直接返回True
if card_type == 'Jump':
return True, ""

# 如果是初始出牌(required_type为'init'),则不需要匹配牌型
if required_type == 'init':
return True, ""

# 检查牌数是否匹配
if required_count > 0 and card_count != required_count and card_type not in ['Quad', 'DualKing']:
return False, f"牌数不匹配,需要{required_count}张"

# 特殊牌型可以直接压制任何非特殊牌型
if card_type == 'Quad': # 炸弹可以压制非炸弹
return True, ""
elif card_type == 'DualKing': # 王炸是最大的
return True, ""

# 如果上家是特殊牌型,当前牌必须也是特殊牌型且能压制
if required_type == 'Quad' and card_type != 'Quad' and card_type != 'DualKing':
return False, "只能用炸弹或王炸压制炸弹"
elif required_type == 'DualKing':
return False, "王炸无法被压制"

# 检查牌型是否匹配
if card_type != required_type:
return False, f"牌型不匹配,需要{required_type}"

# 检查牌力是否足够大
if required_value > 0 and card_value <= required_value:
return False, f"牌力不够大,需要大于{required_value}"

return True, ""

def create_widgets(self):
"""创建GUI界面"""
# 顶部信息栏
self.info_frame = ttk.Frame(self.root)
self.info_frame.pack(fill=tk.X, padx=10, pady=5)

self.info_label = ttk.Label(self.info_frame, text="欢迎来到斗地主游戏!")
self.info_label.pack(side=tk.LEFT)

# 玩家信息标签
self.player_info_label = ttk.Label(self.info_frame, text="玩家ID: 0")
self.player_info_label.pack(side=tk.LEFT, padx=10)

# 当前玩家标签
self.current_player_label = ttk.Label(self.info_frame, text="当前玩家: 0")
self.current_player_label.pack(side=tk.LEFT, padx=10)

# 地主标识标签
self.landlord_label = ttk.Label(self.info_frame, text="地主: 未确定")
self.landlord_label.pack(side=tk.LEFT, padx=10)

# 连接按钮
self.connect_button = ttk.Button(self.info_frame, text="连接服务器", command=self.connect_to_server)
self.connect_button.pack(side=tk.RIGHT)

# 中间游戏区域
self.game_frame = ttk.Frame(self.root)
self.game_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

# 手牌显示区域
self.hand_frame = ttk.LabelFrame(self.game_frame, text="我的手牌")
self.hand_frame.pack(fill=tk.BOTH, expand=True, pady=5)

# 手牌画布和滚动条
self.hand_canvas = tk.Canvas(self.hand_frame, height=150)
self.hand_scrollbar = ttk.Scrollbar(self.hand_frame, orient="horizontal", command=self.hand_canvas.xview)
self.hand_scrollable_frame = ttk.Frame(self.hand_canvas)

self.hand_scrollable_frame.bind(
"<Configure>",
lambda e: self.hand_canvas.configure(
scrollregion=self.hand_canvas.bbox("all")
)
)

self.hand_canvas.create_window((0, 0), window=self.hand_scrollable_frame, anchor="nw")
self.hand_canvas.configure(xscrollcommand=self.hand_scrollbar.set)

self.hand_canvas.pack(side="top", fill="both", expand=True)
self.hand_scrollbar.pack(side="bottom", fill="x")

# 出牌信息区域
self.play_info_frame = ttk.Frame(self.game_frame)
self.play_info_frame.pack(fill=tk.X, pady=5)

self.current_type_label = ttk.Label(self.play_info_frame, text="当前牌型: 无")
self.current_type_label.pack(side=tk.LEFT)

self.current_cards_label = ttk.Label(self.play_info_frame, text="当前出牌: 无")
self.current_cards_label.pack(side=tk.RIGHT)

# 操作按钮区域
self.button_frame = ttk.Frame(self.root)
self.button_frame.pack(fill=tk.X, padx=10, pady=5)

self.play_button = ttk.Button(self.button_frame, text="出牌", command=self.play_cards, state=tk.DISABLED)
self.play_button.pack(side=tk.LEFT, padx=5)

self.pass_button = ttk.Button(self.button_frame, text="不出", command=self.pass_turn, state=tk.DISABLED)
self.pass_button.pack(side=tk.LEFT, padx=5)

self.show_hand_button = ttk.Button(self.button_frame, text="刷新手牌", command=self.show_hand)
self.show_hand_button.pack(side=tk.LEFT, padx=5)

# 消息显示区域
self.message_frame = ttk.LabelFrame(self.root, text="游戏消息")
self.message_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

self.message_text = tk.Text(self.message_frame, height=8)
self.message_scrollbar = ttk.Scrollbar(self.message_frame, orient="vertical", command=self.message_text.yview)
self.message_text.configure(yscrollcommand=self.message_scrollbar.set)

self.message_text.pack(side="left", fill="both", expand=True)
self.message_scrollbar.pack(side="right", fill="y")

# 底部状态栏
self.status_frame = ttk.Frame(self.root)
self.status_frame.pack(fill=tk.X, padx=10, pady=5)

self.status_label = ttk.Label(self.status_frame, text="未连接")
self.status_label.pack()

def connect_to_server(self):
"""连接到游戏服务器"""
try:
if not self.connected:
self.s = socket.socket()
# host = '1.1.1.1'
host = socket.gethostname()
port = 12345
self.s.connect((host, port))
self.connected = True
self.connect_button.config(text="断开连接")
self.add_message("成功连接到服务器")
self.status_label.config(text="已连接")

# 启动接收消息线程
self.receive_thread = threading.Thread(target=self.receive_messages, daemon=True)
self.receive_thread.start()
else:
# 断开连接
self.connected = False
if self.s:
self.s.close()
self.connect_button.config(text="连接服务器")
self.add_message("已断开服务器连接")
self.status_label.config(text="未连接")
except Exception as e:
messagebox.showerror("连接错误", f"无法连接到服务器: {str(e)}")

def receive_messages(self):
"""接收服务器消息的线程函数"""
while self.connected:
try:
receive = self.s.recv(1024)
if len(receive.strip()) == 0:
continue
else:
self.root.after(0, self.json_parse, receive)
except Exception as e:
if self.connected:
self.root.after(0, self.handle_receive_error, str(e))
break

def handle_receive_error(self, error):
"""处理接收消息错误"""
self.add_message(f"接收消息错误: {error}")
self.connected = False
self.connect_button.config(text="连接服务器")
self.status_label.config(text="连接已断开")

def json_parse(self, js):
"""解析服务器消息"""
try:
recjs = eval(js)
if recjs['Operation'] == 'message':
self.add_message(recjs['message'])
elif recjs['Operation'] == 'init':
self.CARD = recjs['message']
self.show_hand()
self.add_message("游戏开始,手牌已发放")
elif recjs['Operation'] == 'AskS':
self.ask_king()
elif recjs['Operation'] == 'Add':
self.Card_num = 20
EX_CARD = [0 for x in range(3)]
for i in range(0, 3):
EX_CARD[i] = recjs['message'][i]
# 将底牌添加到手牌中
self.CARD.append(recjs['message'][i])
self.add_message(f"你获得了底牌: {', '.join(filter(None, (list(map(self.map_card, EX_CARD)))))}")
self.show_hand()
# 更新地主标识
self.landlord = self.player_id
self.landlord_label.config(text=f"地主: {self.landlord}")
if self.landlord == self.player_id:
self.info_label.config(text="你是地主!")
elif recjs['Operation'] == 'SetTurn':
# 保存当前牌型要求
self.required_type = recjs.get('type', 'init')
self.required_value = recjs.get('value', 0)
self.required_count = recjs.get('seq_num', 0)

# 更新当前玩家信息
self.current_player = recjs.get('player', 0) # 假设服务器会发送player字段
self.current_player_label.config(text=f"当前玩家: {self.current_player}")

# 启用出牌按钮
self.play_button.config(state=tk.NORMAL)
self.pass_button.config(state=tk.NORMAL)
self.add_message("轮到你出牌了")
if self.required_type != 'init':
self.current_type_label.config(
text=f"当前牌型: {self.required_type} (需要大于{self.required_value})")
else:
self.current_type_label.config(text="当前牌型: 无限制")
elif recjs['Operation'] == 'Announce':
if recjs['message'] == []:
self.add_message('上家选择跳过')
else:
played_cards = list(filter(None, (list(map(self.map_card, sorted(recjs['message']))))))
self.add_message(f"上家打出了: {', '.join(played_cards)}")
elif recjs['Operation'] == 'GameOver':
winner = recjs.get('winner', '未知')
role = recjs.get('role', '未知')
if winner == self.player_id:
messagebox.showinfo("游戏结束", f"恭喜你赢了!你的角色是{role}")
else:
messagebox.showinfo("游戏结束", f"游戏结束!获胜方是玩家{winner},角色是{role}")
self.game_over = True
self.play_button.config(state=tk.DISABLED)
self.pass_button.config(state=tk.DISABLED)
self.add_message(f"游戏结束!获胜方是玩家{winner},角色是{role}")
else:
self.add_message('未知消息类型')
except Exception as e:
self.add_message(f"消息解析错误: {str(e)}")

def show_hand(self):
"""显示手牌"""
# 清空当前手牌显示
for widget in self.hand_scrollable_frame.winfo_children():
widget.destroy()

# 显示手牌
self.selected_cards = [] # 清空选中状态
sorted_cards = self.sort_cards_for_display(self.CARD)
filtered_cards = list(filter(None, sorted_cards)) # 过滤掉0

for i, card_num in enumerate(filtered_cards):
card_text, card_color = self.get_card_display_info(card_num)
if card_text:
# 创建牌按钮
card_button = tk.Button(
self.hand_scrollable_frame,
text=card_text,
width=12,
height=4,
fg=card_color, # 设置文字颜色
font=('Arial', 8),
command=lambda idx=i, num=card_num: self.toggle_card_selection(idx, num, card_button)
)
card_button.pack(side=tk.LEFT, padx=2, pady=5)

def toggle_card_selection(self, idx, card_num, button):
"""切换牌的选中状态"""
if card_num in self.selected_cards:
self.selected_cards.remove(card_num)
button.config(relief=tk.RAISED, bg="lightgray")
else:
self.selected_cards.append(card_num)
button.config(relief=tk.SUNKEN, bg="yellow")

# 更新当前选中牌显示
selected_names = list(filter(None, (list(map(self.map_card, sorted(self.selected_cards))))))
self.current_cards_label.config(text=f"选中的牌: {', '.join(selected_names) if selected_names else '无'}")

def play_cards(self):
"""出牌"""
if not self.selected_cards:
messagebox.showwarning("出牌错误", "请先选择要出的牌")
return

# 验证出牌是否符合规则
is_valid, message = self.is_valid_play(
self.selected_cards,
self.required_type,
self.required_value,
self.required_count
)

if not is_valid:
messagebox.showwarning("出牌错误", message)
return

# 分析当前出牌的牌型
card_type, card_value, card_count = self.parse_cards_type(self.selected_cards)

# 构造JSON消息
json_data = {
'Status': 200,
'Operation': 'AnsTurn',
'type': card_type,
'seq_num': card_count, # 对于顺子等牌型,这个表示牌的数量
'message': self.selected_cards,
'value': card_value
}

try:
self.s.sendall(str.encode(str(json_data)))
# 从手牌中移除已出的牌
for card in self.selected_cards:
if card in self.CARD:
self.CARD.remove(card)

self.Card_num -= len(self.selected_cards)

# 检查是否出完所有牌
if self.Card_num == 0:
self.game_over = True
self.add_message("恭喜你出完所有牌!")
# 禁用按钮
self.play_button.config(state=tk.DISABLED)
self.pass_button.config(state=tk.DISABLED)

self.selected_cards = []
self.show_hand()
self.current_cards_label.config(text="选中的牌: 无")
self.play_button.config(state=tk.DISABLED)
self.pass_button.config(state=tk.DISABLED)
except Exception as e:
messagebox.showerror("出牌错误", f"出牌失败: {str(e)}")

def pass_turn(self):
"""跳过出牌"""
json_data = {
'Status': 200,
'Operation': 'AnsTurn',
'type': 'Jump',
'seq_num': 0,
'message': [],
'value': 0
}

try:
self.s.sendall(str.encode(str(json_data)))
self.selected_cards = []
self.current_cards_label.config(text="选中的牌: 无")
self.play_button.config(state=tk.DISABLED)
self.pass_button.config(state=tk.DISABLED)
except Exception as e:
messagebox.showerror("操作错误", f"跳过出牌失败: {str(e)}")

def ask_king(self):
"""询问是否抢地主"""
result = messagebox.askyesno("抢地主", "是否抢地主?")
json_data = {
'Status': 200,
'Operation': 'AnsS',
'message': 1 if result else 0
}

try:
self.s.sendall(str.encode(str(json_data)))
except Exception as e:
messagebox.showerror("操作错误", f"抢地主选择发送失败: {str(e)}")

def add_message(self, message):
"""添加消息到消息框"""
self.message_text.insert(tk.END, f"[{time.strftime('%H:%M:%S')}] {message}\n")
self.message_text.see(tk.END)


def main():
root = tk.Tk()
app = DouDiZhuGUI(root)
root.mainloop()


if __name__ == "__main__":
main()