Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
stephtelolahy committed Nov 25, 2023
1 parent ac419d3 commit 4c323b7
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 108 deletions.
199 changes: 123 additions & 76 deletions GameKit/Sources/Game/Reducer/Action+Reducer/ActionPlay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,24 @@ struct ActionPlay: GameActionReducer {
let card: String

func reduce(state: GameState) throws -> GameState {
// verify action
guard let playRule = PlayEffectResolver.playRule(card: card, player: player, state: state) else {
// verify play rule
var cardName = card.extractName()
var aliasCardName: String?

// <resolve card alias>
let playReqContext = PlayReqContext(actor: player, event: .play(card, player: player))
if let alias = state.alias(for: card, player: player, ctx: playReqContext) {
cardName = alias
aliasCardName = alias
}
// </resolve card alias>

guard let cardObj = state.cardRef[cardName],
let playRule = cardObj.rules.first(where: { $0.isPlayRule() }) else {
throw GameError.cardNotPlayable(card)
}

// verify requirements
let playReqContext = PlayReqContext(actor: player, event: .play(card, player: player))
for playReq in playRule.playReqs where !PlayReq.playEvents.contains(playReq) {
try playReq.throwingMatch(state: state, ctx: playReqContext)
}
Expand All @@ -31,7 +42,13 @@ struct ActionPlay: GameActionReducer {
let resolvedTarget = try requiredTarget.resolve(state: state, ctx: ctx)
if case let .selectable(pIds) = resolvedTarget {
let options = pIds.reduce(into: [String: GameAction]()) {
$0[$1] = PlayEffectResolver.playEvent(card: card, player: player, playRule: playRule, target: $1)
$0[$1] = PlayEffectResolver.playAction(
card: card,
player: player,
playRule: playRule,
target: $1,
aliasCardName: aliasCardName
)
}
let chooseOne = try GameAction.validateChooseOne(chooser: player, options: options, state: state)
var state = state
Expand All @@ -41,46 +58,128 @@ struct ActionPlay: GameActionReducer {
}

// queue play action
let action = PlayEffectResolver.playEvent(card: card, player: player, playRule: playRule)
let action = PlayEffectResolver.playAction(
card: card,
player: player,
playRule: playRule,
aliasCardName: aliasCardName
)
var state = state
state.sequence.insert(action, at: 0)
return state
}
}

enum PlayEffectResolver {
static func triggeredEffect(
static func playAction(
card: String,
player: String,
state: GameState,
playRule: CardRule,
target: String? = nil,
aliasCardName: String? = nil
) -> GameAction {
if playRule.isMatching(.playImmediate) {
if let aliasCardName {
return .playAs(aliasCardName, card: card, target: target, player: player)
} else {
return .playImmediate(card, target: target, player: player)
}
} else if playRule.isMatching(.playAbility) {
return .playAbility(card, player: player)
} else if playRule.isMatching(.playEquipment) {
return .playEquipment(card, player: player)
} else if playRule.isMatching(.playHandicap) {
guard let target else {
fatalError("missing handicap target")
}
return .playHandicap(card, target: target, player: player)
} else {
fatalError("unexpected")
}
}

static func triggeredEffect(
event: GameAction,
state: GameState
) -> [GameAction] {
guard let playRule = playRule(
card: card,
player: player,
state: state,
aliasCardName: aliasCardName
) else {
return []
// get play rule
let playRule: CardRule
let player: String
let card: String
var target: String?
switch event {
case let .playImmediate(aCard, aTarget, aPlayer):
player = aPlayer
card = aCard
target = aTarget
let cardName = aCard.extractName()

guard let cardObj = state.cardRef[cardName],
let aRule = cardObj.rules.first(where: { $0.playReqs.contains(.playImmediate) }) else {
return []
}

playRule = aRule

case let .playAs(aliasCardName, aCard, aTarget, aPlayer):
player = aPlayer
card = aCard
target = aTarget

guard let cardObj = state.cardRef[aliasCardName],
let aRule = cardObj.rules.first(where: { $0.playReqs.contains(.playImmediate) }) else {
return []
}

playRule = aRule

case let .playAbility(aCard, aPlayer):
player = aPlayer
card = aCard
let cardName = aCard.extractName()
guard let cardObj = state.cardRef[cardName],
let aRule = cardObj.rules.first(where: { $0.playReqs.contains(.playAbility) }) else {
return []
}

playRule = aRule

case let .playEquipment(aCard, aPlayer):
player = aPlayer
card = aCard
let cardName = aCard.extractName()
guard let cardObj = state.cardRef[cardName],
let aRule = cardObj.rules.first(where: { $0.playReqs.contains(.playEquipment) }) else {
return []
}

playRule = aRule

case let .playHandicap(aCard, aTarget, aPlayer):
player = aPlayer
card = aCard
target = aTarget
let cardName = aCard.extractName()
guard let cardObj = state.cardRef[cardName],
let aRule = cardObj.rules.first(where: { $0.playReqs.contains(.playHandicap) }) else {
return []
}

playRule = aRule

default:
fatalError("unexpected")
}

// set main effect
// get main effect
var sideEffect = playRule.effect

// unwrap target effect, only if provided specific player as target
// unwrap target effect
if case let .target(_, childEffect) = sideEffect,
target != nil {
sideEffect = childEffect
}

let event = playEvent(
card: card,
player: player,
playRule: playRule,
target: target,
aliasCardName: aliasCardName
)
let ctx = EffectContext(
actor: player,
card: card,
Expand All @@ -90,58 +189,6 @@ enum PlayEffectResolver {

return [.effect(sideEffect, ctx: ctx)]
}

static func playRule(
card: String,
player: String,
state: GameState,
aliasCardName: String? = nil
) -> CardRule? {
var cardName = card.extractName()

// <resolve card alias>
if let aliasCardName {
cardName = aliasCardName
} else {
let playReqContext = PlayReqContext(actor: player, event: .play(card, player: player))
if let alias = state.alias(for: card, player: player, ctx: playReqContext) {
cardName = alias
}
}
// </resolve card alias>

guard let cardObj = state.cardRef[cardName],
let playRule = cardObj.rules.first(where: { $0.isPlayRule() }) else {
return nil
}

return playRule
}

static func playEvent(
card: String,
player: String,
playRule: CardRule,
target: String? = nil,
aliasCardName: String? = nil
) -> GameAction {
if playRule.isMatching(.playImmediate) {
if let aliasCardName {
.playAs(aliasCardName, card: card, target: target, player: player)
} else {
.playImmediate(card, target: target, player: player)
}
} else if playRule.isMatching(.playAbility) {
.playAbility(card, player: player)
} else if playRule.isMatching(.playEquipment) {
.playEquipment(card, player: player)
} else if playRule.isMatching(.playHandicap) {
// swiftlint:disable:next force_unwrapping
.playHandicap(card, target: target!, player: player)
} else {
fatalError("unexpected")
}
}
}

private extension PlayReq {
Expand All @@ -159,6 +206,6 @@ private extension CardRule {
}

func isMatching(_ playReq: PlayReq) -> Bool {
playReqs.contains { $0 == playReq }
playReqs.contains(playReq)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ struct ActionPlayAbility: GameActionReducer {
state.incrementPlayedThisTurn(for: card)

// queue triggered effect
let children = PlayEffectResolver.triggeredEffect(card: card, player: player, state: state)
let event = GameAction.playAbility(card, player: player)
let children = PlayEffectResolver.triggeredEffect(event: event, state: state)
state.sequence.insert(contentsOf: children, at: 0)

return state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,8 @@ struct ActionPlayAs: GameActionReducer {
state.incrementPlayedThisTurn(for: aliasCardName)

// queue triggered effect
let children = PlayEffectResolver.triggeredEffect(
card: card,
player: player,
state: state,
target: target,
aliasCardName: aliasCardName
)
let event = GameAction.playAs(aliasCardName, card: card, target: target, player: player)
let children = PlayEffectResolver.triggeredEffect(event: event, state: state)
state.sequence.insert(contentsOf: children, at: 0)

return state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ struct ActionPlayEquipment: GameActionReducer {
state.incrementPlayedThisTurn(for: cardName)

// queue triggered effect
let children = PlayEffectResolver.triggeredEffect(card: card, player: player, state: state)
let event = GameAction.playEquipment(card, player: player)
let children = PlayEffectResolver.triggeredEffect(event: event, state: state)
state.sequence.insert(contentsOf: children, at: 0)

return state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ struct ActionPlayHandicap: GameActionReducer {
state.incrementPlayedThisTurn(for: cardName)

// queue triggered effect
let children = PlayEffectResolver.triggeredEffect(card: card, player: player, state: state, target: target)
let event = GameAction.playHandicap(card, target: target, player: player)
let children = PlayEffectResolver.triggeredEffect(event: event, state: state)
state.sequence.insert(contentsOf: children, at: 0)

return state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ struct ActionPlayImmediate: GameActionReducer {
state.incrementPlayedThisTurn(for: card.extractName())

// queue triggered effect
let children = PlayEffectResolver.triggeredEffect(card: card, player: player, state: state, target: target)
let event = GameAction.playImmediate(card, target: target, player: player)
let children = PlayEffectResolver.triggeredEffect(event: event, state: state)
state.sequence.insert(contentsOf: children, at: 0)

return state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ struct EffectActivateCounterCards: EffectResolver {
func resolve(state: GameState, ctx: EffectContext) throws -> [GameAction] {
let playerObj = state.player(ctx.actor)
let playReqContext = PlayReqContext(actor: ctx.actor, event: ctx.event)
let counterCards = playerObj.hand.cards.filter {
state.isCounterCard($0, player: ctx.actor, ctx: playReqContext)

let counterOptions = playerObj.hand.cards.compactMap {
CounterActionResolver.counterAction(card: $0, player: ctx.actor, state: state, ctx: playReqContext)
}
guard counterCards.isNotEmpty else {

guard counterOptions.isNotEmpty else {
return []
}

var options = counterCards.reduce(into: [String: GameAction]()) {
$0[$1] = GameAction.playImmediate($1, player: ctx.actor)
var options = counterOptions.reduce(into: [String: GameAction]()) {
$0[$1.card] = $1.action
}
options[.pass] = .group([])

let chooseOne = try GameAction.validateChooseOne(
chooser: ctx.actor,
options: options,
Expand All @@ -30,27 +32,36 @@ struct EffectActivateCounterCards: EffectResolver {
}
}

private extension GameState {
func isCounterCard(_ card: String, player: String, ctx: PlayReqContext) -> Bool {
private struct CounterOption {
let card: String
let action: GameAction
}

private enum CounterActionResolver {
static func counterAction(card: String, player: String, state: GameState, ctx: PlayReqContext) -> CounterOption? {
var cardName = card.extractName()

// resolve card alias>
if let alias = alias(for: card, player: player, ctx: ctx) {
if let alias = state.alias(for: card, player: player, ctx: ctx) {
cardName = alias
}
// </resolve card alias>

guard let cardObj = cardRef[cardName] else {
return false
guard let cardObj = state.cardRef[cardName] else {
return nil
}

return cardObj.rules.contains {
guard cardObj.rules.contains(where: {
if $0.playReqs.contains(.playImmediate),
case .counterShoot = $0.effect {
return true
} else {
return false
}
}) else {
return nil
}

return CounterOption(card: card, action: .playImmediate(card, player: player))
}
}
Loading

0 comments on commit 4c323b7

Please sign in to comment.