DEV-1096 - add a new "pipeline" concept (#67)
* add a new "pipeline" concept - added new Pipeline type which is a series of stages - added a global registry to facilitate plugin architecture - 100% test coverage * Refactor rulesets to provide and use Pipeline * fix copypasta comments * fix lint for unused method * include game over stages in ruleset pipelines * clean up unused private standard methods * remove unused private methods in squad ruleset * remove unused private methods in royale ruleset * refactor: pipeline clone + return next board state * YAGNI: remove unused Append * refactor: improve stage names * add no-op behavior to stages for initial state * refactor: no-op decision within stage functions * remove misleading comment that isn't true * dont bother checking for init in gameover stages * remove redundant test * refactor: provide a combined ruleset/pipeline type * fix: movement no-op for GameOver check IsGameOver needs to run pipeline, move snakes needs to no-op for that * add test coverage * refactor: improve stage names and use constants * add Error method Support error checking before calling Execute() * update naming to be American style * panic when overwriting stages in global registry * rename "Error" method and improve docs * use testify lib for panic assertion * remove redundant food stage * use ruleset-specific logic for game over checks * re-work Pipeline errors * rework errors again * add defensive check for zero length snake * use old logic which checks current state, not next * add warning about how PipelineRuleset checks for game over
This commit is contained in:
parent
86ef6ad068
commit
d378759d58
18 changed files with 723 additions and 235 deletions
98
pipeline_internal_test.go
Normal file
98
pipeline_internal_test.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package rules
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPipelineRuleset(t *testing.T) {
|
||||
r := StageRegistry{
|
||||
"doesnt_end": mockStageFn(false, nil),
|
||||
"ends": mockStageFn(true, nil),
|
||||
}
|
||||
|
||||
// Name/Error methods
|
||||
p := NewPipelineFromRegistry(r, "404doesntexist")
|
||||
pr := pipelineRuleset{
|
||||
name: "test",
|
||||
pipeline: p,
|
||||
}
|
||||
require.Equal(t, "test", pr.Name())
|
||||
require.Equal(t, ErrorStageNotFound, pr.Err())
|
||||
|
||||
// test game over when it does end
|
||||
p = NewPipelineFromRegistry(r, "doesnt_end", "ends")
|
||||
pr = pipelineRuleset{
|
||||
name: "test",
|
||||
pipeline: p,
|
||||
}
|
||||
ended, err := pr.IsGameOver(&BoardState{})
|
||||
require.NoError(t, err)
|
||||
require.True(t, ended)
|
||||
|
||||
// Test game over when it doesn't end
|
||||
p = NewPipelineFromRegistry(r, "doesnt_end")
|
||||
pr = pipelineRuleset{
|
||||
name: "test",
|
||||
pipeline: p,
|
||||
}
|
||||
ended, err = pr.IsGameOver(&BoardState{})
|
||||
require.NoError(t, err)
|
||||
require.False(t, ended)
|
||||
|
||||
// test a stage that adds food, except on initialization
|
||||
r.RegisterPipelineStage("add_food", func(bs *BoardState, s Settings, sm []SnakeMove) (bool, error) {
|
||||
if IsInitialization(bs, s, sm) {
|
||||
return false, nil
|
||||
}
|
||||
bs.Food = append(bs.Food, Point{X: 0, Y: 0})
|
||||
return false, nil
|
||||
})
|
||||
b := &BoardState{}
|
||||
p = NewPipelineFromRegistry(r, "add_food")
|
||||
pr = pipelineRuleset{
|
||||
name: "test",
|
||||
pipeline: p,
|
||||
}
|
||||
require.Empty(t, b.Food)
|
||||
b, err = pr.ModifyInitialBoardState(b)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, b.Food, "food should not be added on initialisation phase")
|
||||
b, err = pr.CreateNextBoardState(b, mockSnakeMoves())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, b.Food, "fodo should be added now")
|
||||
}
|
||||
|
||||
func TestPipelineGlobals(t *testing.T) {
|
||||
oldReg := globalRegistry
|
||||
globalRegistry = StageRegistry{}
|
||||
|
||||
// ensure that we can register a function without errors
|
||||
RegisterPipelineStage("test", mockStageFn(false, nil))
|
||||
require.Contains(t, globalRegistry, "test")
|
||||
|
||||
// ensure that the global registry panics if you register an existing stage name
|
||||
require.Panics(t, func() {
|
||||
RegisterPipelineStage("test", mockStageFn(false, nil))
|
||||
})
|
||||
RegisterPipelineStage("other", mockStageFn(true, nil)) // otherwise should not panic
|
||||
|
||||
// ensure that we can build a pipeline using the global registry
|
||||
p := NewPipeline("test", "other")
|
||||
require.NotNil(t, p)
|
||||
|
||||
// ensure that it runs okay too
|
||||
ended, next, err := p.Execute(&BoardState{}, Settings{}, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, next)
|
||||
require.True(t, ended)
|
||||
|
||||
globalRegistry = oldReg
|
||||
}
|
||||
|
||||
func mockStageFn(ended bool, err error) StageFunc {
|
||||
return func(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
||||
return ended, err
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue