Skip to content

Commit cf54709

Browse files
committed
Refactor Block steps for BB2025
As there has been a number of changes in how blocks are handled between BB2020 and BB2025, it was easier to just split the two implementations. This is the first step that add all the building blocks for setting up a normal block and preparing the way for Multiple Block. The only user visible change should be that asking for Follow Up is now being asked a bit sooner (per the rules). We still need to follow up before rolling for injury when pushing people into the crowd. This commit will break Multiple Block support in BB2025, but it wasn't done in BB2020 either, so that is acceptable.
1 parent 654256d commit cf54709

File tree

65 files changed

+5020
-943
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+5020
-943
lines changed

docs/bb2025/rules-faq-bb2025.md

Lines changed: 450 additions & 1 deletion
Large diffs are not rendered by default.

docs/bb2025/todo-base-rules-bb2025.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ book.
422422
- [x] Own player chain pushed into crowd
423423
- [x] Own Player with ball pushed into crowd -> throwin -> turnover
424424
- [x] Player with ball pushed into crowd - > throwin
425+
- [ ] Must choose to follow up before rolling any dice, including injury from Crowd.
425426
- [ ] Chain Push Attacker away
426427
- [ ] Chain Push Defender away
427428
- [ ] Chain Push infinite circle

modules/fumbbl-net/src/commonMain/kotlin/com/jervisffb/fumbbl/net/adapter/impl/InjuryRollMapper.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import com.jervisffb.engine.actions.DirectionSelected
77
import com.jervisffb.engine.actions.SelectDirection
88
import com.jervisffb.engine.model.Game
99
import com.jervisffb.engine.rules.Rules
10-
import com.jervisffb.engine.rules.bb2025.procedures.actions.block.BB2025PushStepInitialMoveSequence
10+
import com.jervisffb.engine.rules.bb2020.procedures.actions.block.BB2020PushStepInitialMoveSequence
1111
import com.jervisffb.engine.rules.common.procedures.tables.injury.ArmourRoll
1212
import com.jervisffb.engine.rules.common.procedures.tables.injury.CasualtyRoll
1313
import com.jervisffb.engine.rules.common.procedures.tables.injury.InjuryRoll
@@ -42,10 +42,10 @@ object InjuryRollMapper: CommandActionMapper {
4242
newActions.add(
4343
action = { state: Game, rules: Rules ->
4444
// Any of them will push the player of the field
45-
val action = BB2025PushStepInitialMoveSequence.SelectPushDirection.getAvailableActions(state, rules).single() as SelectDirection
45+
val action = BB2020PushStepInitialMoveSequence.SelectPushDirection.getAvailableActions(state, rules).single() as SelectDirection
4646
DirectionSelected(action.directions.random())
4747
},
48-
expectedNode = BB2025PushStepInitialMoveSequence.SelectPushDirection
48+
expectedNode = BB2020PushStepInitialMoveSequence.SelectPushDirection
4949
)
5050
}
5151

modules/fumbbl-net/src/commonMain/kotlin/com/jervisffb/fumbbl/net/adapter/impl/block/FollowUpMapper.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.jervisffb.fumbbl.net.adapter.impl.blitz
33
import com.jervisffb.engine.actions.Cancel
44
import com.jervisffb.engine.actions.Confirm
55
import com.jervisffb.engine.model.Game
6-
import com.jervisffb.engine.rules.bb2025.procedures.actions.block.BB2025PushStepInitialMoveSequence
6+
import com.jervisffb.engine.rules.bb2020.procedures.actions.block.BB2020PushStepInitialMoveSequence
77
import com.jervisffb.fumbbl.net.adapter.CommandActionMapper
88
import com.jervisffb.fumbbl.net.adapter.JervisActionHolder
99
import com.jervisffb.fumbbl.net.adapter.add
@@ -48,9 +48,9 @@ object FollowUpMapper: CommandActionMapper {
4848
(previousPreviousCommand is GameSetDialogParameter) &&
4949
previousPreviousCommand.value?.dialogId == com.jervisffb.fumbbl.net.model.DialogId.FOLLOWUP_CHOICE
5050
) {
51-
newActions.add(Confirm, BB2025PushStepInitialMoveSequence.ChooseToFollowUp)
51+
newActions.add(Confirm, BB2020PushStepInitialMoveSequence.DecideToFollowUp)
5252
} else {
53-
newActions.add(Cancel, BB2025PushStepInitialMoveSequence.ChooseToFollowUp)
53+
newActions.add(Cancel, BB2020PushStepInitialMoveSequence.DecideToFollowUp)
5454
}
5555
}
5656
}

modules/fumbbl-net/src/commonMain/kotlin/com/jervisffb/fumbbl/net/adapter/impl/block/PushbackMapper.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.jervisffb.fumbbl.net.adapter.impl.blitz
22

33
import com.jervisffb.engine.actions.DirectionSelected
44
import com.jervisffb.engine.model.Game
5-
import com.jervisffb.engine.rules.bb2025.procedures.actions.block.BB2025PushStepInitialMoveSequence
5+
import com.jervisffb.engine.rules.bb2020.procedures.actions.block.BB2020PushStepInitialMoveSequence
66
import com.jervisffb.fumbbl.net.adapter.CommandActionMapper
77
import com.jervisffb.fumbbl.net.adapter.JervisActionHolder
88
import com.jervisffb.fumbbl.net.adapter.add
@@ -48,7 +48,7 @@ object PushbackMapper: CommandActionMapper {
4848
} as FieldModelRemovePushbackSquare
4949
newActions.add(
5050
DirectionSelected(cmd.value.direction.transformToJervisDirection()),
51-
BB2025PushStepInitialMoveSequence.SelectPushDirection
51+
BB2020PushStepInitialMoveSequence.SelectPushDirection
5252
)
5353
}
5454
}

modules/fumbbl-net/src/commonMain/kotlin/com/jervisffb/fumbbl/net/adapter/impl/block/PushbackUsingSideStepMapper.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.jervisffb.fumbbl.net.adapter.impl.blitz
33
import com.jervisffb.engine.actions.Confirm
44
import com.jervisffb.engine.actions.FieldSquareSelected
55
import com.jervisffb.engine.model.Game
6-
import com.jervisffb.engine.rules.bb2025.procedures.actions.block.BB2025PushStepInitialMoveSequence
6+
import com.jervisffb.engine.rules.bb2020.procedures.actions.block.BB2020PushStepInitialMoveSequence
77
import com.jervisffb.fumbbl.net.adapter.CommandActionMapper
88
import com.jervisffb.fumbbl.net.adapter.JervisActionHolder
99
import com.jervisffb.fumbbl.net.adapter.add
@@ -39,7 +39,7 @@ object PushbackUsingSideStepMapper: CommandActionMapper {
3939
} as FieldModelRemovePushbackSquare
4040
val target = cmd.value.coordinate
4141

42-
newActions.add(Confirm, BB2025PushStepInitialMoveSequence.DecideToUseSidestep)
43-
newActions.add(FieldSquareSelected(target.x, target.y), BB2025PushStepInitialMoveSequence.SelectPushDirection)
42+
newActions.add(Confirm, BB2020PushStepInitialMoveSequence.DecideToUseSidestep)
43+
newActions.add(FieldSquareSelected(target.x, target.y), BB2020PushStepInitialMoveSequence.SelectPushDirection)
4444
}
4545
}

modules/jervis-engine/src/commonMain/kotlin/com/jervisffb/engine/model/context/MultipleBlockContext.kt renamed to modules/jervis-engine/src/commonMain/kotlin/com/jervisffb/engine/model/context/BB2020MultipleBlockContext.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ data class MultipleBlockDiceRoll(
134134
* it exposes an API that makes it possible to access rolls using list
135135
* indexes.
136136
*/
137-
data class MultipleBlockContext(
137+
data class BB2020MultipleBlockContext(
138138
val attacker: Player,
139139
val defender1: Player? = null,
140140
val defender2: Player? = null,
@@ -187,7 +187,7 @@ data class MultipleBlockContext(
187187
}
188188
}
189189

190-
fun copyAndUpdateHasAcceptedResult(index: Int, hasAcceptedResult: Boolean): MultipleBlockContext {
190+
fun copyAndUpdateHasAcceptedResult(index: Int, hasAcceptedResult: Boolean): BB2020MultipleBlockContext {
191191
return when (index) {
192192
0 -> copy(roll1 = roll1!!.copyAndSetHasAcceptedResult(hasAcceptedResult))
193193
1 -> copy(roll2 = roll2!!.copyAndSetHasAcceptedResult(hasAcceptedResult))
@@ -284,8 +284,8 @@ data class MultipleBlockContext(
284284
fun addInjuryReferenceForPlayer(player: Player, injuryContext: RiskingInjuryContext): Command {
285285
return when (player) {
286286
attacker -> AddContextListItem(attackerInjuryContext, injuryContext)
287-
defender1 -> SetContextProperty(MultipleBlockContext::defender1InjuryContext, this, injuryContext)
288-
defender2 -> SetContextProperty(MultipleBlockContext::defender2InjuryContext, this, injuryContext)
287+
defender1 -> SetContextProperty(BB2020MultipleBlockContext::defender1InjuryContext, this, injuryContext)
288+
defender2 -> SetContextProperty(BB2020MultipleBlockContext::defender2InjuryContext, this, injuryContext)
289289
else -> throw IllegalArgumentException("Invalid player: $player")
290290
}
291291
}
@@ -294,7 +294,7 @@ data class MultipleBlockContext(
294294
* Sets the block type for the current active defender.
295295
* This also c
296296
*/
297-
fun copyAndSetBlockTypeForActiveDefender(type: BlockType): MultipleBlockContext {
297+
fun copyAndSetBlockTypeForActiveDefender(type: BlockType): BB2020MultipleBlockContext {
298298
val defender = when (activeDefender) {
299299
0 -> defender1!!
300300
1 -> defender2!!
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package com.jervisffb.engine.model.context
2+
3+
import com.jervisffb.engine.actions.RerollOptionSelected
4+
import com.jervisffb.engine.commands.Command
5+
import com.jervisffb.engine.commands.context.AddContextListItem
6+
import com.jervisffb.engine.commands.context.SetContextProperty
7+
import com.jervisffb.engine.fsm.Procedure
8+
import com.jervisffb.engine.model.Game
9+
import com.jervisffb.engine.model.Player
10+
import com.jervisffb.engine.model.TurnOver
11+
import com.jervisffb.engine.model.locations.FieldCoordinate
12+
import com.jervisffb.engine.rules.DiceRollType
13+
import com.jervisffb.engine.rules.bb2020.procedures.actions.block.standard.StandardBlockApplyResult
14+
import com.jervisffb.engine.rules.bb2020.procedures.actions.block.standard.StandardBlockRerollDice
15+
import com.jervisffb.engine.rules.bb2020.procedures.actions.block.standard.StandardBlockRollDice
16+
import com.jervisffb.engine.rules.common.actions.BlockType
17+
import com.jervisffb.engine.rules.common.actions.BlockType.CHAINSAW
18+
import com.jervisffb.engine.rules.common.actions.BlockType.MULTIPLE_BLOCK
19+
import com.jervisffb.engine.rules.common.actions.BlockType.PROJECTILE_VOMIT
20+
import com.jervisffb.engine.rules.common.actions.BlockType.STAB
21+
import com.jervisffb.engine.rules.common.actions.BlockType.STANDARD
22+
import com.jervisffb.engine.rules.common.procedures.tables.injury.RiskingInjuryContext
23+
/**
24+
* Context containing state related to doing a Multiple Block.
25+
*
26+
* Note, this context has been flattened to make it easier to update, but
27+
* it exposes an API that makes it possible to access rolls using list
28+
* indexes.
29+
*/
30+
data class BB2025MultipleBlockContext(
31+
val attacker: Player,
32+
val defender1: Player? = null,
33+
val defender2: Player? = null,
34+
val actionAborted: Boolean = true,
35+
// Rolls for the two blocks
36+
val roll1: MultipleBlockDiceRoll? = null,
37+
val roll2: MultipleBlockDiceRoll? = null,
38+
// Tracks the index of which defender is currently in focus. If set, it must either be 0 or 1.
39+
var activeDefender: Int? = null,
40+
// If the blocks result in a push, all the data related to the push is stored here.
41+
var defender1PushChain: PushContext? = null,
42+
var defender2PushChain: PushContext? = null,
43+
// Tracks the ball for those players where it needs to bounce. Set back to `null` once the ball has bounced
44+
val defender1BallsHandled: Boolean = false,
45+
val defender2BallsHandled: Boolean = false,
46+
val attackerBallHandled: Boolean = false,
47+
// Set to true, if the player should be Knocked Down, when we come to that phase of Multiple Block
48+
val attackerKnockedDown: Boolean = false,
49+
val defender1KnockedDown: Boolean = false,
50+
val defender2KnockedDown: Boolean = false,
51+
// Set if any of the players involved received an injury. The attacker might suffer an
52+
// injury from both blocks
53+
val attackerInjuryContext: MutableList<RiskingInjuryContext> = mutableListOf(),
54+
var defender1InjuryContext: RiskingInjuryContext? = null,
55+
var defender2InjuryContext: RiskingInjuryContext? = null,
56+
// Set to true, if a turnover happened during the first block.
57+
var postponeTurnOver: TurnOver? = null,
58+
// Player starting locations (as they might leave the field due to injuries)
59+
val attackerLocation: FieldCoordinate = attacker.coordinates,
60+
val defender1Location: FieldCoordinate? = null,
61+
val defender2Location: FieldCoordinate? = null,
62+
): ProcedureContext {
63+
64+
val rolls: List<MultipleBlockDiceRoll>
65+
get() = listOfNotNull(roll1, roll2)
66+
67+
operator fun get(index: Int): MultipleBlockDiceRoll {
68+
return when (index) {
69+
0 -> roll1!!
70+
1 -> roll2!!
71+
else -> throw IllegalArgumentException("Invalid index: $index")
72+
}
73+
}
74+
75+
fun getActiveRerollType(): BlockType {
76+
return get(activeDefender!!).type
77+
}
78+
79+
fun updateRollContext(index: Int, updatedRollContext: ProcedureContext): Command {
80+
return when (index) {
81+
0 -> SetContextProperty(MultipleBlockDiceRoll::rollContext, roll1!!, updatedRollContext)
82+
1 -> SetContextProperty(MultipleBlockDiceRoll::rollContext, roll2!!, updatedRollContext)
83+
else -> throw IllegalArgumentException("Invalid roll index: $index")
84+
}
85+
}
86+
87+
fun copyAndUpdateHasAcceptedResult(index: Int, hasAcceptedResult: Boolean): BB2025MultipleBlockContext {
88+
return when (index) {
89+
0 -> copy(roll1 = roll1!!.copyAndSetHasAcceptedResult(hasAcceptedResult))
90+
1 -> copy(roll2 = roll2!!.copyAndSetHasAcceptedResult(hasAcceptedResult))
91+
else -> throw IllegalArgumentException("Invalid index: $index")
92+
}
93+
}
94+
95+
/**
96+
* Creates a [UseRerollContext] for currently active Multiple Block Action its reroll type
97+
*/
98+
fun createRerollContext(state: Game, action: RerollOptionSelected): UseRerollContext {
99+
return when (getActiveRerollType()) {
100+
BlockType.BREATHE_FIRE -> TODO()
101+
CHAINSAW -> TODO()
102+
MULTIPLE_BLOCK -> TODO()
103+
PROJECTILE_VOMIT -> TODO()
104+
STAB -> TODO()
105+
STANDARD -> UseRerollContext(DiceRollType.BLOCK, action.getRerollSource(state))
106+
}
107+
}
108+
109+
fun getRollDiceProcedure(): Procedure {
110+
return when (getActiveRerollType()) {
111+
BlockType.BREATHE_FIRE -> TODO()
112+
CHAINSAW -> TODO()
113+
MULTIPLE_BLOCK -> TODO()
114+
PROJECTILE_VOMIT -> TODO()
115+
STAB -> TODO()
116+
STANDARD -> StandardBlockRollDice
117+
}
118+
}
119+
/**
120+
* Returns the Procedure used to reroll dice for the given block type.
121+
*/
122+
fun getRerollDiceProcedure(): Procedure {
123+
return when (getActiveRerollType()) {
124+
BlockType.BREATHE_FIRE -> TODO()
125+
CHAINSAW -> TODO()
126+
MULTIPLE_BLOCK -> TODO()
127+
PROJECTILE_VOMIT -> TODO()
128+
STAB -> TODO()
129+
STANDARD -> StandardBlockRerollDice
130+
}
131+
}
132+
133+
/**
134+
* Returns the procedure responsible for applying a active block type
135+
*/
136+
fun getResolveBlockResultProcedure(): Procedure {
137+
return when (getActiveRerollType()) {
138+
BlockType.BREATHE_FIRE -> TODO()
139+
CHAINSAW -> TODO()
140+
MULTIPLE_BLOCK -> TODO()
141+
PROJECTILE_VOMIT -> TODO()
142+
STAB -> TODO()
143+
STANDARD -> StandardBlockApplyResult
144+
}
145+
}
146+
147+
/**
148+
* Calling this method will retrieve the roll context for the given
149+
* block type and replace the active [MultipleBlockDiceRoll.rollContext]
150+
* with it.
151+
*/
152+
fun updateWithLatestBlockTypeContext(state: Game): Command {
153+
val updatedContext = when (getActiveRerollType()) {
154+
BlockType.BREATHE_FIRE -> TODO()
155+
CHAINSAW -> TODO()
156+
MULTIPLE_BLOCK -> TODO()
157+
PROJECTILE_VOMIT -> TODO()
158+
STAB -> TODO()
159+
STANDARD -> state.getContext<BlockContext>()
160+
}
161+
return updateRollContext(activeDefender!!, updatedContext)
162+
}
163+
164+
/**
165+
* Remove the provided player from the context. Also unset it from being
166+
* active if it was set there.
167+
*
168+
* Will throw exception if player was not found
169+
*/
170+
fun copyAndUnsetDefender(player: Player): ProcedureContext {
171+
return when (player) {
172+
defender1 -> copy(defender1 = null, activeDefender = if (activeDefender == 0) null else 0)
173+
defender2 -> copy(defender2 = null, activeDefender = if (activeDefender == 1) null else 1)
174+
else -> throw IllegalArgumentException("Invalid defender: $player")
175+
}
176+
}
177+
178+
/**
179+
* Return the commands needed to add an Injury to the injury pool.
180+
*/
181+
fun addInjuryReferenceForPlayer(player: Player, injuryContext: RiskingInjuryContext): Command {
182+
return when (player) {
183+
attacker -> AddContextListItem(attackerInjuryContext, injuryContext)
184+
defender1 -> SetContextProperty(BB2025MultipleBlockContext::defender1InjuryContext, this, injuryContext)
185+
defender2 -> SetContextProperty(BB2025MultipleBlockContext::defender2InjuryContext, this, injuryContext)
186+
else -> throw IllegalArgumentException("Invalid player: $player")
187+
}
188+
}
189+
190+
/**
191+
* Sets the block type for the current active defender.
192+
* This also c
193+
*/
194+
fun copyAndSetBlockTypeForActiveDefender(type: BlockType): BB2025MultipleBlockContext {
195+
val defender = when (activeDefender) {
196+
0 -> defender1!!
197+
1 -> defender2!!
198+
else -> throw IllegalStateException("Invalid active defender")
199+
}
200+
201+
val context = when (type) {
202+
BlockType.BREATHE_FIRE -> TODO()
203+
CHAINSAW -> TODO()
204+
MULTIPLE_BLOCK -> TODO()
205+
PROJECTILE_VOMIT -> TODO()
206+
STAB -> TODO()
207+
STANDARD -> BlockContext(
208+
attacker = attacker,
209+
defender = defender,
210+
isUsingMultiBlock = true
211+
)
212+
}
213+
214+
return when (activeDefender) {
215+
0 -> copy(roll1 = MultipleBlockDiceRoll(type, context))
216+
1 -> copy(roll2 = MultipleBlockDiceRoll(type, context))
217+
else -> throw IllegalArgumentException("Invalid active defender: $activeDefender")
218+
}
219+
}
220+
221+
fun getActiveDefender(): Player? {
222+
return when (activeDefender) {
223+
0 -> return defender1!!
224+
1 -> return defender2!!
225+
else -> null
226+
}
227+
}
228+
229+
/**
230+
* Returns the block context for the currently active defender.
231+
* Note, it is the context stored in _this_ context that is returned,
232+
* and not the one stored globally.
233+
*
234+
* See [updateWithLatestBlockTypeContext] for that.
235+
*/
236+
fun getContextForCurrentBlock(): ProcedureContext {
237+
return when (activeDefender) {
238+
0 -> roll1!!.rollContext
239+
1 -> roll2!!.rollContext
240+
else -> throw IllegalArgumentException("Invalid active defender: $activeDefender")
241+
}
242+
}
243+
244+
fun copyAndKnockDownActiveDefender(knockedDown: Boolean): BB2025MultipleBlockContext {
245+
return when (activeDefender) {
246+
0 -> this.copy(defender1KnockedDown = knockedDown)
247+
1 -> this.copy(defender2KnockedDown = knockedDown)
248+
else -> throw IllegalArgumentException("Invalid active defender: $activeDefender")
249+
}
250+
}
251+
}

modules/jervis-engine/src/commonMain/kotlin/com/jervisffb/engine/model/context/BlockContext.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package com.jervisffb.engine.model.context
22

33
import com.jervisffb.engine.actions.DBlockResult
44
import com.jervisffb.engine.model.Player
5-
import com.jervisffb.engine.rules.bb2020.procedures.actions.block.standard.calculateBlockDiceToRoll
65
import com.jervisffb.engine.rules.common.actions.BlockType
76
import com.jervisffb.engine.rules.common.procedures.BlockDieRoll
7+
import com.jervisffb.engine.rules.common.procedures.actions.block.calculateBlockDiceToRoll
88

99
/**
1010
* Wrap temporary data needed to track a "standard block". This can either

modules/jervis-engine/src/commonMain/kotlin/com/jervisffb/engine/model/context/PushContext.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ data class PushContext(
3434
// swallowed a player with a ball. Balls should be added and resolved in
3535
// order.
3636
val looseBalls: MutableList<Ball> = mutableListOf(),
37+
// True, if the last player gets pushed into the crowd.
38+
val pushedIntoTheCrowd: Boolean = false
3739
) : ProcedureContext {
3840

3941
// Tracks if the attacker is using Juggernaut.

0 commit comments

Comments
 (0)