diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 24 | ||||
-rw-r--r-- | src/bitmap.cpp | 53 | ||||
-rw-r--r-- | src/bitmap.h | 22 | ||||
-rw-r--r-- | src/level.cpp | 54 | ||||
-rw-r--r-- | src/level.h | 28 | ||||
-rw-r--r-- | src/main.cpp | 154 | ||||
-rw-r--r-- | src/train.cpp | 52 | ||||
-rw-r--r-- | src/train.h | 27 | ||||
-rw-r--r-- | src/window.cpp | 61 | ||||
-rw-r--r-- | src/window.h | 36 |
10 files changed, 511 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..7e71ff2 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,24 @@ +SET(SOURCES + bitmap.cpp + level.cpp + main.cpp + train.cpp + window.cpp +) + +add_executable( + Nonortho + ${SOURCES} +) + +target_include_directories( + Nonortho + PUBLIC + ${PROJECT_SOURCE_DIR}/include +) + +target_link_libraries( + Nonortho + PRIVATE + SDL2::SDL2 +) diff --git a/src/bitmap.cpp b/src/bitmap.cpp new file mode 100644 index 0000000..612f2af --- /dev/null +++ b/src/bitmap.cpp @@ -0,0 +1,53 @@ +#include "bitmap.h" + +#define STB_IMAGE_IMPLEMENTATION +#include <stb/stb_image.h> + +Bitmap::Bitmap(const char* image) +{ + int width, height, bitDepth; + + uint8_t* buffer = stbi_load(image, &width, &height, &bitDepth, STBI_rgb_alpha); + if (!buffer) { + return; + } + + this->width = width; + this->height = height; + data = new uint32_t[width * height]; + + for (int i = 0; i < width * height; i++) { + uint8_t r = buffer[i * 4 + 0]; + uint8_t g = buffer[i * 4 + 1]; + uint8_t b = buffer[i * 4 + 2]; + uint8_t a = buffer[i * 4 + 3]; + + data[i] = (a << 24) | (r << 16) | (g << 8) | b; + } + + stbi_image_free(buffer); +} + +void Bitmap::clear(uint32_t colour) +{ + memset(data, colour, width * height * 4); +} + +void Bitmap::blit(Bitmap const& other, int xo, int yo, int xc, int yc, int w, int h) +{ + constexpr int MaskColour = 0xffff00ff; + + for (int y = 0; y < h; ++y) { + int yp = y + yo; + if (yp < 0 || yp >= height) continue; + + for (int x = 0; x < w; ++x) { + int xp = x + xo; + if (xp < 0 || xp >= width) continue; + + int src = other.data[(x + xc) + (y + yc) * other.width]; + if (src != MaskColour) + data[xp + yp * width] = src; + } + } +} diff --git a/src/bitmap.h b/src/bitmap.h new file mode 100644 index 0000000..76f2f7a --- /dev/null +++ b/src/bitmap.h @@ -0,0 +1,22 @@ +#pragma once + +#include <cstdint> + +class Bitmap { +public: + Bitmap(int width, int height) + : width(width), height(height) + { + data = new uint32_t[width * height]; + } + + Bitmap(const char* image); + + ~Bitmap() { delete[] data; } + + void clear(uint32_t colour); + void blit(Bitmap const& other, int x, int y, int xc, int yc, int w, int h); + + uint32_t* data; + int width, height; +};
\ No newline at end of file diff --git a/src/level.cpp b/src/level.cpp new file mode 100644 index 0000000..4df1d5a --- /dev/null +++ b/src/level.cpp @@ -0,0 +1,54 @@ +#include "level.h" + +Level::Level(int width, int height) + : m_width(width), m_height(height) +{ + m_tiles = new uint8_t[width * height]; + memset(m_tiles, 0, width * height); +} + +uint8_t Level::get(int x, int y) +{ + if (inBounds(x, y)) { + return m_tiles[x + y * m_width]; + } else { + return 0; + } +} + +void Level::set(int x, int y, uint8_t tile) +{ + if (inBounds(x, y)) + m_tiles[x + y * m_width] = tile; +} + +RailDirection ChooseDirection(Level& level, int x, int y) +{ + if (!level.inBounds(x, y)) return NorthSouth; + + bool n = level.get(x, y - 1) > 0; + bool e = level.get(x + 1, y) > 0; + bool s = level.get(x, y + 1) > 0; + bool w = level.get(x - 1, y) > 0; + + if ((n || s) && !(e || w)) { + return NorthSouth; + } + if ((e || w) && !(n || s)) { + return EastWest; + } + else if (s && e) { + return SouthEast; + } + else if (s && w) { + return SouthWest; + } + else if (n && w) { + return NorthWest; + } + else if (n && e) { + return NorthEast; + } + + return NorthSouth; +}
\ No newline at end of file diff --git a/src/level.h b/src/level.h new file mode 100644 index 0000000..0ffdac2 --- /dev/null +++ b/src/level.h @@ -0,0 +1,28 @@ +#pragma once + +#include <cstdint> +#include <cstring> + +class Level { +public: + Level(int width, int height); + ~Level() { delete[] m_tiles; } + + uint8_t get(int x, int y); + void set(int x, int y, uint8_t tile); + bool inBounds(int x, int y) { return x >= 0 && x < m_width && y >= 0 && y < m_height; } +private: + int m_width, m_height; + uint8_t* m_tiles; +}; + +enum RailDirection { + NorthSouth = 1, + EastWest, + SouthEast, + SouthWest, + NorthWest, + NorthEast, +}; + +RailDirection ChooseDirection(Level& level, int x, int y);
\ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..b1942c9 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,154 @@ +#define SDL_MAIN_HANDLED +#include <cmath> + +#include "window.h" +#include "bitmap.h" +#include "level.h" +#include "train.h" + +struct Point2D { + float x, y; +}; + +Point2D screenToTile(Point2D source) +{ + Point2D result; + + constexpr int TILE_SIZE = 24; + + float mx = source.x; + float my = source.y; + float xx = (mx / TILE_SIZE + my / (TILE_SIZE / 2.f)); + float yy = (my / (TILE_SIZE / 2.f) - (mx / TILE_SIZE)); + + result.x = floor(xx - 1.5f); // Magic numbers + result.y = floor(yy - 0.5f); + + return result; +} + +void DrawLevel(Bitmap& bitmap, Bitmap& tiles, Level& level, int xo, int yo) +{ + bitmap.clear(0xff224466); + + for (int y = 0; y < 32; ++y) { + for (int x = 0; x < 32; ++x) { + constexpr int TILE_SIZE = 24; + + auto tile = level.get(x, y); + + int xx = (x - y) * (TILE_SIZE / 2) - xo; + int yy = (x + y) * (TILE_SIZE / 4) - yo; + + int tx = (x + y) % 2; + int ty = 0; + + if (tile == NorthSouth) { + tx = 0; + ty = 2; + } + else if (tile == EastWest) { + tx = 1; + ty = 2; + } + else if (tile == SouthEast) { + tx = 0; + ty = 3; + } + else if (tile == SouthWest) { + tx = 3; + ty = 3; + } + else if (tile == NorthWest) { + tx = 1; + ty = 3; + } + else if (tile == NorthEast) { + tx = 2; + ty = 3; + } + + bitmap.blit(tiles, xx, yy, tx * 24, ty * 24, TILE_SIZE, TILE_SIZE); + } + } +} + +int main(int argc, char **argv) +{ + constexpr int Height = 240; + constexpr int Width = Height * 16 / 9; + constexpr int Scale = 3; + + Window window("Nonortho", Width, Height, Scale); + + Bitmap tiles("../res/tiles.png"); + Level level(32, 32); + + Bitmap bitmap(Width, Height); + TrainCar car(0, 0); + + int xOffs = 0, yOffs = 0; + int xDrag, yDrag; + bool isDragging = false; + + window.onMouseDown([&](int button, int x, int y) { + if (button == 1) { + static const auto update_direction = [&](int xt, int yt) { + if (level.get(xt, yt) > 0) + level.set(xt, yt, ChooseDirection(level, xt, yt)); + }; + + float mx = x / Scale + xOffs; + float my = y / Scale + yOffs; + Point2D tilePos = screenToTile({ mx, my }); + + if (level.inBounds(tilePos.x, tilePos.y)) { + auto tile = level.get(tilePos.x, tilePos.y); + + if (tile > 0) tile = 0; + else tile = ChooseDirection(level, tilePos.x, tilePos.y); + + level.set(tilePos.x, tilePos.y, tile); + + update_direction(tilePos.x - 1, tilePos.y); + update_direction(tilePos.x + 1, tilePos.y); + update_direction(tilePos.x, tilePos.y - 1); + update_direction(tilePos.x, tilePos.y + 1); + } + } else if (button == 2 && !isDragging) { + xDrag = x; + yDrag = y; + isDragging = true; + } else { + float mx = x / Scale + xOffs; + float my = y / Scale + yOffs; + auto pos = screenToTile({ mx, my }); + car.x = pos.x; + car.y = pos.y; + } + }); + + window.onMouseUp([&](int button, int x, int y) { + if (button == 2 && isDragging) + isDragging = false; + }); + + window.onMouseMove([&](int x, int y) { + if (isDragging) { + xOffs -= (x - xDrag) / Scale; + yOffs -= (y - yDrag) / Scale; + xDrag = x; + yDrag = y; + } + }); + + while (!window.shouldClose()) { + window.update(); + DrawLevel(bitmap, tiles, level, xOffs, yOffs); + car.update(level); + car.draw(bitmap, xOffs, yOffs); + window.draw(bitmap); + } + + return 0; +} diff --git a/src/train.cpp b/src/train.cpp new file mode 100644 index 0000000..e369887 --- /dev/null +++ b/src/train.cpp @@ -0,0 +1,52 @@ +#include "train.h" + +#include "level.h" +#include "bitmap.h" + +void TrainCar::update(Level& level) +{ + auto tile = level.get(x, y); + if (tile == 0) + return; + + if (m_progress < 12) { + m_progress++; + return; + } + else { + m_progress = 0; + } + + auto n = level.get(x, y - 1); + auto e = level.get(x + 1, y); + auto s = level.get(x, y + 1); + auto w = level.get(x - 1, y); + + if (m_dir == North) { + if (n == SouthEast) m_dir = East; + if (n == SouthWest) m_dir = West; + y--; + } + else if (m_dir == East) { + if (e == NorthWest) m_dir = North; + if (e == SouthWest) m_dir = South; + x++; + } + else if (m_dir == South) { + if (s == NorthEast) m_dir = East; + if (s == NorthWest) m_dir = West; + y++; + } + else if (m_dir == West) { + if (w == NorthEast) m_dir = North; + if (w == SouthEast) m_dir = South; + x--; + } +} + +void TrainCar::draw(Bitmap& bitmap, int xo, int yo) +{ + int xx = (x - y) * (24 / 2) - xo; + int yy = (x + y) * (24 / 4) - yo; + bitmap.blit(m_sprite, xx, yy, 0, 0, 24, 24); +}
\ No newline at end of file diff --git a/src/train.h b/src/train.h new file mode 100644 index 0000000..ed0de94 --- /dev/null +++ b/src/train.h @@ -0,0 +1,27 @@ +#pragma once + +#include "bitmap.h" + +enum CarDirection { + North, + East, + South, + West +}; + +class Level; + +class TrainCar { +public: + TrainCar(int x, int y) + : x(x), y(y), m_sprite("../res/car.png") {} + + void update(Level&); + void draw(Bitmap&, int, int); + + int x, y; +private: + Bitmap m_sprite; + int m_progress; + CarDirection m_dir; +}; diff --git a/src/window.cpp b/src/window.cpp new file mode 100644 index 0000000..a907aba --- /dev/null +++ b/src/window.cpp @@ -0,0 +1,61 @@ +#include "window.h" + +#include <SDL2/SDL.h> +#include <cstdio> + +#include "bitmap.h" + +Window::Window(const char* title, int width, int height, int scale) + : m_width(width), m_height(height), m_scale(scale) +{ + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "Failed to initalize SDL: %s\n", SDL_GetError()); + exit(1); + } + + m_window = SDL_CreateWindow("Nonortho", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width * scale, height * scale, SDL_WINDOW_SHOWN); + m_renderer = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED); + m_texture = SDL_CreateTexture(m_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, width, height); + + m_isRunning = true; +} + +Window::~Window() +{ + SDL_DestroyTexture(m_texture); + SDL_DestroyRenderer(m_renderer); + SDL_DestroyWindow(m_window); + SDL_Quit(); +} + +void Window::update() +{ + static SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + m_isRunning = false; + break; + case SDL_MOUSEBUTTONDOWN: + m_mouseDown(event.button.button, event.button.x, event.button.y); + break; + case SDL_MOUSEMOTION: + m_mouseMove(event.button.x, event.button.y); + break; + case SDL_MOUSEBUTTONUP: + m_mouseUp(event.button.button, event.button.x, event.button.y); + break; + } + } + + SDL_UpdateWindowSurface(m_window); + SDL_Delay(10); +} + +void Window::draw(Bitmap& bitmap) +{ + SDL_RenderClear(m_renderer); + SDL_UpdateTexture(m_texture, NULL, bitmap.data, m_width * 4); + SDL_RenderCopy(m_renderer, m_texture, NULL, NULL); + SDL_RenderPresent(m_renderer); +}
\ No newline at end of file diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..bdd1e91 --- /dev/null +++ b/src/window.h @@ -0,0 +1,36 @@ +#pragma once + +#include <functional> + +class Bitmap; + +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Texture; + +class Window { +public: + Window(const char* title, int width, int height, int scale); + ~Window(); + + void update(); + void draw(Bitmap&); + + bool shouldClose() { return !m_isRunning; } + + void onMouseDown(std::function<void(int, int, int)> callback) { m_mouseDown = callback; } + void onMouseUp(std::function<void(int, int, int)> callback) { m_mouseUp = callback; } + void onMouseMove(std::function<void(int, int)> callback) { m_mouseMove = callback; } +private: + bool m_isRunning; + + int m_width, m_height, m_scale; + + SDL_Window* m_window; + SDL_Renderer* m_renderer; + SDL_Texture* m_texture; + + std::function<void(int, int, int)> m_mouseDown; + std::function<void(int, int, int)> m_mouseUp; + std::function<void(int, int)> m_mouseMove; +};
\ No newline at end of file |