I am experimenting with writing more statically secure code by implementing a simple card game. There are several unique cards in this game, and each card has a card-specific effect, which may require additional parameters (for example, a target for the effect). The player holds two cards and, in turn, chooses one of them, as a result of which the card effect occurs.
Note. Most of the details in this post can be tried in REPL. I have a less static version of the safe type, but I want to make sure that what I want is possible before diving into it completely.
Here are some relevant definitions:
trait CardEffectParams
case class OneTarget(player: Player) extends CardEffectParams
case class TwoTargets(player1: Player, player2: Player) extends CardEffectParams
// ...
trait Card {
// the parameters to use are specific to the card
type Params <: CardEffectParams
}
trait Hand {
case class CardInHand(card: Card) { /* with ctor not accessible from outside */ }
// a player can hold two cards
val card1: CardInHand
val card2: CardInHand
}
- , , . : , , Hand, , , hand.CardInHand:
trait Strategy {
def choose(hand: Hand, gameState: GameState): hand.CardsInHand
}
: , (, ), (, ). CardEffectParams. hand.CardsInHand cardInHand.card.Params, cardInHand - , , :
/* NOT valid scala */
trait Strategy {
def choose(hand: Hand, gameState: GameState): (c: hand.CardsInHand, c.card.Params)
}
, : ? ?
, CardEffectParams, . - , , :
case object CardA extends Card {
type Params = OneTarget
}
case object CardB extends Card {
type Params = TwoTargets
}
object RandomStrategy extends Strategy {
def choose(hand: Hand, gameState: GameState) = {
val card: Card = /* randomly pick card1 or card2 */
/* the type of the match block is CardEffectParams, not card.Params */
val param: card.Params = card match {
case CardA => OneTarget(...)
case CardB => TwoTargets(...)
}
}
}
factory , hlist , :
trait Card {
type Params <: CardEffectParams
type HListTypeOfParams = /* insert shapeless magic */
def create[L <: HListTypeOfParams](l: L): Params
}
?
// no idea if this works or not
val card: Card = ...
val params: card.Params = card match {
case c: CardA => c.create(1 :: HNil)
case c: CardB => c.create(1 :: 2 :: HNil)
}
, . , ? ? , , - ?