TIS-100

TIS-100

Not enough ratings
【ネタバレなし】TIS-100初心者マニュアル
By MikenekoTaro and 2 collaborators
このガイドでは、マニュアルの内容をなるべく簡単な日本語に書き換えたものです。
TIS-100の基本単位となるノードとその仕組みに端を発し、保存領域、特殊モジュール、そしてTIS-100を扱うコツに触れていきます。
初心者編では条件分岐までの内容を扱います。
   
Award
Favorite
Favorited
Unfavorite
結局何をすれば?
ストーリーと目標
叔父が急逝し、彼の遺品を整理する中で奇妙な機械を見つけた主人公。
叔父の身に何が起こったのか、そしてこの機械は何なのかを調べるべく、主人公は機械を直す決意をしたのだった。

各ステージには必ず破損したノードが含まれており、メモリに遺された叔父のメモを見ることができます。
メモを読み進め、叔父の身に何があったのか解き明かすのが目標となります。

操作がわからない
この質問に答えなければ初心者マニュアルの名折れでしょう。

TIS-100を起動すると、起動シークエンスの後にステージ一覧、TIS-100 SEGMENT MAP(メモリ領域一覧)が表示されます。

この画面でEscキーを押した際に表示されるメニューの選択肢は、上から
ゲームを終了する、フルスクリーン/ウィンドウ表示の切り替え、マニュアルを表示する
の三つです。

1ステージを除いてノイズが走っています。ノイズの走っているセグメントの上にはREPAIR 1 MOREなどと表示されており、この数字だけステージを解く事で次のステージが開放されます。
画面左上が各ステージのセーブスロットです。スロットをダブルクリックするとステージが開きます。

画面の左上にステージの目標が記されています。
各ステージの目標は次のガイドで和訳しています。

この画面でEscキーを押した際に表示されるメニューの選択肢は、上から
ステージ一覧に戻る、全ノードのプログラムを削除する、マニュアルを開く
です。

左下のボタン[▶RUN]、あるいは[F5]キーで装置を実行できます。
[⏸PAUSE]/[F6]で一時停止、もう一度[⏯️STEP]/[F6]で1ステップ実行します。
[▶RUN]/[F5]で再開できます。
[⏹STOP]/[Esc]でプログラムを停止し、実行前の状態に戻します。
ガイド独自の用語
位置記号
このガイドでは説明の便宜上、ノード・ポートに通し番号をつけます。
一般的で、上下左右の位置関係がわかりやすいExcel・チェス式でノード番号を割り振ります。
左上のノードをA1とし、左列を上から順にA1、A2、A3とします。その右隣の列は上からB1、B2、B3、その右隣はC1、C2、C3、一番右端がD1、D2、D3です。B列の2段めといった具合ですね。
ノード間のポートはノード名を連ねて表記します。D2→D3のポートはD2D3、B2→C2のポートならB2C2、B2←C2のポートならC2B2といった具合です。
ノード群の上下に位置するポートは、A1の上のポートならA01、D3の下のポートならD34などと呼びます。
#C2B2をCB2だの、C3C2をC32だのと略記できそうではある


その他
文中で複数行の命令を示す際は改行位置を > で示します。
JLZ JEZ JGZ JNZをまとめてJXZと呼びます。
ノードへの命令・ポートについて
単体のノードは記憶、計算、送受信のみ行えます。
ポートを介して隣のノードと情報を送受信することで複雑な処理が行えます。Tessellated(並列型)の名を冠するTIS-100の真骨頂です。

ノードにカーソルを合わせると、命令を書き込むことができます。

ノードへの命令はいわゆるアセンブリ言語で行います。
一昔前のロボットのように、片言の簡単な文しか使いません。
大雑把に見れば、TIS-100で扱う命令は3つの形しか取りません。

<動詞> 例:SAV(保存する)
<動詞> <何を> 例:ADD 9(9を足す)
<動詞> <何を> <どこへ> 例:MOV UP ACC(上の値をACCに書き込む)
一般に、何を、どこへといった目的語をオペランド、動詞をオペコードと呼びます。

単語同士の分かれ目はスペース" "あるいはコンマ", "で示すことができます。
MOV UP DOWNは、MOV,UP DOWNやMOV, ,, , , , ,UP DOWNとも記せます。

ノード内には15行、1行に18文字しか入力できません。
複数行の命令を与えると、上から順に実行します。
最後の命令を実行すると、先頭の命令に戻ります。
命令が一行だけならば、一行の命令を繰り返します。
空の行は命令に影響を与えません。

