Gogs 4 месяцев назад
Родитель
Сommit
5e441134c7

+ 106 - 0
bin/client_msg/common.proto

@@ -15,6 +15,112 @@ message ReqLogin {
   MsgError error = 4;
 }
 
+//进入大厅
+message EnterHall {
+  string userId = 1;
+}
+
+//离开大厅
+message LeaveHall {
+  string userId = 1;
+}
+
+//加入房间
+message ResJoinRoom {
+  string userId = 1;
+  string roomId = 2;
+  string gameId = 3;
+}
+
+//加入房间
+message ReqJoinRoom {
+  string userId = 1;
+  string roomId = 2;
+  string gameId = 3;
+}
+
+//玩法列表
+message PlayList {
+  string userId = 1;
+}
+
+//商城
+message Shop {
+  string userId = 1;
+}
+
+message ResGameInfo {
+  string gameId = 1;
+}
+message ReqGameInfo {
+  string gameId = 1;
+  string gameName = 2;
+  string gameStatus = 4;
+  string gameType = 5;
+  TeenPattiRoomList teenPattiRoomList = 6;
+}
+
+message TeenPattiRoomList {
+  repeated TeenPattiRoom teenPattiRoom = 1;
+}
+
+message TeenPattiRoom {
+  string boot = 1; //0.1
+  string minBuyin = 2; // 1
+  string chaalLimmit = 3; // 12.8
+  string potLimit = 4; //102.40
+  string totalPlayers = 5; // 3999
+  string roomLevel = 6; //low - mid - high
+  string roomId = 7;
+}
+
+// 通知客户端发牌
+message ReqDealCard {
+  int32 sitPos = 1;
+}
+
+// 通知客户端操作
+message ReqPlayerAction {
+  int32 sitPos = 1;
+}
+
+// 接到玩家操作
+message ResPlayerOptAction {
+  int32 sitPos = 1;
+  PlayerOpt playerOpt = 2;
+  string roomId = 3;
+  string gameId = 4;
+  string userId = 5;
+}
+
+// PlayerOptType 玩家操作类型
+enum PlayerOptType {
+    OPT_NONE        = 0;  // 无操作
+    OPT_LOOK_CARD   = 1;  // 看牌
+    OPT_DISCARD     = 2;  // 弃牌
+    OPT_CALL        = 3;  // 跟注
+    OPT_RAISE       = 4;  // 加注
+    OPT_DOUBLE      = 5;  // 加倍
+    OPT_FOLD        = 6;  // 放弃
+}
+
+message PlayerOpt {
+    PlayerOptType opt_type = 1;  // 操作类型
+    int32 bet_amount = 2;        // 下注金额
+    int64 timestamp = 3;         // 操作时间戳
+}
+
+message ReqRound {
+    // 回合数
+    int32 round = 1;
+    // 回合座位
+    int32 roundSitPos = 2;
+    // 回合操作
+    PlayerOpt playerOpt = 3;
+    // 用户id
+    string userId = 4;
+}
+
 //error
 message MsgError {
   int32 error_code = 1;

+ 8 - 0
bin/client_msg/msg.ts

@@ -3,4 +3,12 @@ export enum MsgID {
     Hello = 0,
     ReqLogin = 1,
     ResLogin = 2,
+    EnterHall = 3,
+    LeaveHall = 4,
+    ResJoinRoom = 5,
+    ReqJoinRoom = 6,
+    PlayList = 7,
+    Shop = 8,
+    ReqGameInfo = 9,
+    ResGameInfo = 10,
 }

+ 2 - 0
src/server/conf/conf.go

@@ -26,4 +26,6 @@ var (
 var (
 	MongoDBAddr = "mongodb://localhost:27017"
 	MongoDBName = "teen_patti"
+	RedisDBAddr = "localhost:6379"
+	MysqlDBAddr = "root:password@tcp(localhost:3306)/teen_patti?charset=utf8mb4&parseTime=True"
 )

+ 2 - 1
src/server/db/mysql/mysqlmgr.go

@@ -2,6 +2,7 @@ package mysqlmgr
 
 import (
 	"database/sql"
+	"server/conf"
 	"time"
 
 	"github.com/name5566/leaf/log"
@@ -20,7 +21,7 @@ func init() {
 // Connect 初始化数据库连接
 func Connect() {
 	var err error
-	db, err = sql.Open("mysql", "root:password@tcp(localhost:3306)/teen_patti?charset=utf8mb4&parseTime=True")
+	db, err = sql.Open("mysql", conf.MysqlDBAddr)
 	if err != nil {
 		panic(err)
 	}

+ 2 - 1
src/server/db/redis/redismgr.go

@@ -1,6 +1,7 @@
 package redismgr
 
 import (
+	"server/conf"
 	"time"
 
 	"github.com/gomodule/redigo/redis"
@@ -24,7 +25,7 @@ func Connect() {
 		Dial: func() (redis.Conn, error) {
 			return redis.Dial(
 				"tcp",
-				"localhost:6379",
+				conf.RedisDBAddr,
 				redis.DialPassword(""), // 如果有密码
 				redis.DialDatabase(0),  // 选择数据库
 			)

+ 19 - 0
src/server/events/events.go

@@ -0,0 +1,19 @@
+package events
+
+import "github.com/name5566/leaf/gate"
+
+type EventType int
+
+const (
+	EventTeenPattiGameInfo EventType = iota
+	EventJoinTeenPattiRoom
+	EventTeenPattiPlayerOptAction
+)
+
+type Event struct {
+	Type  EventType
+	Data  interface{}
+	Agent gate.Agent
+}
+
+var EventChan = make(chan Event, 100)

+ 1 - 0
src/server/game/7u7d/7u7d.go

@@ -0,0 +1 @@
+package u7d

+ 18 - 27
src/server/game/internal/chanrpc.go

@@ -11,27 +11,9 @@ import (
 func init() {
 	skeleton.RegisterChanRPC("NewAgent", rpcNewAgent)
 	skeleton.RegisterChanRPC("CloseAgent", rpcCloseAgent)
+	skeleton.RegisterChanRPC("handleAuth", handleAuth)
 }
 
-const (
-	ConnStatusUnauth = iota // 未认证
-	ConnStatusAuthed        // 已认证
-)
-
-// 未认证连接管理
-type PendingConn struct {
-	Agent      gate.Agent
-	CreateTime int64
-	Status     int
-}
-
-var (
-	// 未认证连接池
-	pendingConns = make(map[gate.Agent]*PendingConn)
-	// 已认证用户
-	authedUsers = make(map[string]gate.Agent) // key: userID
-)
-
 func rpcNewAgent(args []interface{}) {
 	agent := args[0].(gate.Agent)
 	// 将新连接放入未认证池
@@ -61,23 +43,32 @@ func rpcCloseAgent(args []interface{}) {
 
 // 处理认证消息
 func handleAuth(args []interface{}) {
+	// 收到的登录消息
+	m := args[0].(*msg.ReqLogin)
+	// 消息的发送者
+	a := args[1].(gate.Agent)
 
-	m := args[0].(*msg.ResLogin)
-	userID := m.UserId
-	agent := args[1].(gate.Agent)
+	log.Debug("handleAuth: nikeName=%v, userId=%v", m.NikeName, m.UserId)
 
 	// 找到未认证连接
-	if pending, exists := pendingConns[agent]; exists {
+	if pending, exists := pendingConns[a]; exists {
 		// 认证成功,移动到已认证池
-		delete(pendingConns, agent)
-		authedUsers[userID] = agent
+		delete(pendingConns, a)
+		authedUsers[m.UserId] = a
 		pending.Status = ConnStatusAuthed
 
 		// 处理可能的重复登录
-		if oldAgent, exists := authedUsers[userID]; exists && oldAgent != agent {
+		if oldAgent, exists := authedUsers[m.UserId]; exists && oldAgent != a {
 			oldAgent.Close()
-			delete(authedUsers, userID)
+			delete(authedUsers, m.UserId)
 		}
+
+		// 发送登录成功响应
+		a.WriteMsg(&msg.ReqLogin{
+			NikeName:   m.NikeName,
+			UserId:     m.UserId,
+			GameStatus: "online",
+		})
 	}
 }
 

+ 18 - 2
src/server/game/internal/handler.go

@@ -2,6 +2,7 @@ package internal
 
 import (
 	"reflect"
+	"server/events"
 	"server/msg"
 
 	"github.com/name5566/leaf/gate"
@@ -10,13 +11,28 @@ import (
 
 func init() {
 	// 向当前模块(game 模块)注册 Hello 消息的消息处理函数 handleHello
-	handler(&msg.Hello{}, handleHello)
+	Handler(&msg.Hello{}, handleHello)
+	Handler(&msg.ResPlayerOptAction{}, handlePlayerOptAction)
 }
 
-func handler(m interface{}, h interface{}) {
+func Handler(m interface{}, h interface{}) {
 	skeleton.RegisterChanRPC(reflect.TypeOf(m), h)
 }
 
+func handlePlayerOptAction(args []interface{}) {
+	m := args[0].(*msg.ResPlayerOptAction)
+	a := args[1].(gate.Agent)
+	log.Debug("player opt action %v", m.PlayerOpt)
+	switch m.GameId {
+	case "teen_patti":
+		events.EventChan <- events.Event{
+			Type:  events.EventTeenPattiPlayerOptAction,
+			Data:  m,
+			Agent: a,
+		}
+	}
+}
+
 func handleHello(args []interface{}) {
 	// 收到的 Hello 消息
 	m := args[0].(*msg.Hello)

+ 35 - 1
src/server/game/internal/module.go

@@ -1,8 +1,29 @@
 package internal
 
 import (
-	"github.com/name5566/leaf/module"
 	"server/base"
+
+	"github.com/name5566/leaf/gate"
+	"github.com/name5566/leaf/module"
+)
+
+const (
+	ConnStatusUnauth = iota // 未认证
+	ConnStatusAuthed        // 已认证
+)
+
+// 未认证连接管理
+type PendingConn struct {
+	Agent      gate.Agent
+	CreateTime int64
+	Status     int
+}
+
+var (
+	// 未认证连接池
+	pendingConns = make(map[gate.Agent]*PendingConn)
+	// 已认证用户
+	authedUsers = make(map[string]gate.Agent) // key: userID
 )
 
 var (
@@ -21,3 +42,16 @@ func (m *Module) OnInit() {
 func (m *Module) OnDestroy() {
 
 }
+
+func GetAgentByUserID(userID string) gate.Agent {
+	return authedUsers[userID]
+}
+
+func GetUserIDByAgent(agent gate.Agent) string {
+	for k, v := range authedUsers {
+		if v == agent {
+			return k
+		}
+	}
+	return ""
+}

+ 196 - 0
src/server/game/room/room.go

@@ -0,0 +1,196 @@
+package room
+
+import (
+	"github.com/name5566/leaf/gate"
+)
+
+type Round struct {
+	// 回合数
+	Round int
+	// 回合座位
+	RoundSitPos int
+	// 回合下注
+	RoundBet int
+	// 思考时间
+	RoundThinkTime int
+	// 回合结束时间
+	RoundEndTime int64
+	// 操作看牌
+	OpLookCard bool
+	// 操作下注
+	OpBet bool
+	// 操作弃牌
+	OpPack bool
+	// 操作开牌
+	OpOpenCard bool
+}
+
+// 定一个类为每局游戏
+type GameRound struct {
+	//回合列表
+	Rounds []Round
+}
+
+// RoomStatus 房间状态
+const (
+	RoomStatusWaiting = 0 // 等待开局
+	RoomStatusDealing = 1 // 发牌中
+	RoomStatusPlaying = 2 // 游戏进行中
+)
+
+type Room struct {
+	Id      string
+	Players []*Player
+	Status  int
+	// 当前轮次
+	Round int
+	// 当前回合的座位
+	RoundSitPos int
+	// 每局游戏
+	GameRound GameRound
+	// 牌堆
+	CardDeck []Card
+}
+
+// 定一个扑克牌,从1到52
+type Card struct {
+	// 花色
+	Color int
+	// 点数
+	Point int
+}
+
+type Player struct {
+	Id       string
+	Agent    gate.Agent
+	IsRobot  bool
+	UserData interface{}
+	//手牌
+	HandCards []Card
+	// 座位
+	SitPos int
+	// 等待发牌
+	WaitCard bool
+	// 是否已看牌
+	IsLookCard bool
+	// 是否已弃牌
+	IsPacked bool
+	// 是否开牌
+	IsOpenCard bool
+	// 是否已结算
+	IsSettle bool
+	// 是否已离开
+	IsLeave bool
+	// 是否已准备
+	IsReady bool
+	// 是否是庄家
+	IsDealer bool
+}
+
+// 获取玩家ID
+func (p *Player) GetUserId() string {
+	return p.Id
+}
+
+func (r *Room) AddPlayer(p *Player) {
+	r.Players = append(r.Players, p)
+}
+
+func (r *Room) RemovePlayer(p *Player) {
+	for i, player := range r.Players {
+		if player.Id == p.Id {
+			r.Players = append(r.Players[:i], r.Players[i+1:]...)
+			break
+		}
+	}
+}
+
+func (r *Room) RemoveAllRobot() {
+	for i, player := range r.Players {
+		if player.IsRobot {
+			r.Players = append(r.Players[:i], r.Players[i+1:]...)
+		}
+	}
+}
+
+func (r *Room) GetPlayer(id string) *Player {
+	for _, player := range r.Players {
+		if player.Id == id {
+			return player
+		}
+	}
+	return nil
+}
+
+// 获取庄家的玩家
+func (r *Room) GetDealerPlayer() *Player {
+	for _, player := range r.Players {
+		if player.IsDealer {
+			return player
+		}
+	}
+	return nil
+}
+
+// 设置指定玩家为庄家,并设置其他玩家为非庄家
+func (r *Room) SetDealerPlayer(p *Player) {
+	for _, player := range r.Players {
+		player.IsDealer = false
+	}
+	p.IsDealer = true
+}
+
+// 获取WaitCard是false的所有玩家
+func (r *Room) GetWaitCardPlayers() []*Player {
+	players := make([]*Player, 0)
+	for _, player := range r.Players {
+		if !player.WaitCard {
+			players = append(players, player)
+		}
+	}
+	return players
+}
+
+// sitPos 是当前玩家的座位号,GetWaitCardPlayers  从sitPos开始 顺时针方向把所有玩家返回
+// 从指定座位号开始顺时针获取所有玩家
+func (room *Room) GetPlayersBySitPos(startSitPos int) []*Player {
+	players := make([]*Player, 0)
+	maxSitPos := 5 // 假设最大6个座位
+
+	// 先添加大于等于 startSitPos 的座位
+	for sitPos := startSitPos; sitPos < maxSitPos; sitPos++ {
+		for _, player := range room.Players {
+			if player.SitPos == sitPos && !player.IsLeave {
+				players = append(players, player)
+				break
+			}
+		}
+	}
+
+	// 再添加小于 startSitPos 的座位
+	for sitPos := 0; sitPos < startSitPos; sitPos++ {
+		for _, player := range room.Players {
+			if player.SitPos == sitPos && !player.IsLeave {
+				players = append(players, player)
+				break
+			}
+		}
+	}
+
+	return players
+}
+
+// 根据座位号获取玩家
+func (r *Room) GetPlayerBySitPos(sitPos int) *Player {
+	for _, player := range r.Players {
+		if player.SitPos == sitPos {
+			return player
+		}
+	}
+	return nil
+}
+
+// 增加玩家操作到当前回合
+func (r *Room) AddPlayerOpt(round Round) {
+	r.GameRound.Rounds = append(r.GameRound.Rounds, round)
+}

+ 55 - 0
src/server/game/teen/event.go

@@ -0,0 +1,55 @@
+package teen
+
+import (
+	"server/events"
+	"server/game/room"
+	"server/msg"
+	"server/user"
+
+	"github.com/name5566/leaf/log"
+)
+
+func handleEvents() {
+	for event := range events.EventChan {
+		switch event.Type {
+		case events.EventTeenPattiGameInfo:
+			event.Agent.WriteMsg(&msg.ReqGameInfo{
+				GameId:            "teen_patti",
+				TeenPattiRoomList: convertToMsgRoomList(GameConfig.RoomList),
+			})
+
+		case events.EventJoinTeenPattiRoom:
+			m := event.Data.(*msg.ResJoinRoom)
+			userData := event.Agent.UserData().(*user.UserData)
+			if userData.Teen_Patti_Room == nil {
+				userData.Teen_Patti_Room = &room.Room{
+					Id:      "teen_patti",
+					Players: make([]*room.Player, 0),
+					Status:  1,
+				}
+			}
+			userData.Teen_Patti_Room.Players = append(userData.Teen_Patti_Room.Players, &room.Player{
+				Id:       m.UserId,
+				Agent:    event.Agent,
+				IsRobot:  false,
+				UserData: userData,
+				SitPos:   0,
+			})
+			go startGame(m.UserId, m.RoomId, event.Agent)
+		case events.EventTeenPattiPlayerOptAction:
+			// m := event.Data.(*msg.ResPlayerOptAction)
+			m := event.Data.(*msg.ResPlayerOptAction)
+			userData := event.Agent.UserData().(*user.UserData)
+			if userData.Teen_Patti_Room != nil {
+				recvPlayerOptAction(userData.Teen_Patti_Room, int(m.SitPos), m.PlayerOpt.OptType)
+			} else {
+				log.Error("userData.Teen_Patti_Room is nil")
+			}
+		}
+	}
+}
+
+// 通知所有玩家当前回合操作
+func notifyAllPlayerRoundOpt(room *room.Room) {
+
+}

+ 73 - 0
src/server/game/teen/opt.go

@@ -0,0 +1,73 @@
+package teen
+
+import (
+	"fmt"
+	"server/game/room"
+	"server/msg"
+)
+
+// 看牌
+func lookCard(room *room.Room, sitPos int) {
+	SendRoundMsgToAll(room, &msg.ReqRound{
+		Round:       int32(room.Round),
+		RoundSitPos: int32(sitPos),
+		PlayerOpt:   &msg.PlayerOpt{OptType: msg.PlayerOptType_OPT_LOOK_CARD},
+		UserId:      room.GetPlayerBySitPos(sitPos).GetUserId(),
+	})
+}
+
+// 弃牌
+func discardCard(room *room.Room, sitPos int) {
+	SendRoundMsgToAll(room, &msg.ReqRound{
+		Round:       int32(room.Round),
+		RoundSitPos: int32(sitPos),
+		PlayerOpt:   &msg.PlayerOpt{OptType: msg.PlayerOptType_OPT_FOLD},
+		UserId:      room.GetPlayerBySitPos(sitPos).GetUserId(),
+	})
+}
+
+// 跟注
+func call(room *room.Room, sitPos int) {
+	SendRoundMsgToAll(room, &msg.ReqRound{
+		Round:       int32(room.Round),
+		RoundSitPos: int32(sitPos),
+		PlayerOpt:   &msg.PlayerOpt{OptType: msg.PlayerOptType_OPT_CALL},
+		UserId:      room.GetPlayerBySitPos(sitPos).GetUserId(),
+	})
+}
+
+// 加注
+func raise(room *room.Room, sitPos int) {
+	SendRoundMsgToAll(room, &msg.ReqRound{
+		Round:       int32(room.Round),
+		RoundSitPos: int32(sitPos),
+		PlayerOpt:   &msg.PlayerOpt{OptType: msg.PlayerOptType_OPT_RAISE},
+		UserId:      room.GetPlayerBySitPos(sitPos).GetUserId(),
+	})
+}
+
+// 加倍
+func double(room *room.Room, sitPos int) {
+	SendRoundMsgToAll(room, &msg.ReqRound{
+		Round:       int32(room.Round),
+		RoundSitPos: int32(sitPos),
+		PlayerOpt:   &msg.PlayerOpt{OptType: msg.PlayerOptType_OPT_DOUBLE},
+		UserId:      room.GetPlayerBySitPos(sitPos).GetUserId(),
+	})
+}
+
+// 放弃
+func fold(room *room.Room, sitPos int) {
+	SendRoundMsgToAll(room, &msg.ReqRound{
+		Round:       int32(room.Round),
+		RoundSitPos: int32(sitPos),
+		PlayerOpt:   &msg.PlayerOpt{OptType: msg.PlayerOptType_OPT_FOLD},
+		UserId:      room.GetPlayerBySitPos(sitPos).GetUserId(),
+	})
+}
+
+// 取消操作超时
+func cancelOptTimeout(player *room.Player, sitPos int) {
+	eventID := fmt.Sprintf("player_%s_opt_%s", player.Id, sitPos)
+	timerMgr.RemoveEvent(eventID)
+}

+ 46 - 0
src/server/game/teen/robot.go

@@ -0,0 +1,46 @@
+package teen
+
+import (
+	"encoding/json"
+	"math/rand"
+	"os"
+
+	"github.com/name5566/leaf/log"
+)
+
+type RobotNames struct {
+	Names []string `json:"name"`
+}
+
+// 随即印度人名字
+func randomIndianName() string {
+	data, err := os.ReadFile("../../../bin/gamedata/robot_name.json")
+	if err != nil {
+		log.Fatal("%v", err)
+	}
+	var Names RobotNames
+	err = json.Unmarshal(data, &Names)
+	if err != nil {
+		log.Fatal("%v", err)
+	}
+	return Names.Names[rand.Intn(len(Names.Names))]
+}
+
+type RobotHeadImages struct {
+	HeadImages []string `json:"head_image"`
+}
+
+// 随即印度人头像
+func randomIndianHeadImage() string {
+	data, err := os.ReadFile("../../../bin/gamedata/robot_head_image.json")
+	if err != nil {
+		log.Fatal("%v", err)
+	}
+
+	var images RobotHeadImages
+	err = json.Unmarshal(data, &images)
+	if err != nil {
+		log.Fatal("%v", err)
+	}
+	return images.HeadImages[rand.Intn(len(images.HeadImages))]
+}

+ 111 - 0
src/server/game/teen/round.go

@@ -0,0 +1,111 @@
+package teen
+
+import (
+	"fmt"
+	"server/game"
+	"server/game/room"
+	"server/msg"
+	"time"
+)
+
+// 创建定时管理器
+var timerMgr = NewTimerManager()
+
+// 开始发牌,先给庄家发牌
+func startDealCard(r *room.Room) {
+	initCardDeck(r)
+	shuffleCardDeck(r.CardDeck)
+	if r.Status == room.RoomStatusWaiting {
+		r.Status = room.RoomStatusDealing
+		dealer := r.GetDealerPlayer()
+		// 找到庄家下家依次发牌
+		for _, player := range r.GetPlayersBySitPos(dealer.SitPos) {
+			player.HandCards = getThreeCards(r)
+			player.WaitCard = false
+			game.Module.Skeleton.AfterFunc(time.Second*1, func() {
+				notifyPlayerDealCard(r, player.SitPos)
+				// 如果等待的人数小于等于0,则开始通知庄家操作
+				if len(r.GetWaitCardPlayers()) <= 0 {
+					game.Module.Skeleton.Go(func() {
+						notifyPlayerAction(r, dealer.SitPos)
+					}, func() {
+						r.Status = room.RoomStatusPlaying
+					})
+				}
+
+			})
+		}
+	}
+}
+
+// 返回牌堆里3张牌,并从牌堆中移除
+func getThreeCards(room *room.Room) []room.Card {
+	// 获取前三张牌
+	threeCards := room.CardDeck[:3]
+	// 移除这三张牌(保留剩余的牌)
+	room.CardDeck = room.CardDeck[3:]
+	// 返回这三张牌
+	return threeCards
+}
+
+// 通知玩家发牌
+func notifyPlayerDealCard(room *room.Room, sitPos int) {
+	//遍历所有玩家
+	for _, player := range room.Players {
+		if player.Agent != nil {
+			player.Agent.WriteMsg(&msg.ReqDealCard{SitPos: int32(sitPos)})
+		}
+	}
+}
+
+// 通知玩家操作
+func notifyPlayerAction(room *room.Room, sitPos int) {
+	player := room.GetPlayerBySitPos(sitPos)
+	if player.Agent != nil {
+		player.Agent.WriteMsg(&msg.ReqPlayerAction{SitPos: int32(sitPos)})
+	}
+	//玩家超过思考时间,则算起牌,否则算操作
+	addPlayerOptTimeout(room, player, time.Second*time.Duration(GameConfig.ThinkTime))
+	// game.Module.Skeleton.AfterFunc(time.Second*time.Duration(GameConfig.ThinkTime), func() {
+
+	// })
+}
+
+// 添加玩家操作超时事件
+func addPlayerOptTimeout(room *room.Room, player *room.Player, timeout time.Duration) {
+	eventID := fmt.Sprintf("player_%s_opt_%s", player.Id, player.SitPos)
+	timerMgr.AddEvent(eventID, timeout, func() {
+		// 超时处理逻辑,弃牌
+		discardCard(room, player.SitPos)
+	})
+}
+
+// 收到玩家操作
+func recvPlayerOptAction(room *room.Room, sitPos int, optType msg.PlayerOptType) {
+	player := room.GetPlayerBySitPos(sitPos)
+	if player.Agent != nil {
+		cancelOptTimeout(player, sitPos)
+		if optType == msg.PlayerOptType_OPT_LOOK_CARD { // 看牌
+			lookCard(room, sitPos)
+		} else if optType == msg.PlayerOptType_OPT_DISCARD { // 弃牌
+			discardCard(room, sitPos)
+		} else if optType == msg.PlayerOptType_OPT_CALL { // 跟注
+			call(room, sitPos)
+		} else if optType == msg.PlayerOptType_OPT_RAISE { // 加注
+			raise(room, sitPos)
+		} else if optType == msg.PlayerOptType_OPT_DOUBLE { // 加倍
+			double(room, sitPos)
+		} else if optType == msg.PlayerOptType_OPT_FOLD { // 放弃
+			fold(room, sitPos)
+		}
+	}
+}
+
+// 发送消息给所有玩家
+func SendRoundMsgToAll(room *room.Room, msg *msg.ReqRound) {
+	for _, player := range room.Players {
+		if player.Agent != nil {
+			player.Agent.WriteMsg(msg)
+		}
+	}
+}

+ 20 - 0
src/server/game/teen/sit.go

@@ -0,0 +1,20 @@
+package teen
+
+import (
+	"math/rand"
+	"server/game/room"
+)
+
+// 随机选一个一个玩家当庄家
+func randomDealer(room *room.Room) {
+	room.SetDealerPlayer(room.Players[rand.Intn(len(room.Players))])
+}
+
+// 一共4个座位,指定需要的座位数量,且不重复的随即返回
+func randomSitPos(room *room.Room, num int) []int {
+	sitPos := make([]int, 0)
+	for i := 0; i < num; i++ {
+		sitPos = append(sitPos, rand.Intn(4))
+	}
+	return sitPos
+}

+ 165 - 0
src/server/game/teen/teen.go

@@ -0,0 +1,165 @@
+package teen
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"math/rand"
+	"os"
+	"server/game/room"
+	"server/msg"
+	"server/user"
+
+	"github.com/name5566/leaf/gate"
+)
+
+type TeenPattiRoom struct {
+	Boot         string `json:"boot"`
+	MinBuyin     string `json:"minBuyin"`
+	ChaalLimmit  string `json:"chaalLimmit"`
+	PotLimit     string `json:"potLimit"`
+	TotalPlayers string `json:"totalPlayers"`
+	RoomLevel    string `json:"roomLevel"`
+	RoomId       string `json:"roomId"`
+}
+
+var GameConfig struct {
+	GameId    int             `json:"game_id"`
+	Name      string          `json:"name"`
+	Status    int             `json:"status"`
+	RoomList  []TeenPattiRoom `json:"room_list"`
+	ThinkTime int             `json:"think_time"`
+}
+
+// var Teen_Patti_Room = &room.Room{
+// 	Id:      "teen_patti",
+// 	Players: make([]*player.Player, 0),
+// 	Status:  1,
+// }
+
+func init() {
+	data, err := os.ReadFile("../../../bin/gamedata/teen_patti_conf.json")
+	if err != nil {
+		log.Fatal("%v", err)
+	}
+	err = json.Unmarshal(data, &GameConfig)
+	if err != nil {
+		log.Fatal("%v", err)
+	}
+
+	go handleEvents()
+}
+
+func createRoom(userId string, roomId string, agent gate.Agent) {
+	//随即创建 1-3 个人机陪这个userId
+	userData := agent.UserData().(*user.UserData)
+	random := rand.Intn(3) + 1
+	//先移除掉除自己之外的机器人
+	userData.Teen_Patti_Room.RemoveAllRobot()
+	sitPos := randomSitPos(userData.Teen_Patti_Room, random)
+	for i := 0; i < random; i++ {
+		userData.Teen_Patti_Room.Players = append(userData.Teen_Patti_Room.Players, &room.Player{
+			Id:       fmt.Sprintf("%d", i),
+			Agent:    nil,
+			IsRobot:  true,
+			UserData: createRobotUserData(),
+			SitPos:   sitPos[i],
+		})
+	}
+	randomDealer(userData.Teen_Patti_Room)
+}
+
+// 创建人机的UserData
+func createRobotUserData() *user.UserData {
+	return &user.UserData{
+		Id:              "robot",
+		Status:          1,
+		Teen_Patti_Room: nil,
+		Head_Image:      randomIndianHeadImage(),
+		Nickname:        randomIndianName(),
+	}
+}
+
+func startGame(userId string, roomId string, agent gate.Agent) {
+	// agent := Teen_Patti_Room.GetPlayer(userId).Agent
+	createRoom(userId, roomId, agent)
+	agent.WriteMsg(&msg.ReqJoinRoom{
+		UserId: userId,
+		RoomId: roomId,
+		GameId: "teen_patti",
+	})
+}
+
+func convertToMsgRoomList(rooms []TeenPattiRoom) *msg.TeenPattiRoomList {
+	msgRooms := make([]*msg.TeenPattiRoom, len(rooms))
+	for i, room := range rooms {
+		msgRooms[i] = &msg.TeenPattiRoom{
+			Boot:         room.Boot,
+			MinBuyin:     room.MinBuyin,
+			ChaalLimmit:  room.ChaalLimmit,
+			PotLimit:     room.PotLimit,
+			TotalPlayers: room.TotalPlayers,
+			RoomLevel:    room.RoomLevel,
+			RoomId:       room.RoomId,
+		}
+	}
+	return &msg.TeenPattiRoomList{
+		TeenPattiRoom: msgRooms,
+	}
+}
+
+/* 完整的52张牌映射:
+黑桃(0):
+    i=0:  ♠A  (Color=0, Point=1)
+    i=1:  ♠2  (Color=0, Point=2)
+    i=2:  ♠3  (Color=0, Point=3)
+    ...
+    i=10: ♠J  (Color=0, Point=11)
+    i=11: ♠Q  (Color=0, Point=12)
+    i=12: ♠K  (Color=0, Point=13)
+
+红桃(1):
+    i=13: ♥A  (Color=1, Point=1)
+    i=14: ♥2  (Color=1, Point=2)
+    i=15: ♥3  (Color=1, Point=3)
+    ...
+    i=23: ♥J  (Color=1, Point=11)
+    i=24: ♥Q  (Color=1, Point=12)
+    i=25: ♥K  (Color=1, Point=13)
+
+梅花(2):
+    i=26: ♣A  (Color=2, Point=1)
+    i=27: ♣2  (Color=2, Point=2)
+    i=28: ♣3  (Color=2, Point=3)
+    ...
+    i=36: ♣J  (Color=2, Point=11)
+    i=37: ♣Q  (Color=2, Point=12)
+    i=38: ♣K  (Color=2, Point=13)
+
+方块(3):
+    i=39: ♦A  (Color=3, Point=1)
+    i=40: ♦2  (Color=3, Point=2)
+    i=41: ♦3  (Color=3, Point=3)
+    ...
+    i=49: ♦J  (Color=3, Point=11)
+    i=50: ♦Q  (Color=3, Point=12)
+    i=51: ♦K  (Color=3, Point=13)
+*/
+// 初始化房间的卡牌
+func initCardDeck(c_room *room.Room) {
+	// 按照花色和点数初始化卡牌, 52张, 4种花色, 13种点数
+	c_room.CardDeck = make([]room.Card, 52)
+	for i := 0; i < 52; i++ {
+		c_room.CardDeck[i] = room.Card{
+			Color: i / 13,
+			Point: i%13 + 1,
+		}
+	}
+}
+
+// 洗牌
+func shuffleCardDeck(deck []room.Card) {
+	rand.Shuffle(len(deck), func(i, j int) {
+		deck[i], deck[j] = deck[j], deck[i]
+	})
+}

+ 81 - 0
src/server/game/teen/timer.go

@@ -0,0 +1,81 @@
+package teen
+
+import (
+	"sync"
+	"time"
+)
+
+// TimerEvent 定时事件结构
+type TimerEvent struct {
+	ID        string      // 事件ID
+	Timestamp int64       // 触发时间
+	Callback  func()      // 回调函数
+	Timer     *time.Timer // 定时器
+}
+
+// TimerManager 定时管理器
+type TimerManager struct {
+	events map[string]*TimerEvent
+	mutex  sync.RWMutex
+}
+
+// NewTimerManager 创建定时管理器
+func NewTimerManager() *TimerManager {
+	return &TimerManager{
+		events: make(map[string]*TimerEvent),
+	}
+}
+
+// AddEvent 添加定时事件
+func (tm *TimerManager) AddEvent(id string, delay time.Duration, callback func()) {
+	tm.mutex.Lock()
+	defer tm.mutex.Unlock()
+
+	// 创建定时器
+	timer := time.NewTimer(delay)
+	event := &TimerEvent{
+		ID:        id,
+		Timestamp: time.Now().Add(delay).Unix(),
+		Callback:  callback,
+		Timer:     timer,
+	}
+
+	// 保存事件
+	tm.events[id] = event
+
+	// 启动协程等待触发
+	go func() {
+		<-timer.C
+		tm.triggerEvent(id)
+	}()
+}
+
+// TriggerEvent 立即触发事件
+func (tm *TimerManager) TriggerEvent(id string) {
+	tm.mutex.Lock()
+	defer tm.mutex.Unlock()
+	tm.triggerEvent(id)
+}
+
+// 内部触发事件方法
+func (tm *TimerManager) triggerEvent(id string) {
+	if event, exists := tm.events[id]; exists {
+		// 停止定时器
+		event.Timer.Stop()
+		// 执行回调
+		event.Callback()
+		// 删除事件
+		delete(tm.events, id)
+	}
+}
+
+// RemoveEvent 移除事件
+func (tm *TimerManager) RemoveEvent(id string) {
+	tm.mutex.Lock()
+	defer tm.mutex.Unlock()
+
+	if event, exists := tm.events[id]; exists {
+		event.Timer.Stop()
+		delete(tm.events, id)
+	}
+}

+ 2 - 0
src/server/gate/router.go

@@ -2,9 +2,11 @@ package gate
 
 import (
 	"server/game"
+	"server/login"
 	"server/msg"
 )
 
 func init() {
 	msg.Processor.SetRouter(&msg.Hello{}, game.ChanRPC)
+	msg.Processor.SetRouter(&msg.ReqLogin{}, login.ChanRPC)
 }

+ 10 - 0
src/server/hall/external.go

@@ -0,0 +1,10 @@
+package hall
+
+import (
+	"server/hall/internal"
+)
+
+var (
+	Module  = new(internal.Module)
+	ChanRPC = internal.ChanRPC
+)

+ 72 - 0
src/server/hall/internal/handler.go

@@ -0,0 +1,72 @@
+package internal
+
+import (
+	"reflect"
+	"server/events"
+	"server/msg"
+
+	"github.com/name5566/leaf/gate"
+	"github.com/name5566/leaf/log"
+)
+
+func handleMsg(m interface{}, h interface{}) {
+	skeleton.RegisterChanRPC(reflect.TypeOf(m), h)
+}
+
+// 大厅有进入大厅,离开大厅,加入房间,玩法列表,商城
+func init() {
+	handleMsg(&msg.EnterHall{}, enterHallHandler)
+	handleMsg(&msg.LeaveHall{}, leaveHallHandler)
+	handleMsg(&msg.ResJoinRoom{}, joinRoomHandler)
+	handleMsg(&msg.PlayList{}, playListHandler)
+	handleMsg(&msg.Shop{}, shopHandler)
+	handleMsg(&msg.ReqGameInfo{}, reqGameInfoHandler)
+}
+
+func reqGameInfoHandler(args []interface{}) {
+	m := args[0].(*msg.ReqGameInfo)
+	a := args[1].(gate.Agent)
+
+	switch m.GameId {
+	case "teen_patti":
+		events.EventChan <- events.Event{
+			Type:  events.EventTeenPattiGameInfo,
+			Data:  m,
+			Agent: a,
+		}
+	}
+}
+
+func enterHallHandler(args []interface{}) {
+	m := args[0].(*msg.EnterHall)
+	a := args[1].(gate.Agent)
+	log.Debug("enterHallHandler, msg: %v, agent: %v", m, a)
+}
+
+func leaveHallHandler(args []interface{}) {
+	m := args[0].(*msg.LeaveHall)
+	a := args[1].(gate.Agent)
+	log.Debug("leaveHallHandler, msg: %v, agent: %v", m, a)
+}
+
+func joinRoomHandler(args []interface{}) {
+	m := args[0].(*msg.ResJoinRoom)
+	a := args[1].(gate.Agent)
+	switch m.GameId {
+	case "teen_patti":
+		events.EventChan <- events.Event{
+			Type:  events.EventJoinTeenPattiRoom,
+			Data:  m,
+			Agent: a,
+		}
+	}
+	log.Debug("joinRoomHandler, msg: %v, agent: %v", m, a)
+}
+
+func playListHandler(args []interface{}) {
+	log.Debug("playListHandler")
+}
+
+func shopHandler(args []interface{}) {
+	log.Debug("shopHandler")
+}

+ 23 - 0
src/server/hall/internal/module.go

@@ -0,0 +1,23 @@
+package internal
+
+import (
+	"github.com/name5566/leaf/module"
+	"server/base"
+)
+
+var (
+	skeleton = base.NewSkeleton()
+	ChanRPC  = skeleton.ChanRPCServer
+)
+
+type Module struct {
+	*module.Skeleton
+}
+
+func (m *Module) OnInit() {
+	m.Skeleton = skeleton
+}
+
+func (m *Module) OnDestroy() {
+
+}

+ 10 - 1
src/server/login/internal/handler.go

@@ -4,6 +4,9 @@ import (
 	"reflect"
 	"server/game"
 	"server/msg"
+
+	"github.com/name5566/leaf/gate"
+	"github.com/name5566/leaf/log"
 )
 
 func handleMsg(m interface{}, h interface{}) {
@@ -15,5 +18,11 @@ func init() {
 }
 
 func loginHandler(args []interface{}) {
-	game.ChanRPC.Go("handleAuth", args...)
+	// agent := args[1].(gate.Agent)
+	m := args[0].(*msg.ReqLogin)
+	// 消息的发送者
+	a := args[1].(gate.Agent)
+
+	log.Debug("loginHandler: %v", args)
+	game.ChanRPC.Go("handleAuth", m, a)
 }

Разница между файлами не показана из-за своего большого размера
+ 995 - 31
src/server/msg/common.pb.go


+ 8 - 0
src/server/msg/msg.go

@@ -23,6 +23,14 @@ func init() {
 	Processor.Register(&Hello{})
 	Processor.Register(&ReqLogin{})
 	Processor.Register(&ResLogin{})
+	Processor.Register(&EnterHall{})
+	Processor.Register(&LeaveHall{})
+	Processor.Register(&ResJoinRoom{})
+	Processor.Register(&ReqJoinRoom{})
+	Processor.Register(&PlayList{})
+	Processor.Register(&Shop{})
+	Processor.Register(&ReqGameInfo{})
+	Processor.Register(&ResGameInfo{})
 	Processor.Range(func(id uint16, t reflect.Type) {
 		log.Debug("消息ID: %d, 消息类型: %s\n", id, t.Elem().Name())
 		msgList = append(msgList, MsgInfo{

+ 10 - 0
src/server/user/external.go

@@ -0,0 +1,10 @@
+package user
+
+import (
+	"server/user/internal"
+)
+
+var (
+	Module  = new(internal.Module)
+	ChanRPC = internal.ChanRPC
+)

+ 13 - 0
src/server/user/internal/handler.go

@@ -0,0 +1,13 @@
+package internal
+
+import (
+	"reflect"
+)
+
+func handleMsg(m interface{}, h interface{}) {
+	skeleton.RegisterChanRPC(reflect.TypeOf(m), h)
+}
+
+func init() {
+	// handleMsg(&msg.ReqLogin{}, loginHandler)
+}

+ 23 - 0
src/server/user/internal/module.go

@@ -0,0 +1,23 @@
+package internal
+
+import (
+	"github.com/name5566/leaf/module"
+	"server/base"
+)
+
+var (
+	skeleton = base.NewSkeleton()
+	ChanRPC  = skeleton.ChanRPCServer
+)
+
+type Module struct {
+	*module.Skeleton
+}
+
+func (m *Module) OnInit() {
+	m.Skeleton = skeleton
+}
+
+func (m *Module) OnDestroy() {
+
+}

+ 11 - 0
src/server/user/userData.go

@@ -0,0 +1,11 @@
+package user
+
+import "server/game/room"
+
+type UserData struct {
+	Id              string
+	Status          int
+	Teen_Patti_Room *room.Room
+	Head_Image      string
+	Nickname        string
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов