基于go语言的牛牛游戏服务器搭建

游戏规则:
游戏用牌为除大小王以外的一副牌,共计52张。玩家人数为固定4人(初版人数),每人随机发5张牌。

  1. 牌型说明
    牌型 赔率(闲家下注) 说明
    无牛 1倍 五张牌中没有任意三张牌点数之和为10的整数倍,例如:a、8、4、k、7。
    有牛 1~2倍 五张牌中有三张的点数之和为10点的整数倍,并且另外两张牌之和与10进行取余,所得之数即为牛几,例如:2、8、j、6、3,即为牛9。牛一到牛6为1倍,牛七到牛九为2倍。
    牛牛 3倍 五张牌中第一组三张牌和第一组二张牌之和分别为10的整数倍,例如:3、7、k、10、j。
    银牛 4倍 五张牌全由10~k组成且只有一张10,例如:10、j、j、q、k。
    金牛 5倍 五张牌全由j~k组成,例如:j、j、q、q、k。
    炸弹 6倍 五张牌中有4张牌点数相同的牌型,例如:2、2、2、2、k。
    五小 10倍 五张牌的点数加起来小于10,且每张牌点数都小于5,例如:a、3、2、a、2。

  2. 牌型比较
    牌型:五小牛 > 炸弹 > 金牛 > 银牛 > 牛牛 > 有牛 > 无牛。
    单张:K > Q > J > 10 > 9 > 8 > 7 > 6 > 5 > 4 > 3 > 2 > A。
    花色:黑桃 > 红桃 > 梅花 > 方块(不采用)。

同牌型间的比较:
无牛:比最大单张大小。
有牛:牛9 > 牛8 > ~ > 牛2 > 牛1;牛数相同庄家赢(庄吃闲)。
牛牛:比最大单张大小。
银牛(四花):比最大单张大小。
金牛(五花):比最大单张大小。
炸弹:比炸弹牌大小。
五小:庄家赢(庄吃闲)。

  1. 定庄押注
    第一盘随机选择庄家,若游戏过程中没有出现“牛九”及以上牌型则继续由上盘玩家担任庄家,若出现牛九牌型则由获得牛九牌型的玩家下盘担任庄家。若在同一盘中有多名玩家出现牛9牌型则再进行大小比较,由最大牌型的玩家下盘担任庄家。所有的大小比较过程均是庄家和闲家比较,闲家和闲家之间不进行比较。

  2. 规则补充
    特殊:当庄家与闲家牌型相同需要比单张时,系统自动比较两家手中最大的一张牌,谁大谁赢;如果两家最大的牌点数相同,则继续比较两家第二大的牌,同理直到第五张比完(不比花色);如果两家所有牌点数相同,默认庄家赢。

1.协议制定

package gameInfo

/*该包为游戏信息协议定制*/

const (
	INIT_GAMEINFO       = iota
	C2S_LoginInfo_Proto //1 登录信息
	S2C_LoginInfo_Proto //2 登录返回信息

	C2S_PipeiGame_Proto          //3 客户端发送服务器 玩家匹配
	S2C_PipeiGameBroadCast_Proto //4 服务器返回匹配成功 返回房间信息

	C2S_Score_Proto            //5押注信息
	S2C_ShuffleBroadCast_Proto //6服务器广播押注信息并发牌

	C2S_CombineCardsInfo_Proto //7 组牌信息
	S2C_Settlement_Proto       //8 结算信息

)

/*json结构体形式
{
	GameInfo:LoginInfo_Proto,
	PlayerName:xxx                协议名字+结构体内容
}*/

//登录信息
type C2S_LoginInfo struct { //玩家登录信息,暂只传昵称
	GameInfo   int
	PlayerName string
}

type S2C_LoginInfo struct { //服务器返回玩家信息:ID 昵称 余额
	GameInfo int
	Succ     bool
	Player   *PlayerInfo
}

//匹配信息
type C2S_PipeiGame struct {
	GameInfo int
	ID       string
}

type S2C_PipeiGameBroadCast struct {
	GameInfo int
	Sucs     bool
	RoomInfo *Room //房间信息
}

//押注信息
type C2S_Score struct {
	GameInfo int
	RoomID   string //房间id
	ID       string //个人id
	ChairID  int    //个人椅子id
	Score    int    //押注数
}

//押注信息广播并发牌
type S2C_ShuffleBroadCast struct {
	GameInfo    int
	RoomID      string         //房间号(Room.RoomID)
	ChairsScore []int          //所有的椅子的score已经被更新了
	BankerID    string         //庄家id(Room.BankerID)
	Poker       *PersonalPoker //只有自己的牌
}

type C2S_CombineCardsInfo struct { //客户端发送组牌信息
	GameInfo      int
	ID            string
	RoomID        string
	ChairID       int
	PokerType     int   //牌型
	Poker         []int //所有的牌
	NiuCardsInfo  []int //牛牌组成
	LastCardsInfo []int //余牌组成
}

