From b3045ee3975d67f5602695768c0c6e63975d5025 Mon Sep 17 00:00:00 2001 From: Javidx9 Date: Mon, 2 Jul 2018 18:24:19 +0100 Subject: [PATCH] Added Updated and New Versions of olcConsoleGameEngine --- olcConsoleGameEngine.h | 530 +++++++-- olcConsoleGameEngineGL.h | 2066 +++++++++++++++++++++++++++++++++ olcConsoleGameEngineGLOOP.cpp | 1626 ++++++++++++++++++++++++++ olcConsoleGameEngineGLOOP.h | 619 ++++++++++ olcConsoleGameEngineOOP.cpp | 807 +++++++++++++ olcConsoleGameEngineOOP.h | 601 ++++++++++ olcConsoleGameEngineSDL.h | 1175 +++++++++++++++++++ 7 files changed, 7319 insertions(+), 105 deletions(-) create mode 100644 olcConsoleGameEngineGL.h create mode 100644 olcConsoleGameEngineGLOOP.cpp create mode 100644 olcConsoleGameEngineGLOOP.h create mode 100644 olcConsoleGameEngineOOP.cpp create mode 100644 olcConsoleGameEngineOOP.h create mode 100644 olcConsoleGameEngineSDL.h diff --git a/olcConsoleGameEngine.h b/olcConsoleGameEngine.h index 7c1936e..a99b679 100644 --- a/olcConsoleGameEngine.h +++ b/olcConsoleGameEngine.h @@ -2,6 +2,8 @@ OneLoneCoder.com - Command Line Game Engine "Who needs a frame buffer?" - @Javidx9 +The Original & Best :P + License ~~~~~~~ One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9 @@ -53,7 +55,7 @@ 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 +Last Updated: 02/07/2018 Usage: ~~~~~~ @@ -105,12 +107,11 @@ http://www.twitch.tv/javidx9 */ #pragma once +#pragma comment(lib, "winmm.lib") #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 +Character Set -> Use Unicode. Thanks! - Javidx9 #endif #include @@ -122,10 +123,6 @@ Character Set -> Use Unicode. Thanks! For now, I'll try enabling it for you - Ja #include #include #include -using namespace std; - - - enum COLOUR { @@ -184,7 +181,7 @@ public: Create(w, h); } - olcSprite(wstring sFile) + olcSprite(std::wstring sFile) { if (!Load(sFile)) Create(8, 8); @@ -194,14 +191,14 @@ public: int nHeight = 0; private: - wchar_t *m_Glyphs = nullptr; + short *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_Glyphs = new short[w*h]; m_Colours = new short[w*h]; for (int i = 0; i < w*h; i++) { @@ -211,7 +208,7 @@ private: } public: - void SetGlyph(int x, int y, wchar_t c) + void SetGlyph(int x, int y, short c) { if (x <0 || x >= nWidth || y < 0 || y >= nHeight) return; @@ -227,7 +224,7 @@ public: m_Colours[y * nWidth + x] = c; } - wchar_t GetGlyph(int x, int y) + short GetGlyph(int x, int y) { if (x <0 || x >= nWidth || y < 0 || y >= nHeight) return L' '; @@ -243,7 +240,7 @@ public: return m_Colours[y * nWidth + x]; } - wchar_t SampleGlyph(float x, float y) + short SampleGlyph(float x, float y) { int sx = (int)(x * (float)nWidth); int sy = (int)(y * (float)nHeight-1.0f); @@ -263,7 +260,7 @@ public: return m_Colours[sy * nWidth + sx]; } - bool Save(wstring sFile) + bool Save(std::wstring sFile) { FILE *f = nullptr; _wfopen_s(&f, sFile.c_str(), L"wb"); @@ -273,14 +270,14 @@ public: 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); + fwrite(m_Glyphs, sizeof(short), nWidth * nHeight, f); fclose(f); return true; } - bool Load(wstring sFile) + bool Load(std::wstring sFile) { delete[] m_Glyphs; delete[] m_Colours; @@ -292,21 +289,19 @@ public: if (f == nullptr) return false; - fread(&nWidth, sizeof(int), 1, f); - fread(&nHeight, sizeof(int), 1, f); + std::fread(&nWidth, sizeof(int), 1, f); + std::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); + std::fread(m_Colours, sizeof(short), nWidth * nHeight, f); + std::fread(m_Glyphs, sizeof(short), nWidth * nHeight, f); - fclose(f); + std::fclose(f); return true; } - }; - class olcConsoleGameEngine { public: @@ -318,15 +313,22 @@ public: 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)); + std::memset(m_keyNewState, 0, 256 * sizeof(short)); + std::memset(m_keyOldState, 0, 256 * sizeof(short)); + std::memset(m_keys, 0, 256 * sizeof(sKeyState)); m_mousePosX = 0; m_mousePosY = 0; + m_bEnableSound = false; + m_sAppName = L"Default"; } + void EnableSound() + { + m_bEnableSound = true; + } + int ConstructConsole(int width, int height, int fontw, int fonth) { if (m_hConsole == INVALID_HANDLE_VALUE) @@ -377,10 +379,10 @@ public: //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"Lucida Console"); // Everything else :P //wcscpy_s(cfi.FaceName, L"Liberation Mono"); - //wcscpy_s(cfi.FaceName, L"Consolas"); + wcscpy_s(cfi.FaceName, L"Consolas"); if (!SetCurrentConsoleFontEx(m_hConsole, false, &cfi)) return Error(L"SetCurrentConsoleFontEx"); @@ -411,7 +413,7 @@ public: return 1; } - virtual void Draw(int x, int y, wchar_t c = 0x2588, short col = 0x000F) + virtual void Draw(int x, int y, short c = 0x2588, short col = 0x000F) { if (x >= 0 && x < m_nScreenWidth && y >= 0 && y < m_nScreenHeight) { @@ -420,7 +422,7 @@ public: } } - void Fill(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F) + void Fill(int x1, int y1, int x2, int y2, short c = 0x2588, short col = 0x000F) { Clip(x1, y1); Clip(x2, y2); @@ -429,7 +431,7 @@ public: Draw(x, y, c, col); } - void DrawString(int x, int y, wstring c, short col = 0x000F) + void DrawString(int x, int y, std::wstring c, short col = 0x000F) { for (size_t i = 0; i < c.size(); i++) { @@ -438,7 +440,7 @@ public: } } - void DrawStringAlpha(int x, int y, wstring c, short col = 0x000F) + void DrawStringAlpha(int x, int y, std::wstring c, short col = 0x000F) { for (size_t i = 0; i < c.size(); i++) { @@ -458,7 +460,7 @@ public: if (y >= m_nScreenHeight) y = m_nScreenHeight; } - void DrawLine(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F) + void DrawLine(int x1, int y1, int x2, int y2, short c = 0x2588, short col = 0x000F) { int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; dx = x2 - x1; dy = y2 - y1; @@ -510,7 +512,7 @@ public: } } - void DrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, wchar_t c = 0x2588, short col = 0x000F) + void DrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c = 0x2588, short col = 0x000F) { DrawLine(x1, y1, x2, y2, c, col); DrawLine(x2, y2, x3, y3, c, col); @@ -518,7 +520,7 @@ public: } // 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) + void FillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short 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); }; @@ -655,48 +657,7 @@ public: } } - // 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) + void DrawCircle(int xc, int yc, int r, short c = 0x2588, short col = 0x000F) { int x = 0; int y = r; @@ -718,7 +679,7 @@ public: } } - void FillCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F) + void FillCircle(int xc, int yc, int r, short c = 0x2588, short col = 0x000F) { // Taken from wikipedia int x = 0; @@ -774,13 +735,13 @@ public: } } - void DrawWireFrameModel(const vector> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE) + void DrawWireFrameModel(const std::vector> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE, short c = PIXEL_SOLID) { // pair.first = x coordinate // pair.second = y coordinate // Create translated model vector of coordinate pairs - vector> vecTransformedCoordinates; + std::vector> vecTransformedCoordinates; int verts = vecModelCoordinates.size(); vecTransformedCoordinates.resize(verts); @@ -810,7 +771,7 @@ public: { 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); + (int)vecTransformedCoordinates[j % verts].first, (int)vecTransformedCoordinates[j % verts].second, c, col); } } @@ -822,11 +783,10 @@ public: public: void Start() - { + { + // Start the thread m_bAtomActive = true; - - // Star the thread - thread t = thread(&olcConsoleGameEngine::GameThread, this); + std::thread t = std::thread(&olcConsoleGameEngine::GameThread, this); // Wait for thread to be exited t.join(); @@ -846,11 +806,21 @@ private: void GameThread() { // Create user resources as part of this thread - if (!OnUserCreate()) + if (!OnUserCreate()) m_bAtomActive = false; - auto tp1 = chrono::system_clock::now(); - auto tp2 = chrono::system_clock::now(); + // Check if sound system should be enabled + if (m_bEnableSound) + { + if (!CreateAudio()) + { + m_bAtomActive = false; // Failed to create audio system + m_bEnableSound = false; + } + } + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); while (m_bAtomActive) { @@ -858,8 +828,8 @@ private: while (m_bAtomActive) { // Handle Timing - tp2 = chrono::system_clock::now(); - chrono::duration elapsedTime = tp2 - tp1; + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; tp1 = tp2; float fElapsedTime = elapsedTime.count(); @@ -967,15 +937,20 @@ private: // 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); + swprintf_s(s, 256, 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); } + if (m_bEnableSound) + { + // Close and Clean up audio system + } + + // Allow the user to free resources if they have overrided the destroy function if (OnUserDestroy()) { // User has permitted destroy, so exit and clean up - delete[] m_bufScreen; SetConsoleActiveScreenBuffer(m_hOriginalConsole); m_cvGameFinished.notify_one(); @@ -990,15 +965,355 @@ private: public: // User MUST OVERRIDE THESE!! - virtual bool OnUserCreate() = 0; - virtual bool OnUserUpdate(float fElapsedTime) = 0; + virtual bool OnUserCreate() = 0; + virtual bool OnUserUpdate(float fElapsedTime) = 0; // Optional for clean up - virtual bool OnUserDestroy() + virtual bool OnUserDestroy() { return true; } + + + +protected: // Audio Engine ===================================================================== + + class olcAudioSample + { + public: + olcAudioSample() + { + + } + + olcAudioSample(std::wstring sWavFile) + { + // Load Wav file and convert to float format + FILE *f = nullptr; + _wfopen_s(&f, sWavFile.c_str(), L"rb"); + if (f == nullptr) + return; + + char dump[4]; + std::fread(&dump, sizeof(char), 4, f); // Read "RIFF" + if (strncmp(dump, "RIFF", 4) != 0) return; + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&dump, sizeof(char), 4, f); // Read "WAVE" + if (strncmp(dump, "WAVE", 4) != 0) return; + + // Read Wave description chunk + std::fread(&dump, sizeof(char), 4, f); // Read "fmt " + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&wavHeader, sizeof(WAVEFORMATEX) - 2, 1, f); // Read Wave Format Structure chunk + // Note the -2, because the structure has 2 bytes to indicate its own size + // which are not in the wav file + + // Just check if wave format is compatible with olcCGE + if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) + { + std::fclose(f); + return; + } + + // Search for audio data chunk + long nChunksize = 0; + std::fread(&dump, sizeof(char), 4, f); // Read chunk header + std::fread(&nChunksize, sizeof(long), 1, f); // Read chunk size + while (strncmp(dump, "data", 4) != 0) + { + // Not audio data, so just skip it + std::fseek(f, nChunksize, SEEK_CUR); + std::fread(&dump, sizeof(char), 4, f); + std::fread(&nChunksize, sizeof(long), 1, f); + } + + // Finally got to data, so read it all in and convert to float samples + nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); + nChannels = wavHeader.nChannels; + + // Create floating point buffer to hold audio sample + fSample = new float[nSamples * nChannels]; + float *pSample = fSample; + + // Read in audio data and normalise + for (long i = 0; i < nSamples; i++) + { + for (int c = 0; c < nChannels; c++) + { + short s = 0; + std::fread(&s, sizeof(short), 1, f); + *pSample = (float)s / (float)(MAXSHORT); + pSample++; + } + } + + // All done, flag sound as valid + std::fclose(f); + bSampleValid = true; + } + + WAVEFORMATEX wavHeader; + float *fSample = nullptr; + long nSamples = 0; + int nChannels = 0; + bool bSampleValid = false; + }; + + // This vector holds all loaded sound samples in memory + std::vector vecAudioSamples; + + // This structure represents a sound that is currently playing. It only + // holds the sound ID and where this instance of it is up to for its + // current playback + struct sCurrentlyPlayingSample + { + int nAudioSampleID = 0; + long nSamplePosition = 0; + bool bFinished = false; + bool bLoop = false; + }; + std::list listActiveSamples; + + // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID + // number is returned if successful, otherwise -1 + unsigned int LoadAudioSample(std::wstring sWavFile) + { + if (!m_bEnableSound) + return -1; + + olcAudioSample a(sWavFile); + if (a.bSampleValid) + { + vecAudioSamples.push_back(a); + return vecAudioSamples.size(); + } + else + return -1; + } + + // Add sample 'id' to the mixers sounds to play list + void PlaySample(int id, bool bLoop = false) + { + listActiveSamples.push_back({ id, 0, false, bLoop }); + } + + void StopSample(int id) { + + } + + // The audio system uses by default a specific wave format + bool CreateAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, + unsigned int nBlocks = 8, unsigned int nBlockSamples = 512) + { + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_nBlockFree = m_nBlockCount; + m_nBlockCurrent = 0; + m_pBlockMemory = nullptr; + m_pWaveHeaders = nullptr; + + // Device is available + WAVEFORMATEX waveFormat; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nSamplesPerSec = m_nSampleRate; + waveFormat.wBitsPerSample = sizeof(short) * 8; + waveFormat.nChannels = m_nChannels; + waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + // Open Device if valid + if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)waveOutProcWrap, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK) + return DestroyAudio(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); + + m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; + if (m_pWaveHeaders == nullptr) + return DestroyAudio(); + ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); + + // Link headers to block memory + for (unsigned int n = 0; n < m_nBlockCount; n++) + { + m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); + m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); + } + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&olcConsoleGameEngine::AudioThread, this); + + // Start the ball rolling with the sound delivery thread + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); return true; } + // Stop and clean up audio system + bool DestroyAudio() + { + m_bAudioThreadActive = false; + return false; + } + + // Handler for soundcard request for more data + void waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2) + { + if (uMsg != WOM_DONE) return; + m_nBlockFree++; + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + } + + // Static wrapper for sound card handler + static void CALLBACK waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) + { + ((olcConsoleGameEngine*)dwInstance)->waveOutProc(hWaveOut, uMsg, dwParam1, dwParam2); + } + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void AudioThread() + { + m_fGlobalTime = 0.0f; + float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) + { + // Wait for block to become available + if (m_nBlockFree == 0) + { + std::unique_lock lm(m_muxBlockNotZero); + while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly + m_cvBlockNotZero.wait(lm); + } + + // Block is here, so use it + m_nBlockFree--; + + // Prepare block for processing + if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) + waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + + short nNewSample = 0; + int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; + nPreviousSample = nNewSample; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Send block to sound device + waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + m_nBlockCurrent++; + m_nBlockCurrent %= m_nBlockCount; + } + } + + // Overridden by user if they want to generate sound in real-time + virtual float onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep) + { + return 0.0f; + } + + // Overriden by user if they want to manipulate the sound before it is played + virtual float onUserSoundFilter(int nChannel, float fGlobalTime, float fSample) + { + return fSample; + } + + // The Sound Mixer - If the user wants to play many sounds simultaneously, and + // perhaps the same sound overlapping itself, then you need a mixer, which + // takes input from all sound sources for that audio frame. This mixer maintains + // a list of sound locations for all concurrently playing audio samples. Instead + // of duplicating audio data, we simply store the fact that a sound sample is in + // use and an offset into its sample data. As time progresses we update this offset + // until it is beyound the length of the sound sample it is attached to. At this + // point we remove the playing souind from the list. + // + // Additionally, the users application may want to generate sound instead of just + // playing audio clips (think a synthesizer for example) in whcih case we also + // provide an "onUser..." event to allow the user to return a sound for that point + // in time. + // + // Finally, before the sound is issued to the operating system for performing, the + // user gets one final chance to "filter" the sound, perhaps changing the volume + // or adding funky effects + float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) + { + // Accumulate sample for this channel + float fMixerSample = 0.0f; + + for (auto &s : listActiveSamples) + { + // Calculate sample position + s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); + + // If sample position is valid add to the mix + if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) + fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; + else + s.bFinished = true; // Else sound has completed + } + + // If sounds have completed then remove them + listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + + // The users application might be generating sound, so grab that if it exists + fMixerSample += onUserSoundSample(nChannel, fGlobalTime, fTimeStep); + + // Return the sample via an optional user override to filter the sound + return onUserSoundFilter(nChannel, fGlobalTime, fMixerSample); + } + + unsigned int m_nSampleRate; + unsigned int m_nChannels; + unsigned int m_nBlockCount; + unsigned int m_nBlockSamples; + unsigned int m_nBlockCurrent; + + short* m_pBlockMemory = nullptr; + WAVEHDR *m_pWaveHeaders = nullptr; + HWAVEOUT m_hwDevice = nullptr; + + std::thread m_AudioThread; + std::atomic m_bAudioThreadActive = false; + std::atomic m_nBlockFree = 0; + std::condition_variable m_cvBlockNotZero; + std::mutex m_muxBlockNotZero; + std::atomic m_fGlobalTime = 0.0f; + + protected: @@ -1041,7 +1356,7 @@ protected: m_bAtomActive = false; // Wait for thread to be exited - unique_lock ul(m_muxGame); + std::unique_lock ul(m_muxGame); m_cvGameFinished.wait(ul); } return true; @@ -1051,7 +1366,7 @@ protected: int m_nScreenWidth; int m_nScreenHeight; CHAR_INFO *m_bufScreen; - wstring m_sAppName; + std::wstring m_sAppName; HANDLE m_hOriginalConsole; CONSOLE_SCREEN_BUFFER_INFO m_OriginalConsoleInfo; HANDLE m_hConsole; @@ -1061,12 +1376,17 @@ protected: short m_keyNewState[256] = { 0 }; bool m_mouseOldState[5] = { 0 }; bool m_mouseNewState[5] = { 0 }; - bool m_bConsoleInFocus = true; - static atomic m_bAtomActive; - static condition_variable m_cvGameFinished; - static mutex m_muxGame; + bool m_bConsoleInFocus = true; + bool m_bEnableSound = false; + + // These need to be static because of the OnDestroy call the OS may make. The OS + // spawns a special thread just for that + static std::atomic m_bAtomActive; + static std::condition_variable m_cvGameFinished; + static std::mutex m_muxGame; }; -atomic olcConsoleGameEngine::m_bAtomActive(false); -condition_variable olcConsoleGameEngine::m_cvGameFinished; -mutex olcConsoleGameEngine::m_muxGame; \ No newline at end of file +// Define our static variables +std::atomic olcConsoleGameEngine::m_bAtomActive(false); +std::condition_variable olcConsoleGameEngine::m_cvGameFinished; +std::mutex olcConsoleGameEngine::m_muxGame; diff --git a/olcConsoleGameEngineGL.h b/olcConsoleGameEngineGL.h new file mode 100644 index 0000000..4e5275f --- /dev/null +++ b/olcConsoleGameEngineGL.h @@ -0,0 +1,2066 @@ +/* +OneLoneCoder.com - Command Line Game Engine +"Who needs a frame buffer?" - @Javidx9 + +Mega Big Thanks to KrossX from the discord server for taking +the time to develop an OpenGL wrapper for the olcConsoleGameEngine. + +If you have had difficulty getting consoles to look correct it +could be due to your version of Windows. This version of the +console game engine is for you! + +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 + +Last Updated: 02/07/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 +#pragma message("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 + +#pragma comment(lib, "user32.lib") +#pragma comment(lib, "gdi32.lib") +#pragma comment(lib, "opengl32.lib") +#pragma comment(lib, "winmm.lib") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +typedef BOOL(WINAPI wglSwapInterval_t) (int interval); +wglSwapInterval_t *wglSwapInterval; + +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, +}; + +// Based on the PxPlus IBM CGA Font from +// "The Ultimate Oldschool PC Font Pack" http://int10h.org/oldschool-pc-fonts/ +// Rendered into an image (256x56) and converted to char array +// +// The Ultimate Oldschool PC Font Pack is licensed under a Creative Commons +// Attribution-ShareAlike 4.0 International License. +// +// You should have received a copy of the license along with this work. If +// not, see < http://creativecommons.org/licenses/by-sa/4.0/ >. +// +// (c) 2016 VileR +// +// (256x256x8) +unsigned char pxplus_ibm_cga[65536]; +char pxplus_ibm_cga_enc[8509 + 1] = +"0mWOkP0780et0q?qqedtH40@HHVtq106HP10000000001stOLm320q??33cIa?sqlOq3?0000000000200000000mPf1c1030H00e0P=0H000H03000000000e0000000003Hee>8PQ76m1cbS?s0H30sHk0hqS1hH63em13t=hNmV7?0hINhO?00HmlIsq36L0@Fsf<<0D50000000P?H3060mhNqmQ=kehHW=hsf16q=k?e30VQfmQ30003>mQ3S=6<0HSIWI036eP16Hk0he03dnf0m1>m0000mP30000>qP3eQhH0Hh<6<60t1eHhe60q10Ler30kHSlme17N0036e000L60Lmf0W100tmS70eV=0000V107L<30kHh<0eV3qLk7drfI00eKOe1" +"0L00706mS?0HP?m<3m00000000e17NmW" +"7snQONmW7snQ?SIS7emfOtql?trh300e3>0000000000000000000e17000P700000000000L0q3?q07LeQS70q320q32KmhH0e033l30=jF6Hm760m1Os?W?6m33smV7k00000P=0000<@00VQ13aQ1?KeQ7eH3mfH34kPfhakH<Omf<30S=hee6hqW7e00008002000000060ef1kq1LqmQ7hPl7Kq167ef<pLkIWnh?hqh??qlO[nfHSMkHS1e0" +"000000000000000000000e000000S10000000000hef?teh?hPeHhNqhm0pLkI0000?000Oe10P100P10000000000e0000000N0000000<00H0mlHOefOtm0?t<3?trh0ta7>e13Le130HS3HP176H03tPfk0a=00006600000e000000000000" +"00e03e1000000q77O00?>CeqOh103?qe3?qe7h" +"qe??qf?t<0H00000006IhL[sk0eHS=VISI6Kc=kHSfkHaf0?000000P5d:>kt1P1Htr3006H000HP160P10aQ1KaQ1Ka10" +"000000P1KaQ1KaQ1KaQ160006HP10H00KHe6KHe6K00000000He6KHe6KHe6KHP100P16H0060e66af66af6000000006af66af66af66af66afkt00et?0?00D5000" +"0000@0004200ett3POsQ1KaQ1Kar?00ip3e30AZ2000000HLetef1?qqte04mtthkpthkpt300tt0qT8BGqm30000?Oq?OkHVOWW97Uc=0a106a1et00er?0?00X>?k" +"eS?0mlOsrlH2nlerJCPttttttttd300tts3e300GfS=qHSOtt1?qIS@s=Ldkipe0000He6K00ettt0qT@TZqm3?smlt7P1LLHVOWW97IN>00006af60ttOd?0?0Ri50" +"0000s1@00042qeht33@Pt3000P1Ka1etttt3e300Z2000000000000000qtt0mWOHe3q8eQ=8edt0h7O33000000000000000000000000ekV1>2sq77> 0) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 1) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 2) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 3) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 4) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 5) & 1) * 0xFF; + } +} + +#define FUV(a,b,c) case a: *x=b; *y=c; break; + +void GetFontCoords(int id, int *x, int *y) { + switch (id) { + FUV(0, 0, 0)FUV(1, 8, 0)FUV(2, 16, 0)FUV(3, 24, 0)FUV(4, 32, 0)FUV(5, 40, 0)FUV(6, 48, 0) + FUV(7, 56, 0)FUV(8, 64, 0)FUV(9, 72, 0)FUV(10, 80, 0)FUV(11, 88, 0)FUV(12, 96, 0)FUV(13, 104, 0) + FUV(14, 112, 0)FUV(15, 120, 0)FUV(16, 128, 0)FUV(17, 136, 0)FUV(18, 144, 0)FUV(19, 152, 0)FUV(20, 160, 0) + FUV(21, 168, 0)FUV(22, 176, 0)FUV(23, 184, 0)FUV(24, 192, 0)FUV(25, 200, 0)FUV(26, 208, 0)FUV(27, 216, 0) + FUV(28, 224, 0)FUV(29, 232, 0)FUV(30, 240, 0)FUV(31, 248, 0)FUV(32, 0, 8)FUV(33, 8, 8)FUV(34, 16, 8) + FUV(35, 24, 8)FUV(36, 32, 8)FUV(37, 40, 8)FUV(38, 48, 8)FUV(39, 56, 8)FUV(40, 64, 8)FUV(41, 72, 8) + FUV(42, 80, 8)FUV(43, 88, 8)FUV(44, 96, 8)FUV(45, 104, 8)FUV(46, 112, 8)FUV(47, 120, 8)FUV(48, 128, 8) + FUV(49, 136, 8)FUV(50, 144, 8)FUV(51, 152, 8)FUV(52, 160, 8)FUV(53, 168, 8)FUV(54, 176, 8)FUV(55, 184, 8) + FUV(56, 192, 8)FUV(57, 200, 8)FUV(58, 208, 8)FUV(59, 216, 8)FUV(60, 224, 8)FUV(61, 232, 8)FUV(62, 240, 8) + FUV(63, 248, 8)FUV(64, 0, 16)FUV(65, 8, 16)FUV(66, 16, 16)FUV(67, 24, 16)FUV(68, 32, 16)FUV(69, 40, 16) + FUV(70, 48, 16)FUV(71, 56, 16)FUV(72, 64, 16)FUV(73, 72, 16)FUV(74, 80, 16)FUV(75, 88, 16)FUV(76, 96, 16) + FUV(77, 104, 16)FUV(78, 112, 16)FUV(79, 120, 16)FUV(80, 128, 16)FUV(81, 136, 16)FUV(82, 144, 16)FUV(83, 152, 16) + FUV(84, 160, 16)FUV(85, 168, 16)FUV(86, 176, 16)FUV(87, 184, 16)FUV(88, 192, 16)FUV(89, 200, 16)FUV(90, 208, 16) + FUV(91, 216, 16)FUV(92, 224, 16)FUV(93, 232, 16)FUV(94, 240, 16)FUV(95, 248, 16)FUV(96, 0, 24)FUV(97, 8, 24) + FUV(98, 16, 24)FUV(99, 24, 24)FUV(100, 32, 24)FUV(101, 40, 24)FUV(102, 48, 24)FUV(103, 56, 24)FUV(104, 64, 24) + FUV(105, 72, 24)FUV(106, 80, 24)FUV(107, 88, 24)FUV(108, 96, 24)FUV(109, 104, 24)FUV(110, 112, 24)FUV(111, 120, 24) + FUV(112, 128, 24)FUV(113, 136, 24)FUV(114, 144, 24)FUV(115, 152, 24)FUV(116, 160, 24)FUV(117, 168, 24)FUV(118, 176, 24) + FUV(119, 184, 24)FUV(120, 192, 24)FUV(121, 200, 24)FUV(122, 208, 24)FUV(123, 216, 24)FUV(124, 224, 24)FUV(125, 232, 24) + FUV(126, 240, 24)FUV(127, 248, 24)FUV(160, 0, 32)FUV(161, 8, 32)FUV(162, 16, 32)FUV(163, 24, 32)FUV(164, 32, 32) + FUV(165, 40, 32)FUV(166, 48, 32)FUV(167, 56, 32)FUV(168, 64, 32)FUV(169, 72, 32)FUV(170, 80, 32)FUV(171, 88, 32) + FUV(172, 96, 32)FUV(173, 104, 32)FUV(174, 112, 32)FUV(175, 120, 32)FUV(176, 128, 32)FUV(177, 136, 32)FUV(178, 144, 32) + FUV(179, 152, 32)FUV(180, 160, 32)FUV(181, 168, 32)FUV(182, 176, 32)FUV(183, 184, 32)FUV(184, 192, 32)FUV(185, 200, 32) + FUV(186, 208, 32)FUV(187, 216, 32)FUV(188, 224, 32)FUV(189, 232, 32)FUV(190, 240, 32)FUV(191, 248, 32)FUV(192, 0, 40) + FUV(193, 8, 40)FUV(194, 16, 40)FUV(195, 24, 40)FUV(196, 32, 40)FUV(197, 40, 40)FUV(198, 48, 40)FUV(199, 56, 40) + FUV(200, 64, 40)FUV(201, 72, 40)FUV(202, 80, 40)FUV(203, 88, 40)FUV(204, 96, 40)FUV(205, 104, 40)FUV(206, 112, 40) + FUV(207, 120, 40)FUV(208, 128, 40)FUV(209, 136, 40)FUV(210, 144, 40)FUV(211, 152, 40)FUV(212, 160, 40)FUV(213, 168, 40) + FUV(214, 176, 40)FUV(215, 184, 40)FUV(216, 192, 40)FUV(217, 200, 40)FUV(218, 208, 40)FUV(219, 216, 40)FUV(220, 224, 40) + FUV(221, 232, 40)FUV(222, 240, 40)FUV(223, 248, 40)FUV(224, 0, 48)FUV(225, 8, 48)FUV(226, 16, 48)FUV(227, 24, 48) + FUV(228, 32, 48)FUV(229, 40, 48)FUV(230, 48, 48)FUV(231, 56, 48)FUV(232, 64, 48)FUV(233, 72, 48)FUV(234, 80, 48) + FUV(235, 88, 48)FUV(236, 96, 48)FUV(237, 104, 48)FUV(238, 112, 48)FUV(239, 120, 48)FUV(240, 128, 48)FUV(241, 136, 48) + FUV(242, 144, 48)FUV(243, 152, 48)FUV(244, 160, 48)FUV(245, 168, 48)FUV(246, 176, 48)FUV(247, 184, 48)FUV(248, 192, 48) + FUV(249, 200, 48)FUV(250, 208, 48)FUV(251, 216, 48)FUV(252, 224, 48)FUV(253, 232, 48)FUV(254, 240, 48)FUV(255, 248, 48) + FUV(256, 0, 56)FUV(257, 8, 56)FUV(258, 16, 56)FUV(259, 24, 56)FUV(260, 32, 56)FUV(261, 40, 56)FUV(262, 48, 56) + FUV(263, 56, 56)FUV(264, 64, 56)FUV(265, 72, 56)FUV(266, 80, 56)FUV(267, 88, 56)FUV(268, 96, 56)FUV(269, 104, 56) + FUV(270, 112, 56)FUV(271, 120, 56)FUV(272, 128, 56)FUV(273, 136, 56)FUV(274, 144, 56)FUV(275, 152, 56)FUV(276, 160, 56) + FUV(277, 168, 56)FUV(278, 176, 56)FUV(279, 184, 56)FUV(280, 192, 56)FUV(281, 200, 56)FUV(282, 208, 56)FUV(283, 216, 56) + FUV(284, 224, 56)FUV(285, 232, 56)FUV(286, 240, 56)FUV(287, 248, 56)FUV(288, 0, 64)FUV(289, 8, 64)FUV(290, 16, 64) + FUV(291, 24, 64)FUV(292, 32, 64)FUV(293, 40, 64)FUV(294, 48, 64)FUV(295, 56, 64)FUV(296, 64, 64)FUV(297, 72, 64) + FUV(298, 80, 64)FUV(299, 88, 64)FUV(300, 96, 64)FUV(301, 104, 64)FUV(302, 112, 64)FUV(303, 120, 64)FUV(304, 128, 64) + FUV(305, 136, 64)FUV(306, 144, 64)FUV(307, 152, 64)FUV(308, 160, 64)FUV(309, 168, 64)FUV(310, 176, 64)FUV(311, 184, 64) + FUV(312, 192, 64)FUV(313, 200, 64)FUV(314, 208, 64)FUV(315, 216, 64)FUV(316, 224, 64)FUV(317, 232, 64)FUV(318, 240, 64) + FUV(319, 248, 64)FUV(320, 0, 72)FUV(321, 8, 72)FUV(322, 16, 72)FUV(323, 24, 72)FUV(324, 32, 72)FUV(325, 40, 72) + FUV(326, 48, 72)FUV(327, 56, 72)FUV(328, 64, 72)FUV(329, 72, 72)FUV(330, 80, 72)FUV(331, 88, 72)FUV(332, 96, 72) + FUV(333, 104, 72)FUV(334, 112, 72)FUV(335, 120, 72)FUV(336, 128, 72)FUV(337, 136, 72)FUV(338, 144, 72)FUV(339, 152, 72) + FUV(340, 160, 72)FUV(341, 168, 72)FUV(342, 176, 72)FUV(343, 184, 72)FUV(344, 192, 72)FUV(345, 200, 72)FUV(346, 208, 72) + FUV(347, 216, 72)FUV(348, 224, 72)FUV(349, 232, 72)FUV(350, 240, 72)FUV(351, 248, 72)FUV(352, 0, 80)FUV(353, 8, 80) + FUV(354, 16, 80)FUV(355, 24, 80)FUV(356, 32, 80)FUV(357, 40, 80)FUV(358, 48, 80)FUV(359, 56, 80)FUV(360, 64, 80) + FUV(361, 72, 80)FUV(362, 80, 80)FUV(363, 88, 80)FUV(364, 96, 80)FUV(365, 104, 80)FUV(366, 112, 80)FUV(367, 120, 80) + FUV(368, 128, 80)FUV(369, 136, 80)FUV(370, 144, 80)FUV(371, 152, 80)FUV(372, 160, 80)FUV(373, 168, 80)FUV(374, 176, 80) + FUV(375, 184, 80)FUV(376, 192, 80)FUV(377, 200, 80)FUV(378, 208, 80)FUV(379, 216, 80)FUV(380, 224, 80)FUV(381, 232, 80) + FUV(382, 240, 80)FUV(383, 248, 80)FUV(402, 0, 88)FUV(417, 8, 88)FUV(439, 16, 88)FUV(506, 24, 88)FUV(507, 32, 88) + FUV(508, 40, 88)FUV(509, 48, 88)FUV(510, 56, 88)FUV(511, 64, 88)FUV(536, 72, 88)FUV(537, 80, 88)FUV(538, 88, 88) + FUV(539, 96, 88)FUV(593, 104, 88)FUV(632, 112, 88)FUV(710, 120, 88)FUV(711, 128, 88)FUV(713, 136, 88)FUV(728, 144, 88) + FUV(729, 152, 88)FUV(730, 160, 88)FUV(731, 168, 88)FUV(732, 176, 88)FUV(733, 184, 88)FUV(894, 192, 88)FUV(900, 200, 88) + FUV(901, 208, 88)FUV(902, 216, 88)FUV(903, 224, 88)FUV(904, 232, 88)FUV(905, 240, 88)FUV(906, 248, 88)FUV(908, 0, 96) + FUV(910, 8, 96)FUV(911, 16, 96)FUV(912, 24, 96)FUV(913, 32, 96)FUV(914, 40, 96)FUV(915, 48, 96)FUV(916, 56, 96) + FUV(917, 64, 96)FUV(918, 72, 96)FUV(919, 80, 96)FUV(920, 88, 96)FUV(921, 96, 96)FUV(922, 104, 96)FUV(923, 112, 96) + FUV(924, 120, 96)FUV(925, 128, 96)FUV(926, 136, 96)FUV(927, 144, 96)FUV(928, 152, 96)FUV(929, 160, 96)FUV(931, 168, 96) + FUV(932, 176, 96)FUV(933, 184, 96)FUV(934, 192, 96)FUV(935, 200, 96)FUV(936, 208, 96)FUV(937, 216, 96)FUV(938, 224, 96) + FUV(939, 232, 96)FUV(940, 240, 96)FUV(941, 248, 96)FUV(942, 0, 104)FUV(943, 8, 104)FUV(944, 16, 104)FUV(945, 24, 104) + FUV(946, 32, 104)FUV(947, 40, 104)FUV(948, 48, 104)FUV(949, 56, 104)FUV(950, 64, 104)FUV(951, 72, 104)FUV(952, 80, 104) + FUV(953, 88, 104)FUV(954, 96, 104)FUV(955, 104, 104)FUV(956, 112, 104)FUV(957, 120, 104)FUV(958, 128, 104)FUV(959, 136, 104) + FUV(960, 144, 104)FUV(961, 152, 104)FUV(962, 160, 104)FUV(963, 168, 104)FUV(964, 176, 104)FUV(965, 184, 104)FUV(966, 192, 104) + FUV(967, 200, 104)FUV(968, 208, 104)FUV(969, 216, 104)FUV(970, 224, 104)FUV(971, 232, 104)FUV(972, 240, 104)FUV(973, 248, 104) + FUV(974, 0, 112)FUV(976, 8, 112)FUV(1012, 16, 112)FUV(1024, 24, 112)FUV(1025, 32, 112)FUV(1026, 40, 112)FUV(1027, 48, 112) + FUV(1028, 56, 112)FUV(1029, 64, 112)FUV(1030, 72, 112)FUV(1031, 80, 112)FUV(1032, 88, 112)FUV(1033, 96, 112)FUV(1034, 104, 112) + FUV(1035, 112, 112)FUV(1036, 120, 112)FUV(1037, 128, 112)FUV(1038, 136, 112)FUV(1039, 144, 112)FUV(1040, 152, 112)FUV(1041, 160, 112) + FUV(1042, 168, 112)FUV(1043, 176, 112)FUV(1044, 184, 112)FUV(1045, 192, 112)FUV(1046, 200, 112)FUV(1047, 208, 112)FUV(1048, 216, 112) + FUV(1049, 224, 112)FUV(1050, 232, 112)FUV(1051, 240, 112)FUV(1052, 248, 112)FUV(1053, 0, 120)FUV(1054, 8, 120)FUV(1055, 16, 120) + FUV(1056, 24, 120)FUV(1057, 32, 120)FUV(1058, 40, 120)FUV(1059, 48, 120)FUV(1060, 56, 120)FUV(1061, 64, 120)FUV(1062, 72, 120) + FUV(1063, 80, 120)FUV(1064, 88, 120)FUV(1065, 96, 120)FUV(1066, 104, 120)FUV(1067, 112, 120)FUV(1068, 120, 120)FUV(1069, 128, 120) + FUV(1070, 136, 120)FUV(1071, 144, 120)FUV(1072, 152, 120)FUV(1073, 160, 120)FUV(1074, 168, 120)FUV(1075, 176, 120)FUV(1076, 184, 120) + FUV(1077, 192, 120)FUV(1078, 200, 120)FUV(1079, 208, 120)FUV(1080, 216, 120)FUV(1081, 224, 120)FUV(1082, 232, 120)FUV(1083, 240, 120) + FUV(1084, 248, 120)FUV(1085, 0, 128)FUV(1086, 8, 128)FUV(1087, 16, 128)FUV(1088, 24, 128)FUV(1089, 32, 128)FUV(1090, 40, 128) + FUV(1091, 48, 128)FUV(1092, 56, 128)FUV(1093, 64, 128)FUV(1094, 72, 128)FUV(1095, 80, 128)FUV(1096, 88, 128)FUV(1097, 96, 128) + FUV(1098, 104, 128)FUV(1099, 112, 128)FUV(1100, 120, 128)FUV(1101, 128, 128)FUV(1102, 136, 128)FUV(1103, 144, 128)FUV(1104, 152, 128) + FUV(1105, 160, 128)FUV(1106, 168, 128)FUV(1107, 176, 128)FUV(1108, 184, 128)FUV(1109, 192, 128)FUV(1110, 200, 128)FUV(1111, 208, 128) + FUV(1112, 216, 128)FUV(1113, 224, 128)FUV(1114, 232, 128)FUV(1115, 240, 128)FUV(1116, 248, 128)FUV(1117, 0, 136)FUV(1118, 8, 136) + FUV(1119, 16, 136)FUV(1168, 24, 136)FUV(1169, 32, 136)FUV(1470, 40, 136)FUV(1488, 48, 136)FUV(1489, 56, 136)FUV(1490, 64, 136) + FUV(1491, 72, 136)FUV(1492, 80, 136)FUV(1493, 88, 136)FUV(1494, 96, 136)FUV(1495, 104, 136)FUV(1496, 112, 136)FUV(1497, 120, 136) + FUV(1498, 128, 136)FUV(1499, 136, 136)FUV(1500, 144, 136)FUV(1501, 152, 136)FUV(1502, 160, 136)FUV(1503, 168, 136)FUV(1504, 176, 136) + FUV(1505, 184, 136)FUV(1506, 192, 136)FUV(1507, 200, 136)FUV(1508, 208, 136)FUV(1509, 216, 136)FUV(1510, 224, 136)FUV(1511, 232, 136) + FUV(1512, 240, 136)FUV(1513, 248, 136)FUV(1514, 0, 144)FUV(1520, 8, 144)FUV(1521, 16, 144)FUV(1522, 24, 144)FUV(1523, 32, 144) + FUV(1524, 40, 144)FUV(7451, 48, 144)FUV(7462, 56, 144)FUV(7464, 64, 144)FUV(7808, 72, 144)FUV(7809, 80, 144)FUV(7810, 88, 144) + FUV(7811, 96, 144)FUV(7812, 104, 144)FUV(7813, 112, 144)FUV(7839, 120, 144)FUV(7922, 128, 144)FUV(7923, 136, 144)FUV(8208, 144, 144) + FUV(8210, 152, 144)FUV(8211, 160, 144)FUV(8212, 168, 144)FUV(8213, 176, 144)FUV(8215, 184, 144)FUV(8216, 192, 144)FUV(8217, 200, 144) + FUV(8218, 208, 144)FUV(8219, 216, 144)FUV(8220, 224, 144)FUV(8221, 232, 144)FUV(8222, 240, 144)FUV(8223, 248, 144)FUV(8224, 0, 152) + FUV(8225, 8, 152)FUV(8226, 16, 152)FUV(8230, 24, 152)FUV(8231, 32, 152)FUV(8240, 40, 152)FUV(8242, 48, 152)FUV(8243, 56, 152) + FUV(8245, 64, 152)FUV(8249, 72, 152)FUV(8250, 80, 152)FUV(8252, 88, 152)FUV(8254, 96, 152)FUV(8255, 104, 152)FUV(8256, 112, 152) + FUV(8260, 120, 152)FUV(8276, 128, 152)FUV(8308, 136, 152)FUV(8309, 144, 152)FUV(8310, 152, 152)FUV(8311, 160, 152)FUV(8312, 168, 152) + FUV(8313, 176, 152)FUV(8314, 184, 152)FUV(8315, 192, 152)FUV(8319, 200, 152)FUV(8321, 208, 152)FUV(8322, 216, 152)FUV(8323, 224, 152) + FUV(8324, 232, 152)FUV(8325, 240, 152)FUV(8326, 248, 152)FUV(8327, 0, 160)FUV(8328, 8, 160)FUV(8329, 16, 160)FUV(8330, 24, 160) + FUV(8331, 32, 160)FUV(8355, 40, 160)FUV(8356, 48, 160)FUV(8359, 56, 160)FUV(8362, 64, 160)FUV(8364, 72, 160)FUV(8453, 80, 160) + FUV(8467, 88, 160)FUV(8470, 96, 160)FUV(8482, 104, 160)FUV(8486, 112, 160)FUV(8494, 120, 160)FUV(8528, 128, 160)FUV(8529, 136, 160) + FUV(8531, 144, 160)FUV(8532, 152, 160)FUV(8533, 160, 160)FUV(8534, 168, 160)FUV(8535, 176, 160)FUV(8536, 184, 160)FUV(8537, 192, 160) + FUV(8538, 200, 160)FUV(8539, 208, 160)FUV(8540, 216, 160)FUV(8541, 224, 160)FUV(8542, 232, 160)FUV(8592, 240, 160)FUV(8593, 248, 160) + FUV(8594, 0, 168)FUV(8595, 8, 168)FUV(8596, 16, 168)FUV(8597, 24, 168)FUV(8616, 32, 168)FUV(8706, 40, 168)FUV(8709, 48, 168) + FUV(8710, 56, 168)FUV(8712, 64, 168)FUV(8719, 72, 168)FUV(8721, 80, 168)FUV(8722, 88, 168)FUV(8725, 96, 168)FUV(8729, 104, 168) + FUV(8730, 112, 168)FUV(8734, 120, 168)FUV(8735, 128, 168)FUV(8745, 136, 168)FUV(8747, 144, 168)FUV(8776, 152, 168)FUV(8800, 160, 168) + FUV(8801, 168, 168)FUV(8804, 176, 168)FUV(8805, 184, 168)FUV(8857, 192, 168)FUV(8960, 200, 168)FUV(8962, 208, 168)FUV(8976, 216, 168) + FUV(8992, 224, 168)FUV(8993, 232, 168)FUV(9472, 240, 168)FUV(9474, 248, 168)FUV(9484, 0, 176)FUV(9488, 8, 176)FUV(9492, 16, 176) + FUV(9496, 24, 176)FUV(9500, 32, 176)FUV(9508, 40, 176)FUV(9516, 48, 176)FUV(9524, 56, 176)FUV(9532, 64, 176)FUV(9552, 72, 176) + FUV(9553, 80, 176)FUV(9554, 88, 176)FUV(9555, 96, 176)FUV(9556, 104, 176)FUV(9557, 112, 176)FUV(9558, 120, 176)FUV(9559, 128, 176) + FUV(9560, 136, 176)FUV(9561, 144, 176)FUV(9562, 152, 176)FUV(9563, 160, 176)FUV(9564, 168, 176)FUV(9565, 176, 176)FUV(9566, 184, 176) + FUV(9567, 192, 176)FUV(9568, 200, 176)FUV(9569, 208, 176)FUV(9570, 216, 176)FUV(9571, 224, 176)FUV(9572, 232, 176)FUV(9573, 240, 176) + FUV(9574, 248, 176)FUV(9575, 0, 184)FUV(9576, 8, 184)FUV(9577, 16, 184)FUV(9578, 24, 184)FUV(9579, 32, 184)FUV(9580, 40, 184) + FUV(9600, 48, 184)FUV(9601, 56, 184)FUV(9604, 64, 184)FUV(9608, 72, 184)FUV(9612, 80, 184)FUV(9616, 88, 184)FUV(9617, 96, 184) + FUV(9618, 104, 184)FUV(9619, 112, 184)FUV(9632, 120, 184)FUV(9633, 128, 184)FUV(9642, 136, 184)FUV(9643, 144, 184)FUV(9644, 152, 184) + FUV(9650, 160, 184)FUV(9658, 168, 184)FUV(9660, 176, 184)FUV(9668, 184, 184)FUV(9674, 192, 184)FUV(9675, 200, 184)FUV(9679, 208, 184) + FUV(9688, 216, 184)FUV(9689, 224, 184)FUV(9702, 232, 184)FUV(9786, 240, 184)FUV(9787, 248, 184)FUV(9788, 0, 192)FUV(9792, 8, 192) + FUV(9794, 16, 192)FUV(9824, 24, 192)FUV(9827, 32, 192)FUV(9829, 40, 192)FUV(9830, 48, 192)FUV(9834, 56, 192)FUV(9835, 64, 192) + FUV(10003, 72, 192)FUV(64257, 80, 192)FUV(64258, 88, 192)FUV(65533, 96, 192) default: *x = 96; *y = 192; break; + } +} + +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; + + //Yer touching private things king! + //private: + wchar_t *m_Glyphs = nullptr; + short *m_Colours = nullptr; + +private: + 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 +{ + uint32_t m_ColourPalette[16] = // 0xAABBGGRR + { + 0xFF000000, // BLACK + 0xFF800000, // DARK_BLUE + 0xFF008000, // DARK_GREEN + 0xFF808000, // DARK_CYAN + 0xFF000080, // DARK_RED + 0xFF800080, // DARK_MAGENTA + 0xFF008080, // DARK_YELLOW + 0xFFC0C0C0, // GREY + 0xFF808080, // DARK_GREY + 0xFFFF0000, // BLUE + 0xFF00FF00, // GREEN + 0xFFFFFF00, // CYAN + 0xFF0000FF, // RED + 0xFFFF00FF, // MAGENTA + 0xFF00FFFF, // YELLOW + 0xFFFFFFFF // WHITE + }; + + void UpdateMousePosition(int x, int y) + { + float fx = (x - m_fDrawOffsetX) / (m_nScreenWidth * m_nFontWidth * m_fDrawScale); + float fy = (y - m_fDrawOffsetY) / (m_nScreenHeight * m_nFontHeight * m_fDrawScale); + + fx = fx < 0 ? 0 : fx > 1.0f ? 1.0f : fx; + fy = fy < 0 ? 0 : fy > 1.0f ? 1.0f : fy; + + m_mousePosX = (int)(fx * m_nScreenWidth); + m_mousePosY = (int)(fy * m_nScreenHeight); + } + + void ToggleFullscreen(HWND hWnd) + { + static WINDOWPLACEMENT prev = { sizeof(WINDOWPLACEMENT) }; + + DWORD style = GetWindowLong(hWnd, GWL_STYLE); + + if (style & WS_OVERLAPPEDWINDOW) + { + int width = GetSystemMetrics(SM_CXSCREEN); + int height = GetSystemMetrics(SM_CYSCREEN); + + if (GetWindowPlacement(hWnd, &prev)) + { + SetWindowLong(hWnd, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); + SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + } + else + { + SetWindowLong(hWnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); + SetWindowPlacement(hWnd, &prev); + SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + } + + void WindowResize(void) + { + DWORD style = GetWindowLong(m_hWnd, GWL_STYLE); + DWORD stylex = GetWindowLong(m_hWnd, GWL_EXSTYLE); + + RECT rWndRect = { 0, 0, m_nWindowWidth, m_nWindowHeight }; + + if (style & WS_OVERLAPPEDWINDOW) + { + AdjustWindowRectEx(&rWndRect, style, FALSE, stylex); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + SetWindowPos(m_hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); + } + else + { + ToggleFullscreen(m_hWnd); + + AdjustWindowRectEx(&rWndRect, style, FALSE, stylex); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + SetWindowPos(m_hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); + + ToggleFullscreen(m_hWnd); + } + + } + + void WindowUpdateScale(void) + { + int width = m_nWindowWidth; + int height = m_nWindowHeight; + + glViewport(0, 0, width, height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, height, 0, -1, 100); + glMatrixMode(GL_MODELVIEW); + + float scaleX = (float)width / (float)(m_nScreenWidth * m_nFontWidth); + float scaleY = (float)height / (float)(m_nScreenHeight * m_nFontHeight); + + if (scaleX < scaleY) + { + m_fDrawScale = scaleX; + m_fDrawOffsetX = 0; + m_fDrawOffsetY = ((float)height - (float)(m_nScreenHeight * m_nFontHeight * scaleX)) * 0.5f; + } + else + { + m_fDrawScale = scaleY; + m_fDrawOffsetX = ((float)width - (float)(m_nScreenWidth * m_nFontWidth * scaleY)) * 0.5f; + m_fDrawOffsetY = 0; + } + } + + int SetPixelFormatGL(void) + { + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = ChoosePixelFormat(m_hDevCtx, &pfd); + if (!pf) return 0; + + return SetPixelFormat(m_hDevCtx, pf, &pfd); + } + + static + LRESULT CALLBACK olcWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static olcConsoleGameEngine *cge; + + + switch (uMsg) + { + case WM_CREATE: + cge = static_cast(((LPCREATESTRUCT)lParam)->lpCreateParams); + + cge->m_hDevCtx = GetDC(hWnd); + if (!cge->SetPixelFormatGL()) return -1; + + cge->m_hRenCtx = wglCreateContext(cge->m_hDevCtx); + if (!cge->m_hRenCtx) return -1; + + wglMakeCurrent(cge->m_hDevCtx, cge->m_hRenCtx); + ShowWindow(cge->m_hConsole, SW_HIDE); + return 0; + + case WM_SYSCHAR: + //ding ding ding + return 0; + + case WM_MOUSEMOVE: + cge->UpdateMousePosition(LOWORD(lParam), HIWORD(lParam)); + return 0; + + case WM_SIZE: + cge->m_nWindowWidth = LOWORD(lParam); + cge->m_nWindowHeight = HIWORD(lParam); + cge->m_bDoWindowUpdate = true; + return 0; + + case WM_SETFOCUS: + cge->m_bConsoleInFocus = true; + return 0; + + case WM_KILLFOCUS: + cge->m_bConsoleInFocus = false; + return 0; + + case WM_CLOSE: + m_bAtomActive = false; + return 0; + + case WM_DESTROY: + ShowWindow(cge->m_hConsole, SW_SHOW); + PostQuitMessage(0); + return 0; + + case 0x8000: + cge->ToggleFullscreen(hWnd); + return 0; + + case 0x8001: + cge->WindowResize(); + return 0; + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + HWND ConstructWindow(int width, int height) + { + wchar_t wnd_title[] = L"OneLoneCoder.com - Console Game Engine (OGL)"; + wchar_t wnd_class[] = L"OLC_CONSOLE_GAME_ENGINE_CLASS"; + + HINSTANCE hInstance = GetModuleHandle(NULL); + + WNDCLASS wc = + { + CS_HREDRAW | CS_VREDRAW | CS_OWNDC, + olcWndProc, + 0, + 0, + hInstance, + LoadIcon(NULL, IDI_APPLICATION), + LoadCursor(NULL, IDC_ARROW), + NULL, + NULL, + wnd_class + }; + + RegisterClass(&wc); + + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; + DWORD dwStyle = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_VISIBLE; + RECT rWndRect = { 0, 0, width, height }; + + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + + width = rWndRect.right - rWndRect.left; + height = rWndRect.bottom - rWndRect.top; + + return CreateWindowEx(dwExStyle, wnd_class, wnd_title, dwStyle, + CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hInstance, this); + } + +public: + olcConsoleGameEngine() + { + m_nScreenWidth = 80; + m_nScreenHeight = 30; + + 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_bEnableSound = false; + + m_sAppName = L"Default"; + + //grab 1 GB or memory + m_bufMemory = (uint8_t*)VirtualAlloc(NULL, 1024 * 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!m_bufMemory) throw exception("No Memory!"); + + m_bufScreen = (CHAR_INFO*)&m_bufMemory[0]; + m_bufScreen_old = (CHAR_INFO*)&m_bufMemory[21474304]; + + m_fVertexArray = (float*)&m_bufMemory[42948608]; + m_fTexCoordArray = (float*)&m_bufMemory[300640256]; + m_uIndicesArray = (uint32_t*)&m_bufMemory[558331904]; + + m_uForegroundColorArray = (uint32_t*)&m_bufMemory[816023552]; + m_uBackgroundColorArray = (uint32_t*)&m_bufMemory[944869376]; + + m_hConsole = GetConsoleWindow(); + } + + void EnableSound() + { + m_bEnableSound = true; + } + + int ConstructConsole(int width, int height, int fontw, int fonth) + { + m_nScreenWidth = width; + m_nScreenHeight = height; + + m_nFontWidth = fontw; + m_nFontHeight = fonth; + + int newWndWidth = width * fontw; + int newWndHeight = height * fonth; + + if (m_hWnd && ((m_nWindowWidth != newWndWidth) || (m_nWindowHeight != newWndHeight))) + { + SendMessage(m_hWnd, 0x8001, 0, 0); + } + + m_nWindowWidth = newWndWidth; + m_nWindowHeight = newWndHeight; + + // Allocate memory for screen buffer + size_t bufLen = m_nScreenWidth * m_nScreenHeight; + + if (bufLen > 0x51EB00) + { + MessageBoxA(NULL, "Not enough memory!", "ERROR!", MB_OK); + ExitProcess(0xDEADC0DE); + } + + memset(m_bufMemory, 0, bufLen * 200); + + for (int y = 0; y < m_nScreenHeight; y++) + for (int x = 0; x < m_nScreenWidth; x++) + { + int pos = y * m_nScreenWidth + x; + + float x1 = (float)(x); + float y1 = (float)(y); + float x2 = (float)(x + 1); + float y2 = (float)(y + 1); + + pos *= 12; + + m_fVertexArray[pos + 0] = x1; + m_fVertexArray[pos + 1] = y1; + m_fVertexArray[pos + 2] = x2; + m_fVertexArray[pos + 3] = y1; + m_fVertexArray[pos + 4] = x1; + m_fVertexArray[pos + 5] = y2; + m_fVertexArray[pos + 6] = x2; + m_fVertexArray[pos + 7] = y1; + m_fVertexArray[pos + 8] = x1; + m_fVertexArray[pos + 9] = y2; + m_fVertexArray[pos + 10] = x2; + m_fVertexArray[pos + 11] = y2; + + m_uIndicesArray[pos + 0] = pos + 0; + m_uIndicesArray[pos + 1] = pos + 1; + m_uIndicesArray[pos + 2] = pos + 2; + m_uIndicesArray[pos + 3] = pos + 3; + m_uIndicesArray[pos + 4] = pos + 4; + m_uIndicesArray[pos + 5] = pos + 5; + m_uIndicesArray[pos + 6] = pos + 6; + m_uIndicesArray[pos + 7] = pos + 7; + m_uIndicesArray[pos + 8] = pos + 8; + m_uIndicesArray[pos + 9] = pos + 9; + m_uIndicesArray[pos + 10] = pos + 10; + m_uIndicesArray[pos + 11] = pos + 11; + } + + 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; 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); + } + } + } + + 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 DrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short 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, short 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= 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 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= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i= 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 (maxxy3) return; + } + } + + 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 std::vector> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE, short c = PIXEL_SOLID) + { + // pair.first = x coordinate + // pair.second = y coordinate + + // Create translated model vector of coordinate pairs + vector> 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, c, col); + } + } + + ~olcConsoleGameEngine() + { + if (m_bufMemory) VirtualFree(m_bufMemory, 0, MEM_RELEASE); + + m_bufMemory = nullptr; + + m_bufScreen = nullptr; + m_bufScreen_old = nullptr; + + m_fVertexArray = nullptr; + m_fTexCoordArray = nullptr; + m_uIndicesArray = nullptr; + + m_uForegroundColorArray = nullptr; + m_uBackgroundColorArray = nullptr; + } + + void GenerateMipmapPow2(uint8_t *tex_new, uint8_t *tex_old, uint8_t *ref_alpha, int size) + { + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + { + uint8_t *p0 = &tex_old[(y << 1) * (size << 1) + (x << 1)]; + uint8_t *p1 = &p0[(size << 1)]; + + tex_new[y * size + x] = (p0[0] + p0[1] + p1[0] + p1[1]) >> 2; + } + + int char_size = size >> 5; + + for (int i = 0; i < 1024; i++) + { + int alpha = 0; + + int posy = (i >> 5) * char_size; + int posx = (i & 0x1F) * char_size; + + for (int y = 0; y < char_size; y++) + for (int x = 0; x < char_size; x++) + { + alpha += tex_new[(posy + y) * size + (posx + x)]; + } + + alpha /= char_size * char_size; + + float factor = (float)ref_alpha[i] / (float)alpha; + + for (int y = 0; y < char_size; y++) + for (int x = 0; x < char_size; x++) + { + int value = (int)((float)(tex_new[(posy + y) * size + (posx + x)]) * factor); + tex_new[(posy + y) * size + (posx + x)] = value > 255 ? 255 : value; + } + } + + } + +public: + void Start() + { + m_bAtomActive = true; + + m_hWnd = ConstructWindow(m_nWindowWidth, m_nWindowHeight); + if (!m_hWnd) + { + Error(L"Could not create GL window"); + return; + } + + glGenTextures(1, &m_uFontTexture); + glBindTexture(GL_TEXTURE_2D, m_uFontTexture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); + + //mipmap generation + { + font_decode_custom_base64(); // fill pxplus_ibm_cga + + glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY, 256, 256, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pxplus_ibm_cga); + + uint8_t *glyph_alpha = new uint8_t[1024]; // 32 * 32 + + for (int i = 0; i < 1024; i++) + { + int alpha = 0; + + int posy = (i >> 5) << 3; + int posx = (i & 0x1F) << 3; + + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) + { + alpha += pxplus_ibm_cga[(posy + y) * 256 + (posx + x)]; + } + + glyph_alpha[i] = (uint8_t)(alpha >> 6); + } + + int texsize = 128; + uint8_t *texbuf = new uint8_t[texsize * texsize]; + + GenerateMipmapPow2(texbuf, pxplus_ibm_cga, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 1, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 64; + GenerateMipmapPow2(texbuf, texbuf, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 2, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 32; + GenerateMipmapPow2(texbuf, texbuf, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 3, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 16; + for (int level = 4; level < 9; level++) + { + for (int y = 0; y < texsize; y++) + for (int x = 0; x < texsize; x++) + { + uint8_t *p0 = &texbuf[(y << 1) * (texsize << 1) + (x << 1)]; + uint8_t *p1 = &p0[texsize << 1]; + + texbuf[y * texsize + x] = (p0[0] + p0[1] + p1[0] + p1[1]) >> 2; + } + + glTexImage2D(GL_TEXTURE_2D, level, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + texsize >>= 1; + } + + texsize = 1; + for (int level = 9; level < 1000; level++) + { + glTexImage2D(GL_TEXTURE_2D, level, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + } + + delete[] texbuf; + delete[] glyph_alpha; + } + + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + wglSwapInterval(0); + + glClearColor(0.1f, 0.1f, 0.1f, 0.0f); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + wglMakeCurrent(NULL, NULL); + + // Star the thread + thread t = thread(&olcConsoleGameEngine::GameThread, this); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // Wait for thread to be exited + t.join(); + } + + int ScreenWidth() + { + return m_nScreenWidth; + } + + int ScreenHeight() + { + return m_nScreenHeight; + } + +private: + void GameThread() + { + wglMakeCurrent(m_hDevCtx, m_hRenCtx); + + // Create user resources as part of this thread + if (!OnUserCreate()) + m_bAtomActive = false; + + // Check if sound system should be enabled + if (m_bEnableSound) + { + if (!CreateAudio()) + { + m_bAtomActive = false; // Failed to create audio system + m_bEnableSound = false; + } + } + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(2, GL_FLOAT, 0, m_fVertexArray); + glTexCoordPointer(2, GL_FLOAT, 0, m_fTexCoordArray); + + LARGE_INTEGER timeFreq, timeNew, timeOld; + QueryPerformanceFrequency(&timeFreq); + QueryPerformanceCounter(&timeOld); + QueryPerformanceCounter(&timeNew); + + int nFrameCounter = 0; + float fFrameTimeAccum = 0; + + while (m_bAtomActive) + { + // Run as fast as possible + while (m_bAtomActive) + { + QueryPerformanceCounter(&timeNew); + float fElapsedTime = (float)((timeNew.QuadPart - timeOld.QuadPart) / (double)timeFreq.QuadPart); + timeOld = timeNew; + + for (int i = 0; i < 256; i++) + { + m_keyNewState[i] = GetAsyncKeyState(i) >> 15; + + m_keys[i].bPressed = false; + m_keys[i].bReleased = false; + + if (m_keyNewState[i] != m_keyOldState[i]) + { + if (m_keyNewState[i]) + { + m_keys[i].bPressed = true; + m_keys[i].bHeld = true; + } + else + { + m_keys[i].bReleased = true; + m_keys[i].bHeld = false; + } + } + + m_keyOldState[i] = m_keyNewState[i]; + } + + m_mouse[0x00] = m_keys[VK_LBUTTON]; + m_mouse[0x01] = m_keys[VK_RBUTTON]; + m_mouse[0x02] = m_keys[VK_MBUTTON]; + m_mouse[0x03] = m_keys[0x05]; // VK_XBUTTON1 + m_mouse[0x04] = m_keys[0x06]; // VK_XBUTTON2 + + if (m_keys[VK_MENU].bHeld && m_keys[VK_RETURN].bPressed) + { + SendMessage(m_hWnd, 0x8000, 0, 0); + } + + if (m_bDoWindowUpdate) + { + WindowUpdateScale(); + m_bDoWindowUpdate = false; + } + + glClear(GL_COLOR_BUFFER_BIT); + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + { + m_bAtomActive = false; + break; + } + + // draw the things + glPushMatrix(); + glTranslatef(m_fDrawOffsetX, m_fDrawOffsetY, 0.0f); + glScalef(m_fDrawScale * m_nFontWidth, m_fDrawScale * m_nFontHeight, 1.0f); + + for (int y = 0; y < m_nScreenHeight; y++) + for (int x = 0; x < m_nScreenWidth; x++) + { + int pos = y * m_nScreenWidth + x; + + if ((m_bufScreen[pos].Char.UnicodeChar == m_bufScreen_old[pos].Char.UnicodeChar) && + (m_bufScreen[pos].Attributes == m_bufScreen_old[pos].Attributes)) + continue; + + m_bufScreen_old[pos] = m_bufScreen[pos]; + + WCHAR id = m_bufScreen[pos].Char.UnicodeChar; + WORD col = m_bufScreen[pos].Attributes; + + int u, v; + float u1, v1, u2, v2; + uint32_t fg, bg; + + if (id == L' ') + { + u1 = u2 = v1 = v2 = 0.0f; + fg = bg = 0; + } + else + { + GetFontCoords(id, &u, &v); + + u1 = (u) / 256.0f; + v1 = (v) / 256.0f; + u2 = (u + 8) / 256.0f; + v2 = (v + 8) / 256.0f; + + fg = m_ColourPalette[col & 0xF]; + bg = m_ColourPalette[(col >> 4) & 0xF]; + } + + pos *= 6; + + m_uForegroundColorArray[pos + 0] = fg; + m_uForegroundColorArray[pos + 1] = fg; + m_uForegroundColorArray[pos + 2] = fg; + m_uForegroundColorArray[pos + 3] = fg; + m_uForegroundColorArray[pos + 4] = fg; + m_uForegroundColorArray[pos + 5] = fg; + + m_uBackgroundColorArray[pos + 0] = bg; + m_uBackgroundColorArray[pos + 1] = bg; + m_uBackgroundColorArray[pos + 2] = bg; + m_uBackgroundColorArray[pos + 3] = bg; + m_uBackgroundColorArray[pos + 4] = bg; + m_uBackgroundColorArray[pos + 5] = bg; + + pos *= 2; + + m_fTexCoordArray[pos + 0] = u1; + m_fTexCoordArray[pos + 1] = v1; + m_fTexCoordArray[pos + 2] = u2; + m_fTexCoordArray[pos + 3] = v1; + m_fTexCoordArray[pos + 4] = u1; + m_fTexCoordArray[pos + 5] = v2; + m_fTexCoordArray[pos + 6] = u2; + m_fTexCoordArray[pos + 7] = v1; + m_fTexCoordArray[pos + 8] = u1; + m_fTexCoordArray[pos + 9] = v2; + m_fTexCoordArray[pos + 10] = u2; + m_fTexCoordArray[pos + 11] = v2; + } + + glColorPointer(4, GL_UNSIGNED_BYTE, 0, m_uBackgroundColorArray); + glDrawArrays(GL_TRIANGLES, 0, m_nScreenWidth * m_nScreenHeight * 6); + //glDrawElements(GL_TRIANGLES, m_nScreenWidth * m_nScreenHeight * 6, GL_UNSIGNED_INT, m_uIndicesArray); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + glColorPointer(4, GL_UNSIGNED_BYTE, 0, m_uForegroundColorArray); + glDrawArrays(GL_TRIANGLES, 0, m_nScreenWidth * m_nScreenHeight * 6); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + glPopMatrix(); + + // Update Title & Present Screen Buffer + wchar_t sNewTitle[256]; + swprintf_s(sNewTitle, 256, L"OneLoneCoder.com - Console Game Engine (OGL) - %s - FPS: %3.2f", m_sAppName.c_str(), 1.0f / fElapsedTime); + SetWindowText(m_hWnd, sNewTitle); + + SwapBuffers(m_hDevCtx); + } + + if (m_bEnableSound) + { + // Close and Clean up audio system + } + + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + m_cvGameFinished.notify_one(); + } + else + { + // User denied destroy for some reason, so continue running + m_bAtomActive = true; + } + } + + PostMessage(m_hWnd, WM_DESTROY, 0, 0); + } + +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 buff1[256]; + wchar_t buff2[256]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, buff1, 256, NULL); + wsprintf(buff2, L"%s\n\n%s", msg, buff1); + MessageBox(NULL, buff2, L"ERROR", MB_ICONERROR | MB_OK); + return 0; + } + + protected: // Audio Engine ===================================================================== + + class olcAudioSample + { + public: + olcAudioSample() + { + + } + + olcAudioSample(std::wstring sWavFile) + { + // Load Wav file and convert to float format + FILE *f = nullptr; + _wfopen_s(&f, sWavFile.c_str(), L"rb"); + if (f == nullptr) + return; + + char dump[4]; + std::fread(&dump, sizeof(char), 4, f); // Read "RIFF" + if (strncmp(dump, "RIFF", 4) != 0) return; + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&dump, sizeof(char), 4, f); // Read "WAVE" + if (strncmp(dump, "WAVE", 4) != 0) return; + + // Read Wave description chunk + std::fread(&dump, sizeof(char), 4, f); // Read "fmt " + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&wavHeader, sizeof(WAVEFORMATEX) - 2, 1, f); // Read Wave Format Structure chunk + // Note the -2, because the structure has 2 bytes to indicate its own size + // which are not in the wav file + + // Just check if wave format is compatible with olcCGE + if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) + { + std::fclose(f); + return; + } + + // Search for audio data chunk + long nChunksize = 0; + std::fread(&dump, sizeof(char), 4, f); // Read chunk header + std::fread(&nChunksize, sizeof(long), 1, f); // Read chunk size + while (strncmp(dump, "data", 4) != 0) + { + // Not audio data, so just skip it + std::fseek(f, nChunksize, SEEK_CUR); + std::fread(&dump, sizeof(char), 4, f); + std::fread(&nChunksize, sizeof(long), 1, f); + } + + // Finally got to data, so read it all in and convert to float samples + nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); + nChannels = wavHeader.nChannels; + + // Create floating point buffer to hold audio sample + fSample = new float[nSamples * nChannels]; + float *pSample = fSample; + + // Read in audio data and normalise + for (long i = 0; i < nSamples; i++) + { + for (int c = 0; c < nChannels; c++) + { + short s = 0; + std::fread(&s, sizeof(short), 1, f); + *pSample = (float)s / (float)(MAXSHORT); + pSample++; + } + } + + // All done, flag sound as valid + std::fclose(f); + bSampleValid = true; + } + + WAVEFORMATEX wavHeader; + float *fSample = nullptr; + long nSamples = 0; + int nChannels = 0; + bool bSampleValid = false; + }; + + // This vector holds all loaded sound samples in memory + std::vector vecAudioSamples; + + // This structure represents a sound that is currently playing. It only + // holds the sound ID and where this instance of it is up to for its + // current playback + struct sCurrentlyPlayingSample + { + int nAudioSampleID = 0; + long nSamplePosition = 0; + bool bFinished = false; + bool bLoop = false; + }; + std::list listActiveSamples; + + // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID + // number is returned if successful, otherwise -1 + unsigned int LoadAudioSample(std::wstring sWavFile) + { + if (!m_bEnableSound) + return -1; + + olcAudioSample a(sWavFile); + if (a.bSampleValid) + { + vecAudioSamples.push_back(a); + return vecAudioSamples.size(); + } + else + return -1; + } + + // Add sample 'id' to the mixers sounds to play list + void PlaySample(int id, bool bLoop = false) + { + listActiveSamples.push_back({ id, 0, false, bLoop }); + } + + void StopSample(int id) + { + + } + + // The audio system uses by default a specific wave format + bool CreateAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, + unsigned int nBlocks = 8, unsigned int nBlockSamples = 512) + { + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_nBlockFree = m_nBlockCount; + m_nBlockCurrent = 0; + m_pBlockMemory = nullptr; + m_pWaveHeaders = nullptr; + + // Device is available + WAVEFORMATEX waveFormat; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nSamplesPerSec = m_nSampleRate; + waveFormat.wBitsPerSample = sizeof(short) * 8; + waveFormat.nChannels = m_nChannels; + waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + // Open Device if valid + if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)waveOutProcWrap, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK) + return DestroyAudio(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); + + m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; + if (m_pWaveHeaders == nullptr) + return DestroyAudio(); + ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); + + // Link headers to block memory + for (unsigned int n = 0; n < m_nBlockCount; n++) + { + m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); + m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); + } + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&olcConsoleGameEngine::AudioThread, this); + + // Start the ball rolling with the sound delivery thread + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + return true; + } + + // Stop and clean up audio system + bool DestroyAudio() + { + m_bAudioThreadActive = false; + return false; + } + + // Handler for soundcard request for more data + void waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2) + { + if (uMsg != WOM_DONE) return; + m_nBlockFree++; + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + } + + // Static wrapper for sound card handler + static void CALLBACK waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) + { + ((olcConsoleGameEngine*)dwInstance)->waveOutProc(hWaveOut, uMsg, dwParam1, dwParam2); + } + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void AudioThread() + { + m_fGlobalTime = 0.0f; + float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) + { + // Wait for block to become available + if (m_nBlockFree == 0) + { + std::unique_lock lm(m_muxBlockNotZero); + while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly + m_cvBlockNotZero.wait(lm); + } + + // Block is here, so use it + m_nBlockFree--; + + // Prepare block for processing + if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) + waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + + short nNewSample = 0; + int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; + nPreviousSample = nNewSample; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Send block to sound device + waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + m_nBlockCurrent++; + m_nBlockCurrent %= m_nBlockCount; + } + } + + // Overridden by user if they want to generate sound in real-time + virtual float onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep) + { + return 0.0f; + } + + // Overriden by user if they want to manipulate the sound before it is played + virtual float onUserSoundFilter(int nChannel, float fGlobalTime, float fSample) + { + return fSample; + } + + // The Sound Mixer - If the user wants to play many sounds simultaneously, and + // perhaps the same sound overlapping itself, then you need a mixer, which + // takes input from all sound sources for that audio frame. This mixer maintains + // a list of sound locations for all concurrently playing audio samples. Instead + // of duplicating audio data, we simply store the fact that a sound sample is in + // use and an offset into its sample data. As time progresses we update this offset + // until it is beyound the length of the sound sample it is attached to. At this + // point we remove the playing souind from the list. + // + // Additionally, the users application may want to generate sound instead of just + // playing audio clips (think a synthesizer for example) in whcih case we also + // provide an "onUser..." event to allow the user to return a sound for that point + // in time. + // + // Finally, before the sound is issued to the operating system for performing, the + // user gets one final chance to "filter" the sound, perhaps changing the volume + // or adding funky effects + float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) + { + // Accumulate sample for this channel + float fMixerSample = 0.0f; + + for (auto &s : listActiveSamples) + { + // Calculate sample position + s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); + + // If sample position is valid add to the mix + if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) + fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; + else + s.bFinished = true; // Else sound has completed + } + + // If sounds have completed then remove them + listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + + // The users application might be generating sound, so grab that if it exists + fMixerSample += onUserSoundSample(nChannel, fGlobalTime, fTimeStep); + + // Return the sample via an optional user override to filter the sound + return onUserSoundFilter(nChannel, fGlobalTime, fMixerSample); + } + + unsigned int m_nSampleRate; + unsigned int m_nChannels; + unsigned int m_nBlockCount; + unsigned int m_nBlockSamples; + unsigned int m_nBlockCurrent; + + short* m_pBlockMemory = nullptr; + WAVEHDR *m_pWaveHeaders = nullptr; + HWAVEOUT m_hwDevice = nullptr; + + std::thread m_AudioThread; + std::atomic m_bAudioThreadActive = false; + std::atomic m_nBlockFree = 0; + std::condition_variable m_cvBlockNotZero; + std::mutex m_muxBlockNotZero; + std::atomic m_fGlobalTime = 0.0f; + bool m_bEnableSound = false; + +protected: + int m_nScreenWidth; + int m_nScreenHeight; + int m_nWindowWidth; + int m_nWindowHeight; + int m_nFontWidth; + int m_nFontHeight; + float m_fDrawScale; + float m_fDrawOffsetX; + float m_fDrawOffsetY; + float *m_fVertexArray; + uint32_t *m_uIndicesArray; + uint32_t *m_uForegroundColorArray; + uint32_t *m_uBackgroundColorArray; + float *m_fTexCoordArray; + CHAR_INFO *m_bufScreen; + CHAR_INFO *m_bufScreen_old; + uint8_t *m_bufMemory; + wstring m_sAppName; + 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; + bool m_bDoWindowUpdate = false; + HWND m_hConsole = nullptr; + HWND m_hWnd = nullptr; + HDC m_hDevCtx = nullptr; + HGLRC m_hRenCtx = nullptr; + GLuint m_uFontTexture; + static atomic m_bAtomActive; + static condition_variable m_cvGameFinished; + static mutex m_muxGame; +}; + +atomic olcConsoleGameEngine::m_bAtomActive(false); +condition_variable olcConsoleGameEngine::m_cvGameFinished; +mutex olcConsoleGameEngine::m_muxGame; \ No newline at end of file diff --git a/olcConsoleGameEngineGLOOP.cpp b/olcConsoleGameEngineGLOOP.cpp new file mode 100644 index 0000000..ba3a71c --- /dev/null +++ b/olcConsoleGameEngineGLOOP.cpp @@ -0,0 +1,1626 @@ +/* +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 +Last Updated: 18/03/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 +*/ + +#include "olcConsoleGameEngineGLOOP.h" + +wglSwapInterval_t *wglSwapInterval; + +// Based on the PxPlus IBM CGA Font from +// "The Ultimate Oldschool PC Font Pack" http://int10h.org/oldschool-pc-fonts/ +// Rendered into an image (256x56) and converted to char array +// +// The Ultimate Oldschool PC Font Pack is licensed under a Creative Commons +// Attribution-ShareAlike 4.0 International License. +// +// You should have received a copy of the license along with this work. If +// not, see < http://creativecommons.org/licenses/by-sa/4.0/ >. +// +// (c) 2016 VileR +// +// (256x256x8) +unsigned char pxplus_ibm_cga[65536]; +char pxplus_ibm_cga_enc[8509 + 1] = +"0mWOkP0780et0q?qqedtH40@HHVtq106HP10000000001stOLm320q??33cIa?sqlOq3?0000000000200000000mPf1c1030H00e0P=0H000H03000000000e0000000003Hee>8PQ76m1cbS?s0H30sHk0hqS1hH63em13t=hNmV7?0hINhO?00HmlIsq36L0@Fsf<<0D50000000P?H3060mhNqmQ=kehHW=hsf16q=k?e30VQfmQ30003>mQ3S=6<0HSIWI036eP16Hk0he03dnf0m1>m0000mP30000>qP3eQhH0Hh<6<60t1eHhe60q10Ler30kHSlme17N0036e000L60Lmf0W100tmS70eV=0000V107L<30kHh<0eV3qLk7drfI00eKOe1" +"0L00706mS?0HP?m<3m00000000e17NmW" +"7snQONmW7snQ?SIS7emfOtql?trh300e3>0000000000000000000e17000P700000000000L0q3?q07LeQS70q320q32KmhH0e033l30=jF6Hm760m1Os?W?6m33smV7k00000P=0000<@00VQ13aQ1?KeQ7eH3mfH34kPfhakH<Omf<30S=hee6hqW7e00008002000000060ef1kq1LqmQ7hPl7Kq167ef<pLkIWnh?hqh??qlO[nfHSMkHS1e0" +"000000000000000000000e000000S10000000000hef?teh?hPeHhNqhm0pLkI0000?000Oe10P100P10000000000e0000000N0000000<00H0mlHOefOtm0?t<3?trh0ta7>e13Le130HS3HP176H03tPfk0a=00006600000e000000000000" +"00e03e1000000q77O00?>CeqOh103?qe3?qe7h" +"qe??qf?t<0H00000006IhL[sk0eHS=VISI6Kc=kHSfkHaf0?000000P5d:>kt1P1Htr3006H000HP160P10aQ1KaQ1Ka10" +"000000P1KaQ1KaQ1KaQ160006HP10H00KHe6KHe6K00000000He6KHe6KHe6KHP100P16H0060e66af66af6000000006af66af66af66af66afkt00et?0?00D5000" +"0000@0004200ett3POsQ1KaQ1Kar?00ip3e30AZ2000000HLetef1?qqte04mtthkpthkpt300tt0qT8BGqm30000?Oq?OkHVOWW97Uc=0a106a1et00er?0?00X>?k" +"eS?0mlOsrlH2nlerJCPttttttttd300tts3e300GfS=qHSOtt1?qIS@s=Ldkipe0000He6K00ettt0qT@TZqm3?smlt7P1LLHVOWW97IN>00006af60ttOd?0?0Ri50" +"0000s1@00042qeht33@Pt3000P1Ka1etttt3e300Z2000000000000000qtt0mWOHe3q8eQ=8edt0h7O33000000000000000000000000ekV1>2sq77> 0) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 1) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 2) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 3) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 4) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 5) & 1) * 0xFF; + } +} + +#define FUV(a,b,c) case a: *x=b; *y=c; break; + +void GetFontCoords(int id, int *x, int *y) { + switch (id) { + FUV(0, 0, 0)FUV(1, 8, 0)FUV(2, 16, 0)FUV(3, 24, 0)FUV(4, 32, 0)FUV(5, 40, 0)FUV(6, 48, 0) + FUV(7, 56, 0)FUV(8, 64, 0)FUV(9, 72, 0)FUV(10, 80, 0)FUV(11, 88, 0)FUV(12, 96, 0)FUV(13, 104, 0) + FUV(14, 112, 0)FUV(15, 120, 0)FUV(16, 128, 0)FUV(17, 136, 0)FUV(18, 144, 0)FUV(19, 152, 0)FUV(20, 160, 0) + FUV(21, 168, 0)FUV(22, 176, 0)FUV(23, 184, 0)FUV(24, 192, 0)FUV(25, 200, 0)FUV(26, 208, 0)FUV(27, 216, 0) + FUV(28, 224, 0)FUV(29, 232, 0)FUV(30, 240, 0)FUV(31, 248, 0)FUV(32, 0, 8)FUV(33, 8, 8)FUV(34, 16, 8) + FUV(35, 24, 8)FUV(36, 32, 8)FUV(37, 40, 8)FUV(38, 48, 8)FUV(39, 56, 8)FUV(40, 64, 8)FUV(41, 72, 8) + FUV(42, 80, 8)FUV(43, 88, 8)FUV(44, 96, 8)FUV(45, 104, 8)FUV(46, 112, 8)FUV(47, 120, 8)FUV(48, 128, 8) + FUV(49, 136, 8)FUV(50, 144, 8)FUV(51, 152, 8)FUV(52, 160, 8)FUV(53, 168, 8)FUV(54, 176, 8)FUV(55, 184, 8) + FUV(56, 192, 8)FUV(57, 200, 8)FUV(58, 208, 8)FUV(59, 216, 8)FUV(60, 224, 8)FUV(61, 232, 8)FUV(62, 240, 8) + FUV(63, 248, 8)FUV(64, 0, 16)FUV(65, 8, 16)FUV(66, 16, 16)FUV(67, 24, 16)FUV(68, 32, 16)FUV(69, 40, 16) + FUV(70, 48, 16)FUV(71, 56, 16)FUV(72, 64, 16)FUV(73, 72, 16)FUV(74, 80, 16)FUV(75, 88, 16)FUV(76, 96, 16) + FUV(77, 104, 16)FUV(78, 112, 16)FUV(79, 120, 16)FUV(80, 128, 16)FUV(81, 136, 16)FUV(82, 144, 16)FUV(83, 152, 16) + FUV(84, 160, 16)FUV(85, 168, 16)FUV(86, 176, 16)FUV(87, 184, 16)FUV(88, 192, 16)FUV(89, 200, 16)FUV(90, 208, 16) + FUV(91, 216, 16)FUV(92, 224, 16)FUV(93, 232, 16)FUV(94, 240, 16)FUV(95, 248, 16)FUV(96, 0, 24)FUV(97, 8, 24) + FUV(98, 16, 24)FUV(99, 24, 24)FUV(100, 32, 24)FUV(101, 40, 24)FUV(102, 48, 24)FUV(103, 56, 24)FUV(104, 64, 24) + FUV(105, 72, 24)FUV(106, 80, 24)FUV(107, 88, 24)FUV(108, 96, 24)FUV(109, 104, 24)FUV(110, 112, 24)FUV(111, 120, 24) + FUV(112, 128, 24)FUV(113, 136, 24)FUV(114, 144, 24)FUV(115, 152, 24)FUV(116, 160, 24)FUV(117, 168, 24)FUV(118, 176, 24) + FUV(119, 184, 24)FUV(120, 192, 24)FUV(121, 200, 24)FUV(122, 208, 24)FUV(123, 216, 24)FUV(124, 224, 24)FUV(125, 232, 24) + FUV(126, 240, 24)FUV(127, 248, 24)FUV(160, 0, 32)FUV(161, 8, 32)FUV(162, 16, 32)FUV(163, 24, 32)FUV(164, 32, 32) + FUV(165, 40, 32)FUV(166, 48, 32)FUV(167, 56, 32)FUV(168, 64, 32)FUV(169, 72, 32)FUV(170, 80, 32)FUV(171, 88, 32) + FUV(172, 96, 32)FUV(173, 104, 32)FUV(174, 112, 32)FUV(175, 120, 32)FUV(176, 128, 32)FUV(177, 136, 32)FUV(178, 144, 32) + FUV(179, 152, 32)FUV(180, 160, 32)FUV(181, 168, 32)FUV(182, 176, 32)FUV(183, 184, 32)FUV(184, 192, 32)FUV(185, 200, 32) + FUV(186, 208, 32)FUV(187, 216, 32)FUV(188, 224, 32)FUV(189, 232, 32)FUV(190, 240, 32)FUV(191, 248, 32)FUV(192, 0, 40) + FUV(193, 8, 40)FUV(194, 16, 40)FUV(195, 24, 40)FUV(196, 32, 40)FUV(197, 40, 40)FUV(198, 48, 40)FUV(199, 56, 40) + FUV(200, 64, 40)FUV(201, 72, 40)FUV(202, 80, 40)FUV(203, 88, 40)FUV(204, 96, 40)FUV(205, 104, 40)FUV(206, 112, 40) + FUV(207, 120, 40)FUV(208, 128, 40)FUV(209, 136, 40)FUV(210, 144, 40)FUV(211, 152, 40)FUV(212, 160, 40)FUV(213, 168, 40) + FUV(214, 176, 40)FUV(215, 184, 40)FUV(216, 192, 40)FUV(217, 200, 40)FUV(218, 208, 40)FUV(219, 216, 40)FUV(220, 224, 40) + FUV(221, 232, 40)FUV(222, 240, 40)FUV(223, 248, 40)FUV(224, 0, 48)FUV(225, 8, 48)FUV(226, 16, 48)FUV(227, 24, 48) + FUV(228, 32, 48)FUV(229, 40, 48)FUV(230, 48, 48)FUV(231, 56, 48)FUV(232, 64, 48)FUV(233, 72, 48)FUV(234, 80, 48) + FUV(235, 88, 48)FUV(236, 96, 48)FUV(237, 104, 48)FUV(238, 112, 48)FUV(239, 120, 48)FUV(240, 128, 48)FUV(241, 136, 48) + FUV(242, 144, 48)FUV(243, 152, 48)FUV(244, 160, 48)FUV(245, 168, 48)FUV(246, 176, 48)FUV(247, 184, 48)FUV(248, 192, 48) + FUV(249, 200, 48)FUV(250, 208, 48)FUV(251, 216, 48)FUV(252, 224, 48)FUV(253, 232, 48)FUV(254, 240, 48)FUV(255, 248, 48) + FUV(256, 0, 56)FUV(257, 8, 56)FUV(258, 16, 56)FUV(259, 24, 56)FUV(260, 32, 56)FUV(261, 40, 56)FUV(262, 48, 56) + FUV(263, 56, 56)FUV(264, 64, 56)FUV(265, 72, 56)FUV(266, 80, 56)FUV(267, 88, 56)FUV(268, 96, 56)FUV(269, 104, 56) + FUV(270, 112, 56)FUV(271, 120, 56)FUV(272, 128, 56)FUV(273, 136, 56)FUV(274, 144, 56)FUV(275, 152, 56)FUV(276, 160, 56) + FUV(277, 168, 56)FUV(278, 176, 56)FUV(279, 184, 56)FUV(280, 192, 56)FUV(281, 200, 56)FUV(282, 208, 56)FUV(283, 216, 56) + FUV(284, 224, 56)FUV(285, 232, 56)FUV(286, 240, 56)FUV(287, 248, 56)FUV(288, 0, 64)FUV(289, 8, 64)FUV(290, 16, 64) + FUV(291, 24, 64)FUV(292, 32, 64)FUV(293, 40, 64)FUV(294, 48, 64)FUV(295, 56, 64)FUV(296, 64, 64)FUV(297, 72, 64) + FUV(298, 80, 64)FUV(299, 88, 64)FUV(300, 96, 64)FUV(301, 104, 64)FUV(302, 112, 64)FUV(303, 120, 64)FUV(304, 128, 64) + FUV(305, 136, 64)FUV(306, 144, 64)FUV(307, 152, 64)FUV(308, 160, 64)FUV(309, 168, 64)FUV(310, 176, 64)FUV(311, 184, 64) + FUV(312, 192, 64)FUV(313, 200, 64)FUV(314, 208, 64)FUV(315, 216, 64)FUV(316, 224, 64)FUV(317, 232, 64)FUV(318, 240, 64) + FUV(319, 248, 64)FUV(320, 0, 72)FUV(321, 8, 72)FUV(322, 16, 72)FUV(323, 24, 72)FUV(324, 32, 72)FUV(325, 40, 72) + FUV(326, 48, 72)FUV(327, 56, 72)FUV(328, 64, 72)FUV(329, 72, 72)FUV(330, 80, 72)FUV(331, 88, 72)FUV(332, 96, 72) + FUV(333, 104, 72)FUV(334, 112, 72)FUV(335, 120, 72)FUV(336, 128, 72)FUV(337, 136, 72)FUV(338, 144, 72)FUV(339, 152, 72) + FUV(340, 160, 72)FUV(341, 168, 72)FUV(342, 176, 72)FUV(343, 184, 72)FUV(344, 192, 72)FUV(345, 200, 72)FUV(346, 208, 72) + FUV(347, 216, 72)FUV(348, 224, 72)FUV(349, 232, 72)FUV(350, 240, 72)FUV(351, 248, 72)FUV(352, 0, 80)FUV(353, 8, 80) + FUV(354, 16, 80)FUV(355, 24, 80)FUV(356, 32, 80)FUV(357, 40, 80)FUV(358, 48, 80)FUV(359, 56, 80)FUV(360, 64, 80) + FUV(361, 72, 80)FUV(362, 80, 80)FUV(363, 88, 80)FUV(364, 96, 80)FUV(365, 104, 80)FUV(366, 112, 80)FUV(367, 120, 80) + FUV(368, 128, 80)FUV(369, 136, 80)FUV(370, 144, 80)FUV(371, 152, 80)FUV(372, 160, 80)FUV(373, 168, 80)FUV(374, 176, 80) + FUV(375, 184, 80)FUV(376, 192, 80)FUV(377, 200, 80)FUV(378, 208, 80)FUV(379, 216, 80)FUV(380, 224, 80)FUV(381, 232, 80) + FUV(382, 240, 80)FUV(383, 248, 80)FUV(402, 0, 88)FUV(417, 8, 88)FUV(439, 16, 88)FUV(506, 24, 88)FUV(507, 32, 88) + FUV(508, 40, 88)FUV(509, 48, 88)FUV(510, 56, 88)FUV(511, 64, 88)FUV(536, 72, 88)FUV(537, 80, 88)FUV(538, 88, 88) + FUV(539, 96, 88)FUV(593, 104, 88)FUV(632, 112, 88)FUV(710, 120, 88)FUV(711, 128, 88)FUV(713, 136, 88)FUV(728, 144, 88) + FUV(729, 152, 88)FUV(730, 160, 88)FUV(731, 168, 88)FUV(732, 176, 88)FUV(733, 184, 88)FUV(894, 192, 88)FUV(900, 200, 88) + FUV(901, 208, 88)FUV(902, 216, 88)FUV(903, 224, 88)FUV(904, 232, 88)FUV(905, 240, 88)FUV(906, 248, 88)FUV(908, 0, 96) + FUV(910, 8, 96)FUV(911, 16, 96)FUV(912, 24, 96)FUV(913, 32, 96)FUV(914, 40, 96)FUV(915, 48, 96)FUV(916, 56, 96) + FUV(917, 64, 96)FUV(918, 72, 96)FUV(919, 80, 96)FUV(920, 88, 96)FUV(921, 96, 96)FUV(922, 104, 96)FUV(923, 112, 96) + FUV(924, 120, 96)FUV(925, 128, 96)FUV(926, 136, 96)FUV(927, 144, 96)FUV(928, 152, 96)FUV(929, 160, 96)FUV(931, 168, 96) + FUV(932, 176, 96)FUV(933, 184, 96)FUV(934, 192, 96)FUV(935, 200, 96)FUV(936, 208, 96)FUV(937, 216, 96)FUV(938, 224, 96) + FUV(939, 232, 96)FUV(940, 240, 96)FUV(941, 248, 96)FUV(942, 0, 104)FUV(943, 8, 104)FUV(944, 16, 104)FUV(945, 24, 104) + FUV(946, 32, 104)FUV(947, 40, 104)FUV(948, 48, 104)FUV(949, 56, 104)FUV(950, 64, 104)FUV(951, 72, 104)FUV(952, 80, 104) + FUV(953, 88, 104)FUV(954, 96, 104)FUV(955, 104, 104)FUV(956, 112, 104)FUV(957, 120, 104)FUV(958, 128, 104)FUV(959, 136, 104) + FUV(960, 144, 104)FUV(961, 152, 104)FUV(962, 160, 104)FUV(963, 168, 104)FUV(964, 176, 104)FUV(965, 184, 104)FUV(966, 192, 104) + FUV(967, 200, 104)FUV(968, 208, 104)FUV(969, 216, 104)FUV(970, 224, 104)FUV(971, 232, 104)FUV(972, 240, 104)FUV(973, 248, 104) + FUV(974, 0, 112)FUV(976, 8, 112)FUV(1012, 16, 112)FUV(1024, 24, 112)FUV(1025, 32, 112)FUV(1026, 40, 112)FUV(1027, 48, 112) + FUV(1028, 56, 112)FUV(1029, 64, 112)FUV(1030, 72, 112)FUV(1031, 80, 112)FUV(1032, 88, 112)FUV(1033, 96, 112)FUV(1034, 104, 112) + FUV(1035, 112, 112)FUV(1036, 120, 112)FUV(1037, 128, 112)FUV(1038, 136, 112)FUV(1039, 144, 112)FUV(1040, 152, 112)FUV(1041, 160, 112) + FUV(1042, 168, 112)FUV(1043, 176, 112)FUV(1044, 184, 112)FUV(1045, 192, 112)FUV(1046, 200, 112)FUV(1047, 208, 112)FUV(1048, 216, 112) + FUV(1049, 224, 112)FUV(1050, 232, 112)FUV(1051, 240, 112)FUV(1052, 248, 112)FUV(1053, 0, 120)FUV(1054, 8, 120)FUV(1055, 16, 120) + FUV(1056, 24, 120)FUV(1057, 32, 120)FUV(1058, 40, 120)FUV(1059, 48, 120)FUV(1060, 56, 120)FUV(1061, 64, 120)FUV(1062, 72, 120) + FUV(1063, 80, 120)FUV(1064, 88, 120)FUV(1065, 96, 120)FUV(1066, 104, 120)FUV(1067, 112, 120)FUV(1068, 120, 120)FUV(1069, 128, 120) + FUV(1070, 136, 120)FUV(1071, 144, 120)FUV(1072, 152, 120)FUV(1073, 160, 120)FUV(1074, 168, 120)FUV(1075, 176, 120)FUV(1076, 184, 120) + FUV(1077, 192, 120)FUV(1078, 200, 120)FUV(1079, 208, 120)FUV(1080, 216, 120)FUV(1081, 224, 120)FUV(1082, 232, 120)FUV(1083, 240, 120) + FUV(1084, 248, 120)FUV(1085, 0, 128)FUV(1086, 8, 128)FUV(1087, 16, 128)FUV(1088, 24, 128)FUV(1089, 32, 128)FUV(1090, 40, 128) + FUV(1091, 48, 128)FUV(1092, 56, 128)FUV(1093, 64, 128)FUV(1094, 72, 128)FUV(1095, 80, 128)FUV(1096, 88, 128)FUV(1097, 96, 128) + FUV(1098, 104, 128)FUV(1099, 112, 128)FUV(1100, 120, 128)FUV(1101, 128, 128)FUV(1102, 136, 128)FUV(1103, 144, 128)FUV(1104, 152, 128) + FUV(1105, 160, 128)FUV(1106, 168, 128)FUV(1107, 176, 128)FUV(1108, 184, 128)FUV(1109, 192, 128)FUV(1110, 200, 128)FUV(1111, 208, 128) + FUV(1112, 216, 128)FUV(1113, 224, 128)FUV(1114, 232, 128)FUV(1115, 240, 128)FUV(1116, 248, 128)FUV(1117, 0, 136)FUV(1118, 8, 136) + FUV(1119, 16, 136)FUV(1168, 24, 136)FUV(1169, 32, 136)FUV(1470, 40, 136)FUV(1488, 48, 136)FUV(1489, 56, 136)FUV(1490, 64, 136) + FUV(1491, 72, 136)FUV(1492, 80, 136)FUV(1493, 88, 136)FUV(1494, 96, 136)FUV(1495, 104, 136)FUV(1496, 112, 136)FUV(1497, 120, 136) + FUV(1498, 128, 136)FUV(1499, 136, 136)FUV(1500, 144, 136)FUV(1501, 152, 136)FUV(1502, 160, 136)FUV(1503, 168, 136)FUV(1504, 176, 136) + FUV(1505, 184, 136)FUV(1506, 192, 136)FUV(1507, 200, 136)FUV(1508, 208, 136)FUV(1509, 216, 136)FUV(1510, 224, 136)FUV(1511, 232, 136) + FUV(1512, 240, 136)FUV(1513, 248, 136)FUV(1514, 0, 144)FUV(1520, 8, 144)FUV(1521, 16, 144)FUV(1522, 24, 144)FUV(1523, 32, 144) + FUV(1524, 40, 144)FUV(7451, 48, 144)FUV(7462, 56, 144)FUV(7464, 64, 144)FUV(7808, 72, 144)FUV(7809, 80, 144)FUV(7810, 88, 144) + FUV(7811, 96, 144)FUV(7812, 104, 144)FUV(7813, 112, 144)FUV(7839, 120, 144)FUV(7922, 128, 144)FUV(7923, 136, 144)FUV(8208, 144, 144) + FUV(8210, 152, 144)FUV(8211, 160, 144)FUV(8212, 168, 144)FUV(8213, 176, 144)FUV(8215, 184, 144)FUV(8216, 192, 144)FUV(8217, 200, 144) + FUV(8218, 208, 144)FUV(8219, 216, 144)FUV(8220, 224, 144)FUV(8221, 232, 144)FUV(8222, 240, 144)FUV(8223, 248, 144)FUV(8224, 0, 152) + FUV(8225, 8, 152)FUV(8226, 16, 152)FUV(8230, 24, 152)FUV(8231, 32, 152)FUV(8240, 40, 152)FUV(8242, 48, 152)FUV(8243, 56, 152) + FUV(8245, 64, 152)FUV(8249, 72, 152)FUV(8250, 80, 152)FUV(8252, 88, 152)FUV(8254, 96, 152)FUV(8255, 104, 152)FUV(8256, 112, 152) + FUV(8260, 120, 152)FUV(8276, 128, 152)FUV(8308, 136, 152)FUV(8309, 144, 152)FUV(8310, 152, 152)FUV(8311, 160, 152)FUV(8312, 168, 152) + FUV(8313, 176, 152)FUV(8314, 184, 152)FUV(8315, 192, 152)FUV(8319, 200, 152)FUV(8321, 208, 152)FUV(8322, 216, 152)FUV(8323, 224, 152) + FUV(8324, 232, 152)FUV(8325, 240, 152)FUV(8326, 248, 152)FUV(8327, 0, 160)FUV(8328, 8, 160)FUV(8329, 16, 160)FUV(8330, 24, 160) + FUV(8331, 32, 160)FUV(8355, 40, 160)FUV(8356, 48, 160)FUV(8359, 56, 160)FUV(8362, 64, 160)FUV(8364, 72, 160)FUV(8453, 80, 160) + FUV(8467, 88, 160)FUV(8470, 96, 160)FUV(8482, 104, 160)FUV(8486, 112, 160)FUV(8494, 120, 160)FUV(8528, 128, 160)FUV(8529, 136, 160) + FUV(8531, 144, 160)FUV(8532, 152, 160)FUV(8533, 160, 160)FUV(8534, 168, 160)FUV(8535, 176, 160)FUV(8536, 184, 160)FUV(8537, 192, 160) + FUV(8538, 200, 160)FUV(8539, 208, 160)FUV(8540, 216, 160)FUV(8541, 224, 160)FUV(8542, 232, 160)FUV(8592, 240, 160)FUV(8593, 248, 160) + FUV(8594, 0, 168)FUV(8595, 8, 168)FUV(8596, 16, 168)FUV(8597, 24, 168)FUV(8616, 32, 168)FUV(8706, 40, 168)FUV(8709, 48, 168) + FUV(8710, 56, 168)FUV(8712, 64, 168)FUV(8719, 72, 168)FUV(8721, 80, 168)FUV(8722, 88, 168)FUV(8725, 96, 168)FUV(8729, 104, 168) + FUV(8730, 112, 168)FUV(8734, 120, 168)FUV(8735, 128, 168)FUV(8745, 136, 168)FUV(8747, 144, 168)FUV(8776, 152, 168)FUV(8800, 160, 168) + FUV(8801, 168, 168)FUV(8804, 176, 168)FUV(8805, 184, 168)FUV(8857, 192, 168)FUV(8960, 200, 168)FUV(8962, 208, 168)FUV(8976, 216, 168) + FUV(8992, 224, 168)FUV(8993, 232, 168)FUV(9472, 240, 168)FUV(9474, 248, 168)FUV(9484, 0, 176)FUV(9488, 8, 176)FUV(9492, 16, 176) + FUV(9496, 24, 176)FUV(9500, 32, 176)FUV(9508, 40, 176)FUV(9516, 48, 176)FUV(9524, 56, 176)FUV(9532, 64, 176)FUV(9552, 72, 176) + FUV(9553, 80, 176)FUV(9554, 88, 176)FUV(9555, 96, 176)FUV(9556, 104, 176)FUV(9557, 112, 176)FUV(9558, 120, 176)FUV(9559, 128, 176) + FUV(9560, 136, 176)FUV(9561, 144, 176)FUV(9562, 152, 176)FUV(9563, 160, 176)FUV(9564, 168, 176)FUV(9565, 176, 176)FUV(9566, 184, 176) + FUV(9567, 192, 176)FUV(9568, 200, 176)FUV(9569, 208, 176)FUV(9570, 216, 176)FUV(9571, 224, 176)FUV(9572, 232, 176)FUV(9573, 240, 176) + FUV(9574, 248, 176)FUV(9575, 0, 184)FUV(9576, 8, 184)FUV(9577, 16, 184)FUV(9578, 24, 184)FUV(9579, 32, 184)FUV(9580, 40, 184) + FUV(9600, 48, 184)FUV(9601, 56, 184)FUV(9604, 64, 184)FUV(9608, 72, 184)FUV(9612, 80, 184)FUV(9616, 88, 184)FUV(9617, 96, 184) + FUV(9618, 104, 184)FUV(9619, 112, 184)FUV(9632, 120, 184)FUV(9633, 128, 184)FUV(9642, 136, 184)FUV(9643, 144, 184)FUV(9644, 152, 184) + FUV(9650, 160, 184)FUV(9658, 168, 184)FUV(9660, 176, 184)FUV(9668, 184, 184)FUV(9674, 192, 184)FUV(9675, 200, 184)FUV(9679, 208, 184) + FUV(9688, 216, 184)FUV(9689, 224, 184)FUV(9702, 232, 184)FUV(9786, 240, 184)FUV(9787, 248, 184)FUV(9788, 0, 192)FUV(9792, 8, 192) + FUV(9794, 16, 192)FUV(9824, 24, 192)FUV(9827, 32, 192)FUV(9829, 40, 192)FUV(9830, 48, 192)FUV(9834, 56, 192)FUV(9835, 64, 192) + FUV(10003, 72, 192)FUV(64257, 80, 192)FUV(64258, 88, 192)FUV(65533, 96, 192) default: *x = 96; *y = 192; break; + } +} + + + +void olcConsoleGameEngineGLOOP::UpdateMousePosition(int x, int y) +{ + float fx = (x - m_fDrawOffsetX) / (m_nScreenWidth * m_nFontWidth * m_fDrawScale); + float fy = (y - m_fDrawOffsetY) / (m_nScreenHeight * m_nFontHeight * m_fDrawScale); + + fx = fx < 0 ? 0 : fx > 1.0f ? 1.0f : fx; + fy = fy < 0 ? 0 : fy > 1.0f ? 1.0f : fy; + + m_mousePosX = (int)(fx * m_nScreenWidth); + m_mousePosY = (int)(fy * m_nScreenHeight); +} + +void olcConsoleGameEngineGLOOP::ToggleFullscreen(HWND hWnd) +{ + static WINDOWPLACEMENT prev = { sizeof(WINDOWPLACEMENT) }; + + DWORD style = GetWindowLong(hWnd, GWL_STYLE); + + if (style & WS_OVERLAPPEDWINDOW) + { + int width = GetSystemMetrics(SM_CXSCREEN); + int height = GetSystemMetrics(SM_CYSCREEN); + + if (GetWindowPlacement(hWnd, &prev)) + { + SetWindowLong(hWnd, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); + SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + } + else + { + SetWindowLong(hWnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); + SetWindowPlacement(hWnd, &prev); + SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } +} + +void olcConsoleGameEngineGLOOP::WindowResize(void) +{ + DWORD style = GetWindowLong(m_hWnd, GWL_STYLE); + DWORD stylex = GetWindowLong(m_hWnd, GWL_EXSTYLE); + + RECT rWndRect = { 0, 0, m_nWindowWidth, m_nWindowHeight }; + + if (style & WS_OVERLAPPEDWINDOW) + { + AdjustWindowRectEx(&rWndRect, style, FALSE, stylex); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + SetWindowPos(m_hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); + } + else + { + ToggleFullscreen(m_hWnd); + + AdjustWindowRectEx(&rWndRect, style, FALSE, stylex); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + SetWindowPos(m_hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); + + ToggleFullscreen(m_hWnd); + } + +} + +void olcConsoleGameEngineGLOOP::WindowUpdateScale(void) +{ + int width = m_nWindowWidth; + int height = m_nWindowHeight; + + glViewport(0, 0, width, height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, height, 0, -1, 100); + glMatrixMode(GL_MODELVIEW); + + float scaleX = (float)width / (float)(m_nScreenWidth * m_nFontWidth); + float scaleY = (float)height / (float)(m_nScreenHeight * m_nFontHeight); + + if (scaleX < scaleY) + { + m_fDrawScale = scaleX; + m_fDrawOffsetX = 0; + m_fDrawOffsetY = ((float)height - (float)(m_nScreenHeight * m_nFontHeight * scaleX)) * 0.5f; + } + else + { + m_fDrawScale = scaleY; + m_fDrawOffsetX = ((float)width - (float)(m_nScreenWidth * m_nFontWidth * scaleY)) * 0.5f; + m_fDrawOffsetY = 0; + } +} + +int olcConsoleGameEngineGLOOP::SetPixelFormatGL(void) +{ + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = ChoosePixelFormat(m_hDevCtx, &pfd); + if (!pf) return 0; + + return SetPixelFormat(m_hDevCtx, pf, &pfd); +} + + +LRESULT CALLBACK olcConsoleGameEngineGLOOP::olcWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static olcConsoleGameEngineGLOOP *cge; + + + switch (uMsg) + { + case WM_CREATE: + cge = static_cast(((LPCREATESTRUCT)lParam)->lpCreateParams); + + cge->m_hDevCtx = GetDC(hWnd); + if (!cge->SetPixelFormatGL()) return -1; + + cge->m_hRenCtx = wglCreateContext(cge->m_hDevCtx); + if (!cge->m_hRenCtx) return -1; + + wglMakeCurrent(cge->m_hDevCtx, cge->m_hRenCtx); + ShowWindow(cge->m_hConsole, SW_HIDE); + return 0; + + case WM_SYSCHAR: + //ding ding ding + return 0; + + case WM_MOUSEMOVE: + cge->UpdateMousePosition(LOWORD(lParam), HIWORD(lParam)); + return 0; + + case WM_SIZE: + cge->m_nWindowWidth = LOWORD(lParam); + cge->m_nWindowHeight = HIWORD(lParam); + cge->m_bDoWindowUpdate = true; + return 0; + + case WM_SETFOCUS: + cge->m_bConsoleInFocus = true; + return 0; + + case WM_KILLFOCUS: + cge->m_bConsoleInFocus = false; + return 0; + + case WM_CLOSE: + m_bAtomActive = false; + return 0; + + case WM_DESTROY: + ShowWindow(cge->m_hConsole, SW_SHOW); + PostQuitMessage(0); + return 0; + + case 0x8000: + cge->ToggleFullscreen(hWnd); + return 0; + + case 0x8001: + cge->WindowResize(); + return 0; + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +HWND olcConsoleGameEngineGLOOP::ConstructWindow(int width, int height) +{ + wchar_t wnd_title[] = L"OneLoneCoder.com - Console Game Engine (OGL)"; + wchar_t wnd_class[] = L"OLC_CONSOLE_GAME_ENGINE_CLASS"; + + HINSTANCE hInstance = GetModuleHandle(NULL); + + WNDCLASS wc = + { + CS_HREDRAW | CS_VREDRAW | CS_OWNDC, + olcWndProc, + 0, + 0, + hInstance, + LoadIcon(NULL, IDI_APPLICATION), + LoadCursor(NULL, IDC_ARROW), + NULL, + NULL, + wnd_class + }; + + RegisterClass(&wc); + + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; + DWORD dwStyle = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_VISIBLE; + RECT rWndRect = { 0, 0, width, height }; + + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + + width = rWndRect.right - rWndRect.left; + height = rWndRect.bottom - rWndRect.top; + + return CreateWindowEx(dwExStyle, wnd_class, wnd_title, dwStyle, + CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hInstance, this); +} + + +olcConsoleGameEngineGLOOP::olcConsoleGameEngineGLOOP() +{ + m_nScreenWidth = 80; + m_nScreenHeight = 30; + + 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_bEnableSound = false; + + m_sAppName = L"Default"; + + //grab 1 GB or memory + m_bufMemory = (uint8_t*)VirtualAlloc(NULL, 1024 * 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!m_bufMemory) throw exception("No Memory!"); + + m_bufScreen = (CHAR_INFO*)&m_bufMemory[0]; + m_bufScreen_old = (CHAR_INFO*)&m_bufMemory[21474304]; + + m_fVertexArray = (float*)&m_bufMemory[42948608]; + m_fTexCoordArray = (float*)&m_bufMemory[300640256]; + m_uIndicesArray = (uint32_t*)&m_bufMemory[558331904]; + + m_uForegroundColorArray = (uint32_t*)&m_bufMemory[816023552]; + m_uBackgroundColorArray = (uint32_t*)&m_bufMemory[944869376]; + + m_hConsole = GetConsoleWindow(); +} + +void olcConsoleGameEngineGLOOP::EnableSound() +{ + m_bEnableSound = true; +} + +int olcConsoleGameEngineGLOOP::ConstructConsole(int width, int height, int fontw, int fonth) +{ + m_nScreenWidth = width; + m_nScreenHeight = height; + + m_nFontWidth = fontw; + m_nFontHeight = fonth; + + int newWndWidth = width * fontw; + int newWndHeight = height * fonth; + + if (m_hWnd && ((m_nWindowWidth != newWndWidth) || (m_nWindowHeight != newWndHeight))) + { + SendMessage(m_hWnd, 0x8001, 0, 0); + } + + m_nWindowWidth = newWndWidth; + m_nWindowHeight = newWndHeight; + + // Allocate memory for screen buffer + size_t bufLen = m_nScreenWidth * m_nScreenHeight; + + if (bufLen > 0x51EB00) + { + MessageBoxA(NULL, "Not enough memory!", "ERROR!", MB_OK); + ExitProcess(0xDEADC0DE); + } + + memset(m_bufMemory, 0, bufLen * 200); + + for (int y = 0; y < m_nScreenHeight; y++) + for (int x = 0; x < m_nScreenWidth; x++) + { + int pos = y * m_nScreenWidth + x; + + float x1 = (float)(x); + float y1 = (float)(y); + float x2 = (float)(x + 1); + float y2 = (float)(y + 1); + + pos *= 12; + + m_fVertexArray[pos + 0] = x1; + m_fVertexArray[pos + 1] = y1; + m_fVertexArray[pos + 2] = x2; + m_fVertexArray[pos + 3] = y1; + m_fVertexArray[pos + 4] = x1; + m_fVertexArray[pos + 5] = y2; + m_fVertexArray[pos + 6] = x2; + m_fVertexArray[pos + 7] = y1; + m_fVertexArray[pos + 8] = x1; + m_fVertexArray[pos + 9] = y2; + m_fVertexArray[pos + 10] = x2; + m_fVertexArray[pos + 11] = y2; + + m_uIndicesArray[pos + 0] = pos + 0; + m_uIndicesArray[pos + 1] = pos + 1; + m_uIndicesArray[pos + 2] = pos + 2; + m_uIndicesArray[pos + 3] = pos + 3; + m_uIndicesArray[pos + 4] = pos + 4; + m_uIndicesArray[pos + 5] = pos + 5; + m_uIndicesArray[pos + 6] = pos + 6; + m_uIndicesArray[pos + 7] = pos + 7; + m_uIndicesArray[pos + 8] = pos + 8; + m_uIndicesArray[pos + 9] = pos + 9; + m_uIndicesArray[pos + 10] = pos + 10; + m_uIndicesArray[pos + 11] = pos + 11; + } + + return 1; +} + +void olcConsoleGameEngineGLOOP::Draw(int x, int y, wchar_t c, short col) +{ + 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 olcConsoleGameEngineGLOOP::Fill(int x1, int y1, int x2, int y2, wchar_t c, short col) +{ + 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 olcConsoleGameEngineGLOOP::DrawString(int x, int y, wstring c, short col) +{ + 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 olcConsoleGameEngineGLOOP::DrawStringAlpha(int x, int y, wstring c, short col) +{ + 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 olcConsoleGameEngineGLOOP::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 olcConsoleGameEngineGLOOP::DrawLine(int x1, int y1, int x2, int y2, wchar_t c, short col) +{ + 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); + } + } +} + +void olcConsoleGameEngineGLOOP::DrawCircle(int xc, int yc, int r, wchar_t c, short col) +{ + 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 olcConsoleGameEngineGLOOP::DrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c, short col) +{ + 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 olcConsoleGameEngineGLOOP::FillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c, short col) +{ + 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= 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 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= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i= 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 (maxxy3) return; + } +} + +void olcConsoleGameEngineGLOOP::FillCircle(int xc, int yc, int r, wchar_t c, short col) +{ + // 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 olcConsoleGameEngineGLOOP::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 olcConsoleGameEngineGLOOP::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 olcConsoleGameEngineGLOOP::DrawWireFrameModel(const std::vector> &vecModelCoordinates, float x, float y, float r, float s, short col, short c) +{ + // pair.first = x coordinate + // pair.second = y coordinate + + // Create translated model vector of coordinate pairs + vector> 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, c, col); + } +} + +olcConsoleGameEngineGLOOP::~olcConsoleGameEngineGLOOP() +{ + if (m_bufMemory) VirtualFree(m_bufMemory, 0, MEM_RELEASE); + + m_bufMemory = nullptr; + + m_bufScreen = nullptr; + m_bufScreen_old = nullptr; + + m_fVertexArray = nullptr; + m_fTexCoordArray = nullptr; + m_uIndicesArray = nullptr; + + m_uForegroundColorArray = nullptr; + m_uBackgroundColorArray = nullptr; +} + +void olcConsoleGameEngineGLOOP::GenerateMipmapPow2(uint8_t *tex_new, uint8_t *tex_old, uint8_t *ref_alpha, int size) +{ + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + { + uint8_t *p0 = &tex_old[(y << 1) * (size << 1) + (x << 1)]; + uint8_t *p1 = &p0[(size << 1)]; + + tex_new[y * size + x] = (p0[0] + p0[1] + p1[0] + p1[1]) >> 2; + } + + int char_size = size >> 5; + + for (int i = 0; i < 1024; i++) + { + int alpha = 0; + + int posy = (i >> 5) * char_size; + int posx = (i & 0x1F) * char_size; + + for (int y = 0; y < char_size; y++) + for (int x = 0; x < char_size; x++) + { + alpha += tex_new[(posy + y) * size + (posx + x)]; + } + + alpha /= char_size * char_size; + + float factor = (float)ref_alpha[i] / (float)alpha; + + for (int y = 0; y < char_size; y++) + for (int x = 0; x < char_size; x++) + { + int value = (int)((float)(tex_new[(posy + y) * size + (posx + x)]) * factor); + tex_new[(posy + y) * size + (posx + x)] = value > 255 ? 255 : value; + } + } + +} + + +void olcConsoleGameEngineGLOOP::Start() +{ + m_bAtomActive = true; + + m_hWnd = ConstructWindow(m_nWindowWidth, m_nWindowHeight); + if (!m_hWnd) + { + Error(L"Could not create GL window"); + return; + } + + glGenTextures(1, &m_uFontTexture); + glBindTexture(GL_TEXTURE_2D, m_uFontTexture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); + + //mipmap generation + { + font_decode_custom_base64(); // fill pxplus_ibm_cga + + glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY, 256, 256, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pxplus_ibm_cga); + + uint8_t *glyph_alpha = new uint8_t[1024]; // 32 * 32 + + for (int i = 0; i < 1024; i++) + { + int alpha = 0; + + int posy = (i >> 5) << 3; + int posx = (i & 0x1F) << 3; + + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) + { + alpha += pxplus_ibm_cga[(posy + y) * 256 + (posx + x)]; + } + + glyph_alpha[i] = (uint8_t)(alpha >> 6); + } + + int texsize = 128; + uint8_t *texbuf = new uint8_t[texsize * texsize]; + + GenerateMipmapPow2(texbuf, pxplus_ibm_cga, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 1, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 64; + GenerateMipmapPow2(texbuf, texbuf, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 2, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 32; + GenerateMipmapPow2(texbuf, texbuf, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 3, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 16; + for (int level = 4; level < 9; level++) + { + for (int y = 0; y < texsize; y++) + for (int x = 0; x < texsize; x++) + { + uint8_t *p0 = &texbuf[(y << 1) * (texsize << 1) + (x << 1)]; + uint8_t *p1 = &p0[texsize << 1]; + + texbuf[y * texsize + x] = (p0[0] + p0[1] + p1[0] + p1[1]) >> 2; + } + + glTexImage2D(GL_TEXTURE_2D, level, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + texsize >>= 1; + } + + texsize = 1; + for (int level = 9; level < 1000; level++) + { + glTexImage2D(GL_TEXTURE_2D, level, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + } + + delete[] texbuf; + delete[] glyph_alpha; + } + + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + wglSwapInterval(0); + + glClearColor(0.1f, 0.1f, 0.1f, 0.0f); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + wglMakeCurrent(NULL, NULL); + + // Star the thread + thread t = thread(&olcConsoleGameEngineGLOOP::GameThread, this); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // Wait for thread to be exited + t.join(); +} + +void olcConsoleGameEngineGLOOP::GameThread() +{ + wglMakeCurrent(m_hDevCtx, m_hRenCtx); + + // Create user resources as part of this thread + if (!OnUserCreate()) + m_bAtomActive = false; + + // Check if sound system should be enabled + if (m_bEnableSound) + { + if (!CreateAudio()) + { + m_bAtomActive = false; // Failed to create audio system + m_bEnableSound = false; + } + } + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(2, GL_FLOAT, 0, m_fVertexArray); + glTexCoordPointer(2, GL_FLOAT, 0, m_fTexCoordArray); + + LARGE_INTEGER timeFreq, timeNew, timeOld; + QueryPerformanceFrequency(&timeFreq); + QueryPerformanceCounter(&timeOld); + QueryPerformanceCounter(&timeNew); + + int nFrameCounter = 0; + float fFrameTimeAccum = 0; + + while (m_bAtomActive) + { + // Run as fast as possible + while (m_bAtomActive) + { + QueryPerformanceCounter(&timeNew); + float fElapsedTime = (float)((timeNew.QuadPart - timeOld.QuadPart) / (double)timeFreq.QuadPart); + timeOld = timeNew; + + for (int i = 0; i < 256; i++) + { + m_keyNewState[i] = GetAsyncKeyState(i) >> 15; + + m_keys[i].bPressed = false; + m_keys[i].bReleased = false; + + if (m_keyNewState[i] != m_keyOldState[i]) + { + if (m_keyNewState[i]) + { + m_keys[i].bPressed = true; + m_keys[i].bHeld = true; + } + else + { + m_keys[i].bReleased = true; + m_keys[i].bHeld = false; + } + } + + m_keyOldState[i] = m_keyNewState[i]; + } + + m_mouse[0x00] = m_keys[VK_LBUTTON]; + m_mouse[0x01] = m_keys[VK_RBUTTON]; + m_mouse[0x02] = m_keys[VK_MBUTTON]; + m_mouse[0x03] = m_keys[0x05]; // VK_XBUTTON1 + m_mouse[0x04] = m_keys[0x06]; // VK_XBUTTON2 + + if (m_keys[VK_MENU].bHeld && m_keys[VK_RETURN].bPressed) + { + SendMessage(m_hWnd, 0x8000, 0, 0); + } + + if (m_bDoWindowUpdate) + { + WindowUpdateScale(); + m_bDoWindowUpdate = false; + } + + glClear(GL_COLOR_BUFFER_BIT); + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + { + m_bAtomActive = false; + break; + } + + // draw the things + glPushMatrix(); + glTranslatef(m_fDrawOffsetX, m_fDrawOffsetY, 0.0f); + glScalef(m_fDrawScale * m_nFontWidth, m_fDrawScale * m_nFontHeight, 1.0f); + + for (int y = 0; y < m_nScreenHeight; y++) + for (int x = 0; x < m_nScreenWidth; x++) + { + int pos = y * m_nScreenWidth + x; + + if ((m_bufScreen[pos].Char.UnicodeChar == m_bufScreen_old[pos].Char.UnicodeChar) && + (m_bufScreen[pos].Attributes == m_bufScreen_old[pos].Attributes)) + continue; + + m_bufScreen_old[pos] = m_bufScreen[pos]; + + WCHAR id = m_bufScreen[pos].Char.UnicodeChar; + WORD col = m_bufScreen[pos].Attributes; + + int u, v; + float u1, v1, u2, v2; + uint32_t fg, bg; + + if (id == L' ') + { + u1 = u2 = v1 = v2 = 0.0f; + fg = bg = 0; + } + else + { + GetFontCoords(id, &u, &v); + + u1 = (u) / 256.0f; + v1 = (v) / 256.0f; + u2 = (u + 8) / 256.0f; + v2 = (v + 8) / 256.0f; + + fg = m_ColourPalette[col & 0xF]; + bg = m_ColourPalette[(col >> 4) & 0xF]; + } + + pos *= 6; + + m_uForegroundColorArray[pos + 0] = fg; + m_uForegroundColorArray[pos + 1] = fg; + m_uForegroundColorArray[pos + 2] = fg; + m_uForegroundColorArray[pos + 3] = fg; + m_uForegroundColorArray[pos + 4] = fg; + m_uForegroundColorArray[pos + 5] = fg; + + m_uBackgroundColorArray[pos + 0] = bg; + m_uBackgroundColorArray[pos + 1] = bg; + m_uBackgroundColorArray[pos + 2] = bg; + m_uBackgroundColorArray[pos + 3] = bg; + m_uBackgroundColorArray[pos + 4] = bg; + m_uBackgroundColorArray[pos + 5] = bg; + + pos *= 2; + + m_fTexCoordArray[pos + 0] = u1; + m_fTexCoordArray[pos + 1] = v1; + m_fTexCoordArray[pos + 2] = u2; + m_fTexCoordArray[pos + 3] = v1; + m_fTexCoordArray[pos + 4] = u1; + m_fTexCoordArray[pos + 5] = v2; + m_fTexCoordArray[pos + 6] = u2; + m_fTexCoordArray[pos + 7] = v1; + m_fTexCoordArray[pos + 8] = u1; + m_fTexCoordArray[pos + 9] = v2; + m_fTexCoordArray[pos + 10] = u2; + m_fTexCoordArray[pos + 11] = v2; + } + + glColorPointer(4, GL_UNSIGNED_BYTE, 0, m_uBackgroundColorArray); + glDrawArrays(GL_TRIANGLES, 0, m_nScreenWidth * m_nScreenHeight * 6); + //glDrawElements(GL_TRIANGLES, m_nScreenWidth * m_nScreenHeight * 6, GL_UNSIGNED_INT, m_uIndicesArray); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + glColorPointer(4, GL_UNSIGNED_BYTE, 0, m_uForegroundColorArray); + glDrawArrays(GL_TRIANGLES, 0, m_nScreenWidth * m_nScreenHeight * 6); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + glPopMatrix(); + + // Update Title & Present Screen Buffer + wchar_t sNewTitle[256]; + swprintf_s(sNewTitle, 256, L"OneLoneCoder.com - Console Game Engine (OGL) - %s - FPS: %3.2f", m_sAppName.c_str(), 1.0f / fElapsedTime); + SetWindowText(m_hWnd, sNewTitle); + + SwapBuffers(m_hDevCtx); + } + + if (m_bEnableSound) + { + // Close and Clean up audio system + } + + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + m_cvGameFinished.notify_one(); + } + else + { + // User denied destroy for some reason, so continue running + m_bAtomActive = true; + } + } + + PostMessage(m_hWnd, WM_DESTROY, 0, 0); +} + + + +int olcConsoleGameEngineGLOOP::Error(const wchar_t *msg) +{ + wchar_t buff1[256]; + wchar_t buff2[256]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, buff1, 256, NULL); + wsprintf(buff2, L"%s\n\n%s", msg, buff1); + MessageBox(NULL, buff2, L"ERROR", MB_ICONERROR | MB_OK); + return 0; +} + + + +// Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID +// number is returned if successful, otherwise -1 +unsigned int olcConsoleGameEngineGLOOP::LoadAudioSample(std::wstring sWavFile) +{ + if (!m_bEnableSound) + return -1; + + olcAudioSample a(sWavFile); + if (a.bSampleValid) + { + vecAudioSamples.push_back(a); + return vecAudioSamples.size(); + } + else + return -1; +} + +// Add sample 'id' to the mixers sounds to play list +void olcConsoleGameEngineGLOOP::PlaySample(int id, bool bLoop) +{ + listActiveSamples.push_back({ id, 0, false, bLoop }); +} + +void olcConsoleGameEngineGLOOP::StopSample(int id) +{ + +} + +// The audio system uses by default a specific wave format +bool olcConsoleGameEngineGLOOP::CreateAudio(unsigned int nSampleRate, unsigned int nChannels, + unsigned int nBlocks, unsigned int nBlockSamples) +{ + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_nBlockFree = m_nBlockCount; + m_nBlockCurrent = 0; + m_pBlockMemory = nullptr; + m_pWaveHeaders = nullptr; + + // Device is available + WAVEFORMATEX waveFormat; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nSamplesPerSec = m_nSampleRate; + waveFormat.wBitsPerSample = sizeof(short) * 8; + waveFormat.nChannels = m_nChannels; + waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + // Open Device if valid + if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)waveOutProcWrap, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK) + return DestroyAudio(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); + + m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; + if (m_pWaveHeaders == nullptr) + return DestroyAudio(); + ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); + + // Link headers to block memory + for (unsigned int n = 0; n < m_nBlockCount; n++) + { + m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); + m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); + } + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&olcConsoleGameEngineGLOOP::AudioThread, this); + + // Start the ball rolling with the sound delivery thread + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + return true; +} + +// Stop and clean up audio system +bool olcConsoleGameEngineGLOOP::DestroyAudio() +{ + m_bAudioThreadActive = false; + return false; +} + +// Handler for soundcard request for more data +void olcConsoleGameEngineGLOOP::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2) +{ + if (uMsg != WOM_DONE) return; + m_nBlockFree++; + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); +} + +// Static wrapper for sound card handler +void CALLBACK olcConsoleGameEngineGLOOP::waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + ((olcConsoleGameEngineGLOOP*)dwInstance)->waveOutProc(hWaveOut, uMsg, dwParam1, dwParam2); +} + +// Audio thread. This loop responds to requests from the soundcard to fill 'blocks' +// with audio data. If no requests are available it goes dormant until the sound +// card is ready for more data. The block is fille by the "user" in some manner +// and then issued to the soundcard. +void olcConsoleGameEngineGLOOP::AudioThread() +{ + m_fGlobalTime = 0.0f; + float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) + { + // Wait for block to become available + if (m_nBlockFree == 0) + { + std::unique_lock lm(m_muxBlockNotZero); + while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly + m_cvBlockNotZero.wait(lm); + } + + // Block is here, so use it + m_nBlockFree--; + + // Prepare block for processing + if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) + waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + + short nNewSample = 0; + int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; + nPreviousSample = nNewSample; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Send block to sound device + waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + m_nBlockCurrent++; + m_nBlockCurrent %= m_nBlockCount; + } +} + +// Overridden by user if they want to generate sound in real-time +float olcConsoleGameEngineGLOOP::onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep) +{ + return 0.0f; +} + +// Overriden by user if they want to manipulate the sound before it is played +float olcConsoleGameEngineGLOOP::onUserSoundFilter(int nChannel, float fGlobalTime, float fSample) +{ + return fSample; +} + +// The Sound Mixer - If the user wants to play many sounds simultaneously, and +// perhaps the same sound overlapping itself, then you need a mixer, which +// takes input from all sound sources for that audio frame. This mixer maintains +// a list of sound locations for all concurrently playing audio samples. Instead +// of duplicating audio data, we simply store the fact that a sound sample is in +// use and an offset into its sample data. As time progresses we update this offset +// until it is beyound the length of the sound sample it is attached to. At this +// point we remove the playing souind from the list. +// +// Additionally, the users application may want to generate sound instead of just +// playing audio clips (think a synthesizer for example) in whcih case we also +// provide an "onUser..." event to allow the user to return a sound for that point +// in time. +// +// Finally, before the sound is issued to the operating system for performing, the +// user gets one final chance to "filter" the sound, perhaps changing the volume +// or adding funky effects +float olcConsoleGameEngineGLOOP::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) +{ + // Accumulate sample for this channel + float fMixerSample = 0.0f; + + for (auto &s : listActiveSamples) + { + // Calculate sample position + s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); + + // If sample position is valid add to the mix + if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) + fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; + else + s.bFinished = true; // Else sound has completed + } + + // If sounds have completed then remove them + listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + + // The users application might be generating sound, so grab that if it exists + fMixerSample += onUserSoundSample(nChannel, fGlobalTime, fTimeStep); + + // Return the sample via an optional user override to filter the sound + return onUserSoundFilter(nChannel, fGlobalTime, fMixerSample); +} + + +atomic olcConsoleGameEngineGLOOP::m_bAtomActive(false); +condition_variable olcConsoleGameEngineGLOOP::m_cvGameFinished; +mutex olcConsoleGameEngineGLOOP::m_muxGame; \ No newline at end of file diff --git a/olcConsoleGameEngineGLOOP.h b/olcConsoleGameEngineGLOOP.h new file mode 100644 index 0000000..f67d2f4 --- /dev/null +++ b/olcConsoleGameEngineGLOOP.h @@ -0,0 +1,619 @@ +/* +OneLoneCoder.com - Command Line Game Engine +"Who needs a frame buffer?" - @Javidx9 + +Mega Big Thanks to KrossX from the discord server for taking +the time to develop an OpenGL wrapper for the olcConsoleGameEngine. + +If you have had difficulty getting consoles to look correct it +could be due to your version of Windows. This version of the +console game engine is for you! + +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 + +Last Updated: 02/07/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 +#pragma message("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 + +#pragma comment(lib, "user32.lib") +#pragma comment(lib, "gdi32.lib") +#pragma comment(lib, "opengl32.lib") +#pragma comment(lib, "winmm.lib") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + + +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; + + //Yer touching private things king! + //private: + wchar_t *m_Glyphs = nullptr; + short *m_Colours = nullptr; + +private: + 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 olcConsoleGameEngineGLOOP +{ + // Mega Thanks to KrossX for completely converting the olcConsoleGameEngine to OpenGL! + uint32_t m_ColourPalette[16] = // 0xAABBGGRR + { + 0xFF000000, // BLACK + 0xFF800000, // DARK_BLUE + 0xFF008000, // DARK_GREEN + 0xFF808000, // DARK_CYAN + 0xFF000080, // DARK_RED + 0xFF800080, // DARK_MAGENTA + 0xFF008080, // DARK_YELLOW + 0xFFC0C0C0, // GREY + 0xFF808080, // DARK_GREY + 0xFFFF0000, // BLUE + 0xFF00FF00, // GREEN + 0xFFFFFF00, // CYAN + 0xFF0000FF, // RED + 0xFFFF00FF, // MAGENTA + 0xFF00FFFF, // YELLOW + 0xFFFFFFFF // WHITE + }; + + void UpdateMousePosition(int x, int y); + void ToggleFullscreen(HWND hWnd); + void WindowResize(void); + void WindowUpdateScale(void); + int SetPixelFormatGL(void); + static LRESULT CALLBACK olcWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + HWND ConstructWindow(int width, int height); + +public: + olcConsoleGameEngineGLOOP(); + int ConstructConsole(int width, int height, int fontw, int fonth); + virtual void Draw(int x, int y, wchar_t c = 0x2588, short col = 0x000F); + void Fill(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F); + void DrawString(int x, int y, wstring c, short col = 0x000F); + void DrawStringAlpha(int x, int y, wstring c, short col = 0x000F); + void Clip(int &x, int &y); + void DrawLine(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F); + void DrawCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F); + void DrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c = 0x2588, short col = 0x000F); + void FillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c = 0x2588, short col = 0x000F); + void FillCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F); + void DrawSprite(int x, int y, olcSprite *sprite); + void DrawPartialSprite(int x, int y, olcSprite *sprite, int ox, int oy, int w, int h); + void DrawWireFrameModel(const std::vector> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE, short c = PIXEL_SOLID); + ~olcConsoleGameEngineGLOOP(); + + + void GenerateMipmapPow2(uint8_t *tex_new, uint8_t *tex_old, uint8_t *ref_alpha, int size); + +public: + void Start(); + int ScreenWidth(){ return m_nScreenWidth; } + int ScreenHeight(){ return m_nScreenHeight; } + +private: + void GameThread(); + + +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); + + +protected: // Audio Engine ===================================================================== + + class olcAudioSample + { + public: + olcAudioSample() + { + + } + + olcAudioSample(std::wstring sWavFile) + { + // Load Wav file and convert to float format + FILE *f = nullptr; + _wfopen_s(&f, sWavFile.c_str(), L"rb"); + if (f == nullptr) + return; + + char dump[4]; + std::fread(&dump, sizeof(char), 4, f); // Read "RIFF" + if (strncmp(dump, "RIFF", 4) != 0) return; + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&dump, sizeof(char), 4, f); // Read "WAVE" + if (strncmp(dump, "WAVE", 4) != 0) return; + + // Read Wave description chunk + std::fread(&dump, sizeof(char), 4, f); // Read "fmt " + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&wavHeader, sizeof(WAVEFORMATEX) - 2, 1, f); // Read Wave Format Structure chunk + // Note the -2, because the structure has 2 bytes to indicate its own size + // which are not in the wav file + + // Just check if wave format is compatible with olcCGE + if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) + { + std::fclose(f); + return; + } + + // Search for audio data chunk + long nChunksize = 0; + std::fread(&dump, sizeof(char), 4, f); // Read chunk header + std::fread(&nChunksize, sizeof(long), 1, f); // Read chunk size + while (strncmp(dump, "data", 4) != 0) + { + // Not audio data, so just skip it + std::fseek(f, nChunksize, SEEK_CUR); + std::fread(&dump, sizeof(char), 4, f); + std::fread(&nChunksize, sizeof(long), 1, f); + } + + // Finally got to data, so read it all in and convert to float samples + nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); + nChannels = wavHeader.nChannels; + + // Create floating point buffer to hold audio sample + fSample = new float[nSamples * nChannels]; + float *pSample = fSample; + + // Read in audio data and normalise + for (long i = 0; i < nSamples; i++) + { + for (int c = 0; c < nChannels; c++) + { + short s = 0; + std::fread(&s, sizeof(short), 1, f); + *pSample = (float)s / (float)(MAXSHORT); + pSample++; + } + } + + // All done, flag sound as valid + std::fclose(f); + bSampleValid = true; + } + + WAVEFORMATEX wavHeader; + float *fSample = nullptr; + long nSamples = 0; + int nChannels = 0; + bool bSampleValid = false; + }; + + void EnableSound(); + + // This vector holds all loaded sound samples in memory + std::vector vecAudioSamples; + + // This structure represents a sound that is currently playing. It only + // holds the sound ID and where this instance of it is up to for its + // current playback + struct sCurrentlyPlayingSample + { + int nAudioSampleID = 0; + long nSamplePosition = 0; + bool bFinished = false; + bool bLoop = false; + }; + std::list listActiveSamples; + + // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID + // number is returned if successful, otherwise -1 + unsigned int LoadAudioSample(std::wstring sWavFile); + + // Add sample 'id' to the mixers sounds to play list + void PlaySample(int id, bool bLoop = false); + void StopSample(int id); + + // The audio system uses by default a specific wave format + bool CreateAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, + unsigned int nBlocks = 8, unsigned int nBlockSamples = 512); + + // Stop and clean up audio system + bool DestroyAudio(); + + // Handler for soundcard request for more data + void waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); + + // Static wrapper for sound card handler + static void CALLBACK waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void AudioThread(); + + // Overridden by user if they want to generate sound in real-time + virtual float onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep); + + + // Overriden by user if they want to manipulate the sound before it is played + virtual float onUserSoundFilter(int nChannel, float fGlobalTime, float fSample); + + + // The Sound Mixer - If the user wants to play many sounds simultaneously, and + // perhaps the same sound overlapping itself, then you need a mixer, which + // takes input from all sound sources for that audio frame. This mixer maintains + // a list of sound locations for all concurrently playing audio samples. Instead + // of duplicating audio data, we simply store the fact that a sound sample is in + // use and an offset into its sample data. As time progresses we update this offset + // until it is beyound the length of the sound sample it is attached to. At this + // point we remove the playing souind from the list. + // + // Additionally, the users application may want to generate sound instead of just + // playing audio clips (think a synthesizer for example) in whcih case we also + // provide an "onUser..." event to allow the user to return a sound for that point + // in time. + // + // Finally, before the sound is issued to the operating system for performing, the + // user gets one final chance to "filter" the sound, perhaps changing the volume + // or adding funky effects + float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep); + + + unsigned int m_nSampleRate; + unsigned int m_nChannels; + unsigned int m_nBlockCount; + unsigned int m_nBlockSamples; + unsigned int m_nBlockCurrent; + + short* m_pBlockMemory = nullptr; + WAVEHDR *m_pWaveHeaders = nullptr; + HWAVEOUT m_hwDevice = nullptr; + + std::thread m_AudioThread; + std::atomic m_bAudioThreadActive = false; + std::atomic m_nBlockFree = 0; + std::condition_variable m_cvBlockNotZero; + std::mutex m_muxBlockNotZero; + std::atomic m_fGlobalTime = 0.0f; + bool m_bEnableSound = false; + +protected: + int m_nScreenWidth; + int m_nScreenHeight; + int m_nWindowWidth; + int m_nWindowHeight; + int m_nFontWidth; + int m_nFontHeight; + float m_fDrawScale; + float m_fDrawOffsetX; + float m_fDrawOffsetY; + float *m_fVertexArray; + uint32_t *m_uIndicesArray; + uint32_t *m_uForegroundColorArray; + uint32_t *m_uBackgroundColorArray; + float *m_fTexCoordArray; + CHAR_INFO *m_bufScreen; + CHAR_INFO *m_bufScreen_old; + uint8_t *m_bufMemory; + wstring m_sAppName; + 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; + bool m_bDoWindowUpdate = false; + HWND m_hConsole = nullptr; + HWND m_hWnd = nullptr; + HDC m_hDevCtx = nullptr; + HGLRC m_hRenCtx = nullptr; + GLuint m_uFontTexture; + static atomic m_bAtomActive; + static condition_variable m_cvGameFinished; + static mutex m_muxGame; +}; diff --git a/olcConsoleGameEngineOOP.cpp b/olcConsoleGameEngineOOP.cpp new file mode 100644 index 0000000..56d6791 --- /dev/null +++ b/olcConsoleGameEngineOOP.cpp @@ -0,0 +1,807 @@ +/* +OneLoneCoder.com - Battle Royale Engine - Console User Interface + +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. + +GNU GPLv3 +~~~~~~~~~ +https://github.com/OneLoneCoder/videos/blob/master/LICENSE + +Author of Original Works +~~~~~~~~~~~~~~~~~~~~~~~~ +Twitter: http://twitter.com/javidx9 +Blog: http://www.onelonecoder.com +YouTube: http://www.youtube.com/javidx9 +Github: http://www.github.com/OneLoneCoder/videos +Discord: https://discord.gg/WhwHUMV +Twitch: http://www.twitch.tv/javidx9 +SoundCloud: https://www.soundcloud.com/onelonecoder +*/ + +#include "olcConsoleGameEngineOOP.h" + +olcConsoleGameEngineOOP::olcConsoleGameEngineOOP() +{ + 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_bEnableSound = false; + m_sAppName = L"Default"; +} + +olcConsoleGameEngineOOP::~olcConsoleGameEngineOOP() +{ + SetConsoleActiveScreenBuffer(m_hOriginalConsole); + delete[] m_bufScreen; +} + +int olcConsoleGameEngineOOP::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; + //wcscpy_s(cfi.FaceName, L"Lucida Console"); + //wcscpy_s(cfi.FaceName, L"Liberation Mono"); + wcscpy_s(cfi.FaceName, L"Consolas"); + //wcscpy_s(cfi.FaceName, L"Raster"); + 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); + + return 1; +} + +void olcConsoleGameEngineOOP::Draw(int x, int y, wchar_t c, short col) +{ + 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 olcConsoleGameEngineOOP::Fill(int x1, int y1, int x2, int y2, wchar_t c, short col) +{ + 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 olcConsoleGameEngineOOP::DrawString(int x, int y, wstring c, short col) +{ + 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 olcConsoleGameEngineOOP::DrawStringAlpha(int x, int y, wstring c, short col) +{ + 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 olcConsoleGameEngineOOP::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 olcConsoleGameEngineOOP::DrawLine(int x1, int y1, int x2, int y2, wchar_t c, short col) +{ + 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); + } + } +} + +void olcConsoleGameEngineOOP::DrawCircle(int xc, int yc, int r, wchar_t c, short col) +{ + 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 olcConsoleGameEngineOOP::FillCircle(int xc, int yc, int r, wchar_t c, short col) +{ + // 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 olcConsoleGameEngineOOP::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 olcConsoleGameEngineOOP::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 olcConsoleGameEngineOOP::DrawWireFrameModel(const vector> &vecModelCoordinates, float x, float y, float r, float s, short col, wchar_t c) +{ + // pair.first = x coordinate + // pair.second = y coordinate + + // Create translated model vector of coordinate pairs + vector> 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, c, col); + } +} + + + + +void olcConsoleGameEngineOOP::Start() +{ + m_bAtomActive = true; + + // Star the thread + thread t = thread(&olcConsoleGameEngineOOP::GameThread, this); + + // Wait for thread to be exited + t.join(); +} + +int olcConsoleGameEngineOOP::ScreenWidth() +{ + return m_nScreenWidth; +} + +int olcConsoleGameEngineOOP::ScreenHeight() +{ + return m_nScreenHeight; +} + +void olcConsoleGameEngineOOP::GameThread() +{ + // Create user resources as part of this thread + if (!OnUserCreate()) + m_bAtomActive = false; + + // Check if sound system should be enabled + if (m_bEnableSound) + { + if (!CreateAudio()) + { + m_bAtomActive = false; // Failed to create audio system + m_bEnableSound = 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 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 (m_bEnableSound) + { + // Close and Clean up audio system + } + + 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; + } + } +} + + +// Optional for clean up +bool olcConsoleGameEngineOOP::OnUserDestroy() +{ + return true; +} + +int olcConsoleGameEngineOOP::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; +} + +BOOL olcConsoleGameEngineOOP::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 ul(m_muxGame); + m_cvGameFinished.wait(ul); + } + return true; +} + +unsigned int olcConsoleGameEngineOOP::LoadAudioSample(std::wstring sWavFile) +{ + if (!m_bEnableSound) + return -1; + + olcAudioSample a(sWavFile); + if (a.bSampleValid) + { + vecAudioSamples.push_back(a); + return vecAudioSamples.size(); + } + else + return -1; +} + +// Add sample 'id' to the mixers sounds to play list +void olcConsoleGameEngineOOP::PlaySample(int id, bool bLoop) +{ + listActiveSamples.push_back({ id, 0, false, bLoop }); +} + +void StopSample(int id) +{ + +} + +// The audio system uses by default a specific wave format +bool olcConsoleGameEngineOOP::CreateAudio(unsigned int nSampleRate, unsigned int nChannels, + unsigned int nBlocks, unsigned int nBlockSamples) +{ + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_nBlockFree = m_nBlockCount; + m_nBlockCurrent = 0; + m_pBlockMemory = nullptr; + m_pWaveHeaders = nullptr; + + // Device is available + WAVEFORMATEX waveFormat; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nSamplesPerSec = m_nSampleRate; + waveFormat.wBitsPerSample = sizeof(short) * 8; + waveFormat.nChannels = m_nChannels; + waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + // Open Device if valid + if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)waveOutProcWrap, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK) + return DestroyAudio(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); + + m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; + if (m_pWaveHeaders == nullptr) + return DestroyAudio(); + ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); + + // Link headers to block memory + for (unsigned int n = 0; n < m_nBlockCount; n++) + { + m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); + m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); + } + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&olcConsoleGameEngineOOP::AudioThread, this); + + // Start the ball rolling with the sound delivery thread + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + return true; +} + +// Stop and clean up audio system +bool olcConsoleGameEngineOOP::DestroyAudio() +{ + m_bAudioThreadActive = false; + return false; +} + +// Handler for soundcard request for more data +void olcConsoleGameEngineOOP::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2) +{ + if (uMsg != WOM_DONE) return; + m_nBlockFree++; + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); +} + +// Static wrapper for sound card handler +void CALLBACK olcConsoleGameEngineOOP::waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + ((olcConsoleGameEngineOOP*)dwInstance)->waveOutProc(hWaveOut, uMsg, dwParam1, dwParam2); +} + +// Audio thread. This loop responds to requests from the soundcard to fill 'blocks' +// with audio data. If no requests are available it goes dormant until the sound +// card is ready for more data. The block is fille by the "user" in some manner +// and then issued to the soundcard. +void olcConsoleGameEngineOOP::AudioThread() +{ + m_fGlobalTime = 0.0f; + float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) + { + // Wait for block to become available + if (m_nBlockFree == 0) + { + std::unique_lock lm(m_muxBlockNotZero); + while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly + m_cvBlockNotZero.wait(lm); + } + + // Block is here, so use it + m_nBlockFree--; + + // Prepare block for processing + if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) + waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + + short nNewSample = 0; + int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; + nPreviousSample = nNewSample; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Send block to sound device + waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + m_nBlockCurrent++; + m_nBlockCurrent %= m_nBlockCount; + } +} + +// Overridden by user if they want to generate sound in real-time +float olcConsoleGameEngineOOP::onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep) +{ + return 0.0f; +} + +// Overriden by user if they want to manipulate the sound before it is played +float olcConsoleGameEngineOOP::onUserSoundFilter(int nChannel, float fGlobalTime, float fSample) +{ + return fSample; +} + +// The Sound Mixer - If the user wants to play many sounds simultaneously, and +// perhaps the same sound overlapping itself, then you need a mixer, which +// takes input from all sound sources for that audio frame. This mixer maintains +// a list of sound locations for all concurrently playing audio samples. Instead +// of duplicating audio data, we simply store the fact that a sound sample is in +// use and an offset into its sample data. As time progresses we update this offset +// until it is beyound the length of the sound sample it is attached to. At this +// point we remove the playing souind from the list. +// +// Additionally, the users application may want to generate sound instead of just +// playing audio clips (think a synthesizer for example) in whcih case we also +// provide an "onUser..." event to allow the user to return a sound for that point +// in time. +// +// Finally, before the sound is issued to the operating system for performing, the +// user gets one final chance to "filter" the sound, perhaps changing the volume +// or adding funky effects +float olcConsoleGameEngineOOP::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) +{ + // Accumulate sample for this channel + float fMixerSample = 0.0f; + + for (auto &s : listActiveSamples) + { + // Calculate sample position + s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); + + // If sample position is valid add to the mix + if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) + fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; + else + s.bFinished = true; // Else sound has completed + } + + // If sounds have completed then remove them + listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + + // The users application might be generating sound, so grab that if it exists + fMixerSample += onUserSoundSample(nChannel, fGlobalTime, fTimeStep); + + // Return the sample via an optional user override to filter the sound + return onUserSoundFilter(nChannel, fGlobalTime, fMixerSample); +} + + +atomic olcConsoleGameEngineOOP::m_bAtomActive = false; +condition_variable olcConsoleGameEngineOOP::m_cvGameFinished; +mutex olcConsoleGameEngineOOP::m_muxGame; \ No newline at end of file diff --git a/olcConsoleGameEngineOOP.h b/olcConsoleGameEngineOOP.h new file mode 100644 index 0000000..7969d3e --- /dev/null +++ b/olcConsoleGameEngineOOP.h @@ -0,0 +1,601 @@ +/* +OneLoneCoder.com - Command Line Game Engine +"Who needs a frame buffer?" - @Javidx9 + +The Original & Best :P (in OOP form) + +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 + +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 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! +YouTube: wowLinh, JavaJack59, idkwid, kingtatgi + +Last Updated: 02/07/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 + + + +bool IsWin8OrLater() +{ +DWORD version = GetVersion(); +DWORD major = (DWORD) (LOBYTE(LOWORD(version))); +DWORD minor = (DWORD) (HIBYTE(LOWORD(version))); + +return (major > 6) || ((major == 6) && (minor >= 2) && (minor < 4)); +} + +*/ + +#pragma once +#pragma comment(lib, "winmm.lib") + +#ifndef UNICODE +#error Please enable UNICODE for your compiler! VS: Project Properties -> General -> \ +Character Set -> Use Unicode. Thanks! - Javidx9 +#endif + +#include +#include +#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, // 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; + } + + bool LoadFromResource(unsigned int id) + { + HRSRC res = FindResource(NULL, MAKEINTRESOURCE(id), RT_RCDATA); + if (!res) return false; + + HGLOBAL res_handle = LoadResource(NULL, res); + if (!res_handle) return false; + + char* res_data = (char*)LockResource(res_handle); + DWORD res_size = SizeofResource(NULL, res); + + + + + + istringstream iss(res_data); + istream *is = &iss; + + delete[] m_Glyphs; + delete[] m_Colours; + nWidth = 0; + nHeight = 0; + + is->read((char*)&nWidth, sizeof(int) * 1); + is->read((char*)&nHeight, sizeof(int) * 1); + + Create(nWidth, nHeight); + + iss.read((char*)m_Colours, sizeof(short) * nWidth * nHeight); + iss.read((char*)m_Glyphs, sizeof(wchar_t) * nWidth * nHeight); + + + return true; + } + +}; + + +class olcConsoleGameEngineOOP +{ +public: + olcConsoleGameEngineOOP(); + ~olcConsoleGameEngineOOP(); + +public: + int ConstructConsole(int width, int height, int fontw, int fonth); + void Start(); + +public: + virtual void Draw(int x, int y, wchar_t c = 0x2588, short col = 0x000F); + void Fill(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F); + void DrawString(int x, int y, wstring c, short col = 0x000F); + void DrawStringAlpha(int x, int y, wstring c, short col = 0x000F); + void Clip(int &x, int &y); + void DrawLine(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F); + void DrawCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F); + void FillCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F); + void DrawSprite(int x, int y, olcSprite *sprite); + void DrawPartialSprite(int x, int y, olcSprite *sprite, int ox, int oy, int w, int h); + void DrawWireFrameModel(const vector> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE, wchar_t c = PIXEL_SOLID); + int ScreenWidth(); + int ScreenHeight(); + +private: + void GameThread(); + +protected: + // User MUST OVERRIDE THESE!! + virtual bool OnUserCreate() = 0; + virtual bool OnUserUpdate(float fElapsedTime) = 0; + + // Optional for clean up + virtual bool OnUserDestroy(); + + + int Error(const wchar_t *msg); + static BOOL CloseHandler(DWORD evt); + +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: + class olcAudioSample + { + public: + olcAudioSample() + { + + } + + olcAudioSample(std::wstring sWavFile) + { + // Load Wav file and convert to float format + FILE *f = nullptr; + _wfopen_s(&f, sWavFile.c_str(), L"rb"); + if (f == nullptr) + return; + + char dump[4]; + std::fread(&dump, sizeof(char), 4, f); // Read "RIFF" + if (strncmp(dump, "RIFF", 4) != 0) return; + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&dump, sizeof(char), 4, f); // Read "WAVE" + if (strncmp(dump, "WAVE", 4) != 0) return; + + // Read Wave description chunk + std::fread(&dump, sizeof(char), 4, f); // Read "fmt " + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&wavHeader, sizeof(WAVEFORMATEX) - 2, 1, f); // Read Wave Format Structure chunk + // Note the -2, because the structure has 2 bytes to indicate its own size + // which are not in the wav file + + // Just check if wave format is compatible with olcCGE + if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) + { + std::fclose(f); + return; + } + + // Search for audio data chunk + long nChunksize = 0; + std::fread(&dump, sizeof(char), 4, f); // Read chunk header + std::fread(&nChunksize, sizeof(long), 1, f); // Read chunk size + while (strncmp(dump, "data", 4) != 0) + { + // Not audio data, so just skip it + std::fseek(f, nChunksize, SEEK_CUR); + std::fread(&dump, sizeof(char), 4, f); + std::fread(&nChunksize, sizeof(long), 1, f); + } + + // Finally got to data, so read it all in and convert to float samples + nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); + nChannels = wavHeader.nChannels; + + // Create floating point buffer to hold audio sample + fSample = new float[nSamples * nChannels]; + float *pSample = fSample; + + // Read in audio data and normalise + for (long i = 0; i < nSamples; i++) + { + for (int c = 0; c < nChannels; c++) + { + short s = 0; + std::fread(&s, sizeof(short), 1, f); + *pSample = (float)s / (float)(MAXSHORT); + pSample++; + } + } + + // All done, flag sound as valid + std::fclose(f); + bSampleValid = true; + } + + WAVEFORMATEX wavHeader; + float *fSample = nullptr; + long nSamples = 0; + int nChannels = 0; + bool bSampleValid = false; + }; + + // This vector holds all loaded sound samples in memory + std::vector vecAudioSamples; + + // This structure represents a sound that is currently playing. It only + // holds the sound ID and where this instance of it is up to for its + // current playback + struct sCurrentlyPlayingSample + { + int nAudioSampleID = 0; + long nSamplePosition = 0; + bool bFinished = false; + bool bLoop = false; + }; + std::list listActiveSamples; + + // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID + // number is returned if successful, otherwise -1 + unsigned int LoadAudioSample(std::wstring sWavFile); + + // Add sample 'id' to the mixers sounds to play list + void PlaySample(int id, bool bLoop = false); + + void StopSample(int id); + + // The audio system uses by default a specific wave format + bool CreateAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, + unsigned int nBlocks = 8, unsigned int nBlockSamples = 512); + + // Stop and clean up audio system + bool DestroyAudio(); + + // Handler for soundcard request for more data + void waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); + + // Static wrapper for sound card handler + static void CALLBACK waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void AudioThread(); + + // Overridden by user if they want to generate sound in real-time + virtual float onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep); + + // Overriden by user if they want to manipulate the sound before it is played + virtual float onUserSoundFilter(int nChannel, float fGlobalTime, float fSample); + + // The Sound Mixer - If the user wants to play many sounds simultaneously, and + // perhaps the same sound overlapping itself, then you need a mixer, which + // takes input from all sound sources for that audio frame. This mixer maintains + // a list of sound locations for all concurrently playing audio samples. Instead + // of duplicating audio data, we simply store the fact that a sound sample is in + // use and an offset into its sample data. As time progresses we update this offset + // until it is beyound the length of the sound sample it is attached to. At this + // point we remove the playing souind from the list. + // + // Additionally, the users application may want to generate sound instead of just + // playing audio clips (think a synthesizer for example) in whcih case we also + // provide an "onUser..." event to allow the user to return a sound for that point + // in time. + // + // Finally, before the sound is issued to the operating system for performing, the + // user gets one final chance to "filter" the sound, perhaps changing the volume + // or adding funky effects + float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep); + +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; + + bool m_bEnableSound = false; + unsigned int m_nSampleRate; + unsigned int m_nChannels; + unsigned int m_nBlockCount; + unsigned int m_nBlockSamples; + unsigned int m_nBlockCurrent; + short* m_pBlockMemory = nullptr; + WAVEHDR *m_pWaveHeaders = nullptr; + HWAVEOUT m_hwDevice = nullptr; + std::thread m_AudioThread; + std::atomic m_bAudioThreadActive = false; + std::atomic m_nBlockFree = 0; + std::condition_variable m_cvBlockNotZero; + std::mutex m_muxBlockNotZero; + std::atomic m_fGlobalTime = 0.0f; + + static atomic m_bAtomActive; + static condition_variable m_cvGameFinished; + static mutex m_muxGame; +}; + diff --git a/olcConsoleGameEngineSDL.h b/olcConsoleGameEngineSDL.h new file mode 100644 index 0000000..512d568 --- /dev/null +++ b/olcConsoleGameEngineSDL.h @@ -0,0 +1,1175 @@ +/* +OneLoneCoder.com - Command Line Game Engine +"Who needs a frame buffer?" - @Javidx9 + +For the *nix and Mac users - like most Linux software, this +may not be fully compatible or fully featured, and development +will invariably lag behind the rest - sorry. + +NOTE: DOES NOT SUPPORT SOUND YET +NOTE: DOES NOT SUUPORT MOUSE INPUT YET + +I recommend compiling as follows, of course you need SDL from your vendor + + g++ thing.cpp -DUNICODE -I/usr/include/SDL2 -lSDL2 -lpthread -std=C++11 + +I'd like to explain how this version came about, because without the help of +some awesome individuals, this would not be a thing! + +=========== IMPORTANT!!! PLEASE READ ========================================== + +Firstly, Thanks to Jack Clarke (JackOJC on Discord and Github). He had the +idea of porting over the olcConsoleGameEngine to non-windows operating systems. + +He developed a prototype which can be found here: +https://github.com/Jackojc/olcNotSoConsoleGameEngine +which really proved that it could be possible. + +I then added functionality to ensure that this header is 99% compatible with +my existing code base, so almost all the videos I've made should be compatible +with this version of the olcConsoleGameEngine, without modificatcion. + +Finally I'd also like to thank MrBadNews on Discord for testing and finding +many bugs, and creating workarounds, and Slavka on Discord for help debug SDL +double buffering issues - stupid SDL :P + +Please remember I, javidx9, am not a Linux or SDL specialist. Therfore please +report bugs, or ask for help on the Discord Server, as I probably don't use +the same non-windows operating systems as you, and each have their own methods +for building and installing. + +=============================================================================== + +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 + +Last Updated: 01/07/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; +} + + +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, Ill try enabling it for you - Javidx9 +#define UNICODE +#define _UNICODE +#endif + +#define __STDC_LIB_EXT1__ +#define __STDC_WANT_LIB_EXT1__ 1 +#define _CRT_SECURE_NO_WARNINGS + +// NOTE: You will need to have SDL2 installed on your machine for this to work +// https://www.libsdl.org/download-2.0.php +#ifdef _WIN32 +#include +#else +#include +#endif +#undef main + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define KEY_OFFSET 256 +#define VK_SPACE SDL_SCANCODE_SPACE + KEY_OFFSET +#define VK_BACK SDL_SCANCODE_BACKSPACE + KEY_OFFSET +#define VK_DELETE SDL_SCANCODE_DELETE + KEY_OFFSET +#define VK_HOME SDL_SCANCODE_HOME + KEY_OFFSET +#define VK_END SDL_SCANCODE_END + KEY_OFFSET +#define VK_RETURN SDL_SCANCODE_RETURN + KEY_OFFSET +#define VK_TAB SDL_SCANCODE_TAB + KEY_OFFSET +#define VK_INSERT SDL_SCANCODE_INSERT + KEY_OFFSET +#define VK_LSHIFT SDL_SCANCODE_LSHIFT + KEY_OFFSET +#define VK_RSHIFT SDL_SCANCODE_RSHIFT + KEY_OFFSET +#define VK_LCONTROL SDL_SCANCODE_LCTRL + KEY_OFFSET +#define VK_RCONTROL SDL_SCANCODE_RCTRL + KEY_OFFSET +#define VK_PRIOR SDL_SCANCODE_PAGEUP + KEY_OFFSET +#define VK_NEXT SDL_SCANCODE_PAGEDOWN + KEY_OFFSET +#define VK_ESCAPE SDL_SCANCODE_ESCAPE + KEY_OFFSET +#define VK_UP SDL_SCANCODE_UP + KEY_OFFSET +#define VK_DOWN SDL_SCANCODE_DOWN + KEY_OFFSET +#define VK_LEFT SDL_SCANCODE_LEFT + KEY_OFFSET +#define VK_RIGHT SDL_SCANCODE_RIGHT + KEY_OFFSET +#define VK_F1 SDL_SCANCODE_F1 + KEY_OFFSET +#define VK_F2 SDL_SCANCODE_F2 + KEY_OFFSET +#define VK_F3 SDL_SCANCODE_F3 + KEY_OFFSET +#define VK_F4 SDL_SCANCODE_F4 + KEY_OFFSET +#define VK_F5 SDL_SCANCODE_F5 + KEY_OFFSET +#define VK_F6 SDL_SCANCODE_F6 + KEY_OFFSET +#define VK_F7 SDL_SCANCODE_F7 + KEY_OFFSET +#define VK_F8 SDL_SCANCODE_F8 + KEY_OFFSET +#define VK_F9 SDL_SCANCODE_F9 + KEY_OFFSET +#define VK_F10 SDL_SCANCODE_F10 + KEY_OFFSET +#define VK_F11 SDL_SCANCODE_F11 + KEY_OFFSET +#define VK_F12 SDL_SCANCODE_F12 + KEY_OFFSET + +struct CHAR_INFO +{ + unsigned short glyph; + short colour; +}; + +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, +}; + +constexpr SDL_Color colour_lookup[] = { + SDL_Color{ 0,0,0,255 }, // 0 + SDL_Color{ 0,0,127,255 }, // 1 + SDL_Color{ 0,127,0,255 }, // 2 + SDL_Color{ 0,127,127,255 }, // 3 + SDL_Color{ 127,0,0,255 }, // 4 + SDL_Color{ 127,0,127,255 }, // 5 + SDL_Color{ 127,127,0,255 }, // 6 + SDL_Color{ 192,192,192,255 },// 7 + SDL_Color{ 127,127,127,255 }, // 8 + SDL_Color{ 0,0,255,255 }, // 9 + SDL_Color{ 0,255,0,255 }, // A + SDL_Color{ 0,255,255,255 }, // B + SDL_Color{ 255,0,0,255 }, // C + SDL_Color{ 255,0,255,255 }, // D + SDL_Color{ 255,255,0,255 }, // E + SDL_Color{ 255,255,255,255 },// F +}; + +class olcSprite +{ +public: + olcSprite() + { + + } + + olcSprite(int w, int h) + { + Create(w, h); + } + + olcSprite(std::wstring sFile) + { + if (!Load(sFile)) + Create(8, 8); + } + + int nWidth = 0; + int nHeight = 0; + +private: + unsigned short *m_Glyphs = nullptr; + short *m_Colours = nullptr; + + void Create(int w, int h) + { + nWidth = w; + nHeight = h; + m_Glyphs = new unsigned short[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(std::wstring sFile) + { + FILE *f = nullptr; + + char buff[256]; +#ifdef _WIN32 + size_t t; + wcstombs_s(&t, buff, sFile.c_str(), 256); +#else + wcstombs(buff, sFile.c_str(), 256); +#endif + +#ifdef _WIN32 + fopen_s(&f, buff, "wb"); +#else + f = std::fopen(buff, "wb"); +#endif + 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(std::wstring sFile) + { + delete[] m_Glyphs; + delete[] m_Colours; + nWidth = 0; + nHeight = 0; + + FILE *f = nullptr; + char buff[256]; +#ifdef _WIN32 + size_t t; + wcstombs_s(&t, buff, sFile.c_str(), 256); +#else + wcstombs(buff, sFile.c_str(), 256); +#endif + +#ifdef _WIN32 + fopen_s(&f, buff, "rb"); +#else + f = std::fopen(buff, "rb"); +#endif + 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; + + + memset(m_keyNewState, 0, 512 * sizeof(bool)); + memset(m_keyOldState, 0, 512 * sizeof(bool)); + memset(m_keys._state, 0, 512 * sizeof(sKeyState)); + m_mousePosX = 0; + m_mousePosY = 0; + + m_sAppName = L"Default"; + } + + int ConstructConsole(int width, int height, int fontw, int fonth) + { + m_nScreenWidth = width; + m_nScreenHeight = height; + m_nFontWidth = fontw; + m_nFontHeight = fonth; + + + + + // Allocate memory for screen buffer + m_bufScreen[0] = new CHAR_INFO[m_nScreenWidth*m_nScreenHeight]; + m_bufScreen[1] = new CHAR_INFO[m_nScreenWidth*m_nScreenHeight]; + // NOTE(MrBadNewS): set buffers to zero, old way + memset(m_bufScreen[0], 0, m_nScreenWidth*m_nScreenHeight*sizeof(CHAR_INFO)); + memset(m_bufScreen[1], 0, m_nScreenWidth*m_nScreenHeight*sizeof(CHAR_INFO)); + m_nCurrentBuffer = 0; + + return 1; + } + + virtual void Draw(int x, int y, wchar_t c = PIXEL_SOLID, short col = 0x000F) + { + if (x >= 0 && x < m_nScreenWidth && y >= 0 && y < m_nScreenHeight) + { + m_bufScreen[m_nCurrentBuffer][y * m_nScreenWidth + x].glyph = c; + m_bufScreen[m_nCurrentBuffer][y * m_nScreenWidth + x].colour = col; + } + } + + void Fill(int x1, int y1, int x2, int y2, wchar_t c = PIXEL_SOLID, 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, std::wstring c, short col = 0x000F) + { + for (size_t i = 0; i < c.size(); i++) + { + m_bufScreen[m_nCurrentBuffer][y * m_nScreenWidth + x + i].glyph = c[i]; + m_bufScreen[m_nCurrentBuffer][y * m_nScreenWidth + x + i].colour = col; + } + } + + void DrawStringAlpha(int x, int y, std::wstring c, short col = 0x000F) + { + for (size_t i = 0; i < c.size(); i++) + { + if (c[i] != L' ') + { + m_bufScreen[m_nCurrentBuffer][y * m_nScreenWidth + x + i].glyph = c[i]; + m_bufScreen[m_nCurrentBuffer][y * m_nScreenWidth + x + i].colour = 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 = PIXEL_SOLID, 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); + } + } + } + + void DrawCircle(int xc, int yc, int r, wchar_t c = PIXEL_SOLID, 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 = PIXEL_SOLID, 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 std::vector> &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 + std::vector> 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() + { + + } + +public: + void Start() + { + m_bAtomActive = true; + + // Star the thread + std::thread t = std::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() + { + // Start SDL + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); + + char bufAppName[256]; +#ifdef _WIN32 + size_t t; + wcstombs_s(&t, bufAppName, m_sAppName.c_str(), 256); +#else + wcstombs(bufAppName, m_sAppName.c_str(), 256); +#endif + + // Create Window + m_window = SDL_CreateWindow( + bufAppName, + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + m_nScreenWidth * m_nFontWidth, + m_nScreenHeight * m_nFontHeight, + SDL_WINDOW_SHOWN + ); + + // Create renderer + m_render = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE); + + // Now hear this, because its annoying! + // On windows, SDL does not create a double buffer by default, which + // permits incremental rendering, a frankly necessary feature which + // is used extensively by tile renderers (such as this system) to + // get the most performance. On My Linux VM its fine too. However + // on Linux with HW, it caused a non-clear bug, as my incremental + // framing method was rendering differences to successive buffers + // in SDL causing all sorts of on screen artefacts. SDL - You're shit. + m_screen = SDL_CreateTexture(m_render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, m_nScreenWidth * m_nFontWidth, m_nScreenHeight * m_nFontHeight); + + // Load Font File + LoadFontFile("./olcfont_consolas.bmp"); + + // Create user resources as part of this thread + if (!OnUserCreate()) + m_bAtomActive = false; + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); + + while (m_bAtomActive) + { + // Run as fast as possible + while (m_bAtomActive) + { + // Handle Timing + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + float fElapsedTime = elapsedTime.count(); + + SDL_Event e; + while (SDL_PollEvent(&e)) + { + switch (e.type) + { + case SDL_QUIT: + m_bAtomActive = false; + break; + + case SDL_KEYDOWN: + { + int k = e.key.keysym.scancode; // SDL Scancode + m_keyNewState[k] = true; + } + break; + + case SDL_KEYUP: + { + int k = e.key.keysym.scancode; // SDL Scancode + m_keyNewState[k] = false; + } + break; + + } + } + + // Handle Keyboard Input + for (int k = 0; k < 512; k++) + { + m_keys._state[k].bPressed = false; + m_keys._state[k].bReleased = false; + + if (m_keyNewState[k] != m_keyOldState[k]) + { + if (m_keyNewState[k] == true) + { + m_keys._state[k].bPressed = !m_keys._state[k].bHeld; + m_keys._state[k].bHeld = true; + } + else + { + m_keys._state[k].bReleased = true; + m_keys._state[k].bHeld = false; + } + } + + m_keyOldState[k] = m_keyNewState[k]; + } + + // 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 + char s[256]; + snprintf(s, 256, "OneLoneCoder.com - Console Game Engine (SDL) - %s - FPS: %3.2f", bufAppName, 1.0f / fElapsedTime); + SDL_SetWindowTitle(m_window, s); + + // Render differences + CHAR_INFO *buff_old, *buff_new; + buff_new = m_bufScreen[m_nCurrentBuffer]; + buff_old = m_bufScreen[(m_nCurrentBuffer+1) % 2]; + + SDL_SetRenderTarget(m_render, m_screen); + + for (int x = 0; x < m_nScreenWidth; x++) + { + for (int y = 0; y < m_nScreenHeight; y++) + { + int i = y * m_nScreenWidth + x; + if (buff_new[i].colour != buff_old[i].colour || buff_new[i].glyph != buff_old[i].glyph) + { + // Draw Cell + int cx = (buff_new[i].glyph-0) % 64; + int cy = (buff_new[i].glyph-0) / 64; + + // Draw Foreground + int cidx = (buff_new[i].colour & 0x00F0) >> 4; + //SDL_Rect src_bg = { (cidx+45) * 8, 88, 8, 8 }; + SDL_Rect src_bg = { (cidx+0) * 16, 0, 16, 16 }; + SDL_Rect dst = { x * m_nFontWidth, y * m_nFontHeight, m_nFontWidth, m_nFontHeight }; + SDL_SetTextureColorMod(m_fontFile, 255, 255, 255); + SDL_SetTextureAlphaMod(m_fontFile, 255); + SDL_RenderCopy(m_render, m_fontFile, &src_bg, &dst); + + // set colour of font + SDL_Color fg_col = colour_lookup[buff_new[i].colour & 0x000F]; + SDL_SetTextureColorMod(m_fontFile, fg_col.r, fg_col.g, fg_col.b); + SDL_SetTextureAlphaMod(m_fontFile, fg_col.a); + + // Draw glyph + SDL_Rect src_fg = { cx * (16), cy * (16), 16, 16 }; + SDL_RenderCopy(m_render, m_fontFile, &src_fg, &dst); + } + } + } + + // Present buffers + SDL_SetRenderTarget(m_render, nullptr); + SDL_RenderCopy(m_render, m_screen, nullptr, nullptr); + SDL_RenderPresent(m_render); + + + // Flip buffers + m_nCurrentBuffer = (m_nCurrentBuffer + 1) % 2; + } + + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + SDL_DestroyTexture(m_fontFile); + SDL_DestroyRenderer(m_render); + SDL_DestroyWindow(m_window); + SDL_Quit(); + + delete[] m_bufScreen[0]; + delete[] m_bufScreen[1]; + 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; + }; + + struct sKeyStateWrap + { + sKeyState _state[512]; + + public: + sKeyState & operator[] (int nKeyID) + { + SDL_Scancode scode = SDL_SCANCODE_UNKNOWN; + + if (nKeyID > KEY_OFFSET) + { + int scode = (SDL_Scancode)nKeyID - KEY_OFFSET; + return _state[scode]; + } + + switch (nKeyID) + { + case L'A': scode = SDL_SCANCODE_A; break; + case L'B': scode = SDL_SCANCODE_B; break; + case L'C': scode = SDL_SCANCODE_C; break; + case L'D': scode = SDL_SCANCODE_D; break; + case L'E': scode = SDL_SCANCODE_E; break; + case L'F': scode = SDL_SCANCODE_F; break; + case L'G': scode = SDL_SCANCODE_G; break; + case L'H': scode = SDL_SCANCODE_H; break; + case L'I': scode = SDL_SCANCODE_I; break; + case L'J': scode = SDL_SCANCODE_J; break; + case L'K': scode = SDL_SCANCODE_K; break; + case L'L': scode = SDL_SCANCODE_L; break; + case L'M': scode = SDL_SCANCODE_M; break; + case L'N': scode = SDL_SCANCODE_N; break; + case L'O': scode = SDL_SCANCODE_O; break; + case L'P': scode = SDL_SCANCODE_P; break; + case L'Q': scode = SDL_SCANCODE_Q; break; + case L'R': scode = SDL_SCANCODE_R; break; + case L'S': scode = SDL_SCANCODE_S; break; + case L'T': scode = SDL_SCANCODE_T; break; + case L'U': scode = SDL_SCANCODE_U; break; + case L'V': scode = SDL_SCANCODE_V; break; + case L'W': scode = SDL_SCANCODE_W; break; + case L'X': scode = SDL_SCANCODE_X; break; + case L'Y': scode = SDL_SCANCODE_Y; break; + case L'Z': scode = SDL_SCANCODE_Z; break; + case L'0': scode = SDL_SCANCODE_0; break; + case L'1': scode = SDL_SCANCODE_1; break; + case L'2': scode = SDL_SCANCODE_2; break; + case L'3': scode = SDL_SCANCODE_3; break; + case L'4': scode = SDL_SCANCODE_4; break; + case L'5': scode = SDL_SCANCODE_5; break; + case L'6': scode = SDL_SCANCODE_6; break; + case L'7': scode = SDL_SCANCODE_7; break; + case L'8': scode = SDL_SCANCODE_8; break; + case L'9': scode = SDL_SCANCODE_9; break; + + /*case L'\'': scode = SDL_SCANCODE_APOSTROPHE; break; + case L'\\': scode = SDL_SCANCODE_BACKSLASH; break; + case L',': scode = SDL_SCANCODE_COMMA; break; + case L'=': scode = SDL_SCANCODE_EQUALS; break; + case L'[': scode = SDL_SCANCODE_LEFTBRACKET; break; + case L']': scode = SDL_SCANCODE_RIGHTBRACKET; break; + case L'-': scode = SDL_SCANCODE_MINUS; break; + case L'.': scode = SDL_SCANCODE_PERIOD; break; + case L';': scode = SDL_SCANCODE_SEMICOLON; break; + case L'/': scode = SDL_SCANCODE_SLASH; break;*/ + + default: scode = (SDL_Scancode)nKeyID; + } + + return _state[scode]; + } + + } m_keys; + + 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]; + wprintf(L"ERROR: %s\n\t%s\n", msg, buf); + return 0; + } + + void LoadFontFile(const std::string& fname) + { + // Load image. + SDL_Surface* temp = SDL_LoadBMP(fname.c_str()); + + // set color key to 255,0,255; this basically makes + // it transparent. + SDL_SetColorKey(temp, SDL_TRUE, SDL_MapRGB(temp->format, 255, 0, 255)); + + // Convert to texture. + m_fontFile = SDL_CreateTextureFromSurface(m_render, temp); + + // Cleanup and return. + SDL_FreeSurface(temp); + } + + //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 ul(m_muxGame); + // m_cvGameFinished.wait(ul); + // } + // return true; + //} + +protected: + int m_nScreenWidth; + int m_nScreenHeight; + int m_nFontWidth; + int m_nFontHeight; + CHAR_INFO *m_bufScreen[2]; + int m_nCurrentBuffer = 0; + std::wstring m_sAppName; + bool m_keyOldState[512] = { 0 }; + bool m_keyNewState[512] = { 0 }; + bool m_mouseOldState[5] = { 0 }; + bool m_mouseNewState[5] = { 0 }; + bool m_bConsoleInFocus = true; + static std::atomic m_bAtomActive; + static std::condition_variable m_cvGameFinished; + static std::mutex m_muxGame; + +private: + SDL_Window* m_window; + SDL_Renderer* m_render; + SDL_Texture *m_screen; + SDL_Texture* m_fontFile; + + +}; + +std::atomic olcConsoleGameEngine::m_bAtomActive(false); +std::condition_variable olcConsoleGameEngine::m_cvGameFinished; +std::mutex olcConsoleGameEngine::m_muxGame; +