The code in its entirety is shown below. You should click on Download: Zip in order to get the graphics file SpriteSheet.bmp and sound files (win.wav, lose.wav) used by the game.
Ensure your PyPortal is plugged into your computer via a known good USB cable. The PyPortal should show up as a flash drive called CIRCUITPY.
Open the zip file and copy all the files listed below to CIRCUITPY.
- code.py
- SpriteSheet.bmp
- win.wav
- lose.wav
Note: The boot_out.txt file you may see on the CIRCUITPY drive is generated by CircuitPython and is not part of the needed files for this project.
# SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries # # SPDX-License-Identifier: MIT """ PyPortal MineSweeper Adafruit invests time and resources providing this open source code. Please support Adafruit and open source hardware by purchasing products from Adafruit! Written by Dave Astels for Adafruit Industries Copyright (c) 2019 Adafruit Industries Licensed under the MIT license. All text above must be included in any redistribution. """ import time from random import seed, randint import board import digitalio import displayio import audioio try: from audioio import WaveFile except ImportError: from audiocore import WaveFile import adafruit_imageload import adafruit_touchscreen seed(int(time.monotonic())) # Set up audio speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE) speaker_enable.switch_to_output(False) if hasattr(board, 'AUDIO_OUT'): audio = audioio.AudioOut(board.AUDIO_OUT) elif hasattr(board, 'SPEAKER'): audio = audioio.AudioOut(board.SPEAKER) else: raise AttributeError('Board does not have a builtin speaker!') NUMBER_OF_BOMBS = 15 # Board pieces OPEN0 = 0 OPEN1 = 1 OPEN2 = 2 OPEN3 = 3 OPEN4 = 4 OPEN5 = 5 OPEN6 = 6 OPEN7 = 7 OPEN8 = 8 BLANK = 9 BOMBDEATH = 10 BOMBFLAGGED = 11 BOMBMISFLAGGED = 12 BOMBQUESTION = 13 BOMB = 14 sprite_sheet, palette = adafruit_imageload.load("/SpriteSheet.bmp", bitmap=displayio.Bitmap, palette=displayio.Palette) display = board.DISPLAY group = displayio.Group() touchscreen = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR, board.TOUCH_YD, board.TOUCH_YU, calibration=((9000, 59000), (8000, 57000)), size=(display.width, display.height)) tilegrid = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=20, height=15, tile_height=16, tile_width=16, default_tile=BLANK) group.append(tilegrid) display.root_group = group board_data = bytearray(b'\x00' * 300) #pylint:disable=redefined-outer-name def get_data(x, y): return board_data[y * 20 + x] def set_data(x, y, value): board_data[y * 20 + x] = value #pylint:disable=redefined-outer-name def seed_bombs(how_many): for _ in range(how_many): while True: bomb_x = randint(0, 19) bomb_y = randint(0, 14) if get_data(bomb_x, bomb_y) == 0: set_data(bomb_x, bomb_y, 14) break def compute_counts(): """For each bomb, increment the count in each non-bomb square around it""" for y in range(15): for x in range(20): if get_data(x, y) != 14: continue # keep looking for bombs for dx in (-1, 0, 1): if x + dx < 0 or x + dx >= 20: continue # off screen for dy in (-1, 0, 1): if y + dy < 0 or y + dy >= 15: continue # off screen count = get_data(x + dx, y + dy) if count == 14: continue # don't process bombs set_data(x + dx, y + dy, count + 1) def reveal(): for x in range(20): for y in range(15): if tilegrid[x, y] == BOMBFLAGGED and get_data(x, y) != BOMB: tilegrid[x, y] = BOMBMISFLAGGED else: tilegrid[x, y] = get_data(x, y) #pylint:disable=too-many-nested-blocks def expand_uncovered(start_x, start_y): number_uncovered = 1 stack = [(start_x, start_y)] while len(stack) > 0: x, y = stack.pop() if tilegrid[x, y] == BLANK: under_the_tile = get_data(x, y) if under_the_tile <= OPEN8: tilegrid[x, y] = under_the_tile number_uncovered += 1 if under_the_tile == OPEN0: for dx in (-1, 0, 1): if x + dx < 0 or x + dx >= 20: continue # off screen for dy in (-1, 0, 1): if y + dy < 0 or y + dy >= 15: continue # off screen if dx == 0 and dy == 0: continue # don't process where the bomb stack.append((x + dx, y + dy)) return number_uncovered #pylint:enable=too-many-nested-blocks def check_for_win(): """Check for a complete, winning game. That's one with all squares uncovered and all bombs correctly flagged, with no non-bomb squares flaged. """ # first make sure everything has been explored and decided for x in range(20): for y in range(15): if tilegrid[x, y] == BLANK or tilegrid[x, y] == BOMBQUESTION: return None #still ignored or question squares # then check for mistagged bombs for x in range(20): for y in range(15): if tilegrid[x, y] == BOMBFLAGGED and get_data(x, y) != BOMB: return False #misflagged bombs, not done return True #nothing unexplored, and no misflagged bombs #pylint:disable=too-many-branches # This could be broken apart but I think it's more understandable # with it all in one place def play_a_game(): number_uncovered = 0 touch_x = -1 touch_y = -1 touch_time = 0 wait_for_release = False while True: now = time.monotonic() if now >= touch_time: touch_time = now + 0.2 # process touch touch_at = touchscreen.touch_point if touch_at is None: wait_for_release = False else: if wait_for_release: continue wait_for_release = True touch_x = max(min([touch_at[0] // 16, 19]), 0) touch_y = max(min([touch_at[1] // 16, 14]), 0) if tilegrid[touch_x, touch_y] == BLANK: tilegrid[touch_x, touch_y] = BOMBQUESTION elif tilegrid[touch_x, touch_y] == BOMBQUESTION: tilegrid[touch_x, touch_y] = BOMBFLAGGED elif tilegrid[touch_x, touch_y] == BOMBFLAGGED: under_the_tile = get_data(touch_x, touch_y) if under_the_tile == 14: set_data(touch_x, touch_y, BOMBDEATH) #reveal a red bomb tilegrid[touch_x, touch_y] = BOMBDEATH return False #lost elif under_the_tile > OPEN0 and under_the_tile <= OPEN8: tilegrid[touch_x, touch_y] = under_the_tile elif under_the_tile == OPEN0: tilegrid[touch_x, touch_y] = BLANK number_uncovered += expand_uncovered(touch_x, touch_y) else: #something bad happened raise ValueError('Unexpected value on board') status = check_for_win() if status is None: continue return status #pylint:enable=too-many-branches def reset_board(): for x in range(20): for y in range(15): tilegrid[x, y] = BLANK set_data(x, y, 0) seed_bombs(NUMBER_OF_BOMBS) compute_counts() def play_sound(file_name): try: board.DISPLAY.refresh(target_frames_per_second=60) except AttributeError: board.DISPLAY.wait_for_frame() wavfile = open(file_name, "rb") wavedata = WaveFile(wavfile) speaker_enable.value = True audio.play(wavedata) return wavfile def wait_for_sound_and_cleanup(wavfile): while audio.playing: pass wavfile.close() speaker_enable.value = False def win(): print('You won') wait_for_sound_and_cleanup(play_sound('win.wav')) def lose(): print('You lost') wavfile = play_sound('lose.wav') for _ in range(10): tilegrid.x = randint(-2, 2) tilegrid.y = randint(-2, 2) try: board.DISPLAY.refresh(target_frames_per_second=60) except AttributeError: board.DISPLAY.refresh_soon() board.DISPLAY.wait_for_frame() tilegrid.x = 0 tilegrid.y = 0 wait_for_sound_and_cleanup(wavfile) while True: reset_board() if play_a_game(): win() else: reveal() lose() time.sleep(5.0)
Looking at the CIRCUITPY drive in your operating system file explorer/finder, you should see a listing similar to this with all the files loaded from this page and the previous page:
Text editor powered by tinymce.