2010 年度機械工学総合演習第二 計算機演習 B3 GUI アプリケーション

1
2010 年度 機械工学総合演習第二
計算機演習 B3 GUI アプリケーション
担当:谷川智洋 講師,西村邦裕 助教
TA:梶波 崇,木村 健太郎,竹内 俊貴,山崎 充彦,
http://www.cyber.t.u-tokyo.ac.jp/˜tani/class/mech enshu/
2010 年 7 月 6 日
1 演習の目的
本演習では,X Window System 上で動作する GUI(Graphical User Interface) のプログラムを作成するこ
とで,GUI を扱う上で不可欠なイベント駆動型 (event driven) プログラミングの考え方を学習する.また,
画像ファイルの取り扱いやネットワークプログラムの作成を通して,最終的に普段我々が使っているアプリ
ケーションに近いグラフィカルなプログラム作成を目指す.
前回は,C 言語と GUI(Graphical User Interface) ツールキット・ライブラリを用いた,X Window System
上で動作する画像ファイルの取り扱いなどを通して,GUI プログラミングの基礎を学んだ.本演習では,こ
れまで学習したプログラムに GUI を付け加えることで,最終的に普段我々が使っているアプリケーションに
近いマルチクライアントチャットやバトルシップアプリケーションの作成を目指す.
2 画像処理アプリケーションの作成
前回行った GUI プログラミングを応用して,GUI による画像処理プログラムを作成する.
2.1 描画のためのウィジェット (再)
GTK は GDK (GIMP Drawing Kit) のうえに実装されており,GDK は基本的には基礎となるウィンドウ
関数 (X Window system では XLib にあたる) へアクセスする低レベル関数を包むラッパー (Wrapper) であ
る.GDK のリファレンスとしては,http://www.gnome.gr.jp/docs/gtk+-1.2.x-refs/gdk/index.html を見
ると良い.
スクリーンへの描画の処理の為に使うウィジェットは DrawingArea ウィジェットである.描画領域ウィ
ジェットは,空白のキャンバスでそこに好きなものを何でも描ける.描画領域ウィジェットは以下の呼び出し
によって作成される.
2
2 画像処理アプリケーションの作成
GtkWidget* gtk_drawing_area_new ( void );
/* ウィジェットのデフォルトサイズはこの呼び出しで指定できる.*/
void gtk_drawing_area_size ( GtkDrawingArea *darea,
gint width,
gint height);
描画領域を生成した後,次のシグナルに接続する必要がある場合がある.
• ユーザ入力に応答するためのマウスとボタン押下シグナル
• ウィジットが生成されて表示されたときに必要な処理を行う “realize” シグナル
• ウィジットのサイズが変更されたときに必要な処理を行う “size allocate” シグナル
• ウィジットの内容を再描画するための “expose event” シグナル
RGB,白黒とカラーのイメージ (画像) をウィンドウのピクセルに変換して通常のウィンドウに表示するた
め,GDK には GdkRgb という呼び出しが用意されている.GdkRgb の機能を使用する前に gdk_rgb_init()
関数を呼び出す.もしこの処理に失敗すると,core ダンプする.(GtkPreview を含む) GdkRgb を使用する
全ての GTK+ ウィジットは自分のクラスの初期化メソッド (class_init) の中で gdk_rgb_init() 関数を
呼び出す.よって,間接的にのみ GdkRgb を使用する場合は,その呼び出しを考慮する必要はない.
void gdk_rgb_init (void);
void gdk_draw_rgb_image
( GdkDrawable *drawable,
GdkGC *gc,
gint x,
gint y,
gint width,
gint height,
GdkRgbDither dith,
guchar *rgb_buf,
gint rowstride );
2.2 ウィジェットからの情報の取得 (再)
void gdk_draw_gray_image
3
( GdkDrawable *drawable,
GdkGC *gc,
gint x,
gint y,
gint width,
gint height,
GdkRgbDither dith,
guchar *buf,
gint rowstride );
drawable
: 描画する GdkDrawable (通常は GdkWindow)
gc
: グラフィックス・コンテキスト (全ての GDK 描画関数が必要とするもの
中身は無視される)
x
: drawable の左上端の X 座標
y
: drawable の左上端の Y 座標
width
: 描画する矩形の幅
height
: 描画する矩形の高さ
dith
: GdkRgbDither の値で、お好みのディザ・モードを選択する
rgb_buf
: ピクセル・データで Packed24 ビットデータとして表現される
(Packed : ビットの格納方式の一つ.一ピクセルにつき Depth を
並べて格納する)
rowstride : rgb_buf である桁の始点から次の桁が開始する点までのバイト数
gdk_draw_rgb_image () は Drawable の 中 に RGB 画 像 を 描 画 し ,gdk_draw_gray_image () は
Drawable の中に白黒画像を描画する.引数 rowstride はより柔軟に線を配置するためのもので,一般的に,
0 <= i < width と 0 <= j < height で,pixel(x+i, y+j) は赤 (R) = rgb_buf[j*rowstride + i*3],
緑 (G) = rgb_buf[j*rowstride + i*3 + 1],青 (B) = rgb_buf[j*rowstride + i*3 + 2] となる.
2.2 ウィジェットからの情報の取得 (再)
また,GUI によるインタラクティブなプログラムを作るためには,ボタンのような押す/離すの単純な入力
だけでなく,文字列や数値など,より複雑な情報を入力できるようにする必要がある.ここでは,文字列の入
力方法として entry ウィジェット,数値の入力方法として scale ウィジェットの使い方を学ぶ.
entry ウィジェット
文字列を入力するためには entry ウィジェットを用いる.entry ウィジェットの作成は以下のようなコード
を書けば良い.
4
2 画像処理アプリケーションの作成
/* ウィジェットの宣言 */
GtkWidget *entry;
/* 新しい entry ウィジェットの確保,文字数の設定 */
entry = gtk_entry_new_with_max_length (50);
また,entry ウィジェットの文字列を取り出すには以下のようにすればよい.
gchar *entry_text;
entry_text = gtk_entry_get_text (GTK_ENTRY(entry));
scale ウィジェット
数値を入力するためには,entry ウィジェットに直接数値を打ち込んでも良いが,より GUI らしい入力方法
として,scale ウィジェットが用意されている.scale ウィジェットの使用方法はこれまでのウィジェットより
若干複雑になっている.まず,scale ウィジェットの範囲や初期値,増減値などを設定するための,adjustment
オブジェクトを作成する.adjustment オブジェクトを作成するための関数 gtk_adjustment_new() の使い
方は以下の通りである.
GtkObject* gtk_adjustment_new ( gfloat value,
gfloat lower,
gfloat upper,
gfloat step_increment,
gfloat page_increment,
gfloat page_size );
value
: 初期値
lower
: 最小値
upper
: 最大値
step_increment : 増減値(小)
page_increment : 増減値(大)
page_size
: スケールを移動するウィジェットの大きさ
upper は表示領域の最大値であり,値の最大値ではないことに注意せよ.値の最大値は upper - page_size
になる.
その後,これまでのウィジェットと同様に scale ウィジェットを宣言して確保し,先に作成した adjustment
オブジェクトを指定してやればよい.まとめると,以下のようなコードを書けばよい.
2.3 画像の変換
5
/* オブジェクト,ウィジェットの宣言 */
GtkObject *adjustment;
GtkWidget *scale;
/* 新しい adjustment オブジェクトの確保 */
adjustment = gtk_adjustment_new(0, 0, 255, 1, 1, 0);
/* コールバック関数の設定(ここでは on_slider_moved() という関数をよそで定
義している) */
gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed",
GTK_SIGNAL_FUNC(on_slider_moved), adjustment);
/* 新しい水平 scale ウィジェットの確保,adjustment オブジェクトの指定 */
scale = gtk_vscale_new(GTK_ADJUSTMENT(adjustment));
/* 小数点以下の桁数の設定 */
gtk_scale_set_digits(GTK_SCALE(scale), 0);
また,scale ウィジェットの値をコールバック関数から取り出すには以下のようにすればよい.
void on_slider_moved(GtkWidget * widget, GtkAdjustment *adj)
{
int scale_value;
scale_value = adj->value;
}
2.3 画像の変換
二値化
しきい値 T を定め,画像中の画素値が T より大きいときその画素値を 1,それ以外のとき画素値を 0 にす
る変換を画像の二値化 (binarization) と呼ぶ. 二値化は,文書や図面のような白黒の画像をスキャナで濃淡画
像として入力した場合などに,濃淡画像から二値画像に変換するために行われる.また,一般の画像で対象物
と背景を分離するために二値化が行われることも多い.
コントラスト変換
コントラスト変換は,画像が明るすぎたり暗すぎたりする場合や,微妙に濃淡を変化させたいときなどに用
いられる.画像の濃淡を一定の規則に従って変換するため,コントラストだけでなく階調性も変化する.
もっとも簡単な変換は,画像の濃淡を目的に応じた適当な関数で変換する方法である.入力画像の濃淡レベ
ルを x とすると,この x を目的に応じた関数 f で y = f (x) に変換することで画像のコントラストの改善など
を行う.画像の濃淡レベル域 [xm in, xm ax] が,一部分にしか分布していないような場合など,図 1 に示すよ
6
2 画像処理アプリケーションの作成
うに画像の濃淡レベル域をより広い濃淡レベル線形拡大する.変換関数は,以下の式で表される.
xmax ≥ x のとき,y = ymax
xmin ≥ x < xmax のとき,y =
ymax − ymin
(x − xmin ) + ymin
xmax − xmin
x < xmin のとき,y = ymin
y max
y min
x min
図1
x max
線形変換の例
2.4 線形フィルタを用いた画像処理
次は 3 × 3 の線形フィルタを実装しよう.線形フィルタは入力画像と重み行列 (フィルタ) の単純な畳み込
み演算で画像に効果を与えることができる最も基本的な画像操作のひとつである.線形フィルタには一般に
3 × 3 や 5 × 5 の行列が用いられ,行列の変更のみで効果が変わってくる.ここでは平滑化フィルタ,差分フィ
ルタ (エッジ抽出) について簡単に説明する.
平滑化フィルタ
平滑化フィルタは,注目している画素の近傍の画素値の重みつき積分を,出力画素の画素値とし,主に入力
画像のノイズを除去するために使われる.最も単純なのは近傍画素の単純な平均値をとるフィルタで,3 × 3
のフィルタでは,f (i, j) を入力画像,g(i, j) を出力画像の画素 (i, j) の画素値とすると,
g(i, j) =
1
1
1 ∑ ∑
f (i + m, j + n)
9 m=−1 n=−1
で定義される.これは入力画像と行列


