Rename module path github.com/ryanhamamura/c4 to github.com/ryanhamamura/games across go.mod, all source files, and golangci config.
226 lines
3.8 KiB
Go
226 lines
3.8 KiB
Go
package connect4
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/ryanhamamura/games/db/repository"
|
|
"github.com/ryanhamamura/games/player"
|
|
)
|
|
|
|
type PlayerSession struct {
|
|
Player *Player
|
|
}
|
|
|
|
type Store struct {
|
|
games map[string]*Instance
|
|
gamesMu sync.RWMutex
|
|
queries *repository.Queries
|
|
notifyFunc func(gameID string)
|
|
}
|
|
|
|
func NewStore(queries *repository.Queries) *Store {
|
|
return &Store{
|
|
games: make(map[string]*Instance),
|
|
queries: queries,
|
|
}
|
|
}
|
|
|
|
func (s *Store) SetNotifyFunc(f func(gameID string)) {
|
|
s.notifyFunc = f
|
|
}
|
|
|
|
func (s *Store) makeNotify(gameID string) func() {
|
|
return func() {
|
|
if s.notifyFunc != nil {
|
|
s.notifyFunc(gameID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Store) Create() *Instance {
|
|
id := player.GenerateID(4)
|
|
gi := NewInstance(id)
|
|
gi.queries = s.queries
|
|
gi.notify = s.makeNotify(id)
|
|
s.gamesMu.Lock()
|
|
s.games[id] = gi
|
|
s.gamesMu.Unlock()
|
|
|
|
if s.queries != nil {
|
|
gi.save() //nolint:errcheck
|
|
}
|
|
|
|
return gi
|
|
}
|
|
|
|
func (s *Store) Get(id string) (*Instance, bool) {
|
|
s.gamesMu.RLock()
|
|
gi, ok := s.games[id]
|
|
s.gamesMu.RUnlock()
|
|
|
|
if ok {
|
|
return gi, true
|
|
}
|
|
|
|
if s.queries == nil {
|
|
return nil, false
|
|
}
|
|
|
|
g, err := loadGame(s.queries, id)
|
|
if err != nil || g == nil {
|
|
return nil, false
|
|
}
|
|
|
|
players, _ := loadGamePlayers(s.queries, id)
|
|
for _, p := range players {
|
|
switch p.Color {
|
|
case 1:
|
|
g.Players[0] = p
|
|
case 2:
|
|
g.Players[1] = p
|
|
}
|
|
}
|
|
|
|
gi = &Instance{
|
|
game: g,
|
|
queries: s.queries,
|
|
notify: s.makeNotify(id),
|
|
}
|
|
|
|
s.gamesMu.Lock()
|
|
s.games[id] = gi
|
|
s.gamesMu.Unlock()
|
|
|
|
return gi, true
|
|
}
|
|
|
|
func (s *Store) Delete(id string) error {
|
|
s.gamesMu.Lock()
|
|
delete(s.games, id)
|
|
s.gamesMu.Unlock()
|
|
|
|
if s.queries != nil {
|
|
return s.queries.DeleteGame(context.Background(), id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Instance struct {
|
|
game *Game
|
|
gameMu sync.RWMutex
|
|
notify func()
|
|
queries *repository.Queries
|
|
}
|
|
|
|
func NewInstance(id string) *Instance {
|
|
return &Instance{
|
|
game: NewGame(id),
|
|
notify: func() {},
|
|
}
|
|
}
|
|
|
|
func (gi *Instance) ID() string {
|
|
gi.gameMu.RLock()
|
|
defer gi.gameMu.RUnlock()
|
|
return gi.game.ID
|
|
}
|
|
|
|
func (gi *Instance) 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 *Instance) GetGame() *Game {
|
|
gi.gameMu.RLock()
|
|
defer gi.gameMu.RUnlock()
|
|
return gi.game
|
|
}
|
|
|
|
func (gi *Instance) 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 *Instance) CreateRematch(s *Store) *Instance {
|
|
gi.gameMu.Lock()
|
|
defer gi.gameMu.Unlock()
|
|
|
|
if !gi.game.IsFinished() || gi.game.RematchGameID != nil {
|
|
return nil
|
|
}
|
|
|
|
newGI := s.Create()
|
|
newID := newGI.ID()
|
|
gi.game.RematchGameID = &newID
|
|
|
|
if gi.queries != nil {
|
|
if err := gi.save(); err != nil {
|
|
s.Delete(newID) //nolint:errcheck
|
|
gi.game.RematchGameID = nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
gi.notify()
|
|
return newGI
|
|
}
|
|
|
|
func (gi *Instance) 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
|
|
}
|