
从抽卡到投票CardsAgainst 6大网络事件驱动的游戏状态机全链路梳理【免费下载链接】CardsAgainstAn iOS game for horrible people项目地址: https://gitcode.com/gh_mirrors/ca/CardsAgainstCardsAgainst 是一款基于 iOS 局域网Multipeer Connectivity的点对点多人卡片游戏玩家无需服务器即可组队开局。本文以「抽卡 → 投票」的完整一局为线索全链路梳理它如何用6 大网络事件驱动3 个游戏状态的流转帮你看懂事件驱动游戏状态机的设计思路。项目概览零配置组局的 P2P 卡片游戏CardsAgainst 是《Cards Against Humanity》的手游版主持人抽出一张黑卡题目每位玩家从白卡词卡里拼出自己的答案全员互投得分最高者进入下一轮。它的网络层由三块组成全部内置在项目里模块职责位置连接与事件总线封装 Multipeer Connectivity负责收发 6 大事件ConnectionManager.swift游戏状态机管理一局游戏的 3 个状态与 UI 切换GameViewController.swift卡牌池从 JSON 加载黑白卡、随机发牌、自动洗牌CardManager.swift所有设备在同一局域网内自动发现进入菜单后玩家列表实时刷新——这就是「零配置组局」的体验来源。核心概念3 个状态 6 大事件整个游戏逻辑可以浓缩成两张表读文章前先认识它们。状态GameState定义在 GameState.swift状态含义pickingCard抽卡阶段从手牌里挑选白卡填入黑卡题目waitingForOthers等待阶段我已提交等待其他玩家提交答案pickingWinner投票阶段浏览所有答案投出自己的一票网络事件Event定义在 ConnectionManager.swift共 6 个startGame / answer / cancelAnswer / vote / nextCard / endGame每个事件都带一个可选的[String: MPCSerializable]载荷比如answer事件携带拼好的富文本答案vote事件携带被投票人。所有模型卡牌、玩家、答案都实现统一的序列化协议MPCSerializable保证在设备间传输时装得进、拆得出。全链路拆解一局游戏的 6 大事件下面按一局游戏的真实时间线逐个事件看它的触发时机、携带数据和状态迁移。1️⃣ startGame开局发牌黑卡统一菜单页中只要有在线玩家「Start Game」按钮就会点亮。点击后本机从 CardManager.swift 抽 1 张黑卡 10 张白卡然后向每个对端广播startGame事件各自附带不同的白卡但黑卡相同。关键点在 MenuViewController.swift发起者先发牌给他人再自己进局接收者则在startGame事件回调里取出黑卡与白卡进入同一个 GameViewController。初始状态pickingCard2️⃣ answer拼完答案进入等待玩家在抽卡阶段点选白卡词卡会被依次填入黑卡的________占位符见 Card.swift。当占位符被填满的那一刻GameViewController.swift 做三件事状态从pickingCard切换到waitingForOthers把拼好的答案序列化为富文本广播answer事件调用prepareForBlackCards()若所有人已提交立刻进入投票阶段其他设备收到answer后把答案追加进answers列表底部按钮实时显示Waiting for xxx, yyy让你知道还差谁没交作业。3️⃣ cancelAnswer撤回上一张回到抽卡提交前反悔也是允许的。点击黑卡区域会触发removeLastWhiteCard()最后一张白卡被弹回手牌区占位符恢复原样状态回退到pickingCard同时广播cancelAnswer事件。对端收到后直接从answers里过滤掉这个玩家之前的提交GameViewController.swift。这个细节保证了未完成的提交不会污染等待列表。4️⃣ vote全员投票票票有数当所有对端的答案到齐pickWinner()把状态推进到pickingWinner界面横滑分页第 0 页是自己之后每一页是别人的答案底部按钮显示当前页的得票情况。点击投票后GameViewController.swift本地追加一条Vote并广播vote事件携带votee按钮立即置灰hasVoted防止重复投票本机负责收票每收到一张票就刷新按钮文字当votes.count 全员人数时由本机计算胜者并推进下一轮胜负判定在winner计算属性里票数最高且唯一者胜出平局则弹出 Tie Breaker! 对话框由最后一张票的投出人拍板handleTie()——一个朴素但实用的平局裁决。5️⃣ nextCard发新一轮循环开始确认胜者后nextCardWithWinner()广播nextCard事件载荷包含新黑卡、补充的白卡保持 10 张上限和胜者信息胜者记分 1。每台设备收到事件后执行nextBlackCard()清空答案与选票、重置页面、状态回到pickingCard——至此一个完整循环闭环循环往复直到有人退出。6️⃣ endGame有人退场全员收局endGame是最简单的事件但它体现了事件驱动架构的一致性任何玩家在导航栏返回didMove(toParentViewController:)检测到 parent 为 nil时广播endGame其他设备收到后统一执行dismiss()一起弹回菜单页不会出现有人还在局里的悬挂状态。状态机流转全景把上面 6 个事件映射到 3 个状态上整条链路一目了然startGame开局发牌 │ ▼ ┌──────── pickingCard ────────┐ │ ▲ │ answer │ │ cancelAnswer │ nextCard发新卡 ▼ │撤回上一张 │ waitingForOthers │ │ │ 等待全员提交 │ │ vote │ ▼ 全员投完 │ pickingWinner ───────────────────┘ endGame → 全员退出回菜单值得注意的两个设计细节事件注册跟随界面生命周期GameViewController.swift 在viewWillAppear注册 5 个事件处理器在viewWillDisappear全部注销菜单页同理注册自己的startGame/onConnect/onDisconnect。谁显示界面谁负责消费事件天然避免幽灵处理器。卡牌池自动洗牌CardManager.swift 中当某一颜色的卡牌被抽干时会用随机排序自动重建卡池长局也不会断供。适合新手学什么CardsAgainst 代码量不大却是一个难得的事件驱动状态机教学样本想学P2P 网络编程从 ConnectionManager.swift 的onEvent/sendEvent/sendEventForEach三个入口看起理解事件总线的收发模型想学状态机驱动 UI跟着 GameViewController.swift 里gameState的每次赋值看按钮、滚动区、分页器如何随状态联动想学轻量序列化看 Player.swift、Answer.swift 等模型如何统一实现MPCSerializable让任意模型都能直接放进事件载荷想换一套卡牌修改 cards_pg13.json 即可cardType为A的是白卡Q的是黑卡一句话总结CardsAgainst 用 6 个语义明确的事件 3 个状态把多人同步的复杂性收敛到了极小的代码面里——这正是事件驱动架构的魅力所在。【免费下载链接】CardsAgainstAn iOS game for horrible people项目地址: https://gitcode.com/gh_mirrors/ca/CardsAgainst创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考