diff --git a/build/te4core.lua b/build/te4core.lua
index 2ea0f26fbf5133dbbf06403729fd7af3593d881f..3a8348ec0d3ed46778c1b5e4910af95e6eb34c0e 100644
--- a/build/te4core.lua
+++ b/build/te4core.lua
@@ -37,7 +37,7 @@ project "TEngine"
 	if _OPTIONS.steam then
 		files { "../steamworks/luasteam.c", }
 	end
-	links { "physfs", "lua".._OPTIONS.lua, "fov", "luasocket", "luaprofiler", "lpeg", "tcodimport", "lxp", "expatstatic", "luamd5", "luazlib", "luabitop", "te4-bzip" }
+	links { "physfs", "lua".._OPTIONS.lua, "fov", "luasocket", "luaprofiler", "lpeg", "tcodimport", "lxp", "expatstatic", "luamd5", "luazlib", "luabitop", "te4-bzip", "te4-wfc" }
 	if _OPTIONS.discord then defines { "DISCORD_TE4" } end
 	defines { "_DEFAULT_VIDEOMODE_FLAGS_='SDL_HWSURFACE|SDL_DOUBLEBUF'" }
 	defines { [[TENGINE_HOME_PATH='".t-engine"']], "TE4CORE_VERSION="..TE4CORE_VERSION }
@@ -50,6 +50,7 @@ project "TEngine"
 	if _OPTIONS.relpath == "64" then defines{"TE4_RELPATH64"} end
 
 	links { "m" }
+	cppconfig()
 
 	if _OPTIONS.no_rwops_size then defines{"NO_RWOPS_SIZE"} end
 
@@ -171,13 +172,14 @@ project "physfs"
 		files { "../src/physfs/platform/windows.c",  }
 	configuration "macosx"
 		files { "../src/physfs/platform/macosx.c", "../src/physfs/platform/posix.c",  }
-                includedirs { "/Library/Frameworks/SDL.framework/Headers" }
+                includedirs { "/Library/Frameworks/SDL2.framework/Headers" }
 
 if _OPTIONS.lua == "default" then
 	project "luadefault"
 		kind "StaticLib"
 		language "C"
 		targetname "lua"
+		buildoptions{ "-O2" }
 
 		files { "../src/lua/*.c", }
 elseif _OPTIONS.lua == "jit2" then
@@ -359,6 +361,7 @@ elseif _OPTIONS.lua == "jit2" then
 		language "C"
 		targetname "lua"
 		links { "buildvm" }
+		buildoptions { "-O2", "-fomit-frame-pointer" }
 
 		files { "../src/luajit2/src/*.c", "../src/luajit2/src/*.s", "../src/luajit2/src/lj_vm.s", "../src/luajit2/src/lj_bcdef.h", "../src/luajit2/src/lj_ffdef.h", "../src/luajit2/src/lj_ffdef.h", "../src/luajit2/src/lj_libdef.h", "../src/luajit2/src/lj_recdef.h", "../src/luajit2/src/lj_folddef.h" }
 		excludes { "../src/luajit2/src/buildvm*.c", "../src/luajit2/src/luajit.c", "../src/luajit2/src/ljamalg.c" }
@@ -423,6 +426,7 @@ project "luasocket"
 	kind "StaticLib"
 	language "C"
 	targetname "luasocket"
+	buildoptions { "-O2" }
 
 	configuration "not windows"
 		files {
@@ -461,6 +465,7 @@ project "fov"
 	kind "StaticLib"
 	language "C"
 	targetname "fov"
+	buildoptions { "-O2" }
 
 	files { "../src/fov/*.c", }
 
@@ -468,6 +473,7 @@ project "lpeg"
 	kind "StaticLib"
 	language "C"
 	targetname "lpeg"
+	buildoptions { "-O2" }
 
 	files { "../src/lpeg/*.c", }
 
@@ -475,6 +481,7 @@ project "luaprofiler"
 	kind "StaticLib"
 	language "C"
 	targetname "luaprofiler"
+	buildoptions { "-O2" }
 
 	files { "../src/luaprofiler/*.c", }
 
@@ -482,6 +489,7 @@ project "tcodimport"
 	kind "StaticLib"
 	language "C"
 	targetname "tcodimport"
+	buildoptions { "-O2" }
 
 	files { "../src/libtcod_import/*.c", }
 
@@ -490,6 +498,7 @@ project "expatstatic"
 	language "C"
 	targetname "expatstatic"
 	defines{ "HAVE_MEMMOVE" }
+	buildoptions { "-O2" }
 
 	files { "../src/expat/*.c", }
 
@@ -497,6 +506,7 @@ project "lxp"
 	kind "StaticLib"
 	language "C"
 	targetname "lxp"
+	buildoptions { "-O2" }
 
 	files { "../src/lxp/*.c", }
 
@@ -504,6 +514,7 @@ project "luamd5"
 	kind "StaticLib"
 	language "C"
 	targetname "luamd5"
+	buildoptions { "-O2" }
 
 	files { "../src/luamd5/*.c", }
 
@@ -511,6 +522,7 @@ project "luazlib"
 	kind "StaticLib"
 	language "C"
 	targetname "luazlib"
+	buildoptions { "-O2" }
 
 	files { "../src/lzlib/*.c", }
 
@@ -518,6 +530,7 @@ project "luabitop"
 	kind "StaticLib"
 	language "C"
 	targetname "luabitop"
+	buildoptions { "-O2" }
 
 	files { "../src/luabitop/*.c", }
 
@@ -525,9 +538,20 @@ project "te4-bzip"
 	kind "StaticLib"
 	language "C"
 	targetname "te4-bzip"
+	buildoptions { "-O2" }
 
 	files { "../src/bzip2/*.c", }
 
+project "te4-wfc"
+	kind "StaticLib"
+	language "C++"
+	targetname "te4-wfc"
+	buildoptions { "-O3" }
+	buildoptions { "-std=c++11" }
+	cppconfig()
+
+	files { "../src/wfc/*.cpp", }
+
 if _OPTIONS['web-awesomium'] and not _OPTIONS.wincross then
 project "te4-web"
 	kind "SharedLib"
@@ -547,6 +571,9 @@ project "te4-web"
 	language "C++"
 	targetname "te4-web"
 
+	buildoptions { "-O3", "-std=c++11" }
+	cppconfig("web")
+
 	if _OPTIONS.relpath=="32" then linkoptions{"-Wl,-rpath -Wl,\\\$\$ORIGIN "} end
 	if _OPTIONS.relpath=="64" then linkoptions{"-Wl,-rpath -Wl,\\\$\$ORIGIN "} end
 
@@ -554,8 +581,8 @@ project "te4-web"
 
 	configuration "macosx"
 		defines { 'SELFEXE_MACOSX' }
-		libdirs {"/users/tomedev/downloads/cef_binary_3.1547.1597_macosx64/xcodebuild/Release/", "/users/tomedev/downloads/cef_binary_3.1547.1597_macosx64/Release/"}
-		includedirs {"/users/tomedev/downloads/cef_binary_3.1547.1597_macosx64/include/", "/users/tomedev/downloads/cef_binary_3.1547.1597_macosx64/"}
+		libdirs {"/Users/darkmac/libs/CEF/cef_binary_3.1547.1597_macosx64/xcodebuild/Release/", "/Users/darkmac/libs/CEF/cef_binary_3.1547.1597_macosx64/Release/"}
+		includedirs {"/Users/darkmac/libs/CEF/cef_binary_3.1547.1597_macosx64/include/", "/Users/darkmac/libs/CEF/cef_binary_3.1547.1597_macosx64/"}
 		links { "cef", "cef_dll_wrapper" }
 
 	configuration "windows"
@@ -574,6 +601,10 @@ project "cef3spawn"
 	language "C++"
 	targetname "cef3spawn"
 
+	buildoptions { "-O3" }
+	buildoptions { "-std=c++11" }
+	cppconfig("web")
+
 	includedirs {"../src/web-cef3/", }
 	files {
 		"../src/web-cef3/spawn.cpp",
@@ -581,8 +612,8 @@ project "cef3spawn"
 
 	configuration "macosx"
 		defines { 'SELFEXE_MACOSX' }
-		libdirs {"/users/tomedev/downloads/cef_binary_3.1547.1597_macosx64/xcodebuild/Release/", "/users/tomedev/downloads/cef_binary_3.1547.1597_macosx64/Release/"}
-		includedirs {"/users/tomedev/downloads/cef_binary_3.1547.1597_macosx64/include/", "/users/tomedev/downloads/cef_binary_3.1547.1597_macosx64/"}
+		libdirs {"/Users/darkmac/libs/CEF/cef_binary_3.1547.1597_macosx64/xcodebuild/Release/", "/Users/darkmac/libs/CEF/cef_binary_3.1547.1597_macosx64/Release/"}
+		includedirs {"/Users/darkmac/libs/CEF/cef_binary_3.1547.1597_macosx64/include/", "/Users/darkmac/libs/CEF/cef_binary_3.1547.1597_macosx64/"}
 		links { "cef", "cef_dll_wrapper" }
 
 	configuration "linux"
diff --git a/game/engines/default/engine/Zone.lua b/game/engines/default/engine/Zone.lua
index 01c8981f62be8bfd6a60977d01be41dd621c16ac..5cd522de62dbd1afa661b159728040c54dfba5b0 100644
--- a/game/engines/default/engine/Zone.lua
+++ b/game/engines/default/engine/Zone.lua
@@ -1176,5 +1176,7 @@ function _M:runPostGeneration(level)
 		end
 	end
 
-	level.map.room_map = nil -- delete the room map
+	if not config.settings.cheat then
+		level.map.room_map = nil -- delete the room map, but keep it for debugging
+	end
 end
diff --git a/game/engines/default/engine/BSP.lua b/game/engines/default/engine/algorithms/BSP.lua
similarity index 98%
rename from game/engines/default/engine/BSP.lua
rename to game/engines/default/engine/algorithms/BSP.lua
index 20fda92040918e5138333e002999f877fee35438..21091e93c47e0059e613e3a7044cbe2d63766c3f 100644
--- a/game/engines/default/engine/BSP.lua
+++ b/game/engines/default/engine/algorithms/BSP.lua
@@ -21,7 +21,7 @@ require "engine.class"
 
 --- Abstract binary space partitioning  
 -- Can be used to generator levels and so on
--- @classmod engine.BSP
+-- @classmod engine.algorithms.BSP
 module(..., package.seeall, class.make)
 
 --- Init
diff --git a/game/engines/default/engine/algorithms/MST.lua b/game/engines/default/engine/algorithms/MST.lua
new file mode 100644
index 0000000000000000000000000000000000000000..8eab83d453131e7071678315cc42fa75ebf4b127
--- /dev/null
+++ b/game/engines/default/engine/algorithms/MST.lua
@@ -0,0 +1,85 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+local unionfind = require "algorithms.unionfind"
+
+--- Find a MST (Minimum Spanning Tree), aka the smallest fully connected graph, from a graph presented as a list of weighted edges
+-- @classmod engine.algorithms.MST
+module(..., package.seeall, class.make)
+
+-----------------------------------------------------------
+-- Small Edge class
+-----------------------------------------------------------
+local Edge_t
+Edge_t = { __index = {
+	hash = function(e)
+		local s1, s2 = tostring(e.from), tostring(e.to)
+		if s2 < s1 then s2, s1 = s1, s2 end
+		return s1..":"..s2
+	end,
+} }
+
+function Edge(r1, r2, cost, data)
+	return setmetatable({from=r1, to=r2, cost=cost, data=data}, Edge_t)
+end
+-----------------------------------------------------------
+
+function _M:init()
+	self.mst = {}
+	self.edges = {}
+	self.sorted_edges = {}
+end
+
+function _M:edge(r1, r2, cost, data)
+	local e
+	if getmetatable(r1) == Edge_t then e = r1
+	else e = Edge(r1, r2, cost, data) end
+	self.edges[e:hash()] = e
+end
+
+function _M:run()
+	self.sorted_edges = table.values(self.edges)
+	table.sort(self.sorted_edges, "cost")
+
+	-- Find the MST graph
+	local uf = unionfind.create()
+	for _, edge in ipairs(self.sorted_edges) do
+		-- Skip this edge to avoid creating a cycle in MST
+		if not uf:connected(edge.from, edge.to) then
+			-- Include this edge
+			uf:union(edge.from, edge.to)
+
+			-- Add it to the final graph
+			self.mst[edge:hash()] = edge
+
+			-- Remove it from the remaining edges
+			self.edges[edge:hash()] = nil
+		end
+	end
+	return self.mst
+end
+
+function _M:fattenRandom(nb_adds)
+	while nb_adds > 0 and next(self.edges) do
+		local edge = rng.table(table.values(self.edges))
+		self.edges[edge:hash()] = nil
+		self.mst[edge:hash()] = edge
+		nb_adds = nb_adds - 1
+	end
+end
diff --git a/game/engines/default/engine/generator/actor/OnSpots.lua b/game/engines/default/engine/generator/actor/OnSpots.lua
index 45146e327ac78b5469d6041575d0c2b1ceee9ba5..78552ca2a6e6c6fc984ad4a8614f0bc7fba71afb 100644
--- a/game/engines/default/engine/generator/actor/OnSpots.lua
+++ b/game/engines/default/engine/generator/actor/OnSpots.lua
@@ -30,10 +30,27 @@ function _M:init(zone, map, level, spots)
 	self.on_spot_chance = data.on_spot_chance or 70
 	self.spot_radius = data.spot_radius or 5
 	self.spot_filters = data.spot_filters or {}
+	self.nb_spots = data.nb_spots or 2
+
+	if self.nb_spots == true then
+		self.nb_spots = 0
+		for _, spot in ipairs(level.spots) do
+			if #self.spot_filters == 0 then
+				self.nb_spots = self.nb_spots + 1
+			else
+				for _, filter in ipairs(self.spot_filters) do
+					if zone:checkFilter(spot, filter) then
+						self.nb_spots = self.nb_spots + 1
+						break
+					end
+				end
+			end
+		end
+	end
 
 	-- Pick a number of spots
 	self.genspots = {}
-	for i = 1, self.nb_spots or 2 do
+	for i = 1, self.nb_spots do
 		local spot = self.level:pickSpot(rng.table(self.spot_filters))
 		if spot then self.genspots[#self.genspots+1] = spot end
 	end
@@ -41,13 +58,15 @@ end
 
 function _M:getSpawnSpot(m)
 	-- Spawn near a spot
-	if rng.percent(self.on_spot_chance) then
+	local done = true
+	
+	while rng.percent(self.on_spot_chance) do -- NOT A WHILE, this is used as a breakable if
 		-- Cycle through spots, looking for one with empty spaces
 		local tries = 0
 		local spot = rng.table(self.genspots)
 		if not spot then
 			print("No spots for spawning")
-			return
+			break
 		end
 		local _, _, gs = util.findFreeGrid(spot.x, spot.y, self.spot_radius, "block_move", {[Map.ACTOR]=true})
 		while not gs and tries < 10 do
@@ -57,7 +76,7 @@ function _M:getSpawnSpot(m)
 		end
 		if not gs then
 			print("No more free space for spawning")
-			return
+			break
 		end
 		-- Cycle through available spaces
 		tries = 0
@@ -75,17 +94,18 @@ function _M:getSpawnSpot(m)
 			return x, y
 		end
 
-	-- Spawn randomly
-	else
-		local x, y = rng.range(self.area.x1, self.area.x2), rng.range(self.area.y1, self.area.y2)
-		local tries = 0
-		while (not m:canMove(x, y) or (self.map.room_map[x][y] and self.map.room_map[x][y].special)) and tries < 100 do
-			x, y = rng.range(self.area.x1, self.area.x2), rng.range(self.area.y1, self.area.y2)
-			tries = tries + 1
-		end
-		if tries < 100 then
-			return x, y
-		end
+		break
+	end
+
+	-- Otherwise, spawn randomly
+	local x, y = rng.range(self.area.x1, self.area.x2), rng.range(self.area.y1, self.area.y2)
+	local tries = 0
+	while (not m:canMove(x, y) or (self.map.room_map[x][y] and self.map.room_map[x][y].special)) and tries < 100 do
+		x, y = rng.range(self.area.x1, self.area.x2), rng.range(self.area.y1, self.area.y2)
+		tries = tries + 1
+	end
+	if tries < 100 then
+		return x, y
 	end
 end
 
diff --git a/game/engines/default/engine/generator/map/Building.lua b/game/engines/default/engine/generator/map/Building.lua
index aedbf51eda3d84b8e22e31dec491dd6c890e64bc..19f47f8ee080e0b330d7e02ca6be08fd07f7fe1f 100644
--- a/game/engines/default/engine/generator/map/Building.lua
+++ b/game/engines/default/engine/generator/map/Building.lua
@@ -19,7 +19,7 @@
 
 require "engine.class"
 local Map = require "engine.Map"
-local BSP = require "engine.BSP"
+local BSP = require "engine.algorithms.BSP"
 require "engine.Generator"
 local RoomsLoader = require "engine.generator.map.RoomsLoader"
 
diff --git a/game/engines/default/engine/generator/map/MapScript.lua b/game/engines/default/engine/generator/map/MapScript.lua
new file mode 100644
index 0000000000000000000000000000000000000000..b8e66993dd015317c74c847b58507cc739f6dbc6
--- /dev/null
+++ b/game/engines/default/engine/generator/map/MapScript.lua
@@ -0,0 +1,262 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Map = require "engine.Map"
+local BaseGenerator = require "engine.Generator"
+local RoomsLoader = require "engine.generator.map.RoomsLoader"
+require "engine.Generator"
+
+--- @classmod engine.generator.map.MapScript
+module(..., package.seeall, class.inherit(engine.Generator, RoomsLoader))
+
+function _M:init(zone, map, level, data)
+	engine.Generator.init(self, zone, map, level)
+	self.data = data
+	self.grid_list = zone.grid_list
+	self.spots = {}
+	self.mapsize = {self.map.w, self.map.h, w=self.map.w, h=self.map.h}
+	self.post_gen = {}
+	self.maps_positions = {}
+	self.maps_registers = {}
+
+	RoomsLoader.init(self, data)
+end
+
+
+--- Resolve a filename, from /data/ /data-addon/ or subfodler of zonedir
+function _M:getFile(file, folder)
+	folder = folder or "maps"
+	if file:prefix("/") then return file end
+	-- Found in the zone itself ?
+	if file:find("^!") then return self.zone:getBaseName().."/"..folder.."/"..file:sub(2) end
+
+	local _, _, addon, rfile = file:find("^([^+]+)%+(.+)$")
+	if addon and rfile then
+		return "/data-"..addon.."/"..folder.."/"..rfile
+	end
+	return "/data/"..folder.."/"..file
+end
+
+function _M:regenerate()
+	self.force_regen = true
+end
+
+function _M:loadFile(mapscript, lev, old_lev, args)
+	local file = self:getFile(mapscript..".lua", "mapscripts")
+	local f, err = loadfile(file)
+	if not f and err then error(err) end
+	local nenv = {
+		args = args,
+		self = self,
+		zone = self.zone,
+		level = self.level,
+		mapdata = self.data,
+		lev = lev,
+		old_lev = old_lev,
+		loadMapScript = function(name, args) return self:loadFile(name, lev, old_lev, args) end,
+	}
+	for f in fs.iterate("/engine/tilemaps/", function(f) return f:find("%.lua$") end) do
+		local n = f:sub(1, -5)
+		local nf = "engine.tilemaps."..n
+		nenv[n] = require(nf)
+	end
+	setfenv(f, setmetatable(nenv, {__index=_G}))
+	return f()
+end
+
+function _M:custom(lev, old_lev)
+	for f in fs.iterate("/engine/tilemaps/", function(f) return f:find("%.lua$") end) do
+		local n = f:sub(1, -5)
+		local nf = "engine.tilemaps."..n
+		package.loaded[nf] = nil
+	end
+
+
+	local ret = nil
+	if self.data.mapscript then
+		local mapscript = self.data.mapscript
+		if type(mapscript) == "table" then mapscript = rng.table(mapscript) end
+		ret = self:loadFile(mapscript, lev, old_lev)
+	elseif self.data.custom then
+		ret = self.data.custom(self, lev, old_lev)
+	end
+
+	if ret then
+		return ret
+	elseif self.force_regen then
+		return nil
+	else
+		error("Generator MapScript called without mapscript or custom fields set!")
+	end
+end
+
+function _M:generate(lev, old_lev)
+	print("Generating MapScript")
+	self.lev, self.old_lev = lev, old_lev
+	self.force_regen = false
+	local data = self:custom(lev, old_lev)
+
+	for id, map in pairs(self.maps_registers) do
+		local pos = self.maps_positions[id]
+		self.map:import(map, pos.x - 1, pos.y - 1)
+	end
+
+	-- We do it AFTER importing submaps to ensure entities on them are correctly released
+	if self.force_regen then self.level.force_recreate = true return false end
+
+	if not self.entrance_pos then self.entrance_pos = data:locateTile('<') end
+	if not self.exit_pos then self.exit_pos = data:locateTile('>') end
+	if not self.entrance_pos then self.entrance_pos = data:point(1, 1) end
+	if not self.exit_pos then self.exit_pos = data:point(1, 1) end
+
+	data = data:getResult(true)
+	for i = 0, self.map.w - 1 do
+		for j = 0, self.map.h - 1 do
+			if data[j+1][i+1] ~= "⍓" and data[j+1][i+1] ~= "⎕" then
+				self.map(i, j, Map.TERRAIN, self:resolve(data[j+1][i+1] or '#'))
+			end
+		end
+	end
+
+	for _, post in pairs(self.post_gen) do
+		post(self, lev, old_lev)
+	end
+
+	return self.entrance_pos.x - 1, self.entrance_pos.y - 1, self.exit_pos.x - 1, self.exit_pos.y - 1
+end
+
+function _M:setEntrance(pos)
+	self.entrance_pos = pos
+end
+function _M:setExit(pos)
+	self.exit_pos = pos
+end
+
+function _M:addSpot(x, y, type, subtype, data)
+	data = data or {}
+	-- Tilemap uses 1 based indexes
+	data.x = math.floor(x) - 1
+	data.y = math.floor(y) - 1
+	data.type = type
+	data.subtype = subtype
+	self.spots[#self.spots+1] = data
+end
+
+function _M:postGen(fct)
+	self.post_gen[#self.post_gen+1] = fct
+end
+
+function _M:makeTemporaryMap(map_w, map_h, fct)
+	local old_map = self.level.map
+	local old_game_level = game.level
+	game.level = self.level
+
+	local tmp_map = Map.new(map_w, map_h)
+	self.level.map = tmp_map
+	self.map = tmp_map
+	local new_data = table.clone(self.data, true)
+
+	-- Fake a generator call to init tmp_map.room_map
+	local ngen = BaseGenerator.new({}, tmp_map, {}, {})
+
+	fct(tmp_map, new_data)
+
+	game.level = old_game_level
+	self.map = old_map
+	self.level.map = old_map
+
+	return tmp_map
+end
+
+--- Create the stairs inside the level
+function _M:makeStairsInside(lev, old_lev, spots)
+	-- Put down stairs
+	local dx, dy
+	if lev < self.zone.max_level or self.data.force_last_stair then
+		while true do
+			dx, dy = rng.range(1, self.map.w - 1), rng.range(1, self.map.h - 1)
+			if not self.map:checkEntity(dx, dy, Map.TERRAIN, "block_move") and not self.map.room_map[dx][dy].special then
+				self.map(dx, dy, Map.TERRAIN, self:resolve(">"))
+				self.map.room_map[dx][dy].special = "exit"
+				break
+			end
+		end
+	end
+
+	-- Put up stairs
+	local ux, uy
+	while true do
+		ux, uy = rng.range(1, self.map.w - 1), rng.range(1, self.map.h - 1)
+		if not self.map:checkEntity(ux, uy, Map.TERRAIN, "block_move") and not self.map.room_map[ux][uy].special then
+			self.map(ux, uy, Map.TERRAIN, self:resolve("<"))
+			self.map.room_map[ux][uy].special = "exit"
+			break
+		end
+	end
+
+	return ux, uy, dx, dy, spots
+end
+
+--- Create the stairs on the sides
+function _M:makeStairsSides(lev, old_lev, sides, spots)
+	-- Put down stairs
+	local dx, dy
+	if self.forced_down then
+		dx, dy = self.forced_down.x, self.forced_down.y
+	else
+		if lev < self.zone.max_level or self.data.force_last_stair then
+			while true do
+				if     sides[2] == 4 then dx, dy = 0, rng.range(0, self.map.h - 1)
+				elseif sides[2] == 6 then dx, dy = self.map.w - 1, rng.range(0, self.map.h - 1)
+				elseif sides[2] == 8 then dx, dy = rng.range(0, self.map.w - 1), 0
+				elseif sides[2] == 2 then dx, dy = rng.range(0, self.map.w - 1), self.map.h - 1
+				end
+
+				if not self.map.room_map[dx][dy].special then
+					self.map(dx, dy, Map.TERRAIN, self:resolve("down"))
+					self.map.room_map[dx][dy].special = "exit"
+					break
+				end
+			end
+		end
+	end
+
+	-- Put up stairs
+	local ux, uy
+	if self.forced_up then
+		ux, uy = self.forced_up.x, self.forced_up.y
+	else
+		while true do
+			if     sides[1] == 4 then ux, uy = 0, rng.range(0, self.map.h - 1)
+			elseif sides[1] == 6 then ux, uy = self.map.w - 1, rng.range(0, self.map.h - 1)
+			elseif sides[1] == 8 then ux, uy = rng.range(0, self.map.w - 1), 0
+			elseif sides[1] == 2 then ux, uy = rng.range(0, self.map.w - 1), self.map.h - 1
+			end
+
+			if not self.map.room_map[ux][uy].special then
+				self.map(ux, uy, Map.TERRAIN, self:resolve("up"))
+				self.map.room_map[ux][uy].special = "exit"
+				break
+			end
+		end
+	end
+
+	return ux, uy, dx, dy, spots
+end
diff --git a/game/engines/default/engine/generator/map/Town.lua b/game/engines/default/engine/generator/map/Town.lua
index 99447f6dfc263e81a5fe2b46e8ca8ac5f16bbdde..a71bb0a39a9299c1636043104d0bdba0d49ffc9d 100644
--- a/game/engines/default/engine/generator/map/Town.lua
+++ b/game/engines/default/engine/generator/map/Town.lua
@@ -19,7 +19,7 @@
 
 require "engine.class"
 local Map = require "engine.Map"
-local BSP = require "engine.BSP"
+local BSP = require "engine.algorithms.BSP"
 require "engine.Generator"
 local RoomsLoader = require "engine.generator.map.RoomsLoader"
 
diff --git a/game/engines/default/engine/tilemaps/BSP.lua b/game/engines/default/engine/tilemaps/BSP.lua
new file mode 100644
index 0000000000000000000000000000000000000000..0d79786e2cae4acaaefa0ab95fdd5835abcea2c3
--- /dev/null
+++ b/game/engines/default/engine/tilemaps/BSP.lua
@@ -0,0 +1,82 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Tilemap = require "engine.tilemaps.Tilemap"
+local Proxy = require "engine.tilemaps.Proxy"
+local AlgoBSP = require "engine.algorithms.BSP"
+
+--- Generate partitioned "building"
+-- @classmod engine.tilemaps.BSP
+module(..., package.seeall, class.inherit(Tilemap))
+
+function _M:init(min_w, min_h, max_depth, inner_walls)
+	Tilemap.init(self)
+	self.a_min_w, self.a_min_h, self.a_max_depth = min_w, min_h, max_depth
+	self.inner_walls = inner_walls
+end
+
+function _M:make(w, h, floor, wall)
+	local bsp = AlgoBSP.new(w, h, self.a_min_w, self.a_min_h, self.a_max_depth)
+	bsp:partition()
+
+	self:setSize(w, h, ' ')
+
+	self.rooms = {}
+
+	for _, leaf in ipairs(bsp.leafs) do
+		local from = self:point(leaf.rx + 1, leaf.ry + 1)
+		local to = self:point(leaf.rx + 1 + leaf.w - 2, leaf.ry + 1 + leaf.h - 2)
+		local center = from + (to - from) / 2
+
+		if floor then
+			self:carveArea(floor, from, to)
+		end
+		if wall then
+			if self.inner_walls then
+				for i = from.x, to.x do
+					self:put(self:point(i, from.y), wall)
+					self:put(self:point(i, to.y), wall)
+				end
+				for j = from.y, to.y do
+					self:put(self:point(from.x, j), wall)
+					self:put(self:point(to.x, j), wall)
+				end
+			else
+				for i = from.x - 1, to.x + 1 do
+					self:put(self:point(i, from.y - 1), wall)
+					self:put(self:point(i, to.y + 1), wall)
+				end
+				for j = from.y - 1, to.y + 1 do
+					self:put(self:point(from.x - 1, j), wall)
+					self:put(self:point(to.x + 1, j), wall)
+				end
+			end
+		end
+
+		self.rooms[#self.rooms+1] = {
+			from = from, 
+			to = to, 
+			center = center,
+			map = Proxy.new(self, from, leaf.w - 1, leaf.h - 1),
+		}
+	end
+
+	return self
+end
diff --git a/game/engines/default/engine/tilemaps/Heightmap.lua b/game/engines/default/engine/tilemaps/Heightmap.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6faf58cde01dc094e755ea98cbef3b41aa3fd41f
--- /dev/null
+++ b/game/engines/default/engine/tilemaps/Heightmap.lua
@@ -0,0 +1,63 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Tilemap = require "engine.tilemaps.Tilemap"
+local HMap = require "engine.Heightmap"
+
+--- Generate map-like data from a heightmap fractal
+-- @classmod engine.tilemaps.Heightmap
+module(..., package.seeall, class.inherit(Tilemap))
+
+function _M:init(roughness, start)
+	Tilemap.init(self)
+	self.roughness = roughness or 1.2
+	self.start = start or {}
+	for k, e in pairs(start) do start[k] = e * HMap.max end
+end
+
+function _M:make(w, h, chars, normalize)
+	if normalize == nil then normalize = true end
+
+	self:setSize(w, h, ' ')
+
+	local hmap = HMap.new(w, h, self.roughness, self.start)
+	hmap:generate()
+
+	local tmp = {}
+	local min, max = 1, 0
+	for j = 1, h do for i = 1, w do
+		local nv = hmap.hmap[i][j] / hmap.max
+
+		tmp[j] = tmp[j] or {}
+		tmp[j][i] = nv
+		if nv < min then min = nv end
+		if nv > max then max = nv end
+	end end
+
+	for j = 1, h do for i = 1, w do
+		local nv = tmp[j][i]
+		if normalize then nv = (nv - min) / (max - min) end
+
+		local c = util.bound(math.floor(nv * (#chars - 1)) + 1, 1, #chars)
+		self.data[j][i] = chars[c]
+	end end
+	
+	return self
+end
diff --git a/game/engines/default/engine/tilemaps/Maze.lua b/game/engines/default/engine/tilemaps/Maze.lua
new file mode 100644
index 0000000000000000000000000000000000000000..20a03a3f76221871a0220232f2a14a272ec11fdf
--- /dev/null
+++ b/game/engines/default/engine/tilemaps/Maze.lua
@@ -0,0 +1,110 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Tilemap = require "engine.tilemaps.Tilemap"
+local HMap = require "engine.Heightmap"
+
+--- Generate map-like data from a heightmap fractal
+-- @classmod engine.tilemaps.Heightmap
+module(..., package.seeall, class.inherit(Tilemap))
+
+function _M:init()
+	Tilemap.init(self)
+end
+
+function _M:makeSimple(w, h, floors, walls, enclosed)
+	if type(floors) == "string" then floors = {floors} end
+	if type(walls) == "string" then walls = {walls} end
+
+	self:setSize(w, h, ' ')
+
+	local lastx, lasty = 0, 0
+
+	local mazemap = self:makeData(w, h, false)
+
+	local do_tile = function(i, j, wall)
+		self.data[j][i] = wall and rng.table(walls) or rng.table(floors)
+		mazemap[j][i] = wall
+		if not wall then
+			lastx = math.max(lastx, i)
+			lasty = math.max(lasty, j)
+		end
+	end
+
+	for i = 1, w do for j = 1, h do
+		do_tile(i, j, true)
+	end end
+
+	local mw, Mw = 1, w
+	local mh, Mh = 1, h
+	if not enclosed then
+		mw, Mw = 0, w + 1
+		mh, Mh = 0, h + 1
+	end
+
+	local xpos, ypos = mw+1, mh+1
+	local moves = {{xpos,ypos}}
+	local pickp = rng.range(1,4)
+	while #moves > 0 do
+		local pickn = #moves - math.floor((rng.range(1,100000)/100001)^pickp * #moves)
+		local pick = moves[pickn]
+		xpos = pick[1]
+		ypos = pick[2]
+		local dir = {}
+		if xpos+2>mw and xpos+2<Mw and mazemap[ypos][xpos+2] then
+			dir[#dir+1] = 6
+		end
+		if xpos-2>mw and xpos-2<Mw and mazemap[ypos][xpos-2] then
+			dir[#dir+1] = 4
+		end
+		if ypos-2>mh and ypos-2<Mh and mazemap[ypos-2][xpos] then
+			dir[#dir+1] = 8
+		end
+		if ypos+2>mh and ypos+2<Mh and mazemap[ypos+2][xpos] then
+			dir[#dir+1] = 2
+		end
+
+		if #dir > 0 then
+			local d = dir[rng.range(1, #dir)]
+			if d == 4 then
+				do_tile(xpos-2, ypos, false)
+				do_tile(xpos-1, ypos, false)
+				xpos = xpos - 2
+			elseif d == 6 then
+				do_tile(xpos+2, ypos, false)
+				do_tile(xpos+1, ypos, false)
+				xpos = xpos + 2
+			elseif d == 8 then
+				do_tile(xpos, ypos-2, false)
+				do_tile(xpos, ypos-1, false)
+				ypos = ypos - 2
+			elseif d == 2 then
+				do_tile(xpos, ypos+2, false)
+				do_tile(xpos, ypos+1, false)
+				ypos = ypos + 2
+			end
+			table.insert(moves, {xpos, ypos})
+		else
+			local back = table.remove(moves,pickn)
+		end
+	end
+	
+	return self
+end
diff --git a/game/engines/default/engine/tilemaps/Noise.lua b/game/engines/default/engine/tilemaps/Noise.lua
new file mode 100644
index 0000000000000000000000000000000000000000..5d2ec6734885887c8d11a7d70934cdfd702ad16b
--- /dev/null
+++ b/game/engines/default/engine/tilemaps/Noise.lua
@@ -0,0 +1,65 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Tilemap = require "engine.tilemaps.Tilemap"
+
+--- Generate map-like data from a noise generator
+-- @classmod engine.tilemaps.Noise
+module(..., package.seeall, class.inherit(Tilemap))
+
+function _M:init(noise_kind, hurst, lacunarity, zoom, octave)
+	Tilemap.init(self)
+
+	self.noise_kind = noise_kind or "fbm_perlin"
+	hurst = hurst or 0.5
+	lacunarity = lacunarity or 2
+	self.zoom = zoom or 1
+	self.octave = octave or 6
+
+	self.noise = core.noise.new(2, self.hurst, self.lacunarity)
+
+	-- self.data_h = #self.data
+	-- self.data_w = self.data[1] and #self.data[1] or 0
+end
+
+function _M:make(w, h, chars, normalize)
+	if normalize == nil then normalize = true end
+
+	self:setSize(w, h, ' ')
+
+	local tmp = {}
+	local min, max = 1, 0
+	for i = 1, w do for j = 1, h do
+		local nv = self.noise[self.noise_kind](self.noise, self.zoom * (i-1) / w, self.zoom * (j-1) / h, self.octave) / 2 + 0.5
+		tmp[j] = tmp[j] or {}
+		tmp[j][i] = nv
+		if nv < min then min = nv end
+		if nv > max then max = nv end
+	end end
+
+	for i = 1, w do for j = 1, h do
+		local nv = tmp[j][i]
+		if normalize then nv = (nv - min) / (max - min) end
+		local v = math.floor(nv * (#chars - 1))
+		if v > #chars then os.crash() end
+		self.data[j][i] = chars[v+1]
+	end end
+	return self
+end
diff --git a/game/engines/default/engine/tilemaps/Proxy.lua b/game/engines/default/engine/tilemaps/Proxy.lua
new file mode 100644
index 0000000000000000000000000000000000000000..3124f3c86ec2bccaab09da7b557681fb806eb95a
--- /dev/null
+++ b/game/engines/default/engine/tilemaps/Proxy.lua
@@ -0,0 +1,65 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Tilemap = require "engine.tilemaps.Tilemap"
+local HMap = require "engine.Heightmap"
+
+--- Generate map-like data from a heightmap fractal
+-- @classmod engine.tilemaps.Heightmap
+module(..., package.seeall, class.inherit(Tilemap))
+
+function _M:init(source_map, offset, w, h)
+	Tilemap.init(self)
+	self.unmergable = true
+	self.source_map = source_map
+	self.merged_pos = offset
+	self:setSize(w, h)
+
+	-- Make data an actual proxy
+	self.data = {}
+
+	local proxy_t = {
+		__newindex = function(ys, x, v)
+			if self.mask and self.mask[ys.__y] and not self.mask[ys.__y][x] then return end -- Woops, not allowed!
+			local pos = self.merged_pos + self:point(x, ys.__y) - 1 + self.source_map.merged_pos - 1
+			self.source_map:put(pos, v)
+		end,
+		__index = function(ys, x)
+			local pos = self.merged_pos + self:point(x, ys.__y) - 1 + self.source_map.merged_pos - 1
+			return self.source_map.data[pos.y][pos.x]
+		end,
+	}
+
+	for j = 1, h do
+		self.data[j] = setmetatable({__y=j}, proxy_t)
+	end
+end
+
+function _M:maskOtherPoints(list, source_coords)
+	if not self.mask then self.mask = self:makeData(self.data_w, self.data_h, false) end
+	for _, p in ipairs(list) do
+		if source_coords then
+			local np = p - self.merged_pos + 1
+			self.mask[np.y][np.x] = true
+		else
+			self.mask[p.y][p.x] = true
+		end
+	end
+end
diff --git a/game/engines/default/engine/tilemaps/Rooms.lua b/game/engines/default/engine/tilemaps/Rooms.lua
new file mode 100644
index 0000000000000000000000000000000000000000..e02efe9fb47fd3c7da111e24cc58fe26870cf454
--- /dev/null
+++ b/game/engines/default/engine/tilemaps/Rooms.lua
@@ -0,0 +1,160 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Tilemap = require "engine.tilemaps.Tilemap"
+local RoomsLoader = require "engine.generator.map.RoomsLoader"
+
+--- Generate map-like data from a heightmap fractal
+-- @classmod engine.tilemaps.Heightmap
+module(..., package.seeall, class.inherit(Tilemap))
+
+local RoomInstance = class.inherit(Tilemap){}
+
+function _M:init(mapscript, rooms_list)
+	self.mapscript = mapscript
+	if type(rooms_list) == "string" then rooms_list = {rooms_list} end
+
+	self.rooms = {}
+	for _, file in ipairs(rooms_list) do
+		table.insert(self.rooms, mapscript:loadRoom(file))
+	end
+
+	self.room_next_id = 1
+end
+
+function _M:generateRoom(temp_symbol, account_for_border)
+	local mapscript = self.mapscript
+	local roomdef = rng.table(self.rooms)
+	local id = tostring(self)..":"..self.room_next_id
+	local room = mapscript:roomGen(roomdef, id, mapscript.lev, mapscript.old_lev)
+	if not room then
+		print("Tilemap.Rooms failed to generate room")
+		return nil
+	end
+
+	if account_for_border == nil then account_for_border = false end
+
+	temp_symbol = temp_symbol or {wall='⍓', floor='⎕'}
+	
+	local tm = RoomInstance.new({room.w - (account_for_border and 2 or 0), room.h - (account_for_border and 2 or 0)}, temp_symbol and temp_symbol.wall)
+	room.temp_symbol = temp_symbol
+	tm.mapscript = mapscript
+	tm.room_data = room
+	tm.room_id = id
+
+	self.room_next_id = self.room_next_id + 1
+	return tm
+end
+
+function RoomInstance:build()
+	local mapscript = self.mapscript
+	local room = self.room_data
+	local id = self.room_id
+
+	if account_for_border == nil then account_for_border = false end
+
+	local map = mapscript:makeTemporaryMap(room.w, room.h, function(map)
+		mapscript:roomPlace(room, id, 0, 0)
+	end)
+	mapscript.maps_registers[id] = map
+	mapscript.maps_positions[id] = account_for_border and self:point(0, 0) or self:point(1, 1)
+
+	local exits = { openables={}, doors={} }
+
+	local function checkexits(i, j, dir)
+		if map.room_map[i][j].can_open then
+			exits.openables[#exits.openables+1] = self:point(i+1, j+1)
+		end
+		if map(i, j, map.TERRAIN) and map(i, j, map.TERRAIN).door_opened then
+			-- Doors "position" is the actual tile right beside it, not the door itself as we dont want to delete the door
+			local dx, dy = util.coordAddDir(i+1, j+1, dir)
+			exits.doors[#exits.doors+1] = self:point(dx, dy)
+		end
+	end
+	self.static_map = map
+	self.exits = exits
+
+	for i = 0, map.w - 1 do
+		checkexits(i, 0, 8)
+		checkexits(i, map.h - 1, 2)
+	end
+	for j = 0, map.h - 1 do
+		checkexits(0, j, 4)
+		checkexits(map.w - 1, j, 6)
+	end
+
+	-- Mark floor & walls
+	for i = 0, map.w - 1 do	for j = 0, map.h - 1 do
+		if not map:checkEntity(i, j, map.TERRAIN, 'block_move') then
+			self.data[j+1][i+1] = room.temp_symbol.floor
+		end
+	end end
+
+	return self
+end
+
+--- Not used, so we remove all entities
+function RoomInstance:discard()
+	for i = 0, self.static_map.w - 1 do for j = 0, self.static_map.h - 1 do
+		local z = i + j * self.static_map.w
+		if self.static_map.map[z] then
+			for _, e in pairs(self.static_map.map[z]) do
+				e:removed()
+			end
+		end
+	end end
+end
+
+function RoomInstance:mergedAt(x, y)
+	Tilemap.mergedAt(self, x, y)
+
+	local d = self:point(x - 1, y - 1)
+
+	self.mapscript.maps_positions[self.room_id] = self.mapscript.maps_positions[self.room_id] + d
+	for _, open in pairs(self.exits.openables) do open.x, open.y = open.x + d.x, open.y + d.y end
+	for _, door in pairs(self.exits.doors) do door.x, door.y = door.x + d.x, door.y + d.y end
+end
+
+function RoomInstance:findExits(pos, kind)
+	local list = {}
+	if not kind or kind == "openable" then
+		for _, open in pairs(self.exits.openables) do
+			local dist = core.fov.distance(pos.x, pos.y, open.x, open.y)
+			table.insert(list, {dist = dist, pos = open, kind = "open"})			
+		end
+	end
+	if not kind or kind == "door" then
+		for _, door in pairs(self.exits.doors) do
+			local dist = core.fov.distance(pos.x, pos.y, door.x, door.y)
+			table.insert(list, {dist = dist, pos = door, kind = "door"})			
+		end
+	end
+	table.sort(list, "dist")
+	return list
+end
+
+
+
+function _M:flip(axis) error("Cannot use :flip() on a Room tilemap") end
+function _M:rotate(angle) error("Cannot use :rotate() on a Room tilemap") end
+function _M:scale(sx, sy) error("Cannot use :scale() on a Room tilemap") end
+function RoomInstance:flip(axis) error("Cannot use :flip() on a Room tilemap") end
+function RoomInstance:rotate(angle) error("Cannot use :rotate() on a Room tilemap") end
+function RoomInstance:scale(sx, sy) error("Cannot use :scale() on a Room tilemap") end
diff --git a/game/engines/default/engine/tilemaps/Static.lua b/game/engines/default/engine/tilemaps/Static.lua
new file mode 100644
index 0000000000000000000000000000000000000000..063590872f394c50397acd4620c1a3be75373116
--- /dev/null
+++ b/game/engines/default/engine/tilemaps/Static.lua
@@ -0,0 +1,34 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Tilemap = require "engine.tilemaps.Tilemap"
+
+--- Generate map-like data from a tmx file
+-- @classmod engine.tilemaps.Static
+module(..., package.seeall, class.inherit(Tilemap))
+
+function _M:init(file)
+	Tilemap.init(self)
+
+	self.data = self:tmxLoad(file)
+	self.data_h = #self.data
+	self.data_w = self.data[1] and #self.data[1] or 0
+	self.data_size = self:point(self.data_w, self.data_h)
+end
diff --git a/game/engines/default/engine/tilemaps/Tilemap.lua b/game/engines/default/engine/tilemaps/Tilemap.lua
new file mode 100644
index 0000000000000000000000000000000000000000..abc9c295ead143bd64870d7a32ab7b0c6d793380
--- /dev/null
+++ b/game/engines/default/engine/tilemaps/Tilemap.lua
@@ -0,0 +1,1206 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local lom = require "lxp.lom"
+local mime = require "mime"
+
+--- Base class to generate map-like
+-- @classmod engine.tilemaps.Tilemap
+module(..., package.seeall, class.make)
+
+function _M:init(size, fill_with)
+	if size then
+		self:setSize(size[1], size[2], fill_with)
+	end
+	self.merged_pos = self:point(1, 1)
+end
+
+function _M:getSize()
+	return self.data_w, self.data_h
+end
+
+function _M:setSize(w, h, fill_with)
+	self.data_w = math.floor(w)
+	self.data_h = math.floor(h)
+	self.data_size = self:point(self.data_w, self.data_h)
+	if self.data_w and self.data_h then
+		self.data = self:makeData(self.data_w, self.data_h, fill_with or ' ')
+	end
+end
+
+function _M:makeData(w, h, fill_with)
+	local data = {}
+	for y = 1, h do
+		data[y] = {}
+		for x = 1, w do
+			data[y][x] = fill_with
+		end
+	end
+	return data
+end
+
+function _M:makeCharsTable(v, default)
+	if not v then v = default end
+	if type(v) == "string" then v = {v} end
+	return table.reverse(v)
+end
+
+local point_meta = {
+	__add = function(a, b)
+		if type(b) == "number" then return _M:point(a.x + b, a.y + b)
+		else return _M:point(a.x + b.x, a.y + b.y) end
+	end,
+	__sub = function(a, b)
+		if type(b) == "number" then return _M:point(a.x - b, a.y - b)
+		else return _M:point(a.x - b.x, a.y - b.y) end
+	end,
+	__mul = function(a, b)
+		if type(b) == "number" then return _M:point(a.x * b, a.y * b)
+		else return _M:point(a.x * b.x, a.y * b.y) end
+	end,
+	__div = function(a, b)
+		if type(b) == "number" then return _M:point(a.x / b, a.y / b)
+		else return _M:point(a.x / b.x, a.y / b.y) end
+	end,
+	__eq = function(a, b)
+		return a.x == b.x and a.y == b.y
+	end,
+	__tostring = function(p)
+		return ("Point(%d x %d)"):format(p.x, p.y)
+	end,
+	__index = {
+		hashCoord = function(p, tilemap)
+			return p.y * tilemap.data_w + p.x
+		end,
+		area = function(p)
+			return p.y * p.x
+		end,
+	},
+}
+--- Make a point data, can be added
+function _M:point(x, y)
+	if type(x) == "table" then		
+		local p = {x=math.floor(x.x), y=math.floor(x.y)}
+		setmetatable(p, point_meta)
+		return p
+	else
+		local p = {x=math.floor(x), y=math.floor(y)}
+		setmetatable(p, point_meta)
+		return p
+	end
+end
+
+
+--- Returns a point at the center of the map, accounting for merged_pos
+function _M:centerPoint()
+	return self:point(self.data_w / 2, self.data_h / 2) + self.merged_pos - 1
+end
+
+--- Sorts a list of tilemaps by their centerPoint
+function _M:sortListCenter(list)
+	local newlist = {}
+
+	local first = table.remove(list, 1)
+	while #list > 0 do
+		local fp = first:centerPoint()
+		table.sort(list, function(a, b) a = a:centerPoint() b = b:centerPoint() return core.fov.distance(fp.x, fp.y, a.x, a.y) < core.fov.distance(fp.x, fp.y, b.x, b.y) end)
+
+		newlist[#newlist+1] = first
+		first = table.remove(list, 1)
+		if #list == 0 then newlist[#newlist+1] = first end
+	end
+
+	table.replaceWith(list, newlist)
+	return list
+end
+
+--- Sorts a list of tilemaps by their centerPoint
+function _M:findEachClosestCenter(list)
+	local newlist = {}
+
+	for _, map in ipairs(list) do
+		local closests = {}
+		local fp = map:centerPoint()
+
+		for _, cmap in ipairs(list) do if cmap ~= map then closests[#closests+1] = cmap end end
+		table.sort(closests, function(a, b) a = a:centerPoint() b = b:centerPoint() return core.fov.distance(fp.x, fp.y, a.x, a.y) < core.fov.distance(fp.x, fp.y, b.x, b.y) end)
+
+		newlist[map] = closests
+	end
+
+	return newlist
+end
+
+--- Find all empty spaces (defaults to ' ') and fill them with a give char
+function _M:fillAll(fill_with, empty_char)
+	if not self.data then return end
+	empty_char = empty_char or ' '
+	fill_with = fill_with or '#'
+	for y = 1, self.data_h do
+		for x = 1, self.data_w do
+			if self.data[y][x] == empty_char then
+				self.data[y][x] = fill_with
+			end
+		end
+	end
+end
+
+--- Check if the given coords are on the map
+function _M:isBound(pos, y)
+	if y then pos = self:point(pos, y) end
+	if pos.x >= 1 and pos.x <= self.data_w and pos.y >=1 and pos.y <= self.data_h then
+		return true
+	else
+		return false
+	end
+end
+
+--- Draw a single tile
+function _M:put(pos, char)
+	if self:isBound(pos) then
+		if type(char) == "function" then char = char(pos)
+		elseif type(char) == "table" then char = rng.table(char) end
+		self.data[pos.y][pos.x] = char
+	end
+end
+
+--- Flip the map
+function _M:flip(axis)
+	local ndata = self:makeData(self.data_w, self.data_h, '')
+	for y = 1, self.data_h do
+		for x = 1, self.data_w do
+			local nx, ny = x, y
+			if axis == "x" or axis == true then
+				x = self.data_w - (x - 1)
+			elseif axis == "y" or axis == false then
+				y = self.data_h - (y - 1)
+			end
+			ndata[ny][nx] = self.data[y][x]
+		end
+	end
+	self.data = ndata
+	return self
+end
+
+--- Rotate the map
+function _M:rotate(angle)
+	local function rotate_coords(i, j)
+		local ii, jj = i, j
+		if angle == 90 then ii, jj = j, self.data_w - i + 1
+		elseif angle == 180 then ii, jj = self.data_w - i + 1, self.data_h - j + 1
+		elseif angle == 270 then ii, jj = self.data_h - j + 1, i
+		end
+		return ii, jj
+	end
+
+	local ndata_w, ndata_h = self.data_w, self.data_h
+	if angle == 90 or angle == 270 then ndata_w, ndata_h = self.data_h, self.data_w end
+	local ndata = self:makeData(ndata_w, ndata_h, '')
+	for y = 1, self.data_h do
+		for x = 1, self.data_w do
+			local nx, ny = rotate_coords(x, y)
+			ndata[ny][nx] = self.data[y][x]
+		end
+	end
+	self.data = ndata
+	self.data_w = ndata_w
+	self.data_h = ndata_h
+	return self
+end
+
+--- Widen the map
+function _M:scale(sx, sy)
+	local ndata = self:makeData(self.data_w * sx, self.data_h * sy, '')
+	for y = 0, self.data_h - 1 do
+		for x = 0, self.data_w - 1 do
+			for nx = x * sx, x * sx + sx - 1 do
+				for ny = y * sy, y * sy + sy - 1 do
+					ndata[ny + 1][nx + 1] = self.data[y + 1][x + 1]
+				end
+			end
+		end
+	end
+	self.data = ndata
+	self.data_w, self.data_h = self.data_w * sx, self.data_h * sy
+	return self
+end
+
+--- Used internally to load a tilemap from a tmx file
+function _M:tmxLoad(file)
+	local f = fs.open(file, "r") local data = f:read(10485760) f:close()
+	local map = lom.parse(data)
+	local mapprops = {}
+	if map:findOne("properties") then mapprops = map:findOne("properties"):findAllAttrs("property", "name", "value") end
+	self.props = mapprops
+
+	local w, h = tonumber(map.attr.width), tonumber(map.attr.height)
+	if mapprops.map_data then
+		local params = self:loadLuaInEnv(g, nil, "return "..mapprops.map_data)
+		table.merge(self.data, params, true)
+	end
+
+	local gids = {}
+
+	for _, tileset in ipairs(map:findAll("tileset")) do
+		local firstgid = tonumber(tileset.attr.firstgid)
+		local has_tile = tileset:findOne("tile")
+		for _, tile in ipairs(tileset:findAll("tile")) do
+			local tileprops = {}
+			if tile:findOne("properties") then tileprops = tile:findOne("properties"):findAllAttrs("property", "name", "value") end
+			local gid = tonumber(tile.attr.id)
+			gids[firstgid + gid] = {tid=tileprops.id or ' '}
+		end
+	end
+
+	local data = {}
+	for y = 1, h do
+		data[y] = {}
+		for x = 1, w do data[y][x] = ' ' end
+	end
+
+	local function populate(x, y, gid)
+		if not gids[gid] then return end
+		local g = gids[gid]
+		data[y][x] = g.tid
+	end
+
+	for _, layer in ipairs(map:findAll("layer")) do
+		local mapdata = layer:findOne("data")
+		if mapdata.attr.encoding == "base64" then
+			local b64 = mime.unb64(mapdata[1]:trim())
+			local data
+			if mapdata.attr.compression == "zlib" then data = zlib.decompress(b64)
+			elseif not mapdata.attr.compression then data = b64
+			else error("tmx map compression unsupported: "..mapdata.attr.compression)
+			end
+			local gid, i = nil, 1
+			local x, y = 1, 1
+			while i <= #data do
+				gid, i = struct.unpack("<I4", data, i)				
+				populate(x, y, gid)
+				x = x + 1
+				if x > w then x = 1 y = y + 1 end
+			end
+		elseif mapdata.attr.encoding == "csv" then
+			local data = mapdata[1]:gsub("[^,0-9]", ""):split(",")
+			local x, y = 1, 1
+			for i, gid in ipairs(data) do
+				gid = tonumber(gid)
+				populate(x, y, gid)
+				x = x + 1
+				if x > w then x = 1 y = y + 1 end
+			end
+		elseif not mapdata.attr.encoding then
+			local data = mapdata:findAll("tile")
+			local x, y = 1, 1
+			for i, tile in ipairs(data) do
+				local gid = tonumber(tile.attr.gid)
+				populate(x, y, gid)
+				x = x + 1
+				if x > w then x = 1 y = y + 1 end
+			end
+		end
+	end
+	return data, w, h
+end
+
+function _M:collapseToLineFormat(data)
+	if not data then return nil end
+	local ndata = {}
+	for y = 1, #data do ndata[y] = table.concat(data[y]) end
+	return ndata
+end
+
+--- Do we have results, or did we fail?
+function _M:hasResult()
+	return self.data and true or false
+end
+
+--- Locate a specific tile
+function _M:locateTile(char, erase)
+	local res = {}
+	for i = 1, self.data_w do
+		for j = 1, self.data_h do
+			if self.data[j][i] == char then
+				res[#res+1] = self:point(i, j)
+				if erase then self.data[j][i] = erase end
+			end
+		end
+	end
+	if #res == 0 then return nil end
+	return rng.table(res), res
+end
+
+--- Finds a random location with enough area available
+function _M:findRandomArea(from, to, w, h, made_of, margin, tries)
+	if not tries then tries = 500 end
+	if not margin then margin = 0 end
+	if not from then from = self:point(1, 1) end
+	if not to then to = self:point(self.data_w, self.data_h) end
+	if not made_of then made_of = {'#'} end
+	if type(made_of) == "string" then made_of = {made_of} end
+	made_of = table.reverse(made_of)
+
+	while tries > 0 do
+		local pos = self:point(rng.range(from.x, to.x - w - margin * 2), rng.range(from.y, to.y - h - margin * 2))
+		local ok = true
+		for i = pos.x - margin, pos.x + w + margin do
+			for j = pos.y - margin, pos.y + h + margin do
+				if not self:isBound(i, j) or not made_of[self.data[j][i]] then
+					ok = false
+					break
+				end
+			end
+			if not ok then break end
+		end
+		if ok then return pos end
+
+		tries = tries - 1
+	end
+	return nil
+end
+
+--- Returns the bounding rectangle of a list of points
+function _M:pointsBoundingRectangle(list)
+	local to = self:point(1, 1)
+	local from = self:point(self.data_w, self.data_h)
+	for _, p in ipairs(list) do
+		if p.x < from.x then from.x = p.x end
+		if p.x > to.x then to.x = p.x end
+		if p.y < from.y then from.y = p.y end
+		if p.y > to.y then to.y = p.y end
+	end
+	return from, to
+end
+
+--- Return a list of groups of tiles that matches the given cond function
+function _M:findGroups(cond)
+	if not self.data then return {} end
+	local Proxy = require "engine.tilemaps.Proxy"
+
+	local fills = {}
+	local opens = {}
+	local list = {}
+	for i = 1, self.data_w do
+		opens[i] = {}
+		for j = 1, self.data_h do
+			if cond(self.data[j][i]) then
+				opens[i][j] = #list+1
+				list[#list+1] = {x=i, y=j}
+			end
+		end
+	end
+
+	local nbg = 0
+	local function floodFill(x, y)
+		nbg=nbg+1
+		local q = {self:point{x=x,y=y}}
+		local closed = {}
+		while #q > 0 do
+			local n = table.remove(q, 1)
+			if opens[n.x] and opens[n.x][n.y] then
+				-- self.data[n.y][n.x] = string.char(string.byte('0') + nbg) -- Debug to visualize floodfill groups
+				closed[#closed+1] = n
+				list[opens[n.x][n.y]] = nil
+				opens[n.x][n.y] = nil
+				q[#q+1] = self:point{x=n.x-1, y=n.y}
+				q[#q+1] = self:point{x=n.x, y=n.y+1}
+				q[#q+1] = self:point{x=n.x+1, y=n.y}
+				q[#q+1] = self:point{x=n.x, y=n.y-1}
+
+				q[#q+1] = self:point{x=n.x+1, y=n.y-1}
+				q[#q+1] = self:point{x=n.x+1, y=n.y+1}
+				q[#q+1] = self:point{x=n.x-1, y=n.y-1}
+				q[#q+1] = self:point{x=n.x-1, y=n.y+1}
+			end
+		end
+		return closed
+	end
+
+	-- Process all open spaces
+	local groups = {}
+	while next(list) do
+		local i, l = next(list)
+		local closed = floodFill(l.x, l.y)
+
+		local from, to = self:pointsBoundingRectangle(closed)
+		to = to - from + 1
+		local map = Proxy.new(self, from, to.x, to.y)
+		map:maskOtherPoints(closed, true)
+
+		groups[#groups+1] = {list=closed, map=map}
+		print("[Tilemap] Floodfill group", i, #closed)
+	end
+
+	return groups
+end
+
+--- Return a list of groups of tiles representing each of the connected areas
+function _M:findGroupsNotOf(wall)
+	wall = table.reverse(wall)
+	return self:findGroups(function(c) return not wall[c] end)
+end
+
+--- Return a list of groups of tiles representing each of the connected areas
+function _M:findGroupsOf(floor)
+	floor = table.reverse(floor)
+	return self:findGroups(function(c) return floor[c] end)
+end
+
+--- Apply a custom method over the given groups, sorting them from bigger to smaller
+-- It gives the groups in order of bigger to smaller
+function _M:applyOnGroups(groups, fct)
+	if not self.data then return end
+	table.sort(groups, function(a,b) return #a.list > #b.list end)
+	for id, group in ipairs(groups) do
+		fct(group, id)
+	end
+end
+
+--- Given a list of groups, eliminate them all
+function _M:eliminateGroups(wall, groups)
+	if not self.data then return end
+	print("[Tilemap] Eleminating groups", #groups)
+	for i = 1, #groups do
+		print("[Tilemap] Eleminating group "..i.." of", #groups[i].list)
+		for j = 1, #groups[i].list do
+			local jn = groups[i].list[j]
+			self.data[jn.y][jn.x] = wall
+		end
+	end
+end
+
+--- Simply destroy all connected groups except the biggest one
+function _M:eliminateByFloodfill(walls)
+	if not self.data then return 0 end
+	local groups = self:findGroupsNotOf(walls)
+
+	-- If nothing exists, regen
+	if #groups == 0 then
+		print("[Tilemap] Floodfill found nothing")
+		return 0
+	end
+
+	-- Sort to find the biggest group
+	table.sort(groups, function(a,b) return #a.list < #b.list end)
+	local g = table.remove(groups)
+	if g and #g.list > 0 then
+		print("[Tilemap] Ok floodfill with main group size", #g.list)
+		self:eliminateGroups(walls[1], groups)
+		return #g.list
+	else
+		print("[Tilemap] Floodfill left nothing")
+		return 0
+	end
+end
+
+function _M:fillGroup(group, char)
+	-- print("[Tilemap] Filling group of", #group.list, "with", char)
+	for j = 1, #group.list do
+		local jn = group.list[j]
+		self.data[jn.y][jn.x] = char
+	end
+end
+
+function _M:isInGroup(group, x, y)
+	if not group.reverse then
+		group.reverse = {}
+		for j = 1, #group.list do
+			local jn = group.list[j]
+			group.reverse[jn.x] = group.reverse[jn.x] or {}
+			group.reverse[jn.x][jn.y] = true
+		end
+	end
+	return group.reverse[x] and group.reverse[x][y]
+end
+--[=[
+--- Find the biggest rectangle that can fit fully in the given group
+function _M:groupInnerRectangle(group)
+	if #group.list == 0 then return nil end
+
+	-- Make a matrix to work on
+	local outrect = self:groupOuterRectangle(group)
+	local m = self:makeData(outrect.w, outrect.h, 0)
+	local matrix = self:makeData(outrect.w, outrect.h, false)
+	for j = 1, #group.list do
+		local jn = group.list[j]
+		matrix[jn.y - outrect.y1 + 1][jn.x - outrect.x1 + 1] = true
+	end
+
+        for i = 1, outrect.w do
+        	for j = 1, outrect.h do
+        		m[j][i] = matrix[j][i] and (1 + m[j+1][i]) or 0
+        	end
+        end
+                m[i][j]=matrix[i][j]=='1'?1+m[i][j+1]:0;
+end
+
+public int maximalRectangle(char[][] matrix) {
+	int m = matrix.length;
+	int n = m == 0 ? 0 : matrix[0].length;
+	int[][] height = new int[m][n + 1];
+ 
+	int maxArea = 0;
+	for (int i = 0; i < m; i++) {
+		for (int j = 0; j < n; j++) {
+			if (matrix[i][j] == '0') {
+				height[i][j] = 0;
+			} else {
+				height[i][j] = i == 0 ? 1 : height[i - 1][j] + 1;
+			}
+		}
+	}
+ 
+	for (int i = 0; i < m; i++) {
+		int area = maxAreaInHist(height[i]);
+		if (area > maxArea) {
+			maxArea = area;
+		}
+	}
+ 
+	return maxArea;
+}
+ 
+private int maxAreaInHist(int[] height) {
+	Stack<Integer> stack = new Stack<Integer>();
+ 
+	int i = 0;
+	int max = 0;
+ 
+	while (i < height.length) {
+		if (stack.isEmpty() || height[stack.peek()] <= height[i]) {
+			stack.push(i++);
+		} else {
+			int t = stack.pop();
+			max = Math.max(max, height[t]
+					* (stack.isEmpty() ? i : i - stack.peek() - 1));
+		}
+	}
+ 
+	return max;
+}
+
+-- int maximalRectangle(vector<vector<char> > &matrix) {
+--         if(matrix.size()==0 || matrix[0].size()==0)return 0;
+--         vector<vector<int>>m(matrix.size()+1,vector<int>(matrix[0].size()+1,0));
+--         for(int i=0;i<matrix.size();i++)
+--             for(int j=matrix[0].size()-1;j>=0;j--)
+--                 m[i][j]=matrix[i][j]=='1'?1+m[i][j+1]:0;
+--         int max=0;
+--         for(int i=0;i<matrix[0].size();i++){
+--             int p=0;
+--             vector<int>s;
+--             while(p!=m.size()){
+--                 if(s.empty() || m[p][i]>=m[s.back()][i])
+--                     s.push_back(p++);
+--                 else{
+--                     int t=s.back();
+--                     s.pop_back();
+--                     max=std::max(max,m[t][i]*(s.empty()?p:p-s.back()-1));
+--                 }
+--             }
+--         }
+--         return max;
+-- }
+--]=]
+
+--- Find the smallest rectangle that can fit around in the given group
+function _M:groupOuterRectangle(group)
+	local n = group.list[1]
+	if not n then return end -- wtf?
+	local x1, x2 = n.x, n.x
+	local y1, y2 = n.y, n.y
+
+	for j = 1, #group.list do
+		local jn = group.list[j]
+		if jn.x < x1 then x1 = jn.x end
+		if jn.x > x2 then x2 = jn.x end
+		if jn.y < y1 then y1 = jn.y end
+		if jn.y > y2 then y2 = jn.y end
+	end
+
+	-- Debug
+	-- for i = x1, x2 do for j = y1, y2 do
+	-- 	if not self:isInGroup(group, i, j) then
+	-- 		if self.data[j][i] == '#' then
+	-- 			self.data[j][i] = 'T'
+	-- 		end
+	-- 	end
+	-- end end
+
+	return self:point(x1, y1), self:point(x2, y2), x2 - x1 + 1, y2 - y1 + 1
+end
+
+--- Carve out a simple linear path from coords until a tile is reached
+function _M:carveLinearPath(char, from, dir, stop_at, dig_only_into)
+	local x, y = math.floor(from.x), math.floor(from.y)
+	local dx, dy = util.dirToCoord(dir)
+	if type(dig_only_into) == "table" then dig_only_into = table.reverse(dig_only_into) end
+	while x >= 1 and x <= self.data_w and y >= 1 and y <= self.data_h and self.data[y][x] ~= stop_at do
+		if not dig_only_into or (type(dig_only_into) == "table" and dig_only_into[self.data[y][x]]) or (type(dig_only_into) == "function" and dig_only_into(x, y, self.data[y][x])) then 
+			self.data[y][x] = char
+		end
+		x, y = x + dx, y + dy
+	end
+end
+
+--- Carve out a simple rectangle
+function _M:carveArea(char, from, to, dig_only_into)
+	if type(dig_only_into) == "table" then dig_only_into = table.reverse(dig_only_into) end
+	for x = from.x, to.x do for y = from.y, to.y do
+		if x >= 1 and x <= self.data_w and y >= 1 and y <= self.data_h then
+			if not dig_only_into or (type(dig_only_into) == "table" and dig_only_into[self.data[y][x]]) or (type(dig_only_into) == "function" and dig_only_into(x, y, self.data[y][x])) then
+				self.data[y][x] = char
+			end
+		end
+	end end
+end
+
+--- Apply a function over an area
+function _M:applyArea(from, to, fct)
+	for x = from.x, to.x do for y = from.y, to.y do
+		if x >= 1 and x <= self.data_w and y >= 1 and y <= self.data_h then
+			local ret = fct(x, y, self.data[y][x])
+			if ret ~= nil then
+				self.data[y][x] = ret
+			end
+		end
+	end end
+end
+
+--- Get the results
+-- @param is_array if true returns a table[][] of characters, if false a table[] of string lines
+function _M:getResult(is_array)
+	if not self.data then return nil end
+	if is_array then return self.data end
+	local data = {}
+	for y = 1, self.data_h do data[y] = table.concat(self.data[y]) end
+	return data
+end
+
+--- Debug function to print the result to the log
+function _M:printResult()
+	if not self.data then
+		print("-------------")
+		print("------------- Tilemap result")		
+		return
+	end
+	print("------------- Tilemap result --[[")
+	for _, line in ipairs(self:getResult()) do
+		print(line)
+	end
+	print("]]-----------")
+end
+
+--- Merge an other Tilemap's data
+function _M:merge(x, y, tm, char_order, empty_char)
+	if tm.unmergable then error("Trying to merge an unmergable tilemap, likely to be a Proxy") end
+
+	if type(x) == "table" then -- If passed a point, shift the parameters
+		x, y, tm, char_order, empty_char = x.x, x.y, y, tm, char_order
+	end
+
+	if not self.data or not tm.data then return end
+	-- if x is a table it's a point data so we shift parameters
+	if type(x) == "table" then
+		x, y, tm, char_order, empty_char = x.x, x.y, y, tm, char_order
+	end
+
+	x = math.floor(x)
+	y = math.floor(y)
+	
+	char_order = table.reverse(char_order or {})
+	
+	empty_char = empty_char or {' '}
+	if type(empty_char) == "string" then empty_char = {empty_char} end
+	empty_char = table.reverse(empty_char)
+
+	if not tm.data then return end
+
+	for i = 1, tm.data_w do
+		for j = 1, tm.data_h do
+			local si, sj = i + x - 1, j + y - 1
+			if si >= 1 and si <= self.data_w and sj >= 1 and sj <= self.data_h then
+				local c = tm.data[j][i]
+				if not empty_char[c] then
+					local sc = self.data[sj][si]
+					local sc_o = char_order[sc] or 0
+					local c_o = char_order[c] or 0
+
+					if c_o >= sc_o then
+						self.data[sj][si] = tm.data[j][i]
+					end
+				end
+			end
+		end
+	end
+	tm:mergedAt(x, y)
+end
+
+function _M:mergedAt(x, y)
+	self.merged_pos = self.merged_pos + self:point(x, y) - 1
+end
+
+function _M:findExits(pos, kind, exitable_chars)
+	local list = {}
+
+	if type(exitable_chars) == "string" then exitable_chars = {exitable_chars} end
+	exitable_chars = exitable_chars or {'.', ';'}
+	exitable_chars = table.reverse(exitable_chars)
+
+	local function checkexits(i, j, dir)
+		local c = self.data[j][i]
+		if exitable_chars[c] then
+			local dist = core.fov.distance(pos.x, pos.y, i, j)
+			table.insert(list, {dist = dist, pos = self:point(i, j) + self.merged_pos - 1, kind = "open"})
+		end
+	end
+
+	local possibles = {}
+
+	-- Find all possible exits on each sides, but "dig up" inside if the outer layer is just ' '
+	local p, j
+	for i = 1, self.data_w do
+		for j = 1, self.data_h / 2 do
+			p = self:point(i, j); p.dir = 8
+			if self.data[j][i] ~= ' ' then possibles[p:hashCoord(self)] = p break end
+		end
+
+		for j = self.data_h, self.data_h / 2, -1 do
+			p = self:point(i, j); p.dir = 2
+			if self.data[j][i] ~= ' ' then possibles[p:hashCoord(self)] = p break end
+		end
+	end
+	for j = 1, self.data_h do
+		for i = 1, self.data_w / 2 do
+			p = self:point(i, j); p.dir = 4
+			if self.data[j][i] ~= ' ' then possibles[p:hashCoord(self)] = p break end
+		end
+
+		for i = self.data_w, self.data_w / 2, -1 do
+			p = self:point(i, j); p.dir = 6
+			if self.data[j][i] ~= ' ' then possibles[p:hashCoord(self)] = p break end
+		end
+	end
+
+	for _, p in pairs(possibles) do checkexits(p.x, p.y, p.dir) end
+
+	table.sort(list, "dist")
+	return list
+end
+
+function _M:findClosestExit(pos, kind, exitable_chars)
+	local list = self:findExits(pos, kind, exitable_chars)
+	if #list == 0 then return nil end
+	local c = list[1]
+	return c.pos, c.kind, c.dist
+end
+
+function _M:findFurthestExit(pos, kind, exitable_chars)
+	local list = self:findExits(pos, kind, exitable_chars)
+	if #list == 0 then return nil end
+	local c = list[#list]
+	return c.pos, c.kind, c.dist
+end
+
+function _M:findRandomClosestExit(nb, pos, kind, exitable_chars)
+	local list = self:findExits(pos, kind, exitable_chars)
+	if #list == 0 then return nil end
+	local c = list[rng.range(1, math.min(#list, nb))]
+	return c.pos, c.kind, c.dist
+end
+
+function _M:findRandomFurthestExit(nb, pos, kind, exitable_chars)
+	local list = self:findExits(pos, kind, exitable_chars)
+	if #list == 0 then return nil end
+	local c = list[rng.range(math.max(1, #list-nb+1), #list)]
+	return c.pos, c.kind, c.dist
+end
+
+function _M:findRandomExit(pos, kind, exitable_chars)
+	local list = self:findExits(pos, kind, exitable_chars)
+	if #list == 0 then return nil end
+	local c = rng.table(list)
+	return c.pos, c.kind, c.dist
+end
+
+function _M:smartDoor(pos, chance, door_char, walls)
+	if pos.x <= 1 or pos.y <= 1 or pos.x >= self.data_w or pos.y >= self.data_h then return false end
+
+	walls = self:makeCharsTable(walls, {'#','⍓'})
+	
+	local c8 = self.data[pos.y-1][pos.x]
+	local c2 = self.data[pos.y+1][pos.x]
+	local c4 = self.data[pos.y][pos.x-1]
+	local c6 = self.data[pos.y][pos.x+1]
+
+	if (walls[c8] and walls[c2]) and not walls[c4] and not walls[c6] and rng.percent(chance) then
+		if door_char then self.data[pos.y][pos.x] = door_char end
+		return true
+	elseif (walls[c4] and walls[c6]) and not walls[c2] and not walls[c8] and rng.percent(chance) then
+		if door_char then self.data[pos.y][pos.x] = door_char end
+		return true
+	else
+		return false
+	end
+end
+
+------------------------------------------------------------------------------
+-- Simple tunneling
+------------------------------------------------------------------------------
+
+function _M:initTunneling()
+	if not self.tunnels_map then
+		self.tunnels_map = self:makeData(self.data_w, self.data_h, false)
+		self.tunnels_next_id = 'a'
+	end
+	local id = self.tunnels_next_id
+	self.tunnels_next_id = string.char(string.byte(self.tunnels_next_id) + 1)
+	return id
+end
+
+--- Random tunnel dir (no diagonals)
+function _M:tunnelRandDir(sx, sy)
+	local dirs = util.primaryDirs() --{4,6,8,2}
+	return util.dirToCoord(dirs[rng.range(1, #dirs)], sx, sy)
+end
+
+--- Find the direction in which to tunnel (no diagonals)
+function _M:tunnelDir(x1, y1, x2, y2)
+	-- HEX TODO ?
+	local xdir = (x1 == x2) and 0 or ((x1 < x2) and 1 or -1)
+	local ydir = (y1 == y2) and 0 or ((y1 < y2) and 1 or -1)
+	if xdir ~= 0 and ydir ~= 0 then
+		if rng.percent(50) then xdir = 0
+		else ydir = 0
+		end
+	end
+	return xdir, ydir
+end
+
+--- Marks a tunnel as a tunnel and the space behind it
+function _M:tunnelMark(x, y, xdir, ydir, id)
+	x, y = x - xdir, y - ydir
+	local dir = util.coordToDir(xdir, ydir, x, y)
+	local sides = util.dirSides(dir, x, y)
+	local mark_dirs = {dir, sides.left, sides.right}
+	for i, d in ipairs(mark_dirs) do
+		local xd, yd = util.dirToCoord(d, x, y)
+		if self:isBound(x+xd, y+yd) and not self.tunnels_map[y+yd][x+xd] then 
+			self.tunnels_map[y+yd][x+xd] = id
+			-- print("mark tunnel", x+xd, y+yd , id)
+		end
+	end
+	if not self.tunnels_map[y][x] then
+		self.tunnels_map[y][x] = id
+		-- print("mark tunnel", x, y , id)
+	end
+end
+
+--- Tunnel between two points
+-- @param x1, y1 starting coordinates
+-- @param x2, y2 ending coordinates
+-- @param id tunnel id
+-- @param virtual set true to mark the tunnel without changing terrain
+function _M:tunnel(from, to, char, tunnel_through, tunnel_avoid, config, virtual)
+	local x1, y1, x2, y2 = from.x, from.y, to.x, to.y
+	config = config or {}
+	config.tunnel_change = config.tunnel_change or 60
+	config.tunnel_random = config.tunnel_random or 7
+
+	char = char or '.'
+	tunnel_through = table.reverse(tunnel_through or {'ALL'})
+	tunnel_avoid = table.reverse(tunnel_avoid or {'⍓'})
+
+	local id = self:initTunneling()
+
+	if x1 == x2 and y1 == y2 then return end
+	-- Disable the many prints of tunnelling
+	-- local print = function()end
+
+	local xdir, ydir = self:tunnelDir(x1, y1, x2, y2)
+	-- print("tunneling from",x1, y1, "to", x2, y2, "initial dir", xdir, ydir)
+
+	local startx, starty = x1, y1
+	local tun = {}
+
+	local tries = 2000
+	local no_move_tries = 0
+	while tries > 0 do
+		if rng.percent(config.tunnel_change) then
+			if rng.percent(config.tunnel_random) then xdir, ydir = self:tunnelRandDir(x1, x2)
+			else xdir, ydir = self:tunnelDir(x1, y1, x2, y2)
+			end
+		end
+
+		local nx, ny = x1 + xdir, y1 + ydir
+		while true do
+			if self:isBound(nx, ny) then break end
+
+			if rng.percent(config.tunnel_random) then xdir, ydir = self:tunnelRandDir(nx, ny)
+			else xdir, ydir = self:tunnelDir(x1, y1, x2, y2)
+			end
+			nx, ny = x1 + xdir, y1 + ydir
+		end
+		-- print(feat, "try pos", nx, ny, "dir", util.coordToDir(xdir, ydir, nx, ny))
+		local nc = self.data[ny][nx]
+
+		if tunnel_avoid[nc] then
+			if nx == from.x and ny == from.y then
+				tun[#tun+1] = {nx,ny}
+				x1, y1 = nx, ny
+				-- print(feat, "accept avoid (start)", nc)
+			elseif nx == to.x and ny == to.y then
+				tun[#tun+1] = {nx,ny}
+				x1, y1 = nx, ny
+				-- print(feat, "accept avoid (end)", nc)
+			else
+				-- print(feat, "reject avoid", nc)
+				if nx == x2 and ny == y2 then -- stop if next to special target
+					x1, y1 = nx, ny
+					-- print(feat, "end adjacent to special target")
+				end
+			end
+		-- elseif nc.can_open ~= nil then
+		-- 	if nc.can_open then
+		-- 		print(feat, "tunnel crossing can_open", nx,ny)
+		-- 		for _, coord in pairs(util.adjacentCoords(nx, ny)) do
+		-- 			if self:isBound(coord[1], coord[2]) then
+		-- 				self.map.room_map[coord[1]][coord[2]].can_open = false
+		-- 				print(feat, "forbidding crossing at ", coord[1], coord[2])
+		-- 			end
+		-- 		end
+		-- 		tun[#tun+1] = {nx,ny,true}
+		-- 		x1, y1 = nx, ny
+		-- 		print(feat, "accept can_open")
+		-- 	else
+		-- 		print(feat, "reject can_open")
+		-- 	end
+		elseif self.tunnels_map[ny][nx] then
+			if no_move_tries >= 15 then
+				tun[#tun+1] = {nx,ny}
+				x1, y1 = nx, ny
+				-- print(feat, "accept tunnel", nc, id)
+			else
+				-- print(feat, "reject tunnel", nc, id)
+			end
+		elseif tunnel_through[nc] or tunnel_through.ALL then
+			tun[#tun+1] = {nx,ny}
+			x1, y1 = nx, ny
+			-- print(feat, "accept normal", nc)
+		else
+			tun[#tun+1] = {nx,ny,"ignore"}
+			x1, y1 = nx, ny
+			-- print(feat, "reject normal", nc)
+		end
+
+		if x1 == nx and y1 == ny then
+			self:tunnelMark(x1, y1, xdir, ydir, id)
+			no_move_tries = 0
+		else
+			no_move_tries = no_move_tries + 1
+		end
+
+		if x1 == x2 and y1 == y2 then break end
+
+		tries = tries - 1
+	end
+
+	local doors = {}
+	self.possible_doors = self.possible_doors or {}
+	for _, t in ipairs(tun) do
+		local nx, ny = t[1], t[2]
+		-- if t[3] and self.data.door then self.possible_doors[#self.possible_doors+1] = t end
+		if t[3] ~= "ignore" and not virtual then
+			-- print("=======TUNN", nx, ny)
+			-- self.map(nx, ny, Map.TERRAIN, self:resolve('=') or self:resolve('.') or self:resolve('floor'))
+			self:put(self:point(nx, ny), char)
+		end
+	end
+end
+
+------------------------------------------------------------------------------
+-- A* tunneling
+------------------------------------------------------------------------------
+
+--- The default heuristic for A*, tries to come close to the straight path
+-- @int sx
+-- @int sy
+-- @int cx
+-- @int cy
+-- @int tx
+-- @int ty
+local function heuristicCloserPath(sx, sy, cx, cy, tx, ty, config)
+	local h
+	-- Chebyshev  distance
+	h = math.max(math.abs(tx - cx), math.abs(ty - cy))
+
+	-- tie-breaker rule for straighter paths
+	local dx1 = cx - tx
+	local dy1 = cy - ty
+	local dx2 = sx - tx
+	local dy2 = sy - ty
+	return h + 0.01*math.abs(dx1*dy2 - dx2*dy1) + rng.float(-config.erraticness, config.erraticness)
+end
+
+--- A simple heuristic for A*, using distance
+-- @int sx
+-- @int sy
+-- @int cx
+-- @int cy
+-- @int tx
+-- @int ty
+local function heuristicDistance(sx, sy, cx, cy, tx, ty)
+	return core.fov.distance(cx, cy, tx, ty) + rng.float(-config.erraticness, config.erraticness)
+end
+
+--- Converts x & y into a single value
+-- @see astarToDouble
+-- @int x
+-- @int y
+function _M:astarToSingle(x, y)
+	return x + y * self.data_w
+end
+
+--- Converts a single value back into x & y
+-- @see astarToSingle
+-- @int c
+function _M:astarToDouble(c)
+	local y = math.floor(c / self.data_w)
+	return c - y * self.data_w, y
+end
+
+--- Create Path
+-- @param came_from
+-- @param cur
+function _M:astarCreatePath(came_from, from, to, cur, id, char, config, tunnel_through)
+	if not came_from[cur] then return end
+	local rpath, path = {}, {}
+	while came_from[cur] do
+		local x, y = self:astarToDouble(cur)
+		rpath[#rpath+1] = self:point(x, y)
+		if not config.virtual and (tunnel_through.ALL or tunnel_through[self.data[y][x]]) then self:put(self:point(x, y), char) end
+		self.tunnels_map[y][x] = id
+		cur = came_from[cur]
+	end
+
+	-- First one
+	rpath[#rpath+1] = from
+	if not config.virtual then self:put(from, char) end
+	self.tunnels_map[from.y][from.x] = id
+
+	-- Last one
+	rpath[#rpath+1] = to
+	if not config.virtual then self:put(to, char) end
+	self.tunnels_map[to.y][to.x] = id
+
+	for i = #rpath, 1, -1 do path[#path+1] = rpath[i] end
+	return path
+end
+
+--- Compute path from sx/sy to tx/ty
+function _M:tunnelAStar(from, to, char, tunnel_through, tunnel_avoid, config)
+	print("ASTAR Tunnel from ", from, "to", to)
+	local sx, sy, tx, ty = from.x, from.y, to.x, to.y
+	config = config or {}
+
+	char = char or '.'
+	tunnel_through = table.reverse(tunnel_through or {'ALL'})
+	tunnel_avoid = table.reverse(tunnel_avoid or {'⍓'})
+
+	local id = self:initTunneling()
+
+	if sx == tx and sy == ty then return end
+
+	config.weights = config.weights or {}
+	config.weight_erraticness = config.weight_erraticness or 0
+	config.erraticness = config.erraticness or 9
+	config.weight_tunnel = config.weight_tunnel or 20
+	if type(config.forbid_diagonals) == "nil" then config.forbid_diagonals = true end
+
+	local heur = config.heuristic or heuristicCloserPath
+	local w, h = self.data_w, self.data_h
+	local start = self:astarToSingle(sx, sy)
+	local stop = self:astarToSingle(tx, ty)
+	local open = {[start]=true}
+	local closed = {}
+	local g_score = {[start] = 0}
+	local h_score = {[start] = heur(sx, sy, sx, sy, tx, ty, config)}
+	local f_score = {[start] = heur(sx, sy, sx, sy, tx, ty, config)}
+	local came_from = {}
+
+	if not self:isBound(sx, sy) or not self:isBound(tx, ty) then
+		print("Astar fail: source/destination unreachable")
+		return nil
+	end
+	local checkPos = function(node, nx, ny)
+		local npos = self:point(nx, ny)
+		local nnode = self:astarToSingle(nx, ny)
+		if not closed[nnode] and self:isBound(nx, ny) and (
+		   (
+		   	npos == from or npos == to -- Always allow on start & stop
+		   )
+		   or
+		   (
+		   	(not tunnel_avoid[self.data[ny][nx]]) and -- Avoid tunneling in
+		   	(not config.add_check or config.add_check(nx, ny)) -- Extra checks
+		   )
+		) then
+			local nc = self.data[ny][nx]
+			local score = 1
+			if config.weight_erraticness > 0 then score = score + rng.float(0, config.weight_erraticness) end
+			if config.weights[nc] then score = score + config.weights[nc] end
+			if self.tunnels_map[ny][nx] then score = score + config.weight_tunnel end
+			local tent_g_score = g_score[node] + score -- we can adjust here for difficult passable terrain
+			local tent_is_better = false
+			if not open[nnode] then open[nnode] = true; tent_is_better = true
+			elseif tent_g_score < g_score[nnode] then tent_is_better = true
+			end
+
+			if tent_is_better then
+				came_from[nnode] = node
+				g_score[nnode] = tent_g_score
+				h_score[nnode] = heur(sx, sy, tx, ty, nx, ny, config)
+				f_score[nnode] = g_score[nnode] + h_score[nnode]
+			end
+		end
+	end
+
+	while next(open) do
+		-- Find lowest of f_score
+		local node, lowest = nil, 999999999999999
+		local n, _ = next(open)
+		while n do
+			if f_score[n] < lowest then node = n; lowest = f_score[n] end
+			n, _ = next(open, n)
+		end
+
+		if node == stop then return self:astarCreatePath(came_from, from, to, stop, id, char, config, tunnel_through) end
+
+		open[node] = nil
+		closed[node] = true
+		local x, y = self:astarToDouble(node)
+
+		-- Check sides
+		for _, coord in pairs(util.adjacentCoords(x, y, config.forbid_diagonals)) do
+			checkPos(node, coord[1], coord[2])
+		end
+	end
+end
diff --git a/game/engines/default/engine/tilemaps/WaveFunctionCollapse.lua b/game/engines/default/engine/tilemaps/WaveFunctionCollapse.lua
new file mode 100644
index 0000000000000000000000000000000000000000..09445b89c075c528dc34b8e4387a2a645860122c
--- /dev/null
+++ b/game/engines/default/engine/tilemaps/WaveFunctionCollapse.lua
@@ -0,0 +1,95 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Tilemap = require "engine.tilemaps.Tilemap"
+
+--- Generate map-like data from samples using the WaveFunctionCollapse algorithm (in C++)
+-- @classmod engine.tilemaps.WaveFunctionCollapse
+module(..., package.seeall, class.inherit(Tilemap))
+
+--- Run the algorithm
+-- It will produce internal results which this class can then manipulate
+-- If async is true in the parameters the generator will run in an asynchronous thread and you must call :waitCompute()
+-- before using the results, this allows you to run multiple WFCs for later merging wihtout taking more time (if the user have enoguh CPUs obviously)
+function _M:init(t)
+	assert(t.mode == "overlapping", "bad WaveFunctionCollapse mode")
+	assert(t.size, "WaveFunctionCollapse has no size")
+
+	Tilemap.init(self)
+
+	self:setSize(t.size[1], t.size[2], ' ')
+
+	if t.mode == "overlapping" then
+		if type(t.sample) == "string" then
+			t.sample = self:collapseToLineFormat(self:tmxLoad(t.sample))
+		end
+		self:run(t)
+	end
+end
+
+--- Used internally to parse the results
+function _M:parseResult(data)
+	print("[WaveFunctionCollapse] compute done", data)
+	if not data then return end
+	self.data = {}
+	for y = 1, #data do
+		local x = 1
+		self.data[y] = {}
+		for c in data[y]:gmatch('.') do
+			self.data[y][x] = c
+			x = x + 1
+		end
+	end
+end
+
+--- Called by the constructor to actaully start doing stuff
+function _M:run(t)
+	print("[WaveFunctionCollapse] running with parameter table:")
+	table.print(t)
+	if not t.async then
+		local data = core.generator.wfc.overlapping(t.sample, t.size[1], t.size[2], t.n, t.symmetry, t.periodic_out, t.periodic_in, t.has_foundation)
+		self:parseResult(data)
+		return false
+	else
+		self.async_data = core.generator.wfc.asyncOverlapping(t.sample, t.size[1], t.size[2], t.n, t.symmetry, t.periodic_out, t.periodic_in, t.has_foundation)
+		return true
+	end
+end
+
+--- Wait for computation to finish if in async mode, if not it just returns immediately
+function _M:waitCompute()
+	if not self.async_data then return end
+	local data = self.async_data:wait()
+	self.async_data = nil
+
+	self:parseResult(data)	
+	return self:hasResult()
+end
+
+--- Wait for multiple WaveFunctionCollapse at once
+-- Static
+function _M:waitAll(...)
+	local all_have_data = true
+	for _, wfcasync in ipairs{...} do
+		wfcasync:waitCompute()
+		if not wfcasync:hasResult() then all_have_data = false end -- We cant break, we need to wait all the threads to not leave them dangling in the wind
+	end
+	return all_have_data
+end
diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua
index b4739d43c30a604550583b8c0a3439e15c0e594f..50be20969d4493aadbe2479af18240d2454d16aa 100644
--- a/game/engines/default/engine/utils.lua
+++ b/game/engines/default/engine/utils.lua
@@ -89,6 +89,15 @@ function ripairs(t)
 	end
 end
 
+function table.empty(t)
+	while next(t) do t[next(t)] = nil end
+end
+
+function table.replaceWith(t, nt)
+	table.empty(t)
+	for k, e in pairs(nt) do t[k] = e end
+end
+
 function table.count(t)
 	local i = 0
 	for k, v in pairs(t) do
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 65ef082ba9a2495aaf7df1975e9d30c0e3456407..c95912a50410070d91dff77a73b5fd37fe05da7d 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -1996,7 +1996,11 @@ function _M:setupCommands()
 			print("===============")
 		end end,
 		[{"_g","ctrl"}] = function() if config.settings.cheat then
-			self:changeLevel(1, "cults+godfeaster")
+			if self.zone.short_name ~= "test" then
+				self:changeLevel(1, "test")
+			else
+				self:changeLevel(game.level.level + 1)
+			end
 do return end
 			local m = game.zone:makeEntity(game.level, "actor", {name="elven mage"}, nil, true)
 			local x, y = util.findFreeGrid(game.player.x, game.player.y, 20, true, {[Map.ACTOR]=true})
diff --git a/game/modules/tome/data/mapscripts/lib/connect_rooms_linear.lua b/game/modules/tome/data/mapscripts/lib/connect_rooms_linear.lua
new file mode 100644
index 0000000000000000000000000000000000000000..936dd98166b330b2cc87f8d0e6bb5f9e2af056f5
--- /dev/null
+++ b/game/modules/tome/data/mapscripts/lib/connect_rooms_linear.lua
@@ -0,0 +1,41 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+local map = args.map
+local rooms = map:sortListCenter(args.rooms)
+
+local full = true
+for i, room in ipairs(rooms) do
+	if i > 1 then
+		local proom = rooms[i-1]
+		local pos1, kind1 = proom:findRandomClosestExit(7, room:centerPoint(), nil, args.exitable_chars or {'.', ';', '='})
+		local pos2, kind2 = room:findRandomClosestExit(7, proom:centerPoint(), nil, args.exitable_chars or {'.', ';', '='})
+		if pos1 and pos2 then
+			map:tunnelAStar(pos1, pos2, args.tunnel_char or '.', args.tunnel_through or {'#'}, args.tunnel_avoid or nil, {erraticness=args.erraticness or 5})
+			if kind1 == 'open' then map:smartDoor(pos1, args.door_chance or 40, '+') end
+			if kind2 == 'open' then map:smartDoor(pos2, args.door_chance or 40, '+') end
+		else
+			full = false
+		end
+
+	end
+	-- map:carveArea(string.char(string.byte('0')+i-1), room.merged_pos, room.merged_pos + room.data_size - 1)
+end
+
+return full
diff --git a/game/modules/tome/data/mapscripts/lib/connect_rooms_multi.lua b/game/modules/tome/data/mapscripts/lib/connect_rooms_multi.lua
new file mode 100644
index 0000000000000000000000000000000000000000..b414170d0ce28fe87f36e96163064845306bb9f3
--- /dev/null
+++ b/game/modules/tome/data/mapscripts/lib/connect_rooms_multi.lua
@@ -0,0 +1,69 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+
+-- This file uses Kruskals algorithm to find a MST(minimum spanning tree) in a graph of rooms
+
+local MST = require "engine.algorithms.MST"
+
+local max_links = args.max_links or 3
+local map = args.map
+local rooms = args.rooms
+
+if #rooms <= 1 then return true end -- Easy !
+
+local mstrun = MST.new()
+
+-- Generate all possible edges
+for i, room in ipairs(rooms) do
+	local c = room:centerPoint()
+	for j, proom in ipairs(rooms) do if proom ~= room then
+		local c1, c2 = room:centerPoint(), proom:centerPoint()
+		mstrun:edge(room, proom, core.fov.distance(c1.x, c1.y, c2.x, c2.y))
+	end end
+end
+
+-- Compute!
+mstrun:run()
+
+-- Add some more randomly selected edges
+mstrun:fattenRandom(args.edges_surplus or 0)
+
+-- Draw the paths
+local full = true
+for _, edge in pairs(mstrun.mst) do
+	local pos1, kind1
+	local pos2, kind2
+	if args.from_center then
+		pos1, kind1 = edge.to:centerPoint(), 'open'
+		pos2, kind2 = edge.from:centerPoint(), 'open'
+	else
+		pos1, kind1 = edge.from:findRandomClosestExit(7, edge.to:centerPoint(), nil, args.exitable_chars or {'.', ';', '='})
+		pos2, kind2 = edge.to:findRandomClosestExit(7, edge.from:centerPoint(), nil, args.exitable_chars or {'.', ';', '='})
+	end
+	if pos1 and pos2 then
+		map:tunnelAStar(pos1, pos2, args.tunnel_char or '.', args.tunnel_through or {'#'}, args.tunnel_avoid or nil, {erraticness=args.erraticness or 5})
+		if kind1 == 'open' then map:smartDoor(pos1, args.door_chance or 40, '+') end
+		if kind2 == 'open' then map:smartDoor(pos2, args.door_chance or 40, '+') end
+	else
+		full = false
+	end
+end
+
+return full
diff --git a/game/modules/tome/data/rooms/pod.lua b/game/modules/tome/data/rooms/pod.lua
index 0dfb6ac8b97c6cdf4c868f5c63099cb20b38d891..0a2fc0bdfe501e9ec94c97acbba04b70cb4c9c6c 100644
--- a/game/modules/tome/data/rooms/pod.lua
+++ b/game/modules/tome/data/rooms/pod.lua
@@ -19,7 +19,7 @@
 
 return function(gen, id)
 	local w = rng.table{4,6,8,10}
-	local h = h
+	local h = w
 	local function make_pod(self, x, y, is_lit)
 		gen:makePod(x + w / 2, y + h / 2, w, id)
 	end
diff --git a/game/modules/tome/data/zones/test/grids.lua b/game/modules/tome/data/zones/test/grids.lua
index 53c2b6457ab2d3847bc0e177acce411fd87a6098..c1838f78d71b1c60e54420b0de2d5baf2880879f 100644
--- a/game/modules/tome/data/zones/test/grids.lua
+++ b/game/modules/tome/data/zones/test/grids.lua
@@ -19,3 +19,5 @@
 
 load("/data/general/grids/basic.lua")
 load("/data/general/grids/forest.lua")
+load("/data/general/grids/water.lua")
+load("/data/general/grids/jungle_hut.lua")
diff --git a/game/modules/tome/data/zones/test/mapscripts/bsp_islands.lua b/game/modules/tome/data/zones/test/mapscripts/bsp_islands.lua
new file mode 100644
index 0000000000000000000000000000000000000000..b85796e4080080a917627abd3c2fc859a95a9698
--- /dev/null
+++ b/game/modules/tome/data/zones/test/mapscripts/bsp_islands.lua
@@ -0,0 +1,43 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+local BSP = require "engine.tilemaps.BSP"
+
+-- rng.seed(2)
+
+local tm = Tilemap.new(self.mapsize, '=', 1)
+
+local bsp = BSP.new(10, 10, 2):make(50, 50, nil, '=')
+
+for _, room in ipairs(bsp.rooms) do
+	local pond = Heightmap.new(1.6, {up_left=0, down_left=0, up_right=0, down_right=0, middle=1}):make(room.map.data_w, room.map.data_h, {' ', ' ', ';', ';', 'T', ';', ';', ';'})
+	-- Ensure exit from the lake to exterrior
+	local pond_exit = pond:findRandomExit(pond:centerPoint(), nil, {';'})
+	pond:tunnelAStar(pond:centerPoint(), pond_exit, ';', {'T'}, {}, {erraticness=9})
+	-- If lake is big enough and we find a spot, place it
+	if pond:eliminateByFloodfill{'T', ' '} < 8 then return self:regenerate() end
+
+	room.map:merge(1, 1, pond)
+end
+
+tm:merge(1, 1, bsp)
+
+-- if tm:eliminateByFloodfill{'T','#'} < 800 then return self:regenerate() end
+
+return tm
diff --git a/game/modules/tome/data/zones/test/mapscripts/cavernous_forest.lua b/game/modules/tome/data/zones/test/mapscripts/cavernous_forest.lua
new file mode 100644
index 0000000000000000000000000000000000000000..4dfdaf227228e3fa51e3e07c8cc20be59a798b0a
--- /dev/null
+++ b/game/modules/tome/data/zones/test/mapscripts/cavernous_forest.lua
@@ -0,0 +1,38 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+-- rng.seed(2)
+
+local tm = Tilemap.new(self.mapsize, '#')
+
+local noise = Noise.new("simplex", 0.2, 4, 12, 1):make(50, 50, {'T', 'T', ';', ';', '=', '='})
+
+-- Eliminate water ponds that are too small
+noise:applyOnGroups(noise:findGroupsOf{'='}, function(room, idx)
+	if #room.list < 18 then noise:fillGroup(room, ';') end
+	-- room.map:carveArea('#', room.map:point(1, 1), room.map.data_size)
+end)
+
+-- MAKE MINIMUM SPANNING TREE A GENERIC ALGORITHM IN UTIL
+
+tm:merge(1, 1, noise)
+
+-- if tm:eliminateByFloodfill{'T','#'} < 800 then return self:regenerate() end
+
+return tm
diff --git a/game/modules/tome/data/zones/test/mapscripts/inner_outer.lua b/game/modules/tome/data/zones/test/mapscripts/inner_outer.lua
new file mode 100644
index 0000000000000000000000000000000000000000..9c1478f3c81b588aaa0dba8211115d3fa6a21b07
--- /dev/null
+++ b/game/modules/tome/data/zones/test/mapscripts/inner_outer.lua
@@ -0,0 +1,112 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+local merge_order = {'.', '_', 'r', '+', '#', 'O', ';', '=', 'T'}
+
+-- Water & trees layer
+local wfcwater = WaveFunctionCollapse.new{
+	mode="overlapping", async=true,
+	sample=self:getFile("!wfctest2.tmx", "samples"),
+	size={self.mapsize.w/3, self.mapsize.h},
+	n=3, symmetry=8, periodic_out=true, periodic_in=true, has_foundation=false
+}
+-- Outer buildings
+local wfcouter = WaveFunctionCollapse.new{
+	mode="overlapping", async=true,
+	sample=self:getFile("!wfctest.tmx", "samples"),
+	size={self.mapsize.w/3, self.mapsize.h},
+	n=3, symmetry=8, periodic_out=true, periodic_in=true, has_foundation=false
+}
+-- Inner buildings
+local wfcinner = WaveFunctionCollapse.new{
+	mode="overlapping", async=true,
+	sample=self:getFile("!wfctest5.tmx", "samples"),
+	size={self.mapsize.w/2, self.mapsize.h-2},
+	n=3, symmetry=8, periodic_out=false, periodic_in=false, has_foundation=false
+}
+
+-- Load predrawn stuff
+local doorway = Static.new(self:getFile("!door.tmx", "samples"))
+local doorwaytunnel = doorway:locateTile('E', '.')
+
+-- Wait for all generators to finish
+if not WaveFunctionCollapse:waitAll(wfcinner, wfcwater, wfcouter) then print("[inner_outer] a WFC failed") return self:regenerate() end
+
+-- Merge them all
+local tm = Tilemap.new(self.mapsize)
+wfcouter:merge(1, 1, wfcwater, merge_order)
+if wfcouter:eliminateByFloodfill{'#', 'T'} < 400 then print("[inner_outer] outer is too small") return self:regenerate() end
+if wfcinner:eliminateByFloodfill{'#', 'T'} < 400 then print("[inner_outer] inner is too small") return self:regenerate() end
+tm:merge(1, 1, wfcouter, merge_order)
+tm:merge(self.mapsize.w - wfcinner.data_w, 2, wfcinner, merge_order)
+
+-- Place the doorway and tunnel from it to the dungeon part on the right
+local doorwaypos = tm:point(self.mapsize.w / 3, (self.mapsize.h - doorway.data_h) / 2)
+tm:merge(doorwaypos.x, doorwaypos.y, doorway)
+tm:carveLinearPath('.', doorwaytunnel + doorwaypos, 6, '.')
+
+-- Find rooms
+local rooms = tm:findGroupsOf{'r'}
+local noroomforest = true
+tm:applyOnGroups(rooms, function(room, idx)
+	local p1, p2, rw, rh = tm:groupOuterRectangle(room)
+	print("ROOM", idx, "::", rw, rh, "=>", rw * rh)
+	tm:fillGroup(room, '.')
+	if noroomforest and rw >= 8 and rh >= 8 then
+		local pond = Heightmap.new(1.6, {up_left=0, down_left=0, up_right=0, down_right=0, middle=1}):make(rw-2, rh-2, {' ', ';', ';', 'T', '=', '='})
+		pond:printResult()
+		-- tm:fillGroup(room, ';')
+		tm:merge(p1.x+1, p1.y+1, pond)
+		noroomforest = false
+	end
+
+	self:addSpot(p1.x + rw/2, p1.y + rh/2, "spawn-spot", "spawn-spot")
+
+	-- table.print()
+	for j = 1, #room.list do
+		local jn = room.list[j]
+		-- data[jn.y][jn.x] = tostring(idx)
+	end
+end)
+if noroomforest then return self:regenerate() end
+
+-- Complete the map by putting wall in all the remaining blank spaces
+tm:fillAll()
+
+-- Elimitate the rest
+if tm:eliminateByFloodfill{'#', 'T'} < 400 then return self:regenerate() end
+
+tm:printResult()
+
+-- print('---==============---')
+-- local noise = Noise.new(nil, 0.5, 2, 3, 6):make(80, 50, {'T', 'T', '=', '=', '=', ';', ';'})
+-- noise:printResult()
+-- print('---==============---')
+-- print('---==============---')
+-- local pond = Heightmap.new(1.9, {up_left=0, down_left=0, up_right=0, down_right=0, middle=1}):make(30, 30, {';', 'T', '=', '=', ';'})
+-- pond:printResult()
+-- print('---==============---')
+-- print('---==============---')
+-- local maze = Maze.new():makeSimple(31, 31, '.', {'#','T'}, true)
+-- maze:printResult()
+-- print('---==============---')
+
+-- DGDGDGDG: make at least Tilemap handlers for BSP, roomer (single room), roomers and correctly handle up/down stairs
+
+return tm
diff --git a/game/modules/tome/data/zones/test/mapscripts/rooms_test.lua b/game/modules/tome/data/zones/test/mapscripts/rooms_test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..94a0f8485727d3a94c6a5c4d7f538b8b3571c651
--- /dev/null
+++ b/game/modules/tome/data/zones/test/mapscripts/rooms_test.lua
@@ -0,0 +1,84 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+-- rng.seed(2)
+
+local tm = Tilemap.new(self.mapsize, '#')
+
+-- self.data.greater_vaults_list = {"32-chambers"}
+local room_factory = Rooms.new(self, "random_room")
+
+local rooms = {}
+for i = 1, 20 do
+	local proom = room_factory:generateRoom()
+	local pos = tm:findRandomArea(nil, tm.data_size, proom.data_w, proom.data_h, '#', 1)
+	if pos then
+		tm:merge(pos, proom:build())
+		rooms[#rooms+1] = proom
+	end
+end
+
+local up_stairs = true
+for i = 1, 20 do
+	-- Make a little lake
+	local r = rng.range(7, 15)
+	local pond = Heightmap.new(1.6, {up_left=0, down_left=0, up_right=0, down_right=0, middle=1}):make(r, r, {' ', ' ', ';', ';', 'T', '=', '=', up_stairs and '<' or '='})
+	-- Ensure exit from the lake to exterrior
+	local pond_exit = pond:findRandomExit(pond:centerPoint(), nil, {';'})
+	pond:tunnelAStar(pond:centerPoint(), pond_exit, ';', {'T'}, {}, {erraticness=9})
+	-- If lake is big enough and we find a spot, place it
+	if pond:eliminateByFloodfill{'T', ' '} > 8 then
+		local pos = tm:findRandomArea(nil, tm.data_size, pond.data_w, pond.data_h, '#', 1)
+		if pos then
+			tm:merge(pos, pond)
+			rooms[#rooms+1] = pond
+			up_stairs = false
+		end
+	end
+end
+
+if not loadMapScript("lib/connect_rooms_multi", {map=tm, rooms=rooms, edges_surplus=0}) then return self:regenerate() end
+-- loadMapScript("lib/connect_rooms_multi", {map=tm, rooms=rooms})
+
+
+self:setEntrance(tm:locateTile('<'))
+self:setExit(rooms[#rooms]:centerPoint()) tm:put(rooms[#rooms]:centerPoint(), '>')
+
+-- Elimitate the rest
+-- if tm:eliminateByFloodfill{'#', 'T'} < 600 then return self:regenerate() end
+
+tm:printResult()
+
+
+-- print('---==============---')
+-- local noise = Noise.new(nil, 0.5, 2, 3, 6):make(80, 50, {'T', 'T', '=', '=', '=', ';', ';'})
+-- noise:printResult()
+-- print('---==============---')
+-- print('---==============---')
+-- local pond = Heightmap.new(1.9, {up_left=0, down_left=0, up_right=0, down_right=0, middle=1}):make(30, 30, {';', 'T', '=', '=', ';'})
+-- pond:printResult()
+-- print('---==============---')
+-- print('---==============---')
+-- local maze = Maze.new():makeSimple(31, 31, '.', {'#','T'}, true)
+-- maze:printResult()
+-- print('---==============---')
+
+-- DGDGDGDG: make at least Tilemap handlers for BSP, roomer (single room), roomers and correctly handle up/down stairs
+
+return tm
diff --git a/game/modules/tome/data/zones/test/mapscripts/testroom.lua b/game/modules/tome/data/zones/test/mapscripts/testroom.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ce258495d5bfa0d37adf7efc2d309bb2bbe31367
--- /dev/null
+++ b/game/modules/tome/data/zones/test/mapscripts/testroom.lua
@@ -0,0 +1,49 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+-- Merge them all
+local tm = Tilemap.new(self.mapsize, '#')
+tm:carveArea(';', tm:point(1, 1), tm:point(4, 10))
+tm:carveArea('T', tm:point(15, 3), tm:point(15, 16))
+tm:carveArea(';', tm:point(30, 1), tm:point(35, 10))
+
+-- tm:tunnel(tm:point(1, 10), tm:point(36, 10), ';', nil, {'T'}, {tunnel_change=60, tunnel_random=5})
+-- tm:tunnelAStar(tm:point(1, 10), tm:point(36, 1), '=', nil, {'T'}, {})
+tm:tunnelAStar(tm:point(1, 1), tm:point(36, 10), '.', nil, {'T'}, {})
+-- tm:tunnelAStar(tm:point(1, 30), tm:point(36, 30), '.', nil, {}, {})
+
+tm:printResult()
+
+
+-- print('---==============---')
+-- local noise = Noise.new(nil, 0.5, 2, 3, 6):make(80, 50, {'T', 'T', '=', '=', '=', ';', ';'})
+-- noise:printResult()
+-- print('---==============---')
+-- print('---==============---')
+-- local pond = Heightmap.new(1.9, {up_left=0, down_left=0, up_right=0, down_right=0, middle=1}):make(30, 30, {';', 'T', '=', '=', ';'})
+-- pond:printResult()
+-- print('---==============---')
+-- print('---==============---')
+-- local maze = Maze.new():makeSimple(31, 31, '.', {'#','T'}, true)
+-- maze:printResult()
+-- print('---==============---')
+
+-- DGDGDGDG: make at least Tilemap handlers for BSP, roomer (single room), roomers and correctly handle up/down stairs
+
+return tm
diff --git a/game/modules/tome/data/zones/test/mapscripts/testroom2.lua b/game/modules/tome/data/zones/test/mapscripts/testroom2.lua
new file mode 100644
index 0000000000000000000000000000000000000000..5365ae0dcecac9be7b7c25c47de9baf76772fea0
--- /dev/null
+++ b/game/modules/tome/data/zones/test/mapscripts/testroom2.lua
@@ -0,0 +1,75 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009 - 2018 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+local BSP = require "engine.tilemaps.BSP"
+local MST = require "engine.algorithms.MST"
+
+-- rng.seed(1)
+
+local tm = Tilemap.new(self.mapsize, '=', 1)
+
+local bsp = BSP.new(4, 4, 10):make(50, 50, '.', '#')
+
+-- Remove a few rooms
+for i = 1, #bsp.rooms / 4 do
+	local room = rng.tableRemove(bsp.rooms)
+	room.map:carveArea('#', room.map:point(1, 1), room.map.data_size)
+end
+
+local mstrun = MST.new()
+
+-- Generate all possible edges
+for i, room1 in ipairs(bsp.rooms) do
+	local c = room1.map:centerPoint()
+	for j, room2 in ipairs(bsp.rooms) do if room1 ~= room2 then
+		local c1, c2 = room1.map:centerPoint(), room2.map:centerPoint()
+		mstrun:edge(room1, room2, core.fov.distance(c1.x, c1.y, c2.x, c2.y))
+	end end
+end
+
+-- Compute!
+mstrun:run()
+
+for _, edge in pairs(mstrun.mst) do
+	if edge.from.from.x - 1 == edge.to.to.x + 1 then
+		local min_y, max_y = math.max(edge.from.from.y, edge.to.from.y), math.min(edge.from.to.y, edge.to.to.y)
+		bsp:put(bsp:point(edge.from.from.x - 1, rng.range(min_y, max_y)), rng.percent(40) and '+' or '.')
+	elseif edge.from.to.x + 1 == edge.to.from.x - 1 then
+		local min_y, max_y = math.max(edge.from.from.y, edge.to.from.y), math.min(edge.from.to.y, edge.to.to.y)
+		bsp:put(bsp:point(edge.from.to.x + 1, rng.range(min_y, max_y)), rng.percent(40) and '+' or '.')
+	elseif edge.from.from.y - 1 == edge.to.to.y + 1 then
+		local min_x, max_x = math.max(edge.from.from.x, edge.to.from.x), math.min(edge.from.to.x, edge.to.to.x)
+		bsp:put(bsp:point(rng.range(min_x, max_x), edge.from.from.y - 1), rng.percent(40) and '+' or '.')
+	elseif edge.from.to.y + 1 == edge.to.from.y - 1 then
+		local min_x, max_x = math.max(edge.from.from.x, edge.to.from.x), math.min(edge.from.to.x, edge.to.to.x)
+		bsp:put(bsp:point(rng.range(min_x, max_x), edge.from.to.y + 1), rng.percent(40) and '+' or '.')
+	end
+end
+
+bsp:applyOnGroups(bsp:findGroupsOf{'.', '+'}, function(room, idx)
+	if room.map.data_size:area() < 10 then
+		room.map:carveArea('#', room.map:point(1, 1), room.map.data_size)
+	end
+end)
+-- if bsp:eliminateByFloodfill{'#'} < 10 then return self:regenerate() end
+
+tm:merge(1, 1, bsp)
+
+
+return tm
diff --git a/game/modules/tome/data/zones/test/npcs.lua b/game/modules/tome/data/zones/test/npcs.lua
index 69f526b28719907139074bd6ffd497b2bad1bfc0..96628dd6cd0ed39164fe60462fc5399783127a9e 100644
--- a/game/modules/tome/data/zones/test/npcs.lua
+++ b/game/modules/tome/data/zones/test/npcs.lua
@@ -17,5 +17,4 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
---load("/data/zones/town-angolwen/npcs.lua", rarity(0))
---load("/data/zones/golem-graveyard/npcs.lua", rarity(0))
+load("/data/general/npcs/all.lua")
diff --git a/game/modules/tome/data/zones/test/samples/door.tmx b/game/modules/tome/data/zones/test/samples/door.tmx
new file mode 100644
index 0000000000000000000000000000000000000000..510b3fcb6102d78b2bb6fe78efe520f094526762
--- /dev/null
+++ b/game/modules/tome/data/zones/test/samples/door.tmx
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" tiledversion="1.0.3" orientation="orthogonal" renderorder="right-down" width="6" height="11" tilewidth="32" tileheight="32" nextobjectid="31">
+ <properties>
+  <property name="status_all" value="{no_teleport=true}"/>
+ </properties>
+ <tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90" columns="9">
+  <image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
+  <tile id="0">
+   <properties>
+    <property name="id" value="#"/>
+   </properties>
+  </tile>
+  <tile id="3">
+   <properties>
+    <property name="id" value="+"/>
+   </properties>
+  </tile>
+  <tile id="51">
+   <properties>
+    <property name="id" value="."/>
+   </properties>
+  </tile>
+  <tile id="54">
+   <properties>
+    <property name="id" value="_"/>
+   </properties>
+  </tile>
+  <tile id="58">
+   <properties>
+    <property name="id" value="r"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144" columns="8">
+  <image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
+  <tile id="24">
+   <properties>
+    <property name="id" value=";"/>
+   </properties>
+  </tile>
+  <tile id="85">
+   <properties>
+    <property name="id" value="E"/>
+   </properties>
+  </tile>
+  <tile id="102">
+   <properties>
+    <property name="id" value="T"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36" columns="6">
+  <image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
+ </tileset>
+ <layer name="Terrain" width="6" height="11">
+  <data encoding="base64" compression="zlib">
+   eJwrZmBgOAjEjGgYmxg+8WIccsVQzALEJkg0TJwRygfRGwiop6U7Ac8WDHE=
+  </data>
+ </layer>
+</map>
diff --git a/game/modules/tome/data/zones/test/samples/wfctest.tmx b/game/modules/tome/data/zones/test/samples/wfctest.tmx
new file mode 100644
index 0000000000000000000000000000000000000000..74dd827bb4a776ffd9cd68121612c545b06be781
--- /dev/null
+++ b/game/modules/tome/data/zones/test/samples/wfctest.tmx
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="right-down" width="16" height="16" tilewidth="32" tileheight="32" nextobjectid="31">
+ <properties>
+  <property name="status_all" value="{no_teleport=true}"/>
+ </properties>
+ <tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90">
+  <image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
+  <tile id="0">
+   <properties>
+    <property name="id" value="#"/>
+   </properties>
+  </tile>
+  <tile id="3">
+   <properties>
+    <property name="id" value="+"/>
+   </properties>
+  </tile>
+  <tile id="51">
+   <properties>
+    <property name="id" value="_"/>
+   </properties>
+  </tile>
+  <tile id="54">
+   <properties>
+    <property name="id" value="*"/>
+   </properties>
+  </tile>
+  <tile id="63">
+   <properties>
+    <property name="id" value="O"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144">
+  <image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
+  <tile id="22">
+   <properties>
+    <property name="id" value=";"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36">
+  <image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
+ </tileset>
+ <layer name="Terrain" width="16" height="16">
+  <data encoding="base64" compression="zlib">
+   eJxzYGBgcADiQiiNjLGJYRMvRMPYxHCJk6KfWDNJUUsN9xMKL3LCihz/kaN/KMhjC0N8YTrYzKck/mgdvwBZdmTy
+  </data>
+ </layer>
+</map>
diff --git a/game/modules/tome/data/zones/test/samples/wfctest2.tmx b/game/modules/tome/data/zones/test/samples/wfctest2.tmx
new file mode 100644
index 0000000000000000000000000000000000000000..0c4f7cf419d4d5739953c31e6da04b5e03e5062c
--- /dev/null
+++ b/game/modules/tome/data/zones/test/samples/wfctest2.tmx
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" tiledversion="1.0.3" orientation="orthogonal" renderorder="right-down" width="16" height="16" tilewidth="32" tileheight="32" nextobjectid="31">
+ <properties>
+  <property name="status_all" value="{no_teleport=true}"/>
+ </properties>
+ <tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90" columns="9">
+  <image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
+  <tile id="0">
+   <properties>
+    <property name="id" value="#"/>
+   </properties>
+  </tile>
+  <tile id="3">
+   <properties>
+    <property name="id" value="+"/>
+   </properties>
+  </tile>
+  <tile id="51">
+   <properties>
+    <property name="id" value="."/>
+   </properties>
+  </tile>
+  <tile id="54">
+   <properties>
+    <property name="id" value="*"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144" columns="8">
+  <image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
+  <tile id="22">
+   <properties>
+    <property name="id" value=";"/>
+   </properties>
+  </tile>
+  <tile id="36">
+   <properties>
+    <property name="id" value="T"/>
+   </properties>
+  </tile>
+  <tile id="52">
+   <properties>
+    <property name="id" value="="/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36" columns="6">
+  <image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
+ </tileset>
+ <layer name="Terrain" width="16" height="16">
+  <data encoding="base64" compression="zlib">
+   eJxjYKAfKETD5Ojph+AGUvT3Y8FDST+6GdjCD1/YFqLpw6W3HorxmY3LjfUMuPXjA+h21zPgt4sY/eS4gVL91AgDmBmk+B+XOcQCAL8wLm8=
+  </data>
+ </layer>
+</map>
diff --git a/game/modules/tome/data/zones/test/samples/wfctest3.tmx b/game/modules/tome/data/zones/test/samples/wfctest3.tmx
new file mode 100644
index 0000000000000000000000000000000000000000..aee128810c7729ba4bfbeb5a702560051a865608
--- /dev/null
+++ b/game/modules/tome/data/zones/test/samples/wfctest3.tmx
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="right-down" width="16" height="16" tilewidth="32" tileheight="32" nextobjectid="31">
+ <properties>
+  <property name="status_all" value="{no_teleport=true}"/>
+ </properties>
+ <tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90">
+  <image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
+  <tile id="0">
+   <properties>
+    <property name="id" value="#"/>
+   </properties>
+  </tile>
+  <tile id="3">
+   <properties>
+    <property name="id" value="+"/>
+   </properties>
+  </tile>
+  <tile id="27">
+   <properties>
+    <property name="id" value="#"/>
+   </properties>
+  </tile>
+  <tile id="51">
+   <properties>
+    <property name="id" value="."/>
+   </properties>
+  </tile>
+  <tile id="54">
+   <properties>
+    <property name="id" value="*"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144">
+  <image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
+ </tileset>
+ <tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36">
+  <image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
+ </tileset>
+ <layer name="Terrain" width="16" height="16">
+  <data encoding="base64" compression="zlib">
+   eJxjYKAOMMGCSdUrg4SJNQObXlLMQNbLiISRzSDGfpg+FiQ2sfZToh/ZDErigFZgNF0M7nQBAEC8Dvc=
+  </data>
+ </layer>
+</map>
diff --git a/game/modules/tome/data/zones/test/samples/wfctest4.tmx b/game/modules/tome/data/zones/test/samples/wfctest4.tmx
new file mode 100644
index 0000000000000000000000000000000000000000..993ceb9155b9c10c3456681b2a8bed79204690c9
--- /dev/null
+++ b/game/modules/tome/data/zones/test/samples/wfctest4.tmx
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" tiledversion="1.0.3" orientation="orthogonal" renderorder="right-down" width="20" height="20" tilewidth="32" tileheight="32" nextobjectid="31">
+ <properties>
+  <property name="status_all" value="{no_teleport=true}"/>
+ </properties>
+ <tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90" columns="9">
+  <image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
+  <tile id="0">
+   <properties>
+    <property name="id" value="#"/>
+   </properties>
+  </tile>
+  <tile id="3">
+   <properties>
+    <property name="id" value="+"/>
+   </properties>
+  </tile>
+  <tile id="51">
+   <properties>
+    <property name="id" value="."/>
+   </properties>
+  </tile>
+  <tile id="54">
+   <properties>
+    <property name="id" value="_"/>
+   </properties>
+  </tile>
+  <tile id="58">
+   <properties>
+    <property name="id" value="r"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144" columns="8">
+  <image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
+ </tileset>
+ <tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36" columns="6">
+  <image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
+ </tileset>
+ <layer name="Terrain" width="20" height="20">
+  <data encoding="base64" compression="zlib">
+   eJxjZGBgYARiEyiNDxOjBoSt0fBQMg9dDp99Q8U8fGFEDfNgmAWHeSxo6nDpxyZPyH349DLiEackDVHbPBYqmYctrHDFKblqBto85LjGZx56msOmhpi0hm43Kf4lFPekpiNqm0cNNQBZdyK0
+  </data>
+ </layer>
+</map>
diff --git a/game/modules/tome/data/zones/test/samples/wfctest5.tmx b/game/modules/tome/data/zones/test/samples/wfctest5.tmx
new file mode 100644
index 0000000000000000000000000000000000000000..6c13ee129e5773947d6b4f20fe4928f81aeacfa7
--- /dev/null
+++ b/game/modules/tome/data/zones/test/samples/wfctest5.tmx
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" tiledversion="1.0.3" orientation="orthogonal" renderorder="right-down" width="16" height="16" tilewidth="32" tileheight="32" nextobjectid="31">
+ <properties>
+  <property name="status_all" value="{no_teleport=true}"/>
+ </properties>
+ <tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90" columns="9">
+  <image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
+  <tile id="0">
+   <properties>
+    <property name="id" value="#"/>
+   </properties>
+  </tile>
+  <tile id="3">
+   <properties>
+    <property name="id" value="+"/>
+   </properties>
+  </tile>
+  <tile id="51">
+   <properties>
+    <property name="id" value="."/>
+   </properties>
+  </tile>
+  <tile id="54">
+   <properties>
+    <property name="id" value="_"/>
+   </properties>
+  </tile>
+  <tile id="58">
+   <properties>
+    <property name="id" value="r"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144" columns="8">
+  <image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
+ </tileset>
+ <tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36" columns="6">
+  <image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
+ </tileset>
+ <layer name="Terrain" width="16" height="16">
+  <data encoding="base64" compression="zlib">
+   eJxjZGBgYKQDtkbChOSR1ZlAMSX6KbUfWR06H59+EyzqWajgflx+wGa+CVSMWP3E+m+w6ccWfujqcIU9MfqxxSUx+k2I0Eus+2mBAexcFhw=
+  </data>
+ </layer>
+</map>
diff --git a/game/modules/tome/data/zones/test/zone.lua b/game/modules/tome/data/zones/test/zone.lua
index a52ef14710606900e221a557f55bc545c5cfe3b2..344cb52540076839ef580e943460353534069a26 100644
--- a/game/modules/tome/data/zones/test/zone.lua
+++ b/game/modules/tome/data/zones/test/zone.lua
@@ -24,16 +24,28 @@ return {
 	max_level = 4,
 	decay = {300, 800},
 	actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
-	width = 50, height = 50,
-	all_remembered = true,
+	width = 80, height = 80,
+	-- all_remembered = true,
 	all_lited = true,
 	no_level_connectivity = true,
 	
+	nicer_tiler_overlay = "DungeonWallsGrass",
 --	events_by_level = true,
 --	_max_level_generation_count = 2,
 	
 	generator =  {
 		map = {
+-- [[
+			class = "engine.generator.map.MapScript",
+			['<'] = "UP", ['>'] = "DOWN",
+			['.'] = "FLOOR", ['+'] = "DOOR", ['#'] = "WALL",
+			['_'] = "FLOOR", ['O'] = "WALL", 
+			[';'] = "GRASS", ['T'] = "TREE",
+			['='] = "DEEP_WATER",
+			-- mapscript = "!cavernous_forest",
+			mapscript = "!testroom2",
+			-- mapscript = "!inner_outer",
+--]]
 --[[
 			class = "engine.generator.map.Hexacle",
 			up = "FLOOR",
@@ -75,7 +87,7 @@ return {
 			down = "JUNGLE_GRASS_DOWN6",
 			door = "JUNGLE_GRASS",
 --]]
--- [[
+--[[
 			class = "engine.generator.map.Roomer",
 			nb_rooms = 10,
 			edge_entrances = {4,6},
@@ -91,8 +103,9 @@ return {
 --]]
 		},
 		actor = {
-			class = "mod.class.generator.actor.Random",
+			class = "engine.generator.actor.OnSpots",
 			nb_npc = {0, 0},
+			nb_spots = true, on_spot_chance = 90, spot_filters = {{type="spawn-spot"}},
 		},
 --[[
 		object = {
@@ -106,7 +119,7 @@ return {
 ]]
 	},
 	post_process = function(level) -- testing level generation failure
-		if level.level >=3 then level.force_recreate = true end
+		-- if level.level >=3 then level.force_recreate = true end
 	end,
 	
 --[[
diff --git a/game/modules/tome/dialogs/ShowMap.lua b/game/modules/tome/dialogs/ShowMap.lua
index dd38cd59293dfd8a0913ae1d91d9e590657d4fb7..8927dd28ce4f04087bcc47d1393bfacfe241b2d2 100644
--- a/game/modules/tome/dialogs/ShowMap.lua
+++ b/game/modules/tome/dialogs/ShowMap.lua
@@ -30,7 +30,7 @@ function _M:init(mm_mode)
 
 	self.ui = "parchment"
 
-	self.bsize = 22
+	self.bsize = 10
 	local map = game.level.map
 	local mw, mh = map.w * self.bsize, map.h * self.bsize
 	local Mw, Mh = math.floor(game.w * 0.9), math.floor(game.h * 0.9)
diff --git a/game/thirdparty/algorithms/LICENSE b/game/thirdparty/algorithms/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..7db3a0ef236052d11301bd3fc6ecdb61aa612043
--- /dev/null
+++ b/game/thirdparty/algorithms/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Xianshun Chen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/game/thirdparty/algorithms/binarysearch.lua b/game/thirdparty/algorithms/binarysearch.lua
new file mode 100644
index 0000000000000000000000000000000000000000..3fb6804aaabe46df26b2f33c2e2b998140c90b5b
--- /dev/null
+++ b/game/thirdparty/algorithms/binarysearch.lua
@@ -0,0 +1,31 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 29/6/2017
+-- Time: 3:39 PM
+-- To change this template use File | Settings | File Templates.
+--
+
+binarysearch = {}
+binarysearch.__index = binarysearch
+
+function binarysearch.indexOf(a, value, comparator)
+    local lo = 0
+    local hi = a:size() - 1
+    while lo <= hi do
+        local mid = lo + math.floor((hi - lo) / 2)
+        local cmp = comparator(value, a:get(mid))
+        if cmp < 0 then
+            hi = mid - 1
+        elseif cmp > 0 then
+            lo = mid + 1
+        else
+            return mid
+        end
+    end
+    return -1
+
+end
+
+return binarysearch
+
diff --git a/game/thirdparty/algorithms/data/hashmap.lua b/game/thirdparty/algorithms/data/hashmap.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ab76de606152cae8e8ae285d77f16a940ad618ba
--- /dev/null
+++ b/game/thirdparty/algorithms/data/hashmap.lua
@@ -0,0 +1,119 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 26/6/2017
+-- Time: 12:48 AM
+-- To change this template use File | Settings | File Templates.
+--
+
+local hashmap = {}
+
+hashmap.__index = hashmap
+
+hashmap.Node = {}
+hashmap.Node.__index = hashmap.Node
+
+function hashmap.Node.create(key, value)
+    local s = {}
+    setmetatable(s, hashmap.Node)
+
+    s.key = key
+    s.value = value
+    s.next = nil
+
+    return s
+end
+
+function hashmap.create(hash)
+    local s = {}
+    setmetatable(s, hashmap)
+
+    if hash == nil then
+        hash = function(x) return x end
+    end
+
+    s.s = {}
+    s.N = 0
+    s.hash = hash
+
+    return s
+end
+
+function hashmap:put(key, value)
+    local h = self.hash(key)
+    local x = self.s[h]
+    local found = false
+    while x ~= nil do
+        if x.key == key then
+            found = true
+            x.value = value
+            break
+        end
+        x = x.next
+    end
+
+    if found == false then
+        local old = self.s[h]
+        self.s[h] = hashmap.Node.create(key,value)
+        self.s[h].next = old
+        self.N = self.N + 1
+    end
+
+end
+
+function hashmap:get(key)
+    local h = self.hash(key)
+    local x = self.s[h]
+    while x ~= nil do
+        if x.key == key then
+            return x.value
+        end
+        x = x.next
+    end
+    return nil
+end
+
+function hashmap:containsKey(key)
+    local h = self.hash(key)
+    local x = self.s[h]
+    while x ~= nil do
+        if x.key == key then
+            return true
+        end
+        x = x.next
+    end
+    return false
+end
+
+function hashmap:size()
+    return self.N
+end
+
+function hashmap:isEmpty()
+    return self.N == 0
+end
+
+function hashmap:remove(key)
+    local h = self.hash(key)
+    local x = self.s[h]
+    local prev_x = nil
+    while x ~= nil do
+        if x.key == key then
+            local value = x.value
+            if prev_x == nil then
+                self.s[h] = x.next
+            else
+                prev_x.next = x.next
+            end
+            self.N = self.N - 1
+            return value
+        end
+        prev_x = x
+        x = x.next
+    end
+
+    return nil
+end
+
+return hashmap
+
diff --git a/game/thirdparty/algorithms/data/hashset.lua b/game/thirdparty/algorithms/data/hashset.lua
new file mode 100644
index 0000000000000000000000000000000000000000..157c7549a527b39675e5ccd96312fa6a2e07b678
--- /dev/null
+++ b/game/thirdparty/algorithms/data/hashset.lua
@@ -0,0 +1,103 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 28/6/2017
+-- Time: 1:17 PM
+-- To change this template use File | Settings | File Templates.
+--
+
+local hashset = {}
+
+hashset.__index = hashset
+
+hashset.Node = {}
+hashset.Node.__index = hashset.Node
+
+function hashset.Node.create(key)
+    local s = {}
+    setmetatable(s, hashset.Node)
+
+    s.key = key
+    s.next = nil
+
+    return s
+end
+
+function hashset.create(hash)
+    local s = {}
+    setmetatable(s, hashset)
+
+    if hash == nil then
+        hash = function(x) return x end
+    end
+
+    s.s = {}
+    s.N = 0
+    s.hash = hash
+
+    return s
+end
+
+function hashset:add(key)
+    local h = self.hash(key)
+    local x = self.s[h]
+    local found = false
+    while x ~= nil do
+        if x.key == key then
+            found = true
+            break
+        end
+        x = x.next
+    end
+
+    if found == false then
+        local old = self.s[h]
+        self.s[h] = hashset.Node.create(key)
+        self.s[h].next = old
+        self.N = self.N + 1
+    end
+
+end
+
+function hashset:contains(key)
+    local h = self.hash(key)
+    local x = self.s[h]
+    while x ~= nil do
+        if x.key == key then
+            return true
+        end
+        x = x.next
+    end
+    return false
+end
+
+function hashset:size()
+    return self.N
+end
+
+function hashset:isEmpty()
+    return self.N == 0
+end
+
+function hashset:remove(key)
+    local h = self.hash(key)
+    local x = self.s[h]
+    local prev_x = nil
+    while x ~= nil do
+        if x.key == key then
+            if prev_x == nil then
+                self.s[h] = x.next
+            else
+                prev_x.next = x.next
+            end
+            self.N = self.N - 1
+        end
+        prev_x = x
+        x = x.next
+    end
+
+    return nil
+end
+
+return hashset
+
diff --git a/game/thirdparty/algorithms/data/list.lua b/game/thirdparty/algorithms/data/list.lua
new file mode 100644
index 0000000000000000000000000000000000000000..37ccab00d2979f38248e4274074fa5f027ae5baa
--- /dev/null
+++ b/game/thirdparty/algorithms/data/list.lua
@@ -0,0 +1,145 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 25/6/2017
+-- Time: 10:13 AM
+-- To change this template use File | Settings | File Templates.
+--
+
+local list = {}
+
+list.ArrayList = {}
+list.ArrayList.__index = list.ArrayList
+
+function list.ArrayList.create()
+    local s = {}
+    setmetatable(s, list.ArrayList)
+
+    s.a = { nil }
+    s.aLen = 1
+    s.N = 0
+    return s
+end
+
+function list.ArrayList.createWith(a, aLen, N)
+    local s = {}
+    setmetatable(s, list.ArrayList)
+
+    s.a = a
+    s.aLen = aLen
+    s.N = N
+    return s
+end
+
+function list.create()
+    return list.ArrayList.create()
+end
+
+function list.createWith(a, aLen, N)
+    return list.ArrayList.createWith(a, aLen, N)
+end
+
+function list.ArrayList:makeCopy()
+    local temp = {}
+    for key,val in pairs(self.a) do
+        temp[key] = val
+    end
+    return list.ArrayList.createWith(temp, self.aLen, self.N)
+end
+
+function list.ArrayList:add(value)
+    self.a[self.N] = value
+    self.N = self.N + 1
+    if self.N == self.aLen then
+        self:resize(self.aLen * 2)
+    end
+end
+
+function list.ArrayList:set(index,value)
+    self.a[index] = value
+end
+
+function list.ArrayList:get(index)
+    local temp = self.a[index]
+    return temp
+end
+
+function list.ArrayList:removeAt(index)
+    if index == self.N-1 then
+        self.N = self.N - 1
+        return
+    end
+    for i = index+1,self.N-1 do
+        self.a[i-1]=self.a[i]
+    end
+    self.N = self.N - 1
+    if self.N == math.floor(self.aLen / 4) then
+        self:resize(math.floor(self.aLen / 2))
+    end
+
+end
+
+function list.ArrayList:indexOf(value)
+    if self.N == 0 then
+        return -1
+    end
+    for i=0,self.N-1 do
+        if self.a[i] == value then
+            return i
+        end
+    end
+    return -1
+end
+
+function list.ArrayList:remove(value)
+    local index = self:indexOf(value)
+    self:removeAt(index)
+end
+
+function list.ArrayList:resize(newSize)
+    local temp = {}
+    for i = 0,(newSize-1) do
+        temp[i] = self.a[i]
+    end
+
+    self.a = temp
+    self.aLen = newSize
+end
+
+function list.ArrayList:size()
+    return self.N
+end
+
+function list.ArrayList:isEmpty()
+    return self.N == 0
+end
+
+function list.ArrayList:enumerate()
+    local temp = {}
+    for i = 0,(self.N-1) do
+        temp[i] = self.a[i]
+    end
+    return temp
+end
+
+function list.ArrayList:isSortedAscendingly(comparator)
+    for i=0,(self:size()-2) do
+        if comparator(a:get(i), a:get(i+1)) > 0 then
+            return false
+        end
+
+    end
+    return true
+end
+
+function list.ArrayList:isSortedDescendingly(comparator)
+    for i=0,(self:size()-2) do
+        if comparator(a:get(i), a:get(i+1)) < 0 then
+            return false
+        end
+
+    end
+    return true
+end
+
+return list
diff --git a/game/thirdparty/algorithms/data/maxpq.lua b/game/thirdparty/algorithms/data/maxpq.lua
new file mode 100644
index 0000000000000000000000000000000000000000..16dc3ad8f410084dfcd7d15d1e5af07edf008b17
--- /dev/null
+++ b/game/thirdparty/algorithms/data/maxpq.lua
@@ -0,0 +1,109 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 25/6/2017
+-- Time: 11:33 PM
+-- To change this template use File | Settings | File Templates.
+--
+
+local maxpq = {}
+
+maxpq.__index = maxpq
+
+function maxpq.create(comparer)
+    local s = {}
+    setmetatable(s, maxpq)
+
+    s.s = { nil }
+    s.aLen = 1
+    s.N = 0
+    s.comparer = comparer
+
+    return s
+end
+
+function maxpq:enqueue(value)
+    if self.N + 1 == self.aLen then
+        self:resize(self.aLen * 2)
+    end
+    self.N = self.N + 1
+    self.s[self.N] = value
+    self:swim(self.N)
+end
+
+function maxpq:delMax()
+    if self.N == 0 then
+        return nil
+    end
+
+    local value = self.s[1]
+    self:exchange(self.s, 1, self.N)
+    self.N = self.N - 1
+    self:sink(1)
+    if self.N + 1 == math.floor(self.aLen / 4) then
+        self:resize(math.floor(self.aLen / 2))
+    end
+
+    return value
+
+end
+
+function maxpq:resize(newSize)
+    local temp = {}
+    for i = 0,(newSize-1) do
+        temp[i] = self.s[i]
+    end
+
+    self.s = temp
+    self.aLen = newSize
+end
+
+function maxpq:swim(k)
+    while k > 1 do
+
+        local parent = math.floor(k / 2)
+        if self:greater(self.s[k], self.s[parent]) then
+            self:exchange(self.s, k, parent)
+            k = parent
+        else
+            break
+        end
+    end
+end
+
+function maxpq:sink(k)
+    while k * 2 <= self.N do
+        local child = k * 2
+        if child < self.N and self:greater(self.s[child+1], self.s[child]) then
+            child = child + 1
+        end
+        if self:greater(self.s[child], self.s[k]) then
+            self:exchange(self.s, child, k)
+            k = child
+        else
+            break
+        end
+    end
+
+end
+
+function maxpq:size()
+    return self.N
+end
+
+function maxpq:isEmpty()
+    return self.N == 0
+end
+
+function maxpq:greater(a1, a2)
+    return self.comparer(a1, a2) > 0
+end
+
+function maxpq:exchange(s, i, j)
+    local temp = s[i]
+    s[i] = s[j]
+    s[j] = temp
+end
+
+return maxpq
+
diff --git a/game/thirdparty/algorithms/data/minpq.lua b/game/thirdparty/algorithms/data/minpq.lua
new file mode 100644
index 0000000000000000000000000000000000000000..b29d4714665da3049d9eaf2852d5e217cafa4854
--- /dev/null
+++ b/game/thirdparty/algorithms/data/minpq.lua
@@ -0,0 +1,109 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 25/6/2017
+-- Time: 11:33 PM
+-- To change this template use File | Settings | File Templates.
+--
+
+local minpq = {}
+
+minpq.__index = minpq
+
+function minpq.create(comparer)
+    local s = {}
+    setmetatable(s, minpq)
+
+    s.s = { nil }
+    s.aLen = 1
+    s.N = 0
+    s.comparer = comparer
+
+    return s
+end
+
+function minpq:enqueue(value)
+    if self.N + 1 == self.aLen then
+        self:resize(self.aLen * 2)
+    end
+    self.N = self.N + 1
+    self.s[self.N] = value
+    self:swim(self.N)
+end
+
+function minpq:delMin()
+    if self.N == 0 then
+        return nil
+    end
+
+    local value = self.s[1]
+    self:exchange(self.s, 1, self.N)
+    self.N = self.N - 1
+    self:sink(1)
+    if self.N + 1 == math.floor(self.aLen / 4) then
+        self:resize(math.floor(self.aLen / 2))
+    end
+
+    return value
+
+end
+
+function minpq:resize(newSize)
+    local temp = {}
+    for i = 0,(newSize-1) do
+        temp[i] = self.s[i]
+    end
+
+    self.s = temp
+    self.aLen = newSize
+end
+
+function minpq:swim(k)
+    while k > 1 do
+
+        local parent = math.floor(k / 2)
+        if self:less(self.s[k], self.s[parent]) then
+            self:exchange(self.s, k, parent)
+            k = parent
+        else
+            break
+        end
+    end
+end
+
+function minpq:sink(k)
+    while k * 2 <= self.N do
+        local child = k * 2
+        if child < self.N and self:less(self.s[child+1], self.s[child]) then
+            child = child + 1
+        end
+        if self:less(self.s[child], self.s[k]) then
+            self:exchange(self.s, child, k)
+            k = child
+        else
+            break
+        end
+    end
+
+end
+
+function minpq:size()
+    return self.N
+end
+
+function minpq:isEmpty()
+    return self.N == 0
+end
+
+function minpq:less(a1, a2)
+    return self.comparer(a1, a2) < 0
+end
+
+function minpq:exchange(s, i, j)
+    local temp = s[i]
+    s[i] = s[j]
+    s[j] = temp
+end
+
+return minpq
+
diff --git a/game/thirdparty/algorithms/data/queue.lua b/game/thirdparty/algorithms/data/queue.lua
new file mode 100644
index 0000000000000000000000000000000000000000..8a4a3d2ecd9c0bfd1bd8c647f58cb73decba97e6
--- /dev/null
+++ b/game/thirdparty/algorithms/data/queue.lua
@@ -0,0 +1,161 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 25/6/2017
+-- Time: 8:10 PM
+-- To change this template use File | Settings | File Templates.
+--
+
+local queue = {}
+
+queue.LinkedListQueue = {}
+queue.LinkedListQueue.__index = queue.LinkedListQueue
+
+queue.Node = {}
+queue.Node.__index = queue.Node
+
+function queue.Node.create(value)
+    local s = {}
+    setmetatable(s, queue.Node)
+
+    s.value = value
+    s.next = nil
+
+    return s
+end
+
+function queue.LinkedListQueue.create()
+    local s = {}
+    setmetatable(s, queue.LinkedListQueue)
+
+    s.first = nil
+    s.last = nil
+    s.N = 0
+
+    return s
+end
+
+function queue.create()
+    return queue.LinkedListQueue.create()
+end
+
+function queue.LinkedListQueue:enqueue(value)
+    local oldLast = self.last
+    self.last = queue.Node.create(value)
+    if oldLast ~= nil then
+        oldLast.next = self.last
+    end
+    if self.first == nil then
+        self.first = self.last
+    end
+    self.N = self.N + 1
+end
+
+function queue.LinkedListQueue:dequeue()
+    local oldFirst = self.first
+    if oldFirst == nil then
+        return nil
+    end
+    self.first = oldFirst.next
+    local value = oldFirst.value
+    if self.first == nil then
+        self.last = nil
+    end
+    self.N = self.N - 1
+    return value
+end
+
+function queue.LinkedListQueue:size()
+    return self.N
+end
+
+function queue.LinkedListQueue:isEmpty()
+    return self.N == 0
+end
+
+function queue.LinkedListQueue:enumerate()
+    local index = 0
+    local temp = {}
+    local x = self.first
+    while x ~= nil do
+        local value = x.value
+        temp[index] = value
+        index = index + 1
+        x = x.next
+    end
+
+    return temp
+
+end
+
+queue.ArrayQueue = {}
+queue.ArrayQueue.__index = queue.ArrayQueue
+
+function queue.ArrayQueue.create()
+    local s = {}
+    setmetatable(s, queue.ArrayQueue)
+
+    s.head = 0
+    s.tail = 0
+    s.a = { nil }
+    s.aLen = 0
+
+    return s
+end
+
+function queue.ArrayQueue:enqueue(value)
+    self.a[self.tail] = value
+    self.tail = self.tail + 1
+    if self.tail - self.head == self.aLen then
+        self:resize(self.aLen * 2)
+    end
+
+end
+
+function queue.ArrayQueue:size()
+    return self.tail - self.head
+end
+
+function queue.ArrayQueue:isEmpty()
+    return self.tail == self.head
+end
+
+function queue.ArrayQueue:dequeue(value)
+    if self.tail == self.head then
+        return nil
+    end
+
+    value = self.a[self.head]
+    self.head = self.head + 1
+    if self.tail - self.head == math.floor(self.aLen / 4) then
+        self:resize(math.floor(self.aLen / 2))
+    end
+
+    return value
+end
+
+function queue.ArrayQueue:resize(newSize)
+    local temp = {}
+    for i = 0,(newSize-1) do
+        if i < self.tail - self.head then
+            temp[i] = nil
+        else
+            temp[i] = self.a[i + self.head]
+        end
+    end
+
+    self.a = temp
+    self.aLen = newSize
+    self.tail = self.tail - self.head
+    self.head = 0
+end
+
+function queue.ArrayQueue:enumerate()
+    local temp = {}
+    for i = self.head,self.tail-1 do
+        temp[i - self.head] = self.a[i]
+    end
+    return temp
+end
+
+return queue
diff --git a/game/thirdparty/algorithms/data/redblacktree.lua b/game/thirdparty/algorithms/data/redblacktree.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ab96e4ceb231bebff740dcc65dacf0dcf322ae57
--- /dev/null
+++ b/game/thirdparty/algorithms/data/redblacktree.lua
@@ -0,0 +1,250 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 25/6/2017
+-- Time: 11:32 PM
+-- To change this template use File | Settings | File Templates.
+--
+
+local redblacktree = {}
+redblacktree.__index = redblacktree
+
+redblacktree.Node = {}
+redblacktree.Node.__index = redblacktree.Node
+
+function redblacktree.Node.create(key, value)
+    local s = {}
+    setmetatable(s, redblacktree.Node)
+
+    s.key = key
+    s.value = value
+    s.left = nil
+    s.right = nil
+    s.count = 0
+    s.red = true
+
+    return s
+end
+
+function redblacktree.create(comparator)
+    local s = {}
+    setmetatable(s, redblacktree)
+
+    if comparator == nil then
+        comparator = function(a1, a2) return a1 - a2 end
+    end
+
+    s.root = nil
+    s.N = 0
+    s.comparator = comparator
+
+    return s
+end
+
+function redblacktree:put(key, value)
+    self.root = self:_put(self.root, key, value)
+end
+
+function redblacktree:_put(x, key, value)
+    if x == nil then
+        x = redblacktree.Node.create(key, value)
+    end
+
+    local comp = self.comparator(key, x.key)
+    if comp < 0 then
+        x.left = self:_put(x.left, key, value)
+    elseif comp > 0 then
+        x.right = self:_put(x.right, key, value)
+    else
+        x.value = value
+    end
+
+    if self:isRed(x.left) == false and self:isRed(x.right) then
+        x = self:rotateLeft(x)
+    elseif self:isRed(x.left) and self:isRed(x.left.left) then
+        x= self:rotateRight(x)
+    elseif self:isRed(x.left) and self:isRed(x.right) then
+        self:flipColor(x)
+    end
+
+    x.count = self:count(x.left) + self:count(x.right) + 1
+    return x
+end
+
+function redblacktree:isRed(x)
+    if x == nil then
+        return false
+    end
+    return x.red
+end
+
+function redblacktree:flipColor(x)
+    x.left.red = false
+    x.right.red = false
+    x.red = true
+end
+
+function redblacktree:rotateLeft(x)
+    local h = x.right
+    x.right = h.left
+    h.left = x
+    x.red = h.red
+    h.red = false
+
+    x = h
+    x.count = self:count(x.left) + self:count(x.right) + 1
+
+    return x
+end
+
+function redblacktree:rotateRight(x)
+    local h = x.left
+    x.left = h.right
+    h.right = x
+    x.red = h.red
+    h.red = false
+
+    x = h
+    x.count = self:count(x.left) + self:count(x.right) + 1
+
+    return x
+end
+
+function redblacktree:count(x)
+    if x == nil then
+        return 0
+    end
+    return x.count
+end
+
+function redblacktree:get(key)
+    local x = self:_get(self.root, key)
+    if x == nil then
+        return nil
+    end
+
+    return x.value
+end
+
+function redblacktree:containsKey(key)
+    local x = self:_get(self.root, key)
+    return x ~= nil
+end
+
+function redblacktree:_get(x, key, value)
+    if x == nil then
+        return nil
+    end
+
+    local comp = self.comparator(key, x.key)
+    if comp < 0 then
+        return self:_get(x.left, key)
+    elseif comp > 0 then
+        return self:_get(x.right, key)
+    else
+        return x
+    end
+end
+
+function redblacktree:size()
+    return self:count(self.root)
+end
+
+function redblacktree:isEmpty()
+    return self:size() == 0
+end
+
+function redblacktree:remove(key)
+    self.root = self:_remove(self.root, key)
+end
+
+function redblacktree:_min(x)
+    if x.left == nil then
+        return x
+    end
+
+    return self:_min(x.left)
+end
+
+function redblacktree:_max(x)
+    if x.right == nil then
+        return x
+    end
+
+    return self:_max(x.right)
+end
+
+function redblacktree:_delMin(x)
+    if x.left == nil then
+        return x.right
+    end
+    x.left = self:_delMin(x.left)
+    return x
+end
+
+function redblacktree:_remove(x, key)
+    if x == nil then
+        return nil
+    end
+
+    local comp = self.comparator(key, x.key)
+    if comp < 0 then
+        x.left = self:_remove(x.left, key)
+    elseif comp > 0 then
+        x.right = self:_remove(x.right, key)
+    else
+        if x.left == nil then
+            x = x.right
+        elseif x.right == nil then
+            x = x.left
+        else
+            local m = self:_min(x.right)
+            m.left = x.left
+            m.right = self:_delMin(x.right)
+            x = m
+        end
+    end
+
+    if x ~= nil then
+        if self:isRed(x.left) == false and self:isRed(x.right) then
+            x = self:rotateLeft(x)
+        elseif self:isRed(x.left) and self:isRed(x.left.left) then
+            x= self:rotateRight(x)
+        elseif self:isRed(x.left) and self:isRed(x.right) then
+            self:flipColor(x)
+        end
+
+        x.count = self:count(x.left) + self:count(x.right) + 1
+    end
+
+    return x
+end
+
+function redblacktree:minKey()
+    if self.root == nil then
+        return nil
+    end
+
+    local x = self:_min(self.root)
+    if x ~= nil then
+        return x.key
+    end
+    return nil
+end
+
+function redblacktree:maxKey()
+    if self.root == nil then
+        return nil
+    end
+
+    local x= self:_max(self.root)
+
+    if x ~= nil then
+        return x.key
+    end
+    return nil
+end
+
+return redblacktree
+
+
diff --git a/game/thirdparty/algorithms/data/stack.lua b/game/thirdparty/algorithms/data/stack.lua
new file mode 100644
index 0000000000000000000000000000000000000000..f35c38572fe81a862269d1182b0ecb14408a23ae
--- /dev/null
+++ b/game/thirdparty/algorithms/data/stack.lua
@@ -0,0 +1,143 @@
+---
+--- Created by chen0.
+--- DateTime: 24/6/2017 1:50 PM
+---
+
+local stack = {}
+
+stack.Node = {}
+stack.Node.__index = stack.Node
+
+function stack.Node.create(value)
+    local s = {}
+    setmetatable(s, stack.Node)
+
+    s.value = value
+    s.next = nil
+
+    return s
+end
+
+stack.LinkedListStack = {}
+stack.LinkedListStack.__index = stack.LinkedListStack
+
+function stack.LinkedListStack.create()
+    local s = {}
+    setmetatable(s, stack.LinkedListStack)
+
+    s.first = nil
+    s.N = 0
+
+    return s
+end
+
+function stack.create()
+    return stack.LinkedListStack.create()
+end
+
+function stack.LinkedListStack:push(value)
+    local oldFirst = self.first
+    self.first = stack.Node.create(value)
+    self.first.next = oldFirst
+    self.N = self.N + 1
+end
+
+function stack.LinkedListStack:pop()
+    local oldFirst = self.first
+    if oldFirst == nil then
+        return nil
+    end
+    local value = oldFirst.value
+    self.first = oldFirst.next
+    self.N = self.N - 1
+    return value
+end
+
+function stack.LinkedListStack:size()
+    return self.N
+end
+
+function stack.LinkedListStack:isEmpty()
+    return self.N == 0
+end
+
+function stack.LinkedListStack:enumerate()
+    local x = self.first
+    local index = 0
+    local temp = {}
+    while x ~= nil do
+        temp[index] = x.value
+        index = index + 1
+        x = x.next
+    end
+
+    return temp
+
+end
+
+stack.ArrayStack = {}
+stack.ArrayStack.__index = stack.ArrayStack
+
+function stack.ArrayStack.create()
+    local s = {}
+    setmetatable(s, stack.ArrayStack)
+
+    s.a = { nil }
+    s.N = 0
+    s.aLen = 1
+
+    return s
+end
+
+function stack.ArrayStack:push(value)
+    self.a[self.N] = value
+    self.N = self.N + 1
+    if self.N == self.aLen then
+        self:resize(self.aLen * 2)
+    end
+end
+
+function stack.ArrayStack:resize(newSize)
+    local temp = {}
+    for i = 0,(newSize-1) do
+        temp[i] = self.a[i]
+    end
+
+    self.a = temp
+    self.aLen = newSize
+end
+
+function stack.ArrayStack:pop()
+    if self.N == 0 then
+        return nil
+    end
+
+    local value = self.a[self.N-1]
+    self.N = self.N - 1
+
+    if self.N == math.floor(self.aLen / 4) then
+        self:resize(math.floor(self.aLen / 2))
+    end
+    return value
+end
+
+function stack.ArrayStack:size()
+    return self.N
+end
+
+function stack.ArrayStack:isEmpty()
+    return self.N == 0
+end
+
+function stack.ArrayStack:enumerate()
+    local temp = {}
+    for i = 0,(self.N-1) do
+        temp[i] = self.a[i]
+    end
+    return temp
+end
+
+return stack
+
+
+
diff --git a/game/thirdparty/algorithms/shuffling.lua b/game/thirdparty/algorithms/shuffling.lua
new file mode 100644
index 0000000000000000000000000000000000000000..93baf6c5a8d7a2d65b094f15dcae6bea4ccf1ef4
--- /dev/null
+++ b/game/thirdparty/algorithms/shuffling.lua
@@ -0,0 +1,27 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 29/6/2017
+-- Time: 11:47 AM
+-- To change this template use File | Settings | File Templates.
+--
+
+local shuffling = {}
+
+function shuffling.shuffle(a)
+    local N = a:size()
+    for i=1,(N-1) do
+        local j = math.random(i+1) - 1
+        shuffling.exchange(a, i, j)
+    end
+
+end
+
+function shuffling.exchange(a, i, j)
+    local temp = a:get(i)
+    a:set(i, a:get(j))
+    a:set(j, temp)
+end
+
+return shuffling
+
diff --git a/game/thirdparty/algorithms/sorting/heapsort.lua b/game/thirdparty/algorithms/sorting/heapsort.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6b2a7f324bf458c70a4de7c539e49a58f8b0ce18
--- /dev/null
+++ b/game/thirdparty/algorithms/sorting/heapsort.lua
@@ -0,0 +1,54 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 29/6/2017
+-- Time: 10:30 AM
+-- To change this template use File | Settings | File Templates.
+--
+
+local heapsort = {}
+
+heapsort.__index = heapsort
+
+function heapsort.sort(a, comparator)
+    local N = a:size()
+    for i=(math.floor(N/2)),1,-1 do
+        heapsort._sink(a, i, N, comparator)
+    end
+
+    while N > 1 do
+        heapsort._exchange(a, heapsort.index(1), heapsort.index(N))
+        N = N - 1
+        heapsort._sink(a, 1, N, comparator)
+    end
+end
+
+function heapsort.index(i)
+    return i - 1
+end
+
+function heapsort._exchange(a, i, j)
+    local temp = a:get(i)
+    a:set(i, a:get(j))
+    a:set(j, temp)
+end
+
+function heapsort._sink(a, k, N, comparator)
+    while 2 * k <= N do
+        local child = k * 2
+        if child < N and comparator(a:get(heapsort.index(child+1)), a:get(heapsort.index(child))) > 0 then
+            child = child + 1
+        end
+        if comparator(a:get(heapsort.index(child)), a:get(heapsort.index(k))) > 0 then
+            heapsort._exchange(a, heapsort.index(child), heapsort.index(k))
+            k = child
+        else
+            break
+        end
+    end
+
+end
+
+
+return heapsort
+
diff --git a/game/thirdparty/algorithms/sorting/insertion.lua b/game/thirdparty/algorithms/sorting/insertion.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ee27b01eed05c5a0ddd7c0edc1e583b422922f1f
--- /dev/null
+++ b/game/thirdparty/algorithms/sorting/insertion.lua
@@ -0,0 +1,31 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 28/6/2017
+-- Time: 10:44 AM
+-- To change this template use File | Settings | File Templates.
+--
+
+insertion = {}
+insertion.__index = insertion
+
+function insertion.sort(a, comparator)
+    local N = a:size()
+    for i=1,(N-1) do
+        for j=(i-1),0,-1 do
+            if comparator(a:get(j+1), a:get(j)) < 0 then
+                insertion.exchange(a, j, j+1)
+            else
+                break
+            end
+        end
+    end
+end
+
+function insertion.exchange(a, i, j)
+    local temp = a:get(i)
+    a:set(i, a:get(j))
+    a:set(j, temp)
+end
+
+return insertion
diff --git a/game/thirdparty/algorithms/sorting/mergesort.lua b/game/thirdparty/algorithms/sorting/mergesort.lua
new file mode 100644
index 0000000000000000000000000000000000000000..90e66ce74595f4107a09730e3ceed413974a3ffb
--- /dev/null
+++ b/game/thirdparty/algorithms/sorting/mergesort.lua
@@ -0,0 +1,61 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 29/6/2017
+-- Time: 4:20 AM
+-- To change this template use File | Settings | File Templates.
+--
+
+local mergesort = {}
+mergesort.__index = mergesort
+
+function mergesort.sort(a, comparator)
+    local aux = require('lualgorithms.data.list').create()
+    for i=0,(a:size()-1) do
+        aux:add(0)
+    end
+    mergesort._sort(a, aux, 0, a:size()-1, comparator)
+end
+
+function mergesort._sort(a, aux, lo, hi, comparator)
+    if lo >= hi then
+        return
+    end
+
+    local mid = lo + math.floor((hi - lo) / 2)
+    mergesort._sort(a, aux, lo, mid, comparator)
+    mergesort._sort(a, aux, mid+1, hi, comparator)
+    mergesort._merge(a, aux, lo, mid, hi, comparator)
+end
+
+function mergesort._merge(a, aux, lo, mid, hi, comparator)
+    local i = lo
+    local j = mid+1
+    for k=lo,hi do
+        aux:set(k, a:get(k))
+    end
+
+    for k=lo,hi do
+        if i > mid then
+            a:set(k, aux:get(j))
+            j = j+1
+        elseif j > hi then
+            a:set(k, aux:get(i))
+            i = i + 1
+        else
+            local cmp = comparator(aux:get(i), aux:get(j))
+            if cmp <= 0 then
+                a:set(k, aux:get(i))
+                i = i + 1
+            else
+                a:set(k, aux:get(j))
+                j = j + 1
+            end
+        end
+    end
+
+
+end
+
+return mergesort
+
diff --git a/game/thirdparty/algorithms/sorting/quicksort.lua b/game/thirdparty/algorithms/sorting/quicksort.lua
new file mode 100644
index 0000000000000000000000000000000000000000..4616bdfd25ab7384fb3bdcc872645f947a94e229
--- /dev/null
+++ b/game/thirdparty/algorithms/sorting/quicksort.lua
@@ -0,0 +1,62 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 29/6/2017
+-- Time: 10:04 AM
+-- To change this template use File | Settings | File Templates.
+--
+
+local quicksort = {}
+quicksort.__index = quicksort
+
+function quicksort.sort(a, comparator)
+    quicksort._sort(a, 0, a:size()-1, comparator)
+end
+
+function quicksort._sort(a, lo, hi, comparator)
+    if lo >= hi then
+        return
+    end
+
+    local j = quicksort._partition(a, lo, hi, comparator)
+    quicksort._sort(a, lo, j-1, comparator)
+    quicksort._sort(a, j+1, hi, comparator)
+end
+
+function quicksort._partition(a, lo, hi, comparator)
+    local i = lo
+    local j = hi + 1
+    local v = a:get(lo)
+    while true do
+        i = i + 1
+        while comparator(a:get(i), v) < 0 do
+            i = i+1
+            if i >= hi then
+                break
+            end
+        end
+        j = j - 1
+        while comparator(v, a:get(j)) < 0 do
+            j = j - 1
+            if j <= lo then
+                break
+            end
+        end
+
+        if i >= j then
+            break
+        end
+
+        quicksort.exchange(a, i, j)
+    end
+    quicksort.exchange(a, lo, j)
+    return j
+end
+
+function quicksort.exchange(a, i, j)
+    local temp = a:get(i)
+    a:set(i, a:get(j))
+    a:set(j, temp)
+end
+
+return quicksort
diff --git a/game/thirdparty/algorithms/sorting/quicksort3ways.lua b/game/thirdparty/algorithms/sorting/quicksort3ways.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ca76fbb8efd69d56e9e3efd5ec159999605b277c
--- /dev/null
+++ b/game/thirdparty/algorithms/sorting/quicksort3ways.lua
@@ -0,0 +1,51 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 29/6/2017
+-- Time: 10:14 AM
+-- To change this template use File | Settings | File Templates.
+--
+
+local quicksort3ways = {}
+
+quicksort3ways.__index = quicksort3ways
+
+function quicksort3ways.sort(a, comparator)
+    quicksort3ways._sort(a, 0, a:size()-1, comparator)
+end
+
+function quicksort3ways._sort(a, lo, hi, comparator)
+    if lo >= hi then
+        return
+    end
+
+    local i = lo
+    local lt = lo
+    local gt = hi
+    local v = a:get(lo)
+    while i <= gt do
+        local cmp = comparator(a:get(i), v)
+        if cmp < 0 then
+            quicksort3ways.exchange(a, i, lt)
+            i = i + 1
+            lt = lt + 1
+        elseif cmp > 0 then
+            quicksort3ways.exchange(a, i, gt)
+            gt = gt - 1
+        else
+            i = i + 1
+        end
+    end
+
+    quicksort3ways._sort(a, lo, lt-1, comparator)
+    quicksort3ways._sort(a, gt+1, hi, comparator)
+end
+
+function quicksort3ways.exchange(a, i, j)
+    local temp = a:get(i)
+    a:set(i, a:get(j))
+    a:set(j, temp)
+end
+
+return quicksort3ways
+
diff --git a/game/thirdparty/algorithms/sorting/selection.lua b/game/thirdparty/algorithms/sorting/selection.lua
new file mode 100644
index 0000000000000000000000000000000000000000..493fa364577aab2235c49f49f5a8ec4d0e8eb91e
--- /dev/null
+++ b/game/thirdparty/algorithms/sorting/selection.lua
@@ -0,0 +1,35 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 27/6/2017
+-- Time: 9:59 PM
+-- To change this template use File | Settings | File Templates.
+--
+
+local selection = {}
+selection.__index = selection
+
+function selection.sort(a, comparer)
+    local N = a:size()
+
+    for i=0,(N-1) do
+        local J = i
+        for j=i,(N-1) do
+            if comparer(a:get(j), a:get(J)) < 0 then
+                J = j
+            end
+        end
+
+        selection.exchange(a, i, J)
+    end
+end
+
+function selection.exchange(a, i, j)
+    local temp = a:get(i)
+    a:set(i, a:get(j))
+    a:set(j, temp)
+end
+
+return selection
+
+
diff --git a/game/thirdparty/algorithms/sorting/shellsort.lua b/game/thirdparty/algorithms/sorting/shellsort.lua
new file mode 100644
index 0000000000000000000000000000000000000000..37cd8db7e6589acd8e1051d8282792c1950fd948
--- /dev/null
+++ b/game/thirdparty/algorithms/sorting/shellsort.lua
@@ -0,0 +1,40 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 28/6/2017
+-- Time: 1:07 PM
+-- To change this template use File | Settings | File Templates.
+--
+
+local shellsort = {}
+shellsort.__index = shellsort
+
+function shellsort.sort(a, comparator)
+    local N = a:size()
+    local h = 0
+    while h < math.floor(N / 3) do
+        h = h * 3 + 1
+    end
+
+    for step=h,1,-1 do
+        for i=step,(N-1) do
+            for j=i,step,-step do
+                if comparator(a:get(j), a:get(j-step)) < 0 then
+                    shellsort.exchange(a, j, j-step)
+                else
+                    break
+                end
+            end
+        end
+    end
+
+end
+
+function shellsort.exchange(a, i, j)
+    local temp = a:get(i)
+    a:set(i, a:get(j))
+    a:set(j, temp)
+end
+
+return shellsort
+
diff --git a/game/thirdparty/algorithms/tries/rwaytries.lua b/game/thirdparty/algorithms/tries/rwaytries.lua
new file mode 100644
index 0000000000000000000000000000000000000000..c4ef303e36d6c0189246525ffcc0091dd0288313
--- /dev/null
+++ b/game/thirdparty/algorithms/tries/rwaytries.lua
@@ -0,0 +1,152 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 29/6/2017
+-- Time: 5:10 PM
+-- To change this template use File | Settings | File Templates.
+--
+
+local rwaytries = {}
+
+rwaytries.__index = rwaytries
+
+rwaytries.Node = {}
+
+rwaytries.Node.__index = rwaytries.Node
+
+function rwaytries.charAt(s, i)
+    if string.len(s)-1 < i then
+        return -1
+    end
+
+    return string.byte(s, i+1)
+end
+
+function rwaytries.Node.create(key)
+    local s = {}
+    setmetatable(s, rwaytries.Node)
+
+    s.nodes = {}
+    s.value = nil
+    return s
+end
+
+function rwaytries.create()
+    local s = {}
+    setmetatable(s, rwaytries)
+
+    s.root = nil
+    s.N = 0
+    return s
+end
+
+function rwaytries:put(key, value)
+    self.root = self:_put(self.root, key, value, 0)
+end
+
+function rwaytries:_put(x, key, value, d)
+
+
+    if x == nil then
+        x = rwaytries.Node.create()
+    end
+
+    if d == key:len() then
+        if x.value == nil then
+            self.N = self.N + 1
+        end
+        x.value = value
+        return x
+    end
+
+    local c = rwaytries.charAt(key, d)
+    x.nodes[c] = self:_put(x.nodes[c], key, value, d + 1)
+
+    return x
+end
+
+function rwaytries:get(key)
+    local x = self:_get(self.root, key, 0)
+    if x ~= nil then
+        return x.value
+    end
+    return nil
+end
+
+function rwaytries:_get(x, key, d)
+    if x == nil then
+        return nil
+    end
+
+    if d == key:len() then
+        return x
+    end
+
+    local c = rwaytries.charAt(key, d)
+    return self:_get(x.nodes[c],key, d+1)
+end
+
+function rwaytries:remove(key)
+    local x = self:_get(self.root, key, 0)
+    if x ~= nil then
+        if x.value ~= nil then
+            self.N = self.N - 1
+        end
+        x.value = nil
+    end
+end
+
+
+
+function rwaytries:containsKey(key)
+    local x = self:_get(self.root, key, 0)
+    if x == nil then
+        return false
+    end
+
+    return x.value ~= nil
+end
+
+function rwaytries:size()
+    return self.N
+end
+
+function rwaytries:isEmpty()
+    return self.N == 0
+end
+
+function rwaytries:keys()
+    local queue = require('lualgorithms.data.list').create()
+
+    self:collect(self.root, '', queue)
+    return queue
+end
+
+function rwaytries:keysWithPrefix(prefix)
+    local x = self:_get(self.root, prefix, 0)
+
+    local queue = require('lualgorithms.data.list').create()
+
+    self:collect(x, prefix, queue)
+    return queue
+end
+
+function rwaytries:collect(x, prefix, queue)
+    if x == nil then
+        return
+    end
+
+    if x.value ~=nil then
+        queue:add(prefix)
+    end
+
+    for key,value in pairs(x.nodes) do
+        self:collect(value, prefix .. string.char(key), queue)
+    end
+
+end
+
+return rwaytries
+
+
+
diff --git a/game/thirdparty/algorithms/unionfind.lua b/game/thirdparty/algorithms/unionfind.lua
new file mode 100644
index 0000000000000000000000000000000000000000..c2488e1016516fa61a8f83ec49771b64997fd969
--- /dev/null
+++ b/game/thirdparty/algorithms/unionfind.lua
@@ -0,0 +1,49 @@
+--
+-- Created by IntelliJ IDEA.
+-- User: chen0
+-- Date: 29/6/2017
+-- Time: 4:49 PM
+-- To change this template use File | Settings | File Templates.
+--
+
+local unionfind = {}
+
+unionfind.__index = unionfind
+
+function unionfind.create()
+    local s = {}
+    setmetatable(s, unionfind)
+
+    s.id = {}
+    return s
+end
+
+function unionfind:union(v, w)
+    local pv = self:root(v)
+    local pw = self:root(w)
+    if pv ~= pw then
+        self.id[pv] = pw
+    end
+
+end
+
+function unionfind:root(v)
+    if self.id[v] == nil then
+        self.id[v] = v
+    end
+    local x = v
+    while x ~= self.id[x] do
+        self.id[x] = self.id[self.id[x]]
+        x = self.id[x]
+    end
+
+    return x
+
+end
+
+function unionfind:connected(v, w)
+    return self:root(v) == self:root(w)
+end
+
+return unionfind
+
diff --git a/game/thirdparty/licenses/lua-algorithms.txt b/game/thirdparty/licenses/lua-algorithms.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7db3a0ef236052d11301bd3fc6ecdb61aa612043
--- /dev/null
+++ b/game/thirdparty/licenses/lua-algorithms.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Xianshun Chen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/premake4.lua b/premake4.lua
index dc05ecba97e64ddf4750557013789a03ce05b710..5351f8e423e6983a89065920d78a879fb27255c5 100644
--- a/premake4.lua
+++ b/premake4.lua
@@ -71,9 +71,30 @@ configuration "windows"
  	}
 end
 
+function cppconfig(what)
+	links { "stdc++" }
+end
+
 configuration "macosx"
-	buildoptions { "-isysroot /Developer/SDKs/MacOSX10.6.sdk", "-mmacosx-version-min=10.6" }
+	premake.gcc.cc  = 'clang'
+	premake.gcc.cxx = 'clang++'
 
+	buildoptions { "-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk", "-mmacosx-version-min=10.7" }
+	function cppconfig(what)
+		if what == "web" then
+			buildoptions { "-stdlib=libstdc++" }
+			linkoptions { "-stdlib=libstdc++" }
+		else
+			buildoptions { "-stdlib=libc++" }
+			linkoptions { "-stdlib=libc++" }
+		end
+		links { "stdc++" }
+	end
+	includedirs {
+                        "/Library/Frameworks/SDL2.framework/Headers",
+                        "/Library/Frameworks/SDL2_image.framework/Headers",
+                        "/Library/Frameworks/SDL2_ttf.framework/Headers",
+	}
 
 configuration "Debug"
 	defines { }
diff --git a/src/main.c b/src/main.c
index 1353db4449f9d8f0f27f89fbcbdf10442ef0059b..023621e75dd2bfe765527500841b221860331947 100644
--- a/src/main.c
+++ b/src/main.c
@@ -36,6 +36,7 @@
 #include "physfs.h"
 #include "physfsrwops.h"
 #include "core_lua.h"
+#include "wfc/lua_wfc_external.h"
 #include "getself.h"
 #include "music.h"
 #include "serial.h"
@@ -1176,6 +1177,7 @@ void boot_lua(int state, bool rebooting, int argc, char *argv[])
 		luaopen_zlib(L);
 		luaopen_bit(L);
 		luaopen_wait(L);
+		luaopen_wfc(L);
 
 		physfs_reset_dir_allowed(L);
 
diff --git a/src/wfc/direction.hpp b/src/wfc/direction.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..46d2ea8be3dae70241cadfbbbb4ac8c47942cf3a
--- /dev/null
+++ b/src/wfc/direction.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+
+/**
+ * A direction is represented by an unsigned integer in the range [0; 3].
+ * The x and y values of the direction can be retrieved in these tables.
+ */
+constexpr int directions_x[4] = {0, -1, 1, 0};
+constexpr int directions_y[4] = {-1, 0, 0, 1};
+
+/**
+ * Return the opposite direction of direction.
+ */
+constexpr unsigned get_opposite_direction(unsigned direction) noexcept {
+	return 3 - direction;
+}
diff --git a/src/wfc/lua_wfc.cpp b/src/wfc/lua_wfc.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d6eecaa4c6705b82f395579f16f17806ee033a8b
--- /dev/null
+++ b/src/wfc/lua_wfc.cpp
@@ -0,0 +1,184 @@
+/*
+	TE4 - T-Engine 4
+	Copyright (C) 2009 - 2018 Nicolas Casalini
+
+	This program is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+	Nicolas Casalini "DarkGod"
+	darkgod@te4.org
+*/
+
+extern "C" {
+#include "lauxlib.h"
+#include "lualib.h"
+#include "auxiliar.h"
+#include "lua_wfc_external.h"
+#include "SFMT.h"
+}
+#include "stdlib.h"
+#include "string.h"
+#include "lua_wfc.hpp"
+
+static WFCOverlapping *parse_config_overlapping(lua_State *L) {
+	// Iterate the sample lines to find max size
+	int sample_w = 9999;
+	int sample_h = lua_objlen(L, 1);
+	for (int y = 0; y < sample_h; y++) {
+		lua_rawgeti(L, 1, y + 1);
+		size_t len;
+		unsigned char *line = (unsigned char*)strdup(luaL_checklstring(L, -1, &len));
+		if (len < sample_w) sample_w = len;
+		lua_pop(L, 1);
+	}
+
+	WFCOverlapping *config = new WFCOverlapping(
+		luaL_checknumber(L, 4), // n
+		luaL_checknumber(L, 5), // symmetry
+		lua_toboolean(L, 6), // periodic_out
+		lua_toboolean(L, 7), // periodic_in
+		lua_toboolean(L, 8), // has_foundation
+		sample_w, sample_h,
+		luaL_checknumber(L, 2), // output.w
+		luaL_checknumber(L, 3) // output.h
+	);
+
+	// Iterate the sample lines to import them
+	for (int y = 0; y < sample_h; y++) {
+		lua_rawgeti(L, 1, y + 1);
+		size_t len;
+		unsigned char *line = (unsigned char*)strdup(luaL_checklstring(L, -1, &len));
+		for (int x = 0; x < len; x++) {
+			config->sample.get(y, x) = line[x];
+		}
+
+		lua_pop(L, 1);
+	}
+	return config;
+}
+
+static void free_config_overlapping(WFCOverlapping *config) {
+	delete config;
+}
+
+static void generate_table_from_output(lua_State *L, WFCOverlapping *config) {
+	lua_newtable(L);
+	printf("===========RESULT\n");
+	char *buf = new char[config->output.width+1];
+	for (int y = 0; y < config->output.height; y++) {
+		for (int x = 0; x < config->output.width; x++) {
+			printf("%c", config->output.get(y, x));
+			buf[x] = config->output.get(y, x);
+		}
+		buf[config->output.width] = '\0';
+		printf("\n");
+		
+		lua_pushlstring(L, buf, config->output.width);
+		lua_rawseti(L, -2, y + 1);
+	}
+	delete[] buf;
+	printf("===========\n");
+}
+
+static bool wfc_generate_overlapping(WFCOverlapping *config) {
+	OverlappingWFCOptions options = {config->periodic_in, config->periodic_out, config->output.height, config->output.width, config->symmetry, config->has_foundation, config->n};
+	OverlappingWFC<char> wfc(config->sample, options, gen_rand32());
+	cl::optional<Array2D<char>> success = wfc.run();
+	if (success.has_value()) {
+		config->output.import(success.value());
+		return true;
+	} else {
+		return false;
+	}
+}
+
+static int lua_wfc_overlapping(lua_State *L) {
+	WFCOverlapping *config = parse_config_overlapping(L);
+
+	if (wfc_generate_overlapping(config)) {
+		generate_table_from_output(L, config);
+	} else {
+		lua_pushnil(L);
+	}
+
+	// Cleanup
+	free_config_overlapping(config);
+
+	return 1;
+}
+
+static int thread_generate_overlapping(void *ptr) {
+	WFCOverlapping *config = (WFCOverlapping*)ptr;
+	if (wfc_generate_overlapping(config)) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+static int lua_wfc_overlapping_async(lua_State *L) {
+	WFCOverlapping *config = parse_config_overlapping(L);
+
+	SDL_Thread *thread = SDL_CreateThread(thread_generate_overlapping, "particles", config);
+
+	WFCAsync *async = (WFCAsync*)lua_newuserdata(L, sizeof(WFCAsync));
+	auxiliar_setclass(L, "wfc{async}", -1);
+	async->mode = WFCAsyncMode::OVERLAPPING;
+	async->overlapping_config = config;
+	async->thread = thread;
+	return 1;
+}
+
+static int lua_wfc_wait_all_async(lua_State *L) {
+	return 0;
+}
+
+static int lua_wfc_wait_async(lua_State *L) {
+	WFCAsync *async = (WFCAsync*)auxiliar_checkclass(L, "wfc{async}", 1);
+	
+	int ret;
+	SDL_WaitThread(async->thread, &ret);
+	
+	if (async->mode == WFCAsyncMode::OVERLAPPING) {
+		if (ret == 1) {
+			generate_table_from_output(L, async->overlapping_config);
+		} else {
+			lua_pushnil(L);
+		}
+		// Cleanup
+		free_config_overlapping(async->overlapping_config);
+	}
+
+	return 1;
+}
+
+static const struct luaL_Reg async_reg[] =
+{
+	{"wait", lua_wfc_wait_async},
+	{NULL, NULL},
+};
+
+static const struct luaL_Reg wfclib[] =
+{
+	{"asyncOverlapping", lua_wfc_overlapping_async},
+	{"asyncWaitAll", lua_wfc_wait_all_async},
+	{"overlapping", lua_wfc_overlapping},
+	{NULL, NULL},
+};
+
+int luaopen_wfc(lua_State *L) {
+	auxiliar_newclass(L, "wfc{async}", async_reg);
+	luaL_openlib(L, "core.generator.wfc", wfclib, 0);
+	lua_settop(L, 0);
+	return 1;
+}
diff --git a/src/wfc/lua_wfc.hpp b/src/wfc/lua_wfc.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b5090f80f1ac08a369265a4bc8b1b613fcbdf572
--- /dev/null
+++ b/src/wfc/lua_wfc.hpp
@@ -0,0 +1,51 @@
+/*
+	TE4 - T-Engine 4
+	Copyright (C) 2009 - 2018 Nicolas Casalini
+
+	This program is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+	Nicolas Casalini "DarkGod"
+	darkgod@te4.org
+*/
+
+#ifndef _WFC_LUA_H_
+#define _WFC_LUA_H_
+#include "tSDL.h"
+
+#include "wfc.hpp"
+#include "overlapping_wfc.hpp"
+
+struct WFCOverlapping {
+	unsigned int n;
+	unsigned int symmetry;
+	bool periodic_out;
+	bool periodic_in;
+	bool has_foundation;
+
+	Array2D<char> sample;
+	Array2D<char> output;
+
+	WFCOverlapping(int n, int symmetry, bool periodic_out, bool periodic_in, bool has_foundation, int sample_w, int sample_h, int output_w, int output_h) :
+		n(n), symmetry(symmetry), periodic_out(periodic_out), periodic_in(periodic_in), has_foundation(has_foundation), sample(sample_h, sample_w), output(output_h, output_w)
+	{}
+};
+
+enum class WFCAsyncMode { OVERLAPPING, TILED };
+struct WFCAsync {
+	WFCAsyncMode mode = WFCAsyncMode::OVERLAPPING;
+	WFCOverlapping *overlapping_config = nullptr;
+	SDL_Thread *thread;
+};
+
+#endif
diff --git a/src/wfc/lua_wfc_external.h b/src/wfc/lua_wfc_external.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac3e844f8aef4ed773b52d86c6bacddbb0e693ee
--- /dev/null
+++ b/src/wfc/lua_wfc_external.h
@@ -0,0 +1,29 @@
+/*
+	TE4 - T-Engine 4
+	Copyright (C) 2009 - 2018 Nicolas Casalini
+
+	This program is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+	Nicolas Casalini "DarkGod"
+	darkgod@te4.org
+*/
+
+#ifndef _WFC_LUA_EXTERNAL_H_
+#define _WFC_LUA_EXTERNAL_H_
+
+#include "lua.h"
+
+extern int luaopen_wfc(lua_State *L);
+
+#endif
diff --git a/src/wfc/optional.hpp b/src/wfc/optional.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..01880e3bb41808764fef22bfdff54031b54b7445
--- /dev/null
+++ b/src/wfc/optional.hpp
@@ -0,0 +1,613 @@
+// Copyright (c) 2016, Charles Lechasseur
+// Distributed under the MIT license (see LICENSE).
+
+// Implementation of C++17's std::optional
+
+#ifndef CL_OPTIONAL_H
+#define CL_OPTIONAL_H
+
+#include <functional>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+
+// Compiler detection
+#if defined(_MSC_VER)
+#   if _MSC_VER >= 1800 // Visual Studio 2013
+#       define CL_OPTIONAL_VARIADIC
+#       define CL_OPTIONAL_EXPLICIT_OP_BOOL
+#       define CL_OPTIONAL_TEMPLATE_FUNCS_DEFAULT_PARAMS
+#   endif
+#   if _MSC_VER >= 1900 // Visual Studio 2015
+#       define CL_OPTIONAL_NORETURN
+#       define CL_OPTIONAL_MEMBERS_REF_QUALIFIERS
+#   endif
+#   if _MSC_VER >= 1910 // Visual Studio 2017
+#       define CL_OPTIONAL_NOEXCEPT     // Note: in theory this is supported in VC14, but a bug in VC14 update 3 can cause compiler errors
+#       define CL_OPTIONAL_CONSTEXPR
+#   endif
+#elif defined(__clang__)
+#   if __has_feature(cxx_noexcept)
+#       define CL_OPTIONAL_NOEXCEPT
+#   endif
+#   if __has_feature(cxx_constexpr) && __cplusplus >= 201402L
+#       define CL_OPTIONAL_CONSTEXPR
+#   endif
+#   if __has_feature(cxx_attributes)
+#       define CL_OPTIONAL_NORETURN
+#   endif
+#   if __has_feature(cxx_variadic_templates)
+#       define CL_OPTIONAL_VARIADIC
+#   endif
+#   if __has_feature(cxx_reference_qualified_functions) && __cplusplus >= 201402L
+#       define CL_OPTIONAL_MEMBERS_REF_QUALIFIERS
+#   endif
+#   if __has_feature(cxx_explicit_conversions)
+#       define CL_OPTIONAL_EXPLICIT_OP_BOOL
+#   endif
+#   if __has_feature(cxx_default_function_template_args)
+#       define CL_OPTIONAL_TEMPLATE_FUNCS_DEFAULT_PARAMS
+#   endif
+#elif defined(__GNUG__)
+#   if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
+#       define CL_OPTIONAL_NOEXCEPT
+#       define CL_OPTIONAL_NORETURN
+#       define CL_OPTIONAL_VARIADIC
+#       define CL_OPTIONAL_MEMBERS_REF_QUALIFIERS
+#       define CL_OPTIONAL_EXPLICIT_OP_BOOL
+#       define CL_OPTIONAL_TEMPLATE_FUNCS_DEFAULT_PARAMS
+#   endif
+#   if __GNUC__ >= 5
+#       define CL_OPTIONAL_CONSTEXPR
+#   endif
+#endif
+
+
+// Define CL_OPTIONAL_NOEXCEPT to get noexcept specifications
+#ifdef CL_OPTIONAL_NOEXCEPT
+#   define _CL_OPT_NOEXCEPT         noexcept
+#   define _CL_OPT_NOEXCEPTEX(...)  noexcept(__VA_ARGS__)
+#else
+#   define _CL_OPT_NOEXCEPT
+#   define _CL_OPT_NOEXCEPTEX(...)
+#endif
+
+// Define CL_OPTIONAL_CONSTEXPR to get constexpr specifications
+#ifdef CL_OPTIONAL_CONSTEXPR
+#   define _CL_OPT_CONSTEXPR            constexpr
+#   define _CL_OPT_CONSTEXPR_OR_CONST   constexpr
+#else
+#   define _CL_OPT_CONSTEXPR
+#   define _CL_OPT_CONSTEXPR_OR_CONST   const
+#endif
+
+// Define CL_OPTIONAL_VARIADIC to get variadic templates
+#ifdef CL_OPTIONAL_VARIADIC
+#   define _CL_OPT_VARIADIC 1
+#else
+#   define _CL_OPT_VARIADIC 0
+#endif
+
+// Define CL_OPTIONAL_NORETURN to get noreturn specifications
+#ifdef CL_OPTIONAL_NORETURN
+#   define _CL_OPT_NORETURN [[noreturn]]
+#else
+#   define _CL_OPT_NORETURN
+#endif
+
+// Define CL_OPTIONAL_MEMBERS_REF_QUALIFIERS to get lvalue- and rvalue-qualified members
+#ifdef CL_OPTIONAL_MEMBERS_REF_QUALIFIERS
+#   define _CL_OPT_MEMBERS_REF_QUALIFIERS   1
+#   define _CL_OPT_LVALUE_REF_QUALIF        &
+#else
+#   define _CL_OPT_MEMBERS_REF_QUALIFIERS   0
+#   define _CL_OPT_LVALUE_REF_QUALIF
+#endif
+
+// Define CL_OPTIONAL_EXPLICIT_OP_BOOL to get explicit operator bool()
+#ifdef CL_OPTIONAL_EXPLICIT_OP_BOOL
+#   define _CL_OPT_OP_EXPLICIT  explicit
+#else
+#   define _CL_OPT_OP_EXPLICIT
+#endif
+
+// Define CL_OPTIONAL_TEMPLATE_FUNCS_DEFAULT_PARAMS to get template methods
+// with default template params, like assignment operator from value
+#ifdef CL_OPTIONAL_TEMPLATE_FUNCS_DEFAULT_PARAMS
+#   define _CL_OPT_TEMPL_FUNC_DEF_PARAMS    1
+#else
+#   define _CL_OPT_TEMPL_FUNC_DEF_PARAMS    0
+#endif
+
+
+#if _CL_OPT_VARIADIC
+#   include <initializer_list>
+#endif
+
+namespace cl {
+
+// Type indicating an optional object with no value. See:
+// http://en.cppreference.com/w/cpp/utility/optional/nullopt_t
+struct nullopt_t {
+    _CL_OPT_CONSTEXPR nullopt_t(int) { }
+};
+
+// Instance of nullopt_t passed to optional's members. See:
+// http://en.cppreference.com/w/cpp/utility/optional/nullopt
+_CL_OPT_CONSTEXPR_OR_CONST nullopt_t nullopt = 0;
+
+#if _CL_OPT_VARIADIC
+// Empty type used for in_place functions. See:
+// http://en.cppreference.com/w/cpp/utility/in_place_tag
+struct in_place_tag {
+    in_place_tag() = delete;
+private:
+    in_place_tag(int) { }
+    friend in_place_tag in_place();
+};
+
+// Function to identify an optional method with in-place construction. See:
+// http://en.cppreference.com/w/cpp/utility/in_place
+inline in_place_tag in_place() { return in_place_tag(0); }
+using in_place_t = in_place_tag (&)();
+#endif // _CL_OPT_VARIADIC
+
+// Exception thrown by optional when trying to access
+// an optional with no value. See:
+// http://en.cppreference.com/w/cpp/utility/optional/bad_optional_access
+class bad_optional_access : public std::logic_error
+{
+public:
+    bad_optional_access(const std::string& what_arg)
+        : std::logic_error(what_arg) { }
+    bad_optional_access(const char* what_arg)
+        : std::logic_error(what_arg) { }
+};
+
+// Wrapper for an optional value. Part of future C++17 API. See:
+// http://en.cppreference.com/w/cpp/utility/optional
+template<typename T>
+class optional
+{
+    static_assert(!std::is_reference<T>::value, "cl::optional does not work with reference types");
+    static_assert(std::is_destructible<T>::value, "cl::optional requires type to be Destructible");
+
+private:
+    typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type stg_;   // We store any value here.
+    T* const pval_;                                                                     // Pointer to value stored in stg_.
+    bool has_value_;                                                                    // Whether we have a value in stg_.
+
+    // Exception helpers
+    _CL_OPT_NORETURN void throw_bad_optional_access() const {
+        throw bad_optional_access("bad_optional_access");
+    }
+
+    // Helper for swap when one has a value and the other does not
+    static void swap_helper(optional& from, optional& to) _CL_OPT_NOEXCEPTEX(std::is_nothrow_move_constructible<T>::value &&
+                                                                             std::is_nothrow_destructible<T>::value)
+    {
+        new (to.pval_) T(std::move(*from.pval_));
+        from.pval_->~T();
+        to.has_value_ = true;
+        from.has_value_ = false;
+    }
+
+public:
+    typedef T   value_type;
+
+    // Constructors of optional without a value
+    _CL_OPT_CONSTEXPR optional() _CL_OPT_NOEXCEPT
+        : stg_(), pval_(reinterpret_cast<T*>(&stg_)), has_value_(false) { }
+    _CL_OPT_CONSTEXPR optional(nullopt_t) _CL_OPT_NOEXCEPT
+        : stg_(), pval_(reinterpret_cast<T*>(&stg_)), has_value_(false) { }
+
+    // Copy constructor
+    optional(const optional& obj) _CL_OPT_NOEXCEPTEX(std::is_nothrow_copy_constructible<T>::value)
+        : stg_(), pval_(reinterpret_cast<T*>(&stg_)), has_value_(obj.has_value_)
+    {
+        if (has_value_) {
+            new (pval_) T(*obj.pval_);
+        }
+    }
+
+    // Move constructor (note: does not steal has_value_, see API)
+    optional(optional&& obj) _CL_OPT_NOEXCEPTEX(std::is_nothrow_move_constructible<T>::value)
+        : stg_(), pval_(reinterpret_cast<T*>(&stg_)), has_value_(obj.has_value_)
+    {
+        if (has_value_) {
+            new (pval_) T(std::move(*obj.pval_));
+        }
+    }
+
+    // Constructors of optional with a value
+    optional(const T& val) _CL_OPT_NOEXCEPTEX(std::is_nothrow_copy_constructible<T>::value)
+        : stg_(), pval_(reinterpret_cast<T*>(&stg_)), has_value_(true)
+    {
+        new (pval_) T(val);
+    }
+    optional(T&& val) _CL_OPT_NOEXCEPTEX(std::is_nothrow_move_constructible<T>::value)
+        : stg_(), pval_(reinterpret_cast<T*>(&stg_)), has_value_(true)
+    {
+        new (pval_) T(std::move(val));
+    }
+
+#if _CL_OPT_VARIADIC
+    // In-place constructor of optional with a value
+    template<typename... Args>
+    _CL_OPT_CONSTEXPR explicit optional(in_place_t, Args&&... args)
+                _CL_OPT_NOEXCEPTEX(std::is_nothrow_constructible<T, Args&&...>::value)
+        : stg_(), pval_(reinterpret_cast<T*>(&stg_)), has_value_(true)
+    {
+        new (pval_) T(std::forward<Args>(args)...);
+    }
+
+    // Same but with an initializer_list thrown in just for fun
+    template<typename U, typename... Args,
+             typename = typename std::enable_if<std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value, void>::type>
+    _CL_OPT_CONSTEXPR explicit optional(in_place_t,
+                                         std::initializer_list<U> ilist,
+                                         Args&&... args)
+                _CL_OPT_NOEXCEPTEX(std::is_nothrow_constructible<T, std::initializer_list<U>&, Args&&...>::value)
+        : stg_(), pval_(reinterpret_cast<T*>(&stg_)), has_value_(true)
+    {
+        new (pval_) T(ilist, std::forward<Args>(args)...);
+    }
+#endif // _CL_OPT_VARIADIC
+
+    // Destructor
+    ~optional() _CL_OPT_NOEXCEPTEX(std::is_nothrow_destructible<T>::value) {
+        if (has_value_) {
+            // #clp TODO we should not do this for trivially-destructible types as per API
+            pval_->~T();
+        }
+    }
+
+    // Assignment operator that clears any held value
+    optional& operator=(nullopt_t) _CL_OPT_NOEXCEPTEX(std::is_nothrow_destructible<T>::value)
+    {
+        if (has_value_) {
+            pval_->~T();
+            has_value_ = false;
+        }
+        return *this;
+    }
+
+    // Copy assignment operator
+    optional& operator=(const optional& obj) _CL_OPT_NOEXCEPTEX(std::is_nothrow_copy_constructible<T>::value &&
+                                                                std::is_nothrow_copy_assignable<T>::value &&
+                                                                std::is_nothrow_destructible<T>::value)
+    {
+        if (obj.has_value_) {
+            if (has_value_) {
+                // Can assign
+                *pval_ = *obj.pval_;
+            } else {
+                // Need to construct
+                new (pval_) T(*obj.pval_);
+                has_value_ = true;
+            }
+        } else if (has_value_) {
+            pval_->~T();
+            has_value_ = false;
+        }
+        return *this;
+    }
+
+    // Move assignment (note: does not steal has_value_, see API)
+    optional& operator=(optional&& obj) _CL_OPT_NOEXCEPTEX(std::is_nothrow_move_constructible<T>::value &&
+                                                           std::is_nothrow_move_assignable<T>::value &&
+                                                           std::is_nothrow_destructible<T>::value)
+    {
+        if (obj.has_value_) {
+            if (has_value_) {
+                // Can assign
+                *pval_ = std::move(*obj.pval_);
+            } else {
+                // Need to construct
+                new (pval_) T(std::move(*obj.pval_));
+                has_value_ = true;
+            }
+        } else if (has_value_) {
+            pval_->~T();
+            has_value_ = false;
+        }
+        return *this;
+    }
+
+#if _CL_OPT_TEMPL_FUNC_DEF_PARAMS
+    // Assignment from a value
+    template<typename U,
+             typename = typename std::enable_if<std::is_same<typename std::decay<U>::type, T>::value, void>::type>
+    optional& operator=(U&& val)
+            _CL_OPT_NOEXCEPTEX(std::is_nothrow_constructible<T, U>::value &&
+                               std::is_nothrow_assignable<T, U>::value)
+    {
+        if (has_value_) {
+            // Can assign
+            *pval_ = std::forward<U>(val);
+        } else {
+            // Need to construct
+            new (pval_) T(std::forward<U>(val));
+            has_value_ = true;
+        }
+        return *this;
+    }
+#endif // _CL_OPT_TEMPL_FUNC_DEF_PARAMS
+
+    // Unchecked operators that access the value
+    _CL_OPT_CONSTEXPR const T* operator->() const {
+        return pval_;
+    }
+    _CL_OPT_CONSTEXPR T* operator->() {
+        return pval_;
+    }
+    _CL_OPT_CONSTEXPR const T& operator*() const _CL_OPT_LVALUE_REF_QUALIF {
+        return *pval_;
+    }
+    _CL_OPT_CONSTEXPR T& operator*() _CL_OPT_LVALUE_REF_QUALIF {
+        return *pval_;
+    }
+#if _CL_OPT_MEMBERS_REF_QUALIFIERS
+    _CL_OPT_CONSTEXPR const T&& operator*() const && {
+        return std::move(*pval_);
+    }
+    _CL_OPT_CONSTEXPR T&& operator*() && {
+        return std::move(*pval_);
+    }
+#endif // _CL_OPT_MEMBERS_REF_QUALIFIERS
+
+    // Value detection
+    _CL_OPT_CONSTEXPR _CL_OPT_OP_EXPLICIT operator bool() const _CL_OPT_NOEXCEPT {
+        return has_value_;
+    }
+    _CL_OPT_CONSTEXPR bool has_value() const _CL_OPT_NOEXCEPT {
+        return has_value_;
+    }
+
+    // Value accessors that throw if optional doesn't have a value
+    _CL_OPT_CONSTEXPR const T& value() const _CL_OPT_LVALUE_REF_QUALIF {
+        if (!has_value_) {
+            throw_bad_optional_access();
+        }
+        return *pval_;
+    }
+    _CL_OPT_CONSTEXPR T& value() _CL_OPT_LVALUE_REF_QUALIF {
+        if (!has_value_) {
+            throw_bad_optional_access();
+        }
+        return *pval_;
+    }
+#if _CL_OPT_MEMBERS_REF_QUALIFIERS
+    _CL_OPT_CONSTEXPR const T&& value() const && {
+        if (!has_value_) {
+            throw_bad_optional_access();
+        }
+        return std::move(*pval_);
+    }
+    _CL_OPT_CONSTEXPR T&& value() && {
+        if (!has_value_) {
+            throw_bad_optional_access();
+        }
+        return std::move(*pval_);
+    }
+#endif // _CL_OPT_MEMBERS_REF_QUALIFIERS
+
+    // Value accessors with an alternative if optional doesn't have a value
+    template<typename U>
+    _CL_OPT_CONSTEXPR T value_or(U&& default_val) const _CL_OPT_LVALUE_REF_QUALIF
+                _CL_OPT_NOEXCEPTEX(std::is_nothrow_copy_constructible<T>::value &&
+                                   std::is_nothrow_constructible<T, U>::value)
+    {
+        return has_value_ ? *pval_ : T(std::forward<U>(default_val));
+    }
+#if _CL_OPT_MEMBERS_REF_QUALIFIERS
+    template<typename U>
+    _CL_OPT_CONSTEXPR T value_or(U&& default_val) &&
+                _CL_OPT_NOEXCEPTEX(std::is_nothrow_move_constructible<T>::value &&
+                                   std::is_nothrow_constructible<T, U>::value)
+    {
+        return has_value_ ? std::move(*pval_) : T(std::forward<U>(default_val));
+    }
+#endif // _CL_OPT_MEMBERS_REF_QUALIFIERS
+
+    // Swap implementation
+    void swap(optional& obj) _CL_OPT_NOEXCEPTEX(std::is_nothrow_move_constructible<T>::value &&
+                                                std::is_nothrow_destructible<T>::value &&
+                                                noexcept(std::swap(std::declval<T&>(), std::declval<T&>())))  // #clp TODO using std::swap here seems wrong, no?
+    {
+        if (obj.has_value_) {
+            if (has_value_) {
+                using std::swap;
+                swap(*pval_, *obj.pval_);
+            } else {
+                swap_helper(obj, *this);
+            }
+        } else if (has_value_) {
+            swap_helper(*this, obj);
+        }
+    }
+    // Non-member version of swap for ADL
+    friend void swap(optional& obj1, optional& obj2) {
+        obj1.swap(obj2);
+    }
+
+    // Removes any value held
+    void reset() _CL_OPT_NOEXCEPTEX(std::is_nothrow_destructible<T>::value)
+    {
+        if (has_value_) {
+            pval_->~T();
+            has_value_ = false;
+        }
+    }
+
+#if _CL_OPT_VARIADIC
+    // Destroys any held value and constructs a new one in-place
+    template<typename... Args>
+    void emplace(Args&&... args) _CL_OPT_NOEXCEPTEX(std::is_nothrow_destructible<T>::value &&
+                                                    std::is_nothrow_constructible<T, Args...>::value)
+    {
+        reset();
+        new (pval_) T(std::forward<Args>(args)...);
+        has_value_ = true;
+    }
+    template<typename U, typename... Args,
+             typename = typename std::enable_if<std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value, void>::type>
+    void emplace(std::initializer_list<U> ilist,
+                 Args&&... args) _CL_OPT_NOEXCEPTEX(std::is_nothrow_destructible<T>::value &&
+                                                    std::is_nothrow_constructible<T, std::initializer_list<U>&, Args&&...>::value)
+    {
+        reset();
+        new (pval_) T(ilist, std::forward<Args>(args)...);
+        has_value_ = true;
+    }
+#endif // _CL_OPT_VARIADIC
+
+    // Compares two optionals. One without a value is less than one with a value.
+    friend bool operator==(const optional& lhs, const optional& rhs) {
+        return lhs.has_value_ == rhs.has_value_ &&
+               (!lhs.has_value_ || *lhs.pval_ == *rhs.pval_);
+    }
+    friend bool operator!=(const optional& lhs, const optional& rhs) {
+        return !(lhs == rhs);
+    }
+    friend bool operator<(const optional& lhs, const optional& rhs) {
+        if (lhs.has_value_) {
+            if (rhs.has_value_) {
+                return *lhs.pval_ < *rhs.pval_;
+            } else {
+                return false;
+            }
+        } else {
+            return rhs.has_value_;
+        }
+    }
+    friend bool operator<=(const optional& lhs, const optional& rhs) {
+        return !(rhs < lhs);
+    }
+    friend bool operator>(const optional& lhs, const optional& rhs) {
+        return rhs < lhs;
+    }
+    friend bool operator>=(const optional& lhs, const optional& rhs) {
+        return !(lhs < rhs);
+    }
+
+    // Compares optional with a nullopt (like comparing with one without a value).
+    friend bool operator==(const optional& lhs, nullopt_t) _CL_OPT_NOEXCEPT {
+        return !lhs.has_value_;
+    }
+    friend bool operator==(nullopt_t, const optional& rhs) _CL_OPT_NOEXCEPT {
+        return !rhs.has_value_;
+    }
+    friend bool operator!=(const optional& lhs, nullopt_t) _CL_OPT_NOEXCEPT {
+        return lhs.has_value_;
+    }
+    friend bool operator!=(nullopt_t, const optional& rhs) _CL_OPT_NOEXCEPT {
+        return rhs.has_value_;
+    }
+    friend bool operator<(const optional&, nullopt_t) _CL_OPT_NOEXCEPT {
+        return false;
+    }
+    friend bool operator<(nullopt_t, const optional& rhs) _CL_OPT_NOEXCEPT {
+        return rhs.has_value_;
+    }
+    friend bool operator<=(const optional& lhs, nullopt_t) _CL_OPT_NOEXCEPT {
+        return !lhs.has_value_;
+    }
+    friend bool operator<=(nullopt_t, const optional&) _CL_OPT_NOEXCEPT {
+        return true;
+    }
+    friend bool operator>(const optional& lhs, nullopt_t) _CL_OPT_NOEXCEPT {
+        return lhs.has_value_;
+    }
+    friend bool operator>(nullopt_t, const optional&) _CL_OPT_NOEXCEPT {
+        return false;
+    }
+    friend bool operator>=(const optional&, nullopt_t) _CL_OPT_NOEXCEPT {
+        return true;
+    }
+    friend bool operator>=(nullopt_t, const optional& rhs) _CL_OPT_NOEXCEPT {
+        return !rhs.has_value_;
+    }
+
+    // Compares optional with a value (like comparing with one with a value).
+    friend bool operator==(const optional& lhs, const T& rhs) {
+        return lhs.has_value_ ? *lhs.pval_ == rhs : false;
+    }
+    friend bool operator==(const T& lhs, const optional& rhs) {
+        return rhs.has_value_ ? lhs == *rhs.pval_ : false;
+    }
+    friend bool operator!=(const optional& lhs, const T& rhs) {
+        return lhs.has_value_ ? *lhs.pval_ != rhs : true;
+    }
+    friend bool operator!=(const T& lhs, const optional& rhs) {
+        return rhs.has_value_ ? lhs != *rhs.pval_ : true;
+    }
+    friend bool operator<(const optional& lhs, const T& rhs) {
+        return lhs.has_value_ ? *lhs.pval_ < rhs : true;
+    }
+    friend bool operator<(const T& lhs, const optional& rhs) {
+        return rhs.has_value_ ? lhs < *rhs.pval_ : false;
+    }
+    friend bool operator<=(const optional& lhs, const T& rhs) {
+        return lhs.has_value_ ? *lhs.pval_ <= rhs : true;
+    }
+    friend bool operator<=(const T& lhs, const optional& rhs) {
+        return rhs.has_value_ ? lhs <= *rhs.pval_ : false;
+    }
+    friend bool operator>(const optional& lhs, const T& rhs) {
+        return lhs.has_value_ ? *lhs.pval_ > rhs : false;
+    }
+    friend bool operator>(const T& lhs, const optional& rhs) {
+        return rhs.has_value_ ? lhs > *rhs.pval_ : true;
+    }
+    friend bool operator>=(const optional& lhs, const T& rhs) {
+        return lhs.has_value_ ? *lhs.pval_ >= rhs : false;
+    }
+    friend bool operator>=(const T& lhs, const optional& rhs) {
+        return rhs.has_value_ ? lhs >= *rhs.pval_ : true;
+    }
+};
+
+// Creates an optional by inferring type. See:
+// http://en.cppreference.com/w/cpp/utility/optional/make_optional
+template<typename T>
+_CL_OPT_CONSTEXPR auto make_optional(T&& val)
+    -> optional<typename std::decay<T>::type>
+{
+    return optional<typename std::decay<T>::type>(std::forward<T>(val));
+}
+
+#if _CL_OPT_VARIADIC
+// Creates an optional from arguments. See:
+// http://en.cppreference.com/w/cpp/utility/optional/make_optional
+template<typename T, typename... Args>
+_CL_OPT_CONSTEXPR auto make_optional(Args&&... args)
+    -> optional<T>
+{
+    return optional<T>(in_place, std::forward<Args>(args)...);
+}
+template<typename T, typename U, typename... Args>
+_CL_OPT_CONSTEXPR auto make_optional(std::initializer_list<U> ilist, Args&&... args)
+    -> optional<T>
+{
+    return optional<T>(in_place, ilist, std::forward<Args>(args)...);
+}
+#endif // _CL_OPT_VARIADIC
+
+} // cl
+
+namespace std {
+
+// Specialize hash for optional
+template<typename T>
+struct hash<cl::optional<T>> {
+    size_t operator()(const cl::optional<T>& obj) const {
+        return obj.has_value() ? hash<T>()(*obj) : 0;
+    }
+};
+
+} // std
+
+#endif // CL_OPTIONAL_H
diff --git a/src/wfc/overlapping_wfc.hpp b/src/wfc/overlapping_wfc.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ca162e3175e1a909aaeb61031223c1cf8f98f7fd
--- /dev/null
+++ b/src/wfc/overlapping_wfc.hpp
@@ -0,0 +1,286 @@
+#pragma once
+
+#include "wfc.hpp"
+#include <iostream>
+
+/**
+ * Options needed to use the overlapping wfc.
+ */
+struct OverlappingWFCOptions {
+	bool periodic_input;     // True if the input is toric.
+	bool periodic_output;    // True if the output is toric.
+	unsigned out_height;     // The height of the output in pixels.
+	unsigned out_width;      // The width of the output in pixels.
+	unsigned symmetry;       // The number of symmetries (the order is defined in wfc).
+	bool ground;             // True if the ground needs to be set (see init_ground).
+	unsigned pattern_size;   // The width and height in pixel of the patterns.
+
+	/**
+	 * Get the wave height given these options.
+	 */
+	unsigned get_wave_height() const noexcept {
+		return periodic_output ? out_height : out_height - pattern_size + 1;
+	}
+
+	/**
+	 * Get the wave width given these options.
+	 */
+	unsigned get_wave_width() const noexcept {
+		return periodic_output ? out_width : out_width - pattern_size + 1;
+	}
+};
+
+/**
+ * Class generating a new image with the overlapping WFC algorithm.
+ */
+template<typename T>
+class OverlappingWFC {
+
+private:
+	/**
+	 * The input image. T is usually a color.
+	 */
+	Array2D<T> input;
+
+	/**
+	 * Options needed by the algorithm.
+	 */
+	OverlappingWFCOptions options;
+
+	/**
+	 * The array of the different patterns extracted from the input.
+	 */
+	vector<Array2D<T>> patterns;
+
+	/**
+	 * The underlying generic WFC algorithm.
+	 */
+	WFC wfc;
+
+	/**
+	 * Constructor initializing the wfc.
+	 * This constructor is called by the other constructors.
+	 * This is necessary in order to initialize wfc only once.
+	 */
+	OverlappingWFC(const Array2D<T>& input, const OverlappingWFCOptions& options, const int& seed,
+								 const pair<vector<Array2D<T>>, vector<double>>& patterns,
+								 const vector<array<vector<unsigned>, 4>>& propagator) noexcept :
+		input(input),
+		options(options),
+		patterns(patterns.first),
+		wfc(options.periodic_output, seed, patterns.second, propagator,
+				options.get_wave_height(), options.get_wave_width())
+	{
+		// If necessary, the ground is set.
+		if(options.ground) {
+			init_ground(wfc, input, patterns.first, options);
+		}
+	}
+
+	/**
+	 * Constructor used only to call the other constructor with more computed parameters.
+	 */
+	OverlappingWFC(const Array2D<T>& input, const OverlappingWFCOptions& options, const int& seed,
+								 const pair<vector<Array2D<T>>, vector<double>>& patterns) noexcept :
+		OverlappingWFC(input, options, seed, patterns, generate_compatible(patterns.first))
+	{}
+
+
+	/**
+	 * Init the ground of the output image.
+	 * The lowest middle pattern is used as a floor (and ceiling when the input is toric)
+	 * and is placed at the lowest possible pattern position in the output image, on all its width.
+	 * The pattern cannot be used at any other place in the output image.
+	 */
+	static void init_ground(WFC& wfc, const Array2D<T>& input, const vector<Array2D<T>>& patterns, const OverlappingWFCOptions& options) noexcept {
+		unsigned ground_pattern_id = get_ground_pattern_id(input, patterns, options);
+
+		// Place the pattern in the ground.
+		for(unsigned j = 0; j < options.get_wave_width(); j++) {
+			for(unsigned p = 0; p < patterns.size(); p++) {
+				if(ground_pattern_id != p) {
+					wfc.remove_wave_pattern(options.get_wave_height() - 1, j, p);
+				}
+			}
+		}
+
+		// Remove the pattern from the other positions.
+		for(unsigned i = 0; i < options.get_wave_height() - 1; i++) {
+			for(unsigned j = 0; j < options.get_wave_width(); j++) {
+				wfc.remove_wave_pattern(i, j, ground_pattern_id);
+			}
+		}
+
+		// Propagate the information with wfc.
+		wfc.propagate();
+	}
+
+	/**
+	 * Return the id of the lowest middle pattern.
+	 */
+	static unsigned get_ground_pattern_id(const Array2D<T>& input, const vector<Array2D<T>>& patterns, const OverlappingWFCOptions& options) noexcept {
+		// Get the pattern.
+		Array2D<T> ground_pattern = input.get_sub_array(input.height - 1, input.width / 2, options.pattern_size, options.pattern_size);
+
+		// Retrieve the id of the pattern.
+		for(unsigned i = 0; i < patterns.size(); i++) {
+			if(ground_pattern == patterns[i]) {
+				return i;
+			}
+		}
+
+		// The pattern exists.
+		assert(false);
+		return 0;
+	}
+
+	/**
+	 * Return the list of patterns, as well as their probabilities of apparition.
+	 */
+	static pair<vector<Array2D<T>>, vector<double>> get_patterns(const Array2D<T>& input, const OverlappingWFCOptions& options) noexcept {
+		unordered_map<Array2D<T>, unsigned> patterns_id;
+		vector<Array2D<T>> patterns;
+
+		// The number of time a pattern is seen in the input image.
+		vector<double> patterns_frequency;
+
+		vector<Array2D<T>> symmetries(8, Array2D<T>(options.pattern_size, options.pattern_size));
+		unsigned max_i = options.periodic_input ? input.height : input.height - options.pattern_size + 1;
+		unsigned max_j = options.periodic_input ? input.width : input.width - options.pattern_size + 1;
+
+		for(unsigned i = 0; i < max_i; i++) {
+			for(unsigned j = 0; j < max_j; j++) {
+				// Compute the symmetries of every pattern in the image.
+				symmetries[0].data = input.get_sub_array(i, j, options.pattern_size, options.pattern_size).data;
+				symmetries[1].data = symmetries[0].reflected().data;
+				symmetries[2].data = symmetries[0].rotated().data;
+				symmetries[3].data = symmetries[2].reflected().data;
+				symmetries[4].data = symmetries[2].rotated().data;
+				symmetries[5].data = symmetries[4].reflected().data;
+				symmetries[6].data = symmetries[4].rotated().data;
+				symmetries[7].data = symmetries[6].reflected().data;
+
+				// The number of symmetries in the option class define which symetries will be used.
+				for(unsigned k = 0; k<options.symmetry; k++) {
+					auto res = patterns_id.insert(make_pair(symmetries[k],patterns.size()));
+
+					// If the pattern already exist, we just have to increase its number of appearance.
+					if(!res.second) {
+						patterns_frequency[res.first->second] += 1;
+					} else {
+						patterns.push_back(symmetries[k]);
+						patterns_frequency.push_back(1);
+					}
+				}
+			}
+		}
+
+		return {patterns, patterns_frequency};
+	}
+
+	/**
+	 * Return true if the pattern1 is compatible with pattern2
+	 * when pattern2 is at a distance (dy,dx) from pattern1.
+	 */
+	static bool agrees(const Array2D<T>& pattern1, const Array2D<T>& pattern2, int dy, int dx) noexcept {
+		unsigned xmin = dx < 0 ? 0 : dx;
+		unsigned xmax = dx < 0 ? dx + pattern2.width : pattern1.width;
+		unsigned ymin = dy < 0 ? 0 : dy;
+		unsigned ymax = dy < 0 ? dy + pattern2.height : pattern1.width;
+
+		// Iterate on every pixel contained in the intersection of the two pattern.
+		for(unsigned y = ymin; y < ymax; y++) {
+			for(unsigned x = xmin; x < xmax; x++) {
+				// Check if the color is the same in the two patterns in that pixel.
+				if(pattern1.get(y,x) != pattern2.get(y-dy,x-dx)) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Precompute the function agrees(pattern1, pattern2, dy, dx).
+	 * If agrees(pattern1, pattern2, dy, dx), then compatible[pattern1][direction] contains pattern2,
+	 * where direction is the direction defined by (dy, dx) (see direction.hpp).
+	 */
+	static vector<array<vector<unsigned>, 4>> generate_compatible(const vector<Array2D<T>>& patterns) noexcept {
+		vector<array<vector<unsigned>, 4>> compatible = vector<array<vector<unsigned>, 4>>(patterns.size());
+
+		// Iterate on every dy, dx, pattern1 and pattern2
+		for(unsigned pattern1 = 0; pattern1 < patterns.size(); pattern1++) {
+			for(unsigned direction = 0; direction < 4; direction++) {
+				for(unsigned pattern2 = 0; pattern2 < patterns.size(); pattern2++) {
+					if(agrees(patterns[pattern1], patterns[pattern2], directions_y[direction], directions_x[direction])) {
+						compatible[pattern1][direction].push_back(pattern2);
+					}
+				}
+			}
+		}
+
+		return compatible;
+	}
+
+
+	/**
+	 * Transform a 2D array containing the patterns id to a 2D array containing the pixels.
+	 */
+	Array2D<T> to_image(const Array2D<unsigned>& output_patterns) const noexcept {
+		Array2D<T> output = Array2D<T>(options.out_height, options.out_width);
+
+		if(options.periodic_output) {
+			for(unsigned y = 0; y < options.get_wave_height(); y++) {
+				for(unsigned x = 0; x < options.get_wave_width(); x++) {
+					output.get(y,x) = patterns[output_patterns.get(y,x)].get(0,0);
+				}
+			}
+		} else {
+			for(unsigned y = 0; y < options.get_wave_height(); y++) {
+				for(unsigned x = 0; x < options.get_wave_width(); x++) {
+					output.get(y, x) = patterns[output_patterns.get(y,x)].get(0,0);
+				}
+			}
+			for(unsigned y = 0; y < options.get_wave_height(); y++) {
+				const Array2D<T>& pattern = patterns[output_patterns.get(y, options.get_wave_width()-1)];
+				for(unsigned dx = 1; dx < options.pattern_size; dx++) {
+					output.get(y, options.get_wave_width() - 1 + dx) = pattern.get(0, dx);
+				}
+			}
+			for(unsigned x = 0; x < options.get_wave_width(); x++) {
+				const Array2D<T>& pattern = patterns[output_patterns.get(options.get_wave_height() - 1, x)];
+				for(unsigned dy = 1; dy < options.pattern_size; dy++) {
+					output.get(options.get_wave_height() - 1 + dy, x) = pattern.get(dy, 0);
+				}
+			}
+			const Array2D<T>& pattern = patterns[output_patterns.get(options.get_wave_height() - 1, options.get_wave_width() - 1)];
+			for(unsigned dy = 1; dy < options.pattern_size; dy++) {
+				for(unsigned dx = 1; dx < options.pattern_size; dx++) {
+					output.get(options.get_wave_height() - 1 + dy, options.get_wave_width() - 1 + dx) = pattern.get(dy, dx);
+				}
+			}
+		}
+
+		return output;
+	}
+
+public:
+
+	/**
+	 * The constructor used by the user.
+	 */
+	OverlappingWFC(const Array2D<T>& input, const OverlappingWFCOptions& options, int seed) noexcept :
+		OverlappingWFC(input, options, seed, get_patterns(input, options))
+	{}
+
+	/**
+	 * Run the WFC algorithm, and return the result if the algorithm succeeded.
+	 */
+	cl::optional<Array2D<T>> run() noexcept {
+		cl::optional<Array2D<unsigned>> result = wfc.run();
+		if(result.has_value()) {
+			return to_image(*result);
+		}
+		return nullopt;
+	}
+};
diff --git a/src/wfc/propagator.hpp b/src/wfc/propagator.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..933f6f43636d5b35afffd642954e769cd268d9f8
--- /dev/null
+++ b/src/wfc/propagator.hpp
@@ -0,0 +1,155 @@
+#pragma once
+
+#include <vector>
+#include <tuple>
+#include <array>
+#include "utils/array2D.hpp"
+#include "utils/array3D.hpp"
+#include "wave.hpp"
+#include "direction.hpp"
+
+using namespace std;
+
+/**
+ * Propagate information about patterns in the wave.
+ */
+class Propagator {
+
+private:
+
+	/**
+	 * The size of the patterns.
+	 */
+	const unsigned patterns_size;
+
+	/**
+	 * propagator[pattern1][direction] contains all the patterns that can be placed in
+	 * next to pattern1 in the direction direction.
+	 */
+	const vector<array<vector<unsigned>, 4>> propagator;
+
+	/**
+	 * The wave width and height.
+	 */
+	const unsigned wave_width;
+	const unsigned wave_height;
+
+	/**
+	 * True if the wave and the output is toric.
+	 */
+	const bool periodic_output;
+
+	/**
+	 * All the tuples (y, x, pattern) that should be propagated.
+	 * The tuple should be propagated when wave.get(y, x, pattern) is set to false.
+	 */
+	vector<tuple<unsigned, unsigned, unsigned>> propagating;
+
+	/**
+	 * compatible.get(y, x, pattern)[direction] contains the number of patterns present in the wave
+	 * that can be placed in the cell next to (y,x) in the opposite direction of direction without being
+	 * in contradiction with pattern placed in (y,x).
+	 * If wave.get(y, x, pattern) is set to false, then compatible.get(y, x, pattern) has every element negative or null
+	 */
+	Array3D<array<int, 4>> compatible;
+
+	/**
+	 * Initialize compatible.
+	 */
+	void init_compatible() noexcept {
+		array<int, 4> value;
+		// We compute the number of pattern compatible in all directions.
+		for(unsigned y = 0; y < wave_height; y++) {
+			for(unsigned x = 0; x < wave_width; x++) {
+				for(unsigned pattern = 0; pattern < patterns_size; pattern++) {
+					for(int direction = 0; direction < 4; direction++) {
+						value[direction] = propagator[pattern][get_opposite_direction(direction)].size();
+					}
+					compatible.get(y, x, pattern) = value;
+				}
+			}
+		}
+	}
+
+public:
+
+	/**
+	 * Constructor building the propagator and initializing compatible.
+	 */
+	Propagator(unsigned wave_height, unsigned wave_width, bool periodic_output,
+						 vector<array<vector<unsigned>, 4>> propagator) noexcept :
+		patterns_size(propagator.size()), propagator(propagator), wave_width(wave_width),
+		wave_height(wave_height), periodic_output(periodic_output),
+		compatible(wave_height, wave_width, patterns_size)
+	{
+		init_compatible();
+	}
+
+	/**
+	 * Add an element to the propagator.
+	 * This function is called when wave.get(y, x, pattern) is set to false.
+	 */
+	void add_to_propagator(unsigned y, unsigned x, unsigned pattern) noexcept {
+		// All the direction are set to 0, since the pattern cannot be set in (y,x).
+		array<int, 4> temp = {0,0,0,0};
+		compatible.get(y,x,pattern) = temp;
+		propagating.emplace_back(y, x, pattern);
+	}
+
+	/**
+	 * Propagate the information given with add_to_propagator.
+	 */
+	void propagate(Wave& wave) noexcept {
+
+		// We propagate every element while there is element to propagate.
+		while(propagating.size() != 0) {
+
+			// The cell and pattern that has been set to false.
+			unsigned y1, x1, pattern;
+			tie(y1, x1, pattern) = propagating.back();
+			propagating.pop_back();
+
+			// We propagate the information in all 4 directions.
+			for(unsigned direction = 0; direction < 4; direction++) {
+
+				// We get the next cell in the direction direction.
+				int dx = directions_x[direction];
+				int dy = directions_y[direction];
+				int x2, y2;
+				if(periodic_output) {
+					x2 = ((int)x1 + dx + (int)wave.width) % wave.width;
+					y2 = ((int)y1 + dy + (int)wave.height) % wave.height;
+				} else {
+					x2 = x1 + dx;
+					y2 = y1 + dy;
+					if(x2 < 0 || x2 >= (int)wave.width) {
+						continue;
+					}
+					if(y2 < 0 || y2 >= (int)wave.height) {
+						continue;
+					}
+				}
+
+				// The index of the second cell, and the patterns compatible
+				unsigned i2 = x2 + y2 * wave.width;
+				const vector<unsigned>& patterns = propagator[pattern][direction];
+
+				// For every pattern that could be placed in that cell without being in contradiction with pattern1
+				for(auto it = patterns.begin(), it_end = patterns.end(); it < it_end; ++it) {
+
+					// We decrease the number of compatible patterns in the opposite direction
+					// If the pattern was discarded from the wave, the element is still negative, which is not a problem
+					array<int, 4>& value = compatible.get(y2, x2, *it);
+					value[direction]--;
+
+					// If the element was set to 0 with this operation, we need to remove the pattern
+					// from the wave, and propagate the information
+					if(value[direction] == 0) {
+						add_to_propagator(y2, x2, *it);
+						wave.set(i2, *it, false);
+					}
+				}
+			}
+		}
+	}
+};
diff --git a/src/wfc/utils/array2D.hpp b/src/wfc/utils/array2D.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6769d7e442ca8f1a6b57d8d8db07191b49417dbb
--- /dev/null
+++ b/src/wfc/utils/array2D.hpp
@@ -0,0 +1,142 @@
+#pragma once
+
+#include <vector>
+#include <ostream>
+#include "assert.h"
+#include <iostream>
+using namespace std;
+
+/**
+ * Represent a 2D array.
+ * The 2D array is stored in a single array, to improve cache usage.
+ */
+template<typename T>
+class Array2D {
+
+public:
+  /**
+   * Height and width of the 2D array.
+   */
+  unsigned height;
+  unsigned width;
+
+  /**
+   * The array containing the data of the 2D array.
+   */
+  vector<T> data;
+
+  /**
+   * Build a 2D array given its height and width.
+   * All the array elements are initialized to default value.
+   */
+  Array2D(unsigned height, unsigned width) noexcept :
+    height(height), width(width), data(width * height) {
+  }
+
+  /**
+   * Build a 2D array given its height and width.
+   * All the array elements are initialized to value.
+   */
+  Array2D(unsigned height, unsigned width, T value) noexcept :
+    height(height), width(width), data(width * height, value) {
+  }
+
+  void import(Array2D<T> &src) noexcept {
+    height = src.height;
+    width = src.width;
+    data.clear();
+    data = src.data;
+  }
+
+  /**
+   * Return a const reference to the element in the i-th line and j-th column.
+   * i must be lower than height and j lower than width.
+   */
+  const T& get(unsigned i, unsigned j) const noexcept {
+    assert(i < height && j < width);
+    return data[j + i * width];
+  }
+
+  /**
+   * Return a reference to the element in the i-th line and j-th column.
+   * i must be lower than height and j lower than width.
+   */
+  T& get(unsigned i, unsigned j) noexcept {
+    assert(i < height && j < width);
+    return data[j + i * width];
+  }
+
+  /**
+   * Return the current 2D array reflected along the x axis.
+   */
+  Array2D<T> reflected() const noexcept {
+    Array2D<T> result = Array2D<T>(width, height);
+    for(unsigned y = 0; y < height; y++) {
+      for(unsigned x = 0; x < width; x++) {
+        result.get(y, x) = get(y, width - 1 - x);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Return the current 2D array rotated 90° clockwise
+   */
+  Array2D<T> rotated() const noexcept {
+    Array2D<T> result = Array2D<T>(width, height);
+    for(unsigned y = 0; y < width; y++) {
+      for(unsigned x = 0; x < height; x++) {
+        result.get(y, x) = get(x, width - 1 - y);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Return the sub 2D array starting from (y,x) and with size (sub_width, sub_height).
+   * The current 2D array is considered toric for this operation.
+   */
+  Array2D<T> get_sub_array(unsigned y, unsigned x, unsigned sub_width, unsigned sub_height) const noexcept {
+    Array2D<T> sub_array_2d = Array2D<T>(sub_width, sub_height);
+    for(unsigned ki = 0; ki < sub_height; ki++) {
+      for(unsigned kj = 0; kj < sub_width; kj++) {
+        sub_array_2d.get(ki, kj) = get((y+ki) % height, (x+kj) % width);
+      }
+    }
+    return sub_array_2d;
+  }
+
+  /**
+   * Check if two 2D arrays are equals.
+   */
+  bool operator==(const Array2D& a) const noexcept {
+    if(height != a.height || width != a.width) {
+      return false;
+    }
+
+    for(unsigned i = 0; i<data.size(); i++) {
+      if(a.data[i] != data[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+};
+
+/**
+ * Hash function.
+ */
+namespace std {
+  template<typename T>
+  class hash<Array2D<T>> {
+  public:
+    size_t operator()(const Array2D<T> &a) const noexcept
+    {
+        std::size_t seed = a.data.size();
+        for(const T& i: a.data) {
+          seed ^= hash<T>()(i) + (size_t)0x9e3779b9 + (seed << 6) + (seed >> 2);
+        }
+        return seed;
+    }
+  };
+}
diff --git a/src/wfc/utils/array3D.hpp b/src/wfc/utils/array3D.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1751cab57610bb5cad462f2f71c63f310833735e
--- /dev/null
+++ b/src/wfc/utils/array3D.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <vector>
+#include "assert.h"
+
+using namespace std;
+
+/**
+ * Represent a 3D array.
+ * The 3D array is stored in a single array, to improve cache usage.
+ */
+template<typename T>
+class Array3D {
+
+public:
+  /**
+   * The dimensions of the 3D array.
+   */
+  const unsigned height;
+  const unsigned width;
+  const unsigned depth;
+
+  /**
+   * The array containing the data of the 3D array.
+   */
+  vector<T> data;
+
+  /**
+   * Build a 2D array given its height, width and depth.
+   * All the arrays elements are initialized to default value.
+   */
+  Array3D(unsigned height, unsigned width, unsigned depth) noexcept :
+    height(height), width(width), depth(depth), data(width * height * depth) {
+  }
+
+  /**
+   * Build a 2D array given its height, width and depth.
+   * All the arrays elements are initialized to value
+   */
+  Array3D(unsigned height, unsigned width, unsigned depth, T value) noexcept :
+    height(height), width(width), depth(depth), data(width * height * depth, value) {
+  }
+
+  /**
+   * Return a const reference to the element in the i-th line, j-th column, and k-th depth.
+   * i must be lower than height, j lower than width, and k lower than depth.
+   */
+  const T& get(unsigned i, unsigned j, unsigned k) const noexcept {
+    assert(i < height && j < width << k < depth);
+    return data[i * width * depth + j * depth + k];
+  }
+
+  /**
+   * Return a reference to the element in the i-th line, j-th column, and k-th depth.
+   * i must be lower than height, j lower than width, and k lower than depth.
+   */
+  T& get(unsigned i, unsigned j, unsigned k) noexcept {
+    return data[i * width * depth + j * depth + k];
+  }
+
+  /**
+   * Check if two 3D arrays are equals.
+   */
+  bool operator==(const Array3D& a) const noexcept {
+    if(height != a.height || width != a.width || depth != a.depth) {
+      return false;
+    }
+
+    for(unsigned i = 0; i<data.size(); i++) {
+      if(a.data[i] != data[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+};
diff --git a/src/wfc/utils/color.hpp b/src/wfc/utils/color.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4208fc40f1a05c92cab49f8a4d3c4d772bc1ed5a
--- /dev/null
+++ b/src/wfc/utils/color.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <iostream>
+#include <vector>
+
+using namespace std;
diff --git a/src/wfc/wave.hpp b/src/wfc/wave.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9c25a44934b01bb878173e2bfd91151660c9dea5
--- /dev/null
+++ b/src/wfc/wave.hpp
@@ -0,0 +1,215 @@
+#pragma once
+
+#include <vector>
+#include <stdint.h>
+#include <limits>
+#include <math.h>
+#include <random>
+#include <iostream>
+#include "utils/array2D.hpp"
+
+using namespace std;
+
+/**
+ * Struct containing the values needed to compute the entropy of all the cells.
+ * This struct is updated every time the wave is changed.
+ * p'(pattern) is equal to patterns_frequencies[pattern] if wave.get(cell, pattern) is set to true, otherwise 0.
+ */
+struct EntropyMemoisation {
+	vector<double> plogp_sum;     // The sum of p'(pattern) * log(p'(pattern)).
+	vector<double> sum;           // The sum of p'(pattern).
+	vector<double> log_sum;       // The log of sum.
+	vector<unsigned> nb_patterns; // The number of patterns present in the wave in the cell.
+	vector<double> entropy;       // The entropy of the cell.
+};
+
+/**
+ * Contains the pattern possibilities in every cell.
+ * Also contains information about cell entropy.
+ */
+class Wave {
+private:
+
+	/**
+	 * The patterns frequencies p given to wfc.
+	 */
+	const vector<double> patterns_frequencies;
+
+	/**
+	 * The precomputation of p * log(p).
+	 */
+	const vector<double> plogp_patterns_frequencies;
+
+	/**
+	 * The precomputation of min (p * log(p)) / 2.
+	 * This is used to define the maximum value of the noise.
+	 */
+	const double half_min_plogp;
+
+	/**
+	 * The memoisation of important values for the computation of entropy.
+	 */
+	EntropyMemoisation memoisation;
+
+	/**
+	 * This value is set to true if there is a contradiction in the wave (all elements set to false in a cell).
+	 */
+	bool is_impossible;
+
+	/**
+	 * The number of distinct patterns.
+	 */
+	const unsigned nb_patterns;
+
+	/**
+	 * The actual wave. data.get(index, pattern) is equal to 0 if the pattern can be placed in the cell index.
+	 */
+	Array2D<uint8_t> data;
+
+	/**
+	 * Return distribution * log(distribution).
+	 */
+	static vector<double> get_plogp(const vector<double>& distribution) noexcept {
+		vector<double> plogp;
+		for(unsigned i = 0; i < distribution.size(); i++) {
+			plogp.push_back(distribution[i] * log(distribution[i]));
+		}
+		return plogp;
+	}
+
+	/**
+	 * Return min(v) / 2.
+	 */
+	static double get_half_min(const vector<double>& v) noexcept {
+		double half_min = numeric_limits<double>::infinity();
+		for(unsigned i = 0; i < v.size(); i++) {
+			half_min = min(half_min, v[i] / 2.0);
+		}
+		return half_min;
+	}
+
+public:
+	/**
+	 * The size of the wave.
+	 */
+	const unsigned width;
+	const unsigned height;
+	const unsigned size;
+
+	/**
+	 * Initialize the wave with every cell being able to have every pattern.
+	 */
+	Wave(unsigned height, unsigned width, const vector<double>& patterns_frequencies) noexcept :
+		patterns_frequencies(patterns_frequencies),
+		plogp_patterns_frequencies(get_plogp(patterns_frequencies)),
+		half_min_plogp(get_half_min(plogp_patterns_frequencies)),
+		is_impossible(false),
+		nb_patterns(patterns_frequencies.size()),
+		data(width * height, nb_patterns, 1),
+		width(width), height(height), size(height * width)
+	{
+		// Initialize the memoisation of entropy.
+		double base_entropy = 0;
+		double base_s = 0;
+		double half_min_plogp = numeric_limits<double>::infinity();
+		for(unsigned i = 0; i < nb_patterns; i++) {
+			half_min_plogp = min(half_min_plogp, plogp_patterns_frequencies[i] / 2.0);
+			base_entropy += plogp_patterns_frequencies[i];
+			base_s += patterns_frequencies[i];
+		}
+		double log_base_s = log(base_s);
+		double entropy_base = log_base_s - base_entropy / base_s;
+		memoisation.plogp_sum = vector<double>(width * height, base_entropy);
+		memoisation.sum = vector<double>(width * height, base_s);
+		memoisation.log_sum = vector<double>(width * height, log_base_s);
+		memoisation.nb_patterns = vector<unsigned>(width * height, nb_patterns);
+		memoisation.entropy = vector<double>(width * height, entropy_base);
+	}
+
+	/**
+	 * Return true if pattern can be placed in cell index.
+	 */
+	bool get(unsigned index, unsigned pattern) const noexcept {
+		return data.get(index, pattern);
+	}
+
+	/**
+	 * Return true if pattern can be placed in cell (i,j)
+	 */
+	bool get(unsigned i, unsigned j, unsigned pattern) const noexcept {
+		return get(i * width + j, pattern);
+	}
+
+	/**
+	 * Set the value of pattern in cell index.
+	 */
+	void set(unsigned index, unsigned pattern, bool value) noexcept {
+		bool old_value = data.get(index, pattern);
+		// If the value isn't changed, nothing needs to be done.
+		if(old_value == value) {
+			return;
+		}
+		// Otherwise, the memoisation should be updated.
+		data.get(index, pattern) = value;
+		memoisation.plogp_sum[index] -= plogp_patterns_frequencies[pattern];
+		memoisation.sum[index] -= patterns_frequencies[pattern];
+		memoisation.log_sum[index] = log(memoisation.sum[index]);
+		memoisation.nb_patterns[index]--;
+		memoisation.entropy[index] = memoisation.log_sum[index] - memoisation.plogp_sum[index] / memoisation.sum[index];
+		// If there is no patterns possible in the cell, then there is a contradiction.
+		if(memoisation.nb_patterns[index] == 0) {
+			is_impossible = true;
+		}
+	}
+
+	/**
+	 * Set the value of pattern in cell (i,j).
+	 */
+	void set(unsigned i, unsigned j, unsigned pattern, bool value) noexcept {
+		set(i * width + j, pattern, value);
+	}
+
+	/**
+	 * Return the index of the cell with lowest entropy different of 0.
+	 * If there is a contradiction in the wave, return -2.
+	 * If every cell is decided, return -1.
+	 */
+	int get_min_entropy(minstd_rand& gen) const noexcept {
+		if(is_impossible) {
+			return -2;
+		}
+
+		std::uniform_real_distribution<> dis(0,half_min_plogp);
+
+		// The minimum entropy (plus a small noise)
+		double min = numeric_limits<double>::infinity();
+		int argmin = -1;
+
+		for(unsigned i = 0; i < size; i++) {
+
+			// If the cell is decided, we do not compute the entropy (which is equal to 0).
+			double nb_patterns = memoisation.nb_patterns[i];
+			if(nb_patterns == 1) {
+				continue;
+			}
+
+			// Otherwise, we take the memoised entropy.
+			double entropy = memoisation.entropy[i];
+
+			// We first check if the entropy is less than the minimum.
+			// This is important to reduce noise computation (which is not negligible).
+			if(entropy <= min) {
+
+				// Then, we add noise to decide randomly which will be chosen.
+				// noise is smaller than the smallest p * log(p), so the minimum entropy will always be chosen.
+				double noise = dis(gen);
+				if(entropy + noise < min) {
+					min = entropy + noise;
+					argmin = i;
+				}
+			}
+		}
+		return argmin;
+	}
+
+};
diff --git a/src/wfc/wfc.hpp b/src/wfc/wfc.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6c8a7a4174265573a1793754a49ef300f47c6abc
--- /dev/null
+++ b/src/wfc/wfc.hpp
@@ -0,0 +1,177 @@
+#pragma once
+
+#include <unordered_map>
+#include <limits>
+#include <cmath>
+#include <random>
+
+#include "optional.hpp"
+#include "utils/array2D.hpp"
+#include "wave.hpp"
+#include "propagator.hpp"
+
+using namespace std;
+using namespace cl;
+
+/**
+ * Class containing the generic WFC algorithm.
+ */
+class WFC {
+private:
+
+	/**
+	 * The random number generator.
+	 */
+	minstd_rand gen;
+
+	/**
+	 * The wave, indicating which patterns can be put in which cell.
+	 */
+	Wave wave;
+
+	/**
+	 * The distribution of the patterns as given in input.
+	 */
+	const vector<double> patterns_frequencies;
+
+	/**
+	 * The number of distinct patterns.
+	 */
+	const unsigned nb_patterns;
+
+	/**
+	 * The propagator, used to propagate the information in the wave.
+	 */
+	Propagator propagator;
+
+	/**
+	 * True if the output is periodic.
+	 */
+	const bool periodic_output;
+
+	/**
+	 * Transform the wave to a valid output (a 2d array of patterns that aren't in contradiction).
+	 * This function should be used only when all cell of the wave are defined.
+	 */
+	Array2D<unsigned> wave_to_output() const noexcept {
+		Array2D<unsigned> output_patterns(wave.height, wave.width);
+		for(unsigned i = 0; i< wave.size; i++) {
+			for(unsigned k = 0; k < nb_patterns; k++) {
+				if(wave.get(i, k)) {
+					output_patterns.data[i] = k;
+				}
+			}
+		}
+		return output_patterns;
+	}
+
+
+public:
+
+	/**
+	 * Basic constructor initializing the algorithm.
+	 */
+	WFC(bool periodic_output, int seed, vector<double> patterns_frequencies,
+			vector<array<vector<unsigned>, 4>> propagator, unsigned wave_height, unsigned wave_width) noexcept
+		: gen(seed), wave(wave_height, wave_width, patterns_frequencies),
+			patterns_frequencies(patterns_frequencies), nb_patterns(propagator.size()),
+			propagator(wave.height, wave.width, periodic_output, propagator),
+			periodic_output(periodic_output)
+	{
+	}
+
+	/**
+	 * Run the algorithm, and return a result if it succeeded.
+	 */
+	optional<Array2D<unsigned>> run() noexcept {
+		while(true) {
+
+			// Define the value of an undefined cell.
+			ObserveStatus result = observe();
+
+			// Check if the algorithm has terminated.
+			if(result == failure) {
+				return nullopt;
+			} else if(result == success) {
+				return wave_to_output();
+			}
+
+			// Propagate the information.
+			propagator.propagate(wave);
+		}
+	}
+
+	/**
+	 * Return value of observe.
+	 */
+	enum ObserveStatus {
+		success,      // WFC has finished and has succeeded.
+		failure,      // WFC has finished and failed.
+		to_continue   // WFC isn't finished.
+	};
+
+	/**
+	 * Define the value of the cell with lowest entropy.
+	 */
+	ObserveStatus observe() noexcept {
+		// Get the cell with lowest entropy.
+		int argmin = wave.get_min_entropy(gen);
+
+		// If there is a contradiction, the algorithm has failed.
+		if(argmin == -2) {
+			return failure;
+		}
+
+		// If the lowest entropy is 0, then the algorithm has succeeded and finished.
+		if(argmin == -1) {
+			wave_to_output();
+			return success;
+		}
+
+		// Choose an element according to the pattern distribution
+		double s = 0;
+		for(unsigned k = 0; k < nb_patterns; k++) {
+			s+= wave.get(argmin,k) ? patterns_frequencies[k] : 0;
+		}
+
+		std::uniform_real_distribution<> dis(0,s);
+		double random_value = dis(gen);
+		unsigned chosen_value = nb_patterns - 1;
+
+		for(unsigned k = 0; k < nb_patterns; k++) {
+			random_value -= wave.get(argmin,k) ? patterns_frequencies[k] : 0;
+			if(random_value <= 0) {
+				chosen_value = k;
+				break;
+			}
+		}
+
+		// And define the cell with the pattern.
+		for(unsigned k = 0; k < nb_patterns; k++) {
+			if(wave.get(argmin, k) != (k == chosen_value)) {
+				propagator.add_to_propagator(argmin / wave.width, argmin % wave.width, k);
+				wave.set(argmin, k, false);
+			}
+		}
+
+		return to_continue;
+	}
+
+	/**
+	 * Propagate the information of the wave.
+	 */
+	void propagate() noexcept {
+		propagator.propagate(wave);
+	}
+
+	/**
+	 * Remove pattern from cell (i,j).
+	 */
+	void remove_wave_pattern(unsigned i, unsigned j, unsigned pattern) noexcept {
+		if(wave.get(i, j, pattern)) {
+			wave.set(i, j, pattern, false);
+			propagator.add_to_propagator(i, j, pattern);
+		}
+	}
+};
+