diff --git a/html-src/rules/fourkingdoms.html b/html-src/rules/fourkingdoms.html
new file mode 100644
index 000000000..9b0e2d9e9
--- /dev/null
+++ b/html-src/rules/fourkingdoms.html
@@ -0,0 +1,51 @@
+
Four Kingdoms
+
+One-Deck game type. 1 deck. No redeal.
+
+
Object
+
+Move all cards to the different foundations.
+
+
Rules
+
+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.
+
+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:
+
+- 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.
+
- Tower - Tens represent wizards - the ten of each suit can be played
+to the appropriate tower at any time.
+
- 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.
+
- 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.
+
- 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.
+
+
+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.
+
+The game is won if all cards can be moved to their appropriate foundations,
+thus uniting the four kingdoms.
+
+
Notes
+
+Four Kingdoms was invented by David Bernazzani. It was designed to try and
+reclaim some of the original historical feel for the cards.
diff --git a/po/de_pysol.po b/po/de_pysol.po
index 3e6516c87..440265386 100644
--- a/po/de_pysol.po
+++ b/po/de_pysol.po
@@ -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 \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."
diff --git a/po/fr_pysol.po b/po/fr_pysol.po
index b426185bb..b36966e4d 100644
--- a/po/fr_pysol.po
+++ b/po/fr_pysol.po
@@ -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 \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."
diff --git a/po/it_pysol.po b/po/it_pysol.po
index 68513c642..f6a651fbf 100644
--- a/po/it_pysol.po
+++ b/po/it_pysol.po
@@ -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 \n"
"Language-Team: Italiano \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"
diff --git a/po/pl_pysol.po b/po/pl_pysol.po
index 523f5340d..460ef3ccc 100644
--- a/po/pl_pysol.po
+++ b/po/pl_pysol.po
@@ -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 \n"
"Language-Team: Polish \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."
diff --git a/po/pt_BR_pysol.po b/po/pt_BR_pysol.po
index 84c087fdf..2f5116791 100644
--- a/po/pt_BR_pysol.po
+++ b/po/pt_BR_pysol.po
@@ -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 \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."
diff --git a/po/pysol.pot b/po/pysol.pot
index 3a37bccd0..c5c54aa1d 100644
--- a/po/pysol.pot
+++ b/po/pysol.pot
@@ -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 ""
diff --git a/po/ru_pysol.po b/po/ru_pysol.po
index 987de1d57..9f460da27 100644
--- a/po/ru_pysol.po
+++ b/po/ru_pysol.po
@@ -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 \n"
"Language-Team: Russian \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 ""
diff --git a/pysollib/gamedb.py b/pysollib/gamedb.py
index 25720084e..56d8f4ecd 100644
--- a/pysollib/gamedb.py
+++ b/pysollib/gamedb.py
@@ -418,7 +418,7 @@ def _callback(gi, gt=game_type):
# 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 @@ def _callback(gi, gt=game_type):
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 @@ def _callback(gi, gt=game_type):
("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 @@ def _callback(gi, gt=game_type):
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
diff --git a/pysollib/games/moojub.py b/pysollib/games/moojub.py
index 4f581001f..a83f1bffc 100644
--- a/pysollib/games/moojub.py
+++ b/pysollib/games/moojub.py
@@ -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 @@ def startGame(self):
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))