如何在R中基于FEN将LAN转换为SAN并生成PGN?
问题描述
我有一组国际象棋FEN字符串及对应的LAN(长代数记谱法)走法,存储在如下DataFrame中,每行代表一个棋局谜题:
| FEN | Moves | GameUrl |
|---|---|---|
| r3k2r/1b1pbppp/p7/1pp5/3q4/P1NPBp1P/1PP1B1P1/R4Q1K w kq - 0 19 | e3d4 f3g2 f1g2 b7g2 h1g2 c5d4 | Link |
| 3rr1k1/5ppp/6q1/pp6/1bppPn2/5P2/PPP2QPP/2NRBRK1 w - - 8 25 | e1b4 f4h3 g1h1 h3f2 | Link |
| r1bq1rk1/ppp2ppp/2np1b1n/3Q2N1/2B1PP2/4B3/PP2N1PP/R4RK1 b - - 0 13 | h6g4 g5f7 f8f7 d5f7 | Link |
我需要基于初始FEN和LAN走法序列生成PGN(便携式棋局记谱法),理想输出如下:
| FEN | Moves | GameUrl | PGN |
|---|---|---|---|
| r3k2r/1b1pbppp/p7/1pp5/3q4/P1NPBp1P/1PP1B1P1/R4Q1K w kq - 0 19 | e3d4 f3g2 f1g2 b7g2 h1g2 c5d4 | Link | [Variant "From Position"][FEN "r3k2r/1b1pbppp/p7/1pp5/3q4/P1NPBp1P/1PP1B1P1/R4Q1K w kq - 0 19"]19. Bxd4 fxg2+ 20. Qxg2 Bxg2+ 21. Kxg2 cxd4 |
实现该需求需要将LAN转换为SAN(短代数记谱法)。我尝试用R语言的bigchess包转换,代码如下:
library(tidyverse) library(bigchess) df2 <- df %>% mutate(Moves_SAN = sapply(Moves, lan2san))
但lan2san函数默认从初始棋盘位置解析走法,导致转换结果错误。
请问如何在R中借助现有包,基于给定FEN将LAN转换为SAN并生成正确的PGN?我推测解决方案需要通过bigchess、stockfish或chess包加载FEN,再应用LAN走法并完成SAN转换,但尚未找到具体实现方式。
附DataFrame结构代码:
structure(list(FEN = c("r3k2r/1b1pbppp/p7/1pp5/3q4/P1NPBp1P/1PP1B1P1/R4Q1K w kq - 0 19", "3rr1k1/5ppp/6q1/pp6/1bppPn2/5P2/PPP2QPP/2NRBRK1 w - - 8 25", "r1bq1rk1/ppp2ppp/2np1b1n/3Q2N1/2B1PP2/4B3/PP2N1PP/R4RK1 b - - 0 13" ), Moves = c("e3d4 f3g2 f1g2 b7g2 h1g2 c5d4", "e1b4 f4h3 g1h1 h3f2", "h6g4 g5f7 f8f7 d5f7"), GameUrl = c("https://lichess.org/6OEDg4W3#Some(36)", "https://lichess.org/W9LaAFV1#Some(48)", "https://lichess.org/BxbS4jtt/black#Some(25)" )), row.names = c(NA, -3L), class = c("data.table", "data.frame" ), .internal.selfref = <pointer: 0x0000026e39ef2e30>)
解决方案
可以使用bigchess包中的game对象加载指定FEN,逐步应用LAN走法并生成SAN,最终拼接成符合要求的PGN,具体实现如下:
- 加载依赖包
library(tidyverse) library(bigchess)
- 定义转换函数
该函数接收FEN字符串和LAN走法字符串,生成完整PGN:
fen_lan_to_pgn <- function(fen, lan_moves) { # 从指定FEN初始化棋局 game_state <- game(fen = fen) # 将LAN走法拆分为单个走法向量 lan_list <- str_split(lan_moves, " ")[[1]] # 遍历LAN走法,生成对应的SAN序列 san_list <- character(length(lan_list)) for (i in seq_along(lan_list)) { game_state <- move(game_state, lan = lan_list[i]) san_list[i] <- last_move_san(game_state) } # 从FEN中提取起始回合数 start_round <- as.integer(str_split(fen, " ")[[1]][6]) # 为SAN添加回合编号 numbered_san <- vector("character", length(san_list)) current_round <- start_round for (i in seq_along(san_list)) { if (i %% 2 == 1) { # 白方走法,添加回合号 numbered_san[i] <- paste0(current_round, ". ", san_list[i]) } else { # 黑方走法,直接追加 numbered_san[i] <- san_list[i] current_round <- current_round + 1 } } # 拼接PGN头部和走法 pgn_header <- paste0('[Variant "From Position"][FEN "', fen, '"]') pgn_body <- paste(numbered_san, collapse = " ") full_pgn <- paste0(pgn_header, pgn_body) return(full_pgn) }
- 应用函数到DataFrame
df <- df %>% mutate(PGN = map2_chr(FEN, Moves, fen_lan_to_pgn))
关键逻辑说明
game(fen = fen):基于指定FEN创建棋局对象,还原初始棋盘状态move(game_state, lan = lan_move):应用单个LAN走法更新棋局last_move_san(game_state):获取上一步走法的SAN格式- 回合号处理:从FEN的第6个字段读取起始回合数,交替为白方走法添加回合编号
- PGN拼接:组合变体头信息、FEN头信息和带编号的SAN走法序列
内容的提问来源于stack exchange,提问作者Jay Bee
相关产品推荐
相关产品推荐

