veyron/examples/rockpaperscissors: Add command-line player interface.

Add a command-line player interface for humans to play the game. Human
players can either initiate a new game, or wait for someone else to
challenge them.

Also some other minor changes.

Change-Id: If7c664f674f64f2b8cd3230c8ee0cc6d375b00db
diff --git a/examples/rockpaperscissors/common/common.go b/examples/rockpaperscissors/common/common.go
index 5999042..796d8c3 100644
--- a/examples/rockpaperscissors/common/common.go
+++ b/examples/rockpaperscissors/common/common.go
@@ -82,7 +82,7 @@
 			servers = append(servers, pickBestServer(e.Servers))
 		}
 	}
-	vlog.Infof("findAll(%q) elapsed: %s", t, time.Now().Sub(start))
+	vlog.VI(1).Infof("findAll(%q) elapsed: %s", t, time.Now().Sub(start))
 	return servers, nil
 }
 
@@ -118,8 +118,8 @@
 	for i, r := range score.Rounds {
 		roundOffset := time.Duration(r.StartTimeNS - score.StartTimeNS)
 		roundTime := time.Duration(r.EndTimeNS - r.StartTimeNS)
-		fmt.Fprintf(buf, "Round %2d: Player 1 played %-10q. Player 2 played %-10q. Winner: %d [%-10s/%-10s]\n",
-			i+1, r.Moves[0], r.Moves[1], r.Winner, roundOffset, roundTime)
+		fmt.Fprintf(buf, "Round %2d: Player 1 played %-10q. Player 2 played %-10q. Winner: %d %-28s [%-10s/%-10s]\n",
+			i+1, r.Moves[0], r.Moves[1], r.Winner, r.Comment, roundOffset, roundTime)
 	}
 	fmt.Fprintf(buf, "Winner: %d\n", score.Winner)
 	fmt.Fprintf(buf, "Time: %s\n", time.Duration(score.EndTimeNS-score.StartTimeNS))
diff --git a/examples/rockpaperscissors/impl/impl.go b/examples/rockpaperscissors/impl/impl.go
index b58a9ef..41fd8f6 100644
--- a/examples/rockpaperscissors/impl/impl.go
+++ b/examples/rockpaperscissors/impl/impl.go
@@ -49,9 +49,9 @@
 	return r.judge.play(names[0], id, stream)
 }
 
-func (r *RPS) Challenge(ctx ipc.ServerContext, address string, id rps.GameID) error {
-	vlog.VI(1).Infof("Challenge (%q, %+v) from %s", address, id, ctx.RemoteID())
-	return r.player.challenge(address, id)
+func (r *RPS) Challenge(ctx ipc.ServerContext, address string, id rps.GameID, opts rps.GameOptions) error {
+	vlog.VI(1).Infof("Challenge (%q, %+v, %+v) from %s", address, id, opts, ctx.RemoteID())
+	return r.player.challenge(address, id, opts)
 }
 
 func (r *RPS) Record(ctx ipc.ServerContext, score rps.ScoreCard) error {
diff --git a/examples/rockpaperscissors/impl/judge.go b/examples/rockpaperscissors/impl/judge.go
index 6dd75a7..9c607b3 100644
--- a/examples/rockpaperscissors/impl/judge.go
+++ b/examples/rockpaperscissors/impl/judge.go
@@ -245,8 +245,8 @@
 		}
 		round.Moves[in.player-1] = in.action.Move
 	}
-	round.Winner = j.compareMoves(round.Moves[0], round.Moves[1])
-	vlog.VI(1).Infof("Player 1 played %q. Player 2 played %q. Winner: %d", round.Moves[0], round.Moves[1], round.Winner)
+	round.Winner, round.Comment = j.compareMoves(round.Moves[0], round.Moves[1])
+	vlog.VI(1).Infof("Player 1 played %q. Player 2 played %q. Winner: %d %s", round.Moves[0], round.Moves[1], round.Winner, round.Comment)
 
 	action = rps.JudgeAction{RoundResult: round}
 	for _, s := range info.streams {
@@ -299,24 +299,49 @@
 	return nil
 }
 
