├── README.md ├── game ├── cardType.go ├── command.go └── pokerLogic.go ├── main.go └── socket ├── client.go ├── gameServer.go ├── global.go ├── hub.go ├── message.go └── roomServer.go /README.md: -------------------------------------------------------------------------------- 1 | # Doudizhu-server-go 2 | 尝试写的斗地主go版本服务端,需配合[客户端](https://github.com/tianhe1986/Fat-Doudizhu) 使用 3 | 4 | 首先,当然需要安装go语言环境,这个我就不讲怎么安装配置了 5 | 6 | 然后 7 | ```js 8 | go get 9 | go build 10 | ``` 11 | 12 | 然后运行编译出来的二进制文件即可 13 | -------------------------------------------------------------------------------- /game/cardType.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | type CardType int 4 | 5 | const ( 6 | PASS_CARDS CardType = -2 //过 7 | NO_CARDS CardType = -1 //前面还没有牌(首家) 8 | ERROR_CARDS CardType = 0 //错误牌型 9 | SINGLE_CARD CardType = 1 //单牌 10 | DOUBLE_CARD CardType = 2 //对子 11 | THREE_CARD CardType = 3 //3不带 12 | THREE_ONE_CARD CardType = 4 //3带1 13 | THREE_TWO_CARD CardType = 5 //3带2 14 | BOMB_TWO_CARD CardType = 6 //4带2 15 | STRAIGHT CardType = 7 //连牌 16 | CONNECT_CARD CardType = 8 //连对 17 | AIRCRAFT CardType = 9 //飞机不带 18 | AIRCRAFT_CARD CardType = 10 //飞机带单牌 19 | AIRCRAFT_WING CardType = 11 //飞机带对子 20 | BOMB_CARD CardType = 12 //炸弹 21 | KINGBOMB_CARD CardType = 13 //王炸 22 | BOMB_FOUR_CARD CardType = 14 // 4带2对 23 | BOMB_TWO_STRAIGHT_CARD CardType = 15 // 连续4带2 24 | BOMB_FOUR_STRAIGHT_CARD CardType = 16 // 连续4带2对 25 | ) 26 | -------------------------------------------------------------------------------- /game/command.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | // 发起匹配content 4 | type MatchCommand struct { 5 | Name int `json:"name"` 6 | } 7 | 8 | // 匹配结果content 9 | type MatchResultCommand struct { 10 | Players []string `json:"players"` 11 | RoomId int `json:"roomId"` 12 | } 13 | 14 | // 发牌content 15 | type GiveCardCommand struct { 16 | State int `json:"state"` 17 | RoomdId int `json:"roomId"` 18 | Cards []int `json:"cards"` 19 | } 20 | 21 | type CardSet struct { 22 | Type CardType `json:"type"` 23 | Header int `json:"header"` 24 | Cards []int `json:"cards"` 25 | } 26 | 27 | // 状态变化command 28 | type StateChangeCommand struct { 29 | State int `json:"state"` 30 | CurPlayerIndex int `json:"curPlayerIndex"` 31 | CurCard CardSet `json:"curCard"` 32 | Scores map[int]int `json:"scores"` 33 | NowScore int `json:"nowScore"` 34 | } 35 | 36 | // 房间内基本信息 37 | type CommonRoomCommand struct { 38 | RoomId int `json:"roomId"` 39 | Index int `json:"index"` 40 | } 41 | 42 | // 接收到的抢地主消息 43 | type WantDizhuInputCommand struct { 44 | RoomId int `json:"roomId"` 45 | Index int `json:"index"` 46 | Score int `json:"score"` 47 | } 48 | 49 | type WantDizhuOutputCommand struct { 50 | State int `json:"state"` 51 | CurPlayerIndex int `json:"curPlayerIndex"` 52 | NowScore int `json:"nowScore"` 53 | } 54 | 55 | // 抢地主结果消息 56 | type DizhuResultCommand struct { 57 | State int `json:"state"` 58 | Dizhu int `json:"dizhu"` 59 | DizhuCards []int `json:"dizhuCards"` 60 | NowScore int `json:"nowScore"` 61 | } 62 | 63 | // 玩家上传的出牌消息 64 | type PlayCardInCommand struct { 65 | RoomId int `json:"roomId"` 66 | Index int `json:"index"` 67 | CurCard CardSet `json:"curCards"` 68 | } 69 | 70 | // 出牌消息 71 | type CardOutCommand struct { 72 | State int `json:"state"` 73 | CurPlayerIndex int `json:"curPlayerIndex"` 74 | CurCard CardSet `json:"curCard"` 75 | } -------------------------------------------------------------------------------- /game/pokerLogic.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import "sort" 4 | 5 | // 其实现在是空的,但是万一有用呢?所以就没有写成静态方法了 6 | type PokerLogic struct { 7 | 8 | } 9 | 10 | // 计算牌型 11 | func (p *PokerLogic) CalcuPokerType(cards []int) CardType { 12 | // 转换为点数 13 | points := p.CardsToPoints(cards) 14 | 15 | length := len(points) 16 | 17 | if length == 1 { // 一张牌,当然是单牌 18 | return SINGLE_CARD 19 | } else if length == 2 { // 两张牌,王炸或一对 20 | if points[0] == 16 && points[1] == 17 { // 王炸 21 | return KINGBOMB_CARD 22 | } 23 | if points[0] == points[1] { // 对子 24 | return DOUBLE_CARD 25 | } 26 | } else if length == 3 { // 三张,只检查三不带 27 | if points[0] == points[1] && points[1] == points[2] { 28 | return THREE_CARD 29 | } 30 | } else if length == 4 { // 四张,炸弹或三带一 31 | maxSameNum := p.CalcuMaxSameNum(points) 32 | if maxSameNum == 4 { // 四张相同,炸弹 33 | return BOMB_CARD 34 | } 35 | 36 | if maxSameNum == 3 { // 三张点数相同的,3带1 37 | return THREE_ONE_CARD 38 | } 39 | } else if length >= 5 && p.IsStraight(points) && points[length - 1] < 15 { // 大于等于5张,是点数连续,且最大点数不超过2, 则是顺子 40 | return STRAIGHT 41 | } else if length == 5 { // 5张,只需检查3带2 42 | // 最多有3张相等的,且只有两种点数,则是3带2 43 | if p.CalcuMaxSameNum(points) == 3 && p.CalcuDiffPoint(points) == 2 { 44 | return THREE_TWO_CARD 45 | } 46 | } else { // 大于6的情况,再分别判断 47 | maxSameNum := p.CalcuMaxSameNum(points) 48 | diffPointNum := p.CalcuDiffPoint(points) 49 | 50 | // length能被3整除, 最大相同数量是3, 不同点数是length/3, 且最大与最小点数相差 length/3 - 1, 则是连续三张 51 | if length % 3 == 0 && maxSameNum == 3 && diffPointNum == length / 3 && (points[length - 1] - points[0] == length/3 - 1) && points[length - 1] < 15 { 52 | return AIRCRAFT 53 | } 54 | 55 | // 与上面连续三张判断类似,连对 56 | if length % 2 == 0 && maxSameNum == 2 && diffPointNum == length / 2 && (points[length - 1] - points[0] == length/2 - 1) && points[length - 1] < 15 { 57 | return CONNECT_CARD 58 | } 59 | 60 | // 飞机三带一 61 | if length % 4 == 0 { 62 | // 连续3张的数量占了length/4 以上 63 | threePoints := p.GetSameNumMaxStraightPoints(points, 3) 64 | if len(threePoints) >= length/4 && threePoints[length/4 - 1] < 15 { 65 | return AIRCRAFT_CARD 66 | } 67 | } 68 | 69 | // 飞机三带二 70 | if length % 5 == 0 { 71 | threePoints := p.GetSameNumPoints(points, 3) 72 | // 三带二里面,不会出现单牌的情况 73 | onePoints := p.GetSameNumPoints(points, 1) 74 | if len(onePoints) == 0 && len(threePoints) == length/5 && p.IsStraight(threePoints) && threePoints[len(threePoints) - 1] < 15 { 75 | return AIRCRAFT_WING 76 | } 77 | } 78 | 79 | // 四带二 80 | if length == 6 { 81 | if maxSameNum == 4 { 82 | return BOMB_TWO_CARD 83 | } 84 | } 85 | 86 | // 四带两对 87 | if length == 8 { 88 | if maxSameNum == 4 { 89 | // 必须没有一张和三张的出现 90 | onePoints := p.GetSameNumPoints(points, 1) 91 | threePoints := p.GetSameNumPoints(points, 3) 92 | 93 | // TODO: 33334444 这样的到底算连续三带一还是四带两对? 94 | if len(onePoints) == 0 && len(threePoints) == 0 { 95 | return BOMB_FOUR_CARD 96 | } 97 | } 98 | } 99 | // 连续四带二 100 | if length % 6 == 0 { 101 | fourPoints := p.GetSameNumMaxStraightPoints(points, 4) 102 | if len(fourPoints) >= length/6 && fourPoints[length/6 - 1] < 15 { 103 | return BOMB_TWO_STRAIGHT_CARD 104 | } 105 | } 106 | // 连续四带两对 107 | if length % 8 == 0 { 108 | fourPoints := p.GetSameNumMaxStraightPoints(points, 4) 109 | 110 | // 其他的都必须是成对,因此检查是否没有单张和三张的出现即可 111 | onePoints := p.GetSameNumPoints(points, 1) 112 | threePoints := p.GetSameNumPoints(points, 3) 113 | 114 | if len(fourPoints) >= length/8 && len(onePoints) == 0 && len(threePoints) == 0 && fourPoints[length/8 - 1] < 15 { 115 | return BOMB_FOUR_STRAIGHT_CARD 116 | } 117 | } 118 | } 119 | 120 | // 没有这个牌型的 121 | return ERROR_CARDS 122 | } 123 | 124 | // 取出所有点数数量等于num的点数 125 | // 例如,现在牌中有3个3,3个4,2个5,1个6, 取出数量等于3的点数,则返回[3, 4],取出数量等于2的点数,则返回[5],取出数量等于1的点数,则返回[6],其他都返回空数组 126 | func (p *PokerLogic) GetSameNumPoints(points []int, num int) []int { 127 | length := len(points) 128 | newPoints := make([]int, length) 129 | pointIndex := 0 130 | 131 | nowNum := 1 132 | 133 | for i := 1; i < length; i++ { 134 | if points[i] == points[i-1] { // 与前一张相同 135 | nowNum++ 136 | } else { // 与前一张不同,若前一张出现num次,加入数组 137 | if nowNum == num { 138 | newPoints[pointIndex] = points[i-1] 139 | pointIndex++ 140 | } 141 | nowNum = 1 142 | } 143 | } 144 | 145 | if nowNum == num { 146 | newPoints[pointIndex] = points[length-1] 147 | pointIndex++ 148 | } 149 | 150 | return newPoints[0:pointIndex] 151 | } 152 | 153 | // 取出所有点数数量大于等于num的点数 154 | // 例如,现在牌中有3个3,3个4,2个5,1个6, 取出数量大于等于3的点数,则返回[3, 4],取出数量大于等于2的点数,则返回[3, 4, 5],取出数量大于等于1的点数,则返回[3, 4, 5, 6] 155 | func (p *PokerLogic) GetGeNumPoints(points []int, num int) []int { 156 | length := len(points) 157 | newPoints := make([]int, length) 158 | pointIndex := 0 159 | 160 | nowNum := 1 161 | 162 | for i := 1; i < length; i++ { 163 | if points[i] == points[i-1] { // 与前一张相同 164 | nowNum++ 165 | } else { // 与前一张不同,若前一张出现大于等于num次,加入数组 166 | if nowNum >= num { 167 | newPoints[pointIndex] = points[i-1] 168 | pointIndex++ 169 | } 170 | nowNum = 1 171 | } 172 | } 173 | 174 | if nowNum >= num { 175 | newPoints[pointIndex] = points[length-1] 176 | pointIndex++ 177 | } 178 | 179 | return newPoints[0:pointIndex] 180 | } 181 | 182 | // 从所有点数数量大于等于num的列表中,取出最长连续递增子列表 183 | func (p *PokerLogic) GetSameNumMaxStraightPoints(points []int, num int) []int { 184 | geNumPoints := p.GetGeNumPoints(points, num) 185 | 186 | // 没有,直接返回 187 | length := len(geNumPoints) 188 | if length == 0 { 189 | return geNumPoints 190 | } 191 | 192 | maxStartPoint := geNumPoints[0] 193 | maxNum := 1 194 | 195 | nowStartPoint := geNumPoints[0] 196 | nowNum := 1 197 | 198 | for i := 1; i < length; i++ { 199 | if geNumPoints[i] == geNumPoints[i-1] + 1 {// 比上一张多1 200 | nowNum++; 201 | } else { // 重新开始计算 202 | if (nowNum > maxNum) { 203 | maxNum = nowNum; 204 | maxStartPoint = nowStartPoint; 205 | } 206 | nowNum = 1; 207 | nowStartPoint = geNumPoints[i]; 208 | } 209 | } 210 | 211 | if (nowNum > maxNum) { 212 | maxNum = nowNum; 213 | maxStartPoint = nowStartPoint; 214 | } 215 | 216 | newPoints := make([]int, maxNum) 217 | for i := 0; i < maxNum; i++ { 218 | newPoints[i] = maxStartPoint + i 219 | } 220 | 221 | return newPoints 222 | } 223 | 224 | // 是否是顺子 225 | func (p *PokerLogic) IsStraight(points []int) bool { 226 | length := len(points) 227 | for i := 1; i < length; i++ { 228 | if points[i] != points[i-1] + 1 { // 与前一张相同 229 | return false 230 | } 231 | } 232 | 233 | return true 234 | } 235 | 236 | // 有多少种不同的点数 237 | func (p *PokerLogic) CalcuDiffPoint(points []int) int { 238 | diffNum := 1 239 | 240 | length := len(points) 241 | for i := 1; i < length; i++ { 242 | if points[i] != points[i-1] { // 与前一张不同,则出现了新的点数 243 | diffNum++ 244 | } 245 | } 246 | 247 | return diffNum 248 | } 249 | 250 | // 最多有几张点数相等的牌 251 | func (p *PokerLogic) CalcuMaxSameNum(points []int) int { 252 | length := len(points) 253 | nowNum := 1 254 | maxNum := 1 255 | 256 | for i := 1; i < length; i++ { 257 | if points[i] == points[i-1] { // 与前一张相同 258 | nowNum++ 259 | } else { // 与前一张不同,重新开始计数 260 | if nowNum > maxNum { 261 | maxNum = nowNum 262 | } 263 | nowNum = 1 264 | } 265 | } 266 | 267 | if nowNum > maxNum { 268 | maxNum = nowNum 269 | } 270 | 271 | return maxNum 272 | } 273 | 274 | // 牌id转点数 275 | func (p *PokerLogic) CardsToPoints(cards []int) []int { 276 | length := len(cards) 277 | points := make([]int, length) 278 | 279 | var point int 280 | for i := 0; i < length; i++ { 281 | value := cards[i] 282 | if value < 53 { // id 1-4 对应的是3, 5-8对应4, 依此类推 45 - 48对应14(A), 49-52对应15(2) 283 | if value % 4 == 0 { 284 | point = value / 4 + 2 285 | } else { 286 | point = value / 4 + 3 287 | } 288 | } else { // 小王和大王 289 | point = value /4 + 2 + value % 4 290 | } 291 | 292 | points[i] = point 293 | } 294 | 295 | // 按点数升序排序 296 | sort.Ints(points) 297 | 298 | return points 299 | } 300 | 301 | // 计算头牌 302 | func (p *PokerLogic) CalcuPokerHeader(cards []int, cardType CardType) int { 303 | points := p.CardsToPoints(cards) 304 | 305 | switch cardType { 306 | case SINGLE_CARD, DOUBLE_CARD, THREE_CARD, STRAIGHT, CONNECT_CARD, AIRCRAFT, BOMB_CARD: 307 | return points[0] 308 | case THREE_ONE_CARD,THREE_TWO_CARD,BOMB_TWO_CARD: 309 | return points[2] 310 | case AIRCRAFT_CARD: // 连续三带一 311 | threePoints := p.GetSameNumMaxStraightPoints(points, 3) 312 | return threePoints[0] 313 | case AIRCRAFT_WING: // 连续三带二 314 | return p.FirstPoint(points, 3) 315 | case BOMB_FOUR_CARD: // 四带两对 316 | fourPoints := p.GetSameNumPoints(points, 4) 317 | return fourPoints[len(fourPoints) - 1] 318 | case BOMB_TWO_STRAIGHT_CARD,BOMB_FOUR_STRAIGHT_CARD: 319 | fourPoints := p.GetSameNumMaxStraightPoints(points, 4) 320 | return fourPoints[0] 321 | } 322 | 323 | return 0 324 | } 325 | 326 | // 获得首个出现num次的点数 327 | func (p *PokerLogic) FirstPoint(points []int, num int) int { 328 | nowNum := 1; 329 | length := len(points) 330 | 331 | for i := 1; i < length; i++ { 332 | if (points[i] == points[i-1]) { //与上一张相同,数量加1 333 | nowNum++; 334 | } else { //重新开始计算 335 | if (nowNum == num) { 336 | return points[i-1]; 337 | } 338 | nowNum = 1; 339 | } 340 | } 341 | 342 | if (nowNum == num) { 343 | return points[length - 1]; 344 | } 345 | 346 | return 0 347 | } 348 | 349 | // 是否可以出牌 350 | func (p *PokerLogic) CanOut(newCardSet *CardSet, nowCardSet *CardSet) bool { 351 | // 当前是第一次出牌,牌型正确即可 352 | if nowCardSet.Type == NO_CARDS && newCardSet.Type != ERROR_CARDS { 353 | return true 354 | } 355 | 356 | // 王炸,天下第一 357 | if newCardSet.Type == KINGBOMB_CARD { 358 | return true 359 | } 360 | 361 | // 炸弹,检查前面是不是也是炸弹 362 | if newCardSet.Type == BOMB_CARD { 363 | if nowCardSet.Type == BOMB_CARD { 364 | return newCardSet.Header > nowCardSet.Header 365 | } else { 366 | return true 367 | } 368 | } 369 | 370 | // 同类型,张数相同,头牌更大 371 | if newCardSet.Type == nowCardSet.Type && len(newCardSet.Cards) == len(nowCardSet.Cards) && newCardSet.Header > nowCardSet.Header { 372 | return true 373 | } 374 | 375 | return false 376 | } 377 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | 8 | "Doudizhu-server-go/socket" 9 | ) 10 | 11 | var addr = flag.String("addr", ":8181", "http service address") 12 | 13 | func main() { 14 | flag.Parse() 15 | hub := socket.NewHub() 16 | go hub.Run() 17 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 18 | socket.ServeWs(hub, w, r) 19 | }) 20 | err := http.ListenAndServe(*addr, nil) 21 | if err != nil { 22 | log.Fatal("ListenAndServe: ", err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /socket/client.go: -------------------------------------------------------------------------------- 1 | package socket 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "time" 7 | 8 | "github.com/gorilla/websocket" 9 | ) 10 | 11 | // Client is a middleman between the websocket connection and the hub. 12 | type Client struct { 13 | hub *Hub 14 | 15 | // The websocket connection. 16 | conn *websocket.Conn 17 | 18 | // Buffered channel of outbound messages. 19 | send chan []byte 20 | 21 | // 当前所在房间 22 | roomId int 23 | } 24 | 25 | // readPump pumps messages from the websocket connection to the hub. 26 | // 27 | // The application runs readPump in a per-connection goroutine. The application 28 | // ensures that there is at most one reader on a connection by executing all 29 | // reads from this goroutine. 30 | func (c *Client) readPump() { 31 | defer func() { 32 | c.hub.unregister <- c 33 | c.conn.Close() 34 | }() 35 | c.conn.SetReadLimit(maxMessageSize) 36 | c.conn.SetReadDeadline(time.Now().Add(pongWait)) 37 | c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) 38 | for { 39 | _, message, err := c.conn.ReadMessage() 40 | if err != nil { 41 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { 42 | log.Printf("error: %v", err) 43 | } 44 | 45 | // 关闭了,应该报告给中控服务器, 这个已经在defer中完成,无需重复 46 | break 47 | } 48 | 49 | // 将消息json decode成Message,插入中控处理队列 50 | realMessage := Message{} 51 | err = json.Unmarshal(message, &realMessage) 52 | 53 | //解析失败会报错。 54 | if err != nil { 55 | log.Printf("json decode error: %s", message) 56 | continue 57 | } 58 | 59 | data := MessageChanItem{ 60 | client: c, 61 | message: realMessage, 62 | } 63 | 64 | c.hub.dataList <- data 65 | } 66 | } 67 | 68 | // writePump pumps messages from the hub to the websocket connection. 69 | // 70 | // A goroutine running writePump is started for each connection. The 71 | // application ensures that there is at most one writer to a connection by 72 | // executing all writes from this goroutine. 73 | func (c *Client) writePump() { 74 | ticker := time.NewTicker(pingPeriod) 75 | defer func() { 76 | ticker.Stop() 77 | c.conn.Close() 78 | }() 79 | for { 80 | select { 81 | case message, ok := <-c.send: 82 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 83 | if !ok { 84 | // The hub closed the channel. 85 | c.conn.WriteMessage(websocket.CloseMessage, []byte{}) 86 | return 87 | } 88 | 89 | w, err := c.conn.NextWriter(websocket.TextMessage) 90 | if err != nil { 91 | return 92 | } 93 | w.Write(message) 94 | 95 | if err := w.Close(); err != nil { 96 | return 97 | } 98 | case <-ticker.C: // 定期发送ping消息 99 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 100 | if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { 101 | return 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /socket/gameServer.go: -------------------------------------------------------------------------------- 1 | package socket 2 | 3 | import ( 4 | "Doudizhu-server-go/game" 5 | "container/list" 6 | "encoding/json" 7 | "log" 8 | "math/rand" 9 | "strconv" 10 | ) 11 | 12 | type MatchItem struct { 13 | ws *Client 14 | name string 15 | seq int 16 | } 17 | 18 | type GameServer struct { 19 | // 匹配队列 20 | queue *list.List 21 | 22 | // 房间map 23 | rooms map[int]*RoomServer 24 | } 25 | 26 | func NewGameServer() *GameServer { 27 | return &GameServer{ 28 | queue: list.New(), 29 | rooms: make(map[int]*RoomServer), 30 | } 31 | } 32 | 33 | // 处理实际的信息 34 | func (gameServer *GameServer) handleMsg(client *Client, message *Message) { 35 | switch message.Command { 36 | case MATCH_PLAYER: // 匹配 37 | 38 | matchCommand := game.MatchCommand{} 39 | err := json.Unmarshal(message.Content, &matchCommand) 40 | 41 | //解析失败会报错。 42 | if err != nil { 43 | break 44 | } 45 | gameServer.matchPlayer(client, strconv.Itoa(matchCommand.Name), message.Seq) 46 | break 47 | case PLAYER_PLAYCARD: // 一局游戏内消息,每一项单独处理 48 | gameServer.playGame(client, message) 49 | break 50 | case PLAYER_WANTDIZHU: // 抢地主消息 51 | gameServer.wantDizhu(client, message) 52 | break 53 | } 54 | } 55 | 56 | // 进入匹配队列,尝试匹配 57 | func (gameServer *GameServer) matchPlayer(client *Client, name string, seq int) { 58 | log.Printf("user %s try match", name) 59 | // TODO 将client与name进行绑定 60 | newMatchItem := MatchItem{ 61 | ws: client, 62 | name: name, 63 | seq: seq, 64 | } 65 | 66 | gameServer.queue.PushBack(newMatchItem) 67 | 68 | // 每三个进行处理 69 | if gameServer.queue.Len() >= 3 { 70 | var players []MatchItem = make([]MatchItem, 3) 71 | var names []string = make([]string, 3) 72 | var roomPlayers []RoomPlayItem = make([]RoomPlayItem, 3) 73 | 74 | for i := 0; i < 3; i++ { 75 | tempItem := gameServer.queue.Front() 76 | gameServer.queue.Remove(tempItem) 77 | p, ok := tempItem.Value.(MatchItem); 78 | if ! ok { 79 | return 80 | } 81 | 82 | players[i] = p 83 | names[i] = p.name 84 | playItem := RoomPlayItem{ 85 | ws: p.ws, 86 | name: p.name, 87 | } 88 | roomPlayers[i] = playItem 89 | } 90 | 91 | // 随机生成房间号 92 | var roomIndex int 93 | var hasRepeat bool = true 94 | 95 | for ; hasRepeat ; { 96 | roomIndex = rand.Intn(10000000) + 1 97 | _, hasRepeat = gameServer.rooms[roomIndex] 98 | } 99 | 100 | // 发送匹配成功消息 101 | resultContent := game.MatchResultCommand{} 102 | resultContent.Players = names 103 | resultContent.RoomId = roomIndex 104 | for i := 0; i < 3; i++ { 105 | players[i].ws.roomId = roomIndex 106 | resp := Message{} 107 | resp.Code = 0 108 | resp.Command = MATCH_PLAYER 109 | resp.Seq = players[i].seq 110 | resp.Content, _ = json.Marshal(resultContent) 111 | 112 | tempMsg, _ := json.Marshal(resp) 113 | players[i].ws.send <- tempMsg 114 | } 115 | 116 | room := NewRoomServer() 117 | room.roomId = roomIndex; 118 | room.players = roomPlayers 119 | gameServer.rooms[room.roomId] = room 120 | room.InitGame() 121 | } 122 | } 123 | 124 | // 单局游戏内消息 125 | func (gameServer *GameServer) playGame(client *Client, message *Message) { 126 | commonRoomCommand := game.CommonRoomCommand{} 127 | err := json.Unmarshal(message.Content, &commonRoomCommand) 128 | 129 | //解析失败会报错。 130 | if err != nil { 131 | return 132 | } 133 | 134 | room := gameServer.rooms[commonRoomCommand.RoomId] 135 | room.handlePlayCard(message) 136 | } 137 | 138 | // 抢地主消息 139 | func (gameServer *GameServer) wantDizhu(client *Client, message *Message) { 140 | commonRoomCommand := game.CommonRoomCommand{} 141 | err := json.Unmarshal(message.Content, &commonRoomCommand) 142 | 143 | //解析失败会报错。 144 | if err != nil { 145 | return 146 | } 147 | 148 | room := gameServer.rooms[commonRoomCommand.RoomId] 149 | room.handleWantDizhu(message) 150 | } 151 | 152 | // 退出队列 153 | func (gameServer *GameServer) exitQueue(client *Client) { 154 | tempItem := gameServer.queue.Front() 155 | 156 | for ; tempItem != nil; tempItem = tempItem.Next() { 157 | p, ok := tempItem.Value.(MatchItem); 158 | if ! ok { 159 | continue 160 | } 161 | 162 | if p.ws == client { 163 | gameServer.queue.Remove(tempItem) 164 | return 165 | } 166 | } 167 | } 168 | 169 | // 解散房间 170 | func (gameServer *GameServer) exitRoom(roomId int) { 171 | room, ok := gameServer.rooms[roomId] 172 | 173 | if ok { 174 | room.exit() 175 | 176 | // 删除房间 177 | delete(gameServer.rooms, roomId) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /socket/global.go: -------------------------------------------------------------------------------- 1 | package socket 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/gorilla/websocket" 9 | ) 10 | 11 | const ( 12 | // Time allowed to write a message to the peer. 13 | writeWait = 10 * time.Second 14 | 15 | // Time allowed to read the next pong message from the peer. 16 | pongWait = 60 * time.Second 17 | 18 | // Send pings to peer with this period. Must be less than pongWait. 19 | pingPeriod = (pongWait * 9) / 10 20 | 21 | // Maximum message size allowed from peer. 22 | maxMessageSize = 512 23 | 24 | maxDataListSize = 1024 25 | ) 26 | 27 | var ( 28 | newline = []byte{'\n'} 29 | space = []byte{' '} 30 | ) 31 | 32 | var upgrader = websocket.Upgrader{ 33 | ReadBufferSize: 1024, 34 | WriteBufferSize: 1024, 35 | CheckOrigin: func(r *http.Request) bool { 36 | return true 37 | }, 38 | } 39 | 40 | // serveWs handles websocket requests from the peer. 41 | func ServeWs(hub *Hub, w http.ResponseWriter, r *http.Request) { 42 | conn, err := upgrader.Upgrade(w, r, nil) 43 | if err != nil { 44 | log.Println(err) 45 | return 46 | } 47 | client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)} 48 | client.hub.register <- client 49 | 50 | // Allow collection of memory referenced by the caller by doing all work in 51 | // new goroutines. 52 | go client.writePump() 53 | go client.readPump() 54 | } 55 | -------------------------------------------------------------------------------- /socket/hub.go: -------------------------------------------------------------------------------- 1 | package socket 2 | 3 | // 游戏中消息的结构体 4 | type MessageChanItem struct { 5 | // 对应的客户端 6 | client *Client 7 | message Message 8 | } 9 | 10 | // 从websocket的demo中拿来的Hub类,用来做中控 11 | type Hub struct { 12 | // 客户端map 13 | clients map[*Client]bool 14 | 15 | // 数据消息队列,带缓冲区 16 | dataList chan MessageChanItem 17 | 18 | // 新增连接队列 19 | register chan *Client 20 | 21 | // 移除连接队列 22 | unregister chan *Client 23 | } 24 | 25 | func NewHub() *Hub { 26 | return &Hub{ 27 | clients: make(map[*Client]bool), 28 | dataList: make(chan MessageChanItem, maxDataListSize), 29 | register: make(chan *Client), 30 | unregister: make(chan *Client), 31 | } 32 | } 33 | 34 | func (h *Hub) Run() { 35 | gameServer := NewGameServer() 36 | for { 37 | select { 38 | case client := <-h.register: // 新增连接 39 | h.clients[client] = true 40 | case client := <-h.unregister: // 关闭连接 41 | if _, ok := h.clients[client]; ok { 42 | // 从队列中移除 43 | gameServer.exitQueue(client) 44 | // 从房间中退出 45 | gameServer.exitRoom(client.roomId) 46 | delete(h.clients, client) 47 | close(client.send) 48 | } 49 | case data := <-h.dataList: // 实际的数据处理 50 | switch data.message.Command { 51 | // TODO: 系统类消息 52 | case SYSTEM_MSG, REGISTER, LOGIN: 53 | break 54 | // 游戏内消息 55 | case MATCH_PLAYER, PLAY_GAME, PLAYER_PLAYCARD, PLAYER_WANTDIZHU: 56 | gameServer.handleMsg(data.client, &data.message) 57 | break 58 | default: 59 | break 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /socket/message.go: -------------------------------------------------------------------------------- 1 | package socket 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type MessageCode int 8 | 9 | const ( 10 | SYSTEM_MSG MessageCode = 1 11 | REGISTER MessageCode = 2 12 | LOGIN MessageCode = 3 13 | MATCH_PLAYER MessageCode = 4 14 | PLAY_GAME MessageCode = 5 15 | ROOM_NOTIFY MessageCode = 6 16 | PLAYER_PLAYCARD MessageCode = 7 17 | PLAYER_WANTDIZHU MessageCode = 8 18 | ROOM_EXIT MessageCode = 9 19 | ) 20 | 21 | type Message struct { 22 | Seq int `json:"seq"` 23 | Code int `json:"code"` 24 | Command MessageCode `json:"command"` 25 | Content json.RawMessage `json:"content"` 26 | } 27 | -------------------------------------------------------------------------------- /socket/roomServer.go: -------------------------------------------------------------------------------- 1 | package socket 2 | 3 | import ( 4 | "Doudizhu-server-go/game" 5 | "encoding/json" 6 | "math/rand" 7 | "time" 8 | ) 9 | 10 | type RoomPlayItem struct { 11 | ws *Client 12 | name string 13 | } 14 | 15 | type RoomServer struct { 16 | // 房间id 17 | roomId int 18 | 19 | // 扑克逻辑处理 20 | pokerLogic *game.PokerLogic 21 | 22 | // 玩家列表 23 | players []RoomPlayItem 24 | 25 | // 三位玩家手中的牌 26 | playerCards [3][20]int 27 | 28 | // 底牌 29 | dzCards [3]int 30 | 31 | // 当前抢地主/出牌玩家 32 | curPlayerIndex int 33 | 34 | // 当前最大牌情况,牌型,牌型中的头牌,具体牌 35 | curCard game.CardSet 36 | 37 | // 地主座位 38 | dizhu int 39 | 40 | // 抢地主到几分了 41 | dizhuScore int 42 | 43 | // 当前已经抢地主次数 44 | wantDizhuTimes int 45 | 46 | // 当前由于炸弹而翻倍的次数 47 | bombTimes int 48 | 49 | // 当前牌最大的位置 50 | nowBigger int 51 | 52 | // 当前有几个人过牌 53 | passNum int 54 | 55 | // 得分情况 56 | scores map[int]int 57 | } 58 | 59 | func NewRoomServer() *RoomServer { 60 | // TODO: 做一些实际初始化的事 61 | return &RoomServer{ 62 | curPlayerIndex: 1, 63 | pokerLogic: &game.PokerLogic{}, 64 | players: make([]RoomPlayItem, 3), 65 | curCard: game.CardSet{ 66 | Type: game.NO_CARDS, 67 | Header: 0, 68 | Cards: nil, 69 | }, 70 | scores: make(map[int]int), 71 | } 72 | } 73 | 74 | func (roomServer *RoomServer) InitGame() { 75 | // 发牌 76 | cards := roomServer.getNewCards54() 77 | for i := 0; i < 17; i++ { 78 | roomServer.playerCards[0][i] = cards[i] 79 | roomServer.playerCards[1][i] = cards[i + 17] 80 | roomServer.playerCards[2][i] = cards[i + 34] 81 | } 82 | 83 | for i := 0; i < 3; i++ { 84 | roomServer.dzCards[i] = cards[i + 51] 85 | } 86 | 87 | // 将牌消息发送给客户端 88 | for i := 0; i < 3; i++ { 89 | giveCardCommand := game.GiveCardCommand{} 90 | giveCardCommand.State = 0; 91 | giveCardCommand.RoomdId = roomServer.roomId 92 | giveCardCommand.Cards = roomServer.playerCards[i][0:17] 93 | 94 | msg := Message{} 95 | msg.Code = 0 96 | msg.Command = PLAY_GAME 97 | msg.Seq = 0 98 | msg.Content, _ = json.Marshal(giveCardCommand) 99 | 100 | tempMsg, _ := json.Marshal(msg) 101 | roomServer.players[i].ws.send <- tempMsg 102 | } 103 | 104 | roomServer.changeState(3) 105 | } 106 | 107 | // 状态变更 2是结算,1是游戏中, 3是抢地主 108 | func (roomServer *RoomServer) changeState(state int) { 109 | stateChangeCommand := game.StateChangeCommand{ 110 | State: state, 111 | } 112 | msg := Message{} 113 | switch (state) { 114 | case 1: 115 | stateChangeCommand.CurPlayerIndex = roomServer.curPlayerIndex 116 | stateChangeCommand.CurCard = roomServer.curCard 117 | msg.Command = PLAY_GAME 118 | break; 119 | case 2: 120 | stateChangeCommand.Scores = roomServer.scores 121 | msg.Command = PLAY_GAME 122 | break; 123 | case 3: 124 | roomServer.curPlayerIndex = rand.Intn(3) + 1 125 | stateChangeCommand.CurPlayerIndex = roomServer.curPlayerIndex 126 | stateChangeCommand.NowScore = 0 127 | msg.Command = PLAYER_WANTDIZHU 128 | break; 129 | default: 130 | return; 131 | } 132 | 133 | msg.Content, _ = json.Marshal(stateChangeCommand) 134 | roomServer.sendToRoomPlayers(msg) 135 | } 136 | 137 | // 处理出牌消息 138 | func (roomServer *RoomServer) handlePlayCard(message *Message) { 139 | seq := message.Seq 140 | 141 | playCardCommand := game.PlayCardInCommand{} 142 | 143 | // Todo: 发送错误返回? 144 | err := json.Unmarshal(message.Content, &playCardCommand) 145 | if err != nil { 146 | return 147 | } 148 | 149 | index := playCardCommand.Index 150 | 151 | curCard := playCardCommand.CurCard; 152 | if (len(curCard.Cards) > 0) { 153 | // 判断是否符合出牌规则 154 | 155 | // 牌型检查 156 | cardType := roomServer.pokerLogic.CalcuPokerType(curCard.Cards) 157 | if cardType != curCard.Type { // 计算出的牌型与传过来的不匹配 158 | //log.Printf("计算出的类型为 %d,传过来的为 %d", cardType, curCard.Type) 159 | // 告知玩家出牌失败 160 | roomServer.sendAck(PLAYER_PLAYCARD, index, seq, -2) 161 | return 162 | } 163 | 164 | // 头牌检查 165 | cardHeader := roomServer.pokerLogic.CalcuPokerHeader(curCard.Cards, curCard.Type) 166 | if cardHeader != curCard.Header { // 计算出的头牌与传过来的不匹配 167 | //log.Printf("计算出的头牌为 %d,传过来的为 %d", cardHeader, curCard.Header) 168 | // 告知玩家出牌失败 169 | roomServer.sendAck(PLAYER_PLAYCARD, index, seq, -2) 170 | return 171 | } 172 | 173 | // 是否可出牌检查 174 | if ! roomServer.pokerLogic.CanOut(&curCard, &roomServer.curCard) { 175 | //log.Printf("当前牌型为 %d, 头牌为 %d", roomServer.curCard.Type, roomServer.curCard.Header) 176 | //log.Printf("新来的牌型为 %d, 头牌为 %d", curCard.Type, curCard.Header) 177 | roomServer.sendAck(PLAYER_PLAYCARD, index, seq, -3) 178 | return 179 | } 180 | 181 | // 移除手中的牌 182 | if ! roomServer.removeCards(index - 1, curCard.Cards) { 183 | // 告知玩家出牌失败 184 | roomServer.sendAck(PLAYER_PLAYCARD, index, seq, -1) 185 | return 186 | } 187 | } 188 | 189 | // 告知玩家出牌成功 190 | roomServer.sendAck(PLAYER_PLAYCARD, index, seq, 0) 191 | 192 | if curCard.Type != game.PASS_CARDS { // 如果不是过牌,处理新的最大牌 193 | roomServer.curCard = curCard 194 | roomServer.passNum = 0 195 | 196 | // 通知本轮出牌,以及下一个应出牌的玩家 197 | roomServer.addCurIndex() 198 | roomServer.sendNextCardOut() 199 | } else { 200 | roomServer.passNum++ 201 | if (1 == roomServer.passNum) { 202 | roomServer.nowBigger = roomServer.curPlayerIndex - 1; 203 | if (roomServer.nowBigger == 0) { 204 | roomServer.nowBigger = 3 205 | } 206 | roomServer.addCurIndex() 207 | roomServer.sendPassMsg() 208 | } else { // 不是1就是2 209 | roomServer.passNum = 0 210 | roomServer.curPlayerIndex = roomServer.nowBigger 211 | roomServer.curCard = game.CardSet{ 212 | Type: game.NO_CARDS, 213 | Header: 0, 214 | Cards: nil, 215 | } 216 | roomServer.nowBigger = 0 217 | roomServer.sendNextCardOut() 218 | } 219 | } 220 | } 221 | 222 | func (roomServer *RoomServer) sendAck(command MessageCode, index int, seq int, code int) { 223 | ackMsg := Message{} 224 | ackMsg.Command = command 225 | ackMsg.Seq = seq 226 | ackMsg.Code = code 227 | roomServer.sendToOnePlayer(index, ackMsg) 228 | return 229 | } 230 | 231 | func (roomServer *RoomServer) sendPassMsg() { 232 | passCurCard := game.CardSet{ 233 | Type: game.PASS_CARDS, 234 | Header: 0, 235 | Cards: nil, 236 | } 237 | 238 | cardOutCommand := game.CardOutCommand{ 239 | State: 1, 240 | CurPlayerIndex: roomServer.curPlayerIndex, 241 | CurCard: passCurCard, 242 | } 243 | 244 | msg := Message{} 245 | msg.Command = PLAY_GAME 246 | msg.Content, _ = json.Marshal(cardOutCommand) 247 | roomServer.sendToRoomPlayers(msg) 248 | } 249 | 250 | func (roomServer *RoomServer) sendNextCardOut() { 251 | cardOutCommand := game.CardOutCommand{ 252 | State: 1, 253 | CurPlayerIndex: roomServer.curPlayerIndex, 254 | CurCard: roomServer.curCard, 255 | } 256 | 257 | msg := Message{} 258 | msg.Command = PLAY_GAME 259 | msg.Content, _ = json.Marshal(cardOutCommand) 260 | roomServer.sendToRoomPlayers(msg) 261 | } 262 | 263 | // 移除牌 264 | func (roomServer *RoomServer) removeCards(index int, cards []int) bool { 265 | cardGroup := &roomServer.playerCards[index] 266 | 267 | var haveHard bool 268 | for i, length := 0, len(cards); i < length; i++ { 269 | haveHard = false 270 | 271 | for j := 0; j < 20; j++ { 272 | if (cards[i] == cardGroup[j]) { // 清除对应的牌 273 | haveHard = true 274 | cardGroup[j] = 0 275 | break 276 | } 277 | } 278 | 279 | if ( ! haveHard) { 280 | return false 281 | } 282 | } 283 | 284 | // 检查是否出完牌了 285 | hasOut := true 286 | for j := 0; j < 20; j++ { 287 | if (0 != cardGroup[j]) { 288 | hasOut = false 289 | break 290 | } 291 | } 292 | 293 | if (hasOut) { 294 | roomServer.countScore(index + 1) 295 | roomServer.changeState(2) 296 | roomServer.exit() 297 | } 298 | 299 | return true 300 | } 301 | 302 | // 算分 303 | func (roomServer *RoomServer) countScore(winIndex int) { 304 | // 喊地主分 305 | score := roomServer.dizhuScore 306 | 307 | // 翻倍 308 | for i := 0; i < roomServer.bombTimes; i++ { 309 | score = score * 2 310 | } 311 | 312 | other1 := winIndex - 1 313 | if (other1 == 0) { 314 | other1 = 3 315 | } 316 | 317 | other2 := winIndex + 1 318 | if (other2 == 4) { 319 | other2 = 1 320 | } 321 | 322 | dizhu := roomServer.dizhu 323 | if (winIndex == dizhu) { // 胜者是地主 324 | roomServer.scores[other1] = -score 325 | roomServer.scores[other2] = -score 326 | roomServer.scores[dizhu] = 2 * score 327 | } else { 328 | roomServer.scores[other1] = score 329 | roomServer.scores[other2] = score 330 | roomServer.scores[winIndex] = score 331 | roomServer.scores[dizhu] = -2 * score 332 | } 333 | } 334 | 335 | // 处理抢地主消息 336 | func (roomServer *RoomServer) handleWantDizhu(message *Message) { 337 | seq := message.Seq 338 | 339 | wantDizhuCommand := game.WantDizhuInputCommand{} 340 | 341 | // Todo: 发送错误返回? 342 | err := json.Unmarshal(message.Content, &wantDizhuCommand) 343 | if err != nil { 344 | return 345 | } 346 | 347 | score := wantDizhuCommand.Score 348 | index := wantDizhuCommand.Index 349 | roomServer.wantDizhuTimes++; 350 | 351 | // 发送ack 352 | roomServer.sendAck(PLAYER_WANTDIZHU, index, seq, 0) 353 | 354 | if (score == 3) { // 直接喊3分,成为地主 355 | roomServer.dizhuScore = score 356 | 357 | roomServer.giveDzCards(index - 1) 358 | roomServer.dizhu = index 359 | roomServer.curPlayerIndex = index 360 | // 告知地主结果 361 | roomServer.notifyDizhuResult() 362 | 363 | // 状态变更 364 | roomServer.changeState(1) 365 | return 366 | } else if (score > roomServer.dizhuScore) { // 叫了个更高分,更新 367 | roomServer.dizhuScore = score 368 | roomServer.dizhu = index 369 | } 370 | 371 | // 如果是第三次,表示每个人都表过态了 372 | if (roomServer.wantDizhuTimes == 3) { 373 | roomServer.giveDzCards(roomServer.dizhu - 1) 374 | roomServer.curPlayerIndex = roomServer.dizhu 375 | // 告知地主结果 376 | roomServer.notifyDizhuResult() 377 | 378 | // 状态变更 379 | roomServer.changeState(1) 380 | } else { // 继续问下一个人 381 | roomServer.addCurIndex() 382 | 383 | command := game.WantDizhuOutputCommand{ 384 | State: 3, 385 | CurPlayerIndex: roomServer.curPlayerIndex, 386 | NowScore: roomServer.dizhuScore, 387 | } 388 | msg := Message{} 389 | 390 | msg.Command = PLAYER_WANTDIZHU 391 | msg.Content, _ = json.Marshal(command) 392 | roomServer.sendToRoomPlayers(msg) 393 | } 394 | } 395 | 396 | // 下一个座位 397 | func (roomServer *RoomServer) addCurIndex() { 398 | roomServer.curPlayerIndex++; 399 | if (roomServer.curPlayerIndex > 3) { 400 | roomServer.curPlayerIndex = 1; //每次到4就变回1 401 | } 402 | } 403 | 404 | // 将底牌给地主 405 | func (roomServer *RoomServer) giveDzCards(index int) { 406 | for i := 0; i < 3; i++ { 407 | roomServer.playerCards[index][17 + i] = roomServer.dzCards[i] 408 | } 409 | } 410 | 411 | // 通知抢地主结果 412 | func (roomServer *RoomServer) notifyDizhuResult() { 413 | dizhuResultCommand := game.DizhuResultCommand{ 414 | State: 3, 415 | Dizhu: roomServer.curPlayerIndex, 416 | DizhuCards: roomServer.dzCards[0:], 417 | NowScore: roomServer.dizhuScore, 418 | } 419 | 420 | msg := Message{} 421 | msg.Command = PLAYER_WANTDIZHU 422 | msg.Content, _ = json.Marshal(dizhuResultCommand) 423 | roomServer.sendToRoomPlayers(msg) 424 | } 425 | 426 | // 给房间内单个用户发送消息 427 | func (roomServer *RoomServer) sendToOnePlayer(index int, data Message) { 428 | jsonData, _ := json.Marshal(data) 429 | roomServer.players[index - 1].ws.send <- jsonData 430 | } 431 | 432 | // 给房间内所有用户发送消息 433 | func (roomServer *RoomServer) sendToRoomPlayers(data Message) { 434 | jsonData, _ := json.Marshal(data) 435 | for i := 0; i < len(roomServer.players); i++ { 436 | roomServer.players[i].ws.send <- jsonData 437 | } 438 | } 439 | 440 | // 退出房间 441 | func (roomServer *RoomServer) exit() { 442 | for i := 0; i < len(roomServer.players); i++ { 443 | roomServer.players[i].ws.roomId = 0 444 | } 445 | 446 | msg := Message{} 447 | msg.Command = ROOM_EXIT 448 | roomServer.sendToRoomPlayers(msg) 449 | } 450 | 451 | // 拿到一副新好的牌 452 | func (roomServer *RoomServer) getNewCards54() []int { 453 | cards := make([]int, 54) 454 | for i := 0; i < 54; i++ { 455 | cards[i] = i + 1 456 | } 457 | 458 | rand.Seed(time.Now().UnixNano()) 459 | 460 | // 洗牌算法 461 | for i := 53; i >= 0; i-- { 462 | j := rand.Intn(i + 1) 463 | if i != j { 464 | temp := cards[i] 465 | cards[i] = cards[j] 466 | cards[j] = temp 467 | } 468 | } 469 | 470 | return cards 471 | } 472 | --------------------------------------------------------------------------------