1
9
1
9
1
9
1
9
1
9
1
9
1
9
1
9
1
9


のコンボリューションをとる操作である.この単純な平均値を用いたフィルタは,注目する画素が,周囲の画
素にくらべて極端に大きい,あるいは小さい画素値をとる場合に,周囲の画素値に近づけてる効果をもつ.ノ
イズは一般に近傍の画素と大きく異なる画素値を持つため,このフィルタを適用すれば,ノイズを目立たなく
することができる.しかし,同時に画像上の輪郭もぼかしてしまうことになる.
2.4 線形フィルタを用いた画像処理
7
エッジ抽出フィルタ
エッジ抽出フィルタは,注目画素の近傍の画素の変化率から,画像上の線や輪郭を抽出する.平滑化が積分
なのに対してこちらは微分演算にあたる.実際には近傍画素の差分をとるだけのことで,平滑化フィルタの足
し算が引き算に変わっただけである.
■微分フィルタ,Sobel フィルタ
垂直方向のエッジを抽出する場合,入力画像と以下の行列のコンボリュー
ションを計算すればよい.

−1 0
 −1 0
−1 0

1
1 
1
これを 1 次線形微分フィルタと呼ぶ.更に,注目画素に近い近傍画素ほど重みをつけたものもある.つまり
行列


−1 0 1
 −2 0 2 
−1 0 1
をフィルタに用いるのである.これを Sobel フィルタという.
■ラプラシアンフィルタ
画素の輝度値の 1 階微分によるフィルタでは,画像上の輪郭は,隣り合った画素の
輝度差が大きいところである,という仮定が元になっている.しかし,実際は輪郭以外でも輝度値がなだらか
に変化している部分も存在し,必ずしも 1 次線形微分フィルタで輪郭を抽出できるとはいえない.そこで,輝
度値の変化自体が大きく変化する部分を輪郭と仮定して輪郭抽出を行なうという考え方がでてくる.
すなわち輝度分布の2階微分であるラプラシアンを計算することになる.(図 2).
実際のラプラシアンの計算は,これまで同様に 3 × 3 のマトリクスとのコンボリューションで表現できる.
輝度値の分布が f (i, j) である画像の,輝度値の水平方向,垂直方向の 2 階微分をそれぞれ,
∂2
f (i, j) ≡ {f (i + 1, j) − f (i, j)} − {f (i, j) − f (i − 1, j)}
∂i2
∂2
f (i, j) ≡ {f (i, j + 1) − f (i, j)} − {f (i, j) − f (i, j − 1)}
∂j 2
と定義できるとき,画像のラプラシアン
∂2
∂2
f (i, j) + 2 f (i, j)
2
∂i
∂j
を求めるフィルタを 3 × 3 で表現し,実際の画像でエッジ検出ができる.
8
2 画像処理アプリケーションの作成
図 2 輝度値が変化しているところでは,(A),(B) で変化の度合い (微分係数) は同じであるが,(B) のよ
うに局所的に輝度値が変化しない限り輪郭 (エッジ) とはならない.つまり輝度値の分布の微分では必ずし
もエッジを抽出できるとは限らないが,2 階微分すればより精度よく抽出できることがわかる.
◇ 課題 1 ◇
1. sample5.c をもとに,scale ウィジェットを用いて閾値を指定し画像を二値化せよ(図 3).
2. sample5.c 内の関数 image_filter() を書き換え,上の平滑化フィルタを,3 × 3 の配列を
用いて実装せよ.(図 4).
3. 水平・垂直方向のエッジを強調する Sobel フィルタを作成し,上のフィルタと結果を比較せ
よ.(オプション)
4. ラプラシアンフィルタを作成し,上のフィルタと結果を比較せよ.(オプション)
図 3 課題 1.1 実行時の画面
図4
課題 1.2 実行時の画面
9
3 マルチクライアントテキストチャット
マルチクライアントチャットの回において,ネットワーク越しに複数人の人が同時にテキストチャットを作
成した.本演習では,クライアントの GUI 版の作成と対応するサーバの作成を通して,GUI プログラムとソ
ケット通信の復習を行う.
一般に,グラフィカルユーザインタフェースを持つシングルスレッドのアプリケーションでは,時間が掛
かる処理を行うためのボタンが押されたら “待ちカーソル”(砂時計など) を表示して,動かなくなる.アプリ
ケーションをマルチスレッド化することで,アプリケーションとユーザインタフェースを多くのリクエストに
応答することができる.長時間かかる処理のボタンが押されたら,独立したスレッドにその処理を行わせ,そ
の間もユーザーインタフェースは別スレッドで実行を続けることができる.チャットプログラムでは,ネッ
トワーク通信を独立したスレッドにすることで,GUI アプリケーションはネットワーク通信の状況によらず
ユーザの入力を受け付けることができる.
3.1 テキストチャットサーバ
複数人でネットワーク越しにチャットを実現するためには,サーバ側に複数のクライアントからのアクセス
を受け付ける仕組みが必要となる.本演習では,マルチスレッドを利用してマルチクライアントチャットを実
現する.クライアントからサーバに接続要求があると,そのクライアントに対応する専用スレッドを生成し,
その専用スレッドにチャットデータの送受信を個別に担当させる (図 5).
ǵȸȐᚘምೞ
ȞȫȁǯȩǤǢȳȈȁȣȃȈǵȸȐȗȭǻǹ
੗ዓฎLjǯȩǤǢȳȈऴ‫إ‬
ǯȩǤǢȳȈऴ‫إ‬
ǯȩǤǢȳȈऴ‫إ‬
ǯȩǤǢȳȈऴ‫إ‬
ӲᚘምೞƔǒƷȁȣȃȈϋܾ
ᡛӖ̮ǹȬȃȉᲫ
ᡛӖ̮ǹȬȃȉᲬ
ᡛӖ̮ǹȬȃȉ᳨
ӲᚘምೞǁƷȁȣȃȈ
ϋܾƷȖȭȸȉǭȣǹȈ
ǽDZȃȈᡫ̮
Ӗ̮ǹȬȃȉ
Ӗ̮ǹȬȃȉ
ȦȸǶλщ
Ӗ˄ȗȭǻǹ
ȦȸǶλщ
Ӗ˄ȗȭǻǹ
μȁȣȃȈϋܾ
σஊȡȢȪ
μȁȣȃȈϋܾ
σஊȡȢȪ
μȁȣȃȈϋܾ
μȁȣȃȈϋܾ
ȁȣȃȈϋܾᘙᅆȗȭǻǹ
ȁȣȃȈϋܾᘙᅆȗȭǻǹ
ǯȩǤǢȳȈᚘምೞᲫ
ǯȩǤǢȳȈᚘምೞᲬ
図5
Ӗ̮ǹȬȃȉ
ȦȸǶλщ
Ӗ˄ȗȭǻǹ
μȁȣȃȈϋܾ
σஊȡȢȪ
μȁȣȃȈϋܾ
ȁȣȃȈϋܾᘙᅆȗȭǻǹ
ǯȩǤǢȳȈᚘምೞ᳨
コンソール版マルチクライアントテキストチャットのシステム図
この場合,サーバへのクライアントの接続数と同じ数だけスレッドが生成されることになる.サーバのメイ
ン関数では,クライアントからの接続待ちと,スレッドの生成が主な仕事となる.また,サーバは,対応する
10
3 マルチクライアントテキストチャット
クライアントからデータを受け取った時点で,サーバに接続しているクライアント全てにそのデータを配信す
るようにする.接続したクライアント数と接続したクライアントの ID を管理しておき,接続されているクラ
イアントごとに生成された専用スレッドはクライアントからデータを受け取ると ID 順に各クライアントへ送
信する.
3.2 マルチスレッド補足:スレッドへの引数の受け渡し
新しいスレッドのスタートルーチンに渡す引数は 1 個のポインタである.もっと多くの引数が必要なら,構
造体を準備し引数がそれを指すようにすればよい.
struct two_args {
int arg1;
int arg2;
};
void *needs_2_args(void *);
void a()
{
pthread_t t;
struct two_args *ap;
ap = (struct two_args *)malloc(sizeof (struct two_args));
ap->arg1 = 1;
ap->arg2 = 2;
error = pthread_create(&t, NULL, needs_2_args, (void *) ap);
....
}
void *needs_2_args(void *ap)
{
struct two_args *argp = (struct two_args *)ap;
int a1, a2;
a1 = argp->arg1;
a2 = argp->arg2;
free(argp);
....
}
3.3 テキストチャットクライアント
ターミナルを使ったクライアントプログラムでは,ユーザが文字を入力している間でも,他のクライアント
がチャット内容を送信するとその内容が表示されてしまう.マルチクライアントチャットの回では,文字入力
用のターミナルとチャット内容を表示するターミナルの二つのターミナルを準備し,チャット内容の表示が文
字入力に影響を与えないように改良した.入力用のターミナルでユーザからの文字の入力とチャット内容の送
3.4 ソケット通信
11
受信を行い,受信したチャット内容の情報を,チャット内容表示用のターミナルに渡して,そのターミナルで
表示するようにする.ターミナル間では,プロセスが異なりメモリ空間が異なるので,プロセス間の通信に共
有メモリを用いて情報共有を行った (図 5).
本演習では,前回の演習で学習した GUI プログラミングを用いて,文字入力とチャット内容の表示を一つ
のプログラムで実現する (図 6, 図 8).文字入力を待つ部分と相手からのソケットを通じたデータを待つ部分
は排他になるため,文字を入力し送信するまで,相手側からの文字を読み取ることはできない.そのため,相
手側から文字を受け取る部分にスレッドを使い,文字を入力する部分と相手から文字を受け取る部分の処理が
並列に動作するようにする.マルチスレッドの場合は,同一プロセス内での処理となるのでメモリ空間が共有
されており,グローバル変数を利用することで情報の共有が可能である.
ǵȸȐᚘምೞ
ȞȫȁǯȩǤǢȳȈȁȣȃȈǵȸȐȗȭǻǹ
੗ዓฎLjǯȩǤǢȳȈऴ‫إ‬
ǯȩǤǢȳȈऴ‫إ‬
ǯȩǤǢȳȈऴ‫إ‬
ǯȩǤǢȳȈऴ‫إ‬
ӲᚘምೞƔǒƷȁȣȃȈϋܾ
ᡛӖ̮ǹȬȃȉᲫ
ᡛӖ̮ǹȬȃȉᲬ
ᡛӖ̮ǹȬȃȉ᳨
ӲᚘምೞǁƷȁȣȃȈ
ϋܾƷȖȭȸȉǭȣǹȈ
ǽDZȃȈᡫ̮
Ӗ̮ǹȬȃȉ
Ӗ̮ǹȬȃȉ
μȁȣȃȈϋܾ
Ӗ̮ǹȬȃȉ
μȁȣȃȈϋܾ
μȁȣȃȈϋܾ
VGZVXKGY
VGZVXKGY
VGZVXKGY
ᡛ̮ǹȬȃȉ
ᡛ̮ǹȬȃȉ
ᡛ̮ǹȬȃȉ
ȦȸǶλщϋܾ
GPVT[YKFIGV
ȦȸǶλщϋܾ
GPVT[YKFIGV
ȦȸǶλщϋܾ
GPVT[YKFIGV
ȁȣȃȈǯȩǤǢȳȈȗȭǻǹ
ȁȣȃȈǯȩǤǢȳȈȗȭǻǹ
ȁȣȃȈǯȩǤǢȳȈȗȭǻǹ
ǯȩǤǢȳȈᚘምೞᲫ
ǯȩǤǢȳȈᚘምೞ
ǯȩǤǢȳȈᚘምೞP
図 6 GUI 版マルチクライアントテキストチャットのシステム図
図7
テキストチャットクライアントの起動画面
3.4 ソケット通信
なお,ソケット通信では,大量のデータを一度に送受信しようとしてもうまく動作しないことがある.一
度に送受信できるデータのサイズはネットワークの混雑状態などに依存しており,問題がなければ一度に
100Kbyte 送受信できることもあるが,混雑しているときは 1byte しか送受信することができない場合もあ
る.次に行う音声データの送受信には,送るデータのサイズがテキストの送受信の場合にくらべ格段に大き
12
3 マルチクライアントテキストチャット
図8
テキストチャットクライアントの実行画面
くなり,分割して送信する必要がある.このため,read での受信,write での受信,の際,その返り値を参
照して再送 (または再受信) を行う必要がある.例えば,256byte を送信したい場合に,write の返り値が
100 だった場合,残り 156byte が未送信状態なので,このデータを再送信する必要がある.socket read() と
socket write() は,この仕組みを実装したコードである.
3.5 パッキングボックス復習
より多機能な GUI プログラムを作るためには、複数のウィジェットを思い通りの位置に配置する必要があ
る.Gtk+ をはじめとして多くの GUI ツールキットでは “パッキング” という操作によってウィジェットの
配置を行なう.パッキングの操作では,“見えない箱” の中にウィジェットを詰め (=パック),さらにその箱
をより大きな箱に詰めていって,多数のウィジェットを高率良くウィンドウの中に整理して並べるのが基本で
ある.
テキストチャットの GUI 版クライアントの場合,図 8 のようなレイアウトを実現するために,まず,
entry box というパッキングボックスに entry ウィジェットと送信ボタンを順番にパッキングする.次に,
ウィンドウ全体にまたがる main box というパッキングボックスに,テキストを表示するための text view
ウィジェットと,入れ子になるように先に宣言した entry box パッキングボックスをパックする (図 9).
Client
main_box
text_view
entry_msg
図9
button_send
entry_box
テキストチャットクライアントのパッキングボックス
3.5 パッキングボックス復習
◇ 課題 2 ◇
13
複数間のテキストチャットを行えるようなプログラム text chat server と text chat client の GUI
版 を作成せよ.お互いにテストする人がいない場合,自分の計算機上でサーバとクライアントを起
動してチェックせよ.client 127.0.0.1 とすると自分の計算機上で起動したサーバに接続できる.
1. server.c の TODO をうめ,実行せよ.
2. client.c の TODO をうめ,実行せよ(図 7, 図 8).
3. チャットの文章を提示するとき,誰が話しているかも同時に表示せよ.
14
4 バトルシップの GUI アプリケーション (オプション)
4 バトルシップの GUI アプリケーション (オプション)
4.1 バトルシップゲームについて
すでに説明がありましたが,バトルシップゲーム(軍艦ゲーム)とは,2人のプレイヤーがそれぞれのマ
ス状の自陣に軍艦を配置し, 相手陣の配置を推測しながら, どちらかの陣が壊滅するまで, 相手陣へ攻撃を行
うゲームです.バトルシップゲームは,2 人のプレイヤーがそれぞれ以下の手順ですすめる.また,具体的な
マップは図 10) のようになる.
• 9 × 9 マスの自陣に船を配置する (お互いに秘密)
• 先攻, 後攻を決め, 順番に相手陣のマスを指定して攻撃を行う
• 攻撃を受けた側は, 相手の攻撃の結果 (命中, はずれ, 命中した場合は船の種類も) を相手に知らせる
• 攻撃を繰り返し, 先に相手側の全ての船を撃沈したほうが勝ち.
• 船は複数マスからなるが, 撃沈のためには全てのマスに攻撃を命中させねばならない.
図 10 Linux 上でのバトルシッププログラム実行画面
また,戦艦 1 隻,巡洋艦 2 隻,駆逐艦 3 隻,潜水艦 4 隻の軍艦を自陣に配置する (表 1).なお,配置のルー
ルは以下の通りとなる.
• 2 マス以上の船は直列に並べなくてはならない
• 各船は接してはいけない. 縦横だけでなく, 斜めで接してもいけない.
• 4 隅の端の 3 マスは岩 (rock) であるとして船を配置できない.
アルゴリズムの演習に使ったため,演習では探索のアルゴリズムの部分のみでインタフェースについては触
れていなかった.本演習では,ネットワークを介したバトルシップゲームの対戦を行うアプリケーションを課
題とする.同機能を持つクライアントとサーバが相互に接続し,送受信される戦艦の有無,攻撃の結果をやり
とりする.最初に軍艦の配置を行い(図 11),交互に,攻撃するマスを選択して (図 12, 図 13),対戦を行う.
4.1 バトルシップゲームについて
15
和名
英語名
記号
マス数
配置数
戦艦
Battleship
B
4
1
巡洋艦
Cruiser
C
3
2
駆逐艦
Destroyer
D
2
3
潜水艦
Submarine
S
1
4
表1
バトルシップの軍艦リスト
◇ 課題 3 ◇
サーバ・クライアント間のバトルシップ対戦を行えるプログラムを作成せよ.お互いにテスト
する人がいない場合,自分の計算機上でサーバとクライアントを起動してチェックせよ.client
127.0.0.1 とすると自分の計算機上で起動したサーバに接続できる.
1. server.c の TODO をうめ,実行せよ.
2. client.c の TODO をうめ,実行せよ(図 11, 図 12, 図 13).
図 11
軍艦の配置画面.ラジオボタンで軍艦の種類と向きを選択して配置する
16
4 バトルシップの GUI アプリケーション (オプション)
図 12
図 13
対戦画面.攻撃するマスを選択する.
対戦画面.相手が攻撃してくるのを待つ.