티스토리 뷰

Android/Java

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

혀가 길지 않은 개발자 2020. 9. 19. 09:59

MVVM (Model  +  View  +  ViewModel)


build.gradle (Module: app)

android {
    dataBinding {
        enabled = true
    }
}

데이터 바인딩 사용하기 위함

 

 

 

 

 

 

 

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="mainViewModel"
            type="com.jwsoft.javaproject.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.selectButton(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.selectButton(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.selectButton(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.selectButton(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.selectButton(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.selectButton(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.selectButton(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.selectButton(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.selectButton(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.reset()}"/>

    </LinearLayout>

</layout>

activity_main.xml

 

 

 

 

 

 

bindingadapter/BindingAdapters.java

package com.jwsoft.javaproject.bindingadapter;

import android.view.View;
import android.widget.LinearLayout;

import androidx.databinding.BindingAdapter;

public class BindingAdapters {

    @BindingAdapter("android:JamesVisible")
    public static void setVisible(View view, Boolean bool) {
        if (bool == true) ((LinearLayout) view).setVisibility(View.VISIBLE);
        else ((LinearLayout) view).setVisibility(View.GONE);
    }

}

 

 

 

 

 

 

 

 

model/Player.java

package com.jwsoft.javaproject.model;

public enum Player {
    O,
    X,
    NULL
}

 

 

 

 

 

model/Cell.java

package com.jwsoft.javaproject.model;

public class Cell {

    private Player player;

    public void setPlayer(Player player) {
        this.player = player;
    }

    public Player getPlayer() {
        return this.player;
    }

}

 

 

 

 

 

model/Board.java

package com.jwsoft.javaproject.model;

public class Board {

    private Cell[][] cells = new Cell[3][3];
    private Player currentPlayer = Player.X;

    public Board() {        // 모든 셀 초기화
        clearCells();
    }

    public Player mark(int row, int col) {
        if (isValid(row, col)) {
            cells[row][col].setPlayer(currentPlayer);
            return currentPlayer;
        } else {
            return Player.NULL;
        }
    }

    public void flipPlayer() {
        currentPlayer = currentPlayer == Player.X ? Player.O : Player.X;
    }

    private Boolean isValid(int row, int col) {
        return cells[row][col].getPlayer() == Player.NULL;
    }

    public Player getWinner(int row, int col, Player player) {
        if (cells[row][0].getPlayer() == player &&
            cells[row][1].getPlayer() == player &&
            cells[row][2].getPlayer() == player ||
            cells[0][col].getPlayer() == player &&
            cells[1][col].getPlayer() == player &&
            cells[2][col].getPlayer() == player ||
            row == col &&
            cells[0][0].getPlayer() == player &&
            cells[1][1].getPlayer() == player &&
            cells[2][2].getPlayer() == player ||
            row + col == 2 &&
            cells[0][2].getPlayer() == player &&
            cells[1][1].getPlayer() == player &&
            cells[2][0].getPlayer() == player)
        {
            return currentPlayer;
        } else {
            return Player.NULL;
        }
    }

    public void clearCells() {
        for (int i=0; i < 3; i++) {
            for (int j=0; j< 3; j++) {
                cells[i][j] = new Cell();
                cells[i][j].setPlayer(Player.NULL);
            }
        }
    }

}

Model 역할

 

 

 

 

 

 

 


view/MainActivity.java

package com.jwsoft.javaproject.view;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;

import com.jwsoft.javaproject.R;
import com.jwsoft.javaproject.databinding.ActivityMainBinding;
import com.jwsoft.javaproject.viewmodel.MainViewModel;

public class MainActivity extends AppCompatActivity {

    ActivityMainBinding binding;
    MainViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setLifecycleOwner(this);

        viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MainViewModel.class);
        binding.setMainViewModel(viewModel);

        viewModel.onCreate();
    }

    @Override
    protected void onResume() {
        super.onResume();
        viewModel.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        viewModel.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        viewModel.onDestroy();
    }

}

View 역할

 

 

 

 

 

 

 


viewmodel/InterfaceViewModel.java

package com.jwsoft.javaproject.viewmodel;

public interface InterfaceViewModel {

    void onCreate();
    void onResume();
    void onPause();
    void onDestroy();

}

 

 

 

 

 

viewmodel/MainViewModel.java

package com.jwsoft.javaproject.viewmodel;

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import com.jwsoft.javaproject.model.Board;
import com.jwsoft.javaproject.model.Player;

public class MainViewModel extends ViewModel implements InterfaceViewModel {

    private Board model = new Board();

    public Boolean isInitialize = false;
    public MutableLiveData<String>[][] cells = new MutableLiveData[3][3];
    public MutableLiveData<String> winner = new MutableLiveData<>();
    public MutableLiveData<Boolean> isShowWinner = new MutableLiveData<>();

    @Override
    public void onCreate() {
        if (!isInitialize) {        // 모든 객체는 한 번만 초기화. (회전 시 데이터 유지하기 위함)
            for (int i=0; i < 3; i++) {
                for (int j=0; j< 3; j++) {
                    cells[i][j] = new MutableLiveData<>();
                    cells[i][j].setValue("");
                }
                winner.setValue("");
                isShowWinner.setValue(false);
            }
            isInitialize = true;
        }
    }

    @Override
    public void onResume() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onDestroy() {

    }

    public void selectButton(int row, int col) {
        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();
            }
        }

    }

    public void reset() {
        for (int i=0; i < 3; i++) {
            for (int j=0; j< 3; j++) {
                cells[i][j].setValue("");
            }
        }
        winner.setValue("");
        isShowWinner.postValue(false);
        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/04   »
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
글 보관함