mirror of
https://github.com/cmclark00/tetris-3d.git
synced 2025-05-17 15:15:20 +01:00
Implement Android app with improved features: 1. Enhanced swipe controls for smoother movement 2. Improved 3D rotation with visual effects 3. Updated UI to match web app style
This commit is contained in:
parent
210a518ad5
commit
c76cde0f1f
103 changed files with 4180 additions and 357 deletions
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"java.configuration.updateBuildConfiguration": "interactive"
|
||||||
|
}
|
BIN
android-app/.gradle/7.5/checksums/checksums.lock
Normal file
BIN
android-app/.gradle/7.5/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
android-app/.gradle/7.5/fileChanges/last-build.bin
Normal file
BIN
android-app/.gradle/7.5/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/7.5/fileHashes/fileHashes.lock
Normal file
BIN
android-app/.gradle/7.5/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
0
android-app/.gradle/7.5/gc.properties
Normal file
0
android-app/.gradle/7.5/gc.properties
Normal file
BIN
android-app/.gradle/8.10/checksums/checksums.lock
Normal file
BIN
android-app/.gradle/8.10/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.10/checksums/md5-checksums.bin
Normal file
BIN
android-app/.gradle/8.10/checksums/md5-checksums.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.10/checksums/sha1-checksums.bin
Normal file
BIN
android-app/.gradle/8.10/checksums/sha1-checksums.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.10/executionHistory/executionHistory.bin
Normal file
BIN
android-app/.gradle/8.10/executionHistory/executionHistory.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.10/executionHistory/executionHistory.lock
Normal file
BIN
android-app/.gradle/8.10/executionHistory/executionHistory.lock
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.10/fileChanges/last-build.bin
Normal file
BIN
android-app/.gradle/8.10/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.10/fileHashes/fileHashes.bin
Normal file
BIN
android-app/.gradle/8.10/fileHashes/fileHashes.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.10/fileHashes/fileHashes.lock
Normal file
BIN
android-app/.gradle/8.10/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
0
android-app/.gradle/8.10/gc.properties
Normal file
0
android-app/.gradle/8.10/gc.properties
Normal file
BIN
android-app/.gradle/8.11.1/checksums/checksums.lock
Normal file
BIN
android-app/.gradle/8.11.1/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.11.1/checksums/md5-checksums.bin
Normal file
BIN
android-app/.gradle/8.11.1/checksums/md5-checksums.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.11.1/checksums/sha1-checksums.bin
Normal file
BIN
android-app/.gradle/8.11.1/checksums/sha1-checksums.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.11.1/executionHistory/executionHistory.bin
Normal file
BIN
android-app/.gradle/8.11.1/executionHistory/executionHistory.bin
Normal file
Binary file not shown.
Binary file not shown.
BIN
android-app/.gradle/8.11.1/fileChanges/last-build.bin
Normal file
BIN
android-app/.gradle/8.11.1/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.11.1/fileHashes/fileHashes.bin
Normal file
BIN
android-app/.gradle/8.11.1/fileHashes/fileHashes.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.11.1/fileHashes/fileHashes.lock
Normal file
BIN
android-app/.gradle/8.11.1/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.11.1/fileHashes/resourceHashesCache.bin
Normal file
BIN
android-app/.gradle/8.11.1/fileHashes/resourceHashesCache.bin
Normal file
Binary file not shown.
0
android-app/.gradle/8.11.1/gc.properties
Normal file
0
android-app/.gradle/8.11.1/gc.properties
Normal file
BIN
android-app/.gradle/8.8/checksums/checksums.lock
Normal file
BIN
android-app/.gradle/8.8/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.8/checksums/md5-checksums.bin
Normal file
BIN
android-app/.gradle/8.8/checksums/md5-checksums.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.8/checksums/sha1-checksums.bin
Normal file
BIN
android-app/.gradle/8.8/checksums/sha1-checksums.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.8/fileChanges/last-build.bin
Normal file
BIN
android-app/.gradle/8.8/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.8/fileHashes/fileHashes.bin
Normal file
BIN
android-app/.gradle/8.8/fileHashes/fileHashes.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.8/fileHashes/fileHashes.lock
Normal file
BIN
android-app/.gradle/8.8/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
0
android-app/.gradle/8.8/gc.properties
Normal file
0
android-app/.gradle/8.8/gc.properties
Normal file
BIN
android-app/.gradle/8.9/checksums/checksums.lock
Normal file
BIN
android-app/.gradle/8.9/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.9/executionHistory/executionHistory.lock
Normal file
BIN
android-app/.gradle/8.9/executionHistory/executionHistory.lock
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.9/fileChanges/last-build.bin
Normal file
BIN
android-app/.gradle/8.9/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
android-app/.gradle/8.9/fileHashes/fileHashes.lock
Normal file
BIN
android-app/.gradle/8.9/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
0
android-app/.gradle/8.9/gc.properties
Normal file
0
android-app/.gradle/8.9/gc.properties
Normal file
BIN
android-app/.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
BIN
android-app/.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
Binary file not shown.
2
android-app/.gradle/buildOutputCleanup/cache.properties
Normal file
2
android-app/.gradle/buildOutputCleanup/cache.properties
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#Tue Mar 25 23:50:46 EDT 2025
|
||||||
|
gradle.version=8.11.1
|
BIN
android-app/.gradle/buildOutputCleanup/outputFiles.bin
Normal file
BIN
android-app/.gradle/buildOutputCleanup/outputFiles.bin
Normal file
Binary file not shown.
2
android-app/.gradle/config.properties
Normal file
2
android-app/.gradle/config.properties
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#Tue Mar 25 23:36:25 EDT 2025
|
||||||
|
java.home=/home/corey/Downloads/android-studio-2024.3.1.13-linux/android-studio/jbr
|
BIN
android-app/.gradle/file-system.probe
Normal file
BIN
android-app/.gradle/file-system.probe
Normal file
Binary file not shown.
Binary file not shown.
BIN
android-app/.gradle/nb-cache/app-271321845/project-info.ser
Normal file
BIN
android-app/.gradle/nb-cache/app-271321845/project-info.ser
Normal file
Binary file not shown.
BIN
android-app/.gradle/nb-cache/subprojects.ser
Normal file
BIN
android-app/.gradle/nb-cache/subprojects.ser
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
8B64E587393F94313EFF41233F48D4848E985369BD2E4B833B25B3765823A6DB
|
0
android-app/.gradle/vcs-1/gc.properties
Normal file
0
android-app/.gradle/vcs-1/gc.properties
Normal file
3
android-app/.idea/.gitignore
generated
vendored
Normal file
3
android-app/.idea/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
1
android-app/.idea/.name
generated
Normal file
1
android-app/.idea/.name
generated
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Tetris3D
|
6
android-app/.idea/AndroidProjectSystem.xml
generated
Normal file
6
android-app/.idea/AndroidProjectSystem.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
26
android-app/.idea/appInsightsSettings.xml
generated
Normal file
26
android-app/.idea/appInsightsSettings.xml
generated
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AppInsightsSettings">
|
||||||
|
<option name="tabSettings">
|
||||||
|
<map>
|
||||||
|
<entry key="Firebase Crashlytics">
|
||||||
|
<value>
|
||||||
|
<InsightsFilterSettings>
|
||||||
|
<option name="connection">
|
||||||
|
<ConnectionSetting>
|
||||||
|
<option name="appId" value="PLACEHOLDER" />
|
||||||
|
<option name="mobileSdkAppId" value="" />
|
||||||
|
<option name="projectId" value="" />
|
||||||
|
<option name="projectNumber" value="" />
|
||||||
|
</ConnectionSetting>
|
||||||
|
</option>
|
||||||
|
<option name="signal" value="SIGNAL_UNSPECIFIED" />
|
||||||
|
<option name="timeIntervalDays" value="THIRTY_DAYS" />
|
||||||
|
<option name="visibilityType" value="ALL" />
|
||||||
|
</InsightsFilterSettings>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
607
android-app/.idea/caches/deviceStreaming.xml
generated
Normal file
607
android-app/.idea/caches/deviceStreaming.xml
generated
Normal file
|
@ -0,0 +1,607 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DeviceStreaming">
|
||||||
|
<option name="deviceSelectionList">
|
||||||
|
<list>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="27" />
|
||||||
|
<option name="brand" value="DOCOMO" />
|
||||||
|
<option name="codename" value="F01L" />
|
||||||
|
<option name="id" value="F01L" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="FUJITSU" />
|
||||||
|
<option name="name" value="F-01L" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="720" />
|
||||||
|
<option name="screenY" value="1280" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="OnePlus" />
|
||||||
|
<option name="codename" value="OP5552L1" />
|
||||||
|
<option name="id" value="OP5552L1" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="OnePlus" />
|
||||||
|
<option name="name" value="CPH2415" />
|
||||||
|
<option name="screenDensity" value="480" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2412" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="OPPO" />
|
||||||
|
<option name="codename" value="OP573DL1" />
|
||||||
|
<option name="id" value="OP573DL1" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="OPPO" />
|
||||||
|
<option name="name" value="CPH2557" />
|
||||||
|
<option name="screenDensity" value="480" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="28" />
|
||||||
|
<option name="brand" value="DOCOMO" />
|
||||||
|
<option name="codename" value="SH-01L" />
|
||||||
|
<option name="id" value="SH-01L" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="SHARP" />
|
||||||
|
<option name="name" value="AQUOS sense2 SH-01L" />
|
||||||
|
<option name="screenDensity" value="480" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2160" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="Lenovo" />
|
||||||
|
<option name="codename" value="TB370FU" />
|
||||||
|
<option name="formFactor" value="Tablet" />
|
||||||
|
<option name="id" value="TB370FU" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Lenovo" />
|
||||||
|
<option name="name" value="Tab P12" />
|
||||||
|
<option name="screenDensity" value="340" />
|
||||||
|
<option name="screenX" value="1840" />
|
||||||
|
<option name="screenY" value="2944" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="a15" />
|
||||||
|
<option name="id" value="a15" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="A15" />
|
||||||
|
<option name="screenDensity" value="450" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="a35x" />
|
||||||
|
<option name="id" value="a35x" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="A35" />
|
||||||
|
<option name="screenDensity" value="450" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="31" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="a51" />
|
||||||
|
<option name="id" value="a51" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy A51" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="akita" />
|
||||||
|
<option name="id" value="akita" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 8a" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="motorola" />
|
||||||
|
<option name="codename" value="arcfox" />
|
||||||
|
<option name="id" value="arcfox" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Motorola" />
|
||||||
|
<option name="name" value="razr plus 2024" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="1272" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="motorola" />
|
||||||
|
<option name="codename" value="austin" />
|
||||||
|
<option name="id" value="austin" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Motorola" />
|
||||||
|
<option name="name" value="moto g 5G (2022)" />
|
||||||
|
<option name="screenDensity" value="280" />
|
||||||
|
<option name="screenX" value="720" />
|
||||||
|
<option name="screenY" value="1600" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="b0q" />
|
||||||
|
<option name="id" value="b0q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S22 Ultra" />
|
||||||
|
<option name="screenDensity" value="600" />
|
||||||
|
<option name="screenX" value="1440" />
|
||||||
|
<option name="screenY" value="3088" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="32" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="bluejay" />
|
||||||
|
<option name="id" value="bluejay" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 6a" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="caiman" />
|
||||||
|
<option name="id" value="caiman" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="960" />
|
||||||
|
<option name="screenY" value="2142" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="comet" />
|
||||||
|
<option name="default" value="true" />
|
||||||
|
<option name="id" value="comet" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro Fold" />
|
||||||
|
<option name="screenDensity" value="390" />
|
||||||
|
<option name="screenX" value="2076" />
|
||||||
|
<option name="screenY" value="2152" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="29" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="crownqlteue" />
|
||||||
|
<option name="id" value="crownqlteue" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Note9" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2220" />
|
||||||
|
<option name="screenY" value="1080" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="dm2q" />
|
||||||
|
<option name="id" value="dm2q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="S23 Plus" />
|
||||||
|
<option name="screenDensity" value="450" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="dm3q" />
|
||||||
|
<option name="id" value="dm3q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S23 Ultra" />
|
||||||
|
<option name="screenDensity" value="600" />
|
||||||
|
<option name="screenX" value="1440" />
|
||||||
|
<option name="screenY" value="3088" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="e1q" />
|
||||||
|
<option name="default" value="true" />
|
||||||
|
<option name="id" value="e1q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S24" />
|
||||||
|
<option name="screenDensity" value="480" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="e3q" />
|
||||||
|
<option name="id" value="e3q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S24 Ultra" />
|
||||||
|
<option name="screenDensity" value="450" />
|
||||||
|
<option name="screenX" value="1440" />
|
||||||
|
<option name="screenY" value="3120" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="eos" />
|
||||||
|
<option name="id" value="eos" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Eos" />
|
||||||
|
<option name="screenDensity" value="320" />
|
||||||
|
<option name="screenX" value="384" />
|
||||||
|
<option name="screenY" value="384" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="felix" />
|
||||||
|
<option name="id" value="felix" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Fold" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2208" />
|
||||||
|
<option name="screenY" value="1840" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="felix" />
|
||||||
|
<option name="id" value="felix" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Fold" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2208" />
|
||||||
|
<option name="screenY" value="1840" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="felix_camera" />
|
||||||
|
<option name="id" value="felix_camera" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Fold (Camera-enabled)" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2208" />
|
||||||
|
<option name="screenY" value="1840" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="motorola" />
|
||||||
|
<option name="codename" value="fogona" />
|
||||||
|
<option name="id" value="fogona" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Motorola" />
|
||||||
|
<option name="name" value="moto g play - 2024" />
|
||||||
|
<option name="screenDensity" value="280" />
|
||||||
|
<option name="screenX" value="720" />
|
||||||
|
<option name="screenY" value="1600" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="g0q" />
|
||||||
|
<option name="id" value="g0q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="SM-S906U1" />
|
||||||
|
<option name="screenDensity" value="450" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="gta9pwifi" />
|
||||||
|
<option name="id" value="gta9pwifi" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="SM-X210" />
|
||||||
|
<option name="screenDensity" value="240" />
|
||||||
|
<option name="screenX" value="1200" />
|
||||||
|
<option name="screenY" value="1920" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="gts7xllite" />
|
||||||
|
<option name="id" value="gts7xllite" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="SM-T738U" />
|
||||||
|
<option name="screenDensity" value="340" />
|
||||||
|
<option name="screenX" value="1600" />
|
||||||
|
<option name="screenY" value="2560" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="gts8uwifi" />
|
||||||
|
<option name="formFactor" value="Tablet" />
|
||||||
|
<option name="id" value="gts8uwifi" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Tab S8 Ultra" />
|
||||||
|
<option name="screenDensity" value="320" />
|
||||||
|
<option name="screenX" value="1848" />
|
||||||
|
<option name="screenY" value="2960" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="gts8wifi" />
|
||||||
|
<option name="formFactor" value="Tablet" />
|
||||||
|
<option name="id" value="gts8wifi" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Tab S8" />
|
||||||
|
<option name="screenDensity" value="274" />
|
||||||
|
<option name="screenX" value="1600" />
|
||||||
|
<option name="screenY" value="2560" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="gts9fe" />
|
||||||
|
<option name="id" value="gts9fe" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Tab S9 FE 5G" />
|
||||||
|
<option name="screenDensity" value="280" />
|
||||||
|
<option name="screenX" value="1440" />
|
||||||
|
<option name="screenY" value="2304" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="husky" />
|
||||||
|
<option name="id" value="husky" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 8 Pro" />
|
||||||
|
<option name="screenDensity" value="390" />
|
||||||
|
<option name="screenX" value="1008" />
|
||||||
|
<option name="screenY" value="2244" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="30" />
|
||||||
|
<option name="brand" value="motorola" />
|
||||||
|
<option name="codename" value="java" />
|
||||||
|
<option name="id" value="java" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Motorola" />
|
||||||
|
<option name="name" value="G20" />
|
||||||
|
<option name="screenDensity" value="280" />
|
||||||
|
<option name="screenX" value="720" />
|
||||||
|
<option name="screenY" value="1600" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="komodo" />
|
||||||
|
<option name="id" value="komodo" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro XL" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="1008" />
|
||||||
|
<option name="screenY" value="2244" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="lynx" />
|
||||||
|
<option name="id" value="lynx" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 7a" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="motorola" />
|
||||||
|
<option name="codename" value="maui" />
|
||||||
|
<option name="id" value="maui" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Motorola" />
|
||||||
|
<option name="name" value="moto g play - 2023" />
|
||||||
|
<option name="screenDensity" value="280" />
|
||||||
|
<option name="screenX" value="720" />
|
||||||
|
<option name="screenY" value="1600" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="o1q" />
|
||||||
|
<option name="id" value="o1q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S21" />
|
||||||
|
<option name="screenDensity" value="421" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="31" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="oriole" />
|
||||||
|
<option name="id" value="oriole" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 6" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="panther" />
|
||||||
|
<option name="id" value="panther" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 7" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="q5q" />
|
||||||
|
<option name="id" value="q5q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Z Fold5" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1812" />
|
||||||
|
<option name="screenY" value="2176" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="q6q" />
|
||||||
|
<option name="id" value="q6q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Z Fold6" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1856" />
|
||||||
|
<option name="screenY" value="2160" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="30" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="r11" />
|
||||||
|
<option name="formFactor" value="Wear OS" />
|
||||||
|
<option name="id" value="r11" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Watch" />
|
||||||
|
<option name="screenDensity" value="320" />
|
||||||
|
<option name="screenX" value="384" />
|
||||||
|
<option name="screenY" value="384" />
|
||||||
|
<option name="type" value="WEAR_OS" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="r11q" />
|
||||||
|
<option name="id" value="r11q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="SM-S711U" />
|
||||||
|
<option name="screenDensity" value="450" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="30" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="redfin" />
|
||||||
|
<option name="id" value="redfin" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 5" />
|
||||||
|
<option name="screenDensity" value="440" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="shiba" />
|
||||||
|
<option name="id" value="shiba" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 8" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="t2q" />
|
||||||
|
<option name="id" value="t2q" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S21 Plus" />
|
||||||
|
<option name="screenDensity" value="394" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="tangorpro" />
|
||||||
|
<option name="formFactor" value="Tablet" />
|
||||||
|
<option name="id" value="tangorpro" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Tablet" />
|
||||||
|
<option name="screenDensity" value="320" />
|
||||||
|
<option name="screenX" value="1600" />
|
||||||
|
<option name="screenY" value="2560" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="tokay" />
|
||||||
|
<option name="default" value="true" />
|
||||||
|
<option name="id" value="tokay" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2424" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="35" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="tokay" />
|
||||||
|
<option name="default" value="true" />
|
||||||
|
<option name="id" value="tokay" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2424" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
android-app/.idea/compiler.xml
generated
Normal file
6
android-app/.idea/compiler.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="21" />
|
||||||
|
</component>
|
||||||
|
</project>
|
10
android-app/.idea/deploymentTargetSelector.xml
generated
Normal file
10
android-app/.idea/deploymentTargetSelector.xml
generated
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
19
android-app/.idea/gradle.xml
generated
Normal file
19
android-app/.idea/gradle.xml
generated
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
android-app/.idea/kotlinc.xml
generated
Normal file
6
android-app/.idea/kotlinc.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="KotlinJpsPluginSettings">
|
||||||
|
<option name="version" value="1.8.0" />
|
||||||
|
</component>
|
||||||
|
</project>
|
10
android-app/.idea/migrations.xml
generated
Normal file
10
android-app/.idea/migrations.xml
generated
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
10
android-app/.idea/misc.xml
generated
Normal file
10
android-app/.idea/misc.xml
generated
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
17
android-app/.idea/runConfigurations.xml
generated
Normal file
17
android-app/.idea/runConfigurations.xml
generated
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
android-app/.idea/vcs.xml
generated
Normal file
6
android-app/.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
57
android-app/README.md
Normal file
57
android-app/README.md
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# 3D Tetris - Android App
|
||||||
|
|
||||||
|
This is the native Android implementation of the 3D Tetris game. The mobile-specific optimizations from the web app have been removed, and instead, this native app has been created to provide a better experience on Android devices.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
- `app/src/main/java/com/tetris3d/` - Contains all Java/Kotlin source files
|
||||||
|
- `MainActivity.kt` - Main activity that hosts the game
|
||||||
|
- `game/` - Game logic implementation
|
||||||
|
- `views/` - Custom views for rendering game elements
|
||||||
|
|
||||||
|
- `app/src/main/res/` - Contains all resources
|
||||||
|
- `layout/` - XML layouts for the UI
|
||||||
|
- `values/` - String resources, colors, and themes
|
||||||
|
- `drawable/` - Icon and image resources
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 3D Tetris gameplay with modern graphics
|
||||||
|
- Customizable options for 3D effects and animations
|
||||||
|
- Physical button controls optimized for touch
|
||||||
|
- Score tracking and level progression
|
||||||
|
- Game state persistence
|
||||||
|
|
||||||
|
## Required Implementation
|
||||||
|
|
||||||
|
The following components still need to be implemented to complete the Android app:
|
||||||
|
|
||||||
|
1. TetrisGame class - Core game logic ported from JavaScript
|
||||||
|
2. TetrisGameView - Custom view for rendering the game
|
||||||
|
3. NextPieceView - Custom view for rendering the next piece preview
|
||||||
|
4. Tetromino classes - Classes for different tetromino pieces
|
||||||
|
5. Game renderer - OpenGL ES or Canvas-based renderer for the 3D effects
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- AndroidX libraries for UI components
|
||||||
|
- Kotlin coroutines for game loop threading
|
||||||
|
|
||||||
|
## Building and Running
|
||||||
|
|
||||||
|
1. Open the project in Android Studio
|
||||||
|
2. Build the project using Gradle
|
||||||
|
3. Deploy to an Android device or emulator
|
||||||
|
|
||||||
|
## Development Process
|
||||||
|
|
||||||
|
The Android app was created by:
|
||||||
|
|
||||||
|
1. Analyzing the web implementation of the game
|
||||||
|
2. Removing mobile-specific optimizations from the web code
|
||||||
|
3. Creating a native Android app structure
|
||||||
|
4. Implementing the UI layouts and resources
|
||||||
|
5. Porting the core game logic from JavaScript to Kotlin
|
||||||
|
6. Adding Android-specific features and optimizations
|
||||||
|
|
||||||
|
The core game mechanics are kept identical to the web version, ensuring a consistent experience across platforms.
|
50
android-app/app/build.gradle
Normal file
50
android-app/app/build.gradle
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace 'com.tetris3d'
|
||||||
|
compileSdk 33
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.tetris3d"
|
||||||
|
minSdk 26
|
||||||
|
targetSdk 33
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'androidx.core:core-ktx:1.10.1'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
|
implementation 'com.google.android.material:material:1.9.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
|
||||||
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
|
||||||
|
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
|
}
|
23
android-app/app/src/main/AndroidManifest.xml
Normal file
23
android-app/app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.Tetris3D">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/Theme.Tetris3D">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
254
android-app/app/src/main/java/com/tetris3d/MainActivity.kt
Normal file
254
android-app/app/src/main/java/com/tetris3d/MainActivity.kt
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
package com.tetris3d
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.NumberPicker
|
||||||
|
import android.widget.SeekBar
|
||||||
|
import android.widget.Switch
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.tetris3d.game.GameOptions
|
||||||
|
import com.tetris3d.game.TetrisGame
|
||||||
|
import com.tetris3d.views.NextPieceView
|
||||||
|
import com.tetris3d.views.TetrisGameView
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
private lateinit var gameView: TetrisGameView
|
||||||
|
private lateinit var nextPieceView: NextPieceView
|
||||||
|
private lateinit var scoreText: TextView
|
||||||
|
private lateinit var linesText: TextView
|
||||||
|
private lateinit var levelText: TextView
|
||||||
|
private lateinit var startButton: Button
|
||||||
|
private lateinit var pauseButton: Button
|
||||||
|
private lateinit var optionsButton: Button
|
||||||
|
|
||||||
|
private lateinit var tetrisGame: TetrisGame
|
||||||
|
private lateinit var gameOptions: GameOptions
|
||||||
|
|
||||||
|
private var gameOverDialog: Dialog? = null
|
||||||
|
private var optionsDialog: Dialog? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
|
initViews()
|
||||||
|
initGameOptions()
|
||||||
|
initGame()
|
||||||
|
setupButtonListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initViews() {
|
||||||
|
gameView = findViewById(R.id.tetrisGameView)
|
||||||
|
nextPieceView = findViewById(R.id.nextPieceView)
|
||||||
|
scoreText = findViewById(R.id.scoreText)
|
||||||
|
linesText = findViewById(R.id.linesText)
|
||||||
|
levelText = findViewById(R.id.levelText)
|
||||||
|
startButton = findViewById(R.id.startButton)
|
||||||
|
pauseButton = findViewById(R.id.pauseButton)
|
||||||
|
optionsButton = findViewById(R.id.optionsButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initGameOptions() {
|
||||||
|
gameOptions = GameOptions(
|
||||||
|
enable3DEffects = true,
|
||||||
|
enableSpinAnimations = true,
|
||||||
|
animationSpeed = 0.05f,
|
||||||
|
startingLevel = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Load saved options from SharedPreferences
|
||||||
|
val prefs = getSharedPreferences("TetrisOptions", MODE_PRIVATE)
|
||||||
|
gameOptions.enable3DEffects = prefs.getBoolean("enable3DEffects", true)
|
||||||
|
gameOptions.enableSpinAnimations = prefs.getBoolean("enableSpinAnimations", true)
|
||||||
|
gameOptions.animationSpeed = prefs.getFloat("animationSpeed", 0.05f)
|
||||||
|
gameOptions.startingLevel = prefs.getInt("startingLevel", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initGame() {
|
||||||
|
tetrisGame = TetrisGame(gameOptions)
|
||||||
|
gameView.setGame(tetrisGame)
|
||||||
|
nextPieceView.setGame(tetrisGame)
|
||||||
|
|
||||||
|
tetrisGame.setGameStateListener(object : TetrisGame.GameStateListener {
|
||||||
|
override fun onScoreChanged(score: Int) {
|
||||||
|
runOnUiThread {
|
||||||
|
scoreText.text = score.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLinesChanged(lines: Int) {
|
||||||
|
runOnUiThread {
|
||||||
|
linesText.text = lines.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLevelChanged(level: Int) {
|
||||||
|
runOnUiThread {
|
||||||
|
levelText.text = level.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGameOver(finalScore: Int) {
|
||||||
|
runOnUiThread {
|
||||||
|
showGameOverDialog(finalScore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNextPieceChanged() {
|
||||||
|
runOnUiThread {
|
||||||
|
nextPieceView.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start a new game automatically
|
||||||
|
tetrisGame.startNewGame()
|
||||||
|
updateControls()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupButtonListeners() {
|
||||||
|
startButton.setOnClickListener {
|
||||||
|
if (tetrisGame.isGameOver) {
|
||||||
|
tetrisGame.startNewGame()
|
||||||
|
} else {
|
||||||
|
tetrisGame.start()
|
||||||
|
}
|
||||||
|
updateControls()
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseButton.setOnClickListener {
|
||||||
|
if (tetrisGame.isRunning) {
|
||||||
|
tetrisGame.pause()
|
||||||
|
} else {
|
||||||
|
tetrisGame.resume()
|
||||||
|
}
|
||||||
|
updateControls()
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsButton.setOnClickListener {
|
||||||
|
showOptionsDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateControls() {
|
||||||
|
if (tetrisGame.isRunning) {
|
||||||
|
startButton.visibility = View.GONE
|
||||||
|
pauseButton.text = getString(R.string.pause)
|
||||||
|
} else if (tetrisGame.isGameOver) {
|
||||||
|
startButton.visibility = View.VISIBLE
|
||||||
|
startButton.text = getString(R.string.start)
|
||||||
|
pauseButton.text = getString(R.string.pause)
|
||||||
|
} else {
|
||||||
|
startButton.visibility = View.GONE
|
||||||
|
pauseButton.text = getString(R.string.start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showGameOverDialog(finalScore: Int) {
|
||||||
|
if (gameOverDialog != null && gameOverDialog!!.isShowing) {
|
||||||
|
gameOverDialog!!.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
val view = layoutInflater.inflate(R.layout.dialog_game_over, null)
|
||||||
|
val scoreText = view.findViewById<TextView>(R.id.textFinalScore)
|
||||||
|
val playAgainButton = view.findViewById<Button>(R.id.btnPlayAgain)
|
||||||
|
|
||||||
|
scoreText.text = finalScore.toString()
|
||||||
|
|
||||||
|
gameOverDialog = AlertDialog.Builder(this)
|
||||||
|
.setView(view)
|
||||||
|
.setCancelable(false)
|
||||||
|
.create()
|
||||||
|
|
||||||
|
playAgainButton.setOnClickListener {
|
||||||
|
gameOverDialog?.dismiss()
|
||||||
|
tetrisGame.startNewGame()
|
||||||
|
updateControls()
|
||||||
|
}
|
||||||
|
|
||||||
|
gameOverDialog?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showOptionsDialog() {
|
||||||
|
if (optionsDialog != null && optionsDialog!!.isShowing) {
|
||||||
|
optionsDialog!!.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
val view = layoutInflater.inflate(R.layout.dialog_options, null)
|
||||||
|
|
||||||
|
val switch3dEffects = view.findViewById<Switch>(R.id.switch3dEffects)
|
||||||
|
val switchSpinAnimations = view.findViewById<Switch>(R.id.switchSpinAnimations)
|
||||||
|
val seekBarSpeed = view.findViewById<SeekBar>(R.id.seekBarAnimationSpeed)
|
||||||
|
val numberPickerLevel = view.findViewById<NumberPicker>(R.id.numberPickerLevel)
|
||||||
|
val btnApply = view.findViewById<Button>(R.id.btnApplyOptions)
|
||||||
|
val btnClose = view.findViewById<Button>(R.id.btnCloseOptions)
|
||||||
|
|
||||||
|
// Set up controls with current options
|
||||||
|
switch3dEffects.isChecked = gameOptions.enable3DEffects
|
||||||
|
switchSpinAnimations.isChecked = gameOptions.enableSpinAnimations
|
||||||
|
|
||||||
|
// Convert animation speed (0.01-0.1) to progress (0-100)
|
||||||
|
val progress = ((gameOptions.animationSpeed - 0.01f) / 0.09f * 100).toInt()
|
||||||
|
seekBarSpeed.progress = progress
|
||||||
|
|
||||||
|
// Set up level picker
|
||||||
|
numberPickerLevel.minValue = 1
|
||||||
|
numberPickerLevel.maxValue = 10
|
||||||
|
numberPickerLevel.value = gameOptions.startingLevel
|
||||||
|
|
||||||
|
optionsDialog = AlertDialog.Builder(this)
|
||||||
|
.setView(view)
|
||||||
|
.setCancelable(true)
|
||||||
|
.create()
|
||||||
|
|
||||||
|
btnApply.setOnClickListener {
|
||||||
|
// Save new options
|
||||||
|
gameOptions.enable3DEffects = switch3dEffects.isChecked
|
||||||
|
gameOptions.enableSpinAnimations = switchSpinAnimations.isChecked
|
||||||
|
|
||||||
|
// Convert progress (0-100) to animation speed (0.01-0.1)
|
||||||
|
val animationSpeed = 0.01f + (seekBarSpeed.progress / 100f * 0.09f)
|
||||||
|
gameOptions.animationSpeed = animationSpeed
|
||||||
|
|
||||||
|
gameOptions.startingLevel = numberPickerLevel.value
|
||||||
|
|
||||||
|
// Apply options to game
|
||||||
|
tetrisGame.updateOptions(gameOptions)
|
||||||
|
|
||||||
|
// Save options to SharedPreferences
|
||||||
|
val prefs = getSharedPreferences("TetrisOptions", MODE_PRIVATE)
|
||||||
|
prefs.edit().apply {
|
||||||
|
putBoolean("enable3DEffects", gameOptions.enable3DEffects)
|
||||||
|
putBoolean("enableSpinAnimations", gameOptions.enableSpinAnimations)
|
||||||
|
putFloat("animationSpeed", gameOptions.animationSpeed)
|
||||||
|
putInt("startingLevel", gameOptions.startingLevel)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsDialog?.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
btnClose.setOnClickListener {
|
||||||
|
optionsDialog?.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsDialog?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
if (tetrisGame.isRunning) {
|
||||||
|
tetrisGame.pause()
|
||||||
|
updateControls()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
tetrisGame.stop()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.tetris3d.game
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that holds game configuration options
|
||||||
|
*/
|
||||||
|
data class GameOptions(
|
||||||
|
var enable3DEffects: Boolean = true,
|
||||||
|
var enableSpinAnimations: Boolean = true,
|
||||||
|
var animationSpeed: Float = 0.05f,
|
||||||
|
var startingLevel: Int = 1
|
||||||
|
)
|
722
android-app/app/src/main/java/com/tetris3d/game/TetrisGame.kt
Normal file
722
android-app/app/src/main/java/com/tetris3d/game/TetrisGame.kt
Normal file
|
@ -0,0 +1,722 @@
|
||||||
|
package com.tetris3d.game
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main class that handles Tetris game logic
|
||||||
|
*/
|
||||||
|
class TetrisGame(private var options: GameOptions) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ROWS = 20
|
||||||
|
const val COLS = 10
|
||||||
|
const val EMPTY = "black"
|
||||||
|
|
||||||
|
// 3D rotation directions
|
||||||
|
private const val ROTATION_X = 0
|
||||||
|
private const val ROTATION_Y = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game state
|
||||||
|
var isRunning = false
|
||||||
|
var isGameOver = false
|
||||||
|
private var score = 0
|
||||||
|
private var lines = 0
|
||||||
|
private var level = options.startingLevel
|
||||||
|
|
||||||
|
// Board representation
|
||||||
|
private val board = Array(ROWS) { Array(COLS) { EMPTY } }
|
||||||
|
|
||||||
|
// Piece definitions
|
||||||
|
private val pieces = listOf(
|
||||||
|
// I piece - line
|
||||||
|
listOf(
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 0, 0),
|
||||||
|
arrayOf(1, 1, 1, 1),
|
||||||
|
arrayOf(0, 0, 0, 0),
|
||||||
|
arrayOf(0, 0, 0, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 1, 0),
|
||||||
|
arrayOf(0, 0, 1, 0),
|
||||||
|
arrayOf(0, 0, 1, 0),
|
||||||
|
arrayOf(0, 0, 1, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 0, 0),
|
||||||
|
arrayOf(0, 0, 0, 0),
|
||||||
|
arrayOf(1, 1, 1, 1),
|
||||||
|
arrayOf(0, 0, 0, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 1, 0, 0),
|
||||||
|
arrayOf(0, 1, 0, 0),
|
||||||
|
arrayOf(0, 1, 0, 0),
|
||||||
|
arrayOf(0, 1, 0, 0)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// J piece
|
||||||
|
listOf(
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(1, 0, 0),
|
||||||
|
arrayOf(1, 1, 1),
|
||||||
|
arrayOf(0, 0, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 1, 1),
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(0, 1, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 0),
|
||||||
|
arrayOf(1, 1, 1),
|
||||||
|
arrayOf(0, 0, 1)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(1, 1, 0)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// L piece
|
||||||
|
listOf(
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 1),
|
||||||
|
arrayOf(1, 1, 1),
|
||||||
|
arrayOf(0, 0, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(0, 1, 1)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 0),
|
||||||
|
arrayOf(1, 1, 1),
|
||||||
|
arrayOf(1, 0, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(1, 1, 0),
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(0, 1, 0)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// O piece - square
|
||||||
|
listOf(
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 0, 0),
|
||||||
|
arrayOf(0, 1, 1, 0),
|
||||||
|
arrayOf(0, 1, 1, 0),
|
||||||
|
arrayOf(0, 0, 0, 0)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// S piece
|
||||||
|
listOf(
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 1, 1),
|
||||||
|
arrayOf(1, 1, 0),
|
||||||
|
arrayOf(0, 0, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(0, 1, 1),
|
||||||
|
arrayOf(0, 0, 1)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 0),
|
||||||
|
arrayOf(0, 1, 1),
|
||||||
|
arrayOf(1, 1, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(1, 0, 0),
|
||||||
|
arrayOf(1, 1, 0),
|
||||||
|
arrayOf(0, 1, 0)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// T piece
|
||||||
|
listOf(
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(1, 1, 1),
|
||||||
|
arrayOf(0, 0, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(0, 1, 1),
|
||||||
|
arrayOf(0, 1, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 0),
|
||||||
|
arrayOf(1, 1, 1),
|
||||||
|
arrayOf(0, 1, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(1, 1, 0),
|
||||||
|
arrayOf(0, 1, 0)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// Z piece
|
||||||
|
listOf(
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(1, 1, 0),
|
||||||
|
arrayOf(0, 1, 1),
|
||||||
|
arrayOf(0, 0, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 1),
|
||||||
|
arrayOf(0, 1, 1),
|
||||||
|
arrayOf(0, 1, 0)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 0, 0),
|
||||||
|
arrayOf(1, 1, 0),
|
||||||
|
arrayOf(0, 1, 1)
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
arrayOf(0, 1, 0),
|
||||||
|
arrayOf(1, 1, 0),
|
||||||
|
arrayOf(1, 0, 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 3D rotation state
|
||||||
|
private var rotation3DX = 0
|
||||||
|
private var rotation3DY = 0
|
||||||
|
private val maxRotation3D = 4 // Increased from typical 2 to allow for more granular rotation
|
||||||
|
|
||||||
|
// 3D rotation animation
|
||||||
|
private var isRotating = false
|
||||||
|
private var rotationProgress = 0f
|
||||||
|
private var targetRotationX = 0
|
||||||
|
private var targetRotationY = 0
|
||||||
|
private var currentRotation3DX = 0f
|
||||||
|
private var currentRotation3DY = 0f
|
||||||
|
|
||||||
|
// Piece colors
|
||||||
|
private val colors = listOf(
|
||||||
|
"#00FFFF", // cyan - I
|
||||||
|
"#0000FF", // blue - J
|
||||||
|
"#FFA500", // orange - L
|
||||||
|
"#FFFF00", // yellow - O
|
||||||
|
"#00FF00", // green - S
|
||||||
|
"#800080", // purple - T
|
||||||
|
"#FF0000" // red - Z
|
||||||
|
)
|
||||||
|
|
||||||
|
// Current piece state
|
||||||
|
private var currentPiece: Int = 0
|
||||||
|
private var currentRotation: Int = 0
|
||||||
|
private var currentX: Int = 0
|
||||||
|
private var currentY: Int = 0
|
||||||
|
private var currentColor: String = ""
|
||||||
|
|
||||||
|
// Next piece
|
||||||
|
private var nextPiece: Int = 0
|
||||||
|
private var nextColor: String = ""
|
||||||
|
|
||||||
|
// Random bag implementation
|
||||||
|
private val pieceBag = mutableListOf<Int>()
|
||||||
|
private val nextBag = mutableListOf<Int>()
|
||||||
|
|
||||||
|
// Game loop
|
||||||
|
private val gameHandler = Handler(Looper.getMainLooper())
|
||||||
|
private val gameRunnable = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
if (isRunning && !isGameOver) {
|
||||||
|
// Update rotation animation
|
||||||
|
updateRotation()
|
||||||
|
|
||||||
|
// Move the current piece down
|
||||||
|
if (!moveDown()) {
|
||||||
|
// If can't move down, lock the piece
|
||||||
|
lockPiece()
|
||||||
|
clearRows()
|
||||||
|
if (!createNewPiece()) {
|
||||||
|
gameOver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gameHandler.postDelayed(this, getDropInterval())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var gameStateListener: GameStateListener? = null
|
||||||
|
|
||||||
|
interface GameStateListener {
|
||||||
|
fun onScoreChanged(score: Int)
|
||||||
|
fun onLinesChanged(lines: Int)
|
||||||
|
fun onLevelChanged(level: Int)
|
||||||
|
fun onGameOver(finalScore: Int)
|
||||||
|
fun onNextPieceChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setGameStateListener(listener: GameStateListener) {
|
||||||
|
this.gameStateListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
if (!isRunning) {
|
||||||
|
isRunning = true
|
||||||
|
isGameOver = false
|
||||||
|
gameHandler.postDelayed(gameRunnable, getDropInterval())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pause() {
|
||||||
|
isRunning = false
|
||||||
|
gameHandler.removeCallbacks(gameRunnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resume() {
|
||||||
|
if (!isGameOver) {
|
||||||
|
isRunning = true
|
||||||
|
gameHandler.postDelayed(gameRunnable, getDropInterval())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
isRunning = false
|
||||||
|
gameHandler.removeCallbacks(gameRunnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startNewGame() {
|
||||||
|
// Reset game state
|
||||||
|
isRunning = true
|
||||||
|
isGameOver = false
|
||||||
|
score = 0
|
||||||
|
lines = 0
|
||||||
|
level = options.startingLevel
|
||||||
|
|
||||||
|
// Reset 3D rotation state
|
||||||
|
rotation3DX = 0
|
||||||
|
rotation3DY = 0
|
||||||
|
|
||||||
|
// Clear the board
|
||||||
|
for (r in 0 until ROWS) {
|
||||||
|
for (c in 0 until COLS) {
|
||||||
|
board[r][c] = EMPTY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset piece bags
|
||||||
|
pieceBag.clear()
|
||||||
|
nextBag.clear()
|
||||||
|
|
||||||
|
// Create first piece
|
||||||
|
generateBag()
|
||||||
|
createNewPiece()
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
gameStateListener?.onScoreChanged(score)
|
||||||
|
gameStateListener?.onLinesChanged(lines)
|
||||||
|
gameStateListener?.onLevelChanged(level)
|
||||||
|
|
||||||
|
// Start game loop
|
||||||
|
gameHandler.removeCallbacks(gameRunnable)
|
||||||
|
gameHandler.postDelayed(gameRunnable, getDropInterval())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateOptions(options: GameOptions) {
|
||||||
|
this.options = options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game control methods
|
||||||
|
fun moveLeft(): Boolean {
|
||||||
|
if (isRunning && !isGameOver) {
|
||||||
|
if (!checkCollision(currentX - 1, currentY, getCurrentPieceArray())) {
|
||||||
|
currentX--
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveRight(): Boolean {
|
||||||
|
if (isRunning && !isGameOver) {
|
||||||
|
if (!checkCollision(currentX + 1, currentY, getCurrentPieceArray())) {
|
||||||
|
currentX++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveDown(): Boolean {
|
||||||
|
if (isRunning && !isGameOver) {
|
||||||
|
if (!checkCollision(currentX, currentY + 1, getCurrentPieceArray())) {
|
||||||
|
currentY++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rotate(): Boolean {
|
||||||
|
if (isRunning && !isGameOver) {
|
||||||
|
val nextRotation = (currentRotation + 1) % pieces[currentPiece].size
|
||||||
|
val nextPattern = pieces[currentPiece][nextRotation]
|
||||||
|
|
||||||
|
if (!checkCollision(currentX, currentY, nextPattern)) {
|
||||||
|
currentRotation = nextRotation
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// Try wall kicks
|
||||||
|
// Try moving right
|
||||||
|
if (!checkCollision(currentX + 1, currentY, nextPattern)) {
|
||||||
|
currentX++
|
||||||
|
currentRotation = nextRotation
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Try moving left
|
||||||
|
if (!checkCollision(currentX - 1, currentY, nextPattern)) {
|
||||||
|
currentX--
|
||||||
|
currentRotation = nextRotation
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Try moving up (for I piece mostly)
|
||||||
|
if (!checkCollision(currentX, currentY - 1, nextPattern)) {
|
||||||
|
currentY--
|
||||||
|
currentRotation = nextRotation
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hardDrop(): Boolean {
|
||||||
|
if (isRunning && !isGameOver) {
|
||||||
|
while (moveDown()) {}
|
||||||
|
lockPiece()
|
||||||
|
clearRows()
|
||||||
|
if (!createNewPiece()) {
|
||||||
|
gameOver()
|
||||||
|
} else {
|
||||||
|
// Add extra points for hard drop
|
||||||
|
score += 2
|
||||||
|
gameStateListener?.onScoreChanged(score)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rotate3DX(): Boolean {
|
||||||
|
if (isRunning && !isGameOver && options.enable3DEffects) {
|
||||||
|
// In 3D, rotating along X would change the way the piece appears from front/back
|
||||||
|
rotation3DX = (rotation3DX + 1) % maxRotation3D
|
||||||
|
|
||||||
|
// Start rotation animation
|
||||||
|
if (options.enableSpinAnimations) {
|
||||||
|
isRotating = true
|
||||||
|
targetRotationX = rotation3DX
|
||||||
|
rotationProgress = 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a quarter or three-quarter rotation, actually change the piece orientation
|
||||||
|
if (rotation3DX % (maxRotation3D / 2) == 1) {
|
||||||
|
// This simulates a 3D rotation by performing a 2D rotation
|
||||||
|
return rotate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add extra score for 3D rotations when they don't result in a piece rotation
|
||||||
|
score += 1
|
||||||
|
gameStateListener?.onScoreChanged(score)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rotate3DY(): Boolean {
|
||||||
|
if (isRunning && !isGameOver && options.enable3DEffects) {
|
||||||
|
// In 3D, rotating along Y would change the way the piece appears from left/right
|
||||||
|
rotation3DY = (rotation3DY + 1) % maxRotation3D
|
||||||
|
|
||||||
|
// Start rotation animation
|
||||||
|
if (options.enableSpinAnimations) {
|
||||||
|
isRotating = true
|
||||||
|
targetRotationY = rotation3DY
|
||||||
|
rotationProgress = 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a quarter or three-quarter rotation, actually change the piece orientation
|
||||||
|
if (rotation3DY % (maxRotation3D / 2) == 1) {
|
||||||
|
// This simulates a 3D rotation by performing a 2D rotation in the opposite direction
|
||||||
|
val nextRotation = (currentRotation - 1 + pieces[currentPiece].size) % pieces[currentPiece].size
|
||||||
|
val nextPattern = pieces[currentPiece][nextRotation]
|
||||||
|
|
||||||
|
if (!checkCollision(currentX, currentY, nextPattern)) {
|
||||||
|
currentRotation = nextRotation
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// Try wall kicks
|
||||||
|
// Try moving right
|
||||||
|
if (!checkCollision(currentX + 1, currentY, nextPattern)) {
|
||||||
|
currentX++
|
||||||
|
currentRotation = nextRotation
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Try moving left
|
||||||
|
if (!checkCollision(currentX - 1, currentY, nextPattern)) {
|
||||||
|
currentX--
|
||||||
|
currentRotation = nextRotation
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Try moving up (for I piece mostly)
|
||||||
|
if (!checkCollision(currentX, currentY - 1, nextPattern)) {
|
||||||
|
currentY--
|
||||||
|
currentRotation = nextRotation
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add extra score for 3D rotations when they don't result in a piece rotation
|
||||||
|
score += 1
|
||||||
|
gameStateListener?.onScoreChanged(score)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to update rotation animation
|
||||||
|
fun updateRotation() {
|
||||||
|
if (isRotating && options.enableSpinAnimations) {
|
||||||
|
rotationProgress += options.animationSpeed
|
||||||
|
if (rotationProgress >= 1f) {
|
||||||
|
// Animation complete
|
||||||
|
rotationProgress = 1f
|
||||||
|
isRotating = false
|
||||||
|
currentRotation3DX = targetRotationX.toFloat()
|
||||||
|
currentRotation3DY = targetRotationY.toFloat()
|
||||||
|
} else {
|
||||||
|
// Smooth interpolation for rotation
|
||||||
|
currentRotation3DX = rotation3DX * rotationProgress +
|
||||||
|
(rotation3DX - 1 + maxRotation3D) % maxRotation3D * (1f - rotationProgress)
|
||||||
|
currentRotation3DY = rotation3DY * rotationProgress +
|
||||||
|
(rotation3DY - 1 + maxRotation3D) % maxRotation3D * (1f - rotationProgress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateBag() {
|
||||||
|
if (pieceBag.isEmpty()) {
|
||||||
|
// If both bags are empty, initialize both
|
||||||
|
if (nextBag.isEmpty()) {
|
||||||
|
// Fill the next bag with 0-6 (all 7 pieces) in random order
|
||||||
|
val tempBag = (0..6).toMutableList()
|
||||||
|
tempBag.shuffle()
|
||||||
|
nextBag.addAll(tempBag)
|
||||||
|
}
|
||||||
|
// Move the next bag to current and create a new next bag
|
||||||
|
pieceBag.addAll(nextBag)
|
||||||
|
nextBag.clear()
|
||||||
|
|
||||||
|
// Fill the next bag again
|
||||||
|
val tempBag = (0..6).toMutableList()
|
||||||
|
tempBag.shuffle()
|
||||||
|
nextBag.addAll(tempBag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNextPieceFromBag(): Int {
|
||||||
|
if (pieceBag.isEmpty()) {
|
||||||
|
generateBag()
|
||||||
|
}
|
||||||
|
return pieceBag.removeAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNewPiece(): Boolean {
|
||||||
|
// Get next piece from bag
|
||||||
|
currentPiece = nextPiece
|
||||||
|
currentColor = nextColor
|
||||||
|
|
||||||
|
// Generate next piece
|
||||||
|
nextPiece = getNextPieceFromBag()
|
||||||
|
nextColor = colors[nextPiece]
|
||||||
|
|
||||||
|
// If it's the first piece, generate the current one too
|
||||||
|
if (currentColor.isEmpty()) {
|
||||||
|
currentPiece = getNextPieceFromBag()
|
||||||
|
currentColor = colors[currentPiece]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset position and rotation
|
||||||
|
currentRotation = 0
|
||||||
|
currentX = COLS / 2 - 2
|
||||||
|
currentY = 0
|
||||||
|
|
||||||
|
// Notify next piece changed
|
||||||
|
gameStateListener?.onNextPieceChanged()
|
||||||
|
|
||||||
|
// Check if game over (collision at starting position)
|
||||||
|
return !checkCollision(currentX, currentY, getCurrentPieceArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun lockPiece() {
|
||||||
|
val piece = getCurrentPieceArray()
|
||||||
|
|
||||||
|
for (r in piece.indices) {
|
||||||
|
for (c in piece[r].indices) {
|
||||||
|
if (piece[r][c] == 1) {
|
||||||
|
val boardRow = currentY + r
|
||||||
|
val boardCol = currentX + c
|
||||||
|
|
||||||
|
if (boardRow >= 0 && boardRow < ROWS && boardCol >= 0 && boardCol < COLS) {
|
||||||
|
board[boardRow][boardCol] = currentColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearRows() {
|
||||||
|
var linesCleared = 0
|
||||||
|
|
||||||
|
for (r in 0 until ROWS) {
|
||||||
|
var rowFull = true
|
||||||
|
|
||||||
|
for (c in 0 until COLS) {
|
||||||
|
if (board[r][c] == EMPTY) {
|
||||||
|
rowFull = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowFull) {
|
||||||
|
// Move all rows above down
|
||||||
|
for (y in r downTo 1) {
|
||||||
|
for (c in 0 until COLS) {
|
||||||
|
board[y][c] = board[y - 1][c]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear top row
|
||||||
|
for (c in 0 until COLS) {
|
||||||
|
board[0][c] = EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
linesCleared++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linesCleared > 0) {
|
||||||
|
// Update lines and score
|
||||||
|
lines += linesCleared
|
||||||
|
|
||||||
|
// Calculate score based on lines cleared and level
|
||||||
|
when (linesCleared) {
|
||||||
|
1 -> score += 100 * level
|
||||||
|
2 -> score += 300 * level
|
||||||
|
3 -> score += 500 * level
|
||||||
|
4 -> score += 800 * level
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update level (every 10 lines)
|
||||||
|
level = (lines / 10) + options.startingLevel
|
||||||
|
|
||||||
|
// Notify listeners
|
||||||
|
gameStateListener?.onScoreChanged(score)
|
||||||
|
gameStateListener?.onLinesChanged(lines)
|
||||||
|
gameStateListener?.onLevelChanged(level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkCollision(x: Int, y: Int, piece: Array<Array<Int>>): Boolean {
|
||||||
|
for (r in piece.indices) {
|
||||||
|
for (c in piece[r].indices) {
|
||||||
|
if (piece[r][c] == 1) {
|
||||||
|
val boardRow = y + r
|
||||||
|
val boardCol = x + c
|
||||||
|
|
||||||
|
// Check boundaries
|
||||||
|
if (boardCol < 0 || boardCol >= COLS || boardRow >= ROWS) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip check above the board
|
||||||
|
if (boardRow < 0) continue
|
||||||
|
|
||||||
|
// Check if position already filled
|
||||||
|
if (board[boardRow][boardCol] != EMPTY) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun gameOver() {
|
||||||
|
isRunning = false
|
||||||
|
isGameOver = true
|
||||||
|
gameHandler.removeCallbacks(gameRunnable)
|
||||||
|
gameStateListener?.onGameOver(score)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDropInterval(): Long {
|
||||||
|
// Speed increases with level
|
||||||
|
return (1000 * Math.pow(0.8, (level - 1).toDouble())).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters for rendering
|
||||||
|
fun getBoard(): Array<Array<String>> {
|
||||||
|
return board
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentPiece(): Int {
|
||||||
|
return currentPiece
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentRotation(): Int {
|
||||||
|
return currentRotation
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentX(): Int {
|
||||||
|
return currentX
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentY(): Int {
|
||||||
|
return currentY
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentColor(): String {
|
||||||
|
return currentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNextPiece(): Int {
|
||||||
|
return nextPiece
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNextColor(): String {
|
||||||
|
return nextColor
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentPieceArray(): Array<Array<Int>> {
|
||||||
|
return pieces[currentPiece][currentRotation]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNextPieceArray(): Array<Array<Int>> {
|
||||||
|
return pieces[nextPiece][0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateShadowY(): Int {
|
||||||
|
var shadowY = currentY
|
||||||
|
|
||||||
|
while (!checkCollision(currentX, shadowY + 1, getCurrentPieceArray())) {
|
||||||
|
shadowY++
|
||||||
|
}
|
||||||
|
|
||||||
|
return shadowY
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current rotation values for rendering
|
||||||
|
fun getRotation3DX(): Float = if (isRotating) currentRotation3DX else rotation3DX.toFloat()
|
||||||
|
fun getRotation3DY(): Float = if (isRotating) currentRotation3DY else rotation3DY.toFloat()
|
||||||
|
fun isRotating(): Boolean = isRotating
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package com.tetris3d.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.LinearGradient
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.Shader
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import com.tetris3d.game.TetrisGame
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom view for rendering the next Tetris piece
|
||||||
|
*/
|
||||||
|
class NextPieceView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : View(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
private var game: TetrisGame? = null
|
||||||
|
private val paint = Paint()
|
||||||
|
private var blockSize = 0f
|
||||||
|
|
||||||
|
// Background gradient colors
|
||||||
|
private val bgColorStart = Color.parseColor("#1a1a2e")
|
||||||
|
private val bgColorEnd = Color.parseColor("#0f3460")
|
||||||
|
private lateinit var bgGradient: LinearGradient
|
||||||
|
|
||||||
|
fun setGame(game: TetrisGame) {
|
||||||
|
this.game = game
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh)
|
||||||
|
|
||||||
|
// Create background gradient
|
||||||
|
bgGradient = LinearGradient(
|
||||||
|
0f, 0f, w.toFloat(), h.toFloat(),
|
||||||
|
bgColorStart, bgColorEnd,
|
||||||
|
Shader.TileMode.CLAMP
|
||||||
|
)
|
||||||
|
|
||||||
|
// Determine block size based on the smaller dimension
|
||||||
|
blockSize = (Math.min(width, height) / 4).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
super.onDraw(canvas)
|
||||||
|
|
||||||
|
val game = this.game ?: return
|
||||||
|
|
||||||
|
// Draw gradient background
|
||||||
|
paint.shader = bgGradient
|
||||||
|
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
|
||||||
|
paint.shader = null
|
||||||
|
|
||||||
|
// Draw border with glow effect
|
||||||
|
drawBorder(canvas)
|
||||||
|
|
||||||
|
// Draw the next piece
|
||||||
|
drawNextPiece(canvas, game)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawBorder(canvas: Canvas) {
|
||||||
|
// Draw a glowing border
|
||||||
|
val borderRect = RectF(2f, 2f, width - 2f, height - 2f)
|
||||||
|
|
||||||
|
// Outer glow (cyan color like in the web app)
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
paint.strokeWidth = 2f
|
||||||
|
paint.color = Color.parseColor("#00ffff")
|
||||||
|
paint.setShadowLayer(5f, 0f, 0f, Color.parseColor("#00ffff"))
|
||||||
|
canvas.drawRect(borderRect, paint)
|
||||||
|
paint.setShadowLayer(0f, 0f, 0f, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawNextPiece(canvas: Canvas, game: TetrisGame) {
|
||||||
|
val piece = game.getNextPieceArray()
|
||||||
|
val color = game.getNextColor()
|
||||||
|
|
||||||
|
// Center the piece in the view
|
||||||
|
val offsetX = (width - piece[0].size * blockSize) / 2
|
||||||
|
val offsetY = (height - piece.size * blockSize) / 2
|
||||||
|
|
||||||
|
for (r in piece.indices) {
|
||||||
|
for (c in piece[r].indices) {
|
||||||
|
if (piece[r][c] == 1) {
|
||||||
|
drawBlock(canvas, offsetX + c * blockSize, offsetY + r * blockSize, color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawBlock(canvas: Canvas, x: Float, y: Float, colorStr: String) {
|
||||||
|
val left = x
|
||||||
|
val top = y
|
||||||
|
val right = left + blockSize
|
||||||
|
val bottom = top + blockSize
|
||||||
|
val blockRect = RectF(left, top, right, bottom)
|
||||||
|
|
||||||
|
// Draw the block fill
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
paint.color = Color.parseColor(colorStr)
|
||||||
|
canvas.drawRect(blockRect, paint)
|
||||||
|
|
||||||
|
// Draw the highlight (top-left gradient)
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
val highlightPaint = Paint()
|
||||||
|
highlightPaint.shader = LinearGradient(
|
||||||
|
left, top,
|
||||||
|
right, bottom,
|
||||||
|
Color.argb(120, 255, 255, 255),
|
||||||
|
Color.argb(0, 255, 255, 255),
|
||||||
|
Shader.TileMode.CLAMP
|
||||||
|
)
|
||||||
|
canvas.drawRect(blockRect, highlightPaint)
|
||||||
|
|
||||||
|
// Draw the block border
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
paint.strokeWidth = 2f
|
||||||
|
paint.color = Color.BLACK
|
||||||
|
canvas.drawRect(blockRect, paint)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,460 @@
|
||||||
|
package com.tetris3d.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.LinearGradient
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.Shader
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.animation.DecelerateInterpolator
|
||||||
|
import com.tetris3d.game.TetrisGame
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom view for rendering the Tetris game board
|
||||||
|
*/
|
||||||
|
class TetrisGameView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : View(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
private var game: TetrisGame? = null
|
||||||
|
private val paint = Paint()
|
||||||
|
private var blockSize = 0f
|
||||||
|
private var boardLeft = 0f
|
||||||
|
private var boardTop = 0f
|
||||||
|
|
||||||
|
// Shadow and grid configuration
|
||||||
|
private val showShadow = true
|
||||||
|
private val showGrid = true
|
||||||
|
|
||||||
|
// Background gradient colors
|
||||||
|
private val bgColorStart = Color.parseColor("#1a1a2e")
|
||||||
|
private val bgColorEnd = Color.parseColor("#0f3460")
|
||||||
|
private lateinit var bgGradient: LinearGradient
|
||||||
|
|
||||||
|
// Gesture detection for swipe controls
|
||||||
|
private val gestureDetector = GestureDetector(context, TetrisGestureListener())
|
||||||
|
|
||||||
|
// Define minimum swipe velocity and distance
|
||||||
|
private val minSwipeVelocity = 50 // Lowered for better sensitivity
|
||||||
|
private val minSwipeDistance = 20 // Lowered for better sensitivity
|
||||||
|
|
||||||
|
// Movement control
|
||||||
|
private val autoRepeatHandler = Handler(Looper.getMainLooper())
|
||||||
|
private var isAutoRepeating = false
|
||||||
|
private var currentMovement: (() -> Unit)? = null
|
||||||
|
private val autoRepeatDelay = 40L // Faster repeat for smoother movement
|
||||||
|
private val initialAutoRepeatDelay = 100L // Initial delay before repeating
|
||||||
|
private val interpolator = DecelerateInterpolator(1.5f)
|
||||||
|
|
||||||
|
// Touch tracking for continuous swipe
|
||||||
|
private var lastTouchX = 0f
|
||||||
|
private var lastTouchY = 0f
|
||||||
|
private var swipeThreshold = 15f // Distance needed to trigger a move while dragging
|
||||||
|
|
||||||
|
// Refresh timer
|
||||||
|
private val refreshHandler = Handler(Looper.getMainLooper())
|
||||||
|
private val refreshRunnable = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
// Update the game rotation animation
|
||||||
|
game?.updateRotation()
|
||||||
|
invalidate()
|
||||||
|
refreshHandler.postDelayed(this, REFRESH_INTERVAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REFRESH_INTERVAL = 16L // ~60fps
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setGame(game: TetrisGame) {
|
||||||
|
this.game = game
|
||||||
|
invalidate()
|
||||||
|
|
||||||
|
// Start refresh timer
|
||||||
|
startRefreshTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startRefreshTimer() {
|
||||||
|
refreshHandler.removeCallbacks(refreshRunnable)
|
||||||
|
refreshHandler.postDelayed(refreshRunnable, REFRESH_INTERVAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopRefreshTimer() {
|
||||||
|
refreshHandler.removeCallbacks(refreshRunnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh)
|
||||||
|
|
||||||
|
// Create background gradient
|
||||||
|
bgGradient = LinearGradient(
|
||||||
|
0f, 0f, w.toFloat(), h.toFloat(),
|
||||||
|
bgColorStart, bgColorEnd,
|
||||||
|
Shader.TileMode.CLAMP
|
||||||
|
)
|
||||||
|
|
||||||
|
// Calculate block size based on available space
|
||||||
|
val rows = TetrisGame.ROWS
|
||||||
|
val cols = TetrisGame.COLS
|
||||||
|
|
||||||
|
// Determine the maximum block size that will fit in the view
|
||||||
|
val maxBlockWidth = width.toFloat() / cols
|
||||||
|
val maxBlockHeight = height.toFloat() / rows
|
||||||
|
|
||||||
|
// Use the smaller dimension to ensure squares
|
||||||
|
blockSize = min(maxBlockWidth, maxBlockHeight)
|
||||||
|
|
||||||
|
// Center the board
|
||||||
|
boardLeft = (width - cols * blockSize) / 2
|
||||||
|
boardTop = (height - rows * blockSize) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
super.onDraw(canvas)
|
||||||
|
|
||||||
|
val game = this.game ?: return
|
||||||
|
|
||||||
|
// Draw gradient background
|
||||||
|
paint.shader = bgGradient
|
||||||
|
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
|
||||||
|
paint.shader = null
|
||||||
|
|
||||||
|
// Draw grid if enabled
|
||||||
|
if (showGrid) {
|
||||||
|
drawGrid(canvas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw board border with glow effect
|
||||||
|
drawBoardBorder(canvas)
|
||||||
|
|
||||||
|
// Draw the locked pieces on the board
|
||||||
|
drawBoard(canvas, game)
|
||||||
|
|
||||||
|
// Draw shadow piece if enabled
|
||||||
|
if (showShadow && !game.isGameOver && game.isRunning) {
|
||||||
|
drawShadowPiece(canvas, game)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw current active piece with 3D rotation effect
|
||||||
|
if (!game.isGameOver) {
|
||||||
|
drawActivePiece(canvas, game)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawBoardBorder(canvas: Canvas) {
|
||||||
|
// Draw a glowing border around the game board
|
||||||
|
val borderRect = RectF(
|
||||||
|
boardLeft - 4f,
|
||||||
|
boardTop - 4f,
|
||||||
|
boardLeft + TetrisGame.COLS * blockSize + 4f,
|
||||||
|
boardTop + TetrisGame.ROWS * blockSize + 4f
|
||||||
|
)
|
||||||
|
|
||||||
|
// Outer glow (cyan color like in the web app)
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
paint.strokeWidth = 4f
|
||||||
|
paint.color = Color.parseColor("#00ffff")
|
||||||
|
paint.setShadowLayer(8f, 0f, 0f, Color.parseColor("#00ffff"))
|
||||||
|
canvas.drawRect(borderRect, paint)
|
||||||
|
paint.setShadowLayer(0f, 0f, 0f, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawBoard(canvas: Canvas, game: TetrisGame) {
|
||||||
|
val board = game.getBoard()
|
||||||
|
|
||||||
|
for (r in 0 until TetrisGame.ROWS) {
|
||||||
|
for (c in 0 until TetrisGame.COLS) {
|
||||||
|
val color = board[r][c]
|
||||||
|
if (color != TetrisGame.EMPTY) {
|
||||||
|
drawBlock(canvas, c, r, color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawGrid(canvas: Canvas) {
|
||||||
|
paint.color = Color.parseColor("#222222")
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
paint.strokeWidth = 1f
|
||||||
|
|
||||||
|
// Draw vertical lines
|
||||||
|
for (c in 0..TetrisGame.COLS) {
|
||||||
|
val x = boardLeft + c * blockSize
|
||||||
|
canvas.drawLine(x, boardTop, x, boardTop + TetrisGame.ROWS * blockSize, paint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw horizontal lines
|
||||||
|
for (r in 0..TetrisGame.ROWS) {
|
||||||
|
val y = boardTop + r * blockSize
|
||||||
|
canvas.drawLine(boardLeft, y, boardLeft + TetrisGame.COLS * blockSize, y, paint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawActivePiece(canvas: Canvas, game: TetrisGame) {
|
||||||
|
val piece = game.getCurrentPieceArray()
|
||||||
|
val x = game.getCurrentX()
|
||||||
|
val y = game.getCurrentY()
|
||||||
|
val color = game.getCurrentColor()
|
||||||
|
|
||||||
|
// Save canvas state for rotation
|
||||||
|
canvas.save()
|
||||||
|
|
||||||
|
// Get 3D rotation values (0-3 for each axis)
|
||||||
|
val rotationX = game.getRotation3DX()
|
||||||
|
val rotationY = game.getRotation3DY()
|
||||||
|
|
||||||
|
// Convert rotation to radians (0-2π)
|
||||||
|
val angleX = rotationX * Math.PI / 2
|
||||||
|
val angleY = rotationY * Math.PI / 2
|
||||||
|
|
||||||
|
// Calculate center point of the piece for rotation
|
||||||
|
val centerX = boardLeft + (x + piece[0].size / 2f) * blockSize
|
||||||
|
val centerY = boardTop + (y + piece.size / 2f) * blockSize
|
||||||
|
|
||||||
|
// Translate to center point, rotate, then translate back
|
||||||
|
canvas.translate(centerX, centerY)
|
||||||
|
|
||||||
|
// Apply 3D perspective scaling based on rotation angles
|
||||||
|
val scaleX = cos(angleY.toFloat()).coerceAtLeast(0.5f)
|
||||||
|
val scaleY = cos(angleX.toFloat()).coerceAtLeast(0.5f)
|
||||||
|
canvas.scale(scaleX, scaleY)
|
||||||
|
|
||||||
|
// Translate back
|
||||||
|
canvas.translate(-centerX, -centerY)
|
||||||
|
|
||||||
|
// Draw the piece with perspective
|
||||||
|
for (r in piece.indices) {
|
||||||
|
for (c in piece[r].indices) {
|
||||||
|
if (piece[r][c] == 1) {
|
||||||
|
// Calculate position with perspective effect
|
||||||
|
val offsetX = sin(angleY.toFloat()) * blockSize * 0.2f
|
||||||
|
val offsetY = sin(angleX.toFloat()) * blockSize * 0.2f
|
||||||
|
|
||||||
|
drawBlock(
|
||||||
|
canvas,
|
||||||
|
x + c,
|
||||||
|
y + r,
|
||||||
|
color,
|
||||||
|
offsetX = offsetX,
|
||||||
|
offsetY = offsetY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore canvas state
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawShadowPiece(canvas: Canvas, game: TetrisGame) {
|
||||||
|
val piece = game.getCurrentPieceArray()
|
||||||
|
val x = game.getCurrentX()
|
||||||
|
val y = game.calculateShadowY()
|
||||||
|
|
||||||
|
if (y == game.getCurrentY()) {
|
||||||
|
return // Skip if shadow is at the same position as the piece
|
||||||
|
}
|
||||||
|
|
||||||
|
paint.color = Color.parseColor("#444444")
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
paint.strokeWidth = 2f
|
||||||
|
|
||||||
|
for (r in piece.indices) {
|
||||||
|
for (c in piece[r].indices) {
|
||||||
|
if (piece[r][c] == 1) {
|
||||||
|
val left = boardLeft + (x + c) * blockSize
|
||||||
|
val top = boardTop + (y + r) * blockSize
|
||||||
|
val right = left + blockSize
|
||||||
|
val bottom = top + blockSize
|
||||||
|
canvas.drawRect(left, top, right, bottom, paint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawBlock(canvas: Canvas, x: Int, y: Int, colorStr: String, offsetX: Float = 0f, offsetY: Float = 0f) {
|
||||||
|
// Skip drawing outside the board
|
||||||
|
if (y < 0) return
|
||||||
|
|
||||||
|
val left = boardLeft + x * blockSize + offsetX
|
||||||
|
val top = boardTop + y * blockSize + offsetY
|
||||||
|
val right = left + blockSize
|
||||||
|
val bottom = top + blockSize
|
||||||
|
val blockRect = RectF(left, top, right, bottom)
|
||||||
|
|
||||||
|
// Draw the block fill
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
paint.color = Color.parseColor(colorStr)
|
||||||
|
canvas.drawRect(blockRect, paint)
|
||||||
|
|
||||||
|
// Draw the highlight (top-left gradient)
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
val highlightPaint = Paint()
|
||||||
|
highlightPaint.shader = LinearGradient(
|
||||||
|
left, top,
|
||||||
|
right, bottom,
|
||||||
|
Color.argb(120, 255, 255, 255),
|
||||||
|
Color.argb(0, 255, 255, 255),
|
||||||
|
Shader.TileMode.CLAMP
|
||||||
|
)
|
||||||
|
canvas.drawRect(blockRect, highlightPaint)
|
||||||
|
|
||||||
|
// Draw the block border
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
paint.strokeWidth = 2f
|
||||||
|
paint.color = Color.BLACK
|
||||||
|
canvas.drawRect(blockRect, paint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for touch events
|
||||||
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
|
when (event.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
lastTouchX = event.x
|
||||||
|
lastTouchY = event.y
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
val diffX = event.x - lastTouchX
|
||||||
|
val diffY = event.y - lastTouchY
|
||||||
|
|
||||||
|
// Check if drag distance exceeds threshold for continuous movement
|
||||||
|
if (abs(diffX) > swipeThreshold && abs(diffX) > abs(diffY)) {
|
||||||
|
// Horizontal continuous movement
|
||||||
|
if (diffX > 0) {
|
||||||
|
game?.moveRight()
|
||||||
|
} else {
|
||||||
|
game?.moveLeft()
|
||||||
|
}
|
||||||
|
// Update last position after processing the move
|
||||||
|
lastTouchX = event.x
|
||||||
|
lastTouchY = event.y
|
||||||
|
invalidate()
|
||||||
|
return true
|
||||||
|
} else if (abs(diffY) > swipeThreshold && abs(diffY) > abs(diffX)) {
|
||||||
|
// Vertical continuous movement - only for downward
|
||||||
|
if (diffY > 0) {
|
||||||
|
game?.moveDown()
|
||||||
|
// Update last position after processing the move
|
||||||
|
lastTouchX = event.x
|
||||||
|
lastTouchY = event.y
|
||||||
|
invalidate()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP -> {
|
||||||
|
stopAutoRepeat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startAutoRepeat(action: () -> Unit) {
|
||||||
|
isAutoRepeating = true
|
||||||
|
currentMovement = action
|
||||||
|
|
||||||
|
val autoRepeatRunnable = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
if (isAutoRepeating && currentMovement != null) {
|
||||||
|
currentMovement?.invoke()
|
||||||
|
invalidate()
|
||||||
|
autoRepeatHandler.postDelayed(this, autoRepeatDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use initial delay before first repeat
|
||||||
|
autoRepeatHandler.postDelayed(autoRepeatRunnable, initialAutoRepeatDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopAutoRepeat() {
|
||||||
|
isAutoRepeating = false
|
||||||
|
currentMovement = null
|
||||||
|
autoRepeatHandler.removeCallbacksAndMessages(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up refresh timer
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
stopRefreshTimer()
|
||||||
|
stopAutoRepeat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gesture listener for swipe controls
|
||||||
|
inner class TetrisGestureListener : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
|
||||||
|
override fun onDown(e: MotionEvent): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||||
|
// Determine if tap is on left or right side of screen
|
||||||
|
val screenMiddle = width / 2
|
||||||
|
|
||||||
|
if (e.x < screenMiddle) {
|
||||||
|
// Left side - rotate counterclockwise (in a real 3D game)
|
||||||
|
game?.rotate3DX()
|
||||||
|
} else {
|
||||||
|
// Right side - rotate clockwise
|
||||||
|
game?.rotate3DY()
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFling(
|
||||||
|
e1: MotionEvent,
|
||||||
|
e2: MotionEvent,
|
||||||
|
velocityX: Float,
|
||||||
|
velocityY: Float
|
||||||
|
): Boolean {
|
||||||
|
val diffX = e2.x - e1.x
|
||||||
|
val diffY = e2.y - e1.y
|
||||||
|
|
||||||
|
// Check if swipe is horizontal or vertical based on magnitude
|
||||||
|
if (abs(diffX) > abs(diffY)) {
|
||||||
|
// Horizontal swipe
|
||||||
|
if (abs(velocityX) > minSwipeVelocity && abs(diffX) > minSwipeDistance) {
|
||||||
|
if (diffX > 0) {
|
||||||
|
// Swipe right - use auto-repeat for smoother movement
|
||||||
|
startAutoRepeat { game?.moveRight() }
|
||||||
|
} else {
|
||||||
|
// Swipe left - use auto-repeat for smoother movement
|
||||||
|
startAutoRepeat { game?.moveLeft() }
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Vertical swipe
|
||||||
|
if (abs(velocityY) > minSwipeVelocity && abs(diffY) > minSwipeDistance) {
|
||||||
|
if (diffY > 0) {
|
||||||
|
// Swipe down - start soft drop with auto-repeat
|
||||||
|
startAutoRepeat { game?.moveDown() }
|
||||||
|
} else {
|
||||||
|
// Swipe up - hard drop
|
||||||
|
game?.hardDrop()
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/background" />
|
||||||
|
</shape>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</vector>
|
16
android-app/app/src/main/res/drawable/simple_icon.xml
Normal file
16
android-app/app/src/main/res/drawable/simple_icon.xml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</vector>
|
169
android-app/app/src/main/res/layout/activity_main.xml
Normal file
169
android-app/app/src/main/res/layout/activity_main.xml
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#0f142d"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gameTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textColor="@color/cyan"
|
||||||
|
android:textSize="32sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:shadowColor="#00ffff"
|
||||||
|
android:shadowRadius="10"
|
||||||
|
android:shadowDx="0"
|
||||||
|
android:shadowDy="0"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.tetris3d.views.TetrisGameView
|
||||||
|
android:id="@+id/tetrisGameView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/controlsHint"
|
||||||
|
app:layout_constraintDimensionRatio="1:2"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/infoPanel"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/gameTitle" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/controlsHint"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Swipe left/right to move • Swipe down for soft drop • Swipe up for hard drop • Tap to rotate"
|
||||||
|
android:textColor="#00ffff"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/infoPanel"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/infoPanel"
|
||||||
|
android:layout_width="140dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:cardBackgroundColor="#202040"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/gameTitle">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/score"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/scoreText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0"
|
||||||
|
android:textColor="#ff9900"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/lines"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/linesText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0"
|
||||||
|
android:textColor="#ff9900"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/level"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/levelText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="1"
|
||||||
|
android:textColor="#ff9900"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="Next Piece"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="#00ffff"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<com.tetris3d.views.NextPieceView
|
||||||
|
android:id="@+id/nextPieceView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:layout_marginTop="8dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/startButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:backgroundTint="#ff00dd"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:text="@string/start" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pauseButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:backgroundTint="#00ddff"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:text="@string/pause" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/optionsButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:backgroundTint="#9900ff"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:text="@string/options" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
46
android-app/app/src/main/res/layout/dialog_game_over.xml
Normal file
46
android-app/app/src/main/res/layout/dialog_game_over.xml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp"
|
||||||
|
android:background="@color/background">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/game_over"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/magenta"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/final_score"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginBottom="8dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textFinalScore"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/cyan"
|
||||||
|
android:textSize="36sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="32dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnPlayAgain"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/play_again"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
119
android-app/app/src/main/res/layout/dialog_options.xml
Normal file
119
android-app/app/src/main/res/layout/dialog_options.xml
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/options"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/cyan"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/enable_3d_effects"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/switch3dEffects"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="true" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/enable_spin_animations"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/switchSpinAnimations"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="true" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/animation_speed"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:layout_marginBottom="8dp" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/seekBarAnimationSpeed"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:max="100"
|
||||||
|
android:progress="50"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/starting_level"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<NumberPicker
|
||||||
|
android:id="@+id/numberPickerLevel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:theme="@style/NumberPickerTheme" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="end">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnCloseOptions"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/close"
|
||||||
|
android:layout_marginEnd="8dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnApplyOptions"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/apply" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
21
android-app/app/src/main/res/mipmap-hdpi/ic_launcher.xml
Normal file
21
android-app/app/src/main/res/mipmap-hdpi/ic_launcher.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="72dp"
|
||||||
|
android:height="72dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:tint="#00FFFF">
|
||||||
|
<group
|
||||||
|
android:scaleX="0.6666667"
|
||||||
|
android:scaleY="0.6666667">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="72dp"
|
||||||
|
android:height="72dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:tint="#00FFFF">
|
||||||
|
<group
|
||||||
|
android:scaleX="0.6666667"
|
||||||
|
android:scaleY="0.6666667">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
21
android-app/app/src/main/res/mipmap-mdpi/ic_launcher.xml
Normal file
21
android-app/app/src/main/res/mipmap-mdpi/ic_launcher.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="48dp"
|
||||||
|
android:height="48dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:tint="#00FFFF">
|
||||||
|
<group
|
||||||
|
android:scaleX="0.44444445"
|
||||||
|
android:scaleY="0.44444445">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="48dp"
|
||||||
|
android:height="48dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:tint="#00FFFF">
|
||||||
|
<group
|
||||||
|
android:scaleX="0.44444445"
|
||||||
|
android:scaleY="0.44444445">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
21
android-app/app/src/main/res/mipmap-xhdpi/ic_launcher.xml
Normal file
21
android-app/app/src/main/res/mipmap-xhdpi/ic_launcher.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="96dp"
|
||||||
|
android:height="96dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:tint="#00FFFF">
|
||||||
|
<group
|
||||||
|
android:scaleX="0.8888889"
|
||||||
|
android:scaleY="0.8888889">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="96dp"
|
||||||
|
android:height="96dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:tint="#00FFFF">
|
||||||
|
<group
|
||||||
|
android:scaleX="0.8888889"
|
||||||
|
android:scaleY="0.8888889">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
21
android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.xml
Normal file
21
android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="144dp"
|
||||||
|
android:height="144dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:tint="#00FFFF">
|
||||||
|
<group
|
||||||
|
android:scaleX="1.3333334"
|
||||||
|
android:scaleY="1.3333334">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="144dp"
|
||||||
|
android:height="144dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:tint="#00FFFF">
|
||||||
|
<group
|
||||||
|
android:scaleX="1.3333334"
|
||||||
|
android:scaleY="1.3333334">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
21
android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.xml
Normal file
21
android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="192dp"
|
||||||
|
android:height="192dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:tint="#00FFFF">
|
||||||
|
<group
|
||||||
|
android:scaleX="1.7777778"
|
||||||
|
android:scaleY="1.7777778">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="192dp"
|
||||||
|
android:height="192dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:tint="#00FFFF">
|
||||||
|
<group
|
||||||
|
android:scaleX="1.7777778"
|
||||||
|
android:scaleY="1.7777778">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000020"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00FFFF"
|
||||||
|
android:pathData="M24,24h60v60h-60z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF00FF"
|
||||||
|
android:pathData="M54,24h30v30h-30z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
15
android-app/app/src/main/res/values/colors.xml
Normal file
15
android-app/app/src/main/res/values/colors.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
|
<color name="purple_500">#FF6200EE</color>
|
||||||
|
<color name="purple_700">#FF3700B3</color>
|
||||||
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
|
<color name="teal_700">#FF018786</color>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
<color name="cyan">#FF00FFFF</color>
|
||||||
|
<color name="cyan_dark">#FF00AAAA</color>
|
||||||
|
<color name="magenta">#FFFF00FF</color>
|
||||||
|
<color name="magenta_dark">#FFAA00AA</color>
|
||||||
|
<color name="background">#FF000020</color>
|
||||||
|
</resources>
|
19
android-app/app/src/main/res/values/strings.xml
Normal file
19
android-app/app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">3D Tetris</string>
|
||||||
|
<string name="score">Score</string>
|
||||||
|
<string name="lines">Lines</string>
|
||||||
|
<string name="level">Level</string>
|
||||||
|
<string name="pause">Pause</string>
|
||||||
|
<string name="start">Start</string>
|
||||||
|
<string name="game_over">Game Over</string>
|
||||||
|
<string name="final_score">Final Score</string>
|
||||||
|
<string name="play_again">Play Again</string>
|
||||||
|
<string name="shadow">Shadow</string>
|
||||||
|
<string name="options">Options</string>
|
||||||
|
<string name="enable_3d_effects">3D Effects</string>
|
||||||
|
<string name="enable_spin_animations">Spin Animations</string>
|
||||||
|
<string name="animation_speed">Animation Speed</string>
|
||||||
|
<string name="starting_level">Starting Level</string>
|
||||||
|
<string name="apply">Apply</string>
|
||||||
|
<string name="close">Close</string>
|
||||||
|
</resources>
|
18
android-app/app/src/main/res/values/themes.xml
Normal file
18
android-app/app/src/main/res/values/themes.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<style name="Theme.Tetris3D" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||||
|
<item name="colorPrimary">@color/cyan</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/cyan_dark</item>
|
||||||
|
<item name="colorOnPrimary">@color/black</item>
|
||||||
|
<item name="colorSecondary">@color/magenta</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/magenta_dark</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<item name="android:statusBarColor">@color/black</item>
|
||||||
|
<item name="android:windowBackground">@color/background</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="NumberPickerTheme">
|
||||||
|
<item name="android:textColorPrimary">@color/white</item>
|
||||||
|
<item name="android:colorControlNormal">@color/cyan</item>
|
||||||
|
<item name="android:textSize">16sp</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
22
android-app/build.gradle
Normal file
22
android-app/build.gradle
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.9.0'
|
||||||
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
663
android-app/build/reports/problems/problems-report.html
Normal file
663
android-app/build/reports/problems/problems-report.html
Normal file
File diff suppressed because one or more lines are too long
19
android-app/gradle.properties
Normal file
19
android-app/gradle.properties
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
android.useAndroidX=true
|
||||||
|
|
||||||
|
# Enables namespacing of each library's R class so that its R class includes only the
|
||||||
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
|
# thereby reducing the size of the R class for that library
|
||||||
|
android.nonTransitiveRClass=true
|
||||||
|
|
||||||
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
|
kotlin.code.style=official
|
||||||
|
|
||||||
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
|
android.nonFinalResIds=false
|
BIN
android-app/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
android-app/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
android-app/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
android-app/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
252
android-app/gradlew
vendored
Executable file
252
android-app/gradlew
vendored
Executable file
|
@ -0,0 +1,252 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||||
|
' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
94
android-app/gradlew.bat
vendored
Normal file
94
android-app/gradlew.bat
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
8
android-app/local.properties
Normal file
8
android-app/local.properties
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
## This file must *NOT* be checked into Version Control Systems,
|
||||||
|
# as it contains information specific to your local configuration.
|
||||||
|
#
|
||||||
|
# Location of the SDK. This is only used by Gradle.
|
||||||
|
# For customization when using a Version Control System, please read the
|
||||||
|
# header note.
|
||||||
|
#Tue Mar 25 23:36:25 EDT 2025
|
||||||
|
sdk.dir=/home/corey/Android/Sdk
|
2
android-app/settings.gradle
Normal file
2
android-app/settings.gradle
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
rootProject.name = "Tetris3D"
|
||||||
|
include ':app'
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue