テキストエディタはCプログラミングに不可欠な道具だ. 世の中には様々なエディタが存在するが, この授業では,Unix の伝統的なエディタ「 vi 」の改良版「 Vim 」(Vi IMproved)を推奨する.
一般人向けのエディタ (Linux の gedit・leafpad や Windows の notepad(メモ帳)等) では,特別な訓練を必要とせず,誰でもすぐに使い始められるが, 作業を効率的に進めるための機能は貧弱であることが多い. 一方,Vim や vi では,操作方法に多少のクセはあるものの, 慣れてしまえば,異常に高速な編集作業が可能になるだろう.
前回と同様に, 端末を開き,作業用ディレクトリを準備しよう:
$ cd # ホームディレクトリへ移動 $ mkdir c-0502 # 本日の作業用ディレクトリを作成 $ cd c-0502 # 作業用ディレクトリへ移動
まず今日は,反省文「I'm sorry」を 100回書け!! 一般人向けのエディタ gedit や leafpad では: (一般人向けではないが emacs でも OK)
$ leafpad sorry.txt &
I'm sorry. I'm sorry. I'm sorry. ...
パワハラかよ? いいえ,これは実行しなくても構いません.
面倒くさいと思う気持ちが大切. これから効率的な作業方法を紹介します.
Vim を使ってみよう:
$ vim sorry.txt # 末尾に「&」は無用
まず,1行だけ入力しよう: 最初に [i] キーを押してから...
I'm sorry. # 末尾に改行
第1行へカーソルを移動し, 続けて [Esc][y][y][9][9][p] キーを操作しよう. これで1行が 100行に簡単に増えた.
素晴らしい. もし,1,000行とか 1,000,000行にしたくても, 労力はほとんど変わらない.
そして最後に [:][w][q][Enter] キーで保存・終了となる.
Vim の操作方法について,自習用プログラム vimtutor を利用して学習しよう:
$ vimtutor # 日本語版 # または $ LANG=C vimtutor # English version
次のコマンドは是非ともおぼえよう:
また,コマンドによっては,回数を指定して繰り返せたりもする. 例:[1][0][x] で 10文字削除.
端末内でキャラクタを上下左右に操作できるようなプログラムを開発しよう. C言語のソースファイルを Vim で効率的に編集してゆこう.
ソースを編集:
$ vim game.c
#include <ncurses.h> // 端末制御に必要 int main(void) { int x = 10, y = 10; // キャラの位置(xは右方向,yは下方向) initscr(); // 端末制御を開始 while (1) { // ひたすら繰り返す move(y, x); // カーソル(表示位置)を移動 printw("(^o^)"); // キャラを表示 getch(); // キー入力を待つ x++; y--; // キャラの位置を変更 } endwin(); // 端末制御を終了 return (0); }
コンパイルと実行:
$ cc game.c -lncurses -o game $ ./game
何かキーを押すと,キャラが移動するよ.
しかし,移動方向は固定(右上だけ)だし, 画面からハミ出すと異常動作におちいる. また,画面にゴミ(キャラの跡,キーの文字)を残してしまうし, カーソルも目障りだ. 正常終了もできないので,[Ctrl]+[C] で強制終了しよう.
ソースを改造:
#include <ncurses.h> int main(void) { int x = 10, y = 10; int key; // 入力キーの文字が入る initscr(); noecho(); // 入力キーは表示するなよ curs_set(0); // カーソルは表示するなよ while (1) { erase(); // 画面全体をクリア(跡を残さない) move(y, x); printw("(^o^)"); key = getch(); // キー入力を待ち,文字も調べる if (key == 'h') { x--; } // キーの文字に応じた方向へ移動 if (key == 'j') { y++; } if (key == 'k') { y--; } if (key == 'l') { x++; } if (key == 'q') break; // [Q]キーで繰り返しを終了 // これら5個のほぼ同じ行を Vim で効率的に編集せよ. // 1個は手動入力,他はコピペ(yy,4p)と置換(r や cw)等で } endwin(); return (0); }
コンパイルして実行しよう.
キャラを [h],[j],[k],[l] キーで上下左右に移動できるようになった. Vim のカーソル移動コマンドの練習にもなるよね.
画面からゴミは消えたし,[q] キーで正常終了も可能となった.
しかしまだ,ハミ出すとダメです.
#include <ncurses.h> int main(void) { int x = 10, y = 10; int key; int w, h; // 画面の幅と高さ(width,height) char *face = "(^o^)"; // キャラの顔文字 int size = 5; // 顔文字の文字数 initscr(); noecho(); curs_set(0); getmaxyx(stdscr, h, w); // 画面サイズを調べ,h と w に代入 while (1) { erase(); move(y, x); printw(face); // 顔文字は変数化したよ key = getch(); if (key == 'h') { x--; } if (key == 'j') { y++; } if (key == 'k') { y--; } if (key == 'l') { x++; } if (key == 'q') break; if (x < 0) x = 0; // ハミ出したら画面内に戻す if (x + size > w) x = w - size; if (y < 0) y = 0; if (y > h) y = h - 1; // これら4行も Vim で効率的に編集せよ } endwin(); return (0); }
画面上の位置はゼロから数えることに注意しよう. 画面の幅が w なら,x = 0 〜 w-1 が画面内, x < 0 や x > w は画面外ですよ.
#include <ncurses.h> int main(void) { int x = 10, y = 10; int key; int w, h; char *lt="d^ ))"; // 左向き顔(left) char *rt="(( ^b"; // 右向き顔(right) char *dn="(^o^)"; // 正面顔(下方向 down) char *up="((*))"; // 後ろ頭(上方向 up) // これら類似の4行も Vim で効率的に編集せよ char *face = dn; // 最初は正面顔にしておく int size = 5; initscr(); noecho(); curs_set(0); getmaxyx(stdscr, h, w); while (1) { erase(); move(y, x); printw(face); key = getch(); if (key == 'h') { x--; face = lt; } // 方向に応じて顔を変更 if (key == 'l') { x++; face = rt; } if (key == 'j') { y++; face = dn; } if (key == 'k') { y--; face = up; } // これら類似の4行も Vim で... if (key == 'q') break; if (x < 0) x = 0; if (x + size > w) x = w - size; if (y < 0) y = 0; if (y > h) y = h - 1; } endwin(); return (0); }
ちょっとだけ面白くなった?
#include <ncurses.h> #include <stdlib.h> // rand() に必要 int main(void) { int x = 10, y = 10; int key; int w, h; char *lt="d^ ))"; char *rt="(( ^b"; char *dn="(^o^)"; char *up="((*))"; char *face = dn; int size = 5; char *flag="__P__"; // 旗の絵文字 int f = 0; // 旗の有無 int fx, fy; // 旗の位置 initscr(); noecho(); curs_set(0); getmaxyx(stdscr, h, w); while (1) { erase(); if (f == 0) { // 旗が無い場合... fx = rand()%20; // ランダムな位置に... fy = rand()%20; f = 1; // 旗を立てる } move(fy, fx); printw(flag); // 旗を表示 move(y, x); printw(face); key = getch(); switch (key) { // 処理の効率化,コードの適切化 case 'h': x--; face = lt; break; case 'l': x++; face = rt; break; case 'j': y++; face = dn; break; case 'k': y--; face = up; break; // これら4行も Vim で効率的に編集せよ } if (key == 'q') break; if (x < 0) x = 0; else if (x + size > w) x = w - size; // 処理の効率化 if (y < 0) y = 0; else if (y > h) y = h - 1; // 同上 if ((x == fx) && (y == fy)) f = 0; // 旗をゲットしたら消す } endwin(); return (0); }
さらにゲームっぽくなった? この流れで,追いかけっこ系のゲームとか アルゴロジック的なものを開発できそうでは?
vimtutor を再度実行し,より効率的な編集方法を身に付けてゆこう. 意識的に頭で考えなくても自然と手が動くまで練習しよう.