This question is related to Golang function to return an Interface and expands on it.

My setup is the following:

I'm creating the server side of a game where the client can choose different game modes. These modes are realised through different structs with their own implementation of some methods, all of which are their own file in package engines, for example:

package engines

import "fmt"

type Square struct {
    tiles []int
    players []bool
}

func NewSquare() *Square {
    return &Square{
        [25]int{}[:],
        []bool{false, false},
    }
}

func (e *Square) AddPlayer() (int, error) {
    for id := range e.players {
        if !e.players[id] {
            e.players[id] = true
            return id, nil
        }
    }
    return -1, fmt.Printf("game already full")
}

In the main package, when a new game is created, I'm using a map to call the newSomething function of the corresponding game mode. Every engine satifies the interface gameEngine:

package main

import "engines"

type gameEngine interface {
    AddPlayer() (int, error)
}

type GameMode int
const (
    SQUARE   GameMode = 0
    TRIANGLE GameMode = 1
)

This interface is defined within the main package for two primary reasons:

  1. It improves readability to have the interface definition in the same file that uses that interface. If I'm unsure what some object is capable of, I can just scroll to the interface definition and check.
  2. Putting the interface definition inside package engines feels like going back to the situation where classes explicitly state the interfaces they implement, one of the things go is designed not to require.

This means that within the files of my engine implementations, like Square.go and Triangle.go, I don't have access to the interface and the corresponding new-Functions must return their respective type, which makes them of type func() *Square and func() *Triangle, respectively (see above).

Now I can't use these new-functions directly in a map[GameMode]func() gameEngine because their type is wrong. My current solution is to convert them using a rather verbose inline function (inspired by one of the answers in the linked question):

var engine gameEngine

var newGameFuncs = map[GameMode]func() gameEngine {
    SQUARE:   func() gameEngine { return engines.NewSquare() },
    TRIANGLE: func() gameEngine { return engines.NewTriangle() },
}

func JoinGame(mode GameMode) (int, error) {
    engine = newGameFuncs[mode]()
    id, err := engine.AddPlayer()
    // Some other stuff, too
    return id, nil
}

This map feels very clunky and artificial, but as far as I can tell, it's the only way to do it with this map approach.

Is there a better way or design pattern to achieve my goal?

  • func JoinGame(mode GameMode) (int, error) is part of my client interface, it's the starting point of the whole thing and must keep its signature.
  • type gameEngine interface should stay in the main package.
  • When implementing a new mode (say, Hexagon.go), the only steps necessary should be making the file and registering the newHexagon function in exactly one place.

1 Answers

0
Bogdan Daragan On

You would rather define a new type (alias to func) than an interface. type AddPlayer func() (GameMode, error)

And encapsulate this map with functions inside engine package where you can add new handle-functions (on demand) in separate file with init(). Like: engine/main.go:

package engins

type GameMode int

const (
    SQUARE = iota
    TRIANGLE
)

type AddPlayer func() (GameMode, error)

var newGameFuncs = make(map[GameMode]AddPlayer)

func GetAddPlayerFuncByGameMode(gm GameMode) AddPlayer {
    return newGameFuncs[gm]
}

engine/square.go:

package engins

func init() {
    newGameFuncs[SQUARE] = engineSquare{}.NewSquare
}

type engineSquare struct{}

func (engineSquare) NewSquare() (GameMode, error) {
    return SQUARE, nil
}

And from main.go you will be able to use it like:

package main

import e "./engins"

func main() {
    e.GetAddPlayerFuncByGameMode(e.SQUARE)
}