Refactor rulesets into smaller composable operations In order to mix up the functionality from different rulesets like Solo, Royale, etc. the code in these classes needs to be broken up into small functions that can be composed in a pipeline to make a custom game mode.
159 lines
4.4 KiB
Go
159 lines
4.4 KiB
Go
package rules
|
|
|
|
import (
|
|
"errors"
|
|
)
|
|
|
|
type SquadRuleset struct {
|
|
StandardRuleset
|
|
|
|
SquadMap map[string]string
|
|
|
|
// These are intentionally designed so that they default to a standard game.
|
|
AllowBodyCollisions bool
|
|
SharedElimination bool
|
|
SharedHealth bool
|
|
SharedLength bool
|
|
}
|
|
|
|
func (r *SquadRuleset) Name() string { return GameTypeSquad }
|
|
|
|
func (r *SquadRuleset) CreateNextBoardState(prevState *BoardState, moves []SnakeMove) (*BoardState, error) {
|
|
nextBoardState, err := r.StandardRuleset.CreateNextBoardState(prevState, moves)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = r.resurrectSquadBodyCollisions(nextBoardState)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = r.shareSquadAttributes(nextBoardState)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nextBoardState, nil
|
|
}
|
|
|
|
func areSnakesOnSameSquad(squadMap map[string]string, snake *Snake, other *Snake) bool {
|
|
return areSnakeIDsOnSameSquad(squadMap, snake.ID, other.ID)
|
|
}
|
|
|
|
func areSnakeIDsOnSameSquad(squadMap map[string]string, snakeID string, otherID string) bool {
|
|
return squadMap[snakeID] == squadMap[otherID]
|
|
}
|
|
|
|
func (r *SquadRuleset) resurrectSquadBodyCollisions(b *BoardState) error {
|
|
_, err := r.callStageFunc(ResurrectSnakesSquad, b, []SnakeMove{})
|
|
return err
|
|
}
|
|
|
|
func ResurrectSnakesSquad(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
|
if !settings.SquadSettings.AllowBodyCollisions {
|
|
return false, nil
|
|
}
|
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
snake := &b.Snakes[i]
|
|
if snake.EliminatedCause == EliminatedByCollision {
|
|
if snake.EliminatedBy == "" {
|
|
return false, errors.New("snake eliminated by collision and eliminatedby is not set")
|
|
}
|
|
if snake.ID != snake.EliminatedBy && areSnakeIDsOnSameSquad(settings.SquadSettings.squadMap, snake.ID, snake.EliminatedBy) {
|
|
snake.EliminatedCause = NotEliminated
|
|
snake.EliminatedBy = ""
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (r *SquadRuleset) shareSquadAttributes(b *BoardState) error {
|
|
_, err := r.callStageFunc(ShareAttributesSquad, b, []SnakeMove{})
|
|
return err
|
|
}
|
|
|
|
func ShareAttributesSquad(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
|
squadSettings := settings.SquadSettings
|
|
|
|
if !(squadSettings.SharedElimination || squadSettings.SharedLength || squadSettings.SharedHealth) {
|
|
return false, nil
|
|
}
|
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
snake := &b.Snakes[i]
|
|
if snake.EliminatedCause != NotEliminated {
|
|
continue
|
|
}
|
|
|
|
for j := 0; j < len(b.Snakes); j++ {
|
|
other := &b.Snakes[j]
|
|
if areSnakesOnSameSquad(squadSettings.squadMap, snake, other) {
|
|
if squadSettings.SharedHealth {
|
|
if snake.Health < other.Health {
|
|
snake.Health = other.Health
|
|
}
|
|
}
|
|
if squadSettings.SharedLength {
|
|
if len(snake.Body) == 0 || len(other.Body) == 0 {
|
|
return false, errors.New("found snake of zero length")
|
|
}
|
|
for len(snake.Body) < len(other.Body) {
|
|
growSnake(snake)
|
|
}
|
|
}
|
|
if squadSettings.SharedElimination {
|
|
if snake.EliminatedCause == NotEliminated && other.EliminatedCause != NotEliminated {
|
|
snake.EliminatedCause = EliminatedBySquad
|
|
// We intentionally do not set snake.EliminatedBy because there might be multiple culprits.
|
|
snake.EliminatedBy = ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (r *SquadRuleset) IsGameOver(b *BoardState) (bool, error) {
|
|
return r.callStageFunc(GameOverSquad, b, []SnakeMove{})
|
|
}
|
|
|
|
func GameOverSquad(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
|
snakesRemaining := []*Snake{}
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
if b.Snakes[i].EliminatedCause == NotEliminated {
|
|
snakesRemaining = append(snakesRemaining, &b.Snakes[i])
|
|
}
|
|
}
|
|
|
|
for i := 0; i < len(snakesRemaining); i++ {
|
|
if !areSnakesOnSameSquad(settings.SquadSettings.squadMap, snakesRemaining[i], snakesRemaining[0]) {
|
|
// There are multiple squads remaining
|
|
return false, nil
|
|
}
|
|
}
|
|
// no snakes or single squad remaining
|
|
return true, nil
|
|
}
|
|
|
|
func (r SquadRuleset) Settings() Settings {
|
|
s := r.StandardRuleset.Settings()
|
|
s.SquadSettings = SquadSettings{
|
|
squadMap: r.SquadMap,
|
|
AllowBodyCollisions: r.AllowBodyCollisions,
|
|
SharedElimination: r.SharedElimination,
|
|
SharedHealth: r.SharedHealth,
|
|
SharedLength: r.SharedLength,
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Adaptor for integrating stages into SquadRuleset
|
|
func (r *SquadRuleset) callStageFunc(stage StageFunc, boardState *BoardState, moves []SnakeMove) (bool, error) {
|
|
return stage(boardState, r.Settings(), moves)
|
|
}
|