
简介「2020年桌游棋牌研究报告.pdf」面向手游从业者、棋牌品类产品经理与市场研究人员用于系统了解桌游棋牌类手游的品类格局与投放思路。报告以综合数据分析结合案例拆解的方式梳理发展历程、市场现状与热门地区并聚焦2020年上榜Top100的热门产品围绕玩法设计、获客路径、留存数据与变现策略展开。压缩包内为单一PDF文件约18.73MB便于检索与横向对照。内容将500余款样本按玩法归为飞行棋、各国象棋、牌九、UNO、黑白棋、纸牌、Backgammon等16类附中英文对照表并逐类说明规则与玩法特征同时提供印度、美国、巴西、土耳其等热门地区的所属大洲与三位代码以及七种玩法与十三个案例的分析。已有151人学习下载适合需要快速建立棋牌赛道认知、复盘品类机会与制定投放策略的读者参考。1. 2020年桌游棋牌研究报告里最值钱的不是那张DAU曲线身边做休闲游戏出海的朋友拿到这份 2020年桌游棋牌研究报告.pdf第一反应基本都是把 DAU 曲线页截图丢进群里说一句「飞行棋还能打」。截图转手三次就没人看了因为曲线只给结论不给口径。这份报告真正能复用的东西是它对 500 款上榜 Top100 样本做过的字段化处理每款游戏有玩法归类、发布年份、地区热度、留存与变现标签16 种玩法的中英文对照也一并列了。它本质上是一份被排版成 PDF 的半结构化数据集而不是一篇读物。适合三类人。做休闲游戏选题的产品同学可以把玩法×地区热度直接当选题矩阵用做竞品监控的运营同学可以拿 2019/12/1–2020/5/31 与 2020/6/1–2020/11/30 这两个 DAU 窗口做环比基准做数据工程的同学可以把表格抽出来落库做成团队能持续更新的看板。难点集中在第三步——报告里的图表是矢量绘制的排行表还有跨页拆行靠鼠标复制粘贴只会拿到一堆错位文本。2. 16种玩法分类体系怎么落成一张可查询的标签表想让这份报告真正服务后续分析第一步不是抽数而是把玩法分类维度定型。分类定不下来后面所有分组统计都是浮沙。2.1 报告口径与真实玩法机制不是一回事报告按玩法把样本归成 16 类飞行棋、各国象棋、牌九、卡罗姆、Okey、各国跳棋、UNO、酒场游戏、聚会游戏、黑白棋、纸牌、Backgammon、井字棋、Rummikub、Connect Four加一个「其它棋牌」。名字看着直白落到字段上麻烦立刻出现。最典型的是「黑白棋」。报告在这一类下面同时放了 Reversi/Othello 式的黑白翻转棋、五子棋案例页写作 Gomoku·S - line five in a row和围棋案例页对应 Go Free。这三种玩法的用户画像、单局时长、变现节奏差得远混成一个 genre 再分组留存均值会被拉平到没有解释力。第二个是「飞行棋」。报告在玩法分类页把传统飞行棋和蛇梯Snakes Ladders并列成两个子玩法到案例层又常把 Ludo King 和 Snakes and Ladders - Ludo Game 放一起谈。Ludo 是印度语境下的四方竞速飞行棋蛇梯是纯运气线性推进两者在南亚、中东的投放素材差异很大。第三个是「酒场游戏」。报告把 Yahtzee with Buddies Dice骰子归到这里而不是聚会游戏理由是核心场景在酒吧、聚会桌。这是业务判断而非规则判断建模时得留出说明字段。结论一个维度不够至少拆成「报告口径」和「真实机制」两层。2.2 建表report_genre 与 mechanic_tag 分开-- 报告口径的一级分类单值用于和报告原文对齐 CREATE TABLE dim_report_genre ( genre_code TEXT PRIMARY KEY, -- ludo / okey / rummikub / othello ... genre_cn TEXT NOT NULL, genre_en TEXT NOT NULL, stage_hint SMALLINT DEFAULT 0 -- 该玩法在哪个阶段成为头部1/2/30 表示未突出 ); -- 游戏主表 CREATE TABLE report_title ( app_name TEXT NOT NULL, release_date DATE, -- 报告有到日的记日只有年份的留 NULL release_year SMALLINT NOT NULL, report_genre TEXT REFERENCES dim_report_genre(genre_code), mechanic_tags TEXT[], -- 真实机制标签可多值 source_page INT, -- 便于回查 PDF 页码 PRIMARY KEY (app_name, release_year) ); -- 地区热度长表DAU 与 IAP 分两行存不做宽表 CREATE TABLE region_heat ( app_name TEXT NOT NULL, region_iso3 CHAR(3) NOT NULL, -- 附录 P142 给的三位代码 metric TEXT NOT NULL CHECK (metric IN (dau,iap)), win_start DATE NOT NULL, win_end DATE NOT NULL, rank_no SMALLINT, PRIMARY KEY (app_name, region_iso3, metric, win_start) );region_heat用长表的原因很直接报告把「地区现状(DAU)」和「地区现状(IAP)」拆成相邻两页同一地区在两张表里的位次经常不一致。宽表拼接时会自然产生大量 NULL后面做排名差分要反复补空。2.3 从榜单标题召回玩法标签报告正文给了 16 种玩法的英文关键词Backgammon、Connect Four、Okey、Rummikub、UNO、Ludo、Snakes Ladders、Checkers、Chinese Chess、Chess、Chinese Checkers、Gomoku、Baduk。这些词可以直接当召回正则。import re PATTERNS { backgammon: r\bbackgammon\b|\bnarde\b|双陆, connect_four: r\bconnect\s*(4|four)\b|\b4\s*in\s*a\s*row\b|四子棋, okey: r\bokey\b|\byuzbir\b|土耳其麻将, rummikub: r\brummikub\b|\brommikub\b|魔力桥, uno: r\buno\b|优诺, ludo: r\bludo\b|飞行棋, snakes: r\bsnakes?\s*(and|)\s*ladders?\b|蛇梯, checkers: r\bcheckers?\b|\bdraughts\b|跳棋, chess_intl: r\bchess\b|国际象棋, chess_cn: rchinese\s*chess|中国象棋|象棋, gomoku: r\bgomoku\b|\bfive\s*in\s*a\s*row\b|五子棋, baduk: r\bbaduk\b|\bgo\sfree\b|\bweiqi\b|围棋, } def tag_mechanics(title: str) - list[str]: 按标题召回机制标签返回可能命中多条交给人工复核 t title.lower() return [k for k, p in PATTERNS.items() if re.search(p, t)]几个必须注意的点chess_intl的正则\bchess\b会命中 Chinese Chess因为 Chess 前面是空格构成词边界。所以判定顺序上必须先跑chess_cn命中就把chess_intl摘掉否则所有中国象棋都会被标成国际象棋。\buno\b对 UNO!TM、UNO!™ 都能命中也不会误伤 Unofficial——uno 后面紧跟 f不构成词边界。土耳其语标题里的 Ö 经 lower() 会保留Okey 本身写法稳定加yuzbir兜底覆盖 101 Yuzbir Okey Plus。报告玩法英文关键词常见混淆判定规则飞行棋Ludo, Snakes Ladders与其它棋牌里的竞速玩法混掷骰推进多枚棋子入场判 Ludo纯线性爬梯判 Snakes黑白棋Reversi, Othello, Gomoku, Baduk五子棋、围棋被并入翻转吃子→Reversi五连成线→Gomoku围空计分→Baduk酒场游戏Dice, Spin the Bottle, Truth or Dare与聚会游戏重叠是否依赖「惩罚/赌注」机制是则归酒场各国象棋Chess, Chinese Chess, Shogi中日象棋区分将死固定走子Shogi 看是否有持驹打入规则其它棋牌Military chess, Sholo Guti兜底类目易被滥用无法归入前 15 类的才落这里需人工确认提示召回结果必须人工复核复核重点就三类——Ludo/Snakes 混标、黑白棋三类混标、酒场游戏误归。表里加tag_confidence和reviewer两个字段这一步别省。3. 把PDF里的DAU与发布年份抽成表的可复现管线分类定好之后才是抽数。这份 PDF 有两类数据规整的排行表和矢量绘制的折线图。前者能直接抽后者只能反算。3.1 表格页先调对线策略import pdfplumber import pandas as pd PDF 2020年桌游棋牌研究报告.pdf TARGET_PAGES range(25, 30) # 玩法现状(DAU)、地区现状(DAU/IAP) 所在页段 rows [] with pdfplumber.open(PDF) as pdf: for pno in TARGET_PAGES: page pdf.pages[pno] tables page.extract_tables({ vertical_strategy: lines, horizontal_strategy: lines, snap_tolerance: 4, # 容忍印刷级偏移超过 6 会把相邻列吸成一条 join_tolerance: 4, }) for t in tables: for r in t: rows.append({page: pno 1, cells: r}) df_raw pd.DataFrame(rows) print(df_raw.head(10))参数说明vertical_strategylines只在页面存在可见表格线时生效这份报告的排行表基本都有细线比text策略稳定得多snap_tolerance建议在 3–5 之间试调过头会把两列合成一列。extract_tables返回list[list[str]]单元格内的换行不会自动合并游戏名常被排版拆成两行比如 Snakes and Ladders - Ludo Game要二次清洗。def clean_cell(v): if v is None: return None return .join(v.replace(\n, ).split()) def flatten(table_rows): 表头 N 行数据的两列结构压成 dict 列表 out, header [], None for cells in table_rows: cells [clean_cell(c) for c in cells] if not any(cells): continue if header is None: header cells continue out.append(dict(zip(header, cells))) return out3.2 曲线页抽不出数值用刻度线做线性反算报告的 DAU 对比图是矢量折线pdfplumber 拿不到数据点PyMuPDF 能拿到绘制指令。import fitz # PyMuPDF doc fitz.open(PDF) page doc[20] # 0-based对应报告 P21 paths page.get_drawings() series_pts [] for p in paths: for item in p[items]: if item[0] l: # l line折线顶点 (x1, y1), (x2, y2) item[1], item[2] series_pts.append((x1, y1, x2, y2)) elif item[0] c: # c cubic bezier平滑曲线 pass # 控制点不能直接换算成数值 print(len(series_pts))get_drawings()返回的 y 是页面坐标从上往下增大和数值方向相反换算时要注意符号。定标靠两条已知刻度的水平网格线def build_scale(y_zero, v_zero, y_base, v_base): y_zero 是数值 v_zero 对应的像素 yy_base 是数值 v_base 对应的像素 y k (v_base - v_zero) / (y_zero - y_base) return lambda y: v_zero k * (y_zero - y) # 例y520 处是 0y180 处是 14,000,000 scale build_scale(y_zero520, v_zero0, y_base180, v_base14_000_000) print(f{scale(312.5):,.0f})注意这张图叠了「DAU 2020/6/1–2020/11/30」「DAU 2019/12/1–2020/5/31」两条折线和一条环比柱左轴是 DAU、右轴是百分比。定标必须分别取左右轴的刻度线混用会得到荒谬结果。bezier 平滑曲线不要硬取控制点误差能到 20%报告里 DAU 折线按年度节点绘制取折线顶点正好。3.3 三阶段划分写进常量表报告把 2008–2020 拆成三段2008–2013 起步、2014–2016 飞行棋反超、2017–2020 玩法多样化。这套划分要落到常量里后面所有分组统计都引用它别散在注释中。STAGES [ (2008, 2013, 1, 起步期各国象棋占头部), (2014, 2016, 2, 飞行棋反超Ludo King 一家独大), (2017, 2020, 3, Rummikub / UNO 新玩法进场), ] def stage_of(year: int) - int: for s, e, idx, _ in STAGES: if s year e: return idx raise ValueError(fyear out of range: {year}) # 报告给出的发布数量2014:146 2015:75 2016:67合计 288 print(sum({2014: 146, 2015: 75, 2016: 67}.values())) # 288抽取项报告值校验方式不一致时的处理第二阶段发布数量1467567288与报告「总计288」比对差值2 回查表格是否漏行第三阶段发布数量13518816446533与「500样本」表述比对口径冲突记入 source_noteDAU 窗口2019/12/1–2020/5/31检查两条曲线是否同窗口混窗口禁止做差分玩法枚举16 类与分类页逐项比对缺项时补 dim 表第三行那个冲突值得单独说。报告前言说样本是「500 款 2020 年上榜 Top100 的热门游戏」而 2017–2020 四年发布数量相加得到 533已经超过「500」的表述。说明这两个数字不是同一口径可能一个是全量榜单去重、一个是按发布年份计数。落库时给每个统计数字加source_page和source_note两个字段把冲突如实记下比强行圆上更靠得住。4. 地区×玩法热度矩阵三位代码对齐与DAU/IAP双口径报告的地区分析集中在印度、美国、巴西、土耳其、印尼、俄罗斯、墨西哥、越南、巴基斯坦九个市场附录 P142 给了所属大洲和三位代码。这部分最容易出错的是主键。4.1 用 ISO 3166-1 alpha-3 做主键报告地区名ISO3大洲UN M49对齐说明印度IND亚洲与印尼 IDN 极易混入库必须用代码美国USA北美洲州级数据不在报告范围巴西BRA南美洲葡语区标题清洗时注意重音字符土耳其TUR亚洲横跨欧亚M49 归西亚印尼IDN亚洲Indonesia不是 India俄罗斯RUS欧洲与独联体其它市场分开统计墨西哥MEX北美洲西语区与巴西常被合并看越南VNM亚洲与印尼同属东南亚高 DAU 区巴基斯坦PAK亚洲与印度玩法高度重叠「印度 IND」和「印尼 IDN」这一对是最高频的事故源。中文报告里两个词只差一个字一旦上游把地区名直接当主键聚合结果会出现一个凭空多出来的超大市场。4.2 pivot 成玩法-地区交叉矩阵import pandas as pd import seaborn as sns df pd.read_csv(region_heat.csv) # region_iso3, genre_code, metric, rank_no dau (df.query(metric dau) .pivot_table(indexgenre_code, columnsregion_iso3, valuesrank_no, aggfuncmin)) heat 1 / dau # 位次越小越热取倒数让深色代表热门 sns.heatmap(heat, cmapYlOrRd, annotdau, fmt.0f, cbar_kws{label: 1/rank})逻辑说明rank_no是反向指标直接画热力图会让深色代表冷门取倒数才符合直觉。aggfuncmin是因为同一玩法在同一地区可能命中多款游戏取最好位次作为该格代表值。想换成「上榜数量」口径就把 aggfunc 改成count但两张图不能混着解读一个是「最好成绩」一个是「厚度」。4.3 DAU 榜和 IAP 榜分开跑差分报告把地区分析拆成「地区现状(DAU)」和「地区现状(IAP)」两页不是排版偷懒两个结论差得远。做法是合成一张长表再跑差分。long pd.read_csv(region_metric_long.csv) # region_iso3, metric, rank_no wide long.pivot(indexregion_iso3, columnsmetric, valuesrank_no) wide[gap] wide[iap] - wide[dau] # 正数变现位次落后于活跃位次 print(wide.sort_values(gap, ascendingFalse))gap为正说明这个地区人多但花钱少适合走规模优先的买量策略gap为负说明变现效率高于活跃规模适合做 IAP 深度挖掘和精细化运营。这个字段我一般会直接放进期报。注意算 gap 之前确认两张榜用的是同一个 DAU 窗口。报告里的 DAU 图叠了 2019/12/1–2020/5/31 和 2020/6/1–2020/11/30 两个窗口外加一条环比线混窗口做差分没有意义。5. 案例层逆向用玩法参数给13个案例做可比标签报告在 P32–P40 挑出十三个案例每款给基础信息、数据分析、玩法、设计分析、变现分析。字数是有限的直接读容易读成故事。我一般会把案例压成机制字段再做横向比较加三组维度随机性来源骰子/发牌/无、信息结构完全信息/不完全信息、单局时长。import re import pandas as pd CASES [ # app_name, release_hint, report_genre, randomness, info, session_min (Tic Tac Toe Free, 2008-09-01, tic_tac_toe, none, perfect, 1), (天天象棋腾讯版, 2010-12-07, chess_cn, none, perfect, 10), (Gomoku·S - line five in a row, 2012-05-15, othello, none, perfect, 5), (Richman 4 Fun Lite, 2012-12-10, party, dice, imperfect, 30), (101 Yuzbir Okey Plus, 2014-05-08, okey, tile, imperfect, 15), (Backgammon - Narde, 2014-05-08, backgammon, dice, perfect, 8), (Tic Tac Toe, 2014-08-04, tic_tac_toe, none, perfect, 1), (Checkers - strategy board game,2015-01-11, checkers, none, perfect, 6), (Chess, 2015-06-25, chess_intl, none, perfect, 12), (Ludo King, 2016-12-17, ludo, dice, perfect, 10), (Carrom King, 2017-04-12, carrom, none, perfect, 4), (Yahtzee with Buddies Dice, 2017-05-11, bar, dice, imperfect, 5), (4 in a row, 2017-09-29, connect_four, none, imperfect, 3), (Rummikub, 2017-10-03, rummikub, tile, imperfect, 20), (UNO!TM, 2018, uno, card, imperfect, 6), ] def parse_year(hint: str) - int: 报告部分案例只给到年份用正则统一取四位年 return int(re.search(r(19|20)\d{2}, hint).group(0)) df pd.DataFrame(CASES, columns[app_name,release_hint,report_genre, randomness,info,session_min]) df[release_year] df[release_hint].map(parse_year) df[stage] df[release_year].map(stage_of) print(df.groupby([stage, randomness]).size().unstack(fill_value0))跑出来的分组结果能看出一个规律第三阶段案例里 tile / card / dice 这类高随机性玩法明显变多前两阶段基本是随机性为 none 的纯策略棋类。这和报告「第三阶段出现 Rummikub 和 UNO 两种新玩法」的结论互相印证但手里多了一份能按维度切片的数据。两个校验技巧。第一拿release_year反查stage_of如果出现 2018 年发布却挂在第一阶段玩法的记录基本是抽取时年份串行了回 PDF 页码核一遍。第二带商标符号的标题在同一份报告里可能有 UNO!TM、UNO!、UNO 三种写法入库前统一去符号再去重否则同一款游戏会出现三条记录把玩法热度的分母撑大。另外Gomoku·S里的中点符号在部分字体下会被抽成?或直接丢失匹配时用gomoku前缀截断比整串精确匹配更耐用。本文还有配套的精品资源点击获取