Commit b5ec153a authored by ceddral's avatar ceddral
Browse files

split into package

prepare for refactor to establish cleaner separation between what
is now in packages. package will also allow for cleaner namespaces
in the future
parent e618d98e
package common
// encode printable ascii by identity
// encode arrowkeys etc over nonprintable ascii codes
type Key byte
const (
KeyNull Key = iota // telnet sometimes sends 0x00 as part of a end of line
KeyUp
KeyDown
KeyRight
KeyLeft
KeyShiftUp
KeyShiftDown
KeyShiftRight
KeyShiftLeft
KeyCtrlUp
KeyCtrlDown
KeyCtrlRight
KeyCtrlLeft
KeyReturn
KeyRedraw // special redraw input, not available to the player
KeyMaxSpecial // end of special key range
KeyEscape Key = 0x1b
)
func ToKey(keybs []byte) Key {
// this is basically what bytes.Equal does
switch string(keybs) {
case "\x1b[A": return KeyUp
case "\x1b[B": return KeyDown
case "\x1b[C": return KeyRight
case "\x1b[D": return KeyLeft
case "\x1b[1;2A": return KeyShiftUp
case "\x1b[1;2B": return KeyShiftDown
case "\x1b[1;2C": return KeyShiftRight
case "\x1b[1;2D": return KeyShiftLeft
case "\x1b[1;5A": return KeyCtrlUp
case "\x1b[1;5B": return KeyCtrlDown
case "\x1b[1;5C": return KeyCtrlRight
case "\x1b[1;5D": return KeyCtrlLeft
case "\x0d": return KeyReturn // must be handled here, because 0x0d < keyMaxSpecial
default:
if keybs[0] < byte(KeyMaxSpecial) {
return KeyNull
}
return Key(keybs[0])
}
}
package main
package common
import (
tg "fahrradwurstmond2.0/telnet_gui"
......@@ -8,11 +8,32 @@ import (
"time"
"fmt"
"regexp"
"sync"
//"errors"
)
type globalState struct {
lock *sync.Mutex
Players map[string]*Player
}
func (State *globalState) Lock() {
State.lock.Lock()
}
func (State *globalState) Unlock() {
State.lock.Unlock()
}
func InitState() {
State = &globalState{&sync.Mutex{}, make(map[string]*Player)}
}
var (
State *globalState
)
const (
nameMaxLen = 16
NameMaxLen = 16
nameWhitelistChars = "a-zA-Z0-9_"
)
......@@ -33,12 +54,12 @@ type Player struct {
tnState *TelnetState
conn net.Conn
connClosed chan struct{}
inputEvent chan []byte
InputEvent chan []byte
inputKill chan struct{}
windowSizeLoopKill chan struct{}
name string
context Context
scr *tg.Screen
Name string
Context Context
Scr *tg.Screen
}
func NewPlayer(conn net.Conn) (p *Player) {
......@@ -49,32 +70,32 @@ func NewPlayer(conn net.Conn) (p *Player) {
p = &Player{
conn: conn,
tnState: NewTelnetState(),
inputEvent: make(chan []byte, 50),
InputEvent: make(chan []byte, 50),
inputKill: make(chan struct{}),
}
go p.inputLoop()
p.name, err = queryUsername(p.conn, p.tnState)
p.Name, err = queryUsername(p.conn, p.tnState)
if err != nil {
log.Println(err)
}
reason := ""
if len(p.name) == 0 {
reason = "Must set username. `telnet -l <name>`\n"
} else if len(p.name) > nameMaxLen {
reason = fmt.Sprintf("Username must contain at most %d characters.\n", nameMaxLen)
} else if len(nameWhitelist.ReplaceAllString(p.name, "")) != len(p.name) {
if len(p.Name) == 0 {
reason = "Must set username. `telnet -l <Name>`\n"
} else if len(p.Name) > NameMaxLen {
reason = fmt.Sprintf("Username must contain at most %d characters.\n", NameMaxLen)
} else if len(nameWhitelist.ReplaceAllString(p.Name, "")) != len(p.Name) {
reason = fmt.Sprintf("Username must consist only of %s\n", nameWhitelistChars)
}
if reason != "" {
p.inputKill <- struct{}{}
close(p.inputKill)
close(p.inputEvent)
close(p.InputEvent)
conn.Write([]byte(reason))
conn.Close()
return nil
}
if pOld, ok := gs.players[p.name]; ok {
if pOld, ok := State.Players[p.Name]; ok {
// reconnect with replace
pNew := p
// check if old player is still responsive
......@@ -85,10 +106,10 @@ func NewPlayer(conn net.Conn) (p *Player) {
// old player is responsive, disconnect imposter
pNew.inputKill <- struct{}{}
close(pNew.inputKill)
close(pNew.inputEvent)
conn.Write([]byte(fmt.Sprintf("Username %s already taken\n", pNew.name)))
close(pNew.InputEvent)
conn.Write([]byte(fmt.Sprintf("Username %s already taken\n", pNew.Name)))
conn.Close()
log.Printf("%s tried to connect twice", p.name)
log.Printf("%s tried to connect twice", p.Name)
return nil
}
......@@ -99,26 +120,25 @@ func NewPlayer(conn net.Conn) (p *Player) {
// clean up new player input, was only needed to query username
pNew.inputKill <- struct{}{}
close(pNew.inputKill)
close(pNew.inputEvent)
close(pNew.InputEvent)
// assemble reconnected player
p = pOld
// no inputLoop for pOld and pNew may be running during these modifications to p
p.conn = pNew.conn
p.scr.Chown(conn)
p.Scr.Chown(conn)
go p.inputLoop()
log.Printf("%s reconnected\n", p.name)
log.Printf("%s reconnected\n", p.Name)
} else {
p.windowSizeLoopKill = make(chan struct{})
p.connClosed = make(chan struct{}, 1)
// TODO new players need to be sent to the meta lobby context
p.context = glgl
p.scr = tg.NewScreenEmpty(conn, tg.ColorDefault, tg.ColorDefault)
// TODO new Players need to be sent to the meta lobby Context
p.Scr = tg.NewScreenEmpty(conn, tg.ColorDefault, tg.ColorDefault)
go p.windowSizeLoop()
gs.players[p.name] = p
log.Printf("%s connected\n", p.name)
State.Players[p.Name] = p
log.Printf("%s connected\n", p.Name)
}
err = p.scr.Redraw()
err = p.Scr.Redraw()
if err != nil {
log.Println(err)
}
......@@ -133,10 +153,10 @@ func (p *Player) Close() {
p.windowSizeLoopKill <- struct{}{}
p.windowSizeLoopKill <- struct{}{}
close(p.connClosed)
close(p.inputEvent)
close(p.InputEvent)
close(p.windowSizeLoopKill)
delete(gs.players, p.name)
log.Printf("%s disconnected\n", p.name)
delete(State.Players, p.Name)
log.Printf("%s disconnected\n", p.Name)
}
func StopTimer(t *time.Timer) {
......@@ -171,9 +191,9 @@ func (p *Player) inputLoop() {
continue
}
p.connClosed <- struct{}{}
gs.Lock()
p.context.Unregister(p.name)
gs.Unlock()
State.Lock()
p.Context.Unregister(p.Name)
State.Unlock()
// await termination, to unblock a reconnect
<-p.inputKill
return
......@@ -187,9 +207,9 @@ func (p *Player) inputLoop() {
continue
}
out:
if toKey(key) != keyNull {
if ToKey(key) != KeyNull {
select {
case p.inputEvent <- key:
case p.InputEvent <- key:
default:
// discard inputs if they come too fast
}
......@@ -214,15 +234,15 @@ func (p *Player) windowSizeLoop() {
pthread_cond_timedwait_because_fuck_you(p.tnState.cond, timeoutChan)
w := p.tnState.windowWidth
h := p.tnState.windowHeight
if p.scr.W == w && p.scr.H == h {
if p.Scr.W == w && p.Scr.H == h {
continue
}
gs.Lock()
p.context.Lock()
p.scr.Resize(w, h)
p.context.Resize(w, h, p)
p.context.Redraw(p)
p.context.Unlock()
gs.Unlock()
State.Lock()
p.Context.Lock()
p.Scr.Resize(w, h)
p.Context.Resize(w, h, p)
p.Context.Redraw(p)
p.Context.Unlock()
State.Unlock()
}
}
package main
package common
import (
"fmt"
......
package main
package game
func max(a, b int) int {
if a > b { return a }
......
package main
package game
import (
tg "fahrradwurstmond2.0/telnet_gui"
......
package main
package game
import (
tg "fahrradwurstmond2.0/telnet_gui"
common "fahrradwurstmond2.0/common"
"fmt"
"log"
"sync"
......@@ -13,9 +14,9 @@ type GameState interface {
getGame() *Game
String() string
agentTransition(*GamePlayer)
agentAction(*GamePlayer, Key) bool
agentAction(*GamePlayer, common.Key) bool
patientTransition(*GamePlayer)
patientAction(*GamePlayer, Key) bool
patientAction(*GamePlayer, common.Key) bool
}
type GameStateWithPiece interface {
......@@ -26,7 +27,7 @@ type GameStateWithPiece interface {
func (g *Game) broadcastState(state GameState) {
g.Lock()
defer g.Unlock()
for _, p := range g.players {
for _, p := range g.Players {
select {
case p.gameEvent <- state:
default:
......@@ -44,7 +45,7 @@ func (m *GameMenu) Redraw(s GameState) {
fg := tg.ColorDefault
bg := tg.ColorDefault
line := 0
v.DrawTextClearLine(fg, bg, []byte(fmt.Sprintf("Turn: %s", s.getAgent().name)), 0, line)
v.DrawTextClearLine(fg, bg, []byte(fmt.Sprintf("Turn: %s", s.getAgent().Name)), 0, line)
line++
if _, ok := s.(*StatePlacePiece); ok {
render(v, 0, line, nil, PMPlaces, nil, s)
......@@ -66,7 +67,7 @@ func (m *GameMenu) Redraw(s GameState) {
if p == a {
brackets = "<>"
}
buf := []byte(fmt.Sprintf("%c%d%c%s %d", brackets[0], p.id, brackets[1], p.name, p.score))
buf := []byte(fmt.Sprintf("%c%d%c%s %d", brackets[0], p.id, brackets[1], p.Name, p.score))
// TODO append barrels cloths grain
v.DrawTextClearLine(fg, bg, buf, 0, line)
maxlen := len(buf)
......@@ -99,13 +100,13 @@ next:
}
type GamePlayer struct {
name string // global identifier
Name string // global identifier
id int // game/board specific identifier
tokens map[Token]int
inputEvent chan []byte
InputEvent chan []byte
gameEvent chan GameState
gameKill chan struct{}
scr *tg.Screen
Scr *tg.Screen
menu GameMenu
menuv *tg.View
boardv *tg.View
......@@ -293,7 +294,7 @@ func extensionToString(ext string) string {
type Game struct {
lock *sync.Mutex
players map[string]*GamePlayer
Players map[string]*GamePlayer
extensions map[string]struct{}
board *Board
pieces []Piece
......@@ -312,24 +313,24 @@ func (g *Game) Unlock() {
func NewGame(pns []string, extensions []string) (g *Game) {
g = &Game{
lock: &sync.Mutex{},
players: make(map[string]*GamePlayer),
Players: make(map[string]*GamePlayer),
extensions: make(map[string]struct{}),
pieces: make([]Piece, 0),
pieceOrder: make([]int, 0),
}
gs.Lock()
for i, name := range pns {
glp := gs.players[name]
common.State.Lock()
for i, Name := range pns {
glp := common.State.Players[Name]
p := &GamePlayer{
name: name,
Name: Name,
id: i,
tokens: make(map[Token]int),
inputEvent: glp.inputEvent,
InputEvent: glp.InputEvent,
gameEvent: make(chan GameState, 100),
gameKill: make(chan struct{}, 1),
scr: glp.scr,
menuv: tg.NewView(glp.scr, 0, 0, 30, glp.scr.H),
boardv: tg.NewView(glp.scr, 30, 0, glp.scr.W-30, glp.scr.H),
Scr: glp.Scr,
menuv: tg.NewView(glp.Scr, 0, 0, 30, glp.Scr.H),
boardv: tg.NewView(glp.Scr, 30, 0, glp.Scr.W-30, glp.Scr.H),
boardx: -10,
boardy: -10,
boardMode: PMPlayers,
......@@ -337,17 +338,17 @@ func NewGame(pns []string, extensions []string) (g *Game) {
lostConnection: false,
}
p.menu = GameMenu{p}
g.players[name] = p
glp.context = g
g.Players[Name] = p
glp.Context = g
g.Register(glp)
}
gs.Unlock()
common.State.Unlock()
// link turn list
for i := range pns {
cur := pns[i]
nxt := pns[(i+1)%len(pns)]
g.players[cur].next = g.players[nxt]
g.Players[cur].next = g.Players[nxt]
}
// load pieces
......@@ -360,7 +361,7 @@ func NewGame(pns []string, extensions []string) (g *Game) {
g.pieces = append(g.pieces, ps...)
ts := tokensByExtension(ext)
for t, count := range ts {
for _, p := range g.players {
for _, p := range g.Players {
if _, ok := p.tokens[t]; !ok {
p.tokens[t] = 0
}
......@@ -380,22 +381,22 @@ func NewGame(pns []string, extensions []string) (g *Game) {
g.board = NewBoard(g)
// select first player
agent := g.players[pns[0]]
agent := g.Players[pns[0]]
g.broadcastState(NewStatePlacePiece(g, agent))
for _, p := range g.players {
for _, p := range g.Players {
go g.Loop(p, &StateInit{g, agent})
}
return
}
func (g *Game) Register(glp *Player) {
glp.scr.Clear()
func (g *Game) Register(glp *common.Player) {
glp.Scr.Clear()
g.Lock()
p := g.players[glp.name]
p := g.Players[glp.Name]
p.lostConnection = false
err := glp.scr.Redraw()
err := glp.Scr.Redraw()
if err != nil {
log.Println(err)
}
......@@ -403,42 +404,42 @@ func (g *Game) Register(glp *Player) {
g.broadcastState(nil)
}
func (g *Game) Unregister(name string) {
func (g *Game) Unregister(Name string) {
defer g.broadcastState(nil)
g.Lock()
defer g.Unlock()
p, ok := g.players[name]
p, ok := g.Players[Name]
if !ok {
return
}
p.lostConnection = true
anyoneConnected := false
for _, p := range g.players {
for _, p := range g.Players {
if !p.lostConnection {
anyoneConnected = true
}
}
if !anyoneConnected {
// no one connected anymore
// remove game and all associated players
for name, p := range g.players {
// remove game and all associated Players
for Name, p := range g.Players {
p.gameKill <- struct{}{}
delete(g.players, name)
glp := gs.players[name]
delete(g.Players, Name)
glp := common.State.Players[Name]
glp.Close()
}
}
}
func (g *Game) Resize(w, h int, glp *Player) {
p := g.players[glp.name]
func (g *Game) Resize(w, h int, glp *common.Player) {
p := g.Players[glp.Name]
p.menuv.Resize(min(30, w), h)
p.boardv.Resize(max(0, w-30), h)
}
func (g *Game) Redraw(glp *Player) {
p := g.players[glp.name]
p.inputEvent<-[]byte{byte(keyRedraw)}
func (g *Game) Redraw(glp *common.Player) {
p := g.Players[glp.Name]
p.InputEvent<-[]byte{byte(common.KeyRedraw)}
}
func (g *Game) Loop(p *GamePlayer, initState GameState) {
......@@ -457,12 +458,12 @@ func (g *Game) Loop(p *GamePlayer, initState GameState) {
} else {
state.patientTransition(p)
}
case input := <-p.inputEvent:
case input := <-p.InputEvent:
if input == nil {
break
}
key := toKey(input)
if key == keyRedraw || key == ' ' {
key := common.ToKey(input)
if key == common.KeyRedraw || key == ' ' {
fullRedraw = true
break
}
......@@ -481,10 +482,10 @@ func (g *Game) Loop(p *GamePlayer, initState GameState) {
p.menu.Redraw(state)
g.board.Redraw(p, state)
if fullRedraw {
err = p.scr.Redraw()
err = p.Scr.Redraw()
fullRedraw = false
} else {
err = p.scr.Update()
err = p.Scr.Update()
}
if err != nil {
log.Println("Screen Update Error:", err)
......@@ -499,20 +500,20 @@ func (g *Game) drawPiece() (p *Piece) {
}
// returns whether an action was taken
func anyStateAnyRoleAction(s GameState, p *GamePlayer, key Key) bool {
func anyStateAnyRoleAction(s GameState, p *GamePlayer, key common.Key) bool {
switch key {
case keyShiftUp, 'W': p.boardy--
case common.KeyShiftUp, 'W': p.boardy--
s.getGame().board.Redraw(p, s)
p.boardv.ScrollDown(1)
p.menuv.Redraw()
case keyShiftLeft, 'A': p.boardx-=2
case common.KeyShiftLeft, 'A': p.boardx-=2
s.getGame().board.Redraw(p, s)
p.boardv.ScrollRight(2)
case keyShiftDown, 'S': p.boardy++
case common.KeyShiftDown, 'S': p.boardy++
s.getGame().board.Redraw(p, s)
p.boardv.ScrollUp(1)
p.menuv.Redraw()
case keyShiftRight, 'D': p.boardx+=2
case common.KeyShiftRight, 'D': p.boardx+=2
s.getGame().board.Redraw(p, s)
p.boardv.ScrollLeft(2)
case 'm':
......@@ -533,8 +534,8 @@ func (s *StateInit) getGame() *Game { return s.g }
func (s *StateInit) String() string { return "StateInit" }
func (s *StateInit) agentTransition(p *GamePlayer) {}
func (s *StateInit) patientTransition(p *GamePlayer) {}
func (s *StateInit) agentAction(p *GamePlayer, key Key) bool { return true }
func (s *StateInit) patientAction(p *GamePlayer, key Key) bool { return true }
func (s *StateInit) agentAction(p *GamePlayer, key common.Key) bool { return true }
func (s *StateInit) patientAction(p *GamePlayer, key common.Key) bool { return true }
type StatePlacePiece struct {
agent *GamePlayer
......@@ -589,13 +590,13 @@ func (s *StatePlacePiece) agentTransition(p *GamePlayer) {
}
func (s *StatePlacePiece) patientTransition(p *GamePlayer) {
p.boardv.DrawTextClearLine(tg.ColorDefault, tg.ColorDefault, []byte(fmt.Sprintf("%s drew that card", s.agent.name)), 0, 1)
p.boardv.DrawTextClearLine(tg.ColorDefault, tg.ColorDefault, []byte(fmt.Sprintf("%s drew that card", s.agent.Name)), 0, 1)
}
func (s *StatePlacePiece) agentAction(p *GamePlayer, key Key) bool {
func (s *StatePlacePiece) agentAction(p *GamePlayer, key common.Key) bool {
var newState GameState
switch key {
case keyReturn:
case common.KeyReturn:
if _, occupied := s.g.board.Get(s.boardx, s.boardy); occupied {
return true
}
......@@ -610,22 +611,22 @@ func (s *StatePlacePiece) agentAction(p *GamePlayer, key Key) bool {
}
s.g.board.Set(s.boardx, s.boardy, NewPlace(s.p, s.rot))
newState = NewStatePlaceFollower(s.g, s.agent, s.boardx, s.boardy)
case keyUp, 'w':
case common.KeyUp, 'w':
s.boardy--
newState = s
case keyLeft, 'a':
case common.KeyLeft, 'a':
s.boardx--
newState = s
case keyDown, 's':
case common.KeyDown, 's':
s.boardy++
newState = s
case keyRight, 'd':
case common.KeyRight, 'd':
s.boardx++
newState = s
case keyCtrlLeft, 'q':
case common.KeyCtrlLeft, 'q':
s.rot = (s.rot + 1) % 4
newState = s
case keyCtrlRight, 'e':
case common.KeyCtrlRight, 'e':
s.rot = (s.rot + 3) % 4
newState = s
default:
......@@ -638,8 +639,8 @@ func (s *StatePlacePiece) agentAction(p *GamePlayer, key Key) bool {
return true
}
func (s *StatePlacePiece) patientAction(p *GamePlayer, key Key) bool {
p.boardv.DrawTextClearLine(tg.ColorDefault, tg.ColorDefault, []byte(fmt.Sprintf("It is %s turn. PLEASE WAIT!!!!!", s.agent.name)), 0, 1)
func (s *StatePlacePiece) patientAction(p *GamePlayer, key common.Key) bool {
p.boardv.DrawTextClearLine(tg.ColorDefault, tg.ColorDefault, []byte(fmt.Sprintf("It is %s turn. PLEASE WAIT!!!!!", s.agent.Name)), 0, 1)
return false