Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Play $regex as card effect #37

Merged
merged 28 commits into from
Nov 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion App/LoggerMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ import Screen

let loggerMiddleware: Middleware<AppState> = { state, action in
print("➡️ \(action)\n✅ \(state)\n")

return nil
}
8 changes: 4 additions & 4 deletions GameKit/Sources/Game/DSL/Builders/Player+Builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
public extension Player {
class Builder {
private var id: String = UUID().uuidString
private var name: String = ""
private var figure: String = ""
private var attributes: [String: Int] = [:]
private var health: Int = 0
private var hand: CardLocation = .init(cards: [])
Expand All @@ -19,7 +19,7 @@ public extension Player {
public func build() -> Player {
Player(
id: id,
name: name,
figure: figure,
attributes: attributes,
health: health,
hand: hand,
Expand All @@ -32,8 +32,8 @@ public extension Player {
return self
}

public func withName(_ value: String) -> Self {
name = value
public func withFigure(_ value: String) -> Self {
figure = value
return self
}

Expand Down
12 changes: 8 additions & 4 deletions GameKit/Sources/Game/DSL/Modifiers/Card+Modifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,30 @@ public extension Card {
init(
_ name: String,
prototype: Card? = nil,
silent: [String] = [],
attributes: [String: Int] = [:],
silent: [String] = [],
alias: [CardAlias] = [],
priority: Int = 0,
@CardRuleBuilder content: () -> [CardRule] = { [] }
) {
self.name = name
self.priority = 0
self.priority = priority
var attributes = (prototype?.attributes ?? [:]).merging(attributes) { _, new in new }
for attr in silent {
attributes.removeValue(forKey: attr)
}
self.attributes = attributes
self.rules = (prototype?.rules ?? []) + content()
self.alias = alias
}

func withPriority(_ value: Int) -> Self {
.init(
name: name,
attributes: attributes,
rules: rules,
priority: value
alias: alias,
priority: value,
rules: rules
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ protocol GameActionHandler {
}

private let actionHandlers: [GameActionHandler] = [
HandlerGameOver(),
HandlerTriggeredEffects(),
HandlerNextAction(),
HandlerActivateCards()
Expand Down
25 changes: 0 additions & 25 deletions GameKit/Sources/Game/Middleware/GameLoop/HandlerGameOver.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ struct HandlerTriggeredEffects: GameActionHandler {
actor: player,
card: card,
event: event,
cancellingAction: cancellingActionForTriggeredEffect(event: event, state: state)
cancellingAction: cancellingAction(event: event, state: state)
)

return GameAction.effect(rule.effect, ctx: ctx)
Expand All @@ -89,7 +89,7 @@ struct HandlerTriggeredEffects: GameActionHandler {
playerObj.attributes.map(\.key)
}

private func cancellingActionForTriggeredEffect(event: GameAction, state: GameState) -> GameAction? {
private func cancellingAction(event: GameAction, state: GameState) -> GameAction? {
if case let .effect(cardEffect, _) = event,
case .shoot = cardEffect,
let nextAction = state.sequence.first,
Expand Down
174 changes: 130 additions & 44 deletions GameKit/Sources/Game/Reducer/Action+Reducer/ActionPlay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,28 @@ struct ActionPlay: GameActionReducer {
let card: String

func reduce(state: GameState) throws -> GameState {
// verify action
let cardName = card.extractName()
guard let cardObj = state.cardRef[cardName] else {
throw GameError.cardNotPlayable(card)
// 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 playRule = cardObj.rules.first(where: { $0.isPlayRule() }) else {
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)
}

var state = state

// resolve target
if case let .target(requiredTarget, _) = playRule.effect {
let ctx = EffectContext(
Expand All @@ -38,58 +42,140 @@ 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]()) {
let action: GameAction =
if playRule.isMatching(.playImmediate) {
.playImmediate(card, target: $1, player: player)
} else if playRule.isMatching(.playHandicap) {
.playHandicap(card, target: $1, player: player)
} else {
fatalError("unexpected")
}

$0[$1] = action
$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
state.sequence.insert(chooseOne, at: 0)
return state
}
}

let action: GameAction =
if playRule.isMatching(.playImmediate) {
.playImmediate(card, player: player)
} else if playRule.isMatching(.playAbility) {
.playAbility(card, player: player)
} else if playRule.isMatching(.playEquipment) {
.playEquipment(card, player: player)
} else {
fatalError("unexpected")
}

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

extension GameState {
mutating func queueOnPlayEffect(
enum PlayEffectResolver {
static func playAction(
card: String,
player: String,
target: String?,
state: GameState,
event: GameAction
) {
let cardName = card.extractName()
guard let cardObj = state.cardRef[cardName],
let playRule = cardObj.rules.first(where: { $0.isPlayRule() }) else {
return
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")
}
}

// swiftlint:disable:next function_body_length cyclomatic_complexity
static func triggeredEffect(
event: GameAction,
state: GameState
) -> [GameAction] {
// 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
Expand All @@ -101,8 +187,8 @@ extension GameState {
event: event,
target: target
)
let triggered = GameAction.effect(sideEffect, ctx: ctx)
sequence.insert(triggered, at: 0)

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

Expand All @@ -121,6 +207,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,13 +15,9 @@ struct ActionPlayAbility: GameActionReducer {
state.incrementPlayedThisTurn(for: card)

// queue triggered effect
state.queueOnPlayEffect(
card: card,
player: player,
target: nil,
state: state,
event: .playAbility(card, player: player)
)
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
Loading
Loading