LoginSignup
152
90

More than 3 years have passed since last update.

Swiftのコードをすごろくにして、プログラミング初心者がプログラムの動作を体感的に学べるようにした

Posted at

プゴロク

プログラミング × すごろく

数年前、娘とすごろくで遊んでいて、「すごろくってソースコードに似てるな」と思いました。すごろくのマスには色々な指示が書かれていて、ちょっと複雑なものだと「サイコロを振って 6 が出たら矢印の先のマスに進む。」のような感じです。これはまさに条件分岐です。

すごろく vs コード

すごろくなら幼稚園児でも理解できます。プログラムのコードをすごろくで表現すれば、プログラミング初心者がプログラムの動作を理解する助けになるかもしれないと思いました(ただし、普通のすごろくでは止まったマスに書かれた指示だけを実行しますが、プログラムは通過したすべての行を実行しないといけないので、プログラムをすごろくで表現すると、止まったマスだけでなく通過したマスに書かれた指示もすべて実行するルールですごろくを遊ぶ必要があります)。

たとえば、次のようにすごろくのマスの中にコードと日本語の両方で指示を書けば、コードを読めない人でもコードとその意味を対応付けて学ぶことができます。

コードをすごろくで表現

また、実際にコマを動かしながらすごろくを遊ぶことで、プログラムの動作をトレースし、プログラムがどのように動くかを体感することができます。プログラミング学習の初歩でつまづいてしまったという話をよく聞きますが、すごろくという目に見える形でプログラムの動作を繰り返し体験することで、感覚的に理解できるようになるかもしれません。

そのようなアイデアに基づいて、プログラミング初心者のためのすごろく 『プゴロク』 を作りました。学習効果を考えると手を動かして遊べる物理的な紙のすごろくの方が良さそうですが、レイアウトやデザインを変えながら実験し、コンセプトを検証するにはソフトウェアの方が手軽です。僕のスキルセットを元に検討し、 iOS アプリとしてプゴロクを開発しました

ビジュアルプログラミングとの比較

テキストで書かれたコードは初心者や子供にとってわかりづらいので、よりわかりやすい表現をしようという方向性は、 Scratch 等のビジュアルプログラミング言語と似ています。たとえば、↑で挙げたコードを Scratch で表すと次のようになります。

先のコードを Scratch で表現

Scratch のコードはプゴロク同様にビジュアルに表現されていますが、その表現手法には大きな違いがあります。

プゴロクの一つのマスはコードの 1 行に対応していて、まるでプログラムをステップ実行するかのようにコマを進めます。条件分岐やループなどで行をジャンプする箇所には矢印が描かれ、プログラムがどのような順序で実行されるかを直接的に表します。たとえば、 while 文は次のように表されます。

while 文のプゴロクでの表現

一方で、 Scratch のコードはより構造的です。 Scratch のブロックは構文木のノードに対応しており、コードの構造を直接的に表します。

プログラミングに慣れた人から見ると、どれも本質的には同じことを表しており、表面的な表現手法の違いにしか見えないでしょう。プゴロクや Scratch だとわかるけど↓のコードだとわからないのがなぜか理解に苦しむかもしれません。

if 🎲 != 6 {
    ...
}

しかし、人間は(というと主語が大きいですが、少なくとも僕は)自分が慣れていない表現に接すると、途端に認知能力が落ちてしまいます。たとえば、僕はプログラミングで慣れ親しんだ概念であるにも関わらず、ラムダ計算の記法(↓など)になかなか馴染めませんでした。

(λxy. x − y) 2 3

そう考えると、すごろくという慣れ親しんだ表現は、テキストによるコードはもちろんのこと、 Scratch のようなブロックによる表現と比べても、初心者や子供にとって理解しやすい可能性があります。

もしその仮説が正しいなら、プゴロクの表現をベースにしたビジュアルプログラミング言語を考えてみるのもおもしろそうなテーマです。それはつまり、すごろくを作ることを通してコードを書くということです。

