File indexing completed on 2024-04-21 04:01:56

0001 # -*- coding: utf-8 -*-
0002 
0003 """
0004 Copyright (C) 2008-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de>
0005 
0006 SPDX-License-Identifier: GPL-2.0
0007 
0008 """
0009 
0010 import weakref
0011 
0012 from common import StrMixin, Debug
0013 from tile import Tile, elements
0014 
0015 
0016 class WallEmpty(Exception):
0017 
0018     """exception when trying to get a tile off the empty wall"""
0019 
0020 
0021 class KongBox:
0022 
0023     """a non-ui kong box"""
0024 
0025     def __init__(self):
0026         self._tiles = []
0027 
0028     def fill(self, tiles):
0029         """fill the box"""
0030         self._tiles = tiles
0031 
0032     def pop(self, count):
0033         """get count tiles from kong box"""
0034         if len(self._tiles) < count:
0035             raise WallEmpty
0036         tiles = self._tiles[-count:]
0037         self._tiles = self._tiles[:-count]
0038         return tiles
0039 
0040     def __getitem__(self, index):
0041         return self._tiles[index]
0042 
0043     def __len__(self):
0044         """# of tiles in kong box"""
0045         return len(self._tiles)
0046 
0047 
0048 class Wall(StrMixin):
0049 
0050     """represents the wall with four sides. self.wall[] indexes them
0051     counter clockwise, 0..3. 0 is bottom.
0052     Wall.tiles always holds references to all tiles in the game even
0053     when they are used"""
0054     tileClass = Tile
0055     kongBoxClass = KongBox
0056 
0057     def __init__(self, game):
0058         """init and position the wall"""
0059         self._game = weakref.ref(game)  # avoid cycles for garbage collection
0060         wallSize = int(Debug.wallSize)
0061         if not wallSize:
0062             wallSize = elements.count(game.ruleset)
0063         self.tiles = [self.tileClass(Tile.unknown)
0064                       for _ in range(wallSize)]
0065         self.living = None
0066         self.kongBox = self.kongBoxClass()
0067         assert len(self.tiles) % 8 == 0
0068 
0069     @property
0070     def game(self):
0071         """hide the fact that this is a weakref"""
0072         return self._game()
0073 
0074     @staticmethod
0075     def __nameTile(tile, element):
0076         """define what tile this is"""
0077         if element is None:
0078             return tile
0079         assert isinstance(element, Tile), element
0080         if isinstance(tile, Tile):
0081             return element
0082         # tile is UITile
0083         tile.tile = element
0084         return tile
0085 
0086     def deal(self, tiles=None, deadEnd=False):
0087         """deal tiles. May raise WallEmpty.
0088         Returns a list of tiles"""
0089         if tiles is None:
0090             tiles = [None]
0091         count = len(tiles)
0092         if deadEnd:
0093             dealTiles = self.kongBox.pop(count)
0094             if len(self.kongBox) % 2 == 0:
0095                 self._placeLooseTiles()
0096         else:
0097             if len(self.living) < count:
0098                 raise WallEmpty
0099             dealTiles = self.living[:count]
0100             self.living = self.living[count:]
0101         return [self.__nameTile(*x) for x in zip(dealTiles, tiles)]
0102 
0103     def build(self, shuffleFirst=False):
0104         """virtual: build visible wall"""
0105 
0106     def _placeLooseTiles(self, deferredResult=None):
0107         """to be done only for UIWall"""
0108 
0109     def decorate4(self, deferredResult=None): # pylint: disable=unused-argument
0110         """virtual: show player info on the wall"""
0111 
0112     def hide(self):
0113         """virtual: hide all four walls and their decorators"""
0114 
0115     def divide(self):
0116         """divides a wall, building a living end and a dead end"""
0117         # neutralise the different directions of winds and removal of wall
0118         # tiles
0119         assert self.game.divideAt is not None
0120         # shift tiles: tile[0] becomes living end
0121         self.tiles[:] = self.tiles[
0122             self.game.divideAt:] + \
0123             self.tiles[0:self.game.divideAt]
0124         kongBoxSize = self.game.ruleset.kongBoxSize
0125         self.living = self.tiles[:-kongBoxSize]
0126         boxTiles = self.tiles[-kongBoxSize:]
0127         for pair in range(kongBoxSize // 2):
0128             boxTiles = boxTiles[:pair * 2] + [
0129                 boxTiles[pair * 2 + 1],
0130                 boxTiles[pair * 2]] + \
0131                 boxTiles[pair * 2 + 2:]
0132         self.kongBox.fill(boxTiles)
0133 
0134     @staticmethod
0135     def name():
0136         """name for debug messages"""
0137         return '4sided wall'
0138 
0139     def __str__(self):
0140         """for debugging"""
0141         return self.name()