feat:完成游戏结算

This commit is contained in:
max
2024-05-31 12:58:38 +08:00
parent 5be8ebde3a
commit 92a0e4b408
28 changed files with 741 additions and 158 deletions

View File

@@ -12,12 +12,19 @@ import androidx.viewbinding.ViewBinding
import com.chwl.app.R
import com.chwl.app.base.BaseViewBindingActivity
import com.chwl.app.common.widget.dialog.DialogManager
import com.chwl.app.game.core.engine.GameEngineViewModel
import com.chwl.app.game.core.engine.GameEngineAbility
import com.chwl.app.game.data.bean.GameInfoUiState
import com.chwl.app.game.data.bean.GameResultBean
import com.chwl.app.game.ui.game.GameViewModel
import com.chwl.app.game.ui.result.GameResultDialog
import com.chwl.app.ui.pay.ChargeActivity
import com.chwl.core.support.room.RoomContext
import com.chwl.core.support.room.RoomView
import com.chwl.core.support.room.RoomWidget
import com.chwl.core.utils.net.BalanceNotEnoughExeption
import com.chwl.core.utils.net.ServerException
import com.chwl.library.utils.ResUtil
import com.chwl.library.utils.SingleToastUtil
import com.netease.nim.uikit.StatusBarUtil
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.zip
@@ -26,25 +33,29 @@ import kotlinx.coroutines.launch
abstract class BaseGameActivity<T : ViewBinding> : BaseViewBindingActivity<T>(), RoomView {
protected val viewModel: GameViewModel by viewModels()
protected val gameEngineViewModel: GameEngineViewModel by viewModels()
protected var widgets: HashMap<String, RoomWidget> = HashMap()
protected val gameContext get() = viewModel.gameContextLiveData.value
protected val stateAbility get() = gameContext?.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
protected val gameEngineAbility
get() = gameContext?.findAbility<GameEngineAbility>(
GameEngineAbility::class.java.simpleName
)
private var gameInfoDialogManager: DialogManager? = null
protected var loadingDialogManager: DialogManager? = null
protected var matchFailedDialogManager: DialogManager? = null
private var resultDialog: GameResultDialog? = null
override fun init() {
initView()
initEvent()
initObserver()
initWidgets()
gameEngineViewModel.init(lifecycle)
}
protected open fun initView() {
gameInfoDialogManager = DialogManager(this)
loadingDialogManager = DialogManager(this)
}
@@ -52,23 +63,32 @@ abstract class BaseGameActivity<T : ViewBinding> : BaseViewBindingActivity<T>(),
}
protected open fun initObserver() {
lifecycleScope.launch {
viewModel.closeRoomFlow.collectLatest {
loadingDialogManager?.dismissDialog()
finish()
}
}
viewModel.gameContextLiveData.observe(this) {
onGameContext(it)
}
gameEngineViewModel.gameViewLiveData.observe(this) {
onGameViewChanged(it)
}
}
protected open fun initWidgets() {
}
protected open fun onGameContext(gameContext: GameContext) {
gameEngineViewModel.setGameContext(gameContext)
gameContext.onViewAttach(this)
stateAbility?.let {
observeGameInfoUiState(it)
updateGameEngine(it)
lifecycleScope.launch {
it.matchFailedFlow.collectLatest {
onMatchFailed()
}
}
}
initGameEngine(gameContext)
}
private fun observeGameInfoUiState(stateAbility: GameStateAbility) {
@@ -76,17 +96,26 @@ abstract class BaseGameActivity<T : ViewBinding> : BaseViewBindingActivity<T>(),
stateAbility.gameInfoUiStateFlow.collectLatest {
when (it) {
is GameInfoUiState.Loading -> {
if (gameInfoDialogManager == null) {
gameInfoDialogManager = DialogManager(context)
}
gameInfoDialogManager?.showProgressDialog(context)
}
is GameInfoUiState.Success -> {
dialogManager?.dismissDialog()
resultDialog?.dismissAllowingStateLoss()
gameInfoDialogManager?.dismissDialog()
}
is GameInfoUiState.Failed -> {
gameInfoDialogManager?.dismissDialog()
it.throwable.message?.let { msg ->
toast(msg)
if (it.throwable is ServerException && it.throwable.code == BalanceNotEnoughExeption.code) {
onBalanceNotEnough()
} else {
it.throwable.message?.let { msg ->
toast(msg)
}
}
}
@@ -100,8 +129,11 @@ abstract class BaseGameActivity<T : ViewBinding> : BaseViewBindingActivity<T>(),
}
}
private fun updateGameEngine(stateAbility: GameStateAbility) {
protected open fun initGameEngine(gameContext: GameContext) {
val gameEngineAbility =
gameContext.findAbility<GameEngineAbility>(GameEngineAbility::class.java.simpleName)
lifecycleScope.launch {
val stateAbility = stateAbility ?: return@launch
stateAbility.gameIdFlow.zip(stateAbility.roomIdFlow) { gameId, roomId ->
if (gameId?.toLongOrNull() != null && roomId != null) {
Pair(gameId.toLong(), roomId)
@@ -110,7 +142,7 @@ abstract class BaseGameActivity<T : ViewBinding> : BaseViewBindingActivity<T>(),
}
}.collectLatest {
if (it != null) {
gameEngineViewModel.loadGame(
gameEngineAbility?.loadGame(
this@BaseGameActivity,
it.second.toString(),
it.first
@@ -118,6 +150,71 @@ abstract class BaseGameActivity<T : ViewBinding> : BaseViewBindingActivity<T>(),
}
}
}
lifecycleScope.launch {
gameEngineAbility?.gameResultFlow?.collectLatest {
onGameResult(it)
}
}
lifecycleScope.launch {
gameEngineAbility?.gameViewFlow?.collectLatest {
onGameViewChanged(it)
}
}
}
protected open fun onGameResult(list: List<GameResultBean>?) {
if (list == null) {
return
}
resultDialog = GameResultDialog(list, onClose = {
closeRoom()
}, onRestart = {
restart()
})
resultDialog?.show(supportFragmentManager, "GAME_RESULT")
}
private fun onMatchFailed() {
if (matchFailedDialogManager == null) {
matchFailedDialogManager = DialogManager(context)
}
matchFailedDialogManager?.showOkCancelDialog(null,
getString(R.string.game_match_failed),
getString(R.string.game_rematch),
getString(R.string.exit_text), false, false, object :
DialogManager.OkCancelDialogListener {
override fun onOk() {
restart()
}
override fun onCancel() {
super.onCancel()
closeRoom()
}
})
}
protected open fun onBalanceNotEnough() {
dialogManager?.showOkCancelDialog(
ResUtil.getString(R.string.star_send_gift_balance),
ResUtil.getString(R.string.treasure_to_charge)
) {
ChargeActivity.start(context)
}
}
private fun closeRoom() {
loadingDialogManager?.showProgressDialog(this@BaseGameActivity)
viewModel.closeRoom()
}
private fun restart() {
val intent = viewModel.getGameIntent()
if (intent == null) {
toast(R.string.data_error)
return
}
stateAbility?.restart(intent)
}
protected open fun onGameViewChanged(view: View?) {
@@ -140,7 +237,7 @@ abstract class BaseGameActivity<T : ViewBinding> : BaseViewBindingActivity<T>(),
* 注册组件
*/
protected open fun registerWidget(name: String, widget: RoomWidget) {
widgets.put(name, widget)
widgets[name] = widget
widget.onStart(this)
}
@@ -193,5 +290,9 @@ abstract class BaseGameActivity<T : ViewBinding> : BaseViewBindingActivity<T>(),
gameInfoDialogManager = null
loadingDialogManager?.dismissDialog()
loadingDialogManager = null
resultDialog?.dismissAllowingStateLoss()
resultDialog = null
matchFailedDialogManager?.dismissDialog()
matchFailedDialogManager = null
}
}

View File

@@ -1,5 +1,7 @@
package com.chwl.app.game.core
import com.chwl.app.game.core.engine.GameEngineAbility
import com.chwl.core.bean.game.GameRoomInfo
import com.chwl.core.support.room.RoomAbility
import com.chwl.core.support.room.RoomContext
@@ -7,13 +9,18 @@ class GameContext(roomId: Long) : RoomContext(roomId) {
override fun loadAbility(list: MutableMap<String, RoomAbility>) {
super.loadAbility(list)
list.put(GameStateAbility::class.java.simpleName, GameStateAbility())
list.put(GameIMEngineAbility::class.java.simpleName, GameIMEngineAbility())
list.put(GameQueueAbility::class.java.simpleName, GameQueueAbility())
list.put(GameMessageAbility::class.java.simpleName, GameMessageAbility())
list[GameStateAbility::class.java.simpleName] = GameStateAbility()
list[GameEngineAbility::class.java.simpleName] = GameEngineAbility()
list[GameIMEngineAbility::class.java.simpleName] = GameIMEngineAbility()
list[GameQueueAbility::class.java.simpleName] = GameQueueAbility()
list[GameMessageAbility::class.java.simpleName] = GameMessageAbility()
}
override fun performStart() {
super.performStart()
}
fun restart(roomInfo: GameRoomInfo) {
val stateAbility = findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
}
}

View File

@@ -16,4 +16,10 @@ class GameQueueAbility : RoomAbility() {
suspend fun updateQueue(data: List<RoomMicBean>?) {
queueFlow.value = data
}
fun findQueueItem(uid: Long): RoomMicBean? {
return queueFlow.value?.firstOrNull {
it.micUser?.uid == uid
}
}
}

View File

@@ -2,6 +2,7 @@ package com.chwl.app.game.core
import com.chwl.app.game.data.GameModel2
import com.chwl.app.game.data.bean.GameInfoUiState
import com.chwl.app.game.ui.game.GameIntent
import com.chwl.core.bean.game.GameRoomInfo
import com.chwl.core.im.custom.bean.CustomAttachment
import com.chwl.core.im.custom.bean.GameQueueChangedAttachment
@@ -14,12 +15,26 @@ import kotlinx.coroutines.flow.MutableStateFlow
class GameStateAbility : RoomAbility(), GameIMEngineAbility.Listener {
companion object {
// 匹配中
const val STATE_MATCHING = 0
// 匹配成功
const val STATE_MATCH_SUCCESS = 1
// 游戏结束
const val STATE_GAME_END = 2
// 匹配失败
const val STATE_MATCH_FAILED = 3
}
/**
* 游戏状态: 本地定义状态+匹配状态
* 本地定义状态NULL:不合法状态)
* 匹配状态(服务端定义)0:匹配中、1:匹配成功、2:游戏结束、3:匹配失败)
*/
val gameStateFlow: MutableStateFlow<Int?> = MutableStateFlow(0)
val gameStateFlow: MutableStateFlow<Int?> = MutableStateFlow(STATE_MATCHING)
val gameIconFlow = MutableStateFlow<String?>(null)
@@ -64,6 +79,16 @@ class GameStateAbility : RoomAbility(), GameIMEngineAbility.Listener {
}
}
fun restart(intent: GameIntent) {
gameInfoUiStateFlow.value = GameInfoUiState.Loading
safeLaunch(onError = {
gameInfoUiStateFlow.value = GameInfoUiState.Failed(it, gameInfoFlow.value)
}) {
val roomId = GameModel2.startGame(intent.gameId.toString(), intent.gameMode)
requestRoomInfo()
}
}
private suspend fun syncRoomInfo(info: GameRoomInfo) {
roomContext?.roomId = info.chatRoomId ?: 0
gameInfoFlow.value = info

View File

@@ -0,0 +1,112 @@
package com.chwl.app.game.core.engine
import com.chwl.app.game.core.GameQueueAbility
import com.chwl.app.game.core.GameStateAbility
import com.chwl.app.game.data.bean.GameResultBean
import com.chwl.core.sud.state.SudMGPMGState
import com.chwl.core.support.room.RoomContext
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import tech.sud.mgp.core.ISudFSMStateHandle
class GameEngineAbility : SudEngineAbility() {
val gameResultFlow = MutableSharedFlow<List<GameResultBean>?>()
private val stateAbility get() = roomContext?.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
override fun onStart(context: RoomContext) {
super.onStart(context)
safeLaunch {
stateAbility?.gameStateFlow?.collectLatest {
logD("GameEngineAbility gameStateFlow state:${it}")
when (it) {
GameStateAbility.STATE_MATCH_SUCCESS -> {
autoPlayGame()
}
GameStateAbility.STATE_MATCHING -> {
tryStopGame()
}
GameStateAbility.STATE_MATCH_FAILED -> {
tryStopGame()
}
GameStateAbility.STATE_GAME_END -> {
tryStopGame()
}
}
}
}
}
private fun tryStopGame() {
}
private fun autoPlayGame() {
val stateAbility = stateAbility ?: return
logD("autoPlayGame state:${stateAbility.gameStateFlow.value}")
if (stateAbility.gameStateFlow.value != GameStateAbility.STATE_MATCH_SUCCESS) {
return
}
val queueAbility =
roomContext?.findAbility<GameQueueAbility>(GameQueueAbility::class.java.simpleName)
?: return
val queueSize = queueAbility.queueFlow.value?.size ?: 0
if (queueSize == 0) {
return
}
logD("autoPlayGame queueSize:$queueSize size:${sudFSMMG.sudFSMMGCache.playerReadySet.size}")
if (sudFSMMG.sudFSMMGCache.playerReadySet.size >= queueSize) {
sudFSTAPP.notifyAPPCommonSelfPlaying(true, null, null)
}
}
private fun autoJoinGame() {
sudFSTAPP.notifyAPPCommonSelfIn(true, -1, true, 1)
sudFSTAPP.notifyAPPCommonSelfReady(true)
}
override fun onGameStarted() {
super.onGameStarted()
autoJoinGame()
}
override fun onPlayerMGCommonPlayerReady(
handle: ISudFSMStateHandle?,
userId: String?,
model: SudMGPMGState.MGCommonPlayerReady?
) {
super.onPlayerMGCommonPlayerReady(handle, userId, model)
autoPlayGame()
}
override fun onGameMGCommonGameSettle(
handle: ISudFSMStateHandle,
model: SudMGPMGState.MGCommonGameSettle?
) {
super.onGameMGCommonGameSettle(handle, model)
val list = ArrayList<GameResultBean>()
val queueAbility =
roomContext?.findAbility<GameQueueAbility>(GameQueueAbility::class.java.simpleName)
val stateAbility =
roomContext?.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
val scoresList = stateAbility?.scoresFlow?.value
model?.results?.sortedBy { it.rank }?.forEachIndexed { index, it ->
val uid = it.uid?.toLong() ?: -1
val queueItem = queueAbility?.findQueueItem(uid)
val scores = scoresList?.getOrNull(index)
val item = GameResultBean().apply {
this.uid = it.uid
this.rank = it.rank
this.score = it.score
this.nick = queueItem?.micUser?.nick
this.avatar = queueItem?.micUser?.avatar
this.coins = scores
}
list.add(item)
}
safeLaunch {
gameResultFlow.emit(list)
}
}
}

View File

@@ -4,17 +4,12 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.view.View
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import com.chwl.app.R
import com.chwl.app.avroom.game.AppConfig
import com.chwl.app.base.BaseViewModel
import com.chwl.app.game.core.GameContext
import com.chwl.app.game.core.GameQueueAbility
import com.chwl.app.game.core.GameStateAbility
import com.chwl.core.auth.AuthModel
import com.chwl.core.room.game.GameModel
import com.chwl.core.room.game.bean.GameCfg
@@ -24,50 +19,36 @@ import com.chwl.core.sud.decorator.SudFSTAPPDecorator
import com.chwl.core.sud.model.GameViewInfoModel
import com.chwl.core.sud.state.MGStateResponse
import com.chwl.core.sud.state.SudMGPMGState
import com.chwl.core.support.room.RoomAbility
import com.chwl.core.support.room.RoomContext
import com.chwl.library.language.LanguageHelper
import com.chwl.library.utils.SingleToastUtil
import com.chwl.library.utils.json.JsonUtils
import com.example.lib_utils.log.ILog
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import tech.sud.mgp.core.ISudFSMStateHandle
import tech.sud.mgp.core.ISudListenerInitSDK
import tech.sud.mgp.core.SudMGP
import java.lang.IllegalStateException
@SuppressLint("StaticFieldLeak")
open class GameEngineViewModel : BaseViewModel(), SudFSMMGListener, ILog,
LifecycleEventObserver {
open class SudEngineAbility : RoomAbility(), SudFSMMGListener, ILog {
val gameViewLiveData = MutableLiveData<View>(null)
private var isRunning = true
private val sudFSMMG = SudFSMMGDecorator().apply {
setSudFSMMGListener(this@GameEngineViewModel)
val gameViewFlow = MutableStateFlow<View?>(null)
protected var isRunning = true
protected val sudFSMMG = SudFSMMGDecorator().apply {
setSudFSMMGListener(this@SudEngineAbility)
}
private val sudFSTAPP = SudFSTAPPDecorator()
private var lifecycle: Lifecycle? = null
protected val sudFSTAPP = SudFSTAPPDecorator()
private var gameViewRect: GameViewInfoModel.GameViewRectModel =
GameViewInfoModel.GameViewRectModel()
private var roomId: String = ""
private var gameContext: GameContext? = null
fun init(lifecycle: Lifecycle) {
this.lifecycle = lifecycle
lifecycle.addObserver(this)
}
fun setGameContext(gameContext: GameContext) {
this.gameContext = gameContext
}
fun loadGame(activity: Activity, roomId: String, gameId: Long) {
logD("loadGame() roomId:$roomId gameId:$gameId")
if (!this.isRunning) {
return
}
if (lifecycle == null) {
throw IllegalStateException("未初始化")
}
this.roomId = roomId
getGameCode({
initSDK(activity, gameId, it)
@@ -104,7 +85,7 @@ open class GameEngineViewModel : BaseViewModel(), SudFSMMGListener, ILog,
val iSudFSTAPP =
SudMGP.loadMG(activity, userId, getRoomId(), code, gameId, getGameLanguage(), sudFSMMG)
sudFSTAPP.setISudFSTAPP(iSudFSTAPP)
gameViewLiveData.value = iSudFSTAPP.gameView
gameViewFlow.value = iSudFSTAPP.gameView
}
fun setGameViewRect(rect: GameViewInfoModel.GameViewRectModel) {
@@ -137,57 +118,50 @@ open class GameEngineViewModel : BaseViewModel(), SudFSMMGListener, ILog,
handle.success(JsonUtils.toJson(response))
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
onViewDestroy()
}
override fun onStop(context: RoomContext) {
super.onStop(context)
destroyGame()
}
private fun getRoomId() = roomId
private fun getAppId() = AppConfig.APP_ID
private fun getAppKey() = AppConfig.APP_KEY
private fun isTestEnv() = AppConfig.isTestEnv
private fun getGameLanguage(): String {
return when (LanguageHelper.getCurrentLanguageType()) {
LanguageHelper.ZH -> {
"zh-TW"
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
super.onStateChanged(source, event)
when (event) {
Lifecycle.Event.ON_START -> {
sudFSTAPP.startMG()
}
LanguageHelper.AR -> {
"ar-SA"
Lifecycle.Event.ON_STOP -> {
sudFSTAPP.stopMG()
}
Lifecycle.Event.ON_RESUME -> {
sudFSTAPP.playMG()
}
Lifecycle.Event.ON_PAUSE -> {
sudFSTAPP.pauseMG()
}
Lifecycle.Event.ON_DESTROY -> {
destroyGame()
}
else -> {
"en-US"
}
}
}
override fun onCleared() {
super.onCleared()
onViewDestroy()
}
private fun onViewDestroy() {
isRunning = false
lifecycle?.removeObserver(this)
lifecycle = null
}
override fun onGameStarted() {
autoJoinGame()
}
override fun onGameDestroyed() {
}
private fun autoJoinGame() {
sudFSTAPP.notifyAPPCommonSelfIn(true, -1, true, 1)
sudFSTAPP.notifyAPPCommonSelfReady(true)
private fun destroyGame() {
isRunning = false
sudFSTAPP.destroyMG()
}
override fun onExpireCode(handle: ISudFSMStateHandle, p1: String?) {
@@ -200,7 +174,7 @@ open class GameEngineViewModel : BaseViewModel(), SudFSMMGListener, ILog,
}
override fun onGetGameViewInfo(handle: ISudFSMStateHandle, p1: String?) {
val gameLayout = gameViewLiveData.value
val gameLayout = gameViewFlow.value
if (gameLayout == null) {
handle.failure("gameLayout is NULL")
return
@@ -231,22 +205,27 @@ open class GameEngineViewModel : BaseViewModel(), SudFSMMGListener, ILog,
sudFSTAPP.notifyAPPCommonSelfIn(true, -1, true, 1)
}
override fun onPlayerMGCommonPlayerReady(
handle: ISudFSMStateHandle?,
userId: String?,
model: SudMGPMGState.MGCommonPlayerReady?
) {
super.onPlayerMGCommonPlayerReady(handle, userId, model)
val queueAbility =
gameContext?.findAbility<GameQueueAbility>(GameQueueAbility::class.java.simpleName)
?: return
val queueSize = queueAbility.queueFlow.value?.size ?: 0
if (queueSize == 0) {
return
}
logD("onPlayerMGCommonPlayerReady queueSize:$queueSize size:${sudFSMMG.sudFSMMGCache.playerReadySet.size}")
if (sudFSMMG.sudFSMMGCache.playerReadySet.size >= queueSize) {
sudFSTAPP.notifyAPPCommonSelfPlaying(true, null, null)
protected open fun getRoomId() = roomId
protected open fun getAppId() = AppConfig.APP_ID
protected open fun getAppKey() = AppConfig.APP_KEY
protected open fun isTestEnv() = AppConfig.isTestEnv
protected open fun getGameLanguage(): String {
return when (LanguageHelper.getCurrentLanguageType()) {
LanguageHelper.ZH -> {
"zh-TW"
}
LanguageHelper.AR -> {
"ar-SA"
}
else -> {
"en-US"
}
}
}
}

View File

@@ -0,0 +1,29 @@
package com.chwl.app.game.data.bean
import androidx.annotation.Keep
import java.io.Serializable
import java.math.BigDecimal
@Keep
class GameResultBean : Serializable {
var rank: Int? = null
var uid: String? = null
var avatar: String? = null
var nick: String? = null
var score: Int? = null
var coins: Double? = null
fun getCoinsStr(): String {
val coinsValue = coins ?: return "0"
try {
val bigDecimal = BigDecimal.valueOf(coinsValue)
val coinsStr = bigDecimal.stripTrailingZeros().toPlainString()
if (coinsValue > 0) {
return "+$coinsStr"
}
return coinsStr
} catch (e: Exception) {
return coins?.toString() ?: "0"
}
}
}

View File

@@ -47,15 +47,6 @@ class GameActivity : BaseGameActivity<GameActivityBinding>(), RoomView, ILog {
override fun initView() {
super.initView()
binding.spaceGameRect.post {
val rect = GameViewInfoModel.GameViewRectModel().apply {
top = binding.spaceGameRect.top
bottom = binding.root.height - binding.spaceGameRect.bottom
}
logD("initGameEngine() height:${binding.root.height}")
logD("initGameEngine() top:${rect.top} bottom:${rect.bottom}")
gameEngineViewModel.setGameViewRect(rect)
}
}
override fun initEvent() {
@@ -81,12 +72,19 @@ class GameActivity : BaseGameActivity<GameActivityBinding>(), RoomView, ILog {
updateAward(it?.first())
}
}
}
lifecycleScope.launch {
stateAbility.matchFailedFlow.collectLatest {
showMatchFailed()
override fun initGameEngine(gameContext: GameContext) {
binding.spaceGameRect.post {
val rect = GameViewInfoModel.GameViewRectModel().apply {
top = binding.spaceGameRect.top
bottom = binding.root.height - binding.spaceGameRect.bottom
}
logD("initGameEngine() height:${binding.root.height}")
logD("initGameEngine() top:${rect.top} bottom:${rect.bottom}")
gameEngineAbility?.setGameViewRect(rect)
}
super.initGameEngine(gameContext)
}
private fun updateAward(number: Double?) {
@@ -106,19 +104,6 @@ class GameActivity : BaseGameActivity<GameActivityBinding>(), RoomView, ILog {
override fun initObserver() {
super.initObserver()
lifecycleScope.launch {
viewModel.closeRoomFlow.collectLatest {
loadingDialogManager?.dismissDialog()
finish()
}
}
lifecycleScope.launch {
viewModel.restartFlow.collectLatest {
loadingDialogManager?.dismissDialog()
// TODO 待完善
}
}
}
override fun initWidgets() {
@@ -137,24 +122,6 @@ class GameActivity : BaseGameActivity<GameActivityBinding>(), RoomView, ILog {
return binding.layoutGame
}
private fun showMatchFailed() {
dialogManager.showOkCancelDialog(null,
getString(R.string.game_match_failed),
getString(R.string.game_rematch),
getString(R.string.exit_text), false, false, object : OkCancelDialogListener {
override fun onOk() {
loadingDialogManager?.showProgressDialog(this@GameActivity)
viewModel.restart()
}
override fun onCancel() {
super.onCancel()
loadingDialogManager?.showProgressDialog(this@GameActivity)
viewModel.closeRoom()
}
})
}
private fun showExitTips() {
dialogManager.showOkCancelDialog(null,
getString(R.string.game_exit_tips),

View File

@@ -4,6 +4,7 @@ import androidx.lifecycle.MutableLiveData
import com.chwl.app.R
import com.chwl.app.base.BaseViewModel
import com.chwl.app.game.core.GameContext
import com.chwl.app.game.core.GameStateAbility
import com.chwl.app.game.data.GameModel2
import com.chwl.core.bean.response.BeanResult
import com.chwl.library.utils.ResUtil
@@ -15,8 +16,6 @@ class GameViewModel : BaseViewModel() {
val closeRoomFlow = MutableSharedFlow<BeanResult<String?>>()
val restartFlow = MutableSharedFlow<BeanResult<String?>>()
private var gameIntent: GameIntent? = null
fun init(intent: GameIntent) {
@@ -26,25 +25,15 @@ class GameViewModel : BaseViewModel() {
gameContextLiveData.value = gameContext
}
fun getGameIntent(): GameIntent? {
return gameIntent
}
override fun onCleared() {
super.onCleared()
gameContextLiveData.value?.performStop()
}
fun restart() {
val intent = gameIntent
if (intent == null) {
safeLaunch {
restartFlow.emit(BeanResult.failed(NullPointerException(ResUtil.getString(R.string.utils_net_beanobserver_05))))
}
return
}
safeLaunch {
val result = GameModel2.startGame(intent.gameId.toString(), intent.gameMode)
// TODO 待完善
}
}
fun closeRoom() {
val roomId = gameContextLiveData.value?.roomId
if (roomId != null) {

View File

@@ -0,0 +1,37 @@
package com.chwl.app.game.ui.result
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.chwl.app.R
import com.chwl.app.game.data.bean.GameResultBean
import com.chwl.app.ui.utils.loadAvatar
class GameResultAdapter :
BaseQuickAdapter<GameResultBean, BaseViewHolder>(R.layout.game_result_item) {
override fun convert(helper: BaseViewHolder, item: GameResultBean?) {
helper.setText(R.id.tv_name, item?.nick ?: "")
helper.setText(R.id.tv_coins, item?.getCoinsStr() ?: "0")
helper.getView<ImageView>(R.id.iv_user_avatar).loadAvatar(item?.avatar)
val rank = helper.bindingAdapterPosition
val rankView = helper.getView<TextView>(R.id.tv_rank)
val rootView = helper.getView<View>(R.id.layout_root)
if (rank == 0) {
rankView.setBackgroundResource(R.drawable.game_result_ic_top1)
rankView.text = ""
} else if (rank == 1) {
rankView.setBackgroundResource(R.drawable.game_result_ic_top2)
rankView.text = ""
} else {
rankView.background = null
rankView.text = "${(rank + 1)}"
}
if (rank % 2 == 0) {
rootView.setBackgroundResource(R.drawable.game_result_item_bg_top1)
} else {
rootView.setBackgroundResource(R.drawable.game_result_item_bg_top2)
}
}
}

View File

@@ -0,0 +1,90 @@
package com.chwl.app.game.ui.result
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import com.chwl.app.R
import com.chwl.app.databinding.GameResultDialogBinding
import com.chwl.app.game.data.bean.GameResultBean
import com.chwl.app.ui.widget.recyclerview.decoration.SpacingDecoration
import com.chwl.core.auth.AuthModel
import com.chwl.library.common.util.Utils
import com.example.lib_utils.ktx.singleClick
class GameResultDialog(
private val list: List<GameResultBean>,
private var onClose: (() -> Unit)?,
private var onRestart: (() -> Unit)?
) : DialogFragment() {
private var binding: GameResultDialogBinding? = null
private val adapter = GameResultAdapter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
adapter.setNewData(list)
var isSuccess = false
if (list.firstOrNull()?.uid?.toLongOrNull() == AuthModel.get().currentUid) {
isSuccess = true
}
if (isSuccess) {
binding?.ivState?.setBackgroundResource(R.drawable.game_result_bg_win)
} else {
binding?.ivState?.setBackgroundResource(R.drawable.game_result_bg_lose)
}
}
private fun initView() {
binding?.tvClose?.singleClick {
onClose?.invoke()
}
binding?.tvRestart?.singleClick {
onRestart?.invoke()
}
binding?.recyclerView?.addItemDecoration(
SpacingDecoration(
0,
Utils.dip2px(context, 10f),
false
)
)
binding?.recyclerView?.adapter = adapter
}
override fun onDestroyView() {
super.onDestroyView()
onClose = null
onRestart = null
}
override fun getTheme(): Int {
return R.style.full_screen_dialog
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = GameResultDialogBinding.inflate(LayoutInflater.from(context))
return binding?.root
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
this.setCancelable(false)
this.setCanceledOnTouchOutside(false)
window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#1ADCE5" />
<corners android:radius="@dimen/dp_16" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#E2F6FF" />
<corners android:radius="@dimen/dp_12" />
</shape>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:bottomLeftRadius="0px"
android:bottomRightRadius="@dimen/dp_5"
android:topLeftRadius="0px"
android:topRightRadius="@dimen/dp_5" />
<solid android:color="#9B4300" />
<stroke
android:width="@dimen/dp_1"
android:color="#FFBC1E" />
</shape>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:bottomLeftRadius="0px"
android:bottomRightRadius="@dimen/dp_5"
android:topLeftRadius="0px"
android:topRightRadius="@dimen/dp_5" />
<solid android:color="#00666B" />
<stroke
android:width="@dimen/dp_1"
android:color="#FCFF1E" />
</shape>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/layout_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/game_result_bg_1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.47"
app:layout_constraintWidth_percent="0.832">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_7"
android:layout_marginTop="@dimen/dp_41"
android:layout_marginBottom="@dimen/dp_8"
android:background="@drawable/game_result_bg_2">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_13"
android:layout_marginTop="@dimen/dp_15"
android:maxHeight="@dimen/dp_150"
android:minHeight="@dimen/dp_120"
android:orientation="vertical"
android:scrollbars="none"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout_height="@dimen/dp_120" />
<TextView
android:id="@+id/tv_close"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="@dimen/dp_14"
android:layout_marginTop="@dimen/dp_30"
android:layout_marginBottom="@dimen/dp_20"
android:background="@drawable/game_result_bg_close"
android:gravity="center"
android:text="@string/close"
android:textColor="@color/white"
android:textSize="@dimen/dp_18"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="132:40"
app:layout_constraintEnd_toStartOf="@id/tv_restart"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recycler_view" />
<TextView
android:id="@+id/tv_restart"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="@dimen/dp_6"
android:layout_marginEnd="@dimen/dp_14"
android:background="@drawable/game_result_bg_restart"
android:gravity="center"
android:text="@string/game_rematch"
android:textColor="@color/white"
android:textSize="@dimen/dp_18"
app:layout_constraintDimensionRatio="132:40"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_close"
app:layout_constraintTop_toTopOf="@id/tv_close" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
<Space
android:id="@+id/space_state_bottom"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="375:85"
app:layout_constraintTop_toTopOf="@id/layout_content" />
<ImageView
android:id="@+id/iv_state"
android:layout_width="match_parent"
android:layout_height="0dp"
android:src="@drawable/game_result_bg_win"
app:layout_constraintBottom_toBottomOf="@id/space_state_bottom"
app:layout_constraintDimensionRatio="375:317" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_root"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_60"
android:background="@drawable/game_result_item_bg_top1">
<TextView
android:id="@+id/tv_rank"
android:layout_width="@dimen/dp_42"
android:layout_height="@dimen/dp_42"
android:layout_marginStart="@dimen/dp_7"
android:gravity="center"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="@dimen/dp_20"
android:textStyle="bold"
app:autoSizeMaxTextSize="@dimen/dp_20"
app:autoSizeMinTextSize="@dimen/dp_12"
app:autoSizeStepGranularity="1px"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_user_avatar"
android:layout_width="@dimen/dp_46"
android:layout_height="@dimen/dp_46"
android:layout_marginStart="@dimen/dp_5"
android:src="@drawable/default_avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_rank"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/shape_circle" />
<ImageView
android:id="@+id/iv_user_avatar_border"
android:layout_width="@dimen/dp_49"
android:layout_height="@dimen/dp_49"
android:layout_marginStart="@dimen/dp_2"
app:layout_constraintBottom_toBottomOf="@id/iv_user_avatar"
app:layout_constraintEnd_toEndOf="@id/iv_user_avatar"
app:layout_constraintStart_toStartOf="@id/iv_user_avatar"
app:layout_constraintTop_toTopOf="@id/iv_user_avatar"
tools:src="@drawable/game_result_avatar_border_top_1" />
<ImageView
android:id="@+id/iv_coins_bg"
android:layout_width="0dp"
android:layout_height="@dimen/dp_22"
android:layout_marginStart="@dimen/dp_9"
android:background="@drawable/game_result_coins_top1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/tv_coins"
app:layout_constraintStart_toStartOf="@id/iv_coins"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_coins"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/dp_10"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="1"
android:minWidth="@dimen/dp_48"
android:paddingStart="@dimen/dp_5"
android:paddingEnd="@dimen/dp_6"
android:text="0"
android:textColor="#FFE829"
android:textSize="@dimen/dp_15"
app:autoSizeMaxTextSize="@dimen/dp_15"
app:autoSizeMinTextSize="@dimen/dp_10"
app:autoSizeStepGranularity="1px"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_coins"
android:layout_width="@dimen/dp_24"
android:layout_height="@dimen/dp_24"
android:src="@drawable/game_ic_coins"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/tv_coins"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_8"
android:layout_marginEnd="@dimen/dp_3"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="@dimen/dp_14"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/iv_coins"
app:layout_constraintStart_toEndOf="@id/iv_user_avatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="Name" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -8,6 +8,7 @@ class GameRoomData : Serializable {
val mgId: String? = null
val gameRoomIcon: String? = null
val configJson: String? = null
val gameSelectCfg: String? = null
val scores: MutableList<Double>? = null
// 匹配状态0:匹配中、1:匹配成功、2:游戏结束、3:匹配失败)