<commit_before>require 'misc'
require 'Vector'
require 'AABB'
require 'graphgen'
require 'layoutgen'
require 'roomgen'
require 'Level'
require 'Actor'
require 'Scheduler'
require 'action'
require 'KDTree'
require 'metalines'
require 'texture'

local w, h = love.graphics.getWidth(), love.graphics.getHeight()
local camx, camy = 0, 0
local track = false
local actions = {}
local playerAction = nil


local function _gen()
	local w, h = love.graphics.getWidth(), love.graphics.getHeight()

	local rgen =
		function ( ... )
			local r = math.random()
			if r < 0.33 then
				-- return roomgen.browniangrid(...)
				return roomgen.cellulargrid(...)
			elseif r < 0.66 then
				return roomgen.random(...)
			else
				return roomgen.hexgrid(...)
			end
		end

	local level = Level.new {
		aabb = AABB.new {
			xmin = -w,
			ymin = -h,
			xmax = 2 * w,
			ymax = 2 * h,
		},
		-- margin = 50,
		margin = 100,
		layout = layoutgen.splat,
		roomgen = rgen,
		graphgen = graphgen.gabriel,
	}

	local numrooms = table.count(level.rooms)

	local actors = {}

	for _, room in pairs(level.rooms) do
		local vertex = table.random(room.vertices)
		assert(vertex)

		local actor = Actor.new(level, vertex, string.char(64 + #actors))

		actors[#actors+1] = actor
	end

	local scheduler = Scheduler.new()

	scheduler:add(
		function ()
			if playerAction then
				local cost, action = playerAction.cost, playerAction.action
				playerAction = nil
				return cost, action
			end

			return 0, nil
		end)

	for i = 2, #actors do
		local actor = actors[i]

		scheduler:add(
			function ()
				local dir = table.random(Level.Dir)
				local target = actor.vertex.dirs[dir]

				if not target or target.actor then
					return action.search(level, actor)
				else
					return action.move(level, actor, target)
				end
			end)
	end

	return level, actors, scheduler
end

local level, actors, scheduler = _gen()
local actions = {}
local warp = 1

gamemode = {}

function gamemode.update()
	local isDown = love.keyboard.isDown

	local camdx = (isDown('left') and -1 or 0) + (isDown('right') and 1 or 0)
	local camdy = (isDown('up') and -1 or 0) + (isDown('down') and 1 or 0)

	local dt = warp * love.timer.getDelta()
	
	camx = camx + (500 * dt * camdx)
	camy = camy + (500 * dt * camdy)

	if #actions == 0 then
		local complete, ticks = false, 0
		complete, ticks, actions = scheduler:run()
		-- print('run', ticks)
		
		for _, action in ipairs(actions) do
			action.time = 0
		end
	else
		local action = actions[1]

		-- print('actions', #actions)

		if action.sync then
			local running = action.plan(action.time)

			-- print('sync', running)

			if running then
				action.time = action.time + dt
			else
				table.remove(actions, 1)
			end
		else
			local index = 1

			while index <= #actions and not actions[index].sync do
				local action = actions[index]
				local running = action.plan(action.time)

				-- print('not sync', index, action.time)

				if running then
					index = index + 1

					action.time = action.time + dt
				else
					table.remove(actions, index)
				end
			end
		end
	end
end

local zoomed = true
local drawBoxes = false

local unknown = { 0, 0, 0, 255 }
local known = { 0, 45, 0, 255 }

local function _drawKDTree( kdtree, aabb )
	if kdtree.horz then
		love.graphics.line(kdtree.axis, aabb.ymin, kdtree.axis, aabb.ymax)
	else
		love.graphics.line(aabb.xmin, kdtree.axis, aabb.xmax, kdtree.axis)
	end

	local branch1 = kdtree[1]
	local branch2 = kdtree[2]

	if branch1 and branch1.horz ~= nil then
		local aabb = AABB.new(aabb)

		if kdtree.horz then
			aabb.xmax = kdtree.axis
		else
			aabb.ymax = kdtree.axis
		end

		_drawKDTree(branch1, aabb)
	end

	if branch2 and branch2.horz ~= nil then
		local aabb = AABB.new(aabb)

		if kdtree.horz then
			aabb.xmin = kdtree.axis
		else
			aabb.ymin = kdtree.axis
		end

		_drawKDTree(branch2, aabb)
	end
end

local canvas = love.graphics.newCanvas()
local clut =
	(function ()
		-- Non official colour names.
		local lilac = { 132, 83, 255, 255 }
		local verdant = { 22, 178, 39, 255 }
		local drygrass = { 57, 255, 79, 255 }
		local clay = { 204, 114, 25, 255 }
		local sandysoil = { 178, 104, 31, 255 }

		local blur = 5
		local b1 = 25
		local b2 = 35
		local b3 = 50
		local b4 = 80

		local bands = {
			[1] = lilac,
			[b1-blur] = lilac,
			[b1+blur] = verdant,
			[b2-blur] = verdant,
			[b2+blur] = drygrass,
			[b3-blur] = drygrass,
			[b3+blur] = clay,
			[b4-blur] = clay,
			[b4+blur] = sandysoil,
			[100] = sandysoil,
		}

		return texture.bandedCLUT(bands, 256, 256, 'grey')
	end)()

local mound = texture.mound(256, 256)
local blobs = love.graphics.newImage('resources/blobs.png')
local bricks = love.graphics.newImage('resources/bricks.png')
local crystal = love.graphics.newImage('resources/crystal.png')
local triforce = love.graphics.newImage('resources/triforce.png')

local backlight = texture.featheredCircle(256, 256, 0, 255, 0, 255, 0.85)
local forelight = texture.smootherCircle(256, 256, 0, 0, 255, 255, 0.5)

-- local backlight = love.graphics.newImage('resources/blobs-g.png')
-- local forelight = love.graphics.newImage('resources/blobs-b.png')

local moundEffect = love.graphics.newPixelEffect [[
	extern Image clut;

	vec4 effect(vec4 color, Image tex, vec2 tc, vec2 pc)
	{
		vec3 p = Texel(tex, tc).xyz;
		float h = p.x;
		float l = (0.25 * p.g) + (0.75 * p.b);

		return Texel(clut, vec2(l, h));
	}
]]

local drawEdges = true
local drawVertices = true
local drawMetalines = false
local drawMounds = true
local drawHeightfield = false

local _spriteBatches = {}

local function _getSpriteBatches( batches )
	local result = {}

	for name, params in pairs(batches) do
		local spriteBatch = _spriteBatches[name]

		-- TODO: should also check whether the images are the same.
		if not spriteBatch or spriteBatch.size < params.size then
			spriteBatch = {
				batch = love.graphics.newSpriteBatch(params.image, params.size),
				size = params.size
			}

			_spriteBatches[name] = spriteBatch
		end

		result[name] = spriteBatch
	end

	return result
end

function shadowf( x, y, ... )
	love.graphics.setColor(0, 0, 0, 255)

	local text = string.format(...)

	love.graphics.print(text, x-1, y-1)
	love.graphics.print(text, x-1, y+1)
	love.graphics.print(text, x+1, y-1)
	love.graphics.print(text, x+1, y+1)

	love.graphics.setColor(192, 192, 192, 255)

	love.graphics.print(text, x, y)
end

function gamemode.draw()
	love.graphics.push()
	
	local xform = {
		scale = { 1, 1 },
		translate = { 0, 0 },
	}

	if zoomed then
		xform.scale = { 1/3, 1/3 }
		xform.translate = { 800, 600 }
	else
		if track then
			camx, camy = actors[1][1] - (w * 0.5), actors[1][2] - (h * 0.5)
		end

		xform.translate = { -camx, -camy }
	end

	love.graphics.scale(xform.scale[1], xform.scale[2])
	love.graphics.translate(xform.translate[1], xform.translate[2])

	-- LoS and FoW
	--
	-- Three state FoW unknown, known and not in LoS and known and in LoS.
	-- The known flag is stored on the vertices as a boolean or nil and the
	-- distances table is the LoS.
	local maxdepth = 2
	-- local maxdepth = math.min(4, table.count(actors[1].vertex.dirs) - 1)
	local distances = level:distanceMap(actors[1].vertex, maxdepth)

	for vertex, distance in pairs(distances) do
		vertex.known = true
	end

	local width = 50

	-- Metalines
	local count = 0

	if drawMetalines then
		local point1s, point2s = level:points()
		
		local intensities = {}

		for edge, endverts in pairs(level.graph.edges) do
			local vertex1, vertex2 = endverts[1], endverts[2]

			local intensity1 = vertex1.known and 0.05 or 0
			local intensity2 = vertex2.known and 0.05 or 0

			local distance1 = distances[vertex1]
			local distance2 = distances[vertex2]

			if distance1 then
				intensity1 = 0.5 + (maxdepth - distance1) / (maxdepth * 2)
				-- intensity1 = 1
			end

			if distance2 then
				intensity2 = 0.25 + (maxdepth - distance2) / (maxdepth * 2.5)
				-- intensity2 = 1
			end

			intensities[#intensities+1] = { intensity1, intensity2 }
		end

		count = metalines.draw(canvas, xform, w, h, point1s, point2s, intensities, width, clut)
	end

	if drawMounds then
		local numVertices = table.count(level.graph.vertices)
		local numSeenVertices = table.count(distances)
		
		local batches = _getSpriteBatches {
			height = {
				-- image = triforce,
				-- image = crystal,
				image = bricks,
				-- image = blobs,
				-- image = mound,
				size = numVertices,
			},
			backlight = {
				image = backlight,<commit_msg>Added a test CLUT for old school monochrome feel.<commit_after>require 'misc'
require 'Vector'
require 'AABB'
require 'graphgen'
require 'layoutgen'
require 'roomgen'
require 'Level'
require 'Actor'
require 'Scheduler'
require 'action'
require 'KDTree'
require 'metalines'
require 'texture'

local w, h = love.graphics.getWidth(), love.graphics.getHeight()
local camx, camy = 0, 0
local track = false
local actions = {}
local playerAction = nil


local function _gen()
	local w, h = love.graphics.getWidth(), love.graphics.getHeight()

	local rgen =
		function ( ... )
			local r = math.random()
			if r < 0.33 then
				-- return roomgen.browniangrid(...)
				return roomgen.cellulargrid(...)
			elseif r < 0.66 then
				return roomgen.random(...)
			else
				return roomgen.hexgrid(...)
			end
		end

	local level = Level.new {
		aabb = AABB.new {
			xmin = -w,
			ymin = -h,
			xmax = 2 * w,
			ymax = 2 * h,
		},
		-- margin = 50,
		margin = 100,
		layout = layoutgen.splat,
		roomgen = rgen,
		graphgen = graphgen.gabriel,
	}

	local numrooms = table.count(level.rooms)

	local actors = {}

	for _, room in pairs(level.rooms) do
		local vertex = table.random(room.vertices)
		assert(vertex)

		local actor = Actor.new(level, vertex, string.char(64 + #actors))

		actors[#actors+1] = actor
	end

	local scheduler = Scheduler.new()

	scheduler:add(
		function ()
			if playerAction then
				local cost, action = playerAction.cost, playerAction.action
				playerAction = nil
				return cost, action
			end

			return 0, nil
		end)

	for i = 2, #actors do
		local actor = actors[i]

		scheduler:add(
			function ()
				local dir = table.random(Level.Dir)
				local target = actor.vertex.dirs[dir]

				if not target or target.actor then
					return action.search(level, actor)
				else
					return action.move(level, actor, target)
				end
			end)
	end

	return level, actors, scheduler
end

local level, actors, scheduler = _gen()
local actions = {}
local warp = 1

gamemode = {}

function gamemode.update()
	local isDown = love.keyboard.isDown

	local camdx = (isDown('left') and -1 or 0) + (isDown('right') and 1 or 0)
	local camdy = (isDown('up') and -1 or 0) + (isDown('down') and 1 or 0)

	local dt = warp * love.timer.getDelta()
	
	camx = camx + (500 * dt * camdx)
	camy = camy + (500 * dt * camdy)

	if #actions == 0 then
		local complete, ticks = false, 0
		complete, ticks, actions = scheduler:run()
		-- print('run', ticks)
		
		for _, action in ipairs(actions) do
			action.time = 0
		end
	else
		local action = actions[1]

		-- print('actions', #actions)

		if action.sync then
			local running = action.plan(action.time)

			-- print('sync', running)

			if running then
				action.time = action.time + dt
			else
				table.remove(actions, 1)
			end
		else
			local index = 1

			while index <= #actions and not actions[index].sync do
				local action = actions[index]
				local running = action.plan(action.time)

				-- print('not sync', index, action.time)

				if running then
					index = index + 1

					action.time = action.time + dt
				else
					table.remove(actions, index)
				end
			end
		end
	end
end

local zoomed = true
local drawBoxes = false

local unknown = { 0, 0, 0, 255 }
local known = { 0, 45, 0, 255 }

local function _drawKDTree( kdtree, aabb )
	if kdtree.horz then
		love.graphics.line(kdtree.axis, aabb.ymin, kdtree.axis, aabb.ymax)
	else
		love.graphics.line(aabb.xmin, kdtree.axis, aabb.xmax, kdtree.axis)
	end

	local branch1 = kdtree[1]
	local branch2 = kdtree[2]

	if branch1 and branch1.horz ~= nil then
		local aabb = AABB.new(aabb)

		if kdtree.horz then
			aabb.xmax = kdtree.axis
		else
			aabb.ymax = kdtree.axis
		end

		_drawKDTree(branch1, aabb)
	end

	if branch2 and branch2.horz ~= nil then
		local aabb = AABB.new(aabb)

		if kdtree.horz then
			aabb.xmin = kdtree.axis
		else
			aabb.ymin = kdtree.axis
		end

		_drawKDTree(branch2, aabb)
	end
end

local canvas = love.graphics.newCanvas()
local clut =
	(function ()
		-- Non official colour names.
		local lilac = { 132, 83, 255, 255 }
		local verdant = { 22, 178, 39, 255 }
		local drygrass = { 57, 255, 79, 255 }
		local clay = { 204, 114, 25, 255 }
		local sandysoil = { 178, 104, 31, 255 }

		local blur = 5
		local b1 = 25
		local b2 = 35
		local b3 = 50
		local b4 = 80

		local bands = {
			[1] = lilac,
			[b1-blur] = lilac,
			[b1+blur] = verdant,
			[b2-blur] = verdant,
			[b2+blur] = drygrass,
			[b3-blur] = drygrass,
			[b3+blur] = clay,
			[b4-blur] = clay,
			[b4+blur] = sandysoil,
			[100] = sandysoil,
		}

		-- This is a test of black with a white edge for an old-school vibe.
		--
		-- local blur = 5
		-- local b1 = 10
		-- local b2 = 30
	
		-- local black = { 0, 0, 0, 255 }
		-- local white = { 255, 255, 255, 255 }

		-- local bands = {
		-- 	[1] = black,
		-- 	[b1-blur] = white,
		-- 	[b1+blur] = white,
		-- 	[b2-blur] = black,
		-- 	[100] = black,
		-- }

		return texture.bandedCLUT(bands, 256, 256, 'grey')
	end)()

local mound = texture.mound(256, 256)
local blobs = love.graphics.newImage('resources/blobs.png')
local bricks = love.graphics.newImage('resources/bricks.png')
local crystal = love.graphics.newImage('resources/crystal.png')
local triforce = love.graphics.newImage('resources/triforce.png')

local backlight = texture.featheredCircle(256, 256, 0, 255, 0, 255, 0.85)
local forelight = texture.smootherCircle(256, 256, 0, 0, 255, 255, 0.5)

-- local backlight = love.graphics.newImage('resources/blobs-g.png')
-- local forelight = love.graphics.newImage('resources/blobs-b.png')

local moundEffect = love.graphics.newPixelEffect [[
	extern Image clut;

	vec4 effect(vec4 color, Image tex, vec2 tc, vec2 pc)
	{
		vec3 p = Texel(tex, tc).xyz;
		float h = p.x;
		float l = (0.25 * p.g) + (0.75 * p.b);

		return Texel(clut, vec2(l, h));
	}
]]

local drawEdges = true
local drawVertices = true
local drawMetalines = false
local drawMounds = true
local drawHeightfield = false

local _spriteBatches = {}

local function _getSpriteBatches( batches )
	local result = {}

	for name, params in pairs(batches) do
		local spriteBatch = _spriteBatches[name]

		-- TODO: should also check whether the images are the same.
		if not spriteBatch or spriteBatch.size < params.size then
			spriteBatch = {
				batch = love.graphics.newSpriteBatch(params.image, params.size),
				size = params.size
			}

			_spriteBatches[name] = spriteBatch
		end

		result[name] = spriteBatch
	end

	return result
end

function shadowf( x, y, ... )
	love.graphics.setColor(0, 0, 0, 255)

	local text = string.format(...)

	love.graphics.print(text, x-1, y-1)
	love.graphics.print(text, x-1, y+1)
	love.graphics.print(text, x+1, y-1)
	love.graphics.print(text, x+1, y+1)

	love.graphics.setColor(192, 192, 192, 255)

	love.graphics.print(text, x, y)
end

function gamemode.draw()
	love.graphics.push()
	
	local xform = {
		scale = { 1, 1 },
		translate = { 0, 0 },
	}

	if zoomed then
		xform.scale = { 1/3, 1/3 }
		xform.translate = { 800, 600 }
	else
		if track then
			camx, camy = actors[1][1] - (w * 0.5), actors[1][2] - (h * 0.5)
		end

		xform.translate = { -camx, -camy }
	end

	love.graphics.scale(xform.scale[1], xform.scale[2])
	love.graphics.translate(xform.translate[1], xform.translate[2])

	-- LoS and FoW
	--
	-- Three state FoW unknown, known and not in LoS and known and in LoS.
	-- The known flag is stored on the vertices as a boolean or nil and the
	-- distances table is the LoS.
	local maxdepth = 2
	-- local maxdepth = math.min(4, table.count(actors[1].vertex.dirs) - 1)
	local distances = level:distanceMap(actors[1].vertex, maxdepth)

	for vertex, distance in pairs(distances) do
		vertex.known = true
	end

	local width = 50

	-- Metalines
	local count = 0

	if drawMetalines then
		local point1s, point2s = level:points()
		
		local intensities = {}

		for edge, endverts in pairs(level.graph.edges) do
			local vertex1, vertex2 = endverts[1], endverts[2]

			local intensity1 = vertex1.known and 0.05 or 0
			local intensity2 = vertex2.known and 0.05 or 0

			local distance1 = distances[vertex1]
			local distance2 = distances[vertex2]

			if distance1 then
				intensity1 = 0.5 + (maxdepth - distance1) / (maxdepth * 2)
				-- intensity1 = 1
			end

			if distance2 then
				intensity2 = 0.25 + (maxdepth - distance2) / (maxdepth * 2.5)
				-- intensity2 = 1
			end

			intensities[#intensities+1] = { intensity1, intensity2 }
		end

		count = metalines.draw(canvas, xform, w, h, point1s, point2s, intensities, width, clut)
	end

	if drawMounds then
		local numVertices = table.count(level.graph.vertices)
		local numSeenVertices = table.count(distances)
		
		local batches = _getSpriteBatches {
			height = {
				-- image = triforce,
				-- image = crystal,
				-- image = bricks,
				image = blobs,
				-- image = mound,
				size = numVertices,
			},
			backlight = {
				image = backlight,
{'fraction_non_alphanumeric': 0.12632309560992538, 'fraction_numerical': 0.03713343744577477, 'mean_word_length': 3.0312427139193283, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1599789', 'n_tokens_mistral': 7556, 'n_tokens_neox': 6227, 'n_words': 1800}