~bsprague/rummikub

3832ddd2a5a2c07a433a09488b4685a52f24de57 — Brandon Sprague 2 years ago a164538
Refactor server + client a bit and remove Test endpoint

Start getting us ready for primetime by adding a basic REPL for the
testclient and making the server a little less unwieldly for working
with games.
M cmd/testclient/main.go => cmd/testclient/main.go +48 -36
@@ 5,7 5,6 @@ import (
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/bcspragu/rummikub/proto/gen/go/rummikubpb"
	"google.golang.org/grpc"


@@ 26,39 25,52 @@ func main() {
	}
	defer conn.Close()

	client := rummikubpb.NewRummikubClient(conn)

	cmd := "test"
	if len(os.Args) > 1 {
		cmd = os.Args[1]
	playClient, err := rummikubpb.NewRummikubClient(conn).Play(context.Background())
	if err != nil {
		log.Fatalf("failed to open play connection: %w", err)
	}

	switch cmd {
	case "test":
		test(client)
	case "join":
		join(client)
	}
	go listenForUpdates(playClient)

}
	for {
		fmt.Print("Enter a command: ")
		var cmd string
		fmt.Scanln(&cmd)

func test(client rummikubpb.RummikubClient) {
	resp, err := client.Test(context.Background(), &rummikubpb.TestRequest{
		Name: "Testing",
	})
	if err != nil {
		log.Fatalf("error calling Test endpoint: %v", err)
		switch cmd {
		case "join":
			err = join(playClient)
		case "start_game":
			err = startGame(playClient)
		default:
			fmt.Printf("unknown command %q, try again\n", cmd)
			continue
		}
		if err != nil {
			log.Println("error running command %q: %v", cmd, err)
		}
	}

	fmt.Printf("Response message: %s\n", resp.Message)
}

func join(client rummikubpb.RummikubClient) {
	playClient, err := client.Play(context.Background())
	if err != nil {
		log.Fatalf("failed to open play connection: %w", err)
func listenForUpdates(playClient rummikubpb.Rummikub_PlayClient) {
	for {
		msg, err := playClient.Recv()
		if err != nil {
			log.Fatalf("failed to get join response: %w", err)
		}

		switch v := msg.Msg.(type) {
		case *rummikubpb.PlayResponse_JoinResponse:
			log.Println("got the join response!")
		case *rummikubpb.PlayResponse_GameStarted:
			log.Printf("the game has been started with %d players", len(v.GameStarted.Players))
		default:
			log.Printf("unexpected response type %T", msg.Msg)
		}
	}
}

func join(playClient rummikubpb.Rummikub_PlayClient) error {
	if err := playClient.Send(&rummikubpb.PlayRequest{
		Msg: &rummikubpb.PlayRequest_JoinRequest{
			JoinRequest: &rummikubpb.JoinRequest{


@@ 66,18 78,18 @@ func join(client rummikubpb.RummikubClient) {
			},
		},
	}); err != nil {
		log.Fatalf("failed to issue join request: %w", err)
	}

	msg, err := playClient.Recv()
	if err != nil {
		log.Fatalf("failed to get join response: %w", err)
		return fmt.Errorf("failed to issue join request: %w", err)
	}
	return nil
}

	switch msg.Msg.(type) {
	case *rummikubpb.PlayResponse_JoinResponse:
		log.Println("got the join response!")
	default:
		log.Fatalf("unexpected response type %T", msg.Msg)
func startGame(playClient rummikubpb.Rummikub_PlayClient) error {
	if err := playClient.Send(&rummikubpb.PlayRequest{
		Msg: &rummikubpb.PlayRequest_StartGame{
			StartGame: &rummikubpb.StartGame{},
		},
	}); err != nil {
		return fmt.Errorf("failed to issue start game request: %w", err)
	}
	return nil
}

M proto/gen/go/rummikubpb/rummikub.pb.go => proto/gen/go/rummikubpb/rummikub.pb.go +174 -233
@@ 20,16 20,19 @@ const (
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

type TestRequest struct {
type PlayRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
	// Types that are assignable to Msg:
	//	*PlayRequest_JoinRequest
	//	*PlayRequest_StartGame
	Msg isPlayRequest_Msg `protobuf_oneof:"msg"`
}

func (x *TestRequest) Reset() {
	*x = TestRequest{}
func (x *PlayRequest) Reset() {
	*x = PlayRequest{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))


@@ 37,13 40,13 @@ func (x *TestRequest) Reset() {
	}
}

func (x *TestRequest) String() string {
func (x *PlayRequest) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*TestRequest) ProtoMessage() {}
func (*PlayRequest) ProtoMessage() {}

func (x *TestRequest) ProtoReflect() protoreflect.Message {
func (x *PlayRequest) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))


@@ 55,28 58,58 @@ func (x *TestRequest) ProtoReflect() protoreflect.Message {
	return mi.MessageOf(x)
}

// Deprecated: Use TestRequest.ProtoReflect.Descriptor instead.
func (*TestRequest) Descriptor() ([]byte, []int) {
// Deprecated: Use PlayRequest.ProtoReflect.Descriptor instead.
func (*PlayRequest) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{0}
}

func (x *TestRequest) GetName() string {
	if x != nil {
		return x.Name
func (m *PlayRequest) GetMsg() isPlayRequest_Msg {
	if m != nil {
		return m.Msg
	}
	return ""
	return nil
}

func (x *PlayRequest) GetJoinRequest() *JoinRequest {
	if x, ok := x.GetMsg().(*PlayRequest_JoinRequest); ok {
		return x.JoinRequest
	}
	return nil
}

type TestResponse struct {
func (x *PlayRequest) GetStartGame() *StartGame {
	if x, ok := x.GetMsg().(*PlayRequest_StartGame); ok {
		return x.StartGame
	}
	return nil
}

type isPlayRequest_Msg interface {
	isPlayRequest_Msg()
}

type PlayRequest_JoinRequest struct {
	JoinRequest *JoinRequest `protobuf:"bytes,1,opt,name=join_request,json=joinRequest,proto3,oneof"`
}

type PlayRequest_StartGame struct {
	StartGame *StartGame `protobuf:"bytes,2,opt,name=start_game,json=startGame,proto3,oneof"`
}

func (*PlayRequest_JoinRequest) isPlayRequest_Msg() {}

func (*PlayRequest_StartGame) isPlayRequest_Msg() {}

type JoinRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}

func (x *TestResponse) Reset() {
	*x = TestResponse{}
func (x *JoinRequest) Reset() {
	*x = JoinRequest{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[1]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))


@@ 84,13 117,13 @@ func (x *TestResponse) Reset() {
	}
}

func (x *TestResponse) String() string {
func (x *JoinRequest) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*TestResponse) ProtoMessage() {}
func (*JoinRequest) ProtoMessage() {}

func (x *TestResponse) ProtoReflect() protoreflect.Message {
func (x *JoinRequest) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[1]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))


@@ 102,30 135,26 @@ func (x *TestResponse) ProtoReflect() protoreflect.Message {
	return mi.MessageOf(x)
}

// Deprecated: Use TestResponse.ProtoReflect.Descriptor instead.
func (*TestResponse) Descriptor() ([]byte, []int) {
// Deprecated: Use JoinRequest.ProtoReflect.Descriptor instead.
func (*JoinRequest) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{1}
}

func (x *TestResponse) GetMessage() string {
func (x *JoinRequest) GetName() string {
	if x != nil {
		return x.Message
		return x.Name
	}
	return ""
}

type PlayRequest struct {
type StartGame struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	// Types that are assignable to Msg:
	//	*PlayRequest_JoinRequest
	Msg isPlayRequest_Msg `protobuf_oneof:"msg"`
}

func (x *PlayRequest) Reset() {
	*x = PlayRequest{}
func (x *StartGame) Reset() {
	*x = StartGame{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[2]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))


@@ 133,13 162,13 @@ func (x *PlayRequest) Reset() {
	}
}

func (x *PlayRequest) String() string {
func (x *StartGame) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*PlayRequest) ProtoMessage() {}
func (*StartGame) ProtoMessage() {}

func (x *PlayRequest) ProtoReflect() protoreflect.Message {
func (x *StartGame) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[2]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))


@@ 151,35 180,11 @@ func (x *PlayRequest) ProtoReflect() protoreflect.Message {
	return mi.MessageOf(x)
}

// Deprecated: Use PlayRequest.ProtoReflect.Descriptor instead.
func (*PlayRequest) Descriptor() ([]byte, []int) {
// Deprecated: Use StartGame.ProtoReflect.Descriptor instead.
func (*StartGame) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{2}
}

func (m *PlayRequest) GetMsg() isPlayRequest_Msg {
	if m != nil {
		return m.Msg
	}
	return nil
}

func (x *PlayRequest) GetJoinRequest() *JoinRequest {
	if x, ok := x.GetMsg().(*PlayRequest_JoinRequest); ok {
		return x.JoinRequest
	}
	return nil
}

type isPlayRequest_Msg interface {
	isPlayRequest_Msg()
}

type PlayRequest_JoinRequest struct {
	JoinRequest *JoinRequest `protobuf:"bytes,1,opt,name=join_request,json=joinRequest,proto3,oneof"`
}

func (*PlayRequest_JoinRequest) isPlayRequest_Msg() {}

type PlayResponse struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache


@@ 288,53 293,6 @@ func (*PlayResponse_PlayerMove) isPlayResponse_Msg() {}

func (*PlayResponse_YourMove) isPlayResponse_Msg() {}

type JoinRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}

func (x *JoinRequest) Reset() {
	*x = JoinRequest{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[4]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *JoinRequest) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*JoinRequest) ProtoMessage() {}

func (x *JoinRequest) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[4]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use JoinRequest.ProtoReflect.Descriptor instead.
func (*JoinRequest) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{4}
}

func (x *JoinRequest) GetName() string {
	if x != nil {
		return x.Name
	}
	return ""
}

type JoinResponse struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache


@@ 344,7 302,7 @@ type JoinResponse struct {
func (x *JoinResponse) Reset() {
	*x = JoinResponse{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[5]
		mi := &file_rummikub_proto_msgTypes[4]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}


@@ 357,7 315,7 @@ func (x *JoinResponse) String() string {
func (*JoinResponse) ProtoMessage() {}

func (x *JoinResponse) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[5]
	mi := &file_rummikub_proto_msgTypes[4]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {


@@ 370,7 328,7 @@ func (x *JoinResponse) ProtoReflect() protoreflect.Message {

// Deprecated: Use JoinResponse.ProtoReflect.Descriptor instead.
func (*JoinResponse) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{5}
	return file_rummikub_proto_rawDescGZIP(), []int{4}
}

type GameStarted struct {


@@ 386,7 344,7 @@ type GameStarted struct {
func (x *GameStarted) Reset() {
	*x = GameStarted{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[6]
		mi := &file_rummikub_proto_msgTypes[5]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}


@@ 399,7 357,7 @@ func (x *GameStarted) String() string {
func (*GameStarted) ProtoMessage() {}

func (x *GameStarted) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[6]
	mi := &file_rummikub_proto_msgTypes[5]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {


@@ 412,7 370,7 @@ func (x *GameStarted) ProtoReflect() protoreflect.Message {

// Deprecated: Use GameStarted.ProtoReflect.Descriptor instead.
func (*GameStarted) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{6}
	return file_rummikub_proto_rawDescGZIP(), []int{5}
}

func (x *GameStarted) GetPlayers() []*Player {


@@ 434,7 392,7 @@ type Player struct {
func (x *Player) Reset() {
	*x = Player{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[7]
		mi := &file_rummikub_proto_msgTypes[6]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}


@@ 447,7 405,7 @@ func (x *Player) String() string {
func (*Player) ProtoMessage() {}

func (x *Player) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[7]
	mi := &file_rummikub_proto_msgTypes[6]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {


@@ 460,7 418,7 @@ func (x *Player) ProtoReflect() protoreflect.Message {

// Deprecated: Use Player.ProtoReflect.Descriptor instead.
func (*Player) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{7}
	return file_rummikub_proto_rawDescGZIP(), []int{6}
}

func (x *Player) GetId() string {


@@ 493,7 451,7 @@ type PlayerMove struct {
func (x *PlayerMove) Reset() {
	*x = PlayerMove{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[8]
		mi := &file_rummikub_proto_msgTypes[7]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}


@@ 506,7 464,7 @@ func (x *PlayerMove) String() string {
func (*PlayerMove) ProtoMessage() {}

func (x *PlayerMove) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[8]
	mi := &file_rummikub_proto_msgTypes[7]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {


@@ 519,7 477,7 @@ func (x *PlayerMove) ProtoReflect() protoreflect.Message {

// Deprecated: Use PlayerMove.ProtoReflect.Descriptor instead.
func (*PlayerMove) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{8}
	return file_rummikub_proto_rawDescGZIP(), []int{7}
}

func (x *PlayerMove) GetId() string {


@@ 555,7 513,7 @@ type YourMove struct {
func (x *YourMove) Reset() {
	*x = YourMove{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[9]
		mi := &file_rummikub_proto_msgTypes[8]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}


@@ 568,7 526,7 @@ func (x *YourMove) String() string {
func (*YourMove) ProtoMessage() {}

func (x *YourMove) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[9]
	mi := &file_rummikub_proto_msgTypes[8]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {


@@ 581,7 539,7 @@ func (x *YourMove) ProtoReflect() protoreflect.Message {

// Deprecated: Use YourMove.ProtoReflect.Descriptor instead.
func (*YourMove) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{9}
	return file_rummikub_proto_rawDescGZIP(), []int{8}
}

func (x *YourMove) GetBoard() *Board {


@@ 600,7 558,7 @@ type Move struct {
func (x *Move) Reset() {
	*x = Move{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[10]
		mi := &file_rummikub_proto_msgTypes[9]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}


@@ 613,7 571,7 @@ func (x *Move) String() string {
func (*Move) ProtoMessage() {}

func (x *Move) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[10]
	mi := &file_rummikub_proto_msgTypes[9]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {


@@ 626,7 584,7 @@ func (x *Move) ProtoReflect() protoreflect.Message {

// Deprecated: Use Move.ProtoReflect.Descriptor instead.
func (*Move) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{10}
	return file_rummikub_proto_rawDescGZIP(), []int{9}
}

type Board struct {


@@ 638,7 596,7 @@ type Board struct {
func (x *Board) Reset() {
	*x = Board{}
	if protoimpl.UnsafeEnabled {
		mi := &file_rummikub_proto_msgTypes[11]
		mi := &file_rummikub_proto_msgTypes[10]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}


@@ 651,7 609,7 @@ func (x *Board) String() string {
func (*Board) ProtoMessage() {}

func (x *Board) ProtoReflect() protoreflect.Message {
	mi := &file_rummikub_proto_msgTypes[11]
	mi := &file_rummikub_proto_msgTypes[10]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {


@@ 664,65 622,61 @@ func (x *Board) ProtoReflect() protoreflect.Message {

// Deprecated: Use Board.ProtoReflect.Descriptor instead.
func (*Board) Descriptor() ([]byte, []int) {
	return file_rummikub_proto_rawDescGZIP(), []int{11}
	return file_rummikub_proto_rawDescGZIP(), []int{10}
}

var File_rummikub_proto protoreflect.FileDescriptor

var file_rummikub_proto_rawDesc = []byte{
	0x0a, 0x0e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
	0x12, 0x08, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x22, 0x21, 0x0a, 0x0b, 0x54, 0x65,
	0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x28, 0x0a,
	0x0c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a,
	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
	0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x50, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x79, 0x52,
	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0c, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x72,
	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72,
	0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75,
	0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
	0x73, 0x74, 0x42, 0x05, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x22, 0xfc, 0x01, 0x0a, 0x0c, 0x50, 0x6c,
	0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0d, 0x6a, 0x6f,
	0x69, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
	0x0b, 0x32, 0x16, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x4a, 0x6f, 0x69,
	0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x6a, 0x6f, 0x69,
	0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x67, 0x61, 0x6d,
	0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
	0x15, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x47, 0x61, 0x6d, 0x65, 0x53,
	0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x61, 0x6d, 0x65, 0x53, 0x74,
	0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0b, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f,
	0x6d, 0x6f, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x72, 0x75, 0x6d,
	0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x6f, 0x76, 0x65,
	0x48, 0x00, 0x52, 0x0a, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x6f, 0x76, 0x65, 0x12, 0x31,
	0x0a, 0x09, 0x79, 0x6f, 0x75, 0x72, 0x5f, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
	0x0b, 0x32, 0x12, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x59, 0x6f, 0x75,
	0x72, 0x4d, 0x6f, 0x76, 0x65, 0x48, 0x00, 0x52, 0x08, 0x79, 0x6f, 0x75, 0x72, 0x4d, 0x6f, 0x76,
	0x65, 0x42, 0x05, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x22, 0x21, 0x0a, 0x0b, 0x4a, 0x6f, 0x69, 0x6e,
	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x0e, 0x0a, 0x0c, 0x4a,
	0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x0a, 0x0b, 0x47,
	0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x07, 0x70, 0x6c,
	0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x75,
	0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x07, 0x70,
	0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x22, 0x2c, 0x0a, 0x06, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72,
	0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,
	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
	0x6e, 0x61, 0x6d, 0x65, 0x22, 0x67, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x6f,
	0x76, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
	0x69, 0x64, 0x12, 0x22, 0x0a, 0x04, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
	0x32, 0x0e, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x4d, 0x6f, 0x76, 0x65,
	0x52, 0x04, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x18,
	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62,
	0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x05, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x22, 0x31, 0x0a,
	0x08, 0x59, 0x6f, 0x75, 0x72, 0x4d, 0x6f, 0x76, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x62, 0x6f, 0x61,
	0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69,
	0x6b, 0x75, 0x62, 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x05, 0x62, 0x6f, 0x61, 0x72, 0x64,
	0x22, 0x06, 0x0a, 0x04, 0x4d, 0x6f, 0x76, 0x65, 0x22, 0x07, 0x0a, 0x05, 0x42, 0x6f, 0x61, 0x72,
	0x64, 0x32, 0x7c, 0x0a, 0x08, 0x52, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x12, 0x35, 0x0a,
	0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62,
	0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72,
	0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70,
	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x79, 0x12, 0x15, 0x2e, 0x72,
	0x12, 0x08, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x22, 0x86, 0x01, 0x0a, 0x0b, 0x50,
	0x6c, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0c, 0x6a, 0x6f,
	0x69, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
	0x32, 0x15, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x4a, 0x6f, 0x69, 0x6e,
	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x52,
	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f,
	0x67, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x75, 0x6d,
	0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x48,
	0x00, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x42, 0x05, 0x0a, 0x03,
	0x6d, 0x73, 0x67, 0x22, 0x21, 0x0a, 0x0b, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
	0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
	0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x72, 0x74, 0x47,
	0x61, 0x6d, 0x65, 0x22, 0xfc, 0x01, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70,
	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0d, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x73,
	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x75,
	0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
	0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x6a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
	0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72,
	0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x75, 0x6d, 0x6d,
	0x69, 0x6b, 0x75, 0x62, 0x2e, 0x47, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64,
	0x48, 0x00, 0x52, 0x0b, 0x67, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12,
	0x37, 0x0a, 0x0b, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x03,
	0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e,
	0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x6f, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x70, 0x6c,
	0x61, 0x79, 0x65, 0x72, 0x4d, 0x6f, 0x76, 0x65, 0x12, 0x31, 0x0a, 0x09, 0x79, 0x6f, 0x75, 0x72,
	0x5f, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x75,
	0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x59, 0x6f, 0x75, 0x72, 0x4d, 0x6f, 0x76, 0x65, 0x48,
	0x00, 0x52, 0x08, 0x79, 0x6f, 0x75, 0x72, 0x4d, 0x6f, 0x76, 0x65, 0x42, 0x05, 0x0a, 0x03, 0x6d,
	0x73, 0x67, 0x22, 0x0e, 0x0a, 0x0c, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
	0x73, 0x65, 0x22, 0x39, 0x0a, 0x0b, 0x47, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65,
	0x64, 0x12, 0x2a, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
	0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x50, 0x6c,
	0x61, 0x79, 0x65, 0x72, 0x52, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x22, 0x2c, 0x0a,
	0x06, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
	0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x67, 0x0a, 0x0a, 0x50,
	0x6c, 0x61, 0x79, 0x65, 0x72, 0x4d, 0x6f, 0x76, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x22, 0x0a, 0x04, 0x6d, 0x6f, 0x76,
	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b,
	0x75, 0x62, 0x2e, 0x4d, 0x6f, 0x76, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x25, 0x0a,
	0x05, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x72,
	0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x05, 0x62,
	0x6f, 0x61, 0x72, 0x64, 0x22, 0x31, 0x0a, 0x08, 0x59, 0x6f, 0x75, 0x72, 0x4d, 0x6f, 0x76, 0x65,
	0x12, 0x25, 0x0a, 0x05, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
	0x0f, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64,
	0x52, 0x05, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x22, 0x06, 0x0a, 0x04, 0x4d, 0x6f, 0x76, 0x65, 0x22,
	0x07, 0x0a, 0x05, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x32, 0x45, 0x0a, 0x08, 0x52, 0x75, 0x6d, 0x6d,
	0x69, 0x6b, 0x75, 0x62, 0x12, 0x39, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x79, 0x12, 0x15, 0x2e, 0x72,
	0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75,
	0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x75, 0x6d, 0x6d, 0x69, 0x6b, 0x75, 0x62, 0x2e, 0x50,
	0x6c, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42,


@@ 744,40 698,38 @@ func file_rummikub_proto_rawDescGZIP() []byte {
	return file_rummikub_proto_rawDescData
}

var file_rummikub_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_rummikub_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_rummikub_proto_goTypes = []interface{}{
	(*TestRequest)(nil),  // 0: rummikub.TestRequest
	(*TestResponse)(nil), // 1: rummikub.TestResponse
	(*PlayRequest)(nil),  // 2: rummikub.PlayRequest
	(*PlayRequest)(nil),  // 0: rummikub.PlayRequest
	(*JoinRequest)(nil),  // 1: rummikub.JoinRequest
	(*StartGame)(nil),    // 2: rummikub.StartGame
	(*PlayResponse)(nil), // 3: rummikub.PlayResponse
	(*JoinRequest)(nil),  // 4: rummikub.JoinRequest
	(*JoinResponse)(nil), // 5: rummikub.JoinResponse
	(*GameStarted)(nil),  // 6: rummikub.GameStarted
	(*Player)(nil),       // 7: rummikub.Player
	(*PlayerMove)(nil),   // 8: rummikub.PlayerMove
	(*YourMove)(nil),     // 9: rummikub.YourMove
	(*Move)(nil),         // 10: rummikub.Move
	(*Board)(nil),        // 11: rummikub.Board
	(*JoinResponse)(nil), // 4: rummikub.JoinResponse
	(*GameStarted)(nil),  // 5: rummikub.GameStarted
	(*Player)(nil),       // 6: rummikub.Player
	(*PlayerMove)(nil),   // 7: rummikub.PlayerMove
	(*YourMove)(nil),     // 8: rummikub.YourMove
	(*Move)(nil),         // 9: rummikub.Move
	(*Board)(nil),        // 10: rummikub.Board
}
var file_rummikub_proto_depIdxs = []int32{
	4,  // 0: rummikub.PlayRequest.join_request:type_name -> rummikub.JoinRequest
	5,  // 1: rummikub.PlayResponse.join_response:type_name -> rummikub.JoinResponse
	6,  // 2: rummikub.PlayResponse.game_started:type_name -> rummikub.GameStarted
	8,  // 3: rummikub.PlayResponse.player_move:type_name -> rummikub.PlayerMove
	9,  // 4: rummikub.PlayResponse.your_move:type_name -> rummikub.YourMove
	7,  // 5: rummikub.GameStarted.players:type_name -> rummikub.Player
	10, // 6: rummikub.PlayerMove.move:type_name -> rummikub.Move
	11, // 7: rummikub.PlayerMove.board:type_name -> rummikub.Board
	11, // 8: rummikub.YourMove.board:type_name -> rummikub.Board
	0,  // 9: rummikub.Rummikub.Test:input_type -> rummikub.TestRequest
	2,  // 10: rummikub.Rummikub.Play:input_type -> rummikub.PlayRequest
	1,  // 11: rummikub.Rummikub.Test:output_type -> rummikub.TestResponse
	3,  // 12: rummikub.Rummikub.Play:output_type -> rummikub.PlayResponse
	11, // [11:13] is the sub-list for method output_type
	9,  // [9:11] is the sub-list for method input_type
	9,  // [9:9] is the sub-list for extension type_name
	9,  // [9:9] is the sub-list for extension extendee
	0,  // [0:9] is the sub-list for field type_name
	1,  // 0: rummikub.PlayRequest.join_request:type_name -> rummikub.JoinRequest
	2,  // 1: rummikub.PlayRequest.start_game:type_name -> rummikub.StartGame
	4,  // 2: rummikub.PlayResponse.join_response:type_name -> rummikub.JoinResponse
	5,  // 3: rummikub.PlayResponse.game_started:type_name -> rummikub.GameStarted
	7,  // 4: rummikub.PlayResponse.player_move:type_name -> rummikub.PlayerMove
	8,  // 5: rummikub.PlayResponse.your_move:type_name -> rummikub.YourMove
	6,  // 6: rummikub.GameStarted.players:type_name -> rummikub.Player
	9,  // 7: rummikub.PlayerMove.move:type_name -> rummikub.Move
	10, // 8: rummikub.PlayerMove.board:type_name -> rummikub.Board
	10, // 9: rummikub.YourMove.board:type_name -> rummikub.Board
	0,  // 10: rummikub.Rummikub.Play:input_type -> rummikub.PlayRequest
	3,  // 11: rummikub.Rummikub.Play:output_type -> rummikub.PlayResponse
	11, // [11:12] is the sub-list for method output_type
	10, // [10:11] is the sub-list for method input_type
	10, // [10:10] is the sub-list for extension type_name
	10, // [10:10] is the sub-list for extension extendee
	0,  // [0:10] is the sub-list for field type_name
}

func init() { file_rummikub_proto_init() }


@@ 787,7 739,7 @@ func file_rummikub_proto_init() {
	}
	if !protoimpl.UnsafeEnabled {
		file_rummikub_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*TestRequest); i {
			switch v := v.(*PlayRequest); i {
			case 0:
				return &v.state
			case 1:


@@ 799,7 751,7 @@ func file_rummikub_proto_init() {
			}
		}
		file_rummikub_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*TestResponse); i {
			switch v := v.(*JoinRequest); i {
			case 0:
				return &v.state
			case 1:


@@ 811,7 763,7 @@ func file_rummikub_proto_init() {
			}
		}
		file_rummikub_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*PlayRequest); i {
			switch v := v.(*StartGame); i {
			case 0:
				return &v.state
			case 1:


@@ 835,18 787,6 @@ func file_rummikub_proto_init() {
			}
		}
		file_rummikub_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*JoinRequest); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_rummikub_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*JoinResponse); i {
			case 0:
				return &v.state


@@ 858,7 798,7 @@ func file_rummikub_proto_init() {
				return nil
			}
		}
		file_rummikub_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
		file_rummikub_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*GameStarted); i {
			case 0:
				return &v.state


@@ 870,7 810,7 @@ func file_rummikub_proto_init() {
				return nil
			}
		}
		file_rummikub_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
		file_rummikub_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*Player); i {
			case 0:
				return &v.state


@@ 882,7 822,7 @@ func file_rummikub_proto_init() {
				return nil
			}
		}
		file_rummikub_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
		file_rummikub_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*PlayerMove); i {
			case 0:
				return &v.state


@@ 894,7 834,7 @@ func file_rummikub_proto_init() {
				return nil
			}
		}
		file_rummikub_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
		file_rummikub_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*YourMove); i {
			case 0:
				return &v.state


@@ 906,7 846,7 @@ func file_rummikub_proto_init() {
				return nil
			}
		}
		file_rummikub_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
		file_rummikub_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*Move); i {
			case 0:
				return &v.state


@@ 918,7 858,7 @@ func file_rummikub_proto_init() {
				return nil
			}
		}
		file_rummikub_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
		file_rummikub_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*Board); i {
			case 0:
				return &v.state


@@ 931,8 871,9 @@ func file_rummikub_proto_init() {
			}
		}
	}
	file_rummikub_proto_msgTypes[2].OneofWrappers = []interface{}{
	file_rummikub_proto_msgTypes[0].OneofWrappers = []interface{}{
		(*PlayRequest_JoinRequest)(nil),
		(*PlayRequest_StartGame)(nil),
	}
	file_rummikub_proto_msgTypes[3].OneofWrappers = []interface{}{
		(*PlayResponse_JoinResponse)(nil),


@@ 946,7 887,7 @@ func file_rummikub_proto_init() {
			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
			RawDescriptor: file_rummikub_proto_rawDesc,
			NumEnums:      0,
			NumMessages:   12,
			NumMessages:   11,
			NumExtensions: 0,
			NumServices:   1,
		},

M proto/gen/go/rummikubpb/rummikub_grpc.pb.go => proto/gen/go/rummikubpb/rummikub_grpc.pb.go +1 -38
@@ 22,7 22,6 @@ const _ = grpc.SupportPackageIsVersion7
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type RummikubClient interface {
	Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error)
	// Play is a bidirectional stream that should be used for actually playing
	// the game. When a client initializes a Play stream, the first message they
	// should send is a JoinRequest with their desired name. They'll receive an


@@ 41,15 40,6 @@ func NewRummikubClient(cc grpc.ClientConnInterface) RummikubClient {
	return &rummikubClient{cc}
}

func (c *rummikubClient) Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) {
	out := new(TestResponse)
	err := c.cc.Invoke(ctx, "/rummikub.Rummikub/Test", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (c *rummikubClient) Play(ctx context.Context, opts ...grpc.CallOption) (Rummikub_PlayClient, error) {
	stream, err := c.cc.NewStream(ctx, &Rummikub_ServiceDesc.Streams[0], "/rummikub.Rummikub/Play", opts...)
	if err != nil {


@@ 85,7 75,6 @@ func (x *rummikubPlayClient) Recv() (*PlayResponse, error) {
// All implementations must embed UnimplementedRummikubServer
// for forward compatibility
type RummikubServer interface {
	Test(context.Context, *TestRequest) (*TestResponse, error)
	// Play is a bidirectional stream that should be used for actually playing
	// the game. When a client initializes a Play stream, the first message they
	// should send is a JoinRequest with their desired name. They'll receive an


@@ 101,9 90,6 @@ type RummikubServer interface {
type UnimplementedRummikubServer struct {
}

func (UnimplementedRummikubServer) Test(context.Context, *TestRequest) (*TestResponse, error) {
	return nil, status.Errorf(codes.Unimplemented, "method Test not implemented")
}
func (UnimplementedRummikubServer) Play(Rummikub_PlayServer) error {
	return status.Errorf(codes.Unimplemented, "method Play not implemented")
}


@@ 120,24 106,6 @@ func RegisterRummikubServer(s grpc.ServiceRegistrar, srv RummikubServer) {
	s.RegisterService(&Rummikub_ServiceDesc, srv)
}

func _Rummikub_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(TestRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(RummikubServer).Test(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/rummikub.Rummikub/Test",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(RummikubServer).Test(ctx, req.(*TestRequest))
	}
	return interceptor(ctx, in, info, handler)
}

func _Rummikub_Play_Handler(srv interface{}, stream grpc.ServerStream) error {
	return srv.(RummikubServer).Play(&rummikubPlayServer{stream})
}


@@ 170,12 138,7 @@ func (x *rummikubPlayServer) Recv() (*PlayRequest, error) {
var Rummikub_ServiceDesc = grpc.ServiceDesc{
	ServiceName: "rummikub.Rummikub",
	HandlerType: (*RummikubServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "Test",
			Handler:    _Rummikub_Test_Handler,
		},
	},
	Methods:     []grpc.MethodDesc{},
	Streams: []grpc.StreamDesc{
		{
			StreamName:    "Play",

M proto/rummikub.proto => proto/rummikub.proto +7 -14
@@ 5,8 5,6 @@ package rummikub;
option go_package = "github.com/bcspragu/rummikub/proto/gen/go/rummikubpb";

service Rummikub {
  rpc Test(TestRequest) returns (TestResponse);

  // Play is a bidirectional stream that should be used for actually playing
  // the game. When a client initializes a Play stream, the first message they
  // should send is a JoinRequest with their desired name. They'll receive an


@@ 17,20 15,19 @@ service Rummikub {
  rpc Play(stream PlayRequest) returns (stream PlayResponse);
}

message TestRequest {
  string name = 1;
}

message TestResponse {
  string message = 1;
}

message PlayRequest {
  oneof msg {
    JoinRequest join_request = 1;
    StartGame start_game = 2;
  }
}

message JoinRequest {
  string name = 1;
}

message StartGame {}

message PlayResponse {
  oneof msg {
    JoinResponse join_response = 1;


@@ 40,10 37,6 @@ message PlayResponse {
  }
}

message JoinRequest {
  string name = 1;
}

message JoinResponse { }

message GameStarted {

M src/server.rs => src/server.rs +35 -20
@@ 9,7 9,7 @@ use rummikub::Game;
use rummikubpb::play_request::Msg as ReqMsg;
use rummikubpb::play_response::Msg as RespMsg;
use rummikubpb::rummikub_server::{Rummikub, RummikubServer};
use rummikubpb::{JoinResponse, PlayRequest, PlayResponse, TestRequest, TestResponse};
use rummikubpb::{GameStarted, JoinRequest, JoinResponse, PlayRequest, PlayResponse, StartGame};

mod rummikub;



@@ 17,8 17,32 @@ pub mod rummikubpb {
    tonic::include_proto!("rummikub");
}

type SrvGame = Arc<Mutex<Game<OsRng>>>;

pub struct GameServer {
    game: Arc<Mutex<Game<OsRng>>>,
    game: SrvGame,
}

pub struct GameHandle {
    game: SrvGame,
}

impl GameHandle {
    fn join_game(&self, req: JoinRequest) -> PlayResponse {
        let key = self.game.lock().unwrap().add_player(&req.name);
        println!("added user {} to game, key {}", req.name, key);

        PlayResponse {
            msg: Some(RespMsg::JoinResponse(JoinResponse {})),
        }
    }

    fn start_game(&self, req: StartGame) -> PlayResponse {
        println!("starting the game!");
        PlayResponse {
            msg: Some(RespMsg::GameStarted(GameStarted { players: vec![] })),
        }
    }
}

impl GameServer {


@@ 27,17 51,17 @@ impl GameServer {
            game: Arc::new(Mutex::from(Game::new(OsRng {}))),
        }
    }

    fn new_game_handle(&self) -> GameHandle {
        return GameHandle {
            game: self.game.clone(),
        };
    }
}

// implementing rpc for service defined in .proto
#[tonic::async_trait]
impl Rummikub for GameServer {
    async fn test(&self, request: Request<TestRequest>) -> Result<Response<TestResponse>, Status> {
        Ok(Response::new(TestResponse {
            message: format!("hello {}", request.get_ref().name),
        }))
    }

    type PlayStream = Pin<Box<dyn Stream<Item = Result<PlayResponse, Status>> + Send + 'static>>;

    async fn play(


@@ 46,24 70,15 @@ impl Rummikub for GameServer {
    ) -> Result<Response<Self::PlayStream>, Status> {
        let mut stream = request.into_inner();

        let mut game = self.game.clone();
        let mut game_handle = self.new_game_handle();

        let output = async_stream::try_stream! {
            while let Some(play_req) = stream.next().await {
                let play_req = play_req?;
                match play_req.msg {
                    None => continue,
                    Some(ReqMsg::JoinRequest(req)) => {
                        let key = game
                            .lock()
                            .unwrap()
                            .add_player(&req.name);
                       println!("added user {} to game, key {}", req.name, "");

                       yield PlayResponse{
                           msg: Some(RespMsg::JoinResponse(JoinResponse { })),
                       };
                    }
                    Some(ReqMsg::JoinRequest(req)) => yield game_handle.join_game(req),
                    Some(ReqMsg::StartGame(req)) => yield game_handle.start_game(req),
                }
            }
        };