diff --git a/html-src/gen-html.py b/html-src/gen-html.py
index a4ea96ef..82a739f2 100755
--- a/html-src/gen-html.py
+++ b/html-src/gen-html.py
@@ -71,6 +71,7 @@ rules_files = [
('shisensho.html', 'PySol - Rules for Shisen-Sho'),
('spider.html', 'PySol - Rules for Spider'),
('freecell.html', 'PySol - Rules for FreeCell'),
+ ('lightsout.html', 'PySol - Rules for Lights Out'),
]
wikipedia_files = [
('houseinthewood.html', 'PySol - Rules for House in the Woods'),
diff --git a/html-src/rules/lightsout.html b/html-src/rules/lightsout.html
new file mode 100644
index 00000000..cc56caf4
--- /dev/null
+++ b/html-src/rules/lightsout.html
@@ -0,0 +1,15 @@
+
Lights Out
+Lights Out type. One deck. No redeals.
+
+Object
+Flip all cards face-down.
+
+Rules
+
+A grid of cards are dealt in a grid, with some face-up and
+others face-down. Any single card can be flipped - if a card
+is flipped, all of its adjacent cards horizontally or vertically
+are flipped as well.
+
+The game is won if every card is flipped face-down at the same
+time.
diff --git a/po/de_pysol.po b/po/de_pysol.po
index 9b9d60c2..8daa7c4d 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: 2021-12-28 13:52-0500\n"
+"PO-Revision-Date: 2022-01-01 23:20-0500\n"
"Last-Translator: H. Schaekel \n"
"Language-Team: German\n"
"Language: de\n"
@@ -453,6 +453,9 @@ msgstr "4 Deck Spiele"
msgid "Cribbage"
msgstr ""
+msgid "Lights Out"
+msgstr ""
+
#: pysollib/resource.py:236
msgid "Matrix"
msgstr "Matrix"
@@ -600,6 +603,9 @@ msgstr "Puzzle"
msgid "Cribbage type"
msgstr ""
+msgid "Lights Out type"
+msgstr ""
+
msgid "Pegged type"
msgstr ""
diff --git a/po/fr_pysol.po b/po/fr_pysol.po
index 59280410..cefaacba 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: 2021-12-28 13:52-0500\n"
+"PO-Revision-Date: 2022-01-01 23:20-0500\n"
"Last-Translator: Eric Rausch \n"
"Language-Team: French\n"
"Language: fr\n"
@@ -459,6 +459,9 @@ msgstr "Jeux à 4 jeux de cartes"
msgid "Cribbage"
msgstr ""
+msgid "Lights Out"
+msgstr ""
+
#: pysollib/resource.py:236
msgid "Matrix"
msgstr "Matrice"
@@ -606,6 +609,9 @@ msgstr "Type Puzzle"
msgid "Cribbage type"
msgstr ""
+msgid "Lights Out type"
+msgstr ""
+
msgid "Pegged type"
msgstr ""
diff --git a/po/it_pysol.po b/po/it_pysol.po
index c236d2d2..c8853306 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: 2021-12-28 13:52-0500\n"
+"PO-Revision-Date: 2022-01-01 23:21-0500\n"
"Last-Translator: Giuliano Colla \n"
"Language-Team: Italiano \n"
"Language: it\n"
@@ -465,6 +465,9 @@ msgstr "Giochi con quattro mazzi"
msgid "Cribbage"
msgstr ""
+msgid "Lights Out"
+msgstr ""
+
#: pysollib/resource.py:236
msgid "Matrix"
msgstr "Matrice"
@@ -612,6 +615,9 @@ msgstr "Tipo Puzzle"
msgid "Cribbage type"
msgstr ""
+msgid "Lights Out type"
+msgstr ""
+
msgid "Pegged type"
msgstr ""
diff --git a/po/pl_pysol.po b/po/pl_pysol.po
index b17df6ab..504cc06c 100644
--- a/po/pl_pysol.po
+++ b/po/pl_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: 2021-12-28 13:51-0500\n"
+"PO-Revision-Date: 2022-01-01 23:21-0500\n"
"Last-Translator: Jerzy Trzeciak \n"
"Language-Team: Polish \n"
"Language: pl\n"
@@ -460,6 +460,9 @@ msgstr "Gry czterotaliowe"
msgid "Cribbage"
msgstr ""
+msgid "Lights Out"
+msgstr ""
+
#: pysollib/resource.py:236
msgid "Matrix"
msgstr "Matrix"
@@ -607,6 +610,9 @@ msgstr "Gry typu Puzzle"
msgid "Cribbage type"
msgstr ""
+msgid "Lights Out type"
+msgstr ""
+
msgid "Pegged type"
msgstr ""
diff --git a/po/pysol.pot b/po/pysol.pot
index f568cddd..321bfc50 100644
--- a/po/pysol.pot
+++ b/po/pysol.pot
@@ -433,6 +433,9 @@ msgstr ""
msgid "Cribbage"
msgstr ""
+msgid "Lights Out"
+msgstr ""
+
#: pysollib/resource.py:236
msgid "Matrix"
msgstr ""
@@ -578,6 +581,9 @@ msgstr ""
msgid "Cribbage type"
msgstr ""
+msgid "Lights Out type"
+msgstr ""
+
msgid "Pegged type"
msgstr ""
diff --git a/po/ru_pysol.po b/po/ru_pysol.po
index 4955dfbc..58ec41a8 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: 2021-12-28 13:51-0500\n"
+"PO-Revision-Date: 2022-01-01 23:21-0500\n"
"Last-Translator: Skomoroh \n"
"Language-Team: Russian \n"
"Language: ru\n"
@@ -462,6 +462,9 @@ msgstr "Игры с четырьмя колодами"
msgid "Cribbage"
msgstr ""
+msgid "Lights Out"
+msgstr ""
+
#: pysollib/resource.py:236
msgid "Matrix"
msgstr "Мозаика"
@@ -609,6 +612,9 @@ msgstr "Пазлы"
msgid "Cribbage type"
msgstr ""
+msgid "Lights Out type"
+msgstr ""
+
msgid "Pegged type"
msgstr ""
diff --git a/pysollib/gamedb.py b/pysollib/gamedb.py
index 2c4fb275..1ae7ac87 100644
--- a/pysollib/gamedb.py
+++ b/pysollib/gamedb.py
@@ -57,6 +57,7 @@ class GI:
GT_BAKERS_DOZEN = 4
GT_BELEAGUERED_CASTLE = 5
GT_CANFIELD = 6
+ GT_CRIBBAGE_TYPE = 37
GT_DASHAVATARA_GANJIFA = 7
GT_FAN_TYPE = 8
GT_FORTY_THIEVES = 9
@@ -64,8 +65,10 @@ class GI:
GT_GOLF = 11
GT_GYPSY = 12
GT_HANAFUDA = 13
+ GT_HANOI = 35
GT_HEXADECK = 14
GT_KLONDIKE = 15
+ GT_LIGHTS_OUT = 38
GT_MAHJONGG = 16
GT_MATRIX = 17
GT_MEMORY = 18
@@ -75,20 +78,20 @@ class GI:
GT_NAVAGRAHA_GANJIFA = 22
GT_NUMERICA = 23
GT_PAIRING_TYPE = 24
+ GT_PEGGED = 36
GT_POKER_TYPE = 25
GT_PUZZLE_TYPE = 26
GT_RAGLAN = 27
GT_ROW_TYPE = 28
+ GT_SHISEN_SHO = 34
GT_SIMPLE_TYPE = 29
GT_SPIDER = 30
GT_TAROCK = 31
GT_TERRACE = 32
GT_YUKON = 33
- GT_SHISEN_SHO = 34
- GT_HANOI = 35
- GT_PEGGED = 36
- GT_CRIBBAGE_TYPE = 37
+
GT_CUSTOM = 40
+
# extra flags
GT_BETA = 1 << 12 # beta version of game driver
GT_CHILDREN = 1 << 13 # *not used*
@@ -139,6 +142,7 @@ class GI:
GT_CRIBBAGE_TYPE: n_("Cribbage"),
GT_HEXADECK: n_("Hex A Deck"),
+ GT_LIGHTS_OUT: n_("Lights Out"),
GT_MATRIX: n_("Matrix"),
GT_MEMORY: n_("Memory"),
GT_PEGGED: n_("Pegged"),
@@ -246,6 +250,8 @@ class GI:
lambda gi, gt=GT_CRIBBAGE_TYPE: gi.si.game_type == gt),
(n_("Hex A Deck type"),
lambda gi, gt=GT_HEXADECK: gi.si.game_type == gt),
+ (n_("Lights Out type"),
+ lambda gi, gt=GT_LIGHTS_OUT: gi.si.game_type == gt),
(n_("Matrix type"), lambda gi, gt=GT_MATRIX: gi.si.game_type == gt),
(n_("Memory type"), lambda gi, gt=GT_MEMORY: gi.si.game_type == gt),
(n_("Pegged type"), lambda gi, gt=GT_PEGGED: gi.si.game_type == gt),
@@ -521,7 +527,7 @@ class GI:
('fc-2.12', tuple(range(774, 811)) + (16681,) +
tuple(range(22217, 22219))),
('fc-2.14', tuple(range(811, 827))),
- ('fc-2.16', tuple(range(827, 849)))
+ ('fc-2.16', tuple(range(827, 849)) + tuple(range(22400, 22407)))
)
# 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 da8e0144..ab0d54f8 100644
--- a/pysollib/games/special/__init__.py
+++ b/pysollib/games/special/__init__.py
@@ -22,6 +22,7 @@
# ---------------------------------------------------------------------------##
from . import cribbage # noqa: F401
from . import hanoi # noqa: F401
+from . import lightsout # noqa: F401
from . import memory # noqa: F401
from . import pegged # noqa: F401
from . import poker # noqa: F401
diff --git a/pysollib/games/special/lightsout.py b/pysollib/games/special/lightsout.py
new file mode 100644
index 00000000..32b18178
--- /dev/null
+++ b/pysollib/games/special/lightsout.py
@@ -0,0 +1,232 @@
+#!/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 .
+#
+# ---------------------------------------------------------------------------##
+
+import math
+
+from pysollib.game import Game
+from pysollib.gamedb import GI, GameInfo, registerGame
+from pysollib.layout import Layout
+from pysollib.mfxutil import kwdefault
+from pysollib.settings import TOOLKIT
+from pysollib.stack import \
+ InitialDealTalonStack, \
+ OpenStack
+from pysollib.util import ANY_RANK
+
+# ************************************************************************
+# * Matrix Row Stack
+# ************************************************************************
+
+
+class LightsOut_RowStack(OpenStack):
+
+ def __init__(self, x, y, game, **cap):
+ kwdefault(cap, max_move=1, max_accept=1, max_cards=1,
+ base_rank=ANY_RANK)
+ OpenStack.__init__(self, x, y, game, **cap)
+
+ def canFlipCard(self):
+ return 0
+
+ def canDropCards(self, stacks):
+ return (None, 0)
+
+ def cancelDrag(self, event=None):
+ if event is None:
+ self._stopDrag()
+
+ def _findCard(self, event):
+ # we need to override this because the shade may be hiding
+ # the tile (from Tk's stacking view)
+ return len(self.cards) - 1
+
+ def clickHandler(self, event):
+ self.playFlipMove()
+
+ def playFlipMove(self, sound=True, animation=False):
+ rows = int(math.sqrt(self.game.gameinfo.ncards))
+
+ playSpace = self.id
+
+ if playSpace % rows != rows - 1:
+ self.game.s.rows[playSpace + 1].flipMove()
+
+ if playSpace % rows != 0:
+ self.game.s.rows[playSpace - 1].flipMove()
+
+ if playSpace + rows < rows ** 2:
+ self.game.s.rows[playSpace + rows].flipMove()
+
+ if playSpace - rows >= 0:
+ self.game.s.rows[playSpace - rows].flipMove()
+
+ return OpenStack.playFlipMove(self, sound, animation)
+
+
+# Talon that can deal randomly flipped cards.
+class LightsOut_Talon(InitialDealTalonStack):
+
+ def dealToStacks(self, stacks, flip=1, reverse=0, frames=-1):
+ if not self.cards or not stacks:
+ return 0
+ assert len(self.cards) >= len(stacks)
+ old_state = self.game.enterState(self.game.S_DEAL)
+ if reverse:
+ stacks = list(stacks)
+ stacks.reverse()
+ for r in stacks:
+ if self.getCard().face_up:
+ # TODO: This probably needs a refactor.
+ # For some reason, unless I do TWO flipMoves here,
+ # the card will act flipped, but not show as flipped
+ # when dealt.
+ self.game.flipMove(self)
+ self.game.flipMove(self)
+ self.game.moveMove(1, self, r, frames=frames)
+ self.game.leaveState(old_state)
+ if TOOLKIT == 'kivy':
+ self.game.top.waitAnimation()
+ return len(stacks)
+
+
+# ************************************************************************
+# * Lights Out Game
+# ************************************************************************
+
+class LightsOut(Game):
+
+ #
+ # Game layout
+ #
+
+ def createGame(self):
+ self.shownCards = tuple()
+
+ l, s = Layout(self), self.s
+ grid = math.sqrt(self.gameinfo.ncards)
+ assert grid == int(grid)
+ grid = int(grid)
+
+ # Set window size
+ w, h = l.XM * 2 + l.CW * grid, l.YM * 2 + l.CH * grid
+ self.setSize(w, h)
+
+ # Create rows
+ for j in range(grid):
+ x, y = l.XM, l.YM + l.CH * j
+ for i in range(grid):
+ s.rows.append(LightsOut_RowStack(x, y, self))
+ x = x + l.CW
+
+ # Create talon
+ x, y = -2 * l.XS, 0 # invisible
+ s.talon = LightsOut_Talon(x, y, self)
+
+ # Define stack groups
+ l.defaultStackGroups()
+
+ #
+ # Game extras
+ #
+
+ def _shuffleHook(self, cards):
+ # no shuffling
+ cards = self._shuffleHookMoveToTop(cards, lambda c: (1, -c.id))
+
+ cards.reverse()
+ return cards
+
+ #
+ # Game over rides
+ #
+
+ def startGame(self):
+ assert len(self.s.talon.cards) == self.gameinfo.ncards
+
+ rows = int(math.sqrt(self.gameinfo.ncards))
+
+ n = 0
+ cards = self.s.talon.cards
+ for card in cards:
+ if self.random.randint(0, 1) == 1:
+ card.face_up = not card.face_up
+ if n % rows != rows - 1:
+ cards[n + 1].face_up = not cards[n + 1].face_up
+
+ if n % rows != 0:
+ cards[n - 1].face_up = not cards[n - 1].face_up
+
+ if n + rows < rows ** 2:
+ cards[n + rows].face_up = not cards[n + rows].face_up
+
+ if n - rows >= 0:
+ cards[n - rows].face_up = not cards[n - rows].face_up
+
+ n += 1
+
+ self.startDealSample()
+ self.s.talon.dealRow(rows=self.s.rows[:self.gameinfo.ncards],
+ flip=0, frames=3)
+
+ def isGameWon(self):
+ if self.busy:
+ return 0
+ s = self.s.rows
+ for r in s:
+ if r.cards[0].face_up:
+ return 0
+ return 1
+
+ def shallHighlightMatch(self, stack1, card1, stack2, card2):
+ return ((card1.rank + 1 == card2.rank) or
+ (card1.rank - 1 == card2.rank))
+
+
+# ************************************************************************
+# * Register a Matrix game
+# ************************************************************************
+
+def r(id, short_name, width):
+ name = short_name
+ ncards = width ** 2
+ gi = GameInfo(
+ id, LightsOut, name,
+ GI.GT_LIGHTS_OUT, 1, 0, GI.SL_SKILL,
+ category=GI.GC_TRUMP_ONLY, short_name=short_name,
+ suits=(), ranks=(), trumps=list(range(ncards)),
+ si={"decks": 1, "ncards": ncards})
+ gi.ncards = ncards
+ gi.rules_filename = "lightsout.html"
+ registerGame(gi)
+ return gi
+
+
+r(22400, "Lights Out 4x4", 4)
+r(22401, "Lights Out 5x5", 5)
+r(22402, "Lights Out 6x6", 6)
+r(22403, "Lights Out 7x7", 7)
+r(22404, "Lights Out 8x8", 8)
+r(22405, "Lights Out 9x9", 9)
+r(22406, "Lights Out 10x10", 10)
+
+del r
diff --git a/pysollib/games/ultra/matrix.py b/pysollib/games/ultra/matrix.py
index 9d4084a8..1f8bb322 100644
--- a/pysollib/games/ultra/matrix.py
+++ b/pysollib/games/ultra/matrix.py
@@ -131,7 +131,7 @@ class Matrix_RowStack(OpenStack):
# * Matrix Game
# ************************************************************************
-class Matrix3(Game):
+class Matrix(Game):
#
# Game layout
@@ -208,51 +208,15 @@ class Matrix3(Game):
(card1.rank - 1 == card2.rank))
-# ************************************************************************
-# * Size variations
-# ************************************************************************
-
-class Matrix4(Matrix3):
- pass
-
-
-class Matrix5(Matrix3):
- pass
-
-
-class Matrix6(Matrix3):
- pass
-
-
-class Matrix7(Matrix3):
- pass
-
-
-class Matrix8(Matrix3):
- pass
-
-
-class Matrix9(Matrix3):
- pass
-
-
-class Matrix10(Matrix3):
- pass
-
-
-class Matrix20(Matrix3):
- pass
-
-
# ************************************************************************
# * Register a Matrix game
# ************************************************************************
-def r(id, gameclass, short_name):
+def r(id, short_name, width):
name = short_name
- ncards = int(name[:2]) * int(name[:2])
+ ncards = width ** 2
gi = GameInfo(
- id, gameclass, name,
+ id, Matrix, name,
GI.GT_MATRIX, 1, 0, GI.SL_SKILL,
category=GI.GC_TRUMP_ONLY, short_name=short_name,
suits=(), ranks=(), trumps=list(range(ncards)),
@@ -263,14 +227,14 @@ def r(id, gameclass, short_name):
return gi
-r(22223, Matrix3, " 3x3 Matrix")
-r(22224, Matrix4, " 4x4 Matrix")
-r(22225, Matrix5, " 5x5 Matrix")
-r(22226, Matrix6, " 6x6 Matrix")
-r(22227, Matrix7, " 7x7 Matrix")
-r(22228, Matrix8, " 8x8 Matrix")
-r(22229, Matrix9, " 9x9 Matrix")
-r(22230, Matrix10, "10x10 Matrix")
-# r(22240, Matrix20, "20x20 Matrix")
+r(22223, "Matrix 3x3", 3)
+r(22224, "Matrix 4x4", 4)
+r(22225, "Matrix 5x5", 5)
+r(22226, "Matrix 6x6", 6)
+r(22227, "Matrix 7x7", 7)
+r(22228, "Matrix 8x8", 8)
+r(22229, "Matrix 9x9", 9)
+r(22230, "Matrix 10x10", 10)
+# r(22240, "Matrix 20x20", 20)
del r
diff --git a/scripts/all_games.py b/scripts/all_games.py
index 18e0fe09..65640ea8 100755
--- a/scripts/all_games.py
+++ b/scripts/all_games.py
@@ -62,6 +62,7 @@ GAME_BY_TYPE = {
GI.GT_3DECK_TYPE: "Three-Deck game",
GI.GT_4DECK_TYPE: "Four-Deck game",
+ GI.GT_LIGHTS_OUT: "Lights Out",
GI.GT_MATRIX: "Matrix",
GI.GT_MEMORY: "Memory",
GI.GT_POKER_TYPE: "Poker",