博多電光

技術ブログ

SECCON 2017 Quals Writeup - z80, SHA-1 is dead, Qubic Rube

※この記事は TSG Advent Calendar 2017 11日目の記事です。

SECCON 2017 予選にチーム「TSG」として参加した。結果は世界23位、日本5位

SHA-1 is dead (Crypto100)

http://sha1.pwn.seccon.jp/

Upload two files satisfy following conditions:

file1 != file2

SHA1(file1) == SHA1(file2)

SHA256(file1) <> SHA256(file2)

2017KiB < sizeof(file1) < 2018KiB

2017KiB < sizeof(file2) < 2018KiB

1KiB = 1024 bytes

解法

やるだけ。Google様の力を借りる。

$ wget https://shattered.io/static/shattered-1.pdf
--2017-12-11 21:27:40--  https://shattered.io/static/shattered-1.pdf
Resolving shattered.io (shattered.io)... 216.239.36.21, 216.239.38.21, 216.239.34.21, ...
Connecting to shattered.io (shattered.io)|216.239.36.21|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 422435 (413K) [application/pdf]
Saving to: 'shattered-1.pdf'

shattered-1.pdf                   100%[===============================================================>] 412.53K   342KB/s   in 1.2s

2017-12-11 21:27:42 (342 KB/s) - 'shattered-1.pdf' saved [422435/422435]

$ wget https://shattered.io/static/shattered-2.pdf
--2017-12-11 21:27:47--  https://shattered.io/static/shattered-2.pdf
Resolving shattered.io (shattered.io)... 216.239.36.21, 216.239.38.21, 216.239.34.21, ...
Connecting to shattered.io (shattered.io)|216.239.36.21|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 422435 (413K) [application/pdf]
Saving to: 'shattered-2.pdf'

shattered-2.pdf                   100%[===============================================================>] 412.53K   312KB/s   in 1.3s

2017-12-11 21:27:49 (312 KB/s) - 'shattered-2.pdf' saved [422435/422435]

$ dd if=/dev/zero of=dummy bs=1643000 count=1
1+0 レコード入力
1+0 レコード出力
1643000 バイト (1.6 MB) コピーされました、 0.006498 秒、 253 MB/秒

$ cat shattered-1.pdf dummy > sha1-1.dat

$ cat shattered-2.pdf dummy > sha1-2.dat

$ ls -la sha1*.dat
-rwxrwxrwx 1 root root 2065435 12月 11 21:30 sha1-1.dat
-rwxrwxrwx 1 root root 2065435 12月 11 21:30 sha1-2.dat

$ sha1sum sha1*.dat
fa004512dad796e9e5f7958e497166c4df54eb7a  sha1-1.dat
fa004512dad796e9e5f7958e497166c4df54eb7a  sha1-2.dat

評価

な~~にがcryptoじゃ。

評価: ★★☆☆☆

z80 (Binary300)

Reverse it.

BuggyCPUBoard-82e40efd036ae8e87e149de07e6b99f5e01e389a537bf1d630b5af84d8f2b10a.ino

Pictures.tar.gz

本日のメイン。Arduinoスケッチファイルと、Arduinoマイコンが接続されたブレッドボードを360度から撮影した22枚の写真が与えられる。

2年前のSECCONでTSGを混乱に陥れた Reverse-Engineering Hardware の再来か⋯⋯と思われたが、実際にはそれ以上に頭のおかしい問題だった。

解法

前半部分は主に@Joe氏が担当した。まず写真を見ながら回路を検分する。ボードのほうは Arduino Mega だと分かる。マイコンのほうは (嫌らしいことに) わざわざSECCONステッカーが上に貼ってあり、型番がわからないが、問題のタイトルがz80なので、十中八九z80マイコンであると思われる。

@Joe氏がマイコンのデータシートと写真をにらめっこしながら、以下のように手書きの回路図を作成した。(お疲れ様です⋯⋯)

f:id:hakatashi:20171211191450j:plain

ここからは主に僕が担当した。とりあえずArduinoスケッチファイルのほうを読んでみる。とても長いのでざっと眺めるくらいしかしてないが、一番気になるのはmemという怪しい変数。

static unsigned char mem[memsize] = {
  0x22, 0x47, 0x00, 0x3d, 0x53, 0x77, 0x23, 0x3d, 0x45, 0x77, 0x23, 0x3d, 0x43, 
  0x77, 0x23, 0x77, 0x23, 0xc5, 0x0c, 0x77, 0x23, 0xc5, 0xfd, 0x77, 0x23, 0x3d, 
  0x7b, 0x77, 0x23, 0x39, 0x44, 0x00, 0x47, 0xc5, 0x46, 0x31, 0x44, 0x00, 0x78, 
  0x31, 0x46, 0x00, 0xfd, 0x22, 0xf9, 0x1e, 0x00, 0xfd, 0x7b, 0xf1, 0x1e, 0x00, 
  0x77, 0x23, 0x39, 0x45, 0x00, 0x3e, 0x31, 0x45, 0x00, 0xc1, 0x1e, 0x00, 0x3d, 
  0x7d, 0x77, 0x75, 0x03, 0x0b, 0x09, 
};

読むと、memReadやmemWriteという関数がこのmemにバイト単位の読み書きを行っているらしい事がわかる。そしてZ80マイコンがHALTした時、dispWork(0x47);でmemの0x47以降の内容をシリアルポートに書き出している。この時点でどうやらmem変数をZ80マイコンを動作させるためのプログラムメモリとして利用しているのではないかと当たりをつけた。

となるとmem変数の中身はZ80バイナリということになる。データを適当に整形してyazdに投げると、以下のように逆アセンブルできた。

        LD      (L0046+1),HL    ; reference not aligned to instruction
        DEC     A
        LD      D,E
        LD      (HL),A
        INC     HL
        DEC     A
        LD      B,L
        LD      (HL),A
        INC     HL
        DEC     A
        LD      B,E
        LD      (HL),A
        INC     HL
        LD      (HL),A
        INC     HL
        PUSH    BC
        INC     C
        LD      (HL),A
        INC     HL
        PUSH    BC
        LD      (IY+23h),A
        DEC     A
        LD      A,E
        LD      (HL),A
        INC     HL
        ADD     HL,SP
        LD      B,H
        NOP
        LD      B,A
        PUSH    BC
        LD      B,(HL)
        LD      SP,0044h
        LD      A,B
        LD      SP,0046h
        LD      (1EF9h),IY
        NOP
        LD      A,E
        POP     AF
        LD      E,00h
        LD      (HL),A
        INC     HL
        ADD     HL,SP
        LD      B,L
        NOP
        LD      A,31h           ; '1'
        LD      B,L
        NOP
        POP     BC
        LD      E,00h
        DEC     A
        LD      A,L
        LD      (HL),A
        LD      (HL),L
        INC     BC
        DEC     BC
L0046:  ADD     HL,BC

アセンブルできたものの、肝心のHALT命令もないし、いまいち意味の分からない内容である。実際にZ80エミュレーターで動かしてみても特に何も起きないし、0x47以降のアドレスにも意味のある内容が書き出されない。ここでずいぶん悩まされた。

立ち返ってArduinoスケッチファイルのファイル名を確認してみる。ファイル名にはBuggyCPUBoardとある。実は@Joe氏が回路の解析を行った際に、気になった部分があった。写真の赤丸の部分に注目して欲しいのだが、

f:id:hakatashi:20171209224300j:plain

よく見ると、他のピンはツイストされたケーブルの白色のジャンパが手前に刺さっているところが、この部分だけ白色のジャンパが奥になっている。この部分を辿っていくと、.inoに書かれたピン配置の配線と食い違っていることがわかる。

@@ -1,5 +1,5 @@
-unsigned D0=22;
-unsigned D1=23;
+unsigned D0=23;
+unsigned D1=22;
 unsigned D2=24;
 unsigned D3=25;
 unsigned D4=26;

D0ピンとD1ピンが入れ替わっている。Dピンはマイコンへのデータの書き出し/読み出しに対応しているので、ここが間違っているとZ80に入力されるデータの中身が違ってくる。おそらくこの間違いがBuggyCPUBoardの「Bug」なのだと思われる。

つまり、mem変数のデータと、実際にZ80が読み出すデータは異なっているものと思われる。mem変数のデータを適当なスクリプトでビットをswapして、再度逆アセンブルすると、

        LD      HL,0047h
        LD      A,53h           ; 'S'
        LD      (HL),A
        INC     HL
        LD      A,46h           ; 'F'
        LD      (HL),A
        INC     HL
        LD      A,43h           ; 'C'
        LD      (HL),A
        INC     HL
        LD      (HL),A
        INC     HL
        ADD     A,0Ch
        LD      (HL),A
        INC     HL
        ADD     A,0FEh
        LD      (HL),A
        INC     HL
        LD      A,7Bh           ; '{'
        LD      (HL),A
        INC     HL
L001D:  LD      A,(L0044)
        LD      B,A
        ADD     A,45h           ; 'E'
        LD      (L0044),A
        LD      A,B
        LD      (L0045),A
        CP      21h             ; '!'
        JP      M,L001D
        CP      7Bh             ; '{'
        JP      P,L001D
        LD      (HL),A
        INC     HL
        LD      A,(L0046)
        DEC     A
        LD      (L0046),A
        JP      NZ,L001D
        LD      A,7Eh           ; '~'
        LD      (HL),A
        HALT
L0044:  INC     BC
L0045:  DEC     BC
L0046:  LD      A,(BC)

かなり意味の通った逆アセンブル結果になる。特にSECCONのフラグ形式であるSECCON{~}らしき文字列のようなものも見える。

実際にエミュレーターで実行させると、ちゃんとHALT命令で停止して終了する。ちなみにエミュレーターTSGコードゴルフ大会で使用したz80golfというアプリケーションを使っている。以下のようなパッチを入れるとステップ実行でデバッグできるようになって便利。

diff -r -u z80golf/src/main.c z80golf2/src/main.c
--- z80golf/z80golf/src/main.c  Sun Dec 10 04:34:03 2017
+++ z80golf2/z80golf/src/main.c Fri Nov 30 08:01:23 2007
@@ -147,7 +147,6 @@

 /// 1命令実行
 void step() {
+       DebugZ80(&z80);
        ExecZ80(&z80);

                // システムコールされた場合、その処理へ飛ばす
@@ -169,7 +168,7 @@
 void exec_loop(int maxstep) {
        int i;
        for (i=0; maxstep == 0 || i < maxstep; ++i) {
+               // if (is_halt()) break;
-               if (is_halt()) break;
                step();
        }
 }

で、HALTした時点でのメモリの状態を確認してみる。

$ z80golf fixed.dat
AF:0000 HL:0000 DE:0000 BC:0000 IX:0000 IY:0000 I:00
0000[21 - LD HL,0047h] SP:0000[4721] FLAGS:[........] IM0:DI
[Command,'?']->
AF:0000 HL:0047 DE:0000 BC:0000 IX:0000 IY:0000 I:00
0003[3E - LD A,53h] SP:0000[4721] FLAGS:[........] IM0:DI
[Command,'?']->
AF:5300 HL:0047 DE:0000 BC:0000 IX:0000 IY:0000 I:00
0005[77 - LD (HL),A] SP:0000[4721] FLAGS:[........] IM0:DI
[Command,'?']->
(中略)
AF:0043 HL:0058 DE:0000 BC:4A00 IX:0000 IY:0000 I:00
003D[C2 - JP NZ,001Dh] SP:0000[4721] FLAGS:[.Z....NC] IM0:DI
[Command,'?']->
AF:0043 HL:0058 DE:0000 BC:4A00 IX:0000 IY:0000 I:00
0040[3E - LD A,7Eh] SP:0000[4721] FLAGS:[.Z....NC] IM0:DI
[Command,'?']->
AF:7E43 HL:0058 DE:0000 BC:4A00 IX:0000 IY:0000 I:00
0042[77 - LD (HL),A] SP:0000[4721] FLAGS:[.Z....NC] IM0:DI
[Command,'?']->
AF:7E43 HL:0058 DE:0000 BC:4A00 IX:0000 IY:0000 I:00
0043[76 - HALT] SP:0000[4721] FLAGS:[.Z....NC] IM0:DI
[Command,'?']-> m 0

0000: 21 47 00 3E 53 77 23 3E 46 77 23 3E 43 77 23 77  | !G.>Sw#>Fw#>Cw#w
0010: 23 C6 0C 77 23 C6 FE 77 23 3E 7B 77 23 3A 44 00  | #..w#..w#>{w#:D.
0020: 47 C6 45 32 44 00 78 32 45 00 FE 21 FA 1D 00 FE  | G.E2D.x2E..!....
0030: 7B F2 1D 00 77 23 3A 46 00 3D 32 46 00 C2 1D 00  | {...w#:F.=2F....
0040: 3E 7E 77 76 8F 4A 00 53 46 43 43 4F 4D 7B 48 5C  | >~wv.J.SFCCOM{H\
0050: 2B 70 3F 53 22 67 36 4A 7E 00 00 00 00 00 00 00  | +p?S"g6J~.......
0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
0090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
[Command,'?']->

0x47以降のアドレスに何やらflagらしきデータが書き込まれている。

0040:                      53 46 43 43 4F 4D 7B 48 5C  |        SFCCOM{H\
0050: 2B 70 3F 53 22 67 36 4A 7E                       | +p?S"g6J~

ピンのswapによる変更はデータの書き出し時にも影響するので、実際にここに書き込まれるデータを得るには再びこのデータのビットをswapすればよい。すると、

0040:                      53 45 43 43 4F 4E 7B 48 5C  |        SECCON{H\
0050: 2B 70 3F 53 21 67 35 49 7D                       | +p?S!g5I}

となり、flagはSECCON{H\+p?S!g5I}

評価

実際に組まれた回路のデバッグを要求される点や、ビットをswapすると正しいバイナリが得られる点は面白いと思った。が、SECCONステッカーや束ねられたコードなど理不尽な嫌がらせが多く、本質的でない部分で精神を消耗する点で決して良問とは言い難い。また動作に必要とはいえArduinoスケッチファイルが大きく、これがそもそも何をするコードなのか把握するのに時間がかかった。せめて問題文にフォローがあったほうが良かったのでは。

評価: ★★★☆☆

Qubic Rube (Programming300)

Please continue to solve Rubic's Cube and read QR code.

http://qubicrube.pwn.seccon.jp:33654/

2017年になって復活した唯一のQRコード枠。問題タイトルが「QR」をもじったものだと気付くのに時間がかかった。

サイトにアクセスすると、回転するルービックキューブが表示される。

f:id:hakatashi:20171211213613p:plain

6面の画像にはQRコードが表示されており、読み取るとそれぞれ以下のようなテキストになる。

SECCON 2017 Online CTF
Have fun!
Qubic Rube
Next URL is:
No. 1 / 50
http://qubicrube.pwn.seccon.jp:33654/02c286df1bbd7923d1f7

最後のURLにアクセスすると、今度は面が1つ回転したルービックキューブが表示される。

f:id:hakatashi:20171211222337p:plain

この回転を戻してQRコードを読み取って⋯⋯を繰り返していく。10回くらいで完全にスクランブルされたキューブになる。

f:id:hakatashi:20171211222654p:plain

解法

自動化問題。画像のURLはわかりやすく面の色も判別しやすいので、基本的にめんどくさいだけのやるだけ問題である。

肝心のルービックキューブの解法は、たぶん同じ色のピースを9つ集めてきて、並び替えを全探索して有効なQRコードを探すだけで十分に解ける問題だと思われる (うまくやれば探索総数は1面につき2000程度)。が、最近ルービックキューブにはまっていることもあって、ちゃんとルービックキューブを解くプログラムをわざわざ書いてみた。

3x3x3のルービックキューブは、1x1x1のパーツ26個に分けられ、スクランブルされてもそれぞれのパーツに塗られている色の組み合わせは変化しない。3x3x3のルービックキューブではそれぞれのパーツの配色がユニークなので、パーツごとの配色を調べて正しい位置に置き直すだけで解くことができる。

ただし、センターパーツ (3x3の真ん中の1個) の回転だけはルービックキューブの特性上確定しないので、この部分だけ全探索しなければいけない。

画像の取得やQRコードの読み取りなどの自動化は@Joe氏が、ルービックキューブのソルブは僕が担当した。ソースコードはこちら。

github.com

罠として、11問目からルービックキューブの配色が 白⇔青 緑⇔黃 赤⇔橙 の日本配色から、白⇔黃 緑⇔青 赤⇔橙 の国際配色に変化する (は?)。各面の色を決め打ちで解いているとここで躓いてしまう。というか躓いた。

なので、

これは訂正しなければならない。

評価

可もなく不可もなく。300点問題にしては簡単すぎるかと。4x4とか5x5のキューブにしたらさすがに全探索だと難しくなってちょうどよかったのでは?

評価: ★★★★☆

感想

初めてBinary問題を解いたのでもはや実質バイナリアンと言っても過言ではない (は?)

8人くらいで解いたので本戦出れるかは分からないです。

TSGのSlackbot紹介「一人麻雀BOT」

※この記事は TSG Advent Calendar 2017 3日目の記事です。

前日の記事でお送りした通り、TSGのSlackには部員が実装した多くのSlackbotが棲息している。この記事ではそんなSlackbotたちの中でも特に手間をかけて作られた、一人麻雀BOTを紹介する。

一人麻雀BOTとは

名前の通り、麻雀を一人でプレイできるBOTである。

#sandboxチャンネルで「配牌」と言うとゲームが始まる。

f:id:hakatashi:20171203182349p:plain

プレイヤーは「打◯◯」もしくは「ツモ切り」と発声することでゲームを進行させる。和了るか流局するとゲーム終了である。

f:id:hakatashi:20171203182633p:plain

得点は引き継がれ、チーム内全体で共有される。なのでなかなかスリルがある。

一人麻雀のルール

筆者が調べた限り、一人で行う麻雀に関する統一的なルールは存在しない。なので以下のルールは主に筆者が調整したものだが、プレイしている部員からのゲームバランスの評価は高い。

  • 最初の持ち点として25000点を与えられる。
  • ゲームの最初に13牌の手牌を並べ、17回の摸打で和了を目指す。
  • 配牌するたびに場代1500点を支払う必要がある。
  • 和了った場合通常の麻雀と同じように得点計算が行われ、その点数を獲得する。
  • 17回の摸打で和了れなかった場合、流局となる。このときノーテンだと不聴罰符として3000点を取られる。
  • リーチすると一巡ごとに河として牌を三つ公開する。この中に当たり牌が含まれていた場合ロンとなる。リーチ前のロンはできない。
  • リーチ状態で流局した場合、供託点1000点が支払われる (供託点は返ってこない)。
  • 持ち点が0点を下回るか50000点を上回った場合、勝敗が記録され点数がリセットされる。
  • ポン・チー・カンは存在しない。暗槓も (今のところ) できない。
  • 場局は常に東一局、プレイヤーが親となる。(なので東の刻子は常にダブ東である)
  • 誤ツモ・誤ロン・不聴立直は満貫払いの錯和である。

通常の麻雀で必要とされる、相手の河から手牌を読むという一番不毛な考えさせられる手順を必要としないので、気軽にプレイできると評判である。

ちなみに現在までの最大得点は倍満24000点で、2回出ている。

f:id:hakatashi:20171203181959p:plain

f:id:hakatashi:20171203181955p:plain

技術的な話

ソースコードはこちら。

github.com

Node.jsで実装されている。得点とか役の計算とかは自前で実装なんてやってられないので、riichi-coreというライブラリを使用させて頂いている。このライブラリは四人麻雀のゲーム進行全体を実装したものなのだが、無理くりいじって得点計算の部分だけ流用した。

Slackとの繋ぎ込み、ゲームの進行、得点管理、ドラ・天和・海底摸月・ダブルリーチ・一発などの判定はこのライブラリでは賄えないので自前で実装してある。

手牌画像生成

画像生成はmahjong.hakatashi.comに投げている。これは本来別の目的のために実装したものだが、一人麻雀BOTのために大幅に改造したので、ついでに解説する。

ソースコードはこちら。

github.com

mahjong.hakatashi.comは、任意の手牌画像を生成できる Web API である。Unicodeの麻雀文字を使用して以下のようなURLにアクセスすると、いい感じの画像が降ってくる。

例: https://mahjong.hakatashi.com/images/🀊🀊🀋︀🀋🀌🀌🀜🀝︀🀓🀔︀🀕🀘🀘🀞?王牌=🀫🀫🀅🀫🀫🀫🀫🀫🀫🀫🀫🀫🀫🀫

f:id:hakatashi:20171203184216p:plain

赤ドラは赤くしたい牌の直後に U+FE00 を挿入すると表現できる。

画像生成はかなりお手軽実装で、「snap.svgSVG生成 → electronでPNGに変換」という手順を取っている。Web技術だけで完結するので非常に楽ちんだが、バックでいちいちブラウザを起動しているのでとてつもなく重い*1。余裕があればimagemagickとかに置き換えて高速化したいところ。

今後

  • Hubotへの移植
  • 暗槓の実装
  • 三麻モードの実装

他のSlackチームへの移植もご自由にどうぞ (今のところかなりめんどくさそうだが)。プルリクも待ってます。

明日は@_gacinがなにか書きます。

*1:なのでむやみにアクセスされると死ぬ

TSG民の生活を特に支えていないSlackbotたち

※この記事は TSG Advent Calendar 2017 2日目の記事です。堂々と遅刻宣言するスタイル。

おそらく他の多くのプログラマー団体がそうであるように、TSGもサークル内でSlackを大いに活用し、その恩恵に大いに預かっている。

この記事ではそんなTSGのSlackに導入されている、#sandboxという一風変わったチャンネルと、そこで動く、箸にも棒にも掛からないようなSlackbotたちを紹介する。

TL;DR

  • TSGのSlackにはBOT用のチャンネル#sandboxが存在する
  • #sandboxでは古いメッセージが自動的に削除される
  • 加えて、部員のBOTコードを一元的に管理し、幸せなSlackbot開発を支えている

#sandboxとは?

Slackbotを作るのは楽しい。TSGでは部員の技術研鑽のためにもSlackbotの開発を奨励している (?) が、そんなSlackbotの欠点は、時として非常にウザがられるということである。

過剰に反応するBOTが通常の人間同士の会話にたびたび割り込んでくると、会話の流れを追いにくくなるし不要な通知も多く届くことになる。毎日のチャットを便利にしようと開発したBOTが逆に鬱陶しがられることも少なくない。

この二律背反を解決するために、TSGでは#sandboxというチャンネルを用意している。このチャンネルは、主にBOTとの会話や会話未満のチャットをするための部屋で、情報量の少ないチャットを隔離し、人間の会話との棲み分けをするために設けられている。実装したSlackbotの中でも、特にくだらない実用的でないものはこのチャンネルの中でのみ動作するようになっている。

これだけならただのBOT用チャンネルだが、#sandboxには多くのBOT向けの特殊な機能が実装されている。その一つが、24時間以上経過したメッセージが自動的に削除される機能である。

sandboxクリーナー

TSGはSlackの無料プランを使用しているので、履歴として見れるメッセージの数に限りがある。BOTが投稿するメッセージは時として膨大な量になるので、大量のpostで他のチャンネルの会話を不要に消してしまいかねない。

そこで、TSGでは#sandboxにpostされた投稿は、24時間以上経過したものから順次削除し、さらに削除したメッセージをログとして保存しておくようにしている。

技術的にはクリーナーを定期実行しているのは Heroku Scheduler、ログの保存先は AWS DynamoDB である。サーバーレス最高。

ログの保存は当初 Heroku Postgres を使用していたのだが、10000レコードのレコード制限に引っかかるのと、SlackのpostはJSON構造をしておりRDBに不向きであるので、スキーマレスなDynamoDBに移行した。

記事執筆時点で、20,891レコードが記録されている (多すぎ)。ちなみに、DynamoDBは無料枠の25unit/sをすべてここに注ぎ込んでいる。レコード数的にはもっと少なくていいはずなのだが、バッチ処理をしているのでどうしてもこれくらいの処理能力が必要なようである。機会があれば Google Cloud Datastore などへの移行を検討したい。

sandboxクリーナーのソースコードはこちら。 https://github.com/tsg-ut/tsgbot/blob/master/sandbox-cleaner.js

Slackbotフレームワーク

TSGでは部員がSlackbotを作る機会が非常に多いので、BOTを一元管理するための仕組みが存在する。特に常時起動する必要があるタイプのBOTを動かすときには、一元化されたアプリケーションに部員のコードを取り込むことでリソースを削減することができる。

github.com

また、BOTアカウントを作成する時は、各部員が各々tokenを取得するとSlackのアプリケーション数制限に引っかかってしまうので、Slackbot用に使用するBOTアカウントを一つ決め、これを部内で共有することで無意味にtokenが増えることを防いでいる。

コードはNode.jsで実装され、TSGのサーバーで動いている。Herokuにデプロイしてもよいのだが、無料枠で利用してsleepされると困るのでここだけ自分たちで管理している。

まとめ

TSGのSlackのBOT事情の大枠を解説した。具体的に実装されているSlackbotの内容は各TSG部員が明日以降の Advent Calendar で解説してくれるはず⋯⋯と信じている。

Firefoxにパイプライン演算子を実装した

主に大学とバイトで忙しくオープンソース的な活動がしばらくできていない@hakatashiだが、大学の実験でSpiderMonkeyの開発に参加し、なおかつ本家にマージされるという貴重な経験を得たので、これまでの経緯とかをまとめておく。

かつ、本実験では実験レポート代わりにブログ記事を1つ書くことになっている (すごい) ので、そちらのレポートも兼ねている。

相方のブログエントリはこちら:

siquare.hatenablog.com

SpiderMonkeyとは

SpiderMonkeyとは、JavaScriptの処理系の一つである。フロントエンドエンジニアならばまず知らない人はいない。主にMozillaが保守・開発を行っており、Firefoxブラウザに標準で搭載されている。あまり知られてないが当初は Netscape Browser に搭載するために実装されたJSエンジンであり、同時に世界で初めてブラウザに実装されたJSエンジンでもある。

SpiderMonkeyの処理系としての大きな特徴は、JavaScriptコードを実行する際に、一度中間言語であるバイトコードに変換してからインタプリタに通すという点である。この点で、直接マシンコードに落とし込んで実行するV8などの他のJSエンジンとは大きく異なる。これはある意味プラットフォームに応じた最適化がやりづらいとも言えるが、一方でコンパイラのコードがフロントエンドとバックエンドにはっきりと分離できるため、可搬性が高く、開発にかかるコストが低いのも特徴である。今回SpiderMonkeyに新しい機能を実装する際にも、この構成に大いに助けられた。

実装までの経緯

本来ならば、こんな大規模プロジェクトに、しがないフロントエンドエンジニアであるところの僕が関わる機会はまず無いのだが、どういうわけかガッツリと関わりを持ってしまった。これもいろんな偶然と人との出会いがあった故である。

僕が通っている電気系の学科で行われる実験の一つに、「大規模ソフトウェアを手探る」というタイトルのものがある。これは一言で言うと「大規模なOSSプロジェクトを一つ選び、何かコントリビュートする」という実験 (?) で、シンプルながら非常に意欲的な内容である。オープンソース主義者であるところの僕も当然ワクワクする実験だったのだが、さらに驚くべきはこの実験を担当するTAの一人がMozillaのコミッターであり、Firefoxリポジトリに日常的にパッチを投げ続ける、まさに「JavaScriptのプロ」であったという点である。実験中にもレクチャーがあったが、実験課題としてSpiderMonkeyを選択した場合、なんとその場で彼がコードレビューして、本体にマージすることも可能であるとのこと。

実験を通して@siquareとペアを組んだ僕は、2人で相談して「SpiderMonkeyに新しい言語機能を追加する」という課題に取り組むことに決めた。お互いJSエンジンへのコントリビュート経験もなくやや敷居の高そうな内容だったが、TAの一人がMozillaのコミッターであること、2人ともJavaScriptには日常的に世話になっていること、そしてこんな機会はめったに巡ってこないであろうことを考えて、すこし背伸びをしてみることにした。

タイムライン

実験に割り当てられた時間は10日間あった。タイムラインは以下のような感じである。

  • 1日目: 実験に関する大まかなレクチャー。
  • 2日目: 実験で苦楽を共にするペアを組む。取り組む課題を決める。環境構築。パイプライン演算子のドラフトを読む。
  • 3日目: 環境構築。トークナイザの修正。
    • この時点で@siquareがすでにトークナイザを完成させてASTを吐けるようになっていた。はっやーい。
    • そのころ@hakatashiはいまだに環境構築をしていた。
  • 4日目: バイトコードエミッタの修正。
    • ASTをバイトコードに変換する処理。ソースコードで言うところのBytecodeEmitter.cppに相当する。
    • 既存の関数呼び出しのバイトコードを吐いてる所を流用して書いたら、一応それっぽいものが動いた。
      • 提案書に書いてあるサンプルコードとかもちゃんと正しく動いたので、この時点でもう勝った気になっていた。
  • 5日目: 演算子の優先順位の修正。テストの記述。
    • 演算子の優先順位をより仕様に沿った形に修正。および仕様へのフィードバック (後述)。
      • この作業にだいぶ詰まって時間を溶かす。
    • テストは書き慣れたJavaScriptで記述する。楽しい!!
  • 6日目: テストを完成させる。優先順位問題の解決。評価順問題について検討。
    • 一応リグレッションがないように他のテストも全て走らせたところEC2の4コアマシンで8時間程度かかった。世の中のFirefox開発者の開発用マシンはどうなっているのだろうか。
  • 7日目: 評価順問題の解決。Reflect.parse API の実装。Bugzillaにパッチを投げる。
    • Reflect.parseは、SpiderMonkeyのJS部分から利用できるAPIで、JSコードの文字列をパースしてASTをJSオブジェクトとして取得できる関数である。
      • これのデバッグ自体にパイプライン演算子を使ったら、とてつもなく便利だった。誰だこれ実装したの。
      • 例: '10 |> parseInt' |> Reflect.parse |> (_ => JSON.stringify(_, null, ' ')) |> print
    • 実際に投げたパッチはこれ 1405943 - Implement Pipeline Operator |>
  • 8日目: コードレビュー (1回目)。
    • ありがたいことに翌日にはBugzilla上でTAからコードレビューを受けることができたが、不真面目な学生なので実験当日まで放置していた。
    • TA以外からもコメントを受け、configure flag の下にコードを隠すようにしたのもこのタイミング。
    • 2人がかりで修正し、再度パッチを投げる。
  • 9日目: コードレビュー (2回目)。
    • 本家masterへのrebase作業や、パッチ分割作業など。
    • 再度修正し、パッチを投げる。
  • 10日目: 最終発表
    • ⋯⋯があったらしいが、@hakatashiは今年最大級の絶起をキメてしまったため痛恨の欠席。
    • https://pbs.twimg.com/profile_images/3422504765/31886cb104d4d32f9a8e5110dba047eb.png

パイプライン演算子とは

JavaScriptは現在、仕様に関する議論が最も盛んな言語である。新しい機能や構文を追加するためのドラフトが日夜更新され、それを正式な仕様に組み込むための議論や実装が日々進められている。僕らが今回実装した「パイプライン演算子」も、そうしたドラフトの仕様の一つである。

「パイプライン演算子」は、関数呼び出しの新しい文法を定義する演算子で、執筆時点ではstage-1の提案である。

通常、JavaScriptの関数呼び出しは以下のように記述される。

print('hoge');

なんのことはない、他の言語でも見慣れた、丸括弧を用いた記法である。この文法の問題点は、関数呼び出しの多重ネストを行った時にコードの可読性が大きく下がるというものだ。例えば、

print(Boolean(parseInt(getChars(100))));

という感じである。処理の順番としては getCharsparseIntBooleanprint となるはずなのに、コード上では記述する順番が逆転してしまう。これは見方にもよるだろうが、確かに読みづらいかもしれない。

これを解決するために提案されているのが、パイプライン演算子である。上のコードをパイプライン演算子で記述すると、以下のようになる。

100 |> getChars |> parseInt |> Boolean |> print;

見ての通り、関数と引数の順番が逆転している。これにより関数呼び出しを「実際に処理される順番に」記述することができ、コードの可読性が上がる⋯⋯というのがこの演算子が提案された所以である。

関数呼び出しというプログラムの根幹に関わる変更で、個人的に言わせてもらえばヤバい文法といった印象だが、実はこのパイプライン演算子は他の言語でも意外と実装されている。有名どころで言うと Julia, OCaml, F#, Elixir, さらにはLiveScriptやElmといったAltJS系の言語でもこの演算子は実装されている。こうした背景にはLiveScriptやElmのようにJavaScript関数型言語化を推し進めようという流れがあり、pappの陽な仕様を提案しているコミュニティと深いつながりがあったりなかったりするのだが⋯⋯ともかく、関数呼び出しを便利に書ける演算子だということが一番重要なポイントである。

詳しい話は以下あたりの記事が詳しい。

qiita.com

abouthiroppy.hatenablog.jp

今回、SpiderMonkeyに実装する新機能として、このパイプライン演算子の先行実装を選択した。理由はいくつかあるが、主なものは、

  1. 「新しい演算子」という、ユーザーの目に見える部分の実装で、個人的にモチベーションが高かったため
  2. SpiderMonkey (や、その他ブラウザ) で、既に提出されているパッチや実装がなかったため
  3. 関数呼び出しという、既に存在する機能に対する文法なので、バイトコードから先の実装は変更しなくてよく、実装が軽そうに見えたため
  4. この仕様を先行実装するBabelプラグインが存在し、実装も数十行程度とシンプルだったため

という感じである。

開発

実際の開発は、相方の@siquareと協力して淡々と進めていった。最終的に本家に送信したパッチは、実装部分が@siquare, テスト部分が僕という分割がされているが、実際には特にどちらがどちらを担当したということはなく、お互いが実装とテストを行ったり来たりしながら開発を進めていった。

実装に関して、細かい話は@siquareのブログエントリに書いてあるので省略するが、特に難しかったというか、意外なつまづきポイントだったのが「関数と引数の評価順の問題」だった。

例えば、以下のコード

print(hoge);

と、

hoge |> print;

は、ほぼ同じ動作をするが、厳密には等価ではない。これは実装している最中にパイプライン演算子のドラフト仕様を読んでいて発見したことだが、通常の関数呼び出しでは「関数」→「引数」という順番 (printhoge) で評価が行われるのに対し、パイプライン演算子では「引数」→「関数」という順番 (hogeprint) で評価が行われることになっている。要するにコードに記述した順番に評価が行われるのだ。

確かにコードを書く側からすればそっちのほうが直感的かもしれない。しれないが実装する側からするとこれは少し考えものである。というのも僕らの実装ではSpiderMonkey中間言語であるバイトコード自体には手を付けず、パイプライン演算子を既存の関数呼び出しのバイトコードに変換するという手法を取っていた。それなのに厳密には直接の関数呼び出しとは違うとなるとだいぶ困ってしまう。これを厳密に「仕様に忠実に」実装するために大いに悩んだが、最終的にはpickというバイトコードの命令を用いて、「引数」→「関数」の順番でスタックに積んだあと、スタックを回転してから関数呼び出しの命令を実行することで解決した。

仕様へのフィードバック

今回開発を進める上で僕が特に注力したのは、実際のパイプライン演算子の実装というよりもむしろ、社会的な活動のほうだった。実験中にTAも仰っていたとおり、まだ仕様として固まっていないドラフトをJSエンジンに先行実装する意義とは、実際に実装を行ってみて得られた経験や、ユーザーが新しい仕様を使った時の知見を、フィードバックとして仕様に還元することにある。パイプライン演算子に関する議論はおもにGitHubで今も激しく行われているが、実際にJSエンジンに実装してみたのは (おそらく) 僕らが最初ということもあって、実際に実装して初めて得られた疑問や知見などを積極的に仕様にフィードバックしていこうと考えていた。

パイプライン演算子の優先順位問題

そんな中で特に難しい問題だったのは、演算子の優先順位の問題である。

パイプライン演算子の優先順位をどのように定義するかに関して、執筆時点ではまだ仕様が確定していないが、なるべく優先度を低くするという点で見解は概ね一致しているようである。というのも、パイプライン演算子は複数連ねて書くので他の演算子と組み合わせた時に括弧無しで書けるのが望ましく、また見た目にも「大きい」演算子なので、優先順位が低いほうが直感的であるというのが主な理由である。

このような流れもあって、当初はこの意向に従い、「あらゆる演算子の中で最も優先順位が低い」という実装を行う予定だった。しかし実際に実装してみると、思わぬ壁にぶちあたった。

今回はじめて知ったことだが、実はJavaScriptの演算子の優先順位は、「単項演算子」→「二項演算子」→「三項演算子」という順番で並んでいる。ふだんJavaScriptを書いていて意識したことはなかったが、言われてみればたしかにそうである。SpiderMonkeyではこの仕様を受けて、演算子が取る引数の数を最初に見て、まずはその順に優先順位を確定させてしまうという実装になっていた。

今回僕らは三項演算子 (?:) よりもパイプライン演算子の優先順位を低く設定しようとしたため、SpiderMonkeyのコードに大きな変更を加えなければいけないことに気がついた。これには多大な労力を伴う上に、「単項演算子」→「二項演算子」→「三項演算子」というJavaScript演算子の優先順位のルールを崩すことになってしまう。果たして本当にパイプライン演算子の優先順位は「全ての演算子の一番下」であるべきかと疑問に思った僕らは、パイプライン演算子の提案に対してその旨フィードバックし、実装は「二項演算子の一番下」で行うことにした。

(なお、ドラフトの仕様では最初から「二項演算子の一番下」という優先順位になっている。)

スプレッド演算子との組み合わせ

もう一つ、関数呼び出しのコードを手探っていて気になったのは、スプレッド演算子に対する対応である。

ES2015で定義された比較的新しい関数呼び出しの文法に、スプレッド演算子が存在する。例えば、以下のコード

const array = [1, 2, 3];
print(...array);

は、

print(1, 2, 3);

とほぼ等価である。

これをパイプライン演算子に応用するとどうなるのかを考えたところ、以下のような文法が考えられるのではないかと考えた。

...array |> print;

ヤバいを通り越してバイオハザードのコードだが、いちおう論理的には辻褄が合っている⋯⋯かもしれない。

こういった応用手法に関して突っ込んだ議論は見られなかったので、実装していて考えたことをいくつかまとめて、新たにIssueを立てた。さすがにあまりポジティブな反応は得られなかったが、パイプライン演算子の提案を前に進めるためにいちおう一定の貢献はできたのではないかと考えている。

本家コードへのマージ

そんなこんなで紆余曲折を経て、SpiderMonkeyに仕様どおりのパイプライン演算子を実装することができた。

stage-1の提案というかなり実験的な機能だということもあって、当初は本家のコードへのマージは現実的でないと考えていたが、TAの尽力もあって、configure flag 付きという条件のもとで本家ブランチへのマージができるようになった。要するに現時点では配布版のFirefoxのビルドには含まれないし、この演算子を試すにはソースからビルドし直さないといけないけれど、ソースコードには残して貰えるということである。

2回のコードレビューを経て、約束通り本家ブランチにマージして頂き、コミット履歴に僕と@siquareの名前を残すことができた。我々エンジニアにとってはこれ以上ない栄誉である。

hg.mozilla.org上のコミット:

GitHub上のコミット:

というわけで、僕も@siquareも、今では立派な (?) Firefoxコントリビューターである。

今回実装したパイプライン演算子を今すぐ試したい場合は、このページの手順に従ってSpiderMonkeyをビルドする必要がある。その際、./configureのフラグに、--enable-pipeline-operatorをつけるのをお忘れなく。

全体を通しての感想

まず何よりも、本実験を統括してくださった田浦先生、ならびに実験を通して非常にお世話になったTAの藤澤さんに深い感謝を。ありがとうございます。お世辞抜きで最高の実験でした。最終日は本当に申し訳ありませんでした。

加えて、実験ペアの@siquareにも大きな感謝を。最初から最後まで迷惑かけっぱなしで、最終発表に加えてこの実験レポートの執筆も遅れに遅れてしまったので本当に反省したい。謝謝。

そして何度でも書くが、今回の実験でSpiderMonkeyにコントリビュートするという貴重な経験を得ることができて、感激の至りである。やっぱり普段の活動ではこういったプロダクションレベルの大規模OSSに関わるチャンスは少なく、特にSpiderMonkeyのような基盤的なソフトウェアには逆に苦手意識すらあったので、これを機に他のOSSにも手を出していけたらと思っている。

実験全体を通して、レポート代わりのブログ記事や、毎回の進捗発表の時間など、先進的な取り組みを多く取り入れていて、参加して非常に楽しい実験だった。EEICの名物実験と銘打ってもいいくらいだと思う。もしこの記事を読んで実験に興味を持った東大生がいたら、ぜひ電気系に進学してほしい。というわけで、#進振りはEEICへ

祖母が他界しました

さる9月9日、母方の祖母が他界した。

穏やかな往生だった、と聞いている。かねてより病状の芳しくなかった祖母は市の療養所で長いあいだ治療を受けていたが、9月の初めごろから体調が急変、幽明の境を彷徨った。危篤の報を受けた母は直ちに実家に戻り、祖母を見舞った。一時は持ち直したものの、結局は看病の甲斐なく、9月9日の未明、祖母は2人の娘に見守られながら息を引き取った。享年81歳。

僕にとっては、人生で初めて経験した近しい親族の死だった。

盆に実家に帰った時に祖母を見舞ったばかりだった。その時の祖母はたどたどしくも会話ができ、食事に同席することもできた。ふくよかになった孫の姿を見て朗らかに笑っても見せた。が、祖母は傍目に見ても明らかに衰弱している様子だった。骨ばった手からは死相が透けて見えるようだった。病室を退去するとき、これが今生の別れかもしれないと思って祖母の手を握った。だからと言ってはなんだが、僕は祖母の死に関して思い残すことはなかった。祖母の凶報に接した際にも、どこか冷静でいられた。

祖母が息を引き取ったとき、僕はサークルの合宿で一宮に逗留していた。間が悪く連絡手段を喪失していたこともあって、僕がその訃報を受け取ったのは9日の夜になってからだった。父母と相談の上、翌晩に合宿を切り上げて葬儀と告別式に出席することになった。

ひとり帰路についた夜の10時半、僕以外に乗客のいない外房線の電車の中で、僕はおそらく初めて死について本気で考えた。明かりの少ない真っ黒な車窓と、親しかった肉親が失われたという事実は、これまでずっと目を逸らしてきた死という現実を、今まで接してきたどんな人物の死よりもリアルに突きつけてきた。

臨終の瞬間、祖母は何を考えていたのだろうか? 祖母以外の人間がそれを知ることは、絶対にできない。観測できないなら、存在しないのと一緒だ。それなら一体、なんの意味がある?

それまでに経験した記憶も感情も、死という圧倒的な一瞬によって強制的に断ち切られる。酒に酔って正体を無くしたり、寝ぼけて上の空になるのとは根本的に違う。酒に酔った人間は「今の自分は正常じゃない」と認識することができる。少なくともその余地がある。だが死はそうではない。「おらは死んじまった」などと、死を俯瞰するメタな視点を持った自分自身が存在しない。「存在しない」が存在するのではなく、「存在しない」も存在しない。虚無ではない。虚空ではない。まるで事象の地平面のように、僕の世界は死より手前までしか存在しない。宇宙の外側が真空なのではなく空間そのものが存在しないように、精神世界の観測は生と死に区切られた有限区間の中でしか成立しない。そんな絶対のボーダーライン、超えられないはずの彼我の境界面を、死はたやすく突き抜けていく。駅のホームで少し体重を傾けるだけで超えていく。それだけではない。日常生活においてすら、僕らの生命は完全に僕らの手中にあるわけじゃない。人間の命は常に誰かの手に握られている。飛行機の操縦士に。エレベーター技師に。天災に。人災に。誰かの悪意に。

怖かった。22歳の最後の夜、僕は本気で死を恐れていた。

死とはなんだ。死んだら僕の意識はどこへ行く。死んだら何もかもなくなるなら、それまでの人生になんの意味がある。今この瞬間、意識するいとまもなく刹那に僕の脳が吹き飛んだら、僕の主観はどうなるのか。その時未来は存在するのか。すでに通過し克服したと思っていた数多のドグマが僕を責め立てる。

死を連続する意識の終端と捉えるなら、死は眠りに落ちる瞬間と何も変わらない。僕らは毎夜々々死んでいる。そんなことはもちろん知っている。だったらどうして僕はこんなに動揺しているのか。眠りに落ちたあとの人生が存在しないことに、どうして震え上がるほどの恐怖を抱いているのか。それは純粋に本能ゆえか、それとも⋯⋯。

外房線ロングシートで揺られながら、僕はこれまでの人生で感じたことのない孤独と不安で気が狂いそうだった。科学に縋った弱い現代人は、宗教という心のシェルターから永遠に追放される。死への恐怖は、合理化し続ける社会が残した、人類への最後の呪いだ。

仮にジャネーの法則を信じるならば、僕は既に人生の半分以上を体感で過ごしたことになる。だけど足りない。僕はまだ全然生き足りない。こんな僕がもし天寿を全うしたとしても、死に臨み、「もう十分生きた。もう飽きてしまった」と本気で思えるのか、相当に疑わしい。

翌朝、黒いスーツに着替え、新幹線で神戸に向かい、親族と合流した。この日は奇しくも僕の誕生日だった。葬儀、告別式、読経、献花、出棺、火葬、骨上げ、繰り上げ法要、何から何まで初めてのことばかりで、ちゃんと祖母を見送ってあげられたか自信がなかった。僕は死化粧をした祖母の顔を直視することができなかった。棺に花を添えるとき、皆がそうするように祖母の顔を撫でるのが怖かった。骨上げのとき、ひび割れた肩甲骨を拾う箸が震えた。

告別式で、喪主である祖父が挨拶を述べた。20代で結婚し、苦楽を共にし、人生を分かち合った二人が、艱難辛苦を乗り越え、ついに添い遂げた瞬間だった。常に飄々とし、芸術と神戸を愛し、そして何よりも祖母を愛していた祖父の、耳慣れない嗄れ声を、僕はその時初めて聞いた。

「ついに、この日がやって参りました。妻との思い出は、思い出せば、限りがありません。ですが、それを語ることは、もう、できません。さようなら。さようなら。」

祖父が口にした、およそ挨拶らしからぬこの最後の別れの言葉が、今もショッキングに僕の耳にリフレインしている。きっと一生、それこそ、この身がああして焼かれるまで忘れられないだろう。

人事は棺を蓋いて定まる。

いつかこの長い昼が閉じ、僕の棺が永遠の闇に覆われるとき、はたして僕の人事は平穏に定まっているだろうか。祖母の霊柩が閉じられたとき、そんなことを考えていた。

僕は、ついに祖母の遺体に触れることができなかった。

人生を有意義に生きることは難しい。そもそも意義なんてないかもしれない。それでも前を向いて生きよう、なんて、今の僕には言えない。ただ、生きている僕らには容赦なく朝がやってくる。この世界には大切なものが多すぎる。僕はまだ死ねない。死にたくない。たとえ臨終の時まであと半分しか残されていなくても、たとえ意味はなくても、生きることをやめることはできない。宇宙の全てが決定論に支配されていたとしても、現在という時間的特異点に存在する僕の思惟だけは、決して泡沫じゃない。そう信じて今日を生きた。明日も生きるだろう。死ぬまでそんな調子かもしれない。


最後に。

祖母の訃報に際し、忌引などでご迷惑をかけた皆様、慰めの言葉をかけてくださった皆様に深く感謝申し上げます。祖母は浄土真宗に帰依していたため忌中・喪中はありませんが、個人的心情により四十九日の間は喪に服し、慶事・祭事の類はお断りさせていただこうと思います。よろしくお願いします。

パズルゲーム「MNEMO」を製作しました

この記事は TSG Advent Calendar の13日目の記事です。

f:id:hakatashi:20161213205431p:plain

こんにちは、博多市です。今日はサークルTSGが製作するゲーム、「MNEMO(ニーモ)」の紹介をします。

MNEMOは、パズルゲームです。プログラミングを意識した内容になっており、算数を習いたての小学生からガチのプログラマーまで、広い世代の方に楽しんで頂けるゲームになっています。

百聞は一見にしかず。MNEMOはズバリこんな感じにパーツを組み合わせて、条件を満たす回路を組み立てるゲームです。

試行錯誤を繰り返して目的の回路を組み上げたり⋯⋯。

後半になるとこんな複雑な回路を作ったり⋯⋯?

とにかく楽しいゲームです! ブラウザゲームなのですぐ開いて気軽に遊べます。ランキングもあります。

みなさんもhttps://mnemo.pro/からぜひ遊んでみてください!

あ、できれば Google Chrome でプレイしてください。Safariでもそれなりに動くと思います。それからスマホでも一応遊べますがまだちゃんと対応してないのでできればPCからお願いします。

MNEMO製作記

はい、では一通り宣伝をしたところで、MNEMO開発に至るまでの経緯についてお話します。

まず、TSGとは、東京大学のサークルであり、主にコンピューター系の活動を行っています。僕も(なぜか)4年ほど在籍しています。

TSGの晴れ舞台は、年に1回開催される東京大学の学園祭、駒場祭です。部員はこの日に向けて、各々が持つ技術と誇りを賭けて、来場者向けの展示物を作る⋯⋯はずだったのですが、今年は少し違いました。

今年8月、東京大学工学部丁友会主催の科学イベント、TechnoEdgeの第3回が開催されました。東大に興味を持つ学生が「工学に親しむ」ためのイベントなのですが、今年はこれにTSGも声をかけていただいたので、喜び勇んで出展することになりました。そして本番に先駆けて、TSGでは恒例の夏合宿を行い、内部イベントとしてミニハッカソンを開催しました。この時にTechnoEdgeに出展するための企画案として製作されたのが、MNEMOです。

当初はプロトタイプということで、現在よりもだいぶ簡素なデザインでしたが、ゲームの構想やルール自体は今とほとんど変わっておらず、この時点でも遊んでいてかなり楽しいものでした。*1

f:id:hakatashi:20161213232250p:plain

MNEMOという名前もこの時つけられました。名前の由来は、アセンブリ言語で用いられる「ニーモニック(mnemonic)」の頭の部分です。簡単な命令を組み合わせて複雑な回路を作るというアイデアが低級言語を彷彿とさせたので、こう名付けました。命名者は僕です。

なので読み方は「ニーモ」です。一部で「エムネモ」と呼ばれており部内でも浸透していますが正式には「ニーモ」です。「ニーモ」なのです。

そして迎えたTechnoEdge当日、無事MNEMOを見せられるレベルまで完成させ、来場者からそれなりに好評を得ることができました。MNEMOのライブパフォーマンスも行われました。

本来はこのTechnoEdgeのためだけに製作されたゲームだったのですが、MNEMOのパズルゲームとしての単純さと奥深さにポテンシャルの高さを感じ、そのままTSGの部内プロジェクトとして開発を進めていくことになりました。その後の地道な開発や駒場祭における展示を経て、本日パブリックリリースと相成りました。

そんなこんなで、TSGメンバーがそれなりに頑張って作ったゲームです。ぜひぜひ楽しく遊んであげてください。よろしくお願いします。

また、MNEMOはオープンソースプロジェクトです。改善してほしい点や追加したいステージなどありましたらぜひGitHubまでお寄せください。

最後に、以下は2016年12月現在のMNEMOのContributor一覧です。ありがとう!

f:id:hakatashi:20161213234912p:plain

*1:ちなみに、TechnoEdge前日までこのデザインのままでした。

EEICの過酷な課題生活を支える(願望)技術

この記事は eeic Advent Calendar 2016 その2 の3日目の記事です。

qiita.com

←2日目: Android アプリ解析基礎 その2 -PC編- 4日目: チノちゃんと寝る


博多市は、東京大学の2016年度進学選択で、工学部電気電子工学科に内定しました。内定に至るまでの経緯についてはこの Advent Calendar の22日目でもう少し語ろうと思うので、今回は割愛します。

工学部電気電子工学科は、隣接学科でありほぼ同じカリキュラムが適用される工学部電子情報工学と合わせて、EEICと略されます。名前の通り電気と電子を扱う学科であり、量子力学から論理回路、高級プログラミング言語に至るまで、電子技術を支えるあらゆる学問について手広く扱う学科です。

そんなEEICは、工学部の中でも指折りのスパルタな学科であると(少なくとも学科内では)言われています*1。かくいう僕も、EEIC内定から3ヶ月の間に随分思い知らされました。

まあ大体こんな感じです。

さて、そんなEEICで、博多市はWeb長*2を引き受けています。EEICにおいてはサーバー管理やNASの管理、ウェブサイトの管理、Slackの管理など仕事の多いWeb長ですが、相互扶助の精神が育まれるEEICにおいて僕も何かWeb長として人々に貢献できる仕事がないかと探した結果、eeic2017botというslackbotを制作することにしました。

EEIC2017のSlackについて

EEICでは学年内での連絡やコミュニケーションにSlackを用いています。今年度の内定生はなんと学科ガイダンスが行われる以前から自主的にSlackチームが組織されており、ガイダンス時点ですでに半分が加入している状態でした。現在の学年Slackは僕がこれを引き継ぐ形で受け取ったもので、チームとしてはガイダンス以前に組織されたものがそのまま使われています。

例えば、以下のようなチャンネルが存在します。

  • #general: 真面目な話をする部屋
  • #random: 真面目じゃない話をする部屋
  • #assignment: 課題について相談する部屋
  • #programming: プログラミングについて質問する部屋
  • #competitive-prog: 競技プログラミング勉強会の話をする部屋
  • #examination: 試験中(!)に使われる部屋
  • #party: コンパや懇親会の連絡をする部屋

このうち、EEIC生にとって生命線となるのは #general#assignment です。#general には休講情報や落とし物情報などが流れ、#assignment ではシケ対が解いたレポートの解答や課題の解き方について流れてきます。

eeic2017botは、そんな #assignment チャンネルでEEIC生に日々課される課題をお知らせする仕事をしています。

eeic2017botのおしごと

f:id:hakatashi:20161202023039p:plain

これがeeic2017botです。

ひと目見て分かる通り、カオスです。「EEICたん」は僕がeeic2017botに勝手に付けた名前、アイコンは電気系の民なら誰でも知っているルートヴィッヒ・ボルツマンの肖像です。気がついたら誰かが設定してました。きっと課題のやり過ぎで精神を病んだEEIC生の仕業に違いありません。

EEICたんは、先ほどの画像のようにEEIC生の誰かが登録した課題情報をお知らせしたり、

f:id:hakatashi:20161202024449p:plain

週の初めにその週に提出する課題の一覧をお知らせしたり、

f:id:hakatashi:20161202024457p:plain

前日まで課題をやらないうっかりさん*3のために課題締切前日にお知らせを流したりしてくれます。

eeic2017botの仕組み

eeic2017botのソースコードGitHub一般に公開しています。オープンソース最高。

eeic2017botがお知らせする課題の一覧は、EEICのWikiの「課題一覧」というページを参照しています。WikiはEEIC生なら誰でも編集できるので、課題情報の更新が誰か一人に頼り切りにならなくて運用が楽です。

f:id:hakatashi:20161202025145p:plain

一番最初に引用したツイートで呟かれている「EEICで1ヶ月のうちに課された課題の一覧」は、実はこのページの目次を参照しています。

そして、BOTのプログラムはHeroku上で動いています。Heroku Scheduler で動かせば、BOTが眠ることもないし消費するDynoも低く抑えられるので楽ちんです。11月の消費Dynoは10.8時間でした。

プログラムはNode.jsで書かれており、Heroku Scheduler で10分おきに実行されるごとにEEICのMediaWikiにアクセスし、APIを叩きます。ページを取得するとそれを気合でパースし*4、登録されている課題の一覧を取得します。

取得した課題はRedis上にキャッシュされ、新しく追加された場合はSlackに通知、そして指定時刻になったら該当する課題を引いてきてSlackに通知します。新しく追加された課題を検索するとき、RedisのSDIFFを使うと高速で便利です。

ちなみに、このプログラムはコミットログを見るとわかりますが電気回路理論の授業中に作成され、その日のうちに運用開始しました。課題を通知するために課題がおろそかになるとは皮肉なものです(適当)。

EEIC生を支えるその他の技術

EEIC生の過酷な課題生活を支えるのはeeic2017botだけではありません。

EEIC2017のシケ長は専用のTwitterアカウントを持っており、課題や試験の情報を逐次お知らせしてくれています。

twitter.com

それから、EEICの試験問題やシケプリは過去のものも含めてすべて専用のWebDAVサーバーに蓄積されています。こちらはEEIC全学年共用のストレージになっており、ブラウザ上でアクセスできるフロントとしてPydioを採用しています。

f:id:hakatashi:20161202173101p:plain

まとめ

というわけで、EEICのB2の課題生活とそれを支える技術について紹介しました。EEICに内定してから3ヶ月、学科生専用のサーバーがすでに構築されていたり、レポートが出されたその日のうちにシケ対がレポートの解答をSlackに上げたりと、EEICの学生の真面目さ(とクソ真面目さ)には驚くばかりです。

みなさん、#進振りはEEICへ

明日は@Ishotihadusさんの「チノちゃんと寝る」です。お楽しみに。

*1:しかし、東大には2年冬から授業が全て本郷で行われる学科や、一度でも授業を欠席すると留年が確定する学科があったりするので侮れない。

*2:東大用語。主にクラス/学科のメーリングリストやウェブサイトの管理などを行う役職。

*3:つまり僕

*4:MediaWikiにはaction=parseというAPIがあるようなのですが、使用したAPIラッパーが非対応みたいなので使えていません。いつかラッパーを捨てて対応したい⋯⋯。