class Game(object):
    """
    A 2048 game
    """

    __dirs = {
        keypress.UP:      Board.UP,
        keypress.DOWN:    Board.DOWN,
        keypress.LEFT:    Board.LEFT,
        keypress.RIGHT:   Board.RIGHT,
    }

    __clear = 'cls' if os.name == 'nt' else 'clear'

    def __init__(self, **kws):
        """
        Create a new game.
        """
        self.board = Board(**kws)
        self.score = 0
        self.__colors = {
            2:    Fore.GREEN,
            4:    Fore.BLUE + Style.BRIGHT,
            8:    Fore.CYAN,
            16:   Fore.RED,
            32:   Fore.MAGENTA,
            64:   Fore.CYAN,
            128:  Fore.BLUE + Style.BRIGHT,
            256:  Fore.MAGENTA,
            512:  Fore.GREEN,
            1024: Fore.RED,
            2048: Fore.YELLOW,
            # just in case people set an higher goal they still have colors
            4096: Fore.RED,
            8192: Fore.CYAN,
        }

    def incScore(self, pts):
        """
        update the current score by adding it the specified number of points
        """
        self.score += pts

    def end(self):
        """
        return True if the game is finished
        """
        return not (self.board.won() or self.board.canMove())

    def readMove(self):
        """
        read and return a move to pass to a board
        """
        k = keypress.getKey()
        return Game.__dirs.get(k)

    def loop(self):
        """
        main game loop. returns the final score.
        """
        try:
            while True:
                os.system(Game.__clear)
                print(self.__str__(margins={'left': 4, 'top': 4, 'bottom': 4}))
                if self.board.won() or not self.board.canMove():
                    break
                m = self.readMove()
                self.incScore(self.board.move(m))

        except KeyboardInterrupt:
            return

        print('You won!' if self.board.won() else 'Game Over')
        return self.score

    def getCellStr(self, x, y):  # TODO: refactor regarding issue #11
        """
        return a string representation of the cell located at x,y.
        """
        c = self.board.getCell(x, y)

        if c == 0:
            return '  .'
        elif c == 1024:
            s = ' 1k'
        elif c == 2048:
            s = ' 2k'
        else:
            s = '%3d' % c

        return self.__colors.get(c, Fore.RESET) + s + Style.RESET_ALL

    def boardToString(self, margins={}):
        """
        return a string representation of the current board.
        """
        b = self.board
        rg = range(b.size())
        left = ' '*margins.get('left', 0)
        s = '\n'.join(
            [left + ' '.join([self.getCellStr(x, y) for x in rg]) for y in rg])
        return s

    def __str__(self, margins={}):
        b = self.boardToString(margins=margins)
        top = '\n'*margins.get('top', 0)
        bottom = '\n'*margins.get('bottom', 0)
        scores = ' \tScore: %5d\n' % (self.score)
        return top + b.replace('\n', scores, 1) + bottom
Exemple #2
0
class Game(object):
    """
    A 2048 game
    """

    __dirs = {
        keypress.UP:    Board.UP,
        keypress.DOWN:  Board.DOWN,
        keypress.LEFT:  Board.LEFT,
        keypress.RIGHT: Board.RIGHT,
    }

    __clear = 'cls' if os.name == 'nt' else 'clear'

    COLORS = {
           2: Fore.GREEN,
           4: Fore.BLUE,
           8: Fore.CYAN,
          16: Fore.RED,
          32: Fore.MAGENTA,
          64: Fore.CYAN,
         128: Fore.BLUE,
         256: Fore.MAGENTA,
         512: Fore.GREEN,
        1024: Fore.RED,
        2048: Fore.YELLOW,
    }

    # see Game#adjustColors
    # these are color replacements for various modes
    __color_modes = {
        'dark': {
            Fore.BLUE: Fore.WHITE,
        },
        'light': {
            Fore.YELLOW: Fore.BLACK,
        },
    }

    SCORES_FILE = '%s/.term2048.scores' % os.path.expanduser('~')

    def __init__(self, scores_file=SCORES_FILE, colors=COLORS,
            mode=None, **kws):
        """
        Create a new game.
            scores_file: file to use for the best score (default
                         is ~/.term2048.scores)
            colors: dictionnary with colors to use for each tile
            mode: color mode. This adjust a few colors and can be 'dark' or
                  'light'. See the adjustColors functions for more info.
            other options are passed to the underlying Board object.
        """
        self.board = Board(**kws)
        self.score = 0
        self.scores_file = scores_file
        self.__colors = colors
        self.loadBestScore()
        self.adjustColors(mode)

    def adjustColors(self, mode='dark'):
        """
        Change a few colors depending on the mode to use. The default mode
        doesn't assume anything and avoid using white & black colors. The dark
        mode use white and avoid dark blue while the light mode use black and
        avoid yellow, to give a few examples.
        """
        rp = Game.__color_modes.get(mode, {})
        for k, color in self.__colors.items():
            self.__colors[k] = rp.get(color, color)

    def loadBestScore(self):
        """
        load local best score from the default file
        """
        if self.scores_file is None or not os.path.exists(self.scores_file):
            self.best_score = 0
            return
        try:
            f = open(self.scores_file, 'r')
            self.best_score = int(f.readline(), 10)
            f.close()
        except:
            pass # fail silently

    def saveBestScore(self):
        """
        save current best score in the default file
        """
        if self.score > self.best_score:
            self.best_score = self.score
        try:
            f = open(self.scores_file, 'w')
            f.write(str(self.best_score))
            f.close()
        except:
            pass # fail silently

    def end(self):
        """
        return True if the game is finished
        """
        return not (self.board.won() or self.board.canMove())

    def readMove(self):
        """
        read and return a move to pass to a board
        """
        k = keypress.getArrowKey()
        return Game.__dirs.get(k)

    def loop(self):
        """
        main game loop
        """
        while True:
            os.system(Game.__clear)
            print(self.__str__(margins={'left':4, 'top':4, 'bottom':4}))
            if self.board.won() or not self.board.canMove():
                break
            try:
                m = self.readMove()
            except KeyboardInterrupt:
                self.saveBestScore()
                return
            self.score += self.board.move(m)
            if self.score > self.best_score:
                self.best_score = self.score

        self.saveBestScore()
        print('You won!' if self.board.won() else 'Game Over')

    def getCellStr(self, x, y):
        """
        return a string representation of the cell located at x,y.
        """
        c = self.board.getCell(x, y)
        if c == 0:
            return '  .'

        if c == 1024:
            s = ' 1k'
        elif c == 2048:
            s = ' 2k'
        else:
            s = '%3d' % c
        return self.__colors.get(c, Fore.RESET) + s + Fore.RESET

    def boardToString(self, margins={}):
        """
        return a string representation of the current board.
        """
        b = self.board
        rg = xrange(b.size())
        left = ' '*margins.get('left', 0)
        s = '\n'.join(
            [left + ' '.join([self.getCellStr(x, y) for x in rg]) for y in rg])
        return s

    def __str__(self, margins={}):
        b = self.boardToString(margins=margins)
        top = '\n'*margins.get('top', 0)
        bottom = '\n'*margins.get('bottom', 0)
        scores = ' \tScore: %5d  Best: %5d\n' % (self.score, self.best_score)
        return top + b.replace('\n', scores, 1) + bottom
Exemple #3
0
class Game(object):
    """
    A 2048 game
    """

    __dirs = {
        keypress.UP: Board.UP,
        keypress.DOWN: Board.DOWN,
        keypress.LEFT: Board.LEFT,
        keypress.RIGHT: Board.RIGHT,
        keypress.SPACE: Board.PAUSE,
    }

    __is_windows = os.name == 'nt'

    COLORS = {
        2: Fore.GREEN,
        4: Fore.BLUE + Style.BRIGHT,
        8: Fore.CYAN,
        16: Fore.RED,
        # Don't use MAGENTA directly; it doesn't display well on Windows.
        # see https://github.com/bfontaine/term2048/issues/24
        32: Fore.MAGENTA + Style.BRIGHT,
        64: Fore.CYAN,
        128: Fore.BLUE + Style.BRIGHT,
        256: Fore.MAGENTA + Style.BRIGHT,
        512: Fore.GREEN,
        1024: Fore.RED,
        2048: Fore.YELLOW,
        # just in case people set an higher goal they still have colors
        4096: Fore.RED,
        8192: Fore.CYAN,
    }

    # see Game#adjustColors
    # these are color replacements for various modes
    __color_modes = {
        'dark': {
            Fore.BLUE: Fore.WHITE,
            Fore.BLUE + Style.BRIGHT: Fore.WHITE,
        },
        'light': {
            Fore.YELLOW: Fore.BLACK,
        },
    }

    SCORES_FILE = '%s/.term2048.scores' % os.path.expanduser('~')
    STORE_FILE = '%s/.term2048.store' % os.path.expanduser('~')

    def __init__(self,
                 scores_file=SCORES_FILE,
                 colors=COLORS,
                 store_file=STORE_FILE,
                 clear_screen=True,
                 mode=None,
                 azmode=False,
                 **kws):
        """
        Create a new game.
            scores_file: file to use for the best score (default
                         is ~/.term2048.scores)
            colors: dictionnary with colors to use for each tile
            store_file: file that stores game session's snapshot
            mode: color mode. This adjust a few colors and can be 'dark' or
                  'light'. See the adjustColors functions for more info.
            other options are passed to the underlying Board object.
        """
        self.board = Board(**kws)
        self.score = 0
        self.scores_file = scores_file
        self.store_file = store_file
        self.clear_screen = clear_screen

        self.__colors = colors
        self.__azmode = azmode

        self.loadBestScore()
        self.adjustColors(mode)

    def adjustColors(self, mode='dark'):
        """
        Change a few colors depending on the mode to use. The default mode
        doesn't assume anything and avoid using white & black colors. The dark
        mode use white and avoid dark blue while the light mode use black and
        avoid yellow, to give a few examples.
        """
        rp = Game.__color_modes.get(mode, {})
        for k, color in self.__colors.items():
            self.__colors[k] = rp.get(color, color)

    def loadBestScore(self):
        """
        load local best score from the default file
        """
        try:
            with open(self.scores_file, 'r') as f:
                self.best_score = int(f.readline(), 10)
        except:
            self.best_score = 0
            return False
        return True

    def saveBestScore(self):
        """
        save current best score in the default file
        """
        if self.score > self.best_score:
            self.best_score = self.score
        try:
            with open(self.scores_file, 'w') as f:
                f.write(str(self.best_score))
        except:
            return False
        return True

    def incScore(self, pts):
        """
        update the current score by adding it the specified number of points
        """
        self.score += pts
        if self.score > self.best_score:
            self.best_score = self.score

    def readMove(self):
        """
        read and return a move to pass to a board
        """
        k = keypress.getKey()
        return Game.__dirs.get(k)

    def store(self):
        """
        save the current game session's score and data for further use
        """
        size = self.board.SIZE
        cells = []

        for i in range(size):
            for j in range(size):
                cells.append(str(self.board.getCell(j, i)))

        score_str = "%s\n%d" % (' '.join(cells), self.score)

        try:
            with open(self.store_file, 'w') as f:
                f.write(score_str)
        except:
            return False
        return True

    def restore(self):
        """
        restore the saved game score and data
        """

        size = self.board.SIZE

        try:
            with open(self.store_file, 'r') as f:
                lines = f.readlines()
                score_str = lines[0]
                self.score = int(lines[1])
        except:
            return False

        score_str_list = score_str.split(' ')
        count = 0

        for i in range(size):
            for j in range(size):
                value = score_str_list[count]
                self.board.setCell(j, i, int(value))
                count += 1

        return True

    def clearScreen(self):
        """Clear the console"""
        if self.clear_screen:
            os.system('cls' if self.__is_windows else 'clear')
        else:
            print('\n')

    def hideCursor(self):
        """
        Hide the cursor. Don't forget to call ``showCursor`` to restore
        the normal shell behavior. This is a no-op if ``clear_screen`` is
        falsy.
        """
        if not self.clear_screen:
            return
        if not self.__is_windows:
            sys.stdout.write('\033[?25l')

    def showCursor(self):
        """Show the cursor."""
        if not self.__is_windows:
            sys.stdout.write('\033[?25h')

    def loop(self):
        """
        main game loop. returns the final score.
        """
        pause_key = self.board.PAUSE
        margins = {'left': 4, 'top': 4, 'bottom': 4}

        atexit.register(self.showCursor)

        try:
            self.hideCursor()
            while True:
                self.clearScreen()
                print(self.__str__(margins=margins))
                if self.board.won() or not self.board.canMove():
                    break
                m = self.readMove()

                if (m == pause_key):
                    self.saveBestScore()
                    if self.store():
                        print("Game successfully saved. "
                              "Resume it with `term2048 --resume`.")
                        return self.score
                    print("An error ocurred while saving your game.")
                    return

                self.incScore(self.board.move(m))

        except KeyboardInterrupt:
            self.saveBestScore()
            return

        self.saveBestScore()
        print('You won!' if self.board.won() else 'Game Over')
        return self.score

    def getCellStr(self, x, y):  # TODO: refactor regarding issue #11
        """
        return a string representation of the cell located at x,y.
        """
        c = self.board.getCell(x, y)

        if c == 0:
            return '.' if self.__azmode else '  .'

        elif self.__azmode:
            az = {}
            for i in range(1, int(math.log(self.board.goal(), 2))):
                az[2**i] = chr(i + 96)

            if c not in az:
                return '?'
            s = az[c]
        elif c == 1024:
            s = ' 1k'
        elif c == 2048:
            s = ' 2k'
        else:
            s = '%3d' % c

        return self.__colors.get(c, Fore.RESET) + s + Style.RESET_ALL

    def boardToString(self, margins={}):
        """
        return a string representation of the current board.
        """
        b = self.board
        rg = range(b.size())
        left = ' ' * margins.get('left', 0)
        s = '\n'.join(
            [left + ' '.join([self.getCellStr(x, y) for x in rg]) for y in rg])
        return s

    def __str__(self, margins={}):
        b = self.boardToString(margins=margins)
        top = '\n' * margins.get('top', 0)
        bottom = '\n' * margins.get('bottom', 0)
        scores = ' \tScore: %5d  Best: %5d\n' % (self.score, self.best_score)
        return top + b.replace('\n', scores, 1) + bottom
Exemple #4
0
class TestBoard(unittest.TestCase):

    def setUp(self):
        self.b = Board()

    # == init == #
    def test_init_dimensions(self):
        self.assertEqual(len(self.b.cells), Board.SIZE)
        self.assertEqual(len(self.b.cells[0]), Board.SIZE)
        if Board.SIZE > 1:
            self.assertEqual(len(self.b.cells[1]), Board.SIZE)

    def test_init_dimensions_1(self):
        b = Board(size=1)
        c = b.cells[0][0]
        self.assertTrue(c in [2, 4])

    def test_init_dimensions_3_goal_4(self):
        b = Board(size=3, goal=4)
        self.assertEqual(b.size(), 3)

    def test_init_only_two_tiles(self):
        t = 0
        for x in xrange(Board.SIZE):
            for y in xrange(Board.SIZE):
                c = self.b.cells[y][x]
                if not c == 0:
                    t += 1
                else:
                    self.assertEqual(c, 0, 'board[%d][%d] should be 0' % (y, x))

        self.assertEqual(t, 2)

    def test_init_not_won(self):
        self.assertFalse(self.b.won())

    def test_init_not_filled(self):
        self.assertFalse(self.b.filled())

    # == .size == #
    def test_size(self):
        s = 42
        b = Board(size=s)
        self.assertEqual(b.size(), s)

    # == .won == #
    def test_won(self):
        self.b._Board__won = True
        self.assertTrue(self.b.won())
        self.b._Board__won = False
        self.assertFalse(self.b.won())

    # == .canMove == #
    def test_canMove_no_empty_cell(self):
        b = Board(size=1)
        b.setCell(0, 0, 42)
        self.assertFalse(b.canMove())

    def test_canMove_empty_cell(self):
        b = Board(size=2)
        self.assertTrue(b.canMove())

    def test_canMove_no_empty_cell_can_collapse(self):
        b = Board(size=2)
        b.cells = [
            [2, 2],
            [4, 8]
        ]
        self.assertTrue(b.canMove())

    # == .filled == #
    def test_filled(self):
        self.b.cells = [[1]*Board.SIZE for _ in xrange(Board.SIZE)]
        self.assertTrue(self.b.filled())

    # == .addTile == #
    def test_addTile(self):
        b = Board(size=1)
        b.cells = [[0]]
        b.addTile(value=42)
        self.assertEqual(b.cells[0][0], 42)

    # == .getCell == #
    def test_getCell(self):
        x, y = 3, 1
        v = 42
        self.b.cells[y][x] = v
        self.assertEqual(self.b.getCell(x, y), v)

    # == .setCell == #
    def test_setCell(self):
        x, y = 2, 3
        v = 42
        self.b.setCell(x, y, v)
        self.assertEqual(self.b.cells[y][x], v)

    # == .getLine == #
    def test_getLine(self):
        b = Board(size=4)
        l = [42, 17, 12, 3]
        b.cells = [
            [0]*4,
            l,
            [0]*4,
            [0]*4
        ]
        self.assertSequenceEqual(b.getLine(1), l)

    # == .getCol == #
    def test_getCol(self):
        s = 4
        b = Board(size=s)
        l = [42, 17, 12, 3]
        b.cells = [[l[i], 4, 1, 2] for i in xrange(s)]
        self.assertSequenceEqual(b.getCol(0), l)

    # == .setLine == #
    def test_setLine(self):
        i = 2
        l = [1, 2, 3, 4]
        self.b.setLine(i, l)
        self.assertEqual(self.b.getLine(i), l)

    # == .setCol == #
    def test_setLine(self):
        i = 2
        l = [1, 2, 3, 4]
        self.b.setCol(i, l)
        self.assertEqual(self.b.getCol(i), l)

    # == .getEmptyCells == #
    def test_getEmptyCells(self):
        self.assertEqual(len(self.b.getEmptyCells()), Board.SIZE**2 - 2)

    def test_getEmptyCells_filled(self):
        b = Board(size=1)
        b.setCell(0, 0, 42)
        self.assertSequenceEqual(b.getEmptyCells(), [])

    # == .move == #
    def test_move_filled(self):
        b = Board(size=1)
        b.setCell(0, 0, 42)
        b.move(Board.UP)
        self.assertSequenceEqual(b.cells, [[42]])
        b.move(Board.LEFT)
        self.assertSequenceEqual(b.cells, [[42]])
        b.move(Board.RIGHT)
        self.assertSequenceEqual(b.cells, [[42]])
        b.move(Board.DOWN)
        self.assertSequenceEqual(b.cells, [[42]])

    def test_move_add_tile_if_collapse(self):
        b = Board(size=2)
        b.cells = [[2, 0],
                   [2, 0]]
        b.move(Board.UP)
        self.assertEqual(len([e for l in b.cells for e in l if e != 0]), 2)

    def test_move_add_tile_if_move(self):
        b = Board(size=2)
        b.cells = [[0, 0],
                   [2, 0]]
        b.move(Board.UP)
        self.assertEqual(len([e for l in b.cells for e in l if e != 0]), 2)

    def test_move_dont_add_tile_if_nothing_move(self):
        b = Board(size=2)
        b.cells = [[2, 0],
                   [0, 0]]
        b.move(Board.UP)
        self.assertEqual(len([e for l in b.cells for e in l if e != 0]), 1)

    # test for issue #1
    def test_move_dont_add_tile_if_nothing_move2(self):
        b = Board()
        b.cells = [
            [8, 4, 4, 2],
            [0, 2, 2, 0],
            [0]*4,
            [0]*4
        ]
        self.assertEqual(b.move(Board.UP), 0)
        self.assertEqual(len([e for l in b.cells for e in l if e != 0]), 6)
        self.assertEqual(b.getLine(0), [8, 4, 4, 2])
        self.assertEqual(b.getLine(1), [0, 2, 2, 0])

    def test_move_collapse(self):
        b = Board(size=2)
        b.cells = [
            [2, 2],
            [0, 0]
        ]

        b.move(Board.LEFT, add_tile=False)
        self.assertSequenceEqual(b.cells, [
            [4, 0],
            [0, 0]
        ])

    def test_move_collapse_triplet1(self):
        b = Board(size=3)
        b.setLine(0, [2, 2, 2])
        b.move(Board.LEFT, add_tile=False)
        self.assertSequenceEqual(b.getLine(0), [4, 2, 0])

    def test_move_collapse_triplet2(self):
        b = Board(size=3)
        b.setLine(0, [2, 2, 2])
        b.move(Board.RIGHT, add_tile=False)
        self.assertSequenceEqual(b.getLine(0), [0, 2, 4])

    def test_move_collapse_with_empty_cell_in_between(self):
        b = Board(size=3)
        b.setLine(0, [2, 0, 2])
        b.move(Board.RIGHT, add_tile=False)
        self.assertSequenceEqual(b.getLine(0), [0, 0, 4])

    def test_move_collapse_with_empty_cell_in_between2(self):
        b = Board(size=3)
        b.setLine(0, [2, 0, 2])
        b.move(Board.LEFT, add_tile=False)
        self.assertSequenceEqual(b.getLine(0), [4, 0, 0])

    def test_move_collapse_and_win(self):
        b = Board(size=2, goal=4)
        b.cells = [
            [2, 2],
            [0, 0]
        ]
        b.move(Board.LEFT, add_tile=False)
        self.assertTrue(b.won())

    def test_move_wrong_direction(self):
        self.assertEqual(self.b.move(42, add_tile=False), 0)
        self.assertEqual(self.b.move(None), 0)
        self.assertEqual(self.b.move("up"), 0)

    def test_move_collapse_chain_col(self):
        # from https://news.ycombinator.com/item?id=7398249
        b = Board()
        b.setCol(0, [0, 2, 2, 4])
        b.move(Board.DOWN, add_tile=False)
        self.assertSequenceEqual(b.getCol(0), [0, 0, 4, 4])

    def test_move_collapse_chain_line(self):
        # from https://news.ycombinator.com/item?id=7398249
        b = Board()
        b.cells = [
            [0, 2, 2, 4],
            [0]*4,
            [0]*4,
            [0]*4
        ]
        self.assertEqual(b.move(Board.RIGHT, add_tile=False), 4)
        self.assertSequenceEqual(b.getLine(0), [0, 0, 4, 4])