-func (j *Judge) compareMoves(m1, m2 string) rps.WinnerTag {
+var moveComments = map[string]string{
+	"lizard-paper":    "lizard eats paper",
+	"lizard-rock":     "rock crushes lizard",
+	"lizard-scissors": "scissors decapitates lizard",
+	"lizard-spock":    "lizard poisons spock",
+	"paper-rock":      "paper covers rock",
+	"paper-scissors":  "scissors cuts paper",
+	"paper-spock":     "paper disproves spock",
+	"rock-scissors":   "rock crushes scissors",
+	"rock-spock":      "spock vaporizes rock",
+	"scissors-spock":  "spock smashes scissors",
+}
+
+func (j *Judge) compareMoves(m1, m2 string) (winner rps.WinnerTag, comment string) {
+	if m1 < m2 {
+		comment = moveComments[m1+"-"+m2]
+	} else {
+		comment = moveComments[m2+"-"+m1]
+	}
 	if m1 == m2 {
-		return rps.Draw
+		winner = rps.Draw
+		return
 	}
 	if m1 == "rock" && (m2 == "scissors" || m2 == "lizard") {
-		return rps.Player1
+		winner = rps.Player1
+		return
 	}
 	if m1 == "paper" && (m2 == "rock" || m2 == "spock") {
-		return rps.Player1
+		winner = rps.Player1
+		return
 	}
 	if m1 == "scissors" && (m2 == "paper" || m2 == "lizard") {
-		return rps.Player1
+		winner = rps.Player1
+		return
 	}
 	if m1 == "lizard" && (m2 == "paper" || m2 == "spock") {
-		return rps.Player1
+		winner = rps.Player1
+		return
 	}
 	if m1 == "spock" && (m2 == "scissors" || m2 == "rock") {
-		return rps.Player1
+		winner = rps.Player1
+		return
 	}
-	return rps.Player2
+	winner = rps.Player2
+	return
 }
diff --git a/examples/rockpaperscissors/impl/player.go b/examples/rockpaperscissors/impl/player.go
index b800a87..f1b84a1 100644
--- a/examples/rockpaperscissors/impl/player.go
+++ b/examples/rockpaperscissors/impl/player.go
@@ -3,7 +3,6 @@
 import (
 	"io"
 	"math/rand"
-	"sync"
 	"time"
 
 	rps "veyron/examples/rockpaperscissors"
@@ -15,7 +14,6 @@
 )
 
 type Player struct {
-	lock            sync.Mutex
 	gamesPlayed     common.Counter
 	gamesWon        common.Counter
 	gamesInProgress common.Counter
@@ -44,22 +42,24 @@
 		vlog.Infof("FindJudge: %v", err)
 		return err
 	}
-	gameID, err := p.createGame(judge)
+	gameID, gameOpts, err := p.createGame(judge)
 	if err != nil {
 		vlog.Infof("createGame: %v", err)
 		return err
 	}
 	vlog.VI(1).Infof("Created gameID %q on %q", gameID, judge)
 
-	opponent, err := common.FindPlayer()
-	if err != nil {
-		vlog.Infof("FindPlayer: %v", err)
-		return err
-	}
-	vlog.VI(1).Infof("chosen opponent is %q", opponent)
-	if err = p.sendChallenge(opponent, judge, gameID); err != nil {
+	for {
+		opponent, err := common.FindPlayer()
+		if err != nil {
+			vlog.Infof("FindPlayer: %v", err)
+			return err
+		}
+		vlog.VI(1).Infof("chosen opponent is %q", opponent)
+		if err = p.sendChallenge(opponent, judge, gameID, gameOpts); err == nil {
+			break
+		}
 		vlog.Infof("sendChallenge: %v", err)
-		return err
 	}
 	result, err := p.playGame(judge, gameID)
 	if err != nil {
@@ -74,29 +74,31 @@
 	return nil
 }
 
-func (p *Player) createGame(server string) (rps.GameID, error) {
+func (p *Player) createGame(server string) (rps.GameID, rps.GameOptions, error) {
 	j, err := rps.BindRockPaperScissors(server)
 	if err != nil {
-		return rps.GameID{}, err
+		return rps.GameID{}, rps.GameOptions{}, err
 	}
 	numRounds := 3 + rand.Intn(3)
 	gameType := rps.Classic
 	if rand.Intn(2) == 1 {
 		gameType = rps.LizardSpock
 	}
-	return j.CreateGame(rt.R().TODOContext(), rps.GameOptions{NumRounds: int32(numRounds), GameType: gameType})
+	gameOpts := rps.GameOptions{NumRounds: int32(numRounds), GameType: gameType}
+	gameId, err := j.CreateGame(rt.R().TODOContext(), gameOpts)
+	return gameId, gameOpts, err
 }
 
-func (p *Player) sendChallenge(opponent, judge string, gameID rps.GameID) error {
+func (p *Player) sendChallenge(opponent, judge string, gameID rps.GameID, gameOpts rps.GameOptions) error {
 	o, err := rps.BindRockPaperScissors(opponent)
 	if err != nil {
 		return err
 	}
-	return o.Challenge(rt.R().TODOContext(), judge, gameID)
+	return o.Challenge(rt.R().TODOContext(), judge, gameID, gameOpts)
 }
 
 // challenge receives an incoming challenge.
-func (p *Player) challenge(judge string, gameID rps.GameID) error {
+func (p *Player) challenge(judge string, gameID rps.GameID, _ rps.GameOptions) error {
 	vlog.VI(1).Infof("challenge received: %s %v", judge, gameID)
 	go p.playGame(judge, gameID)
 	return nil
@@ -139,8 +141,8 @@
 			}
 		}
 		if len(in.RoundResult.Moves[0]) > 0 {
-			vlog.VI(1).Infof("Player 1 played %q. Player 2 played %q. Winner: %v",
-				in.RoundResult.Moves[0], in.RoundResult.Moves[1], in.RoundResult.Winner)
+			vlog.VI(1).Infof("Player 1 played %q. Player 2 played %q. Winner: %v %s",
+				in.RoundResult.Moves[0], in.RoundResult.Moves[1], in.RoundResult.Winner, in.RoundResult.Comment)
 		}
 		if len(in.Score.Players) > 0 {
 			vlog.VI(1).Infof("Score card: %s", common.FormatScoreCard(in.Score))
diff --git a/examples/rockpaperscissors/rpsplayercli/main.go b/examples/rockpaperscissors/rpsplayercli/main.go
new file mode 100644
index 0000000..092dc3f
--- /dev/null
+++ b/examples/rockpaperscissors/rpsplayercli/main.go
@@ -0,0 +1,325 @@
+// rpsplayer is a command-line implementation of the Player service that allows
+// a human player to join the game.
+package main
+
+import (
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	rps "veyron/examples/rockpaperscissors"
+	"veyron/examples/rockpaperscissors/common"
+	sflag "veyron/security/flag"
+
+	"veyron2"
+	"veyron2/ipc"
+	"veyron2/rt"
+	"veyron2/vlog"
+)
+
+var (
+	// TODO(rthellend): Remove the address and protocol flags when the config manager is working.
+	protocol = flag.String("protocol", "tcp", "network to listen on. For example, set to 'veyron' and set --address to the endpoint/name of a proxy to have this service proxied.")
+	address  = flag.String("address", ":0", "address to listen on")
+)
+
+func main() {
+	r := rt.Init()
+	defer r.Shutdown()
+	for {
+		if selectOne([]string{"Initiate Game", "Wait For Challenge"}) == 0 {
+			initiateGame()
+		} else {
+			fmt.Println("Waiting to receive a challenge...")
+			game := recvChallenge(r)
+			playGame(game.address, game.id)
+		}
+		if selectOne([]string{"Play Again", "Quit"}) == 1 {
+			break
+		}
+	}
+}
+
+type gameChallenge struct {
+	address string
+	id      rps.GameID
+	opts    rps.GameOptions
+}
+
+// impl is a PlayerService implementation that prompts the user to accept or
+// decline challenges. While waiting for a reply from the user, any incoming
+// challenges are auto-declined.
+type impl struct {
+	ch      chan gameChallenge
+	decline bool
+	lock    sync.Mutex
+}
+
+func (i *impl) setDecline(v bool) bool {
+	i.lock.Lock()
+	defer i.lock.Unlock()
+	prev := i.decline
+	i.decline = v
+	return prev
+}
+
+func (i *impl) Challenge(ctx ipc.ServerContext, address string, id rps.GameID, opts rps.GameOptions) error {
+	vlog.VI(1).Infof("Challenge (%q, %+v) from %s", address, id, ctx.RemoteID())
+	// When setDecline(true) returns, future challenges will be declined.
+	// Whether the current challenge should be considered depends on the
+	// previous state. If 'decline' was already true, we need to decline
+	// this challenge. It 'decline' was false, this is the first challenge
+	// that we should process.
+	if i.setDecline(true) {
+		return errors.New("player is busy")
+	}
+	fmt.Println()
+	fmt.Printf("Challenge received from %s for a %d-round ", ctx.RemoteID(), opts.NumRounds)
+	switch opts.GameType {
+	case rps.Classic:
+		fmt.Print("Classic ")
+	case rps.LizardSpock:
+		fmt.Print("Lizard-Spock ")
+	default:
+	}
+	fmt.Println("Game.")
+	if selectOne([]string{"Accept", "Decline"}) == 0 {
+		i.ch <- gameChallenge{address, id, opts}
+		return nil
+	}
+	// Start considering challenges again.
+	i.setDecline(false)
+	return errors.New("player declined challenge")
+}
+
+// recvChallenge runs a server until a game challenge is accepted by the user.
+// The server is stopped afterwards.
+func recvChallenge(rt veyron2.Runtime) gameChallenge {
+	server, err := rt.NewServer()
+	if err != nil {
+		vlog.Fatalf("NewServer failed: %v", err)
+	}
+	ch := make(chan gameChallenge)
+
+	if err := server.Register("", ipc.SoloDispatcher(rps.NewServerPlayer(&impl{ch: ch}), sflag.NewAuthorizerOrDie())); err != nil {
+		vlog.Fatalf("Register failed: %v", err)
+	}
+	ep, err := server.Listen(*protocol, *address)
+	if err != nil {
+		vlog.Fatalf("Listen(%q, %q) failed: %v", "tcp", *address, err)
+	}
+	hostname, err := os.Hostname()
+	if err != nil {
+		vlog.Fatalf("os.Hostname failed: %v", err)
+	}
+	if err := server.Publish(fmt.Sprintf("rps/player/%s@%s", os.Getenv("USER"), hostname)); err != nil {
+		vlog.Fatalf("Publish failed: %v", err)
+	}
+	vlog.Infof("Listening on endpoint /%s", ep)
+	result := <-ch
+	server.Stop()
+	return result
+}
+
+// initiateGame initiates a new game by getting a list of judges and players,
+// and asking the user to select one of each, to select the game options, what
+// to play, etc.
+func initiateGame() error {
+	jChan := make(chan findResult)
+	oChan := make(chan findResult)
+	go findAll("judge", jChan)
+	go findAll("player", oChan)
+
+	fmt.Println("Looking for available participants...")
+	judges := <-jChan
+	opponents := <-oChan
+	fmt.Println()
+	if len(judges.names) == 0 || len(opponents.names) == 0 {
+		return errors.New("no one to play with")
+	}
+
+	fmt.Println("Choose a judge:")
+	j := selectOne(judges.names)
+	fmt.Println("Choose an opponent:")
+	o := selectOne(opponents.names)
+	fmt.Println("Choose the type of rock-paper-scissors game would you like to play:")
+	gameType := selectOne([]string{"Classic", "LizardSpock"})
+	fmt.Println("Choose the number of rounds required to win:")
+	numRounds := selectOne([]string{"1", "2", "3", "4", "5", "6"}) + 1
+	gameOpts := rps.GameOptions{NumRounds: int32(numRounds), GameType: rps.GameTypeTag(gameType)}
+
+	gameID, err := createGame(judges.servers[j], gameOpts)
+	if err != nil {
+		vlog.Infof("createGame: %v", err)
+		return err
+	}
+	for {
+		err := sendChallenge(opponents.servers[o], judges.servers[j], gameID, gameOpts)
+		if err == nil {
+			break
+		}
+		fmt.Printf("Challenge was declined by %s (%v)\n", opponents.names[o], err)
+		fmt.Println("Choose another opponent:")
+		o = selectOne(opponents.names)
+	}
+	fmt.Println("Joining the game...")
+	if _, err = playGame(judges.servers[j], gameID); err != nil {
+		vlog.Infof("playGame: %v", err)
+		return err
+	}
+	return nil
+}
+
+func createGame(server string, opts rps.GameOptions) (rps.GameID, error) {
+	j, err := rps.BindRockPaperScissors(server)
+	if err != nil {
+		return rps.GameID{}, err
+	}
+	return j.CreateGame(rt.R().TODOContext(), opts)
+}
+
+func sendChallenge(opponent, judge string, gameID rps.GameID, gameOpts rps.GameOptions) error {
+	o, err := rps.BindRockPaperScissors(opponent)
+	if err != nil {
+		return err
+	}
+	return o.Challenge(rt.R().TODOContext(), judge, gameID, gameOpts)
+}
+
+func playGame(judge string, gameID rps.GameID) (rps.PlayResult, error) {
+	fmt.Println()
+	j, err := rps.BindRockPaperScissors(judge)
+	if err != nil {
+		return rps.PlayResult{}, err
+	}
+	game, err := j.Play(rt.R().TODOContext(), gameID, veyron2.CallTimeout(10*time.Minute))
+	if err != nil {
+		return rps.PlayResult{}, err
+	}
+	var playerNum int32
+	for {
+		in, err := game.Recv()
+		if err == io.EOF {
+			fmt.Println("Game Ended")
+			break
+		}
+		if err != nil {
+			vlog.Infof("recv error: %v", err)
+			break
+		}
+		if in.PlayerNum > 0 {
+			playerNum = in.PlayerNum
+			fmt.Printf("You are player %d\n", in.PlayerNum)
+		}
+		if len(in.OpponentName) > 0 {
+			fmt.Printf("Your opponent is %q\n", in.OpponentName)
+		}
+		if len(in.RoundResult.Moves[0]) > 0 {
+			if playerNum != 1 && playerNum != 2 {
+				vlog.Fatalf("invalid playerNum: %d", playerNum)
+			}
+			fmt.Printf("You played %q\n", in.RoundResult.Moves[playerNum-1])
+			fmt.Printf("Your opponent played %q\n", in.RoundResult.Moves[2-playerNum])
+			if len(in.RoundResult.Comment) > 0 {
+				fmt.Printf(">>> %s <<<\n", strings.ToUpper(in.RoundResult.Comment))
+			}
+			if in.RoundResult.Winner == 0 {
+				fmt.Println("----- It's a draw -----")
+			} else if rps.WinnerTag(playerNum) == in.RoundResult.Winner {
+				fmt.Println("***** You WIN *****")
+			} else {
+				fmt.Println("##### You LOSE #####")
+			}
+		}
+		if len(in.MoveOptions) > 0 {
+			fmt.Println()
+			fmt.Println("Choose your weapon:")
+			m := selectOne(in.MoveOptions)
+			if err := game.Send(rps.PlayerAction{Move: in.MoveOptions[m]}); err != nil {
+				return rps.PlayResult{}, err
+			}
+		}
+		if len(in.Score.Players) > 0 {
+			fmt.Println()
+			fmt.Println("==== GAME SUMMARY ====")
+			fmt.Print(common.FormatScoreCard(in.Score))
+			fmt.Println("======================")
+			if rps.WinnerTag(playerNum) == in.Score.Winner {
+				fmt.Println("You won! :)")
+			} else {
+				fmt.Println("You lost! :(")
+			}
+		}
+	}
+	return game.Finish()
+}
+
+func selectOne(choices []string) (choice int) {
+	if len(choices) == 0 {
+		vlog.Fatal("No options to choose from!")
+	}
+	fmt.Println()
+	for i, x := range choices {
+		fmt.Printf("  %d. %s\n", i+1, x)
+	}
+	fmt.Println()
+	for {
+		if len(choices) == 1 {
+			fmt.Print("Select one [1] --> ")
+		} else {
+			fmt.Printf("Select one [1-%d] --> ", len(choices))
+		}
+		fmt.Scanf("%d", &choice)
+		if choice >= 1 && choice <= len(choices) {
+			choice -= 1
+			break
+		}
+	}
+	fmt.Println()
+	return
+}
+
+type findResult struct {
+	names   []string
+	servers []string
+}
+
+// byName implements sort.Interface for findResult.
+type byName findResult
+
+func (n byName) Len() int {
+	return len(n.names)
+}
+func (n byName) Swap(i, j int) {
+	n.names[i], n.names[j] = n.names[j], n.names[i]
+	n.servers[i], n.servers[j] = n.servers[j], n.servers[i]
+}
+func (n byName) Less(i, j int) bool {
+	return n.names[i] < n.names[j]
+}
+
+func findAll(t string, out chan findResult) {
+	ns := rt.R().Namespace()
+	var result findResult
+	c, err := ns.Glob(rt.R().TODOContext(), "rps/"+t+"/*")
+	if err != nil {
+		vlog.Infof("ns.Glob failed: %v", err)
+		out <- result
+		return
+	}
+	for e := range c {
+		fmt.Print(".")
+		if len(e.Servers) > 0 {
+			result.names = append(result.names, e.Name)
+			result.servers = append(result.servers, e.Servers[0].Server)
+		}
+	}
+	sort.Sort(byName(result))
+	out <- result
+}
diff --git a/examples/rockpaperscissors/service.vdl b/examples/rockpaperscissors/service.vdl
index 4af66e9..1b927b9 100644
--- a/examples/rockpaperscissors/service.vdl
+++ b/examples/rockpaperscissors/service.vdl
@@ -59,6 +59,7 @@
 // Round represents the state of a round.
 type Round struct {
   Moves       [2]string  // Each player's move.
+  Comment     string     // A text comment from judge about the round.
   Winner      WinnerTag  // Who won the round.
   StartTimeNS int64      // The time at which the round started.
   EndTimeNS   int64      // The time at which the round ended.
@@ -82,7 +83,7 @@
 type Player interface {
   // Challenge is used by other players to challenge this player to a game. If
   // the challenge is accepted, the method returns nil.
-  Challenge(Address string, ID GameID) error
+  Challenge(Address string, ID GameID, Opts GameOptions) error
 }
 
 // ScoreKeeper receives the outcome of games from Judges.
diff --git a/examples/rockpaperscissors/service.vdl.go b/examples/rockpaperscissors/service.vdl.go
index 1d9354d..946d6e7 100644
--- a/examples/rockpaperscissors/service.vdl.go
+++ b/examples/rockpaperscissors/service.vdl.go
@@ -43,6 +43,7 @@
 // Round represents the state of a round.
 type Round struct {
 	Moves       [2]string // Each player's move.
+	Comment     string    // A text comment from judge about the round.
 	Winner      WinnerTag // Who won the round.
 	StartTimeNS int64     // The time at which the round started.
 	EndTimeNS   int64     // The time at which the round ended.
@@ -351,6 +352,7 @@
 		_gen_wiretype.ArrayType{Elem: 0x3, Len: 0x2, Name: "", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "veyron/examples/rockpaperscissors.WinnerTag", Tags: []string(nil)}, _gen_wiretype.StructType{
 			[]_gen_wiretype.FieldType{
 				_gen_wiretype.FieldType{Type: 0x47, Name: "Moves"},
+				_gen_wiretype.FieldType{Type: 0x3, Name: "Comment"},
 				_gen_wiretype.FieldType{Type: 0x48, Name: "Winner"},
 				_gen_wiretype.FieldType{Type: 0x25, Name: "StartTimeNS"},
 				_gen_wiretype.FieldType{Type: 0x25, Name: "EndTimeNS"},
@@ -417,7 +419,7 @@
 type Player_ExcludingUniversal interface {
 	// Challenge is used by other players to challenge this player to a game. If
 	// the challenge is accepted, the method returns nil.
-	Challenge(ctx _gen_context.T, Address string, ID GameID, opts ..._gen_ipc.CallOpt) (err error)
+	Challenge(ctx _gen_context.T, Address string, ID GameID, Opts GameOptions, opts ..._gen_ipc.CallOpt) (err error)
 }
 type Player interface {
 	_gen_ipc.UniversalServiceMethods
@@ -429,7 +431,7 @@
 
 	// Challenge is used by other players to challenge this player to a game. If
 	// the challenge is accepted, the method returns nil.
-	Challenge(context _gen_ipc.ServerContext, Address string, ID GameID) (err error)
+	Challenge(context _gen_ipc.ServerContext, Address string, ID GameID, Opts GameOptions) (err error)
 }
 
 // BindPlayer returns the client stub implementing the Player
@@ -475,9 +477,9 @@
 	name   string
 }
 
-func (__gen_c *clientStubPlayer) Challenge(ctx _gen_context.T, Address string, ID GameID, opts ..._gen_ipc.CallOpt) (err error) {
+func (__gen_c *clientStubPlayer) Challenge(ctx _gen_context.T, Address string, ID GameID, Opts GameOptions, opts ..._gen_ipc.CallOpt) (err error) {
 	var call _gen_ipc.Call
-	if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "Challenge", []interface{}{Address, ID}, opts...); err != nil {
+	if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "Challenge", []interface{}{Address, ID, Opts}, opts...); err != nil {
 		return
 	}
 	if ierr := call.Finish(&err); ierr != nil {
@@ -544,9 +546,10 @@
 		InArgs: []_gen_ipc.MethodArgument{
 			{Name: "Address", Type: 3},
 			{Name: "ID", Type: 65},
+			{Name: "Opts", Type: 67},
 		},
 		OutArgs: []_gen_ipc.MethodArgument{
-			{Name: "", Type: 66},
+			{Name: "", Type: 68},
 		},
 	}
 
@@ -556,6 +559,12 @@
 				_gen_wiretype.FieldType{Type: 0x3, Name: "ID"},
 			},
 			"veyron/examples/rockpaperscissors.GameID", []string(nil)},
+		_gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "veyron/examples/rockpaperscissors.GameTypeTag", Tags: []string(nil)}, _gen_wiretype.StructType{
+			[]_gen_wiretype.FieldType{
+				_gen_wiretype.FieldType{Type: 0x24, Name: "NumRounds"},
+				_gen_wiretype.FieldType{Type: 0x42, Name: "GameType"},
+			},
+			"veyron/examples/rockpaperscissors.GameOptions", []string(nil)},
 		_gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}}
 
 	return result, nil
@@ -579,8 +588,8 @@
 	return
 }
 
-func (__gen_s *ServerStubPlayer) Challenge(call _gen_ipc.ServerCall, Address string, ID GameID) (err error) {
-	err = __gen_s.service.Challenge(call, Address, ID)
+func (__gen_s *ServerStubPlayer) Challenge(call _gen_ipc.ServerCall, Address string, ID GameID, Opts GameOptions) (err error) {
+	err = __gen_s.service.Challenge(call, Address, ID, Opts)
 	return
 }
 
@@ -728,6 +737,7 @@
 		_gen_wiretype.ArrayType{Elem: 0x3, Len: 0x2, Name: "", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "veyron/examples/rockpaperscissors.WinnerTag", Tags: []string(nil)}, _gen_wiretype.StructType{
 			[]_gen_wiretype.FieldType{
 				_gen_wiretype.FieldType{Type: 0x43, Name: "Moves"},
+				_gen_wiretype.FieldType{Type: 0x3, Name: "Comment"},
 				_gen_wiretype.FieldType{Type: 0x44, Name: "Winner"},
 				_gen_wiretype.FieldType{Type: 0x25, Name: "StartTimeNS"},
 				_gen_wiretype.FieldType{Type: 0x25, Name: "EndTimeNS"},