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

Android类棋类双计时器应用开发问询:时间转移功能实现

实现类棋类双计时器(带时间转移功能)的方案

这是个挺有意思的计时规则变种,我之前做棋类工具类应用时也琢磨过类似需求,给你一套可行的Android平台实现思路:

首先咱们先明确核心规则(基于你描述的“时间转移”需求):当玩家结束回合时,将自己回合内消耗的时间从自身剩余时间中扣除,并直接加到对方的剩余时间中——如果你的规则是其他变种(比如己方消耗的时间仅让对方可用时间增加、不扣除己方时间),逻辑可以轻松微调。

一、核心数据结构与状态管理

先封装一个计时器管理类,把双方的时间状态、回合状态统一管理,避免UI层逻辑混乱:

// 单个玩家的计时器数据
data class PlayerTimer(
    var remainingTime: Long, // 剩余时间,单位毫秒
    val initialTime: Long    // 初始时间,用于重置
)

// 回合状态枚举
enum class TurnState {
    PLAYER_1_TURN, PLAYER_2_TURN, IDLE
}

// 双计时器核心管理器
class DualTimerManager(private val initialTimeMs: Long) {
    private var player1Timer = PlayerTimer(initialTimeMs, initialTimeMs)
    private var player2Timer = PlayerTimer(initialTimeMs, initialTimeMs)
    private var currentTurn = TurnState.IDLE
    private var turnStartTime: Long = 0L // 记录当前回合启动的时间戳
    private val timerHandler = Handler(Looper.getMainLooper())
    private var updateRunnable: Runnable? = null

    // 对外暴露的状态获取方法
    fun getPlayer1Time(): Long = player1Timer.remainingTime
    fun getPlayer2Time(): Long = player2Timer.remainingTime
    fun getCurrentTurn(): TurnState = currentTurn
}

二、回合启动/停止的核心逻辑

这部分是实现时间转移的关键,当玩家点击按钮时,执行以下流程:

1. 启动回合

记录当前时间戳,启动定时任务实时更新UI,切换回合状态

2. 停止回合

计算当前回合消耗的时间,执行时间转移操作,切换到对方回合

在DualTimerManager中补充核心方法:

// 启动指定玩家的回合
fun startTurn(player: TurnState) {
    if (currentTurn != TurnState.IDLE) return // 防止重复启动
    currentTurn = player
    turnStartTime = System.currentTimeMillis()
    
    // 启动每秒一次的UI更新任务
    updateRunnable = object : Runnable {
        override fun run() {
            val elapsed = System.currentTimeMillis() - turnStartTime
            // 实时计算当前回合玩家的剩余时间(扣除已消耗时长)
            when (currentTurn) {
                TurnState.PLAYER_1_TURN -> {
                    val tempRemaining = player1Timer.remainingTime - elapsed
                    if (tempRemaining <= 0) {
                        stopTurn()
                        onGameEnded?.invoke(currentTurn) // 触发时间耗尽回调
                    }
                    onTimeUpdated?.invoke(player1Timer.remainingTime - elapsed, player2Timer.remainingTime)
                }
                TurnState.PLAYER_2_TURN -> {
                    val tempRemaining = player2Timer.remainingTime - elapsed
                    if (tempRemaining <= 0) {
                        stopTurn()
                        onGameEnded?.invoke(currentTurn)
                    }
                    onTimeUpdated?.invoke(player1Timer.remainingTime, player2Timer.remainingTime - elapsed)
                }
                else -> {}
            }
            timerHandler.postDelayed(this, 1000)
        }
    }
    timerHandler.post(updateRunnable!!)
}

// 停止当前回合,执行时间转移
fun stopTurn() {
    if (currentTurn == TurnState.IDLE) return
    val elapsedTime = System.currentTimeMillis() - turnStartTime
    
    // 核心:执行时间转移逻辑
    when (currentTurn) {
        TurnState.PLAYER_1_TURN -> {
            player1Timer.remainingTime -= elapsedTime
            player2Timer.remainingTime += elapsedTime
        }
        TurnState.PLAYER_2_TURN -> {
            player2Timer.remainingTime -= elapsedTime
            player1Timer.remainingTime += elapsedTime
        }
    }
    
    // 停止定时更新,切换回合状态
    updateRunnable?.let { timerHandler.removeCallbacks(it) }
    updateRunnable = null
    currentTurn = when (currentTurn) {
        TurnState.PLAYER_1_TURN -> TurnState.PLAYER_2_TURN
        TurnState.PLAYER_2_TURN -> TurnState.PLAYER_1_TURN
        else -> TurnState.IDLE
    }
    
    // 通知UI最终时间更新
    onTimeUpdated?.invoke(player1Timer.remainingTime, player2Timer.remainingTime)
}

