MVC/ MVP / MVVM 架构学习之井字游戏_1_MVC实现

最近发现在有很好的例子,可以精简地介绍 MVC/ MVP / MVVM 架构的区别和关系。

所以也做记录一下,以便后续的学习。 总的类图参考: https://blog.csdn.net/whjk20/article/details/107226213

 

目录

1. Model  (Player - Cell - Board)

2. View( activity_tictactoe_mvc.xml)

3. Controller (Activity)


MVC 的实现,其中Model 是游戏的关键实现,在这里可以忽略,侧重MVC 之间的交互

1. Model  (Player - Cell - Board)

enum class Player {
    X,
    O
}
class Cell {
    var value: Player ?= null
}
class Board {

    // 3*3 格,二维数组,并初始化为空
    private val cells = Array(3) {
        arrayOfNulls<Cell>(3)
    }

    // 当前轮到谁
    private var currentTurnPlayer: Player? = null
    var winner: Player? = null
    // 游戏状态
    private var state: GameState? = null

    private enum class GameState { IN_PROGRESS, FINISHED }

    constructor() {
        restart()
    }

    //重置或者重启一个新的游戏,清除棋盘和赢输状态
    fun restart() {
        clearCells()
        winner = null
        currentTurnPlayer = Player.X
        state = GameState.IN_PROGRESS
    }

    private fun clearCells() {
        for (i in cells.indices) {
            for (j in cells[i].indices) {
                cells[i][j] = Cell()
            }
        }
    }

    // 游戏主要数据更新逻辑 - TODO: 无法分出胜负时提示
    fun mark(row: Int, col: Int): Player? {
        var playerThatMoved: Player? = null

        if (isValid(row, col)) {
            cells[row][col]!!.value = currentTurnPlayer
            playerThatMoved = currentTurnPlayer

            if (isWinningMoveByPlayer(currentTurnPlayer, row, col)) {
                state = GameState.FINISHED
                winner = currentTurnPlayer
            } else {
                flipCurrentTurnPlayer()
            }
        }
        return playerThatMoved
    }

    // 切换到下一个玩家
    private fun flipCurrentTurnPlayer() {
        currentTurnPlayer = if (currentTurnPlayer == Player.X) {
            Player.O
        } else {
            Player.X
        }
    }

    private fun isWinningMoveByPlayer(
        player: Player?,
        currentRow: Int,
        currentCol: Int
    ): Boolean {
        return (cells[currentRow][0]!!.value === player // 3-in-the-row
                && cells[currentRow][1]!!.value === player && cells[currentRow][2]!!.value === player
                ) || (cells[0][currentCol]!!.value === player // 3-in-the-column
                && cells[1][currentCol]!!.value === player && cells[2][currentCol]!!.value === player
                ) || (currentRow == currentCol // 3-in-the-diagonal
                && cells[0][0]!!.value === player && cells[1][1]!!.value === player && cells[2][2]!!.value === player
                ) || (currentRow + currentCol == 2 // 3-in-the-opposite-diagonal
                && cells[0][2]!!.value === player && cells[1][1]!!.value === player && cells[2][0]!!.value === player)
    }

    private fun isValid(row: Int, col: Int): Boolean {
        return if (state === GameState.FINISHED) {
            false
        } else if (isOutOfBounds(row) || isOutOfBounds(col)) {
            false
        } else {
            !isCellValueAlreadySet(row, col)
        }
    }

    private fun isCellValueAlreadySet(row: Int, col: Int): Boolean {
        return cells[row][col]!!.value != null
    }

    private fun isOutOfBounds(index: Int): Boolean {
        return index < 0 || index > 2
    }
}

2. View( activity_tictactoe_mvc.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/tictactoe"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:gravity="center_horizontal"
    tools:context=".mvc.controller.TicTacToeMVCActivity">

    <GridLayout
        android:id="@+id/buttonGrid"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:columnCount="3"
        android:rowCount="3">

        <Button
            android:tag="00"
            android:onClick="onCellClicked"
            style="@style/tictactoebutton"
            />

        <Button
            android:tag="01"
            android:onClick="onCellClicked"
            style="@style/tictactoebutton"
            />

        <Button
            android:tag="02"
            android:onClick="onCellClicked"
            style="@style/tictactoebutton"
            />

        <Button
            android:tag="10"
            android:onClick="onCellClicked"
            style="@style/tictactoebutton"
            />

        <Button
            android:tag="11"
            android:onClick="onCellClicked"
            style="@style/tictactoebutton"
            />

        <Button
            android:tag="12"
            android:onClick="onCellClicked"
            style="@style/tictactoebutton"
            />

        <Button
            android:tag="20"
            android:onClick="onCellClicked"
            style="@style/tictactoebutton"
            />

        <Button
            android:tag="21"
            android:onClick="onCellClicked"
            style="@style/tictactoebutton"
            />

        <Button
            android:tag="22"
            android:onClick="onCellClicked"
            style="@style/tictactoebutton"
            />

    </GridLayout>

    <LinearLayout
        android:id="@+id/winnerPlayerViewGroup"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        android:visibility="gone"
        tools:visibility="visible"
        >

        <TextView
            android:id="@+id/winnerPlayerLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="40sp"
            android:layout_margin="20dp"
            tools:text="X" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30sp"
            android:text="@string/winner" />

    </LinearLayout>

</LinearLayout>

其中,用到的资源:

(1) dimens.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">44dp</dimen>
</resources>

(2) styles.xml

<resources>
    <style name="tictactoebutton">
        <item name="android:layout_width">100dp</item>
        <item name="android:layout_height">100dp</item>
        <item name="android:textSize">30sp</item>
    </style>

</resources>

(3) strings.xml

<resources>
    <string name="winner">Winner</string>
</resources>

3. Controller (Activity)

class TicTacToeMVCActivity : AppCompatActivity() {

    companion object {
        const val TAG = "TicTacToeMVCActivity"
    }

    private var model: Board? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_tictactoe_mvc)
        model = Board()
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.reset_menu, menu)
        return super.onCreateOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.design_reset -> {
                resetView()
                resetModel()
            }
        }
        return super.onOptionsItemSelected(item)
    }

    // 操作VIEW, 更新MODLE 和VIEW -- TODO 需求可能更改(VIEW更改)
    fun onCellClicked(view: View) {
        // 读取VIEW操作
        var clickedButton = view as Button

        var tag = clickedButton.tag.toString()
        var clickedRow = Integer.valueOf(tag.substring(0, 1))
        var clickedCol = Integer.valueOf(tag.substring(1, 2))
        Log.d(TAG, "onCellClicked - clickedRow = $clickedRow, clickedCol=$clickedCol")

        // 获取当前移动的玩家
        var playerThatMoved = model?.mark(clickedRow, clickedCol)

        playerThatMoved?.let { clickedButton.text = it.toString() }
        model?.winner?.let {
            winnerPlayerViewGroup.visibility = View.VISIBLE
            winnerPlayerLabel.text = it.toString()
        }
    }

    // 重置VIEW
    private fun resetView() {
        winnerPlayerViewGroup.visibility = View.GONE
        winnerPlayerLabel.text = EMPTY_TEXT

        for (index in 0 until buttonGrid.childCount) {
            (buttonGrid.getChildAt(index) as Button).text = EMPTY_TEXT
        }
    }
    // 重置MODEL
    private fun resetModel() {
        model?.let { it.restart() }
    }
}

其中,菜单布局:reset_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/design_reset"
        android:title="重置"></item>
</menu>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值