DEV-1761: New rules API (#118)
* DEV-1761: Clean up Ruleset interface (#115) * remove legacy ruleset types and simplify ruleset interface * remove unnecessary settings argument from Ruleset interface * decouple rules.Settings from client API and store settings as strings * DEV 1761: Add new BoardState and Point fields (#117) * add Point.TTL, Point.Value, GameState and PointState to BoardState * allow maps to access BoardState.GameState,PointState * add PreUpdateBoard and refactor snail_mode with it * fix bug where an extra turn was printed to the console * fix formatting * fix lint errors Co-authored-by: JonathanArns <jonathan.arns@googlemail.com>
This commit is contained in:
parent
639362ef46
commit
82e1999126
50 changed files with 1349 additions and 1610 deletions
|
|
@ -88,7 +88,9 @@ func NewPlayCommand() *cobra.Command {
|
|||
if err := gameState.Initialize(); err != nil {
|
||||
log.ERROR.Fatalf("Error initializing game: %v", err)
|
||||
}
|
||||
gameState.Run()
|
||||
if err := gameState.Run(); err != nil {
|
||||
log.ERROR.Fatalf("Error running game: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +145,6 @@ func (gameState *GameState) Initialize() error {
|
|||
|
||||
// Create settings object
|
||||
gameState.settings = map[string]string{
|
||||
rules.ParamGameType: gameState.GameType,
|
||||
rules.ParamFoodSpawnChance: fmt.Sprint(gameState.FoodSpawnChance),
|
||||
rules.ParamMinimumFood: fmt.Sprint(gameState.MinimumFood),
|
||||
rules.ParamHazardDamagePerTurn: fmt.Sprint(gameState.HazardDamagePerTurn),
|
||||
|
|
@ -155,7 +156,7 @@ func (gameState *GameState) Initialize() error {
|
|||
WithSeed(gameState.Seed).
|
||||
WithParams(gameState.settings).
|
||||
WithSolo(len(gameState.URLs) < 2).
|
||||
Ruleset()
|
||||
NamedRuleset(gameState.GameType)
|
||||
gameState.ruleset = ruleset
|
||||
|
||||
// Initialize snake states as empty until we can ping the snake URLs
|
||||
|
|
@ -173,13 +174,22 @@ func (gameState *GameState) Initialize() error {
|
|||
}
|
||||
|
||||
// Setup and run a full game.
|
||||
func (gameState *GameState) Run() {
|
||||
func (gameState *GameState) Run() error {
|
||||
var gameOver bool
|
||||
var err error
|
||||
|
||||
// Setup local state for snakes
|
||||
gameState.snakeStates = gameState.buildSnakesFromOptions()
|
||||
gameState.snakeStates, err = gameState.buildSnakesFromOptions()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting snake metadata: %w", err)
|
||||
}
|
||||
|
||||
rand.Seed(gameState.Seed)
|
||||
|
||||
boardState := gameState.initializeBoardFromArgs()
|
||||
gameOver, boardState, err := gameState.initializeBoardFromArgs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error initializing board: %w", err)
|
||||
}
|
||||
|
||||
gameExporter := GameExporter{
|
||||
game: gameState.createClientGame(),
|
||||
|
|
@ -209,7 +219,7 @@ func (gameState *GameState) Run() {
|
|||
if gameState.ViewInBrowser {
|
||||
serverURL, err := boardServer.Listen()
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("Error starting HTTP server: %v", err)
|
||||
return fmt.Errorf("Error starting HTTP server: %w", err)
|
||||
}
|
||||
defer boardServer.Shutdown()
|
||||
log.INFO.Printf("Board server listening on %s", serverURL)
|
||||
|
|
@ -233,29 +243,37 @@ func (gameState *GameState) Run() {
|
|||
gameState.printState(boardState)
|
||||
}
|
||||
|
||||
// Export game first, if enabled, so that we capture the request for turn zero.
|
||||
if exportGame {
|
||||
// The output file was designed in a way so that (nearly) every entry is equivalent to a valid API request.
|
||||
// This is meant to help unlock further development of tools such as replaying a saved game by simply copying each line and sending it as a POST request.
|
||||
// There was a design choice to be made here: the difference between SnakeRequest and BoardState is the `you` key.
|
||||
// We could choose to either store the SnakeRequest of each snake OR to omit the `you` key OR fill the `you` key with one of the snakes
|
||||
// In all cases the API request is technically non-compliant with how the actual API request should be.
|
||||
// The third option (filling the `you` key with an arbitrary snake) is the closest to the actual API request that would need the least manipulation to
|
||||
// be adjusted to look like an API call for a specific snake in the game.
|
||||
for _, snakeState := range gameState.snakeStates {
|
||||
snakeRequest := gameState.getRequestBodyForSnake(boardState, snakeState)
|
||||
gameExporter.AddSnakeRequest(snakeRequest)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var endTime time.Time
|
||||
for v := false; !v; v, _ = gameState.ruleset.IsGameOver(boardState) {
|
||||
for !gameOver {
|
||||
if gameState.TurnDuration > 0 {
|
||||
endTime = time.Now().Add(time.Duration(gameState.TurnDuration) * time.Millisecond)
|
||||
}
|
||||
|
||||
// Export game first, if enabled, so that we save the board on turn zero
|
||||
if exportGame {
|
||||
// The output file was designed in a way so that (nearly) every entry is equivalent to a valid API request.
|
||||
// This is meant to help unlock further development of tools such as replaying a saved game by simply copying each line and sending it as a POST request.
|
||||
// There was a design choice to be made here: the difference between SnakeRequest and BoardState is the `you` key.
|
||||
// We could choose to either store the SnakeRequest of each snake OR to omit the `you` key OR fill the `you` key with one of the snakes
|
||||
// In all cases the API request is technically non-compliant with how the actual API request should be.
|
||||
// The third option (filling the `you` key with an arbitrary snake) is the closest to the actual API request that would need the least manipulation to
|
||||
// be adjusted to look like an API call for a specific snake in the game.
|
||||
for _, snakeState := range gameState.snakeStates {
|
||||
snakeRequest := gameState.getRequestBodyForSnake(boardState, snakeState)
|
||||
gameExporter.AddSnakeRequest(snakeRequest)
|
||||
break
|
||||
}
|
||||
gameOver, boardState, err = gameState.createNextBoardState(boardState)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error processing game: %w", err)
|
||||
}
|
||||
|
||||
boardState = gameState.createNextBoardState(boardState)
|
||||
if gameOver {
|
||||
// Stop processing here - because game over is detected at the start of the pipeline, nothing will have changed.
|
||||
break
|
||||
}
|
||||
|
||||
if gameState.ViewMap {
|
||||
gameState.printMap(boardState)
|
||||
|
|
@ -274,14 +292,13 @@ func (gameState *GameState) Run() {
|
|||
if gameState.ViewInBrowser {
|
||||
boardServer.SendEvent(gameState.buildFrameEvent(boardState))
|
||||
}
|
||||
}
|
||||
|
||||
// Export final turn
|
||||
if exportGame {
|
||||
for _, snakeState := range gameState.snakeStates {
|
||||
snakeRequest := gameState.getRequestBodyForSnake(boardState, snakeState)
|
||||
gameExporter.AddSnakeRequest(snakeRequest)
|
||||
break
|
||||
if exportGame {
|
||||
for _, snakeState := range gameState.snakeStates {
|
||||
snakeRequest := gameState.getRequestBodyForSnake(boardState, snakeState)
|
||||
gameExporter.AddSnakeRequest(snakeRequest)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -320,24 +337,26 @@ func (gameState *GameState) Run() {
|
|||
if exportGame {
|
||||
lines, err := gameExporter.FlushToFile(gameState.outputFile)
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("Unable to export game. Reason: %v", err)
|
||||
return fmt.Errorf("Unable to export game: %w", err)
|
||||
}
|
||||
log.INFO.Printf("Wrote %d lines to output file: %s", lines, gameState.OutputPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gameState *GameState) initializeBoardFromArgs() *rules.BoardState {
|
||||
func (gameState *GameState) initializeBoardFromArgs() (bool, *rules.BoardState, error) {
|
||||
snakeIds := []string{}
|
||||
for _, snakeState := range gameState.snakeStates {
|
||||
snakeIds = append(snakeIds, snakeState.ID)
|
||||
}
|
||||
boardState, err := maps.SetupBoard(gameState.gameMap.ID(), gameState.ruleset.Settings(), gameState.Width, gameState.Height, snakeIds)
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("Error Initializing Board State: %v", err)
|
||||
return false, nil, fmt.Errorf("Error initializing BoardState with map: %w", err)
|
||||
}
|
||||
boardState, err = gameState.ruleset.ModifyInitialBoardState(boardState)
|
||||
gameOver, boardState, err := gameState.ruleset.Execute(boardState, nil)
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("Error Initializing Board State: %v", err)
|
||||
return false, nil, fmt.Errorf("Error initializing BoardState with ruleset: %w", err)
|
||||
}
|
||||
|
||||
for _, snakeState := range gameState.snakeStates {
|
||||
|
|
@ -351,12 +370,18 @@ func (gameState *GameState) initializeBoardFromArgs() *rules.BoardState {
|
|||
log.WARN.Printf("Request to %v failed", u.String())
|
||||
}
|
||||
}
|
||||
return boardState
|
||||
return gameOver, boardState, nil
|
||||
}
|
||||
|
||||
func (gameState *GameState) createNextBoardState(boardState *rules.BoardState) *rules.BoardState {
|
||||
stateUpdates := make(chan SnakeState, len(gameState.snakeStates))
|
||||
func (gameState *GameState) createNextBoardState(boardState *rules.BoardState) (bool, *rules.BoardState, error) {
|
||||
// apply PreUpdateBoard before making requests to snakes
|
||||
boardState, err := maps.PreUpdateBoard(gameState.gameMap, boardState, gameState.ruleset.Settings())
|
||||
if err != nil {
|
||||
return false, boardState, fmt.Errorf("Error pre-updating board with game map: %w", err)
|
||||
}
|
||||
|
||||
// get moves from snakes
|
||||
stateUpdates := make(chan SnakeState, len(gameState.snakeStates))
|
||||
if gameState.Sequential {
|
||||
for _, snakeState := range gameState.snakeStates {
|
||||
for _, snake := range boardState.Snakes {
|
||||
|
|
@ -393,19 +418,20 @@ func (gameState *GameState) createNextBoardState(boardState *rules.BoardState) *
|
|||
moves = append(moves, rules.SnakeMove{ID: snakeState.ID, Move: snakeState.LastMove})
|
||||
}
|
||||
|
||||
boardState, err := gameState.ruleset.CreateNextBoardState(boardState, moves)
|
||||
gameOver, boardState, err := gameState.ruleset.Execute(boardState, moves)
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("Error producing next board state: %v", err)
|
||||
return false, boardState, fmt.Errorf("Error updating board state from ruleset: %w", err)
|
||||
}
|
||||
|
||||
boardState, err = maps.UpdateBoard(gameState.gameMap.ID(), boardState, gameState.ruleset.Settings())
|
||||
// apply PostUpdateBoard after ruleset operates on snake moves
|
||||
boardState, err = maps.PostUpdateBoard(gameState.gameMap, boardState, gameState.ruleset.Settings())
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("Error updating board with game map: %v", err)
|
||||
return false, boardState, fmt.Errorf("Error post-updating board with game map: %w", err)
|
||||
}
|
||||
|
||||
boardState.Turn += 1
|
||||
|
||||
return boardState
|
||||
return gameOver, boardState, nil
|
||||
}
|
||||
|
||||
func (gameState *GameState) getSnakeUpdate(boardState *rules.BoardState, snakeState SnakeState) SnakeState {
|
||||
|
|
@ -522,13 +548,13 @@ func (gameState *GameState) createClientGame() client.Game {
|
|||
Ruleset: client.Ruleset{
|
||||
Name: gameState.ruleset.Name(),
|
||||
Version: "cli", // TODO: Use GitHub Release Version
|
||||
Settings: gameState.ruleset.Settings(),
|
||||
Settings: client.ConvertRulesetSettings(gameState.ruleset.Settings()),
|
||||
},
|
||||
Map: gameState.gameMap.ID(),
|
||||
}
|
||||
}
|
||||
|
||||
func (gameState *GameState) buildSnakesFromOptions() map[string]SnakeState {
|
||||
func (gameState *GameState) buildSnakesFromOptions() (map[string]SnakeState, error) {
|
||||
bodyChars := []rune{'■', '⌀', '●', '☻', '◘', '☺', '□', '⍟'}
|
||||
var numSnakes int
|
||||
snakes := map[string]SnakeState{}
|
||||
|
|
@ -560,11 +586,11 @@ func (gameState *GameState) buildSnakesFromOptions() map[string]SnakeState {
|
|||
if i < numURLs {
|
||||
u, err := url.ParseRequestURI(gameState.URLs[i])
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("URL %v is not valid: %v", gameState.URLs[i], err)
|
||||
return nil, fmt.Errorf("URL %v is not valid: %w", gameState.URLs[i], err)
|
||||
}
|
||||
snakeURL = u.String()
|
||||
} else {
|
||||
log.ERROR.Fatalf("URL for name %v is missing", gameState.Names[i])
|
||||
return nil, fmt.Errorf("URL for name %v is missing", gameState.Names[i])
|
||||
}
|
||||
|
||||
snakeState := SnakeState{
|
||||
|
|
@ -573,25 +599,25 @@ func (gameState *GameState) buildSnakesFromOptions() map[string]SnakeState {
|
|||
var snakeErr error
|
||||
res, _, err := gameState.httpClient.Get(snakeURL)
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("Snake metadata request to %v failed: %v", snakeURL, err)
|
||||
return nil, fmt.Errorf("Snake metadata request to %v failed: %w", snakeURL, err)
|
||||
}
|
||||
|
||||
snakeState.StatusCode = res.StatusCode
|
||||
|
||||
if res.Body == nil {
|
||||
log.ERROR.Fatalf("Empty response body from snake metadata URL: %v", snakeURL)
|
||||
return nil, fmt.Errorf("Empty response body from snake metadata URL: %v", snakeURL)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.ERROR.Fatalf("Error reading from snake metadata URL %v: %v", snakeURL, readErr)
|
||||
return nil, fmt.Errorf("Error reading from snake metadata URL %v: %w", snakeURL, readErr)
|
||||
}
|
||||
|
||||
pingResponse := client.SnakeMetadataResponse{}
|
||||
jsonErr := json.Unmarshal(body, &pingResponse)
|
||||
if jsonErr != nil {
|
||||
log.ERROR.Fatalf("Failed to parse response from %v: %v", snakeURL, jsonErr)
|
||||
return nil, fmt.Errorf("Failed to parse response from %v: %w", snakeURL, jsonErr)
|
||||
}
|
||||
|
||||
snakeState.Head = pingResponse.Head
|
||||
|
|
@ -608,7 +634,7 @@ func (gameState *GameState) buildSnakesFromOptions() map[string]SnakeState {
|
|||
|
||||
log.INFO.Printf("Snake ID: %v URL: %v, Name: \"%v\"", snakeState.ID, snakeURL, snakeState.Name)
|
||||
}
|
||||
return snakes
|
||||
return snakes, nil
|
||||
}
|
||||
|
||||
func (gameState *GameState) printState(boardState *rules.BoardState) {
|
||||
|
|
@ -762,7 +788,8 @@ func (gameState *GameState) buildFrameEvent(boardState *rules.BoardState) board.
|
|||
func serialiseSnakeRequest(snakeRequest client.SnakeRequest) []byte {
|
||||
requestJSON, err := json.Marshal(snakeRequest)
|
||||
if err != nil {
|
||||
log.ERROR.Fatalf("Error marshalling JSON from State: %v", err)
|
||||
// This is likely to be a programming error like a unsupported type or cyclical reference
|
||||
log.ERROR.Panicf("Error marshalling JSON from State: %v", err)
|
||||
}
|
||||
return requestJSON
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,11 +45,10 @@ func buildDefaultGameState() *GameState {
|
|||
func TestGetIndividualBoardStateForSnake(t *testing.T) {
|
||||
s1 := rules.Snake{ID: "one", Body: []rules.Point{{X: 3, Y: 3}}}
|
||||
s2 := rules.Snake{ID: "two", Body: []rules.Point{{X: 4, Y: 3}}}
|
||||
state := &rules.BoardState{
|
||||
Height: 11,
|
||||
Width: 11,
|
||||
Snakes: []rules.Snake{s1, s2},
|
||||
}
|
||||
state := rules.NewBoardState(11, 11).
|
||||
WithSnakes(
|
||||
[]rules.Snake{s1, s2},
|
||||
)
|
||||
s1State := SnakeState{
|
||||
ID: "one",
|
||||
Name: "ONE",
|
||||
|
|
@ -85,11 +84,8 @@ func TestGetIndividualBoardStateForSnake(t *testing.T) {
|
|||
func TestSettingsRequestSerialization(t *testing.T) {
|
||||
s1 := rules.Snake{ID: "one", Body: []rules.Point{{X: 3, Y: 3}}}
|
||||
s2 := rules.Snake{ID: "two", Body: []rules.Point{{X: 4, Y: 3}}}
|
||||
state := &rules.BoardState{
|
||||
Height: 11,
|
||||
Width: 11,
|
||||
Snakes: []rules.Snake{s1, s2},
|
||||
}
|
||||
state := rules.NewBoardState(11, 11).
|
||||
WithSnakes([]rules.Snake{s1, s2})
|
||||
s1State := SnakeState{
|
||||
ID: "one",
|
||||
Name: "ONE",
|
||||
|
|
@ -255,12 +251,11 @@ func TestBuildFrameEvent(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "snake fields",
|
||||
boardState: &rules.BoardState{
|
||||
Turn: 99,
|
||||
Height: 19,
|
||||
Width: 25,
|
||||
Food: []rules.Point{{X: 9, Y: 4}},
|
||||
Snakes: []rules.Snake{
|
||||
boardState: rules.NewBoardState(19, 25).
|
||||
WithTurn(99).
|
||||
WithFood([]rules.Point{{X: 9, Y: 4}}).
|
||||
WithHazards([]rules.Point{{X: 8, Y: 6}}).
|
||||
WithSnakes([]rules.Snake{
|
||||
{
|
||||
ID: "1",
|
||||
Body: []rules.Point{
|
||||
|
|
@ -273,9 +268,7 @@ func TestBuildFrameEvent(t *testing.T) {
|
|||
EliminatedOnTurn: 45,
|
||||
EliminatedBy: "1",
|
||||
},
|
||||
},
|
||||
Hazards: []rules.Point{{X: 8, Y: 6}},
|
||||
},
|
||||
}),
|
||||
snakeStates: map[string]SnakeState{
|
||||
"1": {
|
||||
URL: "http://example.com",
|
||||
|
|
@ -326,18 +319,15 @@ func TestBuildFrameEvent(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "snake errors",
|
||||
boardState: &rules.BoardState{
|
||||
Height: 19,
|
||||
Width: 25,
|
||||
Snakes: []rules.Snake{
|
||||
boardState: rules.NewBoardState(19, 25).
|
||||
WithSnakes([]rules.Snake{
|
||||
{
|
||||
ID: "bad_status",
|
||||
},
|
||||
{
|
||||
ID: "connection_error",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
snakeStates: map[string]SnakeState{
|
||||
"bad_status": {
|
||||
StatusCode: 504,
|
||||
|
|
@ -366,6 +356,8 @@ func TestBuildFrameEvent(t *testing.T) {
|
|||
Error: "0:Error communicating with server",
|
||||
},
|
||||
},
|
||||
Food: []rules.Point{},
|
||||
Hazards: []rules.Point{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -384,11 +376,7 @@ func TestBuildFrameEvent(t *testing.T) {
|
|||
func TestGetMoveForSnake(t *testing.T) {
|
||||
s1 := rules.Snake{ID: "one", Body: []rules.Point{{X: 3, Y: 3}}}
|
||||
s2 := rules.Snake{ID: "two", Body: []rules.Point{{X: 4, Y: 3}}}
|
||||
boardState := &rules.BoardState{
|
||||
Height: 11,
|
||||
Width: 11,
|
||||
Snakes: []rules.Snake{s1, s2},
|
||||
}
|
||||
boardState := rules.NewBoardState(11, 11).WithSnakes([]rules.Snake{s1, s2})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -530,11 +518,7 @@ func TestGetMoveForSnake(t *testing.T) {
|
|||
|
||||
func TestCreateNextBoardState(t *testing.T) {
|
||||
s1 := rules.Snake{ID: "one", Body: []rules.Point{{X: 3, Y: 3}}}
|
||||
boardState := &rules.BoardState{
|
||||
Height: 11,
|
||||
Width: 11,
|
||||
Snakes: []rules.Snake{s1},
|
||||
}
|
||||
boardState := rules.NewBoardState(11, 11).WithSnakes([]rules.Snake{s1})
|
||||
snakeState := SnakeState{
|
||||
ID: s1.ID,
|
||||
URL: "http://example.com",
|
||||
|
|
@ -549,7 +533,9 @@ func TestCreateNextBoardState(t *testing.T) {
|
|||
gameState.snakeStates = map[string]SnakeState{s1.ID: snakeState}
|
||||
gameState.httpClient = stubHTTPClient{nil, 200, func(_ string) string { return `{"move": "right"}` }, 54 * time.Millisecond}
|
||||
|
||||
nextBoardState := gameState.createNextBoardState(boardState)
|
||||
gameOver, nextBoardState, err := gameState.createNextBoardState(boardState)
|
||||
require.NoError(t, err)
|
||||
require.False(t, gameOver)
|
||||
snakeState = gameState.snakeStates[s1.ID]
|
||||
|
||||
require.NotNil(t, nextBoardState)
|
||||
|
|
@ -593,16 +579,18 @@ func TestOutputFile(t *testing.T) {
|
|||
outputFile := new(closableBuffer)
|
||||
gameState.outputFile = outputFile
|
||||
|
||||
gameState.ruleset = StubRuleset{maxTurns: 1, settings: rules.Settings{
|
||||
FoodSpawnChance: 1,
|
||||
MinimumFood: 2,
|
||||
HazardDamagePerTurn: 3,
|
||||
RoyaleSettings: rules.RoyaleSettings{
|
||||
ShrinkEveryNTurns: 4,
|
||||
},
|
||||
}}
|
||||
gameState.ruleset = StubRuleset{
|
||||
maxTurns: 1,
|
||||
settings: rules.NewSettings(map[string]string{
|
||||
rules.ParamFoodSpawnChance: "1",
|
||||
rules.ParamMinimumFood: "2",
|
||||
rules.ParamHazardDamagePerTurn: "3",
|
||||
rules.ParamShrinkEveryNTurns: "4",
|
||||
}),
|
||||
}
|
||||
|
||||
gameState.Run()
|
||||
err = gameState.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
lines := strings.Split(outputFile.String(), "\n")
|
||||
require.Len(t, lines, 5)
|
||||
|
|
@ -626,14 +614,8 @@ type StubRuleset struct {
|
|||
|
||||
func (ruleset StubRuleset) Name() string { return "standard" }
|
||||
func (ruleset StubRuleset) Settings() rules.Settings { return ruleset.settings }
|
||||
func (ruleset StubRuleset) ModifyInitialBoardState(initialState *rules.BoardState) (*rules.BoardState, error) {
|
||||
return initialState, nil
|
||||
}
|
||||
func (ruleset StubRuleset) CreateNextBoardState(prevState *rules.BoardState, moves []rules.SnakeMove) (*rules.BoardState, error) {
|
||||
return prevState, nil
|
||||
}
|
||||
func (ruleset StubRuleset) IsGameOver(state *rules.BoardState) (bool, error) {
|
||||
return state.Turn >= ruleset.maxTurns, nil
|
||||
func (ruleset StubRuleset) Execute(prevState *rules.BoardState, moves []rules.SnakeMove) (bool, *rules.BoardState, error) {
|
||||
return prevState.Turn >= ruleset.maxTurns, prevState, nil
|
||||
}
|
||||
|
||||
type stubHTTPClient struct {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue