local GameMode = require 'tetris.modes.gamemode' local Grid = require 'tetris.components.grid' local Minesweeper = GameMode:extend() Minesweeper.name = "Minesweeper" Minesweeper.hash = "Minesweeper" Minesweeper.tagline = "Avoid the exploding mines!" local width = 15 local height = 15 function Minesweeper:new() self.super:new() self:initializeGrid() self.menu_state = 1 self.mines = {} self.cursor = {x=1, y=1} self.first_move = true self.ready_frames = 1 self.next_queue_length = 0 end function Minesweeper:isMine(x, y) for _, mine in pairs(self.mines) do if mine.x == x and mine.y == y then return true end end return false end function Minesweeper:initializeGrid() self.grid = Grid(width, height + 4) self.flags = math.floor(0.15 * width * height) self.chosen_colour = ({ "R", "O", "Y", "G", "C", "B", "M" })[math.random(7)] for y = 1, height do for x = 1, width do self.grid.grid[y+4][x] = { skin = "2tie", colour = self.chosen_colour } end end end function Minesweeper:initializeMines(sel_x, sel_y) for i = 1, self.flags do local x, y repeat x = math.random(1, width) y = math.random(1, height) until not (self:isMine(x, y) or (sel_x == x and sel_y == y)) table.insert(self.mines, {x=x, y=y}) end end function Minesweeper:advanceOneFrame(inputs, ruleset) if self.menu_state ~= 0 then self:menuLoop(inputs) return false elseif self.game_over or self.completed or self.ready_frames > 1 then return true else self:mainGameLoop(inputs) return false end end function Minesweeper:menuLoop(inputs) if not self.prev_inputs.right and inputs.right then if self.menu_state == 1 then width = math.min(width + 1, 28) else height = math.min(height + 1, 22) end self:initializeGrid() elseif not self.prev_inputs.left and inputs.left then if self.menu_state == 1 then width = math.max(width - 1, 8) else height = math.max(height - 1, 8) end self:initializeGrid() elseif (not self.prev_inputs.down and inputs.down) or (not self.prev_inputs.up and inputs.up) then self.menu_state = -self.menu_state end if not self.prev_inputs.rotate_left and inputs.rotate_left then self.ready_frames = 100 self.menu_state = 0 end self.prev_inputs = inputs end function Minesweeper:mainGameLoop(inputs) self:moveCursor(inputs) if not self.prev_inputs.rotate_left and inputs.rotate_left and self.grid.grid[self.cursor.y+4][self.cursor.x].colour ~= "W" and self.grid.grid[self.cursor.y+4][self.cursor.x].colour ~= "D" then playSE("lock") if not self:isMine(self.cursor.x, self.cursor.y) then self:uncoverCell(self.cursor.x, self.cursor.y) else self.game_over = true end end if not self.prev_inputs.rotate_right and inputs.rotate_right then self:flagCell(self.cursor.x, self.cursor.y) end self:updateFlagCount() self.frames = self.frames + 1 self.prev_inputs = inputs end function Minesweeper:updateFlagCount() local flags = math.floor(0.15 * width * height) for y = 5, height + 4 do for x = 1, width do if self.grid.grid[y][x].colour == "D" then flags = flags - 1 end end end self.flags = flags end function Minesweeper:moveCursor(inputs) if not self.prev_inputs.right and inputs.right then self.cursor.x = math.min(self.cursor.x + 1, width) elseif not self.prev_inputs.left and inputs.left then self.cursor.x = math.max(self.cursor.x - 1, 1) elseif not self.prev_inputs.down and inputs.down then self.cursor.y = math.min(self.cursor.y + 1, height) elseif not self.prev_inputs.up and inputs.up then self.cursor.y = math.max(self.cursor.y - 1, 1) end end function Minesweeper:flagCell(x, y) if self.flags > 0 and self.grid.grid[y+4][x].skin == "2tie" and self.grid.grid[y+4][x].colour ~= "W" then self.grid.grid[y+4][x] = { skin = "gem", colour = "D" } elseif self.grid.grid[y+4][x].skin == "gem" then self.grid.grid[y+4][x] = { skin = "2tie", colour = self.chosen_colour } end end function Minesweeper:uncoverCell(x, y) if self.first_move then self:initializeMines(x, y) self.first_move = false end local stack = { { x = x, y = y, } } while #stack > 0 do local current = table.remove(stack) local x = current.x local y = current.y self.grid.grid[y+4][x] = { skin = "2tie", colour = "W" } if self:getSurroundingMineCount(x, y) == 0 then for dy = -1, 1 do for dx = -1, 1 do if not (dx == 0 and dy == 0) and x+dx >= 1 and x+dx <= width and y+dy >= 1 and y+dy <= height and self.grid.grid[y+4+dy][x+dx].colour ~= "W" then table.insert(stack, { x = x + dx, y = y + dy, }) end end end end end for y = 5, height + 4 do for x = 1, width do if self.grid.grid[y][x].colour ~= "W" and not self:isMine(x, y-4) then return end end end self.completed = true end function Minesweeper:getSurroundingMineCount(x, y) local mines = 0 for dy = -1, 1 do for dx = -1, 1 do if not (dx == 0 and dy == 0) and self:isMine(x+dx, y+dy) then mines = mines + 1 end end end return mines end function Minesweeper:onGameOver() for y = 5, height + 4 do for x = 1, width do if self:isMine(x, y-4) then self.grid.grid[y][x] = { skin = "gem", colour = "R" } else self.grid.grid[y][x] = { skin = "2tie", colour = "W" } end end end end function Minesweeper:onGameComplete() self:onGameOver() love.graphics.setColor( self.game_over_frames % 4 < 2 and {0.3, 1, 0.3, 1} or {1, 1, 1, 1} ) love.graphics.setFont(font_3x5_3) love.graphics.print("GOOD JOB!", 80 + 16 * width, 280) end local function opacityFunction(game, block, x, y, age) if block.colour == "W" then return 0.5, 0.5, 0.5, 1, 1 else return 1, 1, 1, 1, 1 end end function Minesweeper:drawGrid() self.grid:drawCustom(opacityFunction, self) love.graphics.setColor(1, 1, 1, 1) love.graphics.setFont(font_3x5_2) love.graphics.setLineWidth(2) for y = 5, height + 4 do for x = 1, width do if self.cursor.x == x and self.cursor.y + 4 == y then love.graphics.rectangle("line", 48+x*16, y*16, 16, 16) end if self:getSurroundingMineCount(x, y-4) ~= 0 and self.grid.grid[y][x].colour == "W" then love.graphics.print(self:getSurroundingMineCount(x, y-4), 50+x*16, -2+y*16) end end end end function Minesweeper:drawScoringInfo() love.graphics.setColor(1, 1, 1, 1) local x = 80 + 16 * width love.graphics.setFont(font_3x5_2) love.graphics.print("DIMENSIONS", x, 100) love.graphics.print("FLAGS", x, 160) love.graphics.print("TIME ELAPSED", x, 220) love.graphics.setFont(font_3x5_3) love.graphics.print(width .. " x " .. height, x, 120) love.graphics.print(self.flags, x, 180) love.graphics.print(formatTime(self.frames), x, 240) end function Minesweeper:drawCustom() if self.menu_state ~= 0 then love.graphics.setFont(font_3x5_3) love.graphics.setColor( self.menu_state == 1 and {1, 1, 0.3, 1} or {1, 1, 1, 1} ) love.graphics.print("WIDTH: " .. width, 67, 100) love.graphics.setColor( self.menu_state == -1 and {1, 1, 0.3, 1} or {1, 1, 1, 1} ) love.graphics.print("HEIGHT: " .. height, 67, 140) love.graphics.setColor(1, 1, 1, 1) end end return Minesweeper