From 351abaf9075eba122618d38f8037bcc829ea1f22 Mon Sep 17 00:00:00 2001 From: Joe R Date: Sun, 3 Mar 2024 11:38:13 -0500 Subject: [PATCH] Added Samegame game. --- html-src/gen-html.py | 1 + html-src/rules/samegame.html | 17 ++ po/de_pysol.po | 8 +- po/fr_pysol.po | 8 +- po/it_pysol.po | 8 +- po/pl_pysol.po | 8 +- po/pt_BR_pysol.po | 8 +- po/pysol.pot | 6 + po/ru_pysol.po | 8 +- pysollib/game/__init__.py | 3 +- pysollib/gamedb.py | 7 +- pysollib/games/special/__init__.py | 1 + pysollib/games/special/samegame.py | 330 +++++++++++++++++++++++++++++ 13 files changed, 405 insertions(+), 8 deletions(-) create mode 100644 html-src/rules/samegame.html create mode 100644 pysollib/games/special/samegame.py 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