Invitees no longer need to enter a nickname - they automatically join with a random name like "Swift Tiger" or "Happy Falcon". Game creators still enter their nickname manually.
201 lines
3.7 KiB
Go
201 lines
3.7 KiB
Go
package game
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Syncable interface {
|
|
Sync()
|
|
}
|
|
|
|
type PlayerSession struct {
|
|
Player *Player
|
|
Sync Syncable
|
|
}
|
|
|
|
type GameStore struct {
|
|
games map[string]*GameInstance
|
|
gamesMu sync.RWMutex
|
|
}
|
|
|
|
func NewGameStore() *GameStore {
|
|
return &GameStore{
|
|
games: make(map[string]*GameInstance),
|
|
}
|
|
}
|
|
|
|
func (gs *GameStore) Create() *GameInstance {
|
|
id := GenerateID(4)
|
|
gi := NewGameInstance(id)
|
|
gs.gamesMu.Lock()
|
|
gs.games[id] = gi
|
|
gs.gamesMu.Unlock()
|
|
go gi.run()
|
|
return gi
|
|
}
|
|
|
|
func (gs *GameStore) Get(id string) (*GameInstance, bool) {
|
|
gs.gamesMu.RLock()
|
|
defer gs.gamesMu.RUnlock()
|
|
gi, ok := gs.games[id]
|
|
return gi, ok
|
|
}
|
|
|
|
func GenerateID(size int) string {
|
|
b := make([]byte, size)
|
|
rand.Read(b)
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
func GenerateNickname() string {
|
|
adjectives := []string{
|
|
"Swift", "Happy", "Clever", "Brave", "Mighty",
|
|
"Quick", "Calm", "Bold", "Lucky", "Wise",
|
|
"Eager", "Noble", "Keen", "Bright", "Vivid",
|
|
}
|
|
nouns := []string{
|
|
"Tiger", "Falcon", "Dragon", "Phoenix", "Wolf",
|
|
"Eagle", "Panda", "Otter", "Fox", "Bear",
|
|
"Hawk", "Lion", "Raven", "Shark", "Owl",
|
|
}
|
|
b := make([]byte, 2)
|
|
rand.Read(b)
|
|
adj := adjectives[int(b[0])%len(adjectives)]
|
|
noun := nouns[int(b[1])%len(nouns)]
|
|
return adj + " " + noun
|
|
}
|
|
|
|
type GameInstance struct {
|
|
game *Game
|
|
gameMu sync.RWMutex
|
|
players map[PlayerID]Syncable
|
|
playersMu sync.RWMutex
|
|
leave chan PlayerID
|
|
done chan struct{}
|
|
dirty bool
|
|
}
|
|
|
|
func NewGameInstance(id string) *GameInstance {
|
|
return &GameInstance{
|
|
game: NewGame(id),
|
|
players: make(map[PlayerID]Syncable),
|
|
leave: make(chan PlayerID, 5),
|
|
done: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
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()
|
|
|
|
// Assign player to an open slot
|
|
if gi.game.Players[0] == nil {
|
|
ps.Player.Color = 1 // Red
|
|
gi.game.Players[0] = ps.Player
|
|
} else if gi.game.Players[1] == nil {
|
|
ps.Player.Color = 2 // Yellow
|
|
gi.game.Players[1] = ps.Player
|
|
gi.game.Status = StatusInProgress
|
|
} else {
|
|
return false // Game is full
|
|
}
|
|
|
|
gi.playersMu.Lock()
|
|
gi.players[ps.Player.ID] = ps.Sync
|
|
gi.playersMu.Unlock()
|
|
|
|
gi.dirty = true
|
|
return true
|
|
}
|
|
|
|
func (gi *GameInstance) GetGame() *Game {
|
|
gi.gameMu.RLock()
|
|
defer gi.gameMu.RUnlock()
|
|
return gi.game
|
|
}
|
|
|
|
func (gi *GameInstance) GetPlayerColor(pid PlayerID) 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) 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()
|
|
}
|
|
|
|
gi.dirty = true
|
|
return true
|
|
}
|
|
|
|
func (gi *GameInstance) run() {
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case pid := <-gi.leave:
|
|
gi.playersMu.Lock()
|
|
delete(gi.players, pid)
|
|
gi.playersMu.Unlock()
|
|
case <-ticker.C:
|
|
gi.publish()
|
|
case <-gi.done:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (gi *GameInstance) publish() {
|
|
gi.gameMu.Lock()
|
|
if !gi.dirty {
|
|
gi.gameMu.Unlock()
|
|
return
|
|
}
|
|
gi.dirty = false
|
|
|
|
gi.playersMu.RLock()
|
|
syncers := make([]Syncable, 0, len(gi.players))
|
|
for _, sync := range gi.players {
|
|
syncers = append(syncers, sync)
|
|
}
|
|
gi.playersMu.RUnlock()
|
|
gi.gameMu.Unlock()
|
|
|
|
for _, sync := range syncers {
|
|
sync.Sync()
|
|
}
|
|
}
|