Two Eleven App aka 2048 (#16)

This commit is contained in:
Shadowtrance
2025-12-31 05:24:50 +10:00
committed by GitHub
parent b769ede7a8
commit 64402e261a
26 changed files with 1972 additions and 145 deletions
+36 -24
View File
@@ -10,8 +10,6 @@ import urllib.request
import zipfile
import requests
import tarfile
import shutil
import configparser
ttbuild_path = ".tactility"
ttbuild_version = "2.4.0"
@@ -23,12 +21,12 @@ use_local_sdk = False
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")
@@ -457,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
@@ -531,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"]
@@ -545,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")
@@ -566,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):
@@ -589,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
+36 -24
View File
@@ -10,8 +10,6 @@ import urllib.request
import zipfile
import requests
import tarfile
import shutil
import configparser
ttbuild_path = ".tactility"
ttbuild_version = "2.4.0"
@@ -23,12 +21,12 @@ use_local_sdk = False
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")
@@ -457,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
@@ -531,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"]
@@ -545,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")
@@ -566,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):
@@ -589,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
+36 -24
View File
@@ -10,8 +10,6 @@ import urllib.request
import zipfile
import requests
import tarfile
import shutil
import configparser
ttbuild_path = ".tactility"
ttbuild_version = "2.4.0"
@@ -23,12 +21,12 @@ use_local_sdk = False
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")
@@ -457,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
@@ -531,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"]
@@ -545,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")
@@ -566,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):
@@ -589,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
+36 -24
View File
@@ -10,8 +10,6 @@ import urllib.request
import zipfile
import requests
import tarfile
import shutil
import configparser
ttbuild_path = ".tactility"
ttbuild_version = "2.4.0"
@@ -23,12 +21,12 @@ use_local_sdk = False
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")
@@ -457,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
@@ -531,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"]
@@ -545,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")
@@ -566,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):
@@ -589,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
+36 -24
View File
@@ -10,8 +10,6 @@ import urllib.request
import zipfile
import requests
import tarfile
import shutil
import configparser
ttbuild_path = ".tactility"
ttbuild_version = "2.4.0"
@@ -23,12 +21,12 @@ use_local_sdk = False
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")
@@ -457,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
@@ -531,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"]
@@ -545,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")
@@ -566,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):
@@ -589,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
+36 -24
View File
@@ -10,8 +10,6 @@ import urllib.request
import zipfile
import requests
import tarfile
import shutil
import configparser
ttbuild_path = ".tactility"
ttbuild_version = "2.4.0"
@@ -23,12 +21,12 @@ use_local_sdk = False
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")
@@ -457,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
@@ -531,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"]
@@ -545,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")
@@ -566,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):
@@ -589,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
+16
View File
@@ -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)
+57
View File
@@ -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?)
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

+15
View File
@@ -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
)
+202
View File
@@ -0,0 +1,202 @@
#include "TwoEleven.h"
#include <tt_lvgl_toolbar.h>
#include <tt_app_alertdialog.h>
#include <TactilityCpp/LvglLock.h>
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_grow(selection, 1);
lv_obj_remove_flag(selection, LV_OBJ_FLAG_SCROLLABLE);
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, 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_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);
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* 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_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, 40);
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();
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <tt_app.h>
#include <lvgl.h>
#include <TactilityCpp/App.h>
#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;
};
@@ -0,0 +1,130 @@
#include "TwoElevenHelpers.h"
#include <string.h>
#include <stdio.h>
/**
* @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++;
}
}
}
@@ -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*/
+162
View File
@@ -0,0 +1,162 @@
#include "TwoElevenLogic.h"
#include "TwoElevenHelpers.h"
#include <stdlib.h>
#include <time.h>
#include <string.h>
/**
* @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);
}
@@ -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*/
+242
View File
@@ -0,0 +1,242 @@
#include "TwoElevenUi.h"
#include "TwoElevenLogic.h"
#include "TwoElevenHelpers.h"
#include <stdlib.h>
#include <string.h>
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);
if (!obj) return NULL;
twoeleven_t * game_2048 = (twoeleven_t *)lv_malloc(sizeof(twoeleven_t));
if (!game_2048) {
lv_obj_delete(obj);
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*));
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
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;
}
}
}
+35
View File
@@ -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*/
+11
View File
@@ -0,0 +1,11 @@
#include "TwoEleven.h"
#include <TactilityCpp/App.h>
extern "C" {
int main(int argc, char* argv[]) {
registerApp<TwoEleven>();
return 0;
}
}
+11
View File
@@ -0,0 +1,11 @@
[manifest]
version=0.1
[target]
sdk=0.7.0-SNAPSHOT2
platforms=esp32,esp32s3
[app]
id=one.tactility.twoeleven
versionName=0.2.0
versionCode=2
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).
+690
View File
@@ -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}")
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}")
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}")
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 False
else:
print_status_success("Installing")
return True
except requests.RequestException as e:
print_status_error(f"Install request failed: {e}")
return False
except IOError as e:
print_status_error(f"Install file error: {e}")
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}")
#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