Both game and snake packages had identical PlayerID types and the snake package imported game.GenerateID. Now both use player.ID and player.GenerateID from the shared player package.
226 lines
3.9 KiB
Go
226 lines
3.9 KiB
Go
package game
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/ryanhamamura/c4/db/repository"
|
|
"github.com/ryanhamamura/c4/player"
|
|
)
|
|
|
|
type PlayerSession struct {
|
|
Player *Player
|
|
}
|
|
|
|
type GameStore struct {
|
|
games map[string]*GameInstance
|
|
gamesMu sync.RWMutex
|
|
queries *repository.Queries
|
|
notifyFunc func(gameID string)
|
|
}
|
|
|
|
func NewGameStore(queries *repository.Queries) *GameStore {
|
|
return &GameStore{
|
|
games: make(map[string]*GameInstance),
|
|
queries: queries,
|
|
}
|
|
}
|
|
|
|
func (gs *GameStore) SetNotifyFunc(f func(gameID string)) {
|
|
gs.notifyFunc = f
|
|
}
|
|
|
|
func (gs *GameStore) makeNotify(gameID string) func() {
|
|
return func() {
|
|
if gs.notifyFunc != nil {
|
|
gs.notifyFunc(gameID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (gs *GameStore) Create() *GameInstance {
|
|
id := player.GenerateID(4)
|
|
gi := NewGameInstance(id)
|
|
gi.queries = gs.queries
|
|
gi.notify = gs.makeNotify(id)
|
|
gs.gamesMu.Lock()
|
|
gs.games[id] = gi
|
|
gs.gamesMu.Unlock()
|
|
|
|
if gs.queries != nil {
|
|
gi.save() //nolint:errcheck
|
|
}
|
|
|
|
return gi
|
|
}
|
|
|
|
func (gs *GameStore) Get(id string) (*GameInstance, bool) {
|
|
gs.gamesMu.RLock()
|
|
gi, ok := gs.games[id]
|
|
gs.gamesMu.RUnlock()
|
|
|
|
if ok {
|
|
return gi, true
|
|
}
|
|
|
|
if gs.queries == nil {
|
|
return nil, false
|
|
}
|
|
|
|
g, err := loadGame(gs.queries, id)
|
|
if err != nil || g == nil {
|
|
return nil, false
|
|
}
|
|
|
|
players, _ := loadGamePlayers(gs.queries, id)
|
|
for _, p := range players {
|
|
switch p.Color {
|
|
case 1:
|
|
g.Players[0] = p
|
|
case 2:
|
|
g.Players[1] = p
|
|
}
|
|
}
|
|
|
|
gi = &GameInstance{
|
|
game: g,
|
|
queries: gs.queries,
|
|
notify: gs.makeNotify(id),
|
|
}
|
|
|
|
gs.gamesMu.Lock()
|
|
gs.games[id] = gi
|
|
gs.gamesMu.Unlock()
|
|
|
|
return gi, true
|
|
}
|
|
|
|
func (gs *GameStore) Delete(id string) error {
|
|
gs.gamesMu.Lock()
|
|
delete(gs.games, id)
|
|
gs.gamesMu.Unlock()
|
|
|
|
if gs.queries != nil {
|
|
return gs.queries.DeleteGame(context.Background(), id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type GameInstance struct {
|
|
game *Game
|
|
gameMu sync.RWMutex
|
|
notify func()
|
|
queries *repository.Queries
|
|
}
|
|
|
|
func NewGameInstance(id string) *GameInstance {
|
|
return &GameInstance{
|
|
game: NewGame(id),
|
|
notify: func() {},
|
|
}
|
|
}
|
|
|
|
func (gi *GameInstance) ID() string {
|
|
gi.gameMu.RLock()
|
|
defer gi.gameMu.RUnlock()
|
|
return gi.game.ID
|
|
}
|
|
|
|
func (gi *GameInstance) Join(ps *PlayerSession) bool {
|
|
gi.gameMu.Lock()
|
|
defer gi.gameMu.Unlock()
|
|
|
|
var slot int
|
|
if gi.game.Players[0] == nil {
|
|
ps.Player.Color = 1
|
|
gi.game.Players[0] = ps.Player
|
|
slot = 0
|
|
} else if gi.game.Players[1] == nil {
|
|
ps.Player.Color = 2
|
|
gi.game.Players[1] = ps.Player
|
|
gi.game.Status = StatusInProgress
|
|
slot = 1
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
if gi.queries != nil {
|
|
gi.savePlayer(ps.Player, slot) //nolint:errcheck
|
|
gi.save() //nolint:errcheck
|
|
}
|
|
|
|
gi.notify()
|
|
return true
|
|
}
|
|
|
|
func (gi *GameInstance) GetGame() *Game {
|
|
gi.gameMu.RLock()
|
|
defer gi.gameMu.RUnlock()
|
|
return gi.game
|
|
}
|
|
|
|
func (gi *GameInstance) GetPlayerColor(pid player.ID) int {
|
|
gi.gameMu.RLock()
|
|
defer gi.gameMu.RUnlock()
|
|
for _, p := range gi.game.Players {
|
|
if p != nil && p.ID == pid {
|
|
return p.Color
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (gi *GameInstance) CreateRematch(gs *GameStore) *GameInstance {
|
|
gi.gameMu.Lock()
|
|
defer gi.gameMu.Unlock()
|
|
|
|
if !gi.game.IsFinished() || gi.game.RematchGameID != nil {
|
|
return nil
|
|
}
|
|
|
|
newGI := gs.Create()
|
|
newID := newGI.ID()
|
|
gi.game.RematchGameID = &newID
|
|
|
|
if gi.queries != nil {
|
|
if err := gi.save(); err != nil {
|
|
gs.Delete(newID) //nolint:errcheck
|
|
gi.game.RematchGameID = nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
gi.notify()
|
|
return newGI
|
|
}
|
|
|
|
func (gi *GameInstance) DropPiece(col int, playerColor int) bool {
|
|
gi.gameMu.Lock()
|
|
defer gi.gameMu.Unlock()
|
|
|
|
row, ok := gi.game.DropPiece(col, playerColor)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
if gi.game.CheckWin(row, col) {
|
|
for _, p := range gi.game.Players {
|
|
if p != nil && p.Color == playerColor {
|
|
gi.game.Winner = p
|
|
break
|
|
}
|
|
}
|
|
} else if gi.game.CheckDraw() {
|
|
// Status already set by CheckDraw
|
|
} else {
|
|
gi.game.SwitchTurn()
|
|
}
|
|
|
|
if gi.queries != nil {
|
|
gi.save() //nolint:errcheck
|
|
}
|
|
|
|
gi.notify()
|
|
return true
|
|
}
|