Coverage for GuiHandViewer.py: 0%

238 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-14 11:07 +0000

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3 

4# Copyright 2010-2011 Maxime Grandchamp 

5# This program is free software: you can redistribute it and/or modify 

6# it under the terms of the GNU Affero General Public License as published by 

7# the Free Software Foundation, version 3 of the License. 

8# 

9# This program is distributed in the hope that it will be useful, 

10# but WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12# GNU General Public License for more details. 

13# 

14# You should have received a copy of the GNU Affero General Public License 

15# along with this program. If not, see <http://www.gnu.org/licenses/>. 

16# In the "official" distribution you can find the license in agpl-3.0.txt. 

17# 

18 

19 

20# This code once was in GuiReplayer.py and was split up in this and the former by zarturo. 

21 

22 

23# import L10n 

24# _ = L10n.get_translation() 

25 

26from functools import partial 

27 

28import Hand 

29import Card 

30import Configuration 

31import Database 

32import SQL 

33import Filters 

34import Deck 

35 

36from PyQt5.QtCore import QCoreApplication, QSortFilterProxyModel, Qt 

37from PyQt5.QtGui import QPainter, QPixmap, QStandardItem, QStandardItemModel 

38from PyQt5.QtWidgets import ( 

39 QApplication, 

40 QFrame, 

41 QMenu, 

42 QProgressDialog, 

43 QScrollArea, 

44 QSplitter, 

45 QTableView, 

46 QVBoxLayout, 

47) 

48 

49from io import StringIO 

50 

51import GuiReplayer 

52 

53 

54class GuiHandViewer(QSplitter): 

55 def __init__(self, config, querylist, mainwin): 

56 QSplitter.__init__(self, mainwin) 

57 self.config = config 

58 self.main_window = mainwin 

59 self.sql = querylist 

60 self.replayer = None 

61 

62 self.db = Database.Database(self.config, sql=self.sql) 

63 

64 filters_display = { 

65 "Heroes": True, 

66 "Sites": True, 

67 "Games": True, 

68 "Currencies": False, 

69 "Limits": True, 

70 "LimitSep": True, 

71 "LimitType": True, 

72 "Positions": True, 

73 "Type": True, 

74 "Seats": False, 

75 "SeatSep": False, 

76 "Dates": True, 

77 "Cards": False, 

78 "Groups": False, 

79 "GroupsAll": False, 

80 "Button1": True, 

81 "Button2": False, 

82 } 

83 

84 self.filters = Filters.Filters(self.db, display=filters_display) 

85 self.filters.registerButton1Name("Load Hands") 

86 self.filters.registerButton1Callback(self.loadHands) 

87 self.filters.registerCardsCallback(self.filter_cards_cb) 

88 

89 scroll = QScrollArea() 

90 scroll.setWidget(self.filters) 

91 

92 self.handsFrame = QFrame() 

93 self.handsVBox = QVBoxLayout() 

94 self.handsFrame.setLayout(self.handsVBox) 

95 

96 self.addWidget(scroll) 

97 self.addWidget(self.handsFrame) 

98 self.setStretchFactor(0, 0) 

99 self.setStretchFactor(1, 1) 

100 

101 self.deck_instance = Deck.Deck(self.config, height=42, width=30) 

102 self.cardImages = self.init_card_images() 

103 

104 # !Dict of colnames and their column idx in the model/ListStore 

105 self.colnum = { 

106 "Stakes": 0, 

107 "Players": 1, 

108 "Pos": 2, 

109 "Street0": 3, 

110 "Action0": 4, 

111 "Street1-4": 5, 

112 "Action1-4": 6, 

113 "Won": 7, 

114 "Bet": 8, 

115 "Net": 9, 

116 "Game": 10, 

117 "HandId": 11, 

118 "Total Pot": 12, 

119 "Rake": 13, 

120 "SiteHandNo": 14, 

121 } 

122 self.view = QTableView() 

123 self.view.setSelectionBehavior(QTableView.SelectRows) 

124 self.handsVBox.addWidget(self.view) 

125 self.model = QStandardItemModel(0, len(self.colnum), self.view) 

126 self.filterModel = QSortFilterProxyModel() 

127 self.filterModel.setSourceModel(self.model) 

128 self.filterModel.setSortRole(Qt.UserRole) 

129 

130 self.view.setModel(self.filterModel) 

131 self.view.verticalHeader().hide() 

132 self.model.setHorizontalHeaderLabels( 

133 [ 

134 "Stakes", 

135 "Nb Players", 

136 "Position", 

137 "Hands", 

138 "Preflop Action", 

139 "Board", 

140 "Postflop Action", 

141 "Won", 

142 "Bet", 

143 "Net", 

144 "Game", 

145 "HandId", 

146 "Total Pot", 

147 "Rake", 

148 "SiteHandId", 

149 ] 

150 ) 

151 

152 self.view.doubleClicked.connect(self.row_activated) 

153 self.view.contextMenuEvent = self.contextMenu 

154 self.filterModel.rowsInserted.connect( 

155 lambda index, start, end: [self.view.resizeRowToContents(r) for r in range(start, end + 1)] 

156 ) 

157 self.filterModel.filterAcceptsRow = lambda row, sourceParent: self.is_row_in_card_filter(row) 

158 

159 self.view.resizeColumnsToContents() 

160 self.view.setSortingEnabled(True) 

161 

162 def init_card_images(self): 

163 suits = ("s", "h", "d", "c") 

164 ranks = (14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2) 

165 

166 card_images = [0] * 53 

167 for j in range(0, 13): 

168 for i in range(0, 4): 

169 loc = Card.cardFromValueSuit(ranks[j], suits[i]) 

170 card_image = self.deck_instance.card(suits[i], ranks[j]) 

171 card_images[loc] = card_image 

172 back_image = self.deck_instance.back() 

173 card_images[0] = back_image 

174 return card_images 

175 

176 def loadHands(self, checkState): 

177 hand_ids = self.get_hand_ids_from_date_range(self.filters.getDates()[0], self.filters.getDates()[1]) 

178 # ! print(hand_ids) 

179 self.reload_hands(hand_ids) 

180 

181 def get_hand_ids_from_date_range(self, start, end): 

182 q = self.db.sql.query["handsInRangeSessionFilter"] 

183 q = q.replace("<datetest>", "between '" + start + "' and '" + end + "'") 

184 

185 # Apply filters 

186 q = self.filters.replace_placeholders_with_filter_values(q) 

187 

188 # debug 

189 # print("Requête SQL filtrée :", q) 

190 

191 c = self.db.get_cursor() 

192 c.execute(q) 

193 return [r[0] for r in c.fetchall()] 

194 

195 def rankedhand(self, hand, game): 

196 ranks = { 

197 "0": 0, 

198 "2": 2, 

199 "3": 3, 

200 "4": 4, 

201 "5": 5, 

202 "6": 6, 

203 "7": 7, 

204 "8": 8, 

205 "9": 9, 

206 "T": 10, 

207 "J": 11, 

208 "Q": 12, 

209 "K": 13, 

210 "A": 14, 

211 } 

212 suits = {"x": 0, "s": 1, "c": 2, "d": 3, "h": 4} 

213 

214 if game == "holdem": 

215 card1 = ranks[hand[0]] 

216 card2 = ranks[hand[3]] 

217 suit1 = suits[hand[1]] 

218 suit2 = suits[hand[4]] 

219 if card1 < card2: 

220 (card1, card2) = (card2, card1) 

221 (suit1, suit2) = (suit2, suit1) 

222 if suit1 == suit2: 

223 suit1 += 4 

224 return card1 * 14 * 14 + card2 * 14 + suit1 

225 else: 

226 return 0 

227 

228 def reload_hands(self, handids): 

229 self.hands = {} 

230 self.model.removeRows(0, self.model.rowCount()) 

231 if len(handids) == 0: 

232 return 

233 progress = QProgressDialog("Loading hands", "Abort", 0, len(handids), self) 

234 progress.setValue(0) 

235 progress.show() 

236 for idx, handid in enumerate(handids): 

237 if progress.wasCanceled(): 

238 break 

239 self.hands[handid] = self.importhand(handid) 

240 self.addHandRow(handid, self.hands[handid]) 

241 

242 progress.setValue(idx + 1) 

243 if idx % 10 == 0: 

244 QCoreApplication.processEvents() 

245 self.view.resizeColumnsToContents() 

246 self.view.resizeColumnsToContents() 

247 

248 def addHandRow(self, handid, hand): 

249 hero = self.filters.getHeroes()[hand.sitename] 

250 won = 0 

251 nbplayers = len(hand.players) 

252 # ! print("max seaT: ", hand.maxseats) 

253 if hero in list(hand.collectees.keys()): 

254 won = hand.collectees[hero] 

255 bet = 0 

256 if hero in list(hand.pot.committed.keys()): 

257 bet = hand.pot.committed[hero] 

258 net = won - bet 

259 pos = hand.get_player_position(hero) 

260 gt = hand.gametype["category"] 

261 row = [] 

262 totalpot = hand.totalpot 

263 rake = hand.rake 

264 sitehandid = hand.handid 

265 if hand.gametype["base"] == "hold": 

266 board = [] 

267 board.extend(hand.board["FLOP"]) 

268 board.extend(hand.board["TURN"]) 

269 board.extend(hand.board["RIVER"]) 

270 

271 pre_actions = hand.get_actions_short(hero, "PREFLOP") 

272 post_actions = "" 

273 if "F" not in pre_actions: # if player hasen't folded preflop 

274 post_actions = hand.get_actions_short_streets(hero, "FLOP", "TURN", "RIVER") 

275 

276 row = [ 

277 hand.getStakesAsString(), 

278 str(nbplayers), 

279 pos, 

280 hand.join_holecards(hero), 

281 pre_actions, 

282 " ".join(board), 

283 post_actions, 

284 str(won), 

285 str(bet), 

286 str(net), 

287 gt, 

288 str(handid), 

289 str(totalpot), 

290 str(rake), 

291 str(sitehandid), 

292 ] 

293 elif hand.gametype["base"] == "stud": 

294 third = " ".join(hand.holecards["THIRD"][hero][0]) + " " + " ".join(hand.holecards["THIRD"][hero][1]) 

295 # ugh - fix the stud join_holecards function so we can retrieve sanely 

296 later_streets = [] 

297 later_streets.extend(hand.holecards["FOURTH"][hero][0]) 

298 later_streets.extend(hand.holecards["FIFTH"][hero][0]) 

299 later_streets.extend(hand.holecards["SIXTH"][hero][0]) 

300 later_streets.extend(hand.holecards["SEVENTH"][hero][0]) 

301 

302 pre_actions = hand.get_actions_short(hero, "THIRD") 

303 post_actions = "" 

304 if "F" not in pre_actions: 

305 post_actions = hand.get_actions_short_streets(hero, "FOURTH", "FIFTH", "SIXTH", "SEVENTH") 

306 

307 row = [ 

308 hand.getStakesAsString(), 

309 str(nbplayers), 

310 pos, 

311 third, 

312 pre_actions, 

313 " ".join(later_streets), 

314 post_actions, 

315 str(won), 

316 str(bet), 

317 str(net), 

318 gt, 

319 str(handid), 

320 str(totalpot), 

321 str(rake), 

322 ] 

323 elif hand.gametype["base"] == "draw": 

324 row = [ 

325 hand.getStakesAsString(), 

326 str(nbplayers), 

327 pos, 

328 hand.join_holecards(hero, street="DEAL"), 

329 hand.get_actions_short(hero, "DEAL"), 

330 None, 

331 None, 

332 str(won), 

333 str(bet), 

334 str(net), 

335 gt, 

336 str(handid), 

337 str(totalpot), 

338 str(rake), 

339 ] 

340 

341 modelrow = [QStandardItem(r) for r in row] 

342 for index, item in enumerate(modelrow): 

343 item.setEditable(False) 

344 if index in (self.colnum["Street0"], self.colnum["Street1-4"]): 

345 cards = item.data(Qt.DisplayRole) 

346 item.setData(self.render_cards(cards), Qt.DecorationRole) 

347 item.setData("", Qt.DisplayRole) 

348 item.setData(cards, Qt.UserRole + 1) 

349 if index in (self.colnum["Bet"], self.colnum["Net"], self.colnum["Won"]): 

350 item.setData(float(item.data(Qt.DisplayRole)), Qt.UserRole) 

351 self.model.appendRow(modelrow) 

352 

353 def copyHandToClipboard(self, checkState, hand): 

354 handText = StringIO() 

355 hand.writeHand(handText) 

356 QApplication.clipboard().setText(handText.getvalue()) 

357 

358 def contextMenu(self, event): 

359 index = self.view.currentIndex() 

360 if index.row() < 0: 

361 return 

362 hand = self.hands[int(index.sibling(index.row(), self.colnum["HandId"]).data())] 

363 m = QMenu() 

364 copyAction = m.addAction("Copy to clipboard") 

365 copyAction.triggered.connect(partial(self.copyHandToClipboard, hand=hand)) 

366 m.move(event.globalPos()) 

367 m.exec_() 

368 

369 def filter_cards_cb(self, card): 

370 if hasattr(self, "hands"): 

371 self.filterModel.invalidateFilter() 

372 

373 def is_row_in_card_filter(self, rownum): 

374 """Returns true if the cards of the given row are in the card filter""" 

375 # Does work but all cards that should NOT be displayed have to be clicked. 

376 card_filter = self.filters.getCards() 

377 hcs = self.model.data(self.model.index(rownum, self.colnum["Street0"]), Qt.UserRole + 1).split(" ") 

378 

379 if "0x" in hcs: # if cards are unknown return True 

380 return True 

381 

382 gt = self.model.data(self.model.index(rownum, self.colnum["Game"])) 

383 

384 if gt not in ("holdem", "omahahi", "omahahilo"): 

385 return True 

386 

387 # Holdem: Compare the real start cards to the selected filter (ie. AhKh = AKs) 

388 value1 = Card.card_map[hcs[0][0]] 

389 value2 = Card.card_map[hcs[1][0]] 

390 idx = Card.twoStartCards(value1, hcs[0][1], value2, hcs[1][1]) 

391 abbr = Card.twoStartCardString(idx) 

392 

393 # Debug output to trace unexpected keys 

394 if abbr not in card_filter: 

395 print(f"Unexpected key in card filter: {abbr}") 

396 

397 return card_filter.get(abbr, True) # Default to True if key is not found 

398 

399 def row_activated(self, index): 

400 handlist = list(sorted(self.hands.keys())) 

401 # ! print('handlist:') 

402 # ! print(handlist) 

403 self.replayer = GuiReplayer.GuiReplayer(self.config, self.sql, self.main_window, handlist) 

404 index = handlist.index(int(index.sibling(index.row(), self.colnum["HandId"]).data())) 

405 # ! print('index:') 

406 # ! print(index) 

407 self.replayer.play_hand(index) 

408 

409 def importhand(self, handid=1): 

410 h = Hand.hand_factory(handid, self.config, self.db) 

411 

412 # Safely get the hero for this hand's sitename 

413 heroes = self.filters.getHeroes() 

414 h.hero = heroes.get(h.sitename, None) 

415 if h.hero is None: 

416 print(f"No hero found for site {h.sitename}") 

417 return h 

418 

419 def render_cards(self, cardstring): 

420 card_width = 30 

421 card_height = 42 

422 if cardstring is None or cardstring == "": 

423 cardstring = "0x" 

424 cardstring = cardstring.replace("'", "") 

425 cardstring = cardstring.replace("[", "") 

426 cardstring = cardstring.replace("]", "") 

427 cardstring = cardstring.replace("'", "") 

428 cardstring = cardstring.replace(",", "") 

429 cards = [Card.encodeCard(c) for c in cardstring.split(" ")] 

430 n_cards = len(cards) 

431 

432 pixbuf = QPixmap(card_width * n_cards, card_height) 

433 painter = QPainter(pixbuf) 

434 x = 0 # x coord where the next card starts in pixbuf 

435 for card in cards: 

436 painter.drawPixmap(x, 0, self.cardImages[card]) 

437 x += card_width 

438 return pixbuf 

439 

440 

441if __name__ == "__main__": 

442 config = Configuration.Config() 

443 

444 settings = {} 

445 

446 settings.update(config.get_db_parameters()) 

447 settings.update(config.get_import_parameters()) 

448 settings.update(config.get_default_paths()) 

449 

450 from PyQt5.QtWidgets import QMainWindow 

451 

452 app = QApplication([]) 

453 sql = SQL.Sql(db_server=settings["db-server"]) 

454 main_window = QMainWindow() 

455 i = GuiHandViewer(config, sql, main_window) 

456 main_window.setCentralWidget(i) 

457 main_window.show() 

458 main_window.resize(1400, 800) 

459 app.exec_()