Skip to content

Instantly share code, notes, and snippets.

@hisui
Created May 5, 2012 16:39
Show Gist options
  • Save hisui/2603882 to your computer and use it in GitHub Desktop.
Save hisui/2603882 to your computer and use it in GitHub Desktop.
プログラミング教材用テトリス(Win32)
// build: g++ tetris.cpp -o tetris.exe gdi32.dll -Xlinker --enable-stdcall-fixup
#include <windows.h>
#include <time.h>
#include <algorithm>
#include <stdexcept>
static const int CELL_DIM_X = 17;
static const int CELL_DIM_Y = 17;
// ブロックを構成するセルを表現
struct Cell
{
int h; // 水平位置
int v; // 垂直位置
};
enum { NUM_CASES = 7, NUM_CELLS = 4 };
static const Cell BLOCK_CASES[NUM_CASES][NUM_CELLS] = {
{{0,0},{1,0},{2,0},{3,0}},
{{0,0},{1,0},{2,0},{2,1}},
{{0,1},{1,1},{2,1},{2,0}},
{{0,0},{1,0},{2,0},{1,1}},
{{0,0},{1,0},{0,1},{1,1}},
{{0,0},{1,0},{1,1},{1,2}},
{{0,1},{1,1},{1,0},{2,0}}
};
// 現在落ちてるブロックの状態
static const Cell *block_cells; // ブロックのパターン
static int block_angle90; // 回転
static int block_ch; // 回転の重心
static int block_cv;
static int block_base_h; // 平行移動
static int block_base_v;
static int block_color = 0; // ブロックの色
// 既に落ちてるブロックを非0で表現する表
enum { STAGE_H = 11, STAGE_V = 20 };
static int stage[STAGE_V][STAGE_H] = {0};
// ゲームウィンドウ
static HWND game_window;
// クライアント領域の大きさ
static const int CLIENT_DIM_X = CELL_DIM_X * STAGE_H;
static const int CLIENT_DIM_Y = CELL_DIM_Y * STAGE_V;
// ブロックのカラーパターン
enum { NUM_COLORS = 6 };
static const COLORREF BLOCK_RGB_COLORS[NUM_COLORS] = {
RGB(255, 0, 0),
RGB( 0, 255, 0),
RGB( 0, 0, 255),
RGB(255, 255, 0),
RGB( 0, 255, 255),
RGB(255, 0, 255)
};
// [cos(rad) -sin(rad)]
// [sin(rad) cos(rad)]
void block_rotate(int &h, int &v, int angle90)
{
switch(angle90 % 4) {
case 3: std::swap(h, v); v *= -1; break;
case 1: std::swap(h, v); h *= -1; break;
case 2:
h *= -1;
v *= -1;
break;
}
}
// ブロックの各セルの現在位置を取得
void block_location(Cell *cells)
{
for(int i = 0; i < NUM_CELLS; ++i) {
const Cell &cell = block_cells[i];
int h = cell.h - block_ch;
int v = cell.v - block_cv;
block_rotate(h, v, block_angle90);
cells->h = h + block_ch + block_base_h;
cells->v = v + block_cv + block_base_v;
cells++;
}
}
// ブロックの当たり判定を行う
bool block_hittest()
{
Cell cells[NUM_CELLS];
block_location(cells);
for(int i = 0; i < NUM_CELLS; ++i) {
int h = cells[i].h;
int v = cells[i].v;
if(!(0 <= h && h < STAGE_H) || v >= STAGE_V || stage[v][h]) {
return true;
}
}
return false;
}
// ブロックを新しく落とす
void block_drop()
{
int k = rand() % NUM_CASES;
block_cells = BLOCK_CASES[k];
block_ch = block_base_h = 0;
block_cv = block_base_v = 0;
block_base_h = STAGE_H / 2 - 2;// てきとー
block_angle90 = 0;
block_color = (block_color + 1) % NUM_COLORS;
for(int i = 0; i < NUM_CELLS; ++i) {
block_ch += block_cells[i].h;
block_cv += block_cells[i].v;
}
block_ch = int(block_ch / double(NUM_CELLS));
block_cv = int(block_cv / double(NUM_CELLS));
if(block_hittest()) { // ゲーモーバー
exit(-1); // <(^_^;) めんどくっさいので即終了
}
}
// ブロックを一つ下にずらす
void block_down()
{
// 落下の処理
++block_base_v;
if(block_hittest()) { // 地面についた!
--block_base_v;
Cell cells[NUM_CELLS];
block_location(cells);
for(int i = 0; i < NUM_CELLS; ++i) {
stage[cells[i].v][cells[i].h] = block_color + 1;
}
block_drop();
int n = -1;
// ブロックの除去
for(int i = STAGE_V - 1; i >= 0; --i) {
bool clear = true;
for(int j = 0; j < STAGE_H; ++j) {
if(!stage[i][j]) {
clear = false;
if(n > i) {
memcpy(stage[n--], stage[i], sizeof(int) * STAGE_H);
}
break;
}
}
if(clear) n = (std::max)(n, i);
}
memset(stage, 0, sizeof(int) * STAGE_H * (n + 1));
}
}
// セルを描画する
void show_cell(HDC hDC, int h, int v, int color)
{
static HBRUSH color_brushes[NUM_COLORS] = {0};
if(!*color_brushes) {
for(int i = 0; i < NUM_COLORS; ++i) {
color_brushes[i] = CreateSolidBrush(BLOCK_RGB_COLORS[i]);
}
}
SelectObject(hDC, color_brushes[color]);
Rectangle(hDC,
h * CELL_DIM_X,
v * CELL_DIM_Y,
h * CELL_DIM_X + CELL_DIM_X + 1,
v * CELL_DIM_Y + CELL_DIM_Y + 1);
}
// フレームの描画を行う
void show_frame(HDC hDC)
{
static const HBRUSH BG_BRUSH = CreateHatchBrush(
HS_DIAGCROSS, RGB(130, 130, 90));
// 背景
RECT bg = {0, 0, CLIENT_DIM_X, CLIENT_DIM_Y};
FillRect(hDC, &bg, BG_BRUSH);
// 落ちていくブロックの描画
Cell cells[NUM_CELLS];
block_location(cells);
for(int i = 0; i < NUM_CELLS; ++i) {
show_cell(hDC, cells[i].h, cells[i].v, block_color);
}
// 既に落ちたブロックの描画
for(int i = STAGE_V - 1; i >= 0; --i) {
bool none = true;
for(int j = 0; j < STAGE_H; ++j) {
if(stage[i][j]) {
none = false;
show_cell(hDC, j, i, stage[i][j] - 1);
}
}
if(none) {
break;
}
}
}
// ゲームの状態を進行
void PASCAL game_timer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
block_down();
InvalidateRect(game_window, NULL, TRUE);
}
// ゲームを開始
void game_init()
{
if(!SetTimer(game_window, 0, 700u, &game_timer)) {
throw std::runtime_error("タイマーの設定に失敗!");
}
srand(time(NULL));
block_drop();
}
// WndProc
LRESULT CALLBACK window_procedure(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
switch(msg) {
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_CREATE:
game_init();
break;
case WM_KEYDOWN:
switch(wp) {
case VK_SPACE:
++block_angle90; // ブロックの回転
if(block_hittest()) --block_angle90;
break;
case VK_LEFT:
--block_base_h; // ブロックを左にずらす
if(block_hittest()) ++block_base_h;
break;
case VK_RIGHT:
++block_base_h; // ブロックを右にずらす
if(block_hittest()) --block_base_h;
break;
case VK_DOWN:
block_down(); // ブロックの落下を促す
break;
}
InvalidateRect(game_window, NULL, TRUE);
break;
case WM_PAINT:
{
// ダブルバッファリングで描画
HDC hDC = GetDC(hWnd);
HDC hCompatibleDC = CreateCompatibleDC(hDC);
HBITMAP hBitmap = CreateCompatibleBitmap(hDC, CLIENT_DIM_X, CLIENT_DIM_Y);
HBITMAP hOldBitmap = HBITMAP(SelectObject(hCompatibleDC, hBitmap));
RECT rect = {0, 0, CLIENT_DIM_X, CLIENT_DIM_Y};
FillRect(hCompatibleDC, &rect, HBRUSH(GetStockObject(BLACK_BRUSH)));
show_frame(hCompatibleDC);
PAINTSTRUCT ps;
hDC = BeginPaint( hWnd, &ps );
BitBlt(hDC, 0, 0, CLIENT_DIM_X, CLIENT_DIM_Y, hCompatibleDC, 0, 0, SRCCOPY);
EndPaint(hWnd, &ps);
SelectObject(hCompatibleDC, hOldBitmap);
DeleteObject(hBitmap);
DeleteDC(hCompatibleDC);
}
break;
}
return DefWindowProc(hWnd, msg, wp, lp);
}
int main(int argc, char **argv)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = window_procedure;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetModuleHandle(NULL);
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = HBRUSH(GetStockObject(WHITE_BRUSH));
wc.lpszMenuName = NULL;
wc.lpszClassName = "TETRIS_WINDOW";
wc.hIconSm = NULL;
if(RegisterClassEx(&wc) == 0) {
throw std::runtime_error("ウィンドウクラスの作成に失敗!");
}
enum { WINDOW_STYLE = WS_OVERLAPPEDWINDOW &~(WS_THICKFRAME | WS_MAXIMIZEBOX) };
// ウィンドウサイズをクライアント領域のサイズに合わせる
RECT rect = {0, 0, CLIENT_DIM_X, CLIENT_DIM_Y};
AdjustWindowRect(&rect, WINDOW_STYLE, FALSE);
game_window = CreateWindowEx(
WS_EX_TOPMOST,
"TETRIS_WINDOW",
"tetris",
WINDOW_STYLE,
200,
200,
rect.right - rect.left,
rect.bottom - rect.top,
NULL, NULL,
GetModuleHandle(NULL),
NULL);
ShowWindow(game_window, SW_SHOW);
UpdateWindow(game_window);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment