The official distribution of olcConsoleGameEngine, a tool used in javidx9's YouTube videos and projects
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
videos/olcConsoleGameEngine.h

1072 lines
28 KiB

/*
OneLoneCoder.com - Command Line Game Engine
"Who needs a frame buffer?" - @Javidx9
License
~~~~~~~
One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; See license for details.
Original works located at:
https://www.github.com/onelonecoder
https://www.onelonecoder.com
https://www.youtube.com/javidx9
GNU GPLv3
https://github.com/OneLoneCoder/videos/blob/master/LICENSE
From Javidx9 :)
~~~~~~~~~~~~~~~
Hello! Ultimately 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. You acknowledge
that I am not responsible for anything bad that happens as a result of
your actions. However this code is protected by GNU GPLv3, see the license in the
github repo. This means you must attribute me if you use it. You can view this
license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE
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 http://twitter.com/javidx9
Blog: http://www.onelonecoder.com
YouTube: http://www.youtube.com/javidx9
Videos:
~~~~~~
Original: https://youtu.be/cWc0hgYwZyc
Added mouse support: https://youtu.be/tdqc9hZhHxM
Beginners Guide: https://youtu.be/u5BhrA8ED0o
Shout Outs!
~~~~~~~~~~~
Thanks to cool people who helped with testing, bug-finding and fixing!
wowLinh, JavaJack59, idkwid, kingtatgi, Return Null, CPP Guy, MaGetzUb
Last Updated: 27/05/2018
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 same applies to mouse! m_mousePosX and Y can be used to get
the current cursor position, and m_mouse[1..5] returns the mouse buttons.
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!
http://www.youtube.com/javidx9
Lots of programs to try:
http://www.github.com/OneLoneCoder/videos
Chat on the Discord server:
https://discord.gg/WhwHUMV
Be bored by Twitch:
http://www.twitch.tv/javidx9
*/
#pragma once
#ifndef UNICODE
#error Please enable UNICODE for your compiler! VS: Project Properties -> General -> \
Character Set -> Use Unicode. Thanks! For now, I'll try enabling it for you - Javidx9
#define UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include <iostream>
#include <chrono>
#include <vector>
#include <list>
#include <thread>
#include <atomic>
#include <condition_variable>
using namespace std;
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, // Thanks MS :-/
FG_DARK_GREY = 0x0008,
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_DARK_GREY = 0x0080,
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_THREEQUARTERS = 0x2593,
PIXEL_HALF = 0x2592,
PIXEL_QUARTER = 0x2591,
};
class olcSprite
{
public:
olcSprite()
{
}
olcSprite(int w, int h)
{
Create(w, h);
}
olcSprite(wstring sFile)
{
if (!Load(sFile))
Create(8, 8);
}
int nWidth = 0;
int nHeight = 0;
private:
wchar_t *m_Glyphs = nullptr;
short *m_Colours = nullptr;
void Create(int w, int h)
{
nWidth = w;
nHeight = h;
m_Glyphs = new wchar_t[w*h];
m_Colours = new short[w*h];
for (int i = 0; i < w*h; i++)
{
m_Glyphs[i] = L' ';
m_Colours[i] = FG_BLACK;
}
}
public:
void SetGlyph(int x, int y, wchar_t c)
{
if (x <0 || x >= nWidth || y < 0 || y >= nHeight)
return;
else
m_Glyphs[y * nWidth + x] = c;
}
void SetColour(int x, int y, short c)
{
if (x <0 || x >= nWidth || y < 0 || y >= nHeight)
return;
else
m_Colours[y * nWidth + x] = c;
}
wchar_t GetGlyph(int x, int y)
{
if (x <0 || x >= nWidth || y < 0 || y >= nHeight)
return L' ';
else
return m_Glyphs[y * nWidth + x];
}
short GetColour(int x, int y)
{
if (x <0 || x >= nWidth || y < 0 || y >= nHeight)
return FG_BLACK;
else
return m_Colours[y * nWidth + x];
}
wchar_t SampleGlyph(float x, float y)
{
int sx = (int)(x * (float)nWidth);
int sy = (int)(y * (float)nHeight-1.0f);
if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight)
return L' ';
else
return m_Glyphs[sy * nWidth + sx];
}
short SampleColour(float x, float y)
{
int sx = (int)(x * (float)nWidth);
int sy = (int)(y * (float)nHeight-1.0f);
if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight)
return FG_BLACK;
else
return m_Colours[sy * nWidth + sx];
}
bool Save(wstring sFile)
{
FILE *f = nullptr;
_wfopen_s(&f, sFile.c_str(), L"wb");
if (f == nullptr)
return false;
fwrite(&nWidth, sizeof(int), 1, f);
fwrite(&nHeight, sizeof(int), 1, f);
fwrite(m_Colours, sizeof(short), nWidth * nHeight, f);
fwrite(m_Glyphs, sizeof(wchar_t), nWidth * nHeight, f);
fclose(f);
return true;
}
bool Load(wstring sFile)
{
delete[] m_Glyphs;
delete[] m_Colours;
nWidth = 0;
nHeight = 0;
FILE *f = nullptr;
_wfopen_s(&f, sFile.c_str(), L"rb");
if (f == nullptr)
return false;
fread(&nWidth, sizeof(int), 1, f);
fread(&nHeight, sizeof(int), 1, f);
Create(nWidth, nHeight);
fread(m_Colours, sizeof(short), nWidth * nHeight, f);
fread(m_Glyphs, sizeof(wchar_t), nWidth * nHeight, f);
fclose(f);
return true;
}
};
class olcConsoleGameEngine
{
public:
olcConsoleGameEngine()
{
m_nScreenWidth = 80;
m_nScreenHeight = 30;
m_hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
m_hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
memset(m_keyNewState, 0, 256 * sizeof(short));
memset(m_keyOldState, 0, 256 * sizeof(short));
memset(m_keys, 0, 256 * sizeof(sKeyState));
m_mousePosX = 0;
m_mousePosY = 0;
m_sAppName = L"Default";
}
int ConstructConsole(int width, int height, int fontw, int fonth)
{
if (m_hConsole == INVALID_HANDLE_VALUE)
return Error(L"Bad Handle");
m_nScreenWidth = width;
m_nScreenHeight = height;
// Update 13/09/2017 - It seems that the console behaves differently on some systems
// and I'm unsure why this is. It could be to do with windows default settings, or
// screen resolutions, or system languages. Unfortunately, MSDN does not offer much
// by way of useful information, and so the resulting sequence is the reult of experiment
// that seems to work in multiple cases.
//
// The problem seems to be that the SetConsoleXXX functions are somewhat circular and
// fail depending on the state of the current console properties, i.e. you can't set
// the buffer size until you set the screen size, but you can't change the screen size
// until the buffer size is correct. This coupled with a precise ordering of calls
// makes this procedure seem a little mystical :-P. Thanks to wowLinh for helping - Jx9
// Change console visual size to a minimum so ScreenBuffer can shrink
// below the actual visual size
m_rectWindow = { 0, 0, 1, 1 };
SetConsoleWindowInfo(m_hConsole, TRUE, &m_rectWindow);
// Set the size of the screen buffer
COORD coord = { (short)m_nScreenWidth, (short)m_nScreenHeight };
if (!SetConsoleScreenBufferSize(m_hConsole, coord))
Error(L"SetConsoleScreenBufferSize");
// Assign screen buffer to the console
if (!SetConsoleActiveScreenBuffer(m_hConsole))
return Error(L"SetConsoleActiveScreenBuffer");
// Set the font size now that the screen buffer has been assigned to the console
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;
/* DWORD version = GetVersion();
DWORD major = (DWORD)(LOBYTE(LOWORD(version)));
DWORD minor = (DWORD)(HIBYTE(LOWORD(version)));*/
//if ((major > 6) || ((major == 6) && (minor >= 2) && (minor < 4)))
// wcscpy_s(cfi.FaceName, L"Raster"); // Windows 8 :(
//else
wcscpy_s(cfi.FaceName, L"Lucida Console"); // Everything else :P
//wcscpy_s(cfi.FaceName, L"Liberation Mono");
//wcscpy_s(cfi.FaceName, L"Consolas");
if (!SetCurrentConsoleFontEx(m_hConsole, false, &cfi))
return Error(L"SetCurrentConsoleFontEx");
// Get screen buffer info and check the maximum allowed window size. Return
// error if exceeded, so user knows their dimensions/fontsize are too large
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (!GetConsoleScreenBufferInfo(m_hConsole, &csbi))
return Error(L"GetConsoleScreenBufferInfo");
if (m_nScreenHeight > csbi.dwMaximumWindowSize.Y)
return Error(L"Screen Height / Font Height Too Big");
if (m_nScreenWidth > csbi.dwMaximumWindowSize.X)
return Error(L"Screen Width / Font Width Too Big");
// Set Physical Console Window Size
m_rectWindow = { 0, 0, (short)m_nScreenWidth - 1, (short)m_nScreenHeight - 1 };
if (!SetConsoleWindowInfo(m_hConsole, TRUE, &m_rectWindow))
return Error(L"SetConsoleWindowInfo");
// Set flags to allow mouse input
if (!SetConsoleMode(m_hConsoleIn, ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT))
return Error(L"SetConsoleMode");
// Allocate memory for screen buffer
m_bufScreen = new CHAR_INFO[m_nScreenWidth*m_nScreenHeight];
memset(m_bufScreen, 0, sizeof(CHAR_INFO) * m_nScreenWidth * m_nScreenHeight);
SetConsoleCtrlHandler((PHANDLER_ROUTINE)CloseHandler, TRUE);
return 1;
}
virtual 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 DrawStringAlpha(int x, int y, wstring c, short col = 0x000F)
{
for (size_t i = 0; i < c.size(); i++)
{
if (c[i] != L' ')
{
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);
}
}
}
void DrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, wchar_t c = 0x2588, short col = 0x000F)
{
DrawLine(x1, y1, x2, y2, c, col);
DrawLine(x2, y2, x3, y3, c, col);
DrawLine(x3, y3, x1, y1, c, col);
}
// https://www.avrfreaks.net/sites/default/files/triangles.c
void FillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, wchar_t c = 0x2588, short col = 0x000F)
{
auto SWAP = [](int &x, int &y) { int t = x; x = y; y = t; };
auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, c, col); };
int t1x, t2x, y, minx, maxx, t1xp, t2xp;
bool changed1 = false;
bool changed2 = false;
int signx1, signx2, dx1, dy1, dx2, dy2;
int e1, e2;
// Sort vertices
if (y1>y2) { SWAP(y1, y2); SWAP(x1, x2); }
if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); }
if (y2>y3) { SWAP(y2, y3); SWAP(x2, x3); }
t1x = t2x = x1; y = y1; // Starting points
dx1 = (int)(x2 - x1); if (dx1<0) { dx1 = -dx1; signx1 = -1; }
else signx1 = 1;
dy1 = (int)(y2 - y1);
dx2 = (int)(x3 - x1); if (dx2<0) { dx2 = -dx2; signx2 = -1; }
else signx2 = 1;
dy2 = (int)(y3 - y1);
if (dy1 > dx1) { // swap values
SWAP(dx1, dy1);
changed1 = true;
}
if (dy2 > dx2) { // swap values
SWAP(dy2, dx2);
changed2 = true;
}
e2 = (int)(dx2 >> 1);
// Flat top, just process the second half
if (y1 == y2) goto next;
e1 = (int)(dx1 >> 1);
for (int i = 0; i < dx1;) {
t1xp = 0; t2xp = 0;
if (t1x<t2x) { minx = t1x; maxx = t2x; }
else { minx = t2x; maxx = t1x; }
// process first line until y value is about to change
while (i<dx1) {
i++;
e1 += dy1;
while (e1 >= dx1) {
e1 -= dx1;
if (changed1) t1xp = signx1;//t1x += signx1;
else goto next1;
}
if (changed1) break;
else t1x += signx1;
}
// Move line
next1:
// process second line until y value is about to change
while (1) {
e2 += dy2;
while (e2 >= dx2) {
e2 -= dx2;
if (changed2) t2xp = signx2;//t2x += signx2;
else goto next2;
}
if (changed2) break;
else t2x += signx2;
}
next2:
if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x;
if (maxx<t1x) maxx = t1x; if (maxx<t2x) maxx = t2x;
drawline(minx, maxx, y); // Draw line from min to max points found on the y
// Now increase y
if (!changed1) t1x += signx1;
t1x += t1xp;
if (!changed2) t2x += signx2;
t2x += t2xp;
y += 1;
if (y == y2) break;
}
next:
// Second half
dx1 = (int)(x3 - x2); if (dx1<0) { dx1 = -dx1; signx1 = -1; }
else signx1 = 1;
dy1 = (int)(y3 - y2);
t1x = x2;
if (dy1 > dx1) { // swap values
SWAP(dy1, dx1);
changed1 = true;
}
else changed1 = false;
e1 = (int)(dx1 >> 1);
for (int i = 0; i <= dx1; i++) {
t1xp = 0; t2xp = 0;
if (t1x<t2x) { minx = t1x; maxx = t2x; }
else { minx = t2x; maxx = t1x; }
// process first line until y value is about to change
while (i<dx1) {
e1 += dy1;
while (e1 >= dx1) {
e1 -= dx1;
if (changed1) { t1xp = signx1; break; }//t1x += signx1;
else goto next3;
}
if (changed1) break;
else t1x += signx1;
if (i<dx1) i++;
}
next3:
// process second line until y value is about to change
while (t2x != x3) {
e2 += dy2;
while (e2 >= dx2) {
e2 -= dx2;
if (changed2) t2xp = signx2;
else goto next4;
}
if (changed2) break;
else t2x += signx2;
}
next4:
if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x;
if (maxx<t1x) maxx = t1x; if (maxx<t2x) maxx = t2x;
drawline(minx, maxx, y);
if (!changed1) t1x += signx1;
t1x += t1xp;
if (!changed2) t2x += signx2;
t2x += t2xp;
y += 1;
if (y>y3) return;
}
}
// Non-bresenham method
/*void FillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, wchar_t c = 0x2588, short col = 0x000F)
{
if ((x1 == x2 && x1 == x3) || (y1 == y2) && (y1 == y3))
return;
int tmp;
if (y2 < y1){ tmp = y1; y1 = y2; y2 = tmp; tmp = x1; x1 = x2; x2 = tmp; }
if (y3 < y1){ tmp = y1; y1 = y3; y3 = tmp; tmp = x1; x1 = x3; x3 = tmp; }
if (y3 < y2){ tmp = y2; y2 = y3; y3 = tmp; tmp = x2; x2 = x3; x3 = tmp; }
int dy1 = y2 - y1, dx1 = x2 - x1;
int dy2 = y3 - y1, dx2 = x3 - x1;
if (dy1)
{
for (int i = y1; i < y2; i++)
{
int ax = x1 + ((i - y1)*dx1) / dy1;
int bx = x1 + ((i - y1)*dx2) / dy2;
tmp = ax;
if (ax > bx) ax = bx, bx = tmp;
for (int j = ax; j < bx; j++) Draw(j, i, c, col);
}
}
dy1 = y3 - y2; dx1 = x3 - x2;
if (dy1)
{
for (int i = y2; i < y3; i++)
{
int ax = x2 + ((i - y2)*dx1) / dy1;
int bx = x1 + ((i - y1)*dx2) / dy2;
tmp = ax;
if (ax > bx) ax = bx, bx = tmp;
for (int j = ax; j < bx; j++) Draw(j, i, c, col);
}
}
}*/
void DrawCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F)
{
int x = 0;
int y = r;
int p = 3 - 2 * r;
if (!r) return;
while (y >= x) // only formulate 1/8 of circle
{
Draw(xc - x, yc - y, c, col);//upper left left
Draw(xc - y, yc - x, c, col);//upper upper left
Draw(xc + y, yc - x, c, col);//upper upper right
Draw(xc + x, yc - y, c, col);//upper right right
Draw(xc - x, yc + y, c, col);//lower left left
Draw(xc - y, yc + x, c, col);//lower lower left
Draw(xc + y, yc + x, c, col);//lower lower right
Draw(xc + x, yc + y, c, col);//lower right right
if (p < 0) p += 4 * x++ + 6;
else p += 4 * (x++ - y--) + 10;
}
}
void FillCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F)
{
// Taken from wikipedia
int x = 0;
int y = r;
int p = 3 - 2 * r;
if (!r) return;
auto drawline = [&](int sx, int ex, int ny)
{
for (int i = sx; i <= ex; i++)
Draw(i, ny, c, col);
};
while (y >= x)
{
// Modified to draw scan-lines instead of edges
drawline(xc - x, xc + x, yc - y);
drawline(xc - y, xc + y, yc - x);
drawline(xc - x, xc + x, yc + y);
drawline(xc - y, xc + y, yc + x);
if (p < 0) p += 4 * x++ + 6;
else p += 4 * (x++ - y--) + 10;
}
};
void DrawSprite(int x, int y, olcSprite *sprite)
{
if (sprite == nullptr)
return;
for (int i = 0; i < sprite->nWidth; i++)
{
for (int j = 0; j < sprite->nHeight; j++)
{
if (sprite->GetGlyph(i, j) != L' ')
Draw(x + i, y + j, sprite->GetGlyph(i, j), sprite->GetColour(i, j));
}
}
}
void DrawPartialSprite(int x, int y, olcSprite *sprite, int ox, int oy, int w, int h)
{
if (sprite == nullptr)
return;
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
if (sprite->GetGlyph(i+ox, j+oy) != L' ')
Draw(x + i, y + j, sprite->GetGlyph(i+ox, j+oy), sprite->GetColour(i+ox, j+oy));
}
}
}
void DrawWireFrameModel(const vector<pair<float, float>> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE)
{
// pair.first = x coordinate
// pair.second = y coordinate
// Create translated model vector of coordinate pairs
vector<pair<float, float>> vecTransformedCoordinates;
int verts = vecModelCoordinates.size();
vecTransformedCoordinates.resize(verts);
// Rotate
for (int i = 0; i < verts; i++)
{
vecTransformedCoordinates[i].first = vecModelCoordinates[i].first * cosf(r) - vecModelCoordinates[i].second * sinf(r);
vecTransformedCoordinates[i].second = vecModelCoordinates[i].first * sinf(r) + vecModelCoordinates[i].second * cosf(r);
}
// Scale
for (int i = 0; i < verts; i++)
{
vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first * s;
vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second * s;
}
// Translate
for (int i = 0; i < verts; i++)
{
vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first + x;
vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second + y;
}
// Draw Closed Polygon
for (int i = 0; i < verts + 1; i++)
{
int j = (i + 1);
DrawLine((int)vecTransformedCoordinates[i % verts].first, (int)vecTransformedCoordinates[i % verts].second,
(int)vecTransformedCoordinates[j % verts].first, (int)vecTransformedCoordinates[j % verts].second, PIXEL_SOLID, 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
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())
m_bAtomActive = false;
auto tp1 = chrono::system_clock::now();
auto tp2 = chrono::system_clock::now();
while (m_bAtomActive)
{
// 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 Keyboard 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 Mouse Input - Check for window events
INPUT_RECORD inBuf[32];
DWORD events = 0;
GetNumberOfConsoleInputEvents(m_hConsoleIn, &events);
if (events > 0)
ReadConsoleInput(m_hConsoleIn, inBuf, events, &events);
// Handle events - we only care about mouse clicks and movement
// for now
for (DWORD i = 0; i < events; i++)
{
switch (inBuf[i].EventType)
{
case FOCUS_EVENT:
{
m_bConsoleInFocus = inBuf[i].Event.FocusEvent.bSetFocus;
}
break;
case MOUSE_EVENT:
{
switch (inBuf[i].Event.MouseEvent.dwEventFlags)
{
case MOUSE_MOVED:
{
m_mousePosX = inBuf[i].Event.MouseEvent.dwMousePosition.X;
m_mousePosY = inBuf[i].Event.MouseEvent.dwMousePosition.Y;
}
break;
case 0:
{
for (int m = 0; m < 5; m++)
m_mouseNewState[m] = (inBuf[i].Event.MouseEvent.dwButtonState & (1 << m)) > 0;
}
break;
default:
break;
}
}
break;
default:
break;
// We don't care just at the moment
}
}
for (int m = 0; m < 5; m++)
{
m_mouse[m].bPressed = false;
m_mouse[m].bReleased = false;
if (m_mouseNewState[m] != m_mouseOldState[m])
{
if (m_mouseNewState[m])
{
m_mouse[m].bPressed = true;
m_mouse[m].bHeld = true;
}
else
{
m_mouse[m].bReleased = true;
m_mouse[m].bHeld = false;
}
}
m_mouseOldState[m] = m_mouseNewState[m];
}
// Handle Frame Update
if (!OnUserUpdate(fElapsedTime))
m_bAtomActive = false;
// Update Title & Present Screen Buffer
wchar_t s[256];
swprintf_s(s, 256, L"OneLoneCoder.com - Console Game Engine - %s - FPS: %3.2f - %d ", m_sAppName.c_str(), 1.0f / fElapsedTime, events);
SetConsoleTitle(s);
WriteConsoleOutput(m_hConsole, m_bufScreen, { (short)m_nScreenWidth, (short)m_nScreenHeight }, { 0,0 }, &m_rectWindow);
}
if (OnUserDestroy())
{
// User has permitted destroy, so exit and clean up
delete[] m_bufScreen;
SetConsoleActiveScreenBuffer(m_hOriginalConsole);
m_cvGameFinished.notify_one();
}
else
{
// User denied destroy for some reason, so continue running
m_bAtomActive = true;
}
}
}
public:
// User MUST OVERRIDE THESE!!
virtual bool OnUserCreate() = 0;
virtual bool OnUserUpdate(float fElapsedTime) = 0;
// Optional for clean up
virtual bool OnUserDestroy()
{
return true;
}
protected:
struct sKeyState
{
bool bPressed;
bool bReleased;
bool bHeld;
} m_keys[256], m_mouse[5];
int m_mousePosX;
int m_mousePosY;
public:
sKeyState GetKey(int nKeyID){ return m_keys[nKeyID]; }
int GetMouseX() { return m_mousePosX; }
int GetMouseY() { return m_mousePosY; }
sKeyState GetMouse(int nMouseButtonID) { return m_mouse[nMouseButtonID]; }
bool IsFocused() { return m_bConsoleInFocus; }
protected:
int Error(const 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 0;
}
static BOOL CloseHandler(DWORD evt)
{
// Note this gets called in a seperate OS thread, so it must
// only exit when the game has finished cleaning up, or else
// the process will be killed before OnUserDestroy() has finished
if (evt == CTRL_CLOSE_EVENT)
{
m_bAtomActive = false;
// Wait for thread to be exited
unique_lock<mutex> ul(m_muxGame);
m_cvGameFinished.wait(ul);
}
return true;
}
protected:
int m_nScreenWidth;
int m_nScreenHeight;
CHAR_INFO *m_bufScreen;
wstring m_sAppName;
HANDLE m_hOriginalConsole;
CONSOLE_SCREEN_BUFFER_INFO m_OriginalConsoleInfo;
HANDLE m_hConsole;
HANDLE m_hConsoleIn;
SMALL_RECT m_rectWindow;
short m_keyOldState[256] = { 0 };
short m_keyNewState[256] = { 0 };
bool m_mouseOldState[5] = { 0 };
bool m_mouseNewState[5] = { 0 };
bool m_bConsoleInFocus = true;
static atomic<bool> m_bAtomActive;
static condition_variable m_cvGameFinished;
static mutex m_muxGame;
};
atomic<bool> olcConsoleGameEngine::m_bAtomActive(false);
condition_variable olcConsoleGameEngine::m_cvGameFinished;
mutex olcConsoleGameEngine::m_muxGame;