You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在R中基于FEN将LAN转换为SAN并生成PGN?

问题描述

我有一组国际象棋FEN字符串及对应的LAN(长代数记谱法)走法,存储在如下DataFrame中,每行代表一个棋局谜题:

FENMovesGameUrl
r3k2r/1b1pbppp/p7/1pp5/3q4/P1NPBp1P/1PP1B1P1/R4Q1K w kq - 0 19e3d4 f3g2 f1g2 b7g2 h1g2 c5d4Link
3rr1k1/5ppp/6q1/pp6/1bppPn2/5P2/PPP2QPP/2NRBRK1 w - - 8 25e1b4 f4h3 g1h1 h3f2Link
r1bq1rk1/ppp2ppp/2np1b1n/3Q2N1/2B1PP2/4B3/PP2N1PP/R4RK1 b - - 0 13h6g4 g5f7 f8f7 d5f7Link

我需要基于初始FEN和LAN走法序列生成PGN(便携式棋局记谱法),理想输出如下:

FENMovesGameUrlPGN
r3k2r/1b1pbppp/p7/1pp5/3q4/P1NPBp1P/1PP1B1P1/R4Q1K w kq - 0 19e3d4 f3g2 f1g2 b7g2 h1g2 c5d4Link[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,具体实现如下:

  1. 加载依赖包
library(tidyverse)
library(bigchess)
  1. 定义转换函数
    该函数接收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)
}
  1. 应用函数到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 13:54:57