My solutions to Harvard's online course CS50AI, An Introduction to Machine Learning
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

222 lines
6.7 KiB

4 years ago
  1. import pygame
  2. import sys
  3. import time
  4. from minesweeper import Minesweeper, MinesweeperAI
  5. HEIGHT = 8
  6. WIDTH = 8
  7. MINES = 8
  8. # Colors
  9. BLACK = (0, 0, 0)
  10. GRAY = (180, 180, 180)
  11. WHITE = (255, 255, 255)
  12. # Create game
  13. pygame.init()
  14. size = width, height = 600, 400
  15. screen = pygame.display.set_mode(size)
  16. # Fonts
  17. OPEN_SANS = "assets/fonts/OpenSans-Regular.ttf"
  18. smallFont = pygame.font.Font(OPEN_SANS, 20)
  19. mediumFont = pygame.font.Font(OPEN_SANS, 28)
  20. largeFont = pygame.font.Font(OPEN_SANS, 40)
  21. # Compute board size
  22. BOARD_PADDING = 20
  23. board_width = ((2 / 3) * width) - (BOARD_PADDING * 2)
  24. board_height = height - (BOARD_PADDING * 2)
  25. cell_size = int(min(board_width / WIDTH, board_height / HEIGHT))
  26. board_origin = (BOARD_PADDING, BOARD_PADDING)
  27. # Add images
  28. flag = pygame.image.load("assets/images/flag.png")
  29. flag = pygame.transform.scale(flag, (cell_size, cell_size))
  30. mine = pygame.image.load("assets/images/mine.png")
  31. mine = pygame.transform.scale(mine, (cell_size, cell_size))
  32. # Create game and AI agent
  33. game = Minesweeper(height=HEIGHT, width=WIDTH, mines=MINES)
  34. ai = MinesweeperAI(height=HEIGHT, width=WIDTH)
  35. # Keep track of revealed cells, flagged cells, and if a mine was hit
  36. revealed = set()
  37. flags = set()
  38. lost = False
  39. # Show instructions initially
  40. instructions = True
  41. while True:
  42. # Check if game quit
  43. for event in pygame.event.get():
  44. if event.type == pygame.QUIT:
  45. sys.exit()
  46. screen.fill(BLACK)
  47. # Show game instructions
  48. if instructions:
  49. # Title
  50. title = largeFont.render("Play Minesweeper", True, WHITE)
  51. titleRect = title.get_rect()
  52. titleRect.center = ((width / 2), 50)
  53. screen.blit(title, titleRect)
  54. # Rules
  55. rules = [
  56. "Click a cell to reveal it.",
  57. "Right-click a cell to mark it as a mine.",
  58. "Mark all mines successfully to win!"
  59. ]
  60. for i, rule in enumerate(rules):
  61. line = smallFont.render(rule, True, WHITE)
  62. lineRect = line.get_rect()
  63. lineRect.center = ((width / 2), 150 + 30 * i)
  64. screen.blit(line, lineRect)
  65. # Play game button
  66. buttonRect = pygame.Rect((width / 4), (3 / 4) * height, width / 2, 50)
  67. buttonText = mediumFont.render("Play Game", True, BLACK)
  68. buttonTextRect = buttonText.get_rect()
  69. buttonTextRect.center = buttonRect.center
  70. pygame.draw.rect(screen, WHITE, buttonRect)
  71. screen.blit(buttonText, buttonTextRect)
  72. # Check if play button clicked
  73. click, _, _ = pygame.mouse.get_pressed()
  74. if click == 1:
  75. mouse = pygame.mouse.get_pos()
  76. if buttonRect.collidepoint(mouse):
  77. instructions = False
  78. time.sleep(0.3)
  79. pygame.display.flip()
  80. continue
  81. # Draw board
  82. cells = []
  83. for i in range(HEIGHT):
  84. row = []
  85. for j in range(WIDTH):
  86. # Draw rectangle for cell
  87. rect = pygame.Rect(
  88. board_origin[0] + j * cell_size,
  89. board_origin[1] + i * cell_size,
  90. cell_size, cell_size
  91. )
  92. pygame.draw.rect(screen, GRAY, rect)
  93. pygame.draw.rect(screen, WHITE, rect, 3)
  94. # Add a mine, flag, or number if needed
  95. if game.is_mine((i, j)) and lost:
  96. screen.blit(mine, rect)
  97. elif (i, j) in flags:
  98. screen.blit(flag, rect)
  99. elif (i, j) in revealed:
  100. neighbors = smallFont.render(
  101. str(game.nearby_mines((i, j))),
  102. True, BLACK
  103. )
  104. neighborsTextRect = neighbors.get_rect()
  105. neighborsTextRect.center = rect.center
  106. screen.blit(neighbors, neighborsTextRect)
  107. row.append(rect)
  108. cells.append(row)
  109. # AI Move button
  110. aiButton = pygame.Rect(
  111. (2 / 3) * width + BOARD_PADDING, (1 / 3) * height - 50,
  112. (width / 3) - BOARD_PADDING * 2, 50
  113. )
  114. buttonText = mediumFont.render("AI Move", True, BLACK)
  115. buttonRect = buttonText.get_rect()
  116. buttonRect.center = aiButton.center
  117. pygame.draw.rect(screen, WHITE, aiButton)
  118. screen.blit(buttonText, buttonRect)
  119. # Reset button
  120. resetButton = pygame.Rect(
  121. (2 / 3) * width + BOARD_PADDING, (1 / 3) * height + 20,
  122. (width / 3) - BOARD_PADDING * 2, 50
  123. )
  124. buttonText = mediumFont.render("Reset", True, BLACK)
  125. buttonRect = buttonText.get_rect()
  126. buttonRect.center = resetButton.center
  127. pygame.draw.rect(screen, WHITE, resetButton)
  128. screen.blit(buttonText, buttonRect)
  129. # Display text
  130. text = "Lost" if lost else "Won" if game.mines == flags else ""
  131. text = mediumFont.render(text, True, WHITE)
  132. textRect = text.get_rect()
  133. textRect.center = ((5 / 6) * width, (2 / 3) * height)
  134. screen.blit(text, textRect)
  135. move = None
  136. left, _, right = pygame.mouse.get_pressed()
  137. # Check for a right-click to toggle flagging
  138. if right == 1 and not lost:
  139. mouse = pygame.mouse.get_pos()
  140. for i in range(HEIGHT):
  141. for j in range(WIDTH):
  142. if cells[i][j].collidepoint(mouse) and (i, j) not in revealed:
  143. if (i, j) in flags:
  144. flags.remove((i, j))
  145. else:
  146. flags.add((i, j))
  147. time.sleep(0.2)
  148. elif left == 1:
  149. mouse = pygame.mouse.get_pos()
  150. # If AI button clicked, make an AI move
  151. if aiButton.collidepoint(mouse) and not lost:
  152. move = ai.make_safe_move()
  153. if move is None:
  154. move = ai.make_random_move()
  155. if move is None:
  156. flags = ai.mines.copy()
  157. print("No moves left to make.")
  158. else:
  159. print("No known safe moves, AI making random move.")
  160. else:
  161. print("AI making safe move.")
  162. time.sleep(0.2)
  163. # Reset game state
  164. elif resetButton.collidepoint(mouse):
  165. game = Minesweeper(height=HEIGHT, width=WIDTH, mines=MINES)
  166. ai = MinesweeperAI(height=HEIGHT, width=WIDTH)
  167. revealed = set()
  168. flags = set()
  169. lost = False
  170. continue
  171. # User-made move
  172. elif not lost:
  173. for i in range(HEIGHT):
  174. for j in range(WIDTH):
  175. if (cells[i][j].collidepoint(mouse)
  176. and (i, j) not in flags
  177. and (i, j) not in revealed):
  178. move = (i, j)
  179. # Make move and update AI knowledge
  180. if move:
  181. if game.is_mine(move):
  182. lost = True
  183. else:
  184. nearby = game.nearby_mines(move)
  185. revealed.add(move)
  186. ai.add_knowledge(move, nearby)
  187. pygame.display.flip()