尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Python Pygame井字棋进阶:状态管理、计分系统与UI交互实战

Python Pygame井字棋进阶:状态管理、计分系统与UI交互实战
📅 发布时间:2026/7/29 5:40:40

1. 项目概述:从简单到复杂的井字棋进化

井字棋,这个几乎每个人童年都玩过的三连棋游戏,在编程入门教程里也常常作为第一个图形化小项目的练手选择。但大多数教程止步于一个能轮流下棋、能判断胜负的基础版本,玩上几局就索然无味了。今天,我想分享的是如何用 Python 的 Pygame 库,把一个“玩具级”的井字棋,打磨成一个功能完整、体验流畅的“产品级”小游戏。我们不仅仅要实现棋盘绘制和胜负逻辑,更要为它注入灵魂:一个持续追踪战况的计分系统、一个能随时清空战场重新对弈的“重新开始”功能,以及精准判断“平局”的智能逻辑。这不仅仅是几行代码的堆砌,更是对游戏状态管理、事件驱动编程和用户体验设计的一次深入实践。无论你是刚学完 Python 基础语法,想找个项目练手巩固;还是已经对 Pygame 有初步了解,希望提升项目架构能力,这个从简单到复杂的进化过程,都能让你收获颇丰。你会发现,实现核心玩法只是起点,让游戏“好玩”和“耐玩”,才是更有挑战性的部分。

2. 整体设计与核心思路拆解

在动手写代码之前,清晰的顶层设计能避免后期陷入混乱的调试。我们这个复杂井字棋的核心目标很明确:在基础的双人对战功能上,增加计分、重新开始和智能平局判断。这三大功能看似独立,实则都紧密围绕着同一个核心——游戏状态(Game State)的管理。

2.1 状态驱动的游戏架构

传统的简单井字棋,状态可能就是一个 3x3 的二维列表,记录‘X’、‘O’和空位。但在我们的版本中,状态必须升级为一个更复杂的对象。我将其设计为几个关键部分:

  1. 棋盘状态(Board):依然是 3x3 的网格,存储当前棋局。
  2. 当前玩家(Current Player):记录本轮该谁下棋(‘X’ 或 ‘O’)。
  3. 游戏阶段(Game Phase):这是关键。它至少需要区分“进行中(PLAYING)”、“已结束(ENDED)”(包括胜负和平局)和“等待重置(IDLE)”?不,更优雅的方式是,将“重新开始”视为对当前所有状态的一次重置,而非一个独立阶段。因此,阶段可以简化为PLAYING和GAME_OVER。
  4. 计分板(Scoreboard):一个独立于单局棋局的状态,需要持久化记录两位玩家的胜利次数。它应该在游戏生命周期内一直存在,不随单局游戏重置而清零(除非玩家主动重置计分)。

基于这个状态模型,我们的游戏主循环逻辑就清晰了:

  • 在PLAYING阶段:响应鼠标点击,更新棋盘和当前玩家,并检查是否触发GAME_OVER(胜利或平局)。若胜利,则更新计分板。
  • 在GAME_OVER阶段:停止响应落子事件,转而响应“重新开始”按钮的点击。点击后,重置棋盘状态和当前玩家,并将阶段设回PLAYING,但保留计分板状态。

2.2 Pygame 模块的角色划分

Pygame 是一个功能丰富的库,我们需要合理规划各个模块的职责:

  • pygame.display&pygame.draw:负责窗口创建、棋盘网格、‘X’和‘O’图形的绘制,以及计分板、按钮等UI元素的渲染。
  • pygame.event:核心中的核心。用于捕获鼠标点击事件,并分发给棋盘逻辑模块或按钮逻辑模块处理。
  • pygame.font:用于在屏幕上渲染文字,显示当前玩家提示、胜负信息、平局提示和计分。
  • pygame.Rect:虽然不是一个模块,但Rect对象至关重要。我们将用它来定义棋盘每个格子的点击区域、按钮的点击区域,这是实现交互的基础。

