summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CApi.h235
-rw-r--r--src/Level.cpp161
-rw-r--r--src/Level.h84
-rw-r--r--src/LuaApi.h120
-rw-r--r--src/MessageBox.h98
-rw-r--r--src/Scene.h31
-rw-r--r--src/SfmlUtils.h110
-rw-r--r--src/linux/res.h77
-rw-r--r--src/lua.hpp8
-rw-r--r--src/main.cpp278
10 files changed, 1202 insertions, 0 deletions
diff --git a/src/CApi.h b/src/CApi.h
new file mode 100644
index 0000000..21ad9eb
--- /dev/null
+++ b/src/CApi.h
@@ -0,0 +1,235 @@
+//========================================================================
+// dng
+//------------------------------------------------------------------------
+// Copyright (c) 2022 Steph Enders <steph@senders.io>
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would
+// be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+// distribution.
+//
+//========================================================================
+
+#ifndef DNG_CAPI_H
+#define DNG_CAPI_H
+
+#include "Level.h"
+#include "Scene.h"
+#include "lua.hpp"
+#include <memory>
+
+extern std::shared_ptr<Level> lvl;
+extern Scene scene;
+
+/*
+ * c_get_player_position(int x, int y)
+ */
+static int c_get_player_position(lua_State *L) {
+ lua_createtable(L, 0, 2);
+ lua_pushnumber(L, lvl->player.y + 1);
+ lua_setfield(L, -2, "y");
+ lua_pushnumber(L, lvl->player.x + 1);
+ lua_setfield(L, -2, "x");
+ return 1;
+}
+
+/*
+ * c_move_player(int dx, int dy)
+ */
+static int c_move_player(lua_State *L) {
+ // stack ordering
+ int dy = static_cast<int>(lua_tonumber(L, -1));
+ int dx = static_cast<int>(lua_tonumber(L, -2));
+
+ if (lvl->playerCanStep(dx, dy)) {
+ lvl->player.x += dx;
+ lvl->player.y += dy;
+ }
+
+ lua_createtable(L, 0, 2);
+ lua_pushnumber(L, lvl->player.y + 1);
+ lua_setfield(L, -2, "y");
+ lua_pushnumber(L, lvl->player.x + 1);
+ lua_setfield(L, -2, "x");
+
+ return 1;
+}
+
+/*
+ * c_move_enemy(int id, int dx, int dy)
+ */
+static int c_move_enemy(lua_State *L) {
+ // stack ordering
+ int dy = static_cast<int>(lua_tonumber(L, -1));
+ int dx = static_cast<int>(lua_tonumber(L, -2));
+ int id = static_cast<int>(lua_tonumber(L, -3));
+
+ int i = lvl->getEnemyIndex(id);
+ // guard against enemy not found
+ if (i == -1) {
+ return 1;
+ }
+
+ if (lvl->enemyCanStep(lvl->enemyPositions[i], dx, dy)) {
+ lvl->enemyPositions[i].x += dx;
+ lvl->enemyPositions[i].y += dy;
+ }
+ lua_createtable(L, 0, 3);
+ lua_pushnumber(L, lvl->enemyPositions[i].y + 1);
+ lua_setfield(L, -2, "y");
+ lua_pushnumber(L, lvl->enemyPositions[i].x + 1);
+ lua_setfield(L, -2, "x");
+ lua_pushnumber(L, lvl->enemyPositions[i].id);
+ lua_setfield(L, -2, "id");
+
+ return 1;
+}
+
+/*
+ * c_get_enemies()
+ */
+static int c_get_enemies(lua_State *L) {
+ lua_createtable(L, int(lvl->enemyPositions.size()), 0);
+
+ int idx = 0;
+
+ for (auto &pos : lvl->enemyPositions) {
+ lua_pushnumber(L, ++idx);
+ lua_createtable(L, 0, 3);
+ lua_pushnumber(L, pos.id);
+ lua_setfield(L, -2, "id");
+ lua_pushnumber(L, pos.x + 1);
+ lua_setfield(L, -2, "x");
+ lua_pushnumber(L, pos.y + 1);
+ lua_setfield(L, -2, "y");
+ lua_settable(L, -3);
+ }
+
+ return 1;
+}
+
+/*
+ * c_spawn_enemy(int x, int y)
+ */
+static int c_spawn_enemy(lua_State *L) { return 1; }
+
+/*
+ * c_destroy_enemy(int id)
+ */
+static int c_destroy_enemy(lua_State *L) { return 1; }
+
+/*
+ * c_get_map()
+ */
+static int c_get_map(lua_State *L) {
+ lua_createtable(L, int(lvl->map.size()), 0);
+ int idx = 0;
+ for (auto &vec : lvl->map) {
+ lua_pushnumber(L, ++idx);
+ lua_createtable(L, int(vec.size()), 0);
+ int inner_idx = 0;
+ for (auto &c : vec) {
+ lua_pushnumber(L, ++inner_idx);
+ lua_pushnumber(L, c == WALL_SPACE ? 1 : 0);
+ lua_rawset(L, -3);
+ }
+ lua_rawset(L, -3);
+ }
+ return 1;
+}
+
+/*
+ * c_trigger_level_start()
+ */
+static int c_trigger_level_start(lua_State *L) {
+ scene = Scene::LEVEL;
+ lua_pushboolean(L, true);
+ return 1;
+}
+/*
+ * c_trigger_win()
+ */
+static int c_trigger_win(lua_State *L) {
+ scene = Scene::WIN;
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+/*
+ * c_trigger_loss()
+ */
+static int c_trigger_loss(lua_State *L) {
+ scene = Scene::LOSS;
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int c_get_scene(lua_State *L) {
+ lua_pushnumber(L, scene);
+ return 1;
+}
+
+static int c_get_treasures(lua_State *L) {
+ lua_createtable(L, static_cast<int>(lvl->treasurePositions.size()), 0);
+ int idx = 0;
+ for (auto &t : lvl->treasurePositions) {
+ lua_pushnumber(L, ++idx);
+ lua_createtable(L, 0, 3);
+ lua_pushnumber(L, t.y + 1);
+ lua_setfield(L, -2, "y");
+ lua_pushnumber(L, t.x + 1);
+ lua_setfield(L, -2, "x");
+ lua_pushnumber(L, t.id);
+ lua_setfield(L, -2, "id");
+ lua_settable(L, -3);
+ }
+ return 1;
+}
+
+static int c_score_treasure(lua_State *L) {
+ int id = static_cast<int>(lua_tonumber(L, -1));
+
+ erase_if(lvl->treasurePositions, [id](Pos t) { return t.id == id; });
+
+ return 1;
+}
+
+static int c_trigger_restart(lua_State *L) {
+ lvl->reset();
+ scene = Scene::INTRO;
+ return 1;
+}
+
+// not for lua use
+void init_c_api(lua_State *L) {
+ lua_register(L, "c_move_player", c_move_player);
+ lua_register(L, "c_move_enemy", c_move_enemy);
+ lua_register(L, "c_spawn_enemy", c_spawn_enemy);
+ lua_register(L, "c_destroy_enemy", c_destroy_enemy);
+ lua_register(L, "c_get_enemies", c_get_enemies);
+ lua_register(L, "c_get_player_position", c_get_player_position);
+ lua_register(L, "c_get_map", c_get_map);
+ lua_register(L, "c_trigger_level_start", c_trigger_level_start);
+ lua_register(L, "c_trigger_win", c_trigger_win);
+ lua_register(L, "c_trigger_loss", c_trigger_loss);
+ lua_register(L, "c_get_scene", c_get_scene);
+ lua_register(L, "c_score_treasure", c_score_treasure);
+ lua_register(L, "c_get_treasures", c_get_treasures);
+ lua_register(L, "c_trigger_restart", c_trigger_restart);
+}
+
+#endif // DNG_CAPI_H
diff --git a/src/Level.cpp b/src/Level.cpp
new file mode 100644
index 0000000..6db4b4e
--- /dev/null
+++ b/src/Level.cpp
@@ -0,0 +1,161 @@
+//========================================================================
+// dng
+//------------------------------------------------------------------------
+// Copyright (c) 2022 Steph Enders <steph@senders.io>
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would
+// be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+// distribution.
+//
+//========================================================================
+
+#include "Level.h"
+#include "SfmlUtils.h"
+#include <fstream>
+#include <iostream>
+#include <string>
+
+bool canStep(Pos pos, int dx, int dy, std::vector<std::vector<char>> map) {
+ return map[pos.y + dy][pos.x + dx] != WALL_SPACE;
+}
+
+void Level::loadLevelFromFile(const char *filePath) {
+ std::ifstream mapFile(filePath);
+ if (mapFile.is_open()) {
+ this->file = filePath;
+ // each element in the map has a unique ID
+ // some magic but player is always 0
+ const int playerId = 0;
+ // from 1 -> N each enemy and treasure has its own unique ID
+ // IDs are unique entirely, not just per enemy or treasure
+
+ std::string line;
+ int y = 0;
+ while (std::getline(mapFile, line)) {
+ this->map.emplace_back();
+ int x = 0;
+ for (char c : line) {
+ if (c == WALL_TKN) {
+ this->map[y].push_back(WALL_SPACE);
+ auto w = create_wall(x, y);
+ this->displayMap.push_back(w);
+ } else if (c == EMPTY_TKN) {
+ this->map[y].push_back(BLANK_SPACE);
+ // no display info
+ } else if (c == ENEMY_TKN) {
+ auto e = create_enemy(x, y);
+ this->enemyPositions.push_back(
+ {.id = this->nextId(), .x = x, .y = y, .sprite = e});
+ this->map[y].push_back(BLANK_SPACE);
+ } else if (c == PLAYER_TKN) {
+ auto p = create_player(x, y);
+ this->player = {.id = playerId, .x = x, .y = y, .sprite = p};
+ this->map[y].push_back(BLANK_SPACE);
+ } else if (c == TREASURE_TKN) {
+ auto t = create_treasure(x, y);
+ this->treasurePositions.push_back(
+ {.id = this->nextId(), .x = x, .y = y, .sprite = t});
+ this->map[y].push_back(BLANK_SPACE);
+ } else {
+ continue;
+ }
+ ++x;
+ }
+ ++y;
+ }
+
+ unsigned long max_width = 0;
+ for (auto &rows : this->map) {
+ if (max_width < rows.size()) {
+ max_width = rows.size();
+ }
+ }
+ this->width = static_cast<int>(max_width);
+ this->height = static_cast<int>(this->map.size());
+ }
+ mapFile.close();
+}
+
+void Level::reset() {
+ this->map.clear();
+ this->treasurePositions.clear();
+ this->displayMap.clear();
+ this->enemyPositions.clear();
+ this->loadLevelFromFile(this->file);
+}
+
+bool Level::isEmpty(int x, int y) { return map[y][x] == BLANK_SPACE; }
+
+bool Level::playerCanStep(int dx, int dy) {
+ return canStep(player, dx, dy, map);
+}
+
+void Level::print() {
+ int x = 0;
+ int y = 0;
+ for (auto &row : map) {
+ for (auto &tile : row) {
+ bool printed = false;
+ if (player.x == x && player.y == y) {
+ std::cout << "p";
+ printed = true;
+ }
+ for (auto pos : enemyPositions) {
+ if (pos.x == x && pos.y == y) {
+ std::cout << "e";
+ printed = true;
+ }
+ }
+ for (auto pos : treasurePositions) {
+ if (pos.x == x && pos.y == y) {
+ std::cout << "t";
+ printed = true;
+ }
+ }
+ if (tile == WALL_SPACE) {
+ std::cout << tile;
+ printed = true;
+ }
+ if (!printed) {
+ std::cout << " ";
+ }
+ std::cout << " ";
+ ++x;
+ }
+ std::cout << "\n";
+ ++y;
+ x = 0;
+ }
+}
+
+int Level::nextId() { return idCounter++; }
+
+int Level::getEnemyIndex(int id) {
+ for (int i = 0; i < enemyPositions.size(); i++) {
+ if (enemyPositions[i].id == id) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+bool Level::enemyCanStep(Pos pos, int dx, int dy) {
+ return canStep(pos, dx, dy, map);
+}
+int Level::getWidth() { return this->width; }
+int Level::getHeight() { return this->height; }
diff --git a/src/Level.h b/src/Level.h
new file mode 100644
index 0000000..f0cec68
--- /dev/null
+++ b/src/Level.h
@@ -0,0 +1,84 @@
+//========================================================================
+// dng
+//------------------------------------------------------------------------
+// Copyright (c) 2022 Steph Enders <steph@senders.io>
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would
+// be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+// distribution.
+//
+//========================================================================
+
+#ifndef DNG_LEVEL_H
+#define DNG_LEVEL_H
+
+#include "SFML/Graphics/RectangleShape.hpp"
+#include <memory>
+#include <vector>
+
+// tokens from map file
+static const char PLAYER_TKN = 'p';
+static const char WALL_TKN = 'w';
+static const char EMPTY_TKN = '0';
+static const char TREASURE_TKN = 't';
+static const char ENEMY_TKN = 'e';
+static const char BLANK_SPACE = '\0';
+static const char WALL_SPACE = '#';
+
+struct Pos {
+ int id;
+ int x;
+ int y;
+ sf::RectangleShape sprite;
+} typedef Pos;
+
+class Level {
+
+public:
+ void loadLevelFromFile(const char *filePath);
+
+ bool isEmpty(int x, int y);
+
+ bool playerCanStep(int dx, int dy);
+
+ int getEnemyIndex(int id);
+
+ bool enemyCanStep(Pos pos, int dx, int dy);
+
+ void reset();
+ /* deprecate*/
+ void print();
+
+ int nextId();
+ int getWidth();
+ int getHeight();
+
+ std::vector<std::vector<char>> map; // source copy of map
+ std::vector<sf::RectangleShape> displayMap;
+ Pos player;
+ std::vector<Pos> enemyPositions;
+ std::vector<Pos> treasurePositions;
+
+private:
+ int idCounter = 1; // defaults at 1 (player always 0)
+ int width;
+ int height;
+ const char *file;
+};
+
+#endif // DNG_LEVEL_H
diff --git a/src/LuaApi.h b/src/LuaApi.h
new file mode 100644
index 0000000..43bd50f
--- /dev/null
+++ b/src/LuaApi.h
@@ -0,0 +1,120 @@
+//========================================================================
+// dng
+//------------------------------------------------------------------------
+// Copyright (c) 2022 Steph Enders <steph@senders.io>
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would
+// be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+// distribution.
+//
+//========================================================================
+
+#ifndef DNG_LUA_API_H
+#define DNG_LUA_API_H
+
+#include "lua.hpp"
+#include <iostream>
+
+struct LState {
+ lua_State *onkeypress;
+ lua_State *onupdate;
+ lua_State *onintro;
+ lua_State *onwin;
+ lua_State *onloss;
+} typedef LState;
+
+const char *ON_KEYPRESS = "onKeyPress";
+const char *ON_UPDATE = "onUpdate";
+const char *ON_INTRO = "onIntro";
+const char *ON_WIN = "onWin";
+const char *ON_LOSS = "onLoss";
+
+LState *init_default(lua_State *L) {
+ auto *state = static_cast<LState *>(malloc(sizeof(LState)));
+
+ state->onkeypress = L;
+ state->onupdate = L;
+ state->onintro = L;
+ state->onwin = L;
+ state->onloss = L;
+
+ return state;
+}
+
+bool check_fn(lua_State *L, const char *fn) {
+ lua_getglobal(L, fn);
+ return lua_isfunction(L, -1);
+}
+
+void override_file_fns(lua_State *L, LState *state) {
+ if (check_fn(L, ON_KEYPRESS)) {
+ state->onkeypress = L;
+ }
+ if (check_fn(L, ON_UPDATE)) {
+ state->onupdate = L;
+ }
+ if (check_fn(L, ON_INTRO)) {
+ state->onintro = L;
+ }
+ if (check_fn(L, ON_WIN)) {
+ state->onwin = L;
+ }
+ if (check_fn(L, ON_LOSS)) {
+ state->onloss = L;
+ }
+}
+
+bool lua_dofn(lua_State *L, const char *fn) {
+ lua_getglobal(L, fn);
+ if (!lua_isfunction(L, -1)) {
+ std::cout << "[C] Error " << fn << " not function | not found" << std::endl;
+ return false;
+ }
+ lua_pcall(L, 0, 1, 0);
+ return true;
+}
+
+bool lua_dofn_with_number(lua_State *L, const char *fn, lua_Number num) {
+ lua_getglobal(L, fn);
+ if (!lua_isfunction(L, -1)) {
+ std::cout << "[C] Error " << fn << " not function | not found" << std::endl;
+ return false;
+ }
+ lua_pushnumber(L, num);
+ lua_pcall(L, 1, 1, 0);
+ return true;
+}
+
+bool lua_onkeypress(lua_State *L, int pressedKey) {
+ if (pressedKey == -1) {
+ return true;
+ }
+ return lua_dofn_with_number(L, "onKeyPress", pressedKey);
+}
+
+bool lua_onupdate(lua_State *L, float dt) {
+ return lua_dofn_with_number(L, "onUpdate", dt);
+}
+
+bool lua_onintro(lua_State *L) { return lua_dofn(L, "onIntro"); }
+
+bool lua_onwin(lua_State *L) { return lua_dofn(L, "onWin"); }
+
+bool lua_onloss(lua_State *L) { return lua_dofn(L, "onLoss"); }
+
+#endif // DNG_LUA_API_H
diff --git a/src/MessageBox.h b/src/MessageBox.h
new file mode 100644
index 0000000..0e1fa8f
--- /dev/null
+++ b/src/MessageBox.h
@@ -0,0 +1,98 @@
+//========================================================================
+// dng
+//------------------------------------------------------------------------
+// Copyright (c) 2022 Steph Enders <steph@senders.io>
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would
+// be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+// distribution.
+//
+//========================================================================
+
+#ifndef DNG_MESSAGE_BOX_H
+#define DNG_MESSAGE_BOX_H
+
+#include "SFML/Graphics/RectangleShape.hpp"
+#include "SFML/Graphics/Text.hpp"
+#include "SfmlUtils.h"
+#include <vector>
+
+const int LARGE_TEXT = 54;
+const int MEDIUM_TEXT = 36;
+const int SMALL_TEXT = 24;
+const float PADDING = 16.f;
+const float LINE_HEIGHT = 1.2f;
+struct MessageBox {
+
+ std::vector<sf::Text> msgs;
+ sf::RectangleShape box;
+
+} typedef MessageBox;
+
+struct DisplayText {
+ std::string msg;
+ int textSize;
+} typedef DisplayText;
+
+inline MessageBox initializeMessageBox(const std::vector<DisplayText> &strs,
+ const sf::Font &font,
+ sf::Vector2u windowSize) {
+
+ std::vector<sf::Text> messages;
+ sf::RectangleShape box;
+ float width = 0.f;
+ float height = 0.f;
+ float left = MAXFLOAT;
+ sf::Text prev;
+ for (const auto &str : strs) {
+ sf::Text text = write_text(str.msg.c_str(), str.textSize, LINE_HEIGHT, font,
+ windowSize);
+ // move below previous
+ if (!prev.getString().isEmpty()) {
+ text.move(0.f, prev.getLocalBounds().height * prev.getLineSpacing());
+ }
+ // add to vector
+ messages.push_back(text);
+
+ // find widest
+ if (text.getGlobalBounds().width > width) {
+ width = text.getGlobalBounds().width;
+ }
+ // find leftmost
+ if (text.getGlobalBounds().left < left) {
+ left = text.getGlobalBounds().left;
+ }
+ // heights added together + line height
+ height += (text.getLocalBounds().height * text.getLineSpacing());
+ prev = text;
+ }
+ box = sf::RectangleShape(sf::Vector2f(width + PADDING, height + PADDING));
+ box.setOutlineColor(sf::Color::Black);
+ box.setOutlineThickness(4.f);
+ box.setFillColor(sf::Color(128, 128, 128, 128));
+ box.setPosition(left - (PADDING / 2.f),
+ // Top will always be the first message
+ messages[0].getGlobalBounds().top - (PADDING / 2.f));
+
+ return {
+ .msgs = messages,
+ .box = box,
+ };
+}
+
+#endif // DNG_MESSAGE_BOX_H \ No newline at end of file
diff --git a/src/Scene.h b/src/Scene.h
new file mode 100644
index 0000000..0db80cd
--- /dev/null
+++ b/src/Scene.h
@@ -0,0 +1,31 @@
+//========================================================================
+// dng
+//------------------------------------------------------------------------
+// Copyright (c) 2022 Steph Enders <steph@senders.io>
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would
+// be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+// distribution.
+//
+//========================================================================
+
+#ifndef DNG_SCENE_H
+#define DNG_SCENE_H
+/* sync with constants.lua */
+enum Scene { INTRO, LEVEL, WIN, LOSS };
+#endif // DNG_SCENE_H
diff --git a/src/SfmlUtils.h b/src/SfmlUtils.h
new file mode 100644
index 0000000..dd63cd6
--- /dev/null
+++ b/src/SfmlUtils.h
@@ -0,0 +1,110 @@
+//========================================================================
+// dng
+//------------------------------------------------------------------------
+// Copyright (c) 2022 Steph Enders <steph@senders.io>
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would
+// be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+// distribution.
+//
+//========================================================================
+
+#ifndef DNG_SFML_UTILS_H
+#define DNG_SFML_UTILS_H
+
+#include "SFML/Graphics/RectangleShape.hpp"
+#include "SFML/Graphics/Text.hpp"
+#include "SFML/Window/Event.hpp"
+#include "SFML/Window/Keyboard.hpp"
+#include <cmath>
+#include <iostream>
+
+const float SPRITE_SIZE = 16.f; // squares
+const sf::Color WALL_COLOR = sf::Color(150, 150, 150, 255);
+const sf::Color BLANK_COLOR = sf::Color(216, 216, 216, 255);
+
+inline sf::Keyboard::Key get_key(sf::Event event) {
+ if (event.type == sf::Event::KeyPressed ||
+ sf::Keyboard::isKeyPressed(event.key.code)) {
+ return event.key.code;
+ }
+ return sf::Keyboard::Unknown;
+}
+
+inline sf::Vector2f to_position_xy(int x, int y) {
+ return {static_cast<float>(x) * SPRITE_SIZE,
+ static_cast<float>(y) * SPRITE_SIZE};
+}
+inline sf::Vector2f to_position(const Pos &pos) {
+ return to_position_xy(pos.x, pos.y);
+}
+
+inline sf::RectangleShape create_square(sf::Color color, int x, int y) {
+ sf::RectangleShape rect({SPRITE_SIZE - 1.f, SPRITE_SIZE - 1.f});
+ rect.setFillColor(color);
+ rect.setOutlineColor(sf::Color::Black);
+ rect.setOutlineThickness(1.f);
+ rect.setPosition(to_position_xy(x, y));
+ return rect;
+}
+
+inline sf::RectangleShape create_wall(int x, int y) {
+ return create_square(WALL_COLOR, x, y);
+}
+
+inline sf::RectangleShape create_enemy(int x, int y) {
+ return create_square(sf::Color::Magenta, x, y);
+}
+
+inline sf::RectangleShape create_player(int x, int y) {
+ return create_square(sf::Color::Cyan, x, y);
+}
+
+inline sf::RectangleShape create_treasure(int x, int y) {
+ return create_square(sf::Color::Yellow, x, y);
+}
+
+inline sf::Vector2f round(const sf::Vector2f vector) {
+ return sf::Vector2f{std::round(vector.x), std::round(vector.y)};
+}
+
+inline sf::Text write_text(const char *msg, unsigned int fontSize,
+ float lineSpacing, const sf::Font &font,
+ const sf::Vector2u windowSize) {
+ sf::Text text(msg, font, fontSize);
+ text.setOutlineThickness(4.f);
+ text.setOrigin(round(sf::Vector2f{text.getLocalBounds().width / 2,
+ text.getLocalBounds().height / 2}));
+ text.setPosition(sf::Vector2f(windowSize / 2u));
+ text.setLineSpacing(lineSpacing);
+ while (text.getGlobalBounds().top < 0 || text.getGlobalBounds().left < 0 ||
+ static_cast<unsigned int>(
+ (text.getGlobalBounds().width + text.getGlobalBounds().left) *
+ text.getLineSpacing()) > windowSize.x ||
+ static_cast<unsigned int>(
+ (text.getGlobalBounds().height + text.getGlobalBounds().top) *
+ text.getLineSpacing()) > windowSize.y) {
+ text.setCharacterSize(text.getCharacterSize() - 1);
+ text.setOrigin(round(sf::Vector2f{text.getLocalBounds().width / 2,
+ text.getLocalBounds().height / 2}));
+ text.setPosition(sf::Vector2f(windowSize / 2u));
+ }
+ return text;
+}
+
+#endif // DNG_SFML_UTILS_H \ No newline at end of file
diff --git a/src/linux/res.h b/src/linux/res.h
new file mode 100644
index 0000000..9e3b8c5
--- /dev/null
+++ b/src/linux/res.h
@@ -0,0 +1,77 @@
+//========================================================================
+// dng
+//------------------------------------------------------------------------
+// Copyright (c) 2022 Steph Enders <steph@senders.io>
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would
+// be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+// distribution.
+//
+//========================================================================
+
+#ifndef DNG_RES_H
+#define DNG_RES_H
+
+#include <filesystem>
+
+const std::filesystem::path DEFAULT_PROC{"dnglib/defaults.lua"};
+const std::filesystem::path DEFAULT_FONT{"res/PressStart2P-vaV7.ttf"};
+// TODO setup to allow switching to monospace instead of game font
+// const std::filesystem::path DEFAULT_MONOSPACE_FONT{
+// "res/LiberationMono-Regular.ttf"};
+
+struct Res {
+
+ std::filesystem::path defaultsFile;
+ std::filesystem::path fontFile;
+
+} typedef Res;
+
+inline Res get_resources() {
+ using namespace std::filesystem;
+
+ auto current_dir = current_path();
+ auto exe_dir = canonical("/proc/self/exe").remove_filename();
+ auto install_dir = path{"/usr/local/share/dng/"};
+
+ Res res;
+ if (exists(current_dir / DEFAULT_PROC)) {
+ res.defaultsFile = current_dir / DEFAULT_PROC;
+ } else if (exists(exe_dir / DEFAULT_PROC)) {
+ res.defaultsFile = exe_dir / DEFAULT_PROC;
+ } else if (exists(install_dir / DEFAULT_PROC)) {
+ res.defaultsFile = install_dir / DEFAULT_PROC;
+ } else {
+ res.defaultsFile = DEFAULT_PROC; // just return w/ no path info
+ }
+ // TODO make configurable
+ path fontp = DEFAULT_FONT;
+ if (exists(current_dir / fontp)) {
+ res.fontFile = current_dir / fontp;
+ } else if (exists(exe_dir / fontp)) {
+ res.fontFile = exe_dir / fontp;
+ } else if (exists(install_dir / fontp)) {
+ res.fontFile = install_dir / fontp;
+ } else {
+ res.fontFile = fontp; // just return w/ no path info
+ }
+
+ return res;
+}
+
+#endif // DNG_RES_H \ No newline at end of file
diff --git a/src/lua.hpp b/src/lua.hpp
new file mode 100644
index 0000000..4cf79d2
--- /dev/null
+++ b/src/lua.hpp
@@ -0,0 +1,8 @@
+// Included locally
+// Building from sources doesn't include this by default
+// See Copyright Notice in thirdparty/lua/lua.h
+extern "C" {
+#include "lauxlib.h"
+#include "lua.h"
+#include "lualib.h"
+}
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..99f8603
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,278 @@
+//========================================================================
+// dng
+//------------------------------------------------------------------------
+// Copyright (c) 2022 Steph Enders <steph@senders.io>
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would
+// be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+// distribution.
+//
+//========================================================================
+
+#include "CApi.h"
+#include "Level.h"
+#include "LuaApi.h"
+#include "MessageBox.h"
+#include "SfmlUtils.h"
+#ifdef __linux__
+#include "linux/res.h"
+#endif
+#include "lua.hpp"
+#include <SFML/Graphics.hpp>
+#include <cmath>
+#include <filesystem>
+#include <iostream>
+
+std::shared_ptr<Level> lvl;
+
+Scene scene;
+const int MAX_WIDTH = static_cast<int>(SPRITE_SIZE) * 20 * 4;
+const int MAX_HEIGHT = static_cast<int>(SPRITE_SIZE) * 20 * 3;
+
+int main(int argc, char **argv) {
+
+ if (argc <= 1) {
+ std::cerr << "Must pass in path to level directory" << std::endl;
+ return -1;
+ }
+
+ auto res = get_resources();
+
+ std::string lvl_pfx = argv[1];
+
+ std::filesystem::path mapFile{lvl_pfx + "/dng.map"};
+ std::filesystem::path luaFile{lvl_pfx + "/proc.lua"};
+
+ lvl = std::make_shared<Level>();
+ scene = Scene::INTRO;
+
+ lvl->loadLevelFromFile(mapFile.c_str());
+
+ lua_State *L_lvl = luaL_newstate();
+ luaL_openlibs(L_lvl);
+ init_c_api(L_lvl);
+
+ lua_State *L_default = luaL_newstate();
+ luaL_openlibs(L_default);
+ init_c_api(L_default);
+
+ if (std::filesystem::exists(res.defaultsFile) &&
+ luaL_dofile(L_default, res.defaultsFile.c_str()) != LUA_OK) {
+ std::cout << "Failed to load default proc" << std::endl;
+ luaL_error(L_default, "Error: %s", lua_tostring(L_default, -1));
+ return EXIT_FAILURE;
+ }
+
+ // Initialize to default
+ LState *l_state = init_default(L_default);
+
+ if (std::filesystem::exists(luaFile) &&
+ luaL_dofile(L_lvl, luaFile.c_str()) == LUA_OK) {
+ override_file_fns(L_lvl, l_state);
+ } else if (std::filesystem::exists(luaFile)) {
+ std::cout << "[C] No Good" << std::endl;
+ luaL_error(L_lvl, "Error: %s\n", lua_tostring(L_lvl, -1));
+ return EXIT_FAILURE;
+ }
+
+ float ZOOM = 0.5f;
+ sf::Vector2f mapBounds = {static_cast<float>(lvl->getWidth()) * SPRITE_SIZE,
+ static_cast<float>(lvl->getHeight()) * SPRITE_SIZE};
+ int width = static_cast<int>(static_cast<float>(lvl->getWidth()) / ZOOM *
+ SPRITE_SIZE / ZOOM);
+ int height = static_cast<int>(static_cast<float>(lvl->getHeight()) / ZOOM *
+ SPRITE_SIZE / ZOOM);
+ bool useViewport = false;
+ if (width > MAX_WIDTH) {
+ width = MAX_WIDTH;
+ useViewport = true;
+ }
+ if (height > MAX_HEIGHT) {
+ height = MAX_HEIGHT;
+ useViewport = true;
+ }
+
+ sf::RenderWindow window(sf::VideoMode(width, height), "dng");
+ window.setMouseCursorVisible(false);
+ window.setFramerateLimit(30);
+ sf::View view(window.getDefaultView());
+ view.zoom(ZOOM);
+ view.setSize(view.getSize() * ZOOM);
+ view.setViewport(sf::FloatRect(0.f, 0.f, 1.f, 1.f));
+ if (useViewport) {
+ view.setCenter(to_position(lvl->player) +
+ sf::Vector2f(SPRITE_SIZE / 2.f, SPRITE_SIZE / 2.f));
+ } else {
+ view.setCenter(view.getSize() / 2.f);
+ }
+
+ window.setView(view);
+ sf::Clock deltaClock;
+ sf::Font font;
+ font.loadFromFile(res.fontFile);
+
+ MessageBox intro;
+ MessageBox win;
+ MessageBox loss;
+
+ do {
+ sf::Event event{};
+ float dt = deltaClock.restart().asSeconds();
+
+ while (window.pollEvent(event)) {
+ if (event.type == sf::Event::Closed ||
+ sf::Keyboard::isKeyPressed(sf::Keyboard::Q)) {
+ window.close();
+ }
+ if (!lua_onkeypress(l_state->onkeypress, get_key(event))) {
+ window.close();
+ }
+ }
+
+ if (scene == Scene::INTRO) {
+ if (!lua_onintro(l_state->onintro)) {
+ window.close();
+ }
+ } else if (scene == Scene::LEVEL) {
+ if (!lua_onupdate(l_state->onupdate, dt)) {
+ window.close();
+ }
+ } else if (scene == Scene::WIN) {
+ lua_onwin(l_state->onwin);
+ // window.close();
+ } else if (scene == Scene::LOSS) {
+ lua_onloss(l_state->onloss);
+ }
+
+ // Render
+ if (useViewport) {
+ // Reinitialize center view
+ if (scene == INTRO) {
+ if (useViewport) {
+ view.setCenter(to_position(lvl->player) +
+ sf::Vector2f(SPRITE_SIZE / 2.f, SPRITE_SIZE / 2.f));
+ } else {
+ view.setCenter(view.getSize() / 2.f);
+ }
+ }
+ sf::Vector2f newPos = to_position(lvl->player) + // center
+ sf::Vector2f(SPRITE_SIZE / 2.f, SPRITE_SIZE / 2.f);
+ sf::Vector2f diff =
+ newPos - (lvl->player.sprite.getPosition() +
+ sf::Vector2f(SPRITE_SIZE / 2.f, SPRITE_SIZE / 2.f));
+
+ if (diff.x > 0.f && newPos.x > view.getSize().x / 2.f) {
+ view.move({diff.x, 0.f});
+ }
+ if (diff.y > 0.f && newPos.y > view.getSize().y / 2.f) {
+ view.move({0.f, diff.y});
+ }
+ if (diff.x < 0.f && newPos.x < view.getCenter().x) {
+ view.move({diff.x, 0.f});
+ }
+ if (diff.y < 0.f && newPos.y < view.getCenter().y) {
+ view.move({0.f, diff.y});
+ }
+ // readjust for OB
+ if (view.getCenter().x - view.getSize().x / 2.f < 0.f) {
+ view.move({(view.getCenter().x - view.getSize().x / 2.f) * -1.f, 0.f});
+ }
+ if (view.getCenter().y - view.getSize().y / 2.f < 0.f) {
+ view.move({0.f, (view.getCenter().y - view.getSize().y / 2.f) * -1.f});
+ }
+ if (view.getCenter().x + view.getSize().x / 2.f > mapBounds.x) {
+ view.move(
+ {-1 * ((view.getCenter().x + view.getSize().x / 2.f) - mapBounds.x),
+ 0.f});
+ }
+ if (view.getCenter().y + view.getSize().y / 2.f > mapBounds.y) {
+ view.move({0.f, -1 * ((view.getCenter().y + view.getSize().y / 2.f) -
+ mapBounds.y)});
+ }
+ }
+
+ window.clear(BLANK_COLOR);
+ window.setView(view);
+ for (auto &rect : lvl->displayMap) {
+ window.draw(rect);
+ }
+ if (scene == Scene::LEVEL) {
+ for (auto &enemy : lvl->enemyPositions) {
+ enemy.sprite.setPosition(to_position(enemy));
+ window.draw(enemy.sprite);
+ }
+ for (auto &treasure : lvl->treasurePositions) {
+ treasure.sprite.setPosition(to_position(treasure));
+ window.draw(treasure.sprite);
+ }
+ }
+
+ if (scene != Scene::LOSS) {
+ lvl->player.sprite.setPosition(to_position(lvl->player));
+ window.draw(lvl->player.sprite);
+ }
+ if (scene == Scene::WIN) {
+ window.setView(window.getDefaultView());
+ if (win.msgs.empty()) {
+ win = initializeMessageBox({{"You Win!", LARGE_TEXT},
+ {"press [space] to restart", SMALL_TEXT}},
+ font, window.getSize());
+ }
+
+ window.draw(win.box);
+ for (auto &msg : win.msgs) {
+ window.draw(msg);
+ }
+ window.setView(view);
+ }
+ if (scene == Scene::LOSS) {
+ window.setView(window.getDefaultView());
+ if (loss.msgs.empty()) {
+ loss = initializeMessageBox({{"You Lose!", LARGE_TEXT},
+ {"press [space] to restart", SMALL_TEXT}},
+ font, window.getSize());
+ }
+
+ window.draw(loss.box);
+ for (auto &msg : loss.msgs) {
+ window.draw(msg);
+ }
+ window.setView(view);
+ }
+ if (scene == INTRO) {
+ window.setView(window.getDefaultView());
+ if (intro.msgs.empty()) {
+ intro = initializeMessageBox(
+ {{"Start!", LARGE_TEXT}, {"press [space]", SMALL_TEXT}}, font,
+ window.getSize());
+ }
+
+ window.draw(intro.box);
+ for (auto &msg : intro.msgs) {
+ window.draw(msg);
+ }
+ window.setView(view);
+ }
+ window.setView(view);
+ window.display();
+ } while (window.isOpen());
+
+ std::cout << "[C] Quit" << std::endl;
+
+ return EXIT_SUCCESS;
+}