mirror of
https://github.com/shlomif/PySolFC.git
synced 2025-04-05 00:02:29 -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,
|
||||
800, 814, 820, 825, 889, 911, 926)),
|
||||
("Mary Whitmore Jones", (421, 624,)),
|
||||
("Jan Wolter", (917,)),
|
||||
("Jan Wolter", (917, 939,)),
|
||||
)
|
||||
|
||||
GAMES_BY_PYSOL_VERSION = (
|
||||
|
@ -592,7 +592,7 @@ class GI:
|
|||
('fc-2.20', tuple(range(855, 897))),
|
||||
('fc-2.21', tuple(range(897, 900)) + tuple(range(11014, 11017)) +
|
||||
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(22303, 22311)) + tuple(range(22353, 22361))),
|
||||
)
|
||||
|
|
|
@ -56,6 +56,7 @@ from . import gypsy # noqa: F401
|
|||
from . import harp # noqa: F401
|
||||
from . import headsandtails # noqa: F401
|
||||
from . import hitormiss # noqa: F401
|
||||
from . import interlock # noqa: F401
|
||||
from . import katzenschwanz # noqa: F401
|
||||
from . import klondike # 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()
|
||||
|
||||
|
||||
# ************************************************************************
|
||||
# * 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
|
||||
registerGame(GameInfo(2, Klondike, "Klondike",
|
||||
GI.GT_KLONDIKE, 1, -1, GI.SL_BALANCED,
|
||||
|
@ -1770,8 +1710,6 @@ registerGame(GameInfo(821, Trigon, "Trigon",
|
|||
registerGame(GameInfo(849, RelaxedRaglan, "Relaxed Raglan",
|
||||
GI.GT_RAGLAN | GI.GT_RELAXED | GI.GT_OPEN, 1, 0,
|
||||
GI.SL_MOSTLY_SKILL))
|
||||
registerGame(GameInfo(852, Guardian, "Guardian",
|
||||
GI.GT_KLONDIKE, 1, -1, GI.SL_BALANCED))
|
||||
registerGame(GameInfo(855, HalfKlondike, "Half Klondike",
|
||||
GI.GT_KLONDIKE | GI.GT_STRIPPED, 1, -1, GI.SL_BALANCED,
|
||||
suits=(1, 2)))
|
||||
|
|
Loading…
Add table
Reference in a new issue