package game import ( "github.com/c-my/MahjongServer/config" "github.com/c-my/MahjongServer/message" "github.com/c-my/MahjongServer/model" "github.com/c-my/MahjongServer/rule" "log" ) type MahjongManager struct { wall *model.Wall playerTile []model.PlayerTile rules rule.MahjongRule firstPlayer int gameRecvCh chan message.GameMsgRecv gameSendCh chan message.GameMsgSend tableOrderCh chan int gameResultCh chan message.GameResultMsg lastTableOrder int currentTableOrder int cancelList [4]bool lastMsgRecv message.GameMsgRecv } //NewMahjongManager return new Mahjong instance, with initiated game channels // and tile-wall func NewMahjongManager(gameRecvCh chan message.GameMsgRecv, gameSendCh chan message.GameMsgSend, tableOrderCh chan int, gameResultCh chan message.GameResultMsg, r rule.MahjongRule) *MahjongManager { mahjong := MahjongManager{} mahjong.rules = r mahjong.wall = model.NewWall(r.GenerateTiles) mahjong.playerTile = make([]model.PlayerTile, 4) mahjong.firstPlayer = 0 mahjong.gameRecvCh = gameRecvCh mahjong.gameSendCh = gameSendCh mahjong.tableOrderCh = tableOrderCh mahjong.gameResultCh = gameResultCh return &mahjong } //Start will start the main loop of a game in a new goroutine func (m *MahjongManager) Start() { go m.gameLoop(m.gameRecvCh, m.gameSendCh) } func (m MahjongManager) GetFirstPlayer() { panic("implement me") } func (m *MahjongManager) gameLoop(gameRecvCh chan message.GameMsgRecv, gameSendCh chan message.GameMsgSend) { for { msg := <-gameRecvCh log.Printf("player:[%d], action:[%d] ", msg.TableOrder, msg.Action) switch msg.Action { case config.Start: //inform each player their order m.tableOrderCh <- m.firstPlayer m.handleStart(msg) case config.Discard: m.handleDiscard(msg, true) case config.Chow: m.handleChow(msg) case config.Pong: m.handlePong(msg) case config.ExposedKong: m.handleExposedKong(msg) case config.ConcealedKong: m.handleConcealedKong(msg) case config.AddedKong: m.handleAddedKong(msg) case config.Win: m.handleWin(msg) case config.Cancel: m.handleCancel(msg) } } } func (m *MahjongManager) handleStart(msg message.GameMsgRecv) { m.resetCancelList() m.dealTile() //m.dealTileTest() newTile := m.wall.FrontDraw() if newTile.IsEmpty() { m.gameResultCh <- getTieResult() return } //TODO:判断第一张牌能否碰、杠、胡 availableActions := m.getAvailableActions(m.playerTile[msg.TableOrder].HandTiles, m.playerTile[msg.TableOrder].ShownTiles, newTile) m.playerTile[msg.TableOrder].HandTiles = append(m.playerTile[msg.TableOrder].HandTiles, newTile) model.SortTiles(m.playerTile[msg.TableOrder].HandTiles) var msgSend message.GameMsgSend msgSend = message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: msg.TableOrder, CurrentTile: newTile, AvailableActions: availableActions, LastTurn: msg.TableOrder, LastAction: config.Draw, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend } func (m *MahjongManager) Draw(hand []model.Tile, shown []model.ShownTile, newTile model.Tile) { } func (m *MahjongManager) handleDiscard(msg message.GameMsgRecv, removeHand bool) { currentOrder := msg.TableOrder m.lastTableOrder = msg.TableOrder if removeHand { //从手牌中删除 m.lastMsgRecv = msg hand := m.playerTile[msg.TableOrder].HandTiles m.playerTile[msg.TableOrder].HandTiles = model.RemoveTile(hand, msg.Tile, 1) } // 检查是否有人胡牌 for i := 1; i <= 3; i++ { order := (currentOrder + i) % 4 if m.cancelList[order] { continue } hand := m.playerTile[order].HandTiles shown := m.playerTile[order].ShownTiles if m.rules.CanWin(hand, shown, msg.Tile) { //send msg to this potential winner msgSend := message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: order, CurrentTile: msg.Tile, AvailableActions: []int{config.Win}, LastTurn: msg.TableOrder, LastAction: config.Discard, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend return } } // 检查是否有人碰、杠 for i := 1; i <= 3; i++ { order := (currentOrder + i) % 4 if m.cancelList[order] { continue } hand := m.playerTile[order].HandTiles if m.rules.CanExposedKong(hand, msg.Tile) { availableAction := make([]int, 0) chowTypes := make([]int, 0) availableAction = append(availableAction, config.Pong, config.ExposedKong) if i == 1 { canChow, types := m.rules.CanChow(hand, msg.Tile) if canChow { availableAction = append(availableAction, config.Chow) chowTypes = types } } msgSend := message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: order, CurrentTile: msg.Tile, AvailableActions: availableAction, LastTurn: msg.TableOrder, LastAction: config.Discard, ChowTypes: chowTypes, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend return } if m.rules.CanPong(hand, msg.Tile) { availableAction := make([]int, 0) chowTypes := make([]int, 0) availableAction = append(availableAction, config.Pong) if i == 1 { canChow, types := m.rules.CanChow(hand, msg.Tile) if canChow { availableAction = append(availableAction, config.Chow) chowTypes = types } } msgSend := message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: order, CurrentTile: msg.Tile, AvailableActions: availableAction, LastTurn: msg.TableOrder, LastAction: config.Discard, ChowTypes: chowTypes, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend return } } // 检查是否有人吃 order := (currentOrder + 1) % 4 nextHand := m.playerTile[order].HandTiles canChow, chowTypes := m.rules.CanChow(nextHand, msg.Tile) if !m.cancelList[order] && canChow { msgSend := message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: order, CurrentTile: msg.Tile, AvailableActions: []int{config.Chow}, LastTurn: msg.TableOrder, LastAction: config.Discard, ChowTypes: chowTypes, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend return } // 都没有 // 加入打牌区 dropTile := m.playerTile[msg.TableOrder].DropTiles m.playerTile[msg.TableOrder].DropTiles = append(dropTile, msg.Tile) //为下一位玩家发牌 m.lastTableOrder = msg.TableOrder m.currentTableOrder = (msg.TableOrder + 1) % 4 newTile := m.wall.FrontDraw() if newTile.IsEmpty() { m.gameResultCh <- getTieResult() return } availableActions := m.getAvailableActions(m.playerTile[m.currentTableOrder].HandTiles, m.playerTile[m.currentTableOrder].ShownTiles, newTile) m.playerTile[m.currentTableOrder].HandTiles = append(m.playerTile[m.currentTableOrder].HandTiles, newTile) model.SortTiles(m.playerTile[m.currentTableOrder].HandTiles) msgSend := message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: m.currentTableOrder, CurrentTile: newTile, AvailableActions: availableActions, LastTurn: msg.TableOrder, LastAction: config.Discard, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend // end } func (m *MahjongManager) handleChow(msg message.GameMsgRecv) { m.resetCancelList() //更新手牌和明牌 shown := m.playerTile[msg.TableOrder].ShownTiles // 删除手牌 switch msg.ChowType { case config.LeftChow: mid := msg.Tile.GetRightTile() right := (*mid).GetRightTile() m.playerTile[msg.TableOrder].HandTiles = model.RemoveTile(m.playerTile[msg.TableOrder].HandTiles, *mid, 1) m.playerTile[msg.TableOrder].HandTiles = model.RemoveTile(m.playerTile[msg.TableOrder].HandTiles, *right, 1) m.playerTile[msg.TableOrder].ShownTiles = append(shown, model.ShownTile{ ShownType: config.Chow, Tiles: []model.Tile{msg.Tile, *mid, *right}, }) case config.MidChow: left := msg.Tile.GetLeftTile() right := msg.Tile.GetRightTile() m.playerTile[msg.TableOrder].HandTiles = model.RemoveTile(m.playerTile[msg.TableOrder].HandTiles, *left, 1) m.playerTile[msg.TableOrder].HandTiles = model.RemoveTile(m.playerTile[msg.TableOrder].HandTiles, *right, 1) m.playerTile[msg.TableOrder].ShownTiles = append(shown, model.ShownTile{ ShownType: config.Chow, Tiles: []model.Tile{*left, msg.Tile, *right}, }) case config.RightChow: mid := msg.Tile.GetLeftTile() left := (*mid).GetLeftTile() m.playerTile[msg.TableOrder].HandTiles = model.RemoveTile(m.playerTile[msg.TableOrder].HandTiles, *mid, 1) m.playerTile[msg.TableOrder].HandTiles = model.RemoveTile(m.playerTile[msg.TableOrder].HandTiles, *left, 1) m.playerTile[msg.TableOrder].ShownTiles = append(shown, model.ShownTile{ ShownType: config.Chow, Tiles: []model.Tile{*left, *mid, msg.Tile}, }) } //发送消息 msgSend := message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: msg.TableOrder, //CurrentTile: nil, AvailableActions: []int{config.Discard}, LastTurn: msg.TableOrder, LastAction: config.Chow, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend } // handlePong收到碰的消息,通知每个玩家,并等待该玩家发出打牌的消息 func (m *MahjongManager) handlePong(msg message.GameMsgRecv) { m.resetCancelList() m.lastTableOrder = m.currentTableOrder m.currentTableOrder = msg.TableOrder hand := m.playerTile[msg.TableOrder].HandTiles shown := m.playerTile[msg.TableOrder].ShownTiles // 更新明牌 m.playerTile[msg.TableOrder].ShownTiles = append(shown, model.ShownTile{ ShownType: config.Pong, Tiles: []model.Tile{msg.Tile, msg.Tile, msg.Tile}, }) // 更新手牌 newHand := model.RemoveTile(hand, msg.Tile, 2) m.playerTile[msg.TableOrder].HandTiles = newHand // 发送消息 msgSend := message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: m.currentTableOrder, //CurrentTile: nil, //其他玩家不会管, 当前玩家会忽略 AvailableActions: []int{config.Discard}, LastTurn: m.currentTableOrder, LastAction: config.Pong, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend } func (m *MahjongManager) handleExposedKong(msg message.GameMsgRecv) { m.resetCancelList() m.lastTableOrder = m.currentTableOrder m.currentTableOrder = msg.TableOrder // 更新手牌和明牌 hand := m.playerTile[msg.TableOrder].HandTiles shown := m.playerTile[msg.TableOrder].ShownTiles newHand := model.RemoveTile(hand, msg.Tile, 3) newShown := append(shown, model.ShownTile{ ShownType: config.ExposedKong, Tiles: []model.Tile{msg.Tile, msg.Tile, msg.Tile, msg.Tile}, }) m.playerTile[msg.TableOrder].HandTiles = newHand m.playerTile[msg.TableOrder].ShownTiles = newShown //为玩家发牌 newTile := m.wall.BackDraw() if newTile.IsEmpty() { m.gameResultCh <- getTieResult() return } availableActions := m.getAvailableActions(m.playerTile[msg.TableOrder].HandTiles, m.playerTile[msg.TableOrder].ShownTiles, newTile) m.playerTile[msg.TableOrder].HandTiles = append(m.playerTile[msg.TableOrder].HandTiles, newTile) model.SortTiles(m.playerTile[msg.TableOrder].HandTiles) //发送消息 msgSend := message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: msg.TableOrder, CurrentTile: newTile, AvailableActions: availableActions, LastTurn: msg.TableOrder, LastAction: config.ExposedKong, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend } func (m *MahjongManager) handleConcealedKong(msg message.GameMsgRecv) { m.resetCancelList() // 更新手牌和明牌 hand := m.playerTile[msg.TableOrder].HandTiles shown := m.playerTile[msg.TableOrder].ShownTiles newHand := model.RemoveTile(hand, msg.Tile, 4) newShown := append(shown, model.ShownTile{ ShownType: config.ConcealedKong, Tiles: []model.Tile{msg.Tile, msg.Tile, msg.Tile, msg.Tile}, }) m.playerTile[msg.TableOrder].HandTiles = newHand m.playerTile[msg.TableOrder].ShownTiles = newShown //为玩家发牌 newTile := m.wall.BackDraw() if newTile.IsEmpty() { m.gameResultCh <- getTieResult() return } availableActions := m.getAvailableActions(m.playerTile[msg.TableOrder].HandTiles, m.playerTile[msg.TableOrder].ShownTiles, newTile) m.playerTile[msg.TableOrder].HandTiles = append(m.playerTile[msg.TableOrder].HandTiles, newTile) model.SortTiles(m.playerTile[msg.TableOrder].HandTiles) //发送消息 msgSend := message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: msg.TableOrder, CurrentTile: newTile, AvailableActions: availableActions, LastTurn: msg.TableOrder, LastAction: config.ConcealedKong, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend } func (m *MahjongManager) handleAddedKong(msg message.GameMsgRecv) { m.resetCancelList() //更新手牌和明牌 hand := m.playerTile[msg.TableOrder].HandTiles shown := m.playerTile[msg.TableOrder].ShownTiles m.playerTile[msg.TableOrder].HandTiles = model.RemoveTile(hand, msg.Tile, 1) pos := 0 for i, v := range shown { if v.ShownType == config.Pong && v.Tiles[0].Equals(msg.Tile) { pos = i break } } newShown := make([]model.ShownTile, 0) newShown = append(newShown, shown[:pos]...) newShown = append(newShown, model.ShownTile{ ShownType: config.AddedKong, Tiles: []model.Tile{msg.Tile, msg.Tile, msg.Tile, msg.Tile}, }) newShown = append(newShown, shown[pos+1:]...) m.playerTile[msg.TableOrder].ShownTiles = newShown //发牌 newTile := m.wall.BackDraw() if newTile.IsEmpty() { m.gameResultCh <- getTieResult() return } availableActions := m.getAvailableActions(m.playerTile[msg.TableOrder].HandTiles, m.playerTile[msg.TableOrder].ShownTiles, newTile) m.playerTile[msg.TableOrder].HandTiles = append(m.playerTile[msg.TableOrder].HandTiles, newTile) model.SortTiles(m.playerTile[msg.TableOrder].HandTiles) //发送消息 msgSend := message.GameMsgSend{ MsgType: config.GameMsgType, TableOrder: -1, CurrentTurn: msg.TableOrder, CurrentTile: newTile, AvailableActions: availableActions, LastTurn: msg.TableOrder, LastAction: config.AddedKong, PlayerTile: m.playerTile, WallCount: m.wall.Length(), } m.gameSendCh <- msgSend } func (m *MahjongManager) handleWin(msg message.GameMsgRecv) { m.resetCancelList() msgSend := message.GameResultMsg{ MsgType: config.GameResultMsgType, Winner: msg.TableOrder, FinalTile: msg.Tile, } m.gameResultCh <- msgSend } func (m *MahjongManager) handleCancel(msg message.GameMsgRecv) { m.cancelList[msg.TableOrder] = true m.handleDiscard(m.lastMsgRecv, false) } func (m *MahjongManager) getAvailableActions(hand []model.Tile, shown []model.ShownTile, newTile model.Tile) []int { availableActions := make([]int, 0) availableActions = append(availableActions, config.Discard) if m.rules.CanConcealedKong(hand, newTile) { availableActions = append(availableActions, config.ConcealedKong) } else if m.rules.CanAddedKong(shown, newTile) { availableActions = append(availableActions, config.AddedKong) } if m.rules.CanWin(hand, shown, newTile) { availableActions = append(availableActions, config.Win) } return availableActions } func (m *MahjongManager) resetCancelList() { m.cancelList[0] = false m.cancelList[1] = false m.cancelList[2] = false m.cancelList[3] = false } func getTieResult() message.GameResultMsg { return message.GameResultMsg{ MsgType: config.GameResultMsgType, Winner: -1, FinalTile: model.Tile{}, } } func (m *MahjongManager) dealTile() { for i := 0; i < 3; i++ { m.playerTile[0].HandTiles = append(m.playerTile[0].HandTiles, m.wall.FrontDraw()) m.playerTile[0].HandTiles = append(m.playerTile[0].HandTiles, m.wall.FrontDraw()) m.playerTile[0].HandTiles = append(m.playerTile[0].HandTiles, m.wall.FrontDraw()) m.playerTile[0].HandTiles = append(m.playerTile[0].HandTiles, m.wall.FrontDraw()) m.playerTile[1].HandTiles = append(m.playerTile[1].HandTiles, m.wall.FrontDraw()) m.playerTile[1].HandTiles = append(m.playerTile[1].HandTiles, m.wall.FrontDraw()) m.playerTile[1].HandTiles = append(m.playerTile[1].HandTiles, m.wall.FrontDraw()) m.playerTile[1].HandTiles = append(m.playerTile[1].HandTiles, m.wall.FrontDraw()) m.playerTile[2].HandTiles = append(m.playerTile[2].HandTiles, m.wall.FrontDraw()) m.playerTile[2].HandTiles = append(m.playerTile[2].HandTiles, m.wall.FrontDraw()) m.playerTile[2].HandTiles = append(m.playerTile[2].HandTiles, m.wall.FrontDraw()) m.playerTile[2].HandTiles = append(m.playerTile[2].HandTiles, m.wall.FrontDraw()) m.playerTile[3].HandTiles = append(m.playerTile[3].HandTiles, m.wall.FrontDraw()) m.playerTile[3].HandTiles = append(m.playerTile[3].HandTiles, m.wall.FrontDraw()) m.playerTile[3].HandTiles = append(m.playerTile[3].HandTiles, m.wall.FrontDraw()) m.playerTile[3].HandTiles = append(m.playerTile[3].HandTiles, m.wall.FrontDraw()) } m.playerTile[0].HandTiles = append(m.playerTile[0].HandTiles, m.wall.FrontDraw()) m.playerTile[1].HandTiles = append(m.playerTile[1].HandTiles, m.wall.FrontDraw()) m.playerTile[2].HandTiles = append(m.playerTile[2].HandTiles, m.wall.FrontDraw()) m.playerTile[3].HandTiles = append(m.playerTile[3].HandTiles, m.wall.FrontDraw()) for i := 0; i <= 3; i++ { m.playerTile[i].SortHand() } } func (m *MahjongManager) dealTileTest() { for i := 1; i <= 6; i++ { m.playerTile[0].HandTiles = append(m.playerTile[0].HandTiles, model.Tile{Suit: model.Character, Number: i}) m.playerTile[1].HandTiles = append(m.playerTile[1].HandTiles, model.Tile{Suit: model.Character, Number: i}, model.Tile{Suit: model.Character, Number: i}) } for i := 1; i <= 4; i++ { m.playerTile[0].HandTiles = append(m.playerTile[0].HandTiles, model.Tile{Suit: model.Bamboo, Number: i}) m.playerTile[2].HandTiles = append(m.playerTile[2].HandTiles, model.Tile{Suit: model.Bamboo, Number: i}, model.Tile{Suit: model.Bamboo, Number: i}, model.Tile{Suit: model.Bamboo, Number: i}) } m.playerTile[0].HandTiles = append(m.playerTile[0].HandTiles, model.Tile{Suit: model.Dragon, Number: 1}) m.playerTile[0].HandTiles = append(m.playerTile[0].HandTiles, model.Tile{Suit: model.Dragon, Number: 1}) m.playerTile[0].HandTiles = append(m.playerTile[0].HandTiles, model.Tile{Suit: model.Character, Number: 1}) m.playerTile[1].HandTiles = append(m.playerTile[1].HandTiles, model.Tile{Suit: model.Dragon, Number: 1}) m.playerTile[2].HandTiles = append(m.playerTile[2].HandTiles, model.Tile{Suit: model.Dragon, Number: 1}) for i := 0; i < 13; i++ { m.playerTile[3].HandTiles = append(m.playerTile[3].HandTiles, m.wall.FrontDraw()) } for i := 0; i <= 3; i++ { m.playerTile[i].SortHand() } }