├── .gitignore ├── Makefile ├── README.md ├── command ├── poker_print.go └── simple_print.go ├── common ├── ClientTransferDataProtoc.pb.go ├── ServerTransferDataProtoc.pb.go └── poker.go ├── event ├── .gitkeep ├── event_code.go ├── event_context.go ├── event_listener_client_connect.go ├── event_listener_client_exit.go ├── event_listener_client_kick.go ├── event_listener_client_nickname_set.go ├── event_listener_game_landlord_confirm.go ├── event_listener_game_landlord_cycle.go ├── event_listener_game_landlord_elect.go ├── event_listener_game_over.go ├── event_listener_game_poker_play.go ├── event_listener_game_poker_play_cant_pass.go ├── event_listener_game_poker_play_invalid.go ├── event_listener_game_poker_play_less.go ├── event_listener_game_poker_play_mismatch.go ├── event_listener_game_poker_play_order_error.go ├── event_listener_game_poker_play_pass.go ├── event_listener_game_poker_play_redirect.go ├── event_listener_game_starting.go ├── event_listener_pve_difficulty_not_support.go ├── event_listener_room_create_success.go ├── event_listener_room_join_fail_by_full.go ├── event_listener_room_join_fail_by_in_exist.go ├── event_listener_room_join_success.go ├── event_listener_show_options.go ├── event_listener_show_options_pve.go ├── event_listener_show_options_pvp.go ├── event_listener_show_options_settings.go ├── event_listener_show_pokers.go └── event_listener_show_rooms.go ├── go.mod ├── go.sum ├── main.go ├── network ├── buffer.go ├── codec.go └── conn_context.go ├── protoc-resource ├── ClientTransferDataProtoc.proto ├── ServerTransferDataProtoc.proto └── generate.sh └── test ├── print_test.go ├── proto_test.go ├── show_pokers.json └── syntax_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINARY = ratel 2 | VERSION = 1.0.0 3 | GOOS = darwin 4 | GOARCH = amd64 5 | 6 | build: 7 | @export GOOS=${GOOS}; \ 8 | export GOARCH=${GOARCH}; \ 9 | go build -o ratel-${GOOS}-${GOARCH}-${VERSION} 10 | 11 | test: build 12 | @./$(BINARY) -h 39.105.65.8 -p 1024 13 | 14 | .PHONY: build test 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-ratel 2 | 3 | [Ratel](https://github.com/ainilili/ratel) 命令行斗地主 `golang`客户端,完全支持Netty + Protobuf的通信协议,100%兼容`Ratel`服务端。 4 | 5 | ## 开始游戏 6 | 7 | - 1.启动方式一:指定服务器 8 | 9 | ``` 10 | $ ./ratel -h 39.105.65.8 -p 1024 11 | ``` 12 | 13 | - 2.启动方式二:使用可选的服务器 14 | 15 | ``` 16 | $ ./ratel 17 | ``` 18 | 19 | ## 视频演示 20 | 21 | [Ratel 命令行斗地主 golang客户端](https://www.bilibili.com/video/BV1kV411k79c) 22 | 23 | ## 关键点剖析 24 | 25 | **1.使用Google Protobuf - 实现跨平台跨语言的序列化/反序列化** 26 | 27 | **2.Ratel使用Netty内置的Protobuf编解码器** 28 | 29 | - 编码器:ProtobufVarint32LengthFieldPrepender 30 | 31 | ``` 32 | BEFORE DECODE (302 bytes) AFTER DECODE (300 bytes) 33 | +--------+---------------+ +---------------+ 34 | | Length | Protobuf Data |----->| Protobuf Data | 35 | | 0xAC02 | (300 bytes) | | (300 bytes) | 36 | +--------+---------------+ +---------------+ 37 | ``` 38 | 39 | - 解码器:ProtobufVarint32FrameDecoder 40 | 41 | ``` 42 | BEFORE ENCODE (300 bytes) AFTER ENCODE (302 bytes) 43 | +---------------+ +--------+---------------+ 44 | | Protobuf Data |-------------->| Length | Protobuf Data | 45 | | (300 bytes) | | 0xAC02 | (300 bytes) | 46 | +---------------+ +--------+---------------+ 47 | ``` 48 | 49 | 大致的协议就是,一个完整包由`头部(Header)`和`数据体(Body)`组成。`编码器`会计算出`Body`的长度放在`Header`中。`Body`的长度 50 | 采用 [Varint](https://developers.google.com/protocol-buffers/docs/encoding#varints),会把整数编码为变长字节。对于 51 | 32位整型数据经过Varint编码后需要1~5个字节,小的数字使用1个byte,大的数字使用5个bytes。 52 | 53 | **3.Golang客户端处理粘包和拆包** 54 | 55 | - 编码:序列化结构体,计算数据包长度,换算成Varint 56 | 57 | ```go 58 | func (c *Codec) Encode(transferData *common.ServerTransferDataProtoc, duration duration.Duration) error { 59 | // protobuf 序列化 60 | encodeData, e := proto.Marshal(transferData) 61 | if e != nil { 62 | return e 63 | } 64 | // 计算数据体长度 65 | bodyLen := len(encodeData) 66 | if bodyLen > MaxContextLen { 67 | return errors.New("not enough") 68 | } 69 | // 使用Varint类型 70 | header := proto.EncodeVarint(uint64(bodyLen)) 71 | 72 | buffer := make([]byte, len(header) + bodyLen) 73 | copy(buffer, header) 74 | copy(buffer[len(header):], encodeData) 75 | 76 | _, e = c.Conn.Write(buffer) 77 | return e 78 | } 79 | ``` 80 | 81 | - 解码:计算数据包头部,换算Varint,处理粘包,反序列结构体 82 | 83 | ```go 84 | func (c *Codec) Decode() (*common.ServerTransferDataProtoc, bool, error) { 85 | // 计算出 body 体长度,以及头部占用的字节数 size 86 | bodyLen, size := proto.DecodeVarint(c.Buffer.buf[c.Buffer.start:]) 87 | if bodyLen > MaxContextLen { 88 | return nil, false, errors.New("not enough") 89 | } 90 | if bodyLen == 0 { 91 | return nil, false, nil 92 | } 93 | // 在当读取的字节数不满足 body 体的长度,进入下一轮 94 | body, e := c.Buffer.read(size, int(bodyLen)) 95 | if e != nil { 96 | return nil, false, nil 97 | } 98 | 99 | transferData := common.ServerTransferDataProtoc{} 100 | // 反序列化 101 | e = proto.Unmarshal(body, &transferData) 102 | if e != nil { 103 | return nil, false, e 104 | } 105 | return &transferData, true, nil 106 | } 107 | ``` 108 | 109 | ## 参考资料 110 | 111 | 1.[详解varint编码原理](https://segmentfault.com/a/1190000020500985?utm_source=tag-newest) 112 | 113 | 2.[Netty源码分析-ProtobufVarint32FrameDecoder](https://blog.csdn.net/nimasike/article/details/101392803) 114 | 115 | 3.[golang 使用 protobuf 的教程](https://www.cnblogs.com/smallleiit/p/10926794.html) -------------------------------------------------------------------------------- /command/poker_print.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "go-ratel/common" 6 | ) 7 | 8 | type Poker = common.Poker 9 | type PokerEntity = common.PokerEntity 10 | 11 | func PrintPokers(pokers []Poker, printerType int) { 12 | pokerEntitys := common.ConvertEnumPoker(&pokers) 13 | sortPokers(pokerEntitys) 14 | switch printerType { 15 | case 0: 16 | buildHandStringSharp(*pokerEntitys) 17 | case 1: 18 | buildHandStringRounded(*pokerEntitys) 19 | case 2: 20 | textOnly(*pokerEntitys) 21 | case 3: 22 | textOnlyNoType(*pokerEntitys) 23 | default: 24 | buildHandStringSharp(*pokerEntitys) 25 | } 26 | } 27 | 28 | func sortPokers(pokers *[]PokerEntity) { 29 | innerPokers := *pokers 30 | length := len(innerPokers) 31 | for i := 0; i < length-1; i++ { 32 | for j := 0; j < length-1-i; j++ { 33 | 34 | if innerPokers[j].Level.Level > innerPokers[j+1].Level.Level { 35 | temp := innerPokers[j] 36 | innerPokers[j] = innerPokers[j+1] 37 | innerPokers[j+1] = temp 38 | } 39 | } 40 | } 41 | *pokers = innerPokers 42 | } 43 | 44 | func buildHandStringSharp(pokerEntitys []PokerEntity) { 45 | outputStr := "" 46 | if pokerEntitys != nil && len(pokerEntitys) > 0 { 47 | for i := 0; i < len(pokerEntitys); i++ { 48 | if i == 0 { 49 | outputStr += "┌──┐" 50 | } else { 51 | outputStr += "──┐" 52 | } 53 | } 54 | } 55 | outputStr += "\n" 56 | for i := 0; i < len(pokerEntitys); i++ { 57 | if i == 0 { 58 | outputStr += "|" 59 | } 60 | name := pokerEntitys[i].Level.Name 61 | if len(name) == 1 { 62 | outputStr += name + " " + "|" 63 | } else { 64 | outputStr += name + "|" 65 | } 66 | } 67 | outputStr += "\n" 68 | for i := 0; i < len(pokerEntitys); i++ { 69 | if i == 0 { 70 | outputStr += "|" 71 | } 72 | outputStr += pokerEntitys[i].Type + " |" 73 | } 74 | outputStr += "\n" 75 | for i := 0; i < len(pokerEntitys); i++ { 76 | if i == 0 { 77 | outputStr += "└──┘" 78 | } else { 79 | outputStr += "──┘" 80 | } 81 | } 82 | fmt.Println(outputStr) 83 | } 84 | 85 | func buildHandStringRounded(pokerEntitys []PokerEntity) { 86 | outputStr := "" 87 | if pokerEntitys != nil && len(pokerEntitys) > 0 { 88 | for i := 0; i < len(pokerEntitys); i++ { 89 | if i == 0 { 90 | outputStr += "┌──╮" 91 | } else { 92 | outputStr += "──╮" 93 | } 94 | } 95 | outputStr += "\n" 96 | for i := 0; i < len(pokerEntitys); i++ { 97 | if i == 0 { 98 | outputStr += "|" 99 | } 100 | name := pokerEntitys[i].Level.Name 101 | if len(name) == 1 { 102 | outputStr += name + " " + "|" 103 | } else { 104 | outputStr += name + "|" 105 | } 106 | } 107 | outputStr += "\n" 108 | for i := 0; i < len(pokerEntitys); i++ { 109 | if i == 0 { 110 | outputStr += "└──╯" 111 | } else { 112 | outputStr += "──╯" 113 | } 114 | } 115 | } 116 | fmt.Println(outputStr) 117 | } 118 | 119 | func textOnly(pokerEntitys []PokerEntity) { 120 | outputStr := "" 121 | if pokerEntitys != nil && len(pokerEntitys) > 0 { 122 | for i := 0; i < len(pokerEntitys); i++ { 123 | name := pokerEntitys[i].Level.Name 124 | pokerType := pokerEntitys[i].Type 125 | outputStr += name + pokerType 126 | } 127 | } 128 | fmt.Println(outputStr) 129 | } 130 | 131 | func textOnlyNoType(pokerEntitys []PokerEntity) { 132 | outputStr := "" 133 | if pokerEntitys != nil && len(pokerEntitys) > 0 { 134 | for i := 0; i < len(pokerEntitys); i++ { 135 | name := pokerEntitys[i].Level.Name 136 | outputStr += name + " " 137 | } 138 | } 139 | fmt.Println(outputStr) 140 | } 141 | -------------------------------------------------------------------------------- /command/simple_print.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | var input = bufio.NewScanner(os.Stdin) 10 | 11 | func Write(message string) string { 12 | fmt.Print("[ratel@" + message + "]$ ") 13 | input.Scan() 14 | return input.Text() 15 | } 16 | 17 | func PrintNotice(msg string) { 18 | fmt.Println(msg) 19 | } 20 | 21 | func DeletePreAndSufSpace(str string) string { 22 | strList := []byte(str) 23 | spaceCount, count := 0, len(strList) 24 | for i := 0; i <= len(strList)-1; i++ { 25 | if strList[i] == 32 { 26 | spaceCount++ 27 | } else { 28 | break 29 | } 30 | } 31 | 32 | strList = strList[spaceCount:] 33 | spaceCount, count = 0, len(strList) 34 | for i := count - 1; i >= 0; i-- { 35 | if strList[i] == 32 { 36 | spaceCount++ 37 | } else { 38 | break 39 | } 40 | } 41 | 42 | return string(strList[:count-spaceCount]) 43 | } 44 | -------------------------------------------------------------------------------- /common/ClientTransferDataProtoc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.23.0 4 | // protoc v3.7.1 5 | // source: ClientTransferDataProtoc.proto 6 | 7 | package common 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // This is a compile-time assertion that a sufficiently up-to-date version 25 | // of the legacy proto package is being used. 26 | const _ = proto.ProtoPackageIsVersion4 27 | 28 | type ClientTransferDataProtoc struct { 29 | state protoimpl.MessageState 30 | sizeCache protoimpl.SizeCache 31 | unknownFields protoimpl.UnknownFields 32 | 33 | Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` 34 | Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` 35 | Info string `protobuf:"bytes,3,opt,name=info,proto3" json:"info,omitempty"` 36 | } 37 | 38 | func (x *ClientTransferDataProtoc) Reset() { 39 | *x = ClientTransferDataProtoc{} 40 | if protoimpl.UnsafeEnabled { 41 | mi := &file_ClientTransferDataProtoc_proto_msgTypes[0] 42 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 43 | ms.StoreMessageInfo(mi) 44 | } 45 | } 46 | 47 | func (x *ClientTransferDataProtoc) String() string { 48 | return protoimpl.X.MessageStringOf(x) 49 | } 50 | 51 | func (*ClientTransferDataProtoc) ProtoMessage() {} 52 | 53 | func (x *ClientTransferDataProtoc) ProtoReflect() protoreflect.Message { 54 | mi := &file_ClientTransferDataProtoc_proto_msgTypes[0] 55 | if protoimpl.UnsafeEnabled && x != nil { 56 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 57 | if ms.LoadMessageInfo() == nil { 58 | ms.StoreMessageInfo(mi) 59 | } 60 | return ms 61 | } 62 | return mi.MessageOf(x) 63 | } 64 | 65 | // Deprecated: Use ClientTransferDataProtoc.ProtoReflect.Descriptor instead. 66 | func (*ClientTransferDataProtoc) Descriptor() ([]byte, []int) { 67 | return file_ClientTransferDataProtoc_proto_rawDescGZIP(), []int{0} 68 | } 69 | 70 | func (x *ClientTransferDataProtoc) GetCode() string { 71 | if x != nil { 72 | return x.Code 73 | } 74 | return "" 75 | } 76 | 77 | func (x *ClientTransferDataProtoc) GetData() string { 78 | if x != nil { 79 | return x.Data 80 | } 81 | return "" 82 | } 83 | 84 | func (x *ClientTransferDataProtoc) GetInfo() string { 85 | if x != nil { 86 | return x.Info 87 | } 88 | return "" 89 | } 90 | 91 | var File_ClientTransferDataProtoc_proto protoreflect.FileDescriptor 92 | 93 | var file_ClientTransferDataProtoc_proto_rawDesc = []byte{ 94 | 0x0a, 0x1e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 95 | 0x44, 0x61, 0x74, 0x61, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 96 | 0x22, 0x56, 0x0a, 0x18, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 97 | 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x12, 0x12, 0x0a, 0x04, 98 | 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 99 | 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 100 | 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 101 | 0x28, 0x09, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x3b, 0x63, 0x6f, 102 | 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 103 | } 104 | 105 | var ( 106 | file_ClientTransferDataProtoc_proto_rawDescOnce sync.Once 107 | file_ClientTransferDataProtoc_proto_rawDescData = file_ClientTransferDataProtoc_proto_rawDesc 108 | ) 109 | 110 | func file_ClientTransferDataProtoc_proto_rawDescGZIP() []byte { 111 | file_ClientTransferDataProtoc_proto_rawDescOnce.Do(func() { 112 | file_ClientTransferDataProtoc_proto_rawDescData = protoimpl.X.CompressGZIP(file_ClientTransferDataProtoc_proto_rawDescData) 113 | }) 114 | return file_ClientTransferDataProtoc_proto_rawDescData 115 | } 116 | 117 | var file_ClientTransferDataProtoc_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 118 | var file_ClientTransferDataProtoc_proto_goTypes = []interface{}{ 119 | (*ClientTransferDataProtoc)(nil), // 0: ClientTransferDataProtoc 120 | } 121 | var file_ClientTransferDataProtoc_proto_depIdxs = []int32{ 122 | 0, // [0:0] is the sub-list for method output_type 123 | 0, // [0:0] is the sub-list for method input_type 124 | 0, // [0:0] is the sub-list for extension type_name 125 | 0, // [0:0] is the sub-list for extension extendee 126 | 0, // [0:0] is the sub-list for field type_name 127 | } 128 | 129 | func init() { file_ClientTransferDataProtoc_proto_init() } 130 | func file_ClientTransferDataProtoc_proto_init() { 131 | if File_ClientTransferDataProtoc_proto != nil { 132 | return 133 | } 134 | if !protoimpl.UnsafeEnabled { 135 | file_ClientTransferDataProtoc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 136 | switch v := v.(*ClientTransferDataProtoc); i { 137 | case 0: 138 | return &v.state 139 | case 1: 140 | return &v.sizeCache 141 | case 2: 142 | return &v.unknownFields 143 | default: 144 | return nil 145 | } 146 | } 147 | } 148 | type x struct{} 149 | out := protoimpl.TypeBuilder{ 150 | File: protoimpl.DescBuilder{ 151 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 152 | RawDescriptor: file_ClientTransferDataProtoc_proto_rawDesc, 153 | NumEnums: 0, 154 | NumMessages: 1, 155 | NumExtensions: 0, 156 | NumServices: 0, 157 | }, 158 | GoTypes: file_ClientTransferDataProtoc_proto_goTypes, 159 | DependencyIndexes: file_ClientTransferDataProtoc_proto_depIdxs, 160 | MessageInfos: file_ClientTransferDataProtoc_proto_msgTypes, 161 | }.Build() 162 | File_ClientTransferDataProtoc_proto = out.File 163 | file_ClientTransferDataProtoc_proto_rawDesc = nil 164 | file_ClientTransferDataProtoc_proto_goTypes = nil 165 | file_ClientTransferDataProtoc_proto_depIdxs = nil 166 | } 167 | -------------------------------------------------------------------------------- /common/ServerTransferDataProtoc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.23.0 4 | // protoc v3.7.1 5 | // source: ServerTransferDataProtoc.proto 6 | 7 | package common 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // This is a compile-time assertion that a sufficiently up-to-date version 25 | // of the legacy proto package is being used. 26 | const _ = proto.ProtoPackageIsVersion4 27 | 28 | type ServerTransferDataProtoc struct { 29 | state protoimpl.MessageState 30 | sizeCache protoimpl.SizeCache 31 | unknownFields protoimpl.UnknownFields 32 | 33 | Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` 34 | Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` 35 | Info string `protobuf:"bytes,3,opt,name=info,proto3" json:"info,omitempty"` 36 | } 37 | 38 | func (x *ServerTransferDataProtoc) Reset() { 39 | *x = ServerTransferDataProtoc{} 40 | if protoimpl.UnsafeEnabled { 41 | mi := &file_ServerTransferDataProtoc_proto_msgTypes[0] 42 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 43 | ms.StoreMessageInfo(mi) 44 | } 45 | } 46 | 47 | func (x *ServerTransferDataProtoc) String() string { 48 | return protoimpl.X.MessageStringOf(x) 49 | } 50 | 51 | func (*ServerTransferDataProtoc) ProtoMessage() {} 52 | 53 | func (x *ServerTransferDataProtoc) ProtoReflect() protoreflect.Message { 54 | mi := &file_ServerTransferDataProtoc_proto_msgTypes[0] 55 | if protoimpl.UnsafeEnabled && x != nil { 56 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 57 | if ms.LoadMessageInfo() == nil { 58 | ms.StoreMessageInfo(mi) 59 | } 60 | return ms 61 | } 62 | return mi.MessageOf(x) 63 | } 64 | 65 | // Deprecated: Use ServerTransferDataProtoc.ProtoReflect.Descriptor instead. 66 | func (*ServerTransferDataProtoc) Descriptor() ([]byte, []int) { 67 | return file_ServerTransferDataProtoc_proto_rawDescGZIP(), []int{0} 68 | } 69 | 70 | func (x *ServerTransferDataProtoc) GetCode() string { 71 | if x != nil { 72 | return x.Code 73 | } 74 | return "" 75 | } 76 | 77 | func (x *ServerTransferDataProtoc) GetData() string { 78 | if x != nil { 79 | return x.Data 80 | } 81 | return "" 82 | } 83 | 84 | func (x *ServerTransferDataProtoc) GetInfo() string { 85 | if x != nil { 86 | return x.Info 87 | } 88 | return "" 89 | } 90 | 91 | var File_ServerTransferDataProtoc_proto protoreflect.FileDescriptor 92 | 93 | var file_ServerTransferDataProtoc_proto_rawDesc = []byte{ 94 | 0x0a, 0x1e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 95 | 0x44, 0x61, 0x74, 0x61, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 96 | 0x22, 0x56, 0x0a, 0x18, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 97 | 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x12, 0x12, 0x0a, 0x04, 98 | 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 99 | 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 100 | 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 101 | 0x28, 0x09, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x3b, 0x63, 0x6f, 102 | 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 103 | } 104 | 105 | var ( 106 | file_ServerTransferDataProtoc_proto_rawDescOnce sync.Once 107 | file_ServerTransferDataProtoc_proto_rawDescData = file_ServerTransferDataProtoc_proto_rawDesc 108 | ) 109 | 110 | func file_ServerTransferDataProtoc_proto_rawDescGZIP() []byte { 111 | file_ServerTransferDataProtoc_proto_rawDescOnce.Do(func() { 112 | file_ServerTransferDataProtoc_proto_rawDescData = protoimpl.X.CompressGZIP(file_ServerTransferDataProtoc_proto_rawDescData) 113 | }) 114 | return file_ServerTransferDataProtoc_proto_rawDescData 115 | } 116 | 117 | var file_ServerTransferDataProtoc_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 118 | var file_ServerTransferDataProtoc_proto_goTypes = []interface{}{ 119 | (*ServerTransferDataProtoc)(nil), // 0: ServerTransferDataProtoc 120 | } 121 | var file_ServerTransferDataProtoc_proto_depIdxs = []int32{ 122 | 0, // [0:0] is the sub-list for method output_type 123 | 0, // [0:0] is the sub-list for method input_type 124 | 0, // [0:0] is the sub-list for extension type_name 125 | 0, // [0:0] is the sub-list for extension extendee 126 | 0, // [0:0] is the sub-list for field type_name 127 | } 128 | 129 | func init() { file_ServerTransferDataProtoc_proto_init() } 130 | func file_ServerTransferDataProtoc_proto_init() { 131 | if File_ServerTransferDataProtoc_proto != nil { 132 | return 133 | } 134 | if !protoimpl.UnsafeEnabled { 135 | file_ServerTransferDataProtoc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 136 | switch v := v.(*ServerTransferDataProtoc); i { 137 | case 0: 138 | return &v.state 139 | case 1: 140 | return &v.sizeCache 141 | case 2: 142 | return &v.unknownFields 143 | default: 144 | return nil 145 | } 146 | } 147 | } 148 | type x struct{} 149 | out := protoimpl.TypeBuilder{ 150 | File: protoimpl.DescBuilder{ 151 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 152 | RawDescriptor: file_ServerTransferDataProtoc_proto_rawDesc, 153 | NumEnums: 0, 154 | NumMessages: 1, 155 | NumExtensions: 0, 156 | NumServices: 0, 157 | }, 158 | GoTypes: file_ServerTransferDataProtoc_proto_goTypes, 159 | DependencyIndexes: file_ServerTransferDataProtoc_proto_depIdxs, 160 | MessageInfos: file_ServerTransferDataProtoc_proto_msgTypes, 161 | }.Build() 162 | File_ServerTransferDataProtoc_proto = out.File 163 | file_ServerTransferDataProtoc_proto_rawDesc = nil 164 | file_ServerTransferDataProtoc_proto_goTypes = nil 165 | file_ServerTransferDataProtoc_proto_depIdxs = nil 166 | } 167 | -------------------------------------------------------------------------------- /common/poker.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type PokerType = string 4 | type PokerLevel = string 5 | 6 | type Poker struct { 7 | Level PokerLevel 8 | Type PokerType 9 | } 10 | 11 | type Level struct { 12 | Level int 13 | Name string 14 | Alias []string 15 | } 16 | 17 | type Room struct { 18 | Id int 19 | } 20 | 21 | var pokerTypeMap = make(map[string]string) 22 | var pokerLevelMap = make(map[string]Level) 23 | 24 | func init() { 25 | pokerTypeMap["BLANK"] = " " 26 | pokerTypeMap["DIAMOND"] = "♦" 27 | pokerTypeMap["CLUB"] = "♣" 28 | pokerTypeMap["SPADE"] = "♠" 29 | pokerTypeMap["HEART"] = "♥" 30 | 31 | pokerLevelMap["LEVEL_3"] = Level{Level: 3, Name: "3", Alias: []string{"3"}} 32 | pokerLevelMap["LEVEL_4"] = Level{Level: 4, Name: "4", Alias: []string{"4"}} 33 | pokerLevelMap["LEVEL_5"] = Level{Level: 5, Name: "5", Alias: []string{"5"}} 34 | pokerLevelMap["LEVEL_6"] = Level{Level: 6, Name: "6", Alias: []string{"6"}} 35 | pokerLevelMap["LEVEL_7"] = Level{Level: 7, Name: "7", Alias: []string{"7"}} 36 | pokerLevelMap["LEVEL_8"] = Level{Level: 8, Name: "8", Alias: []string{"8"}} 37 | pokerLevelMap["LEVEL_9"] = Level{Level: 9, Name: "9", Alias: []string{"9"}} 38 | pokerLevelMap["LEVEL_10"] = Level{Level: 10, Name: "10", Alias: []string{"T", "t", "0"}} 39 | pokerLevelMap["LEVEL_J"] = Level{Level: 11, Name: "J", Alias: []string{"J", "j"}} 40 | pokerLevelMap["LEVEL_Q"] = Level{Level: 12, Name: "Q", Alias: []string{"Q", "q"}} 41 | pokerLevelMap["LEVEL_K"] = Level{Level: 13, Name: "K", Alias: []string{"K", "k"}} 42 | pokerLevelMap["LEVEL_A"] = Level{Level: 14, Name: "A", Alias: []string{"A", "a", "1"}} 43 | pokerLevelMap["LEVEL_2"] = Level{Level: 15, Name: "2", Alias: []string{"2"}} 44 | pokerLevelMap["LEVEL_SMALL_KING"] = Level{Level: 16, Name: "S", Alias: []string{"S", "s"}} 45 | pokerLevelMap["LEVEL_BIG_KING"] = Level{Level: 17, Name: "X", Alias: []string{"X", "x"}} 46 | } 47 | 48 | // 将Java枚举转换成struct实体 49 | type PokerEntity struct { 50 | Type PokerType 51 | Level Level 52 | } 53 | 54 | func ConvertEnumPoker(pokers *[]Poker) *[]PokerEntity { 55 | pokerEntitys := make([]PokerEntity, 0) 56 | for _, poker := range *pokers { 57 | pokerEntitys = append(pokerEntitys, PokerEntity{ 58 | Type: pokerTypeMap[poker.Type], 59 | Level: pokerLevelMap[poker.Level], 60 | }) 61 | } 62 | return &pokerEntitys 63 | } 64 | -------------------------------------------------------------------------------- /event/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZuoFuhong/go-ratel/0d3e7d015f1931496e3ec82086c6278d94e41ae2/event/.gitkeep -------------------------------------------------------------------------------- /event/event_code.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | const ( 4 | CODE_CLIENT_NICKNAME_SET = "CODE_CLIENT_NICKNAME_SET" // 设置昵称 5 | CODE_CLIENT_EXIT = "CODE_CLIENT_EXIT" // 客户端退出 6 | CODE_CLIENT_KICK = "CODE_CLIENT_KICK" // 客户端被踢出 7 | CODE_CLIENT_CONNECT = "CODE_CLIENT_CONNECT" // 客户端加入成功 8 | CODE_SHOW_OPTIONS = "CODE_SHOW_OPTIONS" // 全局选项列表 9 | CODE_SHOW_OPTIONS_SETTING = "CODE_SHOW_OPTIONS_SETTING" // 设置选项 10 | CODE_SHOW_OPTIONS_PVP = "CODE_SHOW_OPTIONS_PVP" // 玩家对战选项 11 | CODE_SHOW_OPTIONS_PVE = "CODE_SHOW_OPTIONS_PVE" // 人机对战选项 12 | CODE_SHOW_ROOMS = "CODE_SHOW_ROOMS" // 展示房间列表 13 | CODE_SHOW_POKERS = "CODE_SHOW_POKERS" // 展示Poker 14 | CODE_ROOM_CREATE_SUCCESS = "CODE_ROOM_CREATE_SUCCESS" // 创建房间成功 15 | CODE_ROOM_JOIN_SUCCESS = "CODE_ROOM_JOIN_SUCCESS" // 加入房间成功 16 | CODE_ROOM_JOIN_FAIL_BY_FULL = "CODE_ROOM_JOIN_FAIL_BY_FULL" // 房间人数已满 17 | CODE_ROOM_JOIN_FAIL_BY_INEXIST = "CODE_ROOM_JOIN_FAIL_BY_INEXIST" // 加入-房间不存在 18 | CODE_GAME_STARTING = "CODE_GAME_STARTING" // 开始游戏 19 | CODE_GAME_LANDLORD_ELECT = "CODE_GAME_LANDLORD_ELECT" // 抢地主 20 | CODE_GAME_LANDLORD_CONFIRM = "CODE_GAME_LANDLORD_CONFIRM" // 地主确认 21 | CODE_GAME_LANDLORD_CYCLE = "CODE_GAME_LANDLORD_CYCLE" // 地主一轮确认结束 22 | CODE_GAME_POKER_PLAY = "CODE_GAME_POKER_PLAY" // 出牌回合 23 | CODE_GAME_POKER_PLAY_REDIRECT = "CODE_GAME_POKER_PLAY_REDIRECT" // 出牌重定向 24 | CODE_GAME_POKER_PLAY_MISMATCH = "CODE_GAME_POKER_PLAY_MISMATCH" // 出牌不匹配 25 | CODE_GAME_POKER_PLAY_LESS = "CODE_GAME_POKER_PLAY_LESS" // 出牌太小 26 | CODE_GAME_POKER_PLAY_PASS = "CODE_GAME_POKER_PLAY_PASS" // 不出 27 | CODE_GAME_POKER_PLAY_CANT_PASS = "CODE_GAME_POKER_PLAY_CANT_PASS" // 不允许不出 28 | CODE_GAME_POKER_PLAY_INVALID = "CODE_GAME_POKER_PLAY_INVALID" // 无效 29 | CODE_GAME_POKER_PLAY_ORDER_ERROR = "CODE_GAME_POKER_PLAY_ORDER_ERROR" // 顺序错误 30 | CODE_GAME_OVER = "CODE_GAME_OVER" // 游戏结束 31 | CODE_PVE_DIFFICULTY_NOT_SUPPORT = "CODE_PVE_DIFFICULTY_NOT_SUPPORT" // 人机难度不支持 32 | ) 33 | 34 | const ( 35 | SERVER_CODE_CLIENT_EXIT = "CODE_CLIENT_EXIT" // 玩家退出 36 | SERVER_CODE_CLIENT_OFFLINE = "CODE_CLIENT_OFFLINE" // 玩家离线 37 | SERVER_CODE_CLIENT_NICKNAME_SET = "CODE_CLIENT_NICKNAME_SET" // 设置昵称 38 | SERVER_CODE_CLIENT_HEAD_BEAT = "CODE_CLIENT_HEAD_BEAT" // 不出 39 | SERVER_CODE_ROOM_CREATE = "CODE_ROOM_CREATE" // 创建PVP房间 40 | SERVER_CODE_ROOM_CREATE_PVE = "CODE_ROOM_CREATE_PVE" // 创建PVE房间 41 | SERVER_CODE_GET_ROOMS = "CODE_GET_ROOMS" // 获取房间列表 42 | SERVER_CODE_ROOM_JOIN = "CODE_ROOM_JOIN" // 加入房间 43 | SERVER_CODE_GAME_STARTING = "CODE_GAME_STARTING" // 游戏开始 44 | SERVER_CODE_GAME_LANDLORD_ELECT = "CODE_GAME_LANDLORD_ELECT" // 抢地主 45 | SERVER_CODE_GAME_POKER_PLAY = "CODE_GAME_POKER_PLAY" // 出牌环节 46 | SERVER_CODE_GAME_POKER_PLAY_REDIRECT = "CODE_GAME_POKER_PLAY_REDIRECT" // 出牌重定向 47 | SERVER_CODE_GAME_POKER_PLAY_PASS = "CODE_GAME_POKER_PLAY_PASS" // 不出 48 | ) 49 | -------------------------------------------------------------------------------- /event/event_context.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "go-ratel/common" 5 | "log" 6 | ) 7 | 8 | type Poker = common.Poker 9 | type Room = common.Room 10 | 11 | const NICKNAME_MAX_LENGTH = 10 12 | 13 | // 应用上下文(应用层) 14 | type Context struct { 15 | clientChan *chan common.ClientTransferDataProtoc 16 | serverChan *chan common.ServerTransferDataProtoc 17 | UserId int 18 | LastPokers *[]Poker 19 | LastSellClientNickname string 20 | LastSellClientType string 21 | PokerPrinterType int 22 | } 23 | 24 | func NewEventContext(clientChan *chan common.ClientTransferDataProtoc, serverChan *chan common.ServerTransferDataProtoc) *Context { 25 | return &Context{ 26 | clientChan: clientChan, 27 | serverChan: serverChan, 28 | UserId: 0, 29 | LastPokers: nil, 30 | LastSellClientNickname: "", 31 | LastSellClientType: "", 32 | PokerPrinterType: 0, 33 | } 34 | } 35 | 36 | func (ctx *Context) DoListen() { 37 | go func() { 38 | for { 39 | transferData := <-*ctx.clientChan 40 | ctx.call(transferData.Code, transferData.Data) 41 | } 42 | }() 43 | } 44 | 45 | func (ctx *Context) call(code string, data string) { 46 | switch code { 47 | case CODE_CLIENT_CONNECT: 48 | ListenerClientConnect(ctx, data) 49 | case CODE_CLIENT_EXIT: 50 | ListenerClientExit(ctx, data) 51 | case CODE_CLIENT_KICK: 52 | ListenerClientKick(ctx, data) 53 | case CODE_CLIENT_NICKNAME_SET: 54 | ListenerClientNicknameSet(ctx, data) 55 | case CODE_GAME_LANDLORD_CONFIRM: 56 | ListenerGameLandlordConfirm(ctx, data) 57 | case CODE_GAME_LANDLORD_CYCLE: 58 | ListenerGameLandlordCycle(ctx, data) 59 | case CODE_GAME_LANDLORD_ELECT: 60 | ListenerGameLandlordElect(ctx, data) 61 | case CODE_GAME_OVER: 62 | ListenerGameOver(ctx, data) 63 | case CODE_GAME_POKER_PLAY: 64 | ListenerGamePokerPlay(ctx, data) 65 | case CODE_GAME_POKER_PLAY_CANT_PASS: 66 | ListenerGamePokerPlayCantPass(ctx, data) 67 | case CODE_GAME_POKER_PLAY_INVALID: 68 | ListenerGamePokerPlayInvalid(ctx, data) 69 | case CODE_GAME_POKER_PLAY_LESS: 70 | ListenerGamePokerPlayLess(ctx, data) 71 | case CODE_GAME_POKER_PLAY_MISMATCH: 72 | ListenerGamePokerPlayMismatch(ctx, data) 73 | case CODE_GAME_POKER_PLAY_ORDER_ERROR: 74 | ListenerGamePokerPlayOrderError(ctx, data) 75 | case CODE_GAME_POKER_PLAY_PASS: 76 | ListenerGamePokerPlayPass(ctx, data) 77 | case CODE_GAME_POKER_PLAY_REDIRECT: 78 | ListenerGamePokerPlayRedirect(ctx, data) 79 | case CODE_GAME_STARTING: 80 | ListenerGameStarting(ctx, data) 81 | case CODE_PVE_DIFFICULTY_NOT_SUPPORT: 82 | ListenerPVEDifficultyNotSupport(ctx, data) 83 | case CODE_ROOM_CREATE_SUCCESS: 84 | ListenerRoomCreateSuccess(ctx, data) 85 | case CODE_ROOM_JOIN_FAIL_BY_FULL: 86 | ListenerRoomJoinFailByFull(ctx, data) 87 | case CODE_ROOM_JOIN_FAIL_BY_INEXIST: 88 | ListenerRoomJoinFailByInExist(ctx, data) 89 | case CODE_ROOM_JOIN_SUCCESS: 90 | ListenerRoomJoinSuccess(ctx, data) 91 | case CODE_SHOW_OPTIONS: 92 | ListenerShowOptions(ctx, data) 93 | case CODE_SHOW_OPTIONS_PVE: 94 | ListenerShowOptionsPVE(ctx, data) 95 | case CODE_SHOW_OPTIONS_PVP: 96 | ListenerShowOptionsPVP(ctx, data) 97 | case CODE_SHOW_OPTIONS_SETTING: 98 | ListenerShowOptionsSettings(ctx, data) 99 | case CODE_SHOW_POKERS: 100 | ListenerShowPokers(ctx, data) 101 | case CODE_SHOW_ROOMS: 102 | ListenerShowRooms(ctx, data) 103 | default: 104 | log.Println("Event code invalid") 105 | } 106 | } 107 | 108 | func (ctx *Context) pushToServer(serverCode string, data string) { 109 | transferData := common.ServerTransferDataProtoc{ 110 | Code: serverCode, 111 | Data: data, 112 | } 113 | *ctx.serverChan <- transferData 114 | } 115 | 116 | func (ctx *Context) InitLastSellInfo() { 117 | ctx.LastPokers = nil 118 | ctx.LastSellClientNickname = "" 119 | ctx.LastSellClientType = "" 120 | } 121 | -------------------------------------------------------------------------------- /event/event_listener_client_connect.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "go-ratel/command" 5 | "strconv" 6 | ) 7 | 8 | func ListenerClientConnect(ctx *Context, data string) { 9 | command.PrintNotice("Connection to server is successful. Welcome to ratel!!") 10 | ctx.UserId, _ = strconv.Atoi(data) 11 | } 12 | -------------------------------------------------------------------------------- /event/event_listener_client_exit.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | ) 7 | 8 | func ListenerClientExit(ctx *Context, data string) { 9 | dataMap := make(map[string]interface{}) 10 | _ = json.Unmarshal([]byte(data), &dataMap) 11 | 12 | exitClientId := int(dataMap["exitClientId"].(float64)) 13 | var role string 14 | if exitClientId == ctx.UserId { 15 | role = "You" 16 | } else { 17 | role = dataMap["exitClientNickname"].(string) 18 | } 19 | command.PrintNotice(role + " exit from the room. Room disbanded!!\n") 20 | ListenerShowOptions(ctx, data) 21 | } 22 | -------------------------------------------------------------------------------- /event/event_listener_client_kick.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "go-ratel/command" 4 | 5 | func ListenerClientKick(ctx *Context, data string) { 6 | command.PrintNotice("As a result of long time do not operate, be forced by the system to kick out of the room\n") 7 | ListenerShowOptions(ctx, data) 8 | } 9 | -------------------------------------------------------------------------------- /event/event_listener_client_nickname_set.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | "strconv" 7 | ) 8 | 9 | func ListenerClientNicknameSet(ctx *Context, data string) { 10 | if data == "" { 11 | dataMap := make(map[string]interface{}) 12 | if dataMap["invalidLength"] != nil { 13 | command.PrintNotice("Your nickname length was invalid: " + strconv.Itoa(dataMap["invalidLength"].(int))) 14 | } 15 | } 16 | 17 | command.PrintNotice("Please set your nickname (upto " + strconv.Itoa(NICKNAME_MAX_LENGTH) + " characters)") 18 | nickname := command.DeletePreAndSufSpace(command.Write("nickname")) 19 | if len(nickname) > NICKNAME_MAX_LENGTH { 20 | result := make(map[string]interface{}) 21 | result["invalidLength"] = len(nickname) 22 | resultJson, _ := json.Marshal(&result) 23 | ListenerClientNicknameSet(ctx, string(resultJson)) 24 | } else { 25 | ctx.pushToServer(SERVER_CODE_CLIENT_NICKNAME_SET, nickname) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /event/event_listener_game_landlord_confirm.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | ) 7 | 8 | func ListenerGameLandlordConfirm(ctx *Context, data string) { 9 | dataMap := make(map[string]interface{}) 10 | _ = json.Unmarshal([]byte(data), &dataMap) 11 | 12 | landlordNickname := dataMap["landlordNickname"].(string) 13 | command.PrintNotice(landlordNickname + " grabbed the landlord and got extra three poker shots") 14 | 15 | // 序列化 16 | additionalPokers := make([]Poker, 0) 17 | pokersBytes, _ := json.Marshal(dataMap["additionalPokers"]) 18 | _ = json.Unmarshal([]byte(pokersBytes), &additionalPokers) 19 | 20 | command.PrintPokers(additionalPokers, ctx.PokerPrinterType) 21 | ctx.pushToServer(CODE_GAME_POKER_PLAY_REDIRECT, "") 22 | } 23 | -------------------------------------------------------------------------------- /event/event_listener_game_landlord_cycle.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "go-ratel/command" 4 | 5 | func ListenerGameLandlordCycle(ctx *Context, data string) { 6 | command.PrintNotice("No player takes the landlord, so redealing cards.") 7 | } 8 | -------------------------------------------------------------------------------- /event/event_listener_game_landlord_elect.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | "strings" 7 | ) 8 | 9 | func ListenerGameLandlordElect(ctx *Context, data string) { 10 | dataMap := make(map[string]interface{}) 11 | _ = json.Unmarshal([]byte(data), &dataMap) 12 | turnClientId := int(dataMap["nextClientId"].(float64)) 13 | 14 | if dataMap["preClientNickname"] != nil { 15 | command.PrintNotice(dataMap["preClientNickname"].(string) + " don't rob the landlord!") 16 | } 17 | if turnClientId == ctx.UserId { 18 | command.PrintNotice("It's your turn. Do you want to rob the landlord? [Y/N] (enter [EXIT] to exit current room)") 19 | line := strings.ToUpper(command.DeletePreAndSufSpace(command.Write("Y/N"))) 20 | switch line { 21 | case "EXIT": 22 | ctx.pushToServer(SERVER_CODE_CLIENT_EXIT, "") 23 | case "Y": 24 | ctx.pushToServer(CODE_GAME_LANDLORD_ELECT, "TRUE") 25 | case "N": 26 | ctx.pushToServer(CODE_GAME_LANDLORD_ELECT, "FALSE") 27 | default: 28 | command.PrintNotice("Invalid options") 29 | ListenerGameLandlordElect(ctx, data) 30 | } 31 | } else { 32 | command.PrintNotice("It's " + dataMap["nextClientNickname"].(string) + "'s turn. Please wait patiently for his/her confirmation !") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /event/event_listener_game_over.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | ) 7 | 8 | func ListenerGameOver(ctx *Context, data string) { 9 | dataMap := make(map[string]interface{}) 10 | _ = json.Unmarshal([]byte(data), &dataMap) 11 | 12 | command.PrintNotice("\nPlayer " + dataMap["winnerNickname"].(string) + "[" + dataMap["winnerType"].(string) + "]" + " won the game") 13 | command.PrintNotice("Game over, friendship first, competition second\n") 14 | } 15 | -------------------------------------------------------------------------------- /event/event_listener_game_poker_play.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | "strings" 7 | ) 8 | 9 | func ListenerGamePokerPlay(ctx *Context, data string) { 10 | dataMap := make(map[string]interface{}) 11 | _ = json.Unmarshal([]byte(data), &dataMap) 12 | 13 | command.PrintNotice("It's your turn to play, your pokers are as follows: ") 14 | 15 | pokers := make([]Poker, 0) 16 | pokersBytes, _ := json.Marshal(dataMap["pokers"]) 17 | _ = json.Unmarshal([]byte(pokersBytes), &pokers) 18 | command.PrintPokers(pokers, ctx.PokerPrinterType) 19 | 20 | command.PrintNotice("Please enter the card you came up with (enter [EXIT] to exit current room, enter [PASS] to jump current round)") 21 | line := command.DeletePreAndSufSpace(command.Write("card")) 22 | if line == "" { 23 | command.PrintNotice("Invalid enter") 24 | ListenerGamePokerPlay(ctx, data) 25 | } else { 26 | if strings.ToUpper(line) == "PASS" { 27 | ctx.pushToServer(SERVER_CODE_GAME_POKER_PLAY_PASS, "") 28 | } else if strings.ToUpper(line) == "EXIT" { 29 | ctx.pushToServer(SERVER_CODE_CLIENT_EXIT, "") 30 | } else { 31 | strs := strings.Split(line, " ") 32 | options := make([]string, 0) 33 | access := true 34 | for i := 0; i < len(strs); i++ { 35 | str := strs[i] 36 | for _, v := range []byte(str) { 37 | if string(v) == " " || string(v) == "\t" { 38 | } else { 39 | if !pokerLevelAliasContainer(v) { 40 | access = false 41 | break 42 | } else { 43 | options = append(options, string(v)) 44 | } 45 | } 46 | } 47 | } 48 | if access { 49 | bytes, _ := json.Marshal(&options) 50 | ctx.pushToServer(SERVER_CODE_GAME_POKER_PLAY, string(bytes)) 51 | } else { 52 | command.PrintNotice("Invalid enter") 53 | if ctx.LastPokers != nil { 54 | command.PrintNotice(ctx.LastSellClientNickname + "[" + ctx.LastSellClientType + "] playd:") 55 | command.PrintPokers(*ctx.LastPokers, ctx.PokerPrinterType) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | func pokerLevelAliasContainer(b byte) bool { 63 | pokerAlias := []string{"3", "4", "5", "6", "7", "8", "9", "T", "t", "0", "J", "j", "Q", "q", "K", "k", "A", "a", "1", "2", "S", "s", "X", "x"} 64 | for _, v := range pokerAlias { 65 | if v == string(b) { 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | -------------------------------------------------------------------------------- /event/event_listener_game_poker_play_cant_pass.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "go-ratel/command" 4 | 5 | func ListenerGamePokerPlayCantPass(ctx *Context, data string) { 6 | command.PrintNotice("You played the previous card, so you can't pass.") 7 | ctx.pushToServer(SERVER_CODE_GAME_POKER_PLAY_REDIRECT, "") 8 | } 9 | -------------------------------------------------------------------------------- /event/event_listener_game_poker_play_invalid.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "go-ratel/command" 4 | 5 | func ListenerGamePokerPlayInvalid(ctx *Context, data string) { 6 | command.PrintNotice("Out pokers' format is invalid") 7 | if ctx.LastPokers != nil { 8 | command.PrintNotice(ctx.LastSellClientNickname + "[" + ctx.LastSellClientType + "] played:") 9 | command.PrintPokers(*ctx.LastPokers, ctx.PokerPrinterType) 10 | } 11 | ctx.pushToServer(SERVER_CODE_GAME_POKER_PLAY_REDIRECT, "") 12 | } 13 | -------------------------------------------------------------------------------- /event/event_listener_game_poker_play_less.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "go-ratel/command" 4 | 5 | func ListenerGamePokerPlayLess(ctx *Context, data string) { 6 | command.PrintNotice("Your pokers' type has lower rank than the previous. You could not play this combination !!") 7 | if ctx.LastPokers != nil { 8 | command.PrintNotice(ctx.LastSellClientNickname + "[" + ctx.LastSellClientType + "] played:") 9 | command.PrintPokers(*ctx.LastPokers, ctx.PokerPrinterType) 10 | } 11 | ctx.pushToServer(SERVER_CODE_GAME_POKER_PLAY_REDIRECT, "") 12 | } 13 | -------------------------------------------------------------------------------- /event/event_listener_game_poker_play_mismatch.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | "strconv" 7 | ) 8 | 9 | func ListenerGamePokerPlayMismatch(ctx *Context, data string) { 10 | dataMap := make(map[string]interface{}) 11 | _ = json.Unmarshal([]byte(data), &dataMap) 12 | 13 | command.PrintNotice("Your pokers' type is " + dataMap["playType"].(string) + " (" + strconv.Itoa(int(dataMap["playCount"].(float64))) + 14 | ") but previous pokers' type is " + dataMap["preType"].(string) + " (" + strconv.Itoa(int(dataMap["preCount"].(float64))) + "), mismatch !!") 15 | 16 | if ctx.LastPokers != nil { 17 | command.PrintNotice(ctx.LastSellClientNickname + "[" + ctx.LastSellClientType + "] played:") 18 | command.PrintPokers(*ctx.LastPokers, ctx.PokerPrinterType) 19 | } 20 | ctx.pushToServer(SERVER_CODE_GAME_POKER_PLAY_REDIRECT, "") 21 | } 22 | -------------------------------------------------------------------------------- /event/event_listener_game_poker_play_order_error.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "go-ratel/command" 4 | 5 | func ListenerGamePokerPlayOrderError(ctx *Context, data string) { 6 | command.PrintNotice("Not turn you to operate, please wait other player !!") 7 | } 8 | -------------------------------------------------------------------------------- /event/event_listener_game_poker_play_pass.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | ) 7 | 8 | func ListenerGamePokerPlayPass(ctx *Context, data string) { 9 | dataMap := make(map[string]interface{}) 10 | _ = json.Unmarshal([]byte(data), &dataMap) 11 | 12 | command.PrintNotice(dataMap["clientNickname"].(string) + " passed. It is now " + dataMap["nextClientNickname"].(string) + "'s turn.") 13 | 14 | turnClientId := int(dataMap["nextClientId"].(float64)) 15 | if ctx.UserId == turnClientId { 16 | ctx.pushToServer(SERVER_CODE_GAME_POKER_PLAY_REDIRECT, "") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /event/event_listener_game_poker_play_redirect.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "go-ratel/command" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | var choose = [...]string{"UP", "DOWN"} 12 | var format = "\n[%-4s] %-" + strconv.Itoa(NICKNAME_MAX_LENGTH) + "s surplus %-2s [%-8s]" 13 | 14 | func ListenerGamePokerPlayRedirect(ctx *Context, data string) { 15 | dataMap := make(map[string]interface{}) 16 | _ = json.Unmarshal([]byte(data), &dataMap) 17 | 18 | sellClientId := int(dataMap["sellClientId"].(float64)) 19 | 20 | clientInfos := make([]map[string]interface{}, 0) 21 | clientInfoBytes, _ := json.Marshal(dataMap["clientInfos"]) 22 | _ = json.Unmarshal(clientInfoBytes, &clientInfos) 23 | 24 | for index := 0; index < 2; index++ { 25 | for _, clientInfo := range clientInfos { 26 | position := clientInfo["position"].(string) 27 | if strings.ToUpper(position) == strings.ToUpper(choose[index]) { 28 | command.PrintNotice(fmt.Sprintf(format, clientInfo["position"].(string), clientInfo["clientNickname"].(string), strconv.Itoa(int(clientInfo["surplus"].(float64))), clientInfo["type"].(string))) 29 | } 30 | } 31 | } 32 | command.PrintNotice("") 33 | if sellClientId == ctx.UserId { 34 | ListenerGamePokerPlay(ctx, data) 35 | } else { 36 | command.PrintNotice("Next player is " + dataMap["sellClinetNickname"].(string) + ". Please wait for him to play his cards.") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /event/event_listener_game_starting.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | ) 7 | 8 | func ListenerGameStarting(ctx *Context, data string) { 9 | dataMap := make(map[string]interface{}) 10 | _ = json.Unmarshal([]byte(data), &dataMap) 11 | 12 | command.PrintNotice("Game starting !!") 13 | 14 | pokers := make([]Poker, 0) 15 | pokersBytes, _ := json.Marshal(dataMap["pokers"]) 16 | _ = json.Unmarshal([]byte(pokersBytes), &pokers) 17 | 18 | command.PrintNotice("") 19 | command.PrintNotice("Your pokers are") 20 | command.PrintPokers(pokers, ctx.PokerPrinterType) 21 | 22 | ListenerGameLandlordElect(ctx, data) 23 | } 24 | -------------------------------------------------------------------------------- /event/event_listener_pve_difficulty_not_support.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "go-ratel/command" 4 | 5 | func ListenerPVEDifficultyNotSupport(ctx *Context, data string) { 6 | command.PrintNotice("The current difficulty coefficient is not supported, please pay attention to the following.\n") 7 | ListenerShowOptionsPVE(ctx, data) 8 | } 9 | -------------------------------------------------------------------------------- /event/event_listener_room_create_success.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | "strconv" 7 | ) 8 | 9 | func ListenerRoomCreateSuccess(ctx *Context, data string) { 10 | room := Room{} 11 | _ = json.Unmarshal([]byte(data), &room) 12 | 13 | ctx.InitLastSellInfo() 14 | 15 | command.PrintNotice("You have created a room with id " + strconv.Itoa(room.Id)) 16 | command.PrintNotice("Please wait for other players to join !") 17 | } 18 | -------------------------------------------------------------------------------- /event/event_listener_room_join_fail_by_full.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | "strconv" 7 | ) 8 | 9 | func ListenerRoomJoinFailByFull(ctx *Context, data string) { 10 | dataMap := make(map[string]interface{}) 11 | _ = json.Unmarshal([]byte(data), &dataMap) 12 | 13 | command.PrintNotice("Join room failed. Room " + strconv.Itoa(dataMap["roomId"].(int)) + " player count is full!") 14 | ListenerShowOptions(ctx, data) 15 | } 16 | -------------------------------------------------------------------------------- /event/event_listener_room_join_fail_by_in_exist.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | ) 7 | 8 | func ListenerRoomJoinFailByInExist(ctx *Context, data string) { 9 | dataMap := make(map[string]interface{}) 10 | _ = json.Unmarshal([]byte(data), &dataMap) 11 | 12 | command.PrintNotice("Join room failed. Room " + dataMap["roomId"].(string) + " inexists!") 13 | ListenerShowOptions(ctx, data) 14 | } 15 | -------------------------------------------------------------------------------- /event/event_listener_room_join_success.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | "strconv" 7 | ) 8 | 9 | func ListenerRoomJoinSuccess(ctx *Context, data string) { 10 | dataMap := make(map[string]interface{}) 11 | _ = json.Unmarshal([]byte(data), &dataMap) 12 | 13 | ctx.InitLastSellInfo() 14 | joinClientId := int(dataMap["clientId"].(float64)) 15 | 16 | if ctx.UserId == joinClientId { 17 | command.PrintNotice("You have joined room:" + strconv.Itoa(int(dataMap["roomId"].(float64))) + ". There are " + strconv.Itoa(int(dataMap["roomClientCount"].(float64))) + " players in the room now.") 18 | command.PrintNotice("Please wait for other players to join, start a good game when the room player reaches three !") 19 | } else { 20 | command.PrintNotice(dataMap["clientNickname"].(string) + " joined room, the current number of room player is " + strconv.Itoa(int(dataMap["roomClientCount"].(float64)))) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /event/event_listener_show_options.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "go-ratel/command" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | func ListenerShowOptions(ctx *Context, data string) { 11 | command.PrintNotice("Options: ") 12 | command.PrintNotice("1. PvP") 13 | command.PrintNotice("2. PvE") 14 | command.PrintNotice("3. Setting") 15 | command.PrintNotice("Please enter the number of options (enter [EXIT] log out)") 16 | 17 | line := strings.ToUpper(command.DeletePreAndSufSpace(command.Write("options"))) 18 | if line == "EXIT" { 19 | os.Exit(0) 20 | } else { 21 | choose, e := strconv.Atoi(line) 22 | if e != nil { 23 | choose = -1 24 | } 25 | switch choose { 26 | case 1: 27 | ListenerShowOptionsPVP(ctx, data) 28 | case 2: 29 | ListenerShowOptionsPVE(ctx, data) 30 | case 3: 31 | ListenerShowOptionsSettings(ctx, data) 32 | default: 33 | command.PrintNotice("Invalid option, please choose again:") 34 | ListenerShowOptions(ctx, data) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /event/event_listener_show_options_pve.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "go-ratel/command" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | func ListenerShowOptionsPVE(ctx *Context, data string) { 10 | command.PrintNotice("PVE: ") 11 | command.PrintNotice("1. Simple Model") 12 | command.PrintNotice("2. Medium Model") 13 | command.PrintNotice("3. Difficulty Model") 14 | command.PrintNotice("Please enter the number of options (enter [BACK] return options list)") 15 | 16 | line := command.Write("pve") 17 | if strings.ToUpper(line) == "BACK" { 18 | ListenerShowOptions(ctx, data) 19 | } else { 20 | choose, e := strconv.Atoi(line) 21 | if e != nil { 22 | choose = -1 23 | } 24 | if choose > 0 && choose < 4 { 25 | ctx.InitLastSellInfo() 26 | ctx.pushToServer(SERVER_CODE_ROOM_CREATE_PVE, strconv.Itoa(choose)) 27 | } else { 28 | command.PrintNotice("Invalid option, please choose again:") 29 | ListenerShowOptionsPVE(ctx, data) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /event/event_listener_show_options_pvp.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "go-ratel/command" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | func ListenerShowOptionsPVP(ctx *Context, data string) { 10 | command.PrintNotice("PVP: ") 11 | command.PrintNotice("1. Create Room") 12 | command.PrintNotice("2. Room List") 13 | command.PrintNotice("3. Join Room") 14 | command.PrintNotice("Please enter the number of options (enter [BACK] return options list)") 15 | line := strings.ToUpper(command.DeletePreAndSufSpace(command.Write("pvp"))) 16 | if line == "BACK" { 17 | ListenerShowOptions(ctx, data) 18 | } else { 19 | choose, e := strconv.Atoi(line) 20 | if e != nil { 21 | choose = -1 22 | } 23 | switch choose { 24 | case 1: 25 | ctx.pushToServer(SERVER_CODE_ROOM_CREATE, "") 26 | case 2: 27 | ctx.pushToServer(SERVER_CODE_GET_ROOMS, "") 28 | case 3: 29 | command.PrintNotice("Please enter the room id you want to join (enter [BACK] return options list)") 30 | line := command.DeletePreAndSufSpace(command.Write("roomid")) 31 | if strings.ToUpper(line) == "BACK" { 32 | ListenerShowOptionsPVP(ctx, data) 33 | } else { 34 | roomid, e := strconv.Atoi(line) 35 | if e != nil { 36 | roomid = -1 37 | } 38 | if roomid < 1 { 39 | command.PrintNotice("Invalid options, please choose again:") 40 | ListenerShowOptionsPVP(ctx, data) 41 | } else { 42 | ctx.pushToServer(SERVER_CODE_ROOM_JOIN, strconv.Itoa(roomid)) 43 | } 44 | } 45 | default: 46 | command.PrintNotice("Invalid option, please choose again:") 47 | ListenerShowOptionsPVP(ctx, data) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /event/event_listener_show_options_settings.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "go-ratel/command" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | func ListenerShowOptionsSettings(ctx *Context, data string) { 10 | command.PrintNotice("Setting: ") 11 | command.PrintNotice("1. Card with shape edges (Default)") 12 | command.PrintNotice("2. Card with rounded edges") 13 | command.PrintNotice("3. Text Only with types") 14 | command.PrintNotice("4. Text Only without types") 15 | command.PrintNotice("5. Unicode Cards") 16 | 17 | command.PrintNotice("Please enter the number of setting (enter [BACK] return options list)") 18 | line := command.DeletePreAndSufSpace(command.Write("setting")) 19 | if strings.ToUpper(line) == "BACK" { 20 | ListenerShowOptions(ctx, data) 21 | } else { 22 | choose, e := strconv.Atoi(line) 23 | if e != nil { 24 | choose = -1 25 | } 26 | if choose >= 1 && choose <= 5 { 27 | ctx.PokerPrinterType = choose - 1 28 | ListenerShowOptions(ctx, data) 29 | } else { 30 | command.PrintNotice("Invalid setting, please choose again:") 31 | ListenerShowOptionsSettings(ctx, data) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /event/event_listener_show_pokers.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "go-ratel/command" 6 | ) 7 | 8 | func ListenerShowPokers(ctx *Context, data string) { 9 | dataMap := make(map[string]interface{}) 10 | _ = json.Unmarshal([]byte(data), &dataMap) 11 | 12 | ctx.LastSellClientNickname = dataMap["clientNickname"].(string) 13 | ctx.LastSellClientType = dataMap["clientType"].(string) 14 | 15 | command.PrintNotice(ctx.LastSellClientNickname + "[" + ctx.LastSellClientType + "] played:") 16 | 17 | pokers := make([]Poker, 0) 18 | pokersBytes, _ := json.Marshal(dataMap["pokers"]) 19 | _ = json.Unmarshal(pokersBytes, &pokers) 20 | command.PrintPokers(pokers, ctx.PokerPrinterType) 21 | 22 | if dataMap["sellClinetNickname"] != nil { 23 | command.PrintNotice("Next player is " + dataMap["sellClinetNickname"].(string) + ". Please wait for him to play his pokers.") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /event/event_listener_show_rooms.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "go-ratel/command" 7 | "strconv" 8 | ) 9 | 10 | func ListenerShowRooms(ctx *Context, data string) { 11 | roomList := make([]map[string]interface{}, 0) 12 | _ = json.Unmarshal([]byte(data), &roomList) 13 | 14 | if len(roomList) > 0 { 15 | format := "#\t%s\t|\t%-" + strconv.Itoa(NICKNAME_MAX_LENGTH) + "s\t|\t%-6s\t|\t%-6s\t#" 16 | command.PrintNotice(fmt.Sprintf(format, "ID", "OWNER", "COUNT", "TYPE")) 17 | for _, room := range roomList { 18 | command.PrintNotice(fmt.Sprintf(format, strconv.Itoa(int(room["roomId"].(float64))), room["roomOwner"].(string), strconv.Itoa(int(room["roomClientCount"].(float64))), room["roomType"].(string))) 19 | } 20 | command.PrintNotice("") 21 | ListenerShowOptionsPVP(ctx, data) 22 | } else { 23 | command.PrintNotice("No available room, please create a room !") 24 | ListenerShowOptionsPVP(ctx, data) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go-ratel 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/golang/protobuf v1.4.2 7 | google.golang.org/protobuf v1.23.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 2 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 3 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 4 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 5 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 6 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 7 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 8 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 9 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 10 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 11 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 12 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 13 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 14 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 15 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 16 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 17 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= 18 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 19 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "go-ratel/command" 7 | "go-ratel/common" 8 | "go-ratel/event" 9 | "go-ratel/network" 10 | "io" 11 | "log" 12 | "net" 13 | "net/http" 14 | "os" 15 | "strconv" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func main() { 21 | host, port := parseFlag() 22 | if port == 0 { 23 | host, port = selectServer() 24 | } 25 | start(host, port) 26 | } 27 | 28 | func parseFlag() (string, int) { 29 | if len(os.Args) == 5 { 30 | host := os.Args[2] 31 | port, e := strconv.Atoi(os.Args[4]) 32 | if e == nil { 33 | return host, port 34 | } 35 | } 36 | return "", 0 37 | } 38 | 39 | func selectServer() (string, int) { 40 | servers := getServerList() 41 | serverList := make([]string, 0) 42 | e := json.Unmarshal([]byte(servers), &serverList) 43 | if e != nil { 44 | log.Panic(e) 45 | } 46 | command.PrintNotice("Please select a server:") 47 | for i := 0; i < len(serverList); i++ { 48 | command.PrintNotice(strconv.Itoa(i+1) + ". " + serverList[i]) 49 | } 50 | serverPick := -1 51 | for { 52 | serverPick, e = strconv.Atoi(command.Write("option")) 53 | if e == nil && serverPick > 0 { 54 | if serverPick > len(serverList) { 55 | command.PrintNotice("The server address does not exist!") 56 | continue 57 | } 58 | break 59 | } 60 | } 61 | serverAddress := strings.Split(serverList[serverPick-1], ":") 62 | host := serverAddress[0] 63 | port, _ := strconv.Atoi(serverAddress[1]) 64 | return host, port 65 | } 66 | 67 | func getServerList() string { 68 | client := http.Client{Timeout: 5 * time.Second} 69 | resp, err := client.Get("https://gitee.com/ainilili/ratel/raw/master/serverlist.json") 70 | if err != nil { 71 | log.Panic(err) 72 | } 73 | defer resp.Body.Close() 74 | var buffer [512]byte 75 | result := bytes.NewBuffer(nil) 76 | for { 77 | n, err := resp.Body.Read(buffer[0:]) 78 | result.Write(buffer[0:n]) 79 | if err != nil && err == io.EOF { 80 | break 81 | } else if err != nil { 82 | panic(err) 83 | } 84 | } 85 | return result.String() 86 | } 87 | 88 | func start(host string, port int) { 89 | addr, e := net.ResolveTCPAddr("tcp", host+":"+strconv.Itoa(port)) 90 | if e != nil { 91 | log.Panic(e) 92 | } 93 | conn, e := net.DialTCP("tcp", nil, addr) 94 | if e != nil { 95 | log.Panic(e) 96 | } 97 | 98 | // 用两个chan用于 应用层 与 网络层 之间的解耦 99 | clientChan := make(chan common.ClientTransferDataProtoc) 100 | serverChan := make(chan common.ServerTransferDataProtoc) 101 | 102 | ectx := event.NewEventContext(&clientChan, &serverChan) 103 | ectx.DoListen() 104 | 105 | cctx := network.NewConnContext(conn, &clientChan, &serverChan) 106 | cctx.DoConn() 107 | } 108 | -------------------------------------------------------------------------------- /network/buffer.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | ) 7 | 8 | type Buffer struct { 9 | reader io.Reader 10 | buf []byte 11 | start int 12 | end int 13 | } 14 | 15 | func newBuffer(reader io.Reader, len int) Buffer { 16 | return Buffer{ 17 | reader: reader, 18 | buf: make([]byte, len), 19 | start: 0, 20 | end: 0, 21 | } 22 | } 23 | 24 | func (b *Buffer) len() int { 25 | return b.end - b.start 26 | } 27 | 28 | func (b *Buffer) readFromReader() error { 29 | b.grow() 30 | n, err := b.reader.Read(b.buf[b.end:]) 31 | if err != nil { 32 | return err 33 | } 34 | b.end += n 35 | return nil 36 | } 37 | 38 | func (b *Buffer) grow() { 39 | if b.start == 0 { 40 | return 41 | } 42 | copy(b.buf[0:], b.buf[b.start:b.end]) 43 | b.end = b.end - b.start 44 | b.start = 0 45 | } 46 | 47 | func (b *Buffer) seek(offset, limit int) ([]byte, error) { 48 | if b.len() < offset+limit { 49 | return nil, errors.New("not enough") 50 | } 51 | return b.buf[b.start+offset : b.start+offset+limit], nil 52 | } 53 | 54 | func (b *Buffer) read(offset, limit int) ([]byte, error) { 55 | if b.len() < limit-offset { 56 | return nil, errors.New("not enough") 57 | } 58 | b.start += offset 59 | buf := b.buf[b.start : b.start+limit] 60 | b.start += limit 61 | return buf, nil 62 | } 63 | -------------------------------------------------------------------------------- /network/codec.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "errors" 5 | "github.com/golang/protobuf/proto" 6 | "go-ratel/common" 7 | "net" 8 | "time" 9 | ) 10 | 11 | const ( 12 | MaxContextLen = 4092 13 | ) 14 | 15 | type Codec struct { 16 | Conn *net.TCPConn 17 | Buffer Buffer 18 | } 19 | 20 | func NewCodec(conn *net.TCPConn) *Codec { 21 | return &Codec{ 22 | Conn: conn, 23 | Buffer: newBuffer(conn, MaxContextLen), 24 | } 25 | } 26 | 27 | func (c *Codec) Read() error { 28 | return c.Buffer.readFromReader() 29 | } 30 | 31 | func (c *Codec) Encode(transferData *common.ServerTransferDataProtoc, duration time.Duration) error { 32 | encodeData, e := proto.Marshal(transferData) 33 | if e != nil { 34 | return e 35 | } 36 | bodyLen := len(encodeData) 37 | if bodyLen > MaxContextLen { 38 | return errors.New("not enough") 39 | } 40 | header := proto.EncodeVarint(uint64(bodyLen)) 41 | 42 | buffer := make([]byte, len(header)+bodyLen) 43 | copy(buffer, header) 44 | copy(buffer[len(header):], encodeData) 45 | 46 | err := c.Conn.SetWriteDeadline(time.Now().Add(duration)) 47 | if err != nil { 48 | return err 49 | } 50 | _, e = c.Conn.Write(buffer) 51 | return e 52 | } 53 | 54 | func (c *Codec) Decode() (*common.ClientTransferDataProtoc, bool, error) { 55 | bodyLen, size := proto.DecodeVarint(c.Buffer.buf[c.Buffer.start:]) 56 | if bodyLen > MaxContextLen { 57 | return nil, false, errors.New("not enough") 58 | } 59 | if bodyLen == 0 { 60 | return nil, false, nil 61 | } 62 | body, e := c.Buffer.read(size, int(bodyLen)) 63 | if e != nil { 64 | return nil, false, nil 65 | } 66 | 67 | transferData := common.ClientTransferDataProtoc{} 68 | e = proto.Unmarshal(body, &transferData) 69 | if e != nil { 70 | return nil, false, e 71 | } 72 | return &transferData, true, nil 73 | } 74 | -------------------------------------------------------------------------------- /network/conn_context.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "go-ratel/common" 5 | "log" 6 | "net" 7 | "time" 8 | ) 9 | 10 | // TCP连接上下文(网络层) 11 | type ConnContext struct { 12 | codec *Codec 13 | clientChan *chan common.ClientTransferDataProtoc 14 | serverChan *chan common.ServerTransferDataProtoc 15 | } 16 | 17 | func NewConnContext(conn *net.TCPConn, clientChan *chan common.ClientTransferDataProtoc, serverChan *chan common.ServerTransferDataProtoc) *ConnContext { 18 | return &ConnContext{ 19 | codec: NewCodec(conn), 20 | clientChan: clientChan, 21 | serverChan: serverChan, 22 | } 23 | } 24 | 25 | func (ctx *ConnContext) DoConn() { 26 | ctx.DoSend() 27 | for { 28 | e := ctx.codec.Read() 29 | if e != nil { 30 | log.Panic(e) 31 | } 32 | for { 33 | transferData, b, e := ctx.codec.Decode() 34 | if e != nil { 35 | log.Panic(e) 36 | } 37 | if b { 38 | *ctx.clientChan <- *transferData 39 | continue 40 | } 41 | break 42 | } 43 | } 44 | } 45 | 46 | func (ctx *ConnContext) DoSend() { 47 | go func() { 48 | for { 49 | transferData := <-*ctx.serverChan 50 | err := ctx.codec.Encode(&transferData, time.Second*10) 51 | if err != nil { 52 | log.Println(err) 53 | } 54 | } 55 | }() 56 | } 57 | -------------------------------------------------------------------------------- /protoc-resource/ClientTransferDataProtoc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = ".;common"; 4 | 5 | message ClientTransferDataProtoc{ 6 | 7 | string code = 1; 8 | 9 | string data = 2; 10 | 11 | string info = 3; 12 | } -------------------------------------------------------------------------------- /protoc-resource/ServerTransferDataProtoc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = ".;common"; 4 | 5 | message ServerTransferDataProtoc{ 6 | 7 | string code = 1; 8 | 9 | string data = 2; 10 | 11 | string info = 3; 12 | } -------------------------------------------------------------------------------- /protoc-resource/generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | protoc --go_out=. *.proto -------------------------------------------------------------------------------- /test/print_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "go-ratel/command" 7 | "go-ratel/common" 8 | "io/ioutil" 9 | "os" 10 | "testing" 11 | ) 12 | 13 | func Test_PrintPokers(t *testing.T) { 14 | //command.PrintPokers() 15 | fmt.Println(os.Getwd()) 16 | bytes, e := ioutil.ReadFile("./show_pokers.json") 17 | if e != nil { 18 | panic(e) 19 | } 20 | fmt.Println(string(bytes)) 21 | dataMap := make(map[string]interface{}) 22 | e = json.Unmarshal(bytes, &dataMap) 23 | if e != nil { 24 | panic(e) 25 | } 26 | // 需要两次转换 27 | pokers, e := json.Marshal(dataMap["pokers"]) 28 | if e != nil { 29 | panic(e) 30 | } 31 | pokerList := make([]common.Poker, 0) 32 | e = json.Unmarshal([]byte(pokers), &pokerList) 33 | if e != nil { 34 | panic(e) 35 | } 36 | command.PrintPokers(pokerList, 0) 37 | } 38 | -------------------------------------------------------------------------------- /test/proto_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "github.com/golang/protobuf/proto" 7 | "go-ratel/common" 8 | "testing" 9 | ) 10 | 11 | func Test_proto(t *testing.T) { 12 | clientData := common.ClientTransferDataProtoc{ 13 | Code: "code", 14 | Data: "data", 15 | Info: "info", 16 | } 17 | 18 | encodeClientData, e := proto.Marshal(&clientData) 19 | if e != nil { 20 | panic(e) 21 | } 22 | 23 | clientData2 := common.ClientTransferDataProtoc{} 24 | e = proto.Unmarshal(encodeClientData, &clientData2) 25 | if e != nil { 26 | panic(e) 27 | } 28 | fmt.Println(clientData2) 29 | } 30 | 31 | func Test_Hex(t *testing.T) { 32 | fmt.Println(0xffffffff) 33 | fmt.Println(ComputeRawVarint32Size(127)) 34 | fmt.Println(ComputeRawVarint32Size(128)) 35 | } 36 | 37 | func ComputeRawVarint32Size(value int) int { 38 | if value&(0xffffffff<<7) == 0 { 39 | return 1 40 | } 41 | if value&(0xffffffff<<14) == 0 { 42 | return 2 43 | } 44 | if value&(0xffffffff<<21) == 0 { 45 | return 3 46 | } 47 | if value&(0xffffffff<<28) == 0 { 48 | return 4 49 | } 50 | return 5 51 | } 52 | 53 | func Test_BigEndian(t *testing.T) { 54 | x := uint16(255) 55 | 56 | buf := make([]byte, 2) 57 | binary.BigEndian.PutUint16(buf, x) 58 | fmt.Println(buf) // output: [0 255] 59 | } 60 | 61 | func Test_Varint(t *testing.T) { 62 | buf := make([]byte, 10) 63 | 64 | //x := uint64(127) 65 | x := uint64(128) 66 | tmp := proto.EncodeVarint(x) 67 | copy(buf, tmp) 68 | 69 | u, i := proto.DecodeVarint(buf) 70 | fmt.Println("u = ", u, " i = ", i) 71 | } 72 | -------------------------------------------------------------------------------- /test/show_pokers.json: -------------------------------------------------------------------------------- 1 | { 2 | "clientId": 4753, 3 | "clientNickname": "dazuo2", 4 | "clientType": "LANDLORD", 5 | "pokers": [ 6 | { 7 | "level": "LEVEL_3", 8 | "type": "CLUB" 9 | }, 10 | { 11 | "level": "LEVEL_4", 12 | "type": "CLUB" 13 | }, 14 | { 15 | "level": "LEVEL_5", 16 | "type": "DIAMOND" 17 | }, 18 | { 19 | "level": "LEVEL_6", 20 | "type": "DIAMOND" 21 | }, 22 | { 23 | "level": "LEVEL_7", 24 | "type": "CLUB" 25 | } 26 | ], 27 | "sellClinetNickname": "dazuo3" 28 | } -------------------------------------------------------------------------------- /test/syntax_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "go-ratel/common" 7 | "strconv" 8 | "testing" 9 | ) 10 | 11 | func Test_Map(t *testing.T) { 12 | data := make(map[string]interface{}) 13 | fmt.Println(data["name"]) // output: nil 14 | data["age"] = 24 15 | fmt.Println(data["age"].(string)) // occur error 16 | } 17 | 18 | func Test_Nexted(t *testing.T) { 19 | dataMap := make(map[string]interface{}) 20 | dataMap["name"] = "dazuo" 21 | dataMap["room"] = common.Room{Id: 1} 22 | bytes, _ := json.Marshal(&dataMap) 23 | 24 | dataMap2 := make(map[string]interface{}) 25 | _ = json.Unmarshal(bytes, &dataMap2) 26 | fmt.Print(dataMap2["room"].(map[string]interface{})) 27 | } 28 | 29 | func Test_Byte(t *testing.T) { 30 | bytes := []byte("hello world\t") 31 | for _, v := range bytes { 32 | if string(v) == "\t" { 33 | fmt.Println(v) 34 | } 35 | } 36 | } 37 | 38 | func Test_format(t *testing.T) { 39 | NICKNAME_MAX_LENGTH := 10 40 | base := "\n[%-4s] %-" + strconv.Itoa(NICKNAME_MAX_LENGTH) + "s surplus %-2s [%-8s]" 41 | sprintf := fmt.Sprintf(base, 123, "222", "333", "44") 42 | fmt.Print(sprintf) 43 | } 44 | --------------------------------------------------------------------------------