プログラミングコンテストについて



「中級プログラミング及び演習」では、 最後の数回を使ってプログラミングコンテストを行います。 詳しい実施方法については、Moodleの方を参照してください。

ここにはちょっとしたアドバイス・参考情報が書いてあります。


ライブラリの制約

コンテストで使用するグラフィックライブラリは、 初心者が簡単に使えるように設計されています。 このため、できないことや苦手なことがいくつかあります。 作成するプログラムの題材を選んだり、 具体的な仕様を決定するときの参考にしてください。

できないこと

苦手なこと


題材の選択

過去、ほとんどのグループがゲームを作成しています。 以下、ジャンル毎に難易度や基本方針など思いついた情報を書いておきます。

アクションゲーム

単純なゲームが作りやすいこと、短時間で動作確認ができること、 思考ルーチンなど高度なアルゴリズムが必要ないことから、 このジャンルのゲームを作るグループが多いです。 演習課題で作成したものに毛が生えたレベルであれば誰でも作れますが、 楽しく遊べるものを作ろうとすると、 グラフィックに凝ったり敵やステージのバリエーションを作ったりと、 それなりのセンスと労力が必要です。

1秒間にある程度のコマ数を動かさないとゲームにならないため、 1ステップの処理(敵・味方・弾・障害物などの移動、 各キャラや背景・爆発エフェクトなどの描画) が重すぎると破綻します。 凝ったゲームを作るときは、効率の良い計算や描画の方法を工夫してください。

よく見かけるもの・コンテストでよく作成されるものを大雑把に分類すると、 以下のようになります(名称は適当)。

シューティング型は、最低限のものならコード量も少なく、 アルゴリズムやデータ構造も簡単です。 残り時間や実力に合わせて、 敵や攻撃の種類を増やす、 パワーアップなどのルールを追加する、 といった段階的な改良もしやすいので、比較的初心者向けです。

迷路型はメジャーなゲームの多いジャンルであり、見栄えもします。 うまく計画を立てて実行できると、良い作品ができると思いますが、 シューティング型より難易度が高く、作業量も多くなりがちなので注意してください。

タイミング型は機能を絞ればシューティング型以上に簡単に作れますが、 EzGraphでは音が出せないため、リズムゲームが作れません。 (最新版で音楽を演奏できるようになりましたが、) 処理と演奏の同期を取ったりすることはできないため、 音楽ゲームを作るのは困難です。 グラフィックの変化に合わせてキーを押す、といったデザインになります。 シンプルに作ると、 「ゲームといえるか怪しい」レベルのつまらない作品になりかねず、 プログラムとしても簡単すぎて評価の低いものになります。 演出を凝る、ある程度複雑なゲームになるようルールを考えるなど、 工夫してください。

自機コントロール型も敵がいないぶん簡単そうに見えますが、 EzGraphでは「マウスの移動」イベントが取得できないため、 「マウスを動かすと自機が動く」ような操作は実現できません。 複雑な形状の壁と当たり判定をしようとすると、 それなりに難しいアルゴリズムを使う必要があります。 見た目も地味になりがちなので、 動く障害物を導入するなど工夫してください。

ガンシューティング型は、 極端に単純化すれば「ランダムな位置に的を出現させる」 「クリックした位置が的と一致したら命中と判定する」だけで作れるので、 プログラミング的には楽です。 しかしこのままでは簡単すぎ、ゲームとしても単調でつまらないものになります。 的を動かす、種類を増やす、弾切れや的(敵?)からの攻撃、 弾着までのタイムラグ(砲撃ゲーム?)といった要素を追加するなど、 工夫をしてください。 また、市販されているような見栄えのするものに近づけようとすると、

などが問題になります。

パズルゲーム

「テトリス」や「ぷよぷよ」のようないわゆる「落ちもの」型のアクションゲームは、 毎年数グループが作成します。

一般のアクションゲームと比べてもシンプルで、 それなりの労力でちゃんと遊べるものが作れます。 ただし、単純なアクションゲームに比べると、 「テトリス」であればピースを回転させたり、 画面に積み上がっているピースとの衝突判定を行ったりと、 多少難しい(演習の発展課題レベルの)アルゴリズムが必要です。 メジャーな「落ちもの」であれば、こうしたアルゴリズムはネットで見つけられますが、 内容を理解できる力は必要です。 また、他のグループとネタが被りやすい(2グループ以上が「テトリス」を作ることも多い)ので、 ルールを拡張する、演出を凝るなど、作品の特徴を工夫してください。

「倉庫番」「15パズル」「箱入り娘」のような非リアルタイムのパズルは、 シンプルなものであればわりと容易に作成できます。 ただし、紙の上で簡単にできるものでは寂しいので、 あるていど複雑なルールのものを作る、 自動的に解いてくれる、 問題をランダムに作ってくれる、などの工夫をしてください。

ロールプレイングゲーム

毎年1グループ程度ですが、挑戦する人がいます。

コンテストの期間が短いため、 「ドラゴンクエスト」のように本格的なシナリオ型のRPGを作成するのは困難です。 しかしリアルタイム性が必要なく、 アイテムやマップ、 能力値などの情報は構造体や配列をうまく組み合わせていけば素直に設計できるため、 アクションゲームよりも作りやすい面もあります。 時間が限られているため全体の仕様をコンパクトに設計する必要があり、 それでもかなりのデータ構造や関数を作成する必要があるため、 効率よくコードを作成できるプログラミングスキルが必要です。

「ローグ」タイプの見下ろし型ランダムダンジョンタイプは、 戦闘と移動を別々に作らなくて良い、シナリオやイベントが必要ないなど、 シンプルに作りやすい利点があります。 ただし、ダンジョン生成ルーチンを作成する必要があります (固定する手もあるが、できあがりがかなり寂しい)。

ボードゲーム・カードゲーム

あまり作成する人がいませんが、過去に「五目並べ」「ドンジャラ」を作ったグループがあります。

これも「人間二人で交互に入力するオセロ」のようにシンプルなものは簡単すぎるので、 コンピュータと対戦できるように思考ルーチンを作成するなどして、 「コンピュータ上で遊ぶ」意味のあるものに仕上げてください。 また、人間を相手に善戦できる「将棋」は難易度が高すぎますし、 「ブラックジャック」のように単純な確率ゲームの思考ルーチンは簡単すぎます。 適切な難易度のゲームを選択することが重要です。 「七並べ」や「バックギャモン」のように、 単純でそれなりに有効な戦略が存在し(前者なら、 自分がその先のカードを持たないならそのラインを止める、 後者なら、なるべく孤立した駒を作らないように移動を割り振る)、 ランダム性のあるもの(多少思考ルーチンが馬鹿でも対戦相手になる)が狙い目でしょう。

シミュレーションゲーム

過去に、農場シミュレーションを作ったグループがあります。

戦闘系のシミュレーションゲームの場合、 ユニットの能力値やマップの管理についてはRPGと同様です。 ただし、ヘックスマップで戦うような本格的な戦術シミュレーションは、 コンピュータ側にそれなりの思考ルーチンが必要になるため、 敵の動きがランダムでもなんとかなるRPGの戦闘より難易度が高くなります (「一番近くの敵に近づいて殴る」レベルの思考ルーチンなら簡単)。

あまり作るグループがありませんが、 販売シミュレーション・育成シミュレーションは敵の思考ルーチンが必要なく、 データ処理も比較的単純(需要や店のパラメータに応じた乱数値で販売数が決まり、 その分の利益が資金に加算される、など)なので、 じつはわりと作りやすいジャンルです。

アドベンチャーゲーム

過去に、画面クリック型のいわゆる「脱出ゲーム」を作ったグループがあります。

プログラミングの難易度は低いのですが、 グラフィックやメッセージなどを個々にEzGraphの関数で直接描いていると大変なコード量になるため、 うまくデータ化し、管理する必要があります(たとえばメッセージはすべて配列にしておいて、 「指定した番号のメッセージを画面に描く」関数を作成する)。 ちなみにサンプルゲームの「Mastery of the House」では グラフィック描画がすべてコードになっており膨大な行数の描画関数呼び出しがありますが、 これは専用の簡易グラフィックエディタを作成し、 編集したグラフィックから描画コードを自動生成するルーチンを実装しています (線画というベクトル型のグラフィックだから可能な手段ですが)。

他のジャンルのゲームと違って、シナリオやトリックなどを考えないとゲームにならないため、 そういうセンスがないと作りにくいジャンルかもしれません。


開発時のヒント

デバッグモード

ゲームを作る場合、動作確認のため何度も自分でプレイする必要が出てきます。 完成版のゲームではそれなりの難易度が必要ですが、 デバッグ中はいろいろと「ずる」をできる仕掛けをしておくと、 楽に動作確認ができます。

printf デバッグ

printf()puts() といったおなじみの標準出力用関数は、 EzGraphのウィンドウに文字を表示することができません。 したがって、通常は EzDrawString() を使ってゲーム内の文字描画を行います。

しかし、プログラム中で printf() などを呼び出すことは可能であり、 出力内容はプログラムを起動したターミナルウィンドウ上に出力されます。 したがって、 動きの怪しいキャラクタの現在座標をタイマハンドラ内でprintf()してみる、 といった方法で、 ゲーム画面を崩さずにプログラムの内部情報をターミナルウィンドウ側に表示し、 追跡することができます。

2年の「上級プログラミングI」で習う内容ですが、デバッガ gdbの使い方を知っていれば、 デバッガ内でプログラムを起動して動きを追うことも可能です。


xpmファイルの作成方法

EzGraphはXPM形式の画像ファイルを読み込み、画面に描画することができます。 この形式の画像ファイルは、以下のようなやり方で用意できます。

簡単なグラフィックエディタを使って、直接描く

電算室環境には、 簡単なグラフィックエディタが 用意されています(コマンド名 xpaint)。 これで画像を作成し、メニューで 「File」→「Save As」と選んで ウィンドウ右側のフォーマットから「XPM Format」 を選んで保存します。

他のソフトで作成した画像を変換する

ImageMagickの変換コマンド convert を使うと、 様々な画像形式を相互変換できます。 XPM形式で保存できないソフトで画像を作成したい、 デジカメ等で撮影した写真を使いたい、 といった場合は、 JPEG画像などからこのコマンドで変換してください。 ただし、XPM形式は高解像度のフルカラー画像に適した形式ではないため、 変換時に減色やサイズ変更を行った方が良いかもしれません。

また、元画像の形式によっては透過色がうまく設定されないことがあります。 その場合は、生成後のXPMファイルをエディタで開き、 透過色にしたい部分に割り当てられた文字の色情報を透過色に書き換えてください (下の記述を参照)。

Emacsやプログラムで作成する

XPM形式のファイルはテキストファイルであり、Emacsなどのエディタで編集できます。 xpmファイルを開いているときにCTRL+Cを2回入力することで、 編集中のxpmファイルを実寸大でプレビューできます (下の例のように先頭行に/* XPM */と書いておく必要があります)。 再度CTRL+Cを2回入力すると、テキスト編集に戻ります。

xpm形式ファイルの例を、以下に示します。

/* XPM */
static char *sbomb_xpm[] = {
    "12 12 6 1",
    ". c none",
    "0 c #000000",
    "1 c #000000",
    "5 c #a0a0a0",
    "6 c #f0f0f0",
    "7 c #ffffff",
    "............",
    "..........6.",
    "..........6.",
    "....1111.6..",
    "...171111...",
    "..17111111..",
    "..11111111..",
    "..11111111..",
    "..11111111..",
    "...111111...",
    "....1111....",
    "............",
}; 

大きい画像・色数の多い画像をテキストエディタで描くのは現実的ではありませんが、 32x32程度までで各データ毎の色数が少ない場合は、 プレビュー機能を使いながら直接XPMデータを打ち込むことも可能です。

少し高度な技として、xpm画像を実行時に作成/加工することができます。 通常は事前に用意したxpmファイルをincludeし、 EzReadXPM() にそのファイル中で宣言されている配列変数名を与えることで、 EzPut()で使える形式に変換します。 しかし、実際には EzReadXPM() に対して、 上記のフォーマットになっている文字列の並びを与えれば、 変換を行うことができます。したがって、

といった場合には、エディタで同じような絵をたくさん作る代わりに、 基本のxpmファイルからプログラム中で必要なXPM形式画像をメモリ上に作り出し、 それぞれを EzReadXPM() に与えることができます(コード例は2009年度サンプル「Blast Off」を見てください)。

注意点として、 通常のxpmデータは上記例のように 文字列リテラルへのポインタの配列になっており、 直接内容を書き換えることができません。 実行時にxpmデータを作り出す場合は、データの格納先として必要な大きさの char型配列などを用意してください。


その他のヒント


上級者向けの話

キー入力処理について

EzGraphでは、キー入力の判定手段を2種類用意しています。

これを使って自機を移動する方法を考えます。 お手軽なのは、以下の方法です。

  1. EzSetKeyHandler() を用いてハンドラを登録し、 キーが押されたときにその種類に応じて移動する (あるいはその場で移動せず、入力内容を変数に覚えておく)
  2. (タイマハンドラ内で、覚えておいた入力内容に応じて移動する)

メニューのカーソルなどはキーハンドラ内で移動してしまってかまいませんが、 ゲームの自機などの場合、 上で書いた「1フレーム内で複数回動けてしまう」問題を避けるため、 移動処理はタイマ側で行う必要があります。

この方法はキーを押したときにしか反応しないので、 「押している間は動き、放したら止まる」といった動作が作れません。 実際にやってみると、押している間だけ動いて見えるのですが、 じつは 「キーを押しっぱなしにするとキーリピートが発生し、 何度もそのキーを押したようにイベントが発生している」 だけです。 エディタ上などでカーソルキーを押しっぱなしにしてみるとわかるように、 キーは押した瞬間に一回分の入力が発生し、 その後は一定時間たってからキーリピートによる入力が発生します (普通にタイプしている際に、2回以上の入力を誤発生させないため)。 したがって、これをそのままゲームに使うと、 「キーを押した瞬間に1回移動するが、 その後リピートが始まるまで移動しない『間』が生じる」 ということになり、移動がシビアな場合は問題となります。

EzIsKeyPress()を用いることで、 タイマハンドラが呼び出される度に「現在押されているか否か」を判定し、 リアルタイムにキー検出が可能です。 ただし、こちらのやり方も以下のような問題を生じます。

なお、 EzSetKeyHandler(), EzReleaseKeyHandler() を併用して、各キーが押された/放された瞬間を記録していけば、 「今現在、特定のキーが押されているか否か」を判定できそうに見えます。 しかし、実際にはキーリピートが発生したとき、 両方のイベントが交互に発生しています。 つまり、物理的にはキーを押しっぱなしでも EzReleaseKeyHandler() で登録した関数が呼ばれてしまうので、 この方法はうまく行きません。


EzGraph Q&A

EzIsKeyPress()を使って キー入力を検出しようとしたが、 'Q'キーを押すとプログラムが終了してしまう。

EzGraphでは、マウス・キーボードハンドラがともに設定されていない場合は、 'Q'キーを押すとプログラムを終了するようになっています。 EzIsKeyPress()を使ってキー検出を行う場合も、 中身が空のキーボードハンドラ関数を作成して登録しておけば、 このデフォルト動作を止めることができます。

アルファベットが文字により横幅が異なるため、綺麗に整列できない。

デフォルトのフォントがプロポーショナルフォントであるため、 英字の表示が美しい一方で、たとえば'I'と'M'では横幅が異なります。 このため、同じ文字数でも右端が揃わず、レイアウトが困難です。

EzSetFont()を使って等幅フォントを使うよう指定すれば、 すべてのアルファベットや数字を日本語文字の半分の幅で表示できます。 使用できるフォントはコマンド

    fc-list
と実行することで表示されます。たとえば、
    EzSetFont("IPAゴシック");
とすれば、等幅フォントになります。



最終更新: 2023年 4月 13日 木曜日 18:00:46 JST

御意見、御感想は ohno@arch.info.mie-u.ac.jp まで