hantas's blog

ブログ移転しました→ http://blog.taniho.net/

エスケープシーケンス

先日の講義の課題として,次のような課題が出されました。

任意の整数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;
}

英語が正しいかはさておき,ひとまずこれで動作しました。
ざっとコメントを書いておいたので,真似れば使えるようになるはずです。
f:id:hantas:20150627015115p:plain

Windowsで使用する場合

今回紹介したエスケープシーケンスはANSIで決められたものだそうです。
Windowsのcmd.exeで使用する場合は少し小細工が必要です。
"ansicon"を使いましょう。
ダウンロードしてインストールコマンドを打てばすぐに使えるようになりました。
わかりやすくまとめてある記事があったので,紹介にとどめておきます。
Windows環境でRSpecなどの出力に色を付けるには - 萬由無事覚書

☆参考
もう一度基礎からC言語 第47回 特殊な画面制御~コンソール入出力関数とエスケープシーケンス エスケープシーケンスによる画面制御
シェル - echoで文字に色をつける その1 - Miuran Business Systems
Windows環境でRSpecなどの出力に色を付けるには - 萬由無事覚書