コードを書いてプログラムを作るというとハードルが高そうですが、すごろくを作るのなら子供の遊びです。子供たちは、「スタートに戻る」のようなループも含めて、その挙動をイメージしながらすごろくを作ることができます。もしかすると、すごろくを作るという形式であれば、プログラミングのハードルを下げることができるかもしれません。

コードをすごろくで表現する

一口にコードをすごろくで表現すると言っても検討することは色々あります。

たとえば、 if 文の矢印をどのように引くのかという一点をとっても、他との一貫性を考えて決めなければなりません。

if の矢印の引き方

どのような表現が必要かは言語によっても異なります。たとえば、 Python では } がありません。 Python の while 文をすごろくで表現しようとすると次のようになるでしょう。プゴロクは、現状では Swift しかサポートしていませんが、後述のように他言語のサポートも検討中です。

単純な if だけでなく、 else を伴う if についても考えると矢印の引き方はもっとややこしくなります。 Swift では } else { のように 1 行で書くことが一般的ですが、すごろくとして表現するには }else { の二つのマスに分割するのが良さそうです。

if else の矢印の引き方

また、上記の表現では矢印が同じ階層に複数本並行して走っていますが、一つの階層に最大何本の矢印を描画しなければならないでしょうか。色々なケースを検討した結果、最大 3 本描画できれば良さそうでした(下図の赤丸の箇所)。

ここまで検討してきた表現を用いて、色々な制御構造を表すと次のようになります。 breakcontinue のように階層を飛び越えるジャンプや、 switch, do/try/catch のような多くのジャンプを伴う制御構造も表現することができます。

関数についても、本流のすごろくとは別のすごろく片として表現し、関数呼び出しはそれらをつなぐ矢印で表現できそうです。

複数箇所から同じ関数が呼ばれている場合、関数を抜けて呼び出し元に戻るときに、適切な呼び出し元に戻らなければなりません。上図のように、矢印を色分けすることで呼び出し元との対応がわかりやすくなります。

その他に検討しなければならないのが変数の表現です。変数については、ボードゲームのスコアボードが参考になります。

ボードゲームのスコアボードでは、 0, 1, 2, 3, ... とスコアが書かれたマスが用意されていて、そこにコマを置くことで現在のスコアを表現することが多いです。スコアの増減はコマを動かすことで表します。これはスコアという変数を扱っているのと同じです。

たとえば、 a という Int 型の変数をスコアボード形式で表すと次のようになります。

このような検討の結果、初歩的なプログラムをすごろくとして表現することは現実的に可能だという結論に至りました。

どのようなコードをすごろくにするか

コードをどのようにすごろくで表すかという表現の問題とは別に、どのようなコードをすごろくにするかも重要です。色々なすごろくを作っても良いですが、まず初めに遊んでほしい定番のすごろくが示された方が、ユーザーにとってはわかりやすいと思います。

検討の結果、次のような要素は必須だろうと考えました。

  • 条件分岐
  • 繰り返し(ループ)
  • 変数

さすがにこの三つの要素を欠いては、プログラミングというものをイメージしづらいと思います(一部の関数型言語などは事情が異なりますが、ここでは一般的な手続き型言語を想定しています)。

配列や関数、制御構造のネストなども検討しましたが、理解のための難易度が高まりますし、すごろくとしての表現も複雑になってしまいます。まず初めに遊んで楽しんでもらうには、あまり欲張って詰め込みすぎずに上記の 3 要素にとどめておく方が良さそうです。

これら 3 要素を含んだコードを作るのは簡単ですが、単にそれらの要素を含んでいるだけでなく、意味のあるコードをすごろくにしたいと考えました。コード自体に意味がないと、プゴロクを通してコードの挙動が理解できるようになっても、それが実際に意味のあるプログラムとしてどのように機能するのかイメージできないと思います。プログラミング学習の第一歩としてプゴロクを遊んでもらうことを期待するなら、意味のあるコードをすごろくにすることは欠かせないと思いました。

また、すごろくとして遊ぶからには、単に遊びとして見てもおもしろい方が望ましいです。普通のすごろくと違ってプゴロクでは通過したすべてのマスに書かれた指示を実行するため、どのマスに止まるかというドキドキがありません。単にサイコロを振って進むだけでは、大きな目をたくさん出した人が勝つことになってしまいます。マスの指示でサイコロを振らせるなどして何らかのランダム性を持たせなければなりません。加えて、すごろくとして遊ぶのに適切な時間で終わるように長さも考慮する必要があります。

これらの条件を元に検討した結果、ユークリッドの互除法(二つの自然数の最大公約数を求める最古のアルゴリズム)を実装したコードをすごろくにすることにしました。↓がそのコードです1

var 🎲: Int { return (1...6).randomElement()! }

var a = 🎲
print(a)

var b = 🎲 + 🎲
print(b)

if a < b {
    let t = b
    b = a
    a = t
}

while b != 0 {
    let t = b
    b = a % b
    a = t
}

print(a)

Swift では 🎲 を含む一部の絵文字を識別子として使えます。また、グローバルな Computed Property を作ることができます。そのため、 ↑のコードは実際に実行可能です。

このコードを実行すると、 ab の二つの自然数がランダムに決定・表示され、ユークリッドの互除法でそれらの最大公約数を求めた上で最後に結果が表示されます。これをすごろくとして遊ぶと、サイコロを振って a, b の値を決めるため、その結果次第で後の挙動が変化し、「大きな目をたくさん出した人が勝つ」だけにはなりません。また、 ab がランダムに決定されることで、様々な自然数の組み合わせに対してユークリッドの互除法で最大公約数を求められることも体験してもらえます。

b の初期値はサイコロを 2 回振った合計なので a の初期値より大きくなりやすいですが、運良く ab 以上になれば if{} をスキップできます。すごろくの序盤ではそれが重要そうに見えますが、しかしもっと重要なのがその後の while ループを何回繰り返さなければならないかです。何回ループするかは実際に計算してみないとわかりづらいので、コマを進めながらドキドキすることになります。

お世辞にもこのすごろくがめちゃくちゃおもしろいとは言えませんが、教材としては最低限のゲーム性を確保できたかなと思います。実際に娘と遊んでみましたが、後日「プゴロクやらせてー!」と言ってくる程度には楽しんでくれたようです。

上記のコードをすごろくにしたものの画像は巨大すぎて掲載できないので、興味のある方はアプリをダウンロードして確認してみて下さい。

余談ですが、 Swift で 🎲 を識別子として使えるのは重要です。 Swift と併せて Python と JavaScript を最初からサポートすることを検討していたのですが、 Python や JS で 🎲 が識別子として使えないことをどう扱うか(わかりやすさ優先で実行可能なコードを諦めて 🎲 を使うか、実行可能なことにこだわって dice()saikoro() のような関数を作るか)の結論が出ず、ひとまず Swift だけをサポートすることにしました。

紙のすごろくの作成

まずは iOS アプリとしてプゴロクを作りましたが、教材としての効果を考えると、アプリに加えて物理的な紙のすごろくも作りたいと考えています。

アプリでは、サイコロを振ると自動的にコマが動かされます。それを見てプログラムの動作を観察することはできますが、自分の手でコードの意味を考えながらコマを動かすのとでは得られる理解の深さが違うでしょう。紙のすごろくで遊んでもらってこそ、本当に「プログラムの動作を体感的に学べる」ものになると思います。

しかし、物理的なすごろくとなると印刷にもコストがかかりますし、配布もアプリと同じようにはいきません。そのあたりの問題は棚上げして、ひとまずは紙のプゴロクのプロトタイプ作成に取り組みたいと考えています。

なお、仮に紙のプゴロクが普及しても、アプリ版は正しい遊び方を確認するのに有用なんじゃないかと思います。僕はときどきボードゲームで遊びますが、紙版で遊んだ後でアプリ版をプレイしたときにルールを間違えていたことに気付くという経験を何度かしています。来年( 2020 年)から小学校でのプログラミング必修化が始まりますが、学校の先生はプログラミングの専門家ではありません。プゴロクをアンプラグドなプログラミング教育の手段として活用してもらうことまで考えれば、プログラミングに精通した人がいなくても正しい遊び方を確かめるアプリという方法があることは重要だと思います2

レイアウトとレンダリング

アプリにせよ、紙のすごろくにせよ、何らかの方法ですごろくを描画しなければなりません。プゴロクのマスや矢印の描画を考えると、規則性が強く、同じようなパターンが繰り返し出現するので、イラレ等で作るよりもコードで生成した方が労力が小さそうです。

すごろくの描画は次の要件を満たす必要があります。

  1. マスの中にはテキスト(コードや日本語での指示)が書かれ、その長さに応じてマスの height が伸び縮みする
  2. テキストが短い場合にマスが縦に潰れてしまわないように、マスのアスペクト比( width : height または width / height )が一定値より大きくならないようにする
  3. 各種要素間に適切な余白を指定できる
  4. 任意のマス間で矢印を描画できる
  5. (印刷を見据えると)ベクター形式で書き出し可能か、印刷に耐えうる高解像度で描画できる

マスの柔軟なレイアウトと矢印の複雑なレンダリングを実現するために、僕のスキルセットで最適と思われた Auto Layout + Core Graphics を選択しました。 1, 2, 3 を Auto Layout で対応し、 4 を Core Graphics で対応します。

5 については、 UIView からベクター形式で書き出すことはできないですが、単純なデザインなので計算された座標を元にベクター形式で書き出すコードを書くことは難しくないだろうと考えました(こちらは現時点で未検証で、紙版のプロトタイプ作成のためにこれから取り組む予定です)。

マスのレイアウト

Auto Layout によるマスのレイアウトは↓のように実現できます。

マスの制約

マスのアスペクト比について、 10 : 8 より横長にならないようにする( <= 10 / 8 の) constraint (制約)を設定するのと同時に、 priority (優先度)を下げて == 10 / 8 の constraint も追加するのがポイントです。このときに、 == 10 / 8 の priority は UILabel の content compression resistance priority よりも小さな値にする必要があります。

UILabel は保持するテキストのフォントや長さに応じたサイズ intrinsic content size を持っています。たとえば、 width を固定するとテキストの長さに応じて height が変化します。 UILabel の intrinsic content size の priority は content compression resistance priority (伸びる場合の priority )と content hugging priority (縮む場合の priority )によって決定されます。今は伸びる場合を考えているので、もし == 10 / 8 の priority を content compression resistance priority 以上に設定してしまうと、テキストの長さに応じて UILabelheight が伸びるよりもアスペクト比が 10 : 8 になることが優先されてしまいます。つまり、テキストが長くてもマスの hieght が伸びなくなってしまいます。

Auto Layout を使えば、親子・隣接 view 間はもちろん、同じ view ツリーに属していれば階層を飛び越えて constraint を設定できたり、任意の対象の間で、 Y == a * X + b (または、 == の代わりに <=, >=X, Y は制約の対象)の形で自由に制約を記述できるなど、柔軟なレイアウトが可能です。柔軟性が高いと、少々考慮漏れがあっても後からリカバリーしやすいです。今回も、スタートのマスと scroll view の左右中心をそろえて表示するというやや複雑な制約の考慮が漏れていましたが、後から簡単に対応することができました。

矢印の描画

UIViewdraw(_: CGRect) メソッドをオーバーライドすることで、 view をカスタマイズして任意の描画が可能になります。

たとえば、 Core Graphics を用いて右上から左下に赤い線を引くだけの view を↓のように作ることができます。

@IBDesignable class CustomView: UIView {
    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else {
            return
        }

        context.setLineWidth(6.0)
        context.setStrokeColor(UIColor.red.cgColor)

        context.move(to: CGPoint(x: bounds.maxX, y: bounds.minY))
        context.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
        context.strokePath()
    }
}

