diff --git a/core/database.py b/core/database.py index e2a1551..ee6187d 100644 --- a/core/database.py +++ b/core/database.py @@ -54,6 +54,37 @@ class Database: raise return self._conn + def _column_exists(self, table_name: str, column_name: str) -> bool: + """检查表中列是否存在 + + Args: + table_name: 表名 + column_name: 列名 + + Returns: + 是否存在 + """ + cursor = self.conn.cursor() + cursor.execute(f"PRAGMA table_info({table_name})") + columns = [row[1] for row in cursor.fetchall()] + return column_name in columns + + def _add_column_if_not_exists(self, table_name: str, column_name: str, column_def: str): + """安全地添加列(如果不存在) + + Args: + table_name: 表名 + column_name: 列名 + column_def: 列定义(如 "INTEGER" 或 "TEXT DEFAULT ''") + """ + if not self._column_exists(table_name, column_name): + try: + cursor = self.conn.cursor() + cursor.execute(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_def}") + logger.info(f"为表 {table_name} 添加列 {column_name}") + except Exception as e: + logger.warning(f"添加列失败: {e}") + def init_tables(self): """初始化数据库表""" cursor = self.conn.cursor() @@ -166,6 +197,20 @@ class Database: ) """) + # 21点手牌表 + cursor.execute(""" + CREATE TABLE IF NOT EXISTS casino_blackjack_hands ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + hand_data TEXT NOT NULL, + hand_status TEXT DEFAULT 'playing', + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + FOREIGN KEY (session_id) REFERENCES casino_sessions(id) + ) + """) + # 创建索引 cursor.execute(""" CREATE INDEX IF NOT EXISTS idx_casino_bets @@ -177,6 +222,21 @@ class Database: ON casino_sessions(chat_id, game_type, status) """) + cursor.execute(""" + CREATE INDEX IF NOT EXISTS idx_casino_blackjack_hands + ON casino_blackjack_hands(session_id, user_id) + """) + + # 兼容性检查:为casino_sessions表添加新字段 + self._add_column_if_not_exists('casino_sessions', 'current_phase', "TEXT DEFAULT 'betting'") + self._add_column_if_not_exists('casino_sessions', 'blackjack_multiplier', 'REAL DEFAULT 1.5') + + # 兼容性检查:为casino_bets表添加新字段(轮盘和21点专用) + self._add_column_if_not_exists('casino_bets', 'bet_category', "TEXT") + self._add_column_if_not_exists('casino_bets', 'bet_number', 'INTEGER') + self._add_column_if_not_exists('casino_bets', 'bet_value', "TEXT") + self._add_column_if_not_exists('casino_bets', 'hand_status', "TEXT") + logger.info("数据库表初始化完成") # ===== 用户相关操作 ===== @@ -654,9 +714,31 @@ class Database: # ===== 赌场相关操作 ===== + def get_any_active_casino_session(self, chat_id: int) -> Optional[Dict]: + """获取任意活跃的游戏会话(用于单场限制检查) + + Args: + chat_id: 会话ID + + Returns: + 会话信息字典或None + """ + cursor = self.conn.cursor() + cursor.execute(""" + SELECT * FROM casino_sessions + WHERE chat_id = ? AND status = 'open' + ORDER BY id DESC LIMIT 1 + """, (chat_id,)) + row = cursor.fetchone() + + if row: + return dict(row) + return None + def create_casino_session(self, chat_id: int, game_type: str, banker_id: int, min_bet: int, max_bet: int, multiplier: float, - house_fee: float = 0.05) -> int: + house_fee: float = 0.05, current_phase: str = 'betting', + blackjack_multiplier: float = 1.5) -> int: """创建新的赌场游戏会话 Args: @@ -667,6 +749,8 @@ class Database: max_bet: 最大下注金额 multiplier: 赔率 house_fee: 抽水率 + current_phase: 当前阶段(默认'betting') + blackjack_multiplier: 21点黑杰克倍数(默认1.5) Returns: session_id @@ -674,20 +758,28 @@ class Database: cursor = self.conn.cursor() current_time = int(time.time()) - # 检查是否已有活跃的会话 + # 检查是否已有活跃的会话(单场限制:同一chat_id只能有一个活跃游戏) + existing = self.get_any_active_casino_session(chat_id) + if existing: + # 如果已有活跃游戏,返回其ID(保持向后兼容,但实际应该在应用层阻止) + return existing['id'] + + # 检查是否已有相同game_type的活跃会话(向后兼容) cursor.execute(""" SELECT id FROM casino_sessions WHERE chat_id = ? AND game_type = ? AND status = 'open' """, (chat_id, game_type)) - existing = cursor.fetchone() - if existing: - return existing['id'] + existing_same_type = cursor.fetchone() + if existing_same_type: + return existing_same_type['id'] cursor.execute(""" INSERT INTO casino_sessions - (chat_id, game_type, banker_id, min_bet, max_bet, multiplier, house_fee, status, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, 'open', ?) - """, (chat_id, game_type, banker_id, min_bet, max_bet, multiplier, house_fee, current_time)) + (chat_id, game_type, banker_id, min_bet, max_bet, multiplier, house_fee, + status, created_at, current_phase, blackjack_multiplier) + VALUES (?, ?, ?, ?, ?, ?, ?, 'open', ?, ?, ?) + """, (chat_id, game_type, banker_id, min_bet, max_bet, multiplier, house_fee, + current_time, current_phase, blackjack_multiplier)) return cursor.lastrowid @@ -714,16 +806,22 @@ class Database: return None def create_casino_bet(self, chat_id: int, game_type: str, user_id: int, - bet_type: str, amount: int, multiplier: float) -> int: + bet_type: str, amount: int, multiplier: float, + bet_category: str = None, bet_number: int = None, + bet_value: str = None, hand_status: str = None) -> int: """创建下注记录 Args: chat_id: 会话ID game_type: 游戏类型 user_id: 用户ID - bet_type: 下注类型 + bet_type: 下注类型(大小游戏:"大"/"小";21点:"标准"等) amount: 下注金额 multiplier: 赔率 + bet_category: 下注类别(轮盘游戏:["数字"/"颜色"/"奇偶"/"大小"/"区间"]) + bet_number: 数字下注(轮盘游戏:0-36) + bet_value: 下注值(轮盘游戏:如"红色"、"奇数"、"1-12"等) + hand_status: 手牌状态(21点游戏:["playing"/"stood"/"busted"/"blackjack"]) Returns: bet_id @@ -733,9 +831,11 @@ class Database: cursor.execute(""" INSERT INTO casino_bets - (chat_id, game_type, user_id, bet_type, amount, multiplier, status, created_at) - VALUES (?, ?, ?, ?, ?, ?, 'pending', ?) - """, (chat_id, game_type, user_id, bet_type, amount, multiplier, current_time)) + (chat_id, game_type, user_id, bet_type, amount, multiplier, status, created_at, + bet_category, bet_number, bet_value, hand_status) + VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?, ?, ?) + """, (chat_id, game_type, user_id, bet_type, amount, multiplier, current_time, + bet_category, bet_number, bet_value, hand_status)) return cursor.lastrowid @@ -871,6 +971,107 @@ class Database: WHERE chat_id = ? AND game_type = ? AND status = 'open' """, (current_time, chat_id, game_type)) + # ===== 21点手牌管理 ===== + + def create_blackjack_hand(self, session_id: int, user_id: int, + hand_data: List[int], hand_status: str = 'playing') -> int: + """创建21点手牌记录 + + Args: + session_id: 会话ID + user_id: 用户ID(0表示庄家) + hand_data: 手牌列表 + hand_status: 手牌状态 + + Returns: + hand_id + """ + cursor = self.conn.cursor() + current_time = int(time.time()) + hand_data_json = json.dumps(hand_data) + + cursor.execute(""" + INSERT INTO casino_blackjack_hands + (session_id, user_id, hand_data, hand_status, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?) + """, (session_id, user_id, hand_data_json, hand_status, current_time, current_time)) + + return cursor.lastrowid + + def get_blackjack_hand(self, session_id: int, user_id: int) -> Optional[Dict]: + """获取21点手牌 + + Args: + session_id: 会话ID + user_id: 用户ID + + Returns: + 手牌信息字典或None + """ + cursor = self.conn.cursor() + cursor.execute(""" + SELECT * FROM casino_blackjack_hands + WHERE session_id = ? AND user_id = ? + ORDER BY id DESC LIMIT 1 + """, (session_id, user_id)) + row = cursor.fetchone() + + if row: + hand_dict = dict(row) + hand_dict['hand_data'] = json.loads(hand_dict['hand_data']) + return hand_dict + return None + + def update_blackjack_hand(self, session_id: int, user_id: int, + hand_data: List[int], hand_status: str): + """更新21点手牌 + + Args: + session_id: 会话ID + user_id: 用户ID + hand_data: 手牌列表 + hand_status: 手牌状态 + """ + cursor = self.conn.cursor() + current_time = int(time.time()) + hand_data_json = json.dumps(hand_data) + + cursor.execute(""" + UPDATE casino_blackjack_hands + SET hand_data = ?, hand_status = ?, updated_at = ? + WHERE session_id = ? AND user_id = ? + """, (hand_data_json, hand_status, current_time, session_id, user_id)) + + def get_all_blackjack_hands(self, session_id: int) -> Dict[int, Dict]: + """获取所有21点手牌(用于结算) + + Args: + session_id: 会话ID + + Returns: + 手牌字典 {user_id: {'cards': [...], 'status': ...}, ...} + """ + cursor = self.conn.cursor() + cursor.execute(""" + SELECT * FROM casino_blackjack_hands + WHERE session_id = ? + ORDER BY user_id, id DESC + """, (session_id,)) + rows = cursor.fetchall() + + hands_dict = {} + for row in rows: + hand_dict = dict(row) + user_id = hand_dict['user_id'] + # 只保留最新的手牌(每个用户) + if user_id not in hands_dict: + hands_dict[user_id] = { + 'cards': json.loads(hand_dict['hand_data']), + 'status': hand_dict['hand_status'] + } + + return hands_dict + def close(self): """关闭数据库连接""" if self._conn: