require "xxhash" # Game logic main class class GameLogic attr_reader :pos def initialize @seed = rand(111_111..999_999) @pos = { board: {}, lost: {}, cache: {}, } end # Returns a pseudorandom number between 0 and 100 def hash(data) XXhash.xxh32(data.to_s, @seed) % 100 end def reset_pos @pos.each_value(&:clear) end def click(button, g_x, g_y) return if clickable?(g_x, g_y) case button when "left" if revealed?(g_x, g_y) if count(:mines, g_x, g_y) == count(:flags, g_x, g_y) (-1..1).each do |x| (-1..1).each do |y| next if x.zero? && y.zero? reveal(g_x + x, g_y + y) end end end else reveal(g_x, g_y) end when "right" flag(g_x, g_y) end end def clickable?(g_x, g_y) s_x = (g_x / 9).floor s_y = (g_y / 9).floor if @pos.lost["#{s_x}:#{s_y}"] || @pos.board.key?("#{s_x}:#{s_y}") return false end (-1..1).each do |x| (-1..1).each do |y| next if x.zero? && y.zero? return true if revealed?(g_x + x, g_y + y) || flagged?(g_x + x, g_y + y) end end false end def revealed?(g_x, g_y) l_x = ((g_x % 9) + 9) % 9 l_y = ((g_y % 9) + 9) % 9 s_x = (g_x / 9).floor s_y = (g_y / 9).floor return false if @pos.board.key?("#{s_x}:#{s_y}") || @pos.board["#{s_x}:#{s_y}"] == true @pos.board["#{s_x}:#{s_y}"][l_x][l_y][1] end def flagged?(g_x, g_y) l_x = ((g_x % 9) + 9) % 9 l_y = ((g_y % 9) + 9) % 9 s_x = (g_x / 9).floor s_y = (g_y / 9).floor return false if @pos.board.key?("#{s_x}:#{s_y}") return true if @pos.board["#{s_x}:#{s_y}"] == true && mine?(g_x, g_y) @pos.board["#{s_x}:#{s_y}"][l_x][l_y][2] end def reveal(g_x, g_y) l_x = ((g_x % 9) + 9) % 9 l_y = ((g_y % 9) + 9) % 9 s_x = (g_x / 9).floor s_y = (g_y / 9).floor return if @pos.board["#{s_x}:#{s_y}"] == true build_sector(s_x, s_y) if @pos.board["#{s_x}:#{s_y}"].nil? return if @pos.board["#{s_x}:#{s_y}"][l_x][l_y][1] || @pos.board["#{s_x}:#{s_y}"][l_x][l_y][2] if @pos.board["#{s_x}:#{s_y}"][l_x][l_y][0] == -1 @pos.lost["#{s_x}:#{s_y}"] = 1 elsif @pos.board["#{s_x}:#{s_y}"][l_x][l_y][0].zero? (-1..1).each do |x| (-1..1).each do |y| next if x.zero? && y.zero? reveal(g_x + x, g_y + y) end end else @pos.board["#{s_x}:#{s_y}"][l_x][l_y][1] = true end @pos.board["#{s_x}:#{s_y}"] = true if solved?(s_x, s_y) end def flag(g_x, g_y) l_x = ((g_x % 9) + 9) % 9 l_y = ((g_y % 9) + 9) % 9 s_x = (g_x / 9).floor s_y = (g_y / 9).floor return if @pos.board["#{s_x}:#{s_y}"] == true || revealed?(g_x, g_y) build_sector(s_x, s_y) if @pos.board["#{s_x}:#{s_y}"].nil? @pos.board["#{s_x}:#{s_y}"][l_x][l_y][2] ^= true end def solved?(s_x, s_y) (0..8).each do |l_x| (0..8).each do |l_y| return false if !@pos.board["#{s_x}:#{s_y}"][l_x][l_y][1] && @pos.board["#{s_x}:#{s_y}"][l_x][l_y][0] != -1 end end true end def count(type, g_x, g_y) sum = 0 (-1..1).each do |x| (-1..1).each do |y| next if x.zero? && y.zero? sum += 1 if case type when :mines then mine?(g_x + x, g_y + y) when :flags then flagged?(g_x + x, g_y + y) else false end end end sum end def build_sector(s_x, s_y) @pos.board["#{s_x}:#{s_y}"] = [] (0..8).each do |l_x| @pos.board["#{s_x}:#{s_y}"][l_x] = [] (0..8).each do |l_y| g_x = l_x + s_x * 9 g_y = l_y + s_y * 9 @pos.board["#{s_x}:#{s_y}"][l_x][l_y] = [ mine?(g_x, g_y) ? -1 : count(:mines, g_x, g_y), false, false, ] end end end def build_cache(s_x, s_y) return if @pos.cache["#{s_x}:#{s_y}"] return unless @pos.board["#{s_x}:#{s_y}"] == 1 @pos.cache["#{s_x}:#{s_y}"] = [] (0..8).each do |l_x| @pos.cache["#{s_x}:#{s_y}"][l_x] = [] (0..8).each do |l_y| g_x = l_x + s_x * 9 g_y = l_y + s_y * 9 @pos.cache["#{s_x}:#{s_y}"][l_x][l_y] = mine?(g_x, g_y) ? [-1, false, true] : [count(:mines, g_x, g_y), true, false] end end end def mine?(g_x, g_y) g_x == 4 || g_y == 4 ? false : hash("#{g_x}:#{g_y}") < 17 end end