1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-04-05 00:02:29 -04:00

Compare commits

...

2 commits

Author SHA1 Message Date
Joe R
bd2edf2f19 Add Four Kingdoms game 2024-09-08 09:55:22 -04:00
Joe R
792fbca78a Add option to replay a specific game/deal from the log 2024-09-06 22:45:08 -04:00
12 changed files with 363 additions and 29 deletions

View file

@ -0,0 +1,51 @@
<h1>Four Kingdoms</h1>
<p>
One-Deck game type. 1 deck. No redeal.
<h3>Object</h3>
<p>
Move all cards to the different foundations.
<h3>Rules</h3>
<p>
Cards are dealt to seven piles of four cards each, with only the top
card of each pile face-up. These are the free lands, and may be built
up by same suit. Any valid sequence can be moved, and any card or
sequence can be played in an empty space.
<p>
Above the free lands are multiple columns of foundations/reserves,
which accept different cards according depending on which other
piles have been played to. There is one row of these piles for each
suit. From left to right, these are:
<ul>
<li>Dungeon - Aces represent dragons - no card can be played on an ace,
as such, an ace will essentially block its pile until it can be "banished"
to a dungeon. The ace can only be moved to the dungeon if the king, queen,
jack, and ten of the same suit are played in the tower/castle for that suit,
appropriately.
<li>Tower - Tens represent wizards - the ten of each suit can be played
to the appropriate tower at any time.
<li>Guest Chamber - The guest chamber acts as a free cell for each suit. Any
single card other than the ace of that suit can be temporarily played to the
guest chamber, until it can be moved to a space in the free lands or another
foundation later. It can only be used if the king and queen of the same suit
are played to the castle.
<li>Castle - The castle is a series of three foundations for each suit,
and the king, queen, and jack can be played to them, in that order.
<li>Subjects - The remaining cards are played to the subjects foundation,
built down by same suit from nine through two. Cards can only be moved to
the subjects foundation once the king, queen, and jack of the same suit
have all been played to the castle.
</ul>
<p>
Cards can be dealt from the talon one at a time, and moved to an
appropriate pile in the free lands, or any of the aforementioned
foundation/reserve piles. No redeal is allowed.
<p>
The game is won if all cards can be moved to their appropriate foundations,
thus uniting the four kingdoms.
<h3>Notes</h3>
<p>
Four Kingdoms was invented by David Bernazzani. It was designed to try and
reclaim some of the original historical feel for the cards.

View file

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: PySol 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-10 10:19-0500\n"
"PO-Revision-Date: 2024-08-04 20:21-0400\n"
"PO-Revision-Date: 2024-09-08 09:52-0400\n"
"Last-Translator: H. Schaekel <Holger.Schaekel@web.de>\n"
"Language-Team: German\n"
"Language: de\n"
@ -2347,6 +2347,21 @@ msgstr ""
"7: A 8 2 9 3 T 4 J 5 Q 6 K\n"
"8: 3 J 6 A 9 4 Q 7 2 T 5 K"
msgid "Dungeon"
msgstr ""
msgid "Tower"
msgstr ""
msgid "Guest"
msgstr ""
msgid "Castle"
msgstr ""
msgid "Subjects"
msgstr ""
#: pysollib/games/curdsandwhey.py:76
msgid "Tableau. Build down by suit or of the same rank."
msgstr "Tableau. Erstellt nach unten nach Farbe oder denselben Rang."

View file

@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: 1.02\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-10 10:19-0500\n"
"PO-Revision-Date: 2024-08-04 20:20-0400\n"
"PO-Revision-Date: 2024-09-08 09:52-0400\n"
"Last-Translator: Eric Rausch <neelix570@gmail.com>\n"
"Language-Team: French\n"
"Language: fr\n"
@ -2387,6 +2387,21 @@ msgstr ""
"7: A 8 2 9 3 T 4 J 5 Q 6 K\n"
"8: 3 J 6 A 9 4 Q 7 2 T 5 K"
msgid "Dungeon"
msgstr ""
msgid "Tower"
msgstr ""
msgid "Guest"
msgstr ""
msgid "Castle"
msgstr ""
msgid "Subjects"
msgstr ""
#: pysollib/games/curdsandwhey.py:76
msgid "Tableau. Build down by suit or of the same rank."
msgstr "Tableau. Décroissant par enseigne ou de même valeur."

View file

@ -12,7 +12,7 @@ msgstr ""
"Project-Id-Version: it_pysol\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-10 10:19-0500\n"
"PO-Revision-Date: 2024-08-04 20:19-0400\n"
"PO-Revision-Date: 2024-09-08 09:54-0400\n"
"Last-Translator: Giuliano Colla <giuliano.colla@gmail.com>\n"
"Language-Team: Italiano <it@li.org>\n"
"Language: it\n"
@ -2395,6 +2395,21 @@ msgstr ""
"7: A 8 2 9 3 T 4 J 5 Q 6 K\n"
"8: 3 J 6 A 9 4 Q 7 2 T 5 K"
msgid "Dungeon"
msgstr ""
msgid "Tower"
msgstr ""
msgid "Guest"
msgstr ""
msgid "Castle"
msgstr ""
msgid "Subjects"
msgstr ""
#: pysollib/games/curdsandwhey.py:76
msgid "Tableau. Build down by suit or of the same rank."
msgstr "Tableau: Sequenza decrescente dello stesso seme"

View file

@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PySolFC\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-10 10:19-0500\n"
"PO-Revision-Date: 2024-08-04 20:19-0400\n"
"PO-Revision-Date: 2024-09-08 09:53-0400\n"
"Last-Translator: Jerzy Trzeciak <artusek@wp.pl>\n"
"Language-Team: Polish <pl@li.org>\n"
"Language: pl\n"
@ -2401,6 +2401,21 @@ msgstr ""
"7: A 8 2 9 3 T 4 J 5 Q 6 K\n"
"8: 3 J 6 A 9 4 Q 7 2 T 5 K"
msgid "Dungeon"
msgstr ""
msgid "Tower"
msgstr ""
msgid "Guest"
msgstr ""
msgid "Castle"
msgstr ""
msgid "Subjects"
msgstr ""
#: pysollib/games/curdsandwhey.py:76
msgid "Tableau. Build down by suit or of the same rank."
msgstr "Stół gry. Układaj w dół wg koloru lub wg tej samej wartości."

View file

@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-10 10:19-0500\n"
"PO-Revision-Date: 2024-08-04 20:18-0400\n"
"PO-Revision-Date: 2024-09-08 09:53-0400\n"
"Last-Translator: Matheus Knack <mtknack555@gmail.com>\n"
"Language-Team: \n"
"Language: pt_BR\n"
@ -2408,6 +2408,21 @@ msgstr ""
"7: A 8 2 9 3 T 4 J 5 Q 6 K\n"
"8: 3 J 6 A 9 4 Q 7 2 T 5 K"
msgid "Dungeon"
msgstr ""
msgid "Tower"
msgstr ""
msgid "Guest"
msgstr ""
msgid "Castle"
msgstr ""
msgid "Subjects"
msgstr ""
#: pysollib/games/curdsandwhey.py:76
msgid "Tableau. Build down by suit or of the same rank."
msgstr "Tableau. Construa decrescente por naipe or por valor igual."

View file

@ -2234,6 +2234,21 @@ msgid "\n"
"8: 3 J 6 A 9 4 Q 7 2 T 5 K"
msgstr ""
msgid "Dungeon"
msgstr ""
msgid "Tower"
msgstr ""
msgid "Guest"
msgstr ""
msgid "Castle"
msgstr ""
msgid "Subjects"
msgstr ""
#: pysollib/games/curdsandwhey.py:76
msgid "Tableau. Build down by suit or of the same rank."
msgstr ""

View file

@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-10 10:19-0500\n"
"PO-Revision-Date: 2024-08-04 20:17-0400\n"
"PO-Revision-Date: 2024-09-08 09:54-0400\n"
"Last-Translator: Skomoroh <skomoroh@gmail.com>\n"
"Language-Team: Russian <ru@li.org>\n"
"Language: ru\n"
@ -2393,6 +2393,21 @@ msgstr ""
"7: A 8 2 9 3 10 4 В 5 Д 6 K\n"
"8: 3 В 6 A 9 4 Д 7 2 10 5 K"
msgid "Dungeon"
msgstr ""
msgid "Tower"
msgstr ""
msgid "Guest"
msgstr ""
msgid "Castle"
msgstr ""
msgid "Subjects"
msgstr ""
#: pysollib/games/curdsandwhey.py:76
msgid "Tableau. Build down by suit or of the same rank."
msgstr ""

View file

@ -606,6 +606,7 @@ class PysolMenubar(PysolMenubarTk):
mode = kw.get("mode", 101)
demo = 0
gameid = None
gamenum = None
while mode > 0:
if mode > 1000:
demo = not demo
@ -640,10 +641,14 @@ class PysolMenubar(PysolMenubarTk):
header = (_("%(app)s Demo Full log") if demo
else _("Full log for %(player)s")) % transkw
d = FullLog_StatsDialog(self.top, header, self.app, player)
gameid = d.selected_game
gamenum = d.selected_game_num
elif mode == 104:
header = (_("%(app)s Demo Session log") if demo
else _("Session log for %(player)s")) % transkw
d = SessionLog_StatsDialog(self.top, header, self.app, player)
gameid = d.selected_game
gamenum = d.selected_game_num
elif mode == 105:
# TRANSLATORS: eg. top 10 or top 5 results for a certain game
header = (_("%(app)s Demo Top %(tops)d for %(game)s") if demo
@ -698,8 +703,12 @@ class PysolMenubar(PysolMenubarTk):
self.game.quitGame(gameid)
elif mode == 402:
# start a new game with a gameid / gamenumber
# TODO
pass
if (gameid and gamenum and
(gameid != self.game.id or
gamenum != self.game.getGameNumber(format=0))):
self.game.endGame()
self.game.quitGame(gameid,
random=construct_random(gamenum))
else:
print_err("stats problem: %s %s %s" % (mode, demo, player))
pass

View file

@ -418,7 +418,7 @@ class GI:
# Solitude for Windows
# still missing:
# Bowling (Sackson version), Four Kingdoms, Icicles
# Bowling (Sackson version), Icicles
("Solitude for Windows", (
2, 8, 11, 13, 19, 24, 25, 29, 30, 31, 33, 34, 36, 38, 42,
43, 45, 48, 50, 53, 56, 57, 58, 62, 64, 67, 69, 71, 86, 87,
@ -426,7 +426,7 @@ class GI:
128, 133, 134, 135, 139, 146, 147, 171, 172, 173, 221, 222,
224, 228, 233, 234, 235, 256, 257, 258, 282, 314, 327, 330,
355, 356, 398, 406, 414, 418, 434, 437, 484, 593, 715, 716,
737, 751, 805, 830, 845, 847, 888, 901, 903
737, 751, 805, 830, 845, 847, 888, 901, 903, 970
)),
# XM Solitaire
@ -479,7 +479,7 @@ class GI:
("Paul Alfille", (8,)),
("C.L. Baker", (45,)),
("Mark S. Ball", (909,)),
("David Bernazzani", (314, 830,)),
("David Bernazzani", (314, 830, 970,)),
("Gordon Bower", (763, 783, 852, 959,)),
("Art Cabral", (9,)),
("Richard A. Canfield", (105, 835,)),
@ -603,7 +603,7 @@ class GI:
tuple(range(13168, 13170)) + tuple(range(18000, 18005)) +
tuple(range(19000, 19012)) + tuple(range(22303, 22311)) +
tuple(range(22353, 22361))),
('dev', tuple(range(961, 970))),
('dev', tuple(range(961, 971))),
)
# deprecated - the correct way is to or a GI.GT_XXX flag

View file

@ -24,17 +24,25 @@
from pysollib.game import Game
from pysollib.gamedb import GI, GameInfo, registerGame
from pysollib.layout import Layout
from pysollib.mygettext import _
from pysollib.pysoltk import MfxCanvasText
from pysollib.stack import \
DealRowTalonStack, \
OpenStack, \
RK_FoundationStack
from pysollib.util import ACE, ANY_SUIT, KING
RK_FoundationStack, \
ReserveStack, \
SS_FoundationStack, \
SS_RowStack, \
Stack, \
WasteStack, \
WasteTalonStack
from pysollib.util import ACE, ANY_SUIT, JACK, KING, QUEEN
# ************************************************************************
# * Moojub
# ************************************************************************
class Moojub_Foundation(RK_FoundationStack):
def acceptsCards(self, from_stack, cards):
if len(self.cards) > 0:
@ -107,6 +115,143 @@ class Moojub(Game):
self._startAndDealRow()
# ************************************************************************
# * Four Kingdoms
# ************************************************************************
class FourKingdoms_Foundation(SS_FoundationStack):
RequiredStacks = ()
def acceptsCards(self, from_stack, cards):
for stackID in self.RequiredStacks:
if len(self.game.s.foundations[self.id + stackID].cards) == 0:
return False
return SS_FoundationStack.acceptsCards(self, from_stack, cards)
def getHelp(self):
return _('Foundation.')
class FourKingdoms_DungeonFoundation(FourKingdoms_Foundation):
RequiredStacks = (1, 2, 3, 4)
class FourKingdoms_QueenFoundation(FourKingdoms_Foundation):
RequiredStacks = (-1,)
class FourKingdoms_JackFoundation(FourKingdoms_Foundation):
RequiredStacks = (-1, -2)
class FourKingdoms_SubjectsFoundation(FourKingdoms_Foundation):
RequiredStacks = (-1, -2, -3)
class FourKingdoms_Reserve(ReserveStack):
getBottomImage = Stack._getSuitBottomImage
def acceptsCards(self, from_stack, cards):
if cards[0].rank == ACE:
return False
checkStack = (6 * self.cap.base_suit) + 2
for s in range(2):
if len(self.game.s.foundations[checkStack + s].cards) == 0:
return False
return ReserveStack.acceptsCards(self, from_stack, cards)
class FourKingdoms_RowStack(SS_RowStack):
def acceptsCards(self, from_stack, cards):
if self.cards and self.cards[-1].rank == ACE:
return False
return SS_RowStack.acceptsCards(self, from_stack, cards)
class FourKingdoms(Game):
def createGame(self):
# create layout
l, s = Layout(self), self.s
# set window
self.setSize(l.XM + (l.XS * 9),
l.YM + (6 * l.YS) + l.TEXT_HEIGHT)
# create stacks
for i in range(4):
x, y = l.XM, l.YM + l.TEXT_HEIGHT + (l.YS * i)
s.foundations.append(
FourKingdoms_DungeonFoundation(x, y, self, i, base_rank=ACE,
max_cards=1, max_accept=1))
x += (1.5 * l.XS)
s.foundations.append(
FourKingdoms_Foundation(x, y, self, i, base_rank=9,
max_cards=1, max_accept=1))
x += (3 * l.XS)
s.foundations.append(
FourKingdoms_Foundation(x, y, self, i, base_rank=KING,
max_cards=1, max_accept=1))
x += l.XS
s.foundations.append(
FourKingdoms_QueenFoundation(x, y, self, i, base_rank=QUEEN,
max_cards=1, max_accept=1))
x += l.XS
s.foundations.append(
FourKingdoms_JackFoundation(x, y, self, i, base_rank=JACK,
max_cards=1, max_accept=1))
x += (1.5 * l.XS)
s.foundations.append(
FourKingdoms_SubjectsFoundation(x, y, self, i, base_rank=8,
dir=-1, max_cards=8,
max_accept=1))
for i in range(4):
x, y = l.XM + (l.XS * 3), l.YM + l.TEXT_HEIGHT + (l.YS * i)
s.reserves.append(
FourKingdoms_Reserve(x, y, self, max_cards=1, max_accept=1))
s.reserves[i].cap.base_suit = i
if self.preview <= 1:
self.setLabel(l, self.s.foundations[0], "Dungeon")
self.setLabel(l, self.s.foundations[1], "Tower")
self.setLabel(l, self.s.foundations[3], "Castle")
self.setLabel(l, self.s.foundations[5], "Subjects")
self.setLabel(l, self.s.reserves[0], "Guest")
x, y, = l.XM, l.YM + l.TEXT_HEIGHT + (l.YS * 4)
s.talon = WasteTalonStack(x, y, self, max_rounds=1)
l.createText(s.talon, 'se')
y += l.YS
s.waste = WasteStack(x, y, self)
l.createText(s.waste, 'se')
x, y, = l.XM + l.XS, l.YM + l.TEXT_HEIGHT + (l.YS * 4)
for i in range(7):
x += l.XS
s.rows.append(FourKingdoms_RowStack(x, y, self, dir=1))
# define stack-groups
l.defaultStackGroups()
def setLabel(self, layout, stack, text):
font = self.app.getFont("canvas_default")
tx, ty, ta, tf = layout.getTextAttr(stack, anchor="n")
stack.texts.misc = MfxCanvasText(self.canvas,
tx, ty,
anchor=ta,
font=font)
stack.texts.misc.config(text=_(text))
def startGame(self):
for i in range(3):
self.s.talon.dealRow(frames=0, flip=0)
self._startAndDealRowAndCards()
# register the game
registerGame(GameInfo(845, Moojub, "Moojub",
GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK))
registerGame(GameInfo(970, FourKingdoms, "Four Kingdoms",
GI.GT_1DECK_TYPE, 1, 0, GI.SL_BALANCED))