TIS-100は、内部的には3桁までの数字以外を扱うことができません。
-999から999までの整数のみを扱うため、オーバーフロー/アンダーフローが発生します。MOV -73589 DOWNと命令しても、下のポートへ書き込まれる値は-999です。
値の先頭についた+や0は、明記しても省略しても構いません。
MOV +00056 UPとMOV 56 UPは同じ命令です。

ポートについて
ノードの間には矢印(ポート)が見えます。
ポートを介することで、ノード間で値を送受信できます。
ポートはノード間で値を送受信するための郵便受けのようなものです。
ポートは送信された値が受信されるまでの置き場であり、未だ送信されない値を催促する場でもあります。

矢印の向きに値を送受信できます。基本的に双方向に値を送受しできると考えて構いません。
が、図のように双方のポートが埋まってしまうとポートがロックされてしまいます。動作が止まる原因となるため気をつけましょう。
値を次々に入出力するIN/OUTポートは異彩を放っています。紙テープか何かで大量の値を扱っているのでしょうが、作中では詳細に触れていません。
1ステージの流れ
左上の説明文を読み、ノードに命令し、整合性をテストし、スコアを磨きます。
左上の説明については以下のガイドで和訳しています。

SELF-TEST DIAGNOSTICのステージを開いてみましょう。
画面左側、IN.XとIN.AにはINポートから与えられる数列が、OUT.AとOUT.Xの左列にはOUTポートへ書き込むべき数列が記されています。
このステージではINから読んだ値をそのままOUTへ書き込むので、INとOUTには同じ数列が並んでいます。

SELF-TEST DIAGNOSTICでは、A1, A2, A3に既に命令が書き込まれています。試しにこのまま装置を実行[▶RUN]/[F5]してみましょう。画面右側では、OUT.Xのポートに書き込まれた値がOUT.Xの右欄に表示されています。
ノードに書き込まれている命令はMOV UP, DOWN(上から下に値を移せ)の1行だけですが、IN.Xから流れてきた39個の値をすべて読み取り、OUT.Xに書き込むことができました。
ノードは命令を繰り返すため、1行の命令ですべての値を
[⏹STOP]/[Esc]でプログラムを停止し、実行前の状態に戻します。
ステップ実行[⏯️STEP]/[F6]で、ノード・ポートの動きを細かく見ることができます。

1ステップ動かすと、IN.Xに値"51"が保持されています。この値は次のステップではポートA1A2に移り、その次のステップではポートA2A3に、そしてOUT.Xへと移されていきました。
3つのノードそれぞれがバケツリレー式に値を受け渡しています。
これと同じことをIN.AとOUT.Aでも行えばステージクリアです。
ノードD2に命令を書き込めないため、うまく迂回しましょう。

テストは39個の値からなる数列を4度通すことで行われます。INポートから与えられる値の順番はランダムではなく、何度試験を行っても同じ数列が与えられます。
例えばSELF-TEST DIAGNOSTICのIN.Xに注目すると、
1度めの試験は51, 61, 16, 83, 61, ...
2度めの試験は68, 59, 59, 49, 82, ...
3度めの試験は83, 59, 84, 43, 68, ...
4度めの試験は56, 40, 22, 22, 45, ...
と決まっています。
ACCを使った命令の基本
ACC
ACCの特性に軽く触れておきましょう。
ACCはポートに似ています。MOVの読み取り元・書き込み先どちらにも指定できます。一度値を書き込めば値を記憶しますが、何度ACCから値を読み取っても同じ値を出力できます。しかしACCに値を書き込むと、元あった値は上書きされて消滅します。

実際にACCを使ったコードを動かしてみましょう。
SELF-TEST DIAGNOSTICのノードA1にCODE1、CODE2それぞれを命令すると、OUT.Xにはどのような数列が書き込まれるでしょうか。ノードA2,A3は編集せず、A1にだけ書き込んで実行してみましょう。

#CODE 1
MOV UP DOWN
MOV UP DOWN

#CODE 2
MOV UP ACC
MOV ACC DOWN
MOV ACC DOWN

CODE 1では単純にMOV UP DOWNを繰り返すだけで、OUT.XにはIN.Xと同じ数列が書き込まれました。
CODE 2では、IN.Xに書き込まれた値がA1のACCに書き込まれ、続いてA1はACCの値をA1A2に二度書き込んでいます。このためOUT.Xには51,51,62,62,16,16,83,83,61,61,14,14,35,35,...と、値を2回ずつ重ねた数列が入力されていきます。
ACCに値を保存するイメージが掴めたでしょうか。

MOV
値の送受信を行う関数です。
MOV <受信元> <送信先>の構文で受信元と送信先を指定します。
受信元と送信先は同じ単語でも構いません。
オペランドに取れる単語はUPなどのポート、ACCなどのレジスタです。

受信元に数値を指定すると、特定の数値を送信できます。
ACCの値を特定の値にしたい場合や、ポートを介して合図を送る際に有用です。

ADD, SUB, NEG
ACCへ値の足し引きを行う関数です。
ADD 1でACCの値が+1されます。
SUB 1でACCの値が-1されます。
NEGでACCの値の符号が反転されます。

では、ADD ACCと命令するとどうなるでしょうか。
ACCの値にACCの値を加えるため、ACCを倍にする命令となります。
SUB ACCと命令すると、ACCの値からACCの値を引くので、ACCを0にできます。
MOV 0 ACCと同じ内容を2文字少なく命令できます。
これに対し、ADD UP単体ではMOV UP ACCの代わりになりません。
MOV UP ACCを実行するたびにACCの値は上書きされます。
ADD UPはあくまで上のポートの値を読み取りACCの値に足し合わせる命令なので、MOV UP ACCと同じく動作するためにはACCの値が0である必要があります。
例えばSELF-TEST DIAGNOSTICのノードA1を
ADD UP
MOV ACC DOWN
と書き換えて実行すると、OUT.Xには51,113,129,212,273,287,322,339,...と、書き込まれた数列の第n項までの和を出力してしまいます。正しい値を得るには、
ADD UP
MOV ACC DOWN
SUB 0
と命令しなければなりません。
実行しない命令
コメント
命令の行頭に"#"を書き入れると、その行に書かれた文字列を一切無視します。
このガイドでも、以下命令の注釈は#をつけて記します。

文字数の都合、実際に書けることはかなり限られてしまいます。
MOV LEFT RIGHT、MOV RIGHT DOWNなどの命令はこれだけで14字もあるため、1行にコメントや後述するラベルを行内に収めようとするとかなり手狭になってしまいます。
命令で扱っている変数や命令の意味を3-4文字程度に省略して示すと効果的です。
例:
#A=L AはLと等しい
#A>B AはBより大きい
#LOP ループ
#JR3 JRO 3の遷移先

隣に空のノードがあれば、そこに同行目の命令に対するコメントを書き込むのも有効です。

プログラムのタイトル
セグメント一覧では新しいプログラムを作った際UNTITLED PROGRAM(無題のプログラム)と書かれていますが、肝心のタイトルを編集するボタンがありません。
プログラム中のどこかに#が2つ、##と並んで書かれていれば、それに続く文字列がそのプログラムのタイトルになります。コメント内に##を入力しても##以降の文字列がタイトルに反映されます。
 例: #TITLE IS ##123456 → タイトルは123456

##直後の空白はタイトルに反映されません。
 例: ## A B → タイトルはA B
1つのノード内の複数箇所に##が書き込まれている場合、先頭に近い行のタイトルが優先して表示されます。そのためタイトルを複数行に分けて書く事はできません。
 例: ##BIGBROTHERIS > ##WATCHINGYOU → タイトルはBIGBROTHERIS