Exemple #5
0
class Game(object):
    """
    A 2048 game
    """

    __dirs = {
        keypress.UP:      Board.UP,
        keypress.DOWN:    Board.DOWN,
        keypress.LEFT:    Board.LEFT,
        keypress.RIGHT:   Board.RIGHT,
    }

    __clear = 'cls' if os.name == 'nt' else 'clear'

    COLORS = {
           2: Fore.GREEN,
           4: Fore.BLUE + Style.BRIGHT,
           8: Fore.CYAN,
          16: Fore.RED,
          32: Fore.MAGENTA,
          64: Fore.CYAN,
         128: Fore.BLUE + Style.BRIGHT,
         256: Fore.MAGENTA,
         512: Fore.GREEN,
        1024: Fore.RED,
        2048: Fore.YELLOW,
        # just in case people set an higher goal they still have colors
        4096: Fore.MAGENTA,
        8192: Fore.CYAN,
    }

    # see Game#adjustColors
    # these are color replacements for various modes
    __color_modes = {
        'dark': {
            Fore.BLUE: Fore.WHITE,
            Fore.BLUE + Style.BRIGHT: Fore.WHITE,
        },
        'light': {
            Fore.YELLOW: Fore.BLACK,
        },
    }

    SCORES_FILE = '%s/.term2048.scores' % os.path.expanduser('~')

    def __init__(self, scores_file=SCORES_FILE, colors=COLORS,
            clear_screen=True,
            mode=None, azmode=False, **kws):
        """
        Create a new game.
            scores_file: file to use for the best score (default
                         is ~/.term2048.scores)
            colors: dictionnary with colors to use for each tile
            mode: color mode. This adjust a few colors and can be 'dark' or
                  'light'. See the adjustColors functions for more info.
            other options are passed to the underlying Board object.
        """
        self.board = Board(**kws)
        self.score = 0
        self.scores_file = scores_file
        self.clear_screen = clear_screen

        self.__colors = colors
        self.__azmode = azmode

        self.loadBestScore()
        self.adjustColors(mode)

    def adjustColors(self, mode='dark'):
        """
        Change a few colors depending on the mode to use. The default mode
        doesn't assume anything and avoid using white & black colors. The dark
        mode use white and avoid dark blue while the light mode use black and
        avoid yellow, to give a few examples.
        """
        rp = Game.__color_modes.get(mode, {})
        for k, color in self.__colors.items():
            self.__colors[k] = rp.get(color, color)

    def loadBestScore(self):
        """
        load local best score from the default file
        """
        if self.scores_file is None or not os.path.exists(self.scores_file):
            self.best_score = 0
            return
        try:
            f = open(self.scores_file, 'r')
            self.best_score = int(f.readline(), 10)
            f.close()
        except:
            pass # fail silently

    def saveBestScore(self):
        """
        save current best score in the default file
        """
        if self.score > self.best_score:
            self.best_score = self.score
        try:
            f = open(self.scores_file, 'w')
            f.write(str(self.best_score))
            f.close()
        except:
            pass # fail silently

    def incScore(self, pts):
        """
        update the current score by adding it the specified number of points
        """
        self.score += pts
        if self.score > self.best_score:
            self.best_score = self.score

    def end(self):
        """
        return True if the game is finished
        """
        return not (self.board.won() or self.board.canMove())

    def readMove(self):
        """
        read and return a move to pass to a board
        """
        k = keypress.getKey()
        return Game.__dirs.get(k)

    def loop(self):
        """
        main game loop. returns the final score.
        """
        try:
            while True:
                if self.clear_screen:
                    os.system(Game.__clear)
                else:
                    print("\n")
                print(self.__str__(margins={'left':4, 'top':4, 'bottom':4}))
                if self.board.won() or not self.board.canMove():
                    break
                m = self.readMove()
                self.incScore(self.board.move(m))

        except KeyboardInterrupt:
            self.saveBestScore()
            return

        self.saveBestScore()
        print('You won!' if self.board.won() else 'Game Over')
        return self.score
        
    def loopAI(self,sleep_time=0.1):
        """
        main game loop. returns the final score.
        """
        try:
            while True:
                if self.clear_screen:
                    os.system(Game.__clear)
                else:
                    print("\n")
                print(self.__str__(margins={'left':4, 'top':4, 'bottom':4}))
                if self.board.won() or not self.board.canMove():
                    break
                m = AI.nextMove(self.board)
                self.incScore(self.board.move(m))
                time.sleep(0.01)

        except KeyboardInterrupt:
            self.saveBestScore()
            return

        self.saveBestScore()
        print('You won!' if self.board.won() else 'Game Over')
        return self.score

    def getCellStr(self, x, y): # TODO: refactor regarding issue #11
        """
        return a string representation of the cell located at x,y.
        """
        c = self.board.getCell(x, y)

        az = {}
        for i in range(1, int(math.log(self.board.goal(), 2))):
            az[2**i] = chr(i+96)

        if c==0 and self.__azmode:
            return '.'
        elif c == 0:
            return '  .'

        elif self.__azmode:
            if c not in az:
                return '?'
            s = az[c]
        elif c == 1024:
            s = ' 1k'
        elif c == 2048:
            s = ' 2k'
        elif c == 4096:
            s = ' 4k'
        elif c == 8192:
            s = ' 8k'
        else:
            s = '%3d' % c

        return self.__colors.get(c, Fore.RESET) + s + Style.RESET_ALL

    def boardToString(self, margins={}):
        """
        return a string representation of the current board.
        """
        b = self.board
        rg = range(b.size())
        left = ' '*margins.get('left', 0)
        s = '\n'.join(
            [left + ' '.join([self.getCellStr(x, y) for x in rg]) for y in rg])
        return s

    def __str__(self, margins={}):
        b = self.boardToString(margins=margins)
        top = '\n'*margins.get('top', 0)
        bottom = '\n'*margins.get('bottom', 0)
        scores = ' \tScore: %5d  Best: %5d\n' % (self.score, self.best_score)
        return top + b.replace('\n', scores, 1) + bottom