custom view

これを使えば矢印の描画も簡単です。各マスの座標は Auto Layout が constraint を元に自動的に決定してくれるので、 draw(_:) メソッドでその座標を元に矢印を引くだけです。

レイアウトとレンダリングの課題

小学生に使ってもらうことを考えるとマスの中の文章にふりがなを付けたかったんですが、 UILabel の技術的制約で簡単にはできませんでした。今後なんとかして対応したいと考えています。

おまけ: 物理演算と 3D レンダリング

プゴロクの iOS アプリを作る上でどうしてもやってみたかったことが、物理演算を使ってサイコロを振ることです。

物理演算でサイコロを振る

1 から 6 までの整数をランダムに一つ選べばいいだけなのでやりすぎ感はありますが、すごろくをプレイしてる感じを表現したくてやりました。 SceneKit を使えば物理演算も 3D レンダリングも簡単です。

意外と面倒なのが、出た目が何かを計算することです。人間が見れば一目瞭然ですが、出目を求めるために具体的に何を計算すれば良いかを考えるとそこまで単純ではありません。プゴロクでは、サイコロの座標系における各面の方向を表すベクトルと鉛直上向きのベクトルの成す角を計算して、最小のものを選択するようにしました。

let directions: [SCNVector3] = [
    SCNVector3(0, 1, 0),
    SCNVector3(0, 0, 1),
    SCNVector3(1, 0, 0),
    SCNVector3(-1, 0, 0),
    SCNVector3(0, 0, -1),
    SCNVector3(0, -1, 0),
]

