From 0e1795884fb9b67e26329cb3927c89b112462294 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Tue, 30 Dec 2025 02:13:05 +1000 Subject: [PATCH 1/5] Two Eleven App Windows friendly tactility.py --- Apps/Calculator/tactility.py | 46 +- Apps/Diceware/tactility.py | 46 +- Apps/GPIO/tactility.py | 46 +- Apps/GraphicsDemo/tactility.py | 46 +- Apps/HelloWorld/tactility.py | 46 +- Apps/SerialConsole/tactility.py | 46 +- Apps/TwoEleven/CMakeLists.txt | 16 + Apps/TwoEleven/README.md | 57 ++ Apps/TwoEleven/images/3x3.png | Bin 0 -> 3990 bytes Apps/TwoEleven/images/4x4.png | Bin 0 -> 3679 bytes Apps/TwoEleven/images/5x5.png | Bin 0 -> 3773 bytes Apps/TwoEleven/images/6x6.png | Bin 0 -> 4050 bytes Apps/TwoEleven/images/selection.png | Bin 0 -> 3016 bytes Apps/TwoEleven/main/CMakeLists.txt | 15 + Apps/TwoEleven/main/Source/TwoEleven.cpp | 183 +++++ Apps/TwoEleven/main/Source/TwoEleven.h | 24 + Apps/TwoEleven/main/Source/TwoElevenHelpers.c | 130 ++++ Apps/TwoEleven/main/Source/TwoElevenHelpers.h | 89 +++ Apps/TwoEleven/main/Source/TwoElevenLogic.c | 162 ++++ Apps/TwoEleven/main/Source/TwoElevenLogic.h | 71 ++ Apps/TwoEleven/main/Source/TwoElevenUi.c | 226 ++++++ Apps/TwoEleven/main/Source/TwoElevenUi.h | 35 + Apps/TwoEleven/main/Source/main.cpp | 11 + Apps/TwoEleven/manifest.properties | 10 + Apps/TwoEleven/tactility.py | 690 ++++++++++++++++++ 25 files changed, 1893 insertions(+), 102 deletions(-) create mode 100644 Apps/TwoEleven/CMakeLists.txt create mode 100644 Apps/TwoEleven/README.md create mode 100644 Apps/TwoEleven/images/3x3.png create mode 100644 Apps/TwoEleven/images/4x4.png create mode 100644 Apps/TwoEleven/images/5x5.png create mode 100644 Apps/TwoEleven/images/6x6.png create mode 100644 Apps/TwoEleven/images/selection.png create mode 100644 Apps/TwoEleven/main/CMakeLists.txt create mode 100644 Apps/TwoEleven/main/Source/TwoEleven.cpp create mode 100644 Apps/TwoEleven/main/Source/TwoEleven.h create mode 100644 Apps/TwoEleven/main/Source/TwoElevenHelpers.c create mode 100644 Apps/TwoEleven/main/Source/TwoElevenHelpers.h create mode 100644 Apps/TwoEleven/main/Source/TwoElevenLogic.c create mode 100644 Apps/TwoEleven/main/Source/TwoElevenLogic.h create mode 100644 Apps/TwoEleven/main/Source/TwoElevenUi.c create mode 100644 Apps/TwoEleven/main/Source/TwoElevenUi.h create mode 100644 Apps/TwoEleven/main/Source/main.cpp create mode 100644 Apps/TwoEleven/manifest.properties create mode 100644 Apps/TwoEleven/tactility.py diff --git a/Apps/Calculator/tactility.py b/Apps/Calculator/tactility.py index 959146f..f80241c 100644 --- a/Apps/Calculator/tactility.py +++ b/Apps/Calculator/tactility.py @@ -10,8 +10,6 @@ import zipfile import requests import tarfile -import shutil -import configparser ttbuild_path = ".tactility" ttbuild_version = "2.4.0" @@ -23,12 +21,12 @@ local_base_path = None if sys.platform == "win32": - shell_color_red = "" - shell_color_orange = "" - shell_color_green = "" - shell_color_purple = "" - shell_color_cyan = "" - shell_color_reset = "" + shell_color_red = "\033[91m" + shell_color_orange = "\033[93m" + shell_color_green = "\033[32m" + shell_color_purple = "\033[35m" + shell_color_cyan = "\033[36m" + shell_color_reset = "\033[m" else: shell_color_red = "\033[91m" shell_color_orange = "\033[93m" @@ -196,7 +194,10 @@ def fetch_sdkconfig_files(platform_targets): def validate_environment(): if os.environ.get("IDF_PATH") is None: - exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") + if sys.platform == "win32": + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via %IDF_PATH%\\export.ps1") + else: + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") if not os.path.exists("manifest.properties"): exit_with_error("manifest.properties not found") if use_local_sdk == False and os.environ.get("TACTILITY_SDK_PATH") is not None: @@ -336,15 +337,24 @@ def build_all(version, platforms, skip_build): def wait_for_process(process): buffer = [] - os.set_blocking(process.stdout.fileno(), False) + if sys.platform != "win32": + os.set_blocking(process.stdout.fileno(), False) while process.poll() is None: while True: line = process.stdout.readline() - decoded_line = line.decode("UTF-8") - if decoded_line != "": - buffer.append(decoded_line) + if line: + decoded_line = line.decode("UTF-8") + if decoded_line != "": + buffer.append(decoded_line) + else: + break else: break + # Read any remaining output + for line in process.stdout: + decoded_line = line.decode("UTF-8") + if decoded_line: + buffer.append(decoded_line) return buffer # The first build must call "idf.py build" and consecutive builds must call "idf.py elf" as it finishes faster. @@ -356,7 +366,7 @@ def build_first(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") elf_path = find_elf_file(platform) # Remove previous elf file: re-creation of the file is used to measure if the build succeeded, # as the actual build job will always fail due to technical issues with the elf cmake script @@ -367,7 +377,8 @@ def build_first(version, platform, skip_build): print(f"Building first {platform} build") cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) # The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case if process.returncode == 0: @@ -389,12 +400,13 @@ def build_consecutively(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") if skip_build: return True cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) if process.returncode == 0: print_status_success(f"Building {platform} ELF") diff --git a/Apps/Diceware/tactility.py b/Apps/Diceware/tactility.py index 959146f..f80241c 100644 --- a/Apps/Diceware/tactility.py +++ b/Apps/Diceware/tactility.py @@ -10,8 +10,6 @@ import zipfile import requests import tarfile -import shutil -import configparser ttbuild_path = ".tactility" ttbuild_version = "2.4.0" @@ -23,12 +21,12 @@ local_base_path = None if sys.platform == "win32": - shell_color_red = "" - shell_color_orange = "" - shell_color_green = "" - shell_color_purple = "" - shell_color_cyan = "" - shell_color_reset = "" + shell_color_red = "\033[91m" + shell_color_orange = "\033[93m" + shell_color_green = "\033[32m" + shell_color_purple = "\033[35m" + shell_color_cyan = "\033[36m" + shell_color_reset = "\033[m" else: shell_color_red = "\033[91m" shell_color_orange = "\033[93m" @@ -196,7 +194,10 @@ def fetch_sdkconfig_files(platform_targets): def validate_environment(): if os.environ.get("IDF_PATH") is None: - exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") + if sys.platform == "win32": + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via %IDF_PATH%\\export.ps1") + else: + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") if not os.path.exists("manifest.properties"): exit_with_error("manifest.properties not found") if use_local_sdk == False and os.environ.get("TACTILITY_SDK_PATH") is not None: @@ -336,15 +337,24 @@ def build_all(version, platforms, skip_build): def wait_for_process(process): buffer = [] - os.set_blocking(process.stdout.fileno(), False) + if sys.platform != "win32": + os.set_blocking(process.stdout.fileno(), False) while process.poll() is None: while True: line = process.stdout.readline() - decoded_line = line.decode("UTF-8") - if decoded_line != "": - buffer.append(decoded_line) + if line: + decoded_line = line.decode("UTF-8") + if decoded_line != "": + buffer.append(decoded_line) + else: + break else: break + # Read any remaining output + for line in process.stdout: + decoded_line = line.decode("UTF-8") + if decoded_line: + buffer.append(decoded_line) return buffer # The first build must call "idf.py build" and consecutive builds must call "idf.py elf" as it finishes faster. @@ -356,7 +366,7 @@ def build_first(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") elf_path = find_elf_file(platform) # Remove previous elf file: re-creation of the file is used to measure if the build succeeded, # as the actual build job will always fail due to technical issues with the elf cmake script @@ -367,7 +377,8 @@ def build_first(version, platform, skip_build): print(f"Building first {platform} build") cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) # The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case if process.returncode == 0: @@ -389,12 +400,13 @@ def build_consecutively(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") if skip_build: return True cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) if process.returncode == 0: print_status_success(f"Building {platform} ELF") diff --git a/Apps/GPIO/tactility.py b/Apps/GPIO/tactility.py index 959146f..f80241c 100644 --- a/Apps/GPIO/tactility.py +++ b/Apps/GPIO/tactility.py @@ -10,8 +10,6 @@ import zipfile import requests import tarfile -import shutil -import configparser ttbuild_path = ".tactility" ttbuild_version = "2.4.0" @@ -23,12 +21,12 @@ local_base_path = None if sys.platform == "win32": - shell_color_red = "" - shell_color_orange = "" - shell_color_green = "" - shell_color_purple = "" - shell_color_cyan = "" - shell_color_reset = "" + shell_color_red = "\033[91m" + shell_color_orange = "\033[93m" + shell_color_green = "\033[32m" + shell_color_purple = "\033[35m" + shell_color_cyan = "\033[36m" + shell_color_reset = "\033[m" else: shell_color_red = "\033[91m" shell_color_orange = "\033[93m" @@ -196,7 +194,10 @@ def fetch_sdkconfig_files(platform_targets): def validate_environment(): if os.environ.get("IDF_PATH") is None: - exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") + if sys.platform == "win32": + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via %IDF_PATH%\\export.ps1") + else: + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") if not os.path.exists("manifest.properties"): exit_with_error("manifest.properties not found") if use_local_sdk == False and os.environ.get("TACTILITY_SDK_PATH") is not None: @@ -336,15 +337,24 @@ def build_all(version, platforms, skip_build): def wait_for_process(process): buffer = [] - os.set_blocking(process.stdout.fileno(), False) + if sys.platform != "win32": + os.set_blocking(process.stdout.fileno(), False) while process.poll() is None: while True: line = process.stdout.readline() - decoded_line = line.decode("UTF-8") - if decoded_line != "": - buffer.append(decoded_line) + if line: + decoded_line = line.decode("UTF-8") + if decoded_line != "": + buffer.append(decoded_line) + else: + break else: break + # Read any remaining output + for line in process.stdout: + decoded_line = line.decode("UTF-8") + if decoded_line: + buffer.append(decoded_line) return buffer # The first build must call "idf.py build" and consecutive builds must call "idf.py elf" as it finishes faster. @@ -356,7 +366,7 @@ def build_first(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") elf_path = find_elf_file(platform) # Remove previous elf file: re-creation of the file is used to measure if the build succeeded, # as the actual build job will always fail due to technical issues with the elf cmake script @@ -367,7 +377,8 @@ def build_first(version, platform, skip_build): print(f"Building first {platform} build") cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) # The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case if process.returncode == 0: @@ -389,12 +400,13 @@ def build_consecutively(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") if skip_build: return True cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) if process.returncode == 0: print_status_success(f"Building {platform} ELF") diff --git a/Apps/GraphicsDemo/tactility.py b/Apps/GraphicsDemo/tactility.py index 959146f..f80241c 100644 --- a/Apps/GraphicsDemo/tactility.py +++ b/Apps/GraphicsDemo/tactility.py @@ -10,8 +10,6 @@ import zipfile import requests import tarfile -import shutil -import configparser ttbuild_path = ".tactility" ttbuild_version = "2.4.0" @@ -23,12 +21,12 @@ local_base_path = None if sys.platform == "win32": - shell_color_red = "" - shell_color_orange = "" - shell_color_green = "" - shell_color_purple = "" - shell_color_cyan = "" - shell_color_reset = "" + shell_color_red = "\033[91m" + shell_color_orange = "\033[93m" + shell_color_green = "\033[32m" + shell_color_purple = "\033[35m" + shell_color_cyan = "\033[36m" + shell_color_reset = "\033[m" else: shell_color_red = "\033[91m" shell_color_orange = "\033[93m" @@ -196,7 +194,10 @@ def fetch_sdkconfig_files(platform_targets): def validate_environment(): if os.environ.get("IDF_PATH") is None: - exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") + if sys.platform == "win32": + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via %IDF_PATH%\\export.ps1") + else: + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") if not os.path.exists("manifest.properties"): exit_with_error("manifest.properties not found") if use_local_sdk == False and os.environ.get("TACTILITY_SDK_PATH") is not None: @@ -336,15 +337,24 @@ def build_all(version, platforms, skip_build): def wait_for_process(process): buffer = [] - os.set_blocking(process.stdout.fileno(), False) + if sys.platform != "win32": + os.set_blocking(process.stdout.fileno(), False) while process.poll() is None: while True: line = process.stdout.readline() - decoded_line = line.decode("UTF-8") - if decoded_line != "": - buffer.append(decoded_line) + if line: + decoded_line = line.decode("UTF-8") + if decoded_line != "": + buffer.append(decoded_line) + else: + break else: break + # Read any remaining output + for line in process.stdout: + decoded_line = line.decode("UTF-8") + if decoded_line: + buffer.append(decoded_line) return buffer # The first build must call "idf.py build" and consecutive builds must call "idf.py elf" as it finishes faster. @@ -356,7 +366,7 @@ def build_first(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") elf_path = find_elf_file(platform) # Remove previous elf file: re-creation of the file is used to measure if the build succeeded, # as the actual build job will always fail due to technical issues with the elf cmake script @@ -367,7 +377,8 @@ def build_first(version, platform, skip_build): print(f"Building first {platform} build") cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) # The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case if process.returncode == 0: @@ -389,12 +400,13 @@ def build_consecutively(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") if skip_build: return True cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) if process.returncode == 0: print_status_success(f"Building {platform} ELF") diff --git a/Apps/HelloWorld/tactility.py b/Apps/HelloWorld/tactility.py index 959146f..f80241c 100644 --- a/Apps/HelloWorld/tactility.py +++ b/Apps/HelloWorld/tactility.py @@ -10,8 +10,6 @@ import zipfile import requests import tarfile -import shutil -import configparser ttbuild_path = ".tactility" ttbuild_version = "2.4.0" @@ -23,12 +21,12 @@ local_base_path = None if sys.platform == "win32": - shell_color_red = "" - shell_color_orange = "" - shell_color_green = "" - shell_color_purple = "" - shell_color_cyan = "" - shell_color_reset = "" + shell_color_red = "\033[91m" + shell_color_orange = "\033[93m" + shell_color_green = "\033[32m" + shell_color_purple = "\033[35m" + shell_color_cyan = "\033[36m" + shell_color_reset = "\033[m" else: shell_color_red = "\033[91m" shell_color_orange = "\033[93m" @@ -196,7 +194,10 @@ def fetch_sdkconfig_files(platform_targets): def validate_environment(): if os.environ.get("IDF_PATH") is None: - exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") + if sys.platform == "win32": + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via %IDF_PATH%\\export.ps1") + else: + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") if not os.path.exists("manifest.properties"): exit_with_error("manifest.properties not found") if use_local_sdk == False and os.environ.get("TACTILITY_SDK_PATH") is not None: @@ -336,15 +337,24 @@ def build_all(version, platforms, skip_build): def wait_for_process(process): buffer = [] - os.set_blocking(process.stdout.fileno(), False) + if sys.platform != "win32": + os.set_blocking(process.stdout.fileno(), False) while process.poll() is None: while True: line = process.stdout.readline() - decoded_line = line.decode("UTF-8") - if decoded_line != "": - buffer.append(decoded_line) + if line: + decoded_line = line.decode("UTF-8") + if decoded_line != "": + buffer.append(decoded_line) + else: + break else: break + # Read any remaining output + for line in process.stdout: + decoded_line = line.decode("UTF-8") + if decoded_line: + buffer.append(decoded_line) return buffer # The first build must call "idf.py build" and consecutive builds must call "idf.py elf" as it finishes faster. @@ -356,7 +366,7 @@ def build_first(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") elf_path = find_elf_file(platform) # Remove previous elf file: re-creation of the file is used to measure if the build succeeded, # as the actual build job will always fail due to technical issues with the elf cmake script @@ -367,7 +377,8 @@ def build_first(version, platform, skip_build): print(f"Building first {platform} build") cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) # The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case if process.returncode == 0: @@ -389,12 +400,13 @@ def build_consecutively(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") if skip_build: return True cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) if process.returncode == 0: print_status_success(f"Building {platform} ELF") diff --git a/Apps/SerialConsole/tactility.py b/Apps/SerialConsole/tactility.py index 959146f..f80241c 100644 --- a/Apps/SerialConsole/tactility.py +++ b/Apps/SerialConsole/tactility.py @@ -10,8 +10,6 @@ import zipfile import requests import tarfile -import shutil -import configparser ttbuild_path = ".tactility" ttbuild_version = "2.4.0" @@ -23,12 +21,12 @@ local_base_path = None if sys.platform == "win32": - shell_color_red = "" - shell_color_orange = "" - shell_color_green = "" - shell_color_purple = "" - shell_color_cyan = "" - shell_color_reset = "" + shell_color_red = "\033[91m" + shell_color_orange = "\033[93m" + shell_color_green = "\033[32m" + shell_color_purple = "\033[35m" + shell_color_cyan = "\033[36m" + shell_color_reset = "\033[m" else: shell_color_red = "\033[91m" shell_color_orange = "\033[93m" @@ -196,7 +194,10 @@ def fetch_sdkconfig_files(platform_targets): def validate_environment(): if os.environ.get("IDF_PATH") is None: - exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") + if sys.platform == "win32": + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via %IDF_PATH%\\export.ps1") + else: + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") if not os.path.exists("manifest.properties"): exit_with_error("manifest.properties not found") if use_local_sdk == False and os.environ.get("TACTILITY_SDK_PATH") is not None: @@ -336,15 +337,24 @@ def build_all(version, platforms, skip_build): def wait_for_process(process): buffer = [] - os.set_blocking(process.stdout.fileno(), False) + if sys.platform != "win32": + os.set_blocking(process.stdout.fileno(), False) while process.poll() is None: while True: line = process.stdout.readline() - decoded_line = line.decode("UTF-8") - if decoded_line != "": - buffer.append(decoded_line) + if line: + decoded_line = line.decode("UTF-8") + if decoded_line != "": + buffer.append(decoded_line) + else: + break else: break + # Read any remaining output + for line in process.stdout: + decoded_line = line.decode("UTF-8") + if decoded_line: + buffer.append(decoded_line) return buffer # The first build must call "idf.py build" and consecutive builds must call "idf.py elf" as it finishes faster. @@ -356,7 +366,7 @@ def build_first(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") elf_path = find_elf_file(platform) # Remove previous elf file: re-creation of the file is used to measure if the build succeeded, # as the actual build job will always fail due to technical issues with the elf cmake script @@ -367,7 +377,8 @@ def build_first(version, platform, skip_build): print(f"Building first {platform} build") cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) # The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case if process.returncode == 0: @@ -389,12 +400,13 @@ def build_consecutively(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - os.system(f"cp {sdkconfig_path} sdkconfig") + shutil.copy(sdkconfig_path, "sdkconfig") if skip_build: return True cmake_path = get_cmake_path(platform) print_status_busy(f"Building {platform} ELF") - with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process: + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) if process.returncode == 0: print_status_success(f"Building {platform} ELF") diff --git a/Apps/TwoEleven/CMakeLists.txt b/Apps/TwoEleven/CMakeLists.txt new file mode 100644 index 0000000..7dda003 --- /dev/null +++ b/Apps/TwoEleven/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.20) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +if (DEFINED ENV{TACTILITY_SDK_PATH}) + set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) +else() + set(TACTILITY_SDK_PATH "../../release/TactilitySDK") + message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +endif() + +include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") +set(EXTRA_COMPONENT_DIRS ${TACTILITY_SDK_PATH}) + +project(TwoEleven) +tactility_project(TwoEleven) diff --git a/Apps/TwoEleven/README.md b/Apps/TwoEleven/README.md new file mode 100644 index 0000000..ebc2980 --- /dev/null +++ b/Apps/TwoEleven/README.md @@ -0,0 +1,57 @@ +# Two Eleven (2048 Game) + +A customizable 2048 sliding tile puzzle game for Tactility. +Named Two Eleven because numbers can be a pain in the butt with file names and 2^11=2048. + +## Overview + +TwoEleven is a faithful implementation of the popular 2048 game, where you slide numbered tiles to combine them and reach the 2048 tile. Unlike the standard 4x4 grid, this version allows you to choose grid sizes from 3x3 to 6x6, providing varying levels of challenge. + +## Features + +- **Configurable Grid Sizes**: Choose from 3x3 (easy), 4x4 (classic), 5x5 (moderate), or 6x6 (expert) grids. +- **Intuitive Controls**: Swipe gestures on touchscreens or use arrow keys for keyboard input. +- **Visual Feedback**: Color-coded tiles with smooth animations. +- **Score Tracking**: Real-time score display with win/lose detection. +- **Responsive UI**: Optimized for small screens with clean, modern design. +- **Thread-Safe**: Proper handling of UI updates to prevent crashes. + +## Screenshots + +Screenshots taken directly from my Lilygo T-Deck Plus. +Which is also the only device it has been tested on so far. + +![alt text](images/3x3.png) ![alt text](images/4x4.png) ![alt text](images/5x5.png) +![alt text](images/6x6.png) ![alt text](images/selection.png) + +## Requirements + +- Tactility - obviously... +- Touchscreen or keyboard input + +## Usage + +1. Launch the TwoEleven app. +2. Select your preferred grid size (3x3 to 6x6). +3. Swipe tiles in any direction to move and combine them. +4. Reach the 2048 tile to win, or get stuck to lose. +5. Press the refresh button to restart with the same grid size. + +## Controls + +- **Touchscreen**: Swipe up, down, left, or right to move tiles. +- **Keyboard**: Use arrow keys (↑, ↓, ←, →) for movement. +- **New Game**: Press the "New" button to reset the board. + +## Game Rules + +- Start with random tiles (2 or 4). +- Slide tiles in four directions. +- Tiles with the same number merge when they collide. +- New tiles appear after each move. +- Game ends when you reach 2048 (win) or no moves are possible (lose). + +## TODO + +- Maybe trackball one day? +- Maybe other keys rather being limited to arrow directions. (why so limited lvgl?) diff --git a/Apps/TwoEleven/images/3x3.png b/Apps/TwoEleven/images/3x3.png new file mode 100644 index 0000000000000000000000000000000000000000..eeb5de7dde2331e662be2abd8f97c880cf48d130 GIT binary patch literal 3990 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 literal 0 HcmV?d00001 diff --git a/Apps/TwoEleven/images/4x4.png b/Apps/TwoEleven/images/4x4.png new file mode 100644 index 0000000000000000000000000000000000000000..5d592c2c694dd13d4043c86bfbf1ca5d615de79d GIT binary patch literal 3679 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 P|4=|r%Sf|Y-685(LH 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 literal 0 HcmV?d00001 diff --git a/Apps/TwoEleven/images/selection.png b/Apps/TwoEleven/images/selection.png new file mode 100644 index 0000000000000000000000000000000000000000..26881bbc05e265f48b8057435da9e12fe020b627 GIT binary patch literal 3016 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 literal 0 HcmV?d00001 diff --git a/Apps/TwoEleven/main/CMakeLists.txt b/Apps/TwoEleven/main/CMakeLists.txt new file mode 100644 index 0000000..08cb55b --- /dev/null +++ b/Apps/TwoEleven/main/CMakeLists.txt @@ -0,0 +1,15 @@ +file(GLOB_RECURSE SOURCE_FILES + Source/*.c* + # Library source files must be included directly, + # because all regular dependencies get stripped by elf_loader's cmake script + ../../../Libraries/Str/Source/*.c** +) + +idf_component_register( + SRCS ${SOURCE_FILES} + # Library headers must be included directly, + # because all regular dependencies get stripped by elf_loader's cmake script + INCLUDE_DIRS ../../../Libraries/Str/Include + INCLUDE_DIRS ../../../Libraries/TactilityCpp/Include + REQUIRES TactilitySDK +) \ No newline at end of file diff --git a/Apps/TwoEleven/main/Source/TwoEleven.cpp b/Apps/TwoEleven/main/Source/TwoEleven.cpp new file mode 100644 index 0000000..0f0a8ab --- /dev/null +++ b/Apps/TwoEleven/main/Source/TwoEleven.cpp @@ -0,0 +1,183 @@ +#include "TwoEleven.h" + +#include +#include +#include + +constexpr auto* TAG = "TwoEleven"; + +static lv_obj_t* scoreLabel = nullptr; +static lv_obj_t* scoreWrapper = nullptr; +static lv_obj_t* toolbar = nullptr; +static lv_obj_t* mainWrapper = nullptr; +static lv_obj_t* newGameWrapper = nullptr; +static lv_obj_t* gameObject = nullptr; + +static uint16_t selectedSize = 4; + +void TwoEleven::twoElevenEventCb(lv_event_t* e) { + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t* obj_2048 = lv_event_get_target_obj(e); + lv_obj_t* scoreLabel = (lv_obj_t *)lv_event_get_user_data(e); + + const char* alertDialogLabels[] = { "OK" }; + + if (code == LV_EVENT_VALUE_CHANGED) { + if (twoeleven_get_best_tile(obj_2048) >= 2048) { + char message[64]; + sprintf(message, "YOU WIN!\n\nSCORE: %d", twoeleven_get_score(obj_2048)); + tt_app_alertdialog_start("YOU WIN!", message, alertDialogLabels, 1); + } else if (twoeleven_get_status(obj_2048)) { + char message[64]; + sprintf(message, "GAME OVER!\n\nSCORE: %d", twoeleven_get_score(obj_2048)); + tt_app_alertdialog_start("GAME OVER!", message, alertDialogLabels, 1); + } else { + lv_label_set_text_fmt(scoreLabel, "SCORE: %d", twoeleven_get_score(obj_2048)); + } + } +} + +void TwoEleven::newGameBtnEvent(lv_event_t* e) { + lv_obj_t* obj_2048 = (lv_obj_t *)lv_event_get_user_data(e); + twoeleven_set_new_game(obj_2048); +} + +void TwoEleven::create_game(lv_obj_t* parent, uint16_t size, lv_obj_t* toolbar) { + lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + + //game... + gameObject = twoeleven_create(parent, size); + lv_obj_set_style_text_font(gameObject, lv_font_get_default(), 0); + lv_obj_set_size(gameObject, LV_PCT(100), LV_PCT(100)); + lv_obj_set_flex_grow(gameObject, 1); + + scoreWrapper = lv_obj_create(toolbar); + lv_obj_set_size(scoreWrapper, LV_SIZE_CONTENT, LV_PCT(100)); + lv_obj_set_style_pad_top(scoreWrapper, 4, LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(scoreWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(scoreWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(scoreWrapper, 10, LV_STATE_DEFAULT); + lv_obj_set_style_pad_row(scoreWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_pad_column(scoreWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(scoreWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(scoreWrapper, 0, LV_STATE_DEFAULT); + lv_obj_remove_flag(scoreWrapper, LV_OBJ_FLAG_SCROLLABLE); + + //toolbar new score + scoreLabel = lv_label_create(scoreWrapper); + lv_label_set_text_fmt(scoreLabel, "SCORE: %d", twoeleven_get_score(gameObject)); + lv_obj_set_style_text_align(scoreLabel, LV_TEXT_ALIGN_LEFT, LV_STATE_DEFAULT); + lv_obj_align(scoreLabel, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_size(scoreLabel, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_text_font(scoreLabel, lv_font_get_default(), 0); + lv_obj_set_style_text_color(scoreLabel, lv_palette_main(LV_PALETTE_AMBER), LV_PART_MAIN); + lv_obj_add_event_cb(gameObject, twoElevenEventCb, LV_EVENT_ALL, scoreLabel); + + newGameWrapper = lv_obj_create(toolbar); + lv_obj_set_width(newGameWrapper, LV_SIZE_CONTENT); + lv_obj_set_flex_flow(newGameWrapper, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(newGameWrapper, 2, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(newGameWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(newGameWrapper, 0, LV_STATE_DEFAULT); + + //toolbar reset + lv_obj_t* newGameBtn = lv_btn_create(newGameWrapper); + lv_obj_set_size(newGameBtn, 34, 34); + lv_obj_set_style_pad_all(newGameBtn, 0, LV_STATE_DEFAULT); + lv_obj_align(newGameBtn, LV_ALIGN_CENTER, 0, 0); + lv_obj_add_event_cb(newGameBtn, newGameBtnEvent, LV_EVENT_CLICKED, gameObject); + + lv_obj_t* btnLabel = lv_image_create(newGameBtn); + lv_image_set_src(btnLabel, LV_SYMBOL_REFRESH); + lv_obj_align(btnLabel, LV_ALIGN_CENTER, 0, 0); +} + +void TwoEleven::create_selection(lv_obj_t* parent, lv_obj_t* toolbar) { + lv_obj_t* selection = lv_obj_create(parent); + lv_obj_set_size(selection, LV_PCT(100), LV_PCT(100)); + lv_obj_set_flex_flow(selection, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(selection, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_remove_flag(selection, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_pad_all(selection, 2, LV_PART_MAIN); + lv_obj_set_style_border_width(selection, 0, LV_STATE_DEFAULT); + + lv_obj_t* titleWrapper = lv_obj_create(selection); + lv_obj_set_size(titleWrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(titleWrapper, 2, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(titleWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(titleWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_flex_flow(titleWrapper, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(titleWrapper, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_remove_flag(titleWrapper, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* titleLabel = lv_label_create(titleWrapper); + lv_label_set_text(titleLabel, "Select Matrix Size"); + lv_obj_align(titleLabel, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_size(titleLabel, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + + lv_obj_t* buttonContainer = lv_obj_create(selection); + lv_obj_set_flex_flow(buttonContainer, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(buttonContainer, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_remove_flag(buttonContainer, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(buttonContainer, LV_PCT(100), LV_PCT(100) - 20); + lv_obj_set_style_bg_opa(buttonContainer, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(buttonContainer, 0, LV_STATE_DEFAULT); + + for(int s = 3; s <= 6; s++) { + lv_obj_t* btn = lv_btn_create(buttonContainer); + lv_obj_set_size(btn, 60, 50); + lv_obj_t* lbl = lv_label_create(btn); + char txt[10]; + sprintf(txt, "%dx%d", s, s); + lv_label_set_text(lbl, txt); + lv_obj_center(lbl); + lv_obj_add_event_cb(btn, size_select_cb, LV_EVENT_CLICKED, (void*)s); + } +} + +void TwoEleven::size_select_cb(lv_event_t* e) { + selectedSize = (uint16_t)(uintptr_t)lv_event_get_user_data(e); + lv_obj_t* selection = lv_obj_get_parent(lv_event_get_target_obj(e)); + lv_obj_t* selectionWrapper = lv_obj_get_parent(selection); + lv_obj_clean(selectionWrapper); + scoreLabel = nullptr; + scoreWrapper = nullptr; + newGameWrapper = nullptr; + gameObject = nullptr; + create_game(selectionWrapper, selectedSize, toolbar); +} + +void TwoEleven::onShow(AppHandle appHandle, lv_obj_t* parent) { + lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + + toolbar = tt_lvgl_toolbar_create_for_app(parent, appHandle); + lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); + + mainWrapper = lv_obj_create(parent); + lv_obj_set_width(mainWrapper, LV_PCT(100)); + lv_obj_set_height(mainWrapper, LV_PCT(100)); + lv_obj_set_flex_grow(mainWrapper, 1); + lv_obj_set_style_pad_all(mainWrapper, 2, LV_PART_MAIN); + lv_obj_set_style_pad_row(mainWrapper, 2, LV_PART_MAIN); + lv_obj_set_style_pad_column(mainWrapper, 2, LV_PART_MAIN); + lv_obj_set_style_border_width(mainWrapper, 0, LV_PART_MAIN); + lv_obj_remove_flag(mainWrapper, LV_OBJ_FLAG_SCROLLABLE); + + create_selection(mainWrapper, toolbar); +} + +void TwoEleven::onResult(AppHandle appHandle, void* _Nullable data, AppLaunchId launchId, AppResult result, BundleHandle resultData) { + if (result == APP_RESULT_OK && resultData != nullptr) { + // Dialog closed with OK, go back to selection + tt_lvgl_lock(TT_LVGL_DEFAULT_LOCK_TIME); + lv_obj_clean(mainWrapper); + scoreLabel = nullptr; + scoreWrapper = nullptr; + newGameWrapper = nullptr; + gameObject = nullptr; + create_selection(mainWrapper, toolbar); + tt_lvgl_unlock(); + } +} diff --git a/Apps/TwoEleven/main/Source/TwoEleven.h b/Apps/TwoEleven/main/Source/TwoEleven.h new file mode 100644 index 0000000..c9a0494 --- /dev/null +++ b/Apps/TwoEleven/main/Source/TwoEleven.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include +#include + +#include "TwoElevenUi.h" +#include "TwoElevenLogic.h" +#include "TwoElevenHelpers.h" + +class TwoEleven final : public App { + + static void twoElevenEventCb(lv_event_t* e); + static void newGameBtnEvent(lv_event_t* e); + static void create_game(lv_obj_t* parent, uint16_t size, lv_obj_t* toolbar); + static void create_selection(lv_obj_t* parent, lv_obj_t* toolbar); + static void size_select_cb(lv_event_t* e); + +public: + + void onShow(AppHandle context, lv_obj_t* parent) override; + void onResult(AppHandle appHandle, void* _Nullable data, AppLaunchId launchId, AppResult result, BundleHandle resultData) override; +}; \ No newline at end of file diff --git a/Apps/TwoEleven/main/Source/TwoElevenHelpers.c b/Apps/TwoEleven/main/Source/TwoElevenHelpers.c new file mode 100644 index 0000000..7c3c58c --- /dev/null +++ b/Apps/TwoEleven/main/Source/TwoElevenHelpers.c @@ -0,0 +1,130 @@ +#include "TwoElevenHelpers.h" +#include +#include + +/** + * @brief Get the color for a given tile value + */ +lv_color_t get_num_color(uint16_t num) +{ + switch (num) { + case 0: + return ELEVENTWO_NUMBER_EMPTY_COLOR; + case 1: + return ELEVENTWO_NUMBER_EMPTY_COLOR; + case 2: + return ELEVENTWO_NUMBER_2_COLOR; + case 4: + return ELEVENTWO_NUMBER_4_COLOR; + case 8: + return ELEVENTWO_NUMBER_8_COLOR; + case 16: + return ELEVENTWO_NUMBER_16_COLOR; + case 32: + return ELEVENTWO_NUMBER_32_COLOR; + case 64: + return ELEVENTWO_NUMBER_64_COLOR; + case 128: + return ELEVENTWO_NUMBER_128_COLOR; + case 256: + return ELEVENTWO_NUMBER_256_COLOR; + case 512: + return ELEVENTWO_NUMBER_512_COLOR; + case 1024: + return ELEVENTWO_NUMBER_1024_COLOR; + case 2048: + return ELEVENTWO_NUMBER_2048_COLOR; + default: + return ELEVENTWO_NUMBER_2048_COLOR; + } +} + +/** + * @brief Count empty cells in the matrix + */ +uint8_t count_empty(uint16_t matrix_size, const uint16_t **matrix) { + uint8_t count = 0; + for (uint8_t x = 0; x < matrix_size; x++) { + for (uint8_t y = 0; y < matrix_size; y++) { + if (matrix[x][y] == 0) count++; + } + } + return count; +} + +/** + * @brief Find if any adjacent pairs exist (for move possibility) + */ +bool find_pair_down(uint16_t matrix_size, const uint16_t **matrix) { + for (uint8_t x = 0; x < matrix_size; x++) { + for (uint8_t y = 0; y < matrix_size - 1; y++) { + if (matrix[x][y] == matrix[x][y+1]) return true; + } + } + return false; +} + +/** + * @brief Rotate the matrix 90 degrees clockwise (in-place) + * Used for move logic + */ +void rotate_matrix(uint16_t matrix_size, uint16_t **matrix) { + uint8_t n = matrix_size; + for (uint8_t i = 0; i < n / 2; i++) { + for (uint8_t j = i; j < n - i - 1; j++) { + uint16_t tmp = matrix[i][j]; + matrix[i][j] = matrix[n - j - 1][i]; + matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1]; + matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1]; + matrix[j][n - i - 1] = tmp; + } + } +} + +/** + * @brief Slide and merge a single row/column + */ +bool slide_array(uint16_t * score, uint16_t matrix_size, uint16_t *array) { + bool success = false; + uint8_t stop = 0; + for (uint8_t x = 0; x < matrix_size; x++) { + if (array[x] != 0) { + uint8_t t = x; + while (t > stop && array[t-1] == 0) { + array[t-1] = array[t]; + array[t] = 0; + t--; + success = true; + } + if (t > stop && array[t-1] == array[t]) { + array[t-1]++; + *score += (1U << array[t-1]); + array[t] = 0; + stop = t; + success = true; + } + } + } + return success; +} + +/** + * @brief Update the button map with current matrix values + */ +void update_btnm_map(uint16_t matrix_size, char ** btnm_map, const uint16_t **matrix) +{ + uint8_t index = 0; + for (uint8_t x = 0; x < matrix_size; x++) { + for (uint8_t y = 0; y < matrix_size; y++) { + if (((index + 1) % (matrix_size + 1)) == 0) { + index++; + } + if (matrix[x][y] != 0) { + snprintf(btnm_map[index], 16, "%d", (1 << matrix[x][y])); + } else { + strcpy(btnm_map[index], " "); + } + index++; + } + } +} diff --git a/Apps/TwoEleven/main/Source/TwoElevenHelpers.h b/Apps/TwoEleven/main/Source/TwoElevenHelpers.h new file mode 100644 index 0000000..f36a2a8 --- /dev/null +++ b/Apps/TwoEleven/main/Source/TwoElevenHelpers.h @@ -0,0 +1,89 @@ +#ifndef TWOELEVEN_HELPERS_H +#define TWOELEVEN_HELPERS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lvgl.h" + +/********************* + * DEFINES + *********************/ + +#define ELEVENTWO_BG_COLOR lv_color_hex(0xb3a397) +#define ELEVENTWO_TEXT_BLACK_COLOR lv_color_hex(0x6c635b) +#define ELEVENTWO_TEXT_WHITE_COLOR lv_color_hex(0xf8f5f0) +#define ELEVENTWO_NUMBER_EMPTY_COLOR lv_color_hex(0xc7b9ac) +#define ELEVENTWO_NUMBER_2_COLOR lv_color_hex(0xeee4da) +#define ELEVENTWO_NUMBER_4_COLOR lv_color_hex(0xede0c8) +#define ELEVENTWO_NUMBER_8_COLOR lv_color_hex(0xf2b179) +#define ELEVENTWO_NUMBER_16_COLOR lv_color_hex(0xf59563) +#define ELEVENTWO_NUMBER_32_COLOR lv_color_hex(0xf67c5f) +#define ELEVENTWO_NUMBER_64_COLOR lv_color_hex(0xf75f3b) +#define ELEVENTWO_NUMBER_128_COLOR lv_color_hex(0xedcf72) +#define ELEVENTWO_NUMBER_256_COLOR lv_color_hex(0xedcc61) +#define ELEVENTWO_NUMBER_512_COLOR lv_color_hex(0xedc850) +#define ELEVENTWO_NUMBER_1024_COLOR lv_color_hex(0xedc53f) +#define ELEVENTWO_NUMBER_2048_COLOR lv_color_hex(0xedc22e) + +/********************** + * TYPEDEFS + **********************/ +/** + * @brief Struct for 2048 game state + */ +struct twoeleven_t { + lv_obj_t * btnm; + uint16_t score; + uint16_t map_count; + uint16_t matrix_size; + uint16_t **matrix; + char ** btnm_map; + bool game_over; +}; + +typedef struct twoeleven_t twoeleven_t; + +/*********************** + * FUNCTION PROTOTYPES + **********************/ +/** + * @brief Get the color for a given tile value + */ +lv_color_t get_num_color(uint16_t num); + +/** + * @brief Count empty cells in the matrix + */ +uint8_t count_empty(uint16_t matrix_size, const uint16_t **matrix); + +/** + * @brief Find if any adjacent pairs exist (for move possibility) + */ +bool find_pair_down(uint16_t matrix_size, const uint16_t **matrix); + +/** + * @brief Rotate the matrix 90 degrees clockwise (in-place) + * Used for move logic + */ +void rotate_matrix(uint16_t matrix_size, uint16_t **matrix); + +/** + * @brief Slide and merge a single row/column + */ +bool slide_array(uint16_t * score, uint16_t matrix_size, uint16_t *array); + +/** + * @brief Update the button map with current matrix values + */ +void update_btnm_map(uint16_t matrix_size, char ** btnm_map, const uint16_t **matrix); + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*TWOELEVEN_HELPERS_H*/ \ No newline at end of file diff --git a/Apps/TwoEleven/main/Source/TwoElevenLogic.c b/Apps/TwoEleven/main/Source/TwoElevenLogic.c new file mode 100644 index 0000000..a3001be --- /dev/null +++ b/Apps/TwoEleven/main/Source/TwoElevenLogic.c @@ -0,0 +1,162 @@ +#include "TwoElevenLogic.h" +#include "TwoElevenHelpers.h" +#include +#include +#include + +/** + * @brief Initialize the matrix with two random tiles + */ +void init_matrix_num(uint16_t matrix_size, uint16_t **matrix) +{ + for (uint8_t x = 0; x < matrix_size; x++) { + for (uint8_t y = 0; y < matrix_size; y++) { + matrix[x][y] = 0; + } + } + // Add two random tiles + add_random(matrix_size, matrix); + add_random(matrix_size, matrix); +} + +/** + * @brief Add a random tile (2 or 4) to the matrix + */ +void add_random(uint16_t matrix_size, uint16_t **matrix) +{ + static bool initialized = false; + if (!initialized) { + srand((unsigned int)time(NULL)); + initialized = true; + } + uint16_t empty[matrix_size * matrix_size][2]; + uint16_t len = 0; + for (uint8_t x = 0; x < matrix_size; x++) { + for (uint8_t y = 0; y < matrix_size; y++) { + if (matrix[x][y] == 0) { + empty[len][0] = x; + empty[len][1] = y; + len++; + } + } + } + if (len > 0) { + uint16_t r = rand() % len; + uint8_t x = empty[r][0]; + uint8_t y = empty[r][1]; + uint16_t n = (rand() % 10 == 0) ? 2 : 1; // 10% chance for 4, else 2 + matrix[x][y] = n; + } +} + +/** + * @brief Move up (or left/right/down via rotation) + */ +bool move_up(uint16_t * score, uint16_t matrix_size, uint16_t **matrix) { + bool success = false; + for (uint8_t x = 0; x < matrix_size; x++) { + success |= slide_array(score, matrix_size, matrix[x]); + } + return success; +} + +bool move_down(uint16_t * score, uint16_t matrix_size, uint16_t **matrix) { + rotate_matrix(matrix_size, matrix); + rotate_matrix(matrix_size, matrix); + bool success = move_up(score, matrix_size, matrix); + rotate_matrix(matrix_size, matrix); + rotate_matrix(matrix_size, matrix); + return success; +} + +bool move_left(uint16_t * score, uint16_t matrix_size, uint16_t **matrix) { + rotate_matrix(matrix_size, matrix); + bool success = move_up(score, matrix_size, matrix); + rotate_matrix(matrix_size, matrix); + rotate_matrix(matrix_size, matrix); + rotate_matrix(matrix_size, matrix); + return success; +} + +bool move_right(uint16_t * score, uint16_t matrix_size, uint16_t **matrix) { + rotate_matrix(matrix_size, matrix); + rotate_matrix(matrix_size, matrix); + rotate_matrix(matrix_size, matrix); + bool success = move_up(score, matrix_size, matrix); + rotate_matrix(matrix_size, matrix); + return success; +} + +/** + * @brief Check if the game is over (no moves left) + * Does not mutate the original matrix + */ +bool game_over(uint16_t matrix_size, const uint16_t **matrix) { + if (count_empty(matrix_size, matrix) > 0) return false; + if (find_pair_down(matrix_size, matrix)) return false; + // Check for possible moves in rotated matrices + uint16_t **temp = lv_malloc(matrix_size * sizeof(uint16_t*)); + for (uint16_t i = 0; i < matrix_size; i++) { + temp[i] = lv_malloc(matrix_size * sizeof(uint16_t)); + memcpy(temp[i], matrix[i], matrix_size * sizeof(uint16_t)); + } + rotate_matrix(matrix_size, temp); + if (find_pair_down(matrix_size, (const uint16_t **)temp)) { + for (uint16_t i = 0; i < matrix_size; i++) lv_free(temp[i]); + lv_free(temp); + return false; + } + rotate_matrix(matrix_size, temp); + if (find_pair_down(matrix_size, (const uint16_t **)temp)) { + for (uint16_t i = 0; i < matrix_size; i++) lv_free(temp[i]); + lv_free(temp); + return false; + } + rotate_matrix(matrix_size, temp); + if (find_pair_down(matrix_size, (const uint16_t **)temp)) { + for (uint16_t i = 0; i < matrix_size; i++) lv_free(temp[i]); + lv_free(temp); + return false; + } + for (uint16_t i = 0; i < matrix_size; i++) lv_free(temp[i]); + lv_free(temp); + return true; +} + +/** + * @brief Get the current score + */ +uint16_t twoeleven_get_score(lv_obj_t * obj) +{ + const twoeleven_t * game_2048 = (const twoeleven_t *)lv_obj_get_user_data(obj); + if (!game_2048) return 0; + return game_2048->score; +} + +/** + * @brief Get the game over status + */ +bool twoeleven_get_status(lv_obj_t * obj) +{ + const twoeleven_t * game_2048 = (const twoeleven_t *)lv_obj_get_user_data(obj); + if (!game_2048) return true; + return game_2048->game_over; +} + +/** + * @brief Get the best tile value + */ +uint16_t twoeleven_get_best_tile(lv_obj_t * obj) +{ + const twoeleven_t * game_2048 = (const twoeleven_t *)lv_obj_get_user_data(obj); + if (!game_2048) return 0; + uint16_t best_tile = 0; + for (uint8_t x = 0; x < game_2048->matrix_size; x++) { + for (uint8_t y = 0; y < game_2048->matrix_size; y++) { + if (best_tile < game_2048->matrix[x][y]) { + best_tile = game_2048->matrix[x][y]; + } + } + } + return (uint16_t)(1 << best_tile); +} diff --git a/Apps/TwoEleven/main/Source/TwoElevenLogic.h b/Apps/TwoEleven/main/Source/TwoElevenLogic.h new file mode 100644 index 0000000..1c01873 --- /dev/null +++ b/Apps/TwoEleven/main/Source/TwoElevenLogic.h @@ -0,0 +1,71 @@ +#ifndef TWOELEVEN_LOGIC_H +#define TWOELEVEN_LOGIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "TwoElevenHelpers.h" + +/*********************** + * FUNCTION PROTOTYPES + **********************/ +/** + * @brief Initialize the matrix with two random tiles + */ +void init_matrix_num(uint16_t matrix_size, uint16_t **matrix); + +/** + * @brief Add a random tile (2 or 4) to the matrix + */ +void add_random(uint16_t matrix_size, uint16_t **matrix); + +/** + * @brief Move up + */ +bool move_up(uint16_t * score, uint16_t matrix_size, uint16_t **matrix); + +/** + * @brief Move down + */ +bool move_down(uint16_t * score, uint16_t matrix_size, uint16_t **matrix); + +/** + * @brief Move left + */ +bool move_left(uint16_t * score, uint16_t matrix_size, uint16_t **matrix); + +/** + * @brief Move right + */ +bool move_right(uint16_t * score, uint16_t matrix_size, uint16_t **matrix); + +/** + * @brief Check if the game is over (no moves left) + * Does not mutate the original matrix + */ +bool game_over(uint16_t matrix_size, const uint16_t **matrix); + +/** + * @brief Get the current score + */ +uint16_t twoeleven_get_score(lv_obj_t * obj); + +/** + * @brief Get the game over status + */ +bool twoeleven_get_status(lv_obj_t * obj); + +/** + * @brief Get the best tile value + */ +uint16_t twoeleven_get_best_tile(lv_obj_t * obj); + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*TWOELEVEN_LOGIC_H*/ \ No newline at end of file diff --git a/Apps/TwoEleven/main/Source/TwoElevenUi.c b/Apps/TwoEleven/main/Source/TwoElevenUi.c new file mode 100644 index 0000000..1e0f4fa --- /dev/null +++ b/Apps/TwoEleven/main/Source/TwoElevenUi.c @@ -0,0 +1,226 @@ +#include "TwoElevenUi.h" +#include "TwoElevenLogic.h" +#include "TwoElevenHelpers.h" +#include +#include + +static void game_play_event(lv_event_t * e); +static void btnm_event_cb(lv_event_t * e); + +/** + * @brief Free all resources for the 2048 game object + */ +static void delete_event(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target_obj(e); + twoeleven_t * game_2048 = (twoeleven_t *)lv_obj_get_user_data(obj); + if (game_2048) { + for (uint16_t index = 0; index < game_2048->map_count; index++) { + if (game_2048->btnm_map[index]) { + lv_free(game_2048->btnm_map[index]); + game_2048->btnm_map[index] = NULL; + } + } + lv_free(game_2048->btnm_map); + // Free matrix + for (uint16_t i = 0; i < game_2048->matrix_size; i++) { + lv_free(game_2048->matrix[i]); + } + lv_free(game_2048->matrix); + lv_free(game_2048); + lv_obj_set_user_data(obj, NULL); + } +} + +/** + * @brief Create a new 2048 game object + */ +lv_obj_t * twoeleven_create(lv_obj_t * parent, uint16_t matrix_size) +{ + lv_obj_t * obj = lv_obj_create(parent); + twoeleven_t * game_2048 = (twoeleven_t *)lv_malloc(sizeof(twoeleven_t)); + if (!game_2048) return NULL; + lv_obj_set_user_data(obj, game_2048); + + game_2048->score = 0; + game_2048->game_over = false; + // Limit matrix size to 3x3 to 6x6, default to 4x4 if out of range + if (matrix_size < 3 || matrix_size > 6) { + matrix_size = 4; + } + game_2048->matrix_size = matrix_size; + game_2048->map_count = game_2048->matrix_size * game_2048->matrix_size + game_2048->matrix_size; + + // Allocate matrix + game_2048->matrix = lv_malloc(game_2048->matrix_size * sizeof(uint16_t*)); + for (uint16_t i = 0; i < game_2048->matrix_size; i++) { + game_2048->matrix[i] = lv_malloc(game_2048->matrix_size * sizeof(uint16_t)); + } + + // Allocate button map + game_2048->btnm_map = lv_malloc(game_2048->map_count * sizeof(char*)); + for (uint16_t index = 0; index < game_2048->map_count; index++) { + if (((index + 1) % (game_2048->matrix_size + 1)) == 0) { + game_2048->btnm_map[index] = (char *)lv_malloc(2); + if (!game_2048->btnm_map[index]) continue; + if ((index + 1) == game_2048->map_count) + strcpy(game_2048->btnm_map[index], ""); + else + strcpy(game_2048->btnm_map[index], "\n"); + } else { + game_2048->btnm_map[index] = (char *)lv_malloc(16); + if (!game_2048->btnm_map[index]) continue; + strcpy(game_2048->btnm_map[index], " "); + } + } + + init_matrix_num(game_2048->matrix_size, game_2048->matrix); + update_btnm_map(game_2048->matrix_size, game_2048->btnm_map, (const uint16_t **)game_2048->matrix); + + // Style initialization (unchanged) + lv_obj_set_style_outline_color(obj, lv_theme_get_color_primary(obj), LV_STATE_FOCUS_KEY); + lv_obj_set_style_outline_width(obj, lv_display_dpx(lv_obj_get_disp(obj), 2), LV_STATE_FOCUS_KEY); + lv_obj_set_style_outline_pad(obj, lv_display_dpx(lv_obj_get_disp(obj), 2), LV_STATE_FOCUS_KEY); + lv_obj_set_style_outline_opa(obj, LV_OPA_50, LV_STATE_FOCUS_KEY); + + // Create button matrix + game_2048->btnm = lv_btnmatrix_create(obj); + lv_obj_set_size(game_2048->btnm, LV_PCT(100), LV_PCT(100)); + lv_obj_center(game_2048->btnm); + lv_obj_set_style_pad_all(game_2048->btnm, 10, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(game_2048->btnm, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(game_2048->btnm, ELEVENTWO_BG_COLOR, LV_PART_MAIN); + lv_group_remove_obj(game_2048->btnm); + lv_obj_add_flag(game_2048->btnm, LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS); + lv_obj_remove_flag(game_2048->btnm, LV_OBJ_FLAG_GESTURE_BUBBLE); + + lv_btnmatrix_set_map(game_2048->btnm, (const char **)game_2048->btnm_map); + lv_btnmatrix_set_btn_ctrl_all(game_2048->btnm, LV_BTNMATRIX_CTRL_DISABLED); + + lv_obj_add_event_cb(game_2048->btnm, game_play_event, LV_EVENT_ALL, obj); + lv_obj_add_event_cb(game_2048->btnm, btnm_event_cb, LV_EVENT_DRAW_TASK_ADDED, NULL); + lv_obj_add_event_cb(obj, delete_event, LV_EVENT_DELETE, NULL); + + return obj; +} + +/** + * @brief Start a new game (reset state) + */ +void twoeleven_set_new_game(lv_obj_t * obj) +{ + twoeleven_t * game_2048 = (twoeleven_t *)lv_obj_get_user_data(obj); + if (!game_2048) return; + game_2048->score = 0; + game_2048->game_over = false; + game_2048->map_count = game_2048->matrix_size * game_2048->matrix_size + game_2048->matrix_size; + init_matrix_num(game_2048->matrix_size, game_2048->matrix); + update_btnm_map(game_2048->matrix_size, game_2048->btnm_map, (const uint16_t **)game_2048->matrix); + lv_btnmatrix_set_map(game_2048->btnm, (const char **)game_2048->btnm_map); + lv_obj_invalidate(game_2048->btnm); + lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL); +} + +/** + * @brief Event callback for game play (gesture/key) + */ +static void game_play_event(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_user_data(e); + twoeleven_t * game_2048 = (twoeleven_t *)lv_obj_get_user_data(obj); + bool success = false; + if (!game_2048) return; + + if (code == LV_EVENT_GESTURE) { + game_2048->game_over = game_over(game_2048->matrix_size, (const uint16_t **)game_2048->matrix); + if (!game_2048->game_over) { + lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_active()); + switch (dir) { + case LV_DIR_TOP: + success = move_left(&(game_2048->score), game_2048->matrix_size, game_2048->matrix); + break; + case LV_DIR_BOTTOM: + success = move_right(&(game_2048->score), game_2048->matrix_size, game_2048->matrix); + break; + case LV_DIR_LEFT: + success = move_up(&(game_2048->score), game_2048->matrix_size, game_2048->matrix); + break; + case LV_DIR_RIGHT: + success = move_down(&(game_2048->score), game_2048->matrix_size, game_2048->matrix); + break; + default: + break; + } + } else { + lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL); + return; + } + } else if (code == LV_EVENT_KEY) { + game_2048->game_over = game_over(game_2048->matrix_size, (const uint16_t **)game_2048->matrix); + if (!game_2048->game_over) { + switch (*((const uint8_t *) lv_event_get_param(e))) { + case LV_KEY_UP: + success = move_left(&(game_2048->score), game_2048->matrix_size, game_2048->matrix); + break; + case LV_KEY_DOWN: + success = move_right(&(game_2048->score), game_2048->matrix_size, game_2048->matrix); + break; + case LV_KEY_LEFT: + success = move_up(&(game_2048->score), game_2048->matrix_size, game_2048->matrix); + break; + case LV_KEY_RIGHT: + success = move_down(&(game_2048->score), game_2048->matrix_size, game_2048->matrix); + break; + default: + break; + } + } else { + lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL); + return; + } + } + + if (success) { + add_random(game_2048->matrix_size, game_2048->matrix); + update_btnm_map(game_2048->matrix_size, game_2048->btnm_map, (const uint16_t **)game_2048->matrix); + lv_btnmatrix_set_map(game_2048->btnm, (const char **)game_2048->btnm_map); + lv_obj_invalidate(game_2048->btnm); + lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL); + } +} + +/** + * @brief Event callback for button matrix drawing (coloring tiles) + */ +static void btnm_event_cb(lv_event_t * e) +{ + lv_obj_t * btnm = lv_event_get_target_obj(e); + lv_obj_t * parent = lv_obj_get_parent(btnm); + twoeleven_t * game_2048 = (twoeleven_t *)lv_obj_get_user_data(parent); + if (!game_2048) return; + + lv_draw_task_t * draw_task = lv_event_get_draw_task(e); + lv_draw_dsc_base_t * base_dsc = lv_draw_task_get_draw_dsc(draw_task); + + if (base_dsc->part == LV_PART_ITEMS) { + lv_draw_label_dsc_t * label_draw_dsc = lv_draw_task_get_label_dsc(draw_task); + lv_draw_fill_dsc_t * fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task); + + if ((int32_t)base_dsc->id1 >= 0) { + uint16_t x = (uint16_t)((base_dsc->id1) / game_2048->matrix_size); + uint16_t y = (uint16_t)((base_dsc->id1) % game_2048->matrix_size); + uint16_t num = (uint16_t)(1 << (game_2048->matrix[x][y])); + + if (fill_draw_dsc) { + fill_draw_dsc->radius = 3; + fill_draw_dsc->color = get_num_color(num); + } + if (label_draw_dsc) { + label_draw_dsc->color = (num < 8) ? ELEVENTWO_TEXT_BLACK_COLOR : ELEVENTWO_TEXT_WHITE_COLOR; + } + } else { + if (fill_draw_dsc) fill_draw_dsc->radius = 5; + } + } +} diff --git a/Apps/TwoEleven/main/Source/TwoElevenUi.h b/Apps/TwoEleven/main/Source/TwoElevenUi.h new file mode 100644 index 0000000..959d97f --- /dev/null +++ b/Apps/TwoEleven/main/Source/TwoElevenUi.h @@ -0,0 +1,35 @@ +#ifndef TWOELEVEN_UI_H +#define TWOELEVEN_UI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "TwoElevenHelpers.h" +#include "lvgl.h" + +/*********************** + * FUNCTION PROTOTYPES + **********************/ +/** + * @brief Create a new 2048 game object + * @param parent Parent LVGL object + * @param matrix_size Size of the game matrix (3 to 6, defaults to 4 if out of range) + * @return Pointer to the created LVGL object + */ +lv_obj_t * twoeleven_create(lv_obj_t * parent, uint16_t matrix_size); + +/** + * @brief Start a new game (reset state) + * @param obj 2048 game LVGL object + */ +void twoeleven_set_new_game(lv_obj_t * obj); + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*TWOELEVEN_UI_H*/ \ No newline at end of file diff --git a/Apps/TwoEleven/main/Source/main.cpp b/Apps/TwoEleven/main/Source/main.cpp new file mode 100644 index 0000000..12d95aa --- /dev/null +++ b/Apps/TwoEleven/main/Source/main.cpp @@ -0,0 +1,11 @@ +#include "TwoEleven.h" +#include + +extern "C" { + +int main(int argc, char* argv[]) { + registerApp(); + return 0; +} + +} diff --git a/Apps/TwoEleven/manifest.properties b/Apps/TwoEleven/manifest.properties new file mode 100644 index 0000000..b617ada --- /dev/null +++ b/Apps/TwoEleven/manifest.properties @@ -0,0 +1,10 @@ +[manifest] +version=0.1 +[target] +sdk=0.7.0-SNAPSHOT2 +platforms=esp32,esp32s3 +[app] +id=two.shadowtrance.twoeleven +versionName=0.2.0 +versionCode=2 +name=Two Eleven \ No newline at end of file diff --git a/Apps/TwoEleven/tactility.py b/Apps/TwoEleven/tactility.py new file mode 100644 index 0000000..f80241c --- /dev/null +++ b/Apps/TwoEleven/tactility.py @@ -0,0 +1,690 @@ +import configparser +import json +import os +import re +import shutil +import sys +import subprocess +import time +import urllib.request +import zipfile +import requests +import tarfile + +ttbuild_path = ".tactility" +ttbuild_version = "2.4.0" +ttbuild_cdn = "https://cdn.tactility.one" +ttbuild_sdk_json_validity = 3600 # seconds +ttport = 6666 +verbose = False +use_local_sdk = False +local_base_path = None + +if sys.platform == "win32": + shell_color_red = "\033[91m" + shell_color_orange = "\033[93m" + shell_color_green = "\033[32m" + shell_color_purple = "\033[35m" + shell_color_cyan = "\033[36m" + shell_color_reset = "\033[m" +else: + shell_color_red = "\033[91m" + shell_color_orange = "\033[93m" + shell_color_green = "\033[32m" + shell_color_purple = "\033[35m" + shell_color_cyan = "\033[36m" + shell_color_reset = "\033[m" + +def print_help(): + print("Usage: python tactility.py [action] [options]") + print("") + print("Actions:") + print(" build [esp32,esp32s3] Build the app. Optionally specify a platform.") + print(" esp32: ESP32") + print(" esp32s3: ESP32 S3") + print(" clean Clean the build folders") + print(" clearcache Clear the SDK cache") + print(" updateself Update this tool") + print(" run [ip] Run the application") + print(" install [ip] Install the application") + print(" uninstall [ip] Uninstall the application") + print(" bir [ip] [esp32,esp32s3] Build, install then run. Optionally specify a platform.") + print(" brrr [ip] [esp32,esp32s3] Functionally the same as \"bir\", but \"app goes brrr\" meme variant.") + print("") + print("Options:") + print(" --help Show this commandline info") + print(" --local-sdk Use SDK specified by environment variable TACTILITY_SDK_PATH with platform subfolders matching target platforms.") + print(" --skip-build Run everything except the idf.py/CMake commands") + print(" --verbose Show extra console output") + +# region Core + +def download_file(url, filepath): + global verbose + if verbose: + print(f"Downloading from {url} to {filepath}") + request = urllib.request.Request( + url, + data=None, + headers={ + "User-Agent": f"Tactility Build Tool {ttbuild_version}" + } + ) + try: + response = urllib.request.urlopen(request) + file = open(filepath, mode="wb") + file.write(response.read()) + file.close() + return True + except OSError as error: + if verbose: + print_error(f"Failed to fetch URL {url}\n{error}") + return False + +def print_warning(message): + print(f"{shell_color_orange}WARNING: {message}{shell_color_reset}") + +def print_error(message): + print(f"{shell_color_red}ERROR: {message}{shell_color_reset}") + +def print_status_busy(status): + sys.stdout.write(f"⌛ {status}\r") + +def print_status_success(status): + # Trailing spaces are to overwrite previously written characters by a potentially shorter print_status_busy() text + print(f"✅ {shell_color_green}{status}{shell_color_reset} ") + +def print_status_error(status): + # Trailing spaces are to overwrite previously written characters by a potentially shorter print_status_busy() text + print(f"❌ {shell_color_red}{status}{shell_color_reset} ") + +def exit_with_error(message): + print_error(message) + sys.exit(1) + +def get_url(ip, path): + return f"http://{ip}:{ttport}{path}" + +def read_properties_file(path): + config = configparser.RawConfigParser() + config.read(path) + return config + +#endregion Core + +#region SDK helpers + +def read_sdk_json(): + json_file_path = os.path.join(ttbuild_path, "sdk.json") + json_file = open(json_file_path) + return json.load(json_file) + +def get_sdk_dir(version, platform): + global use_local_sdk, local_base_path + if use_local_sdk: + base_path = local_base_path + if base_path is None: + exit_with_error("TACTILITY_SDK_PATH environment variable is not set") + sdk_parent_dir = os.path.join(base_path, f"{version}-{platform}") + sdk_dir = os.path.join(sdk_parent_dir, "TactilitySDK") + if not os.path.isdir(sdk_dir): + exit_with_error(f"Local SDK folder not found for platform {platform}: {sdk_dir}") + return sdk_dir + else: + return os.path.join(ttbuild_path, f"{version}-{platform}", "TactilitySDK") + +def validate_local_sdks(platforms, version): + if not use_local_sdk: + return + global local_base_path + base_path = local_base_path + for platform in platforms: + sdk_parent_dir = os.path.join(base_path, f"{version}-{platform}") + sdk_dir = os.path.join(sdk_parent_dir, "TactilitySDK") + if not os.path.isdir(sdk_dir): + exit_with_error(f"Local SDK folder missing for {platform}: {sdk_dir}") + +def get_sdk_root_dir(version, platform): + global ttbuild_cdn + return os.path.join(ttbuild_path, f"{version}-{platform}") + +def get_sdk_url(version, platform): + global ttbuild_cdn + return f"{ttbuild_cdn}/TactilitySDK-{version}-{platform}.zip" + +def sdk_exists(version, platform): + sdk_dir = get_sdk_dir(version, platform) + return os.path.isdir(sdk_dir) + +def should_update_sdk_json(): + global ttbuild_cdn + json_filepath = os.path.join(ttbuild_path, "sdk.json") + if os.path.exists(json_filepath): + json_modification_time = os.path.getmtime(json_filepath) + now = time.time() + global ttbuild_sdk_json_validity + minimum_seconds_difference = ttbuild_sdk_json_validity + return (now - json_modification_time) > minimum_seconds_difference + else: + return True + +def update_sdk_json(): + global ttbuild_cdn, ttbuild_path + json_url = f"{ttbuild_cdn}/sdk.json" + json_filepath = os.path.join(ttbuild_path, "sdk.json") + return download_file(json_url, json_filepath) + +def should_fetch_sdkconfig_files(platform_targets): + for platform in platform_targets: + sdkconfig_filename = f"sdkconfig.app.{platform}" + if not os.path.exists(os.path.join(ttbuild_path, sdkconfig_filename)): + return True + return False + +def fetch_sdkconfig_files(platform_targets): + for platform in platform_targets: + sdkconfig_filename = f"sdkconfig.app.{platform}" + target_path = os.path.join(ttbuild_path, sdkconfig_filename) + if not download_file(f"{ttbuild_cdn}/{sdkconfig_filename}", target_path): + exit_with_error(f"Failed to download sdkconfig file for {platform}") + +#endregion SDK helpers + +#region Validation + +def validate_environment(): + if os.environ.get("IDF_PATH") is None: + if sys.platform == "win32": + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via %IDF_PATH%\\export.ps1") + else: + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") + if not os.path.exists("manifest.properties"): + exit_with_error("manifest.properties not found") + if use_local_sdk == False and os.environ.get("TACTILITY_SDK_PATH") is not None: + print_warning("TACTILITY_SDK_PATH is set, but will be ignored by this command.") + print_warning("If you want to use it, use the '--local-sdk' parameter") + elif use_local_sdk == True and os.environ.get("TACTILITY_SDK_PATH") is None: + exit_with_error("local build was requested, but TACTILITY_SDK_PATH environment variable is not set.") + +def validate_version_and_platforms(sdk_json, sdk_version, platforms_to_build): + version_map = sdk_json["versions"] + if not sdk_version in version_map: + exit_with_error(f"Version not found: {sdk_version}") + version_data = version_map[sdk_version] + available_platforms = version_data["platforms"] + for desired_platform in platforms_to_build: + if not desired_platform in available_platforms: + exit_with_error(f"Platform {desired_platform} is not available. Available ones: {available_platforms}") + +def validate_self(sdk_json): + if not "toolVersion" in sdk_json: + exit_with_error("Server returned invalid SDK data format (toolVersion not found)") + if not "toolCompatibility" in sdk_json: + exit_with_error("Server returned invalid SDK data format (toolCompatibility not found)") + if not "toolDownloadUrl" in sdk_json: + exit_with_error("Server returned invalid SDK data format (toolDownloadUrl not found)") + tool_version = sdk_json["toolVersion"] + tool_compatibility = sdk_json["toolCompatibility"] + if tool_version != ttbuild_version: + print_warning(f"New version available: {tool_version} (currently using {ttbuild_version})") + print_warning(f"Run 'tactility.py updateself' to update.") + if re.search(tool_compatibility, ttbuild_version) is None: + print_error("The tool is not compatible anymore.") + print_error("Run 'tactility.py updateself' to update.") + sys.exit(1) + +#endregion Validation + +#region Manifest + +def read_manifest(): + return read_properties_file("manifest.properties") + +def validate_manifest(manifest): + # [manifest] + if not "manifest" in manifest: + exit_with_error("Invalid manifest format: [manifest] not found") + if not "version" in manifest["manifest"]: + exit_with_error("Invalid manifest format: [manifest] version not found") + # [target] + if not "target" in manifest: + exit_with_error("Invalid manifest format: [target] not found") + if not "sdk" in manifest["target"]: + exit_with_error("Invalid manifest format: [target] sdk not found") + if not "platforms" in manifest["target"]: + exit_with_error("Invalid manifest format: [target] platforms not found") + # [app] + if not "app" in manifest: + exit_with_error("Invalid manifest format: [app] not found") + if not "id" in manifest["app"]: + exit_with_error("Invalid manifest format: [app] id not found") + if not "versionName" in manifest["app"]: + exit_with_error("Invalid manifest format: [app] versionName not found") + if not "versionCode" in manifest["app"]: + exit_with_error("Invalid manifest format: [app] versionCode not found") + if not "name" in manifest["app"]: + exit_with_error("Invalid manifest format: [app] name not found") + +def is_valid_manifest_platform(manifest, platform): + manifest_platforms = manifest["target"]["platforms"].split(",") + return platform in manifest_platforms + +def validate_manifest_platform(manifest, platform): + if not is_valid_manifest_platform(manifest, platform): + exit_with_error(f"Platform {platform} is not available in the manifest.") + +def get_manifest_target_platforms(manifest, requested_platform): + if requested_platform == "" or requested_platform is None: + return manifest["target"]["platforms"].split(",") + else: + validate_manifest_platform(manifest, requested_platform) + return [requested_platform] + +#endregion Manifest + +#region SDK download + +def sdk_download(version, platform): + sdk_root_dir = get_sdk_root_dir(version, platform) + os.makedirs(sdk_root_dir, exist_ok=True) + sdk_url = get_sdk_url(version, platform) + filepath = os.path.join(sdk_root_dir, f"{version}-{platform}.zip") + print(f"Downloading SDK version {version} for {platform}") + if download_file(sdk_url, filepath): + with zipfile.ZipFile(filepath, "r") as zip_ref: + zip_ref.extractall(os.path.join(sdk_root_dir, "TactilitySDK")) + return True + else: + return False + +def sdk_download_all(version, platforms): + for platform in platforms: + if not sdk_exists(version, platform): + if not sdk_download(version, platform): + return False + else: + if verbose: + print(f"Using cached download for SDK version {version} and platform {platform}") + return True + +#endregion SDK download + +#region Building + +def get_cmake_path(platform): + return os.path.join("build", f"cmake-build-{platform}") + +def find_elf_file(platform): + cmake_dir = get_cmake_path(platform) + if os.path.exists(cmake_dir): + for file in os.listdir(cmake_dir): + if file.endswith(".app.elf"): + return os.path.join(cmake_dir, file) + return None + +def build_all(version, platforms, skip_build): + for platform in platforms: + # First build command must be "idf.py build", otherwise it fails to execute "idf.py elf" + # We check if the ELF file exists and run the correct command + # This can lead to code caching issues, so sometimes a clean build is required + if find_elf_file(platform) is None: + if not build_first(version, platform, skip_build): + return False + else: + if not build_consecutively(version, platform, skip_build): + return False + return True + +def wait_for_process(process): + buffer = [] + if sys.platform != "win32": + os.set_blocking(process.stdout.fileno(), False) + while process.poll() is None: + while True: + line = process.stdout.readline() + if line: + decoded_line = line.decode("UTF-8") + if decoded_line != "": + buffer.append(decoded_line) + else: + break + else: + break + # Read any remaining output + for line in process.stdout: + decoded_line = line.decode("UTF-8") + if decoded_line: + buffer.append(decoded_line) + return buffer + +# The first build must call "idf.py build" and consecutive builds must call "idf.py elf" as it finishes faster. +# The problem is that the "idf.py build" always results in an error, even though the elf file is created. +# The solution is to suppress the error if we find that the elf file was created. +def build_first(version, platform, skip_build): + sdk_dir = get_sdk_dir(version, platform) + if verbose: + print(f"Using SDK at {sdk_dir}") + os.environ["TACTILITY_SDK_PATH"] = sdk_dir + sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") + shutil.copy(sdkconfig_path, "sdkconfig") + elf_path = find_elf_file(platform) + # Remove previous elf file: re-creation of the file is used to measure if the build succeeded, + # as the actual build job will always fail due to technical issues with the elf cmake script + if elf_path is not None: + os.remove(elf_path) + if skip_build: + return True + print(f"Building first {platform} build") + cmake_path = get_cmake_path(platform) + print_status_busy(f"Building {platform} ELF") + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: + build_output = wait_for_process(process) + # The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case + if process.returncode == 0: + print(f"{shell_color_green}Building for {platform} ✅{shell_color_reset}") + return True + else: + if find_elf_file(platform) is None: + for line in build_output: + print(line, end="") + print_status_error(f"Building {platform} ELF") + return False + else: + print_status_success(f"Building {platform} ELF") + return True + +def build_consecutively(version, platform, skip_build): + sdk_dir = get_sdk_dir(version, platform) + if verbose: + print(f"Using SDK at {sdk_dir}") + os.environ["TACTILITY_SDK_PATH"] = sdk_dir + sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") + shutil.copy(sdkconfig_path, "sdkconfig") + if skip_build: + return True + cmake_path = get_cmake_path(platform) + print_status_busy(f"Building {platform} ELF") + shell_needed = sys.platform == "win32" + with subprocess.Popen(["idf.py", "-B", cmake_path, "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: + build_output = wait_for_process(process) + if process.returncode == 0: + print_status_success(f"Building {platform} ELF") + return True + else: + for line in build_output: + print(line, end="") + print_status_error(f"Building {platform} ELF") + return False + +#endregion Building + +#region Packaging + +def package_intermediate_manifest(target_path): + if not os.path.isfile("manifest.properties"): + print_error("manifest.properties not found") + return + shutil.copy("manifest.properties", os.path.join(target_path, "manifest.properties")) + +def package_intermediate_binaries(target_path, platforms): + elf_dir = os.path.join(target_path, "elf") + os.makedirs(elf_dir, exist_ok=True) + for platform in platforms: + elf_path = find_elf_file(platform) + if elf_path is None: + print_error(f"ELF file not found at {elf_path}") + return + shutil.copy(elf_path, os.path.join(elf_dir, f"{platform}.elf")) + +def package_intermediate_assets(target_path): + if os.path.isdir("assets"): + shutil.copytree("assets", os.path.join(target_path, "assets"), dirs_exist_ok=True) + +def package_intermediate(platforms): + target_path = os.path.join("build", "package-intermediate") + if os.path.isdir(target_path): + shutil.rmtree(target_path) + os.makedirs(target_path, exist_ok=True) + package_intermediate_manifest(target_path) + package_intermediate_binaries(target_path, platforms) + package_intermediate_assets(target_path) + +def package_name(platforms): + elf_path = find_elf_file(platforms[0]) + elf_base_name = os.path.basename(elf_path).removesuffix(".app.elf") + return os.path.join("build", f"{elf_base_name}.app") + + +def package_all(platforms): + status = f"Building package with {platforms}" + print_status_busy(status) + package_intermediate(platforms) + # Create build/something.app + try: + tar_path = package_name(platforms) + tar = tarfile.open(tar_path, mode="w", format=tarfile.USTAR_FORMAT) + tar.add(os.path.join("build", "package-intermediate"), arcname="") + tar.close() + print_status_success(status) + return True + except Exception as e: + print_status_error(f"Building package failed: {e.message}") + return False + +#endregion Packaging + +def setup_environment(): + global ttbuild_path + os.makedirs(ttbuild_path, exist_ok=True) + +def build_action(manifest, platform_arg): + # Environment validation + validate_environment() + platforms_to_build = get_manifest_target_platforms(manifest, platform_arg) + + if use_local_sdk: + global local_base_path + local_base_path = os.environ.get("TACTILITY_SDK_PATH") + validate_local_sdks(platforms_to_build, manifest["target"]["sdk"]) + + if should_fetch_sdkconfig_files(platforms_to_build): + fetch_sdkconfig_files(platforms_to_build) + + if not use_local_sdk: + sdk_json = read_sdk_json() + validate_self(sdk_json) + if not "versions" in sdk_json: + exit_with_error("Version data not found in sdk.json") + # Build + sdk_version = manifest["target"]["sdk"] + if not use_local_sdk: + validate_version_and_platforms(sdk_json, sdk_version, platforms_to_build) + if not sdk_download_all(sdk_version, platforms_to_build): + exit_with_error("Failed to download one or more SDKs") + if not build_all(sdk_version, platforms_to_build, skip_build): # Environment validation + return False + if not skip_build: + package_all(platforms_to_build) + return True + +def clean_action(): + if os.path.exists("build"): + print_status_busy("Removing build/") + shutil.rmtree("build") + print_status_success("Removed build/") + else: + print("Nothing to clean") + +def clear_cache_action(): + if os.path.exists(ttbuild_path): + print_status_busy(f"Removing {ttbuild_path}/") + shutil.rmtree(ttbuild_path) + print_status_success(f"Removed {ttbuild_path}/") + else: + print("Nothing to clear") + +def update_self_action(): + sdk_json = read_sdk_json() + tool_download_url = sdk_json["toolDownloadUrl"] + if download_file(tool_download_url, "tactility.py"): + print("Updated") + else: + exit_with_error("Update failed") + +def get_device_info(ip): + print_status_busy(f"Requesting device info") + url = get_url(ip, "/info") + try: + response = requests.get(url) + if response.status_code != 200: + print_error("Run failed") + else: + print_status_success(f"Received device info:") + print(response.json()) + except requests.RequestException as e: + print_status_error(f"Device info request failed: {e.message}") + +def run_action(manifest, ip): + app_id = manifest["app"]["id"] + print_status_busy("Running") + url = get_url(ip, "/app/run") + params = {'id': app_id} + try: + response = requests.post(url, params=params) + if response.status_code != 200: + print_error("Run failed") + else: + print_status_success("Running") + except requests.RequestException as e: + print_status_error(f"Running request failed: {e.message}") + +def install_action(ip, platforms): + print_status_busy("Installing") + for platform in platforms: + elf_path = find_elf_file(platform) + if elf_path is None: + print_status_error(f"ELF file not built for {platform}") + return False + package_path = package_name(platforms) + # print(f"Installing {package_path} to {ip}") + url = get_url(ip, "/app/install") + try: + # Prepare multipart form data + with open(package_path, 'rb') as file: + files = { + 'elf': file + } + response = requests.put(url, files=files) + if response.status_code != 200: + print_status_error("Install failed") + return True + else: + print_status_success("Installing") + return True + except requests.RequestException as e: + print_status_error(f"Install request failed: {e.message}") + return False + except IOError as e: + print_status_error(f"Install file error: {e.message}") + return False + +def uninstall_action(manifest, ip): + app_id = manifest["app"]["id"] + print_status_busy("Uninstalling") + url = get_url(ip, "/app/uninstall") + params = {'id': app_id} + try: + response = requests.put(url, params=params) + if response.status_code != 200: + print_status_error("Server responded that uninstall failed") + else: + print_status_success("Uninstalled") + except requests.RequestException as e: + print_status_success(f"Uninstall request failed: {e.message}") + +#region Main + +if __name__ == "__main__": + print(f"Tactility Build System v{ttbuild_version}") + if "--help" in sys.argv: + print_help() + sys.exit() + # Argument validation + if len(sys.argv) == 1: + print_help() + sys.exit() + if "--verbose" in sys.argv: + verbose = True + sys.argv.remove("--verbose") + skip_build = False + if "--skip-build" in sys.argv: + skip_build = True + sys.argv.remove("--skip-build") + if "--local-sdk" in sys.argv: + use_local_sdk = True + sys.argv.remove("--local-sdk") + action_arg = sys.argv[1] + + # Environment setup + setup_environment() + if not os.path.isfile("manifest.properties"): + exit_with_error("manifest.properties not found") + manifest = read_manifest() + validate_manifest(manifest) + all_platform_targets = manifest["target"]["platforms"].split(",") + # Update SDK cache (sdk.json) + if should_update_sdk_json() and not update_sdk_json(): + exit_with_error("Failed to retrieve SDK info") + # Actions + if action_arg == "build": + if len(sys.argv) < 2: + print_help() + exit_with_error("Commandline parameter missing") + platform = None + if len(sys.argv) > 2: + platform = sys.argv[2] + build_action(manifest, platform) + elif action_arg == "clean": + clean_action() + elif action_arg == "clearcache": + clear_cache_action() + elif action_arg == "updateself": + update_self_action() + elif action_arg == "run": + if len(sys.argv) < 3: + print_help() + exit_with_error("Commandline parameter missing") + run_action(manifest, sys.argv[2]) + elif action_arg == "install": + if len(sys.argv) < 3: + print_help() + exit_with_error("Commandline parameter missing") + platform = None + platforms_to_install = all_platform_targets + if len(sys.argv) >= 4: + platform = sys.argv[3] + platforms_to_install = [platform] + install_action(sys.argv[2], platforms_to_install) + elif action_arg == "uninstall": + if len(sys.argv) < 3: + print_help() + exit_with_error("Commandline parameter missing") + uninstall_action(manifest, sys.argv[2]) + elif action_arg == "bir" or action_arg == "brrr": + if len(sys.argv) < 3: + print_help() + exit_with_error("Commandline parameter missing") + platform = None + platforms_to_install = all_platform_targets + if len(sys.argv) >= 4: + platform = sys.argv[3] + platforms_to_install = [platform] + if build_action(manifest, platform): + if install_action(sys.argv[2], platforms_to_install): + run_action(manifest, sys.argv[2]) + else: + print_help() + exit_with_error("Unknown commandline parameter") + +#endregion Main From fa009968ee7eaa714e961137552fe905771a26f5 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Wed, 31 Dec 2025 01:50:15 +1000 Subject: [PATCH 2/5] Update matrix selection screen with control info --- Apps/TwoEleven/main/Source/TwoEleven.cpp | 31 +++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/Apps/TwoEleven/main/Source/TwoEleven.cpp b/Apps/TwoEleven/main/Source/TwoEleven.cpp index 0f0a8ab..4472a9e 100644 --- a/Apps/TwoEleven/main/Source/TwoEleven.cpp +++ b/Apps/TwoEleven/main/Source/TwoEleven.cpp @@ -97,17 +97,17 @@ void TwoEleven::create_selection(lv_obj_t* parent, lv_obj_t* toolbar) { lv_obj_t* selection = lv_obj_create(parent); lv_obj_set_size(selection, LV_PCT(100), LV_PCT(100)); lv_obj_set_flex_flow(selection, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(selection, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_flex_grow(selection, 1); lv_obj_remove_flag(selection, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_style_pad_all(selection, 2, LV_PART_MAIN); + lv_obj_set_style_pad_all(selection, 0, LV_PART_MAIN); lv_obj_set_style_border_width(selection, 0, LV_STATE_DEFAULT); lv_obj_t* titleWrapper = lv_obj_create(selection); lv_obj_set_size(titleWrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(titleWrapper, 2, LV_STATE_DEFAULT); + lv_obj_set_style_pad_all(titleWrapper, 0, LV_STATE_DEFAULT); lv_obj_set_style_border_width(titleWrapper, 0, LV_STATE_DEFAULT); lv_obj_set_style_bg_opa(titleWrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_flex_flow(titleWrapper, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_flow(titleWrapper, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(titleWrapper, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_remove_flag(titleWrapper, LV_OBJ_FLAG_SCROLLABLE); @@ -116,17 +116,36 @@ void TwoEleven::create_selection(lv_obj_t* parent, lv_obj_t* toolbar) { lv_obj_align(titleLabel, LV_ALIGN_CENTER, 0, 0); lv_obj_set_size(titleLabel, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_t* controlsWrapper = lv_obj_create(titleWrapper); + lv_obj_set_size(controlsWrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(controlsWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(controlsWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(controlsWrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_flex_flow(controlsWrapper, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(controlsWrapper, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_remove_flag(controlsWrapper, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* touchControlsLabel = lv_label_create(controlsWrapper); + lv_label_set_text(touchControlsLabel, "Touchscreen:\nSwipe up, down, left, right to move tiles."); + lv_obj_set_style_text_font(touchControlsLabel, lv_font_get_default(), 0); + lv_obj_set_style_text_align(touchControlsLabel, LV_TEXT_ALIGN_CENTER, 0); + + lv_obj_t* keyControlsLabel = lv_label_create(controlsWrapper); + lv_label_set_text_fmt(keyControlsLabel, "Keyboard:\nUse arrow keys (%s, %s, %s, %s) to move tiles.", LV_SYMBOL_UP, LV_SYMBOL_DOWN, LV_SYMBOL_LEFT, LV_SYMBOL_RIGHT); + lv_obj_set_style_text_font(keyControlsLabel, lv_font_get_default(), 0); + lv_obj_set_style_text_align(keyControlsLabel, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_t* buttonContainer = lv_obj_create(selection); lv_obj_set_flex_flow(buttonContainer, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(buttonContainer, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_remove_flag(buttonContainer, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(buttonContainer, LV_PCT(100), LV_PCT(100) - 20); + lv_obj_set_size(buttonContainer, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_style_bg_opa(buttonContainer, 0, LV_STATE_DEFAULT); lv_obj_set_style_border_width(buttonContainer, 0, LV_STATE_DEFAULT); for(int s = 3; s <= 6; s++) { lv_obj_t* btn = lv_btn_create(buttonContainer); - lv_obj_set_size(btn, 60, 50); + lv_obj_set_size(btn, 60, 40); lv_obj_t* lbl = lv_label_create(btn); char txt[10]; sprintf(txt, "%dx%d", s, s); From 6568caafe9ebadfa4cf2c16f21463ef5b0f5a445 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Wed, 31 Dec 2025 03:08:51 +1000 Subject: [PATCH 3/5] Update manifest.properties --- Apps/TwoEleven/manifest.properties | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Apps/TwoEleven/manifest.properties b/Apps/TwoEleven/manifest.properties index b617ada..1ce7e24 100644 --- a/Apps/TwoEleven/manifest.properties +++ b/Apps/TwoEleven/manifest.properties @@ -4,7 +4,8 @@ version=0.1 sdk=0.7.0-SNAPSHOT2 platforms=esp32,esp32s3 [app] -id=two.shadowtrance.twoeleven +id=one.tactility.twoeleven versionName=0.2.0 versionCode=2 -name=Two Eleven \ No newline at end of file +name=2048 +description=A fun, customizable 2048 sliding tile game for tactility!\nSlide tiles to combine numbers and reach 2048.\nChoose grid sizes: 3x3 (easy), 4x4 (classic), 5x5, or 6x6 (expert). \ No newline at end of file From 249ee943a5996f18376f229a5ca6370f4c022872 Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Wed, 31 Dec 2025 03:45:22 +1000 Subject: [PATCH 4/5] Fixes --- Apps/Calculator/tactility.py | 14 +++++++------- Apps/Diceware/tactility.py | 14 +++++++------- Apps/GPIO/tactility.py | 14 +++++++------- Apps/GraphicsDemo/tactility.py | 14 +++++++------- Apps/HelloWorld/tactility.py | 14 +++++++------- Apps/SerialConsole/tactility.py | 14 +++++++------- Apps/TwoEleven/main/Source/TwoElevenUi.c | 18 +++++++++++++++++- Apps/TwoEleven/tactility.py | 14 +++++++------- 8 files changed, 66 insertions(+), 50 deletions(-) diff --git a/Apps/Calculator/tactility.py b/Apps/Calculator/tactility.py index f80241c..8861a0e 100644 --- a/Apps/Calculator/tactility.py +++ b/Apps/Calculator/tactility.py @@ -469,7 +469,7 @@ def package_all(platforms): print_status_success(status) return True except Exception as e: - print_status_error(f"Building package failed: {e.message}") + print_status_error(f"Building package failed: {e}") return False #endregion Packaging @@ -543,7 +543,7 @@ def get_device_info(ip): print_status_success(f"Received device info:") print(response.json()) except requests.RequestException as e: - print_status_error(f"Device info request failed: {e.message}") + print_status_error(f"Device info request failed: {e}") def run_action(manifest, ip): app_id = manifest["app"]["id"] @@ -557,7 +557,7 @@ def run_action(manifest, ip): else: print_status_success("Running") except requests.RequestException as e: - print_status_error(f"Running request failed: {e.message}") + print_status_error(f"Running request failed: {e}") def install_action(ip, platforms): print_status_busy("Installing") @@ -578,15 +578,15 @@ def install_action(ip, platforms): response = requests.put(url, files=files) if response.status_code != 200: print_status_error("Install failed") - return True + return False else: print_status_success("Installing") return True except requests.RequestException as e: - print_status_error(f"Install request failed: {e.message}") + print_status_error(f"Install request failed: {e}") return False except IOError as e: - print_status_error(f"Install file error: {e.message}") + print_status_error(f"Install file error: {e}") return False def uninstall_action(manifest, ip): @@ -601,7 +601,7 @@ def uninstall_action(manifest, ip): else: print_status_success("Uninstalled") except requests.RequestException as e: - print_status_success(f"Uninstall request failed: {e.message}") + print_status_success(f"Uninstall request failed: {e}") #region Main diff --git a/Apps/Diceware/tactility.py b/Apps/Diceware/tactility.py index f80241c..8861a0e 100644 --- a/Apps/Diceware/tactility.py +++ b/Apps/Diceware/tactility.py @@ -469,7 +469,7 @@ def package_all(platforms): print_status_success(status) return True except Exception as e: - print_status_error(f"Building package failed: {e.message}") + print_status_error(f"Building package failed: {e}") return False #endregion Packaging @@ -543,7 +543,7 @@ def get_device_info(ip): print_status_success(f"Received device info:") print(response.json()) except requests.RequestException as e: - print_status_error(f"Device info request failed: {e.message}") + print_status_error(f"Device info request failed: {e}") def run_action(manifest, ip): app_id = manifest["app"]["id"] @@ -557,7 +557,7 @@ def run_action(manifest, ip): else: print_status_success("Running") except requests.RequestException as e: - print_status_error(f"Running request failed: {e.message}") + print_status_error(f"Running request failed: {e}") def install_action(ip, platforms): print_status_busy("Installing") @@ -578,15 +578,15 @@ def install_action(ip, platforms): response = requests.put(url, files=files) if response.status_code != 200: print_status_error("Install failed") - return True + return False else: print_status_success("Installing") return True except requests.RequestException as e: - print_status_error(f"Install request failed: {e.message}") + print_status_error(f"Install request failed: {e}") return False except IOError as e: - print_status_error(f"Install file error: {e.message}") + print_status_error(f"Install file error: {e}") return False def uninstall_action(manifest, ip): @@ -601,7 +601,7 @@ def uninstall_action(manifest, ip): else: print_status_success("Uninstalled") except requests.RequestException as e: - print_status_success(f"Uninstall request failed: {e.message}") + print_status_success(f"Uninstall request failed: {e}") #region Main diff --git a/Apps/GPIO/tactility.py b/Apps/GPIO/tactility.py index f80241c..8861a0e 100644 --- a/Apps/GPIO/tactility.py +++ b/Apps/GPIO/tactility.py @@ -469,7 +469,7 @@ def package_all(platforms): print_status_success(status) return True except Exception as e: - print_status_error(f"Building package failed: {e.message}") + print_status_error(f"Building package failed: {e}") return False #endregion Packaging @@ -543,7 +543,7 @@ def get_device_info(ip): print_status_success(f"Received device info:") print(response.json()) except requests.RequestException as e: - print_status_error(f"Device info request failed: {e.message}") + print_status_error(f"Device info request failed: {e}") def run_action(manifest, ip): app_id = manifest["app"]["id"] @@ -557,7 +557,7 @@ def run_action(manifest, ip): else: print_status_success("Running") except requests.RequestException as e: - print_status_error(f"Running request failed: {e.message}") + print_status_error(f"Running request failed: {e}") def install_action(ip, platforms): print_status_busy("Installing") @@ -578,15 +578,15 @@ def install_action(ip, platforms): response = requests.put(url, files=files) if response.status_code != 200: print_status_error("Install failed") - return True + return False else: print_status_success("Installing") return True except requests.RequestException as e: - print_status_error(f"Install request failed: {e.message}") + print_status_error(f"Install request failed: {e}") return False except IOError as e: - print_status_error(f"Install file error: {e.message}") + print_status_error(f"Install file error: {e}") return False def uninstall_action(manifest, ip): @@ -601,7 +601,7 @@ def uninstall_action(manifest, ip): else: print_status_success("Uninstalled") except requests.RequestException as e: - print_status_success(f"Uninstall request failed: {e.message}") + print_status_success(f"Uninstall request failed: {e}") #region Main diff --git a/Apps/GraphicsDemo/tactility.py b/Apps/GraphicsDemo/tactility.py index f80241c..8861a0e 100644 --- a/Apps/GraphicsDemo/tactility.py +++ b/Apps/GraphicsDemo/tactility.py @@ -469,7 +469,7 @@ def package_all(platforms): print_status_success(status) return True except Exception as e: - print_status_error(f"Building package failed: {e.message}") + print_status_error(f"Building package failed: {e}") return False #endregion Packaging @@ -543,7 +543,7 @@ def get_device_info(ip): print_status_success(f"Received device info:") print(response.json()) except requests.RequestException as e: - print_status_error(f"Device info request failed: {e.message}") + print_status_error(f"Device info request failed: {e}") def run_action(manifest, ip): app_id = manifest["app"]["id"] @@ -557,7 +557,7 @@ def run_action(manifest, ip): else: print_status_success("Running") except requests.RequestException as e: - print_status_error(f"Running request failed: {e.message}") + print_status_error(f"Running request failed: {e}") def install_action(ip, platforms): print_status_busy("Installing") @@ -578,15 +578,15 @@ def install_action(ip, platforms): response = requests.put(url, files=files) if response.status_code != 200: print_status_error("Install failed") - return True + return False else: print_status_success("Installing") return True except requests.RequestException as e: - print_status_error(f"Install request failed: {e.message}") + print_status_error(f"Install request failed: {e}") return False except IOError as e: - print_status_error(f"Install file error: {e.message}") + print_status_error(f"Install file error: {e}") return False def uninstall_action(manifest, ip): @@ -601,7 +601,7 @@ def uninstall_action(manifest, ip): else: print_status_success("Uninstalled") except requests.RequestException as e: - print_status_success(f"Uninstall request failed: {e.message}") + print_status_success(f"Uninstall request failed: {e}") #region Main diff --git a/Apps/HelloWorld/tactility.py b/Apps/HelloWorld/tactility.py index f80241c..8861a0e 100644 --- a/Apps/HelloWorld/tactility.py +++ b/Apps/HelloWorld/tactility.py @@ -469,7 +469,7 @@ def package_all(platforms): print_status_success(status) return True except Exception as e: - print_status_error(f"Building package failed: {e.message}") + print_status_error(f"Building package failed: {e}") return False #endregion Packaging @@ -543,7 +543,7 @@ def get_device_info(ip): print_status_success(f"Received device info:") print(response.json()) except requests.RequestException as e: - print_status_error(f"Device info request failed: {e.message}") + print_status_error(f"Device info request failed: {e}") def run_action(manifest, ip): app_id = manifest["app"]["id"] @@ -557,7 +557,7 @@ def run_action(manifest, ip): else: print_status_success("Running") except requests.RequestException as e: - print_status_error(f"Running request failed: {e.message}") + print_status_error(f"Running request failed: {e}") def install_action(ip, platforms): print_status_busy("Installing") @@ -578,15 +578,15 @@ def install_action(ip, platforms): response = requests.put(url, files=files) if response.status_code != 200: print_status_error("Install failed") - return True + return False else: print_status_success("Installing") return True except requests.RequestException as e: - print_status_error(f"Install request failed: {e.message}") + print_status_error(f"Install request failed: {e}") return False except IOError as e: - print_status_error(f"Install file error: {e.message}") + print_status_error(f"Install file error: {e}") return False def uninstall_action(manifest, ip): @@ -601,7 +601,7 @@ def uninstall_action(manifest, ip): else: print_status_success("Uninstalled") except requests.RequestException as e: - print_status_success(f"Uninstall request failed: {e.message}") + print_status_success(f"Uninstall request failed: {e}") #region Main diff --git a/Apps/SerialConsole/tactility.py b/Apps/SerialConsole/tactility.py index f80241c..8861a0e 100644 --- a/Apps/SerialConsole/tactility.py +++ b/Apps/SerialConsole/tactility.py @@ -469,7 +469,7 @@ def package_all(platforms): print_status_success(status) return True except Exception as e: - print_status_error(f"Building package failed: {e.message}") + print_status_error(f"Building package failed: {e}") return False #endregion Packaging @@ -543,7 +543,7 @@ def get_device_info(ip): print_status_success(f"Received device info:") print(response.json()) except requests.RequestException as e: - print_status_error(f"Device info request failed: {e.message}") + print_status_error(f"Device info request failed: {e}") def run_action(manifest, ip): app_id = manifest["app"]["id"] @@ -557,7 +557,7 @@ def run_action(manifest, ip): else: print_status_success("Running") except requests.RequestException as e: - print_status_error(f"Running request failed: {e.message}") + print_status_error(f"Running request failed: {e}") def install_action(ip, platforms): print_status_busy("Installing") @@ -578,15 +578,15 @@ def install_action(ip, platforms): response = requests.put(url, files=files) if response.status_code != 200: print_status_error("Install failed") - return True + return False else: print_status_success("Installing") return True except requests.RequestException as e: - print_status_error(f"Install request failed: {e.message}") + print_status_error(f"Install request failed: {e}") return False except IOError as e: - print_status_error(f"Install file error: {e.message}") + print_status_error(f"Install file error: {e}") return False def uninstall_action(manifest, ip): @@ -601,7 +601,7 @@ def uninstall_action(manifest, ip): else: print_status_success("Uninstalled") except requests.RequestException as e: - print_status_success(f"Uninstall request failed: {e.message}") + print_status_success(f"Uninstall request failed: {e}") #region Main diff --git a/Apps/TwoEleven/main/Source/TwoElevenUi.c b/Apps/TwoEleven/main/Source/TwoElevenUi.c index 1e0f4fa..cd15459 100644 --- a/Apps/TwoEleven/main/Source/TwoElevenUi.c +++ b/Apps/TwoEleven/main/Source/TwoElevenUi.c @@ -38,8 +38,12 @@ static void delete_event(lv_event_t * e) lv_obj_t * twoeleven_create(lv_obj_t * parent, uint16_t matrix_size) { lv_obj_t * obj = lv_obj_create(parent); + if (!obj) return NULL; twoeleven_t * game_2048 = (twoeleven_t *)lv_malloc(sizeof(twoeleven_t)); - if (!game_2048) return NULL; + if (!game_2048) { + lv_obj_delete(obj); + return NULL; + } lv_obj_set_user_data(obj, game_2048); game_2048->score = 0; @@ -53,8 +57,20 @@ lv_obj_t * twoeleven_create(lv_obj_t * parent, uint16_t matrix_size) // Allocate matrix game_2048->matrix = lv_malloc(game_2048->matrix_size * sizeof(uint16_t*)); + if (!game_2048->matrix) { + lv_free(game_2048); + lv_obj_delete(obj); + return NULL; + } for (uint16_t i = 0; i < game_2048->matrix_size; i++) { game_2048->matrix[i] = lv_malloc(game_2048->matrix_size * sizeof(uint16_t)); + if (!game_2048->matrix[i]) { + for (uint16_t j = 0; j < i; j++) lv_free(game_2048->matrix[j]); + lv_free(game_2048->matrix); + lv_free(game_2048); + lv_obj_delete(obj); + return NULL; + } } // Allocate button map diff --git a/Apps/TwoEleven/tactility.py b/Apps/TwoEleven/tactility.py index f80241c..8861a0e 100644 --- a/Apps/TwoEleven/tactility.py +++ b/Apps/TwoEleven/tactility.py @@ -469,7 +469,7 @@ def package_all(platforms): print_status_success(status) return True except Exception as e: - print_status_error(f"Building package failed: {e.message}") + print_status_error(f"Building package failed: {e}") return False #endregion Packaging @@ -543,7 +543,7 @@ def get_device_info(ip): print_status_success(f"Received device info:") print(response.json()) except requests.RequestException as e: - print_status_error(f"Device info request failed: {e.message}") + print_status_error(f"Device info request failed: {e}") def run_action(manifest, ip): app_id = manifest["app"]["id"] @@ -557,7 +557,7 @@ def run_action(manifest, ip): else: print_status_success("Running") except requests.RequestException as e: - print_status_error(f"Running request failed: {e.message}") + print_status_error(f"Running request failed: {e}") def install_action(ip, platforms): print_status_busy("Installing") @@ -578,15 +578,15 @@ def install_action(ip, platforms): response = requests.put(url, files=files) if response.status_code != 200: print_status_error("Install failed") - return True + return False else: print_status_success("Installing") return True except requests.RequestException as e: - print_status_error(f"Install request failed: {e.message}") + print_status_error(f"Install request failed: {e}") return False except IOError as e: - print_status_error(f"Install file error: {e.message}") + print_status_error(f"Install file error: {e}") return False def uninstall_action(manifest, ip): @@ -601,7 +601,7 @@ def uninstall_action(manifest, ip): else: print_status_success("Uninstalled") except requests.RequestException as e: - print_status_success(f"Uninstall request failed: {e.message}") + print_status_success(f"Uninstall request failed: {e}") #region Main From dbafefbc1b30e9a6cd36f8efdc719a4a0eec5fbe Mon Sep 17 00:00:00 2001 From: Shadowtrance Date: Wed, 31 Dec 2025 05:23:10 +1000 Subject: [PATCH 5/5] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6626e06..4b65d07 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: Build: strategy: matrix: - app_name: [Calculator, Diceware, GPIO, GraphicsDemo, HelloWorld, SerialConsole] + app_name: [Calculator, Diceware, GPIO, GraphicsDemo, HelloWorld, SerialConsole, TwoEleven] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4