aboutsummaryrefslogtreecommitdiff
path: root/go/game/playerControl.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/game/playerControl.go')
-rw-r--r--go/game/playerControl.go174
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())
}