Today, a brief look at organizing code into the MVC framework.

Model

A layer that handles data storage and business logic. This should be a separate class from UI concerns and should not be involved in any direct UI manipulation. You should be able to unit test this logic.

View

A layer that handles presentation. In android, this is represented by the XML and image resources.

Controller

The layer that glues the data/logic to the view. This layer is the Activity in our project and handles things like connecting buttons to actions, updating text in the view, and so on.

TicTacToe

Grid.kt
package edu.ius.c490.tictactoe

import android.util.Log

data class Grid(val player1: String, val player2: String) {
    val empty = " "
    var grid: Array<Array<String>> = Array(3) { i ->
        Array(3) { j ->
            empty
        }
    }
    var player = player1

    fun reset() {
        for (y in 0..2)
            for (x in 0..2)
                grid[y][x] = empty
        player = player1
    }

    fun checkWinner(): String {
        // check columns
        for (y in 0..2) {
            if (grid[y][0] == grid[y][1] && grid[y][0] == grid[y][2])
                return grid[y][0] as String
        }
        // check rows
        for (x in 0..2) {
            if (grid[0][x] == grid[1][x] && grid[0][x] == grid[2][x])
                return grid[0][x] as String
        }
        // check diagonals
        if (grid[0][0] == grid[1][1] && grid[0][0] == grid[2][2])
            return grid[0][0] as String
        if (grid[0][2] == grid[1][1] && grid[0][2] == grid[2][0])
            return grid[0][0] as String
        return "draw"
    }

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

        if (player == player1)
            player = player2
        else
            player = player1

        Log.v("play", "Changing player to '$player'")

        return grid[y][x]
    }
}
MainActivity.kt
package edu.ius.c490.tictactoe

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.Toast

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 check: Button

    private lateinit var grid: Grid

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

        grid = Grid("X", "O")

        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) }

        check = findViewById(R.id.check_win)
        check.setOnClickListener {
            var winner = grid.checkWinner()
            if (winner == "draw")
                Toast.makeText(this@MainActivity, "It's a draw.", Toast.LENGTH_LONG).show()
            else
                Toast.makeText(this@MainActivity, "$winner won the game", Toast.LENGTH_LONG).show()
            reset()
        }
    }

    fun reset() {
        grid.reset()
        topLeft.text = grid.empty
        topCenter.text = grid.empty
        topRight.text = grid.empty
        centerLeft.text = grid.empty
        centerCenter.text = grid.empty
        centerRight.text = grid.empty
        bottomLeft.text = grid.empty
        bottomCenter.text = grid.empty
        bottomRight.text = grid.empty
    }

    fun btn(x: Int, y: Int, view: View) {
        val b: Button = view as Button
        try {
            val p = grid.play(x,y)
            b.setText(p)
        } catch (e: IllegalArgumentException) {
            Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show()
        }
    }
}
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:orientation="vertical"
        android:gravity="center">

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

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            >
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            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="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:gravity="center">
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/ButtonStyle"
                android:id="@+id/center_left" />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/ButtonStyle"
                android:id="@+id/center_center" />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/ButtonStyle"
                android:id="@+id/center_right" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:gravity="center">
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/ButtonStyle"
                android:id="@+id/bottom_left" />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/ButtonStyle"
                android:id="@+id/bottom_center" />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/ButtonStyle"
                android:id="@+id/bottom_right" />
        </LinearLayout>
            <Button
                android:layout_gravity="center"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                style="@style/ButtonStyle"
                android:text="@string/check_win"
                android:id="@+id/check_win" />
    </LinearLayout>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
strings.xml
<resources>
    <string name="app_name">TicTacToe</string>
    <string name="image_description">TicTacToe Banner</string>
    <string name="check_win">Check</string>
</resources>