type S2C_Settlement struct { //服务器发送结算信息
	GameInfo int
	Succs    map[int]bool //胜负标志位
	RoomInfo *Room
}

//服务器内部逻辑结构体
type PlayerInfo struct {
	ID          string
	PlayerName  string
	PlayerMoney int
}

type PersonalPoker struct {
	ID            string //存人的ID
	Cards         []int
	NiuCardsInfo  []int
	LastCardsInfo []int
	PokerType     int
} //牌

type Chair struct {
	ChairID  int
	Player   *PlayerInfo
	Lefttime int
	Score    int //下注底分
	Poker    *PersonalPoker
}

type Room struct {
	RoomID   string
	Chairs   [4]*Chair
	BankerID string //庄家号
	//roomLimit  int
	//RoomStatus int
}


2.主函数启用websocket

package main

import (
	"flag"
	"fmt"
	"net/http"

	//"runtime"
	"go-concurrentMap-master"

	"github.com/golang/glog"
	"github.com/gorilla/websocket"
)

//ip:ws://100.64.15.187:8800/ws
var (
	M *concurrent.ConcurrentMap
)

func init() {

	M = concurrent.NewConcurrentMap() //初始化玩家信息MAP

	flag.Set("alsologtostderr", "true") // 日志写入文件的同时,输出到stderr
	flag.Set("log_dir", "./log")        // 日志文件保存目录
	flag.Set("v", "3")                  // 配置V输出的等级。
	flag.Parse()
	return
}

