diff --git a/html-src/gen-html.py b/html-src/gen-html.py
index f64b754d..93516173 100755
--- a/html-src/gen-html.py
+++ b/html-src/gen-html.py
@@ -79,6 +79,7 @@ rules_files = [
('lightsout.html', 'PySol - Rules for Lights Out'),
('fourrivers.html', 'PySol - Rules for Four Rivers'),
('tilepuzzle.html', 'PySol - Rules for Tile Puzzle'),
+ ('samegame.html', 'PySol - Rules for Samegame'),
]
diff --git a/html-src/rules/samegame.html b/html-src/rules/samegame.html
new file mode 100644
index 00000000..9c2d09bb
--- /dev/null
+++ b/html-src/rules/samegame.html
@@ -0,0 +1,17 @@
+
Samegame
+
+Samegame is a single-player tile matching game.
+
+
Object
+
+The object of the game is to remove all tiles from the field.
+
+
Rules
+
+Tiles of a number of different colors are dealt to the board. You
+may remove a group of two or more adjacent tiles of the same color.
+Once a group is removed, all tiles directly above them will fall to
+fill in the gap. If a column is emptied, the adjacent columns will
+slide to the left to fill in the gap.
+
+The game is won if you manage to clear all of the tiles.
diff --git a/po/de_pysol.po b/po/de_pysol.po
index 69557fe2..9a0f489b 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-02-11 11:04-0500\n"
+"PO-Revision-Date: 2024-03-03 11:34-0500\n"
"Last-Translator: H. Schaekel \n"
"Language-Team: German\n"
"Language: de\n"
@@ -478,6 +478,9 @@ msgstr ""
msgid "Puzzle"
msgstr ""
+msgid "Samegame"
+msgstr ""
+
msgid "Shisen-Sho"
msgstr ""
@@ -627,6 +630,9 @@ msgstr ""
msgid "Ishido type"
msgstr ""
+msgid "Samegame type"
+msgstr ""
+
#: pysollib/help.py:43
msgid "A Python Solitaire Game Collection"
msgstr "Eine Python Spielesammlung"
diff --git a/po/fr_pysol.po b/po/fr_pysol.po
index e806703e..998fd503 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-02-11 11:04-0500\n"
+"PO-Revision-Date: 2024-03-03 11:34-0500\n"
"Last-Translator: Eric Rausch \n"
"Language-Team: French\n"
"Language: fr\n"
@@ -484,6 +484,9 @@ msgstr ""
msgid "Puzzle"
msgstr ""
+msgid "Samegame"
+msgstr ""
+
msgid "Shisen-Sho"
msgstr ""
@@ -633,6 +636,9 @@ msgstr ""
msgid "Ishido type"
msgstr ""
+msgid "Samegame type"
+msgstr ""
+
#: pysollib/help.py:43
msgid "A Python Solitaire Game Collection"
msgstr "Une collection de jeux de solitaire en Python"
diff --git a/po/it_pysol.po b/po/it_pysol.po
index d0b19210..790eca68 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-02-11 11:03-0500\n"
+"PO-Revision-Date: 2024-03-03 11:35-0500\n"
"Last-Translator: Giuliano Colla \n"
"Language-Team: Italiano \n"
"Language: it\n"
@@ -490,6 +490,9 @@ msgstr ""
msgid "Puzzle"
msgstr ""
+msgid "Samegame"
+msgstr ""
+
msgid "Shisen-Sho"
msgstr ""
@@ -639,6 +642,9 @@ msgstr ""
msgid "Ishido type"
msgstr ""
+msgid "Samegame type"
+msgstr ""
+
#: pysollib/help.py:43
msgid "A Python Solitaire Game Collection"
msgstr "Una raccolta di solitari in Python"
diff --git a/po/pl_pysol.po b/po/pl_pysol.po
index 442b4893..eeb2930a 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-02-11 11:03-0500\n"
+"PO-Revision-Date: 2024-03-03 11:35-0500\n"
"Last-Translator: Jerzy Trzeciak \n"
"Language-Team: Polish \n"
"Language: pl\n"
@@ -484,6 +484,9 @@ msgstr "Poker"
msgid "Puzzle"
msgstr ""
+msgid "Samegame"
+msgstr ""
+
msgid "Shisen-Sho"
msgstr ""
@@ -631,6 +634,9 @@ msgstr "Gra typu Wieża Hanoi"
msgid "Ishido type"
msgstr ""
+msgid "Samegame type"
+msgstr ""
+
#: pysollib/help.py:43
msgid "A Python Solitaire Game Collection"
msgstr "Kolekcja gier Python Solitaire"
diff --git a/po/pt_BR_pysol.po b/po/pt_BR_pysol.po
index 940c4c33..9b53e9c9 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-02-11 11:02-0500\n"
+"PO-Revision-Date: 2024-03-03 11:36-0500\n"
"Last-Translator: Matheus Knack \n"
"Language-Team: \n"
"Language: pt_BR\n"
@@ -483,6 +483,9 @@ msgstr "Pôquer"
msgid "Puzzle"
msgstr ""
+msgid "Samegame"
+msgstr ""
+
msgid "Shisen-Sho"
msgstr "Mahjong Shinsen-Sho"
@@ -630,6 +633,9 @@ msgstr "estilo Torre de Lucas"
msgid "Ishido type"
msgstr ""
+msgid "Samegame type"
+msgstr ""
+
#: pysollib/help.py:43
msgid "A Python Solitaire Game Collection"
msgstr "Coleção de Jogos de Paciência Python"
diff --git a/po/pysol.pot b/po/pysol.pot
index 860db9ff..1c966109 100644
--- a/po/pysol.pot
+++ b/po/pysol.pot
@@ -458,6 +458,9 @@ msgstr ""
msgid "Puzzle"
msgstr ""
+msgid "Samegame"
+msgstr ""
+
msgid "Shisen-Sho"
msgstr ""
@@ -605,6 +608,9 @@ msgstr ""
msgid "Ishido type"
msgstr ""
+msgid "Samegame type"
+msgstr ""
+
#: pysollib/help.py:43
msgid "A Python Solitaire Game Collection"
msgstr ""
diff --git a/po/ru_pysol.po b/po/ru_pysol.po
index f06a74bf..0406d7b6 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-02-11 11:02-0500\n"
+"PO-Revision-Date: 2024-03-03 11:36-0500\n"
"Last-Translator: Skomoroh \n"
"Language-Team: Russian \n"
"Language: ru\n"
@@ -487,6 +487,9 @@ msgstr ""
msgid "Puzzle"
msgstr ""
+msgid "Samegame"
+msgstr ""
+
msgid "Shisen-Sho"
msgstr ""
@@ -636,6 +639,9 @@ msgstr ""
msgid "Ishido type"
msgstr ""
+msgid "Samegame type"
+msgstr ""
+
#: pysollib/help.py:43
msgid "A Python Solitaire Game Collection"
msgstr "Коллекция питоновских пасьянсов"
diff --git a/pysollib/game/__init__.py b/pysollib/game/__init__.py
index 9fa92a58..efe015a6 100644
--- a/pysollib/game/__init__.py
+++ b/pysollib/game/__init__.py
@@ -3495,7 +3495,8 @@ class Game(object):
# for find_card_dialog
def canFindCard(self):
- return self.gameinfo.category not in (GI.GC_MATCHING, GI.GC_PUZZLE)
+ return self.gameinfo.category not in (GI.GC_MATCHING, GI.GC_PUZZLE) \
+ and self.gameinfo.si.game_type != GI.GT_SAMEGAME
def canShowFullPicture(self):
return self.gameinfo.category == GI.GC_PUZZLE
diff --git a/pysollib/gamedb.py b/pysollib/gamedb.py
index 2ad188a3..31912bca 100644
--- a/pysollib/gamedb.py
+++ b/pysollib/gamedb.py
@@ -109,6 +109,7 @@ class GI:
GT_PUZZLE_TYPE = 26
GT_RAGLAN = 27
GT_ROW_TYPE = 28
+ GT_SAMEGAME = 42
GT_SHISEN_SHO = 34
GT_SIMPLE_TYPE = 29
GT_SPIDER = 30
@@ -177,6 +178,7 @@ class GI:
GT_PEGGED: n_("Pegged"),
GT_POKER_TYPE: n_("Poker"),
GT_PUZZLE_TYPE: n_("Puzzle"),
+ GT_SAMEGAME: n_("Samegame"),
GT_SHISEN_SHO: n_("Shisen-Sho"),
GT_TAROCK: n_("Tarock"),
GT_HANOI: n_("Tower of Hanoi"),
@@ -299,6 +301,8 @@ class GI:
(n_("Poker type"), lambda gi, gt=GT_POKER_TYPE: gi.si.game_type == gt),
(n_("Puzzle type"),
lambda gi, gt=GT_PUZZLE_TYPE: gi.si.game_type == gt),
+ (n_("Samegame type"),
+ lambda gi, gt=GT_SAMEGAME: gi.si.game_type == gt),
(n_("Shisen-Sho type"),
lambda gi, gt=GT_SHISEN_SHO: gi.si.game_type == gt),
(n_("Tarock type"), lambda gi, gt=GT_TAROCK: gi.si.game_type == gt),
@@ -596,7 +600,8 @@ class GI:
tuple(range(13160, 13163)) + (16682,)),
('dev', tuple(range(906, 952)) + tuple(range(11017, 11020)) +
tuple(range(5600, 5624)) + tuple(range(18000, 18005)) +
- tuple(range(22303, 22311)) + tuple(range(22353, 22361))),
+ tuple(range(19000, 19012)) + tuple(range(22303, 22311)) +
+ tuple(range(22353, 22361))),
)
# deprecated - the correct way is to or a GI.GT_XXX flag
diff --git a/pysollib/games/special/__init__.py b/pysollib/games/special/__init__.py
index f0617f3b..f9f5eadc 100644
--- a/pysollib/games/special/__init__.py
+++ b/pysollib/games/special/__init__.py
@@ -34,6 +34,7 @@ from . import memory # noqa: F401
from . import mughal # noqa: F401
from . import pegged # noqa: F401
from . import poker # noqa: F401
+from . import samegame # noqa: F401
from . import tarock # noqa: F401
from . import tarock1 # noqa: F401
from . import tilepuzzle # noqa: F401
diff --git a/pysollib/games/special/samegame.py b/pysollib/games/special/samegame.py
new file mode 100644
index 00000000..34552e5f
--- /dev/null
+++ b/pysollib/games/special/samegame.py
@@ -0,0 +1,330 @@
+#!/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 .
+#
+# ---------------------------------------------------------------------------
+
+from pysollib.game import Game
+from pysollib.gamedb import GI, GameInfo, registerGame
+from pysollib.hint import AbstractHint
+from pysollib.layout import Layout
+from pysollib.mfxutil import kwdefault
+from pysollib.pysoltk import Card, MfxCanvasText
+from pysollib.settings import TOOLKIT
+from pysollib.stack import \
+ AbstractFoundationStack, \
+ InitialDealTalonStack, \
+ OpenStack
+from pysollib.util import ANY_SUIT
+
+from six.moves import range
+
+
+# ************************************************************************
+# * Samegame
+# ************************************************************************
+
+class Samegame_Hint(AbstractHint):
+ # FIXME: no intelligence whatsoever is implemented here
+
+ def computeHints(self):
+ game = self.game
+ for r in game.s.rows:
+ if r.cards:
+ removeStacks = r.getRemoveStacks()
+ score = 100 * len(removeStacks)
+ if score > 100:
+ self.addHint(score, 1, r, game.s.foundations[0])
+
+
+class Samegame_Foundation(AbstractFoundationStack):
+ def __init__(self, x, y, game, suit=ANY_SUIT, **cap):
+ kwdefault(cap, max_move=0, max_accept=0, max_cards=game.NCARDS)
+ AbstractFoundationStack.__init__(self, x, y, game, suit, **cap)
+
+ def acceptsCards(self, from_stack, cards):
+ return 1
+
+
+class Samegame_RowStack(OpenStack):
+ def clickHandler(self, event):
+ if len(self.cards) == 0:
+ return False
+ self.playMoveMove(1, self.game.s.foundations[0], sound=False)
+
+ def rightclickHandler(self, event):
+ return self.clickHandler(event)
+
+ def moveMove(self, ncards, to_stack, frames=-1, shadow=-1):
+ assert ncards == 1
+ if to_stack in self.game.s.foundations:
+ game = self.game
+ removeStacks = self.getRemoveStacks()
+ if len(removeStacks) < 2:
+ return False
+
+ old_state = game.enterState(game.S_FILL)
+ game.updateStackMove(self, 2 | 16)
+ for stack in removeStacks:
+ game.moveMove(1, stack, game.s.foundations[0], frames=0)
+ for stack in removeStacks:
+ stack.fillStack()
+ game.slideStacks()
+
+ if not game.demo:
+ game.playSample("drop", priority=200)
+
+ game.updateStackMove(self, 1 | 16) # for redo
+ game.leaveState(old_state)
+ return True
+ else:
+ return OpenStack.moveMove(self, ncards, to_stack, frames=frames,
+ shadow=shadow)
+
+ def fillStack(self):
+ self.game.fillStack(self)
+
+ def getRemoveStacks(self):
+ removeStacks = [self]
+ spotsChecked = 0
+ while spotsChecked < len(removeStacks):
+ adjacent = self.getAdjacent(removeStacks[spotsChecked].id)
+ for adjacentStack in adjacent:
+ if adjacentStack not in removeStacks and \
+ len(adjacentStack.cards) > 0 and \
+ adjacentStack.cards[0].suit == self.cards[0].suit:
+ removeStacks.append(adjacentStack)
+ spotsChecked += 1
+ return removeStacks
+
+ def getAdjacent(self, playSpace):
+ cols, rows = self.game.L
+ s = self.game.s
+ adjacentRows = []
+ if playSpace % rows != (rows - 1):
+ adjacentRows.append(s.rows[playSpace + 1])
+
+ if playSpace % rows != 0:
+ adjacentRows.append(s.rows[playSpace - 1])
+
+ if playSpace + rows < (cols * rows):
+ adjacentRows.append(s.rows[playSpace + rows])
+
+ if playSpace - rows > -1:
+ adjacentRows.append(s.rows[playSpace - rows])
+
+ return adjacentRows
+
+
+class AbstractSamegameGame(Game):
+ Hint_Class = Samegame_Hint
+ RowStack_Class = Samegame_RowStack
+
+ COLORS = 3
+ NCARDS = 144
+
+ def createGame(self):
+ cols, rows = self.L
+ assert cols*rows == self.NCARDS
+
+ # start layout
+ l, s = Layout(self), self.s
+ # dx, dy = 3, -3
+
+ cs = self.app.images.cs
+ if cs.version == 6 or cs.mahjongg3d:
+ dx = l.XOFFSET
+ dy = -l.YOFFSET
+ d_x = cs.SHADOW_XOFFSET
+ d_y = cs.SHADOW_YOFFSET
+ self._delta_x, self._delta_y = dx, -dy
+ else:
+ dx = 3
+ dy = -3
+ d_x = 0
+ d_y = 0
+ self._delta_x, self._delta_y = 0, 0
+ # TODO - This should be moved to subsample logic in the future.
+ if self.preview > 1:
+ d_x /= 2
+ d_y /= 2
+
+ font = self.app.getFont("canvas_default")
+
+ # set window size
+ dxx, dyy = abs(dx), abs(dy)
+ cardw, cardh = l.CW - d_x, l.CH - d_y
+ w = l.XM + dxx + cols * cardw + d_x + l.XM + l.XM
+ h = l.YM + dyy + rows * cardh + d_y + l.YM
+ self.setSize(w, h)
+
+ #
+ self.cols = [[] for i in range(cols)]
+ cl = range(cols)
+ for col in cl:
+ for row in range(rows):
+ x = l.XM + dxx + col * cardw
+ y = l.YM + dyy + row * cardh
+ stack = self.RowStack_Class(x, y, self)
+ stack.CARD_XOFFSET = 0
+ stack.CARD_YOFFSET = 0
+ stack.coln, stack.rown = col, row
+ s.rows.append(stack)
+ self.cols[col].append(stack)
+
+ # create other stacks
+ y = l.YM + dyy
+ ivx = -l.XS-self.canvas.xmargin
+ if TOOLKIT == 'kivy':
+ ivx = -1000
+ s.foundations.append(Samegame_Foundation(ivx, y, self))
+ self.texts.info = MfxCanvasText(self.canvas,
+ self.width - l.XM, y,
+ anchor="nw", font=font)
+ # the Talon is invisble
+ s.talon = InitialDealTalonStack(-l.XS-self.canvas.xmargin,
+ self.height-dyy, self)
+ # Define stack groups
+ l.defaultStackGroups()
+
+ def startGame(self):
+ assert len(self.s.talon.cards) == self.NCARDS
+ # self.s.talon.dealRow(rows = self.s.rows, frames = 0)
+ n = 12
+ self.s.talon.dealRow(rows=self.s.rows[:self.NCARDS-n], frames=0)
+ self.startDealSample()
+ self.s.talon.dealRow(rows=self.s.rows[self.NCARDS-n:])
+ assert len(self.s.talon.cards) == 0
+
+ def _createCard(self, id, deck, suit, rank, x, y):
+ return Card(id, deck, id % self.COLORS, id % self.COLORS,
+ game=self, x=x, y=y)
+
+ def fillStack(self, stack):
+ to_stack = stack
+ for from_stack in self.cols[stack.coln][stack.rown+1::-1]:
+ if not from_stack.cards:
+ continue
+ self.moveMove(1, from_stack, to_stack, frames=0)
+ to_stack = from_stack
+
+ def slideStacks(self):
+ # Slide to the left to fill empty columns.
+ numrows = self.L[1]
+ card = 0
+ emptycols = 0
+ for c in range(len(self.cols)):
+ iscolempty = True
+ colstart = card
+ for r in range(numrows):
+ if len(self.s.rows[card].cards) > 0:
+ iscolempty = False
+ card += 1
+ if iscolempty:
+ emptycols += 1
+ elif emptycols > 0:
+ for r in range(colstart, card):
+ if len(self.s.rows[r].cards) > 0:
+ self.moveMove(1, self.s.rows[r],
+ self.s.rows[r - (numrows * emptycols)],
+ frames=0)
+
+
+class Samegame3_20x10(AbstractSamegameGame):
+ L = (20, 10)
+ NCARDS = 200
+
+
+class Samegame3_15x10(AbstractSamegameGame):
+ L = (15, 10)
+ NCARDS = 150
+
+
+class Samegame3_25x15(AbstractSamegameGame):
+ L = (25, 15)
+ NCARDS = 375
+
+
+class Samegame4_20x10(Samegame3_20x10):
+ COLORS = 4
+
+
+class Samegame4_15x10(Samegame3_15x10):
+ COLORS = 4
+
+
+class Samegame4_25x15(Samegame3_25x15):
+ COLORS = 4
+
+
+class Samegame5_20x10(Samegame3_20x10):
+ COLORS = 5
+
+
+class Samegame5_15x10(Samegame3_15x10):
+ COLORS = 5
+
+
+class Samegame5_25x15(Samegame3_25x15):
+ COLORS = 5
+
+
+class Samegame6_20x10(Samegame3_20x10):
+ COLORS = 6
+
+
+class Samegame6_15x10(Samegame3_15x10):
+ COLORS = 6
+
+
+class Samegame6_25x15(Samegame3_25x15):
+ COLORS = 6
+
+
+# ************************************************************************
+# * register a Samegame type game
+# ************************************************************************
+
+def r(id, gameclass, name, rules_filename="samegame.html"):
+ gi = GameInfo(id, gameclass, name,
+ GI.GT_SAMEGAME, 1, 0, GI.SL_MOSTLY_SKILL,
+ category=GI.GC_ISHIDO, short_name=name,
+ suits=list(range(1)), ranks=list(range(gameclass.NCARDS)),
+ si={"decks": 1, "ncards": gameclass.NCARDS})
+ gi.ncards = gameclass.NCARDS
+ gi.rules_filename = rules_filename
+ registerGame(gi)
+ return gi
+
+
+r(19000, Samegame3_15x10, "Samegame 3 Colors 15x10")
+r(19001, Samegame4_15x10, "Samegame 4 Colors 15x10")
+r(19002, Samegame5_15x10, "Samegame 5 Colors 15x10")
+r(19003, Samegame6_15x10, "Samegame 6 Colors 15x10")
+r(19004, Samegame3_20x10, "Samegame 3 Colors 20x10")
+r(19005, Samegame4_20x10, "Samegame 4 Colors 20x10")
+r(19006, Samegame5_20x10, "Samegame 5 Colors 20x10")
+r(19007, Samegame6_20x10, "Samegame 6 Colors 20x10")
+r(19008, Samegame3_25x15, "Samegame 3 Colors 25x15")
+r(19009, Samegame4_25x15, "Samegame 4 Colors 25x15")
+r(19010, Samegame5_25x15, "Samegame 5 Colors 25x15")
+r(19011, Samegame6_25x15, "Samegame 6 Colors 25x15")
+
+del r