티스토리 뷰

Android/Kotlin

[Kotlin]  Tic-tac-toe 게임으로 보는 MVVM 패턴

혀가 길지 않은 개발자 2020. 9. 19. 16:08

MVVM (Model  +  View  +  ViewModel)


build.gradle (Module: app)

apply plugin: 'kotlin-kapt'     // BindingAdapter 사용하기 위함

android {
    dataBinding {
        enabled = true
    }

    kotlinOptions {     // by viewModels() 사용하기 위함
        jvmTarget = "1.8"
    }
}

dependencies {
    // by viewModels() 사용하기 위함
    implementation 'androidx.fragment:fragment-ktx:1.2.5'
}

 

 

 

 

 

 

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="mainViewModel"
            type="com.jwsoft.kotlinproject.viewmodel.MainViewModel" />
    </data>

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="30dp"
        tools:context=".view.MainActivity">

        <GridLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:rowCount="3"
            android:columnCount="3">

            <Button
                android:tag="00"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:onClick="@{() -> mainViewModel.onClickButton(0, 0)}"
                android:text="@{mainViewModel.cells[0][0]}"
                android:textSize="30sp"/>

            <Button
                android:tag="01"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:onClick="@{() -> mainViewModel.onClickButton(0, 1)}"
                android:text="@{mainViewModel.cells[0][1]}"
                android:textSize="30sp"/>

            <Button
                android:tag="02"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:onClick="@{() -> mainViewModel.onClickButton(0, 2)}"
                android:text="@{mainViewModel.cells[0][2]}"
                android:textSize="30sp"/>

            <Button
                android:tag="10"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:onClick="@{() -> mainViewModel.onClickButton(1, 0)}"
                android:text="@{mainViewModel.cells[1][0]}"
                android:textSize="30sp"/>

            <Button
                android:tag="11"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:onClick="@{() -> mainViewModel.onClickButton(1, 1)}"
                android:text="@{mainViewModel.cells[1][1]}"
                android:textSize="30sp"/>

            <Button
                android:tag="12"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:onClick="@{() -> mainViewModel.onClickButton(1, 2)}"
                android:text="@{mainViewModel.cells[1][2]}"
                android:textSize="30sp"/>

            <Button
                android:tag="20"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:onClick="@{() -> mainViewModel.onClickButton(2, 0)}"
                android:text="@{mainViewModel.cells[2][0]}"
                android:textSize="30sp"/>

            <Button
                android:tag="21"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:onClick="@{() -> mainViewModel.onClickButton(2, 1)}"
                android:text="@{mainViewModel.cells[2][1]}"
                android:textSize="30sp"/>

            <Button
                android:tag="22"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:onClick="@{() -> mainViewModel.onClickButton(2, 2)}"
                android:text="@{mainViewModel.cells[2][2]}"
                android:textSize="30sp"/>

        </GridLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical"
            android:JamesVisible="@{mainViewModel.isShowWinner}"
            tools:visibility="visible">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="40sp"
                android:layout_margin="20dp"
                android:text="@{mainViewModel.winner}"
                tools:text="X"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Winner"
                android:textSize="40sp"/>

        </LinearLayout>

        <Button
            android:layout_width="200dp"
            android:layout_height="70dp"
            android:layout_marginTop="40dp"
            android:textAllCaps="false"
            android:text="Reset"
            android:textSize="20dp"
            android:onClick="@{() -> mainViewModel.resetAll()}"/>

    </LinearLayout>

</layout>

activity_main.xml

 

 

 

 

 

 

binding/BindingAdapters.kt

package com.jwsoft.kotlinproject.binding

import android.view.View
import android.widget.LinearLayout
import androidx.databinding.BindingAdapter

@BindingAdapter("android:JamesVisible")
fun setVisible(view: View, bool: Boolean) {
    (view as LinearLayout).visibility = if (bool) View.VISIBLE else View.GONE
}

 

 

 

 

 

 

 

 

model/Player.kt

package com.jwsoft.kotlinproject.model

enum class Player {
    O,
    X,
    NULL
}

 

 

 

 

model/Cell.kt

package com.jwsoft.kotlinproject.model

data class Cell (
    var player: Player = Player.NULL
)

 

 

 

 

model/Board.kt

package com.jwsoft.kotlinproject.model


class Board {

    var currentPlayer: Player = Player.X
    var cells: Array<Array<Cell>> = Array(3) {
        Array(3) {
            Cell()
        }
    }


