上周末写了一个德州牌桌demo,桌子有点大,所以我把一个桌子分成了两半,一半是处理用户请求的,包括:加注、看牌、弃牌、跟注、all in等。一半是牌桌自己事件,包括:抽取庄家位,发底牌,发翻牌,发转牌,发河牌,算奖等。其实大部分方法来说都是一两句代码,分出方法是为了维护方便,也是为了能好意思贴出来。
先看这个牌桌这半个类吧,写了这么几年代码,总结来说:1、代码应该简洁;2、代码要有思想;3、代码逻辑性强;4、代码模块要独立。虽然我不敢说自己的代码真能做到,但我一直是往这些方面靠拢。感觉这个牌桌有这么几点自我感觉还行:1、游戏每个事件都有不同方法;2、游戏中几乎没有跟游戏无关的代码(除了事件监听和发送)。其实做事件的目的是为了通知用户赛桌的变化和对赛事包括牌谱持久化,牌谱分析统计等等。
代码中存在一些如M、I的模块,还有Config等涉及公司机密,就不方便贴出来了,M是定义的事件类型;I是定义的消息中的参数名。Config中包括一些状态定义等等,缺少也不会影响代码阅读,自己补充一点点就能跑起来。
先看一下,牌桌测试程序吧,这个就是常说的测试案例,有这么一个代码就好了,你可以随意修改服务,修改完成运行一次就可以知道服务有么有什么问题:
gserv = Game.GameServer.getInstance()
t = Game.Table.Table('id123', 6, 5)
t.match_type = Game.Config.IMMEDIATE
t.init_event()
t.user_join({'u1' : [1000, 1],
'u2' : [1000, 2],
'u3' : [1000, 3],
'u4' : [1000, 4],
'u5' : [1000, 5]})
gserv.start().join_table(t)
n, j = 2, 1
while not Object.END_EVENT.isSet():
if t.wait_next_uid != 0 and n>0:
gserv.do_event(t.table_id, t.wait_next_uid, M.CALL, {})
n -= 1
if t.status == 'turn' and j == 1:
gserv.do_event(t.table_id, t.wait_next_uid, M.RAISE, {'chips':100})
j = 0
time.sleep(1)
代码贴的实在是有点多了,关于GameServer我就再写一篇来描述消息接收和游戏轮询做介绍吧
#coding=gbk
import time
import logging
import M, I
import Code
import Event
import Config
import Poker
import TableUser
import EventHelper
class Table(TableUser.TableUser):
def __init__(self, table_id, seat_num = 6, action_time = 20):
TableUser.TableUser.__init__(self)
self.table_id = table_id #桌子id
self.table_name = '' #桌子名称
self.table_desc = '' #桌子简介
self.table_poker = [] #桌上当前牌
self.match_id = None #牌桌所属赛事id
self.match_type = None #牌桌所属赛事类型
self.seat_num = seat_num #当前桌子座位数
self.action_time = action_time #玩家行动超时时间
self._poker = None #扑克牌队列
self.table_info = { #牌桌相关信息
'bblind':100, 'sblind':50, 'ante':0, 'dealer_uid':0,
'bblind_uid':0, 'sblind_uid':0, 'dealer_uid':0}
self.status_list = [Config.START_STATUS, Config.BLIND_STATUS, Config.PREFLOP_STATUS,
Config.FLOP_STATUS, Config.TURN_STATUS, Config.DRIVE_STATUS,
Config.PRIZE_STATUS, Config.GAMEOVER_STATUS]
self.free_seat = range(1, seat_num+1) #空座位列表[座位号]
self.pot_info = [] #奖池信息
self.pot_uinfo = [] #奖池对应的用户信息
self.status = Config.INIT_STATUS #初始化状态
def init_event(self):
'''初始化事件'''
TableUser.TableUser.init_event(self)
self.event.add_event_listener(Event.PREFLOP_COMPLETE, EventHelper.on_preflop_msg)
self.event.add_event_listener(Event.FLOP_COMPLETE, EventHelper.on_flop_msg)
self.event.add_event_listener(Event.TURN_COMPLETE, EventHelper.on_turn_msg)
self.event.add_event_listener(Event.DRIVE_COMPLETE, EventHelper.on_drive_msg)
self.event.add_event_listener(Event.BLIND_COMPLETE, EventHelper.on_blind_msg)
self.event.add_event_listener(Event.READY_COMPLETE, EventHelper.on_table_ready)
return self
def reset(self):
'''重置桌子信息'''
self.table_poker = []
self.wait_next_uid = 0
self.user_max_chips = 0
self._poker = Poker.get_poker_queue()
def get_dealer_uid(self):
'''获取庄家位子'''
import random
return random.choice(self.cplayer_list)
def first_blind_uid(self):
'''第一次设置大小盲'''
dealer_uid = self.get_dealer_uid()
plen = len(self.cplayer_list)
dindex = self.cplayer_list.index(dealer_uid)
bindex = (dindex+2) % plen
self.table_info['sblind_uid'] = self.cplayer_list[(dindex+1) % plen]
self.table_info['bblind_uid'] = self.cplayer_list[(dindex+2) % plen]
self.table_info['dealer_uid'] = dealer_uid
self.iplayer_list = self.cplayer_list[bindex+1:] + self.cplayer_list[0:bindex+1]
def set_blind_uid(self):
'''设置盲注相关信息'''
lbb = self.table_info['bblind_uid']
if not lbb: #如果是第一手
self.first_blind_uid()
return None
bblind_uid = sblind_uid = 0
if lbb in self.cplayer_list:
sblind_uid = lbb
else: #如果上一手的大盲离开
sblind_uid = 0
bindex = (self.cplayer_list.index(lbb) + 1) % len(self.cplayer_list)
bblind_uid = self.cplayer_list[bindex]
self.table_info['sblind_uid'] = sblind_uid
self.table_info['bblind_uid'] = bblind_uid
dindex = (self.cplayer_list.index(sblind_uid) - 1) % len(self.cplayer_list)
self.table_info['dealer_uid'] = self.cplayer_list[dindex]
self.iplayer_list = self.cplayer_list[bindex+1:] + self.cplayer_list[0:bindex+1]
def start(self):
'''开始游戏'''
#检查是否可以开始比赛
self.cplayer_list = self.get_player_list()
if len(self.cplayer_list) < self.min_player_num:
self.status = Config.INIT_STATUS
return False
logging.info('[%s] start game!', self.table_id)
self.reset()
self.status = Config.BLIND_STATUS
event_content = { 'cplayer_list':self.cplayer_list,
'player_dict' :self.player_dict,
'table_id' :self.table_id}
self.event.dispatch_event(Event.READY_COMPLETE, content = event_content)
return False
def blind(self):
'''设置大小盲注'''
self.set_blind_uid()
logging.info('[%s] set blind over!:%s', self.table_id, self.table_info)
return False
def cut_card(self):
'''切牌'''
ret = self._poker.get()
logging.debug('[cut_card]:%s', ret)
return ret
def pre_flop(self):
'''底牌圈'''
self.cut_card()
for pos in self.cplayer_list:
self.player_dict[pos].poker_list = [self._poker.get(), self._poker.get()]
event_content = {'cplayer_list':self.cplayer_list,
'player_dict' :self.player_dict,
'table_id' :self.table_id}
self.event.dispatch_event(Event.PREFLOP_COMPLETE, content = event_content)
self.do_blind_chips() #扣除大小盲
return True
def flop(self):
'''翻牌圈'''
self.cut_card()
self.table_poker.extend([self._poker.get(), self._poker.get(), self._poker.get()])
event_content = {'cplayer_list':self.cplayer_list,
'table_poker':self.table_poker,
'table_id' :self.table_id}
self.event.dispatch_event(Event.FLOP_COMPLETE, content = event_content)
return True
def turn(self):
'''转牌圈'''
self.cut_card()
self.table_poker.append(self._poker.get())
event_content = {'cplayer_list':self.cplayer_list,
'table_poker':self.table_poker,
'table_id' :self.table_id}
self.event.dispatch_event(Event.TURN_COMPLETE, content = event_content)
return True
def drive(self):
'''河牌圈'''
self.cut_card()
self.table_poker.append(self._poker.get())
event_content = {'cplayer_list':self.cplayer_list,
'table_poker':self.table_poker,
'table_id' :self.table_id}
self.event.dispatch_event(Event.DRIVE_COMPLETE, content = event_content)
return True
def prize(self):
'''派奖'''
_uinfo = {}
for uid in self.cplayer_list:
_player = self.player_dict[uid]
if _player.status != Config.FOLD:
_uinfo[uid] = _player.poker_list + self.table_poker
ret = None
if len(_uinfo) > 1:
ret = Poker.compare(_uinfo)
print _uinfo
logging.info('[%s] prize :%s', self.table_id, ret)
return False
def game_over(self):
'''游戏结束'''
for i in self.cplayer_list:
player = self.player_dict[i]
player.poker_list = []
logging.info('game_over!')
return False
def _do_blind_chips(self, type, uchips = None):
'''扣除盲注'''
player = self.player_dict[self.table_info[type+'_uid']]
bchips = uchips and uchips or self.table_info[type]
info = {'chips':bchips}
self.user_raise(self.table_info[type+'_uid'], info)
return bchips
def do_blind_chips(self):
'''扣除大小盲注'''
schips = self._do_blind_chips('sblind') #扣除小盲注
splayer = self.player_dict[self.table_info['sblind_uid']]
#当前只有大小盲,且小盲all in
if len(self.cplayer_list) == 2 and splayer.status == Config.ALL_IN:
bchips = self._do_blind_chips('bblind', schips) #扣除大盲注
else:
bchips = self._do_blind_chips('bblind') #扣除大盲注
event_content = {'cplayer_list':self.cplayer_list,
'table_info' :self.table_info,
'blind_info' :{self.table_info['bblind_uid']:bchips,
self.table_info['sblind_uid']:schips},
'table_id' :self.table_id}
self.event.dispatch_event(Event.BLIND_COMPLETE, content = event_content)
def check(self, data = {}):
'''服务检查'''
if self.status == Config.INIT_STATUS: #初始态
return None
if data.get('uid', None): #用户有请求
self.player_bet(data['uid'], data['event'], data['info'])
if self.wait_next_uid == 0 : #做游戏事情
if self._check_game_over():
self.status = Config.PRIZE_STATUS
if not hasattr(self, self.status):
self.error('error status attr:%s', self.status)
return None
if getattr(self, self.status)():
self._turn_player(self._get_wait_next())
self.status = self._get_next_status() #跳转到下一状态
else: #判断用户状态
player = self.player_dict[self.wait_next_uid]
if time.time() > player.out_time: #超时
player.status = Config.NURSE
if player.status == Config.NURSE: #托管中
self.user_nurse(self.wait_next_uid)
def error(self, msg = ''):
logging.info('error:%s', msg)
####################################################################################
def _check_game_over(self):
'''判断游戏是否完成'''
if len(self.iplayer_list) - self.fold_unum == 1: #只剩下一个用户
return True
return False
def _get_next_status(self):
'''获取下一个游戏状态'''
if self.status == self.status_list[-1]:
return self.status
sindex = self.status_list.index(self.status)
return self.status_list[sindex + 1]
下面在来看一下,上面类中继承的TableUser.py模块,该模块主要为了响应用户事件,包括用户超时自动设为托管状态等。
#coding=gbk
import time
import logging
import M, I
import Code
import Event
import Player
import Config
import EventHelper
class TableUser:
'''桌上玩家动作'''
def __init__(self):
self.player_dict = {} #当前牌桌用户列表 {玩家uid:player}
self.cplayer_list = [] #当前玩家列表[玩家uid]
self.nplayer_list = [] #新加入玩家list[玩家uid]
self.iplayer_list = [] #当前说话玩家顺序
self.fold_unum = 0 #弃牌用户数
self.wait_next_uid = 0 #等待的出价玩家uid
self.user_max_chips = 0 #用户最大出价
self.min_player_num = 2 #最小玩家数
self.free_seat = [] #空座位列表[座位号]
self.event = Event.Event() #相关事件句柄
self.user_method_conf = {M.CALL:self.user_call, M.RAISE:self.user_raise,
M.FOLD:self.user_fold, M.CHECK:self.user_check,
M.ALL_IN:self.user_all_in}
def init_event(self):
'''初始化事件'''
self.event.add_event_listener(Event.USER_JOIN, EventHelper.on_user_join)
def get_player_list(self):
'''初始化当前玩家列表'''
_cplayer_list = []
for uid, player in self.player_dict.items():
if player.chips > 0 : #取出有筹码的用户
player.unplay_times = 0
_cplayer_list.append(uid)
else:
player.unplay_times += 1
if self.cplayer_list: #不是第一手,就有新手列表
self.nplayer_list = set(_cplayer_list) - set(self.cplayer_list)
_cplayer_list.sort(cmp=lambda x,y:self.player_dict[x].seat_pos-self.player_dict[y].seat_pos)
return _cplayer_list
def _user_join(self, uid, chips=0, seat_pos=None):
'''玩家加入牌桌'''
if not self.free_seat: #没有空座
return Code.NOFREE_SEAT
if not seat_pos: #分配座位号
seat_pos = self.free_seat[0]
elif seat_pos not in self.free_seat: #座位已经被占用
return Code.EXIST_PLAYER
player = Player.Player(uid, chips, Config.OK)
player.seat_pos = seat_pos
self.player_dict[uid] = player
self.free_seat.remove(seat_pos)
event_content = {'info' :player,
'player_dict':self.player_dict,
'player_dict' :self.player_dict,
'table_id' :self.table_id}
self.event.dispatch_event(Event.USER_JOIN, content = event_content)
return Code.OK
def user_join(self, userdict = {'uid':['chips', 'seat_pos']}):
'''用户加入牌桌'''
code = Code.OK
for uid, uinfo in userdict.items():
code = self._user_join(uid, uinfo[0], uinfo[1])
if self.status == Config.INIT_STATUS and self.match_type == Config.IMMEDIATE:
self.start() #开始比赛
return code
def player_bet(self, uid, event, info):
'''玩家下注'''
if self.wait_next_uid != uid:
return Code.NOTURN_YOU #还没轮到该用户
if uid not in self.cplayer_list:
return Code.NOPLAYER #用户非法
if self.player_dict[uid].status in [Config.FOLD, Config.ALL_IN]:
return Code.NOPRIM_DO #没有说话权利
if not self.user_method_conf.has_key(event):
return Code.DO_ERROR #没有该玩家事件
ret = self.user_method_conf[event](uid, info)
if ret is not True:
return ret
self.check_game(uid)
return True
def check_game(self, uid):
'''做系统判断'''
if self._check_street(uid):
self._reset_street() #重置这一街情况
self.wait_next_uid = 0
return None
next_uid = self._get_wait_next()
self._turn_player(next_uid)
def user_call(self, uid, info = None):
'''玩家跟注'''
player = self.player_dict[uid]
uchip = sum(player.bid_list)
ichips = 0
if player.chips+uchip > self.user_max_chips:
ichips = self.user_max_chips - uchip
player.chips -= ichips
player.bid_list.append(ichips)
logging.info('[%s][%s] call :%s', self.table_id, uid, ichips)
else: #筹码不够,all in
self.user_all_in(uid)
return True
def user_raise(self, uid, info = None):
'''玩家加注'''
player = self.player_dict[uid]
chips = info.get('chips')
if player.chips > chips:
player.chips -= chips
player.bid_list.append(chips)
self.user_max_chips = max(chips, self.user_max_chips)
logging.info('[%s][%s] raise :%s', self.table_id, uid, chips)
else: #筹码不够,all in
self.user_all_in(uid)
return True
def user_fold(self, uid, info = None):
'''玩家弃牌'''
self.player_dict[uid].status = Config.FOLD
self.fold_unum += 1
logging.info('[%s][%s] fold!', self.table_id, uid)
return True
def user_check(self, uid, info = None):
'''玩家看牌'''
player = self.player_dict[uid]
uchip = sum(player.bid_list)
print 'user_check test[%s, %s, %s]' % (uid, uchip, self.user_max_chips)
if uchip >= self.user_max_chips:
logging.info('[%s][%s] check!', self.table_id, uid)
return True
return Code.NOPRIM_CHECK #没有权限看牌
def user_all_in(self, uid, info = None):
'''玩家ALL IN'''
player = self.player_dict[uid]
chips = player.chips
player.chips = 0
player.status = Config.ALL_IN
player.bid_list.append(chips)
self.user_max_chips = max(chips, self.user_max_chips)
logging.info('[%s][%s] all in :%s', self.table_id, uid, chips)
return True
def user_nurse(self, uid):
'''玩家智能托管'''
player = self.player_dict[uid]
if self.user_check(uid) is not True: #看牌失败
self.user_fold(uid)
self.check_game(uid)
####################################################################
def _turn_player(self, uid):
'''轮到某玩家说话'''
logging.info('status:%s, wait:%s', self.status, uid)
self.wait_next_uid = uid
if uid == 0:
return uid
player = self.player_dict[uid]
player.out_time = time.time()+self.action_time
def _reset_street(self):
'''一街完成重置'''
self.user_max_chips = 0
uchips = 0 #统计底池
for i in self.cplayer_list:
player = self.player_dict[i]
uchips += sum(player.bid_list)
player.bid_list = []
self.pot_info.append(uchips)
def _check_street(self, uid):
'''检查一街是否完成'''
if uid != self.iplayer_list[-1]:
return False
for i in self.cplayer_list:
player = self.player_dict[i]
if player.status in [Config.FOLD, Config.ALL_IN]:
continue
if sum(player.bid_list) < self.user_max_chips:
return False
return True
def _get_wait_next(self):
'''设置当前轮到下一玩家'''
index = 0
if self.wait_next_uid:
index = (self.iplayer_list.index(self.wait_next_uid)+1) % len(self.iplayer_list)
next_uid = 0
print 'iplayer_list:%s, index:%s, nuid:%s ' % (self.iplayer_list, index, self.wait_next_uid)
for uid in self.iplayer_list[index:]+self.iplayer_list[0:index]:
player = self.player_dict[uid]
if player.status not in [Config.FOLD, Config.ALL_IN]:
next_uid = uid
break
if self.wait_next_uid == next_uid:
next_uid = 0
return next_uid