Skip to content

Commit

Permalink
Added Devil's Grip game.
Browse files Browse the repository at this point in the history
  • Loading branch information
joeraz committed Nov 21, 2023
1 parent 7d1a705 commit 9fb0e33
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 12 deletions.
39 changes: 39 additions & 0 deletions html-src/rules/devilsgrip.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<h1>Devil's Grip</h1>
<p>
Picture Gallery type. 2 decks. No redeal.

<h3>Object</h3>
<p>
Move all cards to the foundations.

<h3>Rules</h3>
<p>
Devil's Grip is played without the aces. Single cards are dealt to
three rows of eight cards each.
<p>
The rows are built up in same suit by threes, so in sequences of the
following:
<ul>
<li>2-5-8-Jack
<li>3-6-9-Queen
<li>4-7-10-King
</ul>
<p>
Sequences starting in a two must be in the top row, sequences starting
with a three must be in the middle row, and sequences starting with a
four must be in the bottom row. You may start building sequences even
if they are in the wrong row, but they must ultimately be moved to the
correct row to win the game.
<p>
Any card or valid sequence of cards can be moved between piles. Piles
may be switched with each other in order to get a sequence starting with
a 2, 3, or 4 into the correct row. Empty piles are immediately filled
from the waste or talon (this is the only way the remaining 2s, 3s, and 4s
can be moved into the rows).
<p>
When there are no moves left, you can deal cards from the talon three at
a time, and move the top card to appropriate row piles. No redeal is
allowed.
<p>
The game is won if you're able to move all cards to the rows, and all the
piles are placed correctly.
2 changes: 1 addition & 1 deletion pysollib/gamedb.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ def _callback(gi, gt=game_type):
('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, 932)) + tuple(range(11017, 11020)) +
('dev', tuple(range(906, 933)) + tuple(range(11017, 11020)) +
tuple(range(5600, 5624)) + tuple(range(18000, 18004)) +
tuple(range(22303, 22311)) + tuple(range(22353, 22361))),
)
Expand Down
122 changes: 111 additions & 11 deletions pysollib/games/picturegallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,13 @@ def canFlipCard(self):


class PictureGallery_TableauStack(SS_RowStack):
max_accept = 1

def __init__(self, x, y, game, base_rank, yoffset, dir=3, max_cards=4):
SS_RowStack.__init__(
self, x, y, game,
base_rank=base_rank, dir=dir, max_cards=max_cards, max_accept=1)
base_rank=base_rank, dir=dir, max_cards=max_cards,
max_accept=self.max_accept)
self.CARD_YOFFSET = yoffset

def acceptsCards(self, from_stack, cards):
Expand Down Expand Up @@ -201,6 +204,8 @@ class PictureGallery(Game):
RowStack_Class = StackWrapper(PictureGallery_RowStack, max_accept=1)
Talon_Class = DealRowTalonStack

NORMAL_OFFSET = False

