Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Implements the rule the 2048 game.
3"""
4import random
5import numpy
8class GameOverException(RuntimeError):
9 """
10 Raised when the game is over.
11 """
12 pass
15class Game2048State:
16 """
17 To store additional information while guessing the best
18 move.
19 """
21 def __init__(self, game):
22 self.game = game
25class Game2048:
26 """
27 Implements the logic of the game 2048.
28 """
30 def __init__(self, game=None):
31 """
32 :param game: None or matrix 4x4
33 """
34 self.game = (game if game is not None
35 else numpy.zeros((4, 4), dtype=int))
36 self.moves = []
37 self.state = Game2048State(self)
39 def __str__(self):
40 "Displays the game as a string."
41 if len(self.moves) > 3:
42 last_moves = self.moves[-3:]
43 else:
44 last_moves = self.moves
45 return "{}\n{}".format(str(self.game), str(last_moves))
47 def gameover(self):
48 "Checks the game is over or not. Returns True in that case."
49 return numpy.ma.masked_not_equal(self.game, 0).count() == 0
51 def copy(self):
52 "Makes a copy of the game."
53 return Game2048(self.game.copy())
55 def next_turn(self):
56 "Adds a number in the game."
57 if self.gameover():
58 raise GameOverException("Game Over\n" + str(self.game))
59 else:
60 while True:
61 i = random.randint(0, self.game.shape[0] - 1)
62 j = random.randint(0, self.game.shape[1] - 1)
63 if self.game[i, j] == 0:
64 n = random.randint(0, 3)
65 self.game[i, j] = 4 if n == 0 else 2
66 self.moves.append((i, j, self.game[i, j]))
67 break
69 @staticmethod
70 def process_line(line):
71 """
72 Moves numbers inside a vector whether this vector represents
73 a row or a column.
74 """
75 res = []
76 for n in line:
77 if n == 0:
78 # Zero: skipped.
79 continue
80 if len(res) == 0:
81 # First number: add.
82 res.append(n)
83 else:
84 prev = res[-1]
85 if prev == n:
86 # The number is identical: combine.
87 res[-1] = 2 * n
88 else:
89 # Otherwise: add.
90 res.append(n)
91 while len(res) < len(line):
92 res.append(0)
93 return res
95 def play(self, direction):
96 "Updates the game after a direction was chosen."
97 if direction == 0:
98 lines = [Game2048.process_line(self.game[i, :])
99 for i in range(self.game.shape[0])]
100 self.game = numpy.array(lines)
101 elif direction == 1:
102 lines = [Game2048.process_line(self.game[:, i])
103 for i in range(self.game.shape[1])]
104 self.game = numpy.array(lines).T
105 elif direction == 2:
106 lines = [list(reversed(Game2048.process_line(self.game[i, ::-1])))
107 for i in range(self.game.shape[0])]
108 self.game = numpy.array(lines)
109 elif direction == 3:
110 lines = [list(reversed(Game2048.process_line(self.game[::-1, i])))
111 for i in range(self.game.shape[1])]
112 self.game = numpy.array(lines).T
114 def score(self):
115 "Returns the maximum values."
116 return numpy.max(self.game)
118 def best_move(self, game=None, state=None, moves=None):
119 """
120 Selects the best move knowing the current game.
121 By default, selects a random direction.
122 This function must not modify the game.
124 :param game: 4x4 matrix or None for the current matrix
125 :param moves: all moves since the begining
126 :param state: to store additional values
127 :return: one integer
128 """
129 if game is None:
130 game = self.game
131 if state is None:
132 state = self.state
133 if moves is None:
134 moves = self.moves
135 if moves is None:
136 raise ValueError("moves cannot be None")
137 if not isinstance(game, numpy.ndarray) or game.shape != (4, 4):
138 raise ValueError("game must be a matrix (4x4).")
139 return random.randint(0, 3)