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

React Native Firebase多人卡牌游戏dealCards函数重复执行问题求助

解决React Native + Firebase多人卡牌游戏发牌函数重复执行问题

问题描述

第一次开发多人游戏,基于React Native和Firebase数据库做一款多人卡牌游戏。在游戏页面的useEffect中调用dealCards函数,原本希望所有玩家进入后只触发一次发牌,但现在函数执行次数和玩家数量一致(共3次),每加载一个玩家就调用一次,界面上能看到卡牌多次变化:第一个玩家进入时发牌,第二个玩家进入时再次发牌,以此类推。需要让该函数仅执行一次。

相关代码片段

dealCards函数

const dealCards=(playerOnGame,deck)=>{
    
    deck.shuffle()        
    
    // 给所有玩家发第一张牌
    for(let i=0;i<playerOnGame.length;i++){
        let card=deck.pop();
        if(playerOnGame[i].money!==0){
           
            playerOnGame[i].card1 = card

        }
        
    }

    
    // 给所有玩家发第二张牌
    for(let i=0;i<playerOnGame.length;i++){
        let card=deck.pop();
        if(playerOnGame[i].money!==0){
        playerOnGame[i].card2=card
        }
    }
    
    let newPlayersOnGame=[...playerOnGame]
    
    functions.playersInGameUpdating(newPlayersOnGame)
    
   // setAllCardOfPlayersInGame(newPlayersOnGame)
}

游戏页面的useEffect

useEffect(() => {
  dealCards()
}, [])

UI代码片段

return(    
    <ImageBackground source={image}  resizeMode='cover' style={{height:'100%'}}>
    <SafeAreaView  >
                
    <View style={styles.upRow}>
                   
     <ImageBackground
    // player1 位置2
    source={require('../assets/circle2.png')}
    resizeMode='center'
    style={styles.circleCornerLeftUp}>

    {showCards ?(
    <View style={styles.fishCardRow}>
    <Image
    source={playersInTable[0].card1.cardImage}
    resizeMode='center'
    style={styles.fishCard}/>
     <Image
    source={playersInTable[0].card2.cardImage}
    resizeMode='center'
    style={styles.fishCard}/>
    </View>
    )
    :
    null
    }
     <Image
    source={playersInTable[0].character}
    resizeMode='center'
    style={styles.charcter}/>
    
                        <Text style={styles.charcterText}>{playersInTable[0].userName}</Text>
                        <Text style={styles.moneyText}><MaterialCommunityIcons name="currency-ils" size={12} color="white" />{playersInTable[0].money.toFixed(2)}</Text>
                        <Text style={styles.blindText}>{playersInTable[0].blind}</Text>
        </ImageBackground> 

         <ImageBackground
        // player2
    source={require('../assets/circle2.png')}
    resizeMode='center'
    style={styles.imageIconCircleUp}>

{showCards ?(
    <View style={styles.fishCardRow}>
    <Image
    source={playersInTable[1].card1.cardImage}
    resizeMode='center'
    style={styles.fishCard}/>
     <Image
    source={playersInTable[1].card2.cardImage}
    resizeMode='center'
    style={styles.fishCard}/>
    </View>
    )
    :
    null
    }
     <Image
                            source={playersInTable[1].character}
    resizeMode='center'
    style={styles.charcter}/>
                        <Text style={styles.charcterText}>{playersInTable[1].userName}</Text>
                        <Text style={styles.moneyText}><MaterialCommunityIcons name="currency-ils" size={12} color="white" />{playersInTable[1].money.toFixed(2)}</Text>
                        <Text style={styles.blindText}>{playersInTable[1].blind}</Text>
    </ImageBackground> 

 <ImageBackground
        // player3
    source={require('../assets/circle2.png')}
    resizeMode='center'
    style={styles.imageIconCircleUp}>

{showCards ?(
    <View style={styles.fishCardRow}>
    <Image
    source={playersInTable[2].card1.cardImage}
    resizeMode='center'
    style={styles.fishCard}/>
     <Image
    source={playersInTable[2].card2.cardImage}
    resizeMode='center'
    style={styles.fishCard}/>
    </View>
    )
    :
    null
    }
   <Image
    source={playersInTable[2].character}
    resizeMode='center'
    style={styles.charcter}/>
                        <Text style={styles.charcterText}>{playersInTable[2].userName}</Text>
                        <Text style={styles.moneyText}><MaterialCommunityIcons name="currency-ils" size={12} color="white" />{playersInTable[2].money.toFixed(2)}</Text>
                        <Text style={styles.blindText}>{playersInTable[2].blind}</Text>
    </ImageBackground> 

解决方案

核心思路

多人游戏中不能依赖客户端本地状态控制发牌,必须用Firebase的全局房间状态同步所有玩家的逻辑,避免重复触发。具体步骤如下:

1. 给Firebase游戏房间添加发牌状态字段

在游戏房间的Firebase文档中新增isDealt字段,初始值设为false,用来标记是否已经完成发牌。

2. 修改dealCards函数,增加状态校验

调用发牌前先从Firebase读取房间状态,只有未发牌时才执行逻辑,执行完成后更新isDealt为true:

const dealCards = async (playerOnGame, deck, gameRoomRef) => {
  // 获取当前房间的发牌状态
  const gameRoomSnapshot = await gameRoomRef.get();
  const gameRoomData = gameRoomSnapshot.data();
  
  if (gameRoomData.isDealt) return; // 已发牌则直接终止函数

  deck.shuffle();        

  // 发第一张牌
  for(let i=0;i<playerOnGame.length;i++){
      let card=deck.pop();
      if(playerOnGame[i].money!==0){
          playerOnGame[i].card1 = card
      }
  }

  // 发第二张牌
  for(let i=0;i<playerOnGame.length;i++){
      let card=deck.pop();
      if(playerOnGame[i].money!==0){
          playerOnGame[i].card2=card
      }
  }
  
  let newPlayersOnGame=[...playerOnGame]
  
  // 同时更新玩家数据和发牌状态,保证原子性
  await Promise.all([
    functions.playersInGameUpdating(newPlayersOnGame),
    gameRoomRef.update({ isDealt: true })
  ])
}

3. 调整useEffect的触发逻辑

让useEffect依赖玩家列表,当玩家数量达到预设人数(比如3人)时,才调用发牌函数:

useEffect(() => {
  // 假设需要3名玩家全部进入后才发牌
  if (playersInTable.length === 3) {
    // 替换为你的游戏房间ID
    const gameRoomRef = firebase.firestore().collection('gameRooms').doc('your-game-room-id');
    dealCards(playersInTable, deck, gameRoomRef);
  }
}, [playersInTable, deck])

额外注意事项

  • 必须用Firebase的全局状态控制,否则不同玩家的客户端本地状态无法同步,可能导致重复发牌。
  • 如果支持玩家中途加入,需要调整逻辑:新玩家进入时直接从Firebase获取已发好的卡牌数据,不再触发发牌逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 22:12:10