behaviors

package
v0.0.0-...-6b15219 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 18, 2023 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package behaviors defines a handful of convenient behaviors that can be anonymously embedded into your SubState (e.g. gameState and playerState) structs.

A behavior is a combination of persistable properties, as well as methods that mutate those properties. They encapsulate commonly required behavior, like setting current player or round robin properties. Think of them like lego bricks you can add to your game and player states.

`boardgame-util codegen` will automatically include the state properties of the behaviors in the generated PropertyReader.

Connectable Behaviors

Behaviors often require access to the struct they're embedded within. These types of behaviors are called Connectable, and if they are this type thentheir ConnectBehavior should always be called within the subState's FinishStateSetUp, like so:

//FinishStateSetUp is called by the main engine when a State is being created.
//ConnectContainingState will have already been called, so State and StatePropertyRef
//will be set. The engine doesn't require anything to be done in this method; it's
//typically used to connect behaviors.
func (g *gameState) FinishStateSetUp() {
	//PlayerColor is a Connectable, which requires us to call ConnectBehavior
	//on it, passing a reference to ourselvs (the struct it's embeddded in).
	g.PlayerColor.ConnectBehavior(g)
	//If you had ohter Connectable's in this struct, you'd call ConnectBehavior
	//here, too.
}

Connectable behaviors that are not connected will error when their ValidConfiguration is called, and the main library will notice that while NewGameManager is being executed, which will fail with a descriptive error.

Seats, Inactivity, and Players

This package defines two behaviors, InactivePlayer and Seat, whose use isn't necessarily obvious. This section describes why they're useful.

The core engine has a notion of players, but--with the exception of players who are configured to be an agent--it doesn't have a sense of who is playing on behalf of any player. That logic is handled at different layers, most notably in the server package. That package is the one that keeps track of the actual users and which ones are tied to which players in the core game logic.

Crucially, when a game is created, a number of the player slots might be unfilled, as we wait for other users to be invited to the game and attach to it. The core game engine has no notion that this is happening, because it doesn't know anything about users in the first place, let alone which ones are actually attached to the game. Your custom game logic also doesn't by default know anything about the players--which ones are actually there, which ones are currently unfilled, etc. For some games, you want to wait until every player is configured before starting. For other games it's possible to get the game rolling and have other players join the next round. But that's not possible to express if your logic doesn't know which players have real users behind them.

For that reason, this package introduces the notion of Seats and InactivePlayers. Instead of thinking of the core engine's notion of a player as a literal player, think of it as a seat, that may or may not be occupied. A seat can be denoted as having a player sitting in it (that it is "Filled"). It can also express that even though it is not filled, it is no longer open for anyone to sit in it (that it is "Closed").

If the server logic sees that your game logic includes the Seat behavior, then it knows that it should seek to communicate to your game logic when a user joins the game, and listen for your game logic to communicate which open seats should no longer be filled, even if there are new users. The only way to modify state in your game logic is by making a move, so that's how the server package tells your game logic that a seat is filled. If it is sees that your game logic has a legal move type that is moves.SeatPlayer (or a move that embeds that move struct), then it will propose that move whenever there is a player to seat.

You can control when the server tries to seat a player by controlling when that move type is legal. For example, if it always OK to seat a player at any point, you'd configure it so that move is legal in any phase. If you wanted to only seat players in a certain phase, you'd use AddMovesForPhase. You can even control the precise logic of when it is legal by using AddOrderedMovesForPhase.

Note that when a user seeks to join the game, they aren't actually finally added to the game until they're seated. This means that your game logic can't really inspect whether there are any players waiting to be seated. It also means that if that user leaves the window before they're seated they might not be seated. For that reason there's another, related behavior called InactivePlayer.

By default, every player slot is considered active--that is, they should be treated as a real, normal player. When the turn order gets to them, the logic of the game waits for them to make their move before continuing on to the other player. But sometimes that seat is empty, and we want to get a move on without waiting for any more players to join. Or sometimes we want to seat a player immediately, but finish out the current round of play without them, only dealing them in next round. The way to do that is to embed InactivePlayer behavior, which contains a flag for whether the player is Inactive.

If you mark the player as inactive, then boardgame.PlayerIndex.Next (and Prev) will skip that player, acting as though they don't exist. This means that nearly all of your game logic (that doesn't just count the number of playerStates naively) will operate as though they don't exist. By default all players are considered active--even seats that are empty. That's because in general the safest default is to wait to start the game until everyone is there.

But if you embed both Seat and InactivePlayer in your playerStates, then the SeatPlayer move will immediately mark any player that is seated to be inactive. This is again a safe default; it's safest to assume that a recently seated player needs to be 'dealt in,' likely before the next round starts, before they're active.

There are other moves that are designed to work with this system. moves.ActivateInactivePlayer will go through and activate any inactive players. This is typically included in a phase progression just before a round starts. moves.CloseEmptySeat will mark as closed any seats that are currently empty, which effectively says "even though there are more seats, no more people may be seated". moves.InactivateEmptySeat marks any empty seat as inactive, which effectively communicates "until I say otherwise, just pretend like the empty seats aren't there".

Because of these concepts, when you want to know the number of logical players in your game at any moment, Game.NumPlayers() is often not what you want. Instead, see boardgame/base.GameDelegate.NumSeatedActivePlayers.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func PlayerIsInactive

func PlayerIsInactive(playerState boardgame.ImmutableSubState) bool

PlayerIsInactive is a convenience method that does the cast to interfaces.PlayerInactiver, so you don't have to. You can pass any playerState to it and it will return true if the player state implements interfaces.PlayerInactiver and the IsInactive returns true, false otherwise.

Types

type ComponentColor

type ComponentColor struct {
	Color enum.ImmutableVal `enum:"color"`
}

ComponentColor is a struct designed to be embedded anonymously in any component structs that have a color that ties them to a given player color, and used in conjunction with PlayerColor. It doesn't do anything on its own other than define a property; it's mainly just a convenience so you don't hav to worry about making sure the token's color property is named correctly. It assumes that you have an enum called 'color' that enumerates the valid colors players may be.

type Connectable

type Connectable interface {
	//ConnectBehavior lets the behavior have a reference to the struct its
	//embedded in, as some behaviors need access to the broader state.
	ConnectBehavior(containgSubState boardgame.SubState)

	//Connectable behaviors should implement ValidConfiguration and return an
	//error if they haven't yet been Connected, which will help the main engine
	//know to fail NewGameManager, allowing the problem to be fixed more quickly.
	boardgame.ConfigurationValidator
}

Connectable is the interface that behaviors that are Connectable implements. Connectable behaviors are ones that must have their ConnectBehavior called within their SubState cdontainer's FinishStateSetUp method. The ValidConfiguration method will return an error if they weren't connected, which will help diagnose the problem early if you forget.

type CurrentPlayerBehavior

type CurrentPlayerBehavior struct {
	CurrentPlayer boardgame.PlayerIndex
}

CurrentPlayerBehavior is a struct designed to be embedded anonymously in your gameState. It encodes the current player for the game. base.GameDelegate's CurrentPlayerIndex works well with this. It's named CurrentPlayerBehavior and not CurrentPlayer because otherwise it would conflict with the internal property name when accessing it from your SubState.

func (*CurrentPlayerBehavior) SetCurrentPlayer

func (c *CurrentPlayerBehavior) SetCurrentPlayer(currentPlayer boardgame.PlayerIndex)

SetCurrentPlayer sets the CurrentPlayer value to the given value. This satisfies the moves/interfaces.CurrentPlayerSetter interface, allowing you to use moves.FinishTurn.

type InactivePlayer

type InactivePlayer struct {
	PlayerInactive bool
}

InactivePlayer is a struct designed to be embedded anonymously in your PlayerStates. It encodes whether a player is Inactive or not. If a player is Inactive, then base.GameDelegate will return false for that player index for PlayerMayBeActive. This is useful if you have 'filled' seats but the players should not be included in the core game logic of your game. For example, if a player is sitting out a round of a game (perhaps becuase they joined the game mid-way through a round). See the package doc of this package for more.

func (*InactivePlayer) IsInactive

func (i *InactivePlayer) IsInactive() bool

IsInactive returns whether Inactive is true. Satisfies the PlayerInactiver interface in moves/interfaces.

func (*InactivePlayer) SetPlayerActive

func (i *InactivePlayer) SetPlayerActive()

SetPlayerActive sets the player to be Active. Satisfies the PlayerInactiver interface in moves/interfaces.

func (*InactivePlayer) SetPlayerInactive

func (i *InactivePlayer) SetPlayerInactive()

SetPlayerInactive sets the player to be Inactive. Satisfies the PlayerInactiver interface in moves/interfaces.

type PhaseBehavior

type PhaseBehavior struct {
	Phase enum.Val `enum:"phase"`
}

PhaseBehavior is a struct designed to be embedded anonymously in your gameState. It encodes the current phase for the game. base.GameDelegate's CurrentPhase works well with this. It expects your phase enum to be named 'phase'. It's named PhaseBehavior and not Phase because otherwise it would conflict with the internal property name when accessing it from your SubState.

func (*PhaseBehavior) SetCurrentPhase

func (p *PhaseBehavior) SetCurrentPhase(phase int)

SetCurrentPhase sets the phase value to the given value. This satisfies the moves/interfaces.CurrentPhaseSetter interface, allowing you to use moves.StartPhase.

type PlayerColor

type PlayerColor struct {
	Color enum.Val `enum:"color"`
	// contains filtered or unexported fields
}

PlayerColor is a struct that's designed to be anonymously embedded in your playerState. It represents the "color" of that player, and its primary use is convenience methods it exposes for you to use. It is a Connectable behavior which means it's an error if you use it and don't call ConnectBehavior from within your playerState's FinishStateSetUp (see the package doc for more).Typically you also embed a ComponentColor in the ComponentValues of the components that represent tokens or any other item that are tied to a specific player color. PlayerColor expects there to be an enum called 'color' that enumerates the valid colors players may be.

func (*PlayerColor) ConnectBehavior

func (p *PlayerColor) ConnectBehavior(containgSubState boardgame.SubState)

ConnectBehavior stores a reference to the container, which it needs to operate.

func (*PlayerColor) OwnsToken

func (p *PlayerColor) OwnsToken(c boardgame.Component) bool

OwnsToken returns whether this player owns the given token. That is, the given component has a property named Color that is the same enum as our Color property and they are set to the same value.

func (*PlayerColor) Token

Token searches through all decks for a component whose Color property matches this player's color, and then returns a ComponentInstance for it within this state. This may return nil. Typically you can use this instead of TokenFromDeck.

func (*PlayerColor) TokenFromDeck

func (p *PlayerColor) TokenFromDeck(deck *boardgame.Deck) boardgame.ComponentInstance

TokenFromDeck searches through the given deck to find a ComponentInstance whose Color matchs this player's color, returning the first one it finds. May return nil if none are found. Typically if only one type of deck has a Color property, you don't need to use this, and can instead use Token().

func (*PlayerColor) TokenSpaceIndex

func (p *PlayerColor) TokenSpaceIndex() int

TokenSpaceIndex returns the index of the token that it is within its current container. Typically the position of a token within a board has semantic significance, for example in chutes and ladders which other spaces are adjacent, and this method captures that. If it's in a stack that's part of a board, it will return the BoardIndex, but if it's in a normal stack it will return normal index. If there are multiple types of components that might be returned, use TokenSpaceIndexFromDeck instead.

func (*PlayerColor) TokenSpaceIndexFromDeck

func (p *PlayerColor) TokenSpaceIndexFromDeck(deck *boardgame.Deck) int

TokenSpaceIndexFromDeck is like TokenSpaceIndex but when you only want to compare against components that are in a specific deck.

func (*PlayerColor) ValidConfiguration

func (p *PlayerColor) ValidConfiguration(example boardgame.State) error

ValidConfiguration returns an error if ConnectBehavior hasn't yet been called.

type PlayerRole

type PlayerRole struct {
	Role enum.Val `enum:"role"`
}

PlayerRole is a struct that is designed to be embedded in your playerState. It assumes there is an enum called `role`. This is typically used for when different players have different roles, for example roleGuesser and roleClueGiver. If your role enum is combined with the group enum, then base.GameDelegate.GroupMembership will pick this up automatically.

type RoundRobin

type RoundRobin struct {
	RRLastPlayer    boardgame.PlayerIndex
	RRStarterPlayer boardgame.PlayerIndex
	RRRoundCount    int
	RRHasStarted    bool
}

RoundRobin is designed to be embedded in your GameState anonymously to automatically satisfy the moves/interfaces.RoundRobinProperties interface, making it easy to use RoundRobin-based moves. You typically embed this IN ADDITION TO base.SubState.

//Example
type gameState struct {
    base.SubState
    //By including this, we can use any round-robin based move without any
    //other changes
    behaviors.RoundRobin
    MyInt int
}

func (*RoundRobin) RoundRobinHasStarted

func (r *RoundRobin) RoundRobinHasStarted() bool

RoundRobinHasStarted returns the value set via SetRoundRobinHasStarted

func (*RoundRobin) RoundRobinLastPlayer

func (r *RoundRobin) RoundRobinLastPlayer() boardgame.PlayerIndex

RoundRobinLastPlayer returns the value set via SetRoundRobinLastPlayer.

func (*RoundRobin) RoundRobinRoundCount

func (r *RoundRobin) RoundRobinRoundCount() int

RoundRobinRoundCount returns the value set via SetRoundRobinRoundCount

func (*RoundRobin) RoundRobinStarterPlayer

func (r *RoundRobin) RoundRobinStarterPlayer() boardgame.PlayerIndex

RoundRobinStarterPlayer returns the value set via SetRoundRobinStarterPlayer

func (*RoundRobin) SetRoundRobinHasStarted

func (r *RoundRobin) SetRoundRobinHasStarted(val bool)

SetRoundRobinHasStarted sets the value to return for RoundRobinHasStarted.

func (*RoundRobin) SetRoundRobinLastPlayer

func (r *RoundRobin) SetRoundRobinLastPlayer(nextPlayer boardgame.PlayerIndex)

SetRoundRobinLastPlayer sets the value to return for RoundRobinLastPlayer

func (*RoundRobin) SetRoundRobinRoundCount

func (r *RoundRobin) SetRoundRobinRoundCount(count int)

SetRoundRobinRoundCount sets the value to return for RoundrobinRoundCount

func (*RoundRobin) SetRoundRobinStarterPlayer

func (r *RoundRobin) SetRoundRobinStarterPlayer(index boardgame.PlayerIndex)

SetRoundRobinStarterPlayer sets the value to return for RoundRobinStarterPlayer

type Seat

type Seat struct {
	SeatFilled bool
	SeatClosed bool
}

Seat is a struct designed to be anonymously embedded into your PlayerState. It allows you to express that a given Player "slot" actually might be filled by a real player or not. When used in conjunction with moves.SeatPlayer, it allows your game logic to detect when new players are seated (when they join the game), as well as to control when that happens. Implements moves/interfaces.Seater . See the package doc of this package for more.

func (*Seat) SeatIsClosed

func (s *Seat) SeatIsClosed() bool

SeatIsClosed returns true if the seat is closed--that is, no new people may sit in it, either because it is already filled, or because it has been affirmatively closed.

func (*Seat) SeatIsFilled

func (s *Seat) SeatIsFilled() bool

SeatIsFilled returns true if a real player is sitting in this seat.

func (*Seat) SetSeatClosed

func (s *Seat) SetSeatClosed()

SetSeatClosed sets that the seat is closed and should not be filled by any players, even if it is not filled. This tells the engine to not seat any more players here.

func (*Seat) SetSeatFilled

func (s *Seat) SetSeatFilled()

SetSeatFilled sets that the seat is filled. Also sets SeatClosed to true.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL