class MainWindow(QMainWindow): AUTOSAVE_INTERVAL = 10000 AUTOSAVE_PATH = './resource/AutoSave.sav' CONFIG_PATH = './resource/config.ini' def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.createUI() self.nameAreaMapping = { 'hand': self.handArea, 'hero': self.heroArea, 'engaged': self.engagedArea, 'staging': self.stagingArea, 'location': self.locationDeck, 'quest': self.questDeck, 'encounter': self.encounterDeck, 'encounterDP': self.encounterDiscardPile, 'prepare': self.prepareDeck, 'removed': self.removedPile, 'playerDP': self.playerDiscardPile, } self.deckManipulatorList = [] # for bookkeeping existing DeckManipulator instances self.scenarioId = 0 self.playerDeckId = 0 self.isFirstPlayer = True # might change in MultiplayerMainWindow self.playerCount = 1 # might change in MultiplayerMainWindow if self.__class__.__name__ == 'MainWindow': # not true in MultiplayerMainWindow if self.checkIfprogramCrashed(): self.loadGame(MainWindow.AUTOSAVE_PATH) else: self.startNewGame() # auto save just work in Solo game self.prevState = self.getState() def autoSave(): state = self.getState() if state != self.prevState: jsonState = self.dumpState(state) with open(MainWindow.AUTOSAVE_PATH, 'w') as f: f.write(jsonState) self.prevState = state timer = QTimer(self) timer.timeout.connect(autoSave) timer.start(MainWindow.AUTOSAVE_INTERVAL) def addDeckManipulator(self, widget): self.deckManipulatorList.append(widget) def cleanupDeckManipulators(self): for widget in self.deckManipulatorList: try: widget.close() except RuntimeError: pass self.deckManipulatorList = [] def cleanup(self): self.victorySpinBox.setValue(0) self.cleanupDeckManipulators() for area in (self.engagedArea, self.heroArea, self.handArea, self.stagingArea, self.locationDeck, self.questDeck, self.encounterDeck, self.encounterDiscardPile, self.prepareDeck, self.removedPile, self.playerDeck, self.playerDiscardPile): area.setList([]) while True: # remove Card from scene until none left for card in area.scene.items(): if isinstance(card, Card): area.scene.removeItem(card) del card break else: break area.update() self.journeyLogger.clearLog() def startNewGame(self): self.cleanup() setupDialog = SetupDialog(self) setupDialog.exec_() self.scenarioId = setupDialog.selectedScenarioId() self.playerDeckId = setupDialog.selectedDeckId() self.setup() self.prisonAct.setEnabled(self.scenarioId == 2) # is it Escape From Dol Guldur? def restartGame(self): self.cleanup() self.setup() self.prisonAct.setEnabled(self.scenarioId == 2) # is it Escape From Dol Guldur? def startNewGameAction(self): self.startNewGame() def restartGameAction(self): self.restartGame() def saveGame(self): state = self.getState() state['version'] = VERSION jsonState = self.dumpState(state) filePath = QFileDialog.getSaveFileName(self, QCoreApplication.translate('MainWindow', 'Save game'), 'LotRLCG.sav', QCoreApplication.translate('MainWindow', 'Game Save (*.sav)')) if filePath: if not saveFile(filePath, jsonState): QMessageBox.critical(self, QCoreApplication.translate('MainWindow', "Can't save game"), QCoreApplication.translate('MainWindow', 'Failed to write file!')) def loadGame(self, filePath=''): if not filePath: filePath = QFileDialog.getOpenFileName(self, QCoreApplication.translate('MainWindow', 'Load game'), '.', QCoreApplication.translate('MainWindow', 'Game Save (*.sav)')) if filePath: file = QFile(filePath) if file.open(QIODevice.ReadOnly | QIODevice.Text): jsonState = str(file.readAll()) try: state = json.loads(jsonState, encoding='ascii') except ValueError: QMessageBox.critical(self, QCoreApplication.translate('MainWindow', "Can't load game"), QCoreApplication.translate('MainWindow', 'Game save corrupted!')) return self.victorySpinBox.setValue(state['victory']) self.threatDial.setValue(state['threat']) for (name, area) in self.nameAreaMapping.items(): area.setState(state[name]) file.close() else: QMessageBox.critical(self, QCoreApplication.translate('MainWindow', "Can't load game"), QCoreApplication.translate('MainWindow', 'Failed to open file!')) def dumpState(self, dictObject): return json.dumps(dictObject, separators=(',', ':'), encoding='ascii') def getState(self): state = {} state['victory'] = self.victorySpinBox.value() state['threat'] = self.threatDial.value for (name, area) in self.nameAreaMapping.items(): state[name] = area.getState() return state def setup(self): self.setupPlayerCards() self.promptMulligan() def setupPlayerCards(self): heroList = [] playerList = [] for (set_, id) in playerDecksInfo[self.playerDeckId]['deck']: if isHeroCard(set_, id): heroList.append((set_, id)) else: playerList.append((set_, id)) random.shuffle(playerList) # start creating Card instances for (set_, id) in heroList: self.heroArea.addCard(Card(cardsInfo[set_][id], revealed=True)) for (set_, id) in playerList: self.playerDeck.addCard(Card(cardsInfo[set_][id])) for i in range(6): if self.playerDeck.getList(): card = self.playerDeck.draw() if not card.revealed(): card.flip() self.handArea.addCard(card) threatValue = 0 for card in self.heroArea.getList(): threatValue += card.info.get('cost', 0) self.threatDial.setValue(threatValue) def promptMulligan(self): mulliganDialog = _MulliganDialog(self) mulliganDialog.show() def takeMulligan(self): for i in range(6): card = self.handArea.draw() card.flip() self.playerDeck.addCard(card) self.playerDeck.shuffle() for i in range(6): card = self.playerDeck.draw() card.flip() self.handArea.addCard(card) self.mulliganDecisionIsMade() def giveUpMulligan(self): self.mulliganDecisionIsMade() def mulliganDecisionIsMade(self): self.setupEncounterCards() self.logInitialState() def setupEncounterCards(self): assert(self.isFirstPlayer) scenarioId = self.scenarioId heroList = [] # additional cards that First Player gains control, according to quest card's instructions questList = [] encounterList = [] stagingList = [] prepareList = [] for encounterName in scenariosInfo[scenarioId]['encounters']: for set_ in SETS: for (id, card) in enumerate(cardsInfo[set_]): if card['icon'] == encounterName and card['type'] != 'quest': for i in range(card['quantity']): encounterList.append((set_, id)) random.shuffle(encounterList) s = '' if scenarioId <= 2: s = 'core' elif scenarioId <= 8: s = 'mirkwood' elif scenarioId == 9: s = 'osgiliath' elif scenarioId <= 12: s = 'khazaddum' else: s = 'dwarrowdelf' # EXPANSION if scenarioId == 0: # Passage Through Mirkwood questList = [(s, 119), (s, 120), (s, 121 + random.choice((0, 1)))] stagingList = [(s, 96), (s, 99)] for card in stagingList: encounterList.remove(card) random.shuffle(encounterList) elif scenarioId == 1: # Journey Along the Anduin questList = [(s, 126), (s, 127), (s, 128)] hillTroll = (s, 82) for i in range(self.playerCount): stagingList.append(encounterList.pop(-1)) # draw one card from encounter deck to staging area. 1 card per player. hillTrollAppeared = False for card in stagingList: if card == hillTroll: hillTrollAppeared = True if not hillTrollAppeared: stagingList.append(hillTroll) encounterList.remove(hillTroll) random.shuffle(encounterList) elif scenarioId == 2: # Escape From Dol Guldur questList = [(s, 123), (s, 124), (s, 125)] prepareList = [(s, 102)] # Nazgul of Dol Guldur encounterList.remove((s, 102)) stagingList = [(s, 108), (s, 109), (s, 110)] # Gandalf's Map, Dungeon Torch, Shadow Key for card in stagingList: encounterList.remove(card) elif scenarioId == 3: # The Hunt for Gollum questList = [(s, 11), (s, 12), (s, 13)] if self.playerCount == 1: stagingList.append(encounterList.pop(-1)) # 1 card per player elif scenarioId == 4: # Conflict at the Carrock questList = [(s, 35), (s, 36)] stagingList = [(s, 43)] # The Carrock encounterList.remove((s, 43)) prepareList = [(s, 38), (s, 39), (s, 40), (s, 41)] # 4 Trolls for card in prepareList: encounterList.remove(card) sacked = (s, 48) while encounterList.count(sacked) > self.playerCount: # 1 Sacked! per player encounterList.remove(sacked) random.shuffle(encounterList) elif scenarioId == 5: # A Journey to Rhosgobel questList = [(s, 60), (s, 61), (s, 62)] heroList.append((s, 64)) # Wilyador, damage tokens will be placed after Card instance created # TODO: this is for first player encounterList.remove((s, 64)) stagingList = [(s, 65)] # Rhosgobel encounterList.remove((s, 65)) elif scenarioId == 6: # The Hills of Emyn Muil questList = [(s, 82)] stagingList = [(s, 83), (s, 84)] # Amon Hen, Amon Lhaw for card in stagingList: encounterList.remove(card) elif scenarioId == 7: # The Dead Marshes questList = [(s, 105), (s, 106)] stagingList = [(s, 107)] # Gollum encounterList.remove((s, 107)) if self.playerCount == 1: stagingList.append(encounterList.pop(-1)) # 1 card per player elif scenarioId == 8: # Return to Mirkwood questList = [(s, 126), (s, 127), (s, 128), (s, 129)] gollum = (s, 130) encounterList.remove(gollum) if self.playerCount == 1: heroList.append(gollum) stagingList.append(encounterList.pop(-1)) # 1 card per player else: stagingList.append(gollum) elif scenarioId == 9: # The Massing at Osgiliath questList = [(s, 16), (s, 17), (s, 18), (s, 19)] scouts = ((s, 2), (s, 3), (s, 4)) # 3 Scouts per player for scout in scouts: for i in range(min(4, self.playerCount)): stagingList.append(scout) for card in stagingList: encounterList.remove(card) random.shuffle(encounterList) prepareList = [(s, 1)] # The Witch-king encounterList.remove((s, 1)) elif scenarioId == 10: # Into the Pit questList = [(s, 64), (s, 65), (s, 66)] stagingList = [(s, 16)] # East-gate, put it to staging area and draw to location deck later encounterList.remove((s, 16)) heroList.append((s, 41)) # Cave Torch, for first player # TODO: this is for first player encounterList.remove((s, 41)) prepareList = [(s, 17), (s, 18)] # First Hall, Bridge of Khazad-dum for card in prepareList: encounterList.remove(card) elif scenarioId == 11: # The Seventh Level questList = [(s, 67), (s, 68)] heroList.append((s, 24)) # Book of Mazarbul, for first player encounterList.remove((s, 24)) elif scenarioId == 12: # Flight from Moria questList = [(s, i) for i in range(70, 77)] random.shuffle(questList) questList.insert(0, (s, 69)) stagingList = [(s, 25)] # The Nameless Fear encounterList.remove((s, 25)) foe = (s, 28) # A Foe Beyond while encounterList.count(foe) > self.playerCount: # 1 "A Foe Beyond" per player encounterList.remove(foe) random.shuffle(encounterList) if self.playerCount == 1: stagingList.append(encounterList.pop(-1)) # 1 card per player elif scenarioId == 13: # The Redhorn Gate questList = [(s, 11), (s, 12), (s, 13)] stagingList = [(s, 15)] # Caradhras encounterList.remove((s, 15)) prepareList = [(s, 22), (s, 22), (s, 22), (s, 22), (s, 22)] # 5 Snowstorms for card in prepareList: encounterList.remove(card) heroList.append((s, 14)) # Arwen Undomiel, for first player # TODO: this is for first player encounterList.remove((s, 14)) if self.playerCount == 1: stagingList.append(encounterList.pop(-1)) # 1 card per player elif scenarioId == 14: # Road to Rivendell questList = [(s, 38), (s, 39), (s, 40)] heroList.append((s, 41)) # Arwen Undomiel, for first player # TODO: this is for first player encounterList.remove((s, 41)) if self.playerCount == 1: stagingList.append(encounterList.pop(-1)) # 1 card per player elif scenarioId == 15: # The Watcher in the Water questList = [(s, 63), (s, 64)] prepareList = [(s, 72), (s, 65)] # The Watcher, Doors of Durin for card in prepareList: encounterList.remove(card) stagingList.append(encounterList.pop(-1)) # reveal 1 card elif scenarioId == 16: # The Long Dark questList = [(s, 86), (s, 87)] heroList.append(('khazaddum', 41)) # Cave Torch, for first player # TODO: this is for first player encounterList.remove(('khazaddum', 41)) # EXPANSION prepareList.reverse() # start creating Card instances for (set_, id) in heroList: self.heroArea.addCard(Card(cardsInfo[set_][id], revealed=True)) for (set_, id) in reversed(questList): self.questDeck.addCard(Card(cardsInfo[set_][id], revealed=True)) for (set_, id) in encounterList: self.encounterDeck.addCard(Card(cardsInfo[set_][id])) for (set_, id) in stagingList: self.stagingArea.addCard(Card(cardsInfo[set_][id], revealed=True)) for (set_, id) in prepareList: self.prepareDeck.addCard(Card(cardsInfo[set_][id], revealed=True)) # post processing title = QCoreApplication.translate('MainWindow', 'Manually Setup Required') if scenarioId == 2: # Escape From Dol Guldur QMessageBox.information(self, title, QCoreApplication.translate('MainWindow', 'Objective cards are Guarded!')) elif scenarioId in (3, 7, 12, 13, 14): # The Hunt for Gollum, The Dead Marshes, Flight from Moria, The Redhorn Gate, Road to Rivendell if self.playerCount > 1: QMessageBox.information(self, title, QCoreApplication.translate('MainWindow', 'Reveal 1 card per player!')) elif scenarioId == 5: # A Journey to Rhosgobel for i in range(2): # attach 2 damage token to Wilyador self.heroArea.getList()[-1].attach(Token('damage')) elif scenarioId == 8: # Return to Mirkwood if self.playerCount > 1: QMessageBox.information(self, title, QString('%1<br>%2').arg(QCoreApplication.translate('MainWindow', 'Choose a player to guard %1,').arg('<b>"Gollum"</b>')).arg(QCoreApplication.translate('MainWindow', 'then reveal 1 card per player.'))) elif scenarioId == 10: # Into the Pit self.locationDeck.addCard(self.stagingArea.draw()) # make East-gate as active location QMessageBox.information(self, title, QString('%1<br>%2').arg(QCoreApplication.translate('MainWindow', 'Attach %1 to a hero,').arg('<b>"Cave Torch"</b>')).arg(QCoreApplication.translate('MainWindow', 'then reveal 1 card per player.'))) elif scenarioId == 11: # The Seventh Level QMessageBox.information(self, title, QString('%1<br>%2').arg(QCoreApplication.translate('MainWindow', 'Attach %1 to a hero,').arg('<b>"Book of Mazarbul"</b>')).arg(QCoreApplication.translate('MainWindow', 'then reveal 1 card per player.'))) elif scenarioId == 15: # The Watcher in the Water card = self.stagingArea.getList()[0] if ('strength' not in card.info) or (card.info['strength'] < self.playerCount * 2): QMessageBox.information(self, title, QCoreApplication.translate('MainWindow', 'Reveal cards until total threat >= %1.').arg(self.playerCount * 2)) elif scenarioId == 16: # The Long Dark QMessageBox.information(self, title, QString('%1<br>%2').arg(QCoreApplication.translate('MainWindow', 'Attach %1 to a hero,').arg('<b>"Cave Torch"</b>')).arg(QCoreApplication.translate('MainWindow', "then follow Quest card's setup instruction."))) # EXPANSION def logInitialState(self): heroes = [repr(card) for card in self.heroArea.getList()] heroesString = '' pronoun = 'their' if len(heroes) == 1: heroesString = heroes[0] pronoun = 'his/her' elif len(heroes) == 2: heroesString = '{0} and {1}'.format(heroes[0], heroes[1]) elif len(heroes) >= 3: heroesString = '{0} and {1}'.format(', '.join(heroes[:-1]), heroes[-1]) self.log('<h3>{0} started {1} journey on <b>[{2}]</b>...</h3><br>'.format(heroesString, pronoun, scenariosInfo[self.scenarioId]['name'])) self.logCurrentState() if self.questDeck.getList(): self.log('Questing {0}'.format(repr(self.questDeck.getList()[-1]))) def logCurrentState(self): self.threatDial.appendLog() hand = [repr(card) for card in self.handArea.getList()] self.log('Hand: {0}'.format(', '.join(hand))) staging = [repr(card) for card in self.stagingArea.getList()] self.log('Staging: {0}<br>'.format(', '.join(staging))) def setLargeImage(self, card): if card.info['type'] != 'quest': if self.largeImageLabel.currentCard == (card, card.revealed()): return if card.info['type'] == 'quest': ratio = float(CARD_WIDTH) / CARD_HEIGHT transform = QTransform.fromScale(ratio, ratio) transform = transform.rotate(90) pixmap = card.pixmap().transformed(transform, Qt.SmoothTransformation) else: pixmap = card.currentImage() self.largeImageLabel.setPixmap(pixmap) self.largeImageLabel.currentCard = (card, card.revealed()) def proceedResourcePhase(self): for card in self.heroArea.getList(): if card.info['type'] == 'hero' and card.revealed(): card.attach(Token('resource')) tokenCount = card.getState().get('R', 0) self.log('{0}->{1}({2})'.format('resource', repr(card), tokenCount)) card = self.playerDeck.draw() if card: if not card.revealed(): card.flip() self.handArea.addCard(card) self.log('Draw {0}'.format(repr(card))) def proceedRefreshPhase(self): for card in self.heroArea.getList(): card.ready() for child in card.attachedItems.equipments: child.ready() self.heroArea.update() self.heroArea.update() # don't ask me why... self.log('All card readied') self.threatDial.increaseValue() self.threatDial.appendLog() # TODO: pass first player token in multiplayer game def proceedDealShadows(self): enemies = filter(lambda card: 'cost' in card.info, list(self.engagedArea.getList())) enemies.sort(reverse=True, key=lambda card: card.info['cost']) # sort from highest to lowest engagement cost for enemy in enemies: card = self.encounterDeck.draw() if card: enemy.attach(card) shadow = repr(card) if card.revealed() else '[???]' self.log('Deal shadow {0} to {1}'.format(shadow, enemy)) def writeSettings(self): settings = QSettings(MainWindow.CONFIG_PATH, QSettings.IniFormat) settings.beginGroup('Localization') settings.setValue('Interface', self.locale) settings.endGroup() settingMapping = { 'MainWindow': self, 'JourneyLogger': self.journeyLogger, 'ScoringDialog': self.scoringDialog, 'PhaseTips': self.phaseTips, } settings.beginGroup('Geometry') for (name, widget) in settingMapping.items(): settings.beginGroup(name) if name == 'MainWindow': settings.setValue('maximized', widget.isMaximized()) settings.setValue('size', widget.size()) settings.setValue('pos', widget.pos()) settings.endGroup() settings.endGroup() settings.beginGroup('ProgramState') settings.setValue('crashed', False) # if program ended up normally, this flag is set to False settings.endGroup() def readSettings(self): settings = QSettings(MainWindow.CONFIG_PATH, QSettings.IniFormat) settings.beginGroup('Geometry') settings.beginGroup('MainWindow') maximized = settings.value('maximized', True).toBool() if maximized: self.showMaximized() else: self.resize(settings.value('size', QSize(1024, 728)).toSize()) self.move(settings.value('pos', QPoint(0, 0)).toPoint()) settings.endGroup() settingMapping = { 'JourneyLogger': self.journeyLogger, 'PhaseTips': self.phaseTips, } for (name, widget) in settingMapping.items(): settings.beginGroup(name) widget.resize(settings.value('size', QSize(500, 300)).toSize()) pos = settings.value('pos').toPoint() if pos != QPoint(): widget.move(pos) settings.endGroup() settings.beginGroup('ScoringDialog') self.scoringDialog.resize(settings.value('size', QSize(300, 300)).toSize()) pos = settings.value('pos').toPoint() if pos != QPoint(): self.scoringDialog.move(pos) settings.endGroup() settings.endGroup() # Geometry def checkIfprogramCrashed(self): '''did program crash on last time running?''' settings = QSettings(MainWindow.CONFIG_PATH, QSettings.IniFormat) settings.beginGroup('ProgramState') crashed = settings.value('crashed', False).toBool() settings.setValue('crashed', True) # set it to True to detect next crash settings.endGroup() return crashed def log(self, message): '''method for logging what happened. Will be rebinded to JourneyLogger in createUI()''' pass def createUI(self): self.newGameAct = QAction(QCoreApplication.translate('MainWindow', '&New Journey...'), self) self.newGameAct.triggered.connect(self.startNewGameAction) self.newGameAct.setShortcut(QKeySequence.New) self.restartGameAct = QAction(QCoreApplication.translate('MainWindow', '&Restart Journey'), self) self.restartGameAct.triggered.connect(self.restartGameAction) self.restartGameAct.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R)) self.saveGameAct = QAction(QCoreApplication.translate('MainWindow', '&Save Game'), self) self.saveGameAct.triggered.connect(self.saveGame) self.saveGameAct.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S)) self.loadGameAct = QAction(QCoreApplication.translate('MainWindow', '&Load Game'), self) self.loadGameAct.triggered.connect(self.loadGame) self.loadGameAct.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_L)) quitAct = QAction(QCoreApplication.translate('MainWindow', '&Quit'), self) quitAct.triggered.connect(self.close) quitAct.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q)) gameMenu = self.menuBar().addMenu(QCoreApplication.translate('MainWindow', '&Game')) gameMenu.addAction(self.newGameAct) gameMenu.addAction(self.restartGameAct) gameMenu.addSeparator() gameMenu.addAction(self.saveGameAct) gameMenu.addAction(self.loadGameAct) gameMenu.addSeparator() gameMenu.addAction(quitAct) self.journeyLogger = JourneyLogger(self) self.log = self.journeyLogger.append # this 'log' function will be called by those who wants to write journey log self.journeyLoggerAct = QAction(QCoreApplication.translate('MainWindow', '&Journey Logger'), self) self.journeyLoggerAct.triggered.connect(lambda: self.journeyLogger.show()) self.journeyLoggerAct.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_J)) self.journeyLoggerAct.setIcon(QIcon(':/images/tokens/progress.png')) self.phaseTips = _PhaseTips(self) phaseTipsAct = QAction(QCoreApplication.translate('MainWindow', '&Phase Tips'), self) phaseTipsAct.triggered.connect(lambda: self.phaseTips.show()) phaseTipsAct.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_P)) def prisonRandomHero(): hero = random.choice(self.heroArea.getList()) self.log('{0} is prisoned!'.format(hero)) hero.flip() hero.attach(Token('damage')) self.prisonAct = QAction(QCoreApplication.translate('MainWindow', 'Prison a random Hero'), self) self.prisonAct.triggered.connect(prisonRandomHero) self.prisonAct.setToolTip(QCoreApplication.translate('MainWindow', 'For "Escape From Dol Guldur" scenario')) self.scoringDialog = _ScoringDialog(self) self.scoringAct = QAction(QCoreApplication.translate('MainWindow', 'Scoring...'), self) self.scoringAct.triggered.connect(lambda: self.scoringDialog.show()) utilityMenu = self.menuBar().addMenu(QCoreApplication.translate('MainWindow', '&Utility')) utilityMenu.addAction(self.journeyLoggerAct) utilityMenu.addAction(phaseTipsAct) utilityMenu.addSeparator() utilityMenu.addAction(self.prisonAct) utilityMenu.addAction(self.scoringAct) self.about = _About(self) aboutAct = QAction(QCoreApplication.translate('MainWindow', '&About'), self) aboutAct.triggered.connect(lambda: self.about.show()) helpMenu = self.menuBar().addMenu(QCoreApplication.translate('MainWindow', '?')) def currentLocaleSetting(): settings = QSettings(MainWindow.CONFIG_PATH, QSettings.IniFormat) settings.beginGroup('Localization') locale = str(settings.value('Interface', 'None').toString()) settings.endGroup() return locale def detectUsableLocalization(): locale = QLocale().system().name() for qmFilePath in glob.glob('./resource/translations/*.qm'): qmFilePath = qmFilePath.replace('\\', '/') qm = qmFilePath[qmFilePath.rindex('/') + 1 : -3] qm = qm.replace('qt_', '') if locale == qm: return locale return 'en_US' self.locale = currentLocaleSetting() if self.locale == 'None': # first start up self.locale = detectUsableLocalization() def changeLocale(locale): def changeLocale_(): self.locale = locale QMessageBox.information(self, QCoreApplication.translate('MainWindow', 'Setting Changed'), QCoreApplication.translate('MainWindow', 'Restart program to apply change.')) return changeLocale_ languages = collections.OrderedDict() languages[QCoreApplication.translate('MainWindow', 'English')] = 'en_US' languages[QCoreApplication.translate('MainWindow', 'Traditional Chinese')] = 'zh_TW' languages[QCoreApplication.translate('MainWindow', 'Simplified Chinese')] = 'zh_CN' interfaceLanguageMenu = helpMenu.addMenu(QCoreApplication.translate('MainWindow', 'Interface Langauge')) languageGroup = QActionGroup(self) for (language, locale) in languages.items(): changeLanguageAct = QAction(language, self, checkable=True) changeLanguageAct.triggered.connect(changeLocale(locale)) languageGroup.addAction(changeLanguageAct) interfaceLanguageMenu.addAction(changeLanguageAct) if locale == self.locale: changeLanguageAct.setChecked(True) helpMenu.addAction(aboutAct) self.largeImageLabel = QLabel() self.largeImageLabel.setFixedSize(CARD_WIDTH, CARD_HEIGHT) self.largeImageLabel.setPixmap(QPixmap(':/images/player_card_back.jpg')) self.largeImageLabel.currentCard = None self.threatDial = ThreatDial() self.threatDial.setFixedWidth(CARD_WIDTH) resourcePhaseButton = QPushButton(QCoreApplication.translate('MainWindow', 'Resource Phase')) resourcePhaseButton.clicked.connect(self.proceedResourcePhase) resourcePhaseButton.setToolTip(QString('%1<br>%2').arg(QCoreApplication.translate('MainWindow', 'Add 1 resource to each hero and draw 1 card.')).arg(QCoreApplication.translate('MainWindow', 'Special card-effects are not concerned.'))) resourcePhaseButton.setFocusPolicy(Qt.NoFocus) dealShadowsButton = QPushButton(QCoreApplication.translate('MainWindow', 'Deal Shadows')) dealShadowsButton.clicked.connect(self.proceedDealShadows) dealShadowsButton.setToolTip(QString('%1<br>%2').arg(QCoreApplication.translate('MainWindow', 'Deal 1 shadow card to each engaged enemy.')).arg(QCoreApplication.translate('MainWindow', 'Special card-effects are not concerned.'))) dealShadowsButton.setFocusPolicy(Qt.NoFocus) refreshPhaseButton = QPushButton(QCoreApplication.translate('MainWindow', 'Refresh Phase')) refreshPhaseButton.clicked.connect(self.proceedRefreshPhase) refreshPhaseButton.setToolTip(QString('%1<br>%2').arg(QCoreApplication.translate('MainWindow', 'Ready all cards and raise 1 threat.')).arg(QCoreApplication.translate('MainWindow', 'Special card-effects are not concerned.'))) refreshPhaseButton.setFocusPolicy(Qt.NoFocus) self.victorySpinBox = QSpinBox() self.victorySpinBox.valueChanged.connect(lambda: self.log('<font color="#3f48cc">Victory Points: {0}</font>'.format(self.victorySpinBox.value()))) victoryLabel = QLabel(QCoreApplication.translate('MainWindow', '&Victory:')) victoryLabel.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) victoryLabel.setBuddy(self.victorySpinBox) self.engagedArea = Area('Engaged Area') self.heroArea = Area('Hero Area') self.handArea = Area('Hand Area') self.stagingArea = Area('Staging Area', orientation=Qt.Vertical) self.locationDeck = Deck('Active Location', QCoreApplication.translate('MainWindow', 'Active<br>Location', 'Deck')) self.questDeck = Deck('Quest Deck', QCoreApplication.translate('MainWindow', 'Quest<br>Deck', 'Deck')) self.encounterDeck = Deck('Encounter Deck', QCoreApplication.translate('MainWindow', 'Encounter<br>Deck', 'Deck')) self.encounterDiscardPile = Deck('Encounter Discard Pile', QCoreApplication.translate('MainWindow', 'Encounter<br>Discard<br>Pile', 'Deck')) self.tokenBank = TokenBank() self.prepareDeck = Deck('Prepare Deck', QCoreApplication.translate('MainWindow', 'Prepare<br>Deck', 'Deck'), Qt.Horizontal) self.removedPile = Deck('Removed From Play', QCoreApplication.translate('MainWindow', 'Removed<br>From<br>Play', 'Deck'), Qt.Horizontal) self.playerDeck = Deck('Player Deck', QCoreApplication.translate('MainWindow', 'Player<br>Deck', 'Deck')) self.playerDiscardPile = Deck('Player Discard Pile', QCoreApplication.translate('MainWindow', 'Player<br>Discard<br>Pile', 'Deck')) self.engagedArea.setBackgroundBrush(QBrush(Qt.darkRed)) self.heroArea.setBackgroundBrush(QBrush(Qt.darkBlue)) self.handArea.setBackgroundBrush(QBrush(Qt.darkCyan)) self.stagingArea.setBackgroundBrush(QBrush(Qt.black)) self.locationDeck.setBackgroundBrush(QBrush(Qt.darkGreen)) self.questDeck.setBackgroundBrush(QBrush(Qt.darkGreen)) self.encounterDeck.setBackgroundBrush(QBrush(Qt.gray)) self.encounterDiscardPile.setBackgroundBrush(QBrush(Qt.darkGray)) self.prepareDeck.setBackgroundBrush(QBrush(Qt.gray)) self.removedPile.setBackgroundBrush(QBrush(Qt.black)) self.playerDeck.setBackgroundBrush(QBrush(Qt.yellow)) self.playerDiscardPile.setBackgroundBrush(QBrush(Qt.darkYellow)) leftLayout = QVBoxLayout() leftLayout.addWidget(self.largeImageLabel) leftLayout.addStretch(1) leftLayout.addWidget(self.threatDial) littleLayout = QGridLayout() littleLayout.addWidget(resourcePhaseButton, 0, 0, 1, 2) littleLayout.addWidget(dealShadowsButton, 0, 2, 1, 2) littleLayout.addWidget(refreshPhaseButton, 0, 4, 1, 2) littleLayout.addWidget(victoryLabel, 0, 6, 1, 1) littleLayout.addWidget(self.victorySpinBox, 0, 7, 1, 1) leftLayout.addLayout(littleLayout) midLayout = QVBoxLayout() midLayout.addWidget(self.engagedArea) midLayout.addWidget(self.heroArea) midLayout.addWidget(self.handArea) rightLayout = QGridLayout() rightLayout.addWidget(self.locationDeck, 0, 0, 1, 1) rightLayout.addWidget(self.questDeck, 0, 1, 1, 1) rightLayout.addWidget(self.encounterDeck, 1, 0, 1, 1) rightLayout.addWidget(self.encounterDiscardPile, 1, 1, 1, 1) rightLayout.addWidget(self.prepareDeck, 2, 0, 1, 1) rightLayout.addWidget(self.removedPile, 2, 1, 1, 1) rightLayout.addWidget(self.tokenBank, 3, 0, 1, 2) rightLayout.addWidget(self.playerDeck, 4, 0, 1, 1) rightLayout.addWidget(self.playerDiscardPile, 4, 1, 1, 1) layout = QHBoxLayout() layout.addLayout(leftLayout) layout.addLayout(midLayout) layout.addWidget(self.stagingArea) layout.addLayout(rightLayout) centralWidget = QWidget() centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.setWindowTitle(QCoreApplication.translate('MainWindow', 'The Lord of the Rings: The Card Game')) self.setWindowIcon(QIcon(':/images/icons/LotRLCG.ico')) self.showMaximized() # will trigger resizeEvent() self.readSettings() def resizeEvent(self, event): if hasattr(self, 'locationDeck'): # if self.createUI() is called for deck in (self.locationDeck, self.questDeck, self.encounterDeck, self.encounterDiscardPile, self.playerDeck, self.playerDiscardPile): ratio = float(deck.height()) / CARD_HEIGHT deck.setFixedWidth((CARD_WIDTH + PADDING * 2) * ratio) for deck in (self.prepareDeck, self.removedPile): deck.setFixedSize(self.locationDeck.width(), self.locationDeck.height() * 2 / 5) self.stagingArea.setMinimumWidth(self.locationDeck.width()) self.stagingArea.setMaximumWidth(self.locationDeck.width() * 1.1) self.engagedArea.setMinimumHeight(self.height() / 3) self.heroArea.setMinimumHeight(self.height() / 3) self.handArea.setMinimumHeight(self.height() / 4) width = self.stagingArea.width() * 2 self.engagedArea.setMinimumWidth(width) self.heroArea.setMinimumWidth(width) self.handArea.setMinimumWidth(width) QApplication.processEvents() # force immediate update def closeEvent(self, event): self.writeSettings() event.accept()
class _PlayerStatePanel(QWidget): def __init__(self, address, nickname, parent=None): super(_PlayerStatePanel, self).__init__(parent) self.address = address # this panel is which player's state? self.nickname = nickname # this panel is which player's state? self.createUI() self.nameAreaMapping = { 'hero': self.heroArea, 'engaged': self.engagedArea, 'playerDP': self.discardPile, } def updateState(self, field, state): if field == 'threat': self.threatValue.display(str(state)) elif field == 'hand': self.handValue.display(str(state)) elif field == 'player': self.deckValue.display(str(state)) elif field == 'hero': self.heroArea.setState(state) elif field == 'engaged': self.engagedArea.setState(state) elif field == 'playerDP': self.discardPile.setState(state) def getState(self): state = {} for (name, area) in self.nameAreaMapping.items(): state[name] = area.getState() return state def resizeEvent(self, event): halfHieght = self.height() / 2 self.engagedArea.setFixedHeight(halfHieght) self.heroArea.setFixedHeight(halfHieght) self.discardPile.setFixedHeight(halfHieght) ratio = float(self.discardPile.height()) / CARD_HEIGHT width = CARD_WIDTH * ratio self.nicknameLabel.setMaximumWidth(width * 1.1) self.discardPile.setMinimumWidth(width) self.discardPile.setMaximumWidth(width * 1.1) self.handValue.setMinimumWidth(width / 2) self.deckValue.setMinimumWidth(width / 2) self.threatValue.setMinimumWidth(width) for lcd in (self.threatValue, self.handValue, self.deckValue): lcd.setMinimumHeight(width * 2 / 5) lcd.setMaximumHeight(width * 3 / 5) # then we adjust nickanmeLabel's size def tooWide(fontMetrics): return fontMetrics.width(self.nicknameLabel.text()) > self.nicknameLabel.width() - 10 def tooHigh(fontMetrics): return fontMetrics.height() > self.nicknameLabel.height() - 5 font = self.nicknameLabel.font() pointSize = 80 font.setPointSize(pointSize) fm = QFontMetrics(font) while tooWide(fm) or tooHigh(fm): if pointSize <= 10: break pointSize -= 2 font.setPointSize(pointSize) fm = QFontMetrics(font) self.nicknameLabel.setFont(font) def createUI(self): self.nicknameLabel = QLabel(self.nickname) palette = QPalette() palette.setColor(QPalette.WindowText, Qt.darkCyan) self.handValue = QLCDNumber(2) self.handValue.setPalette(palette) self.handValue.setSegmentStyle(QLCDNumber.Flat) self.handValue.setToolTip(self.tr('Hand Size')) self.handValue.display(6) palette.setColor(QPalette.WindowText, Qt.black) self.deckValue = QLCDNumber(2) self.deckValue.setPalette(palette) self.deckValue.setSegmentStyle(QLCDNumber.Flat) self.deckValue.setToolTip(self.tr('Deck Size')) self.deckValue.display(30) palette.setColor(QPalette.WindowText, Qt.red) self.threatValue = QLCDNumber(2) self.threatValue.setPalette(palette) self.threatValue.setSegmentStyle(QLCDNumber.Flat) self.threatValue.setToolTip(self.tr('Threat Level')) self.threatValue.display(20) self.engagedArea = Area('Engaged Area') self.engagedArea.setBackgroundBrush(QBrush(Qt.darkRed)) self.heroArea = Area('Hero Area') self.heroArea.setBackgroundBrush(QBrush(Qt.darkBlue)) self.discardPile = Deck('Player Discard Pile', self.tr('Player<br>Discard<br>Pile', 'Deck')) self.discardPile.setBackgroundBrush(QBrush(Qt.darkYellow)) lcdLayout = QGridLayout() lcdLayout.addWidget(self.handValue, 0, 0, 1, 1) lcdLayout.addWidget(self.deckValue, 0, 1, 1, 1) lcdLayout.addWidget(self.threatValue, 1, 0, 1, 2) topLeftLayout = QVBoxLayout() topLeftLayout.addWidget(self.nicknameLabel, 1) topLeftLayout.addLayout(lcdLayout) layout = QGridLayout() layout.addLayout(topLeftLayout, 0, 0, 1, 1) layout.addWidget(self.discardPile, 1, 0, 1, 1) layout.addWidget(self.engagedArea, 0, 1, 1, 1) layout.addWidget(self.heroArea, 1, 1, 1, 1) layout.setRowStretch(0, 1) layout.setColumnStretch(1, 1) self.setLayout(layout)