pico8-0hh1/board.lua

447 lines
11 KiB
Lua
Raw Normal View History

2022-05-29 18:38:07 +02:00
local Board = {}
function Board.new()
local debug = false
2022-06-01 23:14:22 +02:00
2022-07-12 21:47:10 +02:00
local width = 4 -- tiles
2022-07-10 16:39:11 +02:00
local tile_width = 10 -- pixels
2022-06-01 23:14:22 +02:00
local padding = 1
2022-06-03 23:31:26 +02:00
local tiles = {}
local locked = {} -- list of indexes
2022-06-01 23:14:22 +02:00
2022-06-02 22:39:23 +02:00
local reset = function()
2022-07-01 19:50:08 +02:00
tiles = {}
for i = 1, width * width do
tiles[i] = 0
end
2022-06-03 23:31:26 +02:00
locked = {}
2022-06-02 22:39:23 +02:00
end
reset()
2022-05-29 18:38:07 +02:00
return {
2022-06-02 22:39:23 +02:00
reset = reset,
2022-05-29 18:38:07 +02:00
2022-06-02 20:18:02 +02:00
get_tile = function(self, idx)
2022-05-29 18:38:07 +02:00
return tiles[idx]
end,
-- returns a COPY of the tiles array
2022-06-02 20:18:02 +02:00
get_tiles_copy = function(self)
2022-05-29 18:38:07 +02:00
return copy(tiles)
end,
-- overwrites the whole tiles array
2022-06-02 20:18:02 +02:00
set_tiles = function(self, newtiles)
2022-05-29 18:38:07 +02:00
assert(#newtiles == #tiles, "New tiles array must have a length of " .. #tiles)
tiles = copy(newtiles)
end,
idx_xy = function(self, idx)
return idx_xy(idx, width)
end,
xy_idx = function(self, x, y)
return xy_idx(x, y, width)
end,
2022-06-01 23:14:22 +02:00
draw_coords = function(self, x, y)
2022-07-02 13:52:31 +02:00
local margin = (128 - (tile_width + padding) * width) / 2- padding
2022-06-01 23:14:22 +02:00
if not y then x, y = self:idx_xy(x) end
2022-07-02 13:52:31 +02:00
return margin + (x-1)*tile_width + (x-1)*padding,
margin + (y-1)*tile_width + (y-1)*padding
2022-06-01 23:14:22 +02:00
end,
2022-06-02 20:18:02 +02:00
get_size = function(self)
2022-06-01 23:14:22 +02:00
return width
end,
2022-06-02 20:18:02 +02:00
get_tile_width = function(self)
2022-07-10 16:39:11 +02:00
return tile_width + padding
2022-06-01 23:14:22 +02:00
end,
2022-05-29 18:38:07 +02:00
fill = function(self, idx, color, invert)
2022-06-02 20:08:20 +02:00
if idx > width*width then return end
2022-05-29 18:38:07 +02:00
if invert then
color = color == YELLOW and BLUE or YELLOW
end
tiles[idx] = color
end,
2022-07-01 19:50:08 +02:00
try_flip_tile = function(self, id)
2022-07-12 21:47:10 +02:00
local to_color = nil
2022-07-01 19:50:08 +02:00
if locked[id] then return end
2022-07-12 21:47:10 +02:00
local from_color = tiles[id]
2022-07-01 19:50:08 +02:00
if tiles[id] == 0 then
2022-07-12 21:47:10 +02:00
to_color = YELLOW
2022-07-01 19:50:08 +02:00
elseif tiles[id] == YELLOW then
2022-07-12 21:47:10 +02:00
to_color = BLUE
2022-07-01 19:50:08 +02:00
else tiles[id] = 0
2022-07-12 21:47:10 +02:00
-- empty tile
2022-07-01 19:50:08 +02:00
end
2022-07-12 21:47:10 +02:00
if to_color then
tiles[id] = to_color
end
local x,y = self:draw_coords(id)
spawn_tile_transition(x, y, tile_width-1, tile_width-1, from_color, to_color)
2022-07-01 19:50:08 +02:00
end,
2022-06-02 20:18:02 +02:00
get_rows = function(self)
2022-05-29 18:38:07 +02:00
local ret = {}
for i = 1, width do
add(ret, slice(tiles, ((i - 1) * width) + 1, i * width))
end
return ret
end,
2022-06-02 20:18:02 +02:00
get_cols = function(self)
2022-05-29 18:38:07 +02:00
local ret = {}
2022-06-02 20:18:02 +02:00
local rows = self.get_rows(self)
2022-05-29 18:38:07 +02:00
for i = 1, width do
add(ret, map(rows, function(v) return v[i] end))
end
return ret
end,
2022-06-02 20:18:02 +02:00
is_complete = function(self)
2022-05-29 18:38:07 +02:00
return count(tiles, 0) == 0
end,
2022-06-02 20:18:02 +02:00
is_valid = function(self)
2022-07-02 20:45:11 +02:00
return #self:get_issues() == 0
end,
2022-07-03 19:09:15 +02:00
-- returns a list of issues of the board's current state
2022-07-02 20:45:11 +02:00
get_issues = function(self, details)
2022-06-02 20:18:02 +02:00
local rows = self:get_rows()
2022-07-02 20:45:11 +02:00
local issues = {}
2022-05-29 18:38:07 +02:00
for y,row in ipairs(rows) do
2022-07-03 19:09:15 +02:00
local filled = count(row, 0) == 0
2022-05-29 18:38:07 +02:00
-- check count
2022-07-03 19:09:15 +02:00
if filled and count(row, BLUE) ~= count(row, YELLOW) then
2022-07-02 20:45:11 +02:00
add(issues, {"row", "count", row, y})
2022-05-29 18:38:07 +02:00
if (debug) printh("uneven count on row "..y)
2022-07-02 20:45:11 +02:00
if (not details) return issues
2022-05-29 18:38:07 +02:00
end
-- check identical lines
for k,other in ipairs(rows) do
2022-07-03 19:09:15 +02:00
if filled and equal(other, row) and other ~= row then
2022-07-02 20:45:11 +02:00
add(issues, {"row", "identical", row, y, k})
2022-05-29 18:38:07 +02:00
if (debug) printh("equal rows "..k)
2022-07-02 20:45:11 +02:00
if (not details) return issues
2022-05-29 18:38:07 +02:00
end
end
-- check triples
2022-06-02 20:18:02 +02:00
if self:count_consecutives(row) > 2 then
2022-07-02 20:45:11 +02:00
add(issues, {"row", "triples", row, y})
2022-05-29 18:38:07 +02:00
if (debug) printh("triples")
2022-07-02 20:45:11 +02:00
if (not details) return issues
2022-05-29 18:38:07 +02:00
end
end
2022-06-02 20:18:02 +02:00
local cols = self:get_cols()
2022-07-02 20:45:11 +02:00
for x,col in ipairs(cols) do
2022-07-03 19:09:15 +02:00
local filled = count(col, 0) == 0
2022-05-29 18:38:07 +02:00
-- check count
2022-07-03 19:09:15 +02:00
if filled and count(col, BLUE) ~= count(col, YELLOW) then
2022-07-02 20:45:11 +02:00
add(issues, {"col", "count", col, x})
2022-05-29 18:38:07 +02:00
if (debug) printh("uneven count")
2022-07-02 20:45:11 +02:00
if (not details) return issues
2022-05-29 18:38:07 +02:00
end
-- check identical lines
2022-07-02 20:45:11 +02:00
for k,other in ipairs(cols) do
2022-07-03 19:09:15 +02:00
if filled and equal(other, col) and other ~= col then
2022-07-02 20:45:11 +02:00
add(issues, {"col", "identical", col, x, k})
2022-05-29 18:38:07 +02:00
if (debug) printh("equal cols")
2022-07-02 20:45:11 +02:00
if (not details) return issues
2022-05-29 18:38:07 +02:00
end
end
-- check triples
2022-06-02 20:18:02 +02:00
if self:count_consecutives(col) > 2 then
2022-07-02 20:45:11 +02:00
add(issues, {"col", "triples", col, x})
2022-05-29 18:38:07 +02:00
if (debug) printh("triples")
2022-07-02 20:45:11 +02:00
if (not details) return issues
2022-05-29 18:38:07 +02:00
end
end
2022-07-02 20:45:11 +02:00
return issues
2022-05-29 18:38:07 +02:00
end,
2022-06-02 20:18:02 +02:00
count_consecutives = function(self, line)
2022-07-01 20:20:57 +02:00
local top = 0
local current = 0
2022-05-29 18:38:07 +02:00
local last = 0
for v in all(line) do
if v ~= last then
2022-07-01 20:20:57 +02:00
top = max(current, top)
current = 1
2022-05-29 18:38:07 +02:00
last = v
2022-07-03 19:09:15 +02:00
elseif v~= 0 then
2022-07-01 20:20:57 +02:00
current += 1
2022-05-29 18:38:07 +02:00
end
end
2022-07-03 14:39:25 +02:00
return max(current, top)
2022-05-29 18:38:07 +02:00
end,
--
-- Returns the index of a random zero tile
--
2022-06-02 20:18:02 +02:00
get_random_zero = function(self)
2022-05-29 18:38:07 +02:00
assert(count(tiles, 0) > 0, "No zero left")
local zeroes = filter(tiles, function(v) return v == 0 end, true)
local z = {}
for k,v in pairs(zeroes) do
add(z, k)
end
return rnd(z)
end,
--
-- Returns the index of a random non-zero tile
--
2022-06-02 20:18:02 +02:00
get_random_non_zero = function(self)
2022-05-29 18:38:07 +02:00
assert(count(tiles, 0) < #tiles, "All zeroes")
local numbers = filter(tiles, function(v) return v ~= 0 end, true)
local z = {}
for k,v in pairs(numbers) do
add(z, k)
end
return rnd(z)
end,
tostring = function(self)
local str = ''
for v in all(tiles) do
str ..= ", " .. v
end
return str
end,
-- Solves 1 step of the board
-- Returns "valid" if it solved it without guessing
-- Returns "invalid" if the board cannot be solved
2022-06-02 20:18:02 +02:00
solve_step = function(self, random)
2022-05-29 18:38:07 +02:00
local zeroes = count(tiles, 0)
2022-06-02 20:18:02 +02:00
self:surround_doubles()
self:split_triples()
self:fill_lines()
self:no_identical_lines()
2022-05-29 18:38:07 +02:00
local changed = zeroes ~= count(tiles, 0)
2022-07-03 19:09:15 +02:00
2022-06-02 20:18:02 +02:00
if not changed and random and not self:is_complete() then
2022-05-29 18:38:07 +02:00
-- Set a random color
2022-06-02 20:18:02 +02:00
local z = self:get_random_zero()
2022-05-29 18:38:07 +02:00
self:fill(z, rnd({BLUE, YELLOW}))
if (debug) printh("!!!!!!!!!!!!!!!!! RANDOM FILL AT " .. z)
return "invalid"
end
2022-06-02 20:18:02 +02:00
return (changed or self:is_complete()) and "valid" or "invalid"
2022-05-29 18:38:07 +02:00
end,
2022-06-02 20:18:02 +02:00
surround_doubles = function(self)
2022-05-29 18:38:07 +02:00
for idx,v in ipairs(tiles) do
local x,y = self:idx_xy(idx)
if v == 0 then
local neighbors = {}
-- 2 tiles on the left
if x >= 3 then
add(neighbors, {idx, idx-1, idx-2})
end
-- 2 tiles on the right
if x <= width-2 then
add(neighbors, {idx, idx+1, idx+2})
end
-- 2 tiles on top
if y >= 3 then
add(neighbors, {idx, idx-width, idx - width*2})
end
-- 2 tiles under
if y <= width-2 then
add(neighbors, {idx, idx+width, idx + width*2})
end
-- only keep pairs that are identical (and not 0)
neighbors = filter(neighbors, function (o) return tiles[o[2]] == tiles[o[3]] and tiles[o[2]] ~= 0 end)
-- do the surrounding
for item in all(neighbors) do
if item[1] then
if (debug) printh("Surrounding at " .. item[1])
self:fill(item[1], tiles[item[2]], true)
end
end
end
end
end,
2022-06-02 20:18:02 +02:00
split_triples = function(self)
2022-05-29 18:38:07 +02:00
for idx, col in ipairs(tiles) do
local x,y = self:idx_xy(idx)
if col == 0 then
if x > 1 and x < width then
-- check horizontal
local prev = tiles[idx-1]
local next = tiles[idx+1]
if prev ~= 0 and prev == next then
if (debug) printh("Splitting at " .. idx)
self:fill(idx, prev, true)
end
end
if y>1 and y < width then
-- check vertical
local prev = tiles[idx-width]
local next = tiles[idx+width]
if prev ~= 0 and prev == next then
if (debug) printh("Splitting at " .. idx)
self:fill(idx, prev, true)
end
end
end
end
end,
2022-06-02 20:18:02 +02:00
fill_lines = function(self)
local rows = self:get_rows()
local cols = self:get_cols()
2022-05-29 18:38:07 +02:00
-- rows
for y,row in ipairs(rows) do
local a = count(row, BLUE)
local b = count(row, YELLOW)
if a ~= b then
2022-06-02 20:18:02 +02:00
if a == width/2 then self:fill_row(y, YELLOW) end
if b == width/2 then self:fill_row(y, BLUE) end
2022-05-29 18:38:07 +02:00
end
end
-- columns
for x,col in ipairs(cols) do
local a = count(col, BLUE)
local b = count(col, YELLOW)
if a ~= b then
2022-06-02 20:18:02 +02:00
if a == width/2 then self:fill_col(x, YELLOW) end
if b == width/2 then self:fill_col(x, BLUE) end
2022-05-29 18:38:07 +02:00
end
end
end,
2022-06-02 20:18:02 +02:00
fill_row = function(self, y, color)
2022-05-29 18:38:07 +02:00
if (debug) printh("Filling line " .. y .. " in " .. (color == BLUE and "blue" or "yellow"))
local idx = self:xy_idx(1, y)
for i = idx, (idx+width-1) do
2022-06-02 20:18:02 +02:00
if self:get_tile(i) == 0 then
2022-05-29 18:38:07 +02:00
self:fill(i, color)
end
end
end,
2022-06-02 20:18:02 +02:00
fill_col = function(self, x, color)
2022-05-29 18:38:07 +02:00
if (debug) printh("Filling column " .. x .. " in " .. (color == BLUE and "blue" or "yellow"))
local idx = self:xy_idx(x, 1)
for i = idx, #tiles, width do
2022-06-02 20:18:02 +02:00
if self:get_tile(i) == 0 then
2022-05-29 18:38:07 +02:00
self:fill(i, color)
end
end
end,
2022-07-03 19:09:15 +02:00
-- Finds "identical" lines, and fill the 2 remaining tiles with inverted colors
2022-06-02 20:18:02 +02:00
no_identical_lines = function(self)
2022-05-29 18:38:07 +02:00
-- columns
2022-06-02 20:18:02 +02:00
local cols = self:get_cols()
2022-05-29 18:38:07 +02:00
for x,col in ipairs(cols) do
2022-07-03 19:09:15 +02:00
-- if the line has the corrent number of colors,
-- but missing 2 tiles
2022-05-29 18:38:07 +02:00
if count(col, 0) == 2 and count(col, BLUE) == count(col, YELLOW) then
2022-07-03 19:09:15 +02:00
local y1, y2 = unpack(find(col, 0)) -- get the position of the 2 missing tiles
-- create both both solutions
2022-05-29 18:38:07 +02:00
local ab = copy(col) ab[y1] = BLUE ab[y2] = YELLOW
local ba = copy(col) ba[y1] = YELLOW ba[y2] = BLUE
-- Check if a dupe exists
for x2,col in ipairs(cols) do
if equal(col, ab) then
self:fill(self:xy_idx(x,y1), YELLOW)
self:fill(self:xy_idx(x,y2), BLUE)
goto continue
elseif equal(col, ba) then
self:fill(self:xy_idx(x,y1), BLUE)
self:fill(self:xy_idx(x,y2), YELLOW)
goto continue
end
end
end
::continue::
end
-- rows
2022-06-02 20:18:02 +02:00
local rows = self:get_rows()
2022-05-29 18:38:07 +02:00
for y,row in ipairs(rows) do
if count(row, 0) == 2 and count(row, BLUE) == count(row, YELLOW) then
local x1, x2 = unpack(find(row, 0))
local ab = copy(row) ab[x1] = BLUE ab[x2] = YELLOW
local ba = copy(row) ba[x1] = YELLOW ba[x2] = BLUE
-- Check if a dupe exists
for y2,row in ipairs(rows) do
if equal(row, ab) then
self:fill(self:xy_idx(x1,y), YELLOW)
self:fill(self:xy_idx(x2,y), BLUE)
goto continue
elseif equal(row, ba) then
self:fill(self:xy_idx(x1,y), BLUE)
self:fill(self:xy_idx(x2,y), YELLOW)
goto continue
end
end
end
::continue::
end
end,
2022-06-03 23:31:26 +02:00
lock_tiles = function(self)
locked = {}
for k,v in ipairs(tiles) do
2022-07-01 19:50:08 +02:00
if v > 0 then locked[k] = true end
2022-06-03 23:31:26 +02:00
end
end,
is_locked = function(self, idx)
2022-07-01 19:50:08 +02:00
return locked[idx]
2022-06-03 23:31:26 +02:00
end,
2022-07-07 23:06:28 +02:00
draw_bg_tile = function(self, k)
local w = tile_width
local x,y = self:draw_coords(k)
rectfill2(x+1, y, w-2, w, 1)
rectfill2(x, y+1, w, w-2, 1)
end,
draw = function(self)
2022-07-01 19:50:08 +02:00
local w = tile_width
for k,v in ipairs(tiles) do
2022-06-03 23:31:26 +02:00
self:draw_tile(k)
end
end,
draw_tile = function(self, idx)
2022-07-01 19:50:08 +02:00
local w = tile_width
2022-06-03 23:31:26 +02:00
local v = tiles[idx]
if v > 0 then
local x,y = self:draw_coords(idx)
2022-07-12 21:47:10 +02:00
local color = get_main_color(v)
local shade = get_shade_color(v)
2022-06-03 23:31:26 +02:00
if color == 1 then fillp() else fillp() end
if self:is_locked(idx) then
2022-07-02 13:52:31 +02:00
rectfill2(x, y, w, w, color)
else
roundedrect(x, y, w, w, color)
2022-07-12 21:47:10 +02:00
line(x+1, y+w-1, x+w-2, y+w-1, shade)
line(x+w-1, y+1, x+w-1, y+w-2, shade)
end
2022-07-07 23:06:28 +02:00
else
fillp()
self:draw_bg_tile(idx)
2022-05-29 18:38:07 +02:00
end
2022-06-03 23:31:26 +02:00
end,
2022-05-29 18:38:07 +02:00
}
end