mirror of
https://github.com/cmclark00/tetris-3d.git
synced 2025-05-22 09:25:20 +01:00
Compare commits
No commits in common. "8716102f92840b403015232cc43e680bba10c744" and "9d3a190888805312a572177bf960f842481cab9a" have entirely different histories.
8716102f92
...
9d3a190888
103 changed files with 496 additions and 5316 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"java.configuration.updateBuildConfiguration": "interactive"
|
|
||||||
}
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,2 +0,0 @@
|
||||||
#Tue Mar 25 23:50:46 EDT 2025
|
|
||||||
gradle.version=8.11.1
|
|
Binary file not shown.
|
@ -1,2 +0,0 @@
|
||||||
#Tue Mar 25 23:36:25 EDT 2025
|
|
||||||
java.home=/home/corey/Downloads/android-studio-2024.3.1.13-linux/android-studio/jbr
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
||||||
8B64E587393F94313EFF41233F48D4848E985369BD2E4B833B25B3765823A6DB
|
|
3
android-app/.idea/.gitignore
generated
vendored
3
android-app/.idea/.gitignore
generated
vendored
|
@ -1,3 +0,0 @@
|
||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
1
android-app/.idea/.name
generated
1
android-app/.idea/.name
generated
|
@ -1 +0,0 @@
|
||||||
Tetris3D
|
|
6
android-app/.idea/AndroidProjectSystem.xml
generated
6
android-app/.idea/AndroidProjectSystem.xml
generated
|
@ -1,6 +0,0 @@
|
||||||
<?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
26
android-app/.idea/appInsightsSettings.xml
generated
|
@ -1,26 +0,0 @@
|
||||||
<?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
607
android-app/.idea/caches/deviceStreaming.xml
generated
|
@ -1,607 +0,0 @@
|
||||||
<?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
6
android-app/.idea/compiler.xml
generated
|
@ -1,6 +0,0 @@
|
||||||
<?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
10
android-app/.idea/deploymentTargetSelector.xml
generated
|
@ -1,10 +0,0 @@
|
||||||
<?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
19
android-app/.idea/gradle.xml
generated
|
@ -1,19 +0,0 @@
|
||||||
<?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
6
android-app/.idea/kotlinc.xml
generated
|
@ -1,6 +0,0 @@
|
||||||
<?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
10
android-app/.idea/migrations.xml
generated
|
@ -1,10 +0,0 @@
|
||||||
<?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
10
android-app/.idea/misc.xml
generated
|
@ -1,10 +0,0 @@
|
||||||
<?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
17
android-app/.idea/runConfigurations.xml
generated
|
@ -1,17 +0,0 @@
|
||||||
<?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
6
android-app/.idea/vcs.xml
generated
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
|
@ -1,57 +0,0 @@
|
||||||
# 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.
|
|
|
@ -1,50 +0,0 @@
|
||||||
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'
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,254 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
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
|
|
||||||
)
|
|
|
@ -1,967 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
// Refresh interval for animations
|
|
||||||
const val REFRESH_INTERVAL = 16L // ~60fps
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
|
|
||||||
// If a line clear effect is in progress, wait for it to complete
|
|
||||||
// The view will handle updating and completing the animation
|
|
||||||
if (lineClearEffect) {
|
|
||||||
gameHandler.postDelayed(this, REFRESH_INTERVAL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the current piece down
|
|
||||||
if (!moveDown()) {
|
|
||||||
// If can't move down, lock the piece
|
|
||||||
lockPiece()
|
|
||||||
clearRows()
|
|
||||||
|
|
||||||
// If a line clear effect started, wait for next frame
|
|
||||||
if (lineClearEffect) {
|
|
||||||
gameHandler.postDelayed(this, REFRESH_INTERVAL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, continue with creating a new piece
|
|
||||||
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 line clear animation started, return and let the game loop handle it
|
|
||||||
if (lineClearEffect) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
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 flip the piece vertically
|
|
||||||
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 mirror the piece vertically
|
|
||||||
if (rotation3DX % (maxRotation3D / 2) == 1) {
|
|
||||||
// Create a vertically mirrored version of the current piece
|
|
||||||
val currentPattern = getCurrentPieceArray()
|
|
||||||
val rows = currentPattern.size
|
|
||||||
val cols = if (rows > 0) currentPattern[0].size else 0
|
|
||||||
val mirroredPattern = Array(rows) { r -> Array(cols) { c -> currentPattern[rows - 1 - r][c] } }
|
|
||||||
|
|
||||||
// Check if the mirrored position is valid
|
|
||||||
if (!checkCollision(currentX, currentY, mirroredPattern)) {
|
|
||||||
// Replace the current rotation with the mirrored pattern
|
|
||||||
// Since we don't actually modify the pieces, simulate this by finding a rotation
|
|
||||||
// that most closely resembles the mirrored pattern, if one exists
|
|
||||||
|
|
||||||
// For symmetrical pieces like O, this may not change anything
|
|
||||||
val pieceVariants = pieces[currentPiece]
|
|
||||||
for (i in pieceVariants.indices) {
|
|
||||||
if (patternsAreEquivalent(mirroredPattern, pieceVariants[i])) {
|
|
||||||
currentRotation = i
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no matching rotation found, just use regular rotation as fallback
|
|
||||||
return rotate()
|
|
||||||
} else {
|
|
||||||
// Try wall kicks with the mirrored pattern
|
|
||||||
// Try moving right
|
|
||||||
if (!checkCollision(currentX + 1, currentY, mirroredPattern)) {
|
|
||||||
currentX++
|
|
||||||
// Find equivalent rotation
|
|
||||||
val pieceVariants = pieces[currentPiece]
|
|
||||||
for (i in pieceVariants.indices) {
|
|
||||||
if (patternsAreEquivalent(mirroredPattern, pieceVariants[i])) {
|
|
||||||
currentRotation = i
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rotate()
|
|
||||||
}
|
|
||||||
// Try moving left
|
|
||||||
if (!checkCollision(currentX - 1, currentY, mirroredPattern)) {
|
|
||||||
currentX--
|
|
||||||
// Find equivalent rotation
|
|
||||||
val pieceVariants = pieces[currentPiece]
|
|
||||||
for (i in pieceVariants.indices) {
|
|
||||||
if (patternsAreEquivalent(mirroredPattern, pieceVariants[i])) {
|
|
||||||
currentRotation = i
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rotate()
|
|
||||||
}
|
|
||||||
// Try moving up
|
|
||||||
if (!checkCollision(currentX, currentY - 1, mirroredPattern)) {
|
|
||||||
currentY--
|
|
||||||
// Find equivalent rotation
|
|
||||||
val pieceVariants = pieces[currentPiece]
|
|
||||||
for (i in pieceVariants.indices) {
|
|
||||||
if (patternsAreEquivalent(mirroredPattern, pieceVariants[i])) {
|
|
||||||
currentRotation = i
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rotate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all fails, don't change the actual piece, just visual effect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 flip the piece horizontally
|
|
||||||
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 mirror the piece horizontally
|
|
||||||
if (rotation3DY % (maxRotation3D / 2) == 1) {
|
|
||||||
// Create a horizontally mirrored version of the current piece
|
|
||||||
val currentPattern = getCurrentPieceArray()
|
|
||||||
val rows = currentPattern.size
|
|
||||||
val cols = if (rows > 0) currentPattern[0].size else 0
|
|
||||||
val mirroredPattern = Array(rows) { r -> Array(cols) { c -> currentPattern[r][cols - 1 - c] } }
|
|
||||||
|
|
||||||
// Try to find an equivalent pattern in any piece type
|
|
||||||
// This allows for pieces to transform into different piece types when mirrored
|
|
||||||
for (pieceType in 0 until pieces.size) {
|
|
||||||
val pieceVariants = pieces[pieceType]
|
|
||||||
for (rotation in pieceVariants.indices) {
|
|
||||||
// Check if this variant matches our mirrored pattern
|
|
||||||
if (patternsAreEquivalent(mirroredPattern, pieceVariants[rotation])) {
|
|
||||||
// Transform into this piece type with this rotation
|
|
||||||
currentPiece = pieceType
|
|
||||||
currentRotation = rotation
|
|
||||||
currentColor = colors[currentPiece]
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no exact match was found, find the most similar pattern
|
|
||||||
var bestPieceType = -1
|
|
||||||
var bestRotation = -1
|
|
||||||
var bestScore = -1
|
|
||||||
|
|
||||||
for (pieceType in 0 until pieces.size) {
|
|
||||||
val pieceVariants = pieces[pieceType]
|
|
||||||
for (rotation in pieceVariants.indices) {
|
|
||||||
val score = patternMatchScore(mirroredPattern, pieceVariants[rotation])
|
|
||||||
if (score > bestScore) {
|
|
||||||
bestScore = score
|
|
||||||
bestPieceType = pieceType
|
|
||||||
bestRotation = rotation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we found a reasonable match
|
|
||||||
if (bestScore > 0) {
|
|
||||||
// If this is a collision-free position, perform the transformation
|
|
||||||
if (!checkCollision(currentX, currentY, pieces[bestPieceType][bestRotation])) {
|
|
||||||
currentPiece = bestPieceType
|
|
||||||
currentRotation = bestRotation
|
|
||||||
currentColor = colors[currentPiece]
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
// Try wall kicks with the new piece
|
|
||||||
// Try moving right
|
|
||||||
if (!checkCollision(currentX + 1, currentY, pieces[bestPieceType][bestRotation])) {
|
|
||||||
currentX++
|
|
||||||
currentPiece = bestPieceType
|
|
||||||
currentRotation = bestRotation
|
|
||||||
currentColor = colors[currentPiece]
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Try moving left
|
|
||||||
if (!checkCollision(currentX - 1, currentY, pieces[bestPieceType][bestRotation])) {
|
|
||||||
currentX--
|
|
||||||
currentPiece = bestPieceType
|
|
||||||
currentRotation = bestRotation
|
|
||||||
currentColor = colors[currentPiece]
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Try moving up
|
|
||||||
if (!checkCollision(currentX, currentY - 1, pieces[bestPieceType][bestRotation])) {
|
|
||||||
currentY--
|
|
||||||
currentPiece = bestPieceType
|
|
||||||
currentRotation = bestRotation
|
|
||||||
currentColor = colors[currentPiece]
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we couldn't find a good transformation, just do a regular rotation
|
|
||||||
// as a fallback to ensure some response to the user's action
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Line clearing animation properties
|
|
||||||
private var lineClearEffect = false
|
|
||||||
private var clearedRows = mutableListOf<Int>()
|
|
||||||
private var lineClearProgress = 0f
|
|
||||||
private val maxLineClearDuration = 0.5f // In seconds
|
|
||||||
private var lineClearStartTime = 0L
|
|
||||||
|
|
||||||
// Getter for line clear effect
|
|
||||||
fun isLineClearEffect(): Boolean = lineClearEffect
|
|
||||||
|
|
||||||
// Get the cleared rows for animation
|
|
||||||
fun getClearedRows(): List<Int> = clearedRows
|
|
||||||
|
|
||||||
// Get line clear animation progress (0-1)
|
|
||||||
fun getLineClearProgress(): Float = lineClearProgress
|
|
||||||
|
|
||||||
private fun clearRows() {
|
|
||||||
clearedRows.clear()
|
|
||||||
|
|
||||||
// First pass: identify full rows
|
|
||||||
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) {
|
|
||||||
clearedRows.add(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have cleared rows, start the animation
|
|
||||||
if (clearedRows.isNotEmpty()) {
|
|
||||||
lineClearEffect = true
|
|
||||||
lineClearProgress = 0f
|
|
||||||
lineClearStartTime = System.currentTimeMillis()
|
|
||||||
|
|
||||||
// The actual row clearing will be done when the animation completes
|
|
||||||
// This is handled in the updateLineClear method
|
|
||||||
|
|
||||||
// The rows are still part of the board during animation but will be
|
|
||||||
// displayed with a special effect by the view
|
|
||||||
} else {
|
|
||||||
// No rows to clear, continue with normal gameplay
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark the start of the animation
|
|
||||||
// The actual row clearance will happen after the animation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update line clear animation
|
|
||||||
fun updateLineClear(): Boolean {
|
|
||||||
if (!lineClearEffect) return false
|
|
||||||
|
|
||||||
// Calculate progress based on elapsed time
|
|
||||||
val elapsedTime = (System.currentTimeMillis() - lineClearStartTime) / 1000f
|
|
||||||
lineClearProgress = (elapsedTime / maxLineClearDuration).coerceIn(0f, 1f)
|
|
||||||
|
|
||||||
// If animation is complete, apply the row clearing
|
|
||||||
if (lineClearProgress >= 1f) {
|
|
||||||
// Actually clear the rows and update score
|
|
||||||
completeLineClear()
|
|
||||||
|
|
||||||
// Reset animation state
|
|
||||||
lineClearEffect = false
|
|
||||||
lineClearProgress = 0f
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete the line clearing after animation
|
|
||||||
private fun completeLineClear() {
|
|
||||||
val linesCleared = clearedRows.size
|
|
||||||
|
|
||||||
// Process cleared rows in descending order to avoid index issues
|
|
||||||
val sortedRows = clearedRows.sortedDescending()
|
|
||||||
|
|
||||||
for (row in sortedRows) {
|
|
||||||
// Move all rows above down
|
|
||||||
for (y in row 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// Helper function to check if two patterns are equivalent (ignoring empty space)
|
|
||||||
private fun patternsAreEquivalent(pattern1: Array<Array<Int>>, pattern2: Array<Array<Int>>): Boolean {
|
|
||||||
// Quick check for size match
|
|
||||||
if (pattern1.size != pattern2.size) return false
|
|
||||||
if (pattern1.isEmpty() || pattern2.isEmpty()) return pattern1.isEmpty() && pattern2.isEmpty()
|
|
||||||
if (pattern1[0].size != pattern2[0].size) return false
|
|
||||||
|
|
||||||
// Check if cells with 1s match in both patterns
|
|
||||||
for (r in pattern1.indices) {
|
|
||||||
for (c in pattern1[r].indices) {
|
|
||||||
if (pattern1[r][c] != pattern2[r][c]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to score how well two patterns match (higher score = better match)
|
|
||||||
private fun patternMatchScore(pattern1: Array<Array<Int>>, pattern2: Array<Array<Int>>): Int {
|
|
||||||
// Quick check for size match
|
|
||||||
if (pattern1.size != pattern2.size) return 0
|
|
||||||
if (pattern1.isEmpty() || pattern2.isEmpty()) return if (pattern1.isEmpty() && pattern2.isEmpty()) 1 else 0
|
|
||||||
if (pattern1[0].size != pattern2[0].size) return 0
|
|
||||||
|
|
||||||
// Count matching cells
|
|
||||||
var matchCount = 0
|
|
||||||
for (r in pattern1.indices) {
|
|
||||||
for (c in pattern1[r].indices) {
|
|
||||||
if (pattern1[r][c] == pattern2[r][c]) {
|
|
||||||
matchCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchCount
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,861 +0,0 @@
|
||||||
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
|
|
||||||
private val showGlowEffects = true
|
|
||||||
|
|
||||||
// Background gradient colors
|
|
||||||
private val bgColorStart = Color.parseColor("#06071B") // Darker space background
|
|
||||||
private val bgColorEnd = Color.parseColor("#0B1026") // Slightly lighter space background
|
|
||||||
private lateinit var bgGradient: LinearGradient
|
|
||||||
|
|
||||||
// Glow and star effects
|
|
||||||
private val stars = ArrayList<Star>()
|
|
||||||
private val random = java.util.Random()
|
|
||||||
private val starCount = 50
|
|
||||||
private val starColors = arrayOf(
|
|
||||||
Color.parseColor("#FFFFFF"), // White
|
|
||||||
Color.parseColor("#AAAAFF"), // Light blue
|
|
||||||
Color.parseColor("#FFAAAA"), // Light red
|
|
||||||
Color.parseColor("#AAFFAA") // Light green
|
|
||||||
)
|
|
||||||
|
|
||||||
// Gesture detection for swipe controls
|
|
||||||
private val gestureDetector = GestureDetector(context, TetrisGestureListener())
|
|
||||||
|
|
||||||
// Define minimum swipe velocity and distance
|
|
||||||
private val minSwipeVelocity = 30 // Lower for better responsiveness
|
|
||||||
private val minSwipeDistance = 15 // Lower for better responsiveness
|
|
||||||
|
|
||||||
// Movement control
|
|
||||||
private val autoRepeatHandler = Handler(Looper.getMainLooper())
|
|
||||||
private var isAutoRepeating = false
|
|
||||||
private var currentMovement: (() -> Unit)? = null
|
|
||||||
private val autoRepeatDelay = 100L // Faster for smoother continuous movement
|
|
||||||
private val initialAutoRepeatDelay = 150L // Faster initial delay
|
|
||||||
private val interpolator = DecelerateInterpolator(1.5f)
|
|
||||||
|
|
||||||
// Touch tracking for continuous swipe
|
|
||||||
private var lastTouchX = 0f
|
|
||||||
private var lastTouchY = 0f
|
|
||||||
private var swipeThreshold = 20f // More sensitive
|
|
||||||
private var lastMoveTime = 0L
|
|
||||||
private val moveCooldown = 110L // Shorter cooldown for more responsive movement
|
|
||||||
private var tapThreshold = 10f // Slightly more forgiving tap detection
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Game state flags
|
|
||||||
private var gameOver = false
|
|
||||||
private var paused = false
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val REFRESH_INTERVAL = 16L // ~60fps
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setGame(game: TetrisGame) {
|
|
||||||
this.game = game
|
|
||||||
invalidate()
|
|
||||||
|
|
||||||
// Start refresh timer
|
|
||||||
startRefreshTimer()
|
|
||||||
|
|
||||||
// Update game state flags
|
|
||||||
gameOver = game.isGameOver
|
|
||||||
paused = !game.isRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
// Initialize stars for background
|
|
||||||
initializeStars(w, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initializeStars(width: Int, height: Int) {
|
|
||||||
stars.clear()
|
|
||||||
for (i in 0 until starCount) {
|
|
||||||
stars.add(Star(
|
|
||||||
x = random.nextFloat() * width,
|
|
||||||
y = random.nextFloat() * height,
|
|
||||||
size = 1f + random.nextFloat() * 2f,
|
|
||||||
color = starColors[random.nextInt(starColors.size)],
|
|
||||||
blinkSpeed = 0.5f + random.nextFloat() * 2f
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Star class for background effect
|
|
||||||
private data class Star(
|
|
||||||
val x: Float,
|
|
||||||
val y: Float,
|
|
||||||
val size: Float,
|
|
||||||
val color: Int,
|
|
||||||
val blinkSpeed: Float,
|
|
||||||
var brightness: Float
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
private val random = java.util.Random()
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(x: Float, y: Float, size: Float, color: Int, blinkSpeed: Float) : this(
|
|
||||||
x = x,
|
|
||||||
y = y,
|
|
||||||
size = size,
|
|
||||||
color = color,
|
|
||||||
blinkSpeed = blinkSpeed,
|
|
||||||
brightness = random.nextFloat()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
|
||||||
super.onDraw(canvas)
|
|
||||||
|
|
||||||
val game = this.game ?: return
|
|
||||||
|
|
||||||
// Update game state flags
|
|
||||||
gameOver = game.isGameOver
|
|
||||||
paused = !game.isRunning
|
|
||||||
|
|
||||||
// Draw space background with gradient
|
|
||||||
paint.shader = bgGradient
|
|
||||||
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
|
|
||||||
paint.shader = null
|
|
||||||
|
|
||||||
// Draw stars in background
|
|
||||||
drawStars(canvas)
|
|
||||||
|
|
||||||
// Draw grid if enabled
|
|
||||||
if (showGrid) {
|
|
||||||
drawGrid(canvas)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw board border with enhanced glow effect
|
|
||||||
drawBoardBorder(canvas)
|
|
||||||
|
|
||||||
// Draw the locked pieces on the board
|
|
||||||
drawBoard(canvas, game)
|
|
||||||
|
|
||||||
// Draw line clear effect if active
|
|
||||||
if (game.isLineClearEffect()) {
|
|
||||||
drawLineClearEffect(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update animations
|
|
||||||
if (game.isLineClearEffect()) {
|
|
||||||
// Update line clear animation
|
|
||||||
if (game.updateLineClear()) {
|
|
||||||
// If line clear animation completed, invalidate again
|
|
||||||
invalidate()
|
|
||||||
} else {
|
|
||||||
// Animation still in progress
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update star animation
|
|
||||||
updateStars()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateStars() {
|
|
||||||
val currentTime = System.currentTimeMillis() / 1000f
|
|
||||||
for (star in stars) {
|
|
||||||
// Calculate pulsing brightness based on time and individual star speed
|
|
||||||
star.brightness = (kotlin.math.sin(currentTime * star.blinkSpeed) + 1f) / 2f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force regular refresh to animate stars
|
|
||||||
if (!gameOver && !paused) {
|
|
||||||
postInvalidateDelayed(50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun drawStars(canvas: Canvas) {
|
|
||||||
paint.style = Paint.Style.FILL
|
|
||||||
|
|
||||||
for (star in stars) {
|
|
||||||
// Set color with alpha based on brightness
|
|
||||||
paint.color = star.color
|
|
||||||
paint.alpha = (255 * star.brightness).toInt()
|
|
||||||
|
|
||||||
// Draw star with glow effect
|
|
||||||
if (showGlowEffects) {
|
|
||||||
paint.setShadowLayer(star.size * 2, 0f, 0f, star.color)
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawCircle(star.x, star.y, star.size * star.brightness, paint)
|
|
||||||
|
|
||||||
// Reset shadow
|
|
||||||
if (showGlowEffects) {
|
|
||||||
paint.setShadowLayer(0f, 0f, 0f, Color.TRANSPARENT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (enhanced cyan color)
|
|
||||||
paint.style = Paint.Style.STROKE
|
|
||||||
paint.strokeWidth = 4f
|
|
||||||
paint.color = Color.parseColor("#00ffff")
|
|
||||||
|
|
||||||
if (showGlowEffects) {
|
|
||||||
// Stronger glow effect
|
|
||||||
paint.setShadowLayer(16f, 0f, 0f, Color.parseColor("#00ffff"))
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawRect(borderRect, paint)
|
|
||||||
|
|
||||||
// Inner glow
|
|
||||||
if (showGlowEffects) {
|
|
||||||
paint.strokeWidth = 2f
|
|
||||||
paint.color = Color.parseColor("#80ffff")
|
|
||||||
paint.setShadowLayer(8f, 0f, 0f, Color.parseColor("#80ffff"))
|
|
||||||
|
|
||||||
val innerRect = RectF(
|
|
||||||
borderRect.left + 4f,
|
|
||||||
borderRect.top + 4f,
|
|
||||||
borderRect.right - 4f,
|
|
||||||
borderRect.bottom - 4f
|
|
||||||
)
|
|
||||||
|
|
||||||
canvas.drawRect(innerRect, paint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset shadow
|
|
||||||
paint.setShadowLayer(0f, 0f, 0f, Color.TRANSPARENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
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("#333344") // Slightly blue-tinted grid
|
|
||||||
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, apply transformations, then translate back
|
|
||||||
canvas.translate(centerX, centerY)
|
|
||||||
|
|
||||||
// Apply transformations based on rotation state
|
|
||||||
// First apply scaling to simulate flipping
|
|
||||||
val flipX = if (rotationX.toInt() % 2 == 1) -1f else 1f
|
|
||||||
val flipY = if (rotationY.toInt() % 2 == 1) -1f else 1f
|
|
||||||
|
|
||||||
// Check if we're in the middle of an animation
|
|
||||||
if (game.isRotating()) {
|
|
||||||
// For animation, use perspective scaling and smooth transitions
|
|
||||||
val scaleX = cos(angleY.toFloat()).coerceAtLeast(0.5f) * flipY
|
|
||||||
val scaleY = cos(angleX.toFloat()).coerceAtLeast(0.5f) * flipX
|
|
||||||
canvas.scale(scaleX, scaleY)
|
|
||||||
} else {
|
|
||||||
// For static display, just flip directly
|
|
||||||
canvas.scale(flipY, flipX)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate back
|
|
||||||
canvas.translate(-centerX, -centerY)
|
|
||||||
|
|
||||||
// Draw the piece with perspective or flip effect
|
|
||||||
for (r in piece.indices) {
|
|
||||||
for (c in piece[r].indices) {
|
|
||||||
if (piece[r][c] == 1) {
|
|
||||||
// Calculate offset for 3D effect during animation
|
|
||||||
val offsetX = if (game.isRotating()) sin(angleY.toFloat()) * blockSize * 0.3f else 0f
|
|
||||||
val offsetY = if (game.isRotating()) sin(angleX.toFloat()) * blockSize * 0.3f else 0f
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
// Parse the base color
|
|
||||||
val baseColor = Color.parseColor(colorStr)
|
|
||||||
|
|
||||||
// Create a brighter version for the glow
|
|
||||||
val red = Color.red(baseColor)
|
|
||||||
val green = Color.green(baseColor)
|
|
||||||
val blue = Color.blue(baseColor)
|
|
||||||
val glowColor = Color.argb(255,
|
|
||||||
Math.min(255, red + 40),
|
|
||||||
Math.min(255, green + 40),
|
|
||||||
Math.min(255, blue + 40)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add glow effect
|
|
||||||
if (showGlowEffects) {
|
|
||||||
paint.style = Paint.Style.FILL
|
|
||||||
paint.color = baseColor
|
|
||||||
paint.setShadowLayer(blockSize / 4, 0f, 0f, glowColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the block fill
|
|
||||||
paint.style = Paint.Style.FILL
|
|
||||||
paint.color = baseColor
|
|
||||||
canvas.drawRect(blockRect, paint)
|
|
||||||
|
|
||||||
// Reset shadow
|
|
||||||
if (showGlowEffects) {
|
|
||||||
paint.setShadowLayer(0f, 0f, 0f, Color.TRANSPARENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the highlight (top-left gradient)
|
|
||||||
paint.style = Paint.Style.FILL
|
|
||||||
val highlightPaint = Paint()
|
|
||||||
highlightPaint.shader = LinearGradient(
|
|
||||||
left, top,
|
|
||||||
right, bottom,
|
|
||||||
Color.argb(150, 255, 255, 255), // More pronounced highlight
|
|
||||||
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)
|
|
||||||
|
|
||||||
// Draw inner glow edge
|
|
||||||
if (showGlowEffects) {
|
|
||||||
paint.style = Paint.Style.STROKE
|
|
||||||
paint.strokeWidth = 1f
|
|
||||||
paint.color = glowColor
|
|
||||||
|
|
||||||
val innerRect = RectF(
|
|
||||||
left + 2,
|
|
||||||
top + 2,
|
|
||||||
right - 2,
|
|
||||||
bottom - 2
|
|
||||||
)
|
|
||||||
canvas.drawRect(innerRect, paint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw line clear effect
|
|
||||||
private fun drawLineClearEffect(canvas: Canvas, game: TetrisGame) {
|
|
||||||
val clearedRows = game.getClearedRows()
|
|
||||||
val progress = game.getLineClearProgress()
|
|
||||||
|
|
||||||
// Different effect based on animation progress
|
|
||||||
for (row in clearedRows) {
|
|
||||||
for (col in 0 until TetrisGame.COLS) {
|
|
||||||
val left = boardLeft + col * blockSize
|
|
||||||
val top = boardTop + row * blockSize
|
|
||||||
val right = left + blockSize
|
|
||||||
val bottom = top + blockSize
|
|
||||||
|
|
||||||
// Create a pulsing, brightening effect for cleared blocks
|
|
||||||
val alpha = (255 * (0.5f + 0.5f * Math.sin(progress * Math.PI * 3))).toInt()
|
|
||||||
val scale = 1.0f + 0.1f * progress
|
|
||||||
|
|
||||||
// Calculate center for scaling
|
|
||||||
val centerX = left + blockSize / 2
|
|
||||||
val centerY = top + blockSize / 2
|
|
||||||
|
|
||||||
// Save canvas state for transformation
|
|
||||||
canvas.save()
|
|
||||||
|
|
||||||
// Position at center, scale, then move back
|
|
||||||
canvas.translate(centerX, centerY)
|
|
||||||
canvas.scale(scale, scale)
|
|
||||||
canvas.translate(-centerX, -centerY)
|
|
||||||
|
|
||||||
// Get the color from the board
|
|
||||||
val color = game.getBoard()[row][col]
|
|
||||||
|
|
||||||
if (color != TetrisGame.EMPTY) {
|
|
||||||
// Draw with glow effect
|
|
||||||
val baseColor = Color.parseColor(color)
|
|
||||||
|
|
||||||
// Create a brighter glow as animation progresses
|
|
||||||
val red = Color.red(baseColor)
|
|
||||||
val green = Color.green(baseColor)
|
|
||||||
val blue = Color.blue(baseColor)
|
|
||||||
|
|
||||||
// Get increasingly white as effect progresses
|
|
||||||
val whiteBlend = progress * 0.7f
|
|
||||||
val newRed = (red * (1 - whiteBlend) + 255 * whiteBlend).toInt().coerceIn(0, 255)
|
|
||||||
val newGreen = (green * (1 - whiteBlend) + 255 * whiteBlend).toInt().coerceIn(0, 255)
|
|
||||||
val newBlue = (blue * (1 - whiteBlend) + 255 * whiteBlend).toInt().coerceIn(0, 255)
|
|
||||||
|
|
||||||
val effectColor = Color.argb(alpha, newRed, newGreen, newBlue)
|
|
||||||
|
|
||||||
// Draw with glow
|
|
||||||
paint.style = Paint.Style.FILL
|
|
||||||
paint.color = effectColor
|
|
||||||
|
|
||||||
if (showGlowEffects) {
|
|
||||||
// Increase glow radius with progress
|
|
||||||
val glowRadius = blockSize * (0.3f + 0.7f * progress)
|
|
||||||
paint.setShadowLayer(glowRadius, 0f, 0f, effectColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the block
|
|
||||||
canvas.drawRect(left, top, right, bottom, paint)
|
|
||||||
|
|
||||||
// Add horizontal line effect
|
|
||||||
paint.style = Paint.Style.STROKE
|
|
||||||
paint.strokeWidth = 4f * progress
|
|
||||||
canvas.drawLine(left, top + blockSize / 2, right, top + blockSize / 2, paint)
|
|
||||||
|
|
||||||
// Reset shadow
|
|
||||||
if (showGlowEffects) {
|
|
||||||
paint.setShadowLayer(0f, 0f, 0f, Color.TRANSPARENT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore canvas state
|
|
||||||
canvas.restore()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for touch events
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
||||||
if (gameOver || paused) return false
|
|
||||||
|
|
||||||
when (event.action) {
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
|
||||||
lastTouchX = event.x
|
|
||||||
lastTouchY = event.y
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
|
||||||
val diffX = event.x - lastTouchX
|
|
||||||
val diffY = event.y - lastTouchY
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
|
|
||||||
// Check if cooldown has elapsed since last move
|
|
||||||
if (currentTime - lastMoveTime < moveCooldown) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if drag distance exceeds threshold for movement
|
|
||||||
if (abs(diffX) > swipeThreshold && abs(diffX) > abs(diffY) * 1.2f) {
|
|
||||||
// Horizontal movement - requiring less pronounced horizontal movement for smoother control
|
|
||||||
if (diffX > 0) {
|
|
||||||
game?.moveRight()
|
|
||||||
} else {
|
|
||||||
game?.moveLeft()
|
|
||||||
}
|
|
||||||
// Update last position after processing the move
|
|
||||||
lastTouchX = event.x
|
|
||||||
lastMoveTime = currentTime
|
|
||||||
invalidate()
|
|
||||||
return true
|
|
||||||
} else if (abs(diffY) > swipeThreshold && abs(diffY) > abs(diffX) * 1.2f) {
|
|
||||||
// Vertical movement - requiring less pronounced vertical movement for smoother control
|
|
||||||
if (diffY > 0) {
|
|
||||||
game?.moveDown()
|
|
||||||
// Update last position after processing the move
|
|
||||||
lastTouchY = event.y
|
|
||||||
lastMoveTime = currentTime
|
|
||||||
invalidate()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_UP -> {
|
|
||||||
val diffX = event.x - lastTouchX
|
|
||||||
val diffY = event.y - lastTouchY
|
|
||||||
val totalMovement = abs(diffX) + abs(diffY)
|
|
||||||
|
|
||||||
// If this was a tap (very minimal movement)
|
|
||||||
if (totalMovement < tapThreshold) {
|
|
||||||
// Simple tap to rotate
|
|
||||||
game?.rotate()
|
|
||||||
invalidate()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for deliberate swipe up (hard drop) - more forgiving upward movement
|
|
||||||
if (abs(diffY) > swipeThreshold * 1.2f && diffY < 0 && abs(diffY) > abs(diffX) * 1.5f) {
|
|
||||||
game?.hardDrop()
|
|
||||||
invalidate()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
stopAutoRepeat()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're handling taps directly in onTouchEvent
|
|
||||||
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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 - move right once
|
|
||||||
game?.moveRight()
|
|
||||||
} else {
|
|
||||||
// Swipe left - move left once
|
|
||||||
game?.moveLeft()
|
|
||||||
}
|
|
||||||
invalidate()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Vertical swipe
|
|
||||||
if (abs(velocityY) > minSwipeVelocity && abs(diffY) > minSwipeDistance) {
|
|
||||||
if (diffY > 0) {
|
|
||||||
// Swipe down - start soft drop
|
|
||||||
startAutoRepeat { game?.moveDown() }
|
|
||||||
} else {
|
|
||||||
// Swipe up - hard drop
|
|
||||||
game?.hardDrop()
|
|
||||||
}
|
|
||||||
invalidate()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create touch control buttons
|
|
||||||
fun createTouchControlButtons() {
|
|
||||||
// Create 3D rotation buttons
|
|
||||||
val context = context ?: return
|
|
||||||
|
|
||||||
// First check if buttons are already added to prevent duplicates
|
|
||||||
val parent = parent as? android.view.ViewGroup ?: return
|
|
||||||
if (parent.findViewWithTag<View>("rotate_buttons") != null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a container for rotation buttons
|
|
||||||
val rotateButtons = android.widget.LinearLayout(context)
|
|
||||||
rotateButtons.tag = "rotate_buttons"
|
|
||||||
rotateButtons.orientation = android.widget.LinearLayout.HORIZONTAL
|
|
||||||
rotateButtons.layoutParams = android.view.ViewGroup.LayoutParams(
|
|
||||||
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add layout to position at bottom of screen
|
|
||||||
rotateButtons.gravity = android.view.Gravity.CENTER
|
|
||||||
val params = android.widget.FrameLayout.LayoutParams(
|
|
||||||
android.widget.FrameLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
android.widget.FrameLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
params.gravity = android.view.Gravity.BOTTOM
|
|
||||||
params.setMargins(16, 16, 16, 32) // Add more bottom margin for visibility
|
|
||||||
rotateButtons.layoutParams = params
|
|
||||||
|
|
||||||
// Create vertical flip button (X-axis rotation)
|
|
||||||
val verticalFlipButton = android.widget.Button(context)
|
|
||||||
verticalFlipButton.text = "Flip ↑↓"
|
|
||||||
verticalFlipButton.tag = "flip_vertical_button"
|
|
||||||
val buttonParams = android.widget.LinearLayout.LayoutParams(
|
|
||||||
0,
|
|
||||||
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT,
|
|
||||||
1.0f
|
|
||||||
)
|
|
||||||
buttonParams.setMargins(12, 12, 12, 12)
|
|
||||||
verticalFlipButton.layoutParams = buttonParams
|
|
||||||
|
|
||||||
// Style the button
|
|
||||||
verticalFlipButton.setBackgroundColor(android.graphics.Color.parseColor("#0088ff"))
|
|
||||||
verticalFlipButton.setTextColor(android.graphics.Color.WHITE)
|
|
||||||
verticalFlipButton.setPadding(8, 16, 8, 16)
|
|
||||||
|
|
||||||
// Create horizontal flip button (Y-axis rotation)
|
|
||||||
val horizontalFlipButton = android.widget.Button(context)
|
|
||||||
horizontalFlipButton.text = "Flip ←→"
|
|
||||||
horizontalFlipButton.tag = "flip_horizontal_button"
|
|
||||||
horizontalFlipButton.layoutParams = buttonParams
|
|
||||||
|
|
||||||
// Style the button
|
|
||||||
horizontalFlipButton.setBackgroundColor(android.graphics.Color.parseColor("#ff5500"))
|
|
||||||
horizontalFlipButton.setTextColor(android.graphics.Color.WHITE)
|
|
||||||
horizontalFlipButton.setPadding(8, 16, 8, 16)
|
|
||||||
|
|
||||||
// Add buttons to container
|
|
||||||
rotateButtons.addView(verticalFlipButton)
|
|
||||||
rotateButtons.addView(horizontalFlipButton)
|
|
||||||
|
|
||||||
// Add the button container to the parent view
|
|
||||||
val rootView = parent.rootView as? android.widget.FrameLayout
|
|
||||||
rootView?.addView(rotateButtons)
|
|
||||||
|
|
||||||
// Add click listeners
|
|
||||||
verticalFlipButton.setOnClickListener {
|
|
||||||
if (!game?.isGameOver!! && game?.isRunning!!) {
|
|
||||||
game?.rotate3DX()
|
|
||||||
verticalFlipButton.alpha = 0.7f
|
|
||||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
||||||
verticalFlipButton.alpha = 1.0f
|
|
||||||
}, 150)
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
horizontalFlipButton.setOnClickListener {
|
|
||||||
if (!game?.isGameOver!! && game?.isRunning!!) {
|
|
||||||
game?.rotate3DY()
|
|
||||||
horizontalFlipButton.alpha = 0.7f
|
|
||||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
||||||
horizontalFlipButton.alpha = 1.0f
|
|
||||||
}, 150)
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and add instructions text view if needed
|
|
||||||
// Note: For Android implementation we'll show a toast instead of persistent instructions
|
|
||||||
val instructions = "Swipe to move, tap to rotate, swipe down for soft drop, swipe up for hard drop. Use buttons to flip pieces."
|
|
||||||
android.widget.Toast.makeText(context, instructions, android.widget.Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the game state flags from the TetrisGame
|
|
||||||
fun updateGameState() {
|
|
||||||
game?.let {
|
|
||||||
gameOver = it.isGameOver
|
|
||||||
paused = !it.isRunning
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,16 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,183 +0,0 @@
|
||||||
<?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/rotationHint"
|
|
||||||
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/rotationHint"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Tap LEFT side: Horizontal Rotation (X) • Tap RIGHT side: Vertical Rotation (Y)"
|
|
||||||
android:textColor="#ffaa00"
|
|
||||||
android:textSize="13sp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/controlsHint"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/infoPanel"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
<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"
|
|
||||||
android:textColor="#00ffff"
|
|
||||||
android:textSize="13sp"
|
|
||||||
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>
|
|
|
@ -1,46 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,119 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,5 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,5 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,15 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,22 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
|
@ -1,19 +0,0 @@
|
||||||
# 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
BIN
android-app/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
|
@ -1,7 +0,0 @@
|
||||||
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
252
android-app/gradlew
vendored
|
@ -1,252 +0,0 @@
|
||||||
#!/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
94
android-app/gradlew.bat
vendored
|
@ -1,94 +0,0 @@
|
||||||
@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
|
|
|
@ -1,8 +0,0 @@
|
||||||
## 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
|
|
|
@ -1,2 +0,0 @@
|
||||||
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