プログラミング教室 Tetrisの実装 - K0me-Lab

プログラミング教室
Tetrisの実装
K.Yonezawa
オブジェクト指向プログラミングとは


オブジェクト(object):振る舞いを伴ったデータ構造
ソフトウェアを、オブジェクトとその間のやり取りが
集まったものと捉え、実装するということ



ソフトウェアの設計哲学
これから大規模化していくソフトウェア開発には、必須の
概念と言える
C++などの、オブジェクト指向言語を用いると実装
しやすいが、それ以外の言語(C言語など)でも十
分実現可能

あくまで設計哲学
…とはいうものの…


オブジェクト指向設計を良く理解している開発者
は非常に少ないのが現状
オブジェクト指向設計技術の習得は非常に難し
い


本を少々読んだり、授業を受けるくらいで習得するこ
とは出来ない.何度もソフトウェア設計の経験、失敗
を積み重ねることが必要
UML図を描くだけの研修がよくあるが、図と、実装(ソ
ースコード)との対応がイメージ出来ていない段階で
は効果が低い→実際に実装してみる事が重要!
今回の目的





オブジェクト指向設計、実装を一通り体験してイ
メージを掴む
同じ課題に取り組み、各自の設計、実装をレビュ
ーしあう事で、自分の設計、実装の問題点を把
握しやすくする
UML(クラス図など)の習得
オブジェクト指向言語の習得
(Windows GUIの知識を習得)
Tetrisとは?


ソビエト連邦の科学者アレクセイ・パジトノフら3
人が教育用ソフトウェアとして開発した、落ち物
パズルの元祖
実装が比較的容易
今回の課題

Tetrisを、C++あるいはその他(出来るだけオブジェクト
指向に適応していそうな言語)で実装すること


少なくともクラス図を描いて設計を行い、ソースの構造を
説明出来るようにすること


他からソースをもらってきて使っても良い.ただし、そのソースを
完全に理解し、説明可能な状態にする
適宜必要であれば、他の図も追加して説明してよい
将来の拡張性を考えて設計すること



縦横サイズの増減は簡単に可能?
落ちてくるブロックの種類を増やすことは簡単に可能?
何故そのような設計にしたのか?を明確に説明できるように
なぜクラス図?

オブジェクト指向設計されたソフトウェアの静的な構造(つま
り、ソースコード上の構造)を、一番良く表した図


設計者が、どのようなことを意図して設計したかがとてもよく分かる
オブジェクト指向設計の理解度もとても良く分かってしまう
開発手順
1.
クラス図やその他の図を描いて設計する
⇒レビュー
2.
3.
ヘッダファイルを作成する
クラス定義、メンバ関数/変数定義を先に決定
する→必要ならば、クラス図を変更しながら
⇒レビュー
ソースファイルを実装し、実際に動作させる
⇒ソースコードレビュー
方針・注意点


レビューは週一度時間を決めて行う
成果物の作成は各自自分のペースで行う



途中で抜けたり、途中から参加することは自由、レビューの
み参加することも自由とする


業務時間中の作業については、上長に各自調整のこと
本業に支障が出ないように注意
ただし、実際に手を動かさないと、C++とオブジェクト指向の習得は難
しい
レビューで他の人の構造が良いと思ったら、真似するのは全
く問題は無い

相手の許しがあれば、作成途中のソースコードをもらうことも自由.た
だし、すべてを理解してから使用する必要がある
基礎知識の注意点


この企画では、C++や基本的なUMLの言葉につい
て、ある程度知っていることが前提となっている
基本について知らない人でも参加は可能だが、な
るべく早い段階で理解できるように努力すること


理解できていないと、後半のほうのレビューについてい
くことは難しい
分からないところがあれば、米澤までどうぞ
基礎知識の確認










下記の言葉について、意味が分かっているかど
うかを確認しておく
クラス(class)
メンバ変数
メンバ関数
Staticメンバ変数(クラス変数)
Staticメンバ関数(クラス関数)
new/delete
インスタンス
仮想関数、純粋仮想関数
STL








継承
関連
集約、コンポジション集約
依存
基本クラス、抽象クラス
派生クラス、実装クラス
インタフェースと純粋仮想関数
デザインパターン
GUIに不慣れな人のために

GUIプログラミングに慣れていない人用に、GUI部
分は作成済みで、ほぼ手を入れなくて良い状態の
ソースコードをご提供

フリーの開発環境、Visual Studio Expressでビルド可能

GUIプログラミングは全く気にせず、ブロックの配置
を正しく管理するコードを作成すればOK

もちろん、バグを見つけたり、問題解決のために必
要であれば、GUI部にもどんどん手を入れてよい
参考とするソース


参考にするソースは、
http://voidmain.org/niconicoprogram/?C%2BWin32API%E3%8
1%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の描画の際、各マスの状態を知りたい場合
GUI部は、Win32APIで記述したものと、C#で記述したものを用意
各メンバーは、ITetrisMgrを継承したクラスを実装すればOK


こちらはC++で実装するようになっている
C#版では、TetrisMgr部は別DLLとして実装
提供するソースの
シーケンス図

各種イベントの際、ハンド
ラを呼び出す(On**)




初期化指示
キーが押された
降下処理を行うためのタイ
マーが発火した
GUIの求めに応じて適切
な情報を提供する



各座標のブロックの状態
ゲームオーバーになったか
どうか
降下処理を行う時間を決め
るためのタイマー時間間隔
拡張ネタ(簡単なの)


(提供ソースを使った人のみ) GUI部分のソースコードも
見て、GUIのコードはどのような構造になっているのか
をチェックする
⇒仕事で扱うコードでも、(GUI以外でも)同じような構造
は山ほどある
ブロックの大きさ/デザインを変える、縦横のブロック数を
変えるなどの改造を行ってみる


公式なサイズは、縦20行 × 横10列らしい
ゲームが進むと、だんだん落ちる速度を高速化させるよ
うに実装する
拡張ネタ(少々面倒)





壁際やブロックが迫っている時に回転させたときに、回
転軸をずらして回転するようにする (スーパーローテー
ションというらしい)
ラインが消える時に消えるブロックが点滅するなどの効
果を入れる
点数が表示されるようにする
一度に消すと高得点になるようにする、全消しで高得点
になるようにする
これから落ちてくる予定のブロックが表示されるようにす
る
Appendix: ダブルバッファリング


ウインドウを直接描画すると、描画の過程がユーザーに見えてしまい、見栄
えが悪くなる
⇒内部メモリに一度描画した後、その絵をウインドウに一度に描画するように
実装
.NET Frameworkでは、ダブルバッファリングはフレームワーク内で行ってく
れるため、ユーザーは実装する必要が無い


.NET FrameworkでのOnPaint()関数はとてもシンプル
SetStyle()で、ダブルバッファリングを有効にする設定は行う必要がある
メモリ上で描画
コピー
ウインドウ
BitBlt()
ImageList_Draw()
Appendix: ImageList

同じ大きさの複数の画像を扱う機能


Indexでアクセス可能
登録する画像を横に並べた画像を一つ用意すれば、
それを自動分割してImageListに登録できる機能が
ある



Win32API: ImageList_Create
.NET Framework: AddStrip
GUIのアイコンのような場合に便利
0
1
2
3
4
5
6
7