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

Scala实现牌组抽牌至耗尽及牌面对比技术咨询

嘿,很高兴看到你用Scala捣鼓牌组!函数式编程里的不可变列表确实和命令式那套修改状态的思路不一样,不过咱们一步步拆解问题,用符合Scala风格的方式解决它~

首先,先给你的现有代码做些小重构——原来的Deck类里,drawCard是个固定的val,这意味着不管你“抽”多少次,拿到的都是初始牌组的第一张和剩余牌,因为不可变列表一旦创建就没法修改。咱们得把Deck设计成不可变的状态容器,每次操作(抽牌、洗牌)都返回一个新的Deck实例,这才是函数式的正确姿势。

第一步:重构基础类

先给Face枚举加个权重方法,方便后续对比牌面大小,然后重构Deck:

case class Card(suit: Suit, face: Face)

// 给Face加个rank方法,用于比较牌面大小
object Face extends Enumeration {
  type Face = Value
  val Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King, Ace = Value

  def rank(face: Face): Int = face match {
    case Two => 2
    case Three => 3
    case Four => 4
    case Five => 5
    case Six => 6
    case Seven => 7
    case Eight => 8
    case Nine => 9
    case Ten => 10
    case Jack => 11
    case Queen => 12
    case King => 13
    case Ace => 14
  }
}

object Suit extends Enumeration {
  type Suit = Value
  val Hearts, Diamonds, Clubs, Spades = Value
}

// 不可变Deck类,所有操作返回新实例
case class Deck private (private val cards: List[Card]) {
  // 检查牌组是否为空
  def isEmpty: Boolean = cards.isEmpty

  // 抽一张牌:返回(抽到的牌, 新的Deck),空牌组会抛出异常(也可以用Option优化)
  def draw(): (Card, Deck) = cards match {
    case head :: tail => (head, Deck(tail))
    case Nil => throw new NoSuchElementException("牌组已经空啦,没法继续抽牌!")
  }

  // 洗牌:返回洗过牌的新Deck
  def shuffle(): Deck = Deck(util.Random.shuffle(cards))

  // 交替分牌给两个玩家(标准52张牌刚好平分)
  def splitForTwoPlayers(): (Deck, Deck) = {
    require(cards.size % 2 == 0, "牌组数量必须是偶数才能平分给两个玩家!")
    // 第1、3、5...张给玩家1,第2、4、6...张给玩家2
    val (player1Cards, player2Cards) = cards.grouped(2).map {
      case List(c1, c2) => (c1, c2)
    }.unzip
    (Deck(player1Cards), Deck(player2Cards))
  }
}

// 伴生对象:创建标准52张牌的牌组
object Deck {
  def standard(): Deck = {
    val allCards = for {
      suit <- Suit.values.toList
      face <- Face.values.toList
    } yield Card(suit, face)
    Deck(allCards)
  }
}

第二步:循环抽牌直到耗尽

函数式里不用while循环修改状态,而是用递归或者高阶函数来处理逐步消耗的不可变列表。这里用递归最直观:

// 递归抽完所有牌,返回抽到的牌的列表
def drawAllCards(deck: Deck): List[Card] = {
  if (deck.isEmpty) Nil
  else {
    val (card, remainingDeck) = deck.draw()
    card :: drawAllCards(remainingDeck)
  }
}

// 用法示例
val shuffledStandardDeck = Deck.standard().shuffle()
val allDrawn = drawAllCards(shuffledStandardDeck)
println(s"抽完所有牌:$allDrawn")

如果偏爱高阶函数,也可以用Iterator.iterate来模拟:

def drawAllWithIterator(deck: Deck): List[Card] = {
  Iterator.iterate(deck)(_.draw()._2)
    .takeWhile(!_.isEmpty)
    .map(_.draw()._1)
    .toList
}

第三步:对比两个玩家的牌

先把洗牌后的牌组分给两个玩家,然后递归对比每一轮抽出的牌:

// 对比两个玩家的所有牌,返回每一轮的结果
def comparePlayerHands(player1: Deck, player2: Deck): List[(Card, Card, String)] = {
  def compareRec(p1: Deck, p2: Deck, results: List[(Card, Card, String)]): List[(Card, Card, String)] = {
    if (p1.isEmpty || p2.isEmpty) results.reverse // 反转结果,因为递归是从后往前累加的
    else {
      val (c1, newP1) = p1.draw()
      val (c2, newP2) = p2.draw()

      val roundResult = Face.rank(c1.face) compare Face.rank(c2.face) match {
        case 1 => s"玩家1赢:$c1 > $c2"
        case -1 => s"玩家2赢:$c2 > $c1"
        case 0 => s"平局:$c1 == $c2"
      }

      compareRec(newP1, newP2, (c1, c2, roundResult) :: results)
    }
  }

  compareRec(player1, player2, Nil)
}

// 用法示例
val gameDeck = Deck.standard().shuffle()
val (p1, p2) = gameDeck.splitForTwoPlayers()
val gameResults = comparePlayerHands(p1, p2)

// 打印每一轮的对战结果
gameResults.foreach(result => println(result._3))

核心思路就是:利用不可变数据的特性,每次操作都生成新的Deck实例,递归函数用新的状态继续执行,直到触发终止条件(牌组为空)。这样完全避免了可变状态,符合函数式编程的核心思想。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:22:44