Skip to content
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
67 changes: 40 additions & 27 deletions internal/augments/augments.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,56 @@ var (
TwistOfFate: {
Name: TwistOfFate,
Category: CategoryOffense,
Type: TypeActive,
Type: TypePassive,
Trigger: TriggerConditionAfterRoll,
Effect: "Roll 2d6 and keep the highest.",
Intent: "Front-load pressure in must-win lanes.",
Effect: "If last round was a loss, roll 2d6 keep highest.",
Intent: "Strong catch-up pressure in must-win rounds.",
Action: ActionAddDie,
Target: TargetSelf,
Amount: 1,
Amount: 0,
},
Overpower: {
Name: Overpower,
Category: CategoryOffense,
Type: TypeActive,
Trigger: TriggerConditionBeforeRoll,
Type: TypePassive,
Trigger: TriggerConditionAfterRoll,
Effect: "Gain +1 to your roll total this duel.",
Intent: "Reliable low-variance push.",
Intent: "Reliable low-variance baseline.",
Action: ActionIncrease,
Target: TargetSelf,
Amount: 1,
},
PrecisionStrike: {
Name: PrecisionStrike,
Category: CategoryOffense,
Type: TypeActive,
Effect: "Add +1 to your revealed total.",
Intent: "Skill-expression token for close reads.",
Type: TypePassive,
Trigger: TriggerConditionAfterRoll,
Effect: "If you roll a 4 or higher, gain +1 to your total.",
Intent: "High-variance reward for strong rolls.",
Action: ActionIncrease,
Target: TargetSelf,
Amount: 1,
},
MomentumSurge: {
Name: MomentumSurge,
Category: CategoryOffense,
Type: TypePassive,
Trigger: TriggerConditionAfterRoll,
Effect: "If last duel was a win, gain +2 this duel.",
Intent: "Snowball option with explicit condition gate.",
Effect: "If last duel was a win, gain +1 this duel.",
Intent: "Snowball option to maintain tempo.",
Action: ActionIncrease,
Target: TargetSelf,
Amount: 2,
Amount: 1,
},
}

_defenseRepo = map[Name]Effect{
Brace: {
Name: Brace,
Category: CategoryDefense,
Type: TypeActive,
Trigger: TriggerConditionAfterRoll,
Effect: "Losing roll by 1, convert result to a tie.",
Type: TypePassive,
Trigger: TriggerConditionAfterAugments,
Effect: "Losing roll by 2 or less, convert result to a tie.",
Intent: "Stabilize critical moments after a close loss",
Action: ActionResultTie,
Target: TargetBoth,
Expand All @@ -68,11 +70,11 @@ var (
Category: CategoryDefense,
Type: TypePassive,
Trigger: TriggerConditionAfterRoll,
Effect: "If last duel was a tie, gain +1 to roll",
Effect: "If last duel was a tie, gain +2 to roll",
Intent: "Turn the tides on opponent momentum",
Action: ActionIncrease,
Target: TargetSelf,
Amount: 1,
Amount: 2,
},
SecondChance: {
Name: SecondChance,
Expand All @@ -83,14 +85,14 @@ var (
Intent: "Stabilize critical moments after a miss or tie.",
Action: ActionReRoll,
Target: TargetSelf,
Amount: 1,
Amount: 0,
},
LastStand: {
Name: LastStand,
Category: CategoryDefense,
Type: TypeActive,
Type: TypePassive,
Trigger: TriggerConditionAfterRoll,
Effect: "If last duel was a loss, and only 1 player remains in the lane, +2 to roll",
Effect: "If last duel was a loss, add +2 to roll",
Intent: "Slow down snowball effects.",
Action: ActionIncrease,
Target: TargetSelf,
Expand All @@ -102,18 +104,18 @@ var (
Hamstring: {
Name: Hamstring,
Category: CategorySabotage,
Type: TypeActive,
Type: TypePassive,
Trigger: TriggerConditionAfterRoll,
Effect: "Opponent gets -1 to their roll total this duel.",
Effect: "Opponent gets -2 to their roll total this duel.",
Intent: "Defensive denial and tempo slowdown.",
Action: ActionDecrease,
Target: TargetOpponent,
Amount: 1,
Amount: 2,
},
PocketSand: {
Name: PocketSand,
Category: CategorySabotage,
Type: TypeActive,
Type: TypePassive,
Trigger: TriggerConditionBeforeRoll,
Effect: "Cancel the opponent's declared Pre-roll augment.",
Intent: "Anti-pattern counterplay.",
Expand All @@ -124,9 +126,9 @@ var (
PoisonEdge: {
Name: PoisonEdge,
Category: CategorySabotage,
Type: TypeActive,
Type: TypePassive,
Trigger: TriggerConditionAfterRoll,
Effect: "If last duel was a loss, and only 1 player remains in the lane, +2 to roll",
Effect: "If last duel was a loss, opponent has -2 to roll",
Intent: "Slow down snowball effects.",
Action: ActionIncrease,
Target: TargetSelf,
Expand Down Expand Up @@ -157,6 +159,17 @@ var (
Target: TargetSelf,
Amount: 0,
},
CanceledAugment: {
Name: CanceledAugment,
Category: CategoryNoOp,
Type: TypePassive,
Trigger: TriggerConditionBeforeRoll,
Effect: "Canceled augment.",
Intent: "Augment was affected by an opponent.",
Action: ActionNoOp,
Target: TargetSelf,
Amount: 0,
},
}

_repositories = []map[Name]Effect{
Expand Down
2 changes: 1 addition & 1 deletion internal/augments/augments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestGet(t *testing.T) {
odize.AssertTrue(t, ok)
odize.AssertEqual(t, TwistOfFate, effect.Name)
odize.AssertEqual(t, ActionAddDie, effect.Action)
odize.AssertEqual(t, 1, effect.Amount)
odize.AssertEqual(t, 0, effect.Amount)
}).
Test("should return empty effect and false for non-existent token", func(t *testing.T) {
effect, ok := Get(Name("NonExistent"))
Expand Down
3 changes: 2 additions & 1 deletion internal/augments/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type Name string

const (
NoAugment Name = "No Augment"
CanceledAugment Name = "Canceled Augment"
TwistOfFate Name = "Twist of Fate"
SecondChance Name = "Second Chance"
Overpower Name = "Overpower"
Expand All @@ -15,7 +16,7 @@ const (
IceInVeins Name = "Ice in Veins"
Brace Name = "Brace"
Fortify Name = "Fortify"
PoisonEdge Name = "Critical Hit"
PoisonEdge Name = "Poison Edge"
)

type Action string
Expand Down
6 changes: 6 additions & 0 deletions internal/database/queries/team.sql
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ SELECT * FROM coaches WHERE is_human = false;
-- name: CreateCoach :one
INSERT INTO coaches (name, persona, is_human, is_default) VALUES (?, ?, ?, ?) RETURNING *;

-- name: UpdateCoach :exec
UPDATE coaches
SET name = ?,
persona = ?
WHERE id = ?;

-- name: DeleteCoach :exec
DELETE FROM coaches WHERE id = ?;

Expand Down
18 changes: 18 additions & 0 deletions internal/database/team.sql.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 61 additions & 38 deletions internal/games/dice_roll.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package games

import (
"math/rand/v2"
"slices"

"github.com/code-gorilla-au/rush/internal/augments"
)
Expand All @@ -12,7 +13,7 @@ func DiceRoll() int {
}

type TeamDecisionInput struct {
activeAugment augments.Effect
triggeredAugment augments.Name
passivesAugments []augments.Effect
player int64
roll int
Expand All @@ -26,28 +27,6 @@ type DecisionInput struct {

type DecisionEngineFunc func(input DecisionInput) DecisionInput

func RuleTwistOfFate(input DecisionInput) DecisionInput {
return ruleTwistOfFate(input, DiceRoll)
}

func ruleTwistOfFate(input DecisionInput, roll RollFn) DecisionInput {
if input.teamB.activeAugment.Name == augments.TwistOfFate {
secondRoll := roll()
if input.teamB.roll < secondRoll {
input.teamB.roll = secondRoll
}
}

if input.teamA.activeAugment.Name == augments.TwistOfFate {
secondRoll := roll()
if input.teamA.roll < secondRoll {
input.teamA.roll = secondRoll
}
}

return input
}

type Engine struct {
beforeRoll []DecisionEngineFunc
afterRoll []DecisionEngineFunc
Expand All @@ -57,12 +36,25 @@ type Engine struct {

func NewDecisionEngine() *Engine {
return &Engine{
beforeRoll: []DecisionEngineFunc{},
beforeRoll: []DecisionEngineFunc{
RulePocketSand,
},
afterRoll: []DecisionEngineFunc{
RuleTwistOfFate,
RuleMomentumSurge,
RulePrecisionStrike,
RuleOverPower,
RuleFortify,
RuleSecondChance,
RuleLastStand,
RuleHamstring,
RulePoisonEdge,
},
afterAugments: []DecisionEngineFunc{
RuleBrace,
RuleIceInVeins,
},
afterAugments: []DecisionEngineFunc{},
rollFn: DiceRoll,
rollFn: DiceRoll,
}
}

Expand All @@ -86,29 +78,60 @@ func (e *Engine) Run(input DecisionInput) DuelResult {
}

func makeDecision(input DecisionInput) DuelResult {
teamA := PlayerRoll{
Player: input.teamA.player,
Roll: input.teamA.roll,
TriggeredAugment: input.teamA.triggeredAugment,
}
teamB := PlayerRoll{
Player: input.teamB.player,
Roll: input.teamB.roll,
TriggeredAugment: input.teamB.triggeredAugment,
}

if input.teamA.roll == input.teamB.roll {

return DuelResult{
Player: 0,
Outcome: Draw,
Roll: 0,
RollDelta: 0,
Player: 0,
Outcome: Draw,
Roll: 0,
RollDelta: 0,
TriggeredAugment: augments.NoAugment,
TeamA: teamA,
TeamB: teamB,
}
}

if input.teamA.roll > input.teamB.roll {
return DuelResult{
Player: input.teamA.player,
Outcome: TeamA,
Roll: input.teamA.roll,
RollDelta: input.teamA.roll - input.teamB.roll,
Player: input.teamA.player,
Outcome: TeamA,
Roll: input.teamA.roll,
RollDelta: input.teamA.roll - input.teamB.roll,
TriggeredAugment: input.teamA.triggeredAugment,
TeamA: teamA,
TeamB: teamB,
}
}

return DuelResult{
Player: input.teamB.player,
Outcome: TeamB,
Roll: input.teamB.roll,
RollDelta: input.teamB.roll - input.teamA.roll,
Player: input.teamB.player,
Outcome: TeamB,
Roll: input.teamB.roll,
RollDelta: input.teamB.roll - input.teamA.roll,
TriggeredAugment: input.teamB.triggeredAugment,
TeamA: teamA,
TeamB: teamB,
}

}

func canTriggerAugment(activeAugment augments.Name, list []augments.Effect, name augments.Name) bool {
noActiveAugment := activeAugment == augments.NoAugment || activeAugment == ""

hasAugmentInList := slices.ContainsFunc(list, func(e augments.Effect) bool {
return e.Name == name
})

return noActiveAugment && hasAugmentInList
}
Loading
Loading