-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path2048pygame.py
218 lines (184 loc) · 7.34 KB
/
2048pygame.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env python
"""2048 game using PyGame"""
import argparse
import sys
import pygame
from grid2048 import Grid2048
from players import player, player_factory
from players.user_player import PygamePlayer
player_factory.register("user", PygamePlayer)
# Constants
WINDOW_SIZE = 400
PADDING = 10
# Colors (similar to the image)
COLORS = {
0: (205, 193, 180), # Empty tile
2: (238, 228, 218), # 2
4: (237, 224, 200), # 4
8: (242, 177, 121), # 8
16: (245, 149, 99), # 16
32: (246, 124, 95), # 32
64: (246, 94, 59), # 64
128: (237, 207, 114), # 128
256: (237, 204, 97), # 256
512: (237, 200, 80), # 512
1024: (237, 197, 63), # 1024
2048: (237, 194, 46), # 2048
}
BACKGROUND_COLOR = (187, 173, 160)
TEXT_LIGHT = (249, 246, 242) # For dark tiles
TEXT_DARK = (119, 110, 101) # For light tiles
class Game2048:
"""2048 game class with PyGame interface"""
def __init__(self, width: int, height: int, player_type: str, fps: int):
pygame.init() # pylint: disable=no-member
self.width = width
self.height = height
self.player_type = player_type
self.fps = fps
self.init_game()
# Calculate cell size based on window size and grid dimensions
self.cell_size = (WINDOW_SIZE - (width + 1) * PADDING) // width
window_width = width * self.cell_size + (width + 1) * PADDING
window_height = (
height * self.cell_size + (height + 1) * PADDING + 50
) # Extra height for score
self.window = pygame.display.set_mode((window_width, window_height))
self.font = pygame.font.SysFont("Arial", 36, True)
self.score_font = pygame.font.SysFont("Arial", 24, True)
self.clock = pygame.time.Clock()
self.update_title()
self.game_over = False
self.paused = False
def update_title(self):
"""Update the window title with the current FPS"""
pygame.display.set_caption(
f"2048PyGame: {self.player_type} player ({self.width}x{self.height}) {self.clock.get_fps():.2f} FPS"
if self.player_type
else "2048PyGame"
)
def init_game(self):
"""Initialize or reset the game state"""
self.grid = Grid2048(self.width, self.height)
if self.player_type:
self.player = player_factory.create(self.player_type, self.grid)
else:
self.player = PygamePlayer(self.grid)
self.game_over = False
self.paused = False
def draw_tile(self, value, x, y):
"""Draw a single tile with its value"""
color = COLORS.get(value, COLORS[0])
rect = pygame.Rect(x, y, self.cell_size, self.cell_size)
pygame.draw.rect(self.window, color, rect, border_radius=4)
if value != 0:
text_color = TEXT_LIGHT if value > 4 else TEXT_DARK
text = self.font.render(str(value), True, text_color)
text_rect = text.get_rect(
center=(x + self.cell_size / 2, y + self.cell_size / 2)
)
self.window.blit(text, text_rect)
def draw_overlay_text(self, main_text, sub_text=None):
"""Draw overlay with text messages"""
s = pygame.Surface((self.window.get_width(), self.window.get_height()))
s.set_alpha(128)
s.fill((255, 255, 255))
self.window.blit(s, (0, 0))
main_text_surface = self.font.render(main_text, True, TEXT_DARK)
main_rect = main_text_surface.get_rect(
center=(self.window.get_width() / 2, self.window.get_height() / 2 - 20)
)
self.window.blit(main_text_surface, main_rect)
if sub_text:
sub_text_surface = self.score_font.render(sub_text, True, TEXT_DARK)
sub_rect = sub_text_surface.get_rect(
center=(self.window.get_width() / 2, self.window.get_height() / 2 + 20)
)
self.window.blit(sub_text_surface, sub_rect)
def draw(self):
"""Draw the game board"""
self.window.fill(BACKGROUND_COLOR)
# Draw score
score_text = self.score_font.render(
f"Score: {self.grid.score}", True, TEXT_DARK
)
self.window.blit(score_text, (PADDING, PADDING))
# Draw grid
start_y = 50 # Start grid below score
for i in range(self.grid.height):
for j in range(self.grid.width):
x = j * self.cell_size + (j + 1) * PADDING
y = i * self.cell_size + (i + 1) * PADDING + start_y
self.draw_tile(self.grid[i, j], x, y)
# Draw overlays
if self.game_over:
self.draw_overlay_text("GAME OVER", "Press R to restart")
elif self.paused and isinstance(self.player, PygamePlayer):
self.draw_overlay_text("PAUSED", "Space to resume, ESC to quit")
pygame.display.flip()
def run(self):
"""Main game loop"""
running = True
while running:
self.draw()
self.update_title()
for event in pygame.event.get():
if event.type == pygame.QUIT: # pylint: disable=no-member
running = False
break
elif event.type == pygame.KEYDOWN: # pylint: disable=no-member
if (
event.key == pygame.K_r # pylint: disable=no-member
and self.game_over
):
self.init_game()
elif event.key == pygame.K_SPACE: # pylint: disable=no-member
self.paused = not self.paused
elif event.key == pygame.K_ESCAPE: # pylint: disable=no-member
running = False
break
# Must be drawn here to avoid hangs during AI player's computation
self.draw()
if not self.game_over and not self.paused:
# Allow AI players to play without an event
if self.player_type != "user":
self.player.play()
else:
self.player.play(event=event)
event = None # Clear event to avoid replaying the same move
# Check for game over
if self.grid.no_moves:
self.game_over = True
self.clock.tick(self.fps)
pygame.quit() # pylint: disable=no-member
def main():
"""Main entry point"""
parser = argparse.ArgumentParser(description="2048 game with PyGame")
parser.add_argument(
"-c", "--cols", "--width", type=int, default=4, help="width of the grid"
)
parser.add_argument(
"-r", "--rows", "--height", type=int, default=4, help="height of the grid"
)
parser.add_argument(
"-p",
"--player",
type=str,
help="player type (e.g., random, cycle, minimax, expectimax, mcs, mcst)",
)
parser.add_argument(
"-fps",
"-i",
type=int,
help="interval - max frames per second (default: 10, set 0 for unlimited)",
default=10,
)
args = parser.parse_args()
if args.player and args.player not in player_factory.container:
print(f"Invalid player type: {args.player!r}")
sys.exit(1)
player = args.player or "user"
game = Game2048(args.cols, args.rows, player, args.fps)
game.run()
if __name__ == "__main__":
main()