注意:很多新手会尝试用复杂的数学计算来判断鼠标点击了哪个格子,其实用Rect的collidepoint()方法是最直接、最不易出错的方式。为棋盘上的9个格子各自创建一个Rect对象,管理起来非常方便。

2.3 功能耦合与解耦

三个新增功能相互影响:

  • 计分依赖于胜负判断的准确触发。
  • 重新开始需要重置棋盘和游戏阶段,但不能重置计分。
  • 平局判断是触发GAME_OVER的另一个条件,它和胜负判断是并列关系。

因此,在代码组织上,我会将“检查游戏是否结束”作为一个独立的函数。这个函数内部依次检查:1. 是否有玩家获胜;2. 是否棋盘已满且无人获胜(即平局)。根据检查结果,更新游戏阶段和计分板。这样,主循环只需要在每次落子后调用这个检查函数,逻辑就非常清晰。

3. 核心模块实现与代码解析

接下来,我们进入具体的实现环节。我将分模块构建游戏,并解释关键代码段背后的思考。

3.1 环境初始化与常量定义

首先,我们需要设定游戏窗口、颜色和基础状态。将所有可配置的常量放在代码开头是一个好习惯。

import pygame import sys # 初始化 Pygame pygame.init() # 屏幕尺寸和颜色定义 WIDTH, HEIGHT = 600, 700 # 额外的高度用于显示计分板和按钮 GRID_SIZE = 3 CELL_SIZE = WIDTH // GRID_SIZE LINE_WIDTH = 10 BOARD_OFFSET = 50 # 棋盘上边距 # 颜色定义 (RGB) BACKGROUND = (28, 40, 56) # 深蓝灰色背景 GRID_COLOR = (86, 98, 112) # 网格线颜色 X_COLOR = (242, 76, 76) # 红色 X O_COLOR = (76, 173, 242) # 蓝色 O TEXT_COLOR = (220, 220, 220) # 文字颜色 BUTTON_COLOR = (67, 126, 161) # 按钮颜色 BUTTON_HOVER_COLOR = (52, 152, 219) # 按钮悬停颜色 # 游戏状态常量 PLAYING = 0 GAME_OVER = 1 # 初始化屏幕 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("增强版井字棋 - 带计分与重置") clock = pygame.time.Clock() font = pygame.font.SysFont(None, 36) small_font = pygame.font.SysFont(None, 28)

这里我特意将高度设为700,为下方的信息显示区预留了100像素的空间。颜色选择上采用了对比度较高的搭配,确保在深色背景上‘X’和‘O’清晰可见,这也是提升游戏视觉体验的一个小细节。

3.2 游戏状态与数据模型

这是游戏的大脑。我们用一个字典或一个类来封装所有状态。这里我使用一个简单的类,更易于理解和管理。

class GameState: def __init__(self): # 棋盘:3x3列表,''为空,'X'或'O'为棋子 self.board = [['' for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)] self.current_player = 'X' # 先手玩家 self.game_phase = PLAYING self.winner = None # 获胜者:'X', 'O', 或 'Draw'(平局) self.scores = {'X': 0, 'O': 0} # 计分板 # 为每个棋盘格子创建Rect,用于点击检测 self.cell_rects = [] for row in range(GRID_SIZE): row_rects = [] for col in range(GRID_SIZE): rect = pygame.Rect( col * CELL_SIZE, BOARD_OFFSET + row * CELL_SIZE, CELL_SIZE, CELL_SIZE ) row_rects.append(rect) self.cell_rects.append(row_rects) # 重新开始按钮的Rect self.restart_button_rect = pygame.Rect(WIDTH//2 - 70, HEIGHT - 60, 140, 40) def reset_board(self): """重置棋盘和单局游戏状态,但保留计分。""" self.board = [['' for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)] self.current_player = 'X' self.game_phase = PLAYING self.winner = None

GameState类集中管理了所有动态数据。cell_rects的预计算是个优化点,避免了在每次点击事件或渲染时重复计算矩形区域。reset_board方法清晰地定义了“重新开始”一局游戏的具体行为:清空棋盘、重置玩家和阶段,但不动scores字典。

