diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c5f3f6b..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.configuration.updateBuildConfiguration": "interactive" -} \ No newline at end of file diff --git a/android-app/.gradle/7.5/checksums/checksums.lock b/android-app/.gradle/7.5/checksums/checksums.lock deleted file mode 100644 index 4aeb5ce..0000000 Binary files a/android-app/.gradle/7.5/checksums/checksums.lock and /dev/null differ diff --git a/android-app/.gradle/7.5/fileChanges/last-build.bin b/android-app/.gradle/7.5/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/android-app/.gradle/7.5/fileChanges/last-build.bin and /dev/null differ diff --git a/android-app/.gradle/7.5/fileHashes/fileHashes.lock b/android-app/.gradle/7.5/fileHashes/fileHashes.lock deleted file mode 100644 index 2411039..0000000 Binary files a/android-app/.gradle/7.5/fileHashes/fileHashes.lock and /dev/null differ diff --git a/android-app/.gradle/7.5/gc.properties b/android-app/.gradle/7.5/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android-app/.gradle/8.10/checksums/checksums.lock b/android-app/.gradle/8.10/checksums/checksums.lock deleted file mode 100644 index 9bcb542..0000000 Binary files a/android-app/.gradle/8.10/checksums/checksums.lock and /dev/null differ diff --git a/android-app/.gradle/8.10/checksums/md5-checksums.bin b/android-app/.gradle/8.10/checksums/md5-checksums.bin deleted file mode 100644 index 509ec20..0000000 Binary files a/android-app/.gradle/8.10/checksums/md5-checksums.bin and /dev/null differ diff --git a/android-app/.gradle/8.10/checksums/sha1-checksums.bin b/android-app/.gradle/8.10/checksums/sha1-checksums.bin deleted file mode 100644 index 6bd57f3..0000000 Binary files a/android-app/.gradle/8.10/checksums/sha1-checksums.bin and /dev/null differ diff --git a/android-app/.gradle/8.10/dependencies-accessors/gc.properties b/android-app/.gradle/8.10/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android-app/.gradle/8.10/executionHistory/executionHistory.bin b/android-app/.gradle/8.10/executionHistory/executionHistory.bin deleted file mode 100644 index 1e5933b..0000000 Binary files a/android-app/.gradle/8.10/executionHistory/executionHistory.bin and /dev/null differ diff --git a/android-app/.gradle/8.10/executionHistory/executionHistory.lock b/android-app/.gradle/8.10/executionHistory/executionHistory.lock deleted file mode 100644 index d420e60..0000000 Binary files a/android-app/.gradle/8.10/executionHistory/executionHistory.lock and /dev/null differ diff --git a/android-app/.gradle/8.10/fileChanges/last-build.bin b/android-app/.gradle/8.10/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/android-app/.gradle/8.10/fileChanges/last-build.bin and /dev/null differ diff --git a/android-app/.gradle/8.10/fileHashes/fileHashes.bin b/android-app/.gradle/8.10/fileHashes/fileHashes.bin deleted file mode 100644 index 5fd80eb..0000000 Binary files a/android-app/.gradle/8.10/fileHashes/fileHashes.bin and /dev/null differ diff --git a/android-app/.gradle/8.10/fileHashes/fileHashes.lock b/android-app/.gradle/8.10/fileHashes/fileHashes.lock deleted file mode 100644 index eaa5424..0000000 Binary files a/android-app/.gradle/8.10/fileHashes/fileHashes.lock and /dev/null differ diff --git a/android-app/.gradle/8.10/gc.properties b/android-app/.gradle/8.10/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android-app/.gradle/8.11.1/checksums/checksums.lock b/android-app/.gradle/8.11.1/checksums/checksums.lock deleted file mode 100644 index 3a2f1da..0000000 Binary files a/android-app/.gradle/8.11.1/checksums/checksums.lock and /dev/null differ diff --git a/android-app/.gradle/8.11.1/checksums/md5-checksums.bin b/android-app/.gradle/8.11.1/checksums/md5-checksums.bin deleted file mode 100644 index 9f7f27d..0000000 Binary files a/android-app/.gradle/8.11.1/checksums/md5-checksums.bin and /dev/null differ diff --git a/android-app/.gradle/8.11.1/checksums/sha1-checksums.bin b/android-app/.gradle/8.11.1/checksums/sha1-checksums.bin deleted file mode 100644 index 27b3854..0000000 Binary files a/android-app/.gradle/8.11.1/checksums/sha1-checksums.bin and /dev/null differ diff --git a/android-app/.gradle/8.11.1/executionHistory/executionHistory.bin b/android-app/.gradle/8.11.1/executionHistory/executionHistory.bin deleted file mode 100644 index 1e65897..0000000 Binary files a/android-app/.gradle/8.11.1/executionHistory/executionHistory.bin and /dev/null differ diff --git a/android-app/.gradle/8.11.1/executionHistory/executionHistory.lock b/android-app/.gradle/8.11.1/executionHistory/executionHistory.lock deleted file mode 100644 index 152df37..0000000 Binary files a/android-app/.gradle/8.11.1/executionHistory/executionHistory.lock and /dev/null differ diff --git a/android-app/.gradle/8.11.1/fileChanges/last-build.bin b/android-app/.gradle/8.11.1/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/android-app/.gradle/8.11.1/fileChanges/last-build.bin and /dev/null differ diff --git a/android-app/.gradle/8.11.1/fileHashes/fileHashes.bin b/android-app/.gradle/8.11.1/fileHashes/fileHashes.bin deleted file mode 100644 index 9dde153..0000000 Binary files a/android-app/.gradle/8.11.1/fileHashes/fileHashes.bin and /dev/null differ diff --git a/android-app/.gradle/8.11.1/fileHashes/fileHashes.lock b/android-app/.gradle/8.11.1/fileHashes/fileHashes.lock deleted file mode 100644 index 8190865..0000000 Binary files a/android-app/.gradle/8.11.1/fileHashes/fileHashes.lock and /dev/null differ diff --git a/android-app/.gradle/8.11.1/fileHashes/resourceHashesCache.bin b/android-app/.gradle/8.11.1/fileHashes/resourceHashesCache.bin deleted file mode 100644 index 92a1969..0000000 Binary files a/android-app/.gradle/8.11.1/fileHashes/resourceHashesCache.bin and /dev/null differ diff --git a/android-app/.gradle/8.11.1/gc.properties b/android-app/.gradle/8.11.1/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android-app/.gradle/8.8/checksums/checksums.lock b/android-app/.gradle/8.8/checksums/checksums.lock deleted file mode 100644 index 7f0b8e9..0000000 Binary files a/android-app/.gradle/8.8/checksums/checksums.lock and /dev/null differ diff --git a/android-app/.gradle/8.8/checksums/md5-checksums.bin b/android-app/.gradle/8.8/checksums/md5-checksums.bin deleted file mode 100644 index efd3160..0000000 Binary files a/android-app/.gradle/8.8/checksums/md5-checksums.bin and /dev/null differ diff --git a/android-app/.gradle/8.8/checksums/sha1-checksums.bin b/android-app/.gradle/8.8/checksums/sha1-checksums.bin deleted file mode 100644 index 0c5c37d..0000000 Binary files a/android-app/.gradle/8.8/checksums/sha1-checksums.bin and /dev/null differ diff --git a/android-app/.gradle/8.8/dependencies-accessors/gc.properties b/android-app/.gradle/8.8/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android-app/.gradle/8.8/fileChanges/last-build.bin b/android-app/.gradle/8.8/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/android-app/.gradle/8.8/fileChanges/last-build.bin and /dev/null differ diff --git a/android-app/.gradle/8.8/fileHashes/fileHashes.bin b/android-app/.gradle/8.8/fileHashes/fileHashes.bin deleted file mode 100644 index 689e596..0000000 Binary files a/android-app/.gradle/8.8/fileHashes/fileHashes.bin and /dev/null differ diff --git a/android-app/.gradle/8.8/fileHashes/fileHashes.lock b/android-app/.gradle/8.8/fileHashes/fileHashes.lock deleted file mode 100644 index 28f50b2..0000000 Binary files a/android-app/.gradle/8.8/fileHashes/fileHashes.lock and /dev/null differ diff --git a/android-app/.gradle/8.8/gc.properties b/android-app/.gradle/8.8/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android-app/.gradle/8.9/checksums/checksums.lock b/android-app/.gradle/8.9/checksums/checksums.lock deleted file mode 100644 index 347b45b..0000000 Binary files a/android-app/.gradle/8.9/checksums/checksums.lock and /dev/null differ diff --git a/android-app/.gradle/8.9/dependencies-accessors/gc.properties b/android-app/.gradle/8.9/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android-app/.gradle/8.9/executionHistory/executionHistory.lock b/android-app/.gradle/8.9/executionHistory/executionHistory.lock deleted file mode 100644 index 1716945..0000000 Binary files a/android-app/.gradle/8.9/executionHistory/executionHistory.lock and /dev/null differ diff --git a/android-app/.gradle/8.9/fileChanges/last-build.bin b/android-app/.gradle/8.9/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/android-app/.gradle/8.9/fileChanges/last-build.bin and /dev/null differ diff --git a/android-app/.gradle/8.9/fileHashes/fileHashes.lock b/android-app/.gradle/8.9/fileHashes/fileHashes.lock deleted file mode 100644 index bbe8235..0000000 Binary files a/android-app/.gradle/8.9/fileHashes/fileHashes.lock and /dev/null differ diff --git a/android-app/.gradle/8.9/gc.properties b/android-app/.gradle/8.9/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android-app/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/android-app/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index 8909f05..0000000 Binary files a/android-app/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ diff --git a/android-app/.gradle/buildOutputCleanup/cache.properties b/android-app/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index 86e9063..0000000 --- a/android-app/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Tue Mar 25 23:50:46 EDT 2025 -gradle.version=8.11.1 diff --git a/android-app/.gradle/buildOutputCleanup/outputFiles.bin b/android-app/.gradle/buildOutputCleanup/outputFiles.bin deleted file mode 100644 index 86fc029..0000000 Binary files a/android-app/.gradle/buildOutputCleanup/outputFiles.bin and /dev/null differ diff --git a/android-app/.gradle/config.properties b/android-app/.gradle/config.properties deleted file mode 100644 index f2e3794..0000000 --- a/android-app/.gradle/config.properties +++ /dev/null @@ -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 diff --git a/android-app/.gradle/file-system.probe b/android-app/.gradle/file-system.probe deleted file mode 100644 index c7a2e14..0000000 Binary files a/android-app/.gradle/file-system.probe and /dev/null differ diff --git a/android-app/.gradle/nb-cache/android-app-623140067/project-info.ser b/android-app/.gradle/nb-cache/android-app-623140067/project-info.ser deleted file mode 100644 index 5c2e123..0000000 Binary files a/android-app/.gradle/nb-cache/android-app-623140067/project-info.ser and /dev/null differ diff --git a/android-app/.gradle/nb-cache/app-271321845/project-info.ser b/android-app/.gradle/nb-cache/app-271321845/project-info.ser deleted file mode 100644 index 5de0ac0..0000000 Binary files a/android-app/.gradle/nb-cache/app-271321845/project-info.ser and /dev/null differ diff --git a/android-app/.gradle/nb-cache/subprojects.ser b/android-app/.gradle/nb-cache/subprojects.ser deleted file mode 100644 index faa7cf7..0000000 Binary files a/android-app/.gradle/nb-cache/subprojects.ser and /dev/null differ diff --git a/android-app/.gradle/nb-cache/trust/388F7AB02DFD0183E3FD2FB1B5B4206B0C7407CAFFD7F76F3C1EF65077068EC5 b/android-app/.gradle/nb-cache/trust/388F7AB02DFD0183E3FD2FB1B5B4206B0C7407CAFFD7F76F3C1EF65077068EC5 deleted file mode 100644 index f62917f..0000000 --- a/android-app/.gradle/nb-cache/trust/388F7AB02DFD0183E3FD2FB1B5B4206B0C7407CAFFD7F76F3C1EF65077068EC5 +++ /dev/null @@ -1 +0,0 @@ -8B64E587393F94313EFF41233F48D4848E985369BD2E4B833B25B3765823A6DB diff --git a/android-app/.gradle/vcs-1/gc.properties b/android-app/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/android-app/.idea/.gitignore b/android-app/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/android-app/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/android-app/.idea/.name b/android-app/.idea/.name deleted file mode 100644 index 8f54013..0000000 --- a/android-app/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -Tetris3D \ No newline at end of file diff --git a/android-app/.idea/AndroidProjectSystem.xml b/android-app/.idea/AndroidProjectSystem.xml deleted file mode 100644 index 4a53bee..0000000 --- a/android-app/.idea/AndroidProjectSystem.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android-app/.idea/appInsightsSettings.xml b/android-app/.idea/appInsightsSettings.xml deleted file mode 100644 index 371f2e2..0000000 --- a/android-app/.idea/appInsightsSettings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android-app/.idea/caches/deviceStreaming.xml b/android-app/.idea/caches/deviceStreaming.xml deleted file mode 100644 index 9e9ba09..0000000 --- a/android-app/.idea/caches/deviceStreaming.xml +++ /dev/null @@ -1,607 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android-app/.idea/compiler.xml b/android-app/.idea/compiler.xml deleted file mode 100644 index b86273d..0000000 --- a/android-app/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android-app/.idea/deploymentTargetSelector.xml b/android-app/.idea/deploymentTargetSelector.xml deleted file mode 100644 index b268ef3..0000000 --- a/android-app/.idea/deploymentTargetSelector.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/android-app/.idea/gradle.xml b/android-app/.idea/gradle.xml deleted file mode 100644 index 639c779..0000000 --- a/android-app/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/android-app/.idea/kotlinc.xml b/android-app/.idea/kotlinc.xml deleted file mode 100644 index 2b8a50f..0000000 --- a/android-app/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android-app/.idea/migrations.xml b/android-app/.idea/migrations.xml deleted file mode 100644 index f8051a6..0000000 --- a/android-app/.idea/migrations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android-app/.idea/misc.xml b/android-app/.idea/misc.xml deleted file mode 100644 index 74dd639..0000000 --- a/android-app/.idea/misc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/android-app/.idea/runConfigurations.xml b/android-app/.idea/runConfigurations.xml deleted file mode 100644 index 16660f1..0000000 --- a/android-app/.idea/runConfigurations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android-app/.idea/vcs.xml b/android-app/.idea/vcs.xml deleted file mode 100644 index 6c0b863..0000000 --- a/android-app/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android-app/README.md b/android-app/README.md deleted file mode 100644 index b12cb8b..0000000 --- a/android-app/README.md +++ /dev/null @@ -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. \ No newline at end of file diff --git a/android-app/app/build.gradle b/android-app/app/build.gradle deleted file mode 100644 index dd59f99..0000000 --- a/android-app/app/build.gradle +++ /dev/null @@ -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' -} \ No newline at end of file diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml deleted file mode 100644 index 3f8982d..0000000 --- a/android-app/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - diff --git a/android-app/app/src/main/java/com/tetris3d/MainActivity.kt b/android-app/app/src/main/java/com/tetris3d/MainActivity.kt deleted file mode 100644 index 634ac69..0000000 --- a/android-app/app/src/main/java/com/tetris3d/MainActivity.kt +++ /dev/null @@ -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(R.id.textFinalScore) - val playAgainButton = view.findViewById - - + diff --git a/script.js b/script.js index d44a24a..2c53eb2 100644 --- a/script.js +++ b/script.js @@ -70,7 +70,6 @@ let enableSpinAnimations = true; let animationSpeed = 0.05; let forceMobileControls = false; let reduceEffectsOnMobile = true; // New option to reduce effects on mobile -let startingLevel = 1; // New option for starting level // Controller variables let gamepadConnected = false; @@ -79,28 +78,16 @@ let controllerMapping = { left: [14, 'dpadLeft'], // D-pad left or left stick left right: [15, 'dpadRight'], // D-pad right or left stick right down: [13, 'dpadDown'], // D-pad down or left stick down - up: [12, 'dpadUp'], // D-pad up (added for hard drop) rotateLeft: [0, 'buttonA'], // A button rotateRight: [1, 'buttonB'], // B button mirrorH: [3, 'buttonY'], // Y button - horizontal mirror mirrorV: [2, 'buttonX'], // X button - vertical mirror - hardDrop: [12, 'dpadUp'], // Changed from RT to D-pad up + hardDrop: [7, 'rightTrigger'], // RT button pause: [9, 'start'] // Start button }; let lastControllerState = {}; -let controllerPollingRate = 16; // Increased polling rate (from 100ms to 16ms for ~60fps) +let controllerPollingRate = 100; // ms let controllerInterval; -let buttonHoldDelays = { - left: 150, // Initial delay before repeating - right: 150, // Initial delay before repeating - down: 50 // Fast repeat for down -}; -let buttonRepeatRates = { - left: 100, // Repeat rate after initial delay - right: 100, // Repeat rate after initial delay - down: 50 // Fast repeat rate for down -}; -let buttonHoldTimers = {}; // Fireworks array let fireworks = []; @@ -589,67 +576,78 @@ class Piece { // Rotate with 3D animation rotate(direction) { - // Skip if already in an animation - if (this.rotationTransition || this.showCompletionEffect) { - return; - } + // Direction can be 'right' or 'left' + if (gameOver || paused) return; - // Reset animation state - this.resetAnimationState(); - - // Square pieces (O) don't need to rotate + // Square piece (O) doesn't rotate if (this.tetrominoType === 'O') { return; } - // If animations are disabled, do simple rotation - if (!enableSpinAnimations || !enable3DEffects) { - // Use standard rotation without animation - let nextPattern = (direction === 'right') ? - (this.tetrominoN + 1) % this.tetromino.length : - (this.tetrominoN + this.tetromino.length - 1) % this.tetromino.length; - - // Try to rotate and apply kicks if needed - const kickResult = this.tryRotateWithKicks(nextPattern); - - if (kickResult.success) { - // Apply rotation - this.x += kickResult.kick; - this.tetrominoN = nextPattern; - this.activeTetromino = this.tetromino[this.tetrominoN]; - this.shadowTetromino = this.activeTetromino; - - // Update shadow - this.calculateShadowY(); - - // Play sound - playPieceSound('rotate'); - } - - // Draw the piece - this.draw(); - return; - } - - // Find the next pattern based on rotation direction - let nextPattern = (direction === 'right') ? - (this.tetrominoN + 1) % this.tetromino.length : - (this.tetrominoN + this.tetromino.length - 1) % this.tetromino.length; - - // Try to rotate with kicks - const kickResult = this.tryRotateWithKicks(nextPattern); - - if (!kickResult.success) { - return; // Rotation failed - } - - // We found a valid kick - save for animation completion - const validKick = kickResult.kick; - // Clear previous position this.undraw(); clearPreviousPiecePosition(); + // For I tetromino, rotation pattern is different (cycles through 0-1) + // For other tetrominoes, it cycles through 0-1-2-3 + let nextPattern; + + if (direction === 'right') { + // Rotate clockwise + if (this.tetrominoType === 'I') { + nextPattern = (this.tetrominoN + 1) % 2; + } else { + nextPattern = (this.tetrominoN + 1) % 4; + } + } else if (direction === 'left') { + // Rotate counter-clockwise + if (this.tetrominoType === 'I') { + nextPattern = (this.tetrominoN - 1 + 2) % 2; + } else { + nextPattern = (this.tetrominoN - 1 + 4) % 4; + } + } + + // Wall kick testing for rotation + // Try the standard position first, then the kickTable positions + const kicks = this.getKicks(this.tetrominoN, nextPattern); + + // Try each kick position until we find one that works + let validKick = null; + + for (let i = 0; i < kicks.length; i++) { + const [kickX, kickY] = kicks[i]; + + if (!this.collision(kickX, kickY, this.tetromino[nextPattern])) { + validKick = kickX; + // If a successful position is found, break the loop + break; + } + } + + // If no valid kick position found, keep the original position + if (validKick === null) { + this.draw(); + return; + } + + // If animations disabled, apply rotation immediately + if (!enableSpinAnimations) { + this.x += validKick; + this.tetrominoN = nextPattern; + this.activeTetromino = this.tetromino[this.tetrominoN]; + this.shadowTetromino = this.activeTetromino; + + // Play rotation sound + playPieceSound('rotate'); + + // Update shadow position + this.calculateShadowY(); + + this.draw(); + return; + } + // Store for animation completion this.targetPattern = nextPattern; this.targetKick = validKick; @@ -677,14 +675,20 @@ class Piece { return; } - // First, completely reset all animation state variables - // to prevent any lingering effects from rotations - this.resetAnimationState(); + // Cancel any ongoing rotations or animations + this.rotationTransition = false; + this.showCompletionEffect = false; + this.rotationAngleX = 0; + this.rotationAngleY = 0; + this.rotationAngleZ = 0; - // Save current state for proper undraw - const currentTetromino = this.activeTetromino; - const currentX = this.x; - const currentY = this.y; + // Clear rotation state completely + this.rotationDirection = null; + this.targetTetromino = null; + this.targetPattern = undefined; + this.targetKick = undefined; + this.originalTetromino = null; + this.rotationProgress = 0; // Clear previous position this.undraw(); @@ -695,40 +699,22 @@ class Piece { this.y++; } - // Lock the piece at its final position + // Lock the piece this.lock(); - // Get the next piece + // Replace with getNextPiece function call instead of direct assignment getNextPiece(); // Calculate shadow for new piece p.calculateShadowY(); - // Draw the new piece and board - drawBoard(); + // Draw the new piece p.draw(); // Play hard drop sound playPieceSound('hardDrop'); } - // Reset all animation state - resetAnimationState() { - this.rotationTransition = false; - this.showCompletionEffect = false; - this.rotationAngleX = 0; - this.rotationAngleY = 0; - this.rotationAngleZ = 0; - this.rotationDirection = null; - this.rotationProgress = 0; - this.rotationEasing = false; - this.completionEffectProgress = 0; - this.targetTetromino = null; - this.targetPattern = undefined; - this.targetKick = undefined; - this.originalTetromino = null; - } - // 3D horizontal rotation effect (around Y axis) rotate3DY() { // Square pieces (O) shouldn't rotate @@ -736,14 +722,6 @@ class Piece { return; } - // Skip if already in an animation - if (this.rotationTransition || this.showCompletionEffect) { - return; - } - - // Reset animation state - this.resetAnimationState(); - // Clear previous position this.undraw(); clearPreviousPiecePosition(); @@ -805,14 +783,6 @@ class Piece { return; } - // Skip if already in an animation - if (this.rotationTransition || this.showCompletionEffect) { - return; - } - - // Reset animation state - this.resetAnimationState(); - // Clear previous position this.undraw(); clearPreviousPiecePosition(); @@ -1098,12 +1068,31 @@ class Piece { this.shadowTetromino = this.targetTetromino; } - // Completely reset rotation state - this.resetAnimationState(); + // Reset rotation state + this.rotationTransition = false; + this.rotationProgress = 0; + + // Clear target references to avoid memory leaks and state issues + this.targetTetromino = null; + this.targetPattern = undefined; + this.targetKick = undefined; + this.originalTetromino = null; + + // Reset rotation angles + this.rotationAngleX = 0; + this.rotationAngleY = 0; + this.rotationAngleZ = 0; // Recalculate shadow position this.calculateShadowY(); + // Skip completion effect on mobile for performance + if (!mobilePerformanceMode && enable3DEffects) { + // Start completion effect + this.showCompletionEffect = true; + this.completionEffectProgress = 0; + } + // Draw in new position this.draw(); return; @@ -1134,12 +1123,18 @@ class Piece { this.completionEffectProgress += completionStep; if (this.completionEffectProgress >= 1) { - // Completely reset animation state - this.resetAnimationState(); + this.showCompletionEffect = false; + this.completionEffectProgress = 0; - // Redraw with final state - this.draw(); - return; + // Reset rotation angles + this.rotationAngleX = 0; + this.rotationAngleY = 0; + this.rotationAngleZ = 0; + + // Clear all rotation references + this.rotationDirection = null; + this.originalTetromino = null; + this.targetTetromino = null; } } @@ -1321,24 +1316,6 @@ class Piece { return kickTable[key] || [[0, 0]]; } } - - // Try rotation with wall kicks - tryRotateWithKicks(nextPattern) { - // Get kicks for this rotation - const kicks = this.getKicks(this.tetrominoN, nextPattern); - - // Try each kick position until we find one that works - for (let i = 0; i < kicks.length; i++) { - const [kickX, kickY] = kicks[i]; - - if (!this.collision(kickX, kickY, this.tetromino[nextPattern])) { - return { success: true, kick: kickX }; - } - } - - // No valid kick position found - return { success: false, kick: 0 }; - } } @@ -1788,58 +1765,16 @@ function dropPiece() { // Show game over modal function showGameOver() { - // Stop game interval clearInterval(gameInterval); - - // Clear all controller button hold timers - clearAllButtonHoldTimers(); - - // Update final score finalScoreElement.textContent = score; - - // Show modal gameOverModal.classList.add('active'); } -// Clear all button hold timers -function clearAllButtonHoldTimers() { - ['left', 'right', 'down'].forEach(action => { - if (buttonHoldTimers[action]) { - clearInterval(buttonHoldTimers[action]); - buttonHoldTimers[action] = null; - } - }); -} - -// Toggle pause -function togglePause() { - if (gameOver) return; - - paused = !paused; - - if (paused) { - clearInterval(gameInterval); - // Clear all controller button hold timers - clearAllButtonHoldTimers(); - pauseBtn.textContent = 'Resume'; - // Show pause message - showMessage('PAUSED', 'Press P to Resume'); - } else { - let speed = Math.max(100, 1000 - (level * 100)); - gameInterval = setInterval(dropPiece, speed); - pauseBtn.textContent = 'Pause'; - // Hide pause message - hideMessage(); - } -} - // Reset the game function resetGame() { // Reset game variables score = 0; - console.log("Starting level is set to:", startingLevel); - level = startingLevel; - console.log("Level is now set to:", level); + level = 1; lines = 0; gameOver = false; paused = false; @@ -1867,18 +1802,26 @@ function resetGame() { // Start the game interval dropStart = Date.now(); clearInterval(gameInterval); - // Set interval based on starting level - let speed = Math.max(100, 1000 - (level * 100)); - console.log("Game speed set to:", speed, "ms"); - gameInterval = setInterval(dropPiece, speed); + gameInterval = setInterval(dropPiece, 1000); +} + +// Toggle pause +function togglePause() { + paused = !paused; + + if (paused) { + clearInterval(gameInterval); + pauseBtn.textContent = "Resume"; + } else { + gameInterval = setInterval(dropPiece, Math.max(100, 1000 - (level * 100))); + pauseBtn.textContent = "Pause"; + } } // Toggle options modal function toggleOptionsModal() { if (optionsModal.classList.contains('active')) { optionsModal.classList.remove('active'); - // Apply options when closing the modal - applyOptions(); } else { optionsModal.classList.add('active'); } @@ -1890,7 +1833,6 @@ function applyOptions() { enableSpinAnimations = toggleSpinAnimations.checked; animationSpeed = parseFloat(animationSpeedSlider.value); forceMobileControls = toggleMobileControls.checked; - startingLevel = parseInt(document.getElementById('starting-level').value); // Apply mobile controls if checked or if on mobile device if (forceMobileControls) { @@ -1925,8 +1867,7 @@ function saveOptions() { enableSpinAnimations, animationSpeed, forceMobileControls, - reduceEffectsOnMobile, - startingLevel + reduceEffectsOnMobile })); } @@ -1941,7 +1882,6 @@ function loadOptions() { animationSpeed = options.animationSpeed !== undefined ? options.animationSpeed : 0.05; forceMobileControls = options.forceMobileControls !== undefined ? options.forceMobileControls : false; reduceEffectsOnMobile = options.reduceEffectsOnMobile !== undefined ? options.reduceEffectsOnMobile : true; - startingLevel = options.startingLevel !== undefined ? options.startingLevel : 1; // Update UI controls toggle3DEffects.checked = enable3DEffects; @@ -1955,10 +1895,6 @@ function loadOptions() { if (document.getElementById('toggle-mobile-performance')) { document.getElementById('toggle-mobile-performance').checked = reduceEffectsOnMobile; } - - if (document.getElementById('starting-level')) { - document.getElementById('starting-level').value = startingLevel; - } } } @@ -1970,10 +1906,6 @@ function init() { // Load saved options loadOptions(); - // Set initial level - level = startingLevel; - levelElement.textContent = level; - // Draw the board drawBoard(); @@ -1986,9 +1918,7 @@ function init() { // Set up game interval dropStart = Date.now(); - // Use level-appropriate speed - let speed = Math.max(100, 1000 - (level * 100)); - gameInterval = setInterval(dropPiece, speed); + gameInterval = setInterval(dropPiece, 1000); // Listen for keyboard events document.addEventListener('keydown', control); @@ -2007,14 +1937,6 @@ function init() { optionsBtn.addEventListener('click', toggleOptionsModal); optionsCloseBtn.addEventListener('click', toggleOptionsModal); - // Add event listener for the apply button - if (document.getElementById('options-apply-btn')) { - document.getElementById('options-apply-btn').addEventListener('click', function() { - applyOptions(); - console.log("Options applied, starting level is now:", startingLevel); - }); - } - // Options event listeners toggle3DEffects.addEventListener('change', function() { applyOptions(); @@ -2028,12 +1950,6 @@ function init() { applyOptions(); }); - if (document.getElementById('starting-level')) { - document.getElementById('starting-level').addEventListener('change', function() { - applyOptions(); - }); - } - if (document.getElementById('toggle-mobile-performance')) { document.getElementById('toggle-mobile-performance').addEventListener('change', function() { mobilePerformanceMode = this.checked; @@ -2072,17 +1988,68 @@ function handleResize() { const gameWrapper = document.querySelector('.game-wrapper'); const scoreContainer = document.querySelector('.score-container'); - // Make sure canvas maintains its dimensions - canvas.width = COLS * BLOCK_SIZE; - canvas.height = ROWS * BLOCK_SIZE; - - // Redraw the board and current piece - drawBoard(); - if (p) { - p.draw(); - if (showShadow) { - p.drawShadow(); + if (isMobile || forceMobileControls) { + // Scale the canvas to fit mobile screen + const viewportWidth = Math.min(window.innerWidth, document.documentElement.clientWidth); + const viewportHeight = Math.min(window.innerHeight, document.documentElement.clientHeight); + + // Detect orientation + const isPortrait = viewportHeight > viewportWidth; + + // Calculate available game area (accounting for UI elements) + const titleHeight = 40; // Estimate for title + const scoreWidth = isPortrait ? 120 : 100; // Width for score container in portrait/landscape + const availableWidth = viewportWidth - scoreWidth - 20; // Subtract score width + padding + const availableHeight = viewportHeight - titleHeight - 20; // Subtract title height + padding + + // Calculate optimal dimensions while maintaining aspect ratio + const gameRatio = ROWS / COLS; + + // Calculate scale based on available space + let targetWidth, targetHeight; + + if (isPortrait) { + // For portrait, prioritize fitting the width + targetWidth = availableWidth * 0.95; + targetHeight = targetWidth * gameRatio; + + // If too tall, scale down based on height + if (targetHeight > availableHeight * 0.95) { + targetHeight = availableHeight * 0.95; + targetWidth = targetHeight / gameRatio; + } + } else { + // For landscape, prioritize fitting the height + targetHeight = availableHeight * 0.95; + targetWidth = targetHeight / gameRatio; + + // If too wide, scale down based on width + if (targetWidth > availableWidth * 0.95) { + targetWidth = availableWidth * 0.95; + targetHeight = targetWidth * gameRatio; + } } + + // Apply dimensions + canvas.style.width = `${targetWidth}px`; + canvas.style.height = `${targetHeight}px`; + + // Force a redraw of the nextPiece preview to fix rendering issues + if (nextPiece) { + setTimeout(() => { + nextPieceCtx.clearRect(0, 0, nextPieceCanvas.width, nextPieceCanvas.height); + nextPiece.drawNextPiece(); + }, 100); + } + + // Show touch controls mode + document.body.classList.add('mobile-mode'); + } else { + // Reset to desktop layout + canvas.style.width = ''; + canvas.style.height = ''; + + document.body.classList.remove('mobile-mode'); } } @@ -2107,7 +2074,7 @@ function initControllerSupport() { gamepadConnected = true; controllers[e.gamepad.index] = e.gamepad; - // Start polling for controller input at faster rate + // Start polling for controller input if not already if (!controllerInterval) { controllerInterval = setInterval(pollControllers, controllerPollingRate); } @@ -2125,18 +2092,14 @@ function initControllerSupport() { gamepadConnected = false; clearInterval(controllerInterval); controllerInterval = null; - - // Clear any ongoing button holds - clearAllButtonHoldTimers(); } // Show controller disconnected message showControllerMessage('Controller disconnected'); }); - // Initial scan and setup + // Initial scan if (Object.keys(controllers).length > 0) { - gamepadConnected = true; controllerInterval = setInterval(pollControllers, controllerPollingRate); } } @@ -2169,13 +2132,10 @@ function pollControllers() { if (!lastControllerState[controller.index]) { lastControllerState[controller.index] = { buttons: Array(controller.buttons.length).fill(false), - axes: Array(controller.axes.length).fill(0), - holdStartTimes: {} + axes: Array(controller.axes.length).fill(0) }; } - const now = Date.now(); - // Check buttons for (let action in controllerMapping) { const buttonIndex = controllerMapping[action][0]; @@ -2184,42 +2144,12 @@ function pollControllers() { if (buttonIndex >= 0 && buttonIndex < controller.buttons.length) { const button = controller.buttons[buttonIndex]; const pressed = button.pressed || button.value > 0.5; - const wasPressed = lastControllerState[controller.index].buttons[buttonIndex]; - // New button press (wasn't pressed last time) - if (pressed && !wasPressed) { - // Start tracking hold time for repeatable actions - if (['left', 'right', 'down'].includes(action)) { - lastControllerState[controller.index].holdStartTimes[action] = now; - // Clear any existing timers - if (buttonHoldTimers[action]) { - clearInterval(buttonHoldTimers[action]); - } - - // Schedule repeating actions - buttonHoldTimers[action] = setInterval(() => { - if (gamepadConnected && !gameOver && !paused) { - handleControllerAction(action); - } else { - // Stop repeating if game state changes - clearInterval(buttonHoldTimers[action]); - buttonHoldTimers[action] = null; - } - }, buttonRepeatRates[action]); - } - - // Immediate action on first press + // Check if this is a new press (wasn't pressed last time) + if (pressed && !lastControllerState[controller.index].buttons[buttonIndex]) { + // Handle controller action handleControllerAction(action); } - // Button released - else if (!pressed && wasPressed) { - // Stop repeating on release - if (['left', 'right', 'down'].includes(action) && buttonHoldTimers[action]) { - clearInterval(buttonHoldTimers[action]); - buttonHoldTimers[action] = null; - delete lastControllerState[controller.index].holdStartTimes[action]; - } - } // Update state lastControllerState[controller.index].buttons[buttonIndex] = pressed; @@ -2227,94 +2157,17 @@ function pollControllers() { } // Handle analog stick for movement - const deadzone = 0.5; - // Left stick horizontal - if (controller.axes[0] < -deadzone) { - if (lastControllerState[controller.index].axes[0] >= -deadzone) { - // Initial movement - handleControllerAction('left'); - - // Setup holding behavior - lastControllerState[controller.index].holdStartTimes['left'] = now; - if (buttonHoldTimers['left']) clearInterval(buttonHoldTimers['left']); - - buttonHoldTimers['left'] = setInterval(() => { - if (gamepadConnected && !gameOver && !paused) { - handleControllerAction('left'); - } else { - clearInterval(buttonHoldTimers['left']); - buttonHoldTimers['left'] = null; - } - }, buttonRepeatRates['left']); - } + if (controller.axes[0] < -0.5 && lastControllerState[controller.index].axes[0] >= -0.5) { + handleControllerAction('left'); } - else if (controller.axes[0] > deadzone) { - if (lastControllerState[controller.index].axes[0] <= deadzone) { - // Initial movement - handleControllerAction('right'); - - // Setup holding behavior - lastControllerState[controller.index].holdStartTimes['right'] = now; - if (buttonHoldTimers['right']) clearInterval(buttonHoldTimers['right']); - - buttonHoldTimers['right'] = setInterval(() => { - if (gamepadConnected && !gameOver && !paused) { - handleControllerAction('right'); - } else { - clearInterval(buttonHoldTimers['right']); - buttonHoldTimers['right'] = null; - } - }, buttonRepeatRates['right']); - } - } - else { - // Stick returned to center, clear horizontal timers - if (Math.abs(lastControllerState[controller.index].axes[0]) > deadzone) { - ['left', 'right'].forEach(action => { - if (buttonHoldTimers[action]) { - clearInterval(buttonHoldTimers[action]); - buttonHoldTimers[action] = null; - } - }); - } + else if (controller.axes[0] > 0.5 && lastControllerState[controller.index].axes[0] <= 0.5) { + handleControllerAction('right'); } - // Left stick vertical - down only - if (controller.axes[1] > deadzone) { - if (lastControllerState[controller.index].axes[1] <= deadzone) { - // Initial movement - handleControllerAction('down'); - - // Setup holding behavior - lastControllerState[controller.index].holdStartTimes['down'] = now; - if (buttonHoldTimers['down']) clearInterval(buttonHoldTimers['down']); - - buttonHoldTimers['down'] = setInterval(() => { - if (gamepadConnected && !gameOver && !paused) { - handleControllerAction('down'); - } else { - clearInterval(buttonHoldTimers['down']); - buttonHoldTimers['down'] = null; - } - }, buttonRepeatRates['down']); // Faster repeat for down - } - } - // Up direction for hard drop - else if (controller.axes[1] < -deadzone) { - if (lastControllerState[controller.index].axes[1] >= -deadzone) { - // One-time action for hard drop (no need for repeat) - handleControllerAction('up'); - } - } - else { - // Stick returned to center, clear down timer - if (Math.abs(lastControllerState[controller.index].axes[1]) > deadzone) { - if (buttonHoldTimers['down']) { - clearInterval(buttonHoldTimers['down']); - buttonHoldTimers['down'] = null; - } - } + // Left stick vertical + if (controller.axes[1] > 0.5 && lastControllerState[controller.index].axes[1] <= 0.5) { + handleControllerAction('down'); } // Update axes state @@ -2325,14 +2178,6 @@ function pollControllers() { function handleControllerAction(action) { if (paused && action !== 'pause') return; - // Skip all actions if currently in an animation - if (p.rotationTransition || p.showCompletionEffect) { - if (action === 'pause') { - togglePause(); - } - return; - } - switch(action) { case 'left': p.moveLeft(); @@ -2343,10 +2188,6 @@ function handleControllerAction(action) { case 'down': p.moveDown(); break; - case 'up': - case 'hardDrop': - p.hardDrop(); - break; case 'rotateLeft': p.rotate('left'); break; @@ -2359,6 +2200,12 @@ function handleControllerAction(action) { case 'mirrorV': p.mirrorVertical(); break; + case 'hardDrop': + // Only perform hard drop if not in the middle of an animation + if (!p.rotationTransition && !p.showCompletionEffect) { + p.hardDrop(); + } + break; case 'pause': togglePause(); break; @@ -2403,21 +2250,12 @@ function control(event) { if (gameOver) return; if (paused && event.keyCode !== 80) return; // Allow only P key if paused - // Skip all actions if currently in an animation - if (p.rotationTransition || p.showCompletionEffect) { - if (event.keyCode === 80) { // P key for pause still works - togglePause(); - } - return; - } - switch(event.keyCode) { case 37: // Left arrow case 65: // A key p.moveLeft(); break; - case 38: // Up arrow - p.hardDrop(); + case 38: // Up arrow - no function break; case 39: // Right arrow case 68: // D key @@ -2876,98 +2714,4 @@ window.onload = function() { // Force resize to ensure proper mobile layout window.dispatchEvent(new Event('resize')); } -}; - -// Check for completed rows and clear them -function checkRows() { - let linesCleared = 0; - let clearedRows = []; - - for (let r = 0; r < ROWS; r++) { - let isRowFull = true; - - // Check if the row is full - for (let c = 0; c < COLS; c++) { - if (board[r][c] === EMPTY) { - isRowFull = false; - break; - } - } - - // If the row is full, clear it - if (isRowFull) { - clearedRows.push(r); - linesCleared++; - - // Shift rows down - for (let y = r; y > 0; y--) { - for (let c = 0; c < COLS; c++) { - board[y][c] = board[y-1][c]; - } - } - - // Clear the top row - for (let c = 0; c < COLS; c++) { - board[0][c] = EMPTY; - } - } - } - - // Create fireworks for each cleared row - if (clearedRows.length > 0) { - // Reduce number of fireworks on mobile for better performance - const fireworksPerRow = mobilePerformanceMode ? 1 : 3; - - for (let i = 0; i < clearedRows.length; i++) { - // Create multiple fireworks along the row - for (let j = 0; j < fireworksPerRow; j++) { - const x = (Math.random() * COLS * BLOCK_SIZE) + BLOCK_SIZE/2; - const y = (clearedRows[i] * BLOCK_SIZE) + BLOCK_SIZE/2; - fireworks.push(new Firework(x, y)); - } - } - - // Play sound effect for line clear - playLineClearSound(clearedRows.length); - } - - return linesCleared; -} - -// Update score based on lines cleared -function updateScore() { - // Get number of lines cleared - let linesCleared = checkRows(); - - // Update score - if (linesCleared > 0) { - // Points increase for multiple lines cleared at once - const linePoints = [0, 100, 300, 500, 800]; // 0, 1, 2, 3, 4 lines - score += linePoints[linesCleared] * level; - lines += linesCleared; - - // Level up every 10 lines - if (Math.floor(lines / 10) > level - 1) { - level = Math.floor(lines / 10) + 1; - // Speed up the game as level increases - clearInterval(gameInterval); - gameInterval = setInterval(dropPiece, Math.max(100, 1000 - (level * 100))); - } - - // Update UI - scoreElement.textContent = score; - levelElement.textContent = level; - linesElement.textContent = lines; - } -} - -// Simple function to play audio -function playSound(audioElement) { - // Reset the audio to the beginning - audioElement.currentTime = 0; - // Play the sound - audioElement.play().catch(e => { - // Suppress errors from autoplay restrictions - console.log("Sound play failed:", e); - }); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/style.css b/style.css index bb84ba2..e52a7de 100644 --- a/style.css +++ b/style.css @@ -385,9 +385,8 @@ canvas#tetris { /* Options menu styles */ .option-row { display: flex; - justify-content: space-between; align-items: center; - margin-bottom: 15px; + margin: 15px 0; position: relative; } @@ -404,11 +403,16 @@ canvas#tetris { max-width: 200px; } -/* Mobile-only class should be hidden always since we're removing mobile support */ .mobile-only { display: none; } +@media (max-width: 768px) { + .mobile-only { + display: flex; + } +} + /* Toggle switch styles */ .switch { position: relative; @@ -499,7 +503,14 @@ input[type=range]::-moz-range-thumb { box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); } -/* 3D Rotation buttons - keep these for desktop */ +/* Mobile-specific styles */ +.mobile-mode { + overflow: hidden; + overscroll-behavior: none; + touch-action: none; +} + +/* 3D Rotation buttons */ .rotate-buttons { position: fixed; bottom: 20px; @@ -529,21 +540,258 @@ input[type=range]::-moz-range-thumb { background: linear-gradient(45deg, #0062cc, #00b3ff); } -/* Performance toggle - Since we're removing mobile support */ -/* Removing mobile-specific media queries and styles */ +/* Performance toggle */ +.perf-toggle { + display: flex; + align-items: center; + margin-top: 5px; + background: rgba(0, 0, 0, 0.3); + padding: 5px; + border-radius: 4px; + font-size: 11px; +} + +.perf-label { + margin-left: 5px; + color: #00ffff; + cursor: pointer; +} + +#toggle-mobile-performance { + cursor: pointer; +} + +/* Mobile mode adjustments for rotation buttons */ +.mobile-mode .rotate-buttons { + bottom: 15px; + right: 15px; + gap: 8px; +} + +.mobile-mode .rotate-btn { + padding: 10px 12px; + font-size: 12px; +} + +/* Mobile landscape orientation */ +@media (orientation: landscape) { + .mobile-mode .rotate-buttons { + flex-direction: row; + bottom: 10px; + right: 10px; + } +} + +/* Smaller devices */ +@media (max-width: 400px) { + .mobile-mode .rotate-btn { + padding: 8px 10px; + font-size: 11px; + } +} + +.mobile-mode .game-container { + flex-direction: row; + align-items: flex-start; + gap: 5px; + padding: 10px; + max-width: 100vw; + box-sizing: border-box; + margin-top: 40px; /* Reduced top margin */ +} + +.mobile-mode .game-title { + font-size: 22px; + top: 5px; + text-shadow: 0 0 8px rgba(255, 0, 255, 0.7); +} + +.mobile-mode .game-wrapper { + display: flex; + flex-direction: column; + align-items: center; + margin: 0; + order: 2; /* Move game board to the right */ + flex-grow: 1; +} + +.mobile-mode .score-container { + width: auto; + min-width: 100px; + max-width: 120px; + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + padding: 8px; + background: rgba(0, 0, 0, 0.7); + order: 1; /* Move score container to the left */ + margin-right: 5px; + border-radius: 8px; + height: auto; + align-self: stretch; +} + +.mobile-mode .score-container p { + margin: 3px 0; + font-size: 10px; +} + +.mobile-mode .game-btn { + margin: 3px; + padding: 6px 8px; + font-size: 9px; + width: 95%; +} + +.mobile-mode .controls-info { + display: none; +} + +.mobile-mode #next-piece-preview { + position: relative; + top: auto; + bottom: auto; + left: auto; + right: auto; + transform: none; + margin: 0 0 10px 0; + background: rgba(0, 0, 0, 0.7); + z-index: 10; + border-color: rgba(0, 255, 255, 0.5); + width: 90%; /* Make it fit inside the score container */ + box-sizing: border-box; +} + +.mobile-mode #next-piece-preview h3 { + font-size: 12px; + margin-bottom: 5px; +} + +.mobile-mode canvas#tetris { + max-width: 100%; + height: auto; + border-radius: 8px; +} + +/* Portrait vs landscape adjustments */ +@media (orientation: portrait) { + .mobile-mode .game-container { + padding-top: 35px; + height: calc(100vh - 40px); + } + + .mobile-mode .score-container { + height: calc(100vh - 80px); + justify-content: flex-start; + } + + .mobile-mode canvas#tetris { + height: calc(100vh - 80px); + width: auto; + object-fit: contain; + } +} + +@media (orientation: landscape) { + .mobile-mode .game-container { + margin-top: 35px; + padding: 5px; + height: calc(100vh - 40px); + } + + .mobile-mode .score-container { + height: calc(100vh - 50px); + max-width: 100px; + } + + .mobile-mode canvas#tetris { + height: calc(100vh - 50px); + width: auto; + object-fit: contain; + } +} + +/* Smaller devices */ +@media (max-width: 400px) { + .mobile-mode .game-title { + font-size: 18px; + } + + .mobile-mode .score-container p { + font-size: 9px; + } + + .mobile-mode .score-container { + min-width: 85px; + max-width: 90px; + } + + .mobile-mode .game-btn { + font-size: 8px; + padding: 5px 6px; + } +} + +/* Tablet optimization */ +@media (min-width: 768px) and (max-width: 1024px) { + .mobile-mode .game-container { + flex-direction: row; + flex-wrap: wrap; + gap: 20px; + } + + .mobile-mode .game-title { + font-size: 28px; + } +} + +/* Touch instructions */ +.touch-instructions { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.9); + border: 2px solid rgba(0, 255, 255, 0.7); + border-radius: 10px; + padding: 15px; + z-index: 1000; + box-shadow: 0 0 20px rgba(0, 255, 255, 0.5); + transition: opacity 1s ease; + text-align: center; + max-width: 90vw; +} + +.touch-instructions h3 { + color: #00ffff; + text-shadow: 0 0 5px #00ffff, 0 0 10px #00ffff; + margin-bottom: 15px; + font-size: 16px; +} + +.mobile-mode .touch-instructions { + display: block; +} + +.touch-instructions p { + margin: 10px 0; + font-size: 13px; + color: #fff; + text-align: left; +} + +.touch-instructions p b { + color: #ff9900; + text-shadow: 0 0 5px rgba(255, 153, 0, 0.5); +} + +.touch-instructions.fade-out { + opacity: 0; +} -/* Removing touch-instructions class and related styles */ .instructions-btn { margin-top: 15px; background: linear-gradient(45deg, #ff9900, #ff5500); -} - -.button-row { - display: flex; - justify-content: space-between; - margin-top: 20px; -} - -.button-row .game-btn { - margin: 0 5px; } \ No newline at end of file