#
# game layout
#
Expand All @@ -210,34 +215,46 @@ def createGame(self, waste=False, numstacks=8):
# create layout
l, s = Layout(self), self.s
numtableau = (4 * self.gameinfo.decks)
TABLEAU_YOFFSET = min(numtableau + 1, max(3, l.YOFFSET // 3))
if not self.NORMAL_OFFSET:
TABLEAU_YOFFSET = min(numtableau + 1, max(3, l.YOFFSET // 3))
else:
TABLEAU_YOFFSET = l.YOFFSET

# set window
th = l.YS + ((numtableau + 4) // rows - 1) * TABLEAU_YOFFSET
# (set piles so that at least 2/3 of a card is visible with 10 cards)
h = ((numtableau + 2) - 1) * l.YOFFSET + l.CH * 2 // 3
if self.Foundation_Class is None and self.RowStack_Class is None:
h = 0
else:
# (set piles so at least 2/3 of a card is visible with 10 cards)
h = ((numtableau + 2) - 1) * l.YOFFSET + l.CH * 2 // 3
self.setSize((numtableau + 2) * l.XS + l.XM, l.YM + 3 * th + l.YM + h)

# create stacks
s.addattr(tableaux=[]) # register extra stack variable
x = l.XM + numtableau * l.XS + l.XS // 2
y = l.YM + l.CH // 2
s.foundations.append(self.Foundation_Class(x, y, self))
if self.Foundation_Class is not None:
s.foundations.append(self.Foundation_Class(x, y, self))
y = l.YM
for cl in self.TableauStack_Classes:
x = l.XM
for j in range(numtableau):
s.tableaux.append(cl(x, y, self, yoffset=TABLEAU_YOFFSET))
x = x + l.XS
y = y + th
self.setRegion(s.foundations, (x - l.CW // 2, -999, 999999, y - l.CH))
if self.Foundation_Class is not None:
self.setRegion(s.foundations, (x - l.CW // 2, -999, 999999,
y - l.CH))
x, y = l.XM, y + l.YM
for i in range(numstacks):
s.rows.append(self.RowStack_Class(x, y, self))
x = x + l.XS
if self.RowStack_Class is not None:
for i in range(numstacks):
s.rows.append(self.RowStack_Class(x, y, self))
x = x + l.XS
# self.setRegion(s.rows, (-999, -999, x - l.CW // 2, 999999))
x = l.XM + numstacks * l.XS + l.XS // 2
y = self.height - l.YS
if self.RowStack_Class is None and self.Foundation_Class is None:
y = l.YM + l.YS + l.CH // 2
s.talon = self.Talon_Class(x, y, self)
l.createText(s.talon, "se")
if waste:
Expand Down Expand Up @@ -530,8 +547,8 @@ class RoyalParade(PictureGallery):
]
RowStack_Class = StackWrapper(BasicRowStack, max_accept=0)

def createGame(self, numstacks=8):
PictureGallery.createGame(self, numstacks=numstacks)
def createGame(self, waste=False, numstacks=8):
PictureGallery.createGame(self, waste=waste, numstacks=numstacks)
self.s.internals.append(InvisibleStack(self))

def startGame(self):
Expand Down Expand Up @@ -597,6 +614,84 @@ def createGame(self):
VirginiaReel.createGame(self, numstacks=12)


# ************************************************************************
# * Devil's Grip
# ************************************************************************

class DevilsGrip_TableauStack(RoyalParade_TableauStack):
max_accept = 4

def _canSwapPair(self, from_stack):
if from_stack not in self.game.s.tableaux:
return False
if len(self.cards) == 0 or len(from_stack.cards) == 0:
return False
if self.cap.base_rank == from_stack.cap.base_rank:
return False
c0, c1 = from_stack.cards[0], self.cards[0]
return (c0.rank != c1.rank and
(c0.rank == self.cap.base_rank or
c1.rank == from_stack.cap.base_rank))

def acceptsCards(self, from_stack, cards):
if self._canSwapPair(from_stack):
return True
return SS_RowStack.acceptsCards(
self, from_stack, cards)

def _swapPairMove(self, n, other_stack, frames=-1, shadow=-1):
game = self.game
old_state = game.enterState(game.S_FILL)
swap = game.s.internals[0]
game.moveMove(len(self.cards), self, swap, frames=0)
game.moveMove(len(other_stack.cards), other_stack, self,
frames=frames, shadow=shadow)
game.moveMove(len(swap.cards), swap, other_stack, frames=0)
game.leaveState(old_state)


class DevilsGrip(RoyalParade):
Foundation_Class = None
RowStack_Class = None
TableauStack_Classes = [
StackWrapper(DevilsGrip_TableauStack,
base_rank=1, max_cards=4, dir=3),
StackWrapper(DevilsGrip_TableauStack,
base_rank=2, max_cards=4, dir=3),
StackWrapper(DevilsGrip_TableauStack,
base_rank=3, max_cards=4, dir=3),
]
Talon_Class = StackWrapper(WasteTalonStack, max_rounds=1, num_deal=3)

NORMAL_OFFSET = True

def createGame(self):
RoyalParade.createGame(self, waste=True, numstacks=8)

def startGame(self):
self.startDealSample()
self.s.talon.dealRow(rows=self.s.tableaux, frames=0)
self.s.talon.dealCards()

def isGameWon(self):
for stack in self.s.tableaux:
if len(stack.cards) != 4 or \
stack.cards[0].rank != stack.cap.base_rank:
return False
return True

def fillStack(self, stack):
if not stack.cards and stack in self.s.tableaux:
if self.s.waste.cards:
old_state = self.enterState(self.S_FILL)
self.s.waste.moveMove(1, stack)
self.leaveState(old_state)
elif self.s.talon.cards:
old_state = self.enterState(self.S_FILL)
self.s.talon.moveMove(1, stack)
self.leaveState(old_state)


# register the game
registerGame(GameInfo(7, PictureGallery, "Picture Gallery",
GI.GT_PICTURE_GALLERY, 2, 0, GI.SL_BALANCED,
Expand Down Expand Up @@ -626,3 +721,8 @@ def createGame(self):
GI.GT_PICTURE_GALLERY, 3, 0, GI.SL_BALANCED))
registerGame(GameInfo(928, HugePictureGallery, "Huge Picture Gallery",
GI.GT_PICTURE_GALLERY, 4, 0, GI.SL_BALANCED))
registerGame(GameInfo(932, DevilsGrip, "Devil's Grip",
GI.GT_PICTURE_GALLERY | GI.GT_STRIPPED, 2, 0,
GI.SL_MOSTLY_LUCK,
ranks=list(range(1, 13)) # without Aces
))

0 comments on commit 9fb0e33

Please sign in to comment.