Advertisement
Guest User

Untitled

a guest
May 16th, 2019
97
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 21.65 KB | None | 0 0
  1. import os
  2. import json
  3. import pygame
  4. from . board import Board
  5. from . gui import SelectionGroup, Input, Button, Label, InputDialogue
  6. from . leaderboard import Leaderboard
  7.  
  8.  
  9. ASSETS_DIR = os.path.join(os.path.dirname(__file__), 'assets')
  10.  
  11.  
  12. def load_image(name, size=None):
  13.     """Load image and optionally resize it."""
  14.     path = os.path.join(ASSETS_DIR, name)
  15.     try:
  16.         image = pygame.image.load(path)
  17.     except pygame.error as error:
  18.         print('Cannot load image: ', path)
  19.         raise SystemError(error)
  20.  
  21.     if size is not None:
  22.         if isinstance(size, int):
  23.             size = (size, size)
  24.         image = pygame.transform.scale(image, size)
  25.  
  26.     return image
  27.  
  28.  
  29. def load_font(name, size):
  30.     path = os.path.join(ASSETS_DIR, name)
  31.     try:
  32.         font = pygame.font.Font(path, size)
  33.     except pygame.error as error:
  34.         print('Cannot load font: ', path)
  35.         raise SystemError(error)
  36.     return font
  37.  
  38.  
  39. class Timer:
  40.     """Execute event on timer.
  41.  
  42.    Parameters
  43.    ----------
  44.    on_time_event L callable
  45.        Call this event on timer.
  46.    """
  47.     def __init__(self, on_time_event):
  48.         self.on_time_event = on_time_event
  49.         self.start_time = None
  50.         self.interval = None
  51.         self.running = False
  52.  
  53.     def start(self, interval):
  54.         """Start timer now and trigger event after `interval`."""
  55.         self.running = True
  56.         self.interval = interval
  57.         self.start_time = pygame.time.get_ticks()
  58.  
  59.     def check(self):
  60.         """Check whether event occurred.
  61.  
  62.        Must be called continuously in the main loop."""
  63.         if (self.running and
  64.                 pygame.time.get_ticks() - self.start_time >= self.interval):
  65.             self.running = False
  66.             self.on_time_event()
  67.  
  68.  
  69. def create_count_tiles(tile_size, font_name):
  70.     """Create tiles for mine counts.
  71.  
  72.    Additionally an empty tile without a digit is returned for 0
  73.  
  74.    Parameters
  75.    ----------
  76.    tile_size
  77.        Size of tiles.
  78.    font_name : string
  79.        Font name to be found in resources directory. The size will be 0.9
  80.        of `tile_size`.
  81.  
  82.    Returns
  83.    -------
  84.    tiles : list of pygame.Surface
  85.        List of tiles containing 9 elements.
  86.    """
  87.     colors = [
  88.         None,
  89.         'Blue',
  90.         'Dark Green',
  91.         'Red',
  92.         'Navy',
  93.         'Brown',
  94.         'Light Sea Green',
  95.         'Black',
  96.         'Dim Gray'
  97.     ]
  98.  
  99.     font_size = int(tile_size * 0.9)
  100.     font = load_font(font_name, font_size)
  101.  
  102.     empty_tile = pygame.Surface((tile_size, tile_size), pygame.SRCALPHA)
  103.     center = empty_tile.get_rect().center
  104.  
  105.     tiles = [empty_tile.copy()]
  106.  
  107.     for count in range(1, 9):
  108.         glyph = font.render(str(count), True, pygame.Color(colors[count]))
  109.         width = glyph.get_rect().width
  110.  
  111.         glyph_center = (center[0] + int(0.15 * width), center[1])
  112.         rect = glyph.get_rect(center=glyph_center)
  113.         tile = empty_tile.copy()
  114.         tile.blit(glyph, rect.topleft)
  115.         tiles.append(tile)
  116.  
  117.     return tiles
  118.  
  119.  
  120. def is_key_suitable_for_name(key_name):
  121.     """Check if a key is suitable for name input."""
  122.     return len(key_name) == 1 and key_name.isalnum() or key_name in ['-', '_']
  123.  
  124.  
  125. def is_digit(key_name):
  126.     """Check if a key is a digit."""
  127.     return len(key_name) == 1 and key_name.isnumeric()
  128.  
  129.  
  130. class Game:
  131.     """Main game class."""
  132.     TILE_SIZE = 20
  133.     GUI_WIDTH = 91
  134.     HUD_HEIGHT = 30
  135.     MARGIN = 20
  136.     BG_COLOR = pygame.Color('Light Slate Gray')
  137.     FIELD_BG_COLOR = pygame.Color('#d7dcdc')
  138.     FIELD_LINES_COLOR = pygame.Color('#738383')
  139.     GUI_FONT_COLOR = pygame.Color('Light Yellow')
  140.     GUI_FONT_SIZE = 13
  141.     DIGITS = {chr(c) for c in range(ord('0'), ord('9') + 1)}
  142.     MAX_BOARD_DIMENSION = 50
  143.     MIN_BOARD_DIMENSION_DISPLAY = 10
  144.     MAX_NAME_LENGTH = 8
  145.     DELAY_BEFORE_NAME_INPUT_MS = 1000
  146.  
  147.     def __init__(self, state_file_path):
  148.         try:
  149.             with open(state_file_path) as state_file:
  150.                 state = json.load(state_file)
  151.         except (IOError, json.JSONDecodeError):
  152.             state = {}
  153.  
  154.         display_info = pygame.display.Info()
  155.         self.max_cols = (int(0.95 * display_info.current_w) - self.GUI_WIDTH
  156.                          - 3 * self.MARGIN) // self.TILE_SIZE
  157.         self.max_rows = (int(0.95 * display_info.current_h) - self.HUD_HEIGHT
  158.                          - 3 * self.MARGIN) // self.TILE_SIZE
  159.  
  160.         difficulty = state.get('difficulty', 'EASY')
  161.         if difficulty not in ['EASY', 'NORMAL', 'HARD', 'CUSTOM']:
  162.             difficulty = 'EASY'
  163.  
  164.         if "leaderboard" in state:
  165.             leaderboard_data = state['leaderboard']
  166.         else:
  167.             leaderboard_data = {'EASY': [], 'NORMAL': [], 'HARD': []}
  168.  
  169.         self.n_rows = state.get('n_rows', 10)
  170.         self.n_cols = state.get('n_cols', 10)
  171.         self.n_mines = state.get('n_mines', 10)
  172.         self.set_difficulty(difficulty)
  173.  
  174.         mine_count_images = create_count_tiles(self.TILE_SIZE,
  175.                                                "kenvector_future.ttf")
  176.         tile_image = load_image('tile.png', self.TILE_SIZE)
  177.         mine_image = load_image('mine.png', self.TILE_SIZE)
  178.         flag_image = load_image('flag.png', self.TILE_SIZE)
  179.         gui_font = load_font("Akrobat-Bold.otf", self.GUI_FONT_SIZE)
  180.  
  181.         self.board = Board(
  182.             self.n_rows, self.n_cols, self.n_mines,
  183.             self.FIELD_BG_COLOR, self.FIELD_LINES_COLOR, self.TILE_SIZE,
  184.             tile_image, mine_count_images, flag_image, mine_image,
  185.             on_status_change_callback=self.on_status_change)
  186.  
  187.         self.screen = None
  188.         self.screen_rect = None
  189.         self.board_rect = None
  190.         self.hud_rect = None
  191.         self.gui_rect = None
  192.         self.board_area_rect = None
  193.         self.init_screen()
  194.  
  195.         self.difficulty_selector = SelectionGroup(
  196.             gui_font,
  197.             self.GUI_FONT_COLOR,
  198.             "DIFFICULTY",
  199.             ["EASY", "NORMAL", "HARD", "CUSTOM"],
  200.             initial_value=state.get('difficulty', 'EASY'))
  201.  
  202.         self.difficulty_selector.rect.centerx = self.gui_rect.centerx
  203.         self.difficulty_selector.rect.y = self.MARGIN
  204.         self.difficulty_selector.callback = self.on_difficulty_change
  205.  
  206.         active_input = self.difficulty_selector.selected == "CUSTOM"
  207.         self.width_input = Input(gui_font, self.GUI_FONT_COLOR,
  208.                                  "WIDTH", self.n_cols,
  209.                                  active_input=active_input,
  210.                                  width=self.GUI_WIDTH, max_value_length=3,
  211.                                  key_filter=is_digit,
  212.                                  on_enter_callback=self.on_cols_enter)
  213.         self.height_input = Input(gui_font, self.GUI_FONT_COLOR,
  214.                                   "HEIGHT", self.n_rows, width=self.GUI_WIDTH,
  215.                                   active_input=active_input,
  216.                                   max_value_length=3,
  217.                                   key_filter=is_digit,
  218.                                   on_enter_callback=self.on_rows_enter)
  219.         self.mines_input = Input(gui_font, self.GUI_FONT_COLOR,
  220.                                  "MINES", self.n_mines, width=self.GUI_WIDTH,
  221.                                  active_input=active_input,
  222.                                  max_value_length=3,
  223.                                  key_filter=is_digit,
  224.                                  on_enter_callback=self.on_mines_enter)
  225.  
  226.         self.timer = Input(gui_font, self.GUI_FONT_COLOR,
  227.                            "TIME", self.board.time)
  228.         self.current_mines = Input(gui_font, self.GUI_FONT_COLOR,
  229.                                    "MINES", self.board.n_mines)
  230.  
  231.         self.status = Label(gui_font, self.GUI_FONT_COLOR, "READY TO GO!")
  232.  
  233.         self.restart_button = Button(gui_font,
  234.                                      self.GUI_FONT_COLOR,
  235.                                      "RESTART",
  236.                                      self.board.reset)
  237.  
  238.         self.show_leaderboard_button = Button(gui_font, self.GUI_FONT_COLOR,
  239.                                               "LEADER BOARD",
  240.                                               self.show_leaderboard)
  241.  
  242.         leaderboard_width = (
  243.             self.GUI_WIDTH + 2 * self.MARGIN
  244.             + self.MIN_BOARD_DIMENSION_DISPLAY * self.TILE_SIZE)
  245.         self.leaderboard = Leaderboard(gui_font, self.GUI_FONT_COLOR,
  246.                                        5, leaderboard_width,
  247.                                        data=leaderboard_data)
  248.         self.leaderboard_hint = Label(gui_font, self.GUI_FONT_COLOR,
  249.                                       "CLICK TO CONTINUE")
  250.  
  251.         self.name_input = InputDialogue(gui_font, self.GUI_FONT_COLOR,
  252.                                         "ENTER YOUR NAME",
  253.                                         self.on_name_enter,
  254.                                         max_length=self.MAX_NAME_LENGTH,
  255.                                         key_filter=is_key_suitable_for_name)
  256.  
  257.         self.victory_time = Label(gui_font, self.GUI_FONT_COLOR, "")
  258.         self.leaderboard_announcement = Label(
  259.             gui_font, self.GUI_FONT_COLOR,
  260.             "YOU MADE IT TO THE LEADERBOARD!")
  261.         self.show_name_input_timer = Timer(self.show_name_input)
  262.  
  263.         self.place_gui()
  264.         self.keep_running = None
  265.         self.mode = "game"
  266.  
  267.     def init_screen(self):
  268.         """Initialize screen and compute rectangles for different regions."""
  269.         board_area_width = \
  270.             max(self.n_cols, self.MIN_BOARD_DIMENSION_DISPLAY) * self.TILE_SIZE
  271.         board_area_height = \
  272.             max(self.n_rows, self.MIN_BOARD_DIMENSION_DISPLAY) * self.TILE_SIZE
  273.         window_width = 3 * self.MARGIN + self.GUI_WIDTH + board_area_width
  274.         window_height = 3 * self.MARGIN + self.HUD_HEIGHT + board_area_height
  275.  
  276.         self.board_area_rect = pygame.Rect(2 * self.MARGIN + self.GUI_WIDTH,
  277.                                            2 * self.MARGIN + self.HUD_HEIGHT,
  278.                                            board_area_width,
  279.                                            board_area_height)
  280.  
  281.         self.board.rect.size = (self.n_cols * self.TILE_SIZE,
  282.                                 self.n_rows * self.TILE_SIZE)
  283.         self.board.rect.center = self.board_area_rect.center
  284.  
  285.         self.hud_rect = pygame.Rect(2 * self.MARGIN + self.GUI_WIDTH,
  286.                                     self.MARGIN,
  287.                                     board_area_width,
  288.                                     self.HUD_HEIGHT)
  289.  
  290.         self.screen = pygame.display.set_mode((window_width, window_height))
  291.         self.screen_rect = self.screen.get_rect()
  292.         self.screen.fill(self.BG_COLOR)
  293.         self.gui_rect = pygame.Rect(self.MARGIN,
  294.                                     2 * self.MARGIN + self.HUD_HEIGHT,
  295.                                     self.GUI_WIDTH,
  296.                                     board_area_height)
  297.  
  298.     def set_difficulty(self, difficulty):
  299.         """Adjust game parameters given difficulty.
  300.  
  301.        Custom difficulty is not handled in this function.
  302.        """
  303.         if difficulty == "EASY":
  304.             self.n_rows = 10
  305.             self.n_cols = 10
  306.             self.n_mines = 10
  307.         elif difficulty == "NORMAL":
  308.             self.n_rows = 16
  309.             self.n_cols = 16
  310.             self.n_mines = 40
  311.         elif difficulty == "HARD":
  312.             self.n_rows = 16
  313.             self.n_cols = 30
  314.             self.n_mines = 99
  315.  
  316.     def place_gui(self):
  317.         """Place GUI element according to the current settings."""
  318.         self.width_input.rect.topleft = (
  319.             self.gui_rect.x,
  320.             self.difficulty_selector.rect.bottom
  321.             + 0.2 * self.difficulty_selector.rect.height)
  322.         self.height_input.rect.topleft = (
  323.             self.gui_rect.x,
  324.             self.width_input.rect.bottom + 0.4 * self.height_input.rect.height)
  325.         self.mines_input.rect.topleft = (
  326.             self.gui_rect.x,
  327.             self.height_input.rect.bottom + 0.4 * self.width_input.rect.height)
  328.  
  329.         hud_width = self.place_hud()
  330.  
  331.         self.restart_button.rect.top = self.timer.rect.top
  332.         self.restart_button.rect.centerx = 0.5 * (self.hud_rect.left
  333.                                                   + self.hud_rect.right
  334.                                                   - hud_width)
  335.  
  336.         self.show_leaderboard_button.rect.bottom = (self.screen_rect.height
  337.                                                     - self.MARGIN)
  338.         self.show_leaderboard_button.rect.centerx = (self.MARGIN
  339.                                                      + 0.5 * self.GUI_WIDTH)
  340.  
  341.         screen_center = self.screen.get_rect().centerx
  342.         self.status.rect.top = self.current_mines.rect.top
  343.         self.status.rect.centerx = self.restart_button.rect.centerx
  344.  
  345.         self.leaderboard.rect.top = self.MARGIN
  346.         self.leaderboard.rect.centerx = screen_center
  347.  
  348.         self.leaderboard_hint.rect.bottom = (self.screen_rect.height
  349.                                              - self.MARGIN)
  350.         self.leaderboard_hint.rect.centerx = self.screen_rect.centerx
  351.  
  352.         self.victory_time.rect.top = self.MARGIN
  353.         self.victory_time.rect.centerx = self.screen_rect.centerx
  354.         self.leaderboard_announcement.rect.top = (
  355.             self.victory_time.rect.bottom
  356.             + 0.4 * self.victory_time.rect.height)
  357.         self.leaderboard_announcement.rect.centerx = self.screen_rect.centerx
  358.  
  359.         self.name_input.rect.top = (
  360.             self.leaderboard_announcement.rect.bottom
  361.             + self.leaderboard_announcement.rect.height)
  362.         self.name_input.rect.centerx = self.screen_rect.centerx
  363.  
  364.     def place_hud(self):
  365.         """Place timer and mines info and return width of this block."""
  366.         hud_width = max(self.timer.rect.width, self.current_mines.rect.width)
  367.         self.timer.rect.topleft = (self.hud_rect.right - hud_width,
  368.                                    self.hud_rect.top)
  369.         self.current_mines.rect.topleft = (
  370.             self.timer.rect.left,
  371.             self.timer.rect.bottom + 0.4 * self.timer.rect.height)
  372.         return hud_width
  373.  
  374.     def reset_game(self):
  375.         """Reset the game."""
  376.         self.board.reset(n_rows=self.n_rows,
  377.                          n_cols=self.n_cols,
  378.                          n_mines=self.n_mines)
  379.  
  380.     def show_leaderboard(self):
  381.         """Change screen to leaderboard."""
  382.         self.mode = "leaderboard"
  383.  
  384.     def show_name_input(self):
  385.         """Change screen to name input."""
  386.         self.mode = "name_input"
  387.         self.victory_time.set_text("YOUR TIME IS {} SECONDS"
  388.                                    .format(self.board.time))
  389.         self.name_input.set_value("")
  390.         self.place_gui()
  391.  
  392.     def on_name_enter(self, name):
  393.         """Handle name enter for the leaderboard."""
  394.         if not name:
  395.             return
  396.         self.leaderboard.update(self.difficulty_selector.selected,
  397.                                 name,
  398.                                 self.board.time)
  399.         self.mode = "leaderboard"
  400.  
  401.     def on_status_change(self, new_status):
  402.         """Handle game status change."""
  403.         if new_status == 'game_over':
  404.             self.status.set_text("GAME OVER!")
  405.         elif new_status == 'victory':
  406.             self.status.set_text("VICTORY!")
  407.             if self.leaderboard.needs_update(self.difficulty_selector.selected,
  408.                                              self.board.time):
  409.                 self.show_name_input_timer.start(
  410.                     self.DELAY_BEFORE_NAME_INPUT_MS)
  411.         elif new_status == 'before_start':
  412.             self.status.set_text("READY TO GO!")
  413.         else:
  414.             self.status.set_text("GOOD LUCK!")
  415.  
  416.     def on_difficulty_change(self, difficulty):
  417.         """Handle difficulty change."""
  418.         self.height_input.active_input = False
  419.         self.width_input.active_input = False
  420.         self.mines_input.active_input = False
  421.         self.set_difficulty(difficulty)
  422.         if difficulty == "CUSTOM":
  423.             self.height_input.active_input = True
  424.             self.width_input.active_input = True
  425.             self.mines_input.active_input = True
  426.  
  427.         self.height_input.set_value(self.n_rows)
  428.         self.width_input.set_value(self.n_cols)
  429.         self.mines_input.set_value(self.n_mines)
  430.  
  431.         self.init_screen()
  432.         self.place_gui()
  433.         self.reset_game()
  434.  
  435.     def set_game_parameter(self, parameter, max_value, value):
  436.         """Set either n_rows, n_cols, n_mines."""
  437.         if not value:
  438.             value = 1
  439.  
  440.         value = int(value)
  441.         value = min(max(1, value), max_value)
  442.         setattr(self, parameter, value)
  443.         self.n_mines = min(self.n_mines, self.n_rows * self.n_cols - 1)
  444.         self.mines_input.set_value(self.n_mines)
  445.         self.init_screen()
  446.         self.place_gui()
  447.         self.reset_game()
  448.         return value
  449.  
  450.     def on_rows_enter(self, value):
  451.         """Handle n_rows input."""
  452.         return self.set_game_parameter('n_rows',
  453.                                        self.max_rows,
  454.                                        value)
  455.  
  456.     def on_cols_enter(self, value):
  457.         """Handle n_cols input."""
  458.         return self.set_game_parameter('n_cols',
  459.                                        self.max_cols,
  460.                                        value)
  461.  
  462.     def on_mines_enter(self, value):
  463.         """Hand n_mines input."""
  464.         return self.set_game_parameter('n_mines',
  465.                                        self.n_rows * self.n_cols - 1,
  466.                                        value)
  467.  
  468.     def draw_all(self):
  469.         """Draw all elements."""
  470.         self.screen.fill(self.BG_COLOR)
  471.  
  472.         if self.mode == "leaderboard":
  473.             self.leaderboard.draw(self.screen)
  474.             self.leaderboard_hint.draw(self.screen)
  475.             pygame.display.flip()
  476.             return
  477.         elif self.mode == "name_input":
  478.             self.victory_time.draw(self.screen)
  479.             self.leaderboard_announcement.draw(self.screen)
  480.             self.name_input.draw(self.screen)
  481.             pygame.display.flip()
  482.             return
  483.  
  484.         self.board.draw(self.screen)
  485.  
  486.         self.difficulty_selector.draw(self.screen)
  487.         self.height_input.draw(self.screen)
  488.         self.width_input.draw(self.screen)
  489.         self.mines_input.draw(self.screen)
  490.  
  491.         self.timer.draw(self.screen)
  492.         self.current_mines.draw(self.screen)
  493.         self.status.draw(self.screen)
  494.  
  495.         self.restart_button.draw(self.screen)
  496.         self.show_leaderboard_button.draw(self.screen)
  497.  
  498.         pygame.display.flip()
  499.  
  500.     def process_events(self):
  501.         """Process input events."""
  502.         for event in pygame.event.get():
  503.             if event.type == pygame.QUIT:
  504.                 self.keep_running = False
  505.                 break
  506.  
  507.             if self.mode == "leaderboard":
  508.                 if event.type == pygame.MOUSEBUTTONUP:
  509.                     self.mode = "game"
  510.                 break
  511.             elif self.mode == "name_input":
  512.                 if event.type == pygame.KEYDOWN:
  513.                     self.name_input.on_key_down(event)
  514.                 break
  515.  
  516.             if event.type == pygame.MOUSEBUTTONUP:
  517.                 self.difficulty_selector.on_mouse_up(event.button)
  518.                 self.height_input.on_mouse_up(event.button)
  519.                 self.width_input.on_mouse_up(event.button)
  520.                 self.mines_input.on_mouse_up(event.button)
  521.                 self.restart_button.on_mouse_up(event.button)
  522.                 self.show_leaderboard_button.on_mouse_up(event.button)
  523.  
  524.                 # klikanie na planszy:
  525.                 self.board.on_mouse_up(event.button)
  526.  
  527.             if event.type == pygame.MOUSEBUTTONDOWN:
  528.                 self.board.on_mouse_down(event.button)
  529.  
  530.             elif event.type == pygame.KEYDOWN:
  531.                 self.height_input.on_key_down(event)
  532.                 self.width_input.on_key_down(event)
  533.                 self.mines_input.on_key_down(event)
  534.  
  535.     '''def make_decision(self):
  536.    '''
  537.  
  538.     def start_main_loop(self):
  539.         """Start main game loop."""
  540.         clock = pygame.time.Clock()
  541.         self.keep_running = True
  542.         turn = 0
  543.         while self.keep_running:
  544.             clock.tick(30)
  545.             self.timer.set_value(self.board.time)
  546.             self.current_mines.set_value(self.board.n_mines_left)
  547.             self.place_hud()
  548.  
  549.             #if self.game_status == "before_start":
  550.                 # self._put_mines(i, j)
  551.                 # self.start_time = pygame.time.get_ticks()
  552.             #self._change_game_status("running")
  553.             # self.process_events()
  554.             # self.board._open_tile(1, 1)
  555.  
  556.             if turn == 0:
  557.                 self.board._put_mines(1, 1)
  558.                 self.board.start_time = pygame.time.get_ticks()
  559.                 self.board._open_tile(1, 1)
  560.                 self._change_game_status("running")
  561.             else:
  562.                 self.process_events()
  563.  
  564.             self.show_name_input_timer.check()
  565.             self.draw_all()
  566.             turn += 1
  567.  
  568.     def save_state(self, state_file_path):
  569.         """Save game state on disk."""
  570.         state = {
  571.             "difficulty": self.difficulty_selector.selected,
  572.             "n_rows": self.n_rows,
  573.             "n_cols": self.n_cols,
  574.             "n_mines": self.n_mines,
  575.             "leaderboard": self.leaderboard.data
  576.         }
  577.         with open(state_file_path, "w") as state_file:
  578.             json.dump(state, state_file)
  579.  
  580.  
  581. def run(state_file_path):
  582.     pygame.init()
  583.     pygame.display.set_caption('Minesweeper')
  584.     pygame.mouse.set_visible(True)
  585.     game = Game(state_file_path)
  586.     game.start_main_loop()
  587.     game.save_state(state_file_path)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement