Skip to content

Instantly share code, notes, and snippets.

@neguse
Created June 20, 2020 09:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neguse/39c4307d10d8048e6314d5e4dc29d938 to your computer and use it in GitHub Desktop.
Save neguse/39c4307d10d8048e6314d5e4dc29d938 to your computer and use it in GitHub Desktop.
10年前のテトリスと今のテトリス
#include<windows.h>
#include <mmsystem.h>
#include <stdarg.h>
#include <vector>
#include <fstream>
namespace Debug {
using namespace std;
static const int DEBUG_BUF_SIZE = 1024;
void Trace(const char* fmt, ...){
char buf[DEBUG_BUF_SIZE] = {'\0'};
va_list args;
va_start(args, fmt);
vsprintf(buf, fmt, args);
va_end(args);
OutputDebugStringA(buf);
}
void Open(const char* fmt, ...){
char buf[DEBUG_BUF_SIZE] = {'\0'};
va_list args;
va_start(args, fmt);
vsprintf(buf, fmt, args);
va_end(args);
MessageBoxA(0, buf, "ERROR", MB_OK);
}
void Abort(){
OutputDebugStringA("program aborted\n");
abort();
}
} // namespace Debug
#if _DEBUG
// デバッグ時
#define TRACE(...) Debug::Trace(__VA_ARGS__)
#ifdef _WIN32
// Visual C++用
#define ASSERT_STR ("Assertion failed (%s) at %s(line:%d)\n")
#define ASSERT(e) ((e) ? (void)0 : ((Debug::Open(ASSERT_STR, #e, __FILE__, __LINE__), Debug::Abort())))
#define VERIFY_STR ("Verify failed (%s) at %s(line:%d)\n")
#define VERIFY(e) ((e) ? (void)0 : ((Debug::Open(VERIFY_STR, #e, __FILE__, __LINE__), Debug::Abort())))
#else
// GCC用
#define ASSERT_STR ("Assertion failed (%s) at %s(line:%d) %s\n")
#define ASSERT(e) ((e) ? (void)0 : ((Lib::Debug::Trace(ASSERT_STR, #e, __FILE__, __LINE__, __func__), Lib::Debug::Abort())))
#define VERIFY_STR ("Verify failed (%s) at %s(line:%d)\n")
#define VERIFY(e) ((e) ? (void)0 : ((Lib::Debug::Trace(VERIFY_STR, #e, __FILE__, __LINE__, __func__), Lib::Debug::Abort())))
#endif
#else
// 非デバッグ時
#define TRACE(fmt, ...)
#define ASSERT(e)
#define VERIFY(e) (e)
#endif
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
typedef signed int s32;
typedef signed short s16;
typedef signed char s8;
typedef float f32;
typedef double f64;
static const u32 WND_W = 800;
static const u32 WND_H = 800;
static const u32 DISP_W = 200;
static const u32 DISP_H = 200;
static const u32 FIELD_W = 12;
static const u32 FIELD_H = 30;
enum {
KEY_LEFT,
KEY_RIGHT,
KEY_UP,
KEY_DOWN,
KEY_BTN1,
KEY_BTN2,
KEY_MAX,
};
enum {
F_NONE, // 空
F_FIXBLOCK, // 固定済み
F_MYBLOCK, // 動かし中
F_MAX,
};
static const u32 F_COLOR[F_MAX] = {
0x000000,
0xffffff,
0xffffff,
};
static const u32 MYBLOCK_TYPE = 6;
static const u32 MYBLOCK_ROT = 4;
static const u32 MYBLOCK_DEF_L = 4;
static const u8 MYBLOCK_DEF[MYBLOCK_TYPE][MYBLOCK_ROT][MYBLOCK_DEF_L * MYBLOCK_DEF_L + 1] = {
{ // 四角
"0000"
"0000"
"1100"
"1100",
"0000"
"0000"
"1100"
"1100",
"0000"
"0000"
"1100"
"1100",
"0000"
"0000"
"1100"
"1100",
},
{ // 棒
"1000"
"1000"
"1000"
"1000",
"1111"
"0000"
"0000"
"0000",
"0001"
"0001"
"0001"
"0001",
"0000"
"0000"
"0000"
"1111",
},
{ // Z
"0000"
"0000"
"1100"
"0110",
"0000"
"0100"
"1100"
"1000",
"0000"
"0000"
"1100"
"0110",
"0000"
"0100"
"1100"
"1000",
},
{ // S
"0000"
"0000"
"0110"
"1100",
"0000"
"1000"
"1100"
"0100",
"0000"
"0000"
"0110"
"1100",
"0000"
"1000"
"1100"
"0100",
},
{ // L
"0000"
"1000"
"1000"
"1100",
"0000"
"1110"
"1000"
"0000",
"0000"
"0110"
"0010"
"0010",
"0000"
"0000"
"0010"
"1110",
},
{ // 逆L
"0000"
"0010"
"0010"
"0110",
"0000"
"0000"
"1000"
"1110",
"0000"
"1100"
"1000"
"1000",
"0000"
"1110"
"0010"
"0000",
},
};
u32 KEY_DEFS[KEY_MAX] = {
VK_LEFT,
VK_RIGHT,
VK_UP,
VK_DOWN,
'Z',
'X',
};
static const wchar_t* CLASS_NAME = L"tet";
class WndClass {
public:
WNDCLASSEX m_wc;
public:
WndClass(WNDPROC handler) {
WNDCLASSEX wc = {
sizeof( WNDCLASSEX ), CS_CLASSDC, handler, 0L, 0L,
GetModuleHandle( NULL ), NULL, NULL, NULL, NULL,
CLASS_NAME, NULL
};
m_wc = wc;
RegisterClassEx(&m_wc);
}
~WndClass() {
UnregisterClass( CLASS_NAME, m_wc.hInstance );
}
};
class Window {
private:
HWND m_hWindow;
bool m_isQuit;
u32 m_width, m_height;
public:
Window(u32 w, u32 h)
: m_hWindow(NULL)
, m_isQuit(false)
, m_width(w)
, m_height(h)
{
static WndClass cls(handler);
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = m_width;
rect.bottom = m_height;
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, false);
m_hWindow = CreateWindow( CLASS_NAME, CLASS_NAME,
WS_OVERLAPPEDWINDOW, 100, 100, rect.right, rect.bottom,
NULL, NULL, cls.m_wc.hInstance, NULL );
ShowWindow(m_hWindow, SW_SHOWDEFAULT);
UpdateWindow(m_hWindow);
}
HWND getHandle() {
return m_hWindow;
}
bool isQuit() {
return m_isQuit;
}
u32 getWidth() const {
return m_width;
}
u32 getHeight() const {
return m_height;
}
static LRESULT WINAPI handler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch( msg )
{
case WM_DESTROY:
PostQuitMessage( 0 );
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
void doEvents() {
MSG msg;
while ( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) ) {
if (msg.message == WM_QUIT) {
m_isQuit = true;
break;
}
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
};
class DIBSection {
private:
u32 m_width, m_height;
u32* m_pBits;
HBITMAP hBitmap, hPrevBmp;
HDC hMemDC;
public:
DIBSection(HWND hWnd, u32 w, u32 h)
: m_width(w)
, m_height(h)
{
// setup bitmapinfo
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(bmi));
{
BITMAPINFOHEADER& head = bmi.bmiHeader;
head.biSize = sizeof(BITMAPINFOHEADER);
head.biBitCount = 32;
head.biPlanes = 1;
head.biWidth = w;
head.biHeight = -(int)h;
}
// create DIBSection
hBitmap = CreateDIBSection(
NULL,
&bmi,
DIB_RGB_COLORS,
(void**)&m_pBits,
NULL,
0);
ASSERT(hBitmap);
HDC hDC = GetDC(hWnd);
hMemDC = CreateCompatibleDC(hDC);
ReleaseDC(hWnd, hDC);
hPrevBmp = (HBITMAP)SelectObject(hMemDC, hBitmap);
}
~DIBSection() {
SelectObject(hMemDC, hPrevBmp);
DeleteObject(hBitmap);
}
void setPixel(u32 x, u32 y, u32 color) {
if (x < m_width && y < m_height) {
y = m_height - y - 1;
m_pBits[x + y * m_width] = color;
}
}
void fill(u32 x, u32 y, u32 w, u32 h, u32 color) {
for (u32 py = y; py < y + h; py++) {
for (u32 px = x; px < x + w; px++) {
setPixel(px, py, color);
}
}
}
HDC getDDB() const {
return hMemDC;
}
u32 getWidth() const {
return m_width;
}
u32 getHeight() const {
return m_height;
}
}; // class DIBSection
static bool getBlock (u32 x, u32 y, u32 btype, u32 rot) {
y = MYBLOCK_DEF_L - y - 1;
const u8* def = MYBLOCK_DEF[btype][rot];
return def[x + y * MYBLOCK_DEF_L] == '1';
}
class Field {
private:
static const u32 BLOCK_L = 10;
static const u32 BLOCK_D = 1;
u8 *m_pField;
u32 m_width, m_height;
public:
Field(u32 w, u32 h)
: m_width(w)
, m_height(h)
{
m_pField = new u8[m_width * m_height];
clear();
}
~Field() {
delete[] m_pField;
}
void eraseLine(u32 y) {
for (u32 py = y + 1; py < m_height - 1; py++) {
for (u32 px = 1; px < m_width - 1; px++) {
at(px, py - 1) = at(px, py);
}
}
}
u32 doErase() {
u32 count = 0;
for (u32 y = 1; y < m_height - 1; y++) {
bool erase = true;
for (u32 x = 1; x < m_width - 1; x++) {
if (at(x, y) == F_NONE) {
erase = false;
break;
}
}
if (erase) {
eraseLine(y);
count++;
continue;
}
}
return count;
}
void clear() {
for (u32 x = 0; x < m_width; x++) {
for (u32 y = 0; y < m_height; y++) {
at(x, y) = F_NONE;
}
}
for (u32 y = 0; y < m_height; y++) {
at(0, y) = F_FIXBLOCK;
at(m_width - 1, y) = F_FIXBLOCK;
}
for (u32 x = 0; x < m_width; x++) {
at(x, 0) = F_FIXBLOCK;
at(x, m_height - 1) = F_FIXBLOCK;
}
}
void fix() {
for (u32 x = 0; x < m_width; x++) {
for (u32 y = 0; y < m_height; y++) {
if (at(x, y) == F_MYBLOCK) {
at(x, y) = F_FIXBLOCK;
}
}
}
}
void reset() {
for (u32 x = 0; x < m_width; x++) {
for (u32 y = 0; y < m_height; y++) {
if (at(x, y) == F_MYBLOCK) {
at(x, y) = F_NONE;
}
}
}
}
u8& at(u32 x, u32 y) {
ASSERT(x < m_width && y < m_height);
return m_pField[x + m_width * y];
}
bool isOk(u32 x, u32 y, u32 btype, u32 rot) {
for (u32 px = 0; px < MYBLOCK_DEF_L; px++) {
for (u32 py = 0; py < MYBLOCK_DEF_L; py++) {
u32 fx = px + x;
u32 fy = py + y;
if (getBlock(px, py, btype, rot) && (at(fx, fy) == F_FIXBLOCK)) {
return false;
}
}
}
return true;
}
// おけたらtrue
// おけなかったら、無理やり置いてfalse
bool putBlock(u32 x, u32 y, u32 btype, u32 rot) {
bool ret = true;
for (u32 px = 0; px < MYBLOCK_DEF_L; px++) {
for (u32 py = 0; py < MYBLOCK_DEF_L; py++) {
u32 fx = px + x;
u32 fy = py + y;
if (getBlock(px, py, btype, rot)) {
if (at(fx, fy) == F_FIXBLOCK) {
ret = false;
} else {
at(fx, fy) = F_MYBLOCK;
}
}
}
}
return ret;
}
void render(DIBSection& disp) {
for (u32 x = 0; x < m_width; x++) {
for (u32 y = 0; y < m_height; y++) {
// TRACE("%d", at(x, y));
disp.fill(
x * BLOCK_L + BLOCK_D,
y * BLOCK_L + BLOCK_D,
BLOCK_L - BLOCK_D * 2,
BLOCK_L - BLOCK_D * 2,
F_COLOR[at(x, y)]);
}
// TRACE("\n");
}
// TRACE("\n");
}
}; // class Field
static void Flip(DIBSection& disp, Window& wnd ) {
HDC hdc = GetDC(wnd.getHandle());
StretchBlt(hdc,
0, 0, wnd.getWidth(), wnd.getHeight(),
disp.getDDB(),
0, 0,
disp.getWidth(), disp.getHeight(),
SRCCOPY);
ReleaseDC(wnd.getHandle(), hdc);
}
static bool isPush(u32 key) {
ASSERT(key < KEY_MAX);
return GetAsyncKeyState(KEY_DEFS[key]);
}
static u32 selectType(u32 prevType) {
u32 type;
do {
type = (rand()) % MYBLOCK_TYPE;
} while (type == prevType);
return type;
}
static const int X_INIT_POS = FIELD_W / 2;
static const int Y_INIT_POS = 20;
static const int FALL_D_INIT = 5;
int WINAPI WinMain(
HINSTANCE hInstance ,
HINSTANCE hPrevInstance ,
LPSTR lpCmdLine ,
int nCmdShow )
{
Window wnd(WND_W, WND_H);
DIBSection disp(wnd.getHandle(), DISP_W, DISP_H);
Field f(FIELD_W, FIELD_H);
u32 x = X_INIT_POS;
u32 y = Y_INIT_POS;
u32 rot = 0;
u32 type = selectType(0xff);
u32 fall_d = FALL_D_INIT;
while (!wnd.isQuit()) {
f.reset();
if (isPush(KEY_LEFT)) {
if (f.isOk(x - 1, y, type, rot)) {
x--;
}
}
if (isPush(KEY_RIGHT)) {
if (f.isOk(x + 1, y, type, rot)) {
x++;
}
}
if (isPush(KEY_BTN2)) {
u32 nrot = (rot + MYBLOCK_ROT - 1) % MYBLOCK_ROT;
if (f.isOk(x, y, type, nrot)) {
rot = nrot;
}
}
if (isPush(KEY_BTN1)) {
u32 nrot = (rot + 1) % MYBLOCK_ROT;
if (f.isOk(x, y, type, nrot)) {
rot = nrot;
}
}
if (isPush(KEY_DOWN)) {
fall_d = 0;
}
if (fall_d == 0) {
if (f.isOk(x, y - 1, type, rot)) {
y--;
} else {
f.putBlock(x, y, type, rot);
f.fix();
f.doErase();
x = X_INIT_POS;
y = Y_INIT_POS;
rot = 0;
type = selectType(type);
}
fall_d = FALL_D_INIT;
}
fall_d--;
f.putBlock(x, y, type, rot);
disp.fill(0, 0, DISP_W, DISP_H, 0x00);
f.render(disp);
wnd.doEvents();
Flip(disp, wnd);
Sleep(100);
}
return 0;
}
#include "ng.h"
enum {
KEY_UP,
KEY_DOWN,
KEY_RIGHT,
KEY_LEFT,
KEY_1,
KEY_2,
};
// (320, 240) ~ (-320, -240)
static const vec2 WindowSize = {320, 240};
static const vec2 kCellSize = {18, 18};
static const ivec2 BoardSize = {10, 20};
ngColor kBlack(0x00, 0x00, 0x00, 0xff);
ngColor kWhite(0xff, 0xff, 0xff, 0xff);
ngColor kGray(0x80, 0x80, 0x80, 0xff);
enum class Cell {
None,
Block, // TODO Color
};
// 67
// 45
// 23
// 01
const int kTetriminoTypes = 7;
const ivec2 kTetriminoXY = {2, 4};
const int kTetriminoS = 8;
const bool kTetrimino[kTetriminoTypes][kTetriminoS] = {
{1, 0, 1, 0, 1, 0, 1, 0}, // I
{1, 1, 1, 1, 0, 0, 0, 0}, // O
{0, 1, 1, 1, 1, 0, 0, 0}, // S
{1, 0, 1, 1, 0, 1, 0, 0}, // Z
{1, 1, 0, 1, 0, 1, 0, 0}, // J
{1, 1, 1, 0, 1, 0, 0, 0}, // L
{0, 1, 1, 1, 0, 1, 0, 0}, // T
};
struct Board {
int minoType;
int minoRot;
ivec2 minoPos;
std::vector<Cell> cell;
void Init() {
cell.resize(BoardSize.x * BoardSize.y);
for (int x = 0; x < BoardSize.x; x++) {
for (int y = 0; y < BoardSize.y; y++) {
SetAt({x, y}, Cell::None);
}
// SetAt({x, 0}, Cell::Block);
}
for (int y = 0; y < BoardSize.y; y++) {
// SetAt({0, y}, Cell::Block);
// SetAt({BoardSize.x - 1, y}, Cell::Block);
}
NextMino();
}
Cell GetAt(ivec2 pos) {
if (0 <= pos.x && pos.x < BoardSize.x) {
if (0 <= pos.y && pos.y < BoardSize.y) {
return cell[pos.x + pos.y * BoardSize.x];
}
}
if (pos.y >= BoardSize.y) {
return Cell::None;
} else {
return Cell::Block;
}
}
bool SetAt(ivec2 pos, Cell c) {
if (0 <= pos.x && pos.x < BoardSize.x) {
if (0 <= pos.y && pos.y < BoardSize.y) {
cell[pos.x + pos.y * BoardSize.x] = c;
return true;
}
}
return false;
}
void RenderCell(ngProcess& p, ivec2 pos, Cell c) {
ngColor col;
switch (c) {
case Cell::Block:
col = kBlack;
break;
case Cell::None:
col = kGray;
}
p.Rect(col, col, vec2{pos.x * (kCellSize.x + 2), pos.y * (kCellSize.y + 2)},
kCellSize * 0.5f);
}
bool NextMino() {
minoPos = ivec2{BoardSize.x / 2, BoardSize.y - kTetriminoXY.y};
minoRot = 0;
minoType = rand() % kTetriminoTypes;
if (MinoCollision()) {
return false;
}
return true;
}
void EraseLine(int yo) {
for (int y = yo; y < BoardSize.y; y++) {
for (int x = 0; x < BoardSize.x; x++) {
SetAt({x, y}, GetAt({x, y + 1}));
}
}
}
int Erase() {
int lines = 0;
for (int y = 0; y < BoardSize.y;) {
int n = 0;
for (int x = 0; x < BoardSize.x; x++) {
if (GetAt({x, y}) == Cell::None) {
break;
}
n++;
}
if (n == BoardSize.x) {
EraseLine(y);
lines++;
} else {
y++;
}
}
return lines;
}
void FixMino() {
for (int x = 0; x < kTetriminoXY.x; x++) {
for (int y = 0; y < kTetriminoXY.y; y++) {
int index = x + y * kTetriminoXY.x;
if (kTetrimino[minoType][index]) {
ivec2 pos = minoPos + MinoRotated(ivec2({x, y}));
SetAt(pos, Cell::Block);
}
}
}
}
bool MinoCollision() {
for (int x = 0; x < kTetriminoXY.x; x++) {
for (int y = 0; y < kTetriminoXY.y; y++) {
int index = x + y * kTetriminoXY.x;
if (kTetrimino[minoType][index]) {
ivec2 pos = minoPos + MinoRotated(ivec2({x, y}));
if (GetAt(pos) == Cell::Block) return true;
}
}
}
return false;
}
bool MoveMino(ivec2 dpos) {
ivec2 tmpPos = minoPos;
minoPos += dpos;
if (MinoCollision()) {
minoPos = tmpPos;
return false;
}
return true;
}
bool RotMino(int r) {
int tmpRot = minoRot;
minoRot = (minoRot + r + 4) % 4;
if (MinoCollision()) {
minoRot = tmpRot;
return false;
}
return true;
}
ivec2 MinoRotated(ivec2 pos) {
switch (minoRot % 4) {
case 0:
return pos;
case 1:
return {-pos.y, pos.x};
case 2:
return {-pos.x, -pos.y};
case 3:
return {pos.y, -pos.x};
default:
abort();
}
}
void Render(ngProcess& p) {
p.Push(translate(mat3(1.f), {-WindowSize.x + 30, -WindowSize.y + 30}));
// render board
for (int x = 0; x < BoardSize.x; x++) {
for (int y = 0; y < BoardSize.y; y++) {
RenderCell(p, {x, y}, GetAt({x, y}));
}
}
// render mino
for (int x = 0; x < kTetriminoXY.x; x++) {
for (int y = 0; y < kTetriminoXY.y; y++) {
int index = x + y * kTetriminoXY.x;
if (kTetrimino[minoType][index]) {
ivec2 pos = minoPos + MinoRotated(ivec2({x, y}));
RenderCell(p, pos, Cell::Block);
}
}
}
p.Pop();
}
};
int main(void) {
auto proc = ngProcess::NewProcess();
proc->MapKeyboard('w', KEY_UP);
proc->MapKeyboard('a', KEY_LEFT);
proc->MapKeyboard('s', KEY_DOWN);
proc->MapKeyboard('d', KEY_RIGHT);
proc->MapKeyboard('z', KEY_1);
proc->MapKeyboard('x', KEY_2);
proc->MapMouseButton(1, KEY_1);
proc->MapMouseButton(3, KEY_2); // right mouse button
if (!proc->Init()) {
return 1;
}
Board b;
b.Init();
const float kTimeToDrop = 0.8;
float timeToDrop = kTimeToDrop;
int scores = 0;
bool drop = false;
proc->Run([&](ngProcess& p, float dt) {
// update
p.Clear(kWhite);
if (p.IsJustPressed(KEY_LEFT)) {
b.MoveMino({-1, 0});
}
if (p.IsJustPressed(KEY_RIGHT)) {
b.MoveMino({1, 0});
}
if (p.IsJustPressed(KEY_1)) {
b.RotMino(1);
}
if (p.IsJustPressed(KEY_2)) {
b.RotMino(-1);
}
if (p.IsJustPressed(KEY_DOWN)) {
drop = true;
}
timeToDrop -= dt;
if (timeToDrop < 0 || drop) {
if (!b.MoveMino({0, -1})) {
b.FixMino();
int lines = b.Erase();
scores += lines;
if (!b.NextMino()) {
// TODO ゲームオーバー
}
drop = false;
}
timeToDrop = kTimeToDrop;
}
b.Render(p);
p.Text(kBlack, {150, 200}, 30, fmt::format(u8"score:{0}", scores).c_str());
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment