func (g *GameDelegate) NumSeatedActivePlayers(state boardgame.ImmutableState) int {
	count := 0
	for _, p := range state.ImmutablePlayerStates() {
		if behaviors.PlayerIsInactive(p) {
			continue
		}
		if seater, ok := p.(interfaces.Seater); ok {
			if !seater.SeatIsFilled() {
				continue
			}
		}
		count++
	}
	return count
}
NumSeatedActivePlayers returns the number of players who are both seated and
active. This is typically the number you want to decide how many 'real'
players there are at the moment. See boardgame/behaviors package doc for more.
package base
import (
	"math"
	"sort"
	"strings"
	"github.com/jkomoros/boardgame/behaviors"
	"github.com/jkomoros/boardgame/moves/interfaces"
	"github.com/jkomoros/boardgame"
	"github.com/jkomoros/boardgame/enum"
	"github.com/jkomoros/boardgame/errors"
	"github.com/sirupsen/logrus"
)
type GameDelegate struct {
	manager *boardgame.GameManager
	cachedGroupMembershipProperties []string
}
func (g *GameDelegate) Diagram(state boardgame.ImmutableState) string {
	return "This should be overriden to render a reasonable state here"
}
func (g *GameDelegate) DisplayName() string {
	return strings.Title(g.Manager().Delegate().Name())
}
func (g *GameDelegate) Description() string {
	return ""
}
func (g *GameDelegate) Manager() *boardgame.GameManager {
	return g.manager
}
func (g *GameDelegate) SetManager(manager *boardgame.GameManager) {
	g.manager = manager
}
func (g *GameDelegate) DynamicComponentValuesConstructor(deck *boardgame.Deck) boardgame.ConfigurableSubState {
	return nil
}
func (g *GameDelegate) ProposeFixUpMove(state boardgame.ImmutableState) boardgame.Move {
	isDebug := g.Manager().Logger().Level >= logrus.DebugLevel
	var logEntry *logrus.Entry
	if isDebug {
		logEntry = g.Manager().Logger().WithFields(logrus.Fields{
			"game":    state.Game().ID(),
			"version": state.Version(),
		})
		logEntry.Debug("***** ProposeFixUpMove called *****")
	}
	for _, move := range state.Game().Moves() {
		var entry *logrus.Entry
		if isDebug {
			entry = logEntry.WithField("movetype", move.Info().Name())
		}
		if !IsFixUp(move) {
			continue
		}
		err := move.Legal(state, boardgame.AdminPlayerIndex)
		if err == nil {
			if isDebug {
				entry.Debug(move.Info().Name() + " : MATCH")
			}
			return move
		}
		if isDebug {
			entry.Debug(move.Info().Name() + " : " + err.Error())
		}
	}
	if isDebug {
		logEntry.Debug("NO MATCH")
	}
	return nil
}
func (g *GameDelegate) CurrentPlayerIndex(state boardgame.ImmutableState) boardgame.PlayerIndex {
	index, err := state.ImmutableGameState().Reader().PlayerIndexProp("CurrentPlayer")
	if err != nil {
		return boardgame.ObserverPlayerIndex
	}
	return index.EnsureValid(state)
}
func (g *GameDelegate) CurrentPhase(state boardgame.ImmutableState) int {
	phaseEnum, err := state.ImmutableGameState().Reader().ImmutableEnumProp("Phase")
	if err != nil {
		return -1
	}
	return phaseEnum.Value()
}
func (g *GameDelegate) PhaseEnum() enum.Enum {
	result := g.Manager().Chest().Enums().Enum("phase")
	if result != nil {
		return result
	}
	return g.Manager().Chest().Enums().Enum("Phase")
}
const defaultGroupsName = "group"
func (g *GameDelegate) GroupEnum() enum.Enum {
	return g.Manager().Chest().Enums().Enum(defaultGroupsName)
}
func (g *GameDelegate) DistributeComponentToStarterStack(state boardgame.ImmutableState, c boardgame.Component) (boardgame.ImmutableStack, error) {
	return nil, errors.New("DistributeComponentToStarterStack was called, but the component was not stored in a stack")
}
func (g *GameDelegate) GroupMembership(playerState boardgame.ImmutableSubState) map[int]bool {
	if playerState != nil && g.cachedGroupMembershipProperties == nil {
		groupEnum := g.Manager().Delegate().GroupEnum()
		if groupEnum == nil {
			return nil
		}
		props := make([]string, 0)
		for propName, propType := range playerState.Reader().Props() {
			if propType != boardgame.TypeEnum {
				continue
			}
			enumVal, err := playerState.Reader().ImmutableEnumProp(propName)
			if err != nil {
				continue
			}
			if enumVal.Enum().SubsetOf(groupEnum) {
				props = append(props, propName)
			}
		}
		g.cachedGroupMembershipProperties = props
	}
	if len(g.cachedGroupMembershipProperties) == 0 {
		return nil
	}
	result := make(map[int]bool, len(g.cachedGroupMembershipProperties))
	for _, propName := range g.cachedGroupMembershipProperties {
		enumVal, err := playerState.Reader().ImmutableEnumProp(propName)
		if err != nil {
			continue
		}
		result[enumVal.Value()] = true
	}
	return result
}
const computedGroupNameDelimiter = "-"
const computedGroupNameFunctionSame = "same"
const computedGroupNameFunctionsDifferent = "different"
var legalComputedGroupNameFunctions = map[string]bool{
	computedGroupNameFunctionSame:       true,
	computedGroupNameFunctionsDifferent: true,
}
func doComputedGroupMembership(fun string, e enum.Enum, playerMembership, viewingAsPlayerMembership map[int]bool) bool {
	for _, key := range e.Values() {
		p := playerMembership[key]
		v := viewingAsPlayerMembership[key]
		switch fun {
		case computedGroupNameFunctionSame:
			if p != v {
				return false
			}
		case computedGroupNameFunctionsDifferent:
			if p != v {
				return true
			}
		}
	}
	switch fun {
	case computedGroupNameFunctionSame:
		return true
	case computedGroupNameFunctionsDifferent:
		return false
	}
	return false
}
func (g *GameDelegate) ComputedPlayerGroupMembership(groupName string, playerMembership, viewingAsPlayerMembership map[int]bool) (bool, error) {
	parts := strings.Split(groupName, computedGroupNameDelimiter)
	if len(parts) == 2 {
		fun := strings.ToLower(parts[0])
		if _, ok := legalComputedGroupNameFunctions[fun]; !ok {
			return false, errors.New(parts[0] + " was used as a computed group name function but it's not a known one")
		}
		e := g.Manager().Chest().Enums().Enum(parts[1])
		if e == nil {
			return false, errors.New(parts[1] + " was used as a computed group name enum but it's not a legal enum")
		}
		groupEnum := g.Manager().Delegate().GroupEnum()
		if groupEnum == nil {
			return false, errors.New("A computed group name used an enum, but there is no Group enum")
		}
		if !e.SubsetOf(groupEnum) {
			return false, errors.New(parts[1] + " enum is not a subset of GroupEnum")
		}
		return doComputedGroupMembership(fun, e, playerMembership, viewingAsPlayerMembership), nil
	}
	return false, errors.New("Unsupported group name: " + groupName)
}
func (g *GameDelegate) SanitizationPolicy(prop boardgame.StatePropertyRef, groupMembership map[string]bool) boardgame.Policy {
	manager := g.Manager()
	inflater := manager.Internals().StructInflater(prop)
	if inflater == nil {
		return boardgame.PolicyInvalid
	}
	policyMap := inflater.PropertySanitizationPolicy(prop.PropName)
	var applicablePolicies []int
	for groupName, isMember := range groupMembership {
		if !isMember {
			continue
		}
		if policy, ok := policyMap[groupName]; ok {
			applicablePolicies = append(applicablePolicies, int(policy))
		}
	}
	if len(applicablePolicies) == 0 {
		return boardgame.PolicyVisible
	}
	sort.Ints(applicablePolicies)
	return boardgame.Policy(applicablePolicies[0])
}
func (g *GameDelegate) ComputedGlobalProperties(state boardgame.ImmutableState) boardgame.PropertyCollection {
	return nil
}
func (g *GameDelegate) ComputedPlayerProperties(player boardgame.ImmutableSubState) boardgame.PropertyCollection {
	return nil
}
func (g *GameDelegate) BeginSetUp(state boardgame.State, variant boardgame.Variant) error {
	return nil
}
func (g *GameDelegate) FinishSetUp(state boardgame.State) error {
	return nil
}
type defaultCheckGameFinishedDelegate interface {
	GameEndConditionMet(state boardgame.ImmutableState) bool
	PlayerScore(pState boardgame.ImmutableSubState) int
	LowScoreWins() bool
}
type PlayerGameScorer interface {
	GameScore() int
}
func (g *GameDelegate) CheckGameFinished(state boardgame.ImmutableState) (finished bool, winners []boardgame.PlayerIndex) {
	if g.Manager() == nil {
		return false, nil
	}
	checkGameFinished := g.Manager().Delegate().(defaultCheckGameFinishedDelegate)
	if !checkGameFinished.GameEndConditionMet(state) {
		return false, nil
	}
	lowScoreWins := checkGameFinished.LowScoreWins()
	extremeScore := 0
	if lowScoreWins {
		extremeScore = math.MaxInt32
	}
	for _, player := range state.ImmutablePlayerStates() {
		if behaviors.PlayerIsInactive(player) {
			continue
		}
		score := checkGameFinished.PlayerScore(player)
		if lowScoreWins {
			if score < extremeScore {
				extremeScore = score
			}
		} else {
			if score > extremeScore {
				extremeScore = score
			}
		}
	}
	for i, player := range state.ImmutablePlayerStates() {
		if behaviors.PlayerIsInactive(player) {
			continue
		}
		score := checkGameFinished.PlayerScore(player)
		if score == extremeScore {
			winners = append(winners, boardgame.PlayerIndex(i))
		}
	}
	return true, winners
}
func (g *GameDelegate) LowScoreWins() bool {
	return false
}
func (g *GameDelegate) GameEndConditionMet(state boardgame.ImmutableState) bool {
	return false
}
func (g *GameDelegate) PlayerScore(pState boardgame.ImmutableSubState) int {
	if scorer, ok := pState.(PlayerGameScorer); ok {
		return scorer.GameScore()
	}
	return 0
}