Compare commits
2 Commits
9635e0ea2f
...
b8e00cf340
Author | SHA1 | Date |
---|---|---|
|
b8e00cf340 | |
|
03f1a96112 |
|
@ -1,2 +1,3 @@
|
|||
build/
|
||||
test/
|
||||
test/
|
||||
backup/
|
2
ai.lua
2
ai.lua
|
@ -79,7 +79,7 @@ function t.draw( nodes )
|
|||
end
|
||||
|
||||
function t.save( nodes )
|
||||
return bmp.savePoints( nodes.all, "512rgb24" )
|
||||
return bmp.ai( nodes.all )
|
||||
end
|
||||
|
||||
return t
|
363
bmp.lua
363
bmp.lua
|
@ -1,48 +1,17 @@
|
|||
--Load and save the bmp formats used by DEFCON.
|
||||
local t = {}
|
||||
local assert = assert
|
||||
local print = print
|
||||
local error = error
|
||||
local table = table
|
||||
local math = math
|
||||
local love = assert( love )
|
||||
local lfs = love.filesystem
|
||||
|
||||
--FFI bit-twiddling stuff.
|
||||
local ffi = require 'ffi'
|
||||
local bit = require 'bit'
|
||||
|
||||
local function getHeader( filename )
|
||||
local offset = love.data.unpack( "<I4", assert(love.filesystem.read( "data", filename, 14 )), 11 )
|
||||
local header, size = assert( love.filesystem.read( filename, offset ) )
|
||||
print( "BMP HEADER", filename, size, "\n", header )
|
||||
return header
|
||||
end
|
||||
|
||||
local formats = {
|
||||
["512rgb24"] = {
|
||||
header = getHeader( "data/earth/africa.bmp" ),
|
||||
encode = function( r ) return math.floor( r * 255 ) end,
|
||||
bytesPerPixel = 3,
|
||||
w = 512,
|
||||
h = 285,
|
||||
},
|
||||
["512r4"] = {
|
||||
header = getHeader( "data/earth/sailable.bmp" ),
|
||||
encode = function( r ) return math.floor( r * 255 / 16 ) end,
|
||||
bytesPerPixel = 0.5,
|
||||
w = 512,
|
||||
h = 285,
|
||||
},
|
||||
["800r4"] = {
|
||||
header = getHeader( "data/earth/travel_nodes.bmp" ),
|
||||
encode = function( r ) return math.floor( r * 255 / 16 ) end,
|
||||
bytesPerPixel = 0.5,
|
||||
w = 512,
|
||||
h = 285,
|
||||
}
|
||||
}
|
||||
local test = {}
|
||||
|
||||
function t.header( format )
|
||||
return formats[format] and formats[format].header
|
||||
end
|
||||
|
||||
function t.load( filename )
|
||||
function test.load( filename )
|
||||
local imgd = love.image.newImageData( filename )
|
||||
print( "LOADING BITMAP: ", filename, imgd:getSize(), imgd:getFormat(), imgd:getDimensions() )
|
||||
local img = love.graphics.newImage( imgd )
|
||||
|
@ -50,75 +19,149 @@ function t.load( filename )
|
|||
return img, imgd
|
||||
end
|
||||
|
||||
--maps an array of 1-byte pixel values (numbers 0 to 15 inclusive)
|
||||
--to an array of half-byte pixel values (which can be concatenated into a string)
|
||||
--BUT don't touch the first element (which is assumed to be a header or something)
|
||||
local function foldByteArray( bytes )
|
||||
local nybbles = { bytes[1] }
|
||||
for j = 2, #bytes / 2 do
|
||||
local a, b = bytes[ 2 * j - 2 ], bytes[ 2 * j - 1 ]
|
||||
nybbles[j] = string.char( 16 * a + b )
|
||||
end
|
||||
return nybbles
|
||||
function test.bitmapToWorld( x, y )
|
||||
local w, a = 360.0, 600.0 / 800.0
|
||||
local h = 360.0 * a
|
||||
x = w * ( x - 800 ) / 800 - w / 2 + 360
|
||||
y = h * ( y - 600 ) / 600 + 180
|
||||
return x, y
|
||||
end
|
||||
|
||||
local function packTwentyFour( bytes )
|
||||
local twentyFour = { bytes[1] }
|
||||
for j = 2, #bytes do
|
||||
bytes[j] = string.char( bytes[j], bytes[j], bytes[j] )
|
||||
function test.compareData( a, b )
|
||||
if a == b then return true end
|
||||
print( "lengths:", a:len(), b:len() )
|
||||
local errors = 0
|
||||
for i = 1, math.min( a:len(), b:len() ) do
|
||||
local a, b = a:sub( i, i ), b:sub( i, i )
|
||||
if a ~= b then
|
||||
errors = errors + 1
|
||||
print( "mismatch:", errors, i, string.byte( a ), string.byte( b ) )
|
||||
end
|
||||
if errors > 1000 then break end
|
||||
end
|
||||
error( "test failed. output bitmap does not match!" )
|
||||
end
|
||||
|
||||
function t.save( data, format )
|
||||
local w, h = data:getDimensions()
|
||||
format = assert( formats[format] )
|
||||
local bytes = { format.header }
|
||||
format.byte = 0
|
||||
local i = 2
|
||||
for x = 0, w - 1 do
|
||||
for y = 0, h - 1 do
|
||||
bytes[i] = format.encode( data:getPixel(x, y) )
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
if format.bytesPerPixel < 1 then bytes = foldByteArray( bytes )
|
||||
else packTwentyFour( bytes ) end
|
||||
return table.concat( bytes )
|
||||
function test.worldToBitmap( x, y )
|
||||
x = 800 * ( x + 180 ) / 360
|
||||
y = 600 + 800 * ( y - 180 ) / 360
|
||||
return x, y
|
||||
end
|
||||
|
||||
--takes an array of world-space points in [-180, 180] x [-100, 100]
|
||||
--e.g { { x = 0.5, y = 0.5 }, { x = 0.4, y = 0.6 }, }, etc.
|
||||
--and a function which maps points to colours
|
||||
function t.savePoints( points, format )
|
||||
format = assert( formats[format] )
|
||||
local function getHeader( filename )
|
||||
local offset = 2 + love.data.unpack( "<I4", assert(love.filesystem.read( "data", filename, 14 )), 11 )
|
||||
local header, size = assert( love.filesystem.read( filename, offset ) )
|
||||
print( "BMP HEADER", filename, size, "\n", string.byte( header, 1, size ) )
|
||||
return header
|
||||
end
|
||||
|
||||
--BGR24 format. Takes the input of Love2D's data:getPixel( x, y )
|
||||
--on paper we would only care about one channel because all of these images are grayscale,
|
||||
--but a few of the pixels in the game's default territories are not exactly gray,
|
||||
--so we do it like this to make sure our output is byte-faithful when saving an unmodified vanilla map
|
||||
local function bgrChar( r, g, b )
|
||||
return string.char( math.floor( b * 255 ), math.floor( g * 255 ), math.floor( r * 255 ) )
|
||||
end
|
||||
|
||||
|
||||
local formats = {
|
||||
["africa"] = {
|
||||
header = getHeader( "data/earth/africa.bmp" ),
|
||||
w = 512,
|
||||
h = 285,
|
||||
},
|
||||
["sailable"] = {
|
||||
header = getHeader( "data/earth/sailable.bmp" ),
|
||||
w = 512,
|
||||
h = 285,
|
||||
--technically this information is in the header already but I don't want to write code to parse it since we only use this one header
|
||||
palette = {
|
||||
[0] = 0,
|
||||
[8] = 1,
|
||||
[20] = 2,
|
||||
[33] = 3,
|
||||
[49] = 4,
|
||||
[90] = 5,
|
||||
[139] = 6,
|
||||
[169] = 7,
|
||||
[189] = 8,
|
||||
[206] = 9,
|
||||
[214] = 10,
|
||||
[222] = 11,
|
||||
[231] = 12,
|
||||
[239] = 13,
|
||||
[247] = 14,
|
||||
[255] = 15
|
||||
},
|
||||
},
|
||||
["travel"] = {
|
||||
header = getHeader( "data/earth/travel_nodes.bmp" ),
|
||||
w = 800,
|
||||
h = 400,
|
||||
},
|
||||
["ai"] = {
|
||||
header = getHeader( "data/earth/ai_markers.bmp" ),
|
||||
w = 512,
|
||||
h = 285,
|
||||
},
|
||||
}
|
||||
|
||||
function formats.ai:test( )
|
||||
print "testing ai nodes"
|
||||
local filename = "data/earth/ai_markers.bmp"
|
||||
local img, imgd = test.load( filename )
|
||||
local idx = 1
|
||||
local nodes = {}
|
||||
for x = 0, 511 do
|
||||
for y = 0, 284 do
|
||||
local r, g = imgd:getPixel( x, 284 - y )
|
||||
if r > 0.5 or g > 0.5 then
|
||||
local long = x * (360 / imgd:getWidth()) - 180
|
||||
local lat = y * (200 / img:getHeight()) - 100
|
||||
local attacking = (r > 0.5)
|
||||
local node = {x = long, y = lat, attacking = attacking, idx = idx}
|
||||
print( "ai marker", idx, x, y, long, lat )
|
||||
nodes[ idx ] = node
|
||||
idx = idx + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local encodedString = self:encode( nodes )
|
||||
love.filesystem.write( "ai_markers.bmp", encodedString )
|
||||
|
||||
local eimg, eimgd = test.load( "ai_markers.bmp" ) --the one we just saved
|
||||
for x = 0, 511 do
|
||||
for y = 0, 284 do
|
||||
local r, g = imgd:getPixel( x, 284 - y )
|
||||
local er, eg = eimgd:getPixel( x, 284 - y )
|
||||
if math.max( r, g, er, eg ) > 0.5 then
|
||||
print( "node pixel: ", x, 284 - y, r, g, er, eg )
|
||||
assert( ( r > 0.5 and er > 0.5 ) or ( g > 0.5 and eg > 0.5 ), "ai marker mismatch!" )
|
||||
end
|
||||
end
|
||||
end
|
||||
print( "ai markers OK" )
|
||||
end
|
||||
|
||||
function formats.ai:encode( data )
|
||||
|
||||
--set up bitmap as an array of pixels
|
||||
local w, h = format.w, format.h
|
||||
local size = 2 + format.w * format.h
|
||||
local bitmap = { format.header }
|
||||
local w, h = self.w, self.h
|
||||
local size = 2 + self.w * self.h
|
||||
local bitmap = { self.header:sub( 1, -3 ) }
|
||||
for j = 2, size do bitmap[j] = 0 end
|
||||
|
||||
--this is black-and-white only. easy case
|
||||
if format.bytesPerPixel < 1 then
|
||||
for i, point in ipairs( points ) do
|
||||
local wx, wy = point.x, point.y
|
||||
--get bitmap coordinates
|
||||
local x, y = math.floor(format.w * (wx + 180) / 360), math.floor(format.h * (1.0 - (wy + 100) / 200))
|
||||
--get index into byte array
|
||||
local idx = 2 + x * format.h + y
|
||||
bitmap[idx] = 0x0f --0b00001111
|
||||
end
|
||||
--now map pixels to 4-bit strings, slap on the header, and pass it back up
|
||||
return table.concat( foldByteArray( bitmap ) )
|
||||
end
|
||||
|
||||
--cf. ai.lua: these points can be red or green
|
||||
for i, point in ipairs( points ) do
|
||||
for i, point in ipairs( data ) do
|
||||
local wx, wy = point.x, point.y
|
||||
--get bitmap coordinates
|
||||
local x, y = math.floor(format.w * (wx + 180) / 360), math.floor(format.h * (1.0 - (wy + 100) / 200))
|
||||
local x = math.floor(self.w * (wx + 180) / 360)
|
||||
--idk why exactly I need to round the value here instead of truncating it
|
||||
local y = math.floor(0.5 + self.h * (wy + 100) / 200)
|
||||
print( "export ai marker", i, x, y, wx, wy )
|
||||
--get index into byte array
|
||||
local idx = 2 + x * format.h + y
|
||||
local idx = 2 + y * self.w + x
|
||||
--in-editor we could have two points in the same place, possibly of the same or different types
|
||||
--in case we miss something upstream, don't corrupt these duplicate points,
|
||||
--just saturate the attack/defense value
|
||||
|
@ -133,10 +176,144 @@ function t.savePoints( points, format )
|
|||
for j = 2, size do
|
||||
if bitmap[j] == 0
|
||||
then bitmap[j] = "\0\0\0"
|
||||
else bitmap[j] = string.char( bitmap[j].attack and 0xff or 0, bitmap[j].place and 0xff or 0, 0 )
|
||||
else bitmap[j] = string.char( 0, bitmap[j].place and 0xff or 0, bitmap[j].attack and 0xff or 0 ) --bgr24
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat( bitmap )
|
||||
end
|
||||
|
||||
function formats.travel:test()
|
||||
print "testing travel nodes"
|
||||
local filename = "data/earth/travel_nodes.bmp"
|
||||
local img, imgd = test.load( filename )
|
||||
local nodes = {}
|
||||
local n = 1
|
||||
for x = 0, 799 do
|
||||
for y = 0, 399 do
|
||||
if imgd:getPixel( x, 399 - y ) > 0 then
|
||||
local long, lat = test.bitmapToWorld( x, y )
|
||||
nodes[n] = {x = long, y = lat, idx = n}
|
||||
print( "read:", n, long, lat, x, y )
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print( "loaded ", n, "nodes" )
|
||||
local encodedString = self:encode( nodes )
|
||||
test.compareData( love.filesystem.read( filename ), encodedString )
|
||||
love.filesystem.write( "travel_nodes.bmp", encodedString )
|
||||
print( "travel nodes OK" )
|
||||
end
|
||||
|
||||
--Sparse bitmap with a few white pixels.
|
||||
function formats.travel:encode( points )
|
||||
--set up bitmap as an array of 8-bit pixels
|
||||
local w, h = self.w, self.h
|
||||
local size = 2 + w * h
|
||||
local bitmap = { self.header }
|
||||
for j = 2, size do bitmap[j] = 0 end
|
||||
|
||||
--write white pixels
|
||||
for i, point in ipairs( points ) do
|
||||
local wx, wy = point.x, point.y
|
||||
-- get bitmap coordinates:
|
||||
local x = 800 * ( wx + 180 ) / 360
|
||||
local y = 600 + 800 * ( wy - 180 ) / 360
|
||||
local offset = math.floor( y * self.w + x - 2 )
|
||||
--1 := white
|
||||
bitmap[offset] = 1
|
||||
print( "encoded:", point.idx, wx, wy, x, y )
|
||||
end
|
||||
|
||||
--fold into 4-bit pixel array
|
||||
local nybbles = { bitmap[1] }
|
||||
for j = 2, size / 2 do
|
||||
local a, b = bitmap[ 2 * j - 2 ], bitmap[ 2 * j - 1 ]
|
||||
nybbles[j] = string.char( 16 * a + b )
|
||||
end
|
||||
|
||||
return table.concat( nybbles )
|
||||
end
|
||||
|
||||
function formats.sailable:test()
|
||||
print "testing sailable"
|
||||
local filename = "data/earth/sailable.bmp"
|
||||
local img, imgd = test.load( filename )
|
||||
local encoded = self:encode( imgd )
|
||||
love.filesystem.write( "sailable_out.bmp", encoded )
|
||||
test.compareData( love.filesystem.read( filename ), encoded )
|
||||
print "sailable OK"
|
||||
end
|
||||
|
||||
function formats.sailable:encode( data )
|
||||
local w, h = self.w, self.h
|
||||
local bytes = { self.header:sub( 1, -2 ) }
|
||||
local i = 2
|
||||
|
||||
--y coordinates are written top to bottom
|
||||
for y = h - 1, 0, -1 do
|
||||
for x = 0, w - 1 do
|
||||
bytes[i] = assert( self.palette[ math.floor( data:getPixel(x, y) * 255 )] )
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
--fold into 4-bit pixel array
|
||||
local nybbles = { bytes[1] }
|
||||
for j = 2, #bytes / 2 do
|
||||
local a, b = bytes[ 2 * j ], bytes[ 2 * j + 1 ]
|
||||
nybbles[j] = string.char( 16 * a + b )
|
||||
end
|
||||
|
||||
return table.concat( nybbles )
|
||||
end
|
||||
|
||||
function formats.africa:test()
|
||||
print "testing africa"
|
||||
local filename = "data/earth/africa.bmp"
|
||||
local img, imgd = test.load( filename )
|
||||
local encoded = self:encode( imgd )
|
||||
love.filesystem.write( "africa_out.bmp", encoded )
|
||||
test.compareData( love.filesystem.read( filename ), encoded )
|
||||
print "africa OK"
|
||||
end
|
||||
|
||||
|
||||
function formats.africa:encode( data )
|
||||
local w, h = self.w, self.h
|
||||
local bytes = { self.header:sub( 1, -3 ) }
|
||||
local i = 2
|
||||
|
||||
--y coordinates are written top to bottom
|
||||
for y = h - 1, 0, -1 do
|
||||
for x = 0, w - 1 do
|
||||
bytes[i] = bgrChar( data:getPixel(x, y) )
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return table.concat( bytes )
|
||||
end
|
||||
|
||||
function t.load( filename )
|
||||
local imgd = love.image.newImageData( filename )
|
||||
print( "LOADING BITMAP: ", filename, imgd:getSize(), imgd:getFormat(), imgd:getDimensions() )
|
||||
local img = love.graphics.newImage( imgd )
|
||||
img:setFilter( "nearest", "nearest" )
|
||||
return img, imgd
|
||||
end
|
||||
|
||||
function t.save( data, format )
|
||||
return assert(formats[format]):encode( data )
|
||||
end
|
||||
|
||||
--convenience
|
||||
formats.territory = formats.africa
|
||||
for fmt, tbl in pairs( formats ) do
|
||||
t[fmt] = function( data ) return tbl:encode( data ) end
|
||||
--TESTING
|
||||
tbl:test()
|
||||
end
|
||||
|
||||
return t
|
22
button.lua
22
button.lua
|
@ -19,35 +19,13 @@ function t.contains( button, x, y )
|
|||
and y < button.y + button.h and y > button.y
|
||||
end
|
||||
|
||||
local function debugLoop()
|
||||
local i = 0
|
||||
local j = 0
|
||||
local a = t
|
||||
repeat
|
||||
i = i + 1
|
||||
--print( "BUTTON", i, tostring( a ) )
|
||||
a = a.next
|
||||
until a == t or i > 100
|
||||
a = t
|
||||
repeat
|
||||
j = j + 1
|
||||
--print( "BUTTON", i, tostring( a ) )
|
||||
a = a.prev
|
||||
until a == t or j > 100
|
||||
print( i, j, "BUTTONS" )
|
||||
end
|
||||
|
||||
local k = 1
|
||||
function t.new( b )
|
||||
b = setmetatable( b or {}, t )
|
||||
b.next = t
|
||||
t.prev.next = b
|
||||
b.prev = t.prev
|
||||
t.prev = b
|
||||
--nonsense
|
||||
k = k + 1
|
||||
print( "ADD BUTTON", k, tostring( b ) )
|
||||
debugLoop()
|
||||
return b
|
||||
end
|
||||
|
||||
|
|
|
@ -87,9 +87,9 @@ function t.selectNearest( lines, wx, wy )
|
|||
end
|
||||
|
||||
function t.save( lines )
|
||||
local str = {}
|
||||
local str = { "b" }
|
||||
for i, poly in ipairs( lines ) do
|
||||
str[i] = table.concat( poly, " " )
|
||||
str[i + 1] = table.concat( poly, " " )
|
||||
end
|
||||
str = table.concat( str, "\nb\n" ):gsub("(%S+) (%S+) ", "%1 %2\n")
|
||||
return str
|
||||
|
|
8
main.lua
8
main.lua
|
@ -23,10 +23,10 @@ end
|
|||
function love.update( dt )
|
||||
local tx, ty = 0, 0
|
||||
local moveCamera = false
|
||||
if love.keyboard.isScancodeDown( "w" ) then moveCamera = true; ty = ty + 30 * dt end
|
||||
if love.keyboard.isScancodeDown( "a" ) then moveCamera = true; tx = tx - 30 * dt end
|
||||
if love.keyboard.isScancodeDown( "s" ) then moveCamera = true; ty = ty - 30 * dt end
|
||||
if love.keyboard.isScancodeDown( "d" ) then moveCamera = true; tx = tx + 30 * dt end
|
||||
if love.keyboard.isScancodeDown( "w" ) then moveCamera = true; ty = ty + dt * 150 / Camera.zoom end
|
||||
if love.keyboard.isScancodeDown( "a" ) then moveCamera = true; tx = tx - dt * 150 / Camera.zoom end
|
||||
if love.keyboard.isScancodeDown( "s" ) then moveCamera = true; ty = ty - dt * 150 / Camera.zoom end
|
||||
if love.keyboard.isScancodeDown( "d" ) then moveCamera = true; tx = tx + dt * 150 / Camera.zoom end
|
||||
if love.keyboard.isScancodeDown( "q" ) then Camera.Zoom( dt * 400 ) end
|
||||
if love.keyboard.isScancodeDown( "e" ) then Camera.Zoom( -dt* 400 ) end
|
||||
if moveCamera then Camera.Translate( tx, ty ) end
|
||||
|
|
11
map.lua
11
map.lua
|
@ -179,16 +179,17 @@ function map.draw()
|
|||
end
|
||||
|
||||
local function write( filename, string )
|
||||
print( "Pretending to write", string:len(), "bytes to", filename )
|
||||
--[[ os.rename( filename, filename..".bak" ) --just in case :^)
|
||||
local file = assert( io.open( filename, "w+" ) )
|
||||
--
|
||||
print( "Writing", string:len(), "bytes to", filename )
|
||||
os.rename( filename, filename..".bak" ) --just in case :^)
|
||||
local file = assert( io.open( filename, "wb+" ) )
|
||||
assert( file:write( string ) )
|
||||
file:close()]]
|
||||
assert( file:flush() ) --your toilet is set to stun, not kill
|
||||
file:close()
|
||||
end
|
||||
|
||||
function map.save()
|
||||
for k, layer in pairs( layers ) do
|
||||
print( "SAVING:", k, tostring( layer.filename ) )
|
||||
write( map.path..tostring( layer.filename ), assert( layer:save() ) )
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,10 +57,6 @@ function t.getPixel( territory, x, y )
|
|||
return territory.imgd:getPixel( imgx, imgy )
|
||||
end
|
||||
|
||||
function t.toggleVisibility( territory )
|
||||
|
||||
end
|
||||
|
||||
--[[
|
||||
0
|
||||
20 -- once sailable.bmp is brighter than this, the area is traversable by ships
|
||||
|
@ -147,7 +143,9 @@ function t.computeBorder( territory, threshold, key )
|
|||
end
|
||||
|
||||
function t.save( territory )
|
||||
return bmp.save( territory.imgd, "512rgb24" )
|
||||
local fmt = (territory.name == "sailable") and "sailable" or "territory"
|
||||
print( "saving bitmap: ", territory.name, fmt )
|
||||
return bmp[fmt]( territory.imgd )
|
||||
end
|
||||
|
||||
return t
|
|
@ -153,7 +153,7 @@ function t.drawConnections( nodes )
|
|||
end
|
||||
|
||||
function t.save( nodes )
|
||||
return bmp.savePoints( nodes.nodes, "800r4")
|
||||
return bmp.travel( nodes.nodes )
|
||||
end
|
||||
|
||||
return t
|
Loading…
Reference in New Issue