テトリスゲームに必要な処理は、
次の通りです。
1.ピースを用意する処理
2.ピースを移動する処理
3.ピースを回転する処理
4.ピースを固定する処理
5.行を消す処理
6.描画する処理
〇ピースの種類
5x5の配列を使って、TRUE(赤)、FALSEで表現しています。
配列の内容
ピースは(2,2)を軸に時計方向に回転します。
〇ゲーム画面
〇プログラム例#include <windows.h>
/* フィールドの横と縦のマス数*/
#define FIELD_WIDTH 14
#define FIELD_HEIGHT 24
/* MovePieceの引数*/
#define PIECE_LEFT 2
#define PIECE_RIGHT 4
#define PIECE_DOWN 8
/* フィールドのマスのテキスト表現*/
#define PIECE_FALSE TEXT("□")
#define PIECE_TRUE TEXT("■")
HWND hMainWindow = NULL;
int field[FIELD_WIDTH][FIELD_HEIGHT] = { 0 }; /* ゲームフィールド*/
int currentPiece[5][5] = { 0 }; /* 現在移動中のブロック*/
POINT pieceLocation = { 0,0 }; /* ブロックの位置*/
int currentPieceNumber; /*現在のピース番号*/
BOOL MovePiece(int); /* ブロックの移動*/
BOOL TurnPiece(VOID); /* ブロックの回転*/
VOID NextPiece(VOID); /*次のブロックを出現*/
VOID PieceToField(VOID); /*ブロックの位置と内容をfield配列変数にコピーする*/
int AdjustLine(VOID); /* 横一列がピースで埋まっている場合の処理を実行する*/
int GetPieceTop(VOID); /*ブロック内のピースの最上部の位置を返す*/
int GetPieceBottom(VOID);/*ブロック内のピースの最下部の位置を返す*/
int GetPieceLeft(VOID);/*ブロック内のピースの最左側の位置を返す*/
int GetPieceRight(VOID);/*ブロック内のピースの最右側の位置を返す*/
/* ブロック内のピースの最上部の位置を返す*/
int GetPieceTop() {
int x, y, length = 3; // 中央の位置を3とする
for (y = 1; y >= 0; y--) { // 上に向かって調べる
for (x = 0; x < 5; x++) {
if (currentPiece[x][y]) {
length--;
break; //ピースが見つかったらx方向ループから出る。
}
}
}
return length;// 最小1
}
/* ブロック内のピースの最下部の位置を返す*/
int GetPieceBottom() {
int x, y, length = 3; // 中央の位置を3とする
for (y = 3; y < 5; y++) { // 下へ向かって調べる
for (x = 0; x < 5; x++) {
if (currentPiece[x][y]) {
length++;
break; //ピースが見つかったらy方向ループから出る。
}
}
}
return length; //最大5
}
/* ブロック内のピースの一番左側の位置を 返す */
int GetPieceLeft() {
int x, y, length = 3; // 中央の位置を3とする
for (x = 1; x >= 0; x--) { //左へ向かって調べる
for (y = 0; y < 5; y++) {
if (currentPiece[x][y]) {
length--;
break; //ピースが見つかったらy方向ループから出る。
}
}
}
return length; //最小1
}
/* ブロック内のピースの一番右側の位置を返す*/
int GetPieceRight() {
int x, y, length = 3; // 中央の位置を3とする
for (x = 3; x < 5; x++) { //右へ向かって調べる
for (y = 0; y < 5; y++) {
if (currentPiece[x][y]) {
length++;
break; //ピースが見つかったらy方向ループから出る。
}
}
}
return length; //最大5
}
/* ピースを位置情報に従ってフィールドにコピーする */
VOID PieceToField(VOID) {
int x, y;
for (y = 0; y < 5; y++) {
for (x = 0; x < 5; x++) {
if (currentPiece[x][y])
field[pieceLocation.x + x][pieceLocation.y + y] = currentPiece[x][y];
}
}
}
/* ピースの移動判定*/
BOOL MovePiece(int move) {
if (move == PIECE_LEFT) { // 左に対して
int left = GetPieceLeft();
if (pieceLocation.x > -(left - 1)) { // ブロック内のピースの左の位置が左壁のどうなのか
int x, y, count = 0;
for (y = 0; y < 5; y++) {
for (x = 0; x < 5; x++) {
// 各ピースを確認
if (currentPiece[x][y] && field[pieceLocation.x + x - 1][pieceLocation.y + y]) {
return FALSE; // 左に何かピースがある。
}
}
}
pieceLocation.x--; // ブロックを左に移動
}
else {
return FALSE; // 左枠に当たった。
}
}
else if (move == PIECE_RIGHT) { // 右側にたいして
int right = GetPieceRight();
if (pieceLocation.x < (FIELD_WIDTH - right)) { // 右側の枠より右かどうか
int x, y, count = 0;
for (y = 0; y < 5; y++) {
for (x = 0; x < 5; x++) {
if (currentPiece[x][y] && field[pieceLocation.x + x + 1][pieceLocation.y + y]) {
return FALSE; // 右に何かピースがある。
}
}
}
pieceLocation.x++; // ブロックを右に移動
}
else {
return FALSE; // 左側の枠に当たった。
}
}
else if (move == PIECE_DOWN) { // 下へ移動できるかどうか
int bottom = GetPieceBottom();
// 枠内かどうか確認する
if (pieceLocation.y < (FIELD_HEIGHT - bottom)) {
int x, y, count = 0;
for (y = 0; y < 5; y++) {
for (x = 0; x < 5; x++) {
// 現在のブロック内の各ピースを確認
if (currentPiece[x][y] && field[pieceLocation.x + x][pieceLocation.y + y + 1]) {
return FALSE; // 下にブロックがある。
}
}
}
pieceLocation.y++; // ブロックの位置を下げる。
}
else {
return FALSE; // 下の枠に当たった。
}
}
return TRUE;
}
/* ブロックを回転させる*/
BOOL TurnPiece() {
int x, y;
int copy[5][5];
/*回転したブロックを生成する*/
for (y = 0; y < 5; y++) { // 右回りに回転させる。
for (x = 0; x < 5; x++) {
copy[4 - y][x] = currentPiece[x][y];
}
}
/* 回転可能かどうか調べる*/
for (y = 0; y < 5; y++) {
for (x = 0; x < 5; x++) {
if (copy[x][y]) { // ピースがある場合
int offsetX = pieceLocation.x + x;
int offsetY = pieceLocation.y + y;
if ((offsetX < 0) || // 左枠
(offsetX >= FIELD_WIDTH) || // 右枠
(offsetY >= FIELD_HEIGHT) || // 下枠
field[offsetX][offsetY]) // ピースが重なる場合
return FALSE; // 回転できない
}
}
}
memcpy(currentPiece, copy, sizeof(int) * 25); //回転したブロックを現在のブロックにする
return TRUE; // 回転できた
}
/*各行を調べ、行が埋まっている場合は行を削除する*/
int AdjustLine() {
int x, y, delCount = 0;
for (y = FIELD_HEIGHT - 1; y >= 0;) { // 行でループ(下から)
int iy, lineCount = 0;
for (x = 0; x < FIELD_WIDTH; x++) { // 列でループ
if (field[x][y]) // ピースがある
lineCount++; // ピースのカウント
}
if (lineCount == 0) /* これより上にピースはない*/
break; // ピースが一つもない
if (lineCount != FIELD_WIDTH) {
y--; // ピースがあるので上の行へ
continue;
}
/*1行削除して、削除した上の行を一行下へずらす*/
delCount++;
for (iy = y; iy >= 0; iy--) {
for (x = 0; x < FIELD_WIDTH; x++) {
if (iy > 0)
field[x][iy] = field[x][iy - 1];
else
field[x][0] = 0; // 0行目は0で埋める
}
}
}
return delCount; //削除した行数を返す。
}
/* 次のブロックへ*/
VOID NextPiece() {
int num;
ZeroMemory(currentPiece, sizeof(int) * 25);
num = (int)(((double)rand() / (RAND_MAX + 1)) * 7);
currentPieceNumber = num; //確認用
currentPiece[2][2] = 1;
currentPiece[2][3] = 1;
switch (num) {
case 0:
currentPiece[2][1] = 1;
currentPiece[2][4] = 1;
break;
case 1:
currentPiece[2][1] = 1;
currentPiece[3][3] = 1;
break;
case 2:
currentPiece[2][1] = 1;
currentPiece[1][3] = 1;
break;
case 3:
currentPiece[3][2] = 1;
currentPiece[1][3] = 1;
break;
case 4:
currentPiece[1][2] = 1;
currentPiece[3][3] = 1;
break;
case 5:
currentPiece[1][2] = 1;
currentPiece[1][3] = 1;
break;
case 6:
currentPiece[1][3] = 1;
currentPiece[3][3] = 1;
break;
}
// ブロックが現れる初期値を設定
pieceLocation.x = 5; // ブロックの左の位置
pieceLocation.y = -2; // ブロックの上の位置
}
VOID CALLBACK TimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
if (!MovePiece(PIECE_DOWN)) {
int line, top;
PieceToField();
line = AdjustLine();
top = GetPieceTop();
if (field[7][1] || (pieceLocation.y + line + top - 1) < 0) {
/*ゲーム終了*/
KillTimer(hWnd, idEvent);
MessageBox(hWnd, TEXT("GAME OVER"), TEXT("GAME OVER"), MB_OK);
}
NextPiece();
}
InvalidateRect(hMainWindow, NULL, FALSE);
}
VOID Paint(HDC hdc) {
LOGFONT logFont;
HFONT hFont;
int x, y, pieceFalseLength, pieceTrueLength;
pieceFalseLength = lstrlen(PIECE_FALSE);
pieceTrueLength = lstrlen(PIECE_TRUE);
hFont = GetStockObject(SYSTEM_FONT);
GetObject(hFont, sizeof(LOGFONT), &logFont);
SelectObject(hdc, hFont);
for (y = 0; y < FIELD_HEIGHT; y++) { // 行でループする
int ptx, pty; // 文字の描画位置(座標)
ptx = 1;
pty = 1 + y * logFont.lfHeight;
for (x = 0; x < FIELD_WIDTH; x++) { // 列でループする
if (field[x][y]) { // fieldの要素がTRUEのとき
SetTextColor(hdc, RGB(0, 0xFF, 0));
TextOut(hdc, ptx, pty, PIECE_TRUE, pieceTrueLength);
}
else {
// fieldの要素がTRUEではないとき
int px, py;
px = x - pieceLocation.x; // ブロック内のピース位置に変換x方向
py = y - pieceLocation.y; // ブロック内のピース位置に変換y方向
SetTextColor(hdc, RGB(0xFF, 0, 0));
if (px >= 0 && px < 5 && py >= 0 && py < 5 && currentPiece[px][py])
// ピースを描画する
TextOut(hdc, ptx, pty, PIECE_TRUE, pieceTrueLength);
else
// 単なる□を描画する
TextOut(hdc, ptx, pty, PIECE_FALSE, pieceFalseLength);
SetTextColor(hdc, RGB(0, 0, 0));
}
ptx += logFont.lfHeight; // 文字の高さ分を幅として次の表示する位置にする。
}
}
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_CREATE:
ShowWindow(hWnd, SW_SHOW);
NextPiece();
SetTimer(hWnd, 1, 1000, TimerProc);
return 0;
case WM_KEYDOWN: {
int move;
if (wParam == VK_LEFT) {
move = PIECE_LEFT;
MovePiece(move);
}
else if (wParam == VK_RIGHT) {
move = PIECE_RIGHT;
MovePiece(move);
}
else if (wParam == VK_DOWN) {
move = PIECE_DOWN;
MovePiece(move);
}
else if (wParam == VK_SPACE) {
TurnPiece();
}
InvalidateRect(hWnd, NULL, FALSE);
break;
}
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
Paint(hdc);
EndPaint(hWnd, &ps);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int APIENTRY wWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR ipCmdLine,
_In_ int nCmdShow
) {
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = NULL;
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = TEXT("TestWindow");
wcex.hIconSm = NULL;
RegisterClassExW(&wcex);
hMainWindow = CreateWindowEx(0UL, TEXT("TestWindow"), TEXT("日本"), WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, 640, 480, NULL, NULL, hInstance, NULL);
if (hMainWindow == NULL)
{
return FALSE;
}
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
※参考:windowsゲームプログラミン
〇実行結果