DEV 1364: Allow solo games with all rulesets through the CLI (#80)

* add support for automatic solo through RulesetBuilder

* allow solo games with all modes in CLI
This commit is contained in:
Rob O'Dwyer 2022-06-08 15:45:20 -07:00 committed by GitHub
parent 426da8ac5e
commit 25dc404493
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 7 deletions

View file

@ -133,7 +133,11 @@ func (gameState *GameState) initialize() {
} }
// Build ruleset from settings // Build ruleset from settings
ruleset := rules.NewRulesetBuilder().WithSeed(gameState.Seed).WithParams(gameState.settings).Ruleset() ruleset := rules.NewRulesetBuilder().
WithSeed(gameState.Seed).
WithParams(gameState.settings).
WithSolo(len(gameState.URLs) < 2).
Ruleset()
gameState.ruleset = ruleset gameState.ruleset = ruleset
// Initialize snake states as empty until we can ping the snake URLs // Initialize snake states as empty until we can ping the snake URLs

View file

@ -79,6 +79,7 @@ type rulesetBuilder struct {
params map[string]string // game customisation parameters params map[string]string // game customisation parameters
seed int64 // used for random events in games seed int64 // used for random events in games
rand Rand // used for random number generation rand Rand // used for random number generation
solo bool // if true, only 1 alive snake is required to keep the game from ending
} }
// NewRulesetBuilder returns an instance of a builder for the Ruleset types. // NewRulesetBuilder returns an instance of a builder for the Ruleset types.
@ -118,6 +119,12 @@ func (rb *rulesetBuilder) WithRand(rand Rand) *rulesetBuilder {
return rb return rb
} }
// WithSolo sets whether the ruleset is a solo game.
func (rb *rulesetBuilder) WithSolo(value bool) *rulesetBuilder {
rb.solo = value
return rb
}
// Ruleset constructs a customised ruleset using the parameters passed to the builder. // Ruleset constructs a customised ruleset using the parameters passed to the builder.
func (rb rulesetBuilder) Ruleset() PipelineRuleset { func (rb rulesetBuilder) Ruleset() PipelineRuleset {
name, ok := rb.params[ParamGameType] name, ok := rb.params[ParamGameType]
@ -125,20 +132,28 @@ func (rb rulesetBuilder) Ruleset() PipelineRuleset {
name = GameTypeStandard name = GameTypeStandard
} }
var stages []string
if rb.solo {
stages = append(stages, StageGameOverSoloSnake)
} else {
stages = append(stages, StageGameOverStandard)
}
switch name { switch name {
case GameTypeStandard: case GameTypeStandard:
return rb.PipelineRuleset(name, NewPipeline(standardRulesetStages...)) stages = append(stages, standardRulesetStages[1:]...)
case GameTypeConstrictor: case GameTypeConstrictor:
return rb.PipelineRuleset(name, NewPipeline(constrictorRulesetStages...)) stages = append(stages, constrictorRulesetStages[1:]...)
case GameTypeRoyale: case GameTypeRoyale:
return rb.PipelineRuleset(name, NewPipeline(royaleRulesetStages...)) stages = append(stages, royaleRulesetStages[1:]...)
case GameTypeSolo: case GameTypeSolo:
return rb.PipelineRuleset(name, NewPipeline(soloRulesetStages...)) stages = soloRulesetStages
case GameTypeWrapped: case GameTypeWrapped:
return rb.PipelineRuleset(name, NewPipeline(wrappedRulesetStages...)) stages = append(stages, wrappedRulesetStages[1:]...)
default: default:
return rb.PipelineRuleset(name, NewPipeline(standardRulesetStages...)) stages = append(stages, standardRulesetStages[1:]...)
} }
return rb.PipelineRuleset(name, NewPipeline(stages...))
} }
// PipelineRuleset provides an implementation of the Ruleset using a pipeline with a name. // PipelineRuleset provides an implementation of the Ruleset using a pipeline with a name.

View file

@ -1,6 +1,7 @@
package rules_test package rules_test
import ( import (
"fmt"
"testing" "testing"
"github.com/BattlesnakeOfficial/rules" "github.com/BattlesnakeOfficial/rules"
@ -138,6 +139,94 @@ func TestRulesetBuilder(t *testing.T) {
} }
} }
func TestRulesetBuilderGameOver(t *testing.T) {
settings := rules.Settings{
RoyaleSettings: rules.RoyaleSettings{
ShrinkEveryNTurns: 12,
},
}
moves := []rules.SnakeMove{
{ID: "1", Move: "up"},
}
boardState := rules.NewBoardState(7, 7)
boardState.Snakes = append(boardState.Snakes, rules.Snake{
ID: "1",
Body: []rules.Point{
{X: 3, Y: 3},
{X: 3, Y: 3},
{X: 3, Y: 3},
},
Health: 100,
})
tests := []struct {
gameType string
solo bool
gameOver bool
}{
{
gameType: rules.GameTypeStandard,
solo: false,
gameOver: true,
},
{
gameType: rules.GameTypeConstrictor,
solo: false,
gameOver: true,
},
{
gameType: rules.GameTypeRoyale,
solo: false,
gameOver: true,
},
{
gameType: rules.GameTypeWrapped,
solo: false,
gameOver: true,
},
{
gameType: rules.GameTypeSolo,
solo: false,
gameOver: false,
},
{
gameType: rules.GameTypeStandard,
solo: true,
gameOver: false,
},
{
gameType: rules.GameTypeConstrictor,
solo: true,
gameOver: false,
},
{
gameType: rules.GameTypeRoyale,
solo: true,
gameOver: false,
},
{
gameType: rules.GameTypeWrapped,
solo: true,
gameOver: false,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v_%v", test.gameType, test.solo), func(t *testing.T) {
rsb := rules.NewRulesetBuilder().WithParams(map[string]string{
rules.ParamGameType: test.gameType,
}).WithSolo(test.solo)
ruleset := rsb.Ruleset()
gameOver, _, err := ruleset.Execute(boardState, settings, moves)
require.NoError(t, err)
require.Equal(t, test.gameOver, gameOver)
})
}
}
func TestStageFuncContract(t *testing.T) { func TestStageFuncContract(t *testing.T) {
//nolint:gosimple //nolint:gosimple
var stage rules.StageFunc var stage rules.StageFunc