3.3 核心游戏逻辑实现

这部分包含落子、胜负判断和平局判断,是游戏规则的核心。

def make_move(state, row, col): """在指定位置落子。""" if state.game_phase != PLAYING: return False if state.board[row][col] != '': return False # 该位置已有棋子 state.board[row][col] = state.current_player # 检查游戏是否结束 check_game_over(state) # 如果游戏未结束,切换玩家 if state.game_phase == PLAYING: state.current_player = 'O' if state.current_player == 'X' else 'X' return True def check_winner(board): """检查当前棋盘是否有获胜者,返回获胜棋子('X'/'O')或None。""" # 检查行 for row in board: if row[0] == row[1] == row[2] != '': return row[0] # 检查列 for col in range(3): if board[0][col] == board[1][col] == board[2][col] != '': return board[0][col] # 检查对角线 if board[0][0] == board[1][1] == board[2][2] != '': return board[0][0] if board[0][2] == board[1][1] == board[2][0] != '': return board[0][2] return None def check_draw(board): """检查是否为平局。""" for row in board: for cell in row: if cell == '': return False # 还有空位,不是平局 return True # 棋盘已满且无获胜者 def check_game_over(state): """综合检查游戏是否结束,并更新状态和计分。""" winner = check_winner(state.board) if winner: state.game_phase = GAME_OVER state.winner = winner state.scores[winner] += 1 # 计分! return if check_draw(state.board): state.game_phase = GAME_OVER state.winner = 'Draw' # 用特殊标记表示平局 # 平局不计分 return

check_game_over函数是状态转换的枢纽。它的执行顺序很重要:先判断胜负,再判断平局。因为如果一方获胜,即使棋盘满了也是获胜优先,不存在“获胜且平局”的状态。当判断为获胜时,立即更新对应玩家的分数,这是实现计分功能的关键一步。平局则只是标记状态,不修改分数。

3.4 图形绘制与用户界面

游戏的视觉反馈和交互入口都在这部分。我们需要绘制棋盘、棋子、状态提示、计分板和按钮。

def draw_board(screen, state): """绘制棋盘网格和已下的棋子。""" # 绘制网格线 for i in range(1, GRID_SIZE): # 垂直线 pygame.draw.line(screen, GRID_COLOR, (i * CELL_SIZE, BOARD_OFFSET), (i * CELL_SIZE, BOARD_OFFSET + GRID_SIZE * CELL_SIZE), LINE_WIDTH) # 水平线 pygame.draw.line(screen, GRID_COLOR, (0, BOARD_OFFSET + i * CELL_SIZE), (GRID_SIZE * CELL_SIZE, BOARD_OFFSET + i * CELL_SIZE), LINE_WIDTH) # 绘制棋子 for row in range(GRID_SIZE): for col in range(GRID_SIZE): center_x = col * CELL_SIZE + CELL_SIZE // 2 center_y = BOARD_OFFSET + row * CELL_SIZE + CELL_SIZE // 2 radius = CELL_SIZE // 3 if state.board[row][col] == 'X': # 绘制X:两条交叉的粗线 offset = radius pygame.draw.line(screen, X_COLOR, (center_x - offset, center_y - offset), (center_x + offset, center_y + offset), LINE_WIDTH) pygame.draw.line(screen, X_COLOR, (center_x + offset, center_y - offset), (center_x - offset, center_y + offset), LINE_WIDTH) elif state.board[row][col] == 'O': # 绘制O:一个圆环 pygame.draw.circle(screen, O_COLOR, (center_x, center_y), radius, LINE_WIDTH) def draw_ui(screen, state, mouse_pos): """绘制用户界面:状态提示、计分板和按钮。""" # 1. 绘制顶部状态提示 status_text = "" if state.game_phase == PLAYING: status_text = f"当前玩家: {state.current_player}" else: if state.winner == 'Draw': status_text = "游戏结束:平局!" else: status_text = f"游戏结束:玩家 {state.winner} 获胜!" status_surface = font.render(status_text, True, TEXT_COLOR) screen.blit(status_surface, (WIDTH // 2 - status_surface.get_width() // 2, 10)) # 2. 绘制计分板 score_text = f"得分 - X: {state.scores['X']} | O: {state.scores['O']}" score_surface = small_font.render(score_text, True, TEXT_COLOR) screen.blit(score_surface, (WIDTH // 2 - score_surface.get_width() // 2, 50)) # 3. 绘制重新开始按钮 button_color = BUTTON_HOVER_COLOR if state.restart_button_rect.collidepoint(mouse_pos) else BUTTON_COLOR pygame.draw.rect(screen, button_color, state.restart_button_rect, border_radius=8) pygame.draw.rect(screen, TEXT_COLOR, state.restart_button_rect, 2, border_radius=8) # 边框 restart_text = small_font.render("重新开始游戏", True, TEXT_COLOR) text_rect = restart_text.get_rect(center=state.restart_button_rect.center) screen.blit(restart_text, text_rect)