Exemple #6
0
class Game(object):
    """
    A 2048 game
    """

    __dirs = {
        keypress.UP: Board.UP,
        keypress.DOWN: Board.DOWN,
        keypress.LEFT: Board.LEFT,
        keypress.RIGHT: Board.RIGHT,
    }

    __clear = 'cls' if os.name == 'nt' else 'clear'

    COLORS = {
        2: Fore.GREEN,
        4: Fore.BLUE + Style.BRIGHT,
        8: Fore.CYAN,
        16: Fore.RED,
        32: Fore.MAGENTA,
        64: Fore.CYAN,
        128: Fore.BLUE + Style.BRIGHT,
        256: Fore.MAGENTA,
        512: Fore.GREEN,
        1024: Fore.RED,
        2048: Fore.YELLOW,
        # just in case people set an higher goal they still have colors
        4096: Fore.MAGENTA,
        8192: Fore.CYAN,
    }

    # see Game#adjustColors
    # these are color replacements for various modes
    __color_modes = {
        'dark': {
            Fore.BLUE: Fore.WHITE,
            Fore.BLUE + Style.BRIGHT: Fore.WHITE,
        },
        'light': {
            Fore.YELLOW: Fore.BLACK,
        },
    }

    SCORES_FILE = '%s/.term2048.scores' % os.path.expanduser('~')

    def __init__(self,
                 scores_file=SCORES_FILE,
                 colors=COLORS,
                 clear_screen=True,
                 mode=None,
                 azmode=False,
                 **kws):
        """
        Create a new game.
            scores_file: file to use for the best score (default
                         is ~/.term2048.scores)
            colors: dictionnary with colors to use for each tile
            mode: color mode. This adjust a few colors and can be 'dark' or
                  'light'. See the adjustColors functions for more info.
            other options are passed to the underlying Board object.
        """
        self.board = Board(**kws)
        self.score = 0
        self.scores_file = scores_file
        self.clear_screen = clear_screen

        self.__colors = colors
        self.__azmode = azmode

        self.loadBestScore()
        self.adjustColors(mode)

    def adjustColors(self, mode='dark'):
        """
        Change a few colors depending on the mode to use. The default mode
        doesn't assume anything and avoid using white & black colors. The dark
        mode use white and avoid dark blue while the light mode use black and
        avoid yellow, to give a few examples.
        """
        rp = Game.__color_modes.get(mode, {})
        for k, color in self.__colors.items():
            self.__colors[k] = rp.get(color, color)

    def loadBestScore(self):
        """
        load local best score from the default file
        """
        if self.scores_file is None or not os.path.exists(self.scores_file):
            self.best_score = 0
            return
        try:
            f = open(self.scores_file, 'r')
            self.best_score = int(f.readline(), 10)
            f.close()
        except:
            pass  # fail silently

    def saveBestScore(self):
        """
        save current best score in the default file
        """
        if self.score > self.best_score:
            self.best_score = self.score
        try:
            f = open(self.scores_file, 'w')
            f.write(str(self.best_score))
            f.close()
        except:
            pass  # fail silently

    def incScore(self, pts):
        """
        update the current score by adding it the specified number of points
        """
        self.score += pts
        if self.score > self.best_score:
            self.best_score = self.score

    def end(self):
        """
        return True if the game is finished
        """
        return not (self.board.won() or self.board.canMove())

    def readMove(self):
        """
        read and return a move to pass to a board
        """
        k = keypress.getKey()
        return Game.__dirs.get(k)

    def loop(self):
        """
        main game loop. returns the final score.
        """
        try:
            while True:
                if self.clear_screen:
                    os.system(Game.__clear)
                else:
                    print("\n")
                print(self.__str__(margins={'left': 4, 'top': 4, 'bottom': 4}))
                if self.board.won() or not self.board.canMove():
                    break
                m = self.readMove()
                self.incScore(self.board.move(m))

        except KeyboardInterrupt:
            self.saveBestScore()
            return

        self.saveBestScore()
        print('You won!' if self.board.won() else 'Game Over')
        return self.score

    def loopAI(self, sleep_time=0.1):
        """
        main game loop. returns the final score.
        """
        try:
            while True:
                if self.clear_screen:
                    os.system(Game.__clear)
                else:
                    print("\n")
                print(self.__str__(margins={'left': 4, 'top': 4, 'bottom': 4}))
                if self.board.won() or not self.board.canMove():
                    break
                m = AI.nextMove(self.board)
                self.incScore(self.board.move(m))
                time.sleep(0.01)

        except KeyboardInterrupt:
            self.saveBestScore()
            return

        self.saveBestScore()
        print('You won!' if self.board.won() else 'Game Over')
        return self.score

    def getCellStr(self, x, y):  # TODO: refactor regarding issue #11
        """
        return a string representation of the cell located at x,y.
        """
        c = self.board.getCell(x, y)

        az = {}
        for i in range(1, int(math.log(self.board.goal(), 2))):
            az[2**i] = chr(i + 96)

        if c == 0 and self.__azmode:
            return '.'
        elif c == 0:
            return '  .'

        elif self.__azmode:
            if c not in az:
                return '?'
            s = az[c]
        elif c == 1024:
            s = ' 1k'
        elif c == 2048:
            s = ' 2k'
        elif c == 4096:
            s = ' 4k'
        elif c == 8192:
            s = ' 8k'
        else:
            s = '%3d' % c

        return self.__colors.get(c, Fore.RESET) + s + Style.RESET_ALL

    def boardToString(self, margins={}):
        """
        return a string representation of the current board.
        """
        b = self.board
        rg = range(b.size())
        left = ' ' * margins.get('left', 0)
        s = '\n'.join(
            [left + ' '.join([self.getCellStr(x, y) for x in rg]) for y in rg])
        return s

    def __str__(self, margins={}):
        b = self.boardToString(margins=margins)
        top = '\n' * margins.get('top', 0)
        bottom = '\n' * margins.get('bottom', 0)
        scores = ' \tScore: %5d  Best: %5d\n' % (self.score, self.best_score)
        return top + b.replace('\n', scores, 1) + bottom
Exemple #7
0
class Game(object):
    """
    A 2048 game
    """

    __dirs = {
        keypress.UP:      Board.UP,
        keypress.DOWN:    Board.DOWN,
        keypress.LEFT:    Board.LEFT,
        keypress.RIGHT:   Board.RIGHT,
        keypress.SPACE:   Board.PAUSE,
    }

    __is_windows = os.name == 'nt'

    COLORS = {
        2:    Fore.GREEN,
        4:    Fore.BLUE + Style.BRIGHT,
        8:    Fore.CYAN,
        16:   Fore.RED,
        # Don't use MAGENTA directly; it doesn't display well on Windows.
        # see https://github.com/bfontaine/term2048/issues/24
        32:  Fore.MAGENTA + Style.BRIGHT,
        64:   Fore.CYAN,
        128:  Fore.BLUE + Style.BRIGHT,
        256:  Fore.MAGENTA + Style.BRIGHT,
        512:  Fore.GREEN,
        1024: Fore.RED,
        2048: Fore.YELLOW,
        # just in case people set an higher goal they still have colors
        4096: Fore.RED,
        8192: Fore.CYAN,
    }

    # see Game#adjustColors
    # these are color replacements for various modes
    __color_modes = {
        'dark': {
            Fore.BLUE: Fore.WHITE,
            Fore.BLUE + Style.BRIGHT: Fore.WHITE,
        },
        'light': {
            Fore.YELLOW: Fore.BLACK,
        },
    }

    SCORES_FILE = '%s/.term2048.scores' % os.path.expanduser('~')
    STORE_FILE = '%s/.term2048.store' % os.path.expanduser('~')

    def __init__(self, scores_file=SCORES_FILE, colors=None,
                 store_file=STORE_FILE, clear_screen=True,
                 mode=None, azmode=False, **kws):
        """
        Create a new game.
            scores_file: file to use for the best score (default
                         is ~/.term2048.scores)
            colors: dictionnary with colors to use for each tile
            store_file: file that stores game session's snapshot
            mode: color mode. This adjust a few colors and can be 'dark' or
                  'light'. See the adjustColors functions for more info.
            other options are passed to the underlying Board object.
        """
        self.board = Board(**kws)
        self.score = 0
        self.scores_file = scores_file
        self.store_file = store_file
        self.clear_screen = clear_screen

        self.best_score = 0

        self.__colors = colors or self.COLORS
        self.__azmode = azmode

        self.loadBestScore()
        self.adjustColors(mode)

    def adjustColors(self, mode='dark'):
        """
        Change a few colors depending on the mode to use. The default mode
        doesn't assume anything and avoid using white & black colors. The dark
        mode use white and avoid dark blue while the light mode use black and
        avoid yellow, to give a few examples.
        """
        rp = Game.__color_modes.get(mode, {})
        for k, color in self.__colors.items():
            self.__colors[k] = rp.get(color, color)

    def loadBestScore(self):
        """
        load local best score from the default file
        """
        try:
            with open(self.scores_file, 'r') as f:
                self.best_score = int(f.readline(), 10)
        except:
            return False
        return True

    def saveBestScore(self):
        """
        save current best score in the default file
        """
        if self.score > self.best_score:
            self.best_score = self.score
        try:
            with open(self.scores_file, 'w') as f:
                f.write(str(self.best_score))
        except:
            return False
        return True

    def incScore(self, pts):
        """
        update the current score by adding it the specified number of points
        """
        self.score += pts
        if self.score > self.best_score:
            self.best_score = self.score

    def readMove(self):
        """
        read and return a move to pass to a board
        """
        k = keypress.getKey()
        return Game.__dirs.get(k)

    def store(self):
        """
        save the current game session's score and data for further use
        """
        size = self.board.SIZE
        cells = []

        for i in range(size):
            for j in range(size):
                cells.append(str(self.board.getCell(j, i)))

        score_str = "%s\n%d" % (' '.join(cells), self.score)

        try:
            with open(self.store_file, 'w') as f:
                f.write(score_str)
        except:
            return False
        return True

    def restore(self):
        """
        restore the saved game score and data
        """

        size = self.board.SIZE

        try:
            with open(self.store_file, 'r') as f:
                lines = f.readlines()
                score_str = lines[0]
                self.score = int(lines[1])
        except:
            return False

        score_str_list = score_str.split(' ')
        count = 0

        for i in range(size):
            for j in range(size):
                value = score_str_list[count]
                self.board.setCell(j, i, int(value))
                count += 1

        return True

    def clearScreen(self):
        """Clear the console"""
        if self.clear_screen:
            os.system('cls' if self.__is_windows else 'clear')
        else:
            print('\n')

    def hideCursor(self):
        """
        Hide the cursor. Don't forget to call ``showCursor`` to restore
        the normal shell behavior. This is a no-op if ``clear_screen`` is
        falsy.
        """
        if not self.clear_screen:
            return
        if not self.__is_windows:
            sys.stdout.write('\033[?25l')

    def showCursor(self):
        """Show the cursor."""
        if not self.__is_windows:
            sys.stdout.write('\033[?25h')

    def loop(self):
        """
        main game loop. returns the final score.
        """
        pause_key = self.board.PAUSE
        margins = {'left': 4, 'top': 4, 'bottom': 4}

        atexit.register(self.showCursor)

        try:
            self.hideCursor()
            while True:
                self.clearScreen()
                print(self.__str__(margins=margins))
                if self.board.won() or not self.board.canMove():
                    break
                m = self.readMove()

                if m == pause_key:
                    self.saveBestScore()
                    if self.store():
                        print("Game successfully saved. "
                              "Resume it with `term2048 --resume`.")
                        return self.score

                    print("An error ocurred while saving your game.")
                    return None

                self.incScore(self.board.move(m))

        except KeyboardInterrupt:
            self.saveBestScore()
            return None

        self.saveBestScore()
        print('You won!' if self.board.won() else 'Game Over')
        return self.score

    def getCellStr(self, x, y):  # TODO: refactor regarding issue #11
        """
        return a string representation of the cell located at x,y.
        """
        c = self.board.getCell(x, y)

        if c == 0:
            return '.' if self.__azmode else '  .'

        elif self.__azmode:
            az = {}
            for i in range(1, int(math.log(self.board.goal(), 2))):
                az[2 ** i] = chr(i + 96)

            if c not in az:
                return '?'
            s = az[c]
        elif c == 1024:
            s = ' 1k'
        elif c == 2048:
            s = ' 2k'
        else:
            s = '%3d' % c

        return self.__colors.get(c, Fore.RESET) + s + Style.RESET_ALL

    def boardToString(self, margins=None):
        """
        return a string representation of the current board.
        """
        if margins is None:
            margins = {}

        b = self.board
        rg = range(b.size())
        left = ' '*margins.get('left', 0)
        s = '\n'.join(
            [left + ' '.join([self.getCellStr(x, y) for x in rg]) for y in rg])
        return s

    def __str__(self, margins=None):
        if margins is None:
            margins = {}
        b = self.boardToString(margins=margins)
        top = '\n'*margins.get('top', 0)
        bottom = '\n'*margins.get('bottom', 0)
        scores = ' \tScore: %5d  Best: %5d\n' % (self.score, self.best_score)
        return top + b.replace('\n', scores, 1) + bottom
Exemple #8
0
class Game(object):
    """
    A 2048 game
    """

    __dirs = {
        keypress.UP: Board.UP,
        keypress.DOWN: Board.DOWN,
        keypress.LEFT: Board.LEFT,
        keypress.RIGHT: Board.RIGHT,
        keypress.SPACE: Board.PAUSE,
    }

    __clear = "cls" if os.name == "nt" else "clear"

    COLORS = {
        2: Fore.GREEN,
        4: Fore.BLUE + Style.BRIGHT,
        8: Fore.CYAN,
        16: Fore.RED,
        32: Fore.MAGENTA,
        64: Fore.CYAN,
        128: Fore.BLUE + Style.BRIGHT,
        256: Fore.MAGENTA,
        512: Fore.GREEN,
        1024: Fore.RED,
        2048: Fore.YELLOW,
        # just in case people set an higher goal they still have colors
        4096: Fore.RED,
        8192: Fore.CYAN,
    }

    # see Game#adjustColors
    # these are color replacements for various modes
    __color_modes = {
        "dark": {Fore.BLUE: Fore.WHITE, Fore.BLUE + Style.BRIGHT: Fore.WHITE},
        "light": {Fore.YELLOW: Fore.BLACK},
    }

    SCORES_FILE = "%s/.term2048.scores" % os.path.expanduser("~")
    STORE_FILE = "%s/.term2048.store" % os.path.expanduser("~")

    def __init__(
        self,
        scores_file=SCORES_FILE,
        colors=COLORS,
        store_file=STORE_FILE,
        clear_screen=True,
        mode=None,
        azmode=False,
        **kws
    ):
        """
        Create a new game.
            scores_file: file to use for the best score (default
                         is ~/.term2048.scores)
            colors: dictionnary with colors to use for each tile
            store_file: file that stores game session's snapshot
            mode: color mode. This adjust a few colors and can be 'dark' or
                  'light'. See the adjustColors functions for more info.
            other options are passed to the underlying Board object.
        """
        self.board = Board(**kws)
        self.score = 0
        self.scores_file = scores_file
        self.store_file = store_file
        self.clear_screen = clear_screen

        self.__colors = colors
        self.__azmode = azmode

        self.loadBestScore()
        self.adjustColors(mode)

    def adjustColors(self, mode="dark"):
        """
        Change a few colors depending on the mode to use. The default mode
        doesn't assume anything and avoid using white & black colors. The dark
        mode use white and avoid dark blue while the light mode use black and
        avoid yellow, to give a few examples.
        """
        rp = Game.__color_modes.get(mode, {})
        for k, color in self.__colors.items():
            self.__colors[k] = rp.get(color, color)

    def loadBestScore(self):
        """
        load local best score from the default file
        """
        try:
            with open(self.scores_file, "r") as f:
                self.best_score = int(f.readline(), 10)
        except:
            self.best_score = 0
            return False
        return True

    def saveBestScore(self):
        """
        save current best score in the default file
        """
        if self.score > self.best_score:
            self.best_score = self.score
        try:
            with open(self.scores_file, "w") as f:
                f.write(str(self.best_score))
        except:
            return False
        return True

    def incScore(self, pts):
        """
        update the current score by adding it the specified number of points
        """
        self.score += pts
        if self.score > self.best_score:
            self.best_score = self.score

    def readMove(self):
        """
        read and return a move to pass to a board
        """
        k = keypress.getKey()
        return Game.__dirs.get(k)

    def store(self):
        """
        save the current game session's score and data for further use
        """
        size = self.board.SIZE
        cells = []

        for i in range(size):
            for j in range(size):
                cells.append(str(self.board.getCell(j, i)))

        score_str = "%s\n%d" % (" ".join(cells), self.score)

        try:
            with open(self.store_file, "w") as f:
                f.write(score_str)
        except:
            return False
        return True

    def restore(self):
        """
        restore the saved game score and data
        """

        size = self.board.SIZE

        try:
            with open(self.store_file, "r") as f:
                lines = f.readlines()
                score_str = lines[0]
                self.score = int(lines[1])
        except:
            return False

        score_str_list = score_str.split(" ")
        count = 0

        for i in range(size):
            for j in range(size):
                value = score_str_list[count]
                self.board.setCell(j, i, int(value))
                count += 1

        return True

    def loop(self):
        """
        main game loop. returns the final score.
        """
        pause_key = self.board.PAUSE
        margins = {"left": 4, "top": 4, "bottom": 4}

        try:
            while True:
                if self.clear_screen:
                    os.system(Game.__clear)
                else:
                    print("\n")
                print(self.__str__(margins=margins))
                if self.board.won() or not self.board.canMove():
                    break
                m = self.readMove()

                if m == pause_key:
                    self.saveBestScore()
                    if self.store():
                        print("Game successfully saved. " "Resume it with `term2048 --resume`.")
                        return self.score
                    print("An error ocurred while saving your game.")
                    return

                self.incScore(self.board.move(m))

        except KeyboardInterrupt:
            self.saveBestScore()
            return

        self.saveBestScore()
        print("You won!" if self.board.won() else "Game Over")
        return self.score

    def getCellStr(self, x, y):  # TODO: refactor regarding issue #11
        """
        return a string representation of the cell located at x,y.
        """
        c = self.board.getCell(x, y)

        if c == 0:
            return "." if self.__azmode else "  ."

        elif self.__azmode:
            az = {}
            for i in range(1, int(math.log(self.board.goal(), 2))):
                az[2 ** i] = chr(i + 96)

            if c not in az:
                return "?"
            s = az[c]
        elif c == 1024:
            s = " 1k"
        elif c == 2048:
            s = " 2k"
        else:
            s = "%3d" % c

        return self.__colors.get(c, Fore.RESET) + s + Style.RESET_ALL

    def boardToString(self, margins={}):
        """
        return a string representation of the current board.
        """
        b = self.board
        rg = range(b.size())
        left = " " * margins.get("left", 0)
        s = "\n".join([left + " ".join([self.getCellStr(x, y) for x in rg]) for y in rg])
        return s

    def __str__(self, margins={}):
        b = self.boardToString(margins=margins)
        top = "\n" * margins.get("top", 0)
        bottom = "\n" * margins.get("bottom", 0)
        scores = " \tScore: %5d  Best: %5d\n" % (self.score, self.best_score)
        return top + b.replace("\n", scores, 1) + bottom
