import pygame
|
|
import sys
|
|
import time
|
|
|
|
from minesweeper import Minesweeper, MinesweeperAI
|
|
|
|
HEIGHT = 8
|
|
WIDTH = 8
|
|
MINES = 8
|
|
|
|
# Colors
|
|
BLACK = (0, 0, 0)
|
|
GRAY = (180, 180, 180)
|
|
WHITE = (255, 255, 255)
|
|
|
|
# Create game
|
|
pygame.init()
|
|
size = width, height = 600, 400
|
|
screen = pygame.display.set_mode(size)
|
|
|
|
# Fonts
|
|
OPEN_SANS = "assets/fonts/OpenSans-Regular.ttf"
|
|
smallFont = pygame.font.Font(OPEN_SANS, 20)
|
|
mediumFont = pygame.font.Font(OPEN_SANS, 28)
|
|
largeFont = pygame.font.Font(OPEN_SANS, 40)
|
|
|
|
# Compute board size
|
|
BOARD_PADDING = 20
|
|
board_width = ((2 / 3) * width) - (BOARD_PADDING * 2)
|
|
board_height = height - (BOARD_PADDING * 2)
|
|
cell_size = int(min(board_width / WIDTH, board_height / HEIGHT))
|
|
board_origin = (BOARD_PADDING, BOARD_PADDING)
|
|
|
|
# Add images
|
|
flag = pygame.image.load("assets/images/flag.png")
|
|
flag = pygame.transform.scale(flag, (cell_size, cell_size))
|
|
mine = pygame.image.load("assets/images/mine.png")
|
|
mine = pygame.transform.scale(mine, (cell_size, cell_size))
|
|
|
|
# Create game and AI agent
|
|
game = Minesweeper(height=HEIGHT, width=WIDTH, mines=MINES)
|
|
ai = MinesweeperAI(height=HEIGHT, width=WIDTH)
|
|
|
|
# Keep track of revealed cells, flagged cells, and if a mine was hit
|
|
revealed = set()
|
|
flags = set()
|
|
lost = False
|
|
|
|
# Show instructions initially
|
|
instructions = True
|
|
|
|
while True:
|
|
|
|
# Check if game quit
|
|
for event in pygame.event.get():
|
|
if event.type == pygame.QUIT:
|
|
sys.exit()
|
|
|
|
screen.fill(BLACK)
|
|
|
|
# Show game instructions
|
|
if instructions:
|
|
|
|
# Title
|
|
title = largeFont.render("Play Minesweeper", True, WHITE)
|
|
titleRect = title.get_rect()
|
|
titleRect.center = ((width / 2), 50)
|
|
screen.blit(title, titleRect)
|
|
|
|
# Rules
|
|
rules = [
|
|
"Click a cell to reveal it.",
|
|
"Right-click a cell to mark it as a mine.",
|
|
"Mark all mines successfully to win!"
|
|
]
|
|
for i, rule in enumerate(rules):
|
|
line = smallFont.render(rule, True, WHITE)
|
|
lineRect = line.get_rect()
|
|
lineRect.center = ((width / 2), 150 + 30 * i)
|
|
screen.blit(line, lineRect)
|
|
|
|
# Play game button
|
|
buttonRect = pygame.Rect((width / 4), (3 / 4) * height, width / 2, 50)
|
|
buttonText = mediumFont.render("Play Game", True, BLACK)
|
|
buttonTextRect = buttonText.get_rect()
|
|
buttonTextRect.center = buttonRect.center
|
|
pygame.draw.rect(screen, WHITE, buttonRect)
|
|
screen.blit(buttonText, buttonTextRect)
|
|
|
|
# Check if play button clicked
|
|
click, _, _ = pygame.mouse.get_pressed()
|
|
if click == 1:
|
|
mouse = pygame.mouse.get_pos()
|
|
if buttonRect.collidepoint(mouse):
|
|
instructions = False
|
|
time.sleep(0.3)
|
|
|
|
pygame.display.flip()
|
|
continue
|
|
|
|
# Draw board
|
|
cells = []
|
|
for i in range(HEIGHT):
|
|
row = []
|
|
for j in range(WIDTH):
|
|
|
|
# Draw rectangle for cell
|
|
rect = pygame.Rect(
|
|
board_origin[0] + j * cell_size,
|
|
board_origin[1] + i * cell_size,
|
|
cell_size, cell_size
|
|
)
|
|
pygame.draw.rect(screen, GRAY, rect)
|
|
pygame.draw.rect(screen, WHITE, rect, 3)
|
|
|
|
# Add a mine, flag, or number if needed
|
|
if game.is_mine((i, j)) and lost:
|
|
screen.blit(mine, rect)
|
|
elif (i, j) in flags:
|
|
screen.blit(flag, rect)
|
|
elif (i, j) in revealed:
|
|
neighbors = smallFont.render(
|
|
str(game.nearby_mines((i, j))),
|
|
True, BLACK
|
|
)
|
|
neighborsTextRect = neighbors.get_rect()
|
|
neighborsTextRect.center = rect.center
|
|
screen.blit(neighbors, neighborsTextRect)
|
|
|
|
row.append(rect)
|
|
cells.append(row)
|
|
|
|
# AI Move button
|
|
aiButton = pygame.Rect(
|
|
(2 / 3) * width + BOARD_PADDING, (1 / 3) * height - 50,
|
|
(width / 3) - BOARD_PADDING * 2, 50
|
|
)
|
|
buttonText = mediumFont.render("AI Move", True, BLACK)
|
|
buttonRect = buttonText.get_rect()
|
|
buttonRect.center = aiButton.center
|
|
pygame.draw.rect(screen, WHITE, aiButton)
|
|
screen.blit(buttonText, buttonRect)
|
|
|
|
# Reset button
|
|
resetButton = pygame.Rect(
|
|
(2 / 3) * width + BOARD_PADDING, (1 / 3) * height + 20,
|
|
(width / 3) - BOARD_PADDING * 2, 50
|
|
)
|
|
buttonText = mediumFont.render("Reset", True, BLACK)
|
|
buttonRect = buttonText.get_rect()
|
|
buttonRect.center = resetButton.center
|
|
pygame.draw.rect(screen, WHITE, resetButton)
|
|
screen.blit(buttonText, buttonRect)
|
|
|
|
# Display text
|
|
text = "Lost" if lost else "Won" if game.mines == flags else ""
|
|
text = mediumFont.render(text, True, WHITE)
|
|
textRect = text.get_rect()
|
|
textRect.center = ((5 / 6) * width, (2 / 3) * height)
|
|
screen.blit(text, textRect)
|
|
|
|
move = None
|
|
|
|
left, _, right = pygame.mouse.get_pressed()
|
|
|
|
# Check for a right-click to toggle flagging
|
|
if right == 1 and not lost:
|
|
mouse = pygame.mouse.get_pos()
|
|
for i in range(HEIGHT):
|
|
for j in range(WIDTH):
|
|
if cells[i][j].collidepoint(mouse) and (i, j) not in revealed:
|
|
if (i, j) in flags:
|
|
flags.remove((i, j))
|
|
else:
|
|
flags.add((i, j))
|
|
time.sleep(0.2)
|
|
|
|
elif left == 1:
|
|
mouse = pygame.mouse.get_pos()
|
|
|
|
# If AI button clicked, make an AI move
|
|
if aiButton.collidepoint(mouse) and not lost:
|
|
move = ai.make_safe_move()
|
|
if move is None:
|
|
move = ai.make_random_move()
|
|
if move is None:
|
|
flags = ai.mines.copy()
|
|
print("No moves left to make.")
|
|
else:
|
|
print("No known safe moves, AI making random move.")
|
|
else:
|
|
print("AI making safe move.")
|
|
time.sleep(0.2)
|
|
|
|
# Reset game state
|
|
elif resetButton.collidepoint(mouse):
|
|
game = Minesweeper(height=HEIGHT, width=WIDTH, mines=MINES)
|
|
ai = MinesweeperAI(height=HEIGHT, width=WIDTH)
|
|
revealed = set()
|
|
flags = set()
|
|
lost = False
|
|
continue
|
|
|
|
# User-made move
|
|
elif not lost:
|
|
for i in range(HEIGHT):
|
|
for j in range(WIDTH):
|
|
if (cells[i][j].collidepoint(mouse)
|
|
and (i, j) not in flags
|
|
and (i, j) not in revealed):
|
|
move = (i, j)
|
|
|
|
# Make move and update AI knowledge
|
|
if move:
|
|
if game.is_mine(move):
|
|
lost = True
|
|
else:
|
|
nearby = game.nearby_mines(move)
|
|
revealed.add(move)
|
|
ai.add_knowledge(move, nearby)
|
|
|
|
pygame.display.flip()
|