UI绘制有几个要点:

  1. 状态提示:根据game_phase和winner动态生成文本,让玩家时刻清楚游戏进展。
  2. 计分板:直接从state.scores字典中读取并渲染,位置固定在棋盘上方。
  3. 按钮交互:通过检测mouse_pos是否在按钮Rect内,来改变按钮颜色,实现简单的悬停效果,这是提升用户体验的重要细节。按钮的边框和圆角让它在视觉上更友好。

3.5 事件处理与主循环

最后,我们将所有模块串联起来,形成游戏的主循环。

def main(): state = GameState() running = True while running: mouse_pos = pygame.mouse.get_pos() # 事件处理 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: # 左键点击 # 检查是否点击了“重新开始”按钮 if state.restart_button_rect.collidepoint(event.pos): state.reset_board() # 如果游戏正在进行,检查是否点击了棋盘格子 elif state.game_phase == PLAYING: for row in range(GRID_SIZE): for col in range(GRID_SIZE): if state.cell_rects[row][col].collidepoint(event.pos): make_move(state, row, col) break # 绘制 screen.fill(BACKGROUND) draw_board(screen, state) draw_ui(screen, state, mouse_pos) pygame.display.flip() clock.tick(60) # 60帧每秒 pygame.quit() sys.exit() if __name__ == "__main__": main()

主循环是典型的“事件处理 -> 状态更新 -> 图形渲染”模式。事件处理部分逻辑清晰:

  1. 优先判断是否点击了“重新开始”按钮。这个判断应放在落子判断之前,因为按钮是叠加在棋盘上方的UI元素,从用户体验上,按钮应具有更高的点击优先级。
  2. 只有在游戏处于PLAYING阶段,才处理棋盘格子的点击,并调用make_move。
  3. make_move函数内部会触发状态检查,可能自动切换游戏阶段和更新分数。

实操心得:事件循环中的逻辑顺序非常重要。我曾把棋盘点击判断放在按钮判断之前,结果导致按钮区域如果与棋盘格子重叠(虽然在这个布局里不会),按钮就会失效。记住一个原则:UI层的事件响应优先级通常应高于游戏内容层。

4. 功能深化与扩展思路

基础版本已经完成,但一个健壮的项目还需要考虑更多细节和可能的扩展方向。

4.1 增强的平局判断与用户体验

我们目前的平局判断check_draw是在棋盘满后触发的。但井字棋有一个特点:有时在最后一步棋落下之前,其实胜负已定或平局已定。一个更智能的判断可以提前给出“即将平局”的提示,但这需要更复杂的算法(如搜索所有可能走法)。对于当前项目,一个更简单的优化是:当棋盘只剩下一个空位,且未分胜负时,在UI上给出“最后一手”的提示,增加游戏的紧张感。

def get_game_hint(state): """获取游戏提示,例如即将平局或必胜点(简易版)。""" empty_cells = [(r, c) for r in range(3) for c in range(3) if state.board[r][c] == ''] if len(empty_cells) == 1 and state.winner is None: return "最后一手!" # 这里可以添加更复杂的提示逻辑,例如检查某玩家再下一子即可获胜 return None

