エスケープシーケンス
先日の講義の課題として,次のような課題が出されました。
任意の整数kを1つ発生させ,その数をあてるゲームを作成せよ
なお,予測した数が外れた場合は正解kとの大小関係を表示すること
プログラムの入門者向けの課題としてよく見るやつですね。
今回は,初めて出された「ゲーム」課題ということなので,UIにこだわってみることにしました。
ただしコンソール上で動くプログラムしか作ることが出来ないため,エスケープシーケンスというものに手を出してみました。
ゲームの計画段階では次の動作を考えました。
- ゲームクリア時・ゲームオーバー時は背景をそれっぽい色に染める →文字・背景色の変更
- 表示された大小関係のログを画面上に出す →カーソル位置の移動
エスケープシーケンス
エスケープ文字(\e, \033)に続けて文字列を記述することでコンソールの制御を行うことが出来ます。
Linuxでソフトウェアのインストールを行っている際に「 | / - \ 」がアニメーション表示されている様子を見かけるかと思いますが,恐らくこれのことです。
色の変更
"\033[x;y;z"を出力することで色の変更・属性の変更を行うことが出来ます。
x, y, zには次に示したコードが入ります。
また,これらは省略可能です。
文字色・背景色
色は数字2桁で示します "AB"
Aには,文字色変更の場合"3",背景色変更の場合"4"が入ります。
Bには,変更する色が入ります。次を参考にしてください。
0: 黒
1: 赤
2: 緑
3: 黄
4: 青
5: マゼンタ
6: シアン
7: 白
属性の変更
属性は次の通り指定できます。
1: 太字
2: 弱強調
4: 下線
5: 点滅
7: 反転
8: 非表示
カーソル位置の変更
"\033[y;xH"を出力することでカーソル位置を指定することが出来ます。
x, yにはカーソルを移動させる列,行が入ります。
画面クリア
背景色を変更した後,画面のクリアを行うことでコンソール全ての領域の背景を指定色に変更することが出来ます。
画面クリアは"\033[2J"を出力することで可能です。
サンプル
上記で書いた命令は一部ですが,今回はこれだけのエスケープシーケンスを使用して課題プログラムを作成しました。
サンプルコードを貼り付けておきます。
#include <stdio.h> #include <stdlib.h> #include <time.h> void bye(){ printf("\033[0m"); // 文字属性・文字色を初期化します。これをしないとそのまま残ってしまいます exit(1); } /* 課題として設定された関数群なので気にしないでください */ /* エラーメッセージを表示した後,終了する関数です */ void usage1(int n){ printf("Usage: k62301 a b n\na: max, b: min, n: try\nyour input was %d\n", n-1); bye(); } void usage2(int a, int b){ printf("a must be small than b\nyour input was a=%d, b=%d\n", a, b); bye(); } void usage3(int n){ printf("0 < n < 50\nyour input was n=%d", n); } void showLog(int log[], int current, int k){ int i, j; printf("\033[2J"); for(i=0; i<current; ++i){ // カーソル位置を左上の方に移動させています。 printf("\033[%d;%dH", i%20+1, (int)(i/20)*25); if(log[i]<k) printf("%2d: %d < ans\n", i+1, log[i]); else printf("%2d: ans < %d\n", i+1, log[i]); } } int main(int argc, char *argv[]){ int a, b, n; int i, j; int k; int tmp; int t; int startTime; int log[50]; startTime = (int)time(NULL); srand(startTime); if(argc != 4) usage1(argc); a = atoi(argv[1]); b = atoi(argv[2]); n = atoi(argv[3]); if(b < a) usage2(a, b); while(n < 1 || n > 49){ usage3(n); scanf("%d", &n); } k = rand()%(b-a+1); k += a; // 文字色を黄色,背景を黒色に printf("\033[33;40m"); // 画面をクリア printf("\033[2J"); i = n; while(i--){ // 23行目0列にカーソルを移動 printf("\033[23;0H"); printf("*Challenge %d\n", n-i); printf("your answer = "); scanf("%d", &tmp); log[n-i-1] = tmp; showLog(log, n-i, k); if(tmp < k){ printf("\033[23;40H"); printf("\ttrue answer is bigger than %d\n", tmp); continue; } else if(tmp > k){ printf("\033[23;40H"); printf("\ttrue answer is smaller than %d\n", tmp); continue; } printf("\033[37;43m"); printf("\033[2J"); printf("\033[15;25H"); printf("Clear!\n"); printf("\033[16;25H"); printf("The answer is %d\n", k); printf("\033[17;25H"); printf("Clear time is %d\n", (int)(((int)time(NULL)-startTime))); usleep(1000000); bye(); } printf("\033[37;41m"); printf("\033[2J"); printf("\033[15;25H"); printf("Game over\n"); printf("\033[16;25H"); printf("The answer is %d\n", k); printf("\n"); usleep(1000000); bye(); return 0; }
英語が正しいかはさておき,ひとまずこれで動作しました。
ざっとコメントを書いておいたので,真似れば使えるようになるはずです。
Windowsで使用する場合
今回紹介したエスケープシーケンスはANSIで決められたものだそうです。
Windowsのcmd.exeで使用する場合は少し小細工が必要です。
"ansicon"を使いましょう。
ダウンロードしてインストールコマンドを打てばすぐに使えるようになりました。
わかりやすくまとめてある記事があったので,紹介にとどめておきます。
Windows環境でRSpecなどの出力に色を付けるには - 萬由無事覚書
☆参考
もう一度基礎からC言語 第47回 特殊な画面制御~コンソール入出力関数とエスケープシーケンス エスケープシーケンスによる画面制御
シェル - echoで文字に色をつける その1 - Miuran Business Systems
Windows環境でRSpecなどの出力に色を付けるには - 萬由無事覚書