local Board = {} function Board.new() local debug = false local width = 10 local tile_width = 10 local padding = 1 local tiles = {} local locked = {} -- list of indexes local reset = function() tiles = {} for i = 1, width * width do tiles[i] = 0 end locked = {} end reset() return { reset = reset, get_tile = function(self, idx) return tiles[idx] end, -- returns a COPY of the tiles array get_tiles_copy = function(self) return copy(tiles) end, -- overwrites the whole tiles array set_tiles = function(self, newtiles) 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, draw_coords = function(self, x, y) local margin = (128 - (tile_width + padding) * width) / 2- padding if not y then x, y = self:idx_xy(x) end return margin + (x-1)*tile_width + (x-1)*padding, margin + (y-1)*tile_width + (y-1)*padding end, get_size = function(self) return width end, get_tile_width = function(self) return tile_width end, fill = function(self, idx, color, invert) if idx > width*width then return end if invert then color = color == YELLOW and BLUE or YELLOW end tiles[idx] = color end, try_flip_tile = function(self, id) if locked[id] then return end if tiles[id] == 0 then tiles[id] = YELLOW elseif tiles[id] == YELLOW then tiles[id] = BLUE else tiles[id] = 0 end end, get_rows = function(self) local ret = {} for i = 1, width do add(ret, slice(tiles, ((i - 1) * width) + 1, i * width)) end return ret end, get_cols = function(self) local ret = {} local rows = self.get_rows(self) for i = 1, width do add(ret, map(rows, function(v) return v[i] end)) end return ret end, is_complete = function(self) return count(tiles, 0) == 0 end, is_valid = function(self) return #self:get_issues() == 0 end, get_issues = function(self, details) local rows = self:get_rows() local issues = {} for y,row in ipairs(rows) do -- check count if count(row, BLUE) ~= count(row, YELLOW) then add(issues, {"row", "count", row, y}) if (debug) printh("uneven count on row "..y) if (not details) return issues end -- check identical lines for k,other in ipairs(rows) do if equal(other, row) and other ~= row then add(issues, {"row", "identical", row, y, k}) if (debug) printh("equal rows "..k) if (not details) return issues end end -- check triples if self:count_consecutives(row) > 2 then add(issues, {"row", "triples", row, y}) if (debug) printh("triples") if (not details) return issues end end local cols = self:get_cols() for x,col in ipairs(cols) do -- check count if count(col, BLUE) ~= count(col, YELLOW) then add(issues, {"col", "count", col, x}) if (debug) printh("uneven count") if (not details) return issues end -- check identical lines for k,other in ipairs(cols) do if equal(other, col) and other ~= col then add(issues, {"col", "identical", col, x, k}) if (debug) printh("equal cols") if (not details) return issues end end -- check triples if self:count_consecutives(col) > 2 then add(issues, {"col", "triples", col, x}) if (debug) printh("triples") if (not details) return issues end end return issues end, count_consecutives = function(self, line) local top = 0 local current = 0 local last = 0 for v in all(line) do if v ~= last then top = max(current, top) current = 1 last = v else current += 1 end end return max(current, top) end, -- -- Returns the index of a random zero tile -- get_random_zero = function(self) 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 -- get_random_non_zero = function(self) 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 solve_step = function(self, random) local zeroes = count(tiles, 0) self:surround_doubles() self:split_triples() self:fill_lines() self:no_identical_lines() local changed = zeroes ~= count(tiles, 0) if not changed and random and not self:is_complete() then -- Set a random color local z = self:get_random_zero() self:fill(z, rnd({BLUE, YELLOW})) if (debug) printh("!!!!!!!!!!!!!!!!! RANDOM FILL AT " .. z) return "invalid" end return (changed or self:is_complete()) and "valid" or "invalid" end, surround_doubles = function(self) 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, split_triples = function(self) 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, fill_lines = function(self) local rows = self:get_rows() local cols = self:get_cols() -- rows for y,row in ipairs(rows) do local a = count(row, BLUE) local b = count(row, YELLOW) if a ~= b then if a == width/2 then self:fill_row(y, YELLOW) end if b == width/2 then self:fill_row(y, BLUE) end end end -- columns for x,col in ipairs(cols) do local a = count(col, BLUE) local b = count(col, YELLOW) if a ~= b then if a == width/2 then self:fill_col(x, YELLOW) end if b == width/2 then self:fill_col(x, BLUE) end end end end, fill_row = function(self, y, color) 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 if self:get_tile(i) == 0 then self:fill(i, color) end end end, fill_col = function(self, x, color) 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 if self:get_tile(i) == 0 then self:fill(i, color) end end end, no_identical_lines = function(self) -- columns local cols = self:get_cols() for x,col in ipairs(cols) do if count(col, 0) == 2 and count(col, BLUE) == count(col, YELLOW) then local y1, y2 = unpack(find(col, 0)) 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 if debug then printh("No-dupe at col " .. x .. " from col " .. x2) printh(" " .. self:xy_idx(x,y1) .. " in yellow") printh(" " .. self:xy_idx(x,y2) .. " in blue") end self:fill(self:xy_idx(x,y1), YELLOW) self:fill(self:xy_idx(x,y2), BLUE) goto continue elseif equal(col, ba) then if debug then printh("No-dupe at col " .. x .. " from col " .. x2) printh(" " .. self:xy_idx(x,y1) .. " in blue") printh(" " .. self:xy_idx(x,y2) .. " in yellow") end self:fill(self:xy_idx(x,y1), BLUE) self:fill(self:xy_idx(x,y2), YELLOW) goto continue end end end ::continue:: end -- rows local rows = self:get_rows() 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 if debug then printh("No-dupe at row " .. y .. " from row " .. y2) printh(" " .. self:xy_idx(x1,y) .. " in yellow") printh(" " .. self:xy_idx(x2,y) .. " in blue") end self:fill(self:xy_idx(x1,y), YELLOW) self:fill(self:xy_idx(x2,y), BLUE) goto continue elseif equal(row, ba) then if debug then printh("No-dupe at row " .. y .. " from row " .. y2) printh(" " .. self:xy_idx(x1,y) .. " in blue") printh(" " .. self:xy_idx(x2,y) .. " in yellow") end self:fill(self:xy_idx(x1,y), BLUE) self:fill(self:xy_idx(x2,y), YELLOW) goto continue end end end ::continue:: end end, lock_tiles = function(self) locked = {} for k,v in ipairs(tiles) do if v > 0 then locked[k] = true end end end, is_locked = function(self, idx) return locked[idx] end, draw_bg = function(self) local w = tile_width fillp(▒) for k,v in ipairs(tiles) do local x,y = self:draw_coords(k) rectfill2(x, y, w, w, 1) end end, draw = function(self) self:draw_bg() local w = tile_width for k,v in ipairs(tiles) do self:draw_tile(k) end end, draw_tile = function(self, idx) local w = tile_width local v = tiles[idx] if v > 0 then local x,y = self:draw_coords(idx) local color = v == BLUE and 12 or 8 if color == 1 then fillp(▒) else fillp(█) end if self:is_locked(idx) then rectfill2(x, y, w, w, color) line(x, y+w-1, x+w-1, y+w-1, v == BLUE and 13 or 2) line(x+w-1, y, x+w-1, y+w-1, v == BLUE and 13 or 2) else roundedrect(x, y, w, w, color) end end end, } end