View file

@ -383,6 +383,7 @@ class TreeFormatter(PysolStatsFormatter):
t1, t2, t3, t4, t5, t6 = result
id = self.tree.insert("", "end", text=t1, values=(t2, t3, t4))
self.parent_window.tree_items.append(id)
self.parent_window.games[id] = (t6, t2)
num_rows += 1
if num_rows > self.MAX_ROWS:
break
@ -512,7 +513,8 @@ class LogDialog(MfxDialog):
title = _('Log')
MfxDialog.__init__(self, parent, title, kw.resizable, kw.default)
# self.selected_game = None
self.selected_game = None
self.selected_game_num = None
top_frame, bottom_frame = self.createFrames(kw)
notebook = ttk.Notebook(top_frame)
@ -522,24 +524,40 @@ class LogDialog(MfxDialog):
full_frame = FullLogFrame(self, notebook, app, player)
notebook.add(full_frame, text=_('Full log'))
self.full_log_frame = full_frame
self.notebook_tabs.append(full_frame._w)
session_frame = SessionLogFrame(self, notebook, app, player)
notebook.add(session_frame, text=_('Session log'))
self.session_log_frame = session_frame
self.notebook_tabs.append(session_frame._w)
notebook.select(LogDialog.SELECTED_TAB)
# bind(notebook, '<<NotebookTabChanged>>', self.tabChanged)
bind(notebook, '<<NotebookTabChanged>>', self.tabChanged)
self.notebook = notebook
focus = self.createButtons(bottom_frame, kw)
# self.tabChanged() # configure buttons state
self.tabChanged() # configure buttons state
self.mainloop(focus, kw.timeout)
def tabChanged(self, *args):
w = self.notebook.select()
run_button = self.buttons[0]
indx = self.notebook_tabs.index(w)
if indx == 0:
g = self.full_log_frame.getSelectedGame()
else:
g = self.session_log_frame.getSelectedGame()
if g[0] is None:
run_button.config(state='disabled')
else:
run_button.config(state='normal')
def initKw(self, kw):
kw = KwStruct(kw,
strings=(_("&OK"),
strings=((_("&Play this game"), 402),
"sep", _("&OK"),
(_("&Save to file"), 500)),
default=0,
width=76*self.CHAR_W,
@ -548,16 +566,17 @@ class LogDialog(MfxDialog):
return MfxDialog.initKw(self, kw)
def mDone(self, button):
# self.selected_game = self.all_games_frame.getSelectedGame()
w = self.notebook.select()
indx = self.notebook_tabs.index(w)
LogDialog.SELECTED_TAB = indx
if button == 500: # "Save to file"
assert indx in (0, 1)
if indx == 0: # "Full log"
button = 203
else: # "Session log"
button = 204
if indx == 0:
self.selected_game, self.selected_game_num \
= self.full_log_frame.getSelectedGame()
else:
self.selected_game, self.selected_game_num \
= self.session_log_frame.getSelectedGame()
MfxDialog.mDone(self, button)
@ -576,6 +595,7 @@ class FullLogFrame(AllGamesFrame):
AllGamesFrame.__init__(self, dialog, parent, app, player, **kw)
header = ('', '99999999999999999999', '9999-99-99 99:99',
'XXXXXXXXXXXX')
self.games = {}
self.formatter.resizeHeader(player, header)
def createHeader(self, player):
@ -587,12 +607,16 @@ class FullLogFrame(AllGamesFrame):
return
self.formatter.writeFullLog(player)
def treeviewSelected(self, *args):
pass
def headerClick(self, column):
pass
def getSelectedGame(self):
sel = self.tree.selection()
if sel and len(sel) == 1:
if sel[0] in self.games:
return self.games[sel[0]]
return (None, None)
class SessionLogFrame(FullLogFrame):
def fillTreeview(self, player):