main.cpp
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#include <windows.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
LRESULT CALLBACK WindowProc(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam);
#include "engine.h"
#include "game.h"
LRESULT CALLBACK WindowProc(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam) {
switch(Message) {
case WM_KEYDOWN: {
switch(WParam) {
case 'O': { DestroyWindow(Window); } break;
case VK_UP: { Rotate(); } break;
case VK_LEFT: { Move(LEFT); } break;
case VK_DOWN: { Move(DOWN); } break;
case VK_RIGHT: { Move(RIGHT); } break;
case VK_SPACE: { Drop(); } break;
}
} break;
case WM_DESTROY: { PostQuitMessage(0); } break;
default: {
return DefWindowProc(Window, Message, WParam, LParam);
}
}
return 0;
}
int WINAPI wWinMain(HINSTANCE Instance, HINSTANCE PrevInstance, PWSTR CmdLine, int CmdShow) {
srand((unsigned int)time(NULL));
window Window = {};
Window.ClientWidth = TILE_SIZE * BOARD_X_TILES;
Window.ClientHeight = TILE_SIZE * BOARD_Y_TILES;
Window.Center = true;
DWORD Error = InitWindow(&Window, Instance);
if(Error) { return Error; }
bitmap Bitmap = {};
Error = InitBitmap(&Bitmap, &Window);
if(Error) { return Error; }
timer Timer = {};
Error = InitTimer(&Timer);
if(Error) { return Error; }
CurrentBlock = GetRandomBlock();
bool Running = true;
while(Running) {
MSG Message;
while(PeekMessage(&Message, nullptr, 0, 0, PM_REMOVE)) {
if(Message.message == WM_QUIT) Running = false;
TranslateMessage(&Message);
DispatchMessage(&Message);
}
UpdateTimer(&Timer);
if(Timer.ElapsedMilliSeconds > 500) {
Update();
StartTimer(&Timer);
}
ClearScreen(0x333333, &Bitmap);
DrawStack(&Bitmap);
DrawBlock(&CurrentBlock, &Bitmap);
Blit(&Bitmap, &Window);
}
return 0;
}
game.h
#define SHAPES 7
#define ROTATIONS 4
#define TILE_SIZE 32
#define BOARD_X_TILES 10
#define BOARD_Y_TILES 16
typedef enum { LEFT, RIGHT, UP, DOWN } direction;
typedef struct {
int X;
int Y;
int Shape;
int Rotation;
u32 Color;
} block;
// Globals
u32 StackColors[BOARD_X_TILES * BOARD_Y_TILES];
u32 BlockColors[SHAPES] = {
0x20B2AA, 0x4169E1, 0xffa500, 0xFFFF33,
0x32CD32, 0xBA55D3, 0xff0000
};
u16 Shapes[SHAPES][ROTATIONS] = {
{0x0f00, 0x2222, 0x00f0, 0x4444}, // I
{0x8e00, 0x6440, 0x0e20, 0x44c0}, // J
{0x2e00, 0x4460, 0x0e80, 0xc440}, // L
{0x6600, 0x6600, 0x6600, 0x6600}, // O
{0x6c00, 0x4620, 0x06c0, 0x8c40}, // S
{0x4e00, 0x4640, 0x0e40, 0x4c40}, // T
{0xc600, 0x2640, 0x0c60, 0x4c80}, // Z
};
int WallKickOffsetsCommon[8][10] = { // all except I
0, 0, -1, 0, -1, 1, 0,-2, -1,-2, // 0->R
0, 0, 1, 0, 1,-1, 0, 2, 1, 2, // R->0
0, 0, 1, 0, 1,-1, 0, 2, 1, 2, // R->2
0, 0, -1, 0, -1, 1, 0,-2, -1,-2, // 2->R
0, 0, 1, 0, 1, 1, 0,-2, 1,-2, // 2->L
0, 0, -1, 0, -1,-1, 0, 2, -1, 2, // L->2
0, 0, -1, 0, -1,-1, 0, 2, -1, 2, // L->0
0, 0, 1, 0, 1, 1, 0,-2, 1,-2, // 0->L
};
int WallKickOffsetsI[8][10] = { // I
0, 0, -2, 0, 1, 0, -2,-1, 1, 2, // 0->R
0, 0, 2, 0, -1, 0, 2, 1, -1,-2, // R->0
0, 0, -1, 0, 2, 0, -1, 2, 2,-1, // R->2
0, 0, 1, 0, -2, 0, 1,-2, -2, 1, // 2->R
0, 0, 2, 0, -1, 0, 2, 1, -1,-2, // 2->L
0, 0, -2, 0, 1, 0, -2,-1, 1, 2, // L->2
0, 0, 1, 0, -2, 0, 1,-2, -2, 1, // L->0
0, 0, -1, 0, 2, 0, -1, 2, 2,-1, // 0->L
};
block CurrentBlock;
// Declarations
void Rotate();
int Collides(block *Block);
int WithinBounds(int X, int Y);
void Move(direction Direction);
void Drop();
block GetRandomBlock();
void Drop() {
block TempBlock = CurrentBlock;
while(!Collides(&TempBlock)) {
TempBlock.Y += TILE_SIZE;
}
CurrentBlock.Y = TempBlock.Y - TILE_SIZE;
}
void WallKick(block *TempBlock) {
int *Offsets = NULL;
if(TempBlock->Shape == 0) {
Offsets = &WallKickOffsetsI[0][0];
} else {
Offsets = &WallKickOffsetsCommon[0][0];
}
int OffsetRow = 0;
int R1 = CurrentBlock.Rotation;
int R2 = TempBlock->Rotation;
if(R1 == 0 && R2 == 1) { OffsetRow = 0; }
else if(R1 == 1 && R2 == 2) { OffsetRow = 2; }
else if(R1 == 2 && R2 == 3) { OffsetRow = 4; }
else if(R1 == 3 && R2 == 0) { OffsetRow = 6; }
for(int I = 0; I < 10; I += 2) {
TempBlock->X = CurrentBlock.X +
*(Offsets + OffsetRow * 10 + I + 0) * TILE_SIZE;
TempBlock->Y = CurrentBlock.Y +
*(Offsets + OffsetRow * 10 + I + 1) * TILE_SIZE;
if(!Collides(TempBlock)) {
CurrentBlock = *TempBlock;
return;
}
}
}
void Rotate() {
block TempBlock = CurrentBlock;
++TempBlock.Rotation;
if(TempBlock.Rotation == 4) { TempBlock.Rotation = 0; }
WallKick(&TempBlock);
if(Collides(&TempBlock)) { return; };
CurrentBlock = TempBlock;
}
void Move(direction Direction) {
block TempBlock = CurrentBlock;
switch(Direction) {
case LEFT: { TempBlock.X -= TILE_SIZE; } break;
case RIGHT: { TempBlock.X += TILE_SIZE; } break;
case DOWN: { TempBlock.Y += TILE_SIZE; } break;
}
if(Collides(&TempBlock)) { return; };
CurrentBlock = TempBlock;
}
int IsBlockTile(block *Block, int X, int Y) {
if((0x8000 >> (Y * 4 + X)) &
Shapes[Block->Shape][Block->Rotation]) {
return 1;
}
return 0;
}
void DrawStack(bitmap *Bitmap) {
for(int Y = 0; Y < BOARD_Y_TILES; ++Y) {
for(int X = 0; X < BOARD_X_TILES; ++X) {
if(StackColors[Y * BOARD_X_TILES + X] != 0) {
DrawRectangleBordered(
X * TILE_SIZE,
Y * TILE_SIZE,
TILE_SIZE, TILE_SIZE,
StackColors[Y * BOARD_X_TILES + X],
1, 0x000000,
Bitmap);
}
}
}
}
void DrawBlock(block *Block, bitmap *Bitmap) {
for(int Y = 0; Y < 4; ++Y) {
for(int X = 0; X < 4; ++X) {
if(IsBlockTile(Block, X, Y)) {
DrawRectangleBordered(
Block->X + (X * TILE_SIZE),
Block->Y + (Y * TILE_SIZE),
TILE_SIZE, TILE_SIZE,
Block->Color,
1, 0x000000,
Bitmap
);
}
}
}
}
block GetRandomBlock() {
block Block = {};
Block.X = 4 * TILE_SIZE;
Block.Y = 0;
Block.Shape = rand() % SHAPES;
Block.Rotation = rand() % ROTATIONS;
Block.Color = BlockColors[Block.Shape];
return Block;
}
void DeleteStackRow(int Row) {
for(int Y = Row; Y > 0; --Y) {
for(int X = 0; X < BOARD_X_TILES; ++X) {
StackColors[Y * BOARD_X_TILES + X] =
StackColors[(Y-1) * BOARD_X_TILES + X];
}
}
}
void UpdateStackRows() {
for(int Y = 0; Y < BOARD_Y_TILES; ++Y) {
int LineCounter = 0;
for(int X = 0; X < BOARD_X_TILES; ++X) {
if(StackColors[Y * BOARD_X_TILES + X] != 0) {
++LineCounter;
}
}
if(LineCounter == BOARD_X_TILES) {
DeleteStackRow(Y);
}
}
}
void AddStack(block *Block) {
for(int Y = 0; Y < 4; ++Y) {
for(int X = 0; X < 4; ++X) {
if(IsBlockTile(Block, X, Y)) {
StackColors[
(Block->Y / TILE_SIZE + Y) * BOARD_X_TILES +
Block->X / TILE_SIZE + X] = Block->Color;
}
}
}
UpdateStackRows();
CurrentBlock = GetRandomBlock();
}
int Collides(block *Block) {
for(int Y = 0; Y < 4; ++Y) {
for(int X = 0; X < 4; ++X) {
if(IsBlockTile(Block, X, Y)) {
if(!WithinBounds(Block->X + X * TILE_SIZE, Block->Y + Y * TILE_SIZE)) {
return 1;
}
if(StackColors[(Block->Y / TILE_SIZE + Y) * BOARD_X_TILES + Block->X / TILE_SIZE + X] != 0) {
return 1;
}
}
}
}
return 0;
}
int WithinBounds(int X, int Y) {
if(Y < 0 || Y >= BOARD_Y_TILES * TILE_SIZE) { return 0; }
if(X < 0 || X >= BOARD_X_TILES * TILE_SIZE) { return 0; }
return 1;
}
void Update() {
block TempBlock = CurrentBlock;
TempBlock.Y += TILE_SIZE;
if(!Collides(&TempBlock)) {
CurrentBlock.X = TempBlock.X;
CurrentBlock.Y = TempBlock.Y;
} else {
AddStack(&CurrentBlock);
}
}
engine.h
typedef uint16_t u16;
typedef uint32_t u32;
typedef struct {
HWND Handle;
HDC DeviceContext;
int ClientWidth;
int ClientHeight;
bool Center;
} window;
typedef struct {
void* Memory;
BITMAPINFO Info;
int Width;
int Height;
} bitmap;
typedef struct {
LARGE_INTEGER StartingCount;
LARGE_INTEGER EndingCount;
LARGE_INTEGER TotalCount;
LARGE_INTEGER CountsPerSecond;
double ElapsedMilliSeconds;
} timer;
void DrawPixel(int X, int Y, u32 Color, bitmap *Bitmap) {
u32 *Pixel = (u32 *)Bitmap->Memory;
Pixel += Y * Bitmap->Width + X;
*Pixel = Color;
}
void ClearScreen(u32 Color, bitmap *Bitmap) {
u32 *Pixel = (u32 *)Bitmap->Memory;
for(int Index = 0;
Index < Bitmap->Width * Bitmap->Height;
++Index) { *Pixel++ = Color; }
}
void DrawRectangle(int RectangleX,
int RectangleY,
int RectangleWidth,
int RectangleHeight,
u32 Color,
bitmap *Bitmap) {
u32 *Pixel = (u32 *)Bitmap->Memory;
Pixel += RectangleY * Bitmap->Width + RectangleX;
for(int Y = 0; Y < RectangleHeight; ++Y) {
for(int X = 0;
X < RectangleWidth;
++X) { *Pixel++ = Color; }
Pixel += Bitmap->Width - RectangleWidth;
}
}
void DrawRectangleBordered(int RectangleX, int RectangleY,
int RectangleWidth, int RectangleHeight,
u32 Color,
int BorderSize, u32 BorderColor,
bitmap *Bitmap) {
DrawRectangle(RectangleX, RectangleY, RectangleWidth, RectangleHeight, BorderColor, Bitmap);
DrawRectangle(RectangleX + BorderSize,
RectangleY + BorderSize,
RectangleWidth - BorderSize * 2,
RectangleHeight - BorderSize * 2,
Color, Bitmap);
}
void Blit(bitmap *Bitmap, window *Window) {
StretchDIBits(Window->DeviceContext,
0, 0,
Bitmap->Width, Bitmap->Height,
0, 0,
Window->ClientWidth, Window->ClientHeight,
Bitmap->Memory, &Bitmap->Info,
DIB_RGB_COLORS, SRCCOPY);
}
DWORD InitWindow(window *Window, HINSTANCE Instance) {
// WindowClass
WNDCLASS WindowClass = {};
const wchar_t ClassName[] = L"MyWindowClass";
WindowClass.lpfnWndProc = WindowProc;
WindowClass.hInstance = Instance;
WindowClass.lpszClassName = ClassName;
WindowClass.hCursor = LoadCursor(0, IDC_CROSS);
if(!RegisterClass(&WindowClass)) {
MessageBox(0, L"RegisterClass failed", 0, 0);
return GetLastError();
}
// Window dimensions
int WindowWidth = CW_USEDEFAULT;
int WindowHeight = CW_USEDEFAULT;
// Adjust window dimensions to given client area
// Window dimensions will be client area dimensions + some units for the borders and title bar
if(Window->ClientWidth > 0 &&
Window->ClientHeight > 0) {
RECT WindowRect;
WindowRect.left = 0;
WindowRect.top = 0;
WindowRect.right = Window->ClientWidth;
WindowRect.bottom = Window->ClientHeight;
AdjustWindowRectEx(&WindowRect,
WS_OVERLAPPEDWINDOW, 0, 0);
WindowWidth =
WindowRect.right - WindowRect.left;
WindowHeight =
WindowRect.bottom - WindowRect.top;
}
// Window position
int WindowXPos = CW_USEDEFAULT;
int WindowYPos = CW_USEDEFAULT;
if(Window->Center) {
int ScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int ScreenHeight = GetSystemMetrics(SM_CYSCREEN);
WindowXPos = (ScreenWidth / 2) - (WindowWidth / 2);
WindowYPos = (ScreenHeight / 2) - (WindowHeight / 2);
}
Window->Handle = CreateWindowEx(0, ClassName, L"Program",
WS_OVERLAPPEDWINDOW|WS_VISIBLE,
WindowXPos,
WindowYPos,
WindowWidth, WindowHeight,
0, 0, Instance, 0);
if(!Window->Handle) {
MessageBox(0, L"CreateWindowEx failed", 0, 0);
return GetLastError();
}
// For StretchDIBits
Window->DeviceContext = GetDC(Window->Handle);
return 0;
}
DWORD InitBitmap(bitmap *Bitmap, window *Window) {
// Get client area dimensions
RECT ClientRect;
GetClientRect(Window->Handle, &ClientRect);
Window->ClientWidth = ClientRect.right - ClientRect.left;
Window->ClientHeight = ClientRect.bottom - ClientRect.top;
Bitmap->Width = Window->ClientWidth;
Bitmap->Height = Window->ClientHeight;
// Allocate memory for the bitmap
int BytesPerPixel = 4;
Bitmap->Memory = VirtualAlloc(0,
Bitmap->Width * Bitmap->Height * BytesPerPixel,
MEM_RESERVE|MEM_COMMIT,
PAGE_READWRITE
);
// BitmapInfo struct for StretchDIBits
Bitmap->Info.bmiHeader.biSize = sizeof(Bitmap->Info.bmiHeader);
Bitmap->Info.bmiHeader.biWidth = Bitmap->Width;
// Negative height makes top left as the coordinate system origin for the DrawPixel function, otherwise its bottom left
Bitmap->Info.bmiHeader.biHeight = -Bitmap->Height;
Bitmap->Info.bmiHeader.biPlanes = 1;
Bitmap->Info.bmiHeader.biBitCount = 32;
Bitmap->Info.bmiHeader.biCompression = BI_RGB;
return 0;
}
void StartTimer(timer *Timer) {
QueryPerformanceCounter(&Timer->StartingCount);
}
void UpdateTimer(timer *Timer) {
// get current tick
QueryPerformanceCounter(&Timer->EndingCount);
// get tick count
Timer->TotalCount.QuadPart = Timer->EndingCount.QuadPart - Timer->StartingCount.QuadPart;
// for precision
Timer->TotalCount.QuadPart *= 1000000;
// calculate elapsed milliseconds
Timer->ElapsedMilliSeconds = (double)(Timer->TotalCount.QuadPart / Timer->CountsPerSecond.QuadPart) / 1000;
}
DWORD InitTimer(timer *Timer) {
if(!QueryPerformanceFrequency(&Timer->CountsPerSecond)) {
return GetLastError();
}
StartTimer(Timer);
return 0;
}
void Debugd(const wchar_t *Str, double Value) {
// usage:
// Debugd(L"Elapsedms: %f\n", Timer.ElapsedMilliSeconds);
wchar_t Output[100];
swprintf_s(Output, 100, Str, Value);
OutputDebugString(Output);
}
Build
cl main.cpp /link user32.lib gdi32.lib
build.bat
@echo off
set COMMON_COMPILER_FLAGS=/W4 /wd4100 /Oi /nologo /Fea.exe
set DEBUG_FLAGS=/Zi /MTd
set RELEASE_FLAGS=/O2
set COMPILER_FLAGS=%COMMON_COMPILER_FLAGS% %DEBUG_FLAGS%
REM set COMPILER_FLAGS=%COMMON_COMPILER_FLAGS% %RELEASE_FLAGS%
set LINKER_FLAGS=/INCREMENTAL:NO /OPT:REF
set LIBS=user32.lib gdi32.lib
cl %COMPILER_FLAGS% main.cpp /link %LINKER_FLAGS% %LIBS%
Leave a comment