alaah 5 years ago
parent
commit
43b81fe2a4
87 changed files with 2278 additions and 0 deletions
  1. BIN
      data/Scratch.ttf
  2. 5 0
      data/Scratch.txt
  3. BIN
      data/arrows.png
  4. BIN
      data/basic_background.png
  5. BIN
      data/boom.ogg
  6. BIN
      data/doodles.png
  7. BIN
      data/doodles1.aseprite
  8. BIN
      data/doodles2.aseprite
  9. BIN
      data/hop0.ogg
  10. BIN
      data/hop1.ogg
  11. BIN
      data/hop2.ogg
  12. BIN
      data/hop3.ogg
  13. BIN
      data/hop4.ogg
  14. BIN
      data/hop5.ogg
  15. BIN
      data/hop6.ogg
  16. BIN
      data/hop7.ogg
  17. BIN
      data/hop8.ogg
  18. BIN
      data/hop9.ogg
  19. BIN
      data/intro1.ogg
  20. BIN
      data/intro2.ogg
  21. BIN
      data/loop1.ogg
  22. BIN
      data/loop2.ogg
  23. 86 0
      data/map.json
  24. 60 0
      data/map.tmx
  25. 4 0
      data/map.tsx
  26. BIN
      data/metal.png
  27. BIN
      data/nem-jump.png
  28. BIN
      data/nem-plop.png
  29. BIN
      data/nem-run.png
  30. BIN
      data/nem.png
  31. BIN
      data/nem1.png
  32. BIN
      data/nem2.png
  33. BIN
      data/nem3.png
  34. BIN
      data/nem4.png
  35. BIN
      data/nem5.png
  36. BIN
      data/nem6.png
  37. BIN
      data/nembookless.png
  38. BIN
      data/numbers.png
  39. 26 0
      data/oldmap.tmx
  40. 57 0
      data/oldmapbutnewer.tmx
  41. BIN
      data/ouch.ogg
  42. BIN
      data/owocek.aseprite
  43. BIN
      data/owocek.png
  44. BIN
      data/plop1.ogg
  45. BIN
      data/plop2.ogg
  46. BIN
      data/plop3.ogg
  47. BIN
      data/sand.png
  48. BIN
      data/spike.png
  49. BIN
      data/splash.png
  50. BIN
      data/splash.xcf
  51. BIN
      data/splash2.png
  52. BIN
      data/splash3.png
  53. BIN
      data/splash3.xcf
  54. BIN
      data/tileset.png
  55. BIN
      data/water.aseprite
  56. BIN
      data/water.png
  57. BIN
      data/weird.png
  58. 10 0
      include/Alermath.hpp
  59. 23 0
      include/AnimatedSprite.hpp
  60. 11 0
      include/Animation.hpp
  61. 42 0
      include/Asset.hpp
  62. 180 0
      include/BlockLayer.hpp
  63. 51 0
      include/Collider.hpp
  64. 72 0
      include/Core.hpp
  65. 20 0
      include/Fruit.hpp
  66. 167 0
      include/Grid.hpp
  67. 6 0
      include/IEntity.hpp
  68. 16 0
      include/Launcher.hpp
  69. 88 0
      include/Log.hpp
  70. 21 0
      include/NumText.hpp
  71. 31 0
      include/Player.hpp
  72. 35 0
      include/Renderer.hpp
  73. 317 0
      include/SFMLOrthogonalLayer.hpp
  74. 14 0
      include/Sand.hpp
  75. 24 0
      include/Spike.hpp
  76. 61 0
      src/AnimatedSprite.cpp
  77. 106 0
      src/Block.cpp
  78. 299 0
      src/Core.cpp
  79. 57 0
      src/Fruit.cpp
  80. 29 0
      src/Launcher.cpp
  81. 7 0
      src/Log.cpp
  82. 13 0
      src/Main.cpp
  83. 66 0
      src/NumText.cpp
  84. 123 0
      src/Player.cpp
  85. 80 0
      src/Renderer.cpp
  86. 13 0
      src/Sand.cpp
  87. 58 0
      src/Spike.cpp

BIN
data/Scratch.ttf


+ 5 - 0
data/Scratch.txt

@@ -0,0 +1,5 @@
+This font was found on the internet and did not come with a license. While we try to make sure that all the fonts on fontsquirrel.com are properly licensed for commercial use, there are many fonts that have either been abandoned by their authors or the authors distribute their fonts without an explicit license. 
+
+It is our opinion that if the unlicensed font is freely available for download from either the original source or from multiple free-font sites then we assume it to be safe to use the font commercially. This is no guarantee of such freedom, but there are so many unlicensed free fonts distributed by primary sources that the intentions must be read that the font is free to use how you like.
+
+We are not lawyers and don't pretend to be them on TV. Please report any errors/violations you know of. http://www.fontsquirrel.com/contact

BIN
data/arrows.png


BIN
data/basic_background.png


BIN
data/boom.ogg


BIN
data/doodles.png


BIN
data/doodles1.aseprite


BIN
data/doodles2.aseprite


BIN
data/hop0.ogg


BIN
data/hop1.ogg


BIN
data/hop2.ogg


BIN
data/hop3.ogg


BIN
data/hop4.ogg


BIN
data/hop5.ogg


BIN
data/hop6.ogg


BIN
data/hop7.ogg


BIN
data/hop8.ogg


BIN
data/hop9.ogg


BIN
data/intro1.ogg


BIN
data/intro2.ogg


BIN
data/loop1.ogg


BIN
data/loop2.ogg


File diff suppressed because it is too large
+ 86 - 0
data/map.json


File diff suppressed because it is too large
+ 60 - 0
data/map.tmx


+ 4 - 0
data/map.tsx

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tileset name="tiles" tilewidth="16" tileheight="16" tilecount="6" columns="3">
+ <image source="tiles.png" width="48" height="32"/>
+</tileset>

BIN
data/metal.png


BIN
data/nem-jump.png


BIN
data/nem-plop.png


BIN
data/nem-run.png


BIN
data/nem.png


BIN
data/nem1.png


BIN
data/nem2.png


BIN
data/nem3.png


BIN
data/nem4.png


BIN
data/nem5.png


BIN
data/nem6.png


BIN
data/nembookless.png


BIN
data/numbers.png


+ 26 - 0
data/oldmap.tmx

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.4" orientation="orthogonal" renderorder="right-down" width="40" height="16" tilewidth="16" tileheight="16" infinite="0" nextlayerid="6" nextobjectid="1">
+ <tileset firstgid="1" name="weird" tilewidth="16" tileheight="16" tilecount="3" columns="3">
+  <image source="weird.png" width="48" height="16"/>
+ </tileset>
+ <layer id="1" name="Tile1" width="40" height="16">
+  <data encoding="csv">
+2,1,0,1,1,1,3,3,3,1,1,3,3,2,2,1,1,1,1,1,1,1,1,1,1,1,3,3,3,1,1,1,1,1,1,1,1,1,1,1,
+2,2,1,1,3,3,1,1,1,1,3,3,1,1,1,2,2,1,1,1,1,1,1,1,1,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,
+1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+3,3,2,2,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,3,
+1,1,1,2,2,1,1,3,3,1,1,1,1,1,1,3,3,3,3,3,1,2,2,2,3,3,3,1,1,1,1,3,3,3,3,3,1,1,1,1,
+1,1,1,1,2,3,3,1,1,1,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,1,1,1,1,1,1,1,1,3,
+1,1,1,1,3,3,3,3,3,3,1,3,3,2,2,2,2,1,1,1,1,1,1,3,3,3,3,2,2,1,1,1,1,1,1,2,2,3,3,3,
+1,1,1,1,3,3,3,2,3,3,3,2,2,1,1,1,1,1,1,3,3,3,3,1,1,1,1,2,1,2,2,2,1,2,3,3,3,1,2,2,
+1,1,1,1,1,3,3,3,1,1,1,1,1,1,1,3,3,3,3,1,1,1,1,1,1,1,1,2,2,2,2,2,3,3,3,3,1,1,1,3,
+1,1,1,3,3,2,2,2,2,3,3,3,3,3,3,1,1,1,1,1,1,2,2,2,2,2,2,2,1,1,3,3,3,3,2,2,1,1,3,2,
+3,3,3,1,3,3,3,3,3,2,1,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,2,1,3,3,3,3,1,1,1,1,2,3,2,2,
+3,3,3,3,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,3,3,3,3,3,1,1,1,1,3,3,1,2,2,
+1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,2,3,1,3,3,1,1,1,1,3,3,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,1,1,1,1,1,1,1,1,3,3,3,3,1,1,1,1,1,3,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,3,3,3,3,1,1,1,1,1,3,3,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,3,1,1,1,1,1,3,3,1,1,1,1,1,1,1,1,1,1
+</data>
+ </layer>
+</map>

File diff suppressed because it is too large
+ 57 - 0
data/oldmapbutnewer.tmx


BIN
data/ouch.ogg


BIN
data/owocek.aseprite


BIN
data/owocek.png


BIN
data/plop1.ogg


BIN
data/plop2.ogg


BIN
data/plop3.ogg


BIN
data/sand.png


BIN
data/spike.png


BIN
data/splash.png


BIN
data/splash.xcf


BIN
data/splash2.png


BIN
data/splash3.png


BIN
data/splash3.xcf


BIN
data/tileset.png


BIN
data/water.aseprite


BIN
data/water.png


BIN
data/weird.png


+ 10 - 0
include/Alermath.hpp

@@ -0,0 +1,10 @@
+ #pragma once
+ 
+ namespace math {
+ 
+ template<typename T>
+ inline int sgn(T t) {
+	return t == 0 ? 0 : t > 0 ? 1 : -1;
+ }
+ 
+ }

+ 23 - 0
include/AnimatedSprite.hpp

