1
0
mirror of https://github.com/maaslalani/gambit.git synced 2024-11-24 08:22:12 +02:00
gambit/server/room.go
2022-02-14 20:23:06 -05:00

227 lines
4.6 KiB
Go

package server
import (
"fmt"
"log"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/gliderlabs/ssh"
"github.com/maaslalani/gambit/game"
)
var (
idleTimeout = time.Minute * 3
)
// Room is a game room with a unique id, password, and a list of players.
type Room struct {
id string
password string
players map[string]*Player
whiteToMove bool
sync chan tea.Msg
done chan struct{}
finish chan string
}
// String implements the Stringer interface.
func (r *Room) String() string {
return r.id
}
// NewRoom creates a new room with a unique id and password.
func NewRoom(id, password string, finish chan string) *Room {
s := make(chan tea.Msg)
r := &Room{
id: id,
password: password,
players: make(map[string]*Player, 0),
whiteToMove: true,
sync: s,
done: make(chan struct{}, 1),
finish: finish,
}
go func() {
r.Listen()
}()
return r
}
// P1 returns the player with the first turn.
func (r *Room) P1() *Player {
for _, p := range r.players {
if p.ptype == whitePlayer {
return p
}
}
return nil
}
// P2 returns the player with the second turn.
func (r *Room) P2() *Player {
for _, p := range r.players {
if p.ptype == blackPlayer {
return p
}
}
return nil
}
// FindPlayer returns the player for the given public key.
func (r *Room) FindPlayer(pub PublicKey) *Player {
p, ok := r.players[pub.String()]
if ok {
return p
}
return nil
}
// Write writes data to all players in the room.
func (r *Room) Write(b []byte) (n int, err error) {
for _, p := range r.players {
n, err = p.Write(b)
if err != nil {
return
}
}
return
}
// WriteString writes a string to all players in the room.
func (r *Room) WriteString(s string) (int, error) {
return r.Write([]byte(s))
}
// Close closes the room and deletes the room from the server memory.
func (r *Room) Close() {
log.Printf("closing room %s", r)
for _, p := range r.players {
p.WriteString("Idle timeout.\n")
p.Close()
}
r.done <- struct{}{}
r.finish <- r.id
close(r.sync)
close(r.done)
}
// Listen listens for messages from players in the room and other events.
func (r *Room) Listen() {
for {
select {
case <-r.done:
return
case <-time.After(idleTimeout):
log.Printf("idle timeout for room %s", r)
r.Close()
case m := <-r.sync:
color := whitePlayer
if !r.whiteToMove {
color = blackPlayer
}
switch msg := m.(type) {
case NoteMsg:
r.SendMsg(msg)
case game.MoveMsg:
note := fmt.Sprintf("%s moved %s to %s", color, msg.From, msg.To)
r.whiteToMove = !r.whiteToMove
r.SendMsg(m)
r.SendMsg(NoteMsg(note))
}
}
}
}
// SendMsg sends a bubble tea message to all players in the room.
func (r *Room) SendMsg(m tea.Msg) {
go func() {
for _, p := range r.players {
p.Send(m)
}
}()
}
// Position returns the FEN position of the game in the room.
func (r *Room) Position() string {
p1 := r.P1()
p2 := r.P2()
switch {
case !r.whiteToMove && p1 != nil:
fallthrough
case r.whiteToMove && p2 == nil && p1 != nil:
return p1.Position()
case r.whiteToMove && p2 != nil:
fallthrough
case !r.whiteToMove && p1 == nil && p2 != nil:
return p2.Position()
default:
for _, p := range r.players {
if p.ptype == observerPlayer {
return p.Position()
}
}
return ""
}
}
// MakePlayer creates a new player with the given type and session.
func (r *Room) MakePlayer(pt PlayerType, s ssh.Session) *Player {
pos := r.Position()
pl := &Player{
room: r,
session: s,
ptype: pt,
key: PublicKey{key: s.PublicKey()},
}
m := NewSharedGame(pl, r.sync, &r.whiteToMove, pt == whitePlayer, pt == observerPlayer, pos)
p := tea.NewProgram(
m,
tea.WithAltScreen(),
tea.WithMouseCellMotion(),
tea.WithInput(s),
tea.WithOutput(s),
)
pl.program = p
pl.game = m
return pl
}
// AddPlayer adds a player to the room.
func (r *Room) AddPlayer(s ssh.Session) (*Player, error) {
k := s.PublicKey()
if k == nil {
return nil, fmt.Errorf("no public key")
}
pub := PublicKey{key: k}
p, ok := r.players[pub.String()]
if ok {
return nil, fmt.Errorf("Player %s is already in the room", p)
}
p1 := r.P1()
p2 := r.P2()
if p1 == nil {
p = r.MakePlayer(whitePlayer, s)
} else if p2 == nil {
p = r.MakePlayer(blackPlayer, s)
} else {
p = r.MakePlayer(observerPlayer, s)
}
r.players[pub.String()] = p
r.SendMsg(NoteMsg(fmt.Sprintf("%s joined the room", p)))
return p, nil
}
// ObserversCount returns the number of observer players in the room.
func (r *Room) ObserversCount() int {
n := 0
for _, p := range r.players {
if p.ptype == observerPlayer {
n++
}
}
return n
}