// 对外暴露的回调接口,用于通知UI更新和游戏结束
var onTimeUpdated: ((player1Time: Long, player2Time: Long) -> Unit)? = null
var onGameEnded: ((loser: TurnState) -> Unit)? = null

三、UI层交互实现

在Activity/Fragment中绑定按钮点击事件和UI显示,完成交互闭环:

class TimerActivity : AppCompatActivity() {
    private lateinit var dualTimerManager: DualTimerManager
    private val initialTimeMs = 10 * 60 * 1000 // 初始10分钟

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_timer)
        
        dualTimerManager = DualTimerManager(initialTimeMs)
        
        // 玩家1按钮点击逻辑
        btnPlayer1.setOnClickListener {
            when (dualTimerManager.getCurrentTurn()) {
                TurnState.IDLE -> dualTimerManager.startTurn(TurnState.PLAYER_1_TURN)
                TurnState.PLAYER_1_TURN -> dualTimerManager.stopTurn()
                else -> {} // 非当前回合,不响应
            }
        }
        
        // 玩家2按钮点击逻辑
        btnPlayer2.setOnClickListener {
            when (dualTimerManager.getCurrentTurn()) {
                TurnState.IDLE -> dualTimerManager.startTurn(TurnState.PLAYER_2_TURN)
                TurnState.PLAYER_2_TURN -> dualTimerManager.stopTurn()
                else -> {}
            }
        }
        
        // 时间更新回调,刷新UI
        dualTimerManager.onTimeUpdated = { player1Time, player2Time ->
            tvPlayer1Time.text = formatTime(player1Time)
            tvPlayer2Time.text = formatTime(player2Time)
        }
        
        // 时间耗尽回调,处理游戏结束逻辑
        dualTimerManager.onGameEnded = { loser ->
            val message = if (loser == TurnState.PLAYER_1_TURN) "玩家1时间耗尽!" else "玩家2时间耗尽!"
            Toast.makeText(this, message, Toast.LENGTH_LONG).show()
            resetTimer()
        }
        
        // 初始化显示时间
        tvPlayer1Time.text = formatTime(initialTimeMs)
        tvPlayer2Time.text = formatTime(initialTimeMs)
    }
    
    // 格式化时间为MM:SS格式
    private fun formatTime(timeMs: Long): String {
        val minutes = (timeMs / 1000) / 60
        val seconds = (timeMs / 1000) % 60
        return String.format("%02d:%02d", minutes, seconds)
    }
    
    // 重置计时器
    private fun resetTimer() {
        dualTimerManager.apply {
            player1Timer = PlayerTimer(initialTimeMs, initialTimeMs)
            player2Timer = PlayerTimer(initialTimeMs, initialTimeMs)
            currentTurn = TurnState.IDLE
            updateRunnable?.let { timerHandler.removeCallbacks(it) }
            updateRunnable = null
            onTimeUpdated?.invoke(initialTimeMs, initialTimeMs)
        }
    }
    
    // 页面销毁时清理定时器
    override fun onDestroy() {
        super.onDestroy()
        dualTimerManager.updateRunnable?.let { dualTimerManager.timerHandler.removeCallbacks(it) }
    }
}

四、关键注意事项

  • 线程安全:这里用主线程的Handler更新UI,逻辑简单无需后台线程;如果需要更复杂的计算,建议用Coroutine处理。
  • 时间精度:如果需要更高的刷新频率(比如每秒多次),可以把postDelayed的间隔改小(比如100ms),但注意控制性能消耗。
  • 状态保存:如果Activity重建(比如旋转屏幕),需要在onSaveInstanceState中保存当前时间和回合状态,避免数据丢失。
  • 规则变种:如果你的需求是“己方消耗的时间仅增加对方可用时间、不扣除己方时间”,只需修改stopTurn中的逻辑,去掉己方时间扣除的代码即可。

内容的提问来源于stack exchange,提问作者Milan Maxa Maksimović

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 04:10:20