func httphandle(resp http.ResponseWriter, req *http.Request) {
	up := websocket.Upgrader{
		// 检查区域 可以自行设置是POST 或者GET请求 还有URL等信息 这里直接设置表示都接受
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
	conn, err := up.Upgrade(resp, req, nil)
	if err != nil {
		glog.Error("upgrede failed")
		return
	} else {
		glog.Info("now is websocket")
	}
	c := &Client{
		Socket: conn, //ws连接器
	}
	c.ReadFromClient()

}

func main() {

	http.HandleFunc("/ws", httphandle)
	err := http.ListenAndServe(":8800", nil)
	if err != nil {
		fmt.Println("ListenAndServe : ", err)
		return
	}

}

3.协议处理函数

package main

import (
	"crypto/md5"
	"encoding/json"

	"fmt"
	"gameInfo"
	"math/rand"
	"reflect" //反射
	"time"

	//"go-concurrentMap-master"
	"AI"

	"github.com/golang/glog"
	"github.com/gorilla/websocket"
)

//连接存储结构
type Client struct {
	Socket  *websocket.Conn //ws连接器
	StrROOM string          //房间ID
	Player  *gameInfo.PlayerInfo
}

//json 转化为map(信息反序列化)
func Json2map(content []byte) (s map[string]interface{}, err error) {
	var result map[string]interface{}
	if err := json.Unmarshal(content, &result); err != nil {
		glog.Error("Json2map:", err.Error())
		//fmt.Println(err)
		return nil, err
	}
	return result, nil
}

//json 返回数据(信息序列化)
func (this *Client) MsgSend(senddata interface{}) {
	err := this.Socket.WriteJSON(senddata) //返回json化的数据
	if err != nil {
		glog.Error(err)
		return
	}
	return
}

func (this *Client) ReadFromClient() {
	for {
		var content []byte
		var err error
		if _, content, err = this.Socket.ReadMessage(); err != nil {
			break
		}
		if len(content) == 0 {
			break
		}
		//
		//glog.Info(content)
		//正常处理数据
		//并发
		go this.SyncMeassgeFun(content)
	}
	return
}

func (this *Client) SyncMeassgeFun(content []byte) {
	defer glog.Flush()
	if GameMsg, err := Json2map(content); err == nil {
		//处理函数
		this.HandleFunc(GameMsg["GameInfo"], GameMsg)

	} else {
		glog.Error("解析失败:", err.Error())
		//fmt.Println("解析失败")
	}
}

//协议判断函数
func (this *Client) HandleFunc(MsgType interface{}, GameMsg map[string]interface{}) {
	switch MsgType {
	case float64(gameInfo.C2S_LoginInfo_Proto):
		{
			this.LoginInfo_Handler(GameMsg)
		}
	case float64(gameInfo.C2S_PipeiGame_Proto):
		{
			this.Pipei_Handler(GameMsg)
		}
	case float64(gameInfo.C2S_Score_Proto):
		{
			this.Score_Handler(GameMsg)
		}
	case float64(gameInfo.C2S_CombineCardsInfo_Proto):
		{
			this.CombineCardsInfo_Handler(GameMsg)
		}
	default:
		panic("子协议:不存在!")
	}

}

//协议处理函数

/**登录信息处理**/
/*测试json
{
"GameInfo": 1,
"PlayerName": "rubylee"
}
*/

/*生成id函数*/
//生成随机字符串
func RandString(len int) string {
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	bytes := make([]byte, len)
	for i := 0; i < len; i++ {
		b := r.Intn(26) + 65
		bytes[i] = byte(b)
	}
	return string(bytes)
}

//md5转换函数
func md5Change(str string) string {
	md5String := fmt.Sprintf("%x", md5.Sum([]byte(str)))
	return md5String
}

func (this *Client) LoginInfo_Handler(GameMsg map[string]interface{}) {
	defer glog.Flush() //刷新日志流

	if GameMsg["PlayerName"] == nil {
		glog.Error("协议LoginInfo_Proto ,登录功能错误:登录名不存在!")
		this.Socket.Close() //关闭连接
		return
	}

	this.Player = &gameInfo.PlayerInfo{
		ID:          md5Change(RandString(6)),       //ID获取函数,
		PlayerName:  GameMsg["PlayerName"].(string), //断言
		PlayerMoney: 10000,                          //默认金钱
	}

	data := &gameInfo.S2C_LoginInfo{
		GameInfo: gameInfo.S2C_LoginInfo_Proto,
		Player:   this.Player,
		Succ:     true,
	}
	//数据保存
	M.Put(this.Player.ID, this)
	//发送数据给客户端
	this.MsgSend(data)
	return
}

/**匹配信息处理**/
func (this *Client) Pipei_Handler(GameMsg map[string]interface{}) {
	//匹配逻辑
	//1.队列形式
	defer glog.Flush()
	strID := GameMsg["ID"].(string)
	if strID != this.Player.ID {
		glog.Error("匹配错误:id值不一致!")
		return
	}

	val, err := M.Get(this.Player.ID)
	if err != nil {
		glog.Error("匹配错误:不在全局玩家数据表中!")
		return
	}
	glog.Info("匹配开始!")
	PutMatchList(val.(*Client).Player)
	return
}

/**积分信息处理**/
func (this *Client) Score_Handler(GameMsg map[string]interface{}) {
	defer glog.Flush()
	roomID := GameMsg["RoomID"].(string)
	chairID := int(GameMsg["ChairID"].(float64))
	pID := GameMsg["ID"].(string)
	new_score := int(GameMsg["Score"].(float64))
	GRoomManagerPtr.RoomLock.RLock()           //加锁
	_, ok := GRoomManagerPtr.GRoomData[roomID] //取房间

	//核对有无房间
	if !ok {
		GRoomManagerPtr.RoomLock.RUnlock() //解锁
		glog.Error("Score_Handler:房间信息不存在!")
		return
	}

	//核对个人id
	if pID != GRoomManagerPtr.GRoomData[roomID].Chairs[chairID].Player.ID {
		GRoomManagerPtr.RoomLock.RUnlock() //解锁
		glog.Errorf("Score_Handler:房间号%v,座位号%v,的ID信息不一致!", roomID, chairID)
		glog.Errorln("pid", pID, "new_score", new_score)
		return
	} else {
		//修改底分信息
		GRoomManagerPtr.GRoomData[roomID].Chairs[chairID].Score = new_score
	}
	GRoomManagerPtr.RoomLock.RUnlock()
	return
}

func interArray2intArray(q []interface{}) []int {
	q1 := make([]int, 0)
	for i := 0; i < len(q); i++ {
		q1 = append(q1, int(q[i].(float64)))
	}
	return q1
}

/**组牌信息处理**/
func (this *Client) CombineCardsInfo_Handler(GameMsg map[string]interface{}) {
	defer glog.Flush()
	roomID := GameMsg["RoomID"].(string)                                           //房间号
	chairID := int(GameMsg["ChairID"].(float64))                                   //椅子号
	pID := GameMsg["ID"].(string)                                                  //用户ID
	poker := interArray2intArray(GameMsg["Poker"].([]interface{}))                 //牌型
	new_NiuCards := interArray2intArray(GameMsg["NiuCardsInfo"].([]interface{}))   //新的牛牌
	new_LastCards := interArray2intArray(GameMsg["LastCardsInfo"].([]interface{})) //新的余牌
	new_PokerType := int(GameMsg["PokerType"].(float64))                           //新的牌型编号

	GRoomManagerPtr.RoomLock.RLock()           //加锁
	_, ok := GRoomManagerPtr.GRoomData[roomID] //取房间

	//核对有无房间
	if !ok {
		GRoomManagerPtr.RoomLock.RUnlock() //解锁
		glog.Error("CombineCardsInfo_Handler:房间信息不存在!")
		return
	}

	//核对个人id
	if pID != GRoomManagerPtr.GRoomData[roomID].Chairs[chairID].Player.ID {
		GRoomManagerPtr.RoomLock.RUnlock() //解锁
		glog.Errorf("CombineCardsInfo_Handler:房间号%v,座位号%v,的ID信息不一致!", roomID, chairID)
		return
	} else if !reflect.DeepEqual(poker, GRoomManagerPtr.GRoomData[roomID].Chairs[chairID].Poker.Cards) {
		//牌不一致
		GRoomManagerPtr.RoomLock.RUnlock() //解锁
		glog.Errorf("CombineCardsInfo_Handler:房间号%v,座位号%v,的牌信息不一致!", roomID, chairID)
		return
	} else {
		//牌型校验
		cardstmp := &gameInfo.PersonalPoker{
			Cards:         poker,
			NiuCardsInfo:  new_NiuCards,
			LastCardsInfo: new_LastCards,
			PokerType:     new_PokerType,
		}
		if !AI.CheckType(cardstmp) {
			GRoomManagerPtr.RoomLock.RUnlock() //解锁
			glog.Errorf("CombineCardsInfo_Handler:房间号%v,座位号%v,的牌型校验失败!", roomID, chairID)
			return
		}
		GRoomManagerPtr.GRoomData[roomID].Chairs[chairID].Poker.NiuCardsInfo = new_NiuCards
		GRoomManagerPtr.GRoomData[roomID].Chairs[chairID].Poker.LastCardsInfo = new_LastCards
		GRoomManagerPtr.GRoomData[roomID].Chairs[chairID].Poker.PokerType = new_PokerType
	}
	GRoomManagerPtr.RoomLock.RUnlock()
	return
}

4.匹配函数

package main

import (
	"gameInfo"
	"time"

	"github.com/golang/glog"
)

var (
	GMatchChan chan map[string]*gameInfo.PlayerInfo
	Gmap       map[string]*gameInfo.PlayerInfo
)

func init() {
	GMatchChan = make(chan map[string]*gameInfo.PlayerInfo, 1000)
	Gmap = make(map[string]*gameInfo.PlayerInfo)
	go Timer()
}

func PutMatchList(data *gameInfo.PlayerInfo) {
	Gmap[data.ID] = data
}

//匹配timer
func Timer() {
	for {
		select {
		case <-time.After(time.Millisecond * 1):
			{
				// 进行数据验证 -- 确保我们链接信息完全获取到
				// 不够严谨,如果不验证玩家数据是否保存成功,会导致客户端一个匹配成功,无法游戏。
				// 确保4个人, 如果满足len(Gmap)%4 == 0  --->  GMatchChan
				if len(Gmap)%4 == 0 && len(Gmap) != 0 {
					datatmp := make(map[string]*gameInfo.PlayerInfo)
					for k, v := range Gmap {
						if _, err := M.Get(k); err != nil {
							glog.Error("玩家未在在线玩家数据表中!!")
							delete(Gmap, k)
							break
						}
						datatmp[k] = v
						delete(Gmap, k)
					}
					SendGMatchChan(datatmp)
				}
			}
		}
	}
}

func SendGMatchChan(data map[string]*gameInfo.PlayerInfo) {
	if len(data) == 0 {
		return
	}
	GMatchChan <- data
}

5.游戏逻辑代码

package main

import (
	"AI"
	"gameInfo"
	"math/rand"
	"strconv"
	"sync"
	"time"

	"github.com/golang/glog"
)

/*
房间结构
房间管理器 manager
  1.玩家数据
  2.房间的销毁
  3.游戏数据存储


type Room struct {
	RoomID int
	Chair  [4]*Chair
	Banker *Chair //庄家号
	//roomLimit  int
	RoomStatus int
}
*/
var (
	GRoomManagerPtr *GRoomSTData
)

type GRoomSTData struct {
	GRoomData map[string]*gameInfo.Room
	RoomLock  *sync.RWMutex //并发安全
}

func init() {
	GRoomManagerPtr = &GRoomSTData{
		GRoomData: make(map[string]*gameInfo.Room), //每一个匹配成功数据总和,例如:A,B,C,D
		RoomLock:  new(sync.RWMutex),
	}

	go matchtimer()
}

//获取全局RoomID
func GetRoomID() int {
	GRoomManagerPtr.RoomLock.RLock()
	ilen := len(GRoomManagerPtr.GRoomData) + 1000
	GRoomManagerPtr.RoomLock.RUnlock()
	return ilen
}

/*
说明:1. 4人组合一个房间
	 2. 发送广播消息
	 3.  房间生成规则
     4. 处理数据结构
	 线程通信;
*/
func matchtimer() {
	startTimer := time.NewTicker(time.Millisecond * 10)

	for {
		select {
		case <-startTimer.C:
			{
				datachan := <-GMatchChan //拿绑定的4人
				strRoomID := strconv.Itoa(GetRoomID())
				GRoomManagerPtr.RoomLock.RLock()
				roomtmp := GRoomManagerPtr.GRoomData[strRoomID] //取房间
				GRoomManagerPtr.RoomLock.RUnlock()
				glog.Infoln("房间号", strRoomID)
				if roomtmp == nil {
					roomtmp = &gameInfo.Room{}
				}

				roomtmp.RoomID = strRoomID
				dealgamelogic(roomtmp, datachan, strRoomID)

			}
		}
	}
}
func dealgamelogic(roomtmp *gameInfo.Room, datachan map[string]*gameInfo.PlayerInfo, strRoomID string) {

	//确定庄家

	b := rand.Intn(4) //0-3号

	index := 0
	for id, _ := range datachan {
		val, _ := M.Get(id)
		/*
			    type Chair struct {
				ChairID  int
			    Player   PlayerInfo
			    Lefttime int
				                }
		*/
		chair := &gameInfo.Chair{
			ChairID:  index,
			Player:   val.(*Client).Player,
			Lefttime: 5,
			Score:    10,
		}
		/*
				type Room struct {
			    RoomID string
				Chairs [4]*Chair
				Banker string //庄家号
			    //roomLimit  int
			    //RoomStatus int
					              }
		*/
		roomtmp.Chairs[index] = chair
		val.(*Client).StrROOM = strRoomID
		index++
	}
	roomtmp.BankerID = roomtmp.Chairs[b].Player.ID //保存庄家号
	//再次遍历发送消息
	index = 0
	for id, _ := range datachan {
		val, _ := M.Get(id)

		/* type S2C_PipeiGameBroadCast struct {
			                     GameInfo int
			                     Sucs     bool
			                     RoomInfo Room      //房间信息
		                     }*/

		data := gameInfo.S2C_PipeiGameBroadCast{
			GameInfo: gameInfo.S2C_PipeiGameBroadCast_Proto,
			Sucs:     true,
			RoomInfo: roomtmp,
		}
		//广播 消息
		val.(*Client).MsgSend(data)
		index++
	}
	//房间存储全局房间管理器
	GRoomManagerPtr.GRoomData[strRoomID] = roomtmp

	/*************等待玩家给出底分***********/

	//定时器5s  等玩家给出底分
	timer := time.NewTimer(7 * time.Second) //等5S
	<-timer.C

	/***************开始发牌**********/
	glog.Info("发牌开始!")
	q := AI.Shuffle(4)
	ScoreSet := make([]int, 4) //存储新的底分

	for i := 0; i < len(roomtmp.Chairs); i++ {
		pokertmp := &gameInfo.PersonalPoker{}
		pokertmp.ID = roomtmp.Chairs[i].Player.ID
		//该语句需要在下一条之前 因为MAXtype函数中 会对q[i]按照牛牛牌的逻辑从大到小排序 使得发送到客户端的牌是 从左到右依次减小的
		pokertmp.NiuCardsInfo, pokertmp.LastCardsInfo, pokertmp.PokerType = AI.MaxType(q[i]) //预设为最大牌型
		pokertmp.Cards = q[i]
		roomtmp.Chairs[i].Poker = pokertmp
		ScoreSet[i] = roomtmp.Chairs[i].Score //存储每张chair的新底分
	}

	//广播发牌信息
	index = 0
	for id, _ := range datachan {
		val, _ := M.Get(id)
		roomtmp.Chairs[index].Poker.ID = id
		/*
						type S2C_ShuffleBroadCast struct {
			              GameInfo int
			              RoomID   string        //房间号(Room.RoomID)
			              Chairs   [4]*Chair     //所有的椅子 (Room.Chairs)里面的score已经被更新了
			              BankerID string        //庄家id(Room.BankerID)
			              Poker    PersonalPoker //只有自己的牌
		                 }*/
		data := gameInfo.S2C_ShuffleBroadCast{
			GameInfo:    gameInfo.S2C_ShuffleBroadCast_Proto,
			RoomID:      strRoomID,
			ChairsScore: ScoreSet, //返回新的底分
			Poker:       roomtmp.Chairs[index].Poker,
		}
		//广播 消息
		val.(*Client).MsgSend(data)
		index++
	}

	/**********等待玩家组牌信息*************/
	//定时器20s  等待玩家给出牌型
	timer = time.NewTimer(5 * time.Second) //等10S
	<-timer.C

	/***************开始结算*************/
	glog.Info("结算开始!")

	/*
						type S2C_Settlement struct { //服务器发送结算信息
			              GameInfo int
			              succs    map[int]bool //胜负标志位
			              RoomInfo *Room
		                  }
	*/
	dataSettlment := &gameInfo.S2C_Settlement{
		GameInfo: gameInfo.S2C_Settlement_Proto,
		Succs:    make(map[int]bool),
	}

	banker_poker := roomtmp.Chairs[b].Poker //庄家的牌型
	for index := 0; index < len(roomtmp.Chairs); index++ {
		if index == b {
			continue
		} //跳过庄家自身

		//牌型比较
		flag, multiple := AI.Settlement(banker_poker, roomtmp.Chairs[index].Poker)

		//庄家赢
		if flag {

			tmpscore := roomtmp.Chairs[index].Player.PlayerMoney - multiple*roomtmp.Chairs[index].Score

			if tmpscore < 0 {
				roomtmp.Chairs[index].Player.PlayerMoney = 0
			} else {
				roomtmp.Chairs[index].Player.PlayerMoney = tmpscore
			}
			dataSettlment.Succs[index] = false
			roomtmp.Chairs[b].Player.PlayerMoney += multiple * roomtmp.Chairs[index].Score //庄家赢钱
		} else {
			//闲家赢
			tmpscore := roomtmp.Chairs[b].Player.PlayerMoney - multiple*roomtmp.Chairs[index].Score

			if tmpscore < 0 {
				roomtmp.Chairs[b].Player.PlayerMoney = 0
			} else {
				roomtmp.Chairs[b].Player.PlayerMoney = tmpscore
			}
			dataSettlment.Succs[index] = true
			roomtmp.Chairs[index].Player.PlayerMoney += multiple * roomtmp.Chairs[index].Score //闲家赢钱
		}
	}

	//保存全局房间信息
	dataSettlment.RoomInfo = roomtmp
	//广播结算信息
	for id, _ := range datachan {
		val, _ := M.Get(id)
		val.(*Client).MsgSend(dataSettlment)
	}

	/*********游戏结束********/
	glog.Info("游戏结束!")
	//释放房间信息
	GRoomManagerPtr.RoomLock.RLock()
	delete(GRoomManagerPtr.GRoomData, strRoomID)
	GRoomManagerPtr.RoomLock.RUnlock()
}

6.游戏算法
(1).牌型检查算法

package AI

/*
黑桃:A 2 3 4 5 6 7 8 9 10 J  Q  K
     1                 10 11 12 13
红桃:                  23 24 25 26
方块:                  36 37 38 39
梅花:                  49 50 51 52
*/
import (
	"gameInfo"

	"github.com/golang/glog"
)

/*const pokerType{
	niu=0//无牛
	noniu=1//有牛
	bomb=2//炸弹
	fives=3//五小
}*/
//type PlayerInfo struct {
//	ID          string
//	playerName  string
//	playerMoney int
//}
//type CombineCardsInfo struct {
//	user          PlayerInfo
//	pokerType     int
//	niuCardsInfo  []int
//	lastCardsInfo []int
//}

//判断是否是炸弹
func checkIsbomb(c *gameInfo.PersonalPoker) bool {
	t := c.NiuCardsInfo[0]
	for i := 1; i < len(c.NiuCardsInfo); i++ {
		if t != c.NiuCardsInfo[i] {
			glog.Error("Bomb:PokerTyper Error!")
			return false
		}
	}
	glog.Info("Bomb:Checked passed!")
	return true
}

//判断是否是五小
func checkIsfives(c *gameInfo.PersonalPoker) bool {
	sum, n := 0, 0
	for _, v := range c.NiuCardsInfo {
		n = v % 13
		if n >= 10 || n == 0 {
			sum += 10
		} else {
			sum += n
		}
	}
	if sum > 10 && c.PokerType == 0 {
		glog.Info("noNiu:check passed!")
		return true
	}
	if sum <= 10 && c.PokerType == 14 {
		glog.Info("fivesmall:check passed!")
		return true
	}
	glog.Error("fivesmall or noNiu:PokerTyper Error!")
	return false

}

//判断是否是牛
func checkIsniu(c *gameInfo.PersonalPoker) bool {
	sum1, sum2, n := 0, 0, 0
	isGniu := true //金牛
	isSniu := true //银牛
	for _, v := range c.NiuCardsInfo {
		n = v % 13
		if n < 10 && n > 0 {
			sum1 += n
		} else {
			sum1 += 10
		}
		if _, ok := sniuSet[v]; !ok {
			isSniu = false
			isGniu = false
		}
		if _, ok := gniuSet[v]; !ok && isSniu {
			isGniu = false
		}
	}

	for _, v := range c.LastCardsInfo {
		n = v % 13
		if n < 10 && n > 0 {
			sum2 += n
		} else {
			sum2 += 10
		}
		if _, ok := sniuSet[v]; !ok {
			isSniu = false
			isGniu = false
		}
		if _, ok := gniuSet[v]; !ok && isSniu {
			isGniu = false
		}
	}
	if isGniu && c.PokerType == 12 {
		glog.Info("gniu:check passed!")
		return true
	}
	if isSniu && c.PokerType == 11 {
		glog.Info("sniu:check passed!")
		return true
	}
	if sum1%10 == 0 && c.PokerType == sum2%10 {
		glog.Info("niu %v:check passed!", c.PokerType)
		return true
	}
	if sum1%10 == 0 && c.PokerType == 10 && 0 == sum2%10 {
		glog.Info("niuniu:check passed!")
		return true
	}
	glog.Error("ISNiu:PokerTyper Error!")
	return false
}

func CheckType(c *gameInfo.PersonalPoker) bool {
	defer glog.Flush()
	switch c.PokerType {
	case 0, 14: // 无牛 和 五小
		return checkIsfives(c)
	case 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12: //牛1到牛9 + 牛牛 +银牛 +金牛
		return checkIsniu(c)
	case 13: //炸弹
		return checkIsbomb(c)
	default:
		glog.Error("%v:pokerType is illegal!", c.ID)
	}

	return false
}

(2)最大牌型算法

package AI

import (
	"gameInfo"
	"sort"

	"github.com/golang/glog"
)

/*
黑桃:A 2 3 4 5 6 7 8 9 10 J  Q  K
     1 2                10 11 12 13
红桃:  15                23 24 25 26
方块:  28               36 37 38 39
梅花:  41               49 50 51 52
*/
//const (             倍数
//	noniu  = 0 //无牛  1倍
//	niu1   = 1 //牛一  1
//	niu2   = 2        1
//	niu3   = 3        1
//	niu4   = 4        1
//	niu5   = 5        1
//	niu6   = 6        1
//	niu7   = 7        2
//	niu8   = 8
//	niu9   = 9
//	niuniu = 10 //牛牛   3
//	sniu   = 11 //银牛   4
//	gniu   = 12 //金牛   5
//	bomb   = 13 //炸弹   6
//	fives  = 14 //五小   10
//)

//倍数计算函数
func calmultiple(pokertype int) int {
	//1倍
	if pokertype >= 0 && pokertype <= 6 {
		return 1
	}
	//2倍
	if pokertype >= 7 && pokertype <= 9 {
		return 2
	}
	//3-6倍
	if pokertype >= 10 && pokertype <= 13 {
		return pokertype - 7
	}
	//10倍
	if pokertype == 14 {
		return 10
	}
	//牌型错误
	glog.Error("Pokertype Error!")
	glog.Flush()
	return -1

}

//比较计算函数
func Settlement(banker *gameInfo.PersonalPoker, player *gameInfo.PersonalPoker) (bool, int) {
	if banker.PokerType > player.PokerType {
		/*庄家大于闲家*/
		return true, calmultiple(banker.PokerType)
	} else if banker.PokerType < player.PokerType {
		/*闲家大于庄家*/
		return false, calmultiple(player.PokerType)
	} else {
		/*牌型一致时*/

		if (banker.PokerType >= 1 && banker.PokerType <= 9) || banker.PokerType == 14 {
			return true, calmultiple(banker.PokerType)
		} //牛数相同或者五小 庄家赢

		if banker.PokerType == 13 {
			if compare(banker.NiuCardsInfo, player.NiuCardsInfo) {
				return true, calmultiple(banker.PokerType)
			} else {
				return false, calmultiple(player.PokerType)
			}
		} //同为炸弹 比较炸弹牌

		if compare(banker.Cards, player.Cards) {
			return true, calmultiple(banker.PokerType)
		} else {
			return false, calmultiple(player.PokerType)
		} //其他 比较最大单张大小

	}

}

//相同牌型比较函数
func compare(banker []int, player []int) bool {

	for i := 0; i < len(banker); i++ {
		temp1 := banker[i] % 13
		temp2 := player[i] % 13
		if temp1 == 0 {
			temp1 = 13
		}
		if temp2 == 0 {
			temp2 = 13
		}
		if temp1 > temp2 {
			return true
		} else if temp1 < temp2 {
			return false
		}
	}
	return true
}

//最优牌型函数
var (
	gniuSet = map[int]bool{
		11: true, 12: true, 13: true, 24: true, 25: true, 26: true,
		37: true, 38: true, 39: true, 50: true, 51: true, 52: true,
	}
	sniuSet = map[int]bool{
		11: true, 12: true, 13: true, 24: true, 25: true, 26: true,
		37: true, 38: true, 39: true, 50: true, 51: true, 52: true,
		10: true, 23: true, 36: true, 49: true,
	}
)

func judgeNiu(pokerTen []int, poker []int, sum int) ([]int, []int, int) {
	maxNiu := 0 //无牛
	niuCardsInfo := make([]int, 3)
	lastCardsInfo := make([]int, 0)
	//转化为找pokerTen的三数之和为10
	n := len(pokerTen)
	for i := 0; i < n; i++ {
		for j := i + 1; j < n; j++ {
			for k := j + 1; k < n; k++ {
				niuSum := pokerTen[i] + pokerTen[j] + pokerTen[k]
				if niuSum%10 == 0 {

					tempNiu := (sum - niuSum) % 10

					if tempNiu == 0 {
						tempNiu = 10
					} //牛牛
					//判断当前牛是否最大
					if tempNiu > maxNiu {
						niuCardsInfo[0] = poker[i]
						niuCardsInfo[1] = poker[j]
						niuCardsInfo[2] = poker[k]
						lastCardsInfo = []int{}
						lastCardsInfo = append(lastCardsInfo, poker[:i]...)
						lastCardsInfo = append(lastCardsInfo, poker[i+1:j]...)
						lastCardsInfo = append(lastCardsInfo, poker[j+1:k]...)
						lastCardsInfo = append(lastCardsInfo, poker[k+1:]...)
						maxNiu = tempNiu
					}
					/*else if tempNiu == maxNiu { //当前牛和最大牛相同
						tNiuCardsInfo := make([]int, 3)
						tNiuCardsInfo[0] = poker[i]
						tNiuCardsInfo[1] = poker[j]
						tNiuCardsInfo[2] = poker[k]
						tlastCardsInfo := []int{}
						tlastCardsInfo = append(tlastCardsInfo, poker[:i]...)
						tlastCardsInfo = append(tlastCardsInfo, poker[i+1:j]...)
						tlastCardsInfo = append(tlastCardsInfo, poker[j+1:k]...)
						tlastCardsInfo = append(tlastCardsInfo, poker[k+1:]...)
						niuCardsInfo, lastCardsInfo = compare(tNiuCardsInfo, tlastCardsInfo, niuCardsInfo, lastCardsInfo)
					}*/

				}
			}
		}
	}

	if maxNiu == 0 {
		return poker, lastCardsInfo, 0
	}

	return niuCardsInfo, lastCardsInfo, maxNiu
}

func MaxType(poker []int) ([]int, []int, int) {
	//排序函数
	sort.Slice(poker, func(i, j int) bool {
		i1, j1 := poker[i]%13, poker[j]%13
		if i1 == 0 {
			return true
		} else if j1 == 0 {
			return false
		} else {
			return i1 > j1
		}
	})

	pokerTimes := make(map[int]int) //出现次数统计
	pokerTen := make([]int, 5)      //对10取余
	sum := 0                        //统计总数
	isGniu := true                  //金牛判断位
	isSniu := true                  //银牛判断位
	for i := 0; i < len(poker); i++ {
		if poker[i]%13 > 0 && poker[i]%13 < 10 {
			sum += poker[i] % 13
			pokerTen[i] = poker[i] % 13
			pokerTimes[poker[i]%13]++
		} else {
			sum += 10
			pokerTen[i] = 10
			if poker[i]%13 == 0 {
				pokerTimes[13]++
			} else {
				pokerTimes[poker[i]%13]++
			}

		}

		if _, ok := sniuSet[poker[i]]; !ok {
			isSniu = false
			isGniu = false
		}
		if _, ok := gniuSet[poker[i]]; !ok && isSniu {
			isGniu = false
		}

	}

	niuCardsInfo := make([]int, 0)
	lastCardsInfo := make([]int, 0)

	//返回五小
	if sum <= 10 {
		niuCardsInfo = poker
		return niuCardsInfo, lastCardsInfo, 14
	}

	//返回炸弹
	for k, v := range pokerTimes {
		if v == 4 {
			real := 0
			if poker[0]%13 == 0 {
				real = 13
			} else {
				real = poker[0] % 13
			}
			if k == real {
				niuCardsInfo = poker[0:4]
				lastCardsInfo = poker[4:]
			} else {
				niuCardsInfo = poker[1:]
				lastCardsInfo = poker[0:1]
			}
			return niuCardsInfo, lastCardsInfo, 13
		}
	}

	//返回金牛
	if isGniu {
		niuCardsInfo = poker[0:3]
		lastCardsInfo = poker[3:]
		return niuCardsInfo, lastCardsInfo, 12
	}

	//返回银牛
	if isSniu {
		niuCardsInfo = poker[0:3]
		lastCardsInfo = poker[3:]
		return niuCardsInfo, lastCardsInfo, 11
	}
	return judgeNiu(pokerTen, poker, sum)

	//判断几牛
}

(3)洗牌算法

package AI

import (
	"math/rand"
	"time"
)

func calculate(arr []int) {
	rand.Seed(time.Now().Unix())
	for i := len(arr) - 1; i >= 0; i-- {
		/* 初始化随机数发生器 */
		j := rand.Int() % (i + 1)
		arr[i], arr[j] = arr[j], arr[i]
	}
}
func Shuffle(playerNum int) [][]int {
	pokerSet := make([]int, 52)
	playerPokerSets := make([][]int, playerNum)
	for i := 0; i < playerNum; i++ {
		playerPokerSets[i] = make([]int, 5)
	}

	for i := 0; i < 52; i++ {
		pokerSet[i] = i + 1
	}
	//洗牌
	calculate(pokerSet)
	//fmt.Println(pokerSet)
	//发牌
	index := 0
	for i := 0; i < playerNum; i++ {
		for j := 0; j < 5; j++ {
			playerPokerSets[i][j] = pokerSet[index]
			index++
		}
	}

	return playerPokerSets
}

客户端采用了cocos creator实现,代码就下次更吧!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值