CaveGame Edits -> Filling out TileID

This commit is contained in:
2024-12-04 12:54:54 -05:00
parent 5d2ddb40b9
commit f907a374c6
14 changed files with 884 additions and 188 deletions

View File

@@ -20,7 +20,7 @@ include(cmake/CPM.cmake)
CPMAddPackage(
NAME J3ML
URL https://git.redacted.cc/josh/j3ml/archive/Release-3.4.zip
URL https://git.redacted.cc/josh/j3ml/archive/3.4.3.zip
)
CPMAddPackage(
@@ -30,7 +30,7 @@ CPMAddPackage(
CPMAddPackage(
NAME JGL
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-38.zip
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-40.zip
)
CPMAddPackage(

View File

@@ -26,7 +26,7 @@ namespace CaveGame::Client {
class Container
{
public:
Container(int rows, int columns, );
Container(int rows, int columns);
protected:
private:

View File

@@ -13,6 +13,7 @@
#include <steam_api.h>
#include <ClientApp/CaveGameWindow.hpp>
#include <rewindow/logger/logger.h>
#include <Core/Tile.hpp>
int main(int argc, char** argv) {
@@ -23,6 +24,13 @@ int main(int argc, char** argv) {
//srand(0);
using namespace factory;
auto result = Registry<CaveGame::Core::Tile>::GetKeys();
for (auto& tile : result)
std:: cout << tile << std::endl;
auto* window = new CaveGame::ClientApp::CaveGameWindow("Re-CaveGame", 800, 600);
//window->SetResizable(true);
window->Run();

View File

@@ -1,27 +1,140 @@
#include <cstdint>
#pragma once
#include <cstdint>
namespace CaveGame::Core
{
// TODO: Consider implementing traits.
enum class TileID : std::uint16_t
{
VOID = 65535,
AIR = 0,
STONE,
DIRT,
GRASS,
MUD,
CLAY,
SAND,
TALL_GRASS,
VINE,
TRUNK,
BRANCH,
LEAVES,
PLANK,
GRASS,
MOSSY_STONE, LIMESTONE, BASALT, COBBLESTONE,
WET_CEMENT,
CEMENT,
CONCRETE,
MUD_BRICK,
BRICK,
CLAY_BRICK,
STONE_BRICK,
SANDSTONE,
SAND_BRICK,
GRANITE,
SMOOTH_GRANTITE,
SLATE,
ANDESITE,
TUFF,
CHALK,
GYPSUM,
PUMICE,
QUARTZ,
BRIMSTONE,
OBSIDIAN,
HARDENED_CLAY, CLAY,
SILT, LOAM, QUAGMIRE, ASH,
GLOWY_GRASS, DRY_GRASS, DEAD_GRASS, GMO_GRASS, FAKE_GRASS,
MOSS,
SNOW, PACKED_SNOW,
ICE, PACKED_ICE, ICE_BRICK, THIN_ICE,
SAND, WET_SAND, WHITE_SAND, BLACK_SAND, GRAVEL, DUST,
GLASS, CRACKED_GLASS,
RED_STAINED_GLASS,
ORANGE_STAINED_GLASS,
YELLOW_STAINED_GLASS,
LIME_STAINED_GLASS,
GREEN_STAINED_GLASS,
CYAN_STAINED_GLASS,
BLUE_STAINED_GLASS,
MAGENTA_STAINED_GLASS,
BLACK_STAINED_GLASS,
WHITE_STAINED_GLASS,
RED_TERRACOTTA,
ORANGE_TERRACOTTA,
YELLOW_TERRACOTTA,
LIME_TERRACOTTA,
GREEN_TERRACOTTA,
CYAN_TERRACOTTA,
BLUE_TERRACOTTA,
MAGENTA_TERRACOTTA,
BLACK_TERRACOTTA,
WHITE_TERRACOTTA,
RED_PAINT,
ORANGE_PAINT,
YELLOW_PAINT,
LIME_PAINT,
GREEN_PAINT,
CYAN_PAINT,
BLUE_PAINT,
MAGENTA_PAINT,
BLACK_PAINT,
WHITE_PAINT,
CACTUS,
BLOOMING_CACTUS,
TALL_GRASS,
VINE,
BAMBOO,
FLOWER_STEM,
RED_FLOWER_PETAL,
ORANGE_FLOWER_PETAL,
YELLOW_FLOWER_PETAL,
LIME_FLOWER_PETAL,
GREEN_FLOWER_PETAL,
CYAN_FLOWER_PETAL,
BLUE_FLOWER_PETAL,
MAGENTA_FLOWER_PETAL,
BLACK_FLOWER_PETAL,
WHITE_FLOWER_PETAL,
LILYPAD,
ALGAE,
OAK_LOG, OAK_LEAF, OAK_PLANK,
REDWOOD_LOG, REDWOOD_LEAF, REDWOOD_PLANK,
EBONY_LOG, EBONY_LEAF, EBONY_PLANK,
C4, TNT,
TIN_ORE,
COPPER_ORE,
IRON_ORE,
COAL_ORE,
NICKEL_ORE,
CHROMIUM_ORE,
TITANIUM_ORE,
ARSENIC_ORE,
GALLIUM_ORE,
URANIUM_ORE,
MAGNETITE_ORE,
METEORITE_ORE,
SOVITE, PYRITE, CINNABAR,
WATER, LAVA, SLUDGE, OIL,
BLOOD, PISS, HONEY, ECTOPLASM,
COPPER_WIRE, GOLD_WIRE,
AND_GATE, NOR_GATE, XOR_GATE,
LED,
TORCH_BASE,
TORCH_EMBER,
};
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include <Core/Data.hpp>
namespace CaveGame::Core
{
using TileState = uint16_t;
class ITileMap
{
public:
virtual TileID GetTile(int x, int y) const = 0;
virtual void SetTile(int x, int y, TileID tile) = 0;
virtual TileState GetTileState(int x, int y) const = 0;
virtual void SetTileState(int x, int y, TileState state) = 0;
bool GrassSpreadable(int x, int y) const;
bool IsNonSolidTile(int x, int y) const;
bool GrassShouldSuffocate(int x, int y) const;
void GrassRandomTicc(int wx, int wy);
bool IsSolidTile(int x, int y) const;
bool HasAdjacentOrDiagonalAirBlock(int x, int y) const;
bool HasAdjacentAirBlock(int x, int y) const;
};
}

View File

@@ -1,4 +1,5 @@
#include <set>
#include <Core/Registry.hpp>
#pragma once
@@ -52,6 +53,11 @@ namespace CaveGame::Core
private:
};
class EmptyBottle : public Item
{
REGISTER("EmptyBottle", Item);
};
class ItemFilter
{
@@ -61,5 +67,7 @@ namespace CaveGame::Core
std::set<std::string> AllowedItems;
std::set<std::string> BannedItems;
};
}

View File

@@ -1,8 +1,8 @@
//
// Created by dawsh on 12/4/24.
//
#pragma once
#ifndef RECAVEGAME_MACROS_HPP
#define RECAVEGAME_MACROS_HPP
#endif //RECAVEGAME_MACROS_HPP
namespace CaveGame::Core
{
}

View File

@@ -1,56 +1,428 @@
/// Framework for performing registration of object factories.
//
// The major points of the framework are:
// - header-only library
// - no need to declare registration ahead on the base class,
// which means header need to be included only in files
// declaring subclasses that register themselves.
// - registration is done via a macro called inside the declaration
// of the class, which reduces boilerplate code, and incidentally makes
// the registration name available to the class.
// - ingle macro supports constructors with arbitrary number of arguments.
// - class can be registered for different constructors, making
// it easy to implement logic that try to instantiate an object
// from different parameters.
// - builtin mechanism to override registered classes, making dependency
// injection e.g. for tests very easy.
//
// Basic usage
// -----------
// Given an interface:
//
// class Shape {
// public:
// virtual ~Shape() {}
// virtual void Draw() const = 0;
// };
//
// The framework allows to annotate with REGISTER macro any subclass:
//
// class Circle : public Shape {
// REGISTER("Circle", Shape);
// public:
// void Draw() const override { ... }
// };
//
// The first parameter of the macro can be any string and does not have to
// match the name of the class:
//
// class Rect : public Shape {
// REGISTER("Rectangle", Shape);
// public:
// void Draw() const override { ... }
// };
//
// Note that the annotation is done only inside the derived classes.
// Nothing needs to be added to the Shape class, and no declaration
// other than the Shape class needs to be done. The annotation can be
// put inside the private, protected or public section of the class.
// The annotation adds not bytes to the class, whose size is equal to
// the class would have without the annotation.
//
// With the annotation, a Shape instance can be created from a string:
//
// std::unique_ptr<Shape> shape = Registry<Shape>::New("Rect");
// shape->Draw(); // will draw a rectangle!
//
// The function returns a unique_ptr<> which makes ownership clear and simple.
// If no class is registered, it will return a null unique_ptr<>.
// The CanNew() predicate can be used to check if New() would succeed without
// actually creating an instance.
//
// Advanced usage
// --------------
//
// The basic usage considered classes with parameter-less constructor.
// The REGISTER macro can also be used with classes taking arbitrary
// parameters. For example, shapes with injectable parameters
// can be registered using REGISTER() macro with extra parameters matching
// the constructor signature:
//
// class Ellipsis : public Shape {
// REGISTER("Ellipsis", Shape, const std::string&);
// public:
// explicit Ellipsis(const std::string& params) { ... }
// void Draw() const override { ... }
// };
//
// The class can be instantiated using Registry<> with extra parameters
// matching the constructor signature:
//
// auto shape = Registry<Shape, const string&>::New("Ellipsis");
// shape->Draw(); // will draw a circle!
//
// One very interesting benefit of this approach is that client code can
// extend the supported types without editing the base class or
// any of the existing registered classes. The code below simply test
// if a class can be instantiated with a parameter, and otherwise
// tries to instantiate without parameters:
//
// int main(int argc, char **argv) {
// for (int i = 1; i + 1 < argc; i += 2) {
// const std::string key = argv[i];
// const std::string params = argv[i + 1];
// if (Registry<Shape, const std::string &>::CanNew(key, params)) {
// Registry<Shape, const std::string &>::New(key, params)->Draw();
// } else if (Registry<Shape>::CanNew(key)) {
// Registry<Shape>::New(key)->Draw();
// }
// }
// }
//
// Additionally, it is possible to register a class with different constructors
// so it works with old client code that only uses Registry<Shape> and
// some client code like the one above:
//
// class Ellipsis : public Shape {
// REGISTER("Ellipsis", Shape);
// REGISTER("Ellipsis", Shape, const std::string&);
// public:
// explicit Ellipsis(const std::string& params = "") { ... }
// void Draw() const override { ... }
// };
//
// Testing
// -------
//
// When testing client code using registered classes, it may be not desired to
// actually instantiate real classes. If the Draw() implementations for example
// require a graphic context to be active, calling those functions in an
// offscreen automated test will fail. Injectors can be used to temporarily
// replace registered classes by arbitrary factories:
//
// class FakeShape : public Shape {
// public:
// void Draw() override {}
// };
//
// TEST(Draw, WorkingCase) {
// const Registry<Shape>::Injector injectors[] =
// Registry<Shape>::Injector("Circle", []{ return new FakeShape});
// Registry<Shape>::Injector("Rectangle", []{ return new FakeShape});
// Registry<Shape>::Injector("Circle", []{ return new FakeShape});
// EXPECT_TRUE(FunctionUsingRegistryForShape());
// };
// }
//
// Note that it may be necessary to specify the return type of lambda function
// for code to compile as in [] -> Shape* { return .... }
//
// Injectors can also be used as static global variables to perform
// registration of a class *outside* of the class, in replacement for
// the REGISTER macro. This is useful to register classes whose code
// cannot be edited.
//
// Injectors can also be used to define global or local name alias, as
// illustrated by the example REGISTER_ALIAS macro in this file.
//
// REGISTER_ALIAS(Shape, "Rectangle", "Rect");
//
// Goodies
// -------
//
// Classes registered with the REGISTER macro can know the key under
// which they are registered. The following code:
//
// Registry<Vehicle>::GetKeyFor<Circle>()
//
// will return the string "Circle". This works on object classes,
// passed as template parameter to GetKeyFor(), and not on object
// instances. So there is no such functionality as:
//
// std::unique_ptr<Shape> shape = new Circle();
// std::cout << Registry<Vehicle>::GetKeyFor(*shape); // Not implemented
//
// as it would require runtime type identification. It is possible to
// extend the framework to support it though, by storing `type_info`
// objects in the registry.
//
// The Registry<> class can also be used to list all keys that are
// registered, along with the filename and line number at which the
// registration is defined. It does not list though the type associated
// to the key. Here again, it is pretty easy to extend the framework
// to provide such functionality using type_info objects.
//
// Even though not necessary, one can define intermediate macros to
// reduce boilerplate code even more. For the Shape example above,
// one could define:
//
// #define REGISTER_SHAPE(KEY, ARGS...) REGISTER(#KEY, Shape, ##ARGS)
//
// with which the Ellipsis class above would simply be written:
//
// class Ellipsis : public Shape {
// REGISTER_SHAPE(Ellipsis);
// REGISTER_SHAPE(Ellipsis, const std::string &);
//
// public:
// };
//
// Limitations
// -----------
// The code requires a C++11 compliant compiler.
//
// The code relies on the a GNU preprocessor feature for variadic macros:
//
// #define MACRO(x, opt...) call(x, ##opt)
//
// which extends to call(x) when opt is empty and call(x, ...) otherwise.
//
// None of the static methods of Registry<> can be called from
// a global static, as it would result in an initialization order fiasco.
#include <set>
#include <string>
#include <typeinfo>
#include <vector>
#include <memory>
#include <functional>
#include <map>
#pragma once
namespace CaveGame
{
/// Dead Simple Registry system, however, it relies on implementation-defined behavior:
/** @note 'It is implementation-defined whether the dynamic initialization of a non-local variable
with static storage duration is done before the first statement of main.
If the initialization is deferred to some point in time after the first statement of main,
it shall occur before the first odr-use (3.2) of any function or variable defined in the same
translation unit as the variable to be initialized.' */
template <class T>
struct Registry
{
struct proxy { inline proxy(); };
static proxy p;
};
/// What The Fuck Bro?
template <class T> typename Registry<T>::proxy Registry<T>::p;
#define REGISTER(KEY, TYPE, ARGS...) REGISTER_AT(__LINE__, KEY, TYPE ##ARGS)
#define REGISTER_ALIAS(TYPE, NAME, ALIAS) \
REGISTER_ALIAS_AT(__LINE__, TYPE, NAME, ALIAS)
#define REGISTER_ALIAS_AT(LINE, TYPE, NAME, ALIAS) \
Registry<TYPE>::Injector CONCAT_TOKENS(_xd_injector, LINE)(ALIAS, []() { \
return Registry<TYPE>::New(NAME).release(); \
}, __FILE__, STRINGIFY(LINE)); \
static_assert(true, "") // enforce ; at EOL
struct factory
{
template <typename T> static T* create()
{
Registry<T>::p;
return new T();
namespace factory {
template<typename T, class... Args>
class Registry {
public:
// Return 'true' if there is a class registered for `key` for
// a constructor with signature (Args... args).
//
// This function can not be called from any static initializer
// or it creates initializer order fiasco.
static bool CanNew(const std::string &key, Args... args) {
return GetEntry(key, args...).first;
}
// If there is a class registered for `key` for a constructor
// with signature (Args... args), instantiate an object of that class
// passing args to the constructor. Returns a null pointer otherwise.
//
// This function can not be called from any static initializer
// or it creates initializer order fiasco.
static std::unique_ptr<T> New(const std::string &key, Args... args) {
std::unique_ptr<T> result;
auto entry = GetEntry(key, args...);
if (entry.first) {
result.reset(entry.second->function(args...));
}
registry_mutex_.unlock();
return std::move(result);
}
// Return the key under which class `C` is registered. The header
// defining that class must be included by code calling this
// function. If class is not registered, there will be a compile-
// time failure.
template<typename C>
static const char *GetKeyFor() {
return C::_xd_key(static_cast<const T *>(nullptr),
std::function<void(Args...)>());
}
// Returns the list of keys registered for the registry.
// Keys corresponding to injectors (see below) are suffixed with
// a star.
//
// This function can not be called from any static initializer
// or it creates initializer order fiasco.
static std::vector<std::string> GetKeys() {
std::vector<std::string> keys;
registry_mutex_.lock();
for (const auto &iter: *GetRegistry()) {
keys.emplace_back(iter.first);
}
for (const auto &iter: *GetInjectors()) {
keys.emplace_back(iter.first + "*");
}
registry_mutex_.unlock();
return keys;
}
// Like GetKeys() function, but also returns the filename
// and line number of the corresponding REGISTER() macros.
// For injectors, the filename and line number are those
// passed to the injector constructor.
static std::vector<std::string> GetKeysWithLocations() {
std::vector<std::string> keys;
registry_mutex_.lock();
for (const auto &iter: *GetRegistry()) {
keys.emplace_back(std::string(iter.second.file) + ":" +
std::string(iter.second.line) + ": " + iter.first);
}
for (const auto &iter: *GetInjectors()) {
keys.emplace_back(std::string(iter.second.file) + ":" +
std::string(iter.second.line) + ": " + iter.first +
"*");
}
registry_mutex_.unlock();
return keys;
}
// Helper class which uses RAII to inject a factory which will be used
// instead of any class registered with the same key, for any call
// within the scope of the variable.
// If there are two injectors in the same scope for the same key,
// the last one takes precedence and cancels the first one, which will
// never be active, even if the second one gets out of scope.
struct Injector {
const std::string &key;
Injector(const std::string &key,
const std::function<T *(Args...)> &function,
const char *file = "undefined", const char *line = "undefined")
: key(key) {
registry_mutex_.lock();
const Entry entry = {file, line, function};
GetInjectors()->insert(std::make_pair(key, entry));
registry_mutex_.unlock();
}
~Injector() {
registry_mutex_.lock();
GetInjectors()->erase(key);
registry_mutex_.unlock();
}
};
//***************************************************************************
// Implementation details that can't be made private because used in macros
//***************************************************************************
typedef std::function<T *(Args...)> function_t;
struct Registerer {
Registerer(function_t function, const std::string &key, const char *file,
const char *line) {
const Entry entry = {file, line, function};
registry_mutex_.lock();
GetRegistry()->insert(std::make_pair(key, entry));
registry_mutex_.unlock();
}
};
private:
struct Entry {
const char *const file;
const char *const line;
const function_t function;
};
typedef std::map<std::string, Entry> EntryMap;
// The registry and injectors are created on demand using static variables
// inside a static method so that there is no order initialization fiasco.
static EntryMap *GetRegistry() {
static EntryMap registry;
return &registry;
}
static EntryMap *GetInjectors() {
static EntryMap injectors;
return &injectors;
};
static std::mutex registry_mutex_;
static std::pair<bool, const Entry *> GetEntry(const std::string &key,
Args... args) {
registry_mutex_.lock();
auto it = GetInjectors()->find(key);
bool found = (it != GetInjectors()->end());
if (!found) {
it = GetRegistry()->find(key);
found = (it != GetRegistry()->end());
}
registry_mutex_.unlock();
return std::make_pair(found, &(it->second));
}
};
std::set<std::string> & types();
template<typename T, class... Args>
std::mutex Registry<T, Args...>::registry_mutex_;
template <typename T>
Registry<T>::proxy::proxy() { types().insert(typeid(T).name());}
struct Registry2
{
static std::vector<std::string> entries;
Registry2(std::string name) { entries.push_back(name); }
//*****************************************************************************
// Implementation details of REGISTER() macro.
//
// Creates uniquely named traits class and functions which forces the
// instantiation of a TypeRegisterer class with a static member doing the
// actual registration in the registry. Note that TypeRegisterer being a
// template, there is no violation of the One Definition Rule. The use of
// Trait class is way to pass back information from the class where the
// macro is called, to the definition of TypeRegisterer static member.
// This works only because the Trait functions do not reference any other
// static variable, or it would create an initialization order fiasco.
//*****************************************************************************
template<typename Trait, typename base_type, typename derived_type,
typename... Args>
struct TypeRegisterer {
static const typename Registry<base_type, Args...>::Registerer instance;
};
#define TILE(cls) temp_##cls; static Registry2 tile_##cls(cls); class cls
template<typename Trait, typename base_type, typename derived_type,
typename... Args>
const typename Registry<base_type, Args...>::Registerer
TypeRegisterer<Trait, base_type, derived_type, Args...>::instance(
[](Args... args) { return new derived_type(args...); },
Trait::key(), Trait::file(), Trait::line());
#define CONCAT_TOKENS(x, y) x##y
#define STRINGIFY(x) #x
#define REGISTER_AT(LINE, KEY, TYPE, ARGS...) \
friend class ::factory::Registry<TYPE, ##ARGS>; \
struct CONCAT_TOKENS(_xd_Trait, LINE) { \
static const char *key() { return KEY; } \
static const char *file() { return __FILE__; } \
static const char *line() { return STRINGIFY(LINE); } \
}; \
const void *CONCAT_TOKENS(_xd_unused, LINE)() const { \
return &::factory::TypeRegisterer<CONCAT_TOKENS(_xd_Trait, LINE), TYPE, \
std::decay<decltype(*this)>::type, \
##ARGS>::instance; \
} \
static const char *_xd_key(const TYPE *, std::function<void(ARGS)>) { \
return KEY; \
} \
static_assert(true, "") // enforce ; at EOL
class TILE(Stone)
{
};
}
}

View File

@@ -1,10 +1,24 @@
#pragma once
#include <cstdint>
#include "Color3.hpp"
#include <Color4.hpp>
#include <Colors.hpp>
#include <Core/Data.hpp>
#include <Core/Registry.hpp>
#include <functional>
#include <Core/Interfaces.hpp>
#include <J3ML/LinearAlgebra/Vector2i.hpp>
namespace CaveGame::Core
{
using J3ML::LinearAlgebra::Vector2i;
enum TileFlags : uint16_t
{
SOLID,
NONSOLID = !SOLID,
};
// Use this for space-efficiency and convert to Color3 for doing color manipulations.
@@ -16,4 +30,114 @@ namespace CaveGame::Core
uint8_t g;
};
class TileGroup {};
class Tile
{
public:
TileID numeric_id;
std::string mnemonic_id;
Color4 base_color;
std::vector<Color4> color_pallet;
bool collides;
bool does_random_ticc;
public:
Tile(TileID numeric, const std::string& name, Color4 color)
: numeric_id(numeric), mnemonic_id(name), base_color(color)
{}
virtual TileID NumericID() const { return numeric_id; };
virtual std::string MnemonicID() const { return mnemonic_id; };
bool DoesRandomTicc() const { return does_random_ticc; }
virtual void Draw(TileState state, int x, int y) {}
virtual void ForcedTicc(ITileMap* world, TileState state, int x, int y) {}
virtual void RandomTicc(ITileMap* world, TileState state, int x, int y) {}
template <TileID TDecaysTo>
void DecayTo(ITileMap *world, TileState state, int x, int y)
{
world->SetTile(x, y, TDecaysTo);
}
void DecayTo(ITileMap *world, TileState state, int x, int y, TileID TDecaysTo)
{
world->SetTile(x, y, TDecaysTo);
}
};
class SoilTile : public Tile
{
public:
TileID decays_to;
SoilTile(TileID numeric, const std::string& name, Color4 color, TileID decays_target)
: Tile(numeric, name, color)
{
decays_to = decays_target;
does_random_ticc = true;
}
template <TileID TDecaysTo>
void CheckSuffocate(ITileMap *world, TileState state, int x, int y)
{
if (!world->HasAdjacentOrDiagonalAirBlock(x, y))
DecayTo(world, state, x, y, TDecaysTo);
}
void RandomTicc(ITileMap *world, TileState state, int x, int y) override
{
}
//void CanGrowGrass
};
class DirtTile : public SoilTile
{
void RandomTicc(CaveGame::Core::ITileMap *world, CaveGame::Core::TileState state, int x, int y) override
{
}
};
class GrassyTile : public SoilTile
{
public:
GrassyTile(TileID numeric, const std::string& name, Color4 color)
: SoilTile(numeric, name, color, TileID::DIRT)
{
does_random_ticc = true;
}
};
class GasTile : public Tile
{
};
// TODO: Constexpr-qualify Color4
static std::vector<Tile*> registered_tiles;
static void RegisterTile(Tile* data);
static Tile* GetByNumeric(TileID id);
static Tile* GetByName(const std::string& name);
void Test();
}

View File

@@ -0,0 +1,7 @@
#pragma once
namespace CaveGame::Core
{
}

View File

@@ -2,6 +2,7 @@
#include "Chunk.hpp"
#include "Generator.hpp"
#include "J3ML/LinearAlgebra/Vector2i.hpp"
#include "Interfaces.hpp"
#include <unordered_map>
#include <filesystem>
@@ -22,7 +23,7 @@ struct std::hash<Vector2>
namespace CaveGame::Core
{
class World {
class World : public ITileMap {
public:
constexpr static uint RandomTileTickCoefficient = 100;
World() = default;
@@ -31,9 +32,13 @@ namespace CaveGame::Core
explicit World(const std::string &worldName, int seed = 0, bool overwrite = false);
TileID GetTile(int x, int y) const;
TileID GetTile(int x, int y) const override;
void SetTile(int x, int y, TileID t);
void SetTile(int x, int y, TileID t) override;
TileState GetTileState(int x, int y) const override { return 0; /* TODO: Implement tile state field */ }
void SetTileState(int x, int y, TileState state) { /* TODO: Implement tile state field */ }
// TODO: Doesn't really belong here.
Vector2 ToUnitDirection(float rotation);
@@ -75,18 +80,6 @@ namespace CaveGame::Core
private:
bool GrassSpreadable(int x, int y) const;
bool IsNonSolidTile(int x, int y) const;
bool GrassShouldSuffocate(int x, int y) const;
void GrassRandomTicc(int wx, int wy);
bool IsSolidTile(int x, int y) const;
bool HasAdjacentOrDiagonalAirBlock(int x, int y) const;
bool HasAdjacentAirBlock(int x, int y) const;
};
}

View File

@@ -0,0 +1,116 @@
#include <Core/Interfaces.hpp>
namespace CaveGame::Core
{
bool ITileMap::IsNonSolidTile(int x, int y) const
{
return GetTile(x, y) == TileID::AIR;
}
bool ITileMap::IsSolidTile(int x, int y) const
{
return !IsNonSolidTile(x, y);
}
bool ITileMap::HasAdjacentAirBlock(int x, int y) const
{
if (IsNonSolidTile(x+1, y) || IsNonSolidTile(x-1, y) || IsNonSolidTile(x, y+1) || IsNonSolidTile(x, y-1))
return true;
return false;
}
bool ITileMap::HasAdjacentOrDiagonalAirBlock(int x, int y) const
{
if (IsNonSolidTile(x+1, y) || IsNonSolidTile(x-1, y) || IsNonSolidTile(x, y+1) || IsNonSolidTile(x, y-1) ||
IsNonSolidTile(x+1, y-1) || IsNonSolidTile(x-1, y+1) || IsNonSolidTile(x+1, y+1) || IsNonSolidTile(x-1, y-1))
return true;
return false;
}
bool ITileMap::GrassSpreadable(int x, int y) const
{
if (GetTile(x, y) == TileID::DIRT)
{
if (HasAdjacentOrDiagonalAirBlock(x, y))
return true;
}
return false;
}
bool ITileMap::GrassShouldSuffocate(int x, int y) const
{
//if (IsSolidTile(x+1, y) && IsSolidTile(x-1, y) && IsSolidTile(x, y+1) && IsSolidTile(x, y-1) &&
//IsSolidTile(x+1, y+1) && IsSolidTile(x-1, y+1) && IsSolidTile(x+1, y-1) && IsSolidTile(x-1, y-1))
if (!HasAdjacentOrDiagonalAirBlock(x, y))
return true;
return false;
}
void ITileMap::GrassRandomTicc(int wx, int wy)
{
if (GrassShouldSuffocate(wx, wy))
{
SetTile(wx, wy, TileID::DIRT);
return;
}
// Check adjacent blocks
if (GrassSpreadable(wx, wy+1))
{
SetTile(wx, wy+1, TileID::GRASS);
return;
}
if (GrassSpreadable(wx, wy-1))
{
SetTile(wx, wy-1, TileID::GRASS);
return;
}
if (GrassSpreadable(wx+1, wy))
{
SetTile(wx+1, wy, TileID::GRASS);
return;
}
if (GrassSpreadable(wx-1, wy))
{
SetTile(wx-1, wy, TileID::GRASS);
return;
}
// Check diagonal blocks.
if (GrassSpreadable(wx+1, wy-1))
{
SetTile(wx+1, wy-1, TileID::GRASS);
return;
}
if (GrassSpreadable(wx-1, wy-1))
{
SetTile(wx-1, wy-1, TileID::GRASS);
return;
}
if (GrassSpreadable(wx+1, wy+1))
{
SetTile(wx+1, wy+1, TileID::GRASS);
return;
}
if (GrassSpreadable(wx-1, wy+1))
{
SetTile(wx-1, wy+1, TileID::GRASS);
return;
}
}
}

View File

@@ -1 +1,35 @@
#include <Core/Tile.hpp>
namespace CaveGame::Core
{
void Test() {
RegisterTile(new Tile(TileID::AIR, "air", {0,0,0,0}));
RegisterTile(new SoilTile(TileID::DIRT, "dirt", Colors::Green));
RegisterTile(new SoilTile(TileID::MUD, "mud", Colors::Green));
RegisterTile(new GrassyTile(TileID::STONE, "stone", Colors::Green));
RegisterTile(new GrassyTile(TileID::GRASS, "grass", Colors::Green));
}
Tile *GetByName(const std::string &name) {
// TODO: Optimize with additional mapping!!
for(auto& tile : registered_tiles)
if (tile->mnemonic_id == name)
return tile;
throw std::runtime_error("Invalid mnemonic ID!");
}
Tile *GetByNumeric(TileID id) {
// TODO: Optimize with additional mapping!!
for(auto& tile : registered_tiles)
if (tile->numeric_id == id)
return tile;
throw std::runtime_error("Invalid numeric ID!");
}
void RegisterTile(Tile *data) {
registered_tiles.push_back(data);
}
}

View File

@@ -137,117 +137,6 @@ namespace CaveGame::Core
}
bool World::IsNonSolidTile(int x, int y) const
{
return GetTile(x, y) == TileID::AIR;
}
bool World::IsSolidTile(int x, int y) const
{
return !IsNonSolidTile(x, y);
}
bool World::HasAdjacentAirBlock(int x, int y) const
{
if (IsNonSolidTile(x+1, y) || IsNonSolidTile(x-1, y) || IsNonSolidTile(x, y+1) || IsNonSolidTile(x, y-1))
return true;
return false;
}
bool World::HasAdjacentOrDiagonalAirBlock(int x, int y) const
{
if (IsNonSolidTile(x+1, y) || IsNonSolidTile(x-1, y) || IsNonSolidTile(x, y+1) || IsNonSolidTile(x, y-1) ||
IsNonSolidTile(x+1, y-1) || IsNonSolidTile(x-1, y+1) || IsNonSolidTile(x+1, y+1) || IsNonSolidTile(x-1, y-1))
return true;
return false;
}
bool World::GrassSpreadable(int x, int y) const
{
if (GetTile(x, y) == TileID::DIRT)
{
if (HasAdjacentOrDiagonalAirBlock(x, y))
return true;
}
return false;
}
bool World::GrassShouldSuffocate(int x, int y) const
{
//if (IsSolidTile(x+1, y) && IsSolidTile(x-1, y) && IsSolidTile(x, y+1) && IsSolidTile(x, y-1) &&
//IsSolidTile(x+1, y+1) && IsSolidTile(x-1, y+1) && IsSolidTile(x+1, y-1) && IsSolidTile(x-1, y-1))
if (!HasAdjacentOrDiagonalAirBlock(x, y))
return true;
return false;
}
void World::GrassRandomTicc(int wx, int wy)
{
if (GrassShouldSuffocate(wx, wy))
{
SetTile(wx, wy, TileID::DIRT);
return;
}
// Check adjacent blocks
if (GrassSpreadable(wx, wy+1))
{
SetTile(wx, wy+1, TileID::GRASS);
return;
}
if (GrassSpreadable(wx, wy-1))
{
SetTile(wx, wy-1, TileID::GRASS);
return;
}
if (GrassSpreadable(wx+1, wy))
{
SetTile(wx+1, wy, TileID::GRASS);
return;
}
if (GrassSpreadable(wx-1, wy))
{
SetTile(wx-1, wy, TileID::GRASS);
return;
}
// Check diagonal blocks.
if (GrassSpreadable(wx+1, wy-1))
{
SetTile(wx+1, wy-1, TileID::GRASS);
return;
}
if (GrassSpreadable(wx-1, wy-1))
{
SetTile(wx-1, wy-1, TileID::GRASS);
return;
}
if (GrassSpreadable(wx+1, wy+1))
{
SetTile(wx+1, wy+1, TileID::GRASS);
return;
}
if (GrassSpreadable(wx-1, wy+1))
{
SetTile(wx-1, wy+1, TileID::GRASS);
return;
}
}
void World::DoRandomTileTicks() {
// Every frame, In each loaded chunk, perform a RandomTileTick on N tiles.
@@ -265,11 +154,10 @@ namespace CaveGame::Core
TileID at = chunk.GetTile(x, y);
if (at == TileID::GRASS)
GrassRandomTicc(wx, wy);
//if (at == TileID::SAND)
Tile* base = GetByNumeric(at);
if (base->DoesRandomTicc())
base->RandomTicc(this, 0, wx, wy);
}
}