GDGoC Chuo University

初心者向けプログラミング言語 Puffin を作った

2025年11月1日、2日に中央大学の文化祭でゲーム開発体験を行いました。プログラミングやドット絵制作、効果音づくりなど、ゲーム開発のさまざまな工程を体験でき、最後に作ったゲームを遊べました。

この記事ではゲームのプログラミングに使った自作言語 Puffin について話します。

自作言語を作ることになった

ゲームのプログラミングをどうやってやるか。

初心者向けのプログラミングというとやっぱり一番最初に出てくるのが Scratch 。ドラッグアンドドロップでレゴのブロックを繋げていくようにプログラミングしてゲームを作るサイトです。このブロック系の UI を使ったプログラミングが1つの案でした。

そしてもう1つの案として簡単なプログラミング言語を使って本格的なコードを書くというものもありました。この場合、自作言語を使うことになります。JavaScript をそのまま使うのが一番楽ですが初心者に JavaScript を読ませる、書かせるのは難しいと判断しました。 eval()Function() を使うことに対する抵抗感もあったり。

結果的に自作言語を作ることにしました。

...というのが表向きの理由ですが、

  1. 複雑な UI を作りたくない。
  2. Blockly などのライブラリ・フレームワークを使ったコードベースを管理したくない。
  3. 独自言語の構文考えたい。

と自分勝手な気持ちもありました。趣味やサークル活動でプログラミングするときは基本的に楽しい方を選ぶべきだと思っていて、ここでは圧倒的に自分たちで言語作る方が楽しそうでした。

コンパイラのことなど何も知らずに作ったので実装について話してもあまり役立つことはないと思います。ただ、仕組みとしてはコードから AST のようなもの生成し、深いノード順に実行していくだけです。JavaScript に変換して eval() で実行する手もありましたが、パフォーマンスのことはそんなに気にしてなかったので簡単な方を選びました。

言語のデザイン

まず、初心者用の言語を調べると"自然な文章"を使うものをよく見ますが、構文を日本語や英語の文章に近づける必要はないと最初から決めてました。

foo を 1 とする
foo に 1 をプラスして foo の値にする

set foo, 1
set foo, $foo + 1

を比べると、初心者でも後者の方が読みやすいかと思います。ただ、数学で一般的な記号を本来とは違う意味で使うのは避けました。例えば、 = は数学では「等しい」ことを表すので、値の代入に使いませんし値を比べるときにも使いません。 「1行=1指示」を言語のアイディアとしています。全ての指示が set , add , if など1つの英単語から始まり1行で完結します。ちょっとアセンブリ言語に近いかも?

set foo, []
add foo, 1
# 数学の意味と違うので > ではなく >>
if @size($foo) >> 0:
    do @log($foo[0])

変数を参照する場合は $ 、関数を参照する場合は @ を使います。この方が構文解析がやりやすいのと、 true など使ってはいけない変数名が出てきません。変数名と関数名が被ることもないです。

set はオブジェクトにプロパティを追加やオブジェクトのプロパティの値の代入、配列の要素の値の代入にも使えます。

set message, {content: "hello"}
set messages, [message]
set messages[0].content, "bye"

全ての行が指示の名前から始まるので戻り値を無視してただ関数を読み出す場合は do 指示を使います。

do @log($message)

&&|| は読みづらいし、 A && BA || BA + B と異なり B が実行されないことがあるのがわかりづらいと思ったので消しました。その代わりに ifwhile に複数条件を定義でき、条件が全て満たされた場合に中身が実行されます。 || より && を優先したデザインですが、簡単なプログラミングの場合 && を使う場面の方が圧倒的に多いと勝手に判断しました。

if a > 0, b > 0:
	# a と b は0より大きい

@and()@or() 関数があるので一応複雑な条件も表現できます。

if @and(@or(a > 0, b > 0), c > 0):
    # a また b は0より大きく、c は0より大きい

for 文は C のような i = 0; i < 10; i++; ではなく、Go や Python の for/range ぽい文法にしました。 while もあります。

for i, 0, 3:
    do @log($i)

    set i = 0
    while i << 3:
        do @log($i)
        set i, i + 1

関数はありますが基本的に上から下に読めるコードにしたかったので関数は定義できません。実行前に JavaScript で関数を登録します。ただ、プログラムに引数を渡したり、実行したプログラムが値を return できるので、別のプログラムを実行する関数を用意すれば、似たようなことはできます。実際 ゲーム制作のソフトでは同じプロジェクトにある他のプログラムを実行できる @call() 関数がありました。

# is_prime

if $arg.num < 2:
    return false

for i, 2, @sqrt($arg.num) + 1:
    if $arg.num % $i === 0:
        return false

 return true

変数や浮動小数点型もありません。数字は全部 decimal 型で、0.01 まで表現できます。これも初心者向けの言語で複雑の計算もしなければビット演算もしないのでわかりやすいものを選びました。

# 0.03 ピッタリ
# 0.30000000000000004 にはならない
set sum, 0.1 + 0.2

## エラー
set num, 0.0000001

ローカル変数はありません。変数は全てグローバルです。ローカル変数の実装をしたくなかったこともあるのですが、 set name, value で変数を定義するこの言語ではローカルとかスコープの概念がない方が自然に感じました。ローカル変数がないのも関数を定義できないようにした理由です。

if $lang == "japanese":
    set message, "こんにちは"
else:
    set message, "Hello"

do @log($message)

iffor では ではなく Python みたいにインデントを使います。正直ここが唯ー後悔してる点です。

if condition:
	# condition satisfied

こうやってインデントを使う言語は個人的に嫌いですが「1行 = 1指示」の言語では意外と自然と感じたので選びました。ただ、実際これを未経験者に説明にするのは難しく、C 系の言語みたいに 使った方がわかりやすかったと思ってます。

ゲーム開発

開発したゲーム開発ソフト LAV では初期状態を設定する `_init` プログラムと1秒間に100回実行される `_update` プログラムを使いました。

# _update

# タイトル画面
if $arg.state == "idle":
    do @fill_background(true, true, 60)

    # jump game を表示
	do @fill_character("j", true, 2, 72, 96, false, false)
	do @fill_character("u", true, 2, 88, 96, false, false)
	do @fill_character("m", true, 2, 104, 96, false, false)
	do @fill_character("p", true, 2, 120, 96, false, false)
	do @fill_character("g", true, 2, 72, 80, false, false)
	do @fill_character("a", true, 2, 88, 80, false, false)
	do @fill_character("m", true, 2, 104, 80, false, false)
	do @fill_character("e", true, 2, 120, 80, false, false)

最後に

ソースコードは GitHub で公開してあります。 英語版マニュアル日本語版マニュアル もレポジトリーに置いてあります。

Puffin はツノメドリという可愛い鳥の英語名ですが、この名前に意味は特になくて The Puffin language の響きが好きだったので選びました。

ツノメドリ
"Puffin (Fratercula arctica) at Látrabjarg, Iceland." by Henrik Thorburn, licensed under CC BY 3.0 .