游戏规则:
游戏用牌为除大小王以外的一副牌,共计52张。玩家人数为固定4人(初版人数),每人随机发5张牌。
- 牌型说明
牌型 赔率(闲家下注) 说明
无牛 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。 - 牌型比较
牌型:五小牛 > 炸弹 > 金牛 > 银牛 > 牛牛 > 有牛 > 无牛。
单张:K > Q > J > 10 > 9 > 8 > 7 > 6 > 5 > 4 > 3 > 2 > A。
花色:黑桃 > 红桃 > 梅花 > 方块(不采用)。
同牌型间的比较:
无牛:比最大单张大小。
有牛:牛9 > 牛8 > ~ > 牛2 > 牛1;牛数相同庄家赢(庄吃闲)。
牛牛:比最大单张大小。
银牛(四花):比最大单张大小。
金牛(五花):比最大单张大小。
炸弹:比炸弹牌大小。
五小:庄家赢(庄吃闲)。
- 定庄押注
第一盘随机选择庄家,若游戏过程中没有出现“牛九”及以上牌型则继续由上盘玩家担任庄家,若出现牛九牌型则由获得牛九牌型的玩家下盘担任庄家。若在同一盘中有多名玩家出现牛9牌型则再进行大小比较,由最大牌型的玩家下盘担任庄家。所有的大小比较过程均是庄家和闲家比较,闲家和闲家之间不进行比较。 - 规则补充
特殊:当庄家与闲家牌型相同需要比单张时,系统自动比较两家手中最大的一张牌,谁大谁赢;如果两家最大的牌点数相同,则继续比较两家第二大的牌,同理直到第五张比完(不比花色);如果两家所有牌点数相同,默认庄家赢。
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实现,代码就下次更吧!