From cd476567d912e6f8d5b83e2e4349bed63d9df1ed Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 25 Feb 2019 12:12:52 +0000 Subject: [PATCH] Simple snake movement mechanic demonstration for a friend --- C++/snake-movement-mechanic/main.cpp | 125 + .../olcPixelGameEngine.h | 2534 +++++++++++++++++ C++/snake-movement-mechanic/output.o | Bin 0 -> 165896 bytes 3 files changed, 2659 insertions(+) create mode 100644 C++/snake-movement-mechanic/main.cpp create mode 100644 C++/snake-movement-mechanic/olcPixelGameEngine.h create mode 100755 C++/snake-movement-mechanic/output.o diff --git a/C++/snake-movement-mechanic/main.cpp b/C++/snake-movement-mechanic/main.cpp new file mode 100644 index 0000000..197df13 --- /dev/null +++ b/C++/snake-movement-mechanic/main.cpp @@ -0,0 +1,125 @@ +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#include +#include + +const int MAP_WIDTH = 40; +const int MAP_HEIGHT = 40; + +struct Point { + int x, y; + bool isSnake; +}; + +typedef enum { + DIRECTION_UP, + DIRECTION_RIGHT, + DIRECTION_DOWN, + DIRECTION_LEFT +} SnakeDir; + +int index(int x, int y) { + return y * MAP_HEIGHT + x; +} + +class Snake : public olc::PixelGameEngine { +public: + std::vector map; + std::vector snake_stack; + SnakeDir dir = DIRECTION_RIGHT; + + bool OnUserCreate() { + // Loop over every point in the map column + // by column and initialize them as points + // in the 1d map vector + for (int i = 0; i < MAP_WIDTH; i++) { + for (int j = 0; j < MAP_HEIGHT; j++) { + map.push_back({i, j, false}); + } + } + + // Settup snake stack + snake_stack.push_back({2, 3, true}); + snake_stack.push_back({3, 3, true}); + snake_stack.push_back({4, 3, true}); + snake_stack.push_back({5, 3, true}); + dir = DIRECTION_RIGHT; + + return true; + } + + bool OnUserUpdate(float fElapsedTime) { + // Take input and change direction + if (GetKey(olc::Key::W).bPressed) + dir = DIRECTION_UP; + if (GetKey(olc::Key::A).bPressed) + dir = DIRECTION_LEFT; + if (GetKey(olc::Key::S).bPressed) + dir = DIRECTION_DOWN; + if (GetKey(olc::Key::D).bPressed) + dir = DIRECTION_RIGHT; + + // Push an element for the head + // dependant on the direction + + if (dir == DIRECTION_UP) { + snake_stack.push_back({snake_stack.back().x, snake_stack.back().y - 1, true}); + } + + if (dir == DIRECTION_RIGHT) { + snake_stack.push_back({snake_stack.back().x + 1, snake_stack.back().y, true}); + } + + if (dir == DIRECTION_DOWN) { + snake_stack.push_back({snake_stack.back().x, snake_stack.back().y + 1, true}); + } + + if (dir == DIRECTION_LEFT) { + snake_stack.push_back({snake_stack.back().x - 1, snake_stack.back().y, true}); + } + + // Pop last element of the tail + + snake_stack.erase(snake_stack.begin()); + + updateSnake(); + draw(); + return true; + } + + void updateSnake() { + // Set every map point to no snake + for (int i = 0; i < map.size(); i++) { + map[i].isSnake = false; + } + + // Set the points that the snake are in in the map + // to have a snake in + for (int i = 0; i < snake_stack.size(); i++) { + map[index(snake_stack[i].x, snake_stack[i].y)].isSnake = true; + } + } + + void draw() { + // Loop over every element in the map + // and draw them on the map in their respective + // screen position + for (int i = 0; i < MAP_WIDTH; i++) { + for (int j = 0; j < MAP_HEIGHT; j++) { + if (map[index(i, j)].isSnake) { + DrawRect(i, j, 1, 1, olc::RED); + } else { + DrawRect(i, j, 1, 1, olc::BLUE); + } + } + } + } +}; + +int main(int argc, char** argv) { + Snake app; + app.Construct(MAP_WIDTH, MAP_HEIGHT, 20, 20); + app.Start(); + return 0; +} diff --git a/C++/snake-movement-mechanic/olcPixelGameEngine.h b/C++/snake-movement-mechanic/olcPixelGameEngine.h new file mode 100644 index 0000000..4382a62 --- /dev/null +++ b/C++/snake-movement-mechanic/olcPixelGameEngine.h @@ -0,0 +1,2534 @@ +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v1.12 | + | "Like the command prompt console one, but not..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + The olcConsoleGameEngine has been a surprsing and wonderful + success for me, and I'm delighted how people have reacted so + positively towards it, so thanks for that. + + However, there are limitations that I simply cannot avoid. + Firstly, I need to maintain several different versions of + it to accommodate users on Windows7, 8, 10, Linux, Mac, + Visual Studio & Code::Blocks. Secondly, this year I've been + pushing the console to the limits of its graphical capabilities + and the effect is becoming underwhelming. The engine itself + is not slow at all, but the process that Windows uses to + draw the command prompt to the screen is, and worse still, + it's dynamic based upon the variation of character colours + and glyphs. Sadly I have no control over this, and recent + videos that are extremely graphical (for a command prompt :P ) + have been dipping to unacceptable framerates. As the channel + has been popular with aspiring game developers, I'm concerned + that the visual appeal of the command prompt is perhaps + limited to us oldies, and I dont want to alienate younger + learners. Finally, I'd like to demonstrate many more + algorithms and image processing that exist in the graphical + domain, for which the console is insufficient. + + For this reason, I have created olcPixelGameEngine! The look + and feel to the programmer is almost identical, so all of my + existing code from the videos is easily portable, and the + programmer uses this file in exactly the same way. But I've + decided that rather than just build a command prompt emulator, + that I would at least harness some modern(ish) portable + technologies. + + As a result, the olcPixelGameEngine supports 32-bit colour, is + written in a cross-platform style, uses modern(ish) C++ + conventions and most importantly, renders much much faster. I + will use this version when my applications are predominantly + graphics based, but use the console version when they are + predominantly text based - Don't worry, loads more command + prompt silliness to come yet, but evolution is important!! + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + + Relevant Videos + ~~~~~~~~~~~~~~~ + https://youtu.be/kRH6oJLFYxY Introducing olcPixelGameEngine + + Compiling in Linux + ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: + + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64, so google this. You will also need to enable C++14 + in your build options, and add to your linker the following libraries: + user32 gdi32 opengl32 gdiplus + + Thanks + ~~~~~~ + I'd like to extend thanks to Eremiell, slavka, gurkanctn, Phantim, + JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice + Ralakus, Gorbit99, raoul & MagetzUb for advice, ideas and testing, and I'd like + to extend my appreciation to the 23K YouTube followers and 1.5K Discord server + members who give me the motivation to keep going with all this :D + + Special thanks to those who bring gifts! + GnarGnarHead.......Domina + Gorbit99...........Bastion + + Special thanks to my Patreons too - I wont name you on here, but I've + certainly enjoyed my tea and flapjacks :D + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2018, 2019 +*/ + +////////////////////////////////////////////////////////////////////////////////////////// + +/* Example Usage (main.cpp) + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + // Override base class with your custom functionality + class Example : public olc::PixelGameEngine + { + public: + Example() + { + sAppName = "Example"; + } + public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + bool OnUserUpdate(float fElapsedTime) override + { + // called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 255, rand() % 255, rand()% 255)); + return true; + } + }; + int main() + { + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; + } +*/ + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#ifdef _WIN32 + // Link to libraries +#ifndef __MINGW32__ + #pragma comment(lib, "user32.lib") // Visual Studio Only + #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add + #pragma comment(lib, "opengl32.lib") // these libs to your linker input + #pragma comment(lib, "gdiplus.lib") +#else + // In Code::Blocks, Select C++14 in your build options, and add the + // following libs to your linker: user32 gdi32 opengl32 gdiplus +#endif + // Include WinAPI + #include + #include + + // OpenGL Extension + #include + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t *wglSwapInterval; +#else + #include + #include + #include + #include + #include + typedef int(glSwapInterval_t) (Display *dpy, GLXDrawable drawable, int interval); + static glSwapInterval_t *glSwapIntervalEXT; +#endif + + +// Standard includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef min +#undef max + +class Rect; + +namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace +{ + struct Pixel + { + union + { + uint32_t n = 0xFF000000; + struct + { + uint8_t r; uint8_t g; uint8_t b; uint8_t a; + }; + }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255); + Pixel(uint32_t p); + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + }; + + // Some constants for symbolic naming of Pixels + static const Pixel + WHITE(255, 255, 255), + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), + BLACK(0, 0, 0), + BLANK(0, 0, 0, 0); + + enum rcode + { + FAIL = 0, + OK = 1, + NO_FILE = -1, + }; + + //============================================================= + + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set tru for all frames between pressed and released events + }; + + //============================================================= + + class ResourcePack + { + public: + ResourcePack(); + // ~ResourcePack(); + struct sEntry : public std::streambuf { + uint32_t nID, nFileOffset, nFileSize; uint8_t* data; void _config() { this->setg((char*)data, (char*)data, (char*)(data + nFileSize)); } + }; + + public: + olc::rcode AddToPack(std::string sFile); + + public: + olc::rcode SavePack(std::string sFile); + olc::rcode LoadPack(std::string sFile); + olc::rcode ClearPack(); + + public: + olc::ResourcePack::sEntry GetStreamBuffer(std::string sFile); + + private: + + std::map mapFiles; + }; + + //============================================================= + + // A bitmap-like structure that stores a 2D array of Pixels + class Sprite + { + public: + Sprite(); + Sprite(std::string sImageFile); + Sprite(std::string sImageFile, olc::ResourcePack *pack); + Sprite(int32_t w, int32_t h); + ~Sprite(); + + public: + olc::rcode LoadFromFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode SaveToPGESprFile(std::string sImageFile); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y); + void SetPixel(int32_t x, int32_t y, Pixel p); + Pixel Sample(float x, float y); + Pixel* GetData(); + + private: + Pixel *pColData = nullptr; + Mode modeSample = Mode::NORMAL; + +#ifdef OLC_DBG_OVERDRAW + public: + static int nOverdrawCount; +#endif + + }; + + //============================================================= + + enum Key + { + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + UP, DOWN, LEFT, RIGHT, + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, + }; + + + //============================================================= + + class PixelGameEngine + { + public: + PixelGameEngine(); + + public: + olc::rcode Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h); + olc::rcode Start(); + + public: // Override Interfaces + // Called once on application startup, use to load your resources + virtual bool OnUserCreate(); + // Called every frame, and provides you with a time per frame value + virtual bool OnUserUpdate(float fElapsedTime); + // Called once on application termination, so you can be a clean coder + virtual bool OnUserDestroy(); + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused(); + // Get the state of a specific keyboard button + HWButton GetKey(Key k); + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b); + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX(); + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY(); + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth(); + // Returns the height of the screen in "pixels" + int32_t ScreenHeight(); + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth(); + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight(); + // Returns the currently active draw target + Sprite* GetDrawTarget(); + + public: // Draw Routines + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite *target); + // Change the pixel mode for different optimisations + // olc::Pixel::NORMAL = No transparency + // olc::Pixel::MASK = Transparent if alpha is < 255 + // olc::Pixel::ALPHA = Full transparency + void SetPixelMode(Pixel::Mode m); + Pixel::Mode GetPixelMode(); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor form between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + // Offset texels by sub-pixel amount (advanced, do not use) + void SetSubPixelOffset(float ox, float oy); + + // Draws a single Pixel + virtual void Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Draws an entire sprite at location (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale = 1); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1); + // Draws a single line of text + void DrawString(int32_t x, int32_t y, std::string sText, Pixel col = olc::WHITE, uint32_t scale = 1); + // Clears entire draw target to Pixel + void Clear(Pixel p); + + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + Sprite *pDefaultDrawTarget = nullptr; + Sprite *pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + uint32_t nScreenWidth = 256; + uint32_t nScreenHeight = 240; + uint32_t nPixelWidth = 4; + uint32_t nPixelHeight = 4; + int32_t nMousePosX = 0; + int32_t nMousePosY = 0; + float fPixelX = 1.0f; + float fPixelY = 1.0f; + float fSubPixelOffsetX = 0.0f; + float fSubPixelOffsetY = 0.0f; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + float fFrameTimer = 1.0f; + int nFrameCount = 0; + Sprite *fontSprite = nullptr; + std::function funcPixelMode; + + static std::map mapKeys; + bool pKeyNewState[256]{ 0 }; + bool pKeyOldState[256]{ 0 }; + HWButton pKeyboardState[256]; + + bool pMouseNewState[5]{ 0 }; + bool pMouseOldState[5]{ 0 }; + HWButton pMouseState[5]; + +#ifdef _WIN32 + HDC glDeviceContext = nullptr; + HGLRC glRenderContext = nullptr; +#else + GLXContext glDeviceContext = nullptr; + GLXContext glRenderContext = nullptr; +#endif + GLuint glBuffer; + + void EngineThread(); + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + // Common initialisation functions + void olc_UpdateMouse(int32_t x, int32_t y); + bool olc_OpenGLCreate(); + void olc_ConstructFontSheet(); + +#ifdef _WIN32 + // Windows specific window handling + HWND olc_hWnd = nullptr; + HWND olc_WindowCreate(); + std::wstring wsAppName; + static LRESULT CALLBACK olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +#else + // Non-Windows specific window handling + Display* olc_Display = nullptr; + Window olc_WindowRoot; + Window olc_Window; + XVisualInfo* olc_VisualInfo; + Colormap olc_ColourMap; + XSetWindowAttributes olc_SetWindowAttribs; + Display* olc_WindowCreate(); +#endif + + }; + + + class PGEX + { + friend class olc::PixelGameEngine; + protected: + static PixelGameEngine* pge; + }; + + //============================================================= +} + +#endif // OLC_PGE_DEF + + + + +/* + Object Oriented Mode + ~~~~~~~~~~~~~~~~~~~~ + + If the olcPixelGameEngine.h is called from several sources it can cause + multiple definitions of objects. To prevent this, ONLY ONE of the pathways + to including this file must have OLC_PGE_APPLICATION defined before it. This prevents + the definitions being duplicated. + + Consider the following project structure: + + Class1.h - Includes olcPixelGameEngine.h, overrides olc::PixelGameEngine + Class1.cpp - #define OLC_PGE_APPLICATION #include "Class1.h" + Class2.h - Includes Class1.h, which includes olcPixelGameEngine.h + Class2.cpp - #define OLC_PGE_APPLICATION #include "Class2.h" + main.cpp - Includes Class1.h and Class2.h + + If all of this is a bit too confusing, you can split this file in two! + Everything below this comment block can go into olcPixelGameEngineOOP.cpp + and everything above it can go into olcPixelGameEngineOOP.h + +*/ + +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +namespace olc +{ + Pixel::Pixel() + { + r = 0; g = 0; b = 0; a = 255; + } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { + r = red; g = green; b = blue; a = alpha; + } + + Pixel::Pixel(uint32_t p) + { + n = p; + } + + //========================================================== + +// std::wstring ConvertS2W(std::string s) +// { +// #ifdef _WIN32 +// int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); +// wchar_t* buffer = new wchar_t[count]; +// MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +// std::wstring w(buffer); +// delete[] buffer; +// return w; +// #endif +//#ifdef __MINGW32__ +// wchar_t *buffer = new wchar_t[sImageFile.length() + 1]; +// mbstowcs(buffer, sImageFile.c_str(), sImageFile.length()); +// buffer[sImageFile.length()] = L'\0'; +// wsImageFile = buffer; +// delete[] buffer; +//#else + // } + + Sprite::Sprite() + { + pColData = nullptr; + width = 0; + height = 0; + } + + Sprite::Sprite(std::string sImageFile) + { + LoadFromFile(sImageFile); + } + + Sprite::Sprite(std::string sImageFile, olc::ResourcePack *pack) + { + LoadFromPGESprFile(sImageFile, pack); + } + + Sprite::Sprite(int32_t w, int32_t h) + { + if(pColData) delete[] pColData; + width = w; height = h; + pColData = new Pixel[width * height]; + for (int32_t i = 0; i < width*height; i++) + pColData[i] = Pixel(); + } + + Sprite::~Sprite() + { + if (pColData) delete pColData; + } + + olc::rcode Sprite::LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack) + { + if (pColData) delete[] pColData; + + auto ReadData = [&](std::istream &is) + { + is.read((char*)&width, sizeof(int32_t)); + is.read((char*)&height, sizeof(int32_t)); + pColData = new Pixel[width * height]; + is.read((char*)pColData, width * height * sizeof(uint32_t)); + }; + + // These are essentially Memory Surfaces represented by olc::Sprite + // which load very fast, but are completely uncompressed + if (pack == nullptr) + { + std::ifstream ifs; + ifs.open(sImageFile, std::ifstream::binary); + if (ifs.is_open()) + { + ReadData(ifs); + return olc::OK; + } + else + return olc::FAIL; + } + else + { + auto streamBuffer = pack->GetStreamBuffer(sImageFile); + std::istream is(&streamBuffer); + ReadData(is); + } + + + return olc::FAIL; + } + + olc::rcode Sprite::SaveToPGESprFile(std::string sImageFile) + { + if (pColData == nullptr) return olc::FAIL; + + std::ofstream ofs; + ofs.open(sImageFile, std::ifstream::binary); + if (ofs.is_open()) + { + ofs.write((char*)&width, sizeof(int32_t)); + ofs.write((char*)&height, sizeof(int32_t)); + ofs.write((char*)pColData, width*height*sizeof(uint32_t)); + ofs.close(); + return olc::OK; + } + + return olc::FAIL; + } + + olc::rcode Sprite::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack) + { +#ifdef _WIN32 + // Use GDI+ + std::wstring wsImageFile; +#ifdef __MINGW32__ + wchar_t *buffer = new wchar_t[sImageFile.length() + 1]; + mbstowcs(buffer, sImageFile.c_str(), sImageFile.length()); + buffer[sImageFile.length()] = L'\0'; + wsImageFile = buffer; + delete [] buffer; +#else + // wsImageFile = ConvertS2W(sImageFile); +#endif + Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(wsImageFile.c_str()); + if (bmp == nullptr) + return olc::NO_FILE; + + width = bmp->GetWidth(); + height = bmp->GetHeight(); + pColData = new Pixel[width * height]; + + for(int x=0; xGetPixel(x, y, &c); + SetPixel(x, y, Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::OK; +#else + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + png_structp png; + png_infop info; + + FILE *f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::NO_FILE; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + png_init_io(png, f); + png_read_info(png, info); + + png_byte color_type; + png_byte bit_depth; + png_bytep *row_pointers; + width = png_get_image_width(png, info); + height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + +#ifdef _DEBUG + std::cout << "Loading PNG: " << sImageFile << "\n"; + std::cout << "W:" << width << " H:" << height << " D:" << (int)bit_depth << "\n"; +#endif + + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); + for (int y = 0; y < height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + + // Create sprite array + pColData = new Pixel[width * height]; + + // Iterate through image rows, converting into sprite format + for (int y = 0; y < height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < width; x++) + { + png_bytep px = &(row[x * 4]); + SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + fclose(f); + return olc::OK; + + fail_load: + width = 0; + height = 0; + fclose(f); + pColData = nullptr; + return olc::FAIL; +#endif + } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { + modeSample = mode; + } + + + Pixel Sprite::GetPixel(int32_t x, int32_t y) + { + if (modeSample == olc::Sprite::Mode::NORMAL) + { + if (x >= 0 && x < width && y >= 0 && y < height) + return pColData[y*width + x]; + else + return Pixel(0, 0, 0, 0); + } + else + { + return pColData[abs(y%height)*width + abs(x%width)]; + } + } + + void Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + +#ifdef OLC_DBG_OVERDRAW + nOverdrawCount++; +#endif + + if (x >= 0 && x < width && y >= 0 && y < height) + pColData[y*width + x] = p; + } + + Pixel Sprite::Sample(float x, float y) + { + int32_t sx = (int32_t)(x * (float)width); + int32_t sy = (int32_t)(y * (float)height); + return GetPixel(sx, sy); + } + + Pixel* Sprite::GetData() { return pColData; } + + //========================================================== + + ResourcePack::ResourcePack() + { + + } + + // ResourcePack::~ResourcePack() + // { + // ClearPack(); + // } + + olc::rcode ResourcePack::AddToPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // Get File Size + std::streampos p = 0; + p = ifs.tellg(); + ifs.seekg(0, std::ios::end); + p = ifs.tellg() - p; + ifs.seekg(0, std::ios::beg); + + // Create entry + sEntry e; + e.data = nullptr; + e.nFileSize = (uint32_t)p; + + // Read file into memory + e.data = new uint8_t[(uint32_t)e.nFileSize]; + ifs.read((char*)e.data, e.nFileSize); + ifs.close(); + + // Add To Map + mapFiles[sFile] = e; + return olc::OK; + } + + olc::rcode ResourcePack::SavePack(std::string sFile) + { + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return olc::FAIL; + + // 1) Write Map + size_t nMapSize = mapFiles.size(); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + + // 2) Write Data + std::streampos offset = ofs.tellp(); + for (auto &e : mapFiles) + { + e.second.nFileOffset = (uint32_t)offset; + ofs.write((char*)e.second.data, e.second.nFileSize); + offset += e.second.nFileSize; + } + + // 3) Rewrite Map (it has been updated with offsets now) + ofs.seekp(std::ios::beg); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + ofs.close(); + + return olc::OK; + } + + olc::rcode ResourcePack::LoadPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // 1) Read Map + size_t nMapEntries; + ifs.read((char*)&nMapEntries, sizeof(size_t)); + for (size_t i = 0; i < nMapEntries; i++) + { + size_t nFilePathSize = 0; + ifs.read((char*)&nFilePathSize, sizeof(size_t)); + + std::string sFileName(nFilePathSize, ' '); + for (size_t j = 0; j < nFilePathSize; j++) + sFileName[j] = ifs.get(); + + sEntry e; + e.data = nullptr; + ifs.read((char*)&e.nID, sizeof(uint32_t)); + ifs.read((char*)&e.nFileSize, sizeof(uint32_t)); + ifs.read((char*)&e.nFileOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // 2) Read Data + for (auto &e : mapFiles) + { + e.second.data = new uint8_t[(uint32_t)e.second.nFileSize]; + ifs.seekg(e.second.nFileOffset); + ifs.read((char*)e.second.data, e.second.nFileSize); + e.second._config(); + } + + ifs.close(); + return olc::OK; + } + + olc::ResourcePack::sEntry ResourcePack::GetStreamBuffer(std::string sFile) + { + return mapFiles[sFile]; + } + + olc::rcode ResourcePack::ClearPack() + { + for (auto &e : mapFiles) + { + if (e.second.data != nullptr) + delete[] e.second.data; + } + + mapFiles.clear(); + return olc::OK; + } + + //========================================================== + + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + } + + olc::rcode PixelGameEngine::Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h) + { + nScreenWidth = screen_w; + nScreenHeight = screen_h; + nPixelWidth = pixel_w; + nPixelHeight = pixel_h; + + fPixelX = 2.0f / (float)(nScreenWidth); + fPixelY = 2.0f / (float)(nScreenHeight); + + if (nPixelWidth == 0 || nPixelHeight == 0 || nScreenWidth == 0 || nScreenHeight == 0) + return olc::FAIL; + +#ifdef _WIN32 +#ifdef UNICODE +#ifndef __MINGW32__ + wsAppName = ConvertS2W(sAppName); +#endif +#endif +#endif + // Load the default font sheet + olc_ConstructFontSheet(); + + // Create a sprite that represents the primary drawing target + pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); + SetDrawTarget(nullptr); + return olc::OK; + } + + olc::rcode PixelGameEngine::Start() + { + // Construct the window + if (!olc_WindowCreate()) + return olc::FAIL; + + // Load libraries required for PNG file interaction +#ifdef _WIN32 + // Windows use GDI+ + Gdiplus::GdiplusStartupInput startupInput; + ULONG_PTR token; + Gdiplus::GdiplusStartup(&token, &startupInput, NULL); +#else + // Linux use libpng + +#endif + // Start the thread + bAtomActive = true; + std::thread t = std::thread(&PixelGameEngine::EngineThread, this); + +#ifdef _WIN32 + // Handle Windows Message Loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#endif + + // Wait for thread to be exited + t.join(); + return olc::OK; + } + + void PixelGameEngine::SetDrawTarget(Sprite *target) + { + if (target) + pDrawTarget = target; + else + pDrawTarget = pDefaultDrawTarget; + } + + Sprite* PixelGameEngine::GetDrawTarget() + { + return pDrawTarget; + } + + int32_t PixelGameEngine::GetDrawTargetWidth() + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + bool PixelGameEngine::IsFocused() + { + return bHasInputFocus; + } + + HWButton PixelGameEngine::GetKey(Key k) + { + return pKeyboardState[k]; + } + + HWButton PixelGameEngine::GetMouse(uint32_t b) + { + return pMouseState[b]; + } + + int32_t PixelGameEngine::GetMouseX() + { + return nMousePosX; + } + + int32_t PixelGameEngine::GetMouseY() + { + return nMousePosY; + } + + int32_t PixelGameEngine::ScreenWidth() + { + return nScreenWidth; + } + + int32_t PixelGameEngine::ScreenHeight() + { + return nScreenHeight; + } + + void PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return; + + + if (nPixelMode == Pixel::NORMAL) + { + pDrawTarget->SetPixel(x, y, p); + return; + } + + if (nPixelMode == Pixel::MASK) + { + if(p.a == 255) + pDrawTarget->SetPixel(x, y, p); + return; + } + + if (nPixelMode == Pixel::ALPHA) + { + Pixel d = pDrawTarget->GetPixel(x, y); + float a = (float)(p.a / 255.0f) * fBlendFactor; + float c = 1.0f - a; + float r = a * (float)p.r + c * (float)d.r; + float g = a * (float)p.g + c * (float)d.g; + float b = a * (float)p.b + c * (float)d.b; + pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b)); + return; + } + + if (nPixelMode == Pixel::CUSTOM) + { + pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + return; + } + } + + void PixelGameEngine::SetSubPixelOffset(float ox, float oy) + { + fSubPixelOffsetX = ox * fPixelX; + fSubPixelOffsetY = oy * fPixelY; + } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + // straight lines idea by gurkanctn + if (dx == 0) // Line is vertical + { + if (y2 < y1) std::swap(y1, y2); + for (y = y1; y <= y2; y++) + Draw(x1, y, p); + return; + } + + if (dy == 0) // Line is horizontal + { + if (x2 < x1) std::swap(x1, x2); + for (x = x1; x <= x2; x++) + Draw(x, y1, p); + return; + } + + // Line is Funk-aye + 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, p); + + for (i = 0; x0 && dy>0)) y = y + 1; else y = y - 1; + px = px + 2 * (dy1 - dx1); + } + Draw(x, y, p); + } + } + else + { + if (dy >= 0) + { + x = x1; y = y1; ye = y2; + } + else + { + x = x2; y = y2; ye = y1; + } + + Draw(x, y, p); + + for (i = 0; y0 && dy>0)) x = x + 1; else x = x - 1; + py = py + 2 * (dx1 - dy1); + } + Draw(x, y, p); + } + } + } + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + while (y0 >= x0) // only formulate 1/8 of circle + { + Draw(x - x0, y - y0, p);//upper left left + Draw(x - y0, y - x0, p);//upper upper left + Draw(x + y0, y - x0, p);//upper upper right + Draw(x + x0, y - y0, p);//upper right right + Draw(x - x0, y + y0, p);//lower left left + Draw(x - y0, y + x0, p);//lower lower left + Draw(x + y0, y + x0, p);//lower lower right + Draw(x + x0, y + y0, p);//lower right right + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { + // Taken from wikipedia + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + auto drawline = [&](int sx, int ex, int ny) + { + for (int i = sx; i <= ex; i++) + Draw(i, ny, p); + }; + + while (y0 >= x0) + { + // Modified to draw scan-lines instead of edges + drawline(x - x0, x + x0, y - y0); + drawline(x - y0, x + y0, y - x0); + drawline(x - x0, x + x0, y + y0); + drawline(x - y0, x + y0, y + x0); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + w--; h--; + DrawLine(x, y, x+w, y, p); + DrawLine(x+w, y, x+w, y+h, p); + DrawLine(x+w, y+h, x, y+h, p); + DrawLine(x, y+h, x, y, p); + } + + void PixelGameEngine::Clear(Pixel p) + { + int pixels = GetDrawTargetWidth() * GetDrawTargetHeight(); + Pixel* m = GetDrawTarget()->GetData(); + for (int i = 0; i < pixels; i++) + m[i] = p; +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount += pixels; +#endif + } + + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + int32_t x2 = x + w; + int32_t y2 = y + h; + + if (x < 0) x = 0; + if (x >= (int32_t)nScreenWidth) x = (int32_t)nScreenWidth; + if (y < 0) y = 0; + if (y >= (int32_t)nScreenHeight) y = (int32_t)nScreenHeight; + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)nScreenWidth) x2 = (int32_t)nScreenWidth; + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)nScreenHeight) y2 = (int32_t)nScreenHeight; + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, p); + } + + void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + DrawLine(x1, y1, x2, y2, p); + DrawLine(x2, y2, x3, y3, p); + DrawLine(x3, y3, x1, y1, p); + } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + 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, p); }; + + 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 PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i, j)); + } + else + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + Draw(x + i, y + j, sprite->GetPixel(i, j)); + } + } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i + ox, j + oy)); + } + else + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + Draw(x + i, y + j, sprite->GetPixel(i + ox, j + oy)); + } + } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, std::string sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + if(col.ALPHA != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + sx + (i*scale) + is, y + sy + (j*scale) + js, col); + } + else + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += 8 * scale; + } + } + SetPixelMode(m); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { + nPixelMode = m; + } + + Pixel::Mode PixelGameEngine::GetPixelMode() + { + return nPixelMode; + } + + void PixelGameEngine::SetPixelMode(std::function pixelMode) + { + funcPixelMode = pixelMode; + nPixelMode = Pixel::Mode::CUSTOM; + } + + void PixelGameEngine::SetPixelBlend(float fBlend) + { + fBlendFactor = fBlend; + if (fBlendFactor < 0.0f) fBlendFactor = 0.0f; + if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; + } + + // User must override these functions as required. I have not made + // them abstract because I do need a default behaviour to occur if + // they are not overwritten + bool PixelGameEngine::OnUserCreate() + { return false; } + bool PixelGameEngine::OnUserUpdate(float fElapsedTime) + { return false; } + bool PixelGameEngine::OnUserDestroy() + { return true; } + ////////////////////////////////////////////////////////////////// + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // Mouse coords come in screen space + // But leave in pixel space + nMousePosX = x / (int32_t)nPixelWidth; + nMousePosY = y / (int32_t)nPixelHeight; + + if (nMousePosX >= (int32_t)nScreenWidth) + nMousePosX = nScreenWidth - 1; + if (nMousePosY >= (int32_t)nScreenHeight) + nMousePosY = nScreenHeight - 1; + + if (nMousePosX < 0) + nMousePosX = 0; + if (nMousePosY < 0) + nMousePosY = 0; + } + + void PixelGameEngine::EngineThread() + { + // Start OpenGL, the context is owned by the game thread + olc_OpenGLCreate(); + + // Create Screen Texture - disable filtering + glEnable(GL_TEXTURE_2D); + glGenTextures(1, &glBuffer); + glBindTexture(GL_TEXTURE_2D, glBuffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nScreenWidth, nScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + + // Create user resources as part of this thread + if (!OnUserCreate()) + bAtomActive = false; + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) + { + // Handle Timing + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + +#ifndef _WIN32 + // Handle Xlib Message Loop - we do this in the + // same thread that OpenGL was created so we dont + // need to worry too much about multithreading with X11 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = true; + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = false; + } + else if (xev.type == ButtonPress) + { + pMouseNewState[xev.xbutton.button-1] = true; + } + else if (xev.type == ButtonRelease) + { + pMouseNewState[xev.xbutton.button-1] = false; + } + else if (xev.type == MotionNotify) + { + olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + bHasInputFocus = true; + } + else if (xev.type == FocusOut) + { + bHasInputFocus = false; + } + else if (xev.type == ClientMessage) + { + bAtomActive = false; + } + } +#endif + + // Handle User Input - Keyboard + for (int i = 0; i < 256; i++) + { + pKeyboardState[i].bPressed = false; + pKeyboardState[i].bReleased = false; + + if (pKeyNewState[i] != pKeyOldState[i]) + { + if (pKeyNewState[i]) + { + pKeyboardState[i].bPressed = !pKeyboardState[i].bHeld; + pKeyboardState[i].bHeld = true; + } + else + { + pKeyboardState[i].bReleased = true; + pKeyboardState[i].bHeld = false; + } + } + + pKeyOldState[i] = pKeyNewState[i]; + } + + // Handle User Input - Mouse + for (int i = 0; i < 5; i++) + { + pMouseState[i].bPressed = false; + pMouseState[i].bReleased = false; + + if (pMouseNewState[i] != pMouseOldState[i]) + { + if (pMouseNewState[i]) + { + pMouseState[i].bPressed = !pMouseState[i].bHeld; + pMouseState[i].bHeld = true; + } + else + { + pMouseState[i].bReleased = true; + pMouseState[i].bHeld = false; + } + } + + pMouseOldState[i] = pMouseNewState[i]; + } + +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount = 0; +#endif + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + bAtomActive = false; + + // Display Graphics + + // TODO: This is a bit slow (especially in debug, but 100x faster in release mode???) + // Copy pixel array into texture + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, nScreenWidth, nScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + // Display texture on screen + glBegin(GL_QUADS); + glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 0.0); glVertex3f( 1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 1.0); glVertex3f( 1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glEnd(); + + // Present Graphics to screen +#ifdef _WIN32 + SwapBuffers(glDeviceContext); +#else + glXSwapBuffers(olc_Display, olc_Window); +#endif + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + fFrameTimer -= 1.0f; + + std::string sTitle = sAppName + " - FPS: " + std::to_string(nFrameCount); +#ifdef _WIN32 +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(sTitle).c_str()); +#else + SetWindowText(olc_hWnd, sTitle.c_str()); +#endif +#else + XStoreName(olc_Display, olc_Window, sTitle.c_str()); +#endif + nFrameCount = 0; + } + } + + // Allow the user to free resources if they have overrided the destroy function + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + } + else + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + +#ifdef _WIN32 + wglDeleteContext(glRenderContext); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); +#else + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); + XDestroyWindow(olc_Display, olc_Window); + XCloseDisplay(olc_Display); +#endif + + } + + + void PixelGameEngine::olc_ConstructFontSheet() + { + std::string data; + data += "?Q`0001oOch0o01o@F40o000000000"; + data += "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400"; + data += "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000"; + data += "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000"; + data += "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000"; + data += "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000"; + data += "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000"; + data += "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000"; + data += "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000"; + data += "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000"; + data += "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000"; + data += "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000"; + data += "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000"; + data += "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0"; + data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + if (++py == 48) { px++; py = 0; } + } + } + } + +#ifdef _WIN32 + HWND PixelGameEngine::olc_WindowCreate() + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; +#ifdef UNICODE + wc.lpszClassName = L"OLC_PIXEL_GAME_ENGINE"; +#else + wc.lpszClassName = "OLC_PIXEL_GAME_ENGINE"; +#endif + + RegisterClass(&wc); + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE; + RECT rWndRect = { 0, 0, (LONG)nScreenWidth * (LONG)nPixelWidth, (LONG)nScreenHeight * (LONG)nPixelHeight }; + + // Keep client size as requested + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + +#ifdef UNICODE + olc_hWnd = CreateWindowEx(dwExStyle, L"OLC_PIXEL_GAME_ENGINE", L"", dwStyle, + 30, 30, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#else + olc_hWnd = CreateWindowEx(dwExStyle, "OLC_PIXEL_GAME_ENGINE", "", dwStyle, + 30, 30, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#endif + + // Create Keyboard Mapping + mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + mapKeys[VK_RETURN] = Key::ENTER; //mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + return olc_hWnd; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + // Create Device Context + glDeviceContext = GetDC(olc_hWnd); + 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 = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return false; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return false; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + wglSwapInterval(0); + return true; + } + + // Windows Event Handler + LRESULT CALLBACK PixelGameEngine::olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static PixelGameEngine *sge; + switch (uMsg) + { + case WM_CREATE: sge = (PixelGameEngine*)((LPCREATESTRUCT)lParam)->lpCreateParams; return 0; + case WM_MOUSEMOVE: + { + uint16_t x = lParam & 0xFFFF; // Thanks @ForAbby (Discord) + uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; + int16_t iy = *(int16_t*)&y; + sge->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_MOUSELEAVE: sge->bHasMouseFocus = false; + case WM_SETFOCUS: sge->bHasInputFocus = true; return 0; + case WM_KILLFOCUS: sge->bHasInputFocus = false; return 0; + case WM_KEYDOWN: sge->pKeyNewState[mapKeys[wParam]] = true; return 0; + case WM_KEYUP: sge->pKeyNewState[mapKeys[wParam]] = false; return 0; + case WM_LBUTTONDOWN:sge->pMouseNewState[0] = true; return 0; + case WM_LBUTTONUP: sge->pMouseNewState[0] = false; return 0; + case WM_RBUTTONDOWN:sge->pMouseNewState[1] = true; return 0; + case WM_RBUTTONUP: sge->pMouseNewState[1] = false; return 0; + case WM_MBUTTONDOWN:sge->pMouseNewState[2] = true; return 0; + case WM_MBUTTONUP: sge->pMouseNewState[2] = false; return 0; + case WM_CLOSE: bAtomActive = false; return 0; + case WM_DESTROY: PostQuitMessage(0); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } +#else + // Do the Linux stuff! + Display* PixelGameEngine::olc_WindowCreate() + { + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, 30, 30, nScreenWidth * nPixelWidth, nScreenHeight * nPixelHeight, 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, ""); + + // Create Keyboard Mapping + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + return olc_Display; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + if (glSwapIntervalEXT) + glSwapIntervalEXT(olc_Display, olc_Window, 0); + else + { + printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n"); + printf(" Don't worry though, things will still work, it's just the\n"); + printf(" frame rate will be capped to your monitors refresh rate - javidx9\n"); + } + + return true; + } + +#endif + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + std::map PixelGameEngine::mapKeys; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; +#ifdef OLC_DBG_OVERDRAW + int olc::Sprite::nOverdrawCount = 0; +#endif + //============================================================= +} + +#endif + +#ifndef MATHS_H_ +#define MATHS_H_ + +const float DEG2RAD = 0.01745329251994329576923690768f; +const float RAD2DEG = 57.2957795130823208767981548141f; + +inline float ToRadian(const float Degree) { + return (Degree * DEG2RAD); +} + +inline float ToDegree(const float Radian) { + return (Radian * RAD2DEG); +} + +template +struct Vec4 { + T x, y, z, w; + template + Vec4(P x, P y, P z, P w) : x(x), y(y), z(z), w(w) {} + template + Vec4(P all) : x(all), y(all), z(all), w(all) {} + Vec4() : x(0), y(0), z(0), w(0) {} + inline Vec4& dot(const Vec4& v) { + return (x * v.x + y * v.y + z * v.z + w * v.w); + } + inline const Vec4& operator+() { + return *this; + } + inline Vec4& operator-() { + return Vec4(-x, -y, -z, -w); + } + inline Vec4& operator+(const Vec4& v) { + return new Vec4(x + v.x, y + v.y, z + v.z, w + v.w); + } + inline Vec4& operator-(const Vec4& v) { + return new Vec4(x - v.x, y - v.y, z - v.z, w - v.w); + } + inline Vec4& operator*(const Vec4& v) { + return new Vec4(x * v.x, y * v.y, z * v.z, w * v.w); + } + inline Vec4& operator/(const Vec4& v) { + return new Vec4(x / v.x, y / v.y, z / v.z, w / v.w); + } + inline Vec4& operator+=(const Vec4& v) { + x+=v.x; y+=v.y; z+=v.z; w+=v.w; + return *this; + } + inline Vec4& operator-=(const Vec4& v) { + x-=v.x; y-=v.y; z-=v.z; w-=v.w; + return *this; + } + inline Vec4& operator*=(const Vec4& v) { + x*=v.x; y*=v.y; z*=v.z; w*=v.w; + return *this; + } + inline Vec4& operator/=(const Vec4& v) { + x/=v.x; y/=v.y; z/=v.z; w/=v.w; + return *this; + } + template + inline Vec4& operator+=(P s) { + x+=s; y+=s; z+=s; w+=s; + return *this; + } + template + inline Vec4& operator-=(P s) { + x-=s; y-=s; z-=s; w-=s; + return *this; + } + template + inline Vec4& operator*=(P s) { + x*=s; y*=s; z*=s; w*=s; + return *this; + } + template + inline Vec4& operator/=(P s) { + x/=s; y/=s; z/=s; w/=s; + return *this; + } +}; + +template +struct Vec3 { + T x, y, z; + template + Vec3(P x, P y, P z) : x(x), y(y), z(z) {} + template + Vec3(P all) : x(all), y(all), z(all) {} + Vec3() : x(0), y(0), z(0) {} + inline Vec3& cross(const Vec3& v) { + return new Vec3( + (y * v.z - z * v.y), + (x * v.z - z * v.x), + (x * v.y - y * v.x) + ); + } + inline Vec3& dot(const Vec3& v) { + return (x * v.x + y * v.y + z * v.z); + } + inline const Vec3& operator+() { + return *this; + } + inline Vec3& operator-() { + return Vec3(-x, -y, -z); + } + inline Vec3& operator+(const Vec3& v) { + return new Vec3(x + v.x, y + v.y, z + v.z); + } + inline Vec3& operator-(const Vec3& v) { + return new Vec3(x - v.x, y - v.y, z - v.z); + } + inline Vec3& operator*(const Vec3& v) { + return new Vec3(x * v.x, y * v.y, z * v.z); + } + inline Vec3& operator/(const Vec3& v) { + return new Vec3(x / v.x, y / v.y, z / v.z); + } + inline Vec3& operator+=(const Vec3& v) { + x+=v.x; y+=v.y; z+=v.z; + return *this; + } + inline Vec3& operator-=(const Vec3& v) { + x-=v.x; y-=v.y; z-=v.z; + return *this; + } + inline Vec3& operator*=(const Vec3& v) { + x*=v.x; y*=v.y; z*=v.z; + return *this; + } + inline Vec3& operator/=(const Vec3& v) { + x/=v.x; y/=v.y; z/=v.z; + return *this; + } + template + inline Vec3& operator+=(P s) { + x+=s; y+=s; z+=s; + return *this; + } + template + inline Vec3& operator-=(P s) { + x-=s; y-=s; z-=s; + return *this; + } + template + inline Vec3& operator*=(P s) { + x*=s; y*=s; z*=s; + return *this; + } + template + inline Vec3& operator/=(P s) { + x/=s; y/=s; z/=s; + return *this; + } +}; + +template +struct Vec2 { + T x, y; + template + Vec2(P x, P y) : x(x), y(y) {} + template + Vec2(P all) : x(all), y(all) {} + Vec2() : x(0), y(0) {} + inline const Vec2& operator+() { + return *this; + } + inline Vec2& dot(const Vec3& v) { + return (x * v.x + y * v.y); + } + inline Vec2& operator-() { + return Vec3(-x, -y); + } + inline Vec2& operator+(const Vec2& v) { + return new Vec2(x + v.x, y + v.y); + } + inline Vec2& operator-(const Vec2& v) { + return new Vec2(x - v.x, y - v.y); + } + inline Vec2& operator*(const Vec2& v) { + return new Vec2(x * v.x, y * v.y); + } + inline Vec2& operator/(const Vec2& v) { + return new Vec2(x / v.x, y / v.y); + } + inline Vec2& operator+=(const Vec2& v) { + x+=v.x; y+=v.y; + return *this; + } + inline Vec2& operator-=(const Vec2& v) { + x-=v.x; y-=v.y; + return *this; + } + inline Vec2& operator*=(const Vec2& v) { + x*=v.x; y*=v.y; + return *this; + } + inline Vec2& operator/=(const Vec2& v) { + x/=v.x; y/=v.y; + return *this; + } + template + inline Vec2& operator+=(P s) { + x+=s; y+=s; + return *this; + } + template + inline Vec2& operator-=(P s) { + x-=s; y-=s; + return *this; + } + template + inline Vec2& operator*=(P s) { + x*=s; y*=s; + return *this; + } + template + inline Vec2& operator/=(P s) { + x/=s; y/=s; + return *this; + } +}; + +#endif + +#ifndef RECT_H_ +#define RECT_H_ + +#include + +class Rect { +public: + Rect(); + Rect(int x, int y, int w, int h); + void Clear(); + + static Rect CreateRect(int x, int y, int w, int h) { + Rect tempRect(x, y, w, h); + return tempRect; + } + + Rect operator+(Rect* rect) { + return Rect(this->x + rect->x, this->y + this->x, w, h); + } + Rect operator-(Rect* rect) { + return Rect(this->x - rect->x, this->y - this->x, w, h); + } + bool operator==(const Rect* rect) { + return (x == rect->x && y == rect->y && w == rect->w && h == rect->h); + } + bool operator!=(const Rect* rect) { + return !(x == rect->x && y == rect->y && w == rect->w && h == rect->h); + } + + std::string ToString(); + + bool Intersects(Rect* rect); + // bool Intersects(int x, int y, int w, int h); + + bool Contains(Rect* rect); + bool Contains(Vec2* point); + bool Contains(Vec2 point); + bool Contains(int x, int y, int w, int h); + + Vec2* Position(); + Vec2* Center(); + int CenterX(); + int CenterY(); + + int Left(); + int Right(); + int Top(); + int Bottom(); + int Perimiter(); + int Area(); + + int GetX(); + int GetY(); + int GetW(); + int GetH(); + + void SetRect(int x, int y, int w, int h); + void SetSize(Vec2* size); + void SetPos(Vec2* pos); + void SetPos(int x, int y); + void Translate(Vec2* offset); + void TranslateX(int x); + void TranslateY(int y); + + int x, y, w, h; + + virtual ~Rect(); +private: +}; + +#endif + +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +Rect::Rect() { + Clear(); +} + +Rect::Rect(int x, int y, int w, int h) { + SetRect(x, y, w, h); +} + +void Rect::Clear() { + SetRect(0, 0, 0, 0); +} + +std::string Rect::ToString() { + std::string res = "("; + res += std::to_string(x); + res += ", "; + res += std::to_string(y); + res += ", "; + res += std::to_string(w); + res += ", "; + res += std::to_string(h); + res += ")"; + return res; +} + +bool Rect::Intersects(Rect* rect) { + int leftA = x; + int rightA = x + w; + int topA = y; + int bottomA = y + h; + + int leftB = rect->x; + int rightB = rect->x + rect->w; + int topB = rect->y; + int bottomB = rect->y + rect->h; + + if (bottomA <= topB) return false; + if (topA >= bottomB) return false; + if (rightA <= leftB) return false; + if (leftA >= rightB) return false; + + return true; +} + +// bool Rect::Intersects(int x, int y, int w, int h) { +// return Intersects(&CreateRect(x, y, w, h)); +// } + +bool Rect::Contains(Rect* rect) { + return (rect->x >= x && rect->Right() <= (x + w) && rect->y >= y && rect->Bottom() <= (y + h)); +} + +bool Rect::Contains(Vec2* point) { + return (point->x >= x && point->x <= (x + w) && point->y >= y && point->y <= (y + h)); +} + + +bool Rect::Contains(Vec2 point) { + return (point.x >= x && point.x <= (x + w) && point.y >= y && point.y <= (y + h)); +} + +bool Rect::Contains(int x, int y, int w, int h) { + Rect tempRect(x, y, w, h); + return Contains(&tempRect); +} + +Vec2* Rect::Position() { + Vec2* res = new Vec2(x, y); + return res; +} + +Vec2* Rect::Center() { + Vec2* res = new Vec2(x + (w / 2), y + (h / 2)); + return res; +} + +int Rect::CenterX() { + return (x + (w / 2)); +} + +int Rect::CenterY() { + return (y + (h / 2)); +} + +int Rect::Left() { + return x; +} + +int Rect::Right() { + return (x + w); +} + +int Rect::Top() { + return y; +} + +int Rect::Bottom() { + return y + h; +} + +int Rect::Perimiter() { + return (w + w + h + h); +} + +int Rect::Area() { + return (w + h); +} + +int Rect::GetX() { + return x; +} + +int Rect::GetY() { + return y; +} + +int Rect::GetW() { + return w; +} + +int Rect::GetH() { + return h; +} + +void Rect::SetRect(int x, int y, int w, int h) { + this->x = x; + this->y = y; + this->w = w; + this->h = h; +} + +void Rect::SetSize(Vec2* size) { + this->x = size->x; + this->y = size->y; +} + +void Rect::SetPos(Vec2* pos) { + this->w = pos->x; + this->h = pos->y; +} + +void Rect::SetPos(int x, int y) { + this->w = x; + this->h = y; +} + +void Rect::Translate(Vec2* offset) { + this->x += offset->x; + this->y += offset->y; +} + +void Rect::TranslateX(int x) { + this->x += x; +} + +void Rect::TranslateY(int y) { + this->y += y; +} + +Rect::~Rect() { + +} + +#endif diff --git a/C++/snake-movement-mechanic/output.o b/C++/snake-movement-mechanic/output.o new file mode 100755 index 0000000000000000000000000000000000000000..ab6dcb73e311468bdb45854c0debb0195131207a GIT binary patch literal 165896 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%0A|meH>+f9bcA~VA-(P1>Z}+V1Z-MwY0uG4w5HG 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 Zf6?pe-cR65sQ)`Zk{ACKuY