mirror of
https://github.com/shlomif/PySolFC.git
synced 2025-04-15 02:54:09 -04:00
Added Interlock and Love a Duck games.
This commit is contained in:
parent
7bc56fc101
commit
dda8348806
6 changed files with 295 additions and 64 deletions
35
html-src/rules/interlock.html
Normal file
35
html-src/rules/interlock.html
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<h1>Interlock</h1>
|
||||||
|
<p>
|
||||||
|
Klondike type. 1 deck. Unlimited redeals.
|
||||||
|
|
||||||
|
<h3>Object</h3>
|
||||||
|
<p>
|
||||||
|
Move all cards to the foundations.
|
||||||
|
|
||||||
|
<h3>Rules</h3>
|
||||||
|
<p>
|
||||||
|
The tableau piles are built in a pyramid-esque layout, with eleven cards
|
||||||
|
in the top layer, ten cards in the middle, and nine on the bottom.
|
||||||
|
Each card forms a tableau pile which can be built down by alternate
|
||||||
|
color. The three bottom layer cards can be filled with a king when
|
||||||
|
empty, but the remaining layers cannot be filled.
|
||||||
|
<p>
|
||||||
|
Though all cards are dealt face-up, cards in the lower layers cannot
|
||||||
|
be moved until the cards on top of them are moved. However, if the
|
||||||
|
only card or sequence overlapping a card can legally be played on it,
|
||||||
|
that sequence can be moved down in a dropdown move.
|
||||||
|
<p>
|
||||||
|
Foundations are built up in suit from Ace to King.
|
||||||
|
<p>
|
||||||
|
Cards are dealt from the stock one at a time, and can be moved to
|
||||||
|
foundations or exposed tableau piles. You can go through the deck
|
||||||
|
as many times as needed.
|
||||||
|
<p>
|
||||||
|
The game is won when all cards are moved to the foundations.
|
||||||
|
|
||||||
|
<h3>Notes</h3>
|
||||||
|
<p>
|
||||||
|
Interlock is an original game that's a more straightforward variant
|
||||||
|
of other interlocking tableau games, such as
|
||||||
|
<a href="guardian.html">Guardian</a> or
|
||||||
|
<a href="loveaduck.html">Love a Duck</a>.
|
30
html-src/rules/loveaduck.html
Normal file
30
html-src/rules/loveaduck.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<h1>Love a Duck</h1>
|
||||||
|
<p>
|
||||||
|
Yukon type. 1 deck. No redeal.
|
||||||
|
|
||||||
|
<h3>Object</h3>
|
||||||
|
<p>
|
||||||
|
Move all cards to the foundations.
|
||||||
|
|
||||||
|
<h3>Rules</h3>
|
||||||
|
<p>
|
||||||
|
The tableau piles are built in a pyramid-esque layout, with eleven cards
|
||||||
|
in the top layer, ten cards in the middle, and nine on the bottom.
|
||||||
|
The remaining cards are dealt out to the top layer. Each card forms a
|
||||||
|
tableau pile which can be built down by alternate color, but cards can be
|
||||||
|
moved regardless of sequence. The bottom layer cards can be filled with
|
||||||
|
a king when empty, but the remaining layers cannot be filled.
|
||||||
|
<p>
|
||||||
|
Though all cards are dealt face-up, cards in the lower layers cannot
|
||||||
|
be moved until the cards on top of them are moved. However, if the
|
||||||
|
only card or sequence overlapping a card can legally be played on it,
|
||||||
|
that sequence can be moved down in a dropdown move.
|
||||||
|
<p>
|
||||||
|
Foundations are built up in suit from Ace to King. The game is won when
|
||||||
|
all cards are moved to the foundations.
|
||||||
|
|
||||||
|
<h3>Notes</h3>
|
||||||
|
<p>
|
||||||
|
Love a Duck was invented by Jan Wolter, and first appeared in his app
|
||||||
|
<a href="https://politaire.com">Politaire</a>. The name came from
|
||||||
|
"Lord Love a Duck", an expression of surprise from early 1900s London.
|
|
@ -509,7 +509,7 @@ class GI:
|
||||||
415, 427, 458, 495, 496, 497, 508,
|
415, 427, 458, 495, 496, 497, 508,
|
||||||
800, 814, 820, 825, 889, 911, 926)),
|
800, 814, 820, 825, 889, 911, 926)),
|
||||||
("Mary Whitmore Jones", (421, 624,)),
|
("Mary Whitmore Jones", (421, 624,)),
|
||||||
("Jan Wolter", (917,)),
|
("Jan Wolter", (917, 939,)),
|
||||||
)
|
)
|
||||||
|
|
||||||
GAMES_BY_PYSOL_VERSION = (
|
GAMES_BY_PYSOL_VERSION = (
|
||||||
|
@ -592,7 +592,7 @@ class GI:
|
||||||
('fc-2.20', tuple(range(855, 897))),
|
('fc-2.20', tuple(range(855, 897))),
|
||||||
('fc-2.21', tuple(range(897, 900)) + tuple(range(11014, 11017)) +
|
('fc-2.21', tuple(range(897, 900)) + tuple(range(11014, 11017)) +
|
||||||
tuple(range(13160, 13163)) + (16682,)),
|
tuple(range(13160, 13163)) + (16682,)),
|
||||||
('dev', tuple(range(906, 938)) + tuple(range(11017, 11020)) +
|
('dev', tuple(range(906, 940)) + tuple(range(11017, 11020)) +
|
||||||
tuple(range(5600, 5624)) + tuple(range(18000, 18005)) +
|
tuple(range(5600, 5624)) + tuple(range(18000, 18005)) +
|
||||||
tuple(range(22303, 22311)) + tuple(range(22353, 22361))),
|
tuple(range(22303, 22311)) + tuple(range(22353, 22361))),
|
||||||
)
|
)
|
||||||
|
|
|
@ -56,6 +56,7 @@ from . import gypsy # noqa: F401
|
||||||
from . import harp # noqa: F401
|
from . import harp # noqa: F401
|
||||||
from . import headsandtails # noqa: F401
|
from . import headsandtails # noqa: F401
|
||||||
from . import hitormiss # noqa: F401
|
from . import hitormiss # noqa: F401
|
||||||
|
from . import interlock # noqa: F401
|
||||||
from . import katzenschwanz # noqa: F401
|
from . import katzenschwanz # noqa: F401
|
||||||
from . import klondike # noqa: F401
|
from . import klondike # noqa: F401
|
||||||
from . import knockout # noqa: F401
|
from . import knockout # noqa: F401
|
||||||
|
|
227
pysollib/games/interlock.py
Normal file
227
pysollib/games/interlock.py
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- mode: python; coding: utf-8; -*-
|
||||||
|
# ---------------------------------------------------------------------------##
|
||||||
|
#
|
||||||
|
# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
|
||||||
|
# Copyright (C) 2003 Mt. Hood Playing Card Co.
|
||||||
|
# Copyright (C) 2005-2009 Skomoroh
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# ---------------------------------------------------------------------------##
|
||||||
|
|
||||||
|
from pysollib.game import Game
|
||||||
|
from pysollib.gamedb import GI, GameInfo, registerGame
|
||||||
|
from pysollib.hint import Yukon_Hint
|
||||||
|
from pysollib.layout import Layout
|
||||||
|
from pysollib.stack import \
|
||||||
|
AC_RowStack, \
|
||||||
|
InitialDealTalonStack, \
|
||||||
|
SS_FoundationStack, \
|
||||||
|
StackWrapper, \
|
||||||
|
WasteStack, \
|
||||||
|
WasteTalonStack, \
|
||||||
|
Yukon_AC_RowStack
|
||||||
|
from pysollib.util import ANY_RANK, KING
|
||||||
|
|
||||||
|
|
||||||
|
# ************************************************************************
|
||||||
|
# * Interlock
|
||||||
|
# ************************************************************************
|
||||||
|
|
||||||
|
class Interlock_StackMethods:
|
||||||
|
STEP = (9, 9, 9, 9, 9, 9, 9, 9, 9,
|
||||||
|
10, 10, 10, 10, 10, 10, 10, 10, 10, 10)
|
||||||
|
|
||||||
|
def basicIsBlocked(self):
|
||||||
|
r, step = self.game.s.rows, self.STEP
|
||||||
|
i, n, mylen = self.id, 1, len(step)
|
||||||
|
while i < mylen:
|
||||||
|
i = i + step[i]
|
||||||
|
n = n + 1
|
||||||
|
for j in range(i, i + n):
|
||||||
|
if r[j].cards:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# TODO: The dropdown move logic can be done cleaner - too much duplication.
|
||||||
|
def isDropdownMove(self, other_stack):
|
||||||
|
if other_stack not in self.game.s.rows or len(self.cards) == 0 \
|
||||||
|
or not self.cards[0].face_up:
|
||||||
|
return False
|
||||||
|
r, step = self.game.s.rows, self.STEP
|
||||||
|
i, n, mylen = self.id, 1, len(step)
|
||||||
|
while i < mylen:
|
||||||
|
i = i + step[i]
|
||||||
|
n = n + 1
|
||||||
|
for j in range(i, i + n):
|
||||||
|
if r[j].cards:
|
||||||
|
if r[j] != other_stack:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Use this for dropdown moves, as they are an exception
|
||||||
|
# to the normal accept cards logic.
|
||||||
|
def dropdownAcceptsCards(self, cards):
|
||||||
|
# cards must be an acceptable sequence
|
||||||
|
if not self._isAcceptableSequence(cards):
|
||||||
|
return False
|
||||||
|
# [topcard + cards] must be an acceptable sequence
|
||||||
|
if (self.cards and not
|
||||||
|
self._isAcceptableSequence([self.cards[-1]] + cards)):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Interlock_RowStack(Interlock_StackMethods, AC_RowStack):
|
||||||
|
def acceptsCards(self, from_stack, cards):
|
||||||
|
if len(self.cards) == 0 and self.id > self.STEP[0] - 1:
|
||||||
|
return False
|
||||||
|
if (self.isDropdownMove(from_stack) and
|
||||||
|
len(cards) == len(from_stack.cards)):
|
||||||
|
return self.dropdownAcceptsCards(cards)
|
||||||
|
|
||||||
|
return AC_RowStack.acceptsCards(self, from_stack, cards)
|
||||||
|
|
||||||
|
|
||||||
|
class Interlock(Game):
|
||||||
|
RowStack_Class = StackWrapper(Interlock_RowStack, base_rank=KING)
|
||||||
|
Talon_Class = StackWrapper(WasteTalonStack, num_deal=1, max_rounds=-1)
|
||||||
|
|
||||||
|
MAX_ROWS = 11
|
||||||
|
PLAYCARDS = 13
|
||||||
|
|
||||||
|
TEXT = True
|
||||||
|
WASTE = True
|
||||||
|
|
||||||
|
def createGame(self):
|
||||||
|
lay, s = Layout(self), self.s
|
||||||
|
self.setSize((max(self.MAX_ROWS, 7) * lay.XS) + lay.XM,
|
||||||
|
(2.5 * lay.YS) + (self.PLAYCARDS * lay.YOFFSET) + lay.YM)
|
||||||
|
|
||||||
|
self.min_rows = self.MAX_ROWS - 2
|
||||||
|
gap = max(7, self.MAX_ROWS) - self.min_rows
|
||||||
|
# create stacks
|
||||||
|
for i in range(3):
|
||||||
|
x = lay.XM + (gap - i) * lay.XS // 2
|
||||||
|
y = lay.YM + lay.TEXT_HEIGHT + lay.YS + i * lay.YS // 4
|
||||||
|
for j in range(i + self.min_rows):
|
||||||
|
s.rows.append(self.RowStack_Class(x, y, self))
|
||||||
|
x = x + lay.XS
|
||||||
|
|
||||||
|
x, y = lay.XM, lay.YM
|
||||||
|
s.talon = self.Talon_Class(x, y, self)
|
||||||
|
if self.TEXT:
|
||||||
|
lay.createText(s.talon, "s")
|
||||||
|
if self.WASTE:
|
||||||
|
x += lay.XS
|
||||||
|
s.waste = WasteStack(x, y, self)
|
||||||
|
lay.createText(s.waste, "s")
|
||||||
|
x += lay.XS * max(1, (self.MAX_ROWS - 6) // 2)
|
||||||
|
for i in range(4):
|
||||||
|
x += lay.XS
|
||||||
|
s.foundations.append(SS_FoundationStack(x, y, self, i,
|
||||||
|
mod=13, max_move=0))
|
||||||
|
|
||||||
|
lay.defaultStackGroups()
|
||||||
|
|
||||||
|
def startGame(self):
|
||||||
|
self.startDealSample()
|
||||||
|
self.s.talon.dealRow(rows=self.s.rows[:19], flip=1, frames=0)
|
||||||
|
self.s.talon.dealRow(rows=self.s.rows[19:])
|
||||||
|
self.s.talon.dealCards() # deal first card to WasteStack
|
||||||
|
|
||||||
|
def _getClosestStack(self, cx, cy, stacks, dragstack):
|
||||||
|
closest, cdist = None, 999999999
|
||||||
|
# Since we only compare distances,
|
||||||
|
# we don't bother to take the square root.
|
||||||
|
for stack in stacks:
|
||||||
|
# Interlock special: do not consider stacks
|
||||||
|
# outside the bottom row that have been emptied.
|
||||||
|
if len(stack.cards) == 0 and stack in self.s.rows[self.min_rows:]:
|
||||||
|
continue
|
||||||
|
dist = (stack.x - cx)**2 + (stack.y - cy)**2
|
||||||
|
if dist < cdist:
|
||||||
|
closest, cdist = stack, dist
|
||||||
|
return closest
|
||||||
|
|
||||||
|
|
||||||
|
# ************************************************************************
|
||||||
|
# * Love A Duck
|
||||||
|
# ************************************************************************
|
||||||
|
|
||||||
|
class LoveADuck_RowStack(Interlock_StackMethods, Yukon_AC_RowStack):
|
||||||
|
def acceptsCards(self, from_stack, cards):
|
||||||
|
if len(self.cards) == 0 and self.id > self.STEP[0] - 1:
|
||||||
|
return False
|
||||||
|
if (self.isDropdownMove(from_stack) and
|
||||||
|
len(cards) == len(from_stack.cards)):
|
||||||
|
return self.dropdownAcceptsCards(cards)
|
||||||
|
|
||||||
|
return Yukon_AC_RowStack.acceptsCards(self, from_stack, cards)
|
||||||
|
|
||||||
|
def dropdownAcceptsCards(self, cards):
|
||||||
|
if self.cards and not self._isYukonSequence(self.cards[-1], cards[0]):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class LoveADuck(Interlock):
|
||||||
|
RowStack_Class = StackWrapper(LoveADuck_RowStack, base_rank=KING)
|
||||||
|
Talon_Class = InitialDealTalonStack
|
||||||
|
Waste_Class = None
|
||||||
|
Hint_Class = Yukon_Hint
|
||||||
|
|
||||||
|
PLAYCARDS = 25
|
||||||
|
|
||||||
|
TEXT = False
|
||||||
|
WASTE = False
|
||||||
|
|
||||||
|
def startGame(self):
|
||||||
|
self.startDealSample()
|
||||||
|
self.s.talon.dealRow(rows=self.s.rows[:19], flip=1, frames=0)
|
||||||
|
for i in range(2):
|
||||||
|
self.s.talon.dealRow(rows=self.s.rows[19:], flip=1, frames=0)
|
||||||
|
self.s.talon.dealRow(rows=self.s.rows[19:])
|
||||||
|
self.s.talon.dealCards() # deal first card to WasteStack
|
||||||
|
|
||||||
|
|
||||||
|
# ************************************************************************
|
||||||
|
# * Guardian
|
||||||
|
# ************************************************************************
|
||||||
|
|
||||||
|
class Guardian_RowStack(Interlock_RowStack):
|
||||||
|
STEP = (3, 3, 3, 4, 4, 4, 4)
|
||||||
|
|
||||||
|
|
||||||
|
class Guardian(Interlock):
|
||||||
|
RowStack_Class = StackWrapper(Guardian_RowStack, base_rank=ANY_RANK)
|
||||||
|
Talon_Class = StackWrapper(WasteTalonStack, num_deal=3, max_rounds=-1)
|
||||||
|
|
||||||
|
MAX_ROWS = 5
|
||||||
|
|
||||||
|
def startGame(self):
|
||||||
|
self.startDealSample()
|
||||||
|
self.s.talon.dealRow(rows=self.s.rows[:7], flip=0, frames=0)
|
||||||
|
self.s.talon.dealRow(rows=self.s.rows[7:])
|
||||||
|
self.s.talon.dealCards() # deal first card to WasteStack
|
||||||
|
|
||||||
|
|
||||||
|
# register the game
|
||||||
|
registerGame(GameInfo(852, Guardian, "Guardian",
|
||||||
|
GI.GT_KLONDIKE, 1, -1, GI.SL_BALANCED))
|
||||||
|
registerGame(GameInfo(938, Interlock, "Interlock",
|
||||||
|
GI.GT_KLONDIKE | GI.GT_ORIGINAL, 1, -1, GI.SL_BALANCED))
|
||||||
|
registerGame(GameInfo(939, LoveADuck, "Love a Duck",
|
||||||
|
GI.GT_YUKON | GI.GT_OPEN, 1, 0, GI.SL_BALANCED))
|
|
@ -1555,66 +1555,6 @@ class EightSages(Klondike):
|
||||||
self.s.talon.dealCards()
|
self.s.talon.dealCards()
|
||||||
|
|
||||||
|
|
||||||
# ************************************************************************
|
|
||||||
# * Guardian
|
|
||||||
# ************************************************************************
|
|
||||||
|
|
||||||
class Guardian_RowStack(AC_RowStack):
|
|
||||||
STEP = (3, 3, 3, 4, 4, 4, 4)
|
|
||||||
|
|
||||||
def basicIsBlocked(self):
|
|
||||||
r, step = self.game.s.rows, self.STEP
|
|
||||||
i, n, mylen = self.id, 1, len(step)
|
|
||||||
while i < mylen:
|
|
||||||
i = i + step[i]
|
|
||||||
n = n + 1
|
|
||||||
for j in range(i, i + n):
|
|
||||||
if r[j].cards:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def acceptsCards(self, from_stack, cards):
|
|
||||||
if len(self.cards) == 0 and self.id > 2:
|
|
||||||
return False
|
|
||||||
return AC_RowStack.acceptsCards(self, from_stack, cards)
|
|
||||||
|
|
||||||
|
|
||||||
class Guardian(Game):
|
|
||||||
|
|
||||||
def createGame(self):
|
|
||||||
lay, s = Layout(self), self.s
|
|
||||||
self.setSize((7 * lay.XS) + lay.XM,
|
|
||||||
(2.5 * lay.YS) + (13 * lay.YOFFSET) + lay.YM)
|
|
||||||
|
|
||||||
# create stacks
|
|
||||||
for i in range(3):
|
|
||||||
x = lay.XM + (4 - i) * lay.XS // 2
|
|
||||||
y = lay.YM + lay.TEXT_HEIGHT + lay.YS + i * lay.YS // 4
|
|
||||||
for j in range(i + 3):
|
|
||||||
s.rows.append(Guardian_RowStack(x, y, self))
|
|
||||||
x = x + lay.XS
|
|
||||||
|
|
||||||
x, y = lay.XM, lay.YM
|
|
||||||
s.talon = WasteTalonStack(x, y, self,
|
|
||||||
max_rounds=-1, num_deal=3)
|
|
||||||
lay.createText(s.talon, "s")
|
|
||||||
x += lay.XS
|
|
||||||
s.waste = WasteStack(x, y, self)
|
|
||||||
lay.createText(s.waste, "s")
|
|
||||||
x += lay.XS
|
|
||||||
for i in range(4):
|
|
||||||
x += lay.XS
|
|
||||||
s.foundations.append(SS_FoundationStack(x, y, self, i,
|
|
||||||
mod=13, max_move=0))
|
|
||||||
lay.defaultStackGroups()
|
|
||||||
|
|
||||||
def startGame(self):
|
|
||||||
self.startDealSample()
|
|
||||||
self.s.talon.dealRow(rows=self.s.rows[:7], flip=0)
|
|
||||||
self.s.talon.dealRow(rows=self.s.rows[7:])
|
|
||||||
self.s.talon.dealCards() # deal first card to WasteStack
|
|
||||||
|
|
||||||
|
|
||||||
# register the game
|
# register the game
|
||||||
registerGame(GameInfo(2, Klondike, "Klondike",
|
registerGame(GameInfo(2, Klondike, "Klondike",
|
||||||
GI.GT_KLONDIKE, 1, -1, GI.SL_BALANCED,
|
GI.GT_KLONDIKE, 1, -1, GI.SL_BALANCED,
|
||||||
|
@ -1770,8 +1710,6 @@ registerGame(GameInfo(821, Trigon, "Trigon",
|
||||||
registerGame(GameInfo(849, RelaxedRaglan, "Relaxed Raglan",
|
registerGame(GameInfo(849, RelaxedRaglan, "Relaxed Raglan",
|
||||||
GI.GT_RAGLAN | GI.GT_RELAXED | GI.GT_OPEN, 1, 0,
|
GI.GT_RAGLAN | GI.GT_RELAXED | GI.GT_OPEN, 1, 0,
|
||||||
GI.SL_MOSTLY_SKILL))
|
GI.SL_MOSTLY_SKILL))
|
||||||
registerGame(GameInfo(852, Guardian, "Guardian",
|
|
||||||
GI.GT_KLONDIKE, 1, -1, GI.SL_BALANCED))
|
|
||||||
registerGame(GameInfo(855, HalfKlondike, "Half Klondike",
|
registerGame(GameInfo(855, HalfKlondike, "Half Klondike",
|
||||||
GI.GT_KLONDIKE | GI.GT_STRIPPED, 1, -1, GI.SL_BALANCED,
|
GI.GT_KLONDIKE | GI.GT_STRIPPED, 1, -1, GI.SL_BALANCED,
|
||||||
suits=(1, 2)))
|
suits=(1, 2)))
|
||||||
|
|
Loading…
Add table
Reference in a new issue