MainActivity.kt
package edu.ius.c490.tictactoe

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.PersistableBundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.lifecycle.ViewModelProviders
import java.lang.IllegalArgumentException

private const val TAG = "TicTacToeMain"
private const val KEY_INDEX = "grid"

class MainActivity : AppCompatActivity() {

    private lateinit var topLeft: Button
    private lateinit var topCenter: Button
    private lateinit var topRight: Button

    private lateinit var centerLeft: Button
    private lateinit var centerCenter: Button
    private lateinit var centerRight: Button

    private lateinit var bottomLeft: Button
    private lateinit var bottomCenter: Button
    private lateinit var bottomRight: Button

    private lateinit var checkButton: Button
    private lateinit var resetButton: Button

    private val viewModel: TicTacToeViewModel by lazy {
        ViewModelProviders.of(this).get(TicTacToeViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val grid = savedInstanceState?.getSerializable(KEY_INDEX) as Grid?
        if (grid != null)
            viewModel.grid = grid

        topLeft = findViewById(R.id.top_left)
        topLeft.setOnClickListener { btn(0, 0, it) }
        topCenter = findViewById(R.id.top_center)
        topCenter.setOnClickListener { btn(1, 0, it) }
        topRight = findViewById(R.id.top_right)
        topRight.setOnClickListener { btn(2, 0, it) }

        centerLeft = findViewById(R.id.center_left)
        centerLeft.setOnClickListener { btn(0, 1, it) }
        centerCenter = findViewById(R.id.center_center)
        centerCenter.setOnClickListener { btn(1, 1, it) }
        centerRight = findViewById(R.id.center_right)
        centerRight.setOnClickListener { btn(2, 1, it) }

        bottomLeft = findViewById(R.id.bottom_left)
        bottomLeft.setOnClickListener { btn(0, 2, it) }
        bottomCenter = findViewById(R.id.bottom_center)
        bottomCenter.setOnClickListener { btn(1, 2, it) }
        bottomRight = findViewById(R.id.bottom_right)
        bottomRight.setOnClickListener { btn(2, 2, it) }

        checkButton = findViewById(R.id.check_button)
        checkButton.setOnClickListener {
            if (viewModel.winner == "draw")
                Toast.makeText(this@MainActivity, "It's a draw!", Toast.LENGTH_LONG).show()
            else
                Toast.makeText(this@MainActivity, "${viewModel.winner} won the game.", Toast.LENGTH_LONG).show()
        }

        resetButton = findViewById(R.id.reset_button)
        resetButton.setOnClickListener {
            viewModel.reset()
            update()
        }

        update()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putSerializable(KEY_INDEX, viewModel.grid)
    }

    private fun update() {
        topLeft.text = viewModel.get(0,0)
        topCenter.text = viewModel.get(1,0)
        topRight.text = viewModel.get(2,0)
        centerLeft.text   = viewModel.get(0, 1)
        centerCenter.text = viewModel.get(1, 1)
        centerRight.text  = viewModel.get(2, 1)
        bottomLeft.text   = viewModel.get(0, 2)
        bottomCenter.text = viewModel.get(1, 2)
        bottomRight.text  = viewModel.get(2, 2)
    }

    fun btn(x: Int, y: Int, view: View) {
        val b: Button = view as Button
        try {
            val currentPlayer = viewModel.currentPlayer
            viewModel.play(x, y)
            b.text = currentPlayer
        } catch (e: IllegalArgumentException) {
            Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show()
        }
    }
}
land/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center|center_horizontal"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="200dp"
            android:contentDescription="@string/image_description"
            android:layout_gravity="center"
            app:srcCompat="@drawable/tictactoe_banner" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:orientation="horizontal">

                    <Button
                        android:id="@+id/top_left"
                        style="@style/ButtonStyle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                    <Button
                        android:id="@+id/top_center"
                        style="@style/ButtonStyle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                    <Button
                        android:id="@+id/top_right"
                        style="@style/ButtonStyle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:orientation="horizontal">

                    <Button
                        android:id="@+id/center_left"
                        style="@style/ButtonStyle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                    <Button
                        android:id="@+id/center_center"
                        style="@style/ButtonStyle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                    <Button
                        android:id="@+id/center_right"
                        style="@style/ButtonStyle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:orientation="horizontal">

                    <Button
                        android:id="@+id/bottom_left"
                        style="@style/ButtonStyle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                    <Button
                        android:id="@+id/bottom_center"
                        style="@style/ButtonStyle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                    <Button
                        android:id="@+id/bottom_right"
                        style="@style/ButtonStyle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:orientation="horizontal">

                <Button
                    android:id="@+id/check_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/check_button" />

                <Button
                    android:id="@+id/reset_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/reset_button" />

            </LinearLayout>
            </LinearLayout>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Grid.kt
package edu.ius.c490.tictactoe

import android.util.Log
import java.lang.IllegalArgumentException
import kotlinx.serialization.*

val EMPTY = " "

@Serializable
data class Grid(val player1: String, val player2: String, val currentPlayer: String, val grid: Array<Array<String>>) :
    java.io.Serializable {
    val empty = EMPTY

    companion object {
        fun emptyGrid(): Array<Array<String>> {
            return Array(3) {
                Array(3) {
                    EMPTY
                }
            }
        }
    }

    override fun equals(other: Any?): Boolean {
        val o = other as Grid
        if (player1 != o.player1 || player2 != o.player2 || currentPlayer != o.currentPlayer)
            return false

        for (x in 0..2) {
            for (y in 0..2) {
                if (grid[x][y] != other.grid[x][y]) {
                    return false
                }
            }
        }
        return true
    }

    fun play(x: Int, y: Int): Grid {
        if (grid[x][y] != empty)
            throw IllegalArgumentException("Space already taken")

        grid[x][y] = currentPlayer
        val nextPlayer = when (currentPlayer) {
            player1 -> player2
            else -> player1
        }

        Log.v("play", "Played at $x, $y. Setting next player to $nextPlayer.")

        return Grid(player1, player2, nextPlayer, grid)
    }

    fun checkWinner(): String {
        // Check columns
        for (y in 0..2) {
            if (grid[0][y] == grid[1][y] && grid[0][y] == grid[2][y] && grid[0][y] != empty)
                return grid[0][y]
        }

        // Check rows
        for (x in 0..2) {
            if (grid[x][0] == grid[x][1] && grid[x][0] == grid[x][2] && grid[x][0] != empty)
                return grid[x][0]
        }

        // Check diagonals
        if (grid[0][0] == grid[1][1] && grid[1][1] == grid[2][2] && grid[1][1] != empty)
            return grid[1][1]

        if (grid[2][0] == grid[1][1] && grid[1][1] == grid[0][2] && grid[1][1] != empty)
            return grid[1][1]

        return "draw"

    }
}
TicTacToeViewModel.kt
package edu.ius.c490.tictactoe

import android.util.Log
import androidx.lifecycle.ViewModel

private const val TAG = "TicTacToeViewModel"

class TicTacToeViewModel : ViewModel() {

    private lateinit var _grid: Grid

    var grid: Grid
        get() = _grid
        set(value) { _grid = value }

    val empty get() = grid.empty
    val currentPlayer: String
        get() = grid.currentPlayer
    val winner get() = grid.checkWinner()

    fun reset() {
        Log.d(TAG, "viewModel.reset()")
        grid = Grid("X", "O", "X", Grid.emptyGrid())
    }

    fun get(x: Int, y: Int): String {
        return grid.grid[x][y]
    }

    fun play(x: Int, y: Int) {
        grid = grid.play(x, y)
    }

    init {
        Log.d(TAG, "ViewModel instance created")
        reset()
    }

    override fun onCleared() {
        super.onCleared()
        Log.d(TAG, "ViewModel instance is being destroyed")
    }
}
TicTacToe build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext.kotlin_version = '1.3.50'
    repositories {
        google()
        jcenter()

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()

    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
app build.gradle
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlinx-serialization'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "edu.ius.c490.tictactoe"
        minSdkVersion 24
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.12.0" // JVM dependency
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.core:core-ktx:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}