From 76d2a5ae218a89b09f708b6608a80b714a725691 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 9 Apr 2025 11:17:50 -0500 Subject: [PATCH 1/7] two mice code --- .../Metro_RP2350_Match3/two_mice_demo/code.py | 184 ++++++++++++++++++ .../two_mice_demo/mouse_cursor.bmp | Bin 0 -> 198 bytes 2 files changed, 184 insertions(+) create mode 100644 Metro/Metro_RP2350_Match3/two_mice_demo/code.py create mode 100644 Metro/Metro_RP2350_Match3/two_mice_demo/mouse_cursor.bmp diff --git a/Metro/Metro_RP2350_Match3/two_mice_demo/code.py b/Metro/Metro_RP2350_Match3/two_mice_demo/code.py new file mode 100644 index 000000000..7af3396af --- /dev/null +++ b/Metro/Metro_RP2350_Match3/two_mice_demo/code.py @@ -0,0 +1,184 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT +import array +import supervisor +import terminalio +import usb.core +from adafruit_display_text.bitmap_label import Label +from displayio import Group, OnDiskBitmap, TileGrid, Palette, ColorConverter + +import adafruit_usb_host_descriptors + +# use the default built-in display, +# the HSTX / PicoDVI display for the Metro RP2350 +display = supervisor.runtime.display + +# a group to hold all other visual elements +main_group = Group() + +# set the main group to show on the display +display.root_group = main_group + +# load the cursor bitmap file +mouse_bmp = OnDiskBitmap("mouse_cursor.bmp") + +# lists for labels, mouse tilegrids, and palettes. +# each mouse will get 1 of each item. All lists +# will end up with length 2. +output_lbls = [] +mouse_tgs = [] +palettes = [] + +# the different colors to use for each mouse cursor +# and labels +colors = [0xFF00FF, 0x00FF00] + +for i in range(2): + # create a palette for this mouse + mouse_palette = Palette(3) + # index zero is used for transparency + mouse_palette.make_transparent(0) + # add the palette to the list of palettes + palettes.append(mouse_palette) + + # copy the first two colors from mouse palette + for palette_color_index in range(2): + mouse_palette[palette_color_index] = mouse_bmp.pixel_shader[palette_color_index] + + # replace the last color with different color for each mouse + mouse_palette[2] = colors[i] + + # create a TileGrid for this mouse cursor. + # use the palette created above + mouse_tg = TileGrid(mouse_bmp, pixel_shader=mouse_palette) + + # move the mouse tilegrid to near the center of the display + mouse_tg.x = display.width // 2 - (i * 12) + mouse_tg.y = display.height // 2 + + # add this mouse tilegrid to the list of mouse tilegrids + mouse_tgs.append(mouse_tg) + + # add this mouse tilegrid to the main group so it will show + # on the display + main_group.append(mouse_tg) + + # create a label for this mouse + output_lbl = Label(terminalio.FONT, text=f"{mouse_tg.x},{mouse_tg.y}", color=colors[i], scale=1) + # anchored to the top left corner of the label + output_lbl.anchor_point = (0, 0) + + # move to op left corner of the display, moving + # down by a static amount to static the two labels + # one below the other + output_lbl.anchored_position = (1, 1 + i * 13) + + # add the label to the list of labels + output_lbls.append(output_lbl) + + # add the label to the main group so it will show + # on the display + main_group.append(output_lbl) + +# lists for mouse interface indexes, endpoint addresses, and USB Device instances +# each of these will end up with length 2 once we find both mice +mouse_interface_indexes = [] +mouse_endpoint_addresses = [] +mice = [] + +# scan for connected USB devices +for device in usb.core.find(find_all=True): + # check for boot mouse endpoints on this device + mouse_interface_index, mouse_endpoint_address = ( + adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device) + ) + # if a boot mouse interface index and endpoint address were found + if mouse_interface_index is not None and mouse_endpoint_address is not None: + # add the interface index to the list of indexes + mouse_interface_indexes.append(mouse_interface_index) + # add the endpoint address to the list of addresses + mouse_endpoint_addresses.append(mouse_endpoint_address) + # add the device instance to the list of mice + mice.append(device) + + # print details to the console + print(f"mouse interface: {mouse_interface_index} ", end="") + print(f"endpoint_address: {hex(mouse_endpoint_address)}") + + # detach device from kernel if needed + if device.is_kernel_driver_active(0): + device.detach_kernel_driver(0) + + # set the mouse configuration so it can be used + device.set_configuration() + +# This is ordered by bit position. +BUTTONS = ["left", "right", "middle"] + +# list of buffers, will hold one buffer for each mouse +mouse_bufs = [] +for i in range(2): + # Buffer to hold data read from the mouse + mouse_bufs.append(array.array("b", [0] * 8)) + + +def get_mouse_deltas(buffer, read_count): + """ + Given a buffer and read_count return the x and y delta values + :param buffer: A buffer containing data read from the mouse + :param read_count: How many bytes of data were read from the mouse + :return: tuple x,y delta values + """ + if read_count == 4: + delta_x = buffer[1] + delta_y = buffer[2] + elif read_count == 8: + delta_x = buffer[2] + delta_y = buffer[4] + else: + raise ValueError(f"Unsupported mouse packet size: {read_count}, must be 4 or 8") + return delta_x, delta_y + +# main loop +while True: + # for each mouse instance + for mouse_index, mouse in enumerate(mice): + # try to read data from the mouse + try: + count = mouse.read( + mouse_endpoint_addresses[mouse_index], mouse_bufs[mouse_index], timeout=10 + ) + + # if there is no data it will raise USBTimeoutError + except usb.core.USBTimeoutError: + # Nothing to do if there is no data for this mouse + continue + + # there was mouse data, so get the delta x and y values from it + mouse_deltas = get_mouse_deltas(mouse_bufs[mouse_index], count) + + # update the x position of this mouse cursor using the delta value + # clamped to the display size + mouse_tgs[mouse_index].x = max( + 0, min(display.width - 1, mouse_tgs[mouse_index].x + mouse_deltas[0]) + ) + # update the y position of this mouse cursor using the delta value + # clamped to the display size + mouse_tgs[mouse_index].y = max( + 0, min(display.height - 1, mouse_tgs[mouse_index].y + mouse_deltas[1]) + ) + + # output string with the new cursor position + out_str = f"{mouse_tgs[mouse_index].x},{mouse_tgs[mouse_index].y}" + + # loop over possible button bit indexes + for i, button in enumerate(BUTTONS): + # check each bit index to determin if the button was pressed + if mouse_bufs[mouse_index][0] & (1 << i) != 0: + # if it was pressed, add the button to the output string + out_str += f" {button}" + + # set the output string into text of the label + # to show it on the display + output_lbls[mouse_index].text = out_str \ No newline at end of file diff --git a/Metro/Metro_RP2350_Match3/two_mice_demo/mouse_cursor.bmp b/Metro/Metro_RP2350_Match3/two_mice_demo/mouse_cursor.bmp new file mode 100644 index 0000000000000000000000000000000000000000..94ec328896cfb7dc84ac82d6a5b8b58e8a8a5f12 GIT binary patch literal 198 zcmZ?rJ;ne5(|}YB5VHX>4-hjlumDL01_K~g)`MVXARB}k82-Z$nCIjkR19X2Kro@H r{SPt|2>$;E(h3X=OhOQ>q@=*0C@2KPKpH3p#z1*RAXZ`kD`fxx@`?`& literal 0 HcmV?d00001 From ded8d0098ef5e6a1e7a4c44212dcd0bbd61298b9 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 10 Apr 2025 10:10:45 -0500 Subject: [PATCH 2/7] tilepallettemapper demo code --- .../tilepalettemapper_demo/code.py | 50 ++++++++++++++++++ .../match3_cards_spritesheet.bmp | Bin 0 -> 10910 bytes 2 files changed, 50 insertions(+) create mode 100644 Metro/Metro_RP2350_Match3/tilepalettemapper_demo/code.py create mode 100644 Metro/Metro_RP2350_Match3/tilepalettemapper_demo/match3_cards_spritesheet.bmp diff --git a/Metro/Metro_RP2350_Match3/tilepalettemapper_demo/code.py b/Metro/Metro_RP2350_Match3/tilepalettemapper_demo/code.py new file mode 100644 index 000000000..3040f4bf9 --- /dev/null +++ b/Metro/Metro_RP2350_Match3/tilepalettemapper_demo/code.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +import supervisor +from displayio import Group, OnDiskBitmap, TileGrid, Palette +from tilepalettemapper import TilePaletteMapper + +# use the default built-in display, +# the HSTX / PicoDVI display for the Metro RP2350 +display = supervisor.runtime.display + +# a group to hold all other visual elements +main_group = Group(scale=4, x=30, y=30) + +# set the main group to show on the display +display.root_group = main_group + +# load the sprite sheet bitmap +spritesheet_bmp = OnDiskBitmap("match3_cards_spritesheet.bmp") + +# create a TilePaletteMapper +tile_palette_mapper = TilePaletteMapper( + spritesheet_bmp.pixel_shader, # input pixel_shader + 5, # input color count + 3, # grid width + 1 # grid height +) + +# create a TileGrid to show some cards +cards_tilegrid = TileGrid(spritesheet_bmp, pixel_shader=tile_palette_mapper, + width=3, height=1, tile_width=24, tile_height=32) + +# set each tile in the grid to a different sprite index +cards_tilegrid[0, 0] = 10 +cards_tilegrid[1, 0] = 25 +cards_tilegrid[2, 0] = 2 + +# re-map each tile in the grid to use a different color for index 1 +# all other indexes remain their default values +tile_palette_mapper[0, 0] = [0, 2, 2, 3, 4] +tile_palette_mapper[1, 0] = [0, 3, 2, 3, 4] +tile_palette_mapper[2, 0] = [0, 4, 2, 3, 4] + +# add the tilegrid to the main group +main_group.append(cards_tilegrid) + +# wait forever so it remains visible on the display +while True: + pass diff --git a/Metro/Metro_RP2350_Match3/tilepalettemapper_demo/match3_cards_spritesheet.bmp b/Metro/Metro_RP2350_Match3/tilepalettemapper_demo/match3_cards_spritesheet.bmp new file mode 100644 index 0000000000000000000000000000000000000000..78e34c0af1715eab23becfedbc1d71533dacc651 GIT binary patch literal 10910 zcmeI0LCz#K427!^te8bNCqS%5ED#bGKqDkpY`6z2wk$Zwu;m<_gS-6LN$h-1l~XBt zej%hOO?Nxa^UI50($h2VetQ4)R-X6r{+qo1l-KX_x^17zyF4=g#pm_)nGE%{Z7=qx zjNbkD^RFei@b<}oeR&a1dH(+9m+kG_zqU7jycrPx)8XTBKUKbh0m<$0cwl%Ku3%Uw zDo=$z?)Otn;FM!@w)w~Hc8a;oxuO$2mu4PoAZ9@)d!F?(V$Smgj+}h^fBMID;2BpX3afc0Q+^C=R_v zo}u~m!}rYQ@e*g#xy%W_vX!suq!$^dVZQq|Lk#)kdOs4yzjfunm$I_uJ16cSdl+@9(AqV}41S3maB3(| z@u-GlTnX_6Yce+>KW6Q#h0!58?YLib;v^kvj( zjJaPO!H7C@&eX4Coz{O@h?=>sPMzH&;;D_AiP6~m)<8HB`-Y-;q7%LZzjT*#PTU17 zHH1ZIKUSxlt+@lsF!cK+`30RzoZYVl&OuK)#CFx<6nAB!Uw52`D+C|0-T}XN)06IS zxho52X$8|M*Ep-`^9xDNwx9WlQ#zg|5FCW|zm#)Mhl=|YF)MIOE zS3+LV`V77}rsWOcdF5%{7&*(Xl(Wi}5(8>do|@OFOk_OFxAqy6EKx~@yvi#Rtj#pn zbA9BCF4Oa-pPX&=-CDKIWde5W7ddmCasith$JCa3UKetm+F5-h=lCJ6!$V)Bq~&Y5 zKnETx8}f=veChdraptW@xnDvL(W*lqs~h`JXYKvi7nyy6GxzIIr>>;WpxG3*&+bb_ zTdQ^ChFMMU9e&xoeHux2j|?|{vbWjX=+}YFP&(u6z|^k}LBu{FJ%-^Pjg#Yek+b_X z5znGIuwRGo3Bpm}H3)ndD*2)dycrWoA6&9DF&w?@OGo>&xJm?4b3Q+D_t~ zFAd-GEjr;TD`&pMyfRgxOHycbid)X6y cb&@gCmB-w( Date: Thu, 10 Apr 2025 15:42:55 -0500 Subject: [PATCH 3/7] autosave and resume demo --- .../autosave_resume_demo/code.py | 251 ++++++++++++++++++ .../autosave_resume_demo/mouse_cursor.bmp | Bin 0 -> 198 bytes 2 files changed, 251 insertions(+) create mode 100644 Metro/Metro_RP2350_Match3/autosave_resume_demo/code.py create mode 100644 Metro/Metro_RP2350_Match3/autosave_resume_demo/mouse_cursor.bmp diff --git a/Metro/Metro_RP2350_Match3/autosave_resume_demo/code.py b/Metro/Metro_RP2350_Match3/autosave_resume_demo/code.py new file mode 100644 index 000000000..4f508dbac --- /dev/null +++ b/Metro/Metro_RP2350_Match3/autosave_resume_demo/code.py @@ -0,0 +1,251 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT +""" +This example demonstrates basic autosave and resume functionality. There are two buttons +that can be clicked to increment respective counters. The number of clicks is stored +in a game_state dictionary and saved to a data file on the SDCard. When the code first +launches it will read the data file and load the game_state from it. +""" +import array +from io import BytesIO +import os + +import board +import busio +import digitalio +import displayio +import msgpack +import storage +import supervisor +import terminalio +import usb + +import adafruit_sdcard +from adafruit_display_text.bitmap_label import Label +from adafruit_button import Button + +# use the default built-in display +display = supervisor.runtime.display + +# button configuration +BUTTON_WIDTH = 100 +BUTTON_HEIGHT = 30 +BUTTON_STYLE = Button.ROUNDRECT + +# game state object will get loaded from SDCard +# or a new one initialized as a dictionary +game_state = None + +save_to = None + +# boolean variables for possible SDCard states +sd_pins_in_use = False + +# The SD_CS pin is the chip select line. +SD_CS = board.SD_CS + +# try to Connect to the sdcard card and mount the filesystem. +try: + # initialze CS pin + cs = digitalio.DigitalInOut(SD_CS) +except ValueError: + # likely the SDCard was auto-initialized by the core + sd_pins_in_use = True + +try: + # if sd CS pin was not in use + if not sd_pins_in_use: + # try to initialize and mount the SDCard + sdcard = adafruit_sdcard.SDCard( + busio.SPI(board.SD_SCK, board.SD_MOSI, board.SD_MISO), cs + ) + vfs = storage.VfsFat(sdcard) + storage.mount(vfs, "/sd") + + # check for the autosave data file + if "autosave_demo.dat" in os.listdir("/sd/"): + # if the file is found read data from it into a BytesIO buffer + buffer = BytesIO() + with open("/sd/autosave_demo.dat", "rb") as f: + buffer.write(f.read()) + buffer.seek(0) + + # unpack the game_state object from the read data in the buffer + game_state = msgpack.unpack(buffer) + print(game_state) + + # if placeholder.txt file does not exist + if "placeholder.txt" not in os.listdir("/sd/"): + # if we made it to here then /sd/ exists and has a card + # so use it for save data + save_to = "/sd/autosave_demo.dat" +except OSError as e: + # sdcard init or mounting failed + raise OSError( + "This demo requires an SDCard. Please power off the device " + + "insert an SDCard and then plug it back in." + ) from e + +# if no saved game_state was loaded +if game_state is None: + # create a new game state dictionary + game_state = {"pink_count": 0, "blue_count": 0} + +# Make the display context +main_group = displayio.Group() +display.root_group = main_group + +# make buttons +blue_button = Button( + x=30, + y=40, + width=BUTTON_WIDTH, + height=BUTTON_HEIGHT, + style=BUTTON_STYLE, + fill_color=0x0000FF, + outline_color=0xFFFFFF, + label="BLUE", + label_font=terminalio.FONT, + label_color=0xFFFFFF, +) + +pink_button = Button( + x=30, + y=80, + width=BUTTON_WIDTH, + height=BUTTON_HEIGHT, + style=BUTTON_STYLE, + fill_color=0xFF00FF, + outline_color=0xFFFFFF, + label="PINK", + label_font=terminalio.FONT, + label_color=0x000000, +) + +# add them to a list for easy iteration +all_buttons = [blue_button, pink_button] + +# Add buttons to the display context +main_group.append(blue_button) +main_group.append(pink_button) + +# make labels for each button +blue_lbl = Label( + terminalio.FONT, text=f"Blue: {game_state['blue_count']}", color=0x3F3FFF +) +blue_lbl.anchor_point = (0, 0) +blue_lbl.anchored_position = (4, 4) +pink_lbl = Label( + terminalio.FONT, text=f"Pink: {game_state['pink_count']}", color=0xFF00FF +) +pink_lbl.anchor_point = (0, 0) +pink_lbl.anchored_position = (4, 4 + 14) +main_group.append(blue_lbl) +main_group.append(pink_lbl) + +# load the mouse cursor bitmap +mouse_bmp = displayio.OnDiskBitmap("mouse_cursor.bmp") + +# make the background pink pixels transparent +mouse_bmp.pixel_shader.make_transparent(0) + +# create a TileGrid for the mouse, using its bitmap and pixel_shader +mouse_tg = displayio.TileGrid(mouse_bmp, pixel_shader=mouse_bmp.pixel_shader) + +# move it to the center of the display +mouse_tg.x = display.width // 2 +mouse_tg.y = display.height // 2 + +# add the mouse tilegrid to main_group +main_group.append(mouse_tg) + +# scan for connected USB device and loop over any found +for device in usb.core.find(find_all=True): + # print device info + print(f"{device.idVendor:04x}:{device.idProduct:04x}") + print(device.manufacturer, device.product) + print(device.serial_number) + # assume the device is the mouse + mouse = device + +# detach the kernel driver if needed +if mouse.is_kernel_driver_active(0): + mouse.detach_kernel_driver(0) + +# set configuration on the mouse so we can use it +mouse.set_configuration() + +# buffer to hold mouse data +# Boot mice have 4 byte reports +buf = array.array("b", [0] * 4) + + +def save_game_state(): + """ + msgpack the game_state and save it to the autosave data file + :return: + """ + b = BytesIO() + msgpack.pack(game_state, b) + b.seek(0) + with open(save_to, "wb") as savefile: + savefile.write(b.read()) + + +# main loop +while True: + try: + # attempt to read data from the mouse + # 10ms timeout, so we don't block long if there + # is no data + count = mouse.read(0x81, buf, timeout=10) + except usb.core.USBTimeoutError: + # skip the rest of the loop if there is no data + continue + + # update the mouse tilegrid x and y coordinates + # based on the delta values read from the mouse + mouse_tg.x = max(0, min(display.width - 1, mouse_tg.x + buf[1])) + mouse_tg.y = max(0, min(display.height - 1, mouse_tg.y + buf[2])) + + # if left click is pressed + if buf[0] & (1 << 0) != 0: + # get the current cursor coordinates + coords = (mouse_tg.x, mouse_tg.y, 0) + + # loop over the buttons + for button in all_buttons: + # if the current button contains the mouse coords + if button.contains(coords): + # if the button isn't already in the selected state + if not button.selected: + # enter selected state + button.selected = True + + # if it is the pink button + if button == pink_button: + # increment pink count + game_state["pink_count"] += 1 + # update the label + pink_lbl.text = f"Pink: {game_state['pink_count']}" + + # if it is the blue button + elif button == blue_button: + # increment blue count + game_state["blue_count"] += 1 + # update the label + blue_lbl.text = f"Blue: {game_state['blue_count']}" + + # save the new game state + save_game_state() + + # if the click is not on the current button + else: + # set this button as not selected + button.selected = False + + # left click is not pressed + else: + # set all buttons as not selected + for button in all_buttons: + button.selected = False diff --git a/Metro/Metro_RP2350_Match3/autosave_resume_demo/mouse_cursor.bmp b/Metro/Metro_RP2350_Match3/autosave_resume_demo/mouse_cursor.bmp new file mode 100644 index 0000000000000000000000000000000000000000..94ec328896cfb7dc84ac82d6a5b8b58e8a8a5f12 GIT binary patch literal 198 zcmZ?rJ;ne5(|}YB5VHX>4-hjlumDL01_K~g)`MVXARB}k82-Z$nCIjkR19X2Kro@H r{SPt|2>$;E(h3X=OhOQ>q@=*0C@2KPKpH3p#z1*RAXZ`kD`fxx@`?`& literal 0 HcmV?d00001 From 166852bff062890c6d22b41a0345eb15a0354b54 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 10 Apr 2025 17:13:45 -0500 Subject: [PATCH 4/7] match3 game code --- Metro/Metro_RP2350_Match3/match3_game/code.py | 457 ++++++++++ .../match3_game/match3_cards_spritesheet.bmp | Bin 0 -> 10910 bytes .../match3_game/match3_game_helpers.py | 777 ++++++++++++++++++ .../match3_game/mouse_cursor.bmp | Bin 0 -> 198 bytes 4 files changed, 1234 insertions(+) create mode 100644 Metro/Metro_RP2350_Match3/match3_game/code.py create mode 100644 Metro/Metro_RP2350_Match3/match3_game/match3_cards_spritesheet.bmp create mode 100644 Metro/Metro_RP2350_Match3/match3_game/match3_game_helpers.py create mode 100644 Metro/Metro_RP2350_Match3/match3_game/mouse_cursor.bmp diff --git a/Metro/Metro_RP2350_Match3/match3_game/code.py b/Metro/Metro_RP2350_Match3/match3_game/code.py new file mode 100644 index 000000000..3b694d1a5 --- /dev/null +++ b/Metro/Metro_RP2350_Match3/match3_game/code.py @@ -0,0 +1,457 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT +""" +Match3 game inspired by the Set card game. Two players compete +to find sets of cards that share matching or mis-matching traits. +""" +import array +import atexit +import io +import os +import time + +import board +import busio +import digitalio +import supervisor +import terminalio +import usb +from tilepalettemapper import TilePaletteMapper +from displayio import TileGrid, Group, Palette, OnDiskBitmap, Bitmap +from adafruit_display_text.text_box import TextBox +import adafruit_usb_host_descriptors +from adafruit_debouncer import Debouncer +import adafruit_sdcard +import msgpack +import storage +from match3_game_helpers import ( + Match3Game, + STATE_GAMEOVER, + STATE_PLAYING_SETCALLED, + GameOverException, +) + +original_autoreload_val = supervisor.runtime.autoreload +supervisor.runtime.autoreload = False + +AUTOSAVE_FILENAME = "match3_game_autosave.dat" + +main_group = Group() +display = supervisor.runtime.display + +# set up scale factor of 2 for larger display resolution +scale_factor = 1 +if display.width > 360: + scale_factor = 2 + main_group.scale = scale_factor + +save_to = None +game_state = None +try: + if AUTOSAVE_FILENAME in os.listdir("/saves/"): + savegame_buffer = io.BytesIO() + with open(f"/saves/{AUTOSAVE_FILENAME}", "rb") as f: + savegame_buffer.write(f.read()) + savegame_buffer.seek(0) + game_state = msgpack.unpack(savegame_buffer) + print(game_state) + + # if we made it to here then /saves/ exist so use it for + # save data + save_to = f"/saves/{AUTOSAVE_FILENAME}" +except OSError as e: + # no /saves/ dir likely means no CPSAVES + pass + +sd_pins_in_use = False + +if game_state is None: + # try to use sdcard for saves + # The SD_CS pin is the chip select line. + SD_CS = board.SD_CS + + # Connect to the card and mount the filesystem. + try: + cs = digitalio.DigitalInOut(SD_CS) + except ValueError: + sd_pins_in_use = True + + print(f"sd pins in use: {sd_pins_in_use}") + try: + if not sd_pins_in_use: + sdcard = adafruit_sdcard.SDCard( + busio.SPI(board.SD_SCK, board.SD_MOSI, board.SD_MISO), cs + ) + vfs = storage.VfsFat(sdcard) + storage.mount(vfs, "/sd") + + if "set_game_autosave.dat" in os.listdir("/sd/"): + savegame_buffer = io.BytesIO() + with open("/sd/set_game_autosave.dat", "rb") as f: + savegame_buffer.write(f.read()) + savegame_buffer.seek(0) + game_state = msgpack.unpack(savegame_buffer) + print(game_state) + + if "placeholder.txt" not in os.listdir("/sd/"): + # if we made it to here then /sd/ exists and has a card + # so use it for save data + save_to = "/sd/set_game_autosave.dat" + except OSError: + # no SDcard + pass + +# background color +bg_bmp = Bitmap( + display.width // scale_factor // 10, display.height // scale_factor // 10, 1 +) +bg_palette = Palette(1) +bg_palette[0] = 0x888888 +bg_tg = TileGrid(bg_bmp, pixel_shader=bg_palette) +bg_group = Group(scale=10) +bg_group.append(bg_tg) +main_group.append(bg_group) + + +match3_game = Match3Game( + game_state=game_state, + display_size=(display.width // scale_factor, display.height // scale_factor), + save_location=save_to, +) +main_group.append(match3_game) + +# create a group to hold the game over elements +game_over_group = Group() + +# create a TextBox to hold the game over message +game_over_label = TextBox( + terminalio.FONT, + text="", + color=0xFFFFFF, + background_color=0x111111, + width=display.width // scale_factor // 2, + height=110, + align=TextBox.ALIGN_CENTER, +) +# move it to the center top of the display +game_over_label.anchor_point = (0, 0) +game_over_label.anchored_position = ( + display.width // scale_factor // 2 - (game_over_label.width) // 2, + 40, +) + +# make it hidden, we'll show it when the game is over. +game_over_group.hidden = True + +# add the game over lable to the game over group +game_over_group.append(game_over_label) + +# load the play again, and exit button bitmaps +play_again_btn_bmp = OnDiskBitmap("btn_play_again.bmp") +exit_btn_bmp = OnDiskBitmap("btn_exit.bmp") + +# create TileGrid for the play again button +play_again_btn = TileGrid( + bitmap=play_again_btn_bmp, pixel_shader=play_again_btn_bmp.pixel_shader +) + +# transparent pixels in the corners for the rounded corner effect +play_again_btn_bmp.pixel_shader.make_transparent(0) + +# centered within the display, offset to the left +play_again_btn.x = ( + display.width // scale_factor // 2 - (play_again_btn_bmp.width) // 2 - 30 +) + +# inside the bounds of the game over label, so it looks like a dialog visually +play_again_btn.y = 100 + +# create TileGrid for the exit button +exit_btn = TileGrid(bitmap=exit_btn_bmp, pixel_shader=exit_btn_bmp.pixel_shader) + +# transparent pixels in the corners for the rounded corner effect +exit_btn_bmp.pixel_shader.make_transparent(0) + +# centered within the display, offset to the right +exit_btn.x = display.width // scale_factor // 2 - (exit_btn_bmp.width) // 2 + 30 + +# inside the bounds of the game over label, so it looks like a dialog visually +exit_btn.y = 100 + +# add the play again and exit buttons to the game over group +game_over_group.append(play_again_btn) +game_over_group.append(exit_btn) +main_group.append(game_over_group) + +# wait a second for USB devices to be ready +time.sleep(1) + +# load the mouse bitmap +mouse_bmp = OnDiskBitmap("mouse_cursor.bmp") + +# make the background pink pixels transparent +mouse_bmp.pixel_shader.make_transparent(0) + +# list for mouse tilegrids +mouse_tgs = [] +# list for palette mappers, one for each mouse +palette_mappers = [] +# list for mouse colors +colors = [0x2244FF, 0xFFFF00] + +# remap palette will have the 3 colors from mouse bitmap +# and the two colors from the mouse colors list +remap_palette = Palette(3 + len(colors)) +# index 0 is transparent +remap_palette.make_transparent(0) + +# copy the 3 colors from the mouse bitmap palette +for i in range(3): + remap_palette[i] = mouse_bmp.pixel_shader[i] + +# copy the 2 colors from the mouse colors list +for i in range(2): + remap_palette[i + 3] = colors[i] + +# create tile palette mappers +for i in range(2): + palette_mapper = TilePaletteMapper(remap_palette, 3, 1, 1) + # remap index 2 to each of the colors in mouse colors list + palette_mapper[0] = [0, 1, i + 3] + palette_mappers.append(palette_mapper) + + # create tilegrid for each mouse + mouse_tg = TileGrid(mouse_bmp, pixel_shader=palette_mapper) + mouse_tg.x = display.width // scale_factor // 2 - (i * 12) + mouse_tg.y = display.height // scale_factor // 2 + mouse_tgs.append(mouse_tg) + +# USB info lists +mouse_interface_indexes = [] +mouse_endpoint_addresses = [] +kernel_driver_active_flags = [] +# USB device object instance list +mice = [] +# buffers list for mouse packet data +mouse_bufs = [] +# debouncers list for debouncing mouse left clicks +mouse_debouncers = [] + +# scan for connected USB devices +for device in usb.core.find(find_all=True): + # check if current device is has a boot mouse endpoint + mouse_interface_index, mouse_endpoint_address = ( + adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device) + ) + if mouse_interface_index is not None and mouse_endpoint_address is not None: + # if it does have a boot mouse endpoint then add information to the + # usb info lists + mouse_interface_indexes.append(mouse_interface_index) + mouse_endpoint_addresses.append(mouse_endpoint_address) + + # add the mouse device instance to list + mice.append(device) + print( + f"mouse interface: {mouse_interface_index} " + + f"endpoint_address: {hex(mouse_endpoint_address)}" + ) + + # detach kernel driver if needed + kernel_driver_active_flags.append(device.is_kernel_driver_active(0)) + if device.is_kernel_driver_active(0): + device.detach_kernel_driver(0) + + # set the mouse configuration so it can be used + device.set_configuration() + + +def is_mouse1_left_clicked(): + """ + Check if mouse 1 left click is pressed + :return: True if mouse 1 left click is pressed + """ + return is_left_mouse_clicked(mouse_bufs[0]) + + +def is_mouse2_left_clicked(): + """ + Check if mouse 2 left click is pressed + :return: True if mouse 2 left click is pressed + """ + return is_left_mouse_clicked(mouse_bufs[1]) + + +def is_left_mouse_clicked(buf): + """ + Check if a mouse is pressed given its packet buffer + filled with read data + :param buf: the buffer containing the packet data + :return: True if mouse left click is pressed + """ + val = buf[0] & (1 << 0) != 0 + return val + + +def is_right_mouse_clicked(buf): + """ + check if a mouse right click is pressed given its packet buffer + :param buf: the buffer containing the packet data + :return: True if mouse right click is pressed + """ + val = buf[0] & (1 << 1) != 0 + return val + + +# print(f"addresses: {mouse_endpoint_addresses}") +# print(f"indexes: {mouse_interface_indexes}") + +for mouse_tg in mouse_tgs: + # add the mouse to the main group + main_group.append(mouse_tg) + + # Buffer to hold data read from the mouse + # Boot mice have 4 byte reports + mouse_bufs.append(array.array("b", [0] * 8)) + +# create debouncer objects for left click functions +mouse_debouncers.append(Debouncer(is_mouse1_left_clicked)) +mouse_debouncers.append(Debouncer(is_mouse2_left_clicked)) + +# set main_group as root_group, so it is visible on the display +display.root_group = main_group + +# variable to hold winning player +winner = None + + +def get_mouse_deltas(buffer, read_count): + """ + Given a mouse packet buffer and a read count of number of bytes read, + return the delta x and y values of the mouse. + :param buffer: the buffer containing the packet data + :param read_count: the number of bytes read from the mouse + :return: tuple containing x and y delta values + """ + if read_count == 4: + delta_x = buffer[1] + delta_y = buffer[2] + elif read_count == 8: + delta_x = buffer[2] + delta_y = buffer[4] + else: + raise ValueError(f"Unsupported mouse packet size: {read_count}, must be 4 or 8") + return delta_x, delta_y + + +def atexit_callback(): + """ + re-attach USB devices to kernel if needed, and set + autoreload back to the original state. + :return: + """ + for _i, _mouse in enumerate(mice): + if kernel_driver_active_flags[_i]: + if not _mouse.is_kernel_driver_active(0): + _mouse.attach_kernel_driver(0) + supervisor.runtime.autoreload = original_autoreload_val + + +atexit.register(atexit_callback) + +# main loop +while True: + + # if set has been called + if match3_game.cur_state == STATE_PLAYING_SETCALLED: + # update the progress bar ticking down + match3_game.update_active_turn_progress() + + # loop over the mice objects + for i, mouse in enumerate(mice): + mouse_tg = mouse_tgs[i] + # attempt mouse read + try: + # read data from the mouse, small timeout so we move on + # quickly if there is no data + data_len = mouse.read( + mouse_endpoint_addresses[i], mouse_bufs[i], timeout=10 + ) + mouse_deltas = get_mouse_deltas(mouse_bufs[i], data_len) + # if we got data, then update the mouse cursor on the display + # using min and max to keep it within the bounds of the display + mouse_tg.x = max( + 0, + min( + display.width // scale_factor - 1, mouse_tg.x + mouse_deltas[0] // 2 + ), + ) + mouse_tg.y = max( + 0, + min( + display.height // scale_factor - 1, + mouse_tg.y + mouse_deltas[1] // 2, + ), + ) + + # timeout error is raised if no data was read within the allotted timeout + except usb.core.USBTimeoutError: + pass + + # update the mouse debouncers + mouse_debouncers[i].update() + + try: + # if the current mouse is right-clicking + if is_right_mouse_clicked(mouse_bufs[i]): + # let the game object handle the right-click + match3_game.handle_right_click(i) + + # if the current mouse left-clicked + if mouse_debouncers[i].rose: + # get the current mouse coordinates + coords = (mouse_tg.x, mouse_tg.y, 0) + + # if the current state is GAMEOVER + if match3_game.cur_state != STATE_GAMEOVER: + # let the game object handle the click event + match3_game.handle_left_click(i, coords) + else: + # if the mouse point is within the play again + # button bounding box + if play_again_btn.contains(coords): + # set next code file to this one + supervisor.set_next_code_file(__file__) + # reload + supervisor.reload() + + # if the mouse point is within the exit + # button bounding box + if exit_btn.contains(coords): + supervisor.reload() + + # if the game is over + except GameOverException: + # check for a winner + winner = None + if match3_game.scores[0] > match3_game.scores[1]: + winner = 0 + elif match3_game.scores[0] < match3_game.scores[1]: + winner = 1 + + # if there was a winner + if winner is not None: + # show a message with the winning player + message = f"\nGame Over\nPlayer{winner + 1} Wins!" + game_over_label.color = colors[winner] + game_over_label.text = message + + else: # there wasn't a winner + # show a tie game message + message = "\nGame Over\nTie Game Everyone Wins!" + + # make the gameover group visible + game_over_group.hidden = False + + # delete the autosave file. + os.remove(save_to) diff --git a/Metro/Metro_RP2350_Match3/match3_game/match3_cards_spritesheet.bmp b/Metro/Metro_RP2350_Match3/match3_game/match3_cards_spritesheet.bmp new file mode 100644 index 0000000000000000000000000000000000000000..78e34c0af1715eab23becfedbc1d71533dacc651 GIT binary patch literal 10910 zcmeI0LCz#K427!^te8bNCqS%5ED#bGKqDkpY`6z2wk$Zwu;m<_gS-6LN$h-1l~XBt zej%hOO?Nxa^UI50($h2VetQ4)R-X6r{+qo1l-KX_x^17zyF4=g#pm_)nGE%{Z7=qx zjNbkD^RFei@b<}oeR&a1dH(+9m+kG_zqU7jycrPx)8XTBKUKbh0m<$0cwl%Ku3%Uw zDo=$z?)Otn;FM!@w)w~Hc8a;oxuO$2mu4PoAZ9@)d!F?(V$Smgj+}h^fBMID;2BpX3afc0Q+^C=R_v zo}u~m!}rYQ@e*g#xy%W_vX!suq!$^dVZQq|Lk#)kdOs4yzjfunm$I_uJ16cSdl+@9(AqV}41S3maB3(| z@u-GlTnX_6Yce+>KW6Q#h0!58?YLib;v^kvj( zjJaPO!H7C@&eX4Coz{O@h?=>sPMzH&;;D_AiP6~m)<8HB`-Y-;q7%LZzjT*#PTU17 zHH1ZIKUSxlt+@lsF!cK+`30RzoZYVl&OuK)#CFx<6nAB!Uw52`D+C|0-T}XN)06IS zxho52X$8|M*Ep-`^9xDNwx9WlQ#zg|5FCW|zm#)Mhl=|YF)MIOE zS3+LV`V77}rsWOcdF5%{7&*(Xl(Wi}5(8>do|@OFOk_OFxAqy6EKx~@yvi#Rtj#pn zbA9BCF4Oa-pPX&=-CDKIWde5W7ddmCasith$JCa3UKetm+F5-h=lCJ6!$V)Bq~&Y5 zKnETx8}f=veChdraptW@xnDvL(W*lqs~h`JXYKvi7nyy6GxzIIr>>;WpxG3*&+bb_ zTdQ^ChFMMU9e&xoeHux2j|?|{vbWjX=+}YFP&(u6z|^k}LBu{FJ%-^Pjg#Yek+b_X z5znGIuwRGo3Bpm}H3)ndD*2)dycrWoA6&9DF&w?@OGo>&xJm?4b3Q+D_t~ zFAd-GEjr;TD`&pMyfRgxOHycbid)X6y cb&@gCmB-w(= 3: + # draw 3 new cards + _new_cards = random_selection(self.play_deck, 3) + # place them in 3 of the empty cells + for i, _new_card in enumerate(_new_cards): + self.card_grid.add_content( + _new_card, empty_cells[i], (1, 1) + ) + + else: # there are no 3 empty cells + # redraw the original grid with 12 new cards + + # remove existing cards from the grid and + # return them to the deck. + for _y in range(3): + for _x in range(6): + try: + _remove_card = self.card_grid.pop_content( + (_x, _y) + ) + print(f"remove_card: {_remove_card}") + self.play_deck.append(_remove_card) + + except KeyError: + continue + + # draw 12 new cards from the deck + starting_pool = random_selection(self.play_deck, 12) + # place them into the grid + for y in range(3): + for x in range(4): + self.card_grid.add_content( + starting_pool[y * 4 + x], (x + 1, y), (1, 1) + ) + + # update the deck count label + self.deck_count_lbl.text = f"Deck: {len(self.play_deck)}" + # save the game state + self.save_game_state() + + # update the score labels to show the no set called indicator(s) + self.update_scores() + + # if the current state is set called + elif self.cur_state == STATE_PLAYING_SETCALLED: + # if the player that clicked is the active player + if player_index == self.active_player: + # get the coordinates that were clicked adjusting for the card_grid position + adjusted_coords = ( + coords[0] - self.card_grid.x, + coords[1] - self.card_grid.y, + 0, + ) + # check which cell contains the clicked coordinates + clicked_grid_cell_coordinates = self.card_grid.which_cell_contains( + coords + ) + # print(clicked_grid_cell_coordinates) + + # if a cell in the grid was clicked + if clicked_grid_cell_coordinates is not None: + # try to get the content of the clicked cell, a Card instance potentially + try: + clicked_cell_content = self.card_grid.get_content( + clicked_grid_cell_coordinates + ) + except KeyError: + # if no content is in the cell just return + return + + # check if the Card instance was clicked, and if the card + # isn't already in the list of clicked cards + if ( + clicked_cell_content.contains(adjusted_coords) + and clicked_cell_content not in self.clicked_cards + ): + + clicked_card = clicked_cell_content + # show the clicked card indicator in this cell + clicked_cell_content.insert( + 0, self.clicked_card_indicators[len(self.clicked_cards)] + ) + # add the card instance to the clicked cards list + self.clicked_cards.append(clicked_card) + + # add the coordinates to the clicked coordinates list + self.clicked_coordinates.append(clicked_grid_cell_coordinates) + + # if 3 cards have been clicked + if len(self.clicked_cards) == 3: + # check if the 3 cards make a valid set + valid_set = validate_set(*self.clicked_cards) + + # if they are a valid set + if valid_set: + # award a point to the active player + self.scores[self.active_player] += 1 + + # loop over the clicked coordinates + for coord in self.clicked_coordinates: + # remove the old card from this cell + _remove_card = self.card_grid.pop_content(coord) + # remove border from Match3Card group + _remove_card.pop(0) + + # find empty cells in the grid + empty_cells = self.find_empty_cells() + + # if there are at least 3 cards in the deck and + # at least 6 empty cells in the grid + if len(self.play_deck) >= 3 and len(empty_cells) > 6: + # deal 3 new cards to empty spots in the grid + for i in range(3): + _new_card = random_selection(self.play_deck, 1)[ + 0 + ] + self.card_grid.add_content( + _new_card, empty_cells[i], (1, 1) + ) + # update the deck count label + self.deck_count_lbl.text = ( + f"Deck: {len(self.play_deck)}" + ) + + # there are not at least 3 cards in the deck + # and at least 6 empty cells + else: + # if there are no empty cells + if len(self.find_empty_cells()) == 0: + # set the current state to game over + self.cur_state = STATE_GAMEOVER + raise GameOverException() + + else: # the 3 clicked cards are not a valid set + + # remove the clicked card indicators + for _ in range(3): + coords = self.clicked_coordinates.pop() + self.card_grid.get_content(coords).pop(0) + + # subtract a point from the active player + self.scores[self.active_player] -= 1 + + # save the game state + self.save_game_state() + # reset the clicked cards and coordinates lists + self.clicked_cards = [] + self.clicked_coordinates = [] + + # set the current state to open play + self.cur_state = STATE_PLAYING_OPEN + # set active player and active turn vars + self.active_player = None + self.active_turn_start_time = None + self.active_turn_countdown.hidden = True + # update the score labels + self.update_scores() + + # if the current state is title state + elif self.cur_state == STATE_TITLE: + # if the resume button is visible and was clicked + if ( + not self.title_screen.resume_btn.hidden + and self.title_screen.resume_btn.contains(coords) + ): + + # load the game from the given game state + self.load_from_game_state(self.game_state) + # hide the title screen + self.title_screen.hidden = True + # set the current state to open play + self.cur_state = STATE_PLAYING_OPEN + + # if the new game button was clicked + elif self.title_screen.new_game_btn.contains(coords): + self.game_state = None + # delete the autosave file + try: + os.remove(self.save_location) + print("removed old game save file") + except OSError: + pass + # initialize a new game + self.init_new_game() + # hide the title screen + self.title_screen.hidden = True + # set the current state to open play + self.cur_state = STATE_PLAYING_OPEN + + def find_empty_cells(self): + """ + find the cells within the card grid that are empty + :return: list of empty cell coordinate tuples. + """ + empty_cells = [] + for x in range(6): + for y in range(3): + try: + _content = self.card_grid.get_content((x, y)) + except KeyError: + empty_cells.append((x, y)) + return empty_cells + + def update_active_turn_progress(self): + """ + update the active turn progress bar countdown + :return: + """ + if self.cur_state == STATE_PLAYING_SETCALLED: + time_diff = time.monotonic() - self.active_turn_start_time + if time_diff > ACTIVE_TURN_TIME_LIMIT: + self.scores[self.active_player] -= 1 + self.active_player = None + self.update_scores() + self.cur_state = STATE_PLAYING_OPEN + self.active_turn_countdown.hidden = True + else: + self.active_turn_countdown.value = int( + (ACTIVE_TURN_TIME_LIMIT - time_diff) * 10 + ) + + +class GameOverException(Exception): + """ + Exception that indicates the game is over. + Message will contain the reason. + """ + + +class Match3TitleScreen(Group): + """ + Title screen for the Match3 game. + """ + + def __init__(self, display_size): + super().__init__() + self.display_size = display_size + # background bitmap color + bg_bmp = Bitmap(display_size[0] // 10, display_size[1] // 10, 1) + bg_palette = Palette(1) + bg_palette[0] = 0xFFFFFF + bg_tg = TileGrid(bg_bmp, pixel_shader=bg_palette) + bg_group = Group(scale=10) + bg_group.append(bg_tg) + self.append(bg_group) + + # load title card bitmap + title_card_bmp = OnDiskBitmap("title_card_match3.bmp") + title_card_tg = TileGrid( + title_card_bmp, pixel_shader=title_card_bmp.pixel_shader + ) + title_card_tg.x = 2 + if display_size[1] > 200: + title_card_tg.y = 20 + self.append(title_card_tg) + + # make resume and new game buttons + BUTTON_X = display_size[0] - 90 + BUTTON_WIDTH = 70 + BUTTON_HEIGHT = 20 + self.resume_btn = Button( + x=BUTTON_X, + y=40, + width=BUTTON_WIDTH, + height=BUTTON_HEIGHT, + style=Button.ROUNDRECT, + fill_color=0x6D2EDC, + outline_color=0x888888, + label="Resume", + label_font=terminalio.FONT, + label_color=0xFFFFFF, + ) + self.append(self.resume_btn) + self.new_game_btn = Button( + x=BUTTON_X, + y=40 + BUTTON_HEIGHT + 10, + width=BUTTON_WIDTH, + height=BUTTON_HEIGHT, + style=Button.RECT, + fill_color=0x0C9F0C, + outline_color=0x111111, + label="New Game", + label_font=terminalio.FONT, + label_color=0xFFFFFF, + ) + self.append(self.new_game_btn) diff --git a/Metro/Metro_RP2350_Match3/match3_game/mouse_cursor.bmp b/Metro/Metro_RP2350_Match3/match3_game/mouse_cursor.bmp new file mode 100644 index 0000000000000000000000000000000000000000..94ec328896cfb7dc84ac82d6a5b8b58e8a8a5f12 GIT binary patch literal 198 zcmZ?rJ;ne5(|}YB5VHX>4-hjlumDL01_K~g)`MVXARB}k82-Z$nCIjkR19X2Kro@H r{SPt|2>$;E(h3X=OhOQ>q@=*0C@2KPKpH3p#z1*RAXZ`kD`fxx@`?`& literal 0 HcmV?d00001 From f7e6c1fb3eae9a37b4f4bce137bdd21e2b9d7ec4 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 11 Apr 2025 13:18:15 -0500 Subject: [PATCH 5/7] a few more comments --- Metro/Metro_RP2350_Match3/match3_game/code.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Metro/Metro_RP2350_Match3/match3_game/code.py b/Metro/Metro_RP2350_Match3/match3_game/code.py index 3b694d1a5..3dd855aaf 100644 --- a/Metro/Metro_RP2350_Match3/match3_game/code.py +++ b/Metro/Metro_RP2350_Match3/match3_game/code.py @@ -49,6 +49,7 @@ save_to = None game_state = None try: + # check for autosave file on CPSAVES drive if AUTOSAVE_FILENAME in os.listdir("/saves/"): savegame_buffer = io.BytesIO() with open(f"/saves/{AUTOSAVE_FILENAME}", "rb") as f: @@ -113,7 +114,7 @@ bg_group.append(bg_tg) main_group.append(bg_group) - +# create Game helper object match3_game = Match3Game( game_state=game_state, display_size=(display.width // scale_factor, display.height // scale_factor), From f4d0dc75c26cb560f5c83732c5f378c6f9ddf3a7 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 11 Apr 2025 13:42:30 -0500 Subject: [PATCH 6/7] pylint fixes, add title card --- .../match3_game/match3_game_helpers.py | 8 +++++--- .../match3_game/title_card_match3.bmp | Bin 0 -> 16162 bytes .../tilepalettemapper_demo/code.py | 2 +- Metro/Metro_RP2350_Match3/two_mice_demo/code.py | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 Metro/Metro_RP2350_Match3/match3_game/title_card_match3.bmp diff --git a/Metro/Metro_RP2350_Match3/match3_game/match3_game_helpers.py b/Metro/Metro_RP2350_Match3/match3_game/match3_game_helpers.py index 5d45e5cf5..fe9cbd262 100644 --- a/Metro/Metro_RP2350_Match3/match3_game/match3_game_helpers.py +++ b/Metro/Metro_RP2350_Match3/match3_game/match3_game_helpers.py @@ -580,7 +580,9 @@ def handle_left_click(self, player_index, coords): # if 3 cards have been clicked if len(self.clicked_cards) == 3: # check if the 3 cards make a valid set - valid_set = validate_set(*self.clicked_cards) + valid_set = validate_set(self.clicked_cards[0], + self.clicked_cards[1], + self.clicked_cards[2]) # if they are a valid set if valid_set: @@ -658,7 +660,7 @@ def handle_left_click(self, player_index, coords): # load the game from the given game state self.load_from_game_state(self.game_state) # hide the title screen - self.title_screen.hidden = True + self.title_screen.hidden = True # pylint: disable=attribute-defined-outside-init # set the current state to open play self.cur_state = STATE_PLAYING_OPEN @@ -674,7 +676,7 @@ def handle_left_click(self, player_index, coords): # initialize a new game self.init_new_game() # hide the title screen - self.title_screen.hidden = True + self.title_screen.hidden = True # pylint: disable=attribute-defined-outside-init # set the current state to open play self.cur_state = STATE_PLAYING_OPEN diff --git a/Metro/Metro_RP2350_Match3/match3_game/title_card_match3.bmp b/Metro/Metro_RP2350_Match3/match3_game/title_card_match3.bmp new file mode 100644 index 0000000000000000000000000000000000000000..66f040fb0a4509eef99be7c7afe72c52be735c9b GIT binary patch literal 16162 zcmeHMJC5T<6r>Hy(Iykj1;!X)qfG`%ZD1k6k%8Hfcnn8Q9rz|Td=y7MgzqwbRrOwf zsHVt~gk5T(UWsaUzpk!&pC$+U@t236I<~*<`n!GqvhTn4{igfQu3h)Xk6rh}2YtV{ zdwjd@H9oxi`18we?7ZRfZPWI(`|rcA-TU|dbnpIr*S)^JcKxm0JaF^Cm3!bhS9@>$ zRiE#>&PGh0jo`YPrF8jtF2kgi9}0w04$2R61k*z>EWtSF7+)TbA@NW$h+q~UX;jc> z>b)jBrmkssf!3jL57-RB3SV6cOT}Z_DbS!;*0RN2vs02wy$i*Y1QAs87vq>Sd0@PeFiN==6|Zdt#6P18p^i^fwv z2Sw?C@FJ`hr!4=dQk%REJQ8&&)NXxg#FcT^)osRqq}S%dVemizxaZ<_93{_O8ZR9v zBrzbDNt>C@*34wACl9%MYOxEzE5pF2ZHfUHYgRr^mSaLWo~&ZgBJl>gf-Cg$1Jd*2 z8%Pjez+iQNY5a@gFPfzD*&B%o1;xQg3 z6BLzyO2jB$pW@)ORI;<>R?_WwH)d z>noq*)PfBMWqeexOyn?gRa%O&n7~W8_YP9*dJ4Zj;!@!;B$5Mu1h#ADS{R5&@am&; zDZC(fF+3;O#tU7plp)S>YQ)#n%YzrgyEH%1yBp=Fgrjf-IKu56)5qql?k3`EFmJ?j zdY9rCJZn5@P#QO1w~~Q6eUcgUt_qJ~j8KLm94>LzWP_MZehb0(F{8TI3Y0>$<9JcF z0XP2qn29$acpuAdzf&G4Vm4(uYyR5t#9xVwK8m92o>x5Q`2_Bsb&B!H$NcAm0Csvp zSFd$#JhQY3Lsgw{k8TB99JLw_Jd9-55AbXfNEIG%VCHzSP2j@bbz6E31HX! z3UY2^J8=UL6Td?|$OOp--l1N49Jcu9OY%bUy9AzbHAdt`jU)W%-0&c5ilftgPrXJ7 zkxN%w2W-YyGjt?I15G|Q7C?nP#nR=^D@#LI62QhSj4Q*l;TW?a8Du!D#~0)FOT1zh zT~3XS4JRSS_KbXB8jr0l#sy>7CY}w_jnqWt#nOW%m>b0_7vB0e4}2{iz_F2=&Wn7H z9+kGtnl5M=o`qK&VLBYFNXwm_U&E}lN_{(Ga`T(dhPU5ruzAHHvth5Ao8q_GY_=D~ z+i&)J5Ue=GQXDxZ;zfllz0Zez>8!Q-pTxWC-_+5^=HIa1TA=FeVmbh zBxv+1JkO1Ze0lP=j>IA*5XAbo^K0W7LC~x4=K1dl%ldv-^Xo;g#G6?kBX}>QSK-a8 zj}bhvuJGpNUjy%~`e?=Docd_tea-dZfwUhlq%-TIjdyx|wDC@_j}g3+>th7Zhl}%N zX8q2`n^(W{@#fX)$-^IXz$xO51M7AH7cDG5LrxwI(K*U0auLTY3gya;w?u>ZZ2fY0c;DBUagK5El3n z*Z@!)aaOxE0AXuebHNB6OwdX^?dJ3=f-pR;mtb~T7kVA9Ns#b6cMxt~^b)z3gLV{P z9){pYo5njK|3vbWkn7WBXY`C>;L&*lyfux@F5_u=#4CYW$MB4SZ8{m!Ug|CIDT&ek0cyb5vQ#!?N`Zc|V_muELRnj|x#|*5Yc(*gCj0Z*j zH$wRD2wo#yED%NwkZZsT&pMW>h{t}5;76<63%xN>{1PhRA-R-ahGlrRq{55*BS5o) z0mXV-)8^rUK_oZfxo7*s{2Phy0FUlF!~^WSe1YeTr1-%_CaS`t17VQySZ_@S3Q4MY z!ZG`p4e+?RoDMOA^8Dje%YP1pA0wy$nj=d*%vl3Ha7Rk}>DY+x&Ma-c3nm_7SmI%h lRt28sTlwdX;{xeDz_T3HreFq^bZ|>8nBCgV1K00?{{eGhv4H>p literal 0 HcmV?d00001 diff --git a/Metro/Metro_RP2350_Match3/tilepalettemapper_demo/code.py b/Metro/Metro_RP2350_Match3/tilepalettemapper_demo/code.py index 3040f4bf9..45c82cc39 100644 --- a/Metro/Metro_RP2350_Match3/tilepalettemapper_demo/code.py +++ b/Metro/Metro_RP2350_Match3/tilepalettemapper_demo/code.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT import supervisor -from displayio import Group, OnDiskBitmap, TileGrid, Palette +from displayio import Group, OnDiskBitmap, TileGrid from tilepalettemapper import TilePaletteMapper # use the default built-in display, diff --git a/Metro/Metro_RP2350_Match3/two_mice_demo/code.py b/Metro/Metro_RP2350_Match3/two_mice_demo/code.py index 7af3396af..93f51a511 100644 --- a/Metro/Metro_RP2350_Match3/two_mice_demo/code.py +++ b/Metro/Metro_RP2350_Match3/two_mice_demo/code.py @@ -6,7 +6,7 @@ import terminalio import usb.core from adafruit_display_text.bitmap_label import Label -from displayio import Group, OnDiskBitmap, TileGrid, Palette, ColorConverter +from displayio import Group, OnDiskBitmap, TileGrid, Palette import adafruit_usb_host_descriptors @@ -181,4 +181,4 @@ def get_mouse_deltas(buffer, read_count): # set the output string into text of the label # to show it on the display - output_lbls[mouse_index].text = out_str \ No newline at end of file + output_lbls[mouse_index].text = out_str From 0332716490389e8f549e3bdf5b6e674679c9812a Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 11 Apr 2025 13:50:14 -0500 Subject: [PATCH 7/7] button bitmaps --- .../Metro_RP2350_Match3/match3_game/btn_exit.bmp | Bin 0 -> 922 bytes .../match3_game/btn_no_set.bmp | Bin 0 -> 922 bytes .../match3_game/btn_play_again.bmp | Bin 0 -> 922 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Metro/Metro_RP2350_Match3/match3_game/btn_exit.bmp create mode 100644 Metro/Metro_RP2350_Match3/match3_game/btn_no_set.bmp create mode 100644 Metro/Metro_RP2350_Match3/match3_game/btn_play_again.bmp diff --git a/Metro/Metro_RP2350_Match3/match3_game/btn_exit.bmp b/Metro/Metro_RP2350_Match3/match3_game/btn_exit.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2c3347e6be40fa803ba6427c27eb3295ca393d90 GIT binary patch literal 922 zcmd6lJr2S!4250b0Ci75;~gRA23?p~xCav}=V0h%!^_{&I70^{tk_AuSk{a5miv07 z6f5c*c}1R)1D#M2c$Z7gNHPsXd8L-+_IRbkj&j)ahB%O($Pa}O&ul~oGtIcD(H-0?#@qd!`J&L{4Lwmwqo*`(d6BJU ZclBTOUiaT~+dtg%RnLc}o#ES_&%Y&tX>b4l literal 0 HcmV?d00001 diff --git a/Metro/Metro_RP2350_Match3/match3_game/btn_no_set.bmp b/Metro/Metro_RP2350_Match3/match3_game/btn_no_set.bmp new file mode 100644 index 0000000000000000000000000000000000000000..eb23afbda98d08995f87667e52de1af6dcd90c24 GIT binary patch literal 922 zcmb`FK@P$o5JjnafYKA#nT0!JAn5_RaibfrV&ck^bnQLy1EC=>q%m>AQ1bYlA0{*3 zm%R$HV1FYo$Rn~-2kZ#k^Esv`kvgT~3X=Kyc!t9d+pf?$6r4^kb(=oQITTk~hz8bL zb$XyGM%{M;CK`K54z6`oB!ErMmauI4JJTCOgcW0(v$0mtvmwJ0C$a|4Wql5y_GRl? z%~OH0&c8SlUJ$%P#kCKfBa=l^cedBW_}0V@LE&Dl>To-RuHA(H8Of63;LWtV9eHhjSSc~r}Sz0#FvV69cB2P$IJ zeOJLmV;|+l+2prT>-8<=^qX5U@Zmui9A0n0$y$cr+&Liy_sYTt9?(X&g&s-`cvB#%<(dgff8m{9Z=eTB0HU%Alx`m6_RJ}8~T lvEksAi#{ZeN8~7DOflSR*hJfI)UCd1^