然后在draw_ui函数中,如果游戏仍在进行,可以渲染这个提示信息。

4.2 计分系统的持久化

目前的计分板只在程序运行期间有效,关闭游戏后分数就会清零。为了实现持久化,我们可以将分数保存到本地文件(如JSON格式)。

import json SCORE_FILE = 'tictactoe_scores.json' def load_scores(): """从文件加载计分。""" try: with open(SCORE_FILE, 'r') as f: return json.load(f) except FileNotFoundError: return {'X': 0, 'O': 0} def save_scores(scores): """保存计分到文件。""" with open(SCORE_FILE, 'w') as f: json.dump(scores, f)

在GameState.__init__中,将self.scores = load_scores()。在每次更新分数后(state.scores[winner] += 1),调用save_scores(state.scores)。这样,每次打开游戏,历史战绩都得以保留。

4.3 “重新开始”功能的细化

我们的“重新开始”按钮目前重置了棋盘并清空了winner。但在GAME_OVER阶段,屏幕上显示着获胜信息。玩家点击“重新开始”后,这些信息应该立即消失,棋盘变空。我们的reset_board方法已经做到了这一点。但有时我们可能想要一个“重置计分”的功能。这可以通过在UI上增加第二个按钮来实现,比如“重置计分”,点击后将scores字典归零并保存。这提醒我们,状态管理要清晰:“重新开始游戏”和“重置整个比赛(计分)”是两个不同的操作。

4.4 代码结构与可维护性优化

目前我们将所有函数和主循环放在一个文件中,对于这个小项目是可行的。但如果功能继续增加,更好的做法是进行模块化拆分:

  • game_logic.py:包含GameState类、make_move、check_winner、check_draw等纯逻辑函数。
  • ui_renderer.py:包含draw_board、draw_ui等所有与绘制相关的函数。
  • main.py:只包含Pygame初始化、主循环和事件分发。

这种分离使得逻辑、视图和控制相对独立,便于测试和修改。例如,你可以单独测试game_logic模块,而无需启动Pygame窗口。

5. 常见问题与调试技巧实录

在开发过程中,你几乎一定会遇到下面这些问题。这里记录了我的排查思路和解决方法。

5.1 棋子绘制位置错位或点击区域不准

问题现象:鼠标点击的位置和棋子出现的位置对不上,或者点击没反应。排查步骤:

  1. 检查Rect定义:首先确认cell_rects中每个Rect的坐标和尺寸计算是否正确。打印出第一个和最后一个Rect的(x, y, width, height)看看。
  2. 检查绘制坐标:在draw_board中,确认绘制棋子(‘X’和‘O’)的中心点(center_x, center_y)计算是否与Rect的中心点一致。一个常用的调试方法是,在绘制棋子的代码处,临时增加绘制该格子Rect边框的代码(用鲜艳的颜色),看看边框和棋子是否对齐。
  3. 检查棋盘偏移:确保BOARD_OFFSET在创建Rect和绘制棋子时都被正确使用。Rect的y坐标需要加上BOARD_OFFSET,而绘制网格线时,水平线的y坐标也需要加上这个偏移。

避坑技巧:在开发初期,可以用print语句或临时绘图,将关键的坐标和区域可视化出来。Pygame 的draw.rect函数用width=1参数可以快速绘制一个矩形的边框,是调试的利器。

5.2 游戏状态逻辑混乱,例如赢了还能继续下棋

问题现象:一方获胜后,游戏仍然可以继续落子。根本原因:game_phase状态没有在正确的时间被更新,或者事件处理逻辑没有严格遵循game_phase。解决方案:

  1. 在make_move函数的最开始,检查if state.game_phase != PLAYING: return False。这是第一道防线。
  2. 确保check_game_over函数在判定获胜或平局后,立即将state.game_phase设置为GAME_OVER。
  3. 在主循环的事件处理中,棋盘点击事件的处理必须包裹在if state.game_phase == PLAYING:的条件判断内。

调试方法:在make_move和事件处理的关键分支添加打印语句,输出当前的game_phase和winner,跟踪状态的变化流程。

5.3 计分不更新或更新异常

问题现象:玩家赢了但分数没加,或者平局也加了分。排查步骤:

  1. 确认计分触发点:计分逻辑只应在check_game_over中,当winner为 ‘X’ 或 ‘O’ 时触发。确保平局(winner == 'Draw')时没有执行计分代码。
  2. 检查计分板更新代码:确认是state.scores[winner] += 1,并且winner变量确实是字符串 ‘X’ 或 ‘O’。
  3. 验证胜负判断函数:确保check_winner函数在棋盘出现三连时,返回的是棋子符号 ‘X’/‘O’,而不是True或False。一个常见的错误是写成了return True,这会导致计分时代码state.scores[True]出错。

5.4 按钮点击无响应或悬停效果失灵

问题现象:鼠标移到按钮上颜色不变,或者点击没反应。排查步骤:

  1. 检查Rect位置和大小:打印restart_button_rect的属性,确认它在屏幕上的位置(WIDTH//2 - 70, HEIGHT - 60)和大小(140, 40)是否符合预期。特别是HEIGHT的值,要考虑到你窗口的实际高度。
  2. 检查事件坐标:在MOUSEBUTTONDOWN事件处理中,打印event.pos,看看点击的坐标是否落在按钮的Rect范围内。
  3. 检查绘制顺序:确保按钮绘制在棋盘和其他元素之上(即draw_ui在draw_board之后调用),但这通常不影响点击检测,因为Rect碰撞检测是纯逻辑计算。
  4. 检查悬停逻辑:mouse_pos是否在每一帧都被正确更新(pygame.mouse.get_pos())。悬停颜色的判断state.restart_button_rect.collidepoint(mouse_pos)是否在draw_ui函数中被执行。

5.5 性能问题与代码优化

对于井字棋这种小游戏,性能通常不是问题。但养成好习惯很重要:

  • 避免在循环中创建对象:我们的cell_rects在GameState初始化时一次性创建好,而不是在每次点击或渲染时创建,这是一个好的实践。
  • 减少不必要的绘制:目前我们是每一帧都重绘整个屏幕。对于静态元素多的界面,可以考虑脏矩形优化,但Pygame的screen.fill()和整体重绘在600x700的分辨率下完全不是问题。
  • 字体渲染开销:pygame.font.Font.render()是一个相对耗时的操作。我们应避免在每一帧都渲染相同的文字。可以将静态或变化不频繁的文字表面(如“得分 - X:”这部分)缓存起来,只重新渲染变化的数字部分。

这个项目麻雀虽小,五脏俱全。它涉及了游戏开发的基本范式:状态管理、事件处理、图形渲染和用户交互。当你成功运行它,并和朋友们来上几局有计分比拼的井字棋时,你会发现,编程带来的成就感,不仅在于功能的实现,更在于你创造了一个完整、可交互的体验。试着在此基础上继续扩展吧,比如增加一个简单的AI对手,或者美化UI,让这个经典游戏在你的代码中焕发新的生命力。

相关新闻

  • Python多项式求和编程实战与优化技巧
  • 从零实现C++条件变量:深入理解多线程同步原语
  • AI证书怎么从入门考到进阶?2026人工智能学习与认证路线梳理

最新新闻

  • 智能抄表在能源管理上的用处
  • 超低功耗物联网节点设计:NBM7100A与STM32F042K6优化方案
  • 3步解锁QQ音乐加密文件:Mac用户的QMC格式转换完全指南
  • MOS管驱动感性负载:从反电动势原理到可靠电路设计实践
  • monstra_cve_2020_13384漏洞复现
  • 2026年7月四川省内江市联通单宽带避坑指南!小白怎么选_ - 找卡家园

日新闻

  • 金融舆情监测系统:多语言情感分析与实时可视化技术解析
  • QT C++调用Python异常处理:PyBind11实战与跨语言编程指南
  • A-47双麦回音消除模块:主次麦空间分布与差分连接对ENC性能的影响

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号