-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtimeGUI.py
More file actions
546 lines (481 loc) · 22.3 KB
/
timeGUI.py
File metadata and controls
546 lines (481 loc) · 22.3 KB
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
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from enums import State, DateInfo
from controller import Controller
import os
class Poker(QLabel):
"""
扑克,继承自QLabel,是牌的图形显示
"""
def __init__(self, parent, number, visible=False, *args, **kwargs):
"""
:param parent: 父窗口
:param number: 牌,0~52,52表示牌的背面
:param visible: 是否可见
:param args: 传递给QLabel的位置参数
:param kwargs: 传递给QLabel的关键字参数
"""
super().__init__(parent, *args, **kwargs)
self.setPixmap(QPixmap('cards' + os.path.sep + str(number) + '.png'))
self.resize(57, 87)
self.setVisible(visible)
self.x, self.y = 0, 0
class QPlayer(object):
"""
玩家类,记录玩家手牌应当显示的位置
"""
def __init__(self, position, beginpointx, beginpointy, playpointx,
playpointy):
self.hand_pokers = [] # 手牌
self.played_poker = None # 刚出的牌
self.bpx = beginpointx # 手牌位置
self.bpy = beginpointy
self.px = playpointx # 刚出的牌的位置
self.py = playpointy
self.interval = 20 # 间隔
self.position = position
def update(self, hand_pokers, played_poker):
"""
更新手牌及打出的牌的显示
:param hand_pokers: 手牌,Poker组成的list
:param played_poker: 刚出的牌,Poker
:return: None
"""
self.clear()
self.hand_pokers = hand_pokers
self.played_poker = played_poker
self.move()
def clear(self):
"""将所有牌移除(设为不可见)"""
for poker in self.hand_pokers:
poker.setVisible(False)
self.hand_pokers.clear()
if self.played_poker:
self.played_poker.setVisible(False)
self.played_poker = None
def move_horizontal(self):
"""使手牌沿水平方向摆放"""
for i, poker in enumerate(self.hand_pokers):
poker.move(self.bpx + self.interval * i, self.bpy)
poker.setVisible(True)
def move_vertical(self):
"""使手牌沿竖直方向摆放"""
for i, poker in enumerate(self.hand_pokers):
poker.move(self.bpx, self.bpy + (self.interval + 15) * i)
poker.setVisible(True)
def move(self):
"""将牌摆放到合适的位置"""
if self.position == 1 or self.position == 3:
self.move_vertical()
else:
self.move_horizontal()
if self.played_poker:
self.played_poker.move(self.px, self.py)
self.played_poker.setVisible(True)
#######################################
class WelcomePage(QMainWindow):
close_signal = pyqtSignal()
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('时光桥牌')
# 设置窗口的图标,引用当前目录下的time.png图片
self.setWindowIcon(QIcon('time.png'))
self.setGeometry(300, 300, 600, 600)
self.btn = QToolButton(self)
self.btn.setText("开始游戏")
self.btn.resize(100, 30)
self.btn.move(250, 400)
self.rule = QToolButton(self)
self.rule.setText("规则教学")
self.rule.resize(100, 30)
self.rule.move(250,450)
self.rule_text = self.read_text()
self.rule.clicked.connect(self.rule_clicked)
self.about = QToolButton(self)
self.about.setText("关于我们")
self.about.resize(100, 30)
self.about.move(250,500)
self.about.clicked.connect(self.about_clicked)
self.show()
def read_text(self):
rule = """1.总说
桥牌是一款四人对战的公平扑克游戏,用一副牌(去掉大小王)共52张进行游戏。
游戏开始时,每名玩家分到13张牌,与对面的玩家组成队伍,共同完成目标即可获胜。
如何确定目标及如何判定每轮胜负见后文。
2.桥牌用牌
桥牌所使用的52张扑克牌,共分梅花、方块、红心、黑桃四个花色。
四种花色有高低之分,按照英文各自开头一个字母的顺序排列而成,
即梅花(Club)为C,方块(Diamond)为D,红心(Hearts)为H,黑桃(Spade)为S,
即花色大小为梅花<方块<红心<黑桃。每一种花色有十三张牌,顺序如下:
A(最大)、K、Q、J、10、9、8、7、6、5、4、3、2(最小)。
3.桥牌过程
发牌后,从庄家开始进行叫牌,顺时针轮叫,直到叫牌结束(叫牌介绍见后文)。
叫牌结束后,将确定一个数字(目标)和花色,称为定约。定约后游戏开始,
每次由一名玩家出牌,逆时针的玩家分别出牌,四张牌打出后称为一墩,判定胜负,
胜者进行下一轮出牌。十三墩牌打完后,根据之前的定约判断最终胜负。
以下是各个环节的解释。
4.叫牌
发牌之后出牌之前要进行叫牌。叫牌要用特定的符号和用语来进行。
按规定由发牌者首先叫牌(通常是北,以后轮换),根据牌点的高低,
发牌者可叫也可不叫,此后,再由他的下家(左方)叫牌,依次顺时针轮流进行。
如果四家全都不叫,这副牌就宣告作废,由下家重新发牌。
当一家开叫后,任何一家可以根据花色类别的次序在更高水平上争叫,
只要在前一家同类墩数上叫更高一个数或在更高一类(花色或无将)上叫同一墩数均可。
类别的排列如下,无将(最高)、黑桃、红心、方块、梅花(最低)
所以叫一个黑桃比叫一个红心高,叫一个梅花比叫一个无将高。直到三家不叫表示承认为止。
叫得最高的那个花色就是将牌花色(或无将),而该级别的数字就是定约的水平,两者合称定约。
在叫牌过程中,后一位叫牌者所叫的内容必须在花色或数量上超过前一位叫牌者所叫的内容。
例如北开叫1NT,东争叫2H,南持梅花套,必须应3C;西支持同伴,叫3H即可。
5.定约
所谓定约,是指经过叫牌最后由一方确定经另一方同意的一个叫牌级数协定。
确定定约的一方称定约方,其宗旨是要完成定约;同意的一方称防守方,
其目标是击垮敌方的定约。定约分有将定约和无将定约两种。有将定约是确定某一花色为将牌.
将牌除可以在本花色中赢墩外,还可以将吃其他三门花色(假如没有这个花色的话)。
无将定约就是没有将牌的定约,
其输赢只根据同一花色中的每一张牌的大小来大家假如用户没有这个花色,只好出其他花色,
这称为垫牌,不论大小,都不能赢墩。
6.出牌
一个定约(无将或有将)在叫牌时被确定之后,防守方位于庄家左手的一家称为首攻人,
也就是由他打出第一张牌。首攻人的下家在首攻实现后将自己的牌全部摊开,按同花色摆成四列,
此家称为明手。明手的对家是庄家(又称定约人、暗手),他负责打明、暗两手的牌。明手出牌后,
就轮到首攻人的同伴出牌,最后轮到定约人出牌。至此,桌上共有四张出过的牌,
每家一张,称为一墩牌。每家必须随出牌者出同花色的牌,如手中已无这用花色,
则可用将牌(任何一张将牌都大于其它花色的牌)将吃或垫掉一张闲牌。
在一墩牌里,如果有将牌,则最大的将牌是赢牌。第二轮的出牌由赢得第一墩的那家先出,
其它仍依顺时钟方向出牌,直至十三张牌全部出完。
7.胜负判定
13墩牌出完后,定约方统计本方胜利的墩数,如果达到标准,定约方两人胜利,反之,对方两人胜利。"""
return [line.strip() for line in rule.split('\n') if line]
def paintEvent(self, event):
qp = QPainter(self)
pixmap = QPixmap("welcome_page.png")
qp.drawPixmap(self.rect(), pixmap)
def rule_clicked(self):
result = ScrollMessageBox(self.rule_text, None)
result.exec_()
def about_clicked(self):
about = QMessageBox.information(self, 'About',"https://github.com/Time-bridge/Timebridge",QMessageBox.Yes ,QMessageBox.Yes)
def closeEvent(self, event):
#是否确认退出
reply = QMessageBox.question(self, 'Message', "Are you sure to quit?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
class ScrollMessageBox(QMessageBox):
def __init__(self, l, *args, **kwargs):
QMessageBox.__init__(self, *args, **kwargs)
scroll = QScrollArea(self)
scroll.setWidgetResizable(True)
self.content = QWidget()
scroll.setWidget(self.content)
lay = QVBoxLayout(self.content)
for item in l:
lay.addWidget(QLabel(item, self))
self.layout().addWidget(scroll, 0, 0, 1, self.layout().columnCount())
self.setStyleSheet("QScrollArea{min-width:610 px; min-height: 400px}")
class TimeBridgeGUI(QWidget):
def __init__(self, parent=None, controller=None):
super(TimeBridgeGUI, self).__init__(parent)
#坐标指示器
grid = QGridLayout()
x = 1
y = 0
grid.setHorizontalSpacing(1)
grid.setVerticalSpacing(1)
grid.setContentsMargins(10, 10, 600, 600)
self.cText = "x: {0}, y: {1}".format(x, y)
self.pmText = "提示信息"
#self.setMouseTracking(True)
self.cLabel = QLabel(self.cText, self)
self.pmLabel = QLabel(self.pmText, self)
grid.addWidget(self.cLabel, 0, 0, Qt.AlignTop)
grid.addWidget(self.pmLabel, 1, 0, Qt.AlignTop)
#grid_2 = QGridLayout()
#grid_2.setContentsMargins(60, 90, 60, 90)
self.text0 = "N"
self.text1 = "W"
self.text3 = "E"
self.text2 = "S"
self.label0 = QLabel(self.text0, self)
self.label1 = QLabel(self.text1, self)
self.label3 = QLabel(self.text3, self)
self.label2 = QLabel(self.text2, self)
self.label0.move(350, 120)
self.label1.move(80, 340)
self.label3.move(640, 340)
self.label2.move(350, 580)
self.setLayout(grid)
##############################################
#player
# 桥牌游戏可能使用的全部牌的图像形式,包括52张正面向上的牌与52张背面向上的牌
self._pokers = [Poker(self, i) for i in range(52)] + \
[Poker(self, 52) for _ in range(52)]
points = [(240, 612, 371.5, 520), (4, 100, 100, 306.5),
(240, 4, 371.5, 93), (739, 100, 643, 306.5)] # 玩家手牌放置位置
self.players = [QPlayer(i, *(points[i])) for i in range(4)] # 界面中玩家采用逆时针的顺序显示
###################################################
self.resize(800, 700)
self.setFixedSize(800, 700)
#self.setStyleSheet("background: black")
self.controller = Controller() if controller is None else controller
self.connect_with_controller()
def connect_with_controller(self):
"""将controller内的view_update_signal连接至两个更新函数"""
# 信号的连接
self.controller.view_update_signal.connect(self.update_players)
self.controller.view_update_signal.connect(self.update)
self.controller.output_signal.connect(self.pmLabel.setText)
def get_poker(self, card):
"""
根据数字形式的牌,找到对应的图像形式的牌
:param card: 牌, Card类型
:return:
"""
# 注意参数和返回值都可能是None
if card is None:
return None
return self._pokers[card]
def update_players(self):
"""
更新玩家手牌的显示
:return:
"""
for i, player in enumerate(self.players):
hand_cards, played_card, is_visible = self.controller.get_player_info(i)
played_poker = self.get_poker(played_card)
if is_visible:
hand_pokers = [self.get_poker(card) for card in hand_cards]
else:
hand_pokers = [self.get_poker(52 + 13 * i + j) for j in
range(len(hand_cards))]
player.update(hand_pokers, played_poker)
##################################################################################
def mousePressEvent(self, e):
# TODO: 在之后的版本中,下面的print函数可删除
print(e.x(), e.y())
if 200 <= e.x() <= 600 and 180 <= e.y() <= 520:
# 叫牌区域
x = int((e.x() - 200) / 80)
y = int((e.y() - 180) / 48)
text = "x: {0}, y: {1}".format(x, y)
self.cLabel.setText(text)
if 0 <= x < 5 and 0 <= y < 7:
# bid_result = 10 * (y + 1) + x
bid_result = y + 1, x
self.controller.send(bid_result, DateInfo.Bid)
# 手牌区
if self.players[0].bpy <= e.y() <= self.players[0].bpy + 87:
# 人类手牌区
player = self.players[0]
info = DateInfo.HumanPlay
elif self.players[2].bpy <= e.y() <= self.players[2].bpy + 87:
# AI2 手牌区
player = self.players[2]
info = DateInfo.AIPlay
else:
return
if len(player.hand_pokers) > 0 and (player.bpx <= e.x() <= player.bpx + (len(player.hand_pokers) - 1) * player.interval + 57):
# 计算选中牌的下标
clicklength = e.x() - player.bpx
if clicklength <= player.interval * len(player.hand_pokers):
card_index = clicklength // player.interval
else:
card_index = len(player.hand_pokers) - 1
# print(card_index)
self.controller.send(card_index, info)
def paintEvent(self, e):
# print('paintEvent')
qp = QPainter()
qp.begin(self)
self.draw_player_area(qp)
# print(self.controller.state)
if self.controller.state in (State.Play, State.End):
self.draw_play_area(qp)
self.draw_play_text(qp)
self.draw_play_table(qp)
# print('play draw')
# elif self.controller.state == State.BidEnd:
# #目前存在Bug
# #有时候这里的代码没被全部调用
# self.label0.deleteLater()
# self.label1.deleteLater()
# self.label3.deleteLater()
# self.label2.deleteLater()
elif self.controller.state == State.Biding:
self.draw_bid_update(qp)
self.draw_bid_area(qp)
self.draw_bid_text(qp)
# print('draw end')
qp.end()
return
def draw_player_area(self, qp):
pixmap = QPixmap("background.jpg")
qp.drawPixmap(self.rect(), pixmap)
def draw_bid_area(self, qp):
#叫牌区域
pen = QPen(Qt.black, 1, Qt.SolidLine)
qp.setPen(pen)
# 横线之间间隔48, 竖线之间间隔80
delta_x, delta_y = 80, 48
# 画横线
for i in range(0, 8):
qp.drawLine(200, 180 + i * delta_y, 600, 180 + i * delta_y)
# 画竖线
for i in range(0, 6):
qp.drawLine(200 + i * delta_x, 180, 200 + i * delta_x, 516)
def draw_bid_text(self, qp):
colorList = ['♣', '♦', '♥', '♠', 'NT']
qp.setPen(QColor(71, 53, 135))
qp.setFont(QFont('', 20))
for x in range(0, 5):
for y in range(1, 8):
text = '{0} {1}'.format(y, colorList[x])
qp.drawText(223 + 80 * x, 162 + 48 * y, text)
#左上角提示区更新
cp = self.controller.current_player_position
if cp is None:
return
# 下面的代码我注释掉了,
# 在界面显示轮到谁出牌、叫牌是由Controller决定
# controller有一个信号output_signal,被连接到pmLabel的setText函数
# pmLabel打算给Controller输出信息用,当然GUI如果有某些信息也可以用它输出,
# 但轮到谁叫牌、轮到谁出牌,还是由Controller决定
# self.pmText = '轮到{0}叫牌'.format(cp)
# self.pmLabel.setText(self.pmText)
#玩家叫牌提示信息更新
text = self.controller.get_bid_result(cp)
if cp == 0:
self.label0.setText(text)
elif cp == 1:
self.label1.setText(text)
elif cp == 2:
self.label2.setText(text)
elif cp == 3:
self.label3.setText(text)
def bid_map(self, x, y):
# 将叫牌区格位映射到坐标
return 80 * x + 200, 48 * y + 180, 80, 48
def draw_bid_update(self, qp):
if not self.controller.max_bid:
return
# xb = self.controller.max_bid % 10
# yb = self.controller.max_bid // 10 - 1
yb = self.controller.max_bid[0] - 1
if yb < 0:
return
xb = self.controller.max_bid[1]
if self.controller.win_bid_position is None:
return
#叫牌表更新
qp.setBrush(QColor(self.controller.win_bid_position * 20, 100 + self.controller.win_bid_position * 10, 230 - self.controller.win_bid_position * 15))#皮这一下就很开心
qp.drawRect(*self.bid_map(xb, yb))
qp.setBrush(QColor(200, 200, 200))#把失效区域涂灰
for y in range(0, 7):
for x in range(0, 5):
if y < yb or (y == yb and x < xb):
qp.drawRect(*self.bid_map(x, y))
else:
return
def draw_play_table(self, qp):
"""更新出牌表"""
play_table = self.controller.play_table
rank = [0, 1, 2, 3, 'win']
player_name = ['S', 'W', 'N', 'E']
color_list = ['♣', '♦', '♥', '♠']
poker_list = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q',
'K', 'A']
for y in range(1, len(play_table) + 1):
for x in range(1, 5):
card_number = play_table[y - 1][rank[x - 1]]
index = card_number // 13
number = card_number % 13
qp.drawText(50 * x + 230, 20 * y + 215,
color_list[index] + poker_list[number])
qp.drawText(480, 20 * y + 215,
player_name[play_table[y - 1][rank[4]]])
def draw_play_area(self, qp):
qp.setBrush(QColor(180, 180, 180))
qp.drawRect(371.5, 93, 57, 87)
qp.drawRect(371.5, 520, 57, 87)
qp.drawRect(100, 306.5, 57, 87)
qp.drawRect(643, 306.5, 57, 87)
qp.setBrush(QColor(130, 130, 130))
qp.drawRect(200, 180, 400, 340)
qp.setBrush(QColor(201, 200, 205))
qp.drawRect(225, 200, 350, 320)
pen = QPen(Qt.black, 1, Qt.SolidLine)
qp.setPen(pen)
qp.drawLine(200, 180, 600, 180)
qp.drawLine(200, 180, 200, 520)
qp.drawLine(600, 180, 600, 520)
qp.drawLine(200, 520, 600, 520)
for x in range(1, 16):
qp.drawLine(225, 20 * x + 200, 575, 20 * x + 200)
for x in range(1, 7):
qp.drawLine(50 * x + 225, 200, 50 * x + 225, 520)
def draw_play_text(self, qp):
player_list = ['S', 'W', 'N', 'E']
color_list = ['♣', '♦', '♥', '♠', 'NT']
if self.controller.win_bid_position is not None:
contract = '契约:{0}由{1}叫出'.format(str(self.controller.max_bid[0]) + color_list[self.controller.max_bid[1]], player_list[self.controller.win_bid_position])
qp.drawText(355, 193, contract)
text_list = ['轮次', 'S出牌', 'W出牌', 'N出牌', 'E出牌', '获胜方']
for x in range(0, 6):
qp.drawText(50 * x + 230, 215, text_list[x])
for y in range(1, 14):
qp.drawText(246.5, 20 * y + 215, str(y))
qp.drawText(535, 20 * y + 215, '回溯')
qp.drawText(232, 495, '总胜场')
qp.drawText(237, 515, '总分')
class MainWindow(QMainWindow):
close_event = pyqtSignal()
def __init__(self):
super().__init__()
self.setWindowTitle('时光桥牌')
self.setWindowIcon(QIcon('time.png'))
self.controller = Controller()
widget = TimeBridgeGUI(self, controller=self.controller)
self.setCentralWidget(widget)
# self.show()
# 菜单项
# TODO: 添加其他的菜单项
self.bar = self.menuBar()
self.item = self.bar.addMenu('选项')
self.new_game = QAction(self, text='新游戏')
self.item.addAction(self.new_game)
self.connect_with_controller()
def connect_with_controller(self):
"""
将菜单项与controller中的槽函数连接起来
:return:
"""
self.new_game.triggered.connect(self.controller.new_game)
def closeEvent(self, event):
#是否确认退出
reply = QMessageBox.question(self, 'Message', "是否确认退出?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
if __name__ == "__main__":
App = QApplication(sys.argv)
main = MainWindow()
# main.show()
ex = WelcomePage()
ex.btn.clicked.connect(main.show)
ex.btn.clicked.connect(main.controller.new_game)
ex.btn.clicked.connect(ex.hide)
ex.close_signal.connect(ex.close)
ex.show()
sys.exit(App.exec_())