diff options
Diffstat (limited to 'go/game/playerControl.go')
| -rw-r--r-- | go/game/playerControl.go | 174 |
1 files changed, 159 insertions, 15 deletions
diff --git a/go/game/playerControl.go b/go/game/playerControl.go index c472f4f0..dab1af3e 100644 --- a/go/game/playerControl.go +++ b/go/game/playerControl.go @@ -1,23 +1,31 @@ package game import ( + "bufio" "fmt" + "io" "log" + "regexp" ) type PlayerNotificationType int const ( - DeclaredActionNotification PlayerNotificationType = iota + InvalidNotification PlayerNotificationType = iota + DeclaredActionNotification ResolvedActionNotification PriorityNotification TargetSelectionPrompt DeclareTriggeredActionsPrompt DraftPickPrompt + JoinedPlayerNotification + ReadyPlayerNotification ) func (n PlayerNotificationType) String() string { switch n { + case InvalidNotification: + return "InvalidNotification" case DeclaredActionNotification: return "DeclaredActionNotification" case ResolvedActionNotification: @@ -30,6 +38,10 @@ func (n PlayerNotificationType) String() string { return "DeclareTriggeredActionsPrompt" case DraftPickPrompt: return "DraftPickPrompt" + case JoinedPlayerNotification: + return "JoinedPlayerNotification" + case ReadyPlayerNotification: + return "ReadyPlayerNotification" default: log.Panicf("Unhandled notification %d", n) return "" @@ -46,7 +58,7 @@ type PlayerNotification struct { Error error } -func (n *PlayerNotification) String() string { +func (n PlayerNotification) String() string { if n.Error != nil { return fmt.Sprintf("error %v: %v", n.Notification, n.Error) } @@ -54,6 +66,10 @@ func (n *PlayerNotification) String() string { return fmt.Sprintf("%v: %v", n.Notification, n.Context) } +func (n PlayerNotification) Valid() bool { + return n.Notification != InvalidNotification +} + type TargetSelectionCtx struct { Action Action Prompt string @@ -100,30 +116,158 @@ func newDraftPickPrompt(pack PileOfCards) PlayerNotification { return PlayerNotification{DraftPickPrompt, pack, nil} } +// Create a notification about a joined new player. +// The notification only contains the name since a full `game.Player` may not be +// reconstructable for clients without a game state (e.g. draft client). +func NewJoinedPlayerNotification(p *Player) PlayerNotification { + return PlayerNotification{JoinedPlayerNotification, p.Name, nil} +} + +// Create a notification about a player's readiness. +// The notification only contains the name since a full `game.Player` may not be +// reconstructable for clients without a game state (e.g. draft client). +func NewReadyPlayerNotification(p *Player) PlayerNotification { + return PlayerNotification{ReadyPlayerNotification, p.Name, nil} +} + +// Marshal marshals a PlayerNotification to plain text. +// +// The marshaled PlayerNotification has the form `!TYPE{CONTEXT}`. +func (n PlayerNotification) Marshal(s State) []byte { + out := []byte{'!'} + ctx := []byte{} + switch n.Notification { + case DraftPickPrompt: + out = append(out, []byte("pick")...) + ctx = []byte(n.Context.(PileOfCards).String()) + case PriorityNotification: + out = append(out, []byte("priority")...) + case DeclaredActionNotification: + out = append(out, []byte("declared")...) + ctx = MarshalAction(n.Context.(Action)) + case ResolvedActionNotification: + out = append(out, []byte("resolved")...) + if n.Error != nil { + ctx = []byte(n.Error.Error()) + } + case JoinedPlayerNotification: + out = append(out, []byte("joined")...) + ctx = []byte(n.Context.(string)) + case ReadyPlayerNotification: + out = append(out, []byte("ready")...) + ctx = []byte(n.Context.(string)) + default: + log.Fatalf("Marshal(%s) not implement\n", n) + } + out = fmt.Appendf(out, "{%s}", ctx) + + return out +} + +var PlayerNotificationRegex = regexp.MustCompile(`!(.*)\{(.*)\}`) + +type InvalidPlayerNotificationError struct { + ErrorString string +} + +func (err InvalidPlayerNotificationError) Error() string { + return err.ErrorString +} + +var ( + ErrBadFormat = InvalidPlayerNotificationError{"bad format"} + ErrUnknownNotification = InvalidPlayerNotificationError{"unknown notification"} + ErrNoContextExpected = InvalidPlayerNotificationError{"no context expected"} +) + +// UnmarshalPlayerNotification creates a UnmarshalPlayerNotification from plain text. +// For the PlayerNotification plain text format see MarshalPlayerNotification. +func UnmarshalPlayerNotification(s State, in []byte) (n PlayerNotification, err error) { + m := PlayerNotificationRegex.FindSubmatch(in) + if len(m) != 3 { + return n, ErrBadFormat + } + t := string(m[1]) + ctx := m[2] + + switch t { + case "pick": + pack := NewPileOfCards() + err = pack.FromString(string(ctx)) + if err != nil { + return + } + n = newDraftPickPrompt(pack) + + case "priority": + if len(ctx) > 0 { + err = ErrNoContextExpected + } + n = newPriorityNotification() + + case "resolved": + var err error + if len(ctx) > 0 { + err = ErrNoContextExpected + } + n = newResolvedActionNotification(s.Stack().Actions[len(s.Stack().Actions)-1], err) + + case "joined": + n = PlayerNotification{JoinedPlayerNotification, string(ctx), nil} + + case "ready": + n = PlayerNotification{ReadyPlayerNotification, string(ctx), nil} + default: + return n, ErrUnknownNotification + } + return +} + type PlayerControl interface { Player() *Player - RecvAction() Action - SendNotification(PlayerNotification) + // SendAction sends an Action and blocks until it is sent. + SendAction(Action) error + // RecvAction returns the next Action. + // It blocks until the Action is received or an error occurs. + RecvAction() (Action, error) + // SendNotification sends an PlayerNotification and blocks until it is sent. + SendNotification(PlayerNotification) error + // RecvNotification returns a new PlayerNotification. + // If no new notification is available an zero/invalid PlayerNotification is returned and err == nil. + // RecvNotification never blocks. + RecvNotification() (PlayerNotification, error) Close() } +// ChanPlayerControl implements a PlayerControl using two go channels. +// Objects are transferred directly. +// Therefore ChanPlayerControl is only suitable for communication in the same process. type ChanPlayerControl struct { player *Player actions chan Action notifications chan PlayerNotification } -func (c *ChanPlayerControl) Player() *Player { return c.player } -func (c *ChanPlayerControl) SendAction(a Action) { c.actions <- a } -func (c *ChanPlayerControl) RecvAction() Action { return <-c.actions } -func (c *ChanPlayerControl) SendNotification(n PlayerNotification) { c.notifications <- n } -func (c *ChanPlayerControl) RecvNotification() *PlayerNotification { +func (c *ChanPlayerControl) Player() *Player { return c.player } +func (c *ChanPlayerControl) SendAction(a Action) error { + c.actions <- a + return nil +} +func (c *ChanPlayerControl) RecvAction() (Action, error) { + return <-c.actions, nil +} +func (c *ChanPlayerControl) SendNotification(n PlayerNotification) error { + c.notifications <- n + return nil +} + +func (c *ChanPlayerControl) RecvNotification() (PlayerNotification, error) { + var n PlayerNotification select { - case n := <-c.notifications: - return &n + case n = <-c.notifications: default: - return nil } + return n, nil } func (c *ChanPlayerControl) Close() { close(c.actions) @@ -136,15 +280,15 @@ func NewChanPlayerControl(p *Player) *ChanPlayerControl { return &ChanPlayerControl{p, a, n} } -func prompt(ctrl PlayerControl, notification PlayerNotification) Action { +func prompt(ctrl PlayerControl, notification PlayerNotification) (Action, error) { ctrl.SendNotification(notification) return ctrl.RecvAction() } -func promptBuy(ctrl PlayerControl) Action { +func promptBuy(ctrl PlayerControl) (Action, error) { return prompt(ctrl, newBuyPrompt(ctrl.Player())) } -func promptAction(ctrl PlayerControl) Action { +func promptAction(ctrl PlayerControl) (Action, error) { return prompt(ctrl, newPriorityNotification()) } |
