diff --git a/OneLoneCoder_Mazes.cpp b/OneLoneCoder_Mazes.cpp new file mode 100644 index 0000000..5d7bc7d --- /dev/null +++ b/OneLoneCoder_Mazes.cpp @@ -0,0 +1,220 @@ +/* +OneLoneCoder.com - Recursive Backtracker Maze Algorithm +"Get lost..." - @Javidx9 + +Disclaimer +~~~~~~~~~~ +I don't care what you use this for. It's intended to be educational, and perhaps +to the oddly minded - a little bit of fun. Please hack this, change it and use it +in any way you see fit. BUT, you acknowledge that I am not responsible for anything +bad that happens as a result of your actions. However, if good stuff happens, I +would appreciate a shout out, or at least give the blog some publicity for me. +Cheers! + +Background +~~~~~~~~~~ +I really like perfect algorithms. This one shows how to generate a maze that guarantees +all cells can reach all other cells, it just may take some time to get there. I introduce +stacks, and show how the algorithm generates the maze visually. + +Author +~~~~~~ +Twitter: @javidx9 +Blog: www.onelonecoder.com + +Video: +~~~~~~ +https://youtu.be/Y37-gB83HKE + +Last Updated: 10/07/2017 +*/ + +#include +#include +using namespace std; + +#include "olcConsoleGameEngine.h" + +class OneLoneCoder_Maze : public olcConsoleGameEngine +{ +public: + OneLoneCoder_Maze() + { + m_sAppName = L"MAZE"; + } + +private: + int m_nMazeWidth; + int m_nMazeHeight; + int *m_maze; + + + // Some bit fields for convenience + enum + { + CELL_PATH_N = 0x01, + CELL_PATH_E = 0x02, + CELL_PATH_S = 0x04, + CELL_PATH_W = 0x08, + CELL_VISITED = 0x10, + }; + + + // Algorithm variables + int m_nVisitedCells; + stack> m_stack; // (x, y) coordinate pairs + int m_nPathWidth; + + +protected: + // Called by olcConsoleGameEngine + virtual bool OnUserCreate() + { + // Maze parameters + m_nMazeWidth = 40; + m_nMazeHeight = 25; + m_maze = new int[m_nMazeWidth * m_nMazeHeight]; + memset(m_maze, 0x00, m_nMazeWidth * m_nMazeHeight * sizeof(int)); + m_nPathWidth = 3; + + // Choose a starting cell + int x = rand() % m_nMazeWidth; + int y = rand() % m_nMazeHeight; + m_stack.push(make_pair(x, y)); + m_maze[y * m_nMazeWidth + x] = CELL_VISITED; + m_nVisitedCells = 1; + + return true; + } + + // Called by olcConsoleGameEngine + virtual bool OnUserUpdate(float fElapsedTime) + { + // Slow down for animation + this_thread::sleep_for(10ms); + + // Little lambda function to calculate index in a readable way + auto offset = [&](int x, int y) + { + return (m_stack.top().second + y) * m_nMazeWidth + (m_stack.top().first + x); + }; + + // Do Maze Algorithm + if (m_nVisitedCells < m_nMazeWidth * m_nMazeHeight) + { + // Create a set of unvisted neighbours + vector neighbours; + + // North neighbour + if (m_stack.top().second > 0 && (m_maze[offset(0, -1)] & CELL_VISITED) == 0) + neighbours.push_back(0); + // East neighbour + if (m_stack.top().first < m_nMazeWidth - 1 && (m_maze[offset(1, 0)] & CELL_VISITED) == 0) + neighbours.push_back(1); + // South neighbour + if (m_stack.top().second < m_nMazeHeight - 1 && (m_maze[offset(0, 1)] & CELL_VISITED) == 0) + neighbours.push_back(2); + // West neighbour + if (m_stack.top().first > 0 && (m_maze[offset(-1, 0)] & CELL_VISITED) == 0) + neighbours.push_back(3); + + // Are there any neighbours available? + if (!neighbours.empty()) + { + // Choose one available neighbour at random + int next_cell_dir = neighbours[rand() % neighbours.size()]; + + // Create a path between the neighbour and the current cell + switch (next_cell_dir) + { + case 0: // North + m_maze[offset(0, -1)] |= CELL_VISITED | CELL_PATH_S; + m_maze[offset(0, 0)] |= CELL_PATH_N; + m_stack.push(make_pair((m_stack.top().first + 0), (m_stack.top().second - 1))); + break; + + case 1: // East + m_maze[offset(+1, 0)] |= CELL_VISITED | CELL_PATH_W; + m_maze[offset( 0, 0)] |= CELL_PATH_E; + m_stack.push(make_pair((m_stack.top().first + 1), (m_stack.top().second + 0))); + break; + + case 2: // South + m_maze[offset(0, +1)] |= CELL_VISITED | CELL_PATH_N; + m_maze[offset(0, 0)] |= CELL_PATH_S; + m_stack.push(make_pair((m_stack.top().first + 0), (m_stack.top().second + 1))); + break; + + case 3: // West + m_maze[offset(-1, 0)] |= CELL_VISITED | CELL_PATH_E; + m_maze[offset( 0, 0)] |= CELL_PATH_W; + m_stack.push(make_pair((m_stack.top().first - 1), (m_stack.top().second + 0))); + break; + + } + + m_nVisitedCells++; + } + else + { + // No available neighbours so backtrack! + m_stack.pop(); + } + } + + + // === DRAWING STUFF === + + // Clear Screen by drawing 'spaces' everywhere + Fill(0, 0, ScreenWidth(), ScreenHeight(), L' '); + + // Draw Maze + for (int x = 0; x < m_nMazeWidth; x++) + { + for (int y = 0; y < m_nMazeHeight; y++) + { + // Each cell is inflated by m_nPathWidth, so fill it in + for (int py = 0; py < m_nPathWidth; py++) + for (int px = 0; px < m_nPathWidth; px++) + { + if (m_maze[y * m_nMazeWidth + x] & CELL_VISITED) + Draw(x * (m_nPathWidth + 1) + px, y * (m_nPathWidth + 1) + py, PIXEL_SOLID, FG_WHITE); // Draw Cell + else + Draw(x * (m_nPathWidth + 1) + px, y * (m_nPathWidth + 1) + py, PIXEL_SOLID, FG_BLUE); // Draw Cell + } + + // Draw passageways between cells + for (int p = 0; p < m_nPathWidth; p++) + { + if (m_maze[y * m_nMazeWidth + x] & CELL_PATH_S) + Draw(x * (m_nPathWidth + 1) + p, y * (m_nPathWidth + 1) + m_nPathWidth); // Draw South Passage + + if (m_maze[y * m_nMazeWidth + x] & CELL_PATH_E) + Draw(x * (m_nPathWidth + 1) + m_nPathWidth, y * (m_nPathWidth + 1) + p); // Draw East Passage + } + } + } + + // Draw Unit - the top of the stack + for (int py = 0; py < m_nPathWidth; py++) + for (int px = 0; px < m_nPathWidth; px++) + Draw(m_stack.top().first * (m_nPathWidth + 1) + px, m_stack.top().second * (m_nPathWidth + 1) + py, 0x2588, FG_GREEN); // Draw Cell + + + return true; + } +}; + + +int main() +{ + // Seed random number generator + srand(clock()); + + // Use olcConsoleGameEngine derived app + OneLoneCoder_Maze game; + game.ConstructConsole(160, 100, 8, 8); + game.Start(); + + return 0; +} \ No newline at end of file diff --git a/olcConsoleGameEngine.h b/olcConsoleGameEngine.h new file mode 100644 index 0000000..a7b1ea1 --- /dev/null +++ b/olcConsoleGameEngine.h @@ -0,0 +1,421 @@ +/* +OneLoneCoder.com - Command Line Game Engine +"Who needs a frame buffer?" - @Javidx9 + +Disclaimer +~~~~~~~~~~ +I don't care what you use this for. It's intended to be educational, and perhaps +to the oddly minded - a little bit of fun. Please hack this, change it and use it +in any way you see fit. BUT, you acknowledge that I am not responsible for anything +bad that happens as a result of your actions. However, if good stuff happens, I +would appreciate a shout out, or at least give the blog some publicity for me. +Cheers! + +Background +~~~~~~~~~~ +If you've seen any of my videos - I like to do things using the windows console. It's quick +and easy, and allows you to focus on just the code that matters - ideal when you're +experimenting. Thing is, I have to keep doing the same initialisation and display code +each time, so this class wraps that up. + +Author +~~~~~~ +Twitter: @javidx9 +Blog: www.onelonecoder.com + +Video: +~~~~~~ +https://youtu.be/cWc0hgYwZyc + +Last Updated: 10/07/2017 + +Usage: +~~~~~~ +This class is abstract, so you must inherit from it. Override the OnUserCreate() function +with all the stuff you need for your application (for thready reasons it's best to do +this in this function and not your class constructor). Override the OnUserUpdate(float fElapsedTime) +function with the good stuff, it gives you the elapsed time since the last call so you +can modify your stuff dynamically. Both functions should return true, unless you need +the application to close. + + int main() + { + // Use olcConsoleGameEngine derived app + OneLoneCoder_Example game; + + // Create a console with resolution 160x100 characters + // Each character occupies 8x8 pixels + game.ConstructConsole(160, 100, 8, 8); + + // Start the engine! + game.Start(); + + return 0; + } + +Input is also handled for you - interrogate the m_keys[] array with the virtual +keycode you want to know about. bPressed is set for the frame the key is pressed down +in, bHeld is set if the key is held down, bReleased is set for the frame the key +is released in. + +The draw routines treat characters like pixels. By default they are set to white solid +blocks - but you can draw any unicode character, using any of the colours listed below. + +There may be bugs! + +See my other videos for examples! + +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#include + +enum COLOUR +{ + FG_BLACK = 0x0000, + FG_DARK_BLUE = 0x0001, + FG_DARK_GREEN = 0x0002, + FG_DARK_CYAN = 0x0003, + FG_DARK_RED = 0x0004, + FG_DARK_MAGENTA = 0x0005, + FG_DARK_YELLOW = 0x0006, + FG_GREY = 0x0007, + FG_BLUE = 0x0009, + FG_GREEN = 0x000A, + FG_CYAN = 0x000B, + FG_RED = 0x000C, + FG_MAGENTA = 0x000D, + FG_YELLOW = 0x000E, + FG_WHITE = 0x000F, + BG_BLACK = 0x0000, + BG_DARK_BLUE = 0x0010, + BG_DARK_GREEN = 0x0020, + BG_DARK_CYAN = 0x0030, + BG_DARK_RED = 0x0040, + BG_DARK_MAGENTA = 0x0050, + BG_DARK_YELLOW = 0x0060, + BG_GREY = 0x0070, + BG_BLUE = 0x0090, + BG_GREEN = 0x00A0, + BG_CYAN = 0x00B0, + BG_RED = 0x00C0, + BG_MAGENTA = 0x00D0, + BG_YELLOW = 0x00E0, + BG_WHITE = 0x00F0, +}; + +enum PIXEL_TYPE +{ + PIXEL_SOLID = 0x2588, + PIXEL_HALF = 0x2592, +}; + + +class olcConsoleGameEngine +{ +public: + olcConsoleGameEngine() + { + m_nScreenWidth = 80; + m_nScreenHeight = 30; + + m_hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + m_keyNewState = new short[256]; + m_keyOldState = new short[256]; + memset(m_keyNewState, 0, 256 * sizeof(short)); + memset(m_keyOldState, 0, 256 * sizeof(short)); + + memset(m_keys, 0, 256 * sizeof(sKeyState)); + + + m_sAppName = L"Default"; + + } + + int ConstructConsole(int width, int height, int fontw = 12, int fonth = 12) + { + m_nScreenWidth = width; + m_nScreenHeight = height; + + CONSOLE_FONT_INFOEX cfi; + cfi.cbSize = sizeof(cfi); + cfi.nFont = 0; + cfi.dwFontSize.X = fontw; + cfi.dwFontSize.Y = fonth; + cfi.FontFamily = FF_DONTCARE; + cfi.FontWeight = FW_NORMAL; + wcscpy_s(cfi.FaceName, L"Consolas"); + + if (!SetCurrentConsoleFontEx(m_hConsole, false, &cfi)) + return Error(L"SetCurrentConsoleFontEx"); + + COORD coordLargest = GetLargestConsoleWindowSize(m_hConsole); + if (m_nScreenHeight > coordLargest.Y) + return Error(L"Game Height Too Big"); + if (m_nScreenWidth > coordLargest.X) + return Error(L"Game Width Too Big"); + + COORD buffer = { (short)m_nScreenWidth, (short)m_nScreenHeight }; + if (!SetConsoleScreenBufferSize(m_hConsole, buffer)) + Error(L"SetConsoleScreenBufferSize"); + + m_rectWindow = { 0, 0, (short)m_nScreenWidth - 1, (short)m_nScreenHeight - 1 }; + if (!SetConsoleWindowInfo(m_hConsole, TRUE, &m_rectWindow)) + Error(L"SetConsoleWindowInfo"); + + m_bufScreen = new CHAR_INFO[m_nScreenWidth*m_nScreenHeight]; + + return 1; + } + + void Draw(int x, int y, wchar_t c = 0x2588, short col = 0x000F) + { + if (x >= 0 && x < m_nScreenWidth && y >= 0 && y < m_nScreenHeight) + { + m_bufScreen[y * m_nScreenWidth + x].Char.UnicodeChar = c; + m_bufScreen[y * m_nScreenWidth + x].Attributes = col; + } + } + + void Fill(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F) + { + Clip(x1, y1); + Clip(x2, y2); + for (int x = x1; x < x2; x++) + for (int y = y1; y < y2; y++) + Draw(x, y, c, col); + } + + void DrawString(int x, int y, wstring c, short col = 0x000F) + { + for (size_t i = 0; i < c.size(); i++) + { + m_bufScreen[y * m_nScreenWidth + x + i].Char.UnicodeChar = c[i]; + m_bufScreen[y * m_nScreenWidth + x + i].Attributes = col; + } + } + + void Clip(int &x, int &y) + { + if (x < 0) x = 0; + if (x >= m_nScreenWidth) x = m_nScreenWidth; + if (y < 0) y = 0; + if (y >= m_nScreenHeight) y = m_nScreenHeight; + } + + void DrawLine(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; + dy = y2 - y1; + dx1 = abs(dx); + dy1 = abs(dy); + px = 2 * dy1 - dx1; + py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; + y = y1; + xe = x2; + } + else + { + x = x2; + y = y2; + xe = x1; + } + Draw(x, y, c, col); + for (i = 0; x0 && dy>0)) + y = y + 1; + else + y = y - 1; + px = px + 2 * (dy1 - dx1); + } + Draw(x, y, c, col); + } + } + else + { + if (dy >= 0) + { + x = x1; + y = y1; + ye = y2; + } + else + { + x = x2; + y = y2; + ye = y1; + } + Draw(x, y, c, col); + for (i = 0; y0 && dy>0)) + x = x + 1; + else + x = x - 1; + py = py + 2 * (dx1 - dy1); + } + Draw(x, y, c, col); + } + } + } + + + + ~olcConsoleGameEngine() + { + SetConsoleActiveScreenBuffer(m_hOriginalConsole); + delete[] m_bufScreen; + } + +public: + void Start() + { + m_bAtomActive = true; + + // Star the thread + thread t = thread(&olcConsoleGameEngine::GameThread, this); + + // Wait for thread to be exited + m_cvGameFinished.wait(unique_lock(m_muxGame)); + + // Tidy up + t.join(); + } + + int ScreenWidth() + { + return m_nScreenWidth; + } + + int ScreenHeight() + { + return m_nScreenHeight; + } + +private: + void GameThread() + { + // Create user resources as part of this thread + if (!OnUserCreate()) + return; + + auto tp1 = chrono::system_clock::now(); + auto tp2 = chrono::system_clock::now(); + + // Run as fast as possible + while (m_bAtomActive) + { + // Handle Timing + tp2 = chrono::system_clock::now(); + chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + float fElapsedTime = elapsedTime.count(); + + // Handle Input + for (int i = 0; i < 256; i++) + { + m_keyNewState[i] = GetAsyncKeyState(i); + + m_keys[i].bPressed = false; + m_keys[i].bReleased = false; + + if (m_keyNewState[i] != m_keyOldState[i]) + { + if (m_keyNewState[i] & 0x8000) + { + m_keys[i].bPressed = !m_keys[i].bHeld; + m_keys[i].bHeld = true; + } + else + { + m_keys[i].bReleased = true; + m_keys[i].bHeld = false; + } + } + + m_keyOldState[i] = m_keyNewState[i]; + } + + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + m_bAtomActive = false; + + // Update Title & Present Screen Buffer + wchar_t s[128]; + swprintf_s(s, 128, L"OneLoneCoder.com - Console Game Engine - %s - FPS: %3.2f ", m_sAppName.c_str(), 1.0f / fElapsedTime); + SetConsoleTitle(s); + WriteConsoleOutput(m_hConsole, m_bufScreen, { (short)m_nScreenWidth, (short)m_nScreenHeight }, { 0,0 }, &m_rectWindow); + } + + m_cvGameFinished.notify_one(); + } + +public: + // User MUST OVERRIDE THESE!! + virtual bool OnUserCreate() = 0; + virtual bool OnUserUpdate(float fElapsedTime) = 0; + + +protected: + int m_nScreenWidth; + int m_nScreenHeight; + CHAR_INFO *m_bufScreen; + atomic m_bAtomActive; + condition_variable m_cvGameFinished; + mutex m_muxGame; + wstring m_sAppName; + + struct sKeyState + { + bool bPressed; + bool bReleased; + bool bHeld; + } m_keys[256]; + + +protected: + int Error(wchar_t *msg) + { + wchar_t buf[256]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256, NULL); + SetConsoleActiveScreenBuffer(m_hOriginalConsole); + wprintf(L"ERROR: %s\n\t%s\n", msg, buf); + return -1; + } + +private: + HANDLE m_hOriginalConsole; + CONSOLE_SCREEN_BUFFER_INFO m_OriginalConsoleInfo; + HANDLE m_hConsole; + SMALL_RECT m_rectWindow; + short *m_keyOldState; + short *m_keyNewState; +}; \ No newline at end of file