Minecraftとタートルと僕

PCゲームMinecraftのMOD「ComputerCraft」の情報を集めたニッチなブログです。

ターミナル画面で点を描き、線を引こう

はじめに

これまで僕たちは、CCのターミナル画面(コンピュータやタートルを右クリックして表示される画面)や外付けのモニタを使って、文字を表示してきました。

今回はこれらの上で点を描き、線を引く方法について学びましょう。

色を使いますので、Advanced ComputerやAdvanced Monitorが必要です。
色を使うなら金色と覚えておきましょう。
f:id:hevohevo:20140206195040p:plain:w200f:id:hevohevo:20140206195101p:plain:w200

ターミナル画面で点を描く

まずは金色のAdvanced Computerを用意しましょう。
右クリックで表示されるターミナル画面上で赤い点を描画します。

「edit」コマンドを使うなりして次のプログラムを入力し、実行してみましょう。

term.setBackgroundColor(colors.red)
term.write(" ")

これは、背景色を赤にし、画面上に半角スペースを表示しています。
つまり空白を表示することで背景色の赤を前面に見せているわけですね。
なおここでは赤色を使いましたが、ComputerCraftでは計16色の色が使えます。詳しくは以下を参照のこと。

画面上では以下のように左上の位置に赤い点(というより少しだけ長方形)が表示されています。
また、点のすぐ後ろの位置にカーソルが移動し点滅していますが、これはterm.write()で文字を表示したのですから正常な動きです。
f:id:hevohevo:20140206195441p:plain:w300

じつはこれが、ComputerCraftにおける点の描画です。
これ以上、小さな点は打てません。
もし絵を描くならば、この点を最小の単位として、組み立てていかなくてはなりません。

さて、このままだと画面に赤い点が残ったままですし、点滅カーソルの位置も中途半端ですね。
元に戻すにはいくつかの方法がありますが、もっとも単純なのはCCコンピュータをリセットすることです。
リセットするには「CTRL+R」キーをしばらく押しっぱなしでしたね。

ターミナル画面の画素数を調べる

次に、以下のプログラムを書いて実行しましょう。

w, h = term.getSize()
print(w,' ',h)

実行結果は、「51 19」と表示されます。

「term.getSize()」関数は、ターミナル画面のサイズを返します。返値は2個あり、それぞれ幅と高さを返します。Luaの文法により、多重代入、つまり変数wに幅を変数hに高さを代入しています。

このことから、ターミナル画面は幅51、高さ19しか使えないことがわかります。
言い換えると、このターミナル画面の解像度は51x19であり、このターミナル画面で絵を描こうと思ったら、51x19=969個の点(そして16色の色分け)しか使えないことになります。

任意の位置に点を描く

では、任意の位置に点を打ってみましょう。

term.setCursorPos(10,10)
term.setBackgroundColor(colors.red)
term.write(" ")
  • カーソルの位置を座標(10,10)に移動
  • 背景色を赤にする
  • 空白を表示することで背景色の赤を描く

f:id:hevohevo:20140206200240p:plain:w300
さきほど調べたように、ターミナル画面の幅と高さは51x19です。座標(1,1)から(51,19)までの範囲で自由に点を打つことができます。

ここで注意したいのは、点を打ったあと、カーソル位置がそのすぐ後ろ(今回は、(11,10))に移動していることです。これを利用すると横線を描くのがとても簡単になります。

横線を引く

座標(2,2)から(11,2)まで長さ10の横線を引いてみましょう。
プログラムは以下のとおりです。

term.setCursorPos(2,2)
term.setBackgroundColor(colors.red)
for x=2,11 do
  term.write(" ")
end

f:id:hevohevo:20140206200652p:plain:w300
空白スペースを表示するたびにカーソル位置がどんどん横に移動していくので、
単純に座標(2,2)から10個の空白スペースを表示すればいいですよね。

縦線を引く

縦線のときは、横線のように簡単にはいきません。
毎回、カーソル位置を移動しなくてはなりませんね。
以下は、座標(2,2)から(2,11)まで長さ10の縦線を引きます。

term.setBackgroundColor(colors.red)
for y=2,11 do
  term.setCursorPos(2,y)
  term.write(" ")
end

f:id:hevohevo:20140206201054p:plain:w300

正方形(?)を描く

縦線、横線を描くことができるなら、これらを利用して四角形を描くこともできます。
左上の始点(2,2)、右下の終点(11,11)という一辺が10の正方形を描いてみましょう。

とりあえず2つのプログラムを紹介してみます。

term.setBackgroundColor(colors.red)
for y=2,11 do
  term.setCursorPos(2,y)
  for x=2,11 do
    term.write(" ")
  end
end

まずはこのプログラム。
こちらは、先に紹介した横線プログラム(1つ書き込んだらカーソルが右に1つずれるよ)を利用したプログラムです。カーソル位置を変えるのは、y座標を変えるときだけ。
つまり、一番左側の座標にカーソル移動(setCursorPos())、そして10個の空白を表示という流れを繰り返します。

次にこちらのプログラム。

term.setBackgroundColor(colors.red)
for y=2,11 do
  for x=2,11 do
    term.setCursorPos(x,y)
    term.write(" ")
  end
end

こちらの趣旨を簡単に言うと、
「カーソルが横に1つずれるとか気にしない。とにかく描画したい座標に毎回カーソルを移動するぜ」
というプログラムです。効率で言うと先のプログラムよりは落ちるかもしれませんが、大したロスではありませんし。

どちらを使うかは好みの問題ですね。個人的には、難しいこと考えずに済む後者の「気にせず毎回カーソル移動」プログラムで良いと思います。

このプログラムで描画される10x10の正方形はこちらです。
f:id:hevohevo:20140206201127p:plain:w400


……ええ そうなんです。これ、正方形じゃないですよね。
でもこれは仕方ないのですよ。
だって、1つの点を描いた時点で縦長の長方形だったじゃないですか。それを組み立てて作った四角形もやはり長方形になってしまうのです。

さてこれを無理やり正方形にしてしまいましょう。
1つの点を考えたとき、この縦長の長方形は「横:縦 = 2:3 = 1:1.5 」の比率になっています。
つまり縦の方が長く、横の1.5倍になっています。
そのため、使う点の数をこの比率に合わせて計算してしまえばいいのです。

たとえば横方向の辺の長さ15(個の点)を絶対と考えて正方形を描くならば、
縦方向はその3分の2にしてしまえばいいのです。つまり縦方向は15*2/3=10(個の点)です。

term.setBackgroundColor(colors.red)
for y=2,11 do --2から11までの10回繰り返し
  for x=2,16 do -- 2から16までの15回繰り返し
    term.setCursorPos(x,y)
    term.write(" ")
  end
end

f:id:hevohevo:20140206202630p:plain:w400
ね?正方形でしょ?

点の縦横比についての補足

「横:縦=2:3」といいましたが、厳密に言うと、点を描く場所によって縦横比が異なります。
f:id:hevohevo:20140206205712p:plain:w450
上図の9箇所の点を見るとわかるように、点を描く場所によって縦横比が異なっているように見えます。
そこで、スクリーンショットを拡大して各点がいくつのドットから構成されているか数えてみました。

  1. 最左上(1,1) → 16x22 = (12+4)x(18+4)
  2. 最左中(1,10)→ 16x18 = (12+4)x18
  3. 最左下(1,19)→ 16x22 = (12+4)x(18+4)
  4. 中央上(5,1)→ 12x22 = 12x(18+4)
  5. 中央中(5,10)→ 12x18(基準)
  6. 中央下(5,19)→ 12x22 = 12x(18+4)
  7. 最右上(51,1)→ 16x22 = (12+4)x(18+4)
  8. 最右中(51,10)→16x18 = (12+4)x18
  9. 最右下(51,19)→16x22 = (12+4)x(18+4)

基準となるのは5番目の点(5,10)です。この点のように画面端に隣接していない点は必ず12ドットx18ドットとなっています(つまり、横:縦=2:3です)。
それに対して、画面端に隣接している点はその隣接している側のサイズが4ドット分追加されています。
(例1:1番目の最左上の座標(1,1)の点は左側と上側の2箇所が画面端に隣接しているため、縦方向と横方向の両方が+4ドット追加されて(12+4)x(18+4)=16x22ドットの長方形になっている)
(例2:2番目の最左中の座標(1,10)の点は左側のみ画面端に隣接しているため、横方向のみ+4ドット追加されて12+4=16ドットとなっている)

つまり言い換えると、画面端にある点は、4ドット分の幅や高さが追加されます。
図で示すと、以下のように水色のラインに隣接した点に4ドット分の追加があります。
例えば下の図で、水色のラインを取り除いた赤い四角形は全て同じ大きさの12x18長方形になっているように見えませんか?
f:id:hevohevo:20140206210056p:plain:w400

よって、本当に正確に厳密な正方形を描きたいならば、画面に隣接した正方形は避けたほうが良いでしょう。
その分だけ余分な計算が必要となりますので。

描画内容を完全消去するプログラム

さて、最後に、
毎回CCコンピュータをリセットするのも面倒ですし、描画内容を完全消去するプログラムを紹介しましょう。

term.setBackgroundColor(colors.black)
term.clear()
term.setCursorPos(1,1)
  • 背景色を黒にする
  • 画面を消去(正確には背景色で全面を塗りつぶす)
  • カーソルの位置を左上(1,1)に移動

ポイントは、最初に背景色を黒(colors.black)にしてから、「term.clear()」関数を実行することです。そうしないと、寸前でterm.setBackgroundColor()した色で塗り替えられてしまいます(たとえば直前に背景を赤に指定していたら全面が赤になる)。
ここで勘の良い方は気づいたのではないでしょうか。まさかと思ったあなた、おそらく正解です。

term.claer()は、画面全体、すべての座標を空白スペースで上書きする関数だと思われますwww
残念ながらJava側の処理だと思われるのでソースを見ることはできませんが、おそらく間違いないでしょう。力技ですね:P
ですから、最初に背景色を黒に戻しておかないといけませんし、画面全部に空白スペースを描くのですから、処理にも時間がかかるわけです。

term.claer()をfor文などで高速に繰り返し実行すると画面がちらつくのはこれが原因なんですね。

まとめ

ターミナル画面で絵を描くための基本知識

  • 色を使うなら金色コンピュータ
  • 赤い点を描く → 背景色を赤にして空白スペースを表示
  • 画素数=term.size()
  • 任意の位置に描く → カーソルを移動する「term.setCursorPos(x,y)」
  • 最小単位の点の縦横比が、3:2なので、絵を描くときには縦横の比率をあらかじめ計算すること
    • たとえば、縦の長さを3分の2にするなど
  • 画面消去は、背景色を黒に戻してから「term.clear()」。負荷高いので使い時は気を付けて。
広告を非表示にする