·ÜÝÝííîîþíîîþíîîþíîîþíîîþíîîþþÿÿÝÝÝíÝÝÝíÝÝÝíÝÝÝíÝÝÝíÝÝÝíÝÝÝíîîîîÀ"À"ÀÀÀ""  "" """"  >x2"ÝÝ"ÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíâîî.íîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþâîî.íîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþ"ÿÿ"þÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝííîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝííîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝííîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝííîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝííîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝííîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝííîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝííîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝíÜÝÝííîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþíîîþþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿ™-- title: Minesweeper -- author: VOID404 -- desc: Classic Minesweeper game implemented in TIC-80 -- site: website link -- license: MIT License -- version: 0.2 -- script: lua SPRITE = { hidden = 1, empty = 2, flag = 3, cursor = 4, mine = 5, } StringSet = {} function StringSet:__index(k) return rawget(self, tostring(k)) end function StringSet:__newindex(k, v) return rawset(self, tostring(k), v) end function Set(list, stringKeys) local set = {} if stringKeys then setmetatable(set, StringSet) end for _, l in ipairs(list) do set[l] = true end return set end -- {{{ Vec2 class Vec2 = { x = 0, y = 0 } function Vec2:new(o) o = o or {} setmetatable(o, self) self.__index = self return o end function Vec2:__add(v) if getmetatable(v) == Vec2 then return Vec2:new({ x = self.x + v.x, y = self.y + v.y }) else return Vec2:new({ x = self.x + v, y = self.y + v }) end end function Vec2:__sub(v) return Vec2:new({ x = self.x - v.x, y = self.y - v.y }) end function Vec2:__mul(s) return Vec2:new({ x = self.x * s, y = self.y * s }) end function Vec2:__tostring() return string.format("Vec2(%d,%d)", self.x, self.y) end function Vec2:__eq(v) return self.x == v.x and self.y == v.y end function Vec2:toPixel() return Vec2:new { x = (self.x - 1) * 8, y = (self.y - 1) * 8, } end function Vec2:fromPixel() return Vec2:new { x = math.floor(self.x / 8) + 1, y = math.floor(self.y / 8) + 1, } end -- }}} -- {{{ Tile class Tile = { mine = false, flag = false, hidden = true, mineCount = nil, } function Tile:new(o) o = o or {} setmetatable(o, self) self.__index = self return o end function Tile:draw(pos) if self.hidden then spr(SPRITE.hidden, pos.x, pos.y) elseif self.mine then spr(SPRITE.empty, pos.x, pos.y) spr(SPRITE.mine, pos.x, pos.y, 0) else spr(SPRITE.empty, pos.x, pos.y) if self.mineCount and self.mineCount > 0 then print(self.mineCount, pos.x + 2, pos.y + 2) -- Draw mine count in white end end if self.flag then spr(SPRITE.flag, pos.x, pos.y, 0) end end -- }}} -- {{{ Minefield class Minefield = { offset = Vec2:new({ x = 0, y = 0 }), width = 10, height = 10, tiles = {}, mines = 0, lost = false, } function Minefield:new(o) o = o or {} setmetatable(o, self) self.__index = self -- Initialize tiles for x = 1, o.width do o.tiles[x] = {} for y = 1, o.height do o.tiles[x][y] = Tile:new() end end return o end function Minefield:__tostring() return string.format("Minefield(%d,%d)", self.width, self.height) end function Minefield:get(k) assert(getmetatable(k) == Vec2, "Index must be a Vec2") -- Allow Vec2 indexing for tiles if not self.tiles[k.x] or not self.tiles[k.x][k.y] then return nil -- Out of bounds end return self.tiles[k.x][k.y] end function Minefield:pixelSize() return Vec2:new { x = self.width * 8, y = self.height * 8 } end function Minefield:draw() local offset = self.offset for x = 1, self.width do for y = 1, self.height do local tile = self.tiles[x][y] tile:draw(offset + Vec2:new({ x = x, y = y }):toPixel()) end end end function Minefield:open(pos) -- Open the tile at (x, y) local tile = self.tiles[pos.x][pos.y] if tile.flag then return end -- Do not open flagged tiles tile.hidden = false tile.mineCount = self:countMines(pos) if tile.mineCount == 0 then Minefield:walkOpen(pos) -- Walk open if no mines around end if tile.mine then self.lost = true -- Game over if mine is hit end end function Minefield:flag(pos) -- Toggle flag on the tile at (x, y) local tile = self.tiles[pos.x][pos.y] if tile.hidden then tile.flag = not tile.flag end end function Minefield:placeMines(numMines, blacklist) local blacklist = Set(blacklist or {}, true) -- Randomly place mines in the minefield local placed = 0 while placed < numMines do local pos = Vec2:new { x = math.random(1, self.width), y = math.random(1, self.height), } if not blacklist[pos] and not self:get(pos).mine then -- if not blacklist[pos] and not self.tiles[pos.x][pos.y].mine then self:get(pos).mine = true placed = placed + 1 end end self.mines = placed end function Minefield:countMines(pos) -- Count mines in the minefield local count = 0 for x = pos.x - 1, pos.x + 1 do for y = pos.y - 1, pos.y + 1 do if not (x == 0 and y == 0) and self.tiles[x] and self.tiles[x][y] then local tile = self.tiles[x][y] if tile.mine then count = count + 1 end end end end return count end function Minefield:revealed() local count = 0 for x = 1, self.width do for y = 1, self.height do local tile = self.tiles[x][y] if not tile.hidden then count = count + 1 end end end return count end function Minefield:walkOpen(pos) local walked = Set({}, true) local open = { pos } while #open > 0 do local p = table.remove(open, 1) if walked[p] then goto continue end walked[p] = true local tile = self:get(p) if not tile then goto continue end tile.hidden = false -- Open the tile tile.mineCount = self:countMines(p) if tile.mineCount > 0 then goto continue -- Stop if there are mines around end for dx = -1, 1 do for dy = -1, 1 do if dx == 0 and dy == 0 then goto continue2 -- Skip the current tile end local np = p + Vec2:new { x = dx, y = dy } if Minefield:get(np) == nil then goto continue2 -- Skip out of bounds end table.insert(open, np) ::continue2:: end end ::continue:: end end -- }}} function centerRect(rect) local screenSize = Vec2:new { x = 240, y = 136 } return Vec2:new { x = (screenSize.x - rect.x) // 2, y = (screenSize.y - rect.y) // 2, } end local minefield = Minefield:new() local sel = Vec2:new { x = 1, y = 1 } function sel:update() -- Update selection based on input if btnp(0) then self.y = math.max(1, self.y - 1) end -- Up if btnp(1) then self.y = math.min(minefield.height, self.y + 1) end -- Down if btnp(2) then self.x = math.max(1, self.x - 1) end -- Left if btnp(3) then self.x = math.min(minefield.width, self.x + 1) end -- Right end function sel:draw() -- Draw selection cursor local pos = self:toPixel() spr(SPRITE.cursor, pos.x + minefield.offset.x, pos.y + minefield.offset.y, 0) end function BOOT() -- Initialize minefield minefield = Minefield:new({ width = 10, height = 10, }) -- Set minefield offset to center the field local rectSize = minefield:pixelSize() minefield.offset = centerRect(rectSize) end function TIC() cls() local won = minefield:revealed() + minefield.mines >= minefield.width * minefield.height if minefield.lost or won then if btnp(4) then -- A button BOOT() -- Restart the game end goto draw end sel:update() if btnp(4) then -- A button if minefield.mines == 0 then local blacklist = { sel } minefield:placeMines(10, blacklist) end minefield:open(sel) end if btnp(5) then -- B button minefield:flag(sel) end ::draw:: minefield:draw() sel:draw() if minefield.lost then resetMsg("Game Over!", 12) -- Display game over message in white end if won then resetMsg("You Win!", 12) -- Display win message in green end end function resetMsg(text, color) local w1 = print(text, 0, -10) -- Display game over message local center1 = centerRect(Vec2:new { x = w1, y = 6 }) local w2 = print("Press A to restart", 0, -10) local center2 = centerRect(Vec2:new { x = w2, y = 6 }) print(text, center1.x, 0, color) -- Draw in white print("Press A to restart", center2.x, 7, color) -- Draw in white end