diff --git a/.gitignore b/.gitignore index cded043..4770d49 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/ -test/ \ No newline at end of file +test/ +backup/ \ No newline at end of file diff --git a/ai.lua b/ai.lua index fec5ba0..d35750c 100644 --- a/ai.lua +++ b/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 \ No newline at end of file diff --git a/bmp.lua b/bmp.lua index 1332c1a..fbc9923 100644 --- a/bmp.lua +++ b/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( " 1000 then break end end - return twentyFour + 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 y = h - 1, 0, -1 do - for x = 0, w - 1 do - bytes[i] = format.encode( data:getPixel(x, y) ) - i = i + 1 - end - end - if format.bytesPerPixel < 1 then bytes = foldByteArray( bytes ) - else bytes = 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. -function t.savePoints( points, format ) - format = assert( formats[format] ) +local function getHeader( filename ) + local offset = 2 + love.data.unpack( " 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: - -- N.B., y's divided by 190 rather than 200 because of an idiosyncracy in the travel node image - local x, y = math.floor(format.w * (wx + 180) / 360), math.floor(format.h * (wy + 100) / 190) - --get index into byte array - local idx = 2 + y * format.w + x - print( "SAVING POINT:", wx, wy, x, format.h - y - 1, idx ) - bitmap[idx] = 1 --white: this header uses an indexed palette - 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 @@ -142,4 +182,138 @@ function t.savePoints( points, format ) 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 \ No newline at end of file diff --git a/map.lua b/map.lua index a6cdfb5..7ed00f3 100644 --- a/map.lua +++ b/map.lua @@ -179,10 +179,12 @@ function map.draw() end local function write( filename, string ) + -- print( "Writing", string:len(), "bytes to", filename ) os.rename( filename, filename..".bak" ) --just in case :^) - local file = assert( io.open( filename, "w+" ) ) + local file = assert( io.open( filename, "wb+" ) ) assert( file:write( string ) ) + assert( file:flush() ) --your toilet is set to stun, not kill file:close() end diff --git a/territory.lua b/territory.lua index faa9cbd..bf0a7cf 100644 --- a/territory.lua +++ b/territory.lua @@ -143,9 +143,9 @@ function t.computeBorder( territory, threshold, key ) end function t.save( territory ) - local fmt = (territory.name == "sailable") and "512r4" or "512rgb24" + local fmt = (territory.name == "sailable") and "sailable" or "territory" print( "saving bitmap: ", territory.name, fmt ) - return bmp.save( territory.imgd, fmt ) + return bmp[fmt]( territory.imgd ) end return t \ No newline at end of file diff --git a/travelNodes.lua b/travelNodes.lua index 59a01d5..5612873 100644 --- a/travelNodes.lua +++ b/travelNodes.lua @@ -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 \ No newline at end of file