Coverage for GuiTourHandViewer.py: 0%

278 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-27 18:50 +0000

1from functools import partial 

2import Hand 

3import Card 

4import Configuration 

5import Database 

6import SQL 

7import Filters 

8import Deck 

9 

10from PyQt5.QtCore import QCoreApplication, QSortFilterProxyModel, Qt 

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

12from PyQt5.QtWidgets import QApplication, QFrame, QMenu, QProgressDialog, QScrollArea, QSplitter, QTableView, QVBoxLayout 

13 

14from io import StringIO 

15import GuiReplayer 

16 

17 

18class GuiHandViewer(QSplitter): 

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

20 QSplitter.__init__(self, mainwin) 

21 self.config = config 

22 self.main_window = mainwin 

23 self.sql = querylist 

24 self.replayer = None 

25 

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

27 

28 self.setup_filters() 

29 

30 scroll = QScrollArea() 

31 scroll.setWidget(self.filters) 

32 

33 self.handsFrame = QFrame() 

34 self.handsVBox = QVBoxLayout() 

35 self.handsFrame.setLayout(self.handsVBox) 

36 

37 self.addWidget(scroll) 

38 self.addWidget(self.handsFrame) 

39 self.setStretchFactor(0, 0) 

40 self.setStretchFactor(1, 1) 

41 

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

43 self.cardImages = self.init_card_images() 

44 

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

46 self.colnum = { 

47 'Stakes': 0, 

48 'Players': 1, 

49 'Pos': 2, 

50 'Street0': 3, 

51 'Action0': 4, 

52 'Street1-4': 5, 

53 'Action1-4': 6, 

54 'Won': 7, 

55 'Bet': 8, 

56 'Net': 9, 

57 'Game': 10, 

58 'HandId': 11, 

59 'Total Pot': 12, 

60 'Rake': 13, 

61 'SiteHandNo': 14 

62 } 

63 self.view = QTableView() 

64 self.view.setSelectionBehavior(QTableView.SelectRows) 

65 self.handsVBox.addWidget(self.view) 

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

67 self.filterModel = QSortFilterProxyModel() 

68 self.filterModel.setSourceModel(self.model) 

69 self.filterModel.setSortRole(Qt.UserRole) 

70 

71 self.view.setModel(self.filterModel) 

72 self.view.verticalHeader().hide() 

73 self.model.setHorizontalHeaderLabels( 

74 ['Stakes', 'Nb Players', 'Position', 'Hands', 'Preflop Action', 'Board', 'Postflop Action', 

75 'Won', 'Bet', 'Net', 'Game', 'HandId', 'Total Pot', 'Rake', 'SiteHandId']) 

76 

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

78 self.view.contextMenuEvent = self.contextMenu 

79 self.filterModel.rowsInserted.connect( 

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

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

82 

83 self.view.resizeColumnsToContents() 

84 self.view.setSortingEnabled(True) 

85 

86 def setup_filters(self): 

87 filters_display = { 

88 "Heroes": True, 

89 "Sites": True, 

90 "Games": True, 

91 "Currencies": False, 

92 "Limits": True, 

93 "LimitSep": True, 

94 "LimitType": True, 

95 "Positions": True, 

96 "Type": True, 

97 "Seats": False, 

98 "SeatSep": False, 

99 "Dates": True, 

100 "Cards": False, 

101 "Groups": False, 

102 "GroupsAll": False, 

103 "Button1": True, 

104 "Button2": False 

105 } 

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

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

108 self.filters.registerButton1Callback(self.loadHands) 

109 self.filters.registerCardsCallback(self.filter_cards_cb) 

110 

111 # update games for default hero and site 

112 heroes = self.filters.getHeroes() 

113 sites = self.filters.getSites() 

114 

115 default_hero = next(iter(heroes.values())) if heroes else None 

116 default_site = next(iter(sites)) if sites else None 

117 

118 if default_hero and default_site: 

119 self.filters.update_games_for_hero(default_hero, default_site) 

120 

121 def init_card_images(self): 

122 suits = ('s', 'h', 'd', 'c') 

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

124 

125 card_images = [0] * 53 

126 for j in range(0, 13): 

127 for i in range(0, 4): 

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

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

130 card_images[loc] = card_image 

131 back_image = self.deck_instance.back() 

132 card_images[0] = back_image 

133 return card_images 

134 

135 def loadHands(self, checkState): 

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

137 self.reload_hands(hand_ids) 

138 

139 def get_hand_ids_from_date_range(self, start, end): 

140 q = """ 

141 SELECT DISTINCT h.id, h.startTime, tt.buyin, tt.fee, p.name, tt.siteId, tt.category 

142 FROM Hands h 

143 JOIN Tourneys t ON h.tourneyId = t.id 

144 JOIN TourneyTypes tt ON t.tourneyTypeId = tt.id 

145 JOIN TourneysPlayers tp ON t.id = tp.tourneyId 

146 JOIN Players p ON tp.playerId = p.id 

147 WHERE h.startTime BETWEEN ? AND ? 

148 """ 

149 

150 

151 hero_filter = self.filters.getHeroes() 

152 if hero_filter: 

153 hero_names = ", ".join(f"'{h}'" for h in hero_filter.values()) 

154 q += f" AND p.name IN ({hero_names})" 

155 

156 site_filter = self.filters.getSites() 

157 if site_filter: 

158 site_ids = ", ".join(str(self.filters.siteid[s]) for s in site_filter) 

159 q += f" AND tt.siteId IN ({site_ids})" 

160 

161 category_filter = self.filters.getGames() 

162 if category_filter: 

163 categories = ", ".join(f"'{c}'" for c in category_filter) 

164 q += f" AND tt.category IN ({categories})" 

165 

166 selected_buyins = self.filters.getBuyIn() 

167 if selected_buyins: 

168 buyins_str = ', '.join(map(str, selected_buyins)) 

169 q += f" AND (tt.buyin + tt.fee) IN ({buyins_str})" 

170 

171 #print(f"Buy-ins sélectionnés (incluant les frais) : {selected_buyins}") 

172 

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

174 

175 c = self.db.get_cursor() 

176 c.execute(q, (start, end)) 

177 results = c.fetchall() 

178 for row in results[:10]: # show 10 first results 

179 print(row) 

180 return [r[0] for r in results] 

181 

182 def rankedhand(self, hand, game): 

183 ranks = {'0': 0, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, 'T': 10, 'J': 11, 'Q': 12, 

184 'K': 13, 'A': 14} 

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

186 

187 if game == 'holdem': 

188 card1 = ranks[hand[0]] 

189 card2 = ranks[hand[3]] 

190 suit1 = suits[hand[1]] 

191 suit2 = suits[hand[4]] 

192 if card1 < card2: 

193 (card1, card2) = (card2, card1) 

194 (suit1, suit2) = (suit2, suit1) 

195 if suit1 == suit2: 

196 suit1 += 4 

197 return card1 * 14 * 14 + card2 * 14 + suit1 

198 else: 

199 return 0 

200 

201 def reload_hands(self, handids): 

202 self.hands = {} 

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

204 if len(handids) == 0: 

205 return 

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

207 progress.setValue(0) 

208 progress.show() 

209 for idx, handid in enumerate(handids): 

210 if progress.wasCanceled(): 

211 break 

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

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

214 

215 progress.setValue(idx + 1) 

216 if idx % 10 == 0: 

217 QCoreApplication.processEvents() 

218 self.view.resizeColumnsToContents() 

219 self.view.resizeColumnsToContents() 

220 

221 def addHandRow(self, handid, hand): 

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

223 won = 0 

224 nbplayers = len(hand.players) 

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

226 won = hand.collectees[hero] 

227 bet = 0 

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

229 bet = hand.pot.committed[hero] 

230 net = won - bet 

231 pos = hand.get_player_position(hero) 

232 gt = hand.gametype['category'] 

233 row = [] 

234 totalpot = hand.totalpot 

235 rake = hand.rake 

236 sitehandid = hand.handid 

237 if hand.gametype['base'] == 'hold': 

238 board = [] 

239 board.extend(hand.board['FLOP']) 

240 board.extend(hand.board['TURN']) 

241 board.extend(hand.board['RIVER']) 

242 

243 pre_actions = hand.get_actions_short(hero, 'PREFLOP') 

244 post_actions = '' 

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

246 post_actions = hand.get_actions_short_streets(hero, 'FLOP', 'TURN', 'RIVER') 

247 

248 row = [hand.getStakesAsString(), str(nbplayers), pos, hand.join_holecards(hero), pre_actions, ' '.join(board), post_actions, 

249 str(won), str(bet), str(net), gt, str(handid), str(totalpot), str(rake), str(sitehandid)] 

250 elif hand.gametype['base'] == 'stud': 

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

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

253 later_streets = [] 

254 later_streets.extend(hand.holecards['FOURTH'][hero][0]) 

255 later_streets.extend(hand.holecards['FIFTH'][hero][0]) 

256 later_streets.extend(hand.holecards['SIXTH'][hero][0]) 

257 later_streets.extend(hand.holecards['SEVENTH'][hero][0]) 

258 

259 pre_actions = hand.get_actions_short(hero, 'THIRD') 

260 post_actions = '' 

261 if 'F' not in pre_actions: 

262 post_actions = hand.get_actions_short_streets(hero, 'FOURTH', 'FIFTH', 'SIXTH', 'SEVENTH') 

263 

264 row = [hand.getStakesAsString(), str(nbplayers), pos, third, pre_actions, ' '.join(later_streets), post_actions, str(won), 

265 str(bet), str(net), gt, str(handid), str(totalpot), str(rake)] 

266 elif hand.gametype['base'] == 'draw': 

267 row = [hand.getStakesAsString(), str(nbplayers), pos, hand.join_holecards(hero, street='DEAL'), 

268 hand.get_actions_short(hero, 'DEAL'), None, None, 

269 str(won), str(bet), str(net), gt, str(handid), str(totalpot), str(rake)] 

270 

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

272 for index, item in enumerate(modelrow): 

273 item.setEditable(False) 

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

275 cards = item.data(Qt.DisplayRole) 

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

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

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

279 if index in (self.colnum['Bet'], self.colnum['Net'], self.colnum['Won']): 

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

281 self.model.appendRow(modelrow) 

282 

283 def copyHandToClipboard(self, checkState, hand): 

284 handText = StringIO() 

285 hand.writeHand(handText) 

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

287 

288 def contextMenu(self, event): 

289 index = self.view.currentIndex() 

290 if index.row() < 0: 

291 return 

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

293 m = QMenu() 

294 copyAction = m.addAction('Copy to clipboard') 

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

296 m.move(event.globalPos()) 

297 m.exec_() 

298 

299 def filter_cards_cb(self, card): 

300 if hasattr(self, 'hands'): 

301 self.filterModel.invalidateFilter() 

302 

303 def is_row_in_card_filter(self, rownum): 

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

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

306 card_filter = self.filters.getCards() 

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

308 

309 if '0x' in hcs: # if cards are unknown return True 

310 return True 

311 

312 gt = self.model.data(self.model.index(rownum, self.colnum['Game'])) 

313 

314 if gt not in ('holdem', 'omahahi', 'omahahilo'): 

315 return True 

316 

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

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

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

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

321 abbr = Card.twoStartCardString(idx) 

322 

323 # Debug output to trace unexpected keys 

324 if abbr not in card_filter: 

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

326 

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

328 

329 def row_activated(self, index): 

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

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

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

333 self.replayer.play_hand(index) 

334 

335 def importhand(self, handid=1): 

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

337 

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

339 heroes = self.filters.getHeroes() 

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

341 if h.hero is None: 

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

343 return h 

344 

345 def render_cards(self, cardstring): 

346 card_width = 30 

347 card_height = 42 

348 if cardstring is None or cardstring == '': 

349 cardstring = "0x" 

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

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

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

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

354 cardstring.replace(",", "") 

355 cards = [Card.encodeCard(c) for c in cardstring.split(' ')] 

356 n_cards = len(cards) 

357 

358 pixbuf = QPixmap(card_width * n_cards, card_height) 

359 painter = QPainter(pixbuf) 

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

361 for card in cards: 

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

363 x += card_width 

364 return pixbuf 

365 

366 

367class TourHandViewer(GuiHandViewer): 

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

369 super().__init__(config, querylist, mainwin) 

370 

371 def setup_filters(self): 

372 filters_display = { 

373 "Heroes": True, 

374 "Sites": True, 

375 "Games": False, # cash game 

376 "Tourney": True, 

377 "TourneyCat": True, 

378 "TourneyLim": True, 

379 "TourneyBuyin": True, 

380 "Currencies": True, 

381 "Limits": False, 

382 "LimitSep": True, 

383 "LimitType": True, 

384 "Type": True, 

385 "UseType": 'tour', 

386 "Seats": False, 

387 "SeatSep": True, 

388 "Dates": True, 

389 "Groups": False, 

390 "Button1": True, 

391 "Button2": False 

392 } 

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

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

395 self.filters.registerButton1Callback(self.loadHands) 

396 self.filters.registerCardsCallback(self.filter_cards_cb) 

397 

398 # update games for default hero and site 

399 heroes = self.filters.getHeroes() 

400 sites = self.filters.getSites() 

401 

402 default_hero = next(iter(heroes.values())) if heroes else None 

403 default_site = next(iter(sites)) if sites else None 

404 

405 if default_hero and default_site: 

406 self.filters.update_games_for_hero(default_hero, default_site) 

407 

408 

409if __name__ == "__main__": 

410 config = Configuration.Config() 

411 

412 settings = {} 

413 

414 settings.update(config.get_db_parameters()) 

415 settings.update(config.get_import_parameters()) 

416 settings.update(config.get_default_paths()) 

417 

418 from PyQt5.QtWidgets import QMainWindow 

419 

420 app = QApplication([]) 

421 sql = SQL.Sql(db_server=settings['db-server']) 

422 main_window = QMainWindow() 

423 

424 # create tour viewer 

425 tour_viewer = TourHandViewer(config, sql, main_window) 

426 

427 main_window.setCentralWidget(tour_viewer) 

428 

429 main_window.show() 

430 main_window.resize(1400, 800) 

431 app.exec_()