diff options
| author | Jakob L. Kreuze <jakob@memeware.net> | 2018-12-15 09:21:53 -0500 |
|---|---|---|
| committer | Jakob L. Kreuze <jakob@memeware.net> | 2018-12-15 09:21:53 -0500 |
| commit | 15b4a3158bbb501aef542cba3bf1e429d2d594b1 (patch) | |
| tree | a9b7535eac86ff855e847666f81a087272ea0181 /app | |
| parent | 76fd5eaa9707ed423d442b594ec31997901ce2e6 (diff) | |
Turn into proper Android project.
Diffstat (limited to 'app')
21 files changed, 371 insertions, 0 deletions
diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..6cf711a --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "space.jakob.mines" + minSdkVersion 15 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:design:28.0.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7a4a4d0 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="space.jakob.mines"> + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + <activity + android:name=".SetupActivity" + android:label="@string/title_activity_setup"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".GameActivity"> + </activity> + </application> +</manifest> diff --git a/app/src/main/java/space/jakob/mines/GameActivity.kt b/app/src/main/java/space/jakob/mines/GameActivity.kt new file mode 100644 index 0000000..ad09481 --- /dev/null +++ b/app/src/main/java/space/jakob/mines/GameActivity.kt @@ -0,0 +1,11 @@ +package space.jakob.mines + +import android.support.v7.app.AppCompatActivity +import android.os.Bundle + +class GameActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_game) + } +} diff --git a/app/src/main/java/space/jakob/mines/GameView.kt b/app/src/main/java/space/jakob/mines/GameView.kt new file mode 100644 index 0000000..eed0d9c --- /dev/null +++ b/app/src/main/java/space/jakob/mines/GameView.kt @@ -0,0 +1,21 @@ +package space.jakob.mines + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View + +class GameView(context: Context, attrs: AttributeSet) : View(context, attrs) { + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + val textPaint = Paint().apply { + textSize = 64f + } + + canvas.apply { + drawText("Hello, world!", 24f, 24f, textPaint) + } + } +} diff --git a/app/src/main/java/space/jakob/mines/Grid.kt b/app/src/main/java/space/jakob/mines/Grid.kt new file mode 100644 index 0000000..a532632 --- /dev/null +++ b/app/src/main/java/space/jakob/mines/Grid.kt @@ -0,0 +1,113 @@ +data class Tile(val adjacentMines: Int = 0, val mine: Boolean = false, val masked: Boolean = true) + +/** + * The Minesweeper "grid", containing instances of [Tile]. + */ +class Grid(val width: Int, val height: Int, val tiles: Array<Tile>) { + constructor(width: Int = 8, height: Int = 8, mines: Int = 10) + : this(width, height, Array<Tile>(width * height) { Tile() }) { + for (i in 0 until mines) { + val x = (0 until width).random() + val y = (0 until height).random() + placeMine(x, y) + } + } + + private fun index(x: Int, y: Int) = y * width + x + private fun valid(x: Int, y: Int) = y in 0 until height && x in 0 until width + private fun place(x: Int, y: Int, tile: Tile) { + tiles[index(x, y)] = tile + } + + /** + * Returns the tile at the given coordinates. + * + * @throws IllegalArgumentException if the X coordinate is outside + * the range of [0, width), or if the Y coordinate is outside the + * range of [0, height). + */ + operator fun get(x: Int, y: Int) = if (valid(x, y)) { + tiles[index(x, y)] + } else { + throw IllegalArgumentException("Invalid coordinates (${x}, ${y})") + } + + /** + * Places a mine at the given coordinates. + * + * @throws IllegalArgumentException if the X coordinate is outside + * the range of [0, width), or if the Y coordinate is outside the + * range of [0, height). + */ + fun placeMine(x: Int, y: Int) { + if (!valid(x, y)) { + throw IllegalArgumentException("Invalid coordinates (${x}, ${y})") + } + + if (this[x, y].mine) { + return; + } + + place(x, y, Tile(mine = true)) + + val xMin = (x - 1).coerceAtLeast(0) + val xMax = (x + 1).coerceAtMost(width - 1) + val yMin = (y - 1).coerceAtLeast(0) + val yMax = (y + 1).coerceAtMost(height - 1) + + for (y in yMin..yMax) { + for (x in xMin..xMax) { + with (tiles[index(x, y)]) { + if (!mine) { + place(x, y, copy(adjacentMines + 1)) + } + } + } + } + } + + /** + * Reveals a tile at the given coordinates according to the game rules. + * + * @throws IllegalArgumentException if the X coordinate is outside + * the range of [0, width), or if the Y coordinate is outside the + * range of [0, height). + */ + fun reveal(x: Int, y: Int) { + if (!valid(x, y) || !this[x, y].masked) { + return; + } + + with (this[x, y]) { + place(x, y, copy(masked = false)) + + if (!mine && adjacentMines == 0) { + val xMin = (x - 1).coerceAtLeast(0) + val xMax = (x + 1).coerceAtMost(width - 1) + val yMin = (y - 1).coerceAtLeast(0) + val yMax = (y + 1).coerceAtMost(height - 1) + + for (y in yMin..yMax) { + for (x in xMin..xMax) { + reveal(x, y) + } + } + } + } + } + + override fun toString(): String = buildString { + for (y in 0 until height) { + for (x in 0 until width) { + val tile = this@Grid[x, y] + append(when { + tile.masked -> "." + tile.mine -> "M" + tile.adjacentMines == 0 -> " " + else -> tile.adjacentMines.toString() + } + " ") + } + append("\n") + } + } +} diff --git a/app/src/main/java/space/jakob/mines/SetupActivity.kt b/app/src/main/java/space/jakob/mines/SetupActivity.kt new file mode 100644 index 0000000..e78322e --- /dev/null +++ b/app/src/main/java/space/jakob/mines/SetupActivity.kt @@ -0,0 +1,18 @@ +package space.jakob.mines + +import android.support.v7.app.AppCompatActivity +import android.os.Bundle +import android.widget.Button +import android.content.Intent + +class SetupActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_setup) + + val goButton = findViewById(R.id.goButton) as Button + goButton.setOnClickListener { + startActivity(Intent(this, GameActivity::class.java)) + } + } +} diff --git a/app/src/main/res/drawable/atlas.png b/app/src/main/res/drawable/atlas.png Binary files differnew file mode 100644 index 0000000..b218df9 --- /dev/null +++ b/app/src/main/res/drawable/atlas.png diff --git a/app/src/main/res/layout/activity_game.xml b/app/src/main/res/layout/activity_game.xml new file mode 100644 index 0000000..794c96e --- /dev/null +++ b/app/src/main/res/layout/activity_game.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<space.jakob.mines.GameView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/view" + android:layout_width="fill_parent" + android:layout_height="fill_parent"/> diff --git a/app/src/main/res/layout/activity_setup.xml b/app/src/main/res/layout/activity_setup.xml new file mode 100644 index 0000000..5e3a4cc --- /dev/null +++ b/app/src/main/res/layout/activity_setup.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:context=".SetupActivity" android:orientation="vertical"> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" android:id="@+id/difficultyButtons"> + <Button + android:text="Easy" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:id="@+id/selectEasy" android:layout_weight="1"/> + <Button + android:text="Intermediate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:id="@+id/selectIntermediate" android:layout_weight="1"/> + <Button + android:text="Expert" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:id="@+id/selectExpert" android:layout_weight="1"/> + </LinearLayout> + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" android:id="@+id/widthRow"> + <TextView + android:text="Width:" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:id="@+id/widthLabel" android:layout_weight="1"/> + <EditText + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inputType="number" + android:ems="10" + android:id="@+id/widthEntry" android:layout_weight="1" android:text="8"/> + </LinearLayout> + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" android:id="@+id/heightRow"> + <TextView + android:text="Height:" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:id="@+id/heightLabel" android:layout_weight="1"/> + <EditText + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inputType="number" + android:ems="10" + android:id="@+id/heightEntry" android:layout_weight="1" android:text="8"/> + </LinearLayout> + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" android:id="@+id/minesRow"> + <TextView + android:text="Mines:" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:id="@+id/minesLabel" android:layout_weight="1"/> + <EditText + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inputType="number" + android:ems="10" + android:id="@+id/minesEntry" android:layout_weight="1" android:text="10"/> + </LinearLayout> + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" android:id="@+id/goRow" android:gravity="center"> + <Button + android:text="Let's Do It!" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:id="@+id/goButton" android:layout_weight="1" + android:maxWidth="64dp" android:width="64dp"/> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..310c838 --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..1a2ce8c --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..c515c23 --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..cf67e6d --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..4c5cee6 --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..69b2233 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="colorPrimary">#008577</color> + <color name="colorPrimaryDark">#00574B</color> + <color name="colorAccent">#D81B60</color> +</resources> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..77d7fb3 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ +<resources> + <string name="app_name">Mines</string> + <string name="title_activity_setup">Mines</string> +</resources> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + </style> + +</resources> diff --git a/app/src/test/java/space/jakob/mines/GridTest.kt b/app/src/test/java/space/jakob/mines/GridTest.kt new file mode 100644 index 0000000..94f6c7f --- /dev/null +++ b/app/src/test/java/space/jakob/mines/GridTest.kt @@ -0,0 +1,17 @@ +package space.jakob.mines + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class GridTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} |