Hide keyboard shortcuts

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 

6 

7 

8class GameOverException(RuntimeError): 

9 """ 

10 Raised when the game is over. 

11 """ 

12 pass 

13 

14 

15class Game2048State: 

16 """ 

17 To store additional information while guessing the best 

18 move. 

19 """ 

20 

21 def __init__(self, game): 

22 self.game = game 

23 

24 

25class Game2048: 

26 """ 

27 Implements the logic of the game 2048. 

28 """ 

29 

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) 

38 

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)) 

46 

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 

50 

51 def copy(self): 

52 "Makes a copy of the game." 

53 return Game2048(self.game.copy()) 

54 

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 

68 

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 

94 

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 

113 

114 def score(self): 

115 "Returns the maximum values." 

116 return numpy.max(self.game) 

117 

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. 

123 

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)