    fun mark(row: Int, col: Int): Player {
        if (isValid(row, col)) {
            cells[row][col].player = currentPlayer
            return currentPlayer
        } else {
            return Player.NULL
        }
    }

    fun isValid(row: Int, col: Int): Boolean {
        return cells[row][col].player == Player.NULL
    }

    fun flipPlayer() {
        if (currentPlayer == Player.X) currentPlayer = Player.O
        else currentPlayer = Player.X
    }

    fun getWinner(row: Int, col: Int, player: Player): Player {
        if (cells[row][0].player == player &&
            cells[row][1].player == player &&
            cells[row][2].player == player ||
            cells[0][col].player == player &&
            cells[1][col].player == player &&
            cells[2][col].player == player ||
            row == col &&
            cells[0][0].player == player &&
            cells[1][1].player == player &&
            cells[2][2].player == player ||
            row + col == 2 &&
            cells[0][2].player == player &&
            cells[1][1].player == player &&
            cells[2][0].player == player)
        {
            return player
        } else {
            return Player.NULL
        }
    }

    fun clearCells() {
        currentPlayer = Player.X
        cells = Array(3) {
            Array(3) {
                Cell()
            }
        }
    }

}

Model 역할

 

 

 

 

 

 


view/MainActivity.kt

package com.jwsoft.kotlinproject.view

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.databinding.DataBindingUtil
import com.jwsoft.kotlinproject.R
import com.jwsoft.kotlinproject.databinding.ActivityMainBinding
import com.jwsoft.kotlinproject.viewmodel.MainViewModel

class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.lifecycleOwner = this
        binding.mainViewModel = viewModel

        viewModel.onCreate()
    }

    override fun onResume() {
        super.onResume()
        viewModel.onResume()
    }

    override fun onPause() {
        super.onPause()
        viewModel.onPause()
    }

    override fun onDestroy() {
        super.onDestroy()
        viewModel.onDestroy()
    }

}

View 역할

 

 

 

 

 

 

 


viewmodel/InterfaceViewModel.kt

package com.jwsoft.kotlinproject.viewmodel

interface InterfaceViewModel {

    fun onCreate()
    fun onResume()
    fun onPause()
    fun onDestroy()

}

 

 

 

 

 

 

viewmodel/MainViewModel.kt

package com.jwsoft.kotlinproject.viewmodel

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.jwsoft.kotlinproject.model.Board
import com.jwsoft.kotlinproject.model.Player

class MainViewModel : ViewModel(), InterfaceViewModel {

    var model: Board = Board()
    var isInitialize = false
    var cells: Array<Array<MutableLiveData<String>>> = Array(3) {
        Array(3) {
            MutableLiveData<String>()
        }
    }
    var winner: MutableLiveData<String> = MutableLiveData()
    var isShowWinner: MutableLiveData<Boolean> = MutableLiveData()

    override fun onCreate() {
        if (!isInitialize) {
            isInitialize = true

            for (i in 0 until 3) {
                for (j in 0 until 3) {
                    cells[i][j].value = ""
                }
            }

            winner.value = ""
            isShowWinner.value = false
        }
    }

    override fun onResume() {
    }

    override fun onPause() {
    }

    override fun onDestroy() {
    }


    fun onClickButton(row: Int, col: Int) {
        var player: Player = model.mark(row, col)

        if (player != Player.NULL) {
            cells[row][col].postValue(player.toString())

            if (model.getWinner(row, col, player) != Player.NULL) {
                winner.postValue(player.toString())
                isShowWinner.postValue(true)
            } else {
                model.flipPlayer()
            }
        }
    }

    fun resetAll() {
        winner.postValue("")
        isShowWinner.postValue(false)

        for (i in 0 until 3) {
            for (j in 0 until 3) {
                cells[i][j].value = ""
            }
        }

        model.clearCells()
    }

}

ViewModel 역할

 

실행 결과

 

 

 

 

 

 

참고.

academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/

 

안드로이드의 MVC, MVP, MVVM 종합 안내서

안드로이드 앱을 만드는 개발자를 위한 MVC, MVP, MVVM 패턴 사용법과 장단점에 대한 안내서입니다.

academy.realm.io

github.com/ericmaxwell2003/ticTacToe

 

ericmaxwell2003/ticTacToe

A simple tic tac toe app, to illustrate the use of MVC, MVP, and MVVM architectures to organize the application. - ericmaxwell2003/ticTacToe

github.com

 

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함