mirror of
https://github.com/tiennm99/gomoku.git
synced 2026-10-03 05:20:03 +00:00
feat: add ratel-online server, core, client, and api sources
Import source files from ratel-online organization repos into monorepo structure for gomoku game development. Update README with project structure and credits for original authors.
This commit is contained in:
1 parent
ee7edaf727
commit
6e3670aec8
121 files changed
+11204
-1
No files matched your search
@@ -0,0 +1,501 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
stringx "strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/awesome-cap/hashmap"
|
||||
"github.com/ratel-online/core/log"
|
||||
modelx "github.com/ratel-online/core/model"
|
||||
"github.com/ratel-online/core/network"
|
||||
"github.com/ratel-online/core/util/async"
|
||||
"github.com/ratel-online/core/util/json"
|
||||
"github.com/ratel-online/core/util/strings"
|
||||
"github.com/ratel-online/server/consts"
|
||||
)
|
||||
|
||||
var roomIds int64 = 0
|
||||
var players = hashmap.New() // 存储连接过服务器的全部用户
|
||||
var connPlayers = hashmap.New()
|
||||
var rooms = hashmap.New()
|
||||
var roomPlayers = hashmap.New()
|
||||
var roomSpectators = hashmap.New()
|
||||
var roomKickedPlayers = hashmap.New()
|
||||
var roomPropsSetter = map[string]func(r *Room, v string){
|
||||
consts.RoomPropsSkill: func(r *Room, v string) {
|
||||
r.EnableSkill = v == "on"
|
||||
r.EnableLandlord = !r.EnableSkill
|
||||
},
|
||||
consts.RoomPropsLaiZi: func(r *Room, v string) {
|
||||
r.EnableLaiZi = v == "on"
|
||||
},
|
||||
consts.RoomPropsDotShuffle: func(r *Room, v string) {
|
||||
r.EnableDontShuffle = v == "on"
|
||||
},
|
||||
consts.RoomPropsPassword: func(r *Room, v string) {
|
||||
if v == "off" {
|
||||
r.Password = ""
|
||||
} else {
|
||||
r.Password = v
|
||||
}
|
||||
},
|
||||
consts.RoomPropsChat: func(r *Room, v string) {
|
||||
r.EnableChat = v == "on"
|
||||
},
|
||||
consts.RoomPropsPlayerNum: func(r *Room, v string) {
|
||||
n, _ := strconv.Atoi(v)
|
||||
if n < 2 || n > 50 {
|
||||
n = consts.MaxPlayers
|
||||
}
|
||||
r.MaxPlayers = n
|
||||
},
|
||||
consts.RoomPropsShowIP: func(r *Room, v string) {
|
||||
r.EnableShowIP = v == "on"
|
||||
},
|
||||
consts.RoomPropsJokerAsTarget: func(r *Room, v string) {
|
||||
r.EnableJokerAsTarget = v == "on"
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
async.Async(func() {
|
||||
loopCount := 0
|
||||
for {
|
||||
loopCount++
|
||||
if loopCount%60 == 0 {
|
||||
log.Infof("[database.init] Room cleanup loop count: %d (running for %d hours)\n", loopCount, loopCount/60)
|
||||
}
|
||||
time.Sleep(1 * time.Minute)
|
||||
rooms.Foreach(func(e *hashmap.Entry) {
|
||||
roomCancel(e.Value().(*Room))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Connected(conn *network.Conn, info *modelx.AuthInfo) *Player {
|
||||
player := &Player{
|
||||
ID: conn.ID(),
|
||||
IP: conn.IP(),
|
||||
Name: strings.Desensitize(info.Name),
|
||||
Amount: 2000,
|
||||
}
|
||||
player.Conn(conn) // 初始化play对象
|
||||
players.Set(conn.ID(), player) // 写入用户池
|
||||
connPlayers.Set(conn.ID(), player) // 写入连接用户池
|
||||
return player
|
||||
}
|
||||
|
||||
func CreateRoom(creator int64, t int) *Room {
|
||||
room := &Room{
|
||||
ID: atomic.AddInt64(&roomIds, 1),
|
||||
Type: t,
|
||||
State: consts.RoomStateWaiting,
|
||||
Creator: creator,
|
||||
ActiveTime: time.Now(),
|
||||
MaxPlayers: consts.MaxPlayers,
|
||||
EnableLandlord: true,
|
||||
EnableChat: true,
|
||||
EnableShowIP: false,
|
||||
}
|
||||
switch room.Type {
|
||||
case consts.GameTypeLaiZi:
|
||||
room.EnableLaiZi = true
|
||||
case consts.GameTypeSkill:
|
||||
room.EnableLaiZi = true
|
||||
room.EnableDontShuffle = true
|
||||
room.EnableSkill = true
|
||||
room.EnableLandlord = false
|
||||
case consts.GameTypeRunFast:
|
||||
room.MaxPlayers = 3
|
||||
room.EnableLaiZi = false
|
||||
room.EnableLandlord = false
|
||||
room.EnableDontShuffle = true
|
||||
case consts.GameTypeTexas:
|
||||
room.MaxPlayers = 10
|
||||
case consts.GameTypeLiar:
|
||||
room.MaxPlayers = 4
|
||||
room.EnableJokerAsTarget = true
|
||||
}
|
||||
roomPlayers.Set(room.ID, map[int64]bool{})
|
||||
roomSpectators.Set(room.ID, map[int64]int{})
|
||||
rooms.Set(room.ID, room)
|
||||
return room
|
||||
}
|
||||
|
||||
func deleteRoom(room *Room) {
|
||||
if room != nil {
|
||||
rooms.Del(room.ID)
|
||||
roomPlayers.Del(room.ID)
|
||||
roomSpectators.Del(room.ID)
|
||||
if room.Game != nil {
|
||||
room.Game.Clean()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetRooms() []*Room {
|
||||
list := make([]*Room, 0)
|
||||
rooms.Foreach(func(e *hashmap.Entry) {
|
||||
list = append(list, e.Value().(*Room))
|
||||
})
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].ID < list[j].ID
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
func GetRoom(roomId int64) *Room {
|
||||
return getRoom(roomId)
|
||||
}
|
||||
|
||||
func getRoom(roomId int64) *Room {
|
||||
if v, ok := rooms.Get(roomId); ok {
|
||||
return v.(*Room)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPlayer(playerId int64) *Player {
|
||||
if v, ok := players.Get(playerId); ok {
|
||||
return v.(*Player)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetRoomProps(room *Room, k, v string) {
|
||||
// 根据房间类型限制可设置的属性
|
||||
allowedProps := getAllowedPropsByGameType(room.Type)
|
||||
|
||||
// 检查属性是否允许设置
|
||||
if !allowedProps[k] {
|
||||
return // 不允许的属性直接返回,不执行设置
|
||||
}
|
||||
|
||||
if setter, ok := roomPropsSetter[k]; ok {
|
||||
setter(room, v)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据游戏类型返回允许设置的属性列表
|
||||
func getAllowedPropsByGameType(gameType int) map[string]bool {
|
||||
switch gameType {
|
||||
case consts.GameTypeLiar:
|
||||
// 对于骗子酒馆,只允许设置指示牌规则和显示IP
|
||||
return map[string]bool{
|
||||
consts.RoomPropsJokerAsTarget: true,
|
||||
consts.RoomPropsShowIP: true,
|
||||
consts.RoomPropsPassword: true,
|
||||
}
|
||||
case consts.GameTypeUno, consts.GameTypeMahjong:
|
||||
// 对于Uno和麻将,允许设置玩家数量和显示IP
|
||||
return map[string]bool{
|
||||
consts.RoomPropsPlayerNum: true,
|
||||
consts.RoomPropsShowIP: true,
|
||||
consts.RoomPropsPassword: true,
|
||||
}
|
||||
case consts.GameTypeTexas:
|
||||
// 对于德州扑克,允许设置玩家数量和显示IP
|
||||
return map[string]bool{
|
||||
consts.RoomPropsPlayerNum: true,
|
||||
consts.RoomPropsShowIP: true,
|
||||
consts.RoomPropsPassword: true,
|
||||
}
|
||||
default:
|
||||
// 其他游戏类型允许所有常规属性
|
||||
return map[string]bool{
|
||||
consts.RoomPropsLaiZi: true,
|
||||
consts.RoomPropsDotShuffle: true,
|
||||
consts.RoomPropsSkill: true,
|
||||
consts.RoomPropsPassword: true,
|
||||
consts.RoomPropsPlayerNum: true,
|
||||
consts.RoomPropsChat: true,
|
||||
consts.RoomPropsShowIP: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getRoomPlayers(roomId int64) map[int64]bool {
|
||||
if v, ok := roomPlayers.Get(roomId); ok {
|
||||
return v.(map[int64]bool)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRoomSpectators(roomId int64) map[int64]int {
|
||||
if v, ok := roomSpectators.Get(roomId); ok {
|
||||
return v.(map[int64]int)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsValidPlayer(roomId, playerId int64) bool {
|
||||
room := getRoom(roomId)
|
||||
if room != nil {
|
||||
room.Lock()
|
||||
defer room.Unlock()
|
||||
playersIds := getRoomPlayers(roomId)
|
||||
if playersIds != nil {
|
||||
_, exists := playersIds[playerId]
|
||||
if exists {
|
||||
return true
|
||||
}
|
||||
}
|
||||
spectatorsIds := getRoomSpectators(roomId)
|
||||
if spectatorsIds != nil {
|
||||
_, exists := spectatorsIds[playerId]
|
||||
if exists {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 加入房间
|
||||
func JoinRoom(roomId, playerId int64) error {
|
||||
// 资源检查
|
||||
player := getPlayer(playerId)
|
||||
if player == nil {
|
||||
return consts.ErrorsExist
|
||||
}
|
||||
room := getRoom(roomId)
|
||||
if room == nil {
|
||||
return consts.ErrorsRoomInvalid
|
||||
}
|
||||
|
||||
// 加锁防止并发异常
|
||||
room.Lock()
|
||||
defer room.Unlock()
|
||||
|
||||
if hasKicked(roomId, playerId) {
|
||||
return consts.ErrorsJoinFailForKicked
|
||||
}
|
||||
|
||||
room.ActiveTime = time.Now()
|
||||
|
||||
//房间人数及状态检查
|
||||
if room.Players >= room.MaxPlayers || room.State == consts.RoomStateRunning {
|
||||
spectatorsIds := getRoomSpectators(roomId)
|
||||
spectatorsIds[playerId] = len(spectatorsIds)
|
||||
player.RoomID = roomId
|
||||
player.Role = RoleSpectator
|
||||
} else {
|
||||
playersIds := getRoomPlayers(roomId)
|
||||
playersIds[playerId] = true
|
||||
room.Players++
|
||||
player.RoomID = roomId
|
||||
player.Role = RolePlayer
|
||||
if room.Creator == playerId {
|
||||
player.Role = RoleOwner
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LeaveRoom(roomId, playerId int64) {
|
||||
room := getRoom(roomId)
|
||||
if room != nil {
|
||||
room.Lock()
|
||||
defer room.Unlock()
|
||||
leaveRoom(room, getPlayer(playerId))
|
||||
}
|
||||
}
|
||||
|
||||
func Backfill(roomId int64) *Player {
|
||||
room := getRoom(roomId)
|
||||
if room != nil {
|
||||
room.Lock()
|
||||
defer room.Unlock()
|
||||
return backfill(room)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func backfill(room *Room) *Player {
|
||||
if room.Players >= room.MaxPlayers {
|
||||
return nil
|
||||
}
|
||||
spectatorsIds := getRoomSpectators(room.ID)
|
||||
if len(spectatorsIds) == 0 {
|
||||
return nil
|
||||
}
|
||||
spectators := make([]struct {
|
||||
id int64
|
||||
index int
|
||||
}, 0)
|
||||
|
||||
for id, index := range spectatorsIds {
|
||||
spectators = append(spectators, struct {
|
||||
id int64
|
||||
index int
|
||||
}{id: id, index: index})
|
||||
}
|
||||
sort.Slice(spectators, func(i, j int) bool {
|
||||
return spectators[i].index < spectators[j].index
|
||||
})
|
||||
|
||||
playerId := spectators[0].id
|
||||
|
||||
delete(spectatorsIds, playerId)
|
||||
playersIds := getRoomPlayers(room.ID)
|
||||
playersIds[playerId] = true
|
||||
room.Players++
|
||||
player := getPlayer(playerId)
|
||||
if player != nil {
|
||||
player.Role = RolePlayer
|
||||
}
|
||||
return player
|
||||
}
|
||||
|
||||
func Kicking(roomId, playerId int64) {
|
||||
room := getRoom(roomId)
|
||||
if room != nil {
|
||||
room.Lock()
|
||||
defer room.Unlock()
|
||||
leaveRoom(room, getPlayer(playerId))
|
||||
|
||||
kickedPlayers, ok := roomKickedPlayers.Get(roomId)
|
||||
if !ok {
|
||||
kickedPlayers = map[int64]bool{}
|
||||
roomKickedPlayers.Set(roomId, kickedPlayers)
|
||||
}
|
||||
kickedPlayers.(map[int64]bool)[playerId] = true
|
||||
}
|
||||
}
|
||||
|
||||
func hasKicked(roomId, playerId int64) bool {
|
||||
kickedPlayers, ok := roomKickedPlayers.Get(roomId)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, exists := kickedPlayers.(map[int64]bool)[playerId]
|
||||
return exists
|
||||
}
|
||||
|
||||
func leaveRoom(room *Room, player *Player) {
|
||||
if room == nil || player == nil {
|
||||
return
|
||||
}
|
||||
room.ActiveTime = time.Now()
|
||||
playersIds := getRoomPlayers(room.ID)
|
||||
if _, ok := playersIds[player.ID]; ok {
|
||||
room.Players--
|
||||
player.RoomID = 0
|
||||
player.Role = ""
|
||||
delete(playersIds, player.ID)
|
||||
if len(playersIds) > 0 && room.Creator == player.ID {
|
||||
for k := range playersIds {
|
||||
room.Creator = k
|
||||
if p := getPlayer(k); p != nil {
|
||||
p.Role = RoleOwner
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
spectatorsIds := getRoomSpectators(room.ID)
|
||||
if _, ok := spectatorsIds[player.ID]; ok {
|
||||
player.RoomID = 0
|
||||
player.Role = ""
|
||||
delete(spectatorsIds, player.ID)
|
||||
}
|
||||
if len(playersIds) == 0 && len(spectatorsIds) == 0 {
|
||||
deleteRoom(room)
|
||||
}
|
||||
}
|
||||
|
||||
func roomCancel(room *Room) {
|
||||
if room.ActiveTime.Add(24 * time.Hour).Before(time.Now()) {
|
||||
log.Infof("room %d is timeout 24 hours, removed.\n", room.ID)
|
||||
deleteRoom(room)
|
||||
return
|
||||
}
|
||||
living := false
|
||||
playerIds := getRoomPlayers(room.ID)
|
||||
for id := range playerIds {
|
||||
if getPlayer(id).online {
|
||||
living = true
|
||||
break
|
||||
}
|
||||
}
|
||||
spectatorIds := getRoomSpectators(room.ID)
|
||||
for id := range spectatorIds {
|
||||
if getPlayer(id).online {
|
||||
living = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !living {
|
||||
log.Infof("room %d is not living, removed.\n", room.ID)
|
||||
deleteRoom(room)
|
||||
}
|
||||
}
|
||||
|
||||
func RoomPlayers(roomId int64) map[int64]bool {
|
||||
return getRoomPlayers(roomId)
|
||||
}
|
||||
|
||||
func RoomSpectators(roomId int64) map[int64]int {
|
||||
return getRoomSpectators(roomId)
|
||||
}
|
||||
|
||||
func broadcast(room *Room, msg string, exclude ...int64) {
|
||||
room.ActiveTime = time.Now()
|
||||
excludeSet := map[int64]bool{}
|
||||
for _, exc := range exclude {
|
||||
excludeSet[exc] = true
|
||||
}
|
||||
for playerId := range getRoomPlayers(room.ID) {
|
||||
if player := getPlayer(playerId); player != nil && !excludeSet[playerId] {
|
||||
_ = player.WriteString(">> " + msg)
|
||||
}
|
||||
}
|
||||
for playerId := range getRoomSpectators(room.ID) {
|
||||
if player := getPlayer(playerId); player != nil && !excludeSet[playerId] {
|
||||
_ = player.WriteString(">> " + msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Broadcast(roomId int64, msg string, exclude ...int64) {
|
||||
room := getRoom(roomId)
|
||||
if room == nil {
|
||||
return
|
||||
}
|
||||
broadcast(room, msg, exclude...)
|
||||
}
|
||||
|
||||
func BroadcastChat(player *Player, msg string, exclude ...int64) {
|
||||
log.Infof("chat msg, player %s[%d] %s say: %s\n", player.Name, player.ID, player.IP, stringx.TrimSpace(msg))
|
||||
Broadcast(player.RoomID, strings.Desensitize(msg), exclude...)
|
||||
}
|
||||
|
||||
func BroadcastObject(roomId int64, object interface{}, exclude ...int64) {
|
||||
room := getRoom(roomId)
|
||||
if room == nil {
|
||||
return
|
||||
}
|
||||
room.Lock()
|
||||
defer room.Unlock()
|
||||
excludeSet := map[int64]bool{}
|
||||
for _, exc := range exclude {
|
||||
excludeSet[exc] = true
|
||||
}
|
||||
msg := json.Marshal(object)
|
||||
playerIds := getRoomPlayers(roomId)
|
||||
for playerId := range playerIds {
|
||||
if player := getPlayer(playerId); player != nil && !excludeSet[playerId] {
|
||||
_ = player.WriteString(string(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetPlayer(playerId int64) *Player {
|
||||
return getPlayer(playerId)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ratel-online/core/model"
|
||||
)
|
||||
|
||||
type Liar struct {
|
||||
sync.Mutex
|
||||
Room *Room `json:"room"`
|
||||
PlayerIDs []int64 `json:"playerIds"`
|
||||
Bullets map[int64]int `json:"bullets"`
|
||||
Bong map[int64]int `json:"bong"`
|
||||
Pokers model.Pokers `json:"pokers"`
|
||||
States map[int64]chan int `json:"states"`
|
||||
Hands map[int64]model.Pokers `json:"hands"`
|
||||
Target *model.Poker `json:"target"`
|
||||
Alive map[int64]bool `json:"alive"`
|
||||
LastPlayerID int64 `json:"lastPlayerId"`
|
||||
LastPokers model.Pokers `json:"lastPokers"`
|
||||
Supervisors map[int64]bool `json:"supervisors"`
|
||||
AllowJokers bool `json:"allowJokers"`
|
||||
}
|
||||
|
||||
func (l *Liar) Clean() {
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/feel-easy/mahjong/card"
|
||||
"github.com/feel-easy/mahjong/consts"
|
||||
"github.com/feel-easy/mahjong/event"
|
||||
"github.com/feel-easy/mahjong/game"
|
||||
"github.com/feel-easy/mahjong/tile"
|
||||
"github.com/ratel-online/core/log"
|
||||
rconsts "github.com/ratel-online/server/consts"
|
||||
)
|
||||
|
||||
type Mahjong struct {
|
||||
Room *Room `json:"room"`
|
||||
PlayerIDs []int `json:"playerIds"`
|
||||
States map[int]chan int `json:"states"`
|
||||
Game *game.Game `json:"game"`
|
||||
}
|
||||
|
||||
func (game *Mahjong) Clean() {
|
||||
if game != nil {
|
||||
for _, state := range game.States {
|
||||
close(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OP struct {
|
||||
operation int
|
||||
tiles []int
|
||||
}
|
||||
|
||||
func circled(n int) string {
|
||||
if n >= 1 && n <= 20 {
|
||||
return string(rune(0x2460 + n - 1))
|
||||
}
|
||||
return strconv.Itoa(n)
|
||||
}
|
||||
|
||||
type MahjongPlayer struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func NewPlayer(user *Player) *MahjongPlayer {
|
||||
return &MahjongPlayer{
|
||||
ID: user.ID,
|
||||
Name: user.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *MahjongPlayer) PlayerID() int {
|
||||
return int(p.ID)
|
||||
}
|
||||
|
||||
func (p *MahjongPlayer) NickName() string {
|
||||
return p.Name
|
||||
}
|
||||
|
||||
func (mp *MahjongPlayer) OnPlayTile(payload event.PlayTilePayload) {
|
||||
p := GetPlayer(mp.ID)
|
||||
p.WriteString(fmt.Sprintf("You play %s ! \n", tile.Tile(payload.Tile)))
|
||||
Broadcast(p.RoomID, fmt.Sprintf("%s PlayTile %s !\n", payload.PlayerName, tile.Tile(payload.Tile)), p.ID)
|
||||
}
|
||||
|
||||
func (mp *MahjongPlayer) Take(tiles []int, gameState game.State) (int, []int, error) {
|
||||
p := GetPlayer(mp.ID)
|
||||
Broadcast(p.RoomID, fmt.Sprintf("It's %s take mahjong! \n", p.Name), p.ID)
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString(fmt.Sprintf("It's your take mahjong, %s! \n", p.Name))
|
||||
buf.WriteString(gameState.String())
|
||||
p.WriteString(buf.String())
|
||||
askBuf := bytes.Buffer{}
|
||||
tileOptions := make(map[string]*OP)
|
||||
labelCounter := 1
|
||||
if pvs, ok := gameState.SpecialPrivileges[int(p.ID)]; ok {
|
||||
for _, pv := range pvs {
|
||||
switch pv {
|
||||
case consts.GANG:
|
||||
askBuf.WriteString("You can 杠!!!\n")
|
||||
label := strconv.Itoa(labelCounter)
|
||||
ts := []int{gameState.LastPlayedTile, gameState.LastPlayedTile, gameState.LastPlayedTile}
|
||||
tileOptions[label] = &OP{
|
||||
operation: consts.GANG,
|
||||
tiles: append(ts, gameState.LastPlayedTile),
|
||||
}
|
||||
askBuf.WriteString(fmt.Sprintf("%s. %s \n", circled(labelCounter), tile.ToTileString(ts)))
|
||||
labelCounter++
|
||||
case consts.PENG:
|
||||
askBuf.WriteString("You can 碰!!!\n")
|
||||
label := strconv.Itoa(labelCounter)
|
||||
ts := []int{gameState.LastPlayedTile, gameState.LastPlayedTile}
|
||||
tileOptions[label] = &OP{
|
||||
operation: consts.PENG,
|
||||
tiles: append(ts, gameState.LastPlayedTile),
|
||||
}
|
||||
askBuf.WriteString(fmt.Sprintf("%s. %s \n", circled(labelCounter), tile.ToTileString(ts)))
|
||||
labelCounter++
|
||||
case consts.CHI:
|
||||
askBuf.WriteString("You can 吃!!!\n")
|
||||
for _, ts := range card.CanChiTiles(tiles, gameState.LastPlayedTile) {
|
||||
label := strconv.Itoa(labelCounter)
|
||||
tileOptions[label] = &OP{
|
||||
operation: consts.CHI,
|
||||
tiles: append(ts, gameState.LastPlayedTile),
|
||||
}
|
||||
askBuf.WriteString(fmt.Sprintf("%s. %s \n", circled(labelCounter), tile.ToTileString(ts)))
|
||||
labelCounter++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
label := strconv.Itoa(labelCounter)
|
||||
askBuf.WriteString(fmt.Sprintf("%s. %s \n", circled(labelCounter), "no"))
|
||||
tileOptions[label] = &OP{
|
||||
operation: 0,
|
||||
tiles: []int{},
|
||||
}
|
||||
loopCount := 0
|
||||
for {
|
||||
loopCount++
|
||||
if loopCount%100 == 0 {
|
||||
log.Infof("[MahjongPlayer.Take] Player %d loop count: %d\n", mp.ID, loopCount)
|
||||
}
|
||||
p = getPlayer(p.ID)
|
||||
p.WriteString(askBuf.String())
|
||||
selectedLabel, err := p.AskForString(consts.PlayMahjongTimeout)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case rconsts.ErrorsExist:
|
||||
p.WriteString("Don't quit a good game!\n")
|
||||
selectedLabel = "E"
|
||||
case rconsts.ErrorsTimeout:
|
||||
// Default to "no" action (skip peng/chi/gang) on timeout
|
||||
selectedLabel = label
|
||||
default:
|
||||
return 0, nil, err
|
||||
}
|
||||
}
|
||||
selected, found := tileOptions[strings.ToUpper(selectedLabel)]
|
||||
if !found {
|
||||
BroadcastChat(p, fmt.Sprintf("%s say: %s\n", p.Name, selectedLabel))
|
||||
continue
|
||||
}
|
||||
return selected.operation, selected.tiles, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (mp *MahjongPlayer) Play(tiles []int, gameState game.State) (int, error) {
|
||||
p := GetPlayer(mp.ID)
|
||||
Broadcast(p.RoomID, fmt.Sprintf("It's %s turn! \n", p.Name), p.ID)
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString(fmt.Sprintf("It's your turn, %s! \n", p.Name))
|
||||
buf.WriteString(gameState.String())
|
||||
p.WriteString(buf.String())
|
||||
askBuf := bytes.Buffer{}
|
||||
askBuf.WriteString("Select a tile to play:\n")
|
||||
tileOptions := make(map[string]int)
|
||||
sort.Ints(tiles)
|
||||
for idx, i := range tiles {
|
||||
label := strconv.Itoa(idx + 1)
|
||||
tileOptions[label] = i
|
||||
askBuf.WriteString(fmt.Sprintf("%s. %-6s", circled(idx+1), tile.Tile(i).String()))
|
||||
if (idx+1)%6 == 0 {
|
||||
askBuf.WriteString("\n")
|
||||
} else {
|
||||
askBuf.WriteString(" ")
|
||||
}
|
||||
}
|
||||
askBuf.WriteString("\n")
|
||||
loopCount := 0
|
||||
for {
|
||||
loopCount++
|
||||
if loopCount%100 == 0 {
|
||||
log.Infof("[MahjongPlayer.Play] Player %d loop count: %d\n", mp.ID, loopCount)
|
||||
}
|
||||
p = GetPlayer(p.ID)
|
||||
p.WriteString(askBuf.String())
|
||||
selectedLabel, err := p.AskForString(rconsts.PlayMahjongTimeout)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case rconsts.ErrorsExist:
|
||||
p.WriteString("Don't quit a good game!\n")
|
||||
selectedLabel = "E"
|
||||
case rconsts.ErrorsTimeout:
|
||||
// Default to the last tile (least likely to be useful) on timeout
|
||||
selectedLabel = strconv.Itoa(len(tiles))
|
||||
default:
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
selectedCard, found := tileOptions[strings.ToUpper(selectedLabel)]
|
||||
if !found {
|
||||
BroadcastChat(p, fmt.Sprintf("%s say: %s\n", p.Name, selectedLabel))
|
||||
continue
|
||||
}
|
||||
mp.OnPlayTile(event.PlayTilePayload{
|
||||
PlayerName: p.Name,
|
||||
Tile: selectedCard,
|
||||
})
|
||||
return selectedCard, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ratel-online/core/log"
|
||||
"github.com/ratel-online/core/model"
|
||||
"github.com/ratel-online/core/network"
|
||||
"github.com/ratel-online/core/protocol"
|
||||
"github.com/ratel-online/core/util/arrays"
|
||||
"github.com/ratel-online/core/util/json"
|
||||
"github.com/ratel-online/core/util/poker"
|
||||
"github.com/ratel-online/server/consts"
|
||||
)
|
||||
|
||||
const initialRune = 'A'
|
||||
|
||||
type runeSequence struct {
|
||||
currentRune rune
|
||||
}
|
||||
|
||||
func (s *runeSequence) next() rune {
|
||||
if s.currentRune == 0 {
|
||||
s.currentRune = initialRune
|
||||
}
|
||||
currentRune := s.currentRune
|
||||
s.currentRune++
|
||||
return currentRune
|
||||
}
|
||||
|
||||
type Role string
|
||||
|
||||
const (
|
||||
RolePlayer Role = "player"
|
||||
RoleOwner Role = "owner"
|
||||
RoleSpectator Role = "spectator"
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
ID int64 `json:"id"`
|
||||
IP string `json:"ip"`
|
||||
Name string `json:"name"`
|
||||
Mode int `json:"mode"`
|
||||
Type int `json:"type"`
|
||||
Amount uint `json:"amount"`
|
||||
RoomID int64 `json:"roomId"`
|
||||
Role Role `json:"role"`
|
||||
|
||||
conn *network.Conn
|
||||
data chan *protocol.Packet
|
||||
read bool
|
||||
state consts.StateID
|
||||
online bool
|
||||
}
|
||||
|
||||
func (p *Player) Write(bytes []byte) error {
|
||||
return p.conn.Write(protocol.Packet{
|
||||
Body: bytes,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Player) IsOnline() bool {
|
||||
return p.online
|
||||
}
|
||||
|
||||
func (p *Player) Offline() {
|
||||
p.online = false
|
||||
_ = p.conn.Close()
|
||||
close(p.data)
|
||||
room := getRoom(p.RoomID)
|
||||
if room != nil {
|
||||
room.Lock()
|
||||
defer room.Unlock()
|
||||
broadcast(room, fmt.Sprintf("%s lost connection! \n", p.Name))
|
||||
if room.State == consts.RoomStateWaiting {
|
||||
leaveRoom(room, p)
|
||||
}
|
||||
roomCancel(room)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Player) Listening() error {
|
||||
loopCount := 0
|
||||
for {
|
||||
loopCount++
|
||||
if loopCount%1000 == 0 {
|
||||
log.Infof("[Player.Listening] Player %d loop count: %d, online: %v\n", p.ID, loopCount, p.online)
|
||||
}
|
||||
pack, err := p.conn.Read()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if p.read {
|
||||
p.data <- pack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 向客户端发生消息
|
||||
func (p *Player) WriteString(data string) error {
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
return p.conn.Write(protocol.Packet{
|
||||
Body: []byte(data),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Player) WriteObject(data interface{}) error {
|
||||
return p.conn.Write(protocol.Packet{
|
||||
Body: json.Marshal(data),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Player) WriteError(err error) error {
|
||||
if err == consts.ErrorsExist {
|
||||
return err
|
||||
}
|
||||
return p.conn.Write(protocol.Packet{
|
||||
Body: []byte(err.Error() + "\n"),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Player) AskForPacket(timeout ...time.Duration) (*protocol.Packet, error) {
|
||||
p.StartTransaction()
|
||||
defer p.StopTransaction()
|
||||
return p.askForPacket(timeout...)
|
||||
}
|
||||
|
||||
func (p *Player) askForPacket(timeout ...time.Duration) (*protocol.Packet, error) {
|
||||
var packet *protocol.Packet
|
||||
if len(timeout) > 0 {
|
||||
select {
|
||||
case packet = <-p.data:
|
||||
case <-time.After(timeout[0]):
|
||||
return nil, consts.ErrorsTimeout
|
||||
}
|
||||
} else {
|
||||
packet = <-p.data
|
||||
}
|
||||
if packet == nil {
|
||||
return nil, consts.ErrorsChanClosed
|
||||
}
|
||||
single := strings.ToLower(packet.String())
|
||||
if single == "exit" || single == "e" {
|
||||
return nil, consts.ErrorsExist
|
||||
}
|
||||
return packet, nil
|
||||
}
|
||||
|
||||
func (p *Player) AskForInt(timeout ...time.Duration) (int, error) {
|
||||
packet, err := p.AskForPacket(timeout...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return packet.Int()
|
||||
}
|
||||
|
||||
func (p *Player) AskForInt64(timeout ...time.Duration) (int64, error) {
|
||||
packet, err := p.AskForPacket(timeout...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return packet.Int64()
|
||||
}
|
||||
|
||||
func (p *Player) AskForString(timeout ...time.Duration) (string, error) {
|
||||
packet, err := p.AskForPacket(timeout...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return packet.String(), nil
|
||||
}
|
||||
|
||||
func (p *Player) AskForStringWithoutTransaction(timeout ...time.Duration) (string, error) {
|
||||
packet, err := p.askForPacket(timeout...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return packet.String(), nil
|
||||
}
|
||||
|
||||
func (p *Player) StartTransaction() {
|
||||
p.read = true
|
||||
_ = p.WriteString(consts.IsStart)
|
||||
}
|
||||
|
||||
func (p *Player) StopTransaction() {
|
||||
p.read = false
|
||||
_ = p.WriteString(consts.IsStop)
|
||||
}
|
||||
|
||||
func (p *Player) State(s consts.StateID) {
|
||||
p.state = s
|
||||
}
|
||||
|
||||
func (p *Player) GetState() consts.StateID {
|
||||
return p.state
|
||||
}
|
||||
|
||||
func (p *Player) Conn(conn *network.Conn) {
|
||||
p.conn = conn
|
||||
p.data = make(chan *protocol.Packet, 8)
|
||||
p.online = true
|
||||
}
|
||||
|
||||
func (p Player) Model() model.Player {
|
||||
modelPlayer := model.Player{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
}
|
||||
room := getRoom(p.RoomID)
|
||||
if room != nil && room.Game != nil {
|
||||
game := room.Game.(*Game)
|
||||
modelPlayer.Pokers = len(game.Pokers[p.ID])
|
||||
modelPlayer.Group = game.Groups[p.ID]
|
||||
}
|
||||
return modelPlayer
|
||||
}
|
||||
|
||||
func (p Player) String() string {
|
||||
return fmt.Sprintf("%s[%d]", p.Name, p.ID)
|
||||
}
|
||||
|
||||
type RoomGame interface {
|
||||
Clean()
|
||||
}
|
||||
|
||||
type Room struct {
|
||||
sync.Mutex
|
||||
|
||||
ID int64 `json:"id"`
|
||||
Type int `json:"type"`
|
||||
Game RoomGame `json:"gameId"`
|
||||
State int `json:"state"`
|
||||
Players int `json:"players"`
|
||||
Banker int `json:"banker"`
|
||||
Robots int `json:"robots"`
|
||||
Creator int64 `json:"creator"`
|
||||
ActiveTime time.Time `json:"activeTime"`
|
||||
MaxPlayers int `json:"maxPlayers"`
|
||||
Password string `json:"password"`
|
||||
EnableChat bool `json:"enableChat"`
|
||||
EnableLaiZi bool `json:"enableLaiZi"`
|
||||
EnableSkill bool `json:"enableSkill"`
|
||||
EnableLandlord bool `json:"enableLandlord"`
|
||||
EnableDontShuffle bool `json:"enableDontShuffle"`
|
||||
EnableShowIP bool `json:"enableShowIP"`
|
||||
EnableJokerAsTarget bool `json:"enableJokerAsTarget"`
|
||||
}
|
||||
|
||||
func (r *Room) Model() model.Room {
|
||||
return model.Room{
|
||||
ID: r.ID,
|
||||
Type: r.Type,
|
||||
TypeDesc: consts.GameTypes[r.Type],
|
||||
Players: r.Players,
|
||||
State: r.State,
|
||||
StateDesc: consts.RoomStates[r.State],
|
||||
Creator: r.Creator,
|
||||
}
|
||||
}
|
||||
|
||||
type Game struct {
|
||||
Room *Room `json:"room"`
|
||||
Players []int64 `json:"players"`
|
||||
Groups map[int64]int `json:"groups"`
|
||||
States map[int64]chan int `json:"states"`
|
||||
Pokers map[int64]model.Pokers `json:"pokers"`
|
||||
Universals []int `json:"universals"`
|
||||
Decks int `json:"decks"`
|
||||
Additional model.Pokers `json:"pocket"`
|
||||
Multiple int `json:"multiple"`
|
||||
FirstPlayer int64 `json:"firstPlayer"`
|
||||
LastPlayer int64 `json:"lastPlayer"`
|
||||
Robs []int64 `json:"robs"`
|
||||
FirstRob int64 `json:"firstRob"`
|
||||
LastRob int64 `json:"lastRob"`
|
||||
FinalRob bool `json:"finalRob"`
|
||||
LastFaces *model.Faces `json:"lastFaces"`
|
||||
LastPokers model.Pokers `json:"lastPokers"`
|
||||
Mnemonic map[int]int `json:"mnemonic"`
|
||||
Skills map[int64]int `json:"skills"`
|
||||
PlayTimes map[int64]int `json:"playTimes"`
|
||||
PlayTimeOut map[int64]time.Duration `json:"playTimeOut"`
|
||||
Rules poker.Rules `json:"rules"`
|
||||
Discards model.Pokers `json:"discards"`
|
||||
}
|
||||
|
||||
func (game *Game) Clean() {
|
||||
if game != nil {
|
||||
for _, state := range game.States {
|
||||
close(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (game *Game) Start() {
|
||||
|
||||
}
|
||||
|
||||
func (g Game) NextPlayer(curr int64) int64 {
|
||||
idx := arrays.IndexOf(g.Players, curr)
|
||||
return g.Players[(idx+1)%len(g.Players)]
|
||||
}
|
||||
|
||||
func (g Game) PrevPlayer(curr int64) int64 {
|
||||
idx := arrays.IndexOf(g.Players, curr)
|
||||
return g.Players[(idx+len(g.Players))%len(g.Players)]
|
||||
}
|
||||
|
||||
func (g Game) IsTeammate(player1, player2 int64) bool {
|
||||
return g.Groups[player1] == g.Groups[player2]
|
||||
}
|
||||
|
||||
func (g Game) IsLandlord(playerId int64) bool {
|
||||
return g.Groups[playerId] == 1
|
||||
}
|
||||
|
||||
func (g Game) Team(playerId int64) string {
|
||||
if !g.Room.EnableLandlord {
|
||||
return "team" + strconv.Itoa(g.Groups[playerId])
|
||||
} else {
|
||||
if !g.IsLandlord(playerId) {
|
||||
return "peasant"
|
||||
} else {
|
||||
return "landlord"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/ratel-online/core/model"
|
||||
)
|
||||
|
||||
type Texas struct {
|
||||
Room *Room `json:"room"`
|
||||
Players []*TexasPlayer `json:"players"`
|
||||
Pot uint `json:"pot"`
|
||||
BB int `json:"bb"`
|
||||
SB int `json:"sb"`
|
||||
Pool model.Pokers `json:"pool"`
|
||||
Board model.Pokers `json:"board"`
|
||||
MaxBetAmount uint `json:"maxBetAmount"`
|
||||
MaxBetPlayer *TexasPlayer `json:"maxBetPlayer"`
|
||||
Round string `json:"round"`
|
||||
Folded int `json:"folded"`
|
||||
AllIn int `json:"allIn"`
|
||||
}
|
||||
|
||||
func (g *Texas) Clean() {
|
||||
if g != nil {
|
||||
for _, p := range g.Players {
|
||||
close(p.State)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Texas) NextPlayer(id int64) *TexasPlayer {
|
||||
if g == nil || len(g.Players) == 0 {
|
||||
return nil
|
||||
}
|
||||
idx := -1
|
||||
for i, a := range g.Players {
|
||||
if a.ID == id {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
return g.Players[0]
|
||||
}
|
||||
return g.Players[(idx+1)%len(g.Players)]
|
||||
}
|
||||
|
||||
func (g *Texas) Player(id int64) *TexasPlayer {
|
||||
for _, p := range g.Players {
|
||||
if p.ID == id {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Texas) SBPlayer() *TexasPlayer {
|
||||
return g.Players[g.SB]
|
||||
}
|
||||
|
||||
func (g *Texas) BBPlayer() *TexasPlayer {
|
||||
return g.Players[g.BB]
|
||||
}
|
||||
|
||||
func (g *Texas) Bet(player *TexasPlayer, amount uint) {
|
||||
if amount > 0 {
|
||||
player.Bet(amount)
|
||||
g.Pot += amount
|
||||
if player.Amount() == 0 {
|
||||
player.AllIn = true
|
||||
g.AllIn++
|
||||
}
|
||||
}
|
||||
if g.MaxBetPlayer == nil {
|
||||
g.MaxBetPlayer = player
|
||||
}
|
||||
if player.Bets > g.MaxBetAmount {
|
||||
g.MaxBetAmount = player.Bets
|
||||
g.MaxBetPlayer = player
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Texas) RoundEnd(currentPlayerId int64) bool {
|
||||
if g.AllIn == len(g.Players) ||
|
||||
g.AllIn+g.Folded == len(g.Players) ||
|
||||
(g.MaxBetPlayer != nil && g.MaxBetPlayer.ID == currentPlayerId) {
|
||||
return true
|
||||
}
|
||||
// all players except one folded or allin
|
||||
if g.AllIn+g.Folded == len(g.Players)-1 {
|
||||
isEnd := true
|
||||
for _, p := range g.Players {
|
||||
if p.Folded {
|
||||
continue
|
||||
}
|
||||
if p.Bets != g.MaxBetAmount {
|
||||
isEnd = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return isEnd
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type TexasPlayer struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
State chan int `json:"state"`
|
||||
Hand model.Pokers `json:"hand"`
|
||||
Bets uint `json:"bets"`
|
||||
Folded bool `json:"folded"`
|
||||
AllIn bool `json:"allIn"`
|
||||
}
|
||||
|
||||
func (p *TexasPlayer) Reset() {
|
||||
p.Bets = 0
|
||||
p.Folded = false
|
||||
p.AllIn = false
|
||||
p.Hand = nil
|
||||
p.State = make(chan int, 1)
|
||||
}
|
||||
|
||||
func (p *TexasPlayer) Amount() uint {
|
||||
return GetPlayer(p.ID).Amount
|
||||
}
|
||||
|
||||
func (p *TexasPlayer) Bet(amount uint) {
|
||||
p.Bets += amount
|
||||
GetPlayer(p.ID).Amount -= amount
|
||||
}
|
||||
|
||||
func (p *TexasPlayer) Add(amount uint) {
|
||||
GetPlayer(p.ID).Amount += amount
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/feel-easy/uno/card"
|
||||
"github.com/feel-easy/uno/card/color"
|
||||
"github.com/feel-easy/uno/event"
|
||||
"github.com/feel-easy/uno/game"
|
||||
"github.com/ratel-online/core/log"
|
||||
"github.com/ratel-online/server/consts"
|
||||
)
|
||||
|
||||
type UnoGame struct {
|
||||
Room *Room `json:"room"`
|
||||
Players []int `json:"players"`
|
||||
States map[int]chan int `json:"states"`
|
||||
Game *game.Game `json:"game"`
|
||||
}
|
||||
|
||||
func (ug *UnoGame) HavePlay(player *Player) bool {
|
||||
for _, id := range ug.Players {
|
||||
if id == int(player.ID) && player.online {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (un *UnoGame) NeedExit() bool {
|
||||
return un.Room.Players <= 1
|
||||
}
|
||||
|
||||
func (un *UnoGame) Clean() {
|
||||
if un != nil {
|
||||
for _, state := range un.States {
|
||||
close(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type UnoPlayer struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func NewUnoPlayer(p *Player) game.Player {
|
||||
return &UnoPlayer{
|
||||
ID: int(p.ID),
|
||||
Name: p.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func (up *UnoPlayer) PlayerID() int {
|
||||
return up.ID
|
||||
}
|
||||
|
||||
func (up *UnoPlayer) NickName() string {
|
||||
return up.Name
|
||||
}
|
||||
|
||||
func contains(cards []card.Card, searchedCard card.Card) bool {
|
||||
for _, card := range cards {
|
||||
if card.Equal(searchedCard) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (up *UnoPlayer) NotifyCardsDrawn(cards []card.Card) {
|
||||
p := getPlayer(int64(up.ID))
|
||||
getPlayer(p.ID).WriteString(fmt.Sprintf("You drew %s!\n", cards))
|
||||
}
|
||||
|
||||
func (up *UnoPlayer) NotifyNoMatchingCardsInHand(lastPlayedCard card.Card, hand []card.Card) {
|
||||
p := getPlayer(int64(up.ID))
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString(fmt.Sprintf("%s, none of your cards match %s! \n", p.Name, lastPlayedCard))
|
||||
buf.WriteString(fmt.Sprintf("Your hand is %s \n", hand))
|
||||
getPlayer(p.ID).WriteString(buf.String())
|
||||
}
|
||||
|
||||
func (up *UnoPlayer) OnFirstCardPlayed(payload event.FirstCardPlayedPayload) {
|
||||
p := getPlayer(int64(up.ID))
|
||||
Broadcast(p.RoomID, fmt.Sprintf("First card is %s\n", payload.Card))
|
||||
}
|
||||
|
||||
func (up *UnoPlayer) OnCardPlayed(payload event.CardPlayedPayload) {
|
||||
p := getPlayer(int64(up.ID))
|
||||
Broadcast(p.RoomID, fmt.Sprintf("%s played %s!\n", payload.PlayerName, payload.Card))
|
||||
}
|
||||
|
||||
func (up *UnoPlayer) OnColorPicked(payload event.ColorPickedPayload) {
|
||||
p := getPlayer(int64(up.ID))
|
||||
Broadcast(p.RoomID, fmt.Sprintf("%s picked color %s!\n", payload.PlayerName, payload.Color))
|
||||
}
|
||||
|
||||
func (up *UnoPlayer) OnPlayerPassed(payload event.PlayerPassedPayload) {
|
||||
p := getPlayer(int64(up.ID))
|
||||
Broadcast(p.RoomID, fmt.Sprintf("%s passed!\n", payload.PlayerName))
|
||||
}
|
||||
|
||||
func (up *UnoPlayer) PickColor(gameState game.State) color.Color {
|
||||
p := getPlayer(int64(up.ID))
|
||||
loopCount := 0
|
||||
for {
|
||||
loopCount++
|
||||
if loopCount%100 == 0 {
|
||||
log.Infof("[UnoPlayer.PickColor] Player %d loop count: %d\n", up.ID, loopCount)
|
||||
}
|
||||
p = getPlayer(p.ID)
|
||||
p.WriteString(fmt.Sprintf(
|
||||
"Select a color: %s, %s, %s or %s ? \n",
|
||||
color.Red,
|
||||
color.Yellow,
|
||||
color.Green,
|
||||
color.Blue,
|
||||
))
|
||||
colorName, err := p.AskForString(consts.PlayTimeout)
|
||||
if err != nil {
|
||||
if err == consts.ErrorsTimeout {
|
||||
return color.Red
|
||||
}
|
||||
p.WriteString(fmt.Sprintf("Unknown color '%s' \n", colorName))
|
||||
continue
|
||||
}
|
||||
chosenColor, err := color.ByName(strings.ToLower(colorName))
|
||||
if err != nil {
|
||||
p.WriteString(fmt.Sprintf("Unknown color '%s' \n", colorName))
|
||||
continue
|
||||
}
|
||||
return chosenColor
|
||||
}
|
||||
}
|
||||
|
||||
func (up *UnoPlayer) Play(playableCards []card.Card, gameState game.State) (card.Card, error) {
|
||||
p := getPlayer(int64(up.ID))
|
||||
Broadcast(p.RoomID, fmt.Sprintf("It's %s turn! \n", p.Name), p.ID)
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString(fmt.Sprintf("It's your turn, %s! \n", p.Name))
|
||||
buf.WriteString(gameState.String())
|
||||
p.WriteString(buf.String())
|
||||
runeSequence := runeSequence{}
|
||||
cardOptions := make(map[string]card.Card)
|
||||
for _, card := range playableCards {
|
||||
label := string(runeSequence.next())
|
||||
cardOptions[label] = card
|
||||
}
|
||||
cardSelectionLines := []string{"Select a card to play:"}
|
||||
for label, card := range cardOptions {
|
||||
cardSelectionLines = append(cardSelectionLines, fmt.Sprintf("%s %s", label, card))
|
||||
}
|
||||
cardSelectionMessage := strings.Join(cardSelectionLines, " \n ") + " \n "
|
||||
loopCount := 0
|
||||
for {
|
||||
loopCount++
|
||||
if loopCount%100 == 0 {
|
||||
log.Infof("[UnoPlayer.Play] Player %d loop count: %d\n", up.ID, loopCount)
|
||||
}
|
||||
p = getPlayer(p.ID)
|
||||
p.WriteString(cardSelectionMessage)
|
||||
selectedLabel, err := p.AskForString(consts.PlayTimeout)
|
||||
if err != nil {
|
||||
if err == consts.ErrorsTimeout {
|
||||
selectedLabel = "A"
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
selectedCard, found := cardOptions[strings.ToUpper(selectedLabel)]
|
||||
if !found {
|
||||
BroadcastChat(p, fmt.Sprintf("%s say: %s\n", p.Name, selectedLabel))
|
||||
continue
|
||||
}
|
||||
if !contains(playableCards, selectedCard) {
|
||||
p.WriteString(fmt.Sprintf("Cheat detected! Card %s is not in %s's hand! \n", selectedCard, p.Name))
|
||||
continue
|
||||
}
|
||||
return selectedCard, nil
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user