--luacheck: ignore --[[ It's a Minesweeper thingy - MewMew Cell Values: -- 1 to 8 = adjacent mines -- 9 = empty cell with grid -- 10 = mine -- 11 = marked mine ]] -- if script.active_mods['space-age'] then error('This map is incompatible with the Space Age mod. Disable Space Age to run this map.', 2) end require 'modules.satellite_score' local Functions = require 'maps.minesweeper.functions' local Map_score = require 'utils.gui.map_score' local Map = require 'modules.map_info' local Global = require 'utils.global' local minesweeper = {} Global.register( minesweeper, function (tbl) minesweeper = tbl end ) local number_colors = { [1] = { 0, 0, 210 }, [2] = { 0, 100, 0 }, [3] = { 180, 0, 0 }, [4] = { 0, 0, 120 }, [5] = { 120, 0, 0 }, [6] = { 0, 110, 110 }, [7] = { 0, 0, 0 }, [8] = { 125, 125, 125 }, [11] = { 185, 0, 255 } } local rendering_tile_values = { ['nuclear-ground'] = { offset = { 0.6, -0.2 }, zoom = 3, font = 'scenario-message-dialog' }, --['stone-path'] = {offset = {0.54, -0.27}, zoom = 3, font = 'default-large'}, ['stone-path'] = { offset = { 0.52, -0.28 }, zoom = 3, font = 'default-large-bold' }, ['concrete'] = { offset = { 0.52, -0.28 }, zoom = 3, font = 'default-large-bold' }, ['hazard-concrete-left'] = { offset = { 0.52, -0.28 }, zoom = 3, font = 'default-large-bold' }, ['hazard-concrete-right'] = { offset = { 0.52, -0.28 }, zoom = 3, font = 'default-large-bold' }, ['refined-concrete'] = { offset = { 0.54, -0.26 }, zoom = 3, font = 'default-game' }, ['refined-hazard-concrete-left'] = { offset = { 0.54, -0.26 }, zoom = 3, font = 'default-game' }, ['refined-hazard-concrete-right'] = { offset = { 0.54, -0.26 }, zoom = 3, font = 'default-game' } } local chunk_divide_vectors = {} for x = 0, 30, 2 do for y = 0, 30, 2 do table.insert(chunk_divide_vectors, { x, y }) end end local size_of_chunk_divide_vectors = #chunk_divide_vectors local chunk_vectors = {} for x = -32, 32, 32 do for y = -32, 32, 32 do table.insert(chunk_vectors, { x, y }) end end local cell_update_vectors = {} for x = -2, 2, 2 do for y = -2, 2, 2 do table.insert(cell_update_vectors, { x, y }) end end local cell_adjacent_vectors = {} for x = -2, 2, 2 do for y = -2, 2, 2 do if x == 0 and y == 0 then else table.insert(cell_adjacent_vectors, { x, y }) end end end local solving_vector_tables = {} local i = 1 for r = 3, 10, 1 do solving_vector_tables[i] = {} for x = r * -2, r * 2, 2 do for y = r * -2, r * 2, 2 do table.insert(solving_vector_tables[i], { x, y }) end end i = i + 1 end local size_of_solving_vector_tables = #solving_vector_tables local function update_rendering(cell, position) local surface = game.surfaces[1] local tile = surface.get_tile(position.x, position.y) local tile_values = rendering_tile_values[tile.name] if not tile_values then tile_values = { offset = { 0.6, -0.2 }, zoom = 3, font = 'scenario-message-dialog' } end if cell[2] then cell[2].destroy() end if cell[3] then cell[3].destroy() end local cell_value = cell[1] local color if number_colors[cell_value] then color = number_colors[cell_value] else color = { 125, 125, 125 } end local p = { position.x + tile_values.offset[1], position.y + tile_values.offset[2] } local text = cell_value if cell_value == 10 or cell_value == 9 then text = ' ' end if cell_value == 11 then text = 'X' end cell[2] = rendering.draw_text { text = text, surface = surface, target = p, color = color, scale = tile_values.zoom, font = tile_values.font, draw_on_ground = true, scale_with_zoom = false, only_in_alt_mode = false } if not tile.hidden_tile then return end if tile.hidden_tile ~= 'nuclear-ground' then return end cell[3] = rendering.draw_rectangle { width = 2, filled = false, surface = surface, left_top = position, right_bottom = { position.x + 2, position.y + 2 }, color = { 0, 0, 0 }, draw_on_ground = true, only_in_alt_mode = false } end local function get_adjacent_mine_count(position) local count = 0 for _, vector in pairs(cell_adjacent_vectors) do local p = { x = position.x + vector[1], y = position.y + vector[2] } local key = Functions.position_to_string(p) local cell = minesweeper.cells[key] if cell and cell[1] >= 10 then count = count + 1 end end return count end local function kill_cell(position) local key = Functions.position_to_string(position) local cell = minesweeper.cells[key] if not cell then return end if cell[2] then cell[2].destroy() end if cell[3] then cell[3].destroy() end minesweeper.cells[key] = nil end local function visit_cell(position) local score_change = 0 if not Functions.is_minefield_tile(position, true) then return score_change end local key = Functions.position_to_string(position) local cell = minesweeper.cells[key] local cell_value_before_visit = false if cell then if cell[1] == 10 then Functions.kaboom(position) score_change = -8 cell[1] = -1 for _, vector in pairs(cell_update_vectors) do local p = { x = position.x + vector[1], y = position.y + vector[2] } key = Functions.position_to_string(p) if minesweeper.cells[key] and minesweeper.cells[key][1] < 10 then table.insert(minesweeper.visit_queue, { x = p.x, y = p.y }) end end return score_change end if cell[1] == 11 then update_rendering(cell, position) return score_change end cell_value_before_visit = cell[1] end if not cell then minesweeper.cells[key] = {} end cell = minesweeper.cells[key] cell[1] = get_adjacent_mine_count(position) if cell[1] == 0 then for _, vector in pairs(cell_adjacent_vectors) do local adjacent_position = { x = position.x + vector[1], y = position.y + vector[2] } if Functions.is_minefield_tile(adjacent_position, true) then local adjacent_key = Functions.position_to_string(adjacent_position) if not minesweeper.cells[adjacent_key] then minesweeper.cells[adjacent_key] = {} end local adjacent_cell = minesweeper.cells[adjacent_key] local mine_count = get_adjacent_mine_count(adjacent_position) adjacent_cell[1] = mine_count update_rendering(adjacent_cell, adjacent_position) if mine_count == 0 then table.insert(minesweeper.visit_queue, { x = adjacent_position.x, y = adjacent_position.y }) end end end Functions.uncover_terrain(position) kill_cell(position) return score_change end if cell_value_before_visit and cell_value_before_visit ~= cell[1] then for _, vector in pairs(cell_adjacent_vectors) do local adjacent_position = { x = position.x + vector[1], y = position.y + vector[2] } local adjacent_key = Functions.position_to_string(adjacent_position) local adjacent_cell = minesweeper.cells[adjacent_key] if adjacent_cell and adjacent_cell[1] < 9 then table.insert(minesweeper.visit_queue, { x = adjacent_position.x, y = adjacent_position.y }) end end end update_rendering(cell, position) return score_change end local function get_solving_vectors(position) local distance_to_center = math.sqrt(position.x ^ 2 + position.y ^ 2) local key = math.floor(distance_to_center * 0.005) + 1 if key > size_of_solving_vector_tables then key = size_of_solving_vector_tables end local solving_vectors = solving_vector_tables[key] return solving_vectors end local function are_mines_marked_around_target(position) local marked_positions = {} for _, vector in pairs(get_solving_vectors(position)) do local p = { x = position.x + vector[1], y = position.y + vector[2] } local key = Functions.position_to_string(p) local cell = minesweeper.cells[key] if cell then if cell[1] == 10 then return end if cell[1] == 11 then table.insert(marked_positions, p) end end end return marked_positions end local function solve_attempt(position) local solved = false for _, vector in pairs(get_solving_vectors(position)) do local p = { x = position.x + vector[1], y = position.y + vector[2] } local key = Functions.position_to_string(p) local cell = minesweeper.cells[key] if cell and cell[1] > 10 then local marked_positions = are_mines_marked_around_target(p) if marked_positions then solved = true for _, pp in pairs(marked_positions) do minesweeper.cells[Functions.position_to_string(pp)][1] = -1 visit_cell(pp) Functions.disarm_reward(pp) end end end end return solved end local function mark_mine(entity, player) local position = Functions.position_to_cell_position(entity.position) local key = Functions.position_to_string(position) local cell = minesweeper.cells[key] local score_change = 0 --Success if cell and cell[1] > 9 then local surface = game.surfaces.nauvis if cell[1] == 10 then score_change = 1 end surface.create_entity( { name = 'flying-text', position = entity.position, text = 'Mine marked.', color = { r = 0.98, g = 0.66, b = 0.22 } } ) cell[1] = 11 update_rendering(cell, position) entity.destroy() local solved = solve_attempt(position) if solved then player.insert({ name = 'stone-furnace', count = 1 }) return score_change end local e = surface.create_entity({ name = 'item-on-ground', position = { position.x + 1, position.y + 1 }, stack = { name = 'stone-furnace', count = 1 } }) if e and e.valid then e.to_be_looted = true end return score_change end --Trigger all adjacent mines when missplacing a disarming furnace. for _, vector in pairs(cell_update_vectors) do local p = { x = position.x + vector[1], y = position.y + vector[2] } key = Functions.position_to_string(p) if minesweeper.cells[key] and minesweeper.cells[key][1] == 10 then Functions.kaboom(p) score_change = score_change - 8 minesweeper.cells[key][1] = -1 solve_attempt(p) table.insert(minesweeper.visit_queue, { x = p.x, y = p.y }) end end return score_change end local function add_mines_to_chunk(left_top, distance_to_center) local base_mine_count = 40 local max_mine_count = 128 local mine_count = distance_to_center * 0.043 + base_mine_count if mine_count > max_mine_count then mine_count = max_mine_count end local shuffle_index = {} for iv = 1, size_of_chunk_divide_vectors, 1 do table.insert(shuffle_index, iv) end table.shuffle_table(shuffle_index) -- place shuffled mines if distance_to_center < 128 then for iv = 1, mine_count, 1 do local vector = chunk_divide_vectors[shuffle_index[iv]] local position = { x = left_top.x + vector[1], y = left_top.y + vector[2] } if not Functions.is_spawn(position) then local key = Functions.position_to_string(position) minesweeper.cells[key] = { 10 } minesweeper.active_mines = minesweeper.active_mines + 1 end end else for iv = 1, mine_count, 1 do local vector = chunk_divide_vectors[shuffle_index[iv]] local position = { x = left_top.x + vector[1], y = left_top.y + vector[2] } local key = Functions.position_to_string(position) minesweeper.cells[key] = { 10 } minesweeper.active_mines = minesweeper.active_mines + 1 end end -- remove mines that would form a 3x3 block for _, chunk_vector in pairs(chunk_vectors) do local left_top_2 = { x = left_top.x + chunk_vector[1], y = left_top.y + chunk_vector[2] } for _, vector in pairs(chunk_divide_vectors) do local position = { x = left_top_2.x + vector[1], y = left_top_2.y + vector[2] } local key = Functions.position_to_string(position) local cell = minesweeper.cells[key] if cell and cell[1] == 10 then if get_adjacent_mine_count(position) == 8 then --if cell[2] then rendering.destroy(cell[2]) end minesweeper.cells[key] = nil end end end --[[ for _, vector in pairs(chunk_divide_vectors) do local position = {x = left_top_2.x + vector[1], y = left_top_2.y + vector[2]} local key = Functions.position_to_string(position) local cell = minesweeper.cells[key] if cell then update_rendering(cell, position) end end ]] end end local function on_chunk_generated(event) local left_top = event.area.left_top local surface = event.surface if surface.index ~= 1 then return end local distance_to_center = math.sqrt((left_top.x + 16) ^ 2 + (left_top.y + 16) ^ 2) local tiles = {} if distance_to_center < 128 then for x = 0, 31, 1 do for y = 0, 31, 1 do local position = { x = left_top.x + x, y = left_top.y + y } if Functions.is_spawn(position) then table.insert(tiles, { name = Functions.get_terrain_tile(surface, position), position = position }) else table.insert(tiles, { name = 'nuclear-ground', position = position }) end end end else for x = 0, 31, 1 do for y = 0, 31, 1 do local position = { x = left_top.x + x, y = left_top.y + y } table.insert(tiles, { name = 'nuclear-ground', position = position }) --table.insert(tiles, {name = Functions.get_terrain_tile(surface, position), position = position}) end end end surface.set_tiles(tiles, true) --surface.clear() will cause to trigger on_chunk_generated twice local key = Functions.position_to_string(left_top) if minesweeper.chunks[key] then return end minesweeper.chunks[key] = true add_mines_to_chunk(left_top, distance_to_center) end local function on_player_changed_position(event) local player = game.players[event.player_index] if not Functions.is_minefield_tile(player.position) then return end local cell_position = Functions.position_to_cell_position(player.position) local score_change = visit_cell(cell_position) if score_change < 0 then solve_attempt(cell_position) end Map_score.set_score(player, Map_score.get_score(player) + score_change) end local function deny_building(event) local entity = event.entity if not entity.valid then return end if not prototypes.item[entity.name] then return end if not Functions.is_minefield_tile(entity.position, true) then return end if event.player_index then local player = game.players[event.player_index] if entity.position.x % 2 == 1 and entity.position.y % 2 == 1 and entity.name == 'stone-furnace' then local score_change = mark_mine(entity, player) Map_score.set_score(player, Map_score.get_score(player) + score_change) return end player.insert({ name = entity.name, count = 1 }) else local inventory = event.robot.get_inventory(defines.inventory.robot_cargo) inventory.insert({ name = entity.name, count = 1 }) end entity.destroy() end local function on_built_entity(event) deny_building(event) end local function on_robot_built_entity(event) deny_building(event) end local function update_built_tiles(tiles) for _, placed_tile in pairs(tiles) do local cell_position = Functions.position_to_cell_position(placed_tile.position) local key = Functions.position_to_string(cell_position) local cell = minesweeper.cells[key] if not cell and Functions.is_minefield_tile(placed_tile.position) then minesweeper.cells[key] = { 9 } end cell = minesweeper.cells[key] if cell then update_rendering(cell, cell_position) end end end local function on_player_built_tile(event) update_built_tiles(event.tiles) end local function on_robot_built_tile(event) update_built_tiles(event.tiles) end local function on_player_mined_tile(event) update_built_tiles(event.tiles) end local function on_robot_mined_tile(event) update_built_tiles(event.tiles) end local function on_player_created(event) local player = game.players[event.player_index] player.insert({ name = 'stone-furnace', count = 1 }) end local function on_player_respawned(event) local player = game.players[event.player_index] player.insert({ name = 'stone-furnace', count = 1 }) game.surfaces.nauvis.destroy_decoratives({ name = 'nuclear-ground-patch' }) end local function on_entity_died(event) local entity = event.entity if not entity.valid then return end if entity.force.index ~= 2 then return end local force = event.force if not force then return end if force.name ~= 'minesweeper' then return end local revived_entity = entity.clone({ position = entity.position }) revived_entity.health = entity.max_health entity.destroy() end local function on_nth_tick() for k, position in pairs(minesweeper.visit_queue) do visit_cell(position) table.remove(minesweeper.visit_queue, k) break end end local function on_init() game.create_force('minesweeper') storage.custom_highscore.description = 'Minesweep rank:' local surface = game.surfaces[1] local mgs = surface.map_gen_settings mgs.water = 0 mgs.cliff_settings = { cliff_elevation_interval = 0, cliff_elevation_0 = 0 } mgs.autoplace_controls = { ['coal'] = { frequency = 0, size = 0, richness = 0 }, ['stone'] = { frequency = 0, size = 0, richness = 0 }, ['copper-ore'] = { frequency = 0, size = 0, richness = 0 }, ['iron-ore'] = { frequency = 0, size = 0, richness = 0 }, ['uranium-ore'] = { frequency = 0, size = 0, richness = 0 }, ['crude-oil'] = { frequency = 0, size = 0, richness = 0 }, ['trees'] = { frequency = 4, size = 0.5, richness = 0.1 } } surface.map_gen_settings = mgs surface.clear(true) minesweeper.chunks = {} minesweeper.cells = {} minesweeper.visit_queue = {} minesweeper.player_data = {} minesweeper.active_mines = 0 minesweeper.disarmed_mines = 0 minesweeper.triggered_mines = 0 local T = Map.get_map_information() T.localised_category = 'minesweeper' T.main_caption_color = { r = 255, g = 125, b = 55 } T.sub_caption_color = { r = 0, g = 250, b = 150 } end local Event = require 'utils.event' Event.on_init(on_init) Event.on_nth_tick(2, on_nth_tick) Event.add(defines.events.on_chunk_generated, on_chunk_generated) Event.add(defines.events.on_player_changed_position, on_player_changed_position) Event.add(defines.events.on_built_entity, on_built_entity) Event.add(defines.events.on_entity_died, on_entity_died) Event.add(defines.events.on_robot_built_entity, on_robot_built_entity) Event.add(defines.events.on_player_created, on_player_created) Event.add(defines.events.on_player_respawned, on_player_respawned) Event.add(defines.events.on_robot_built_tile, on_robot_built_tile) Event.add(defines.events.on_player_built_tile, on_player_built_tile) Event.add(defines.events.on_robot_mined_tile, on_robot_mined_tile) Event.add(defines.events.on_player_mined_tile, on_player_mined_tile)