RでBrainf*ckのアニメーション

Tsukuba.R#5で最後にちらっと発表したおまけ.RのanimationパッケージによるBrainf*ckのシミュレーション動画

動画をダブルクリックしてはてなフォトライフにアクセスするとより大きいアニメーションが観られる.

この調子でこれからも役に立たないものを積極的に実装していきたい.

ソースは以下の通り.

# R de Brainf*ck

library(grid)
library(animation)

brainfxxk <- function(code) {
  # local variables
  code <- strsplit(code,"")[[1]]
  len <- length(code)
  pc <- 1
  ptr <- 1
  mem <- rep(0, 128)
  output <- ""

  buf <- ""

  # >
  inc_ptr <- function() {
    ptr <<- ptr + 1
  }

  # <
  dec_ptr <- function() {
    ptr <<- ptr - 1
  }

  # +
  inc_mem <- function() {
    mem[ptr] <<- mem[ptr] + 1
  }

  # -
  dec_mem <- function() {
    mem[ptr] <<- mem[ptr] - 1
  }

  # .
  putc <- function() {
    output <<- paste(output, intToUtf8(mem[ptr]), sep="")
  }

  # ,
  getc <- function() {
    if (nchar(buf) == 0) {
      buf <<- readline()
    }
    mem[ptr] <<- utf8ToInt(substring(buf, 1, 1))
    buf <<- substring(buf, 2)
  }

  # [
  wstart <- function() {
    if (mem[ptr] == 0) {
      count <- 1
      pc <<- pc + 1
      while (count > 0) {
        if (code[pc] == "[") { count <- count + 1; }
        if (code[pc] == "]") { count <- count - 1; }
        pc <<- pc + 1
      }
      pc <<- pc - 1
    }
  }
  
  # ]
  wend <- function() {
    if (mem[ptr] != 0) {
      count <- 1
      pc <<- pc - 1
      while (count > 0) {
        if (code[pc] == "[") { count <- count - 1; }
        if (code[pc] == "]") { count <- count + 1; }
        pc <<- pc - 1
      }
      pc <<- pc + 1
    }
  }

  # 描画用関数
  plot_bf <- function() {
    grid.newpage()

    # memの枠
    grid.lines(c(0.95, 0.1, 0.1, 0.95), c(0.7, 0.7, 0.8, 0.8))
    sapply(seq(0.2, 0.9, 0.1), function(x) {
      grid.lines(x, c(0.7, 0.8))
    })
    grid.text("mem", 0.05, 0.75)

    # memのインデックスと内容
    for (i in 1:8) {
      grid.text(i, 0.05 + 0.1 * i, 0.83)
      grid.text(mem[i], 0.05 + 0.1 * i, 0.75,
        gp=gpar(cex=2, col=ifelse(i==ptr, "red", "black")))
    }

    # ptrの矢印
    grid.arrows(0.05 + 0.1 * ptr, c(0.6, 0.68), gp=gpar(col="red"))

    # 次の命令
    grid.text("next:", 0.46, 0.5)
    if (pc <= len) {
      grid.text(code[pc], 0.52, 0.505, gp=gpar(cex=2.5))
    }

    # Brainf*ckのソース
    wl <- 32
    hl <- 0.05
    wc <- 0.02
    grid.text("source:", 0.1, 0.4)
    if (pc <= len) {
      grid.rect(0.18 + 0.02 * ((pc-1) %% wl),
      0.398 - hl * ((pc-1) %/% wl), 0.022, 0.04,
      gp=gpar(col="red"))
    }
    for (i in 1:len) {
      grid.text(code[i], 0.18 + 0.02 * ((i-1) %% wl),
        0.4 - hl * ((i-1) %/% wl),
        gp=gpar(cex=1.25, fontfamily="Monaco",
          col=ifelse(i==pc, "red", "black")))
    }

    # 出力
    grid.text("output:", 0.1, 0.15)
    grid.text(output, 0.18, 0.15, gp=gpar(cex=2), just=("left"))
  }

  # main loop
  plot_bf()
  while (pc <= len) {
    switch(code[pc],
           ">" = inc_ptr(),
           "<" = dec_ptr(),
           "+" = inc_mem(),
           "-" = dec_mem(),
           "." = putc(),
           "," = getc(),
           "[" = wstart(),
           "]" = wend()
           )
    pc <- pc + 1
    plot_bf()
  }
  output
}

# Hello, world!
hello <- "+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+."

saveMovie(brainfxxk(hello), interval=0.08, 
  moviename="hello", movietype="mpg", outdir=getwd(),
  width=640, height=480)

ポイント

  • plot_bf()でBrainf*ckの処理系の状態を描画している
  • 線や文字はグリッドグラフィックスを使って描画している
  • ptr > 8の範囲の描画に対応していない
  • Brainf*ckのソースの長さや出力の量が大きくなると表示がおかしくなる
  • Brainf*ckのソースの描画にfor文を使いまくっているので非常に遅い
  • Hello, world!くらいにしておけ

何の役にも立たないけど面白かった.