let transform = diceNode.presentation.transform
let upDirection = SCNVector3(transform.m12, transform.m22, transform.m32)
let (index, _) = directions
    .map { $0.angle(from: upDirection) }
    .enumerated()
    .min { $0.1 < $1.1 }!
let number =  index + 1

また、 SceneKit を使っていたらタイトル画面もかっこよく 3D にしたくなってきて、実際にすごろくを遊ぶときは 2D なのに、タイトル画面のためだけに無駄にすごろくを 3D 化してしました(本投稿冒頭の画像です)。

さらに、 Web サイトも 3D にしたいという誘惑には勝てず、 WebGL / three.js で 3D 化しました。 WebGL / three.js は初めて触りましたが、ブラウザ上に簡単に 3D コンテンツが表示できて楽しかったです。

まとめ

  • Swift のコードをすごろくにした( iOS アプリ)
  • プログラミング初心者がすごろくで遊びながらプログラムの動作を学べる
  • ビジュアルプログラミングの表現形式の一緒として考えてもおもしろい
  • ユークリッドの互除法のコードをすごろくにすると楽しい
  • 学習効果を考えると紙のすごろくは欠かせないので紙版も作りたい
  • 3D は楽しい

  1. ただし、 🎲 を宣言している 1 行目についてはすごろくから除外しました。スタートのマスにこのコードを入れることも検討しましたが、コードだけあって説明がないのでは混乱を招くだけになりそうなので、どこか別の場所で暗黙的に宣言されていることにしました。ただ、このままではすごろくに書かれたコードをそのままコピペしても実行できません。プゴロクのアプリには、すごろくのすべてのマスのコードを抜き出して並べて表示するページがあるので、そのページ上では注釈をつけた上で 🎲 の宣言も表示するようにしました。 

  2. 小学校のプログラミング必修化は、算数などの既存教科の中でプログラミングを取り扱うことになっているので、プゴロクが活用できるかは未知数です。 

152
90
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
152
90