├── settings.gradle ├── app ├── src │ └── main │ │ ├── assets │ │ └── Symbola.ttf │ │ ├── res │ │ ├── drawable-hdpi │ │ │ └── logo_2048.png │ │ ├── drawable-mdpi │ │ │ └── logo_2048.png │ │ ├── drawable-xhdpi │ │ │ └── logo_2048.png │ │ ├── drawable-xxhdpi │ │ │ └── logo_2048.png │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── arrays.xml │ │ ├── xml │ │ │ ├── backup_descriptor.xml │ │ │ └── settings.xml │ │ ├── drawable │ │ │ ├── cell_rectangle_128.xml │ │ │ ├── cell_rectangle_16.xml │ │ │ ├── cell_rectangle_2.xml │ │ │ ├── cell_rectangle_32.xml │ │ │ ├── cell_rectangle_4.xml │ │ │ ├── cell_rectangle_64.xml │ │ │ ├── cell_rectangle_8.xml │ │ │ ├── background_rectangle.xml │ │ │ ├── cell_rectangle.xml │ │ │ ├── cell_rectangle_256.xml │ │ │ ├── cell_rectangle_512.xml │ │ │ ├── fade_rectangle.xml │ │ │ ├── light_up_rectangle.xml │ │ │ ├── cell_rectangle_1024.xml │ │ │ └── cell_rectangle_2048.xml │ │ ├── layout │ │ │ └── activity_my_settings.xml │ │ ├── menu │ │ │ └── main.xml │ │ └── values-zh │ │ │ └── strings.xml │ │ ├── java │ │ └── com │ │ │ └── osfans │ │ │ └── android2048 │ │ │ ├── SettingsActivity.java │ │ │ ├── Cell.java │ │ │ ├── AnimationCell.java │ │ │ ├── Tile.java │ │ │ ├── SettingsProvider.java │ │ │ ├── AnimationGrid.java │ │ │ ├── Grid.java │ │ │ ├── InputListener.java │ │ │ ├── SettingsFragment.java │ │ │ ├── MainActivity.java │ │ │ ├── MainGame.java │ │ │ ├── AI.java │ │ │ └── MainView.java │ │ └── AndroidManifest.xml └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /app/src/main/assets/Symbola.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osfans/android2048/HEAD/app/src/main/assets/Symbola.ttf -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osfans/android2048/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/logo_2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osfans/android2048/HEAD/app/src/main/res/drawable-hdpi/logo_2048.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/logo_2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osfans/android2048/HEAD/app/src/main/res/drawable-mdpi/logo_2048.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo_2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osfans/android2048/HEAD/app/src/main/res/drawable-xhdpi/logo_2048.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/logo_2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osfans/android2048/HEAD/app/src/main/res/drawable-xxhdpi/logo_2048.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #f9f6f2 4 | #776e65 5 | #d6cdc4 6 | #faf8ef 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_descriptor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | *.iml 17 | .gradle/ 18 | .idea/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | gradle.properties 24 | *.keystore 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | import android.os.Bundle; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | 7 | public class SettingsActivity extends AppCompatActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_my_settings); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_128.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_16.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_32.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_64.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_rectangle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_256.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_512.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fade_rectangle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/light_up_rectangle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_1024.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_rectangle_2048.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2048方言版 2 | ==== 3 | 4 | ## 簡介 5 | - 源於[fengmoxi的2048](https://github.com/fengmoxi/2048_android) 6 | - 系統要求:>=Android4.0 7 | - 階數:3~7 8 | - 任務:128~2147483648 9 | 10 | ## 多種自定義版 11 | - 原版(2048) 12 | - 揚州話(2048) 13 | - 蘇州閒話(2048) 14 | - 麻將字牌(128) 15 | - 八卦版(256) 16 | - 麻將萬字(512) 17 | - 天干版(1024) 18 | - 地支版(4096) 19 | - 12生肖(4096) 20 | - 12星座(4096) 21 | - 中國朝代(8192) 22 | - 江蘇城市(8192) 23 | - 撲克版(16384) 24 | - 五行版(32768) 25 | - 16韻攝(65536) 26 | - 24節氣(16777216) 27 | - 終極版(2147483648) 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/Cell.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | class Cell { 4 | boolean marked = false; 5 | private int x; 6 | private int y; 7 | 8 | Cell(int x, int y) { 9 | this.x = x; 10 | this.y = y; 11 | } 12 | 13 | int getX() { 14 | return this.x; 15 | } 16 | 17 | void setX(int x) { 18 | this.x = x; 19 | } 20 | 21 | int getY() { 22 | return this.y; 23 | } 24 | 25 | void setY(int y) { 26 | this.y = y; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_my_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 14 | 15 | 20 | 21 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/AnimationCell.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | class AnimationCell extends Cell { 4 | final int[] extras; 5 | private final int animationType; 6 | private long timeElapsed; 7 | private final long animationTime; 8 | private final long delayTime; 9 | 10 | AnimationCell(int x, int y, int animationType, long length, long delay, int[] extras) { 11 | super(x, y); 12 | this.animationType = animationType; 13 | animationTime = length; 14 | delayTime = delay; 15 | this.extras = extras; 16 | } 17 | 18 | int getAnimationType() { 19 | return animationType; 20 | } 21 | 22 | void tick(long timeElapsed) { 23 | this.timeElapsed = this.timeElapsed + timeElapsed; 24 | } 25 | 26 | boolean animationDone() { 27 | return animationTime + delayTime < timeElapsed; 28 | } 29 | 30 | double getPercentageDone() { 31 | return Math.max(0, 1.0 * (timeElapsed - delayTime) / animationTime); 32 | } 33 | 34 | boolean isActive() { 35 | return (timeElapsed >= delayTime); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/Tile.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | class Tile extends Cell { 4 | private final int value; 5 | //private Cell previousPosition = null; 6 | private Tile[] mergedFrom = null; 7 | 8 | Tile(int x, int y, int value) { 9 | super(x, y); 10 | this.value = value; 11 | } 12 | 13 | Tile(Cell cell, int value) { 14 | super(cell.getX(), cell.getY()); 15 | this.value = value; 16 | } 17 | 18 | /* 19 | void savePosition() { 20 | previousPosition = new Cell(this.getX(), this.getY()); 21 | } 22 | */ 23 | 24 | void updatePosition(Cell cell) { 25 | this.setX(cell.getX()); 26 | this.setY(cell.getY()); 27 | } 28 | 29 | int getValue() { 30 | return this.value; 31 | } 32 | 33 | /* 34 | public void setValue(int value) { 35 | this.value = value; 36 | } 37 | */ 38 | 39 | Tile[] getMergedFrom() { 40 | return mergedFrom; 41 | } 42 | 43 | void setMergedFrom(Tile[] tile) { 44 | mergedFrom = tile; 45 | } 46 | 47 | /* 48 | public Cell getPreviousPosition() { 49 | return previousPosition; 50 | } 51 | */ 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2048方言版 4 | 紀錄 5 | 得分 6 | 滑動合併相同的滑塊 7 | 触摸空白方块可添加滑块 8 | 勝利 9 | 結束 10 | 反悔 11 | 設置 12 | 託管 13 | 新建 14 | 靈敏度 15 | 16 | 17 | 18 | 版本 19 | 定製 20 | 反轉 21 | 序號 22 | 階數 23 | 不顯示 24 | 系統字體 25 | 開源 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2048 Variants 4 | HIGH SCORE 5 | SCORE 6 | Swipe to move. 7 | Tap on empty cells to add one. 8 | You Win 9 | Game Over 10 | Undo 11 | Settings 12 | Auto Run 13 | New Game 14 | Sensitivity 15 | High 16 | Medium 17 | Low 18 | Variety 19 | Custom 20 | Inverse Mode 21 | Show Order 22 | None 23 | Row Number 24 | System Font 25 | Project Homepage 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/SettingsProvider.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | class SettingsProvider { 7 | private static final String KEY_PREFERENCES = "com.osfans.android2048_preferences"; 8 | 9 | static final String KEY_SENSITIVITY = "settings_sensitivity"; 10 | static final String KEY_ORDER = "settings_order"; 11 | static final String KEY_ROWS = "settings_rows"; 12 | static final String KEY_VARIETY = "settings_variety"; 13 | static final String KEY_INVERSE_MODE = "settings_inverse_mode"; 14 | static final String KEY_SYSTEM_FONT = "settings_system_font"; 15 | static final String KEY_CUSTOM_VARIETY = "settings_custom_variety"; 16 | 17 | private static SharedPreferences prefs; 18 | 19 | static void initPreferences(Context context) { 20 | prefs = context.getSharedPreferences(KEY_PREFERENCES, Context.MODE_MULTI_PROCESS); 21 | } 22 | 23 | static int getInt(String key, String defaultValue) { 24 | return Integer.parseInt(prefs.getString(key, defaultValue)); 25 | } 26 | 27 | static boolean getBoolean(String key) { 28 | return prefs.getBoolean(key, false); 29 | } 30 | 31 | static String getString(String key, String defaultValue) { 32 | return prefs.getString(key, defaultValue); 33 | } 34 | 35 | static void putBoolean(String key, boolean value) { 36 | prefs.edit().putBoolean(key, value).apply(); 37 | } 38 | 39 | static void putString(String key, String value) { 40 | prefs.edit().putString(key, value).apply(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | def tagCmd = 'git describe --tags' 4 | def tag = tagCmd.execute().text.trim() 5 | def date = new Date().format('yyyyMMdd').toInteger() 6 | 7 | android { 8 | compileSdkVersion 30 9 | 10 | defaultConfig { 11 | applicationId "com.osfans.android2048" 12 | minSdkVersion 14 13 | targetSdkVersion 30 14 | versionCode date 15 | versionName "$tag-$date" 16 | } 17 | 18 | signingConfigs { 19 | release { 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | //proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-android.txt' 27 | signingConfig signingConfigs.release 28 | } 29 | } 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | def propsFile = rootProject.file('gradle.properties') 37 | //store release config in gradle.properties 38 | def configName = 'release' 39 | 40 | if (propsFile.exists() && android.signingConfigs.hasProperty(configName)) { 41 | def props = new Properties() 42 | props.load(new FileInputStream(propsFile)) 43 | if (props != null && props.containsKey('storeFile')) { 44 | android.signingConfigs[configName].storeFile = rootProject.file(props['storeFile']) 45 | android.signingConfigs[configName].storePassword = props['storePassword'] 46 | android.signingConfigs[configName].keyAlias = props['keyAlias'] 47 | android.signingConfigs[configName].keyPassword = props['keyPassword'] 48 | } 49 | } 50 | } 51 | 52 | dependencies { 53 | implementation 'androidx.preference:preference:1.1.0' 54 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 55 | implementation 'androidx.appcompat:appcompat:1.1.0' 56 | implementation 'com.google.android.material:material:1.0.0' 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/res/xml/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 22 | 23 | 29 | 30 | 34 | 35 | 41 | 42 | 46 | 47 | 48 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/AnimationGrid.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | import java.util.ArrayList; 4 | 5 | 6 | class AnimationGrid { 7 | private final ArrayList[][] field; 8 | final ArrayList globalAnimation = new ArrayList<>(); 9 | private int activeAnimations = 0; 10 | private boolean oneMoreFrame = false; 11 | 12 | AnimationGrid(int x, int y) { 13 | field = new ArrayList[x][y]; 14 | 15 | for (int xx = 0; xx < x; xx++) { 16 | for (int yy = 0; yy < y; yy++) { 17 | field[xx][yy] = new ArrayList<>(); 18 | } 19 | } 20 | } 21 | 22 | void startAnimation(int x, int y, int animationType, long length, long delay, int[] extras) { 23 | AnimationCell animationToAdd = new AnimationCell(x, y, animationType, length, delay, extras); 24 | if (x == -1 && y == -1) { 25 | globalAnimation.add(animationToAdd); 26 | } else { 27 | field[x][y].add(animationToAdd); 28 | } 29 | activeAnimations = activeAnimations + 1; 30 | } 31 | 32 | void tickAll(long timeElapsed) { 33 | ArrayList cancelledAnimations = new ArrayList<>(); 34 | for (AnimationCell animation : globalAnimation) { 35 | animation.tick(timeElapsed); 36 | if (animation.animationDone()) { 37 | cancelledAnimations.add(animation); 38 | activeAnimations = activeAnimations - 1; 39 | } 40 | } 41 | 42 | for (ArrayList[] array : field) { 43 | for (ArrayList list : array) { 44 | for (AnimationCell animation : list) { 45 | animation.tick(timeElapsed); 46 | if (animation.animationDone()) { 47 | cancelledAnimations.add(animation); 48 | activeAnimations = activeAnimations - 1; 49 | } 50 | } 51 | } 52 | } 53 | 54 | for (AnimationCell animation : cancelledAnimations) { 55 | cancelAnimation(animation); 56 | } 57 | } 58 | 59 | boolean isAnimationActive() { 60 | if (activeAnimations != 0) { 61 | oneMoreFrame = true; 62 | return true; 63 | } else if (oneMoreFrame) { 64 | oneMoreFrame = false; 65 | return true; 66 | } else { 67 | return false; 68 | } 69 | } 70 | 71 | ArrayList getAnimationCell(int x, int y) { 72 | return field[x][y]; 73 | } 74 | 75 | private void cancelAnimation(AnimationCell animation) { 76 | if (animation.getX() == -1 && animation.getY() == -1) { 77 | globalAnimation.remove(animation); 78 | } else { 79 | field[animation.getX()][animation.getY()].remove(animation); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @string/settings_sensitivity_high 5 | @string/settings_sensitivity_medium 6 | @string/settings_sensitivity_low 7 | 8 | 9 | 0 10 | 1 11 | 2 12 | 13 | 14 | 123 15 | ABC 16 | @string/settings_order_none 17 | 18 | 19 | 0 20 | 1 21 | 2 22 | 23 | 24 | 3 25 | 4 26 | 5 27 | 6 28 | 7 29 | 30 | 2 4 8 16 32 64 128 256 512 1024 2048 31 | 32 | @string/original_2048 33 | 大胖子怕熱 雞蛋怕砑 小車子怕山 破扁擔怕試 文人怕武 回子怕肉 嗝食病怕喫 壞鞋子怕拔 螃蟹怕酒 陰溝洞怕塞 乖乖隆地咚 34 | 歪戴帽子大阿哥 纏夾二先生 橋頭三阿爹 吐血四老倌 五叔叔 六嬸嬸 七巧妹子 八阿姨 九斤老太 十伯伯 蘇州閒話 35 | 🀀 🀁 🀂 🀃 🀄 🀅 🀆 36 | 🀢 🀣 🀤 🀥 🀦 🀧 🀨 🀩 37 | ☰ ☱ ☲ ☳ ☴ ☵ ☶ ☷ 38 | 🀇 🀈 🀉 🀊 🀋 🀌 🀍 🀎 🀏 39 | 甲乙丙丁戊己庚辛壬癸 40 | 子丑寅卯辰巳午未申酉戌亥 41 | 🐀 🐂 🐅 🐇 🐉 🐍 🐎 🐏 🐒 🐓 🐕 🐖 42 | ♈ ♉ ♊ ♋ ♌ ♍ ♎ ♏ ♐ ♑ ♒ ♓ 43 | 〡〢〣〤〥〦〧〨〩〸〹〺 44 | 夏商周秦漢晉隋唐宋元明清天 45 | 宁錫徐常蘇通連淮鹽揚鎮泰宿 46 | 🂡 🂢 🂣 🂤 🂥 🂦 🂧 🂨 🂩 🂪 🂫 🂭 🂮 🃟 47 | 木火土金水林炎圭鍂沝森焱垚鑫淼 48 | 通江止遇蟹臻山效果假宕梗曾流深咸 49 | 立春 雨水 驚蟄 春分 清明 穀雨 立夏 小滿 芒種 夏至 小暑 大暑 立秋 處暑 白露 秋分 寒露 霜降 立冬 小雪 大雪 冬至 小寒 大寒 50 | 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 1073741824 2147483648 51 | @string/original_2048 52 | 53 | 54 | 0 55 | 1 56 | 2 57 | 3 58 | 4 59 | 5 60 | 6 61 | 7 62 | 8 63 | 9 64 | 10 65 | 11 66 | 12 67 | 13 68 | 14 69 | 15 70 | 16 71 | 17 72 | 18 73 | 19 74 | 75 | 76 | 原版 77 | 揚州話 78 | 蘇州閒話 79 | 麻將字牌 80 | 麻將花牌 81 | 八卦 82 | 麻將萬字 83 | 天干 84 | 地支 85 | 生肖 86 | 星座 87 | 蘇州碼子 88 | 中國朝代 89 | 江蘇城市 90 | 撲克 91 | 五行 92 | 韻攝 93 | 節氣 94 | 終極版 95 | @string/settings_custom_variety 96 | 97 | 1 98 | 1 99 | 0 100 | 4 101 | 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/Grid.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class Grid { 6 | 7 | Tile[][] field; 8 | private Tile[][] lastField; 9 | boolean canRevert = false; 10 | 11 | private final int sizeX; 12 | private final int sizeY; 13 | 14 | Grid(int sizeX, int sizeY) { 15 | this.sizeX = sizeX; 16 | this.sizeY = sizeY; 17 | field = new Tile[sizeX][sizeY]; 18 | lastField = new Tile[sizeX][sizeY]; 19 | for (int xx = 0; xx < field.length; xx++) { 20 | for (int yy = 0; yy < field[0].length; yy++) { 21 | field[xx][yy] = null; 22 | lastField[xx][yy] = null; 23 | } 24 | } 25 | } 26 | 27 | Cell randomAvailableCell() { 28 | ArrayList availableCells = getAvailableCells(); 29 | if (availableCells.size() >= 1) { 30 | return availableCells.get((int) Math.floor(Math.random() * availableCells.size())); 31 | } 32 | return null; 33 | } 34 | 35 | ArrayList getAvailableCells() { 36 | ArrayList availableCells = new ArrayList<>(); 37 | for (int xx = 0; xx < field.length; xx++) { 38 | for (int yy = 0; yy < field[0].length; yy++) { 39 | if (field[xx][yy] == null) { 40 | availableCells.add(new Cell(xx, yy)); 41 | } 42 | } 43 | } 44 | return availableCells; 45 | } 46 | 47 | boolean isCellsAvailable() { 48 | return (getAvailableCells().size() >= 1); 49 | } 50 | 51 | boolean isCellAvailable(Cell cell) { 52 | return !isCellOccupied(cell); 53 | } 54 | 55 | boolean isCellOccupied(Cell cell) { 56 | return (getCellContent(cell) != null); 57 | } 58 | 59 | Tile getCellContent(Cell cell) { 60 | if (cell != null && isCellWithinBounds(cell)) { 61 | return field[cell.getX()][cell.getY()]; 62 | } else { 63 | return null; 64 | } 65 | } 66 | 67 | Tile getCellContent(int x, int y) { 68 | if (isCellWithinBounds(x, y)) { 69 | return field[x][y]; 70 | } else { 71 | return null; 72 | } 73 | } 74 | 75 | boolean isCellWithinBounds(Cell cell) { 76 | return 0 <= cell.getX() && cell.getX() < field.length 77 | && 0 <= cell.getY() && cell.getY() < field[0].length; 78 | } 79 | 80 | boolean isCellWithinBounds(int x, int y) { 81 | return 0 <= x && x < field.length 82 | && 0 <= y && y < field[0].length; 83 | } 84 | 85 | void insertTile(Tile tile) { 86 | field[tile.getX()][tile.getY()] = tile; 87 | } 88 | 89 | void removeTile(Tile tile) { 90 | field[tile.getX()][tile.getY()] = null; 91 | } 92 | 93 | void saveTiles() { 94 | canRevert = true; 95 | 96 | lastField = new Tile[sizeX][sizeY]; 97 | for (int xx = 0; xx < field.length; xx++) { 98 | for (int yy = 0; yy < field.length; yy++) { 99 | if (field[xx][yy] == null) { 100 | lastField[xx][yy] = null; 101 | } else { 102 | lastField[xx][yy] = new Tile(xx, yy, field[xx][yy].getValue()); 103 | } 104 | } 105 | } 106 | } 107 | 108 | void revertTiles() { 109 | canRevert = false; 110 | 111 | for (int xx = 0; xx < lastField.length; xx++) { 112 | for (int yy = 0; yy < lastField.length; yy++) { 113 | if (lastField[xx][yy] == null) { 114 | field[xx][yy] = null; 115 | } else { 116 | field[xx][yy] = new Tile(xx, yy, lastField[xx][yy].getValue()); 117 | } 118 | } 119 | } 120 | } 121 | 122 | @Override 123 | public Grid clone() { 124 | Tile[][] newField = new Tile[sizeX][sizeY]; 125 | for (int xx = 0; xx < field.length; xx++) { 126 | for (int yy = 0; yy < field.length; yy++) { 127 | if (field[xx][yy] == null) { 128 | newField[xx][yy] = null; 129 | } else { 130 | newField[xx][yy] = new Tile(xx, yy, field[xx][yy].getValue()); 131 | } 132 | } 133 | } 134 | 135 | Grid newGrid = new Grid(sizeX, sizeY); 136 | newGrid.field = newField; 137 | return newGrid; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/InputListener.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | import android.view.GestureDetector; 4 | import android.view.KeyEvent; 5 | import android.view.MotionEvent; 6 | import android.view.View; 7 | 8 | class InputListener implements View.OnTouchListener, View.OnKeyListener { 9 | 10 | private static final int SWIPE_MIN_DISTANCE = 100; 11 | private static int SWIPE_THRESHOLD_VELOCITY = 40; 12 | private static int MOVE_THRESHOLD = 250; 13 | private final MainView mView; 14 | private final GestureDetector mGestureDetector; 15 | private float x; 16 | private float y; 17 | /* 18 | private static final int RESET_STARTING = 10; 19 | private float lastDx; 20 | private float lastDy; 21 | private float previousX; 22 | private float previousY; 23 | private float startingX; 24 | private float startingY; 25 | private boolean moved = false; 26 | */ 27 | private int previousDirection = 1; 28 | private int veryLastDirection = 1; 29 | 30 | InputListener(MainView view) { 31 | super(); 32 | this.mView = view; 33 | mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { 34 | @Override 35 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 36 | try { 37 | float fX = e1.getX() - e2.getX(); 38 | float fY = e1.getY() - e2.getY(); 39 | if (Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY && Math.abs(fX) + SWIPE_MIN_DISTANCE / 2 >= Math.abs(fY) && Math.abs(fX) > SWIPE_MIN_DISTANCE && Math.abs(fX) < MOVE_THRESHOLD * 2) { 40 | mView.game.move(fX > 0 ? 3 : 1); 41 | } else if (Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY && Math.abs(fY) > SWIPE_MIN_DISTANCE && Math.abs(fY) < MOVE_THRESHOLD * 2) { 42 | mView.game.move(fY > 0 ? 0 : 2); 43 | } else return false; 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean onSingleTapUp(MotionEvent event) { 52 | 53 | x = event.getX(); 54 | y = event.getY(); 55 | previousDirection = 1; 56 | veryLastDirection = 1; 57 | if (inRange(MainView.sXNewGame, x, MainView.sXNewGame + MainView.iconSize) 58 | && inRange(MainView.sYIcons, y, MainView.sYIcons + MainView.iconSize)) { 59 | mView.game.newGame(); 60 | } 61 | 62 | if (MainView.inverseMode) { 63 | for (Cell cell : mView.game.grid.getAvailableCells()) { 64 | int xx = cell.getX(); 65 | int yy = cell.getY(); 66 | int sX = mView.startingX + mView.gridWidth + (mView.cellSize + mView.gridWidth) * xx; 67 | int eX = sX + mView.cellSize; 68 | int sY = mView.startingY + mView.gridWidth + (mView.cellSize + mView.gridWidth) * yy; 69 | int eY = sY + mView.cellSize; 70 | 71 | if (inRange(sX, x, eX) && inRange(sY, y, eY)) { 72 | mView.game.addRandomTile(cell); 73 | mView.invalidate(); 74 | mView.startAi(); 75 | break; 76 | } 77 | } 78 | } 79 | return true; 80 | } 81 | }); 82 | } 83 | 84 | static void loadSensitivity() { 85 | int sensitivity = SettingsProvider.getInt(SettingsProvider.KEY_SENSITIVITY, "1"); 86 | switch (sensitivity) { 87 | case 0: 88 | SWIPE_THRESHOLD_VELOCITY = 20; 89 | MOVE_THRESHOLD = 200; 90 | break; 91 | case 1: 92 | SWIPE_THRESHOLD_VELOCITY = 60; 93 | MOVE_THRESHOLD = 250; 94 | break; 95 | case 2: 96 | SWIPE_THRESHOLD_VELOCITY = 100; 97 | MOVE_THRESHOLD = 300; 98 | break; 99 | } 100 | } 101 | 102 | public boolean onTouch(View view, MotionEvent event) { 103 | view.performClick(); 104 | mGestureDetector.onTouchEvent(event); 105 | return true; 106 | } 107 | 108 | @Override 109 | public boolean onKey(View view, int keyCode, KeyEvent event) { 110 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 111 | switch (event.getKeyCode()) { 112 | case KeyEvent.KEYCODE_DPAD_DOWN: 113 | mView.game.move(2); 114 | return true; 115 | case KeyEvent.KEYCODE_DPAD_UP: 116 | mView.game.move(0); 117 | return true; 118 | case KeyEvent.KEYCODE_DPAD_LEFT: 119 | mView.game.move(3); 120 | return true; 121 | case KeyEvent.KEYCODE_DPAD_RIGHT: 122 | mView.game.move(1); 123 | return true; 124 | } 125 | } 126 | return false; 127 | } 128 | 129 | private boolean inRange(float left, float check, float right) { 130 | return (left <= check && check <= right); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/SettingsFragment.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | import androidx.preference.PreferenceFragmentCompat; 4 | 5 | import android.os.Bundle; 6 | 7 | import androidx.preference.CheckBoxPreference; 8 | import androidx.preference.ListPreference; 9 | import androidx.preference.Preference; 10 | 11 | 12 | /** 13 | * A placeholder fragment containing a simple view. 14 | */ 15 | public class SettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener { 16 | private Preference mCustomVariety; 17 | 18 | @Override 19 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 20 | setPreferencesFromResource(R.xml.settings, rootKey); 21 | ListPreference sensitivity = findPreference(SettingsProvider.KEY_SENSITIVITY); 22 | ListPreference order = findPreference(SettingsProvider.KEY_ORDER); 23 | ListPreference rows = findPreference(SettingsProvider.KEY_ROWS); 24 | ListPreference variety = findPreference(SettingsProvider.KEY_VARIETY); 25 | CheckBoxPreference inverse = findPreference(SettingsProvider.KEY_INVERSE_MODE); 26 | CheckBoxPreference systemFont = findPreference(SettingsProvider.KEY_SYSTEM_FONT); 27 | mCustomVariety = findPreference(SettingsProvider.KEY_CUSTOM_VARIETY); 28 | 29 | if (sensitivity != null) { 30 | int value = SettingsProvider.getInt(SettingsProvider.KEY_SENSITIVITY, getResources().getString(R.string.default_sensitivity)); 31 | String[] summaries = getResources().getStringArray(R.array.settings_sensitivity_entries); 32 | sensitivity.setSummary(summaries[value]); 33 | sensitivity.setOnPreferenceChangeListener(this); 34 | } 35 | if (order != null) { 36 | int value = SettingsProvider.getInt(SettingsProvider.KEY_ORDER, getResources().getString(R.string.default_order)); 37 | String[] summaries = getResources().getStringArray(R.array.settings_order_entries); 38 | order.setSummary(summaries[value]); 39 | order.setOnPreferenceChangeListener(this); 40 | } 41 | if (rows != null) { 42 | String value = SettingsProvider.getString(SettingsProvider.KEY_ROWS, getResources().getString(R.string.default_row)); 43 | rows.setSummary(value); 44 | rows.setOnPreferenceChangeListener(this); 45 | } 46 | if (variety != null) { 47 | int value = SettingsProvider.getInt(SettingsProvider.KEY_VARIETY, getResources().getString(R.string.default_variety)); 48 | String[] summaries = getResources().getStringArray(R.array.settings_variety_entries); 49 | variety.setSummary(summaries[value]); 50 | variety.setOnPreferenceChangeListener(this); 51 | } 52 | if (inverse != null) inverse.setOnPreferenceChangeListener(this); 53 | if (systemFont != null) systemFont.setOnPreferenceChangeListener(this); 54 | if (mCustomVariety != null) mCustomVariety.setOnPreferenceChangeListener(this); 55 | } 56 | 57 | public boolean onPreferenceChange(Preference preference, Object newValue) { 58 | String key = preference.getKey(); 59 | switch (key) { 60 | case SettingsProvider.KEY_SENSITIVITY: 61 | int sensitivity = Integer.parseInt((String) newValue); 62 | String[] sensitivitySummaries = getResources().getStringArray(R.array.settings_sensitivity_entries); 63 | preference.setSummary(sensitivitySummaries[sensitivity]); 64 | SettingsProvider.putString(SettingsProvider.KEY_SENSITIVITY, (String) newValue); 65 | InputListener.loadSensitivity(); 66 | break; 67 | case SettingsProvider.KEY_VARIETY: 68 | int variety = ((ListPreference)preference).findIndexOfValue((String) newValue); 69 | String[] varietySummaries = getResources().getStringArray(R.array.settings_variety_entries); 70 | mCustomVariety.setEnabled(variety == varietySummaries.length - 1); 71 | preference.setSummary(varietySummaries[variety]); 72 | SettingsProvider.putString(SettingsProvider.KEY_VARIETY, (String) newValue); 73 | MainActivity.getInstance().newGame(); 74 | break; 75 | case SettingsProvider.KEY_CUSTOM_VARIETY: 76 | SettingsProvider.putString(SettingsProvider.KEY_CUSTOM_VARIETY, (String) newValue); 77 | MainActivity.getInstance().newGame(); 78 | break; 79 | case SettingsProvider.KEY_INVERSE_MODE: 80 | boolean inverse = (Boolean) newValue; 81 | SettingsProvider.putBoolean(SettingsProvider.KEY_INVERSE_MODE, inverse); 82 | MainView.inverseMode = inverse; 83 | break; 84 | case SettingsProvider.KEY_SYSTEM_FONT: 85 | boolean value = (Boolean) newValue; 86 | SettingsProvider.putBoolean(SettingsProvider.KEY_SYSTEM_FONT, value); 87 | MainActivity.getInstance().newGame(); 88 | break; 89 | case SettingsProvider.KEY_ORDER: 90 | int order = Integer.parseInt((String) newValue); 91 | String[] orderSummaries = getResources().getStringArray(R.array.settings_order_entries); 92 | preference.setSummary(orderSummaries[order]); 93 | SettingsProvider.putString(SettingsProvider.KEY_ORDER, (String) newValue); 94 | MainActivity.getInstance().newCell(); 95 | break; 96 | case SettingsProvider.KEY_ROWS: 97 | SettingsProvider.putString(SettingsProvider.KEY_ROWS, (String) newValue); 98 | preference.setSummary((String) newValue); 99 | clearState(); 100 | MainActivity.getInstance().newGame(); 101 | break; 102 | default: 103 | return false; 104 | } 105 | return true; 106 | } 107 | 108 | private void clearState() { 109 | MainActivity.getInstance().getSharedPreferences("state", 0) 110 | .edit() 111 | .remove("size") 112 | .apply(); 113 | MainActivity.save = false; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | import android.content.Intent; 4 | import android.content.SharedPreferences; 5 | import android.content.res.Resources; 6 | import android.os.Bundle; 7 | import android.view.Menu; 8 | import android.view.MenuInflater; 9 | import android.view.MenuItem; 10 | import android.view.WindowManager; 11 | 12 | import androidx.appcompat.app.AppCompatActivity; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | public static boolean save = true; 17 | private static MainActivity mSelf; 18 | private MainView view; 19 | 20 | public static MainActivity getInstance() { 21 | return mSelf; 22 | } 23 | 24 | public void newGame() { 25 | view = new MainView(getBaseContext()); 26 | 27 | // Restore state 28 | SharedPreferences prefs = getSharedPreferences("state", 0); 29 | int size = prefs.getInt("size", 0); 30 | if (size == MainGame.numSquaresX) { 31 | Tile[][] field = view.game.grid.field; 32 | String[] saveState = new String[field[0].length]; 33 | for (int xx = 0; xx < saveState.length; xx++) { 34 | saveState[xx] = prefs.getString("" + xx, ""); 35 | } 36 | for (int xx = 0; xx < saveState.length; xx++) { 37 | String[] array = saveState[xx].split("\\|"); 38 | for (int yy = 0; yy < array.length; yy++) { 39 | if (!array[yy].startsWith("0")) { 40 | view.game.grid.field[xx][yy] = new Tile(xx, yy, Integer.valueOf(array[yy])); 41 | } else { 42 | view.game.grid.field[xx][yy] = null; 43 | } 44 | } 45 | } 46 | view.game.score = prefs.getLong("score", 0); 47 | view.game.highScore = prefs.getLong("high score", 0); 48 | view.game.won = prefs.getBoolean("won", false); 49 | view.game.lose = prefs.getBoolean("lose", false); 50 | } 51 | setContentView(view); 52 | initTitle(); 53 | } 54 | 55 | public void newCell() { 56 | view.initRectangleDrawables(); 57 | } 58 | 59 | private void initTitle() { 60 | Resources resources = getResources(); 61 | int i = SettingsProvider.getInt(SettingsProvider.KEY_VARIETY, resources.getString(R.string.default_variety)); 62 | String[] varietySummaries = resources.getStringArray(R.array.settings_variety_entries); 63 | setTitle(varietySummaries[i]); 64 | } 65 | 66 | @Override 67 | protected void onCreate(Bundle savedInstanceState) { 68 | super.onCreate(savedInstanceState); 69 | mSelf = this; 70 | SettingsProvider.initPreferences(this); 71 | InputListener.loadSensitivity(); 72 | 73 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 74 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 75 | newGame(); 76 | } 77 | 78 | @Override 79 | public boolean onCreateOptionsMenu(Menu menu) { 80 | MenuInflater inflater = getMenuInflater(); 81 | inflater.inflate(R.menu.main, menu); 82 | return true; 83 | } 84 | 85 | @Override 86 | public boolean onPrepareOptionsMenu(Menu menu) { 87 | menu.findItem(R.id.menu_auto_run).setCheckable(true); 88 | if (MainView.inverseMode) { 89 | menu.findItem(R.id.menu_undo).setEnabled(false); 90 | menu.findItem(R.id.menu_auto_run).setEnabled(false); 91 | } else if (view.aiRunning) { 92 | menu.findItem(R.id.menu_undo).setEnabled(false); 93 | menu.findItem(R.id.menu_auto_run).setEnabled(true); 94 | menu.findItem(R.id.menu_auto_run).setChecked(true); 95 | } else { 96 | menu.findItem(R.id.menu_undo).setEnabled(view.game.grid.canRevert); 97 | menu.findItem(R.id.menu_auto_run).setEnabled(true); 98 | menu.findItem(R.id.menu_auto_run).setChecked(false); 99 | } 100 | 101 | return true; 102 | } 103 | 104 | @Override 105 | public boolean onOptionsItemSelected(MenuItem item) { 106 | switch (item.getItemId()) { 107 | case R.id.menu_undo: 108 | view.game.revertState(); 109 | return true; 110 | case R.id.menu_settings: 111 | Intent i = new Intent(); 112 | i.setAction(Intent.ACTION_MAIN); 113 | i.setClass(this, SettingsActivity.class); 114 | startActivity(i); 115 | return true; 116 | case R.id.menu_auto_run: 117 | view.toggleAi(); 118 | return true; 119 | case R.id.menu_new_game: 120 | view.stopAi(); 121 | view.game.newGame(); 122 | return true; 123 | } 124 | return true; 125 | } 126 | 127 | @Override 128 | public void onPause() { 129 | super.onPause(); 130 | 131 | // If variety switched, do not save 132 | if (!save) return; 133 | 134 | SharedPreferences prefs = getSharedPreferences("state", 0); 135 | SharedPreferences.Editor edit = prefs.edit(); 136 | Tile[][] field = view.game.grid.field; 137 | String[] saveState = new String[field[0].length]; 138 | for (int xx = 0; xx < field.length; xx++) { 139 | saveState[xx] = ""; 140 | for (int yy = 0; yy < field[0].length; yy++) { 141 | if (field[xx][yy] != null) { 142 | saveState[xx] += String.valueOf(field[xx][yy].getValue()); 143 | } else { 144 | saveState[xx] += "0"; 145 | } 146 | if (yy < field[0].length - 1) { 147 | saveState[xx] += "|"; 148 | } 149 | } 150 | } 151 | for (int xx = 0; xx < saveState.length; xx++) { 152 | edit.putString("" + xx, saveState[xx]); 153 | } 154 | edit.putLong("score", view.game.score); 155 | edit.putLong("high score", view.game.highScore); 156 | edit.putBoolean("won", view.game.won); 157 | edit.putBoolean("lose", view.game.lose); 158 | edit.putInt("size", MainGame.numSquaresX); 159 | edit.apply(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/MainGame.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import androidx.preference.PreferenceManager; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | class MainGame { 12 | 13 | static final int SPAWN_ANIMATION = -1; 14 | static final int MOVE_ANIMATION = 0; 15 | static final int MERGE_ANIMATION = 1; 16 | static final int FADE_GLOBAL_ANIMATION = 0; 17 | private static final long MOVE_ANIMATION_TIME = MainView.BASE_ANIMATION_TIME; 18 | private static final long SPAWN_ANIMATION_TIME = (int) (MainView.BASE_ANIMATION_TIME * 1.5); 19 | private static final long NOTIFICATION_ANIMATION_TIME = MainView.BASE_ANIMATION_TIME * 5; 20 | private static final long NOTIFICATION_DELAY_TIME = MOVE_ANIMATION_TIME + SPAWN_ANIMATION_TIME; 21 | private static final String HIGH_SCORE = "high score"; 22 | static int numSquaresX = 4; 23 | static int numSquaresY = 4; 24 | Grid grid; 25 | AnimationGrid aGrid; 26 | private boolean emulating = false; 27 | long score = 0; 28 | private long lastScore = 0; 29 | long highScore = 0; 30 | boolean won = false; 31 | boolean lose = false; 32 | private final Context mContext; 33 | private final MainView mView; 34 | 35 | MainGame(Context context, MainView view) { 36 | mContext = context; 37 | mView = view; 38 | } 39 | 40 | void newGame() { 41 | grid = new Grid(numSquaresX, numSquaresY); 42 | aGrid = new AnimationGrid(numSquaresX, numSquaresY); 43 | highScore = getHighScore(); 44 | if (score >= highScore) { 45 | highScore = score; 46 | recordHighScore(); 47 | } 48 | score = 0; 49 | won = false; 50 | lose = false; 51 | addStartTiles(); 52 | mView.refreshLastTime = true; 53 | mView.reSyncTime(); 54 | mView.postInvalidate(); 55 | } 56 | 57 | private void addStartTiles() { 58 | int startTiles = 2; 59 | for (int xx = 0; xx < startTiles; xx++) { 60 | this.addRandomTile(); 61 | } 62 | } 63 | 64 | private void addRandomTile() { 65 | if (grid.isCellsAvailable()) { 66 | addRandomTile(grid.randomAvailableCell()); 67 | } 68 | } 69 | 70 | void addRandomTile(Cell cell) { 71 | int value = Math.random() < 0.1 ? 2 : 4; 72 | Tile tile = new Tile(cell, value); 73 | grid.insertTile(tile); 74 | if (!emulating) aGrid.startAnimation(tile.getX(), tile.getY(), SPAWN_ANIMATION, 75 | SPAWN_ANIMATION_TIME, MOVE_ANIMATION_TIME, null); //Direction: -1 = EXPANDING 76 | } 77 | 78 | private void recordHighScore() { 79 | SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext); 80 | SharedPreferences.Editor editor = settings.edit(); 81 | editor.putLong(HIGH_SCORE, highScore); 82 | editor.apply(); 83 | } 84 | 85 | private long getHighScore() { 86 | SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext); 87 | return settings.getLong(HIGH_SCORE, -1); 88 | } 89 | 90 | private void prepareTiles() { 91 | for (Tile[] array : grid.field) { 92 | for (Tile tile : array) { 93 | if (grid.isCellOccupied(tile)) { 94 | tile.setMergedFrom(null); 95 | //tile.savePosition(); 96 | } 97 | } 98 | } 99 | } 100 | 101 | private void moveTile(Tile tile, Cell cell) { 102 | grid.field[tile.getX()][tile.getY()] = null; 103 | grid.field[cell.getX()][cell.getY()] = tile; 104 | tile.updatePosition(cell); 105 | } 106 | 107 | private void saveState() { 108 | grid.saveTiles(); 109 | lastScore = score; 110 | } 111 | 112 | void revertState() { 113 | aGrid = new AnimationGrid(numSquaresX, numSquaresY); 114 | grid.revertTiles(); 115 | score = lastScore; 116 | 117 | if (!emulating) { 118 | mView.refreshLastTime = true; 119 | mView.reSyncTime(); 120 | mView.invalidate(); 121 | } 122 | } 123 | 124 | boolean move(int direction) { 125 | saveState(); 126 | 127 | if (!emulating) aGrid = new AnimationGrid(numSquaresX, numSquaresY); 128 | // 0: up, 1: right, 2: down, 3: left 129 | if (lose || won) { 130 | return false; 131 | } 132 | Cell vector = getVector(direction); 133 | List traversalsX = buildTraversalsX(vector); 134 | List traversalsY = buildTraversalsY(vector); 135 | boolean moved = false; 136 | 137 | prepareTiles(); 138 | 139 | for (int xx : traversalsX) { 140 | for (int yy : traversalsY) { 141 | Cell cell = new Cell(xx, yy); 142 | Tile tile = grid.getCellContent(cell); 143 | 144 | if (tile != null) { 145 | Cell[] positions = findFarthestPosition(cell, vector); 146 | Tile next = grid.getCellContent(positions[1]); 147 | 148 | if (next != null && next.getValue() == tile.getValue() && next.getMergedFrom() == null) { 149 | Tile merged = new Tile(positions[1], tile.getValue() * 2); 150 | Tile[] temp = {tile, next}; 151 | merged.setMergedFrom(temp); 152 | 153 | grid.insertTile(merged); 154 | grid.removeTile(tile); 155 | 156 | // Converge the two tiles' positions 157 | tile.updatePosition(positions[1]); 158 | 159 | if (!emulating) { 160 | int[] extras = {xx, yy}; 161 | aGrid.startAnimation(merged.getX(), merged.getY(), MOVE_ANIMATION, 162 | MOVE_ANIMATION_TIME, 0, extras); //Direction: 0 = MOVING MERGED 163 | aGrid.startAnimation(merged.getX(), merged.getY(), MERGE_ANIMATION, 164 | SPAWN_ANIMATION_TIME, MOVE_ANIMATION_TIME, null); 165 | } 166 | 167 | // Update the score 168 | score = score + merged.getValue(); 169 | highScore = Math.max(score, highScore); 170 | 171 | // The mighty max tile 172 | if (merged.getValue() == MainView.maxValue) { 173 | won = true; 174 | endGame(); 175 | } 176 | } else { 177 | moveTile(tile, positions[0]); 178 | int[] extras = {xx, yy, 0}; 179 | if (!emulating) 180 | aGrid.startAnimation(positions[0].getX(), positions[0].getY(), MOVE_ANIMATION, MOVE_ANIMATION_TIME, 0, extras); //Direction: 1 = MOVING NO MERGE 181 | } 182 | 183 | if (!positionsEqual(cell, tile)) { 184 | moved = true; 185 | } 186 | } 187 | } 188 | } 189 | 190 | if (moved) { 191 | if (!emulating && !MainView.inverseMode) { 192 | addRandomTile(); 193 | } 194 | 195 | if (!movesAvailable()) { 196 | lose = true; 197 | endGame(); 198 | } 199 | 200 | } 201 | 202 | if (!emulating) { 203 | mView.reSyncTime(); 204 | mView.postInvalidate(); 205 | } 206 | 207 | return moved; 208 | } 209 | 210 | private void endGame() { 211 | if (emulating) return; 212 | 213 | aGrid.startAnimation(-1, -1, FADE_GLOBAL_ANIMATION, NOTIFICATION_ANIMATION_TIME, NOTIFICATION_DELAY_TIME, null); 214 | if (score >= highScore) { 215 | highScore = score; 216 | recordHighScore(); 217 | } 218 | 219 | grid.canRevert = false; 220 | } 221 | 222 | Cell getVector(int direction) { 223 | Cell[] map = { 224 | new Cell(0, -1), // up 225 | new Cell(1, 0), // right 226 | new Cell(0, 1), // down 227 | new Cell(-1, 0) // left 228 | }; 229 | return map[direction]; 230 | } 231 | 232 | private List buildTraversalsX(Cell vector) { 233 | List traversals = new ArrayList<>(); 234 | 235 | for (int xx = 0; xx < numSquaresX; xx++) { 236 | traversals.add(xx); 237 | } 238 | if (vector.getX() == 1) { 239 | Collections.reverse(traversals); 240 | } 241 | 242 | return traversals; 243 | } 244 | 245 | private List buildTraversalsY(Cell vector) { 246 | List traversals = new ArrayList<>(); 247 | 248 | for (int xx = 0; xx < numSquaresY; xx++) { 249 | traversals.add(xx); 250 | } 251 | if (vector.getY() == 1) { 252 | Collections.reverse(traversals); 253 | } 254 | 255 | return traversals; 256 | } 257 | 258 | Cell[] findFarthestPosition(Cell cell, Cell vector) { 259 | Cell previous; 260 | Cell nextCell = new Cell(cell.getX(), cell.getY()); 261 | do { 262 | previous = nextCell; 263 | nextCell = new Cell(previous.getX() + vector.getX(), 264 | previous.getY() + vector.getY()); 265 | } while (grid.isCellWithinBounds(nextCell) && grid.isCellAvailable(nextCell)); 266 | 267 | return new Cell[]{previous, nextCell}; 268 | } 269 | 270 | private boolean movesAvailable() { 271 | return grid.isCellsAvailable() || tileMatchesAvailable(); 272 | } 273 | 274 | private boolean tileMatchesAvailable() { 275 | Tile tile; 276 | 277 | for (int xx = 0; xx < numSquaresX; xx++) { 278 | for (int yy = 0; yy < numSquaresY; yy++) { 279 | tile = grid.getCellContent(new Cell(xx, yy)); 280 | 281 | if (tile != null) { 282 | for (int direction = 0; direction < 4; direction++) { 283 | Cell vector = getVector(direction); 284 | Cell cell = new Cell(xx + vector.getX(), yy + vector.getY()); 285 | 286 | Tile other = grid.getCellContent(cell); 287 | 288 | if (other != null && other.getValue() == tile.getValue()) { 289 | return true; 290 | } 291 | } 292 | } 293 | } 294 | } 295 | 296 | return false; 297 | } 298 | 299 | private boolean positionsEqual(Cell first, Cell second) { 300 | return first.getX() == second.getX() && first.getY() == second.getY(); 301 | } 302 | 303 | // Only for emulation 304 | @Override 305 | public MainGame clone() { 306 | MainGame newGame = new MainGame(mContext, null); 307 | 308 | newGame.grid = grid.clone(); 309 | newGame.score = score; 310 | newGame.emulating = true; 311 | 312 | return newGame; 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/AI.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | 6 | /* 7 | * 8 | * This is a simple AI for the 2048 game 9 | * Based on alpha-beta method 10 | * Credits to: Matt Overlan 11 | * 12 | */ 13 | 14 | class AI { 15 | private static final long MAX_CONSIDERING_TIME = 100; 16 | private static final float WEIGHT_SMOOTH = 0.1f, WEIGHT_MONO = 1.0f, 17 | WEIGHT_EMPTY = 2.7f, WEIGHT_MAX = 1.0f; 18 | //WEIGHT_ISLANDS = 0.5f, WEIGHT_TWOANDFOUR = 2.5f; 19 | private final MainGame mGame; 20 | 21 | AI(MainGame game) { 22 | mGame = game; 23 | } 24 | 25 | int getBestMove() { 26 | 27 | int bestMove = 0; 28 | int depth = 0; 29 | long start = new Date().getTime(); 30 | 31 | do { 32 | int move = (Integer) search(mGame.clone(), depth, -10000, 10000, Player.DOCTOR)[0]; 33 | if (move == -1) { 34 | break; 35 | } else { 36 | bestMove = move; 37 | depth++; 38 | } 39 | } while (new Date().getTime() - start < MAX_CONSIDERING_TIME); 40 | 41 | return bestMove; 42 | } 43 | 44 | /* 45 | * 46 | * Search for the best move 47 | * Based on alpha-beta search method 48 | * Simulates two players' game 49 | * The Doctor V.S. The Daleks 50 | * 51 | */ 52 | private Object[] search(MainGame game, int depth, int alpha, int beta, Player player) { 53 | int bestMove = -1; 54 | int bestScore = 0; 55 | 56 | if (player == Player.DOCTOR) { 57 | // The Doctor's turn 58 | // Doctor wants to defeat the Daleks 59 | bestScore = alpha; 60 | 61 | for (int i = 0; i <= 3; i++) { 62 | MainGame g = game.clone(); 63 | 64 | if (!g.move(i)) { 65 | continue; 66 | } 67 | 68 | if (game.won) { 69 | // If won, just do it 70 | return new Object[]{i, 10000}; 71 | } 72 | 73 | int score; 74 | 75 | if (depth == 0) { 76 | // Just evaluate if this is at the bottom 77 | score = evaluate(g); 78 | } else { 79 | // Pass the game to the Daleks 80 | score = (Integer) search(g, depth - 1, bestScore, beta, Player.DALEKS)[1]; 81 | 82 | // Don't search any further if won 83 | if (score > 9900) { 84 | score--; 85 | } 86 | } 87 | 88 | if (score > bestScore) { 89 | bestScore = score; 90 | bestMove = i; 91 | } 92 | 93 | // We have found a much much better move 94 | // So, cutoff 95 | if (bestScore > beta) { 96 | return new Object[]{bestMove, beta}; 97 | } 98 | } 99 | } else if (player == Player.DALEKS) { 100 | // The Daleks' turn 101 | // "EXTERMINATE!" 102 | bestScore = beta; 103 | 104 | int maxScore = Integer.MIN_VALUE; 105 | 106 | ArrayList conditions = new ArrayList<>(); 107 | 108 | ArrayList cells = game.grid.getAvailableCells(); 109 | 110 | // Pick out the worst ones for the Doctor 111 | // Try to insert 2 112 | for (Cell cell : cells) { 113 | Tile t = new Tile(cell, 2); 114 | game.grid.insertTile(t); 115 | int score = -getSmoothness(game) + countIslands(game); 116 | conditions.add(new Object[]{cell, 2, score}); 117 | game.grid.removeTile(t); 118 | } 119 | 120 | // Try to insert 4 121 | for (Cell cell : cells) { 122 | Tile t = new Tile(cell, 4); 123 | game.grid.insertTile(t); 124 | int score = -getSmoothness(game) + countIslands(game); 125 | conditions.add(new Object[]{cell, 4, score}); 126 | game.grid.removeTile(t); 127 | } 128 | 129 | // Find the max score(the worst for the Doctor) 130 | for (Object[] obj : conditions) { 131 | int score = (Integer) obj[2]; 132 | if (score > maxScore) { 133 | maxScore = score; 134 | } 135 | } 136 | 137 | // Play all the games with the Doctor 138 | for (Object[] obj : conditions) { 139 | int s = (Integer) obj[2]; 140 | 141 | // If not worst, just skip it 142 | if (s != maxScore) continue; 143 | 144 | Cell cell = (Cell) obj[0]; 145 | int value = (Integer) obj[1]; 146 | MainGame g = game.clone(); 147 | 148 | Tile t = new Tile(cell, value); 149 | g.grid.insertTile(t); 150 | 151 | // Pass the game to human 152 | int score = (Integer) search(g, depth, alpha, bestScore, Player.DOCTOR)[1]; 153 | 154 | if (score < bestScore) { 155 | bestScore = score; 156 | } 157 | 158 | // Computer lose 159 | // Cutoff 160 | if (bestScore < alpha) { 161 | return new Object[]{-1, alpha}; 162 | } 163 | } 164 | //return new Object[]{bestMove, beta}; 165 | } 166 | 167 | return new Object[]{bestMove, bestScore}; 168 | } 169 | 170 | // Evaluate how is it if we take the step 171 | private int evaluate(MainGame game) { 172 | int smooth = getSmoothness(game); 173 | int mono = getMonotonicity(game); 174 | int empty = game.grid.getAvailableCells().size(); 175 | int max = getMaxValue(game); 176 | //int islands = countIslands(game); 177 | //int twoAndFour = countTwosAndFours(game); 178 | 179 | return (int) (smooth * WEIGHT_SMOOTH 180 | + mono * WEIGHT_MONO 181 | + Math.log(empty) * WEIGHT_EMPTY 182 | + max * WEIGHT_MAX 183 | //- islands * WEIGHT_ISLANDS 184 | /*- twoAndFour * WEIGHT_TWOANDFOUR*/); 185 | } 186 | 187 | // How smooth the grid is 188 | private int getSmoothness(MainGame game) { 189 | int smoothness = 0; 190 | for (int x = 0; x < MainGame.numSquaresX; x++) { 191 | for (int y = 0; y < MainGame.numSquaresY; y++) { 192 | Tile t = game.grid.field[x][y]; 193 | if (t != null) { 194 | int value = (int) (Math.log(t.getValue()) / Math.log(2)); 195 | for (int direction = 1; direction <= 2; direction++) { 196 | Cell vector = game.getVector(direction); 197 | Cell targetCell = game.findFarthestPosition(new Cell(x, y), vector)[1]; 198 | 199 | if (game.grid.isCellOccupied(targetCell)) { 200 | Tile target = game.grid.getCellContent(targetCell); 201 | int targetValue = (int) (Math.log(target.getValue()) / Math.log(2)); 202 | 203 | smoothness -= Math.abs(value - targetValue); 204 | } 205 | } 206 | 207 | } 208 | } 209 | } 210 | 211 | return smoothness; 212 | } 213 | 214 | // How monotonic the grid is 215 | private int getMonotonicity(MainGame game) { 216 | int[] totals = {0, 0, 0, 0}; 217 | 218 | // Up-down 219 | for (int x = 0; x < MainGame.numSquaresX; x++) { 220 | int current = 0; 221 | int next = current + 1; 222 | while (next < MainGame.numSquaresY) { 223 | while (next < MainGame.numSquaresY && game.grid.isCellAvailable(new Cell(x, next))) { 224 | next++; 225 | } 226 | if (next >= MainGame.numSquaresY) { 227 | next--; 228 | } 229 | int currentValue = game.grid.isCellOccupied(new Cell(x, current)) ? 230 | (int) (Math.log(game.grid.getCellContent(x, current).getValue()) / Math.log(2)) : 231 | 0; 232 | int nextValue = game.grid.isCellOccupied(new Cell(x, next)) ? 233 | (int) (Math.log(game.grid.getCellContent(x, next).getValue()) / Math.log(2)) : 234 | 0; 235 | if (currentValue > nextValue) { 236 | totals[0] += nextValue - currentValue; 237 | } else if (nextValue > currentValue) { 238 | totals[1] += currentValue - nextValue; 239 | } 240 | current = next; 241 | next++; 242 | } 243 | } 244 | 245 | // Left-right 246 | for (int y = 0; y < MainGame.numSquaresY; y++) { 247 | int current = 0; 248 | int next = current + 1; 249 | while (next < MainGame.numSquaresX) { 250 | while (next < MainGame.numSquaresX && game.grid.isCellAvailable(new Cell(next, y))) { 251 | next++; 252 | } 253 | if (next >= MainGame.numSquaresX) { 254 | next--; 255 | } 256 | int currentValue = game.grid.isCellOccupied(new Cell(current, y)) ? 257 | (int) (Math.log(game.grid.getCellContent(current, y).getValue()) / Math.log(2)) : 258 | 0; 259 | int nextValue = game.grid.isCellOccupied(new Cell(next, y)) ? 260 | (int) (Math.log(game.grid.getCellContent(next, y).getValue()) / Math.log(2)) : 261 | 0; 262 | if (currentValue > nextValue) { 263 | totals[2] += nextValue - currentValue; 264 | } else if (nextValue > currentValue) { 265 | totals[3] += currentValue - nextValue; 266 | } 267 | current = next; 268 | next++; 269 | } 270 | } 271 | 272 | return Math.max(totals[0], totals[1]) + Math.max(totals[2], totals[3]); 273 | } 274 | 275 | private int getMaxValue(MainGame game) { 276 | int max = 0; 277 | for (int x = 0; x < MainGame.numSquaresX; x++) { 278 | for (int y = 0; y < MainGame.numSquaresY; y++) { 279 | Cell cell = new Cell(x, y); 280 | if (game.grid.isCellOccupied(cell)) { 281 | Tile t = game.grid.getCellContent(cell); 282 | int value = t.getValue(); 283 | if (value > max) { 284 | max = value; 285 | } 286 | } 287 | } 288 | } 289 | return max; 290 | } 291 | 292 | private int countIslands(MainGame game) { 293 | int islands = 0; 294 | 295 | for (int x = 0; x < MainGame.numSquaresX; x++) { 296 | for (int y = 0; y < MainGame.numSquaresY; y++) { 297 | if (game.grid.isCellOccupied(new Cell(x, y))) { 298 | game.grid.getCellContent(x, y).marked = false; 299 | } 300 | } 301 | } 302 | 303 | for (int x = 0; x < MainGame.numSquaresX; x++) { 304 | for (int y = 0; y < MainGame.numSquaresY; y++) { 305 | if (game.grid.isCellOccupied(new Cell(x, y))) { 306 | Tile t = game.grid.getCellContent(x, y); 307 | if (!t.marked) { 308 | islands++; 309 | mark(game, x, y, t.getValue()); 310 | } 311 | } 312 | } 313 | } 314 | 315 | return islands; 316 | } 317 | 318 | private void mark(MainGame game, int x, int y, int value) { 319 | if (game.grid.isCellWithinBounds(x, y) && game.grid.isCellOccupied(new Cell(x, y))) { 320 | Tile t = game.grid.getCellContent(x, y); 321 | if (!t.marked && t.getValue() == value) { 322 | t.marked = true; 323 | 324 | for (int i = 0; i <= 3; i++) { 325 | Cell vector = game.getVector(i); 326 | mark(game, x + vector.getX(), y + vector.getY(), value); 327 | } 328 | } 329 | } 330 | } 331 | 332 | /* 333 | private int countTwosAndFours(MainGame game) { 334 | int num = 0; 335 | for (int x = 0; x < MainGame.numSquaresX; x++) { 336 | for (int y = 0; y < MainGame.numSquaresY; y++) { 337 | Cell cell = new Cell(x, y); 338 | if (game.grid.isCellOccupied(cell)) { 339 | Tile t = game.grid.getCellContent(cell); 340 | 341 | if (t.getValue() <= 4) { 342 | num++; 343 | } 344 | } 345 | } 346 | } 347 | return num; 348 | } 349 | */ 350 | 351 | enum Player { 352 | DOCTOR, 353 | DALEKS 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /app/src/main/java/com/osfans/android2048/MainView.java: -------------------------------------------------------------------------------- 1 | package com.osfans.android2048; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.graphics.Typeface; 9 | import android.graphics.drawable.BitmapDrawable; 10 | import android.graphics.drawable.Drawable; 11 | import android.os.Handler; 12 | import android.os.Message; 13 | import android.view.View; 14 | 15 | import java.lang.ref.WeakReference; 16 | import java.util.ArrayList; 17 | import java.util.ConcurrentModificationException; 18 | import java.util.Random; 19 | 20 | public class MainView extends View { 21 | static final int BASE_ANIMATION_TIME = 120000000; 22 | private static final float MOVING_ACCELERATION = (float) 0.6; 23 | private static final float MERGING_ACCELERATION = (float) 0.6; 24 | private static final float MAX_VELOCITY = (float) (MERGING_ACCELERATION * 0.5); // v = at (t = 0.5) 25 | public static boolean inverseMode = false; 26 | static int sYIcons; 27 | static int sXNewGame; 28 | static int iconSize; 29 | static int maxValue; 30 | private static int textPaddingSize = 0; 31 | public final MainGame game; 32 | private final Paint paint = new Paint(); 33 | private final Paint paintOrder = new Paint(); 34 | private AI ai; 35 | int cellSize = 0; 36 | private float textSize = 0; 37 | int gridWidth = 0; 38 | private int boardMiddleX = 0; 39 | private int boardMiddleY = 0; 40 | private Drawable backgroundRectangle; 41 | private Drawable[] cellRectangle = new Drawable[12]; 42 | private Drawable lightUpRectangle; 43 | private Drawable fadeRectangle; 44 | private Bitmap background = null; 45 | private int backgroundColor; 46 | private int TEXT_BLACK; 47 | private int TEXT_WHITE; 48 | private int TEXT_BROWN; 49 | int startingX; 50 | int startingY; 51 | private int endingX; 52 | private int endingY; 53 | private int sYAll; 54 | private int titleStartYAll; 55 | private int bodyStartYAll; 56 | private int eYAll; 57 | private int titleWidthHighScore; 58 | private int titleWidthScore; 59 | private long lastFPSTime = System.nanoTime(); 60 | private float titleTextSize; 61 | private float bodyTextSize; 62 | private float headerTextSize; 63 | private float instructionsTextSize; 64 | private float gameOverTextSize; 65 | boolean refreshLastTime = true; 66 | private String highScore; 67 | private String score; 68 | private String youWin; 69 | private String gameOver; 70 | private String instructions = ""; 71 | private String[] tileTexts; 72 | 73 | static class MyHandler extends Handler { 74 | private final WeakReference mView; 75 | 76 | MyHandler(MainView view) { 77 | mView = new WeakReference<>(view); 78 | } 79 | 80 | @Override 81 | public void handleMessage(Message msg) { 82 | if (mView.get() == null) { 83 | return; 84 | } 85 | MainView view = mView.get(); 86 | if (!view.getGame().move((Integer) msg.obj)) { 87 | // If not moved, random move 88 | sendMessage(this.obtainMessage(0, Math.abs(new Random().nextInt()) % 4)); 89 | } else { 90 | view.invalidate(); 91 | } 92 | } 93 | } 94 | 95 | private final Handler aiHandler = new MyHandler(this); 96 | private Thread aiThread; 97 | boolean aiRunning = false; 98 | private final Runnable aiRunnable = new Runnable() { 99 | @Override 100 | public void run() { 101 | while (!game.won && !game.lose) { 102 | try { 103 | int bestMove = ai.getBestMove(); 104 | aiHandler.sendMessage(aiHandler.obtainMessage(0, bestMove)); 105 | } catch (NullPointerException e) { 106 | break; 107 | } 108 | 109 | if (inverseMode) { 110 | // Run only one step in inverse mode 111 | aiRunning = false; 112 | aiThread = null; 113 | Thread.currentThread().interrupt(); 114 | break; 115 | } 116 | 117 | try { 118 | Thread.sleep(1); 119 | } catch (InterruptedException e) { 120 | ai = null; 121 | break; 122 | } 123 | } 124 | } 125 | }; 126 | 127 | private void updateFont() { 128 | boolean systemFont = SettingsProvider.getBoolean(SettingsProvider.KEY_SYSTEM_FONT); 129 | Typeface font = null; 130 | if (!systemFont) font = Typeface.createFromAsset(getResources().getAssets(), "Symbola.ttf"); 131 | paint.setTypeface(font); 132 | } 133 | 134 | private MainGame getGame() { 135 | return game; 136 | } 137 | 138 | public MainView(Context context) { 139 | super(context); 140 | setFocusable(true); 141 | Resources resources = context.getResources(); 142 | 143 | //Loading resources 144 | game = new MainGame(context, this); 145 | 146 | try { 147 | highScore = resources.getString(R.string.high_score); 148 | score = resources.getString(R.string.score); 149 | youWin = resources.getString(R.string.you_win); 150 | gameOver = resources.getString(R.string.game_over); 151 | backgroundRectangle = resources.getDrawable(R.drawable.background_rectangle); 152 | lightUpRectangle = resources.getDrawable(R.drawable.light_up_rectangle); 153 | fadeRectangle = resources.getDrawable(R.drawable.fade_rectangle); 154 | TEXT_WHITE = resources.getColor(R.color.text_white); 155 | TEXT_BLACK = resources.getColor(R.color.text_black); 156 | TEXT_BROWN = resources.getColor(R.color.text_brown); 157 | backgroundColor = resources.getColor(R.color.background); 158 | paint.setAntiAlias(true); 159 | updateFont(); 160 | } catch (Exception e) { 161 | System.out.println("Error getting assets?"); 162 | } 163 | InputListener listener = new InputListener(this); 164 | setOnTouchListener(listener); 165 | setOnKeyListener(listener); 166 | newGame(); 167 | } 168 | 169 | private static int log2(int n) { 170 | if (n <= 0) throw new IllegalArgumentException(); 171 | return (int) (Math.log(n) / Math.log(2)); 172 | } 173 | 174 | @Override 175 | protected void onSizeChanged(int width, int height, int oldW, int oldH) { 176 | super.onSizeChanged(width, height, oldW, oldH); 177 | getLayout(width, height); 178 | createBackgroundBitmap(width, height); 179 | } 180 | 181 | @Override 182 | public void onDraw(Canvas canvas) { 183 | //Reset the transparency of the screen 184 | 185 | canvas.drawBitmap(background, 0, 0, paint); 186 | 187 | drawScoreText(canvas); 188 | 189 | drawCells(canvas); 190 | 191 | drawEndGameState(canvas); 192 | 193 | if (game.aGrid.isAnimationActive()) { 194 | // Refresh when animation running 195 | invalidate(startingX, startingY, endingX, endingY); 196 | tick(); 197 | } else if ((game.won || game.lose) && refreshLastTime) { 198 | // Refresh last time when game end 199 | invalidate(); 200 | refreshLastTime = false; 201 | } 202 | } 203 | 204 | private void drawDrawable(Canvas canvas, Drawable draw, int startingX, int startingY, int endingX, int endingY) { 205 | draw.setBounds(startingX, startingY, endingX, endingY); 206 | draw.draw(canvas); 207 | } 208 | 209 | private void drawCellText(Canvas canvas, int value) { 210 | int n = tileTexts[value - 1].codePointCount(0, tileTexts[value - 1].length()); 211 | float i = textSize / (float) (n > 1 ? n * 0.51 : 1); 212 | paint.setTextSize(n == 1 && tileTexts[value - 1].length() == 2 ? (float) (i * 1.6) : i); 213 | paintOrder.setTextSize((float) (textSize / 2.5)); 214 | int textShiftY = centerText(); 215 | if (value >= 3) { 216 | paint.setColor(TEXT_WHITE); 217 | paintOrder.setColor(TEXT_WHITE); 218 | } else { 219 | paint.setColor(TEXT_BLACK); 220 | paintOrder.setColor(TEXT_BLACK); 221 | } 222 | canvas.drawText(tileTexts[value - 1], cellSize / 2f, cellSize / 2f - textShiftY, paint); 223 | int order = SettingsProvider.getInt(SettingsProvider.KEY_ORDER, getResources().getString(R.string.default_order)); 224 | if (order < 2) { 225 | canvas.drawText(order == 0 ? String.valueOf(value) : String.valueOf((char) (value - 1 + 'A')), 0, -paintOrder.ascent(), paintOrder); 226 | } 227 | } 228 | 229 | private void drawScoreText(Canvas canvas) { 230 | //Drawing the score text: Ver 2 231 | paint.setTextSize(bodyTextSize); 232 | paint.setTextAlign(Paint.Align.CENTER); 233 | 234 | int bodyWidthHighScore = (int) (paint.measureText("" + game.highScore)); 235 | int bodyWidthScore = (int) (paint.measureText("" + game.score)); 236 | 237 | int textWidthHighScore = Math.max(titleWidthHighScore, bodyWidthHighScore) + textPaddingSize * 2; 238 | int textWidthScore = Math.max(titleWidthScore, bodyWidthScore) + textPaddingSize * 2; 239 | 240 | int textMiddleHighScore = textWidthHighScore / 2; 241 | int textMiddleScore = textWidthScore / 2; 242 | 243 | int eXHighScore = endingX; 244 | int sXHighScore = eXHighScore - textWidthHighScore; 245 | 246 | int eXScore = sXHighScore - textPaddingSize; 247 | int sXScore = eXScore - textWidthScore; 248 | 249 | //Outputting high-scores box 250 | backgroundRectangle.setBounds(sXHighScore, sYAll, eXHighScore, eYAll); 251 | backgroundRectangle.draw(canvas); 252 | paint.setTextSize(titleTextSize); 253 | paint.setColor(TEXT_BROWN); 254 | canvas.drawText(highScore, sXHighScore + textMiddleHighScore, titleStartYAll, paint); 255 | paint.setTextSize(bodyTextSize); 256 | paint.setColor(TEXT_WHITE); 257 | canvas.drawText("" + game.highScore, sXHighScore + textMiddleHighScore, bodyStartYAll, paint); 258 | 259 | 260 | //Outputting scores box 261 | backgroundRectangle.setBounds(sXScore, sYAll, eXScore, eYAll); 262 | backgroundRectangle.draw(canvas); 263 | paint.setTextSize(titleTextSize); 264 | paint.setColor(TEXT_BROWN); 265 | canvas.drawText(score, sXScore + textMiddleScore, titleStartYAll, paint); 266 | paint.setTextSize(bodyTextSize); 267 | paint.setColor(TEXT_WHITE); 268 | canvas.drawText("" + game.score, sXScore + textMiddleScore, bodyStartYAll, paint); 269 | } 270 | 271 | private void drawHeader(Canvas canvas) { 272 | //Drawing the header 273 | paint.setTextSize(headerTextSize); 274 | paint.setColor(TEXT_BLACK); 275 | paint.setTextAlign(Paint.Align.LEFT); 276 | //int textShiftY = centerText() * 2; 277 | //int headerStartY = sYAll - textShiftY + (int) headerTextSize; 278 | canvas.drawText(String.valueOf(maxValue), startingX, bodyStartYAll, paint); 279 | } 280 | 281 | private void drawInstructions(Canvas canvas) { 282 | //Drawing the instructions 283 | paint.setTextSize(instructionsTextSize); 284 | paint.setTextAlign(Paint.Align.LEFT); 285 | int textShiftY = centerText() * 2; 286 | canvas.drawText(instructions, 287 | startingX, endingY - textShiftY + textPaddingSize, paint); 288 | } 289 | 290 | private void drawBackground(Canvas canvas) { 291 | drawDrawable(canvas, backgroundRectangle, startingX, startingY, endingX, endingY); 292 | } 293 | 294 | private void drawBackgroundGrid(Canvas canvas) { 295 | // Outputting the game grid 296 | for (int xx = 0; xx < MainGame.numSquaresX; xx++) { 297 | for (int yy = 0; yy < MainGame.numSquaresY; yy++) { 298 | int sX = startingX + gridWidth + (cellSize + gridWidth) * xx; 299 | int eX = sX + cellSize; 300 | int sY = startingY + gridWidth + (cellSize + gridWidth) * yy; 301 | int eY = sY + cellSize; 302 | 303 | drawDrawable(canvas, cellRectangle[0], sX, sY, eX, eY); 304 | } 305 | } 306 | } 307 | 308 | private void drawCells(Canvas canvas) { 309 | // Outputting the individual cells 310 | for (int xx = 0; xx < MainGame.numSquaresX; xx++) { 311 | for (int yy = 0; yy < MainGame.numSquaresY; yy++) { 312 | int sX = startingX + gridWidth + (cellSize + gridWidth) * xx; 313 | int eX = sX + cellSize; 314 | int sY = startingY + gridWidth + (cellSize + gridWidth) * yy; 315 | int eY = sY + cellSize; 316 | 317 | Tile currentTile = game.grid.field[xx][yy]; 318 | if (currentTile != null) { 319 | //Get and represent the value of the tile 320 | int value = currentTile.getValue(); 321 | int index = log2(value); 322 | if (index >= cellRectangle.length) { 323 | newGame(); 324 | return; 325 | } 326 | 327 | //Check for any active animations 328 | ArrayList aArray = game.aGrid.getAnimationCell(xx, yy); 329 | boolean animated = false; 330 | for (int i = aArray.size() - 1; i >= 0; i--) { 331 | AnimationCell aCell = aArray.get(i); 332 | //If this animation is not active, skip it 333 | if (aCell.getAnimationType() == MainGame.SPAWN_ANIMATION) { 334 | animated = true; 335 | } 336 | if (!aCell.isActive()) { 337 | continue; 338 | } 339 | 340 | if (aCell.getAnimationType() == MainGame.SPAWN_ANIMATION) { // Spawning animation 341 | double percentDone = aCell.getPercentageDone(); 342 | float textScaleSize = (float) (percentDone); 343 | 344 | float cellScaleSize = cellSize / 2f * (1 - textScaleSize); 345 | drawDrawable(canvas, cellRectangle[index], (int) (sX + cellScaleSize), (int) (sY + cellScaleSize), (int) (eX - cellScaleSize), (int) (eY - cellScaleSize)); 346 | } else if (aCell.getAnimationType() == MainGame.MERGE_ANIMATION) { // Merging Animation 347 | double percentDone = aCell.getPercentageDone(); 348 | 349 | float currentVelocity; 350 | 351 | // Accelerate and then moderate 352 | if (percentDone < 0.5) { 353 | currentVelocity = (float) (MERGING_ACCELERATION * percentDone); // v = at 354 | } else { 355 | currentVelocity = (float) (MAX_VELOCITY - MERGING_ACCELERATION * (percentDone - 0.5)); // v = v0 - at 356 | } 357 | 358 | float textScaleSize = (float) (1 + currentVelocity * percentDone); // s = vt 359 | 360 | float cellScaleSize = cellSize / 2f * (1 - textScaleSize); 361 | drawDrawable(canvas, cellRectangle[index], (int) (sX + cellScaleSize), (int) (sY + cellScaleSize), (int) (eX - cellScaleSize), (int) (eY - cellScaleSize)); 362 | } else if (aCell.getAnimationType() == MainGame.MOVE_ANIMATION) { // Moving animation 363 | double percentDone = aCell.getPercentageDone(); 364 | int tempIndex = index; 365 | if (aArray.size() >= 2) { 366 | tempIndex = tempIndex - 1; 367 | } 368 | int previousX = aCell.extras[0]; 369 | int previousY = aCell.extras[1]; 370 | int currentX = currentTile.getX(); 371 | int currentY = currentTile.getY(); 372 | int dX = (int) ((currentX - previousX) * (cellSize + gridWidth) * (percentDone - 1) * (percentDone - 1) * -MOVING_ACCELERATION); 373 | int dY = (int) ((currentY - previousY) * (cellSize + gridWidth) * (percentDone - 1) * (percentDone - 1) * -MOVING_ACCELERATION); 374 | 375 | drawDrawable(canvas, cellRectangle[tempIndex], sX + dX, sY + dY, eX + dX, eY + dY); 376 | } 377 | animated = true; 378 | } 379 | 380 | //No active animations? Just draw the cell 381 | if (!animated) { 382 | drawDrawable(canvas, cellRectangle[index], sX, sY, eX, eY); 383 | } 384 | } 385 | } 386 | } 387 | } 388 | 389 | private void drawEndGameState(Canvas canvas) { 390 | double alphaChange = 1; 391 | //Animation: Dynamically change the alpha 392 | for (AnimationCell animation : game.aGrid.globalAnimation) { 393 | if (animation.getAnimationType() == MainGame.FADE_GLOBAL_ANIMATION) { 394 | alphaChange = animation.getPercentageDone(); 395 | } 396 | 397 | } 398 | // Displaying game over 399 | if (game.won) { 400 | lightUpRectangle.setAlpha((int) (127 * alphaChange)); 401 | drawDrawable(canvas, lightUpRectangle, startingX, startingY, endingX, endingY); 402 | lightUpRectangle.setAlpha(255); 403 | paint.setColor(TEXT_WHITE); 404 | paint.setAlpha((int) (255 * alphaChange)); 405 | paint.setTextSize(gameOverTextSize); 406 | paint.setTextAlign(Paint.Align.CENTER); 407 | canvas.drawText(youWin, boardMiddleX, boardMiddleY - centerText(), paint); 408 | paint.setAlpha(255); 409 | } else if (game.lose) { 410 | fadeRectangle.setAlpha((int) (127 * alphaChange)); 411 | drawDrawable(canvas, fadeRectangle, startingX, startingY, endingX, endingY); 412 | fadeRectangle.setAlpha(255); 413 | paint.setColor(TEXT_BLACK); 414 | paint.setAlpha((int) (255 * alphaChange)); 415 | paint.setTextSize(gameOverTextSize); 416 | paint.setTextAlign(Paint.Align.CENTER); 417 | canvas.drawText(gameOver, boardMiddleX, boardMiddleY - centerText(), paint); 418 | paint.setAlpha(255); 419 | } 420 | } 421 | 422 | private void createBackgroundBitmap(int width, int height) { 423 | background = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 424 | Canvas canvas = new Canvas(background); 425 | canvas.drawColor(backgroundColor); 426 | drawHeader(canvas); 427 | drawBackground(canvas); 428 | drawBackgroundGrid(canvas); 429 | drawInstructions(canvas); 430 | } 431 | 432 | private void tick() { 433 | long currentTime = System.nanoTime(); 434 | 435 | try { 436 | game.aGrid.tickAll(currentTime - lastFPSTime); 437 | } catch (ConcurrentModificationException e) { 438 | // Might be modified in background 439 | } 440 | lastFPSTime = currentTime; 441 | } 442 | 443 | public void reSyncTime() { 444 | lastFPSTime = System.nanoTime(); 445 | } 446 | 447 | private void getLayout(int width, int height) { 448 | cellSize = Math.min(width / (MainGame.numSquaresX + 1), height / (MainGame.numSquaresY + 3)); 449 | gridWidth = cellSize / 7; 450 | int screenMiddleX = width / 2; 451 | int screenMiddleY = height / 2; 452 | boardMiddleX = screenMiddleX; 453 | boardMiddleY = screenMiddleY + cellSize / 2; 454 | iconSize = cellSize / 2; 455 | 456 | paint.setTextAlign(Paint.Align.CENTER); 457 | paint.setTextSize(cellSize); 458 | textSize = cellSize * cellSize / Math.max(cellSize, paint.measureText("0000")); 459 | titleTextSize = textSize / 3; 460 | bodyTextSize = (int) (textSize / 1.5); 461 | instructionsTextSize = (int) (textSize / 1.5); 462 | headerTextSize = Math.min(textSize * 2, cellSize * cellSize * 2 / Math.max(cellSize, paint.measureText(String.valueOf(maxValue)))); 463 | gameOverTextSize = textSize * 2; 464 | textPaddingSize = (int) (textSize / 3); 465 | //int iconPaddingSize = (int) (textSize / 5); 466 | 467 | //Grid Dimensions 468 | double halfNumSquaresX = MainGame.numSquaresX / 2d; 469 | double halfNumSquaresY = MainGame.numSquaresY / 2d; 470 | 471 | startingX = (int) (boardMiddleX - (cellSize + gridWidth) * halfNumSquaresX - gridWidth / 2f); 472 | endingX = (int) (boardMiddleX + (cellSize + gridWidth) * halfNumSquaresX + gridWidth / 2f); 473 | startingY = (int) (boardMiddleY - (cellSize + gridWidth) * halfNumSquaresY - gridWidth / 2f); 474 | endingY = (int) (boardMiddleY + (cellSize + gridWidth) * halfNumSquaresY + gridWidth / 2f); 475 | 476 | paint.setTextSize(titleTextSize); 477 | 478 | int textShiftYAll = centerText(); 479 | //static variables 480 | sYAll = (int) (startingY - cellSize * 1.5); 481 | titleStartYAll = (int) (sYAll + textPaddingSize + titleTextSize / 2 - textShiftYAll); 482 | bodyStartYAll = (int) (titleStartYAll + textPaddingSize + titleTextSize / 2 + bodyTextSize / 2); 483 | 484 | titleWidthHighScore = (int) (paint.measureText(highScore)); 485 | titleWidthScore = (int) (paint.measureText(score)); 486 | paint.setTextSize(bodyTextSize); 487 | textShiftYAll = centerText(); 488 | eYAll = (int) (bodyStartYAll + textShiftYAll + bodyTextSize / 2 + textPaddingSize); 489 | 490 | sYIcons = (startingY + eYAll) / 2 - iconSize / 2; 491 | sXNewGame = (endingX - iconSize); 492 | reSyncTime(); 493 | //boolean getScreenSize = false; 494 | initRectangleDrawables(); 495 | } 496 | 497 | public void initRectangleDrawables() { 498 | updateFont(); 499 | paint.setTextSize(textSize); 500 | paint.setTextAlign(Paint.Align.CENTER); 501 | 502 | Resources resources = getResources(); 503 | 504 | // Tile texts 505 | int index = SettingsProvider.getInt(SettingsProvider.KEY_VARIETY, resources.getString(R.string.default_variety)); 506 | String[] values = resources.getStringArray(R.array.variety_strings); 507 | String s = ""; 508 | if (index == values.length - 1) { // custom 509 | s = SettingsProvider.getString(SettingsProvider.KEY_CUSTOM_VARIETY, ""); 510 | } 511 | if (s.length() == 0) s = values[index]; 512 | tileTexts = s.split(s.contains(" ") ? " +" : "\\B"); 513 | maxValue = (int) Math.pow(2, tileTexts.length); 514 | 515 | cellRectangle = new Drawable[12]; 516 | cellRectangle[0] = resources.getDrawable(R.drawable.cell_rectangle); 517 | cellRectangle[1] = resources.getDrawable(R.drawable.cell_rectangle_2); 518 | cellRectangle[2] = resources.getDrawable(R.drawable.cell_rectangle_4); 519 | cellRectangle[3] = resources.getDrawable(R.drawable.cell_rectangle_8); 520 | cellRectangle[4] = resources.getDrawable(R.drawable.cell_rectangle_16); 521 | cellRectangle[5] = resources.getDrawable(R.drawable.cell_rectangle_32); 522 | cellRectangle[6] = resources.getDrawable(R.drawable.cell_rectangle_64); 523 | cellRectangle[7] = resources.getDrawable(R.drawable.cell_rectangle_128); 524 | cellRectangle[8] = resources.getDrawable(R.drawable.cell_rectangle_256); 525 | cellRectangle[9] = resources.getDrawable(R.drawable.cell_rectangle_512); 526 | cellRectangle[10] = resources.getDrawable(R.drawable.cell_rectangle_1024); 527 | cellRectangle[11] = resources.getDrawable(R.drawable.cell_rectangle_2048); 528 | // The last drawable 529 | Drawable lastDrawable = cellRectangle[11]; 530 | 531 | // Array 532 | Drawable[] newArray = new Drawable[tileTexts.length + 1]; 533 | newArray[0] = cellRectangle[0]; 534 | 535 | // Draw the rectangles into cache 536 | for (int i = 1; i < tileTexts.length + 1; i++) { 537 | Drawable rect; 538 | if (i <= 11) { 539 | rect = cellRectangle[i]; 540 | } else { 541 | rect = lastDrawable; 542 | } 543 | Bitmap bitmap = Bitmap.createBitmap(cellSize, cellSize, Bitmap.Config.ARGB_8888); 544 | Canvas canvas = new Canvas(bitmap); 545 | drawDrawable(canvas, rect, 0, 0, cellSize, cellSize); 546 | drawCellText(canvas, i); 547 | rect = new BitmapDrawable(bitmap); 548 | newArray[i] = rect; 549 | } 550 | 551 | cellRectangle = newArray; 552 | } 553 | 554 | private int centerText() { 555 | return (int) ((paint.descent() + paint.ascent()) / 2); 556 | } 557 | 558 | private void newGame() { 559 | Resources resources = getResources(); 560 | // Inverse mode 561 | inverseMode = SettingsProvider.getBoolean(SettingsProvider.KEY_INVERSE_MODE); 562 | 563 | int i = SettingsProvider.getInt(SettingsProvider.KEY_ROWS, resources.getString(R.string.default_row)); 564 | MainGame.numSquaresX = i; 565 | MainGame.numSquaresY = i; 566 | if (!inverseMode) { 567 | instructions = resources.getString(R.string.instructions); 568 | } else { 569 | instructions = resources.getString(R.string.instructions_inverse); 570 | } 571 | game.newGame(); 572 | } 573 | 574 | public void startAi() { 575 | if (aiThread != null) { 576 | stopAi(); 577 | } 578 | 579 | ai = new AI(game); 580 | aiThread = new Thread(aiRunnable); 581 | aiThread.start(); 582 | aiRunning = true; 583 | } 584 | 585 | public void stopAi() { 586 | if (!aiRunning) return; 587 | aiThread.interrupt(); 588 | 589 | aiThread = null; 590 | aiRunning = false; 591 | } 592 | 593 | public void toggleAi() { 594 | if (aiRunning) { 595 | stopAi(); 596 | return; 597 | } 598 | startAi(); 599 | } 600 | } 601 | --------------------------------------------------------------------------------