Exemple #9
0
class TestBoard(unittest.TestCase):

    def setUp(self):
        self.b = Board()

    # == init == #
    def test_init_dimensions(self):
        self.assertEqual(len(self.b.cells), Board.SIZE)
        self.assertEqual(len(self.b.cells[0]), Board.SIZE)
        if Board.SIZE > 1:
            self.assertEqual(len(self.b.cells[1]), Board.SIZE)

    def test_init_dimensions_1(self):
        b = Board(size=1)
        c = b.cells[0][0]
        self.assertTrue(c in [2, 4])

    def test_init_dimensions_3_goal_4(self):
        b = Board(size=3, goal=4)
        self.assertEqual(b.size(), 3)

    def test_init_only_two_tiles(self):
        t = 0
        for x in xrange(Board.SIZE):
            for y in xrange(Board.SIZE):
                c = self.b.cells[y][x]
                if not c == 0:
                    t += 1
                else:
                    self.assertEqual(c, 0, 'board[%d][%d] should be 0' % (y, x))

        self.assertEqual(t, 2)

    def test_init_not_won(self):
        self.assertFalse(self.b.won())

    def test_init_not_filled(self):
        self.assertFalse(self.b.filled())

    # == .size == #
    def test_size(self):
        s = 42
        b = Board(size=s)
        self.assertEqual(b.size(), s)

    # == .goal == #
    def test_goal(self):
        g = 17
        b = Board(goal=g)
        self.assertEqual(b.goal(), g)

    # == .won == #
    def test_won(self):
        self.b._Board__won = True
        self.assertTrue(self.b.won())
        self.b._Board__won = False
        self.assertFalse(self.b.won())

    # == .canMove == #
    def test_canMove_no_empty_cell(self):
        b = Board(size=1)
        b.setCell(0, 0, 42)
        self.assertFalse(b.canMove())

    def test_canMove_empty_cell(self):
        b = Board(size=2)
        self.assertTrue(b.canMove())

    def test_canMove_no_empty_cell_can_collapse(self):
        b = Board(size=2)
        b.cells = [
            [2, 2],
            [4, 8]
        ]
        self.assertTrue(b.canMove())

    # == .filled == #
    def test_filled(self):
        self.b.cells = [[1]*Board.SIZE for _ in xrange(Board.SIZE)]
        self.assertTrue(self.b.filled())

    # == .addTile == #
    def test_addTile(self):
        b = Board(size=1)
        b.cells = [[0]]
        b.addTile(value=42)
        self.assertEqual(b.cells[0][0], 42)

    # == .getCell == #
    def test_getCell(self):
        x, y = 3, 1
        v = 42
        self.b.cells[y][x] = v
        self.assertEqual(self.b.getCell(x, y), v)

    # == .setCell == #
    def test_setCell(self):
        x, y = 2, 3
        v = 42
        self.b.setCell(x, y, v)
        self.assertEqual(self.b.cells[y][x], v)

    # == .getLine == #
    def test_getLine(self):
        b = Board(size=4)
        l = [42, 17, 12, 3]
        b.cells = [
            [0]*4,
            l,
            [0]*4,
            [0]*4
        ]
        self.assertSequenceEqual(b.getLine(1), l)

    # == .getCol == #
    def test_getCol(self):
        s = 4
        b = Board(size=s)
        l = [42, 17, 12, 3]
        b.cells = [[l[i], 4, 1, 2] for i in xrange(s)]
        self.assertSequenceEqual(b.getCol(0), l)

    # == .setLine == #
    def test_setLine(self):
        i = 2
        l = [1, 2, 3, 4]
        self.b.setLine(i, l)
        self.assertEqual(self.b.getLine(i), l)

    # == .setCol == #
    def test_setCol(self):
        i = 2
        l = [1, 2, 3, 4]
        self.b.setCol(i, l)
        self.assertEqual(self.b.getCol(i), l)

    # == .getEmptyCells == #
    def test_getEmptyCells(self):
        self.assertEqual(len(self.b.getEmptyCells()), Board.SIZE**2 - 2)

    def test_getEmptyCells_filled(self):
        b = Board(size=1)
        b.setCell(0, 0, 42)
        self.assertSequenceEqual(b.getEmptyCells(), [])

    # == .move == #
    def test_move_filled(self):
        b = Board(size=1)
        b.setCell(0, 0, 42)
        b.move(Board.UP)
        self.assertSequenceEqual(b.cells, [[42]])
        b.move(Board.LEFT)
        self.assertSequenceEqual(b.cells, [[42]])
        b.move(Board.RIGHT)
        self.assertSequenceEqual(b.cells, [[42]])
        b.move(Board.DOWN)
        self.assertSequenceEqual(b.cells, [[42]])

    def test_move_add_tile_if_collapse(self):
        b = Board(size=2)
        b.cells = [[2, 0],
                   [2, 0]]
        b.move(Board.UP)
        self.assertEqual(len([e for l in b.cells for e in l if e != 0]), 2)

    def test_move_add_tile_if_move(self):
        b = Board(size=2)
        b.cells = [[0, 0],
                   [2, 0]]
        b.move(Board.UP)
        self.assertEqual(len([e for l in b.cells for e in l if e != 0]), 2)

    def test_move_dont_add_tile_if_nothing_move(self):
        b = Board(size=2)
        b.cells = [[2, 0],
                   [0, 0]]
        b.move(Board.UP)
        self.assertEqual(len([e for l in b.cells for e in l if e != 0]), 1)

    # test for issue #1
    def test_move_dont_add_tile_if_nothing_move2(self):
        b = Board()
        b.cells = [
            [8, 4, 4, 2],
            [0, 2, 2, 0],
            [0]*4,
            [0]*4
        ]
        self.assertEqual(b.move(Board.UP), 0)
        self.assertEqual(len([e for l in b.cells for e in l if e != 0]), 6)
        self.assertEqual(b.getLine(0), [8, 4, 4, 2])
        self.assertEqual(b.getLine(1), [0, 2, 2, 0])

    def test_move_collapse(self):
        b = Board(size=2)
        b.cells = [
            [2, 2],
            [0, 0]
        ]

        b.move(Board.LEFT, add_tile=False)
        self.assertSequenceEqual(b.cells, [
            [4, 0],
            [0, 0]
        ])

    def test_move_collapse_triplet1(self):
        b = Board(size=3)
        b.setLine(0, [2, 2, 2])
        b.move(Board.LEFT, add_tile=False)
        self.assertSequenceEqual(b.getLine(0), [4, 2, 0])

    def test_move_collapse_triplet2(self):
        b = Board(size=3)
        b.setLine(0, [2, 2, 2])
        b.move(Board.RIGHT, add_tile=False)
        self.assertSequenceEqual(b.getLine(0), [0, 2, 4])

    def test_move_collapse_with_empty_cell_in_between(self):
        b = Board(size=3)
        b.setLine(0, [2, 0, 2])
        b.move(Board.RIGHT, add_tile=False)
        self.assertSequenceEqual(b.getLine(0), [0, 0, 4])

    def test_move_collapse_with_empty_cell_in_between2(self):
        b = Board(size=3)
        b.setLine(0, [2, 0, 2])
        b.move(Board.LEFT, add_tile=False)
        self.assertSequenceEqual(b.getLine(0), [4, 0, 0])

    def test_move_collapse_and_win(self):
        b = Board(size=2, goal=4)
        b.cells = [
            [2, 2],
            [0, 0]
        ]
        b.move(Board.LEFT, add_tile=False)
        self.assertTrue(b.won())

    def test_move_wrong_direction(self):
        self.assertEqual(self.b.move(42, add_tile=False), 0)
        self.assertEqual(self.b.move(None), 0)
        self.assertEqual(self.b.move("up"), 0)


    # tests for weird-collapse-bug reported on HN (issue #2)
    #   see: https://news.ycombinator.com/item?id=7398249

    def test_move_collapse_chain_col(self):
        b = Board()
        b.setCol(0, [0, 2, 2, 4])
        b.move(Board.DOWN, add_tile=False)
        self.assertSequenceEqual(b.getCol(0), [0, 0, 4, 4])

    def test_move_collapse_chain_line_right(self):
        b = Board()
        b.cells = [
            [0, 2, 2, 4],
            [0]*4,
            [0]*4,
            [0]*4
        ]
        self.assertEqual(b.move(Board.RIGHT, add_tile=False), 4)
        self.assertSequenceEqual(b.getLine(0), [0, 0, 4, 4])

    def test_move_collapse_chain_line_right2(self):
        b = Board()
        b.cells = [
            [0, 4, 2, 2],
            [0]*4,
            [0]*4,
            [0]*4
        ]
        self.assertEqual(b.move(Board.RIGHT, add_tile=False), 4)
        self.assertSequenceEqual(b.getLine(0), [0, 0, 4, 4])

    def test_move_collapse_chain_line_left(self):
        b = Board()
        b.cells = [
            [0, 2, 2, 4],
            [0]*4,
            [0]*4,
            [0]*4
        ]
        self.assertEqual(b.move(Board.LEFT, add_tile=False), 4)
        self.assertSequenceEqual(b.getLine(0), [4, 4, 0, 0])

    def test_move_collapse_chain_four_same_tiles(self):
        b = Board()
        b.cells = [
            [2, 2, 2, 2],
            [0]*4,
            [0]*4,
            [0]*4
        ]
        self.assertEqual(b.move(Board.LEFT, add_tile=False), 8)
        self.assertSequenceEqual(b.getLine(0), [4, 4, 0, 0])