├── .gitignore ├── README.md ├── android ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── de │ │ └── mannodermaus │ │ └── example │ │ └── android │ │ └── test │ │ └── JUnit4InstrumentationTest.java │ ├── androidTestExperimental │ └── java │ │ └── de │ │ └── mannodermaus │ │ └── example │ │ └── android │ │ └── test │ │ ├── EspressoTests.kt │ │ └── JUnitJupiterInstrumentationTests.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── de │ │ │ └── mannodermaus │ │ │ └── example │ │ │ └── android │ │ │ ├── BlackjackScreen.kt │ │ │ └── PlayerEntryScreen.kt │ └── res │ │ ├── layout │ │ ├── activity_blackjack.xml │ │ └── activity_player_entry.xml │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── de │ └── mannodermaus │ └── example │ └── android │ └── test │ └── AndroidJupiterTests.kt ├── build.gradle ├── cli ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── de │ │ └── mannodermaus │ │ └── example │ │ └── cli │ │ └── CliApplication.kt │ └── test │ └── kotlin │ └── de │ └── mannodermaus │ └── example │ └── cli │ └── test │ ├── coexistence │ ├── JUnit4Tests.kt │ └── JUnitJupiterTests.kt │ └── runner │ └── JUnitPlatformRunnerTests.kt ├── common ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── de │ │ └── mannodermaus │ │ └── example │ │ └── common │ │ ├── Constants.kt │ │ ├── Exceptions.kt │ │ ├── Extensions.kt │ │ ├── GameLogic.kt │ │ └── Models.kt │ └── test │ ├── java │ └── de │ │ └── mannodermaus │ │ └── example │ │ └── common │ │ └── test │ │ ├── assertions │ │ ├── AssertAllTests.kt │ │ ├── AssertSameTests.kt │ │ └── AssertThrowsTests.kt │ │ ├── assumptions │ │ ├── AssumeFalseTests.kt │ │ ├── AssumeTrueTests.kt │ │ └── AssumingThatTests.kt │ │ ├── composition │ │ ├── interfaces │ │ │ ├── ComparableContract.kt │ │ │ ├── ComparableRankTests.kt │ │ │ ├── ComparableSuitTests.kt │ │ │ ├── ParseableCardTests.kt │ │ │ ├── ParseableFromStringContract.kt │ │ │ ├── ParseableRankTests.kt │ │ │ ├── ParseableSuitTests.kt │ │ │ └── ParseableToStringContract.kt │ │ └── lifecycle │ │ │ ├── CountHeartsTests.kt │ │ │ ├── DrawEntireDeck.kt │ │ │ └── DuplicateCheckerTests.kt │ │ ├── conditional │ │ ├── EnvironmentVariableTests.kt │ │ ├── JreTests.kt │ │ ├── OsTests.kt │ │ ├── ScriptTests.kt │ │ └── SystemPropertyTests.kt │ │ ├── disabled │ │ └── DisabledTests.kt │ │ ├── displayname │ │ └── DisplayNameTests.kt │ │ ├── dynamictest │ │ └── DynamicTests.kt │ │ ├── lifecycle │ │ ├── AfterAllTests.java │ │ ├── AfterEachTests.kt │ │ ├── BeforeAllTests.java │ │ ├── BeforeEachTests.kt │ │ ├── TestInstancePerClassTests.kt │ │ └── TestInstancePerMethodTests.kt │ │ ├── nested │ │ └── NestedTests.kt │ │ ├── parallel │ │ ├── CustomParallelExecutionStrategy.kt │ │ ├── ParallelTests.kt │ │ └── ResourceLockTests.kt │ │ ├── parameterizedtest │ │ ├── ParameterizedArgumentsSourceTests.kt │ │ ├── ParameterizedCsvFileSourceTests.kt │ │ ├── ParameterizedCsvSourceTests.kt │ │ ├── ParameterizedEnumSourceTests.kt │ │ ├── ParameterizedMethodSourceTests.java │ │ ├── ParameterizedTypeConverterTests.kt │ │ └── ParameterizedValueSourceTests.kt │ │ ├── repeatedtest │ │ └── RepeatedTests.kt │ │ ├── tags │ │ └── TagTests.java │ │ └── test │ │ └── SimpleTests.kt │ └── resources │ └── data │ ├── csv_file_data.csv │ └── dynamic_test_data.txt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/java,gradle 3 | 4 | ### Java ### 5 | # Compiled class file 6 | *.class 7 | 8 | # Log file 9 | *.log 10 | 11 | # BlueJ files 12 | *.ctxt 13 | 14 | # Mobile Tools for Java (J2ME) 15 | .mtj.tmp/ 16 | 17 | # Package Files # 18 | *.jar 19 | *.war 20 | *.nar 21 | *.ear 22 | *.zip 23 | *.tar.gz 24 | *.rar 25 | 26 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 27 | hs_err_pid* 28 | 29 | ### Gradle ### 30 | .gradle 31 | build/ 32 | 33 | # Ignore Gradle GUI config 34 | gradle-app.setting 35 | 36 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 37 | !gradle-wrapper.jar 38 | 39 | # Cache of project 40 | .gradletasknamecache 41 | 42 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 43 | # gradle/wrapper/gradle-wrapper.properties 44 | 45 | 46 | # End of https://www.gitignore.io/api/java,gradle 47 | 48 | # IntelliJ 49 | .idea/ 50 | out/ 51 | local.properties 52 | *.iml 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Caster.io logo](https://caster.io/assets/cio_logo-369287ed535e10c1e2291304f48ed0ccb9ed4d29ab2d0c6ba82634789f7d4c38.png) 2 | # Caster.IO: Introduction to JUnit 5 3 | 4 | These examples correlate with the course material for "Introduction to JUnit 5" on [Caster.IO](https://caster.io). 5 | For each applicable lesson, there is a corresponding Git branch inside this repository. 6 | 7 | ## Installation 8 | 9 | Clone the repository, check out a branch of your choice & open the project with IntelliJ IDEA or Android Studio. 10 | 11 | ## Multiplatform 12 | 13 | There are three modules inside this repository, which make up the sample project under test: 14 | 15 | * `android`: Android application for Blackjack 16 | * `cli`: Console application for Blackjack 17 | * `common`: Business logic & common code shared by both application modules 18 | 19 | Because IntelliJ IDEA doesn't like Android and pure-Java modules living side by side in the same project, 20 | the `android` module is disabled by default, and the Console application is executable. 21 | If you want to see the Android application in action, or run its tests, 22 | please open the `settings.gradle` file and change the value of the `USE_ANDROID` flag. 23 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "kotlin-android" 3 | apply plugin: "kotlin-android-extensions" 4 | apply plugin: "de.mannodermaus.android-junit5" 5 | 6 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 7 | kotlinOptions.jvmTarget = "1.8" 8 | } 9 | 10 | android { 11 | compileSdkVersion 28 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | defaultConfig { 19 | applicationId "de.mannodermaus.example.android" 20 | minSdkVersion 19 21 | targetSdkVersion 28 22 | versionCode 1 23 | versionName "1.0" 24 | 25 | // Make sure to use the AndroidJUnitRunner (or a sub-class) in order to hook in the JUnit 5 Test Builder 26 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 27 | testInstrumentationRunnerArgument "runnerBuilder", "de.mannodermaus.junit5.AndroidJUnit5Builder" 28 | } 29 | 30 | // Since the minSdkVersion requirement for JUnit 5 Instrumentation Tests is quite high, 31 | // we introduce a product flavor that uses an elevated version other than the application's default. 32 | // With this, we are able to try JUnit 5 tests without sacrificing the minSdkVersion completely. 33 | flavorDimensions "kind" 34 | productFlavors { 35 | experimental { 36 | dimension "kind" 37 | minSdkVersion 26 38 | } 39 | 40 | normal { 41 | dimension "kind" 42 | } 43 | } 44 | 45 | sourceSets { 46 | main.java.srcDirs += "src/main/kotlin" 47 | } 48 | 49 | testOptions { 50 | junitPlatform { 51 | // Configure JUnit 5 tests here 52 | } 53 | unitTests.all { 54 | testLogging { 55 | events "skipped", "passed", "failed" 56 | } 57 | } 58 | } 59 | 60 | packagingOptions { 61 | exclude "META-INF/LICENSE.md" 62 | exclude "META-INF/LICENSE-notice.md" 63 | } 64 | } 65 | 66 | dependencies { 67 | implementation project(":common") 68 | implementation "com.android.support:appcompat-v7:28.0.0-beta01" 69 | implementation "com.android.support.constraint:constraint-layout:1.1.2" 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 71 | 72 | testImplementation "org.junit.jupiter:junit-jupiter-api:5.2.0" 73 | testImplementation "org.junit.jupiter:junit-jupiter-params:5.2.0" 74 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.2.0" 75 | 76 | androidTestImplementation "com.android.support.test:runner:1.0.2" 77 | androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.2" 78 | androidTestImplementation "com.android.support.test.espresso:espresso-intents:3.0.2" 79 | androidTestRuntimeOnly "de.mannodermaus.junit5:android-instrumentation-test-runner:0.2.2" 80 | 81 | // Add the Android Instrumentation Test dependencies to the product flavor only 82 | // (with this, only the "experimental" flavor must have minSdkVersion 26) 83 | androidTestExperimentalImplementation "org.junit.jupiter:junit-jupiter-api:$junit_jupiter_version" 84 | androidTestExperimentalImplementation "de.mannodermaus.junit5:android-instrumentation-test:0.2.2" 85 | 86 | // Runtime dependencies for Android Instrumentation Tests 87 | androidTestExperimentalRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_jupiter_version" 88 | androidTestExperimentalRuntimeOnly "org.junit.platform:junit-platform-runner:$junit_platform_version" 89 | } 90 | -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /android/src/androidTest/java/de/mannodermaus/example/android/test/JUnit4InstrumentationTest.java: -------------------------------------------------------------------------------- 1 | package de.mannodermaus.example.android.test; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | // An instrumentation test like we know it, running with JUnit 4. 13 | // Check out the androidTestExperimental source set for the JUnit Jupiter example. 14 | @RunWith(AndroidJUnit4.class) 15 | public class JUnit4InstrumentationTest { 16 | @Test 17 | public void useAppContext() { 18 | Context appContext = InstrumentationRegistry.getTargetContext(); 19 | assertEquals("de.mannodermaus.example.android", appContext.getPackageName()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/src/androidTestExperimental/java/de/mannodermaus/example/android/test/EspressoTests.kt: -------------------------------------------------------------------------------- 1 | package de.mannodermaus.example.android.test 2 | 3 | import android.content.Intent.FLAG_ACTIVITY_NEW_TASK 4 | import android.support.test.espresso.Espresso 5 | import android.support.test.espresso.Espresso.closeSoftKeyboard 6 | import android.support.test.espresso.Espresso.onView 7 | import android.support.test.espresso.action.ViewActions.click 8 | import android.support.test.espresso.action.ViewActions.typeText 9 | import android.support.test.espresso.assertion.ViewAssertions.matches 10 | import android.support.test.espresso.intent.Intents 11 | import android.support.test.espresso.intent.Intents.intended 12 | import android.support.test.espresso.intent.matcher.IntentMatchers 13 | import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent 14 | import android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra 15 | import android.support.test.espresso.matcher.ViewMatchers.isDisplayed 16 | import android.support.test.espresso.matcher.ViewMatchers.withId 17 | import de.mannodermaus.example.android.BlackjackActivity 18 | import de.mannodermaus.example.android.PlayerEntryActivity 19 | import de.mannodermaus.example.android.R 20 | import de.mannodermaus.junit5.ActivityTest 21 | import de.mannodermaus.junit5.Tested 22 | import org.hamcrest.Matchers.allOf 23 | import org.junit.jupiter.api.AfterEach 24 | import org.junit.jupiter.api.BeforeEach 25 | import org.junit.jupiter.api.Test 26 | 27 | @ActivityTest( 28 | value = PlayerEntryActivity::class, 29 | launchFlags = FLAG_ACTIVITY_NEW_TASK, 30 | launchActivity = false) 31 | class EspressoTests { 32 | 33 | @BeforeEach 34 | fun beforeEach() { 35 | Intents.init() 36 | } 37 | 38 | @AfterEach 39 | fun afterEach() { 40 | Intents.release() 41 | } 42 | 43 | @Test 44 | fun uiTestWithJUnitJupiter(tested: Tested) { 45 | tested.launchActivity() 46 | 47 | onView(withId(R.id.etPlayer1)).check(matches(isDisplayed())) 48 | 49 | tested.finishActivity() 50 | } 51 | 52 | @Test 53 | fun ifNoNamesAreEnteredThenDefaultIsChosen(tested: Tested) { 54 | tested.launchActivity() 55 | 56 | onView(withId(R.id.buttonPlay)).perform(click()) 57 | 58 | intended( 59 | allOf( 60 | hasComponent(BlackjackActivity::class.java.name), 61 | hasExtra(BlackjackActivity.EXTRA_PLAYER1_NAME, "Player 1"), 62 | hasExtra(BlackjackActivity.EXTRA_PLAYER2_NAME, "Player 2") 63 | ) 64 | ) 65 | } 66 | 67 | @Test 68 | fun enteredNamesAreGivenToTheNextScreen(tested: Tested) { 69 | tested.launchActivity() 70 | 71 | onView(withId(R.id.etPlayer1)).perform(typeText("Taro")) 72 | onView(withId(R.id.etPlayer2)).perform(typeText("Hoge")) 73 | closeSoftKeyboard() 74 | onView(withId(R.id.buttonPlay)).perform(click()) 75 | 76 | intended( 77 | allOf( 78 | hasComponent(BlackjackActivity::class.java.name), 79 | hasExtra(BlackjackActivity.EXTRA_PLAYER1_NAME, "Taro"), 80 | hasExtra(BlackjackActivity.EXTRA_PLAYER2_NAME, "Hoge") 81 | ) 82 | ) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /android/src/androidTestExperimental/java/de/mannodermaus/example/android/test/JUnitJupiterInstrumentationTests.kt: -------------------------------------------------------------------------------- 1 | package de.mannodermaus.example.android.test 2 | 3 | import org.junit.jupiter.api.Assertions 4 | import org.junit.jupiter.api.Disabled 5 | import org.junit.jupiter.api.Test 6 | import org.junit.jupiter.api.fail 7 | 8 | class JUnitJupiterInstrumentationTests { 9 | 10 | @Test 11 | fun testRunningWithTheJUnitPlatform() { 12 | Assertions.assertTrue(2 + 2 == 4) 13 | } 14 | 15 | @Disabled 16 | @Test 17 | fun anotherDisabledJUnitJupiterTest() { 18 | fail { "You should've never removed that annotation." } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /android/src/main/java/de/mannodermaus/example/android/BlackjackScreen.kt: -------------------------------------------------------------------------------- 1 | package de.mannodermaus.example.android 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.support.v4.app.Fragment 6 | import android.support.v7.app.AlertDialog 7 | import android.support.v7.app.AppCompatActivity 8 | import android.view.KeyEvent 9 | import android.view.View 10 | import android.widget.Toast 11 | import de.mannodermaus.example.android.BlackjackActivity.Companion.EXTRA_PLAYER1_NAME 12 | import de.mannodermaus.example.android.BlackjackActivity.Companion.EXTRA_PLAYER2_NAME 13 | import de.mannodermaus.example.common.* 14 | import kotlinx.android.synthetic.main.activity_blackjack.* 15 | 16 | /** 17 | * GUI implementation of a simple Blackjack game. 18 | * Uses a retained fragment for configuration changes 19 | */ 20 | class BlackjackActivity : AppCompatActivity(), BlackjackCallbacks { 21 | 22 | companion object { 23 | const val EXTRA_PLAYER1_NAME = "player1-name" 24 | const val EXTRA_PLAYER2_NAME = "player2-name" 25 | 26 | private const val FRAGMENT_TAG = "data-fragment" 27 | } 28 | 29 | /* State */ 30 | 31 | private lateinit var gameHandler: BlackjackFragment 32 | 33 | override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | setContentView(R.layout.activity_blackjack) 36 | 37 | // Restore state, if necessary 38 | val player1 = intent.getStringExtra(EXTRA_PLAYER1_NAME) 39 | val player2 = intent.getStringExtra(EXTRA_PLAYER2_NAME) 40 | 41 | val fragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as BlackjackFragment? 42 | if (fragment == null) { 43 | val newFragment = BlackjackFragment() 44 | newFragment.arguments = Bundle().apply { 45 | putString(EXTRA_PLAYER1_NAME, player1) 46 | putString(EXTRA_PLAYER2_NAME, player2) 47 | } 48 | supportFragmentManager.beginTransaction() 49 | .add(newFragment, FRAGMENT_TAG) 50 | .commit() 51 | this.gameHandler = newFragment 52 | } else { 53 | this.gameHandler = fragment 54 | } 55 | } 56 | 57 | fun onHitButtonClicked(unused: View) { 58 | gameHandler.drawAnotherCard() 59 | } 60 | 61 | fun onStandButtonClicked(unused: View) { 62 | gameHandler.completeCurrentSession() 63 | } 64 | 65 | override fun onKeyDown(keyCode: Int, event: KeyEvent?) = 66 | when (keyCode) { 67 | KeyEvent.KEYCODE_BACK -> { 68 | AlertDialog.Builder(this) 69 | .setTitle(R.string.dialogConfirmCancel) 70 | .setMessage(R.string.dialogConfirmCancelMessage) 71 | .setPositiveButton(R.string.buttonBackToMainMenu) { _, _ -> finish() } 72 | .setNegativeButton(R.string.buttonCancel) { dialog, _ -> dialog.dismiss() } 73 | .setCancelable(false) 74 | .show() 75 | false 76 | } 77 | else -> super.onKeyDown(keyCode, event) 78 | } 79 | 80 | /* BlackjackCallbacks */ 81 | 82 | override fun onSessionStarted(player: Player) { 83 | AlertDialog.Builder(this) 84 | .setTitle(getString(R.string.dialogGameStarted, player)) 85 | .setMessage(getString(R.string.dialogGameStartedMessage)) 86 | .setCancelable(false) 87 | .setPositiveButton(R.string.buttonStart) { _, _ -> gameHandler.drawAnotherCard() } 88 | .show() 89 | } 90 | 91 | override fun onSessionUpdated(player: Player, cards: List, score: Int, turnNumber: Int?) { 92 | supportActionBar?.title = if (turnNumber != null) 93 | getString(R.string.headerPlayerWithTurn, player, turnNumber) 94 | else 95 | player 96 | tvHand.text = cards.toString() 97 | tvSum.text = score.toString() 98 | } 99 | 100 | override fun onSessionCardDrawn(newCard: Card) { 101 | Toast.makeText(this, getString(R.string.toastCardDraw, newCard), Toast.LENGTH_SHORT).show() 102 | } 103 | 104 | override fun onSessionBusted(player: Player, score: Int) { 105 | AlertDialog.Builder(this) 106 | .setTitle(R.string.dialogGameLost) 107 | .setMessage(getString(R.string.dialogGameLostMessage, score)) 108 | .setCancelable(false) 109 | .setPositiveButton(R.string.buttonOk) { _, _ -> 110 | gameHandler.completeCurrentSession() 111 | } 112 | .show() 113 | } 114 | 115 | override fun onSessionHitTarget(player: Player) { 116 | AlertDialog.Builder(this) 117 | .setTitle(R.string.dialogGameExactlyBlackjack) 118 | .setMessage(getString(R.string.dialogGameExactlyBlackjackMessage, TARGET_SUM)) 119 | .setCancelable(false) 120 | .setPositiveButton(R.string.buttonOk) { _, _ -> 121 | gameHandler.completeCurrentSession() 122 | } 123 | .show() 124 | } 125 | 126 | override fun onSessionCompleted() { 127 | } 128 | 129 | override fun onGameCompleted(gameResult: GameResult) { 130 | val winner = when (gameResult) { 131 | is GameResult.OneWinner -> gameResult.player 132 | is GameResult.Draw -> getString(R.string.dialogGameResultDraw) 133 | } 134 | AlertDialog.Builder(this) 135 | .setTitle(R.string.dialogGameCompleted) 136 | .setMessage(getString(R.string.dialogGameResult, winner)) 137 | .setCancelable(false) 138 | .setPositiveButton(R.string.buttonBackToMainMenu) { _, _ -> 139 | finish() 140 | } 141 | .show() 142 | } 143 | } 144 | 145 | interface BlackjackCallbacks { 146 | fun onSessionStarted(player: Player) 147 | fun onSessionUpdated(player: Player, cards: List, score: Int, turnNumber: Int?) 148 | fun onSessionCardDrawn(newCard: Card) 149 | fun onSessionBusted(player: Player, score: Int) 150 | fun onSessionHitTarget(player: Player) 151 | fun onSessionCompleted() 152 | fun onGameCompleted(gameResult: GameResult) 153 | } 154 | 155 | interface BlackjackFunctions { 156 | fun completeCurrentSession() 157 | fun drawAnotherCard() 158 | } 159 | 160 | class BlackjackFragment : Fragment(), BlackjackFunctions { 161 | 162 | private lateinit var callbacks: BlackjackCallbacks 163 | private lateinit var player1: Player 164 | private lateinit var player2: Player 165 | private lateinit var game: Blackjack 166 | 167 | private var currentPhase: Int = 1 168 | 169 | private var _currentSession: Session? = null 170 | 171 | override fun onAttach(context: Context) { 172 | super.onAttach(context) 173 | 174 | if (context is BlackjackCallbacks) { 175 | this.callbacks = context 176 | } 177 | } 178 | 179 | override fun onCreate(savedInstanceState: Bundle?) { 180 | super.onCreate(savedInstanceState) 181 | this.retainInstance = true 182 | 183 | this.player1 = arguments?.getString(EXTRA_PLAYER1_NAME) ?: throw IllegalArgumentException() 184 | this.player2 = arguments?.getString(EXTRA_PLAYER2_NAME) ?: throw IllegalArgumentException() 185 | 186 | // Start with the initial state 187 | startNewGame() 188 | nextPhase() 189 | } 190 | 191 | override fun onResume() { 192 | super.onResume() 193 | 194 | _currentSession?.let { 195 | callbacks.onSessionUpdated(it.player, it.currentCards, it.score, it.turn.orNullIfBelow(0)) 196 | } 197 | } 198 | 199 | private fun startNewGame() { 200 | val deck = Deck(ALL_CARDS) 201 | deck.shuffle() 202 | this.game = Blackjack(deck) 203 | this.currentPhase = 1 204 | } 205 | 206 | private fun nextPhase() { 207 | when (currentPhase) { 208 | 1 -> startNewSessionFor(player1) 209 | 2 -> startNewSessionFor(player2) 210 | 3 -> completeGame() 211 | else -> throw IllegalStateException() 212 | } 213 | currentPhase++ 214 | } 215 | 216 | private fun startNewSessionFor(player: Player) { 217 | val newSession = game.newSession(player) 218 | this._currentSession = newSession 219 | callbacks.onSessionStarted(player) 220 | } 221 | 222 | private fun completeGame() { 223 | val result = game.complete() 224 | callbacks.onGameCompleted(result) 225 | startNewGame() 226 | } 227 | 228 | override fun completeCurrentSession() { 229 | // Iterate to the next state 230 | this._currentSession = null 231 | nextPhase() 232 | } 233 | 234 | override fun drawAnotherCard() { 235 | val session = _currentSession ?: throw IllegalStateException() 236 | val turnResult = session.makeTurn() 237 | callbacks.onSessionUpdated(session.player, session.currentCards, session.score, session.turn.orNullIfBelow(0)) 238 | callbacks.onSessionCardDrawn(turnResult.newCard) 239 | 240 | when (turnResult) { 241 | is TurnResult.TargetHit -> callbacks.onSessionHitTarget(session.player) 242 | is TurnResult.Busted -> callbacks.onSessionBusted(session.player, turnResult.sum) 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /android/src/main/java/de/mannodermaus/example/android/PlayerEntryScreen.kt: -------------------------------------------------------------------------------- 1 | package de.mannodermaus.example.android 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.support.v7.app.AppCompatActivity 6 | import android.view.View 7 | import kotlinx.android.synthetic.main.activity_player_entry.* 8 | 9 | class PlayerEntryActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_player_entry) 14 | supportActionBar?.title = getString(R.string.app_name) 15 | } 16 | 17 | fun onPlayButtonClicked(ignored: View) { 18 | startActivity( 19 | Intent(this, BlackjackActivity::class.java).apply { 20 | val player1Name = if (etPlayer1.text.isNotEmpty()) 21 | etPlayer1.text.toString() 22 | else 23 | getString(R.string.defaultNamePlayer1) 24 | val player2Name = if (etPlayer2.text.isNotEmpty()) 25 | etPlayer2.text.toString() 26 | else 27 | getString(R.string.defaultNamePlayer2) 28 | putExtra(BlackjackActivity.EXTRA_PLAYER1_NAME, player1Name) 29 | putExtra(BlackjackActivity.EXTRA_PLAYER2_NAME, player2Name) 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /android/src/main/res/layout/activity_blackjack.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 33 | 34 |