parent
3c2d01859a
commit
b586979c23
@ -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 <iostream> |
||||||
|
#include <stack> |
||||||
|
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<pair<int, int>> 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<int> 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; |
||||||
|
} |
@ -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 <iostream> |
||||||
|
#include <chrono> |
||||||
|
#include <vector> |
||||||
|
#include <list> |
||||||
|
#include <thread> |
||||||
|
#include <atomic> |
||||||
|
#include <condition_variable> |
||||||
|
using namespace std; |
||||||
|
|
||||||
|
#include <windows.h> |
||||||
|
|
||||||
|
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; x<xe; i++) |
||||||
|
{ |
||||||
|
x = x + 1; |
||||||
|
if (px<0) |
||||||
|
px = px + 2 * dy1; |
||||||
|
else |
||||||
|
{ |
||||||
|
if ((dx<0 && dy<0) || (dx>0 && 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; y<ye; i++) |
||||||
|
{ |
||||||
|
y = y + 1; |
||||||
|
if (py <= 0) |
||||||
|
py = py + 2 * dx1; |
||||||
|
else |
||||||
|
{ |
||||||
|
if ((dx<0 && dy<0) || (dx>0 && 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<mutex>(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<float> 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<bool> 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; |
||||||
|
}; |
Loading…
Reference in new issue