Golang - State Pattern

Golang - State Pattern

In this article, we will be exploring how the State Pattern can be applied to a game in Golang. We will use a simple game where the player can move in different directions and perform different actions based on their current state.

What is the State Pattern?

The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. This pattern is useful when an object needs to change its behavior based on its current state, but you don't want to use a long list of conditional statements to handle all the possible states.

How does it work?

The State Pattern works by defining a separate class for each possible state of an object. Each of these classes implements a common interface that defines the methods for the object's behavior. The object itself holds a reference to the current state class, and delegates all method calls to that class. When the object's state changes, it simply updates the reference to the appropriate state class.

An example

Let's say we have a simple game where the player can move in different directions and perform different actions based on their current state. The player can be in three possible states: "Idle", "Walking", and "Jumping". Here's how we could implement the State Pattern in Golang:

type Player struct {
    currentState State
    idleState State
    walkingState State
    jumpingState State
    x int
    y int
}

func (p *Player) setState(state State) {
    p.currentState = state
}

func (p *Player) moveUp() {
    p.currentState.moveUp()
}

func (p *Player) moveDown() {
    p.currentState.moveDown()
}

func (p *Player) moveLeft() {
    p.currentState.moveLeft()
}

func (p *Player) moveRight() {
    p.currentState.moveRight()
}

func (p *Player) jump() {
    p.currentState.jump()
}

type State interface {
    moveUp()
    moveDown()
    moveLeft()
    moveRight()
    jump()
}

type IdleState struct {
    player *Player
}

func (s *IdleState) moveUp() {}

func (s *IdleState) moveDown() {}

func (s *IdleState) moveLeft() {}

func (s *IdleState) moveRight() {}

func (s *IdleState) jump() {
    fmt.Println("Jumped!")
    s.player.setState(s.player.jumpingState)
}

type WalkingState struct {
    player *Player
}

func (s *WalkingState) moveUp() {
    s.player.y -= 1
}

func (s *WalkingState) moveDown() {
    s.player.y += 1
}

func (s *WalkingState) moveLeft() {
    s.player.x -= 1
}

func (s *WalkingState) moveRight() {
    s.player.x += 1
}

func (s *WalkingState) jump() {
    fmt.Println("Jumped!")
    s.player.setState(s.player.jumpingState)
}

type JumpingState struct {
    player *Player
}

func (s *JumpingState) moveUp() {
    s.player.y -= 2
}

func (s *JumpingState) moveDown() {
    s.player.y += 2
}

func (s *JumpingState) moveLeft() {
    s.player.x -= 2
}

func (s *JumpingState) moveRight() {
    s.player.x += 2
}

func (s *JumpingState) jump() {}

func main() {
    idleState := &IdleState{}
    walkingState := &WalkingState{}
    jumpingState := &JumpingState{}

    player := &Player{
        currentState: idleState,
        idleState: idleState,
        walkingState: walkingState,
        jumpingState: jumpingState,
        x: 0,
        y: 0,
    }

    player.moveRight() // x: 1, y: 0
    player.moveUp() // x: 1, y: -1
    player.jump() // Jumped!
    player.moveRight() // x: 3, y: -3
}

In this example, we define a Player struct that holds a reference to the current state of the player, as well as separate state classes for each possible state. When the player moves or jumps, the appropriate method is called on the current state class. When the state changes, the setState method is called to update the reference to the appropriate state class.

Conclusion

The State Pattern is a useful design pattern for handling game objects that need to change their behavior based on their internal state. By defining separate classes for each possible state, we can simplify our code and avoid long lists of conditional statements. Hopefully, this article has helped you understand the State Pattern in Golang, and provided a realistic example to help illustrate the concept when applied to a game.

Thanks for reading, and happy coding!


References

Did you find this article valuable?

Support Matthias Bruns by becoming a sponsor. Any amount is appreciated!