ラベル
文字列の後に":"を記すことで、それまでの文字列を名前に冠したラベルを作ることができます。
行中に":"を挟むと、":"までの文字列がラベルとなります。
JMP, JLZ, JEZ, JGZ, JNZ(ジャンプ命令)のオペランドとなります。
名付けたラベルを余さず使う必要はありませんが、ジャンプ先に指定した名前のラベルは必ず明示されている必要があります。
また、同じ名前のラベルを複数設けることはできません。
エラー例:
XX:MOV UP ACC SUB 1 MOV 1 DOWN XX:JGZ XX
用いることのできる文字は英数と以下の記号です。
~`$%^&*()_-+={}[]|\;'"<>,.?/
例を見てみましょう。

ラベルのあとにコメントを書き込むことができます。タイトルも同様です。

ブレークポイント
命令の行頭・行末に"!"を書き入れると、その行を実行する直前に一時停止します。
停止例: !NOP NOP! !NOP!
!のみを書き込んだ行がある場合、その次の行を実行する直前に一時停止します。
 例: SWP > ! > SAV SAVの実行直前に一時停止
#以降に!を書き込んでもブレークポイントとして機能しません。
 例: NOP#! 一時停止せず  NOP!# 一時停止
ラベルの前にはブレークポイントを置くことができますが、:の直前に置く事はできません。
 例: LOOP!: ラベル名エラー !LOOP:またはLOOP:! 問題なし
条件分岐・繰り返し
分岐しない条件
ACCの値に応じてラベルまでジャンプするJXZですが、JXZ直後の行にも実行条件があることにお気付きでしょうか。
- JGZなら直後の行はACCが0以下(0または負)で実行
- JEZなら直後の行はACCが0でない時に実行
- JNZなら直後の行はACCが0である時に実行
- JLZなら直後の行はACCが0以上(0または正)で実行
つまり、JXZ直後の行はJXZでジャンプする条件とは真逆の条件で実行されるわけです。
JEZ, JNZを見れば一目瞭然ですね。
0を含む条件での分岐はときおり求められるので、覚えておいて損はありません。

分岐後の合流
この分岐処理、名前から分岐ばかりに注目しがちですが、分岐後の合流点も注目に値します。
もちろん合流しない分岐処理を作ることもできますが、必ず先頭の命令に戻るTIS-100では、基本的に合流点があります。

この合流点を示すのがラベルです。
ラベルのある行は、ラベル直上の行の次のみならず、ラベルを指したJMP、JXZ命令の次にも読まれるわけです。

試しに一つコードを示します。
01:MOV UP ACC 02:JEZ 04 03:MOV 1 DOWN 04:MOV -1 DOWN
と命令すれば、まず上のポートの値をACCに入れ、

ACCが0ならば行04にジャンプ、
 -1を下のポートに書き込みます。
 最終行なので先頭に戻ります。
 
ACCが0以外ならば次の行03を読み、
 1を下のポートに書き込み次の行04を読みます。
 -1を下のポートに書き込みます。
 最終行なので先頭に戻ります。

04行目で合流しているため、下のポートに-1が必ず書き込まれます。
ACCが0であるならば下のポートに-1だけを、
ACCが0でないならば下のポートに1だけを書き込むにはどうすればいいでしょうか。

3行目を実行した後、次の行に進まなければいいのです。
しかし3行で止まってしまっては、次々与えられる値を処理できません。
そのため、3行目の直後で先頭に戻るようにしましょう。
01:MOV UP ACC 02:JEZ 05 03:MOV 1 DOWN 04:JMP 01 05:MOV -1 DOWN
まず上のポートの値をACCに入れ、

ACCが0ならば05にジャンプ、
 -1を下のポートに書き込みます。
 最終行なので先頭に戻ります。
 
ACCが0以外ならば次の行03を読み、
 1を下のポートに書き込み次の行を読みます。
 01、先頭に戻ります。

ラベルの振り方
何行目かわかりやすいようにラベルに行番号を振りましたが、以下のように役割を略記するほうが修正が楽です。
TOP > TP, LOOP > LP, LABEL FOR JXZ > XZなど、いろいろ工夫してみましょう。
# SAMPLE CODE TP:MOV UP ACC ADD 5 JLZ LZ SUB 10 JGZ GZ MOV 0 DOWN JMP CJ LZ:MOV -1 DOWN JMP CJ GZ:MOV 1 DOWN CJ:ADD 5 MOV ACC DOWN

ループ構文
この行04のように、JMP命令より前の行にあるラベルを指定するとループします。
もっと極端な例を示せば、以下のようになります。
01:MOV UP ACC 02:JMP 01 03:MOV 1 DOWN 04:JMP 01 05:MOV -1 DOWN
まず上のポートの値をACCに入れ、次の行02を読みます。行01にジャンプします。
行03から05を実行しないまま、行01と02の間をループし続けます。

ループ構文は大まかに2種類あります。
ループの始点のラベルをLP、ループ終端の次の行の(ループから抜け出す)ラベルをEXとして、JXZとJMPのみに注目すると以下のようにあらわせます。

ループ1: LP: >>> JXZ LP - ACCの値が条件に合っているうちはループします。
ループ2: LP:JXZ EX >>> JMP LP > EX: - ACCの値が条件に合わなければループしません。

JXZのジャンプでループしているか、JXZのジャンプでループから抜け出すかの違いですね。
それぞれ詳しく見てみましょう。

ループ1: LP: >>> JXZ LP - ACCの値が条件に合っているうちはループします。
 LPラベルとJXZの間でMOV UP ACCの命令を与えれば、ポートから与えられる値を条件としてループできます。
 LP: >>> MOV UP ACC > JNZ LP と命令すれば、上のポートから0が与えられた際にループを抜け出します。

 またACCを、繰り返す回数のカウンターとして使うことができます。
 ACC 2 > LP:SUB 1 >>> JGZ LP
 という命令では、2回ループします。

 順を追ってACCの値を考えると以下のようになります。
  ACCに2を書き込んだ直後、ループに入った途端ACCから1が引かれ、ACCの値は1になります。
  命令を読み進めJGZに至ったとき、ACCの値は正なのでLPラベルに戻り、ふたたびACCから1が引かれます。ACCの値は0です。
  JGZに至ったときACCの値は0なので、ループから抜け出します。

ループ2: LP:JXZ EX >>> JMP LP > EX: - ACCの値が条件に合わなければループしません。
 こちらもACCカウンターとして使うことができますが、挙動が若干異なります。
 MOV UP ACC > LP:JLZ EX >>> SUB 1 > JMP LP > EX: と命令したとき、
 上のポートから負の値が与えられた場合ループ内の命令を一度も実行しません。
 0か正の値が与えられた場合は1回余計に実行します。0なら1回、3なら4回です。

分岐の条件にはACCと0との比較しかありませんが、ACCに値を足し引きすることで0以外の値とも比較が行えます。
例えばACCに3を引いた状態でJXZを行えば3を基準に条件分岐できますし、
ACCに二つの値の差を入れてJXZを行えば二つの値の大小関係によって条件分岐ができます。
ショートカット・命令一覧
キー操作 操作内容
Esc プログラムの実行を停止、あるいはメニューの表示
F1 プログラム編集画面でショートカット・命令一覧を開く *1
F2 問題選択画面(セグメント一覧)で改竄防止状態の説明を開く
F5 プログラムを実行([▶RUN])
F6 プログラムを一時停止/プログラムを一行ずつ実行[⏯️STEP]

PageUp カーソルを先頭行へ移動
PageDown カーソルを最終行へ移動
矢印キー 矢印キーの向きに移動
Ctrl+Z 直前の操作の取り消し
Ctrl+Y 取り消しの取り消し
Ctrl+A ノード内の命令を全選択
Ctrl+X 選択した文字列の切り取り
Ctrl+C 選択した文字列のコピー
Ctrl+V コピーした文字列の貼り付け

*1 F1で開くことができるショートカット・命令一覧は当然英語で書かれています。
ショートカットについては上のとおりですが、命令一覧の訳例を以下に示します。
命令 操作内容
NOP 何もしない
MOV <SRC>, <DST> <読み取り元>の値を読み取り<書き込み先>へ書き込む
SWP ACCとBAKの値を入れ替える
SAV ACCの値を読み取りBAKに書き込む
ADD <VALUE> ACCに指定した値を足す(加算)
SUB <VALUE> ACCに指定した値を引く(減算)
NEG ACCの符号を反転
JMP <LABEL> 指定したラベルの位置からプログラムを実行する
JEZ <LABEL> ACCが0ならラベルの位置からプログラムを実行
JNZ <LABEL> ACCが0でないならラベルの位置からプログラムを実行
JGZ <LABEL> ACCが0より大きいならラベルの位置からプログラムを実行
JLZ <LABEL> ACCが0より小さいならラベルの位置からプログラムを実行
JRO <VALUE> 指定した値だけ先の行からプログラムを実行

略称 省略 意味
NOP nop, no operation 何もしない
MOV move 動かす
SRC source 出元
DST destination 行先
ACC accumulator 集めるもの 記憶素子
BAK backup 予備
SWP swap 入れ替える
SAV save 保存
ADD add 足す(加算)
SUB subtract 引く(減算)
NEG negate 否定 符号の反転
JMP jump ジャンプ
JEZ jump if acc is equal to zero ACCが0ならジャンプ
JNZ Jump if acc is Not Zero ACCが0でないならジャンプ
JGZ Jump if acc is Greater than Zero ACCが0より大きいならジャンプ
JLZ Jump if acc is Less than Zero ACCが0より小さいならジャンプ
JRO Jump Relative Offset 相対位置にジャンプ