@@ -0,0 +1,23 @@
+#pragma once
+
+#include <unordered_map>
+#include <SFML/Graphics.hpp>
+#include "Animation.hpp"
+
+struct Animation;
+
+class AnimatedSprite: public sf::Sprite {
+public:
+	AnimatedSprite();
+	virtual ~AnimatedSprite();
+	void addAnimation(std::string filename, unsigned duration, std::string name, bool loop, unsigned width);
+	void setAnimation(std::string name);
+	void setAnimationSpeed(float speed);
+	virtual void animate();
+protected:
+	Animation* m_current_anim;
+	sf::Clock m_clock;
+private:
+	float m_speed;
+	std::unordered_map<std::string, Animation> m_anim;
+};

+ 11 - 0
include/Animation.hpp

@@ -0,0 +1,11 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+
+struct Animation {
+	sf::Texture texture;
+	unsigned count;
+	unsigned duration;
+	std::string name;
+	bool loop;
+};

+ 42 - 0
include/Asset.hpp

@@ -0,0 +1,42 @@
+#pragma once
+ 
+#include <unordered_map>
+#include <SFML/Graphics.hpp>
+#include "Log.hpp"
+
+class Asset {
+public:
+	static const sf::Texture& texture(std::string str) {
+		if(s_tex.find(str) == s_tex.end()) {
+			if(!s_tex[str].loadFromFile(str)) {
+				Log(ERROR, "Unable to load ", str);
+				exit(-1);
+			}
+		}
+		return s_tex[str];
+	}
+	
+	static const sf::SoundBuffer& sound(std::string str) {
+		if(s_snd.find(str) == s_snd.end()) {
+			if(!s_snd[str].loadFromFile(str)) {
+				Log(ERROR, "Unable to load ", str);
+				exit(-1);
+			}
+		}
+		return s_snd[str];
+	}
+	
+	static const sf::Font& font(std::string str) {
+		if(s_fnt.find(str) == s_fnt.end()) {
+			if(!s_fnt[str].loadFromFile(str)) {
+				Log(ERROR, "Unable to load ", str);
+				exit(-1);
+			}
+		}
+		return s_fnt[str];
+	}
+private:
+	static std::unordered_map<std::string, sf::Texture> s_tex;
+	static std::unordered_map<std::string, sf::SoundBuffer> s_snd;
+	static std::unordered_map<std::string, sf::Font> s_fnt;
+};

+ 180 - 0
include/BlockLayer.hpp

@@ -0,0 +1,180 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <nlohmann/json.hpp>
+
+#include <vector>
+#include <fstream>
+#include <array>
+#include <iostream>
+#include <sstream>
+#include <cmath>
+#include "Log.hpp"
+#include "Asset.hpp"
+#include "Collider.hpp"
+#include "Sand.hpp"
+#include "Spike.hpp"
+
+#define CW 20
+#define CH 12 
+
+using json = nlohmann::json;
+using Block = std::array<sf::Vertex, 4u>;
+
+class BlockLayer final: public sf::Drawable {
+public:
+	BlockLayer() = delete;
+	BlockLayer(const std::string& filename, const std::string& tileset):
+	m_tex(&Asset::texture(tileset)),
+	m_tileSize(32.f) {
+		std::ifstream ifs(filename);
+		json j;
+		ifs >> j;
+		
+		auto layer = j["layers"][2];
+		m_size.x = layer["width"];
+		m_size.y = layer["height"];
+		
+		m_blocks.reserve(m_size.x * m_size.y);
+		
+		for(std::size_t i = 0; i < layer["data"].size(); ++i) {
+			int tile = layer["data"][i];
+			size_t x = i % m_size.x;
+			size_t y = i / m_size.x;
+			
+			int collide_type = Collider::AIR;
+			switch(tile) {
+			case 1:
+				collide_type = Collider::SOLID;
+				break;
+			case 2:
+				collide_type = Collider::SAND;
+				break;
+			case 3:
+				collide_type = Collider::LEVER;
+				break;
+			case 4:
+				collide_type = Collider::SPIKE;
+				break;
+			default:
+				break;
+			}
+			m_map.push_back(collide_type);
+			
+			if(0 && tile == 2) {
+				sf::Vector2f tileOffset(x * m_tileSize, y * m_tileSize);
+				std::size_t tileIndex = tile - 1;
+
+				m_blocks.emplace_back(
+					tileOffset, 
+					sf::Color::White, 
+					sf::Vector2f(tileIndex * m_tileSize, 0.f)
+				);
+				m_blocks.emplace_back(
+					tileOffset + sf::Vector2f(m_tileSize, 0.f), 
+					sf::Color::White, 
+					sf::Vector2f((tileIndex + 1) * m_tileSize, 0.f)
+				);
+				m_blocks.emplace_back(
+					tileOffset + sf::Vector2f(m_tileSize, m_tileSize), 
+					sf::Color::White, 
+					sf::Vector2f((tileIndex + 1) * m_tileSize, 
+					m_tileSize)
+				);
+				m_blocks.emplace_back(
+					tileOffset + sf::Vector2f(0.f, m_tileSize), 
+					sf::Color::White, 
+					sf::Vector2f(tileIndex * m_tileSize, 
+					m_tileSize)
+				);
+			} else {
+				for(std::size_t i = 0; i < 4; ++i) {
+					m_blocks.emplace_back(sf::Vector2f(0.f, 0.f), sf::Color::White, sf::Vector2f(0.f, 0.f));
+				}
+			}
+		}
+	}
+	~BlockLayer() {
+		/*for(std::size_t i = 0; i < m_blocks.size(); ++i) {
+			if(m_blocks[i]) {
+				delete m_blocks[i];
+			}
+		}*/
+	}
+	
+	std::vector<int> rebuildCollider(int focus) {
+		std::vector<int> res;
+		res.reserve(CW * CH);
+		for(int j = 0; j < CH; ++j) {
+			for(int i = focus * CW; i < focus * CW + CW; ++i) {
+                int curr = m_map[i + j * m_size.x];
+				if(curr != Collider::SPIKE && curr != Collider::SAND) {
+					res.push_back(m_map[i + j * m_size.x]);
+				} else {
+					res.push_back(Collider::AIR);
+				}
+			}
+		}
+		return res;
+	}
+	
+	std::vector<Spike> reloadSpikes(int focus, Collider* collider) {
+		std::vector<Spike> res;
+		for(int j = 1; j < CH; ++j) {
+			for(int i = focus * CW; i < focus * CW + CW; ++i) {
+				if(m_map[i + j * m_size.x] == Collider::SPIKE) {
+					res.emplace_back(collider, m_map[i + (j - 1) * m_size.x] == Collider::LEVER);
+					res.back().setPosition(i * 32.f, j * 32.f);
+					res.back().setTexture(Asset::texture("data/tileset.png"));
+					res.back().setTextureRect(sf::IntRect(
+						96,
+						0,
+						32,
+						32
+					));
+				}
+			}
+		}
+		return res;
+	}
+	
+    std::vector<Sand> reloadSand(int focus, Collider* collider) {
+		std::vector<Sand> res;
+		for(int j = 0; j < CH; ++j) {
+			for(int i = focus * CW; i < focus * CW + CW; ++i) {
+				if(m_map[i + j * m_size.x] == Collider::SAND) {
+					res.emplace_back(collider);
+					res.back().setPosition(i * 32.f, j * 32.f);
+					res.back().setTexture(Asset::texture("data/tileset.png"));
+					res.back().setTextureRect(sf::IntRect(
+						32,
+						0,
+						32,
+						32
+					));
+				}
+			}
+		}
+		return res;
+	}
+	
+	std::vector<sf::Vertex> m_blocks;
+	std::vector<int> m_map;
+	
+    BlockLayer(const BlockLayer&) = delete;
+    BlockLayer& operator=(const BlockLayer&) = delete;
+
+    //const sf::FloatRect& getGlobalBounds() const { 
+	//	return m_globalBounds; 
+	//}
+
+private:
+    const sf::Texture* m_tex;
+	sf::Vector2u m_size;
+	float m_tileSize;
+
+    void draw(sf::RenderTarget& rt, sf::RenderStates states) const override {
+		states.texture = m_tex;
+        rt.draw(m_blocks.data(), m_blocks.size(), sf::Quads, states);
+    }
+};

+ 51 - 0
include/Collider.hpp

@@ -0,0 +1,51 @@
+#pragma once
+
+#include "Log.hpp"
+
+#define CW 20
+#define CH 12 
+
+class Collider: public IEntity {
+public:
+	enum {
+		AIR, SAND, FALLINGSAND, SOLID, SPIKE, LEVER
+	};
+	bool isSafe(int x, int y) {
+		int tile = m_tile[getTile(x, y)];
+		return tile == -1 ? true : tile != FALLINGSAND && tile != SPIKE;
+	}
+	bool isSolid(int x, int y) {
+		int tile = m_tile[getTile(x, y)];
+		return tile == -1 ? true : tile != AIR && tile != LEVER;
+	}
+	int getBlock(int x, int y) {
+		return m_tile[getTile(x, y)];
+	}
+	void setBlock(int x, int y, int type) {
+		m_tile[getTile(x, y)] = type;
+	}
+	void populate(std::vector<int> data) {
+		std::copy(data.begin(), data.end(), m_tile);
+		std::copy(data.begin(), data.end(), m_base);
+	}
+	float getLocalX() {
+		return m_localX;
+	}
+	void setLocalX(float localX) {
+		m_localX = localX;
+	}
+	void update(float delta) {
+		std::copy(m_base, m_base + CW * CH, m_tile);
+	}
+
+private:
+	int getTile(int x, int y) {
+		if(x < 0 || y < 0 || x >= CW || y >= CH) {
+			return -1;
+		}
+		return x + CW * y;
+	}
+	int m_tile[CW * CH];
+	int m_base[CW * CH];
+	float m_localX;
+};

+ 72 - 0
include/Core.hpp

@@ -0,0 +1,72 @@
+#pragma once
+
+#include <vector>
+#include <SFML/Graphics.hpp>
+#include <SFML/Audio.hpp>
+#include <tmxlite/Map.hpp>
+#include "SFMLOrthogonalLayer.hpp"
+#include "Launcher.hpp"
+#include "Player.hpp"
+#include "IEntity.hpp"
+#include "BlockLayer.hpp"
+#include "Collider.hpp"
+#include "Grid.hpp"
+#include "Spike.hpp"
+#include "Sand.hpp"
+#include "Fruit.hpp"
+
+#define LAYER_COUNT 3
+
+class Core {
+public:
+	Core() = delete;
+	Core(sf::RenderWindow* window);
+	virtual ~Core();
+	score_t getScore(); // TODO why can't I set this to const??;
+	bool hasQuit();
+	void run();
+private:
+	void registerEntity(IEntity* entity);
+	void deregisterEntity(IEntity* entity);
+
+	void events();
+	void update(float delta);
+    void reset();
+	
+	enum State {
+		SPLASH, INGAME, END
+	};
+	State m_state;
+	
+	std::vector<IEntity*> m_entities;
+	
+	score_t m_score;
+	bool m_running;
+	bool m_paused;
+	
+	Collider m_collider;
+	Renderer m_renderer;
+	std::vector<Spike> m_spikes;
+	std::vector<Sand> m_sand;
+	std::vector<Fruit> m_fruit;
+	
+	sf::RenderWindow* m_window;
+ 	Player m_player;
+	
+	BlockLayer m_blockLayer;
+	Grid m_grid;
+	
+	sf::Sound m_music;
+	
+	sf::Sprite m_background;
+	tmx::Map m_map;
+	std::array<MapLayer, LAYER_COUNT> m_layer;
+	
+	sf::Clock m_interlude_clock;
+	sf::Clock m_fruit_clock;
+	sf::Clock m_global_clock;
+	
+	sf::Sprite m_splash;
+	bool m_victory;
+	sf::Text m_victory_text;
+};

+ 20 - 0
include/Fruit.hpp

@@ -0,0 +1,20 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <SFML/Audio.hpp>
+#include "IEntity.hpp"
+
+class Collider;
+
+class Fruit: public sf::Sprite, public IEntity {
+public:
+	Fruit(Collider* collider);
+	void update(float delta) override;
+	void registerColl();
+private:
+	Collider* m_collider;
+	bool m_falling;
+	sf::Clock m_clock;
+	float m_baseY;
+	sf::Sound m_boom;
+};

+ 167 - 0
include/Grid.hpp

@@ -0,0 +1,167 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include "IEntity.hpp"
+#include "Collider.hpp"
+#include "Log.hpp"
+#include "Sand.hpp"
+
+class Grid: public sf::Drawable, public IEntity {
+public:
+	Grid() = delete;
+	Grid(Collider* collider, sf::RenderWindow* window, std::vector<Sand>* sand, std::vector<Spike>* spikes, Renderer* renderer):
+	m_collider(collider),
+	m_window(window),
+	m_visible(false),
+    m_sand(sand),
+	m_spikes(spikes),
+	m_renderer(renderer),
+	m_mouseRelease(false),
+	m_csand(nullptr),
+	m_cspike(nullptr) {
+		m_sprite.setTexture(Asset::texture("data/arrows.png"));
+		setType(-1);
+	}
+	void reset() {
+		setType(-1);
+		m_csand = nullptr;
+		m_cspike = nullptr;
+	}
+	void sendReleaseSignal() {
+		m_mouseRelease = true;
+	}
+	void update(float delta) override {
+		static sf::Vector2i prev_mouse(0, 0);
+		
+		sf::Vector2i pos;
+		sf::Vector2i curr_mouse = sf::Mouse::getPosition(*m_window);
+		pos.x = curr_mouse.x / 64.f;
+		pos.y = curr_mouse.y / 64.f;
+		
+		int mode = m_type;
+
+		if(m_cspike && !sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)) {
+			m_cspike = nullptr;
+		}
+
+		if(pos.x >= 0 && pos.x < 20 && pos.y >= 0 && pos.y < 12) {
+			int block = m_collider->getBlock(pos.x, pos.y);
+
+			if(m_cspike) {
+				
+			} else if(m_csand) {
+                if(block == Collider::AIR) {
+                    mode = 2;
+                } else {
+                    mode = -1;
+                }
+            } else if(block == Collider::SAND) {
+				mode = 1;
+			} else if(block == Collider::SPIKE 
+			&& (m_collider->getBlock(pos.x, pos.y - 1) == Collider::LEVER 
+			|| mode == 0)) { // not checking if y>1, not needed rn
+				mode = 0;
+			} else {
+				mode = -1;
+			}
+		} else {
+			mode = -1;
+		}
+		
+		if(mode == 0 && sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)) {
+			float mouse_w = m_renderer->getPosition() - 320 + curr_mouse.x / 2;
+			if(!m_cspike) {
+				auto res = std::find_if(m_spikes->begin(), m_spikes->end(), [mouse_w](Spike& s) {
+					return s.getPosition().x <= mouse_w && s.getPosition().x + 32.f >= mouse_w;
+				});
+				m_cspike = &(*res);
+			}
+			if(m_cspike) {
+				float delta = (curr_mouse.y - prev_mouse.y) / 2.f;
+				int x = (m_cspike->getPosition().x - m_renderer->getPosition() + 320.f) / 32;
+				if(delta < 0) {
+					if(m_collider->getBlock(x, (m_cspike->getPosition().y + delta) / 32.f) == Collider::LEVER
+					|| m_collider->getBlock(x, (m_cspike->getPosition().y + delta) / 32.f) == Collider::SPIKE) {
+						m_cspike->move(delta);
+						m_sprite.move(0.f, delta);
+					} else {
+						mode = 0;
+					}
+				} else {
+					if(m_collider->getBlock(x, (m_cspike->getPosition().y + 32.f + delta) / 32.f) == Collider::LEVER
+					|| m_collider->getBlock(x, (m_cspike->getPosition().y + 32.f + delta) / 32.f) == Collider::SPIKE
+                    || m_collider->getBlock(x, (m_cspike->getPosition().y + 32.f + delta) / 32.f) == Collider::AIR) {
+						m_cspike->move(delta);
+						m_sprite.move(0.f, delta);
+					} else {
+						mode = 0;
+					}
+
+                }
+			}
+		} else {
+			for(Spike& s: *m_spikes) {
+				s.release();
+			}
+			if(mode >= 0) {
+				m_sprite.setPosition(pos.x * 32.f, pos.y * 32.f);
+			}
+			setType(mode);
+		}
+		
+		if(m_mouseRelease) {
+			if(mode == 1) {
+				float mouse_w = m_renderer->getPosition() - 320.f + curr_mouse.x / 2.f;
+				float mouse_h = curr_mouse.y / 2.f;
+				auto res = std::find_if(m_sand->begin(), m_sand->end(), [mouse_w, mouse_h](Sand& s) {
+					return s.getPosition().y <= mouse_h && s.getPosition().y + 32.f >= mouse_h
+					&& s.getPosition().x <= mouse_w && s.getPosition().x + 32.f >= mouse_w;
+				});
+				if(res != m_sand->end()) {
+					res->setPosition(-32.f, -32.f);
+					m_csand = &(*res);
+					mode = 2;
+				}
+			} else if(mode == 2) {
+				m_csand->setPosition(m_renderer->getPosition() - 320.f + pos.x * 32.f, pos.y * 32.f);
+				mode = -1;
+				m_csand = nullptr;
+			}
+			m_mouseRelease = false;
+		}
+
+		prev_mouse = curr_mouse;
+	}
+private:
+	void setType(int type) {
+		if(type >= 0) {
+			m_sprite.setTextureRect(sf::IntRect(
+				type * 32,
+				0,
+				32,
+				32
+			));
+			m_visible = true;
+		} else {
+			m_visible = false;
+		}
+		m_type = type;
+	}
+	void draw(sf::RenderTarget& rt, sf::RenderStates states) const override {
+		if(m_visible) {
+			rt.draw(m_sprite, states);
+		}
+    }
+	
+	Collider* m_collider;
+	sf::RenderWindow* m_window;
+	bool m_visible;
+	std::vector<Sand>* m_sand;
+	std::vector<Spike>* m_spikes;
+	Renderer* m_renderer;
+	sf::Sprite m_sprite;
+	int m_type;
+	bool m_mouseRelease;
+	Sand* m_csand;
+	Spike* m_cspike;
+};

+ 6 - 0
include/IEntity.hpp

@@ -0,0 +1,6 @@
+#pragma once
+
+class IEntity {
+public:
+	virtual void update(float delta) = 0;
+};

+ 16 - 0
include/Launcher.hpp

@@ -0,0 +1,16 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <SFML/Audio.hpp>
+
+using score_t = unsigned;
+
+class Launcher {
+public:
+	Launcher();
+	void launch();
+private:
+	bool m_running;
+	score_t m_highScore;
+	sf::RenderWindow m_window;
+};

+ 88 - 0
include/Log.hpp

@@ -0,0 +1,88 @@
+#pragma once
+
+#include <iostream>
+#include <initializer_list>
+
+enum LogType {
+	DEBUG, INFO, LOAD, WARNING, ERROR
+};
+
+class Log { 
+public:
+	Log() = delete;
+	template<typename T, typename... Args>
+	Log(LogType a, T t, Args... args);
+	static void setLogLevel(int lv);
+private:;
+	template <typename T>
+	void print(bool cerr, T t);
+	template<typename T, typename... Args>
+	void print(bool cerr, T t, Args... args);
+	static int level;
+};
+
+template<typename T, typename... Args>
+Log::Log(LogType a, T t, Args... args)
+{
+	switch(a) {
+	case LogType::DEBUG:
+		if(level < 3)
+			return;
+		std::cout << "[DEBUG]   ";
+		break;
+	case LogType::INFO:
+		if(level < 2)
+			return;
+		std::cout << "[INFO]    ";
+		break;
+	case LogType::LOAD:
+		if(level < 2)
+			return;
+		std::cout << "[LOAD]    ";
+		break;
+	case LogType::WARNING:
+		if(level < 3)
+			return;
+		std::cout << "[WARNING] ";
+		break;
+	case LogType::ERROR:
+		if(level < 1)
+			return;
+		std::cerr << "[ERROR]   ";
+		break;
+	default:
+		std::cout << "          ";
+		break;
+	}
+	print(a == ERROR, t, args...);
+	if(a == ERROR) {
+		std::cerr << std::endl;
+	}
+	else {
+		std::cout << std::endl;
+	}
+}
+
+template <typename T>
+void Log::print(bool cerr, T t)
+{
+	if(cerr) {
+		std::cerr << t;
+	}
+	else {
+		std::cout << t;
+	}
+}
+
+template<typename T, typename... Args>
+void Log::print(bool cerr, T t, Args... args)
+{
+	if(cerr) {
+		std::cerr << t;
+	}
+	else {
+		std::cout << t;
+	}
+
+    print(cerr, args...);
+}

+ 21 - 0
include/NumText.hpp

@@ -0,0 +1,21 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+
+using namespace sf;
+
+class NumText: public Drawable, public Transformable {
+public:
+	NumText();
+	virtual ~NumText();
+	void setTexture(Texture* texture);
+	void setNumber(unsigned number);
+	void semicolon();
+	void percent();
+private:
+	Texture* _font;
+	std::vector<Sprite> _nums;
+	bool _semicolon;
+	bool _percent;
+	virtual void draw(RenderTarget& target, RenderStates states) const override;
+};

+ 31 - 0
include/Player.hpp

@@ -0,0 +1,31 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <SFML/Audio.hpp>
+#include "AnimatedSprite.hpp"
+#include "IEntity.hpp"
+#include "Renderer.hpp"
+#include "Collider.hpp"
+
+using namespace sf;
+
+#define HOPS 10
+
+class Player: public AnimatedSprite, public IEntity {
+public:
+	Player() = delete;
+	Player(Collider* collider);
+	virtual ~Player();
+	void update(float delta) override;
+	bool isDead() const {
+		return m_dead;
+	}
+	void revive();
+	void turboBoost();
+private:
+	Collider* m_collider;
+	bool m_dead;
+	bool m_jumping;
+	float m_ms;
+	sf::Sound m_hop;
+};

+ 35 - 0
include/Renderer.hpp

@@ -0,0 +1,35 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include "IEntity.hpp"
+
+class Renderer: public IEntity {
+public:
+	Renderer() = delete;
+	Renderer(sf::RenderWindow* window);
+	
+	enum Mode {
+		BG, FG, UI
+	};
+	
+	void registerDrawable(sf::Drawable* drawable, unsigned z_index, Mode mode);
+	void deregisterDrawable(sf::Drawable* drawable);
+	void setFocus(int focus);
+	int  getFocus() const;
+	int  getPosition() const;
+	void sort();
+	void clear();
+	void update(float delta) override;
+	void render();
+
+private:
+	struct RenderObject {
+		sf::Drawable* drawable;
+		unsigned z_index;
+		Mode mode;
+	};
+	sf::RenderWindow* m_window;
+	sf::View m_view;
+	int m_focus;
+	std::vector<RenderObject> m_data;
+};

+ 317 - 0
include/SFMLOrthogonalLayer.hpp

@@ -0,0 +1,317 @@
+/*********************************************************************
+Matt Marchant 2016
+http://trederia.blogspot.com
+
+tmxlite - Zlib license.
+
+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.
+*********************************************************************/
+
+/*
+Creates an SFML drawable from an Orthogonal tmx map layer.
+This is an example of drawing with SFML - not all features,
+such as tile flipping, are implemented. For a more detailed
+implementation, including artifact prevention, see:
+https://github.com/fallahn/xygine/blob/master/xygine/src/components/ComponentTileMapLayer.cpp
+*/
+
+#ifndef SFML_ORTHO_HPP_
+#define SFML_ORTHO_HPP_
+
+#include <tmxlite/Map.hpp>
+#include <tmxlite/TileLayer.hpp>
+
+#include <SFML/Graphics.hpp>
+
+#include <memory>
+#include <vector>
+#include <array>
+#include <map>
+#include <string>
+#include <limits>
+#include <iostream>
+#include <sstream>
+#include <cmath>
+
+#include "Log.hpp"
+
+class MapLayer final : public sf::Drawable
+{
+public:
+	MapLayer() = default;
+    MapLayer(const tmx::Map& map, std::size_t idx)
+    {
+        create(map, idx);
+    }
+	void create(const tmx::Map& map, std::size_t idx)
+	{
+		const auto& layers = map.getLayers();
+        if (map.getOrientation() == tmx::Orientation::Orthogonal &&
+            idx < layers.size() && layers[idx]->getType() == tmx::Layer::Type::Tile)
+        {
+            //round the chunk size to the nearest tile
+            const auto tileSize = map.getTileSize();
+            m_chunkSize.x = std::floor(m_chunkSize.x / tileSize.x) * tileSize.x;
+            m_chunkSize.y = std::floor(m_chunkSize.y / tileSize.y) * tileSize.y;
+
+            const auto& layer = *dynamic_cast<const tmx::TileLayer*>(layers[idx].get());
+            createChunks(map, layer);
+
+            auto mapSize = map.getBounds();
+            m_globalBounds.width = mapSize.width;
+            m_globalBounds.height = mapSize.height;
+        }
+        else
+        {
+            std::cout << "Not a valid othogonal layer, nothing will be drawn." << std::endl;
+        }
+	}
+    ~MapLayer() = default;
+    MapLayer(const MapLayer&) = delete;
+    MapLayer& operator = (const MapLayer&) = delete;
+
+    const sf::FloatRect& getGlobalBounds() const { return m_globalBounds; }
+
+private:
+
+    sf::Vector2f m_chunkSize = sf::Vector2f(1024.f, 256.f);
+    sf::Vector2u m_chunkCount;
+    sf::FloatRect m_globalBounds;
+
+    using TextureResource = std::map<std::string, std::unique_ptr<sf::Texture>>;
+    TextureResource m_textureResource;
+
+    class Chunk final : public sf::Transformable, public sf::Drawable
+    {
+    public:
+        using Ptr = std::unique_ptr<Chunk>;
+        using Tile = std::array<sf::Vertex, 4u>;
+        Chunk(const tmx::TileLayer& layer, std::vector<const tmx::Tileset*> tilesets,
+            const sf::Vector2f& position, const sf::Vector2f& tileCount, std::size_t rowSize,  TextureResource& tr)
+        {
+            auto opacity = static_cast<sf::Uint8>(layer.getOpacity() /  1.f * 255.f);
+            sf::Color vertColour = sf::Color::White;
+            vertColour.a = opacity;
+
+            auto offset = layer.getOffset();
+            sf::Vector2f layerOffset(offset.x, offset.y);
+
+            const auto& tileIDs = layer.getTiles();
+
+            //go through the tiles and create the appropriate arrays
+            for (const auto ts : tilesets)
+            {
+                bool chunkArrayCreated = false;
+                auto tileSize = ts->getTileSize();
+
+                sf::Vector2u tsTileCount;
+
+                std::size_t xPos = static_cast<std::size_t>(position.x / tileSize.x);
+                std::size_t yPos = static_cast<std::size_t>(position.y / tileSize.y);
+
+                for (auto y = yPos; y < yPos + tileCount.y; ++y)
+                {
+                    for (auto x = xPos; x < xPos + tileCount.x; ++x)
+                    {
+                        auto idx = (y * rowSize + x);
+                        if (idx < tileIDs.size() && tileIDs[idx].ID >= ts->getFirstGID()
+                            && tileIDs[idx].ID < (ts->getFirstGID() + ts->getTileCount()))
+                        {
+                            //ID must belong to this set - so add a tile
+                            if (!chunkArrayCreated)
+                            {
+                                m_chunkArrays.emplace_back(std::make_unique<ChunkArray>(*tr.find(ts->getImagePath())->second));
+                                auto texSize = m_chunkArrays.back()->getTextureSize();
+                                tsTileCount.x = texSize.x / tileSize.x;
+                                tsTileCount.y = texSize.y / tileSize.y;
+                                chunkArrayCreated = true;
+                            }
+                            auto& ca = m_chunkArrays.back();
+                            sf::Vector2f tileOffset(x * tileSize.x, y * tileSize.y);
+
+                            auto idIndex = tileIDs[idx].ID - ts->getFirstGID();
+                            sf::Vector2f tileIndex(idIndex % tsTileCount.x, idIndex / tsTileCount.x);
+                            tileIndex.x *= tileSize.x;
+                            tileIndex.y *= tileSize.y;
+                            Tile tile =
+                            {
+                                sf::Vertex(tileOffset, vertColour, tileIndex),
+                                sf::Vertex(tileOffset + sf::Vector2f(tileSize.x, 0.f), vertColour, tileIndex + sf::Vector2f(tileSize.x, 0.f)),
+                                sf::Vertex(tileOffset + sf::Vector2f(tileSize.x, tileSize.y), vertColour, tileIndex + sf::Vector2f(tileSize.x, tileSize.y)),
+                                sf::Vertex(tileOffset + sf::Vector2f(0.f, tileSize.y), vertColour, tileIndex + sf::Vector2f(0.f, tileSize.y))
+                            };
+							
+							if(idIndex != 1 && idIndex != 3) {
+								ca->addTile(tile);
+							}
+                        }
+                    }
+                }
+            }
+
+            setPosition(0, 0);
+        }
+        ~Chunk() = default;
+        Chunk(const Chunk&) = delete;
+        Chunk& operator = (const Chunk&) = delete;
+
+        bool empty() const { return m_chunkArrays.empty(); }
+    private:
+        class ChunkArray final : public sf::Drawable
+        {
+        public:
+            using Ptr = std::unique_ptr<ChunkArray>;
+            explicit ChunkArray(const sf::Texture& t)
+                : m_texture(t) {}
+            ~ChunkArray() = default;
+            ChunkArray(const ChunkArray&) = delete;
+            ChunkArray& operator = (const ChunkArray&) = delete;
+
+            void addTile(const Chunk::Tile& tile)
+            {
+                for (const auto& v : tile)
+                {
+                    m_vertices.push_back(v);
+                }
+            }
+            sf::Vector2u getTextureSize() const { return m_texture.getSize(); }
+
+        private:
+            const sf::Texture& m_texture;
+            std::vector<sf::Vertex> m_vertices;
+            void draw(sf::RenderTarget& rt, sf::RenderStates states) const override
+            {
+                states.texture = &m_texture;
+                rt.draw(m_vertices.data(), m_vertices.size(), sf::Quads, states);
+            }
+        };
+
+        std::vector<ChunkArray::Ptr> m_chunkArrays;
+        void draw(sf::RenderTarget& rt, sf::RenderStates states) const override
+        {
+            states.transform *= getTransform();
+            for (const auto& a : m_chunkArrays)
+            {
+                rt.draw(*a, states);
+            }
+        }
+    };
+
+    std::vector<Chunk::Ptr> m_chunks;
+    mutable std::vector<const Chunk*> m_visibleChunks;
+    void createChunks(const tmx::Map& map, const tmx::TileLayer& layer)
+    {
+        //look up all the tile sets and load the textures
+        const auto& tileSets = map.getTilesets();
+        const auto& layerIDs = layer.getTiles();
+        std::uint32_t maxID = std::numeric_limits<std::uint32_t>::max();
+        std::vector<const tmx::Tileset*> usedTileSets;
+
+        for (auto i = tileSets.rbegin(); i != tileSets.rend(); ++i)
+        {
+            for (const auto& tile : layerIDs)
+            {
+                if (tile.ID >= i->getFirstGID() && tile.ID < maxID)
+                {
+                    usedTileSets.push_back(&(*i));
+                    break;
+                }
+            }
+            maxID = i->getFirstGID();
+        }
+
+        sf::Image fallback;
+        fallback.create(2, 2, sf::Color::Magenta);
+        for (const auto ts : usedTileSets)
+        {
+            const auto& path = ts->getImagePath();
+            std::unique_ptr<sf::Texture> newTexture = std::make_unique<sf::Texture>();
+            sf::Image img;
+            if (!img.loadFromFile(path))
+            {
+                newTexture->loadFromImage(fallback);
+            }
+            else
+            {
+                if (ts->hasTransparency())
+                {
+                    auto transparency = ts->getTransparencyColour();
+                    img.createMaskFromColor({ transparency.r, transparency.g, transparency.b, transparency.a });
+                }
+                newTexture->loadFromImage(img);
+            }
+            m_textureResource.insert(std::make_pair(path, std::move(newTexture)));
+        }
+
+        //calculate the number of chunks in the layer
+        //and create each one
+        const auto bounds = map.getBounds();
+        m_chunkCount.x = static_cast<sf::Uint32>(std::ceil(bounds.width / m_chunkSize.x));
+        m_chunkCount.y = static_cast<sf::Uint32>(std::ceil(bounds.height / m_chunkSize.y));
+
+        sf::Vector2f tileCount(m_chunkSize.x / map.getTileSize().x, m_chunkSize.y / map.getTileSize().y);
+
+        for (auto y = 0u; y < m_chunkCount.y; ++y)
+        {
+            for (auto x = 0u; x < m_chunkCount.x; ++x)
+            {
+                m_chunks.emplace_back(std::make_unique<Chunk>(layer, usedTileSets,
+                    sf::Vector2f(x * m_chunkSize.x, y * m_chunkSize.y), tileCount, map.getTileCount().x, m_textureResource));
+            }
+        }
+    }
+
+    void updateVisibility(const sf::View& view) const
+    {
+        sf::Vector2f viewCorner = view.getCenter();
+        viewCorner -= view.getSize() / 2.f;
+
+        int posX = static_cast<int>(std::floor(viewCorner.x / m_chunkSize.x));
+        int posY = static_cast<int>(std::floor(viewCorner.y / m_chunkSize.y));
+
+        std::vector<const Chunk*> visible;
+        for (auto y = posY; y < posY + 2; ++y)
+        {
+            for (auto x = posX; x < posX + 2; ++x)
+            {
+                auto idx = y * int(m_chunkCount.x) + x;
+                if (idx >= 0 && idx < (int)m_chunks.size() && !m_chunks[idx]->empty())
+                {
+                    visible.push_back(m_chunks[idx].get());
+                }
+            }
+        }
+
+        std::swap(m_visibleChunks, visible);
+    }
+
+    void draw(sf::RenderTarget& rt, sf::RenderStates states) const override
+    {
+        //calc view coverage and draw nearest chunks
+        updateVisibility(rt.getView());
+        for (const auto& c : m_visibleChunks)
+        {
+            rt.draw(*c, states);
+        }
+    }
+};
+
+#endif //SFML_ORTHO_HPP_

+ 14 - 0
include/Sand.hpp

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include "IEntity.hpp"
+
+class Collider;
+
+class Sand: public sf::Sprite, public IEntity {
+public:
+    Sand(Collider* collider);
+    void update(float delta) override;
+private:
+    Collider* m_collider;
+};

+ 24 - 0
include/Spike.hpp

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <SFML/Audio.hpp>
+#include "IEntity.hpp"
+
+class Collider;
+
+class Spike: public sf::Sprite, public IEntity {
+public:
+	Spike(Collider* collider, bool alive);
+	void update(float delta) override;
+	void move(float dy);
+	void release();
+	void registerColl();
+private:
+	Collider* m_collider;
+	bool m_hold;
+	bool m_falling;
+	sf::Clock m_clock;
+	float m_baseY;
+	sf::Sound m_boom;
+	bool m_alive;
+};

+ 61 - 0
src/AnimatedSprite.cpp

@@ -0,0 +1,61 @@
+#include "Animation.hpp"
+#include "AnimatedSprite.hpp"
+#include "Log.hpp"
+
+AnimatedSprite::AnimatedSprite():
+m_current_anim(nullptr),
+m_speed(1.f) {
+	
+}
+
+AnimatedSprite::~AnimatedSprite() {
+	
+}
+
+void AnimatedSprite::addAnimation(std::string filename, unsigned duration, std::string name, bool loop, unsigned width) {
+	m_anim[name].texture.loadFromFile(filename);
+	m_anim[name].duration = duration;
+	m_anim[name].name = name;
+	m_anim[name].loop = loop;
+	m_anim[name].count = m_anim[name].texture.getSize().x / width;
+	Log(LOAD, "Registered animation ", name, " from ", filename, '.');
+}
+
+void AnimatedSprite::setAnimation(std::string name) {
+	/// If it's already set, abort.
+	if(m_current_anim == &m_anim[name]) {
+		return;
+	}
+	m_current_anim = &m_anim[name];
+	if(!m_current_anim) {
+		Log(ERROR, "Attempted to use an inexisting animation ", name, '.');
+	}
+	setTexture(m_current_anim->texture);
+	setTextureRect(sf::IntRect(
+		0, 
+		0, 
+		m_current_anim->texture.getSize().x / m_current_anim->count, 
+		m_current_anim->texture.getSize().y / m_current_anim->count)
+	);
+	m_clock.restart();
+}
+
+void AnimatedSprite::setAnimationSpeed(float speed) {
+	m_speed = speed;
+}
+
+void AnimatedSprite::animate() {
+	if(m_current_anim 
+	&& m_current_anim->duration > 0 
+	&&(m_current_anim->loop || m_clock.getElapsedTime().asMilliseconds() < m_current_anim->duration / m_speed)) {
+		unsigned elapsed = m_clock.getElapsedTime().asMilliseconds() % int(m_current_anim->duration / m_speed);
+		unsigned frame = (elapsed * m_current_anim->count) / int(m_current_anim->duration / m_speed);
+		
+		setTextureRect(sf::IntRect(
+			m_current_anim->texture.getSize().x * frame / m_current_anim->count, 
+			0,
+			m_current_anim->texture.getSize().x / m_current_anim->count, 
+			m_current_anim->texture.getSize().y)
+		);
+	}
+}

+ 106 - 0
src/Block.cpp

@@ -0,0 +1,106 @@
+/*class Block final: public sf::Transformable, public sf::Drawable
+{
+public:
+	using Ptr = std::unique_ptr<Block>;
+	using Tile = std::array<sf::Vertex, 4u>;
+	Block(const tmx::TileLayer& layer, std::vector<const tmx::Tileset*> tilesets,
+	const sf::Vector2f& position, const sf::Vector2f& tileCount, std::size_t rowSize, TextureResource& tr) {
+		auto opacity = static_cast<sf::Uint8>(layer.getOpacity() /  1.f * 255.f);
+		sf::Color vertColour = sf::Color::White;
+		vertColour.a = opacity;
+
+		auto offset = layer.getOffset();
+		sf::Vector2f layerOffset(offset.x, offset.y);
+
+		const auto& tileIDs = layer.getTiles();
+
+		//go through the tiles and create the appropriate arrays
+		for(const auto ts: tilesets) {
+			bool BlockArrayCreated = false;
+			auto tileSize = ts->getTileSize();
+
+			sf::Vector2u tsTileCount;
+
+			std::size_t xPos = static_cast<std::size_t>(position.x / tileSize.x);
+			std::size_t yPos = static_cast<std::size_t>(position.y / tileSize.y);
+
+			for(auto y = 0; y < tileCount.y; ++y) {
+				for(auto x = 0; x < tileCount.x; ++x) {
+					auto idx = (y * rowSize + x);
+					if(idx < tileIDs.size() && tileIDs[idx].ID >= ts->getFirstGID()
+					&& tileIDs[idx].ID < (ts->getFirstGID() + ts->getTileCount())) {
+						//ID must belong to this set - so add a tile
+						if(!BlockArrayCreated)
+						{
+							m_BlockArrays.emplace_back(std::make_unique<BlockArray>(*tr.find(ts->getImagePath())->second));
+							auto texSize = m_BlockArrays.back()->getTextureSize();
+							tsTileCount.x = texSize.x / tileSize.x;
+							tsTileCount.y = texSize.y / tileSize.y;
+							BlockArrayCreated = true;
+						}
+						auto& ca = m_BlockArrays.back();
+						sf::Vector2f tileOffset(x * tileSize.x, y * tileSize.y);
+
+						auto idIndex = tileIDs[idx].ID - ts->getFirstGID();
+						sf::Vector2f tileIndex(idIndex % tsTileCount.x, idIndex / tsTileCount.x);
+						tileIndex.x *= tileSize.x;
+						tileIndex.y *= tileSize.y;
+						Tile tile =
+						{
+							sf::Vertex(tileOffset, vertColour, tileIndex),
+							sf::Vertex(tileOffset + sf::Vector2f(tileSize.x, 0.f), vertColour, tileIndex + sf::Vector2f(tileSize.x, 0.f)),
+							sf::Vertex(tileOffset + sf::Vector2f(tileSize.x, tileSize.y), vertColour, tileIndex + sf::Vector2f(tileSize.x, tileSize.y)),
+							sf::Vertex(tileOffset + sf::Vector2f(0.f, tileSize.y), vertColour, tileIndex + sf::Vector2f(0.f, tileSize.y))
+						};
+						ca->addTile(tile);
+					}
+				}
+			}
+		}
+
+		setPosition(0, 0);
+	}
+	~Block() = default;
+	Block(const Block&) = delete;
+	Block& operator=(const Block&) = delete;
+
+	bool empty() const { 
+		return m_BlockArrays.empty(); 
+	}
+private:
+	class BlockArray final: public sf::Drawable {
+	public:
+		using Ptr = std::unique_ptr<BlockArray>;
+		explicit BlockArray(const sf::Texture& t): m_texture(t) {}
+		~BlockArray() = default;
+		BlockArray(const BlockArray&) = delete;
+		BlockArray& operator= (const BlockArray&) = delete;
+
+		void addTile(const Block::Tile& tile) {
+			for(const auto& v: tile) {
+				m_vertices.push_back(v);
+			}
+		}
+		sf::Vector2u getTextureSize() const { 
+			return m_texture.getSize(); 
+		}
+
+	private:
+		const sf::Texture& m_texture;
+		std::vector<sf::Vertex> m_vertices;
+		void draw(sf::RenderTarget& rt, sf::RenderStates states) const override {
+			states.texture = &m_texture;
+			rt.draw(m_vertices.data(), m_vertices.size(), sf::Quads, states);
+		}
+	};
+
+	std::vector<BlockArray::Ptr> m_BlockArrays;
+	void draw(sf::RenderTarget& rt, sf::RenderStates states) const override
+	{
+		states.transform *= getTransform();
+		for(const auto& a: m_BlockArrays)
+		{
+			rt.draw(*a, states);
+		}
+	}
+};*/

+ 299 - 0
src/Core.cpp

@@ -0,0 +1,299 @@
+#include "Core.hpp"
+#include "Log.hpp"
+#include "Animation.hpp"
+#include "Asset.hpp"
+
+Core::Core(RenderWindow* window):
+m_state(SPLASH),
+m_score(0),
+m_running(true),  
+m_paused(false), 
+m_collider(),
+m_renderer(window),
+m_window(window),   
+m_player(&m_collider),
+m_blockLayer("data/map.json", "data/tileset.png"),
+m_grid(&m_collider, window, &m_sand, &m_spikes, &m_renderer),
+m_victory(false) {
+	m_map.load("data/map.tmx"); 
+ 
+	m_background.setTexture(Asset::texture("data/basic_background.png"));
+	m_renderer.registerDrawable(&m_background, 0, Renderer::Mode::BG);
+      
+	for(unsigned i = 0; i < LAYER_COUNT; ++i) {
+    	m_layer[i].create(m_map, i);
+		m_renderer.registerDrawable(&m_layer[i], i, Renderer::Mode::FG);
+	}  
+	m_renderer.registerDrawable(&m_blockLayer, 10, Renderer::Mode::FG);
+	m_renderer.registerDrawable(&m_player, 1000, Renderer::Mode::FG);
+	m_renderer.registerDrawable(&m_grid, 100000, Renderer::Mode::UI);
+	
+	m_splash.setTexture(Asset::texture("data/splash3.png"));
+	m_renderer.registerDrawable(&m_splash, 101000, Renderer::Mode::UI);
+	
+	m_renderer.setFocus(19); 
+	m_renderer.sort(); 
+	m_fruit.reserve(100);
+
+    reset();
+	//m_collider.populate(m_blockLayer.rebuildCollider(m_renderer.getFocus()));
+	//m_collider.setLocalX(m_renderer.getPosition() - 320.f);
+	 
+	registerEntity(&m_renderer);
+	registerEntity(&m_player);
+	registerEntity(&m_grid);
+	
+	Asset::sound("data/intro1.ogg");
+	Asset::sound("data/intro2.ogg");
+	Asset::sound("data/loop1.ogg");
+	Asset::sound("data/loop2.ogg");
+	
+	reset();
+}
+  
+Core::~Core() {
+
+} 
+
+score_t Core::getScore() {
+	return m_score;
+}
+
+bool Core::hasQuit() {
+	return m_running;
+}
+
+void Core::run() { 
+	sf::Clock clock; 
+	float delta;
+	float minimum = 1.f / 30.f;
+	Log(INFO, "Launching core.");
+	while(m_running) {
+		events();
+		delta = clock.restart().asSeconds();
+		while(delta > minimum) {
+			delta -= minimum;
+			update(minimum);
+		}
+		update(delta);
+		m_renderer.render();
+	}
+	Log(INFO, "Closing core.");
+}
+
+void Core::registerEntity(IEntity* entity) {
+	m_entities.push_back(entity);
+}
+
+void Core::deregisterEntity(IEntity* entity) {
+	m_entities.erase(std::remove_if(m_entities.begin(), m_entities.end(), [entity](IEntity* elem){ 
+		return elem == entity;
+	}), m_entities.end());
+}
+
+void Core::events() {
+    sf::Event event;
+	static sf::Clock headpat_clock;
+	static int headpats = 0; 
+    while(m_window->pollEvent(event)) {
+		if(event.type == sf::Event::MouseButtonPressed) {//Released) {
+			if(m_state == SPLASH) {
+				static bool sploosh = false;
+				if(sploosh) {
+					m_state = INGAME;
+					m_music.setBuffer(Asset::sound("data/intro1.ogg"));
+					m_music.setLoop(false);
+					m_music.setRelativeToListener(true);
+					m_music.setVolume(40.f);
+					m_music.play();
+					m_global_clock.restart();
+				} else {
+					sploosh = true;
+					m_splash.setTexture(Asset::texture("data/splash.png"));
+				}
+			} else {
+				m_grid.sendReleaseSignal();
+			}
+		}
+        if(event.type == sf::Event::Closed || (event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::Escape)) {
+            m_running = false;
+		} else if(event.type == sf::Event::GainedFocus) {
+			m_paused = false;
+		} else if(event.type == sf::Event::LostFocus) {
+			m_paused = true;
+		} else if(event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::P) {
+			m_paused = !m_paused;
+		} else if(event.type == sf::Event::MouseButtonReleased && m_player.isDead()) {
+			sf::Vector2i pos1 = sf::Mouse::getPosition(*m_window);
+			pos1.x /= 2;
+			pos1.x += m_renderer.getPosition() - 320;
+			pos1.y /= 2;
+			sf::Vector2i pos2 = (sf::Vector2i)m_player.getPosition();
+			pos2.x += 21;
+			pos2.y += 13;
+			if(std::abs(pos1.x - pos2.x) < 10 && std::abs(pos1.y - pos2.y) < 8) {
+				headpat_clock.restart();
+				headpats++;
+			}
+			if(!(headpats <3)) {
+				/*m_collider.populate(m_blockLayer.rebuildCollider(m_renderer.getFocus()));
+				m_collider.setLocalX(m_renderer.getPosition() - 320.f);
+				m_grid.reset();
+				m_player.setPosition(((m_renderer.getFocus() + 1) * 20) * 32.f - 32.f, 32.f);
+				m_player.revive();*/
+                reset();
+			}
+		}
+    }
+	if(headpat_clock.getElapsedTime().asSeconds() > 1.f) {
+		headpat_clock.restart();
+		headpats = 0;
+	}
+}
+
+void Core::update(float delta) {
+	if(!m_victory && !m_paused && m_state == INGAME) {
+		if(m_interlude_clock.getElapsedTime().asSeconds() > 1.f) {
+			m_collider.update(delta);
+			for(Sand& s: m_sand) {
+				s.update(delta);
+			}
+			for(Spike& s: m_spikes) {
+				s.update(delta);
+			}
+			for(Fruit& s: m_fruit) {
+				s.update(delta);
+			}
+			for(IEntity* ie: m_entities) {
+				ie->update(delta);
+			}
+		} else {
+			m_grid.update(delta);
+			m_renderer.update(delta);
+		}  
+		   
+		if(m_renderer.getPosition() - m_player.getPosition().x > 320.f) {
+            m_renderer.setFocus(m_renderer.getFocus() - 1);
+			if(m_renderer.getFocus() == 7) {
+				m_music.setBuffer(Asset::sound("data/intro2.ogg"));
+				m_music.setLoop(false);
+				m_music.play();
+				m_player.turboBoost();
+			}
+			reset();
+		}
+		
+		int room = m_renderer.getFocus();
+		float delay = room > 8 ? 1.f : room == 3 ? 0.33f : 0.5f;
+		if(room != 1 && room != 2 && room != 9 && room != 8 && room != 17 && room != 19 && m_fruit_clock.getElapsedTime().asSeconds() > delay) {
+			for(int i = 0; i < 18; ++i) {
+				if(m_fruit.size() < 100 && m_collider.getBlock(i, 0) == Collider::AIR && rand() % 20 == 0) {
+					m_fruit.emplace_back(&m_collider);
+					m_fruit.back().setPosition(m_renderer.getPosition() - 320.f + i * 32.f, 0.f);
+					m_fruit.back().setTexture(Asset::texture("data/owocek.png"));
+					m_renderer.registerDrawable(&(m_fruit.back()), 1000, Renderer::Mode::FG);
+				}
+			}
+			m_renderer.sort();
+			m_fruit_clock.restart();
+		}
+		
+		static float color = 255.f;
+		if(room == 1) {
+			if(color == -100.f) {
+				color = 0.f;
+				std::ostringstream oss;
+				m_victory_text.setFont(Asset::font("data/Scratch.ttf"));
+				m_victory_text.setCharacterSize(62);
+				oss << "You win! IT is safe\nTime: ";
+				oss << m_global_clock.getElapsedTime().asSeconds() << "s\n";
+				oss << "Thanks for playing";
+				m_victory_text.setString(oss.str());
+				m_victory_text.setOrigin(m_victory_text.getLocalBounds().width / 2.f, m_victory_text.getLocalBounds().height / 2.f);
+				m_victory_text.setPosition(320.f, 180.f);
+				m_splash.setTexture(Asset::texture("data/splash2.png"));
+				m_renderer.registerDrawable(&m_splash, 101000, Renderer::Mode::UI);
+				m_renderer.registerDrawable(&m_victory_text, 101001, Renderer::Mode::UI);
+				m_renderer.sort();
+			}
+			color += delta * 255.f / 3.f;
+			m_victory_text.setFillColor(sf::Color(255, 255, 255, color));
+			m_splash.setColor(sf::Color(255, 255, 255, color));
+			if(color >= 255.f) {
+				m_splash.setColor(sf::Color::White);
+				m_victory = true;
+			}
+		} else if(color != -100.f) {
+			color -= delta * 255.f;
+			if(color > 0.f) {
+				m_splash.setColor(sf::Color(255, 255, 255, color));
+			} else if(color != -100.f) {
+				m_renderer.deregisterDrawable(&m_splash);
+				color = -100.f;
+			}
+		}
+	}
+	
+	if(m_renderer.getFocus() > 9) {
+		if(m_music.getBuffer() && m_music.getStatus() == sf::Sound::Stopped) {
+			m_music.setBuffer(Asset::sound("data/loop1.ogg"));
+			m_music.setLoop(true);
+			m_music.play();
+		}
+	} else if(m_renderer.getFocus() > 7) {
+		float vol = m_music.getVolume();
+		if(vol > 0.f) {
+			if(vol - delta * 5.f > 0.f) {
+				m_music.setVolume(vol - delta * 5.f);
+			} else {
+				m_music.stop();
+			}
+		}
+	} else {
+		float vol = m_music.getVolume();
+		if(vol < 40.f) {
+			if(vol + delta * 20.f < 40.f) {
+				m_music.setVolume(vol + delta * 20.f);
+			} else {
+				m_music.setVolume(40.f);
+			}
+		}
+		if(m_music.getBuffer() && m_music.getStatus() == sf::Sound::Stopped) {
+			m_music.setBuffer(Asset::sound("data/loop2.ogg"));
+			m_music.setLoop(true);
+			m_music.play();
+		}
+	}
+}
+
+void Core::reset() {
+    m_collider.populate(m_blockLayer.rebuildCollider(m_renderer.getFocus()));
+    for(Spike& s: m_spikes) {
+        m_renderer.deregisterDrawable(&s); 
+    }  
+    for(Sand& s: m_sand) { 
+        m_renderer.deregisterDrawable(&s);
+    }
+	for(Fruit& s: m_fruit) { 
+        m_renderer.deregisterDrawable(&s);
+    }
+    m_spikes.clear();
+    m_sand.clear();
+	m_fruit.clear();
+    m_spikes = m_blockLayer.reloadSpikes(m_renderer.getFocus(), &m_collider);
+    m_sand = m_blockLayer.reloadSand(m_renderer.getFocus(), &m_collider);
+	m_collider.setLocalX(m_renderer.getPosition() - 320.f);
+	for(Sand& s: m_sand) {
+        m_renderer.registerDrawable(&s, 500, Renderer::Mode::FG);
+		s.update(0.f);
+    }
+    for(Spike& s: m_spikes) {
+        m_renderer.registerDrawable(&s, 500, Renderer::Mode::FG);
+		s.registerColl();
+    }
+    m_renderer.sort();
+    m_grid.reset();
+    m_player.setPosition(((m_renderer.getFocus() + 1) * 20) * 32.f - 32.f, 0.f);
+    m_player.revive();
+	m_interlude_clock.restart();
+}

+ 57 - 0
src/Fruit.cpp

@@ -0,0 +1,57 @@
+#include <cmath>
+#include "Log.hpp"
+#include "Fruit.hpp"
+#include "Collider.hpp"
+#include "Asset.hpp"
+
+Fruit::Fruit(Collider* collider): 
+m_collider(collider),
+m_falling(true) {
+	m_boom.setVolume(50.f);
+	std::string str = "data/plop";
+	str += char('1' + rand() % 3);
+	str += ".ogg";
+	m_boom.setBuffer(Asset::sound(str));
+	setOrigin(0.f, 32.f);
+	m_baseY = -1.f;
+}
+
+void Fruit::registerColl() {
+	if(m_falling) {
+		m_collider->setBlock((getPosition().x + 16.f - m_collider->getLocalX()) / 32.f, (getPosition().y) / 32.f, Collider::FALLINGSAND);
+		m_collider->setBlock((getPosition().x + 16.f - m_collider->getLocalX()) / 32.f, (getPosition().y - 31.f) / 32.f, Collider::FALLINGSAND);
+	}
+}
+
+void Fruit::update(float delta) {
+    bool should_fall = false;
+	
+	if(m_baseY == -1.f) {
+		m_baseY = getPosition().y;
+	}
+	
+    sf::Vector2i bottom;
+    bottom.x = (getPosition().x + 16.f - m_collider->getLocalX()) / 32.f;
+    bottom.y = (getPosition().y) / 32.f;
+    should_fall = !m_collider->isSolid(bottom.x, bottom.y);
+	
+	registerColl();
+	
+	if(m_falling) {
+		if(should_fall) {
+			setPosition(getPosition().x, m_baseY + 200 * 0.25f * std::pow(m_clock.getElapsedTime().asSeconds(), 2));
+		} else {
+			m_falling = false;
+			setPosition(getPosition().x, bottom.y * 32.f);
+			m_clock.restart();
+			m_boom.setPitch(0.8 + (rand() % 21) / 50.f); //0.8-1.2
+			m_boom.play();
+		}
+	} else {
+		if(m_clock.getElapsedTime().asSeconds() < 0.25f) {
+			setScale(1.f, 1.f - 4.f * m_clock.getElapsedTime().asSeconds());
+		} else {
+			setColor(sf::Color::Transparent);
+		}
+	}
+}

+ 29 - 0
src/Launcher.cpp

@@ -0,0 +1,29 @@
+#include "Launcher.hpp"
+#include "Core.hpp"
+#include "Asset.hpp"
+#include "Log.hpp"
+
+std::unordered_map<std::string, sf::Texture> Asset::s_tex;
+std::unordered_map<std::string, sf::SoundBuffer> Asset::s_snd;
+std::unordered_map<std::string, sf::Font> Asset::s_fnt;
+
+Launcher::Launcher():
+m_running(true),
+m_highScore(0) {
+	m_window.create(sf::VideoMode(640, 360), "That Time I Crossed A Barren Desert - LD 46 - Keep it Alive", sf::Style::Titlebar | sf::Style::Close, ContextSettings(0, 0, 0, 2, 0));
+    //m_window.setPosition(sf::Vector2i(0, 0));
+	m_window.setFramerateLimit(240);
+	m_window.setSize({1280, 720});
+	m_window.setKeyRepeatEnabled(false);
+}
+
+void Launcher::launch() {
+	Log(INFO, "Launching.");
+	while(m_running) {
+		Core core(&m_window);
+		core.run();
+		m_highScore = core.getScore();
+		m_running = core.hasQuit();
+	}
+	Log(INFO, "Fin.");
+}

+ 7 - 0
src/Log.cpp

@@ -0,0 +1,7 @@
+#include "Log.hpp"
+
+int Log::level = 3;
+
+void Log::setLogLevel(int lv) {
+	level = lv;
+}

+ 13 - 0
src/Main.cpp

@@ -0,0 +1,13 @@
+#include <SFML/Graphics.hpp>
+
+#include "Core.hpp"
+#include "Log.hpp"
+
+using namespace sf;
+
+int main() {
+	srand(time(nullptr));
+	Launcher l;
+	l.launch();
+	return 0;
+}

+ 66 - 0
src/NumText.cpp

@@ -0,0 +1,66 @@
+#include "NumText.hpp"
+#include "Log.hpp"
+#include <cmath>
+
+NumText::NumText() {
+	_semicolon = false;
+	_percent = false;
+}
+
+NumText::~NumText() {
+
+}
+
+void NumText::setTexture(Texture* texture) {
+	_font = texture;
+}
+void NumText::setNumber(unsigned number) {
+	_nums.clear();
+	if(!number) {
+		_nums.resize(1);
+		_nums.back().setTexture(*_font);
+		_nums.back().setPosition(0 * 13, 0);
+		_nums.back().setTextureRect(IntRect(0, 0 * 14, 13, 14));
+	}
+	else {
+		int count = std::log10(number) + 1 + _percent + _semicolon;
+		_nums.resize(count);
+		for(int i = 0; i < count; ++i) {
+			_nums[i].setTexture(*_font);
+			_nums[i].setPosition((count - i - 1) * 8, 0);
+			unsigned offset = 0;
+			if(_semicolon && i == 2) {
+				offset = 10;
+			}
+			else if(_percent && i == 0) {
+				offset = 11;
+			}
+			else {
+				offset = number % 10;
+				number /= 10;
+			}
+			if(offset < 11) {
+				_nums[i].setTextureRect(IntRect(0, offset * 14, 8, 7));
+			}
+			else {
+				_nums[i].setTextureRect(IntRect(0, offset * 14, 13, 10));
+				_nums[i].move(0, -1.5f);
+			}
+		}
+	}
+}
+
+void NumText::semicolon() {
+	_semicolon = true;
+}
+void NumText::percent() {
+	_percent = true;
+}
+
+void NumText::draw(RenderTarget& target, RenderStates states) const {
+	states.transform *= getTransform();
+
+	for(unsigned i = 0; i < _nums.size(); ++i) {
+		target.draw(_nums[i], states);
+	}
+}

+ 123 - 0
src/Player.cpp

@@ -0,0 +1,123 @@
+#include <cmath>
+#include "Player.hpp"
+#include "Log.hpp"
+#include "Animation.hpp"
+#include "Asset.hpp"
+#include "Alermath.hpp"
+
+Player::Player(Collider* collider):
+m_collider(collider),
+m_dead(false),
+m_jumping(false),
+m_ms(80.f) {
+	setPosition(199 * 32.f, 6 * 32.f);
+	setOrigin(0.f, 0.f);
+	addAnimation("data/nem-run.png", 600, "run", true, 32);
+	addAnimation("data/nem-jump.png", 3000, "jump", true, 32);
+	addAnimation("data/nem-plop.png", 3000, "plop", false, 32);
+	setAnimation("run");
+	setAnimationSpeed(1.f);
+	m_hop.setVolume(20.f);
+	animate();
+}
+
+Player::~Player() {
+
+}
+
+void Player::revive() {
+	m_dead = false;
+	m_jumping = false;
+	sf::Vector2i below;
+	below.x = 19;
+	below.y = 1;
+
+	while(!(m_collider->isSolid(below.x, below.y) && !m_collider->isSolid(below.x, below.y - 1))) {
+		move(0.f, 32.f);
+		below.y++;
+        if(below.y > 12) {
+            setPosition(getPosition().x, 0.f);
+            break;
+        }
+	}
+	
+	setAnimation("run");
+	animate();
+}
+
+void Player::update(float delta) {
+	animate();
+	
+	if(m_dead == true) {
+		return;
+	}
+
+	sf::Vector2i belowL, belowR, ahead, in;
+	static float jump = 0.f;
+	static Vector2f jump_pos;
+	static sf::Clock clock;
+	belowL.x = (getPosition().x + 2.f - m_collider->getLocalX()) / 32.f;
+	belowL.y = (getPosition().y + 16.f) / 32.f + 1;
+	belowR.x = (getPosition().x + 30.f - m_collider->getLocalX()) / 32.f;
+	belowR.y = (getPosition().y + 16.f) / 32.f + 1;
+	ahead.x = (getPosition().x - 1.f - m_collider->getLocalX()) / 32.f;
+	ahead.y = ((getPosition().y + 31.f) / 32.f);
+	in.x = (getPosition().x + 16.f - m_collider->getLocalX()) / 32.f;
+	in.y = (getPosition().y + 16.f) / 32.f;
+	if(jump == 0.f && !m_jumping) {  
+		if(!m_collider->isSolid(belowL.x, belowL.y) && !m_collider->isSolid(belowR.x, belowR.y)) {
+			jump = 32.f;
+		} else if(m_collider->isSolid(ahead.x, ahead.y)) {
+			m_jumping = true;
+			jump_pos = getPosition();
+			clock.restart();
+			std::string str = "data/hop";
+			str.push_back(rand() % HOPS + '0');
+			str += ".ogg";
+			m_hop.setBuffer(Asset::sound(str));
+			m_hop.play();
+		} 
+	}
+	
+	if(m_jumping) {
+		float dt = clock.getElapsedTime().asSeconds() * 2.f;
+		float dx, dy;
+		if(dt < 0.5f) {
+			dx = 32.f * std::pow(dt, 2);
+			dy = 48.f - 192.f * std::pow(dt - 0.5f, 2);
+		} else {
+			dx = 32.f * std::pow(dt, 2);
+			dy = 48.f - 64.f * std::pow(dt - 0.5f, 2);
+		}
+		setPosition(jump_pos - Vector2f(dx, dy));
+		if(dt >= 1.f) {
+			setPosition(jump_pos.x - 32.f, jump_pos.y - 32.f);
+			m_jumping = false;
+		}
+	} else {
+		if(jump != 0.f) {
+			setAnimation("jump");
+			jump -= math::sgn(jump) * delta * 200.f;
+			move(0.f, math::sgn(jump) * delta * 200.f);
+			if(std::fabs(jump) < 3.f) {
+				move(0.f, jump);
+				jump = 0.f;
+			}
+		} else {
+			setAnimation("run");
+			move(-delta * m_ms, 0.f);
+		}
+	}
+	
+	if(!m_collider->isSafe(ahead.x, ahead.y) || !m_collider->isSafe(in.x, in.y) || m_collider->isSolid(in.x, in.y)) {
+		setAnimation("plop");
+		m_dead = true;
+		m_hop.setBuffer(Asset::sound("data/ouch.ogg"));
+		m_hop.play();
+	}
+}
+
+void Player::turboBoost() {
+	setAnimationSpeed(2.f);
+	m_ms *= 2.f;
+}

+ 80 - 0
src/Renderer.cpp

@@ -0,0 +1,80 @@
+#include <cmath>
+#include "Log.hpp"
+#include "Renderer.hpp"
+#include "Alermath.hpp"
+
+#define WIDTH 640
+
+Renderer::Renderer(sf::RenderWindow* window):
+m_window(window),
+m_focus(0) {
+	m_view.setSize(WIDTH, 360);
+	m_view.setCenter(WIDTH * 19.5f, 180);
+}
+
+void Renderer::registerDrawable(sf::Drawable* drawable, unsigned z_index, Mode mode) {
+	RenderObject ro;
+	ro.drawable = drawable;
+	ro.z_index = z_index;
+	ro.mode = mode;
+	m_data.push_back(ro);
+}
+
+void Renderer::deregisterDrawable(sf::Drawable* drawable) {
+	m_data.erase(std::remove_if(m_data.begin(), m_data.end(), [drawable](auto pair){ 
+		return pair.drawable == drawable;
+	}), m_data.end());
+}
+
+void Renderer::setFocus(int focus) {
+	m_focus = focus;
+}
+
+int Renderer::getFocus() const {
+	return m_focus;
+}
+
+int Renderer::getPosition() const {
+	return m_focus * WIDTH + WIDTH / 2;
+}
+
+void Renderer::sort() {
+	std::sort(m_data.begin(), m_data.end(), [](auto o1, auto o2)->bool {
+		return o1.mode == o2.mode ? o1.z_index < o2.z_index : o1.mode < o2.mode;
+	});
+}
+
+void Renderer::clear() {
+	m_data.clear();
+}
+
+void Renderer::update(float delta) {
+	float x1 = m_view.getCenter().x;
+	float x2 = getPosition();
+
+	if(x1 != x2) {
+		if(std::fabs(x2 - x1) > 2.f) {
+			m_view.move(delta * WIDTH * 3.f * math::sgn(x2 - x1), 0.f);
+		} else {
+			m_view.move(x2 - x1, 0.f);
+		}
+	}
+}
+
+void Renderer::render() {
+	bool default_view = true;
+	m_window->setView(m_window->getDefaultView());
+
+	for(RenderObject& ro: m_data) {
+		if(ro.mode == FG && default_view) {
+			default_view = false;
+			m_window->setView(m_view);
+		}
+		if(ro.mode == UI && !default_view) {
+			default_view = true;
+			m_window->setView(m_window->getDefaultView());
+		}
+		m_window->draw(*ro.drawable);
+	}
+	m_window->display();
+}

+ 13 - 0
src/Sand.cpp

@@ -0,0 +1,13 @@
+#include <cmath>
+#include "Log.hpp"
+#include "Sand.hpp"
+#include "Collider.hpp"
+
+Sand::Sand(Collider* collider): m_collider(collider) {
+ 
+}
+
+void Sand::update(float delta) {
+    m_collider->setBlock((getPosition().x + 16.f - m_collider->getLocalX()) / 32.f, (getPosition().y) / 32.f, Collider::SAND);
+	m_collider->setBlock((getPosition().x + 16.f - m_collider->getLocalX()) / 32.f, (getPosition().y + 31.f) / 32.f, Collider::SAND);
+}

+ 58 - 0
src/Spike.cpp

@@ -0,0 +1,58 @@
+#include <cmath>
+#include "Log.hpp"
+#include "Spike.hpp"
+#include "Collider.hpp"
+#include "Asset.hpp"
+
+Spike::Spike(Collider* collider, bool alive): 
+m_collider(collider),
+m_falling(false),
+m_alive(alive) {
+	m_boom.setVolume(60.f);
+	m_boom.setBuffer(Asset::sound("data/boom.ogg"));
+}
+
+void Spike::registerColl() {
+	m_collider->setBlock((getPosition().x + 16.f - m_collider->getLocalX()) / 32.f, (getPosition().y) / 32.f, Collider::SPIKE);
+	m_collider->setBlock((getPosition().x + 16.f - m_collider->getLocalX()) / 32.f, (getPosition().y + 31.f) / 32.f, Collider::SPIKE);
+}
+
+void Spike::update(float delta) {
+	if(!m_alive) {
+		registerColl();
+		return;
+	}
+    bool should_fall = false;
+	
+    sf::Vector2i bottom;
+    bottom.x = (getPosition().x + 16.f - m_collider->getLocalX()) / 32.f;
+    bottom.y = (getPosition().y + 32.f) / 32.f;
+    should_fall = !m_collider->isSolid(bottom.x, bottom.y);
+	
+	registerColl();
+	
+    if(!m_hold) {
+		if(should_fall) {
+			if(!m_falling) {
+				m_clock.restart();
+				m_falling = true;
+				m_baseY = getPosition().y;
+			}
+			setPosition(getPosition().x, m_baseY + 200 * std::pow(m_clock.getElapsedTime().asSeconds(), 2));
+		} else if(m_falling) {
+			m_falling = false;
+			setPosition(getPosition().x, (bottom.y - 1) * 32.f);
+			m_boom.setPitch(0.8 + (rand() % 21) / 50.f); //0.8-1.2
+			m_boom.play();
+		}
+	}
+}
+
+void Spike::move(float dy) {
+	sf::Sprite::move(0.f, dy);
+	m_hold = true;
+}
+
+void Spike::release() {
+	m_hold = false;
+}