#include #include #include #define _USE_MATH_DEFINES #include LARGE_INTEGER getFILETIMEoffset() { SYSTEMTIME s; FILETIME f; LARGE_INTEGER t; s.wYear = 1970; s.wMonth = 1; s.wDay = 1; s.wHour = 0; s.wMinute = 0; s.wSecond = 0; s.wMilliseconds = 0; SystemTimeToFileTime(&s, &f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; return (t); } int clock_gettime(int X, struct timeval *tv) { LARGE_INTEGER t; FILETIME f; double microseconds; static LARGE_INTEGER offset; static double frequencyToMicroseconds; static int initialized = 0; static BOOL usePerformanceCounter = 0; if (!initialized) { LARGE_INTEGER performanceFrequency; initialized = 1; usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency); if (usePerformanceCounter) { QueryPerformanceCounter(&offset); frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.; } else { offset = getFILETIMEoffset(); frequencyToMicroseconds = 10.; } } if (usePerformanceCounter) QueryPerformanceCounter(&t); else { GetSystemTimeAsFileTime(&f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; } t.QuadPart -= offset.QuadPart; microseconds = (double)t.QuadPart / frequencyToMicroseconds; t.QuadPart = microseconds; tv->tv_sec = t.QuadPart / 1000000; tv->tv_usec = t.QuadPart % 1000000; return (0); } typedef unsigned char tile_t; enum Tile : tile_t {AIR, WALL, BLOCK, count}; struct fl2d { float x; float y; }; struct int2d { int x; int y; }; int main() { // game stuff const size_t tile_size = 32; // pixel deimensions of a tile (assuming square tiles) const size_t game_width = 32; // width in number of tiles (X) const size_t game_height = 24; // height in number of tiles (Y) tile_t level[game_height][game_width]; // yes, Y first // window, device context and bitmap stuff HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE); // console handle (printing) HWND hwnd = GetConsoleWindow(); RECT window_rect = { .left = 0, .top = 0, .right = 50 + tile_size*game_width, .bottom = 100 + tile_size*game_height }; AdjustWindowRect(&window_rect, GetWindowLong(hwnd, GWL_STYLE), FALSE); MoveWindow(hwnd, 240, 100, window_rect.right, window_rect.bottom, TRUE); HDC console_hdc = GetDC(GetConsoleWindow()); // console device context (rendering) HDC drawing_hdc = CreateCompatibleDC(console_hdc); // memory device context (drawing) HDC blit_hdc = CreateCompatibleDC(console_hdc); // memory device context (blitting) HBITMAP blit_bitmap = CreateCompatibleBitmap(blit_hdc, tile_size*game_width, tile_size*game_height); SelectObject(blit_hdc, blit_bitmap); HGDIOBJ old_pen; // needed for some reason? maybe not idk // generate level for (size_t y = 0; y < game_height; ++y) { for (size_t x = 0; x < game_width; ++x) { level[y][x] = Tile::AIR; } } for (size_t x = 0; x < game_width; ++x) { level[0][x] = Tile::WALL; } for (size_t y = 0; y < game_height; ++y) { level[y][0] = Tile::WALL; level[y][game_width-1] = Tile::WALL; } for (int y = 1; y < (game_height*2)/3; ++y) { for (int x = 1; x < game_width-1; ++x) { if ((x+y-1) % 10 >= 5) level[y][x] = Tile::BLOCK; } } int score = 0; int blocks = 0; // count of total blocks for (int y = 0; y < game_height; y++) { for (int x = 0; x < game_width; x++) { if (level[y][x] == Tile::BLOCK) blocks++; } } // tile bitmaps. generated via code. HBITMAP tile_bitmaps[Tile::count]; for (tile_t i = 0; i < Tile::count; ++i) { tile_bitmaps[i] = CreateCompatibleBitmap(drawing_hdc, tile_size, tile_size); switch (i) { case Tile::WALL: { SelectObject(drawing_hdc, tile_bitmaps[i]); old_pen = SelectObject(drawing_hdc, GetStockObject(WHITE_PEN)); // draw boxes within boxes, starting with the outline // and ending with a tiny box in the center for (int box = 0; box < tile_size/2; box += 4) { MoveToEx(drawing_hdc, box, box, NULL); // top left LineTo(drawing_hdc, tile_size-1-box, box ); // top right LineTo(drawing_hdc, tile_size-1-box, tile_size-1-box); // bottom right LineTo(drawing_hdc, box, tile_size-1-box); // bottom left LineTo(drawing_hdc, box, box ); // top left again } SelectObject(drawing_hdc, old_pen); } break; case Tile::BLOCK: { SelectObject(drawing_hdc, tile_bitmaps[i]); old_pen = SelectObject(drawing_hdc, GetStockObject(WHITE_PEN)); // draw outline MoveToEx(drawing_hdc, 0, 0, NULL); // top left LineTo(drawing_hdc, tile_size-1, 0 ); // top right LineTo(drawing_hdc, tile_size-1, tile_size-1); // bottom right LineTo(drawing_hdc, 0, tile_size-1); // bottom left LineTo(drawing_hdc, 0, 0 ); // top left again SelectObject(drawing_hdc, old_pen); } break; default: break; } } // ball object fl2d ball_pos = {.x = game_width / 2.0, .y = game_height - 6.0}; fl2d ball_v_dir = {0.0, 1.0}; const float ball_velocity_default = 5.0; float ball_velocity = ball_velocity_default; float ball_radius = 1.0/2.0; // ball trail requires the last 3 seconds of ball positions. size_t ball_history_size = 1000; // 1000 frames should be enough for 3 seconds. fl2d ball_history_pos[ball_history_size]; // positions double ball_history_time[ball_history_size]; // "birth time" of a position for (int i = 0; i < ball_history_size; ++i) { ball_history_pos[i].x = ball_pos.x; ball_history_pos[i].y = ball_pos.y; ball_history_time[i] = 0.0; } // pre-calculate a set of points around the edge of the unit circle // this is used for creating the ball bitmap and performing ball collisions const size_t circle_precision = ball_radius * tile_size * 8; fl2d circle_points[circle_precision]; for (size_t i = 0; i < circle_precision; ++i) { const double angle = 2.0 * M_PI * double(i) / double(circle_precision); circle_points[i] = {.x = float(cos(angle)), .y = float(sin(angle))}; } // ball bitmap size_t ball_bitmap_size = size_t(tile_size * ball_radius + 0.5); HBITMAP ball_bitmap = CreateCompatibleBitmap(drawing_hdc, ball_bitmap_size, ball_bitmap_size); SelectObject(drawing_hdc, ball_bitmap); old_pen = SelectObject(drawing_hdc, GetStockObject(WHITE_PEN)); MoveToEx(drawing_hdc, int((circle_points[0].x + 1.0) * (ball_bitmap_size-1) / 2.0 + 0.5f), int((circle_points[0].y + 1.0) * (ball_bitmap_size-1) / 2.0 + 0.5f), NULL ); // backwards order since we start drawing from the 0th point and need to end up there again for (int i = circle_precision - 1; i >= 0; --i) { LineTo(drawing_hdc, int((circle_points[i].x + 1.0) * (ball_bitmap_size-1) / 2.0 + 0.5f), int((circle_points[i].y + 1.0) * (ball_bitmap_size-1) / 2.0 + 0.5f) ); } SelectObject(drawing_hdc, old_pen); // bat object fl2d bat_pos = {.x = game_width / 2.0, .y = game_height - 1.0}; float bat_width = 2.0; float bat_curve_height = 0.25; float bat_curve_radius = (bat_width*bat_width/4.0 + bat_curve_height*bat_curve_height) / (2.0 * bat_curve_height); // maths! double bat_curve_angle = asin((bat_width/2.0) / bat_curve_radius); printf("%f ", bat_curve_angle); bat_curve_angle = (bat_curve_angle + acos(1.0-(bat_curve_height/bat_curve_radius))) / 2.0; printf("%f ", bat_curve_angle); // bat_curve_center.x = bat_pos.x; // bat_curve_center.y = bat_pos.y - heigth + radius; float bat_thickness = 0.5; // bat bitmap int2d bat_bitmap_size = { .x = int(bat_width*tile_size + 0.5), .y = int((bat_thickness + bat_curve_height)*tile_size + 0.5) }; HBITMAP bat_bitmap = CreateCompatibleBitmap(drawing_hdc, bat_bitmap_size.x, bat_bitmap_size.y); SelectObject(drawing_hdc, bat_bitmap); old_pen = SelectObject(drawing_hdc, GetStockObject(WHITE_PEN)); // draw bottom of bat, starting at the top right corner, going clockwise MoveToEx(drawing_hdc, bat_bitmap_size.x-1, int(bat_curve_height*tile_size + 0.5), NULL); LineTo(drawing_hdc, bat_bitmap_size.x-1, bat_bitmap_size.y-1 ); // bottom right LineTo(drawing_hdc, 0, bat_bitmap_size.y-1 ); // bottom left LineTo(drawing_hdc, 0, int(bat_curve_height*tile_size + 0.5) ); // top left corner // now we draw the top curvey part for (int i = 0; i <= 500; ++i) { double frac = double(i)/double(500) - 0.5; LineTo(drawing_hdc, int((2 * bat_curve_radius * sin(frac*bat_curve_angle) + bat_width / 2.0) * (tile_size-1) + 0.5), int((2 * bat_curve_radius * (1.0-cos(frac*bat_curve_angle))) * (tile_size-1) + 0.5) ); } LineTo(drawing_hdc, bat_bitmap_size.x-1, int(bat_curve_height*tile_size + 0.5) ); SelectObject(drawing_hdc, old_pen); // start the simulation timeval global_time; timeval global_time_old; clock_gettime(0, &global_time); // gotta do this once before the loop double delta_time; while (true) { global_time_old = global_time; clock_gettime(0, &global_time); delta_time = (global_time.tv_sec - global_time_old.tv_sec); delta_time += (global_time.tv_usec - global_time_old.tv_usec) / 1000000.0; // start of loop ^ SetConsoleCursorPosition(console_handle, {0, 0}); printf("FPS=%6.2f ", 1.0/delta_time); // update ball trail for (int i = ball_history_size - 1; i > 0; --i) { ball_history_pos[i] = ball_history_pos[i-1]; ball_history_time[i] = ball_history_time[i-1]; } ball_history_pos[0] = ball_pos; ball_history_time[0] = global_time.tv_sec + global_time.tv_usec/1000000.0; short int text_was_printed = 0; // simulate ball int2d hits[circle_precision]; int hit_count = 0; int steps = int(32.0 * 120.0 * delta_time * (ball_velocity/ball_velocity_default)); if (float(steps) != 32.0 * 120.0 * delta_time * (ball_velocity/ball_velocity_default)) steps++; // round up printf("Steps=%3d ", steps); for (int st = 0; st < steps; st++) { // move ball ball_pos.x += (ball_v_dir.x * delta_time * ball_velocity) / steps; ball_pos.y += (ball_v_dir.y * delta_time * ball_velocity) / steps; fl2d s = {0.0, 0.0}; // s meaning sum, i guess // check 32 points around the edge of the ball for collisions with the level for (int i = 0; i < circle_precision; ++i) { fl2d p = { ball_pos.x + (circle_points[i].x/2 * ball_radius), ball_pos.y + (circle_points[i].y/2 * ball_radius) }; int2d int_p = {int(p.x), int(p.y)}; if (level[int_p.y][int_p.x] != AIR) { // hit level //cout << i << " "; // debug: which direction hit? text_was_printed = 1; // sum up hit directions s.x += circle_points[i].x; s.y += circle_points[i].y; // register position of hit object hits[hit_count] = int_p; hit_count++; } else if (p.x >= bat_pos.x - bat_width/2.0 && p.x <= bat_pos.x + bat_width/2.0 && p.y >= bat_pos.y - bat_curve_height && p.y <= bat_pos.y + bat_thickness && (p.y - (bat_pos.y - bat_curve_height + bat_curve_radius)) * (p.y - (bat_pos.y - bat_curve_height + bat_curve_radius)) + (p.x - bat_pos.x) * (p.x - bat_pos.x) <= bat_curve_radius * bat_curve_radius) { // hit bat //cout << "DONK!" << i << " "; text_was_printed = 1; s.x += circle_points[i].x; s.y += circle_points[i].y; } } // reflect ball velocity const float eps = 0.00001; if ((s.x >= eps || s.x <= -eps) || (s.y >= eps || s.y <= -eps)) { float s_length = sqrt((s.x * s.x) + (s.y * s.y)); s.x /= s_length; s.y /= s_length; float dot = (s.x * ball_v_dir.x) + (s.y * ball_v_dir.y); //cout << "dot=" << dot << " "; text_was_printed = 1; if (dot < 0.0) { // simulation error: ball got hit from "behind". // a simple solution is to negate the dot product. // this guarantees that the ball will move away from the collision. dot = -dot; } ball_v_dir.x -= 2 * dot * s.x; ball_v_dir.y -= 2 * dot * s.y; float ball_v_dir_length = sqrt(ball_v_dir.x * ball_v_dir.x + ball_v_dir.y * ball_v_dir.y); ball_v_dir.x /= ball_v_dir_length; ball_v_dir.y /= ball_v_dir_length; } // game lost if (ball_pos.y > bat_pos.y + bat_thickness) { printf("\tYou Lose!\t\n"); printf("\tPress the X key to restart.\t\n"); while (true) { if (GetKeyState('X') & 0xFFF0) return -1; BitBlt(console_hdc, 8, 32, tile_size*game_width, tile_size*game_height, blit_hdc, 0, 0, SRCCOPY); Sleep(40); } //goto restart_game; } } // destroy hit blocks for (int i = 0; i < hit_count; ++i) { if (level[hits[i].y][hits[i].x] == Tile::BLOCK) { //cout << "POW! "; text_was_printed = 1; level[hits[i].y][hits[i].x] = AIR; score++; ball_velocity *= 1.0065; } } // is game over? if (score == blocks) break; // bat movement if (GetKeyState('A') & 0xFFF0) { bat_pos.x -= delta_time * ball_velocity * 1.2; if (bat_pos.x < 1.0 + bat_width/2.0) bat_pos.x = 1.0 + bat_width/2.0; } if (GetKeyState('D') & 0xFFF0) { bat_pos.x += delta_time * ball_velocity * 1.2; if (bat_pos.x > game_width - (1.0 + bat_width/2.0)) bat_pos.x = game_width - (1.0 + bat_width/2.0); } printf("KeyState'A'=%1d ", (GetKeyState('A') & 0xFFF0) != 0); printf("KeyState'D'=%1d ", (GetKeyState('D') & 0xFFF0) != 0); // rendering // reset to black RECT rect = {.left = 0, .top = 0, .right = tile_size*(game_width+2), .bottom = tile_size*(game_height+2)}; FillRect(blit_hdc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH)); // render level for (size_t y = 0; y < game_height; ++y) { for (size_t x = 0; x < game_width; ++x) { SelectObject(drawing_hdc, tile_bitmaps[level[y][x]]); BitBlt(blit_hdc, x * tile_size, y * tile_size, tile_size, tile_size, drawing_hdc, 0, 0, SRCPAINT); } } // render ball trail old_pen = SelectObject(blit_hdc, GetStockObject(WHITE_PEN)); MoveToEx(blit_hdc, int(ball_history_pos[0].x * tile_size), int(ball_history_pos[0].y * tile_size), NULL); for (int i = 1; i < ball_history_size; ++i) { if (ball_history_time[0] - ball_history_time[i] > 3.0) // don't render trail points that are older than 3 seconds break; LineTo(blit_hdc, int(ball_history_pos[i].x * tile_size), int(ball_history_pos[i].y * tile_size)); } SelectObject(blit_hdc, old_pen); printf("BallTrailHistoryTime=%5.2fs ", ball_history_time[0] - ball_history_time[ball_history_size-1]); printf("AverageFPS=%6.2f ", (ball_history_size)/(ball_history_time[0] - ball_history_time[ball_history_size-1])); // render ball SelectObject(drawing_hdc, ball_bitmap); BitBlt(blit_hdc, int(ball_pos.x * tile_size) - ball_bitmap_size / 2, int(ball_pos.y * tile_size) - ball_bitmap_size / 2, ball_bitmap_size, ball_bitmap_size, drawing_hdc, 0, 0, SRCPAINT); // render bat SelectObject(drawing_hdc, bat_bitmap); BitBlt(blit_hdc, int((bat_pos.x - bat_width/2.0)*tile_size), int((bat_pos.y - bat_curve_height)*tile_size), bat_bitmap_size.x, bat_bitmap_size.y, drawing_hdc, 0, 0, SRCPAINT); // blit everythig to screen BitBlt(console_hdc, 8, 32, tile_size*game_width, tile_size*game_height, blit_hdc, 0, 0, SRCCOPY); //Sleep(1); } /* cout << "---- ---- ---- ---- ---- You Win! ---- ---- ---- ---- ----\n"; cout << "Press x to close. \n"; */ while (true) { if (GetKeyState('X') & 0xFFF0) break; BitBlt(console_hdc, 8, 32, tile_size*game_width, tile_size*game_height, blit_hdc, 0, 0, SRCCOPY); Sleep(40); } return 0; }