You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Kotlin中实现井字棋(XO Game)人机对战模式?

井字棋单人AI模式开发求助:随机落子实现方案

我是一名Kotlin开发者,已经完成了井字棋(XO Game)的双人对战模式,后续还计划开发在线对战模式。目前我不清楚需要创建哪些函数来实现AI的随机落子功能,希望得到技术帮助来完成单人对战AI模式。

以下是我已经实现的双人对战代码:

import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatButton
import androidx.core.content.ContextCompat

class TwoPlayers : AppCompatActivity() {

    private var playerone = ArrayList<Int>()
    private var playertwo = ArrayList<Int>()
    private lateinit var xwinstxt: TextView
    private lateinit var owinstxt: TextView
    var activeplayer = 1
    var attemptsCount = 9

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

        xwinstxt = findViewById(R.id.xwinstxt)
        owinstxt = findViewById(R.id.owinstxt)

        val intent = intent

        val nameone = intent.getStringExtra("firstplayername")
        val nametwo = intent.getStringExtra("secondplayername")
        xwinstxt.text = nameone
        owinstxt.text = nametwo

    }

    fun buttonClicked(view: View) {
        val buttonClicked = view as AppCompatButton
        var num = 0
        when (buttonClicked.id) {
            R.id.button1 -> num = 1
            R.id.button2 -> num = 2
            R.id.button3 -> num = 3
            R.id.button4 -> num = 4
            R.id.button5 -> num = 5
            R.id.button6 -> num = 6
            R.id.button7 -> num = 7
            R.id.button8 -> num = 8
            R.id.button9 -> num = 9
        }
        playGame(num, buttonClicked)
    }

    private fun playGame(num: Int, buttonClicked: AppCompatButton) {
        attemptsCount -= 1
        if (activeplayer == 1) {
            buttonClicked.background = ContextCompat.getDrawable(this, R.drawable.x_icon)
            playerone.add(num)
            activeplayer = 2
        } else {
            buttonClicked.background = ContextCompat.getDrawable(this, R.drawable.o_icon)
            playertwo.add(num)
            activeplayer = 1
        }
        buttonClicked.isEnabled = false
        checkwinner()
    }

    private fun checkwinner() {
        var winner = -1
        if (attemptsCount == 0) {
            val intent = Intent(this, WinnerPage::class.java)
            WinnerPage.winnerPlayertxt = "Draw Game !"
            startActivity(intent)
            finish()
        }

        if (playerone.contains(1) && playerone.contains(2) && playerone.contains(3)) winner = 1
        if (playertwo.contains(1) && playertwo.contains(2) && playertwo.contains(3)) winner = 2

        if (playerone.contains(4) && playerone.contains(5) && playerone.contains(6)) winner = 1
        if (playertwo.contains(4) && playertwo.contains(5) && playertwo.contains(6)) winner = 2

        if (playerone.contains(7) && playerone.contains(8) && playerone.contains(9)) winner = 1
        if (playertwo.contains(7) && playertwo.contains(8) && playertwo.contains(9)) winner = 2

        if (playerone.contains(1) && playerone.contains(4) && playerone.contains(7)) winner = 1
        if (playertwo.contains(1) && playertwo.contains(4) && playertwo.contains(7)) winner = 2

        if (playerone.contains(2) && playerone.contains(5) && playerone.contains(8)) winner = 1
        if (playertwo.contains(2) && playertwo.contains(5) && playertwo.contains(8)) winner = 2

        if (playerone.contains(3) && playerone.contains(6) && playerone.contains(9)) winner = 1
        if (playertwo.contains(3) && playertwo.contains(6) && playertwo.contains(9)) winner = 2

        if (playerone.contains(1) && playerone.contains(5) && playerone.contains(9)) winner = 1
        if (playertwo.contains(1) && playertwo.contains(5) && playertwo.contains(9)) winner = 2

        if (playerone.contains(3) && playerone.contains(5) && playerone.contains(7)) winner = 1
        if (playertwo.contains(3) && playertwo.contains(5) && playertwo.contains(7)) winner = 2

        if (winner == 1) {
            val intent = Intent(this, WinnerPage::class.java)
            WinnerPage.winnerPlayertxt = "X Wins"
            startActivity(intent)
            finish()
        }
        if (winner == 2) {
            val intent = Intent(this, WinnerPage::class.java)
            WinnerPage.winnerPlayertxt = "O Wins"
            startActivity(intent)
            finish()
        }
    }
}

实现AI随机落子的核心函数与修改步骤

1. 获取所有空位的函数

先实现一个函数,筛选出当前棋盘上未被占用的位置,这是AI落子的可选范围:

private fun getEmptyPositions(): List<Int> {
    val allPositions = (1..9).toList()
    val takenPositions = playerone + playertwo
    return allPositions.filter { !takenPositions.contains(it) }
}

2. AI随机落子的核心函数

从空位中随机挑选一个位置,找到对应按钮后调用现有playGame函数完成落子:

private fun aiMakeMove() {
    val emptyPositions = getEmptyPositions()
    if (emptyPositions.isEmpty()) return // 无空位时直接返回

    // 禁用所有按钮,防止玩家在AI落子过程中点击
    disableAllButtons()

    // 随机选择一个空位
    val randomPosition = emptyPositions.random()
    // 根据位置编号找到对应按钮
    val aiButton = when(randomPosition) {
        1 -> findViewById<AppCompatButton>(R.id.button1)
        2 -> findViewById<AppCompatButton>(R.id.button2)
        3 -> findViewById<AppCompatButton>(R.id.button3)
        4 -> findViewById<AppCompatButton>(R.id.button4)
        5 -> findViewById<AppCompatButton>(R.id.button5)
        6 -> findViewById<AppCompatButton>(R.id.button6)
        7 -> findViewById<AppCompatButton>(R.id.button7)
        8 -> findViewById<AppCompatButton>(R.id.button8)
        9 -> findViewById<AppCompatButton>(R.id.button9)
        else -> null
    }

    aiButton?.let {
        playGame(randomPosition, it)
        // 若游戏未结束,恢复可点击的空位按钮
        if (attemptsCount > 0 && checkWinnerStatus() == -1) {
            enableAllButtons()
        }
    }
}

3. 新增辅助函数

为了优化交互体验,需要添加几个辅助函数:

// 禁用所有棋盘按钮
private fun disableAllButtons() {
    findViewById<AppCompatButton>(R.id.button1).isEnabled = false
    findViewById<AppCompatButton>(R.id.button2).isEnabled = false
    findViewById<AppCompatButton>(R.id.button3).isEnabled = false
    findViewById<AppCompatButton>(R.id.button4).isEnabled = false
    findViewById<AppCompatButton>(R.id.button5).isEnabled = false
    findViewById<AppCompatButton>(R.id.button6).isEnabled = false
    findViewById<AppCompatButton>(R.id.button7).isEnabled = false
    findViewById<AppCompatButton>(R.id.button8).isEnabled = false
    findViewById<AppCompatButton>(R.id.button9).isEnabled = false
}

// 启用所有未被占用的棋盘按钮
private fun enableAllButtons() {
    val allButtons = listOf(
        findViewById<AppCompatButton>(R.id.button1),
        findViewById<AppCompatButton>(R.id.button2),
        findViewById<AppCompatButton>(R.id.button3),
        findViewById<AppCompatButton>(R.id.button4),
        findViewById<AppCompatButton>(R.id.button5),
        findViewById<AppCompatButton>(R.id.button6),
        findViewById<AppCompatButton>(R.id.button7),
        findViewById<AppCompatButton>(R.id.button8),
        findViewById<AppCompatButton>(R.id.button9)
    )
    allButtons.forEach { button ->
        val buttonNum = when(button.id) {
            R.id.button1 -> 1
            R.id.button2 -> 2
            R.id.button3 -> 3
            R.id.button4 -> 4
            R.id.button5 -> 5
            R.id.button6 -> 6
            R.id.button7 -> 7
            R.id.button8 -> 8
            R.id.button9 -> 9
            else -> 0
        }
        if (!playerone.contains(buttonNum) && !playertwo.contains(buttonNum)) {
            button.isEnabled = true
        }
    }
}

// 检查当前胜负状态,避免重复调用checkwinner的跳转逻辑
private fun checkWinnerStatus(): Int {
    var winner = -1
    if (playerone.contains(1) && playerone.contains(2) && playerone.contains(3)) winner = 1
    if (playertwo.contains(1) && playertwo.contains(2) && playertwo.contains(3)) winner = 2

    if (playerone.contains(4) && playerone.contains(5) && playerone.contains(6)) winner = 1
    if (playertwo.contains(4) && playertwo.contains(5) && playertwo.contains(6)) winner = 2

    if (playerone.contains(7) && playerone.contains(8) && playerone.contains(9)) winner = 1
    if (playertwo.contains(7) && playertwo.contains(8) && playertwo.contains(9)) winner = 2

    if (playerone.contains(1) && playerone.contains(4) && playerone.contains(7)) winner = 1
    if (playertwo.contains(1) && playertwo.contains(4) && playertwo.contains(7)) winner = 2

    if (playerone.contains(2) && playerone.contains(5) && playerone.contains(8)) winner = 1
    if (playertwo.contains(2) && playertwo.contains(5) && playertwo.contains(8)) winner = 2

    if (playerone.contains(3) && playerone.contains(6) && playerone.contains(9)) winner = 1
    if (playertwo.contains(3) && playertwo.contains(6) && playertwo.contains(9)) winner = 2

    if (playerone.contains(1) && playerone.contains(5) && playerone.contains(9)) winner = 1
    if (playertwo.contains(1) && playertwo.contains(5) && playertwo.contains(9)) winner = 2

    if (playerone.contains(3) && playerone.contains(5) && playerone.contains(7)) winner = 1
    if (playertwo.contains(3) && playertwo.contains(5) && playertwo.contains(7)) winner = 2

    return winner
}

4. 修改现有playGame函数,触发AI回合

在玩家(activeplayer=1)落子完成后,延迟触发AI落子,让玩家有时间看到自己的操作:

private fun playGame(num: Int, buttonClicked: AppCompatButton) {
    attemptsCount -= 1
    if (activeplayer == 1) {
        buttonClicked.background = ContextCompat.getDrawable(this, R.drawable.x_icon)
        playerone.add(num)
        activeplayer = 2
        // 延迟500ms触发AI落子,提升交互体验
        buttonClicked.postDelayed({
            aiMakeMove()
        }, 500)
    } else {
        buttonClicked.background = ContextCompat.getDrawable(this, R.drawable.o_icon)
        playertwo.add(num)
        activeplayer = 1
    }
    buttonClicked.isEnabled = false
    checkwinner()
}

额外说明

  • 确保布局文件中按钮ID与代码中的button1至button9完全匹配
  • 延迟AI落子的时间可根据需求调整,500ms是兼顾体验和响应速度的合理值
  • 若后续想升级AI难度(比如实现 minimax 最优算法),只需替换aiMakeMove中的随机选择逻辑即可

内容的提问来源于stack exchange,提问作者onlyforquestions

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 03:34:55