プログラミング教室 Tetrisの実装 K.Yonezawa
オブジェクト指向プログラミングとは オブジェクト(object):振る舞いを伴ったデータ構造 ソフトウェアを、オブジェクトとその間のやり取りが集まったものと捉え、実装するということ ソフトウェアの設計哲学 これから大規模化していくソフトウェア開発には、必須の概念と言える C++などの、オブジェクト指向言語を用いると実装しやすいが、それ以外の言語(C言語など)でも十分実現可能 あくまで設計哲学
…とはいうものの… オブジェクト指向設計を良く理解している開発者は非常に少ないのが現状 オブジェクト指向設計技術の習得は非常に難しい 本を少々読んだり、授業を受けるくらいで習得することは出来ない.何度もソフトウェア設計の経験、失敗を積み重ねることが必要 UML図を描くだけの研修がよくあるが、図と、実装(ソースコード)との対応がイメージ出来ていない段階では効果が低い→実際に実装してみる事が重要!
今回の目的 オブジェクト指向設計、実装を一通り体験してイメージを掴む 同じ課題に取り組み、各自の設計、実装をレビューしあう事で、自分の設計、実装の問題点を把握しやすくする UML(クラス図など)の習得 オブジェクト指向言語の習得 (Windows GUIの知識を習得)
Tetrisとは? ソビエト連邦の科学者アレクセイ・パジトノフら3人が教育用ソフトウェアとして開発した、落ち物パズルの元祖 実装が比較的容易
今回の課題 Tetrisを、C++あるいはその他(出来るだけオブジェクト指向に適応していそうな言語)で実装すること 他からソースをもらってきて使っても良い.ただし、そのソースを完全に理解し、説明可能な状態にする 少なくともクラス図を描いて設計を行い、ソースの構造を説明出来るようにすること 適宜必要であれば、他の図も追加して説明してよい 将来の拡張性を考えて設計すること 縦横サイズの増減は簡単に可能? 落ちてくるブロックの種類を増やすことは簡単に可能? 何故そのような設計にしたのか?を明確に説明できるように
なぜクラス図? オブジェクト指向設計されたソフトウェアの静的な構造(つまり、ソースコード上の構造)を、一番良く表した図 設計者が、どのようなことを意図して設計したかがとてもよく分かる オブジェクト指向設計の理解度もとても良く分かってしまう
開発手順 クラス図やその他の図を描いて設計する ⇒レビュー ヘッダファイルを作成する クラス定義、メンバ関数/変数定義を先に決定する→必要ならば、クラス図を変更しながら ⇒レビュー ソースファイルを実装し、実際に動作させる ⇒ソースコードレビュー
方針・注意点 レビューは週一度時間を決めて行う 成果物の作成は各自自分のペースで行う 業務時間中の作業については、上長に各自調整のこと 本業に支障が出ないように注意 途中で抜けたり、途中から参加することは自由、レビューのみ参加することも自由とする ただし、実際に手を動かさないと、C++とオブジェクト指向の習得は難しい レビューで他の人の構造が良いと思ったら、真似するのは全く問題は無い 相手の許しがあれば、作成途中のソースコードをもらうことも自由.ただし、すべてを理解してから使用する必要がある
基礎知識の注意点 この企画では、C++や基本的なUMLの言葉について、ある程度知っていることが前提となっている 基本について知らない人でも参加は可能だが、なるべく早い段階で理解できるように努力すること 理解できていないと、後半のほうのレビューについていくことは難しい 分からないところがあれば、米澤までどうぞ
基礎知識の確認 下記の言葉について、意味が分かっているかどうかを確認しておく クラス(class) メンバ変数 メンバ関数 Staticメンバ変数(クラス変数) Staticメンバ関数(クラス関数) new/delete インスタンス 仮想関数、純粋仮想関数 STL 継承 関連 集約、コンポジション集約 依存 基本クラス、抽象クラス 派生クラス、実装クラス インタフェースと純粋仮想関数 デザインパターン
GUIに不慣れな人のために GUIプログラミングに慣れていない人用に、GUI部分は作成済みで、ほぼ手を入れなくて良い状態のソースコードをご提供 フリーの開発環境、Visual Studio Expressでビルド可能 GUIプログラミングは全く気にせず、ブロックの配置を正しく管理するコードを作成すればOK もちろん、バグを見つけたり、問題解決のために必要であれば、GUI部にもどんどん手を入れてよい
参考とするソース 参考にするソースは、 http://void-main.org/niconicoprogram/?C%2BWin32API%E3%81%AE%E3%83%86%E3%83%88%E3%83%AA%E3%82%B9 で、これは、以前ニコニコ動画で話題になっていた、「一時間でテトリスを作ってみた」の動画で作られていたものhttp://itpro.nikkeibp.co.jp/article/Interview/20091104/340019/ 一時間で作れるテトリスを、我々が時間をかけて作れないはずは無い!
GUIコードの大まかな構造(1) メッセージループと呼ばれるループを持ち、Windowに届く各種メッセージに対する応答を記述していく イベントドリブンである 組み込みソフトウェアとも共通する構造 初期化イベント処理 GUI 各種イベント 描画イベント処理 マウスイベント処理 メッセージ ループ タイマーイベント処理 終了イベント処理 while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
GUIコードの大まかな構造(2) .NET Framework, MFCなどのフレームワークを使用する場合、メッセージループはフレームワーク内で実装されているので、アプリケーション実装者は、そのハンドラだけを記述することになる 自分で記述することが無くても、この構造が隠れていることは意識していなければならない フレームワーク内で実装 初期化イベント処理 各種イベント 描画イベント処理 マウスイベント処理 メッセージ ループ タイマーイベント処理 終了イベント処理 while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
GUIコードの大まかな構造(3) Window描画処理の例(Win32 API) WM_PAINTメッセージを受けた時の処理として記述する LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { ... case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); HDC hMemDC = CreateCompatibleDC(hdc); SaveDC(hMemDC); SelectObject(hMemDC, g_hMemBitmap); for(int x = 0; x < TETRIS_BLOCKNUM_X; x++) { for(int y = 0; y < TETRIS_BLOCKNUM_Y; y++) { ImageList_Draw(g_hImageList, (int)g_pTetrisMgr->GetBlockState(x, y), hMemDC, x * BLOCK_SIZE, y * BLOCK_SIZE, ILD_NORMAL); } BitBlt(hdc, 0, 0, BLOCK_SIZE * TETRIS_BLOCKNUM_X, BLOCK_SIZE * TETRIS_BLOCKNUM_Y, hMemDC, 0, 0, SRCCOPY); RestoreDC(hMemDC, -1); DeleteObject(hMemDC); EndPaint(hWnd, &ps); break;
GUIコードの大まかな構造(4) Window描画処理の例(.NET Framework + C#) FormクラスのOnPaint()関数をオーバーライド protected override void OnPaint(PaintEventArgs e) { for (int i = 0; i < TetrisBlockNumX; i++) for (int j = 0; j < TetrisBlockNumY; j++) blockImageList.Draw(e.Graphics, i * BlockSize, j * BlockSize, (int)tetrisMgr.GetBlockState((uint)i, (uint)j)); } base.OnPaint(e);
提供するソースの構造 GUI部は、テトリスのGUI部分を実装し、適切なタイミングでITetrisMgrクラスで定義されるIFを呼び出す GUI部は、Win32APIで記述したものと、C#で記述したものを用意 各メンバーは、ITetrisMgrを継承したクラスを実装すればOK こちらはC++で実装するようになっている C#版では、TetrisMgr部は別DLLとして実装
提供するソースのシーケンス図 各種イベントの際、ハンドラを呼び出す(On**) GUIの求めに応じて適切な情報を提供する 初期化指示 キーが押された 降下処理を行うためのタイマーが発火した GUIの求めに応じて適切な情報を提供する 各座標のブロックの状態 ゲームオーバーになったかどうか 降下処理を行う時間を決めるためのタイマー時間間隔
拡張ネタ(簡単なの) (提供ソースを使った人のみ) GUI部分のソースコードも見て、GUIのコードはどのような構造になっているのかをチェックする ⇒仕事で扱うコードでも、(GUI以外でも)同じような構造は山ほどある ブロックの大きさ/デザインを変える、縦横のブロック数を変えるなどの改造を行ってみる 公式なサイズは、縦20行 × 横10列らしい ゲームが進むと、だんだん落ちる速度を高速化させるように実装する
拡張ネタ(少々面倒) 壁際やブロックが迫っている時に回転させたときに、回転軸をずらして回転するようにする (スーパーローテーションというらしい) ラインが消える時に消えるブロックが点滅するなどの効果を入れる 点数が表示されるようにする 一度に消すと高得点になるようにする、全消しで高得点になるようにする これから落ちてくる予定のブロックが表示されるようにする
Appendix: ダブルバッファリング ウインドウを直接描画すると、描画の過程がユーザーに見えてしまい、見栄えが悪くなる ⇒内部メモリに一度描画した後、その絵をウインドウに一度に描画するように実装 .NET Frameworkでは、ダブルバッファリングはフレームワーク内で行ってくれるため、ユーザーは実装する必要が無い .NET FrameworkでのOnPaint()関数はとてもシンプル SetStyle()で、ダブルバッファリングを有効にする設定は行う必要がある ウインドウ メモリ上で描画 コピー BitBlt() ImageList_Draw()
Appendix: ImageList 同じ大きさの複数の画像を扱う機能 Indexでアクセス可能 Win32API: ImageList_Create .NET Framework: AddStrip GUIのアイコンのような場合に便利 1 2 3 4 5 6 7