Class: Map
- Inherits:
-
Object
- Object
- Map
- Includes:
- GameData
- Defined in:
- lib/terminal_hero/classes/map.rb
Overview
Represents a map for the player to navigate
Constant Summary
Constants included from GameData
GameData::ASCII_ART, GameData::COMBAT_ACTIONS, GameData::COMBAT_MENU_OPTIONS, GameData::COMMAND_LINE_ARGUMENTS, GameData::CON_TO_HP, GameData::DEFAULT_COORDS, GameData::DEFAULT_STATS, GameData::EXIT_KEYS, GameData::GAME_STATES, GameData::LEVELING_CONSTANT, GameData::LEVELING_EXPONENT, GameData::MAP_HEADER, GameData::MAP_HEIGHT, GameData::MAP_TILES, GameData::MAP_WIDTH, GameData::MAX_H_VIEW_DIST, GameData::MAX_V_VIEW_DIST, GameData::MESSAGES, GameData::MONSTER_LEVEL_VARIANCE, GameData::MOVE_KEYS, GameData::PROMPTS, GameData::STAT_POINTS_PER_LEVEL, GameData::TILE_DESCRIPTIONS, GameData::TITLE_MENU_OPTIONS, GameData::VALIDATION_REQUIREMENTS, GameData::XP_LOSS_MULTIPLIER
Instance Attribute Summary collapse
-
#grid ⇒ Object
readonly
Returns the value of attribute grid.
-
#symbols ⇒ Object
readonly
Returns the value of attribute symbols.
Instance Method Summary collapse
-
#export ⇒ Object
Export all values required for map initialization to a hash, to be stored in a JSON save file.
-
#generate_grid_params ⇒ Object
Calculate relevant paramaters for setup_grid.
-
#generate_map(player) ⇒ Object
Generate a new map when starting a new game.
-
#in_radius?(indices, centrepoints, radii, variance) ⇒ Boolean
Given indices, centrepoints, a radius, and a modification to the radius, determine whether indices fall inside the radius from the centrepoints.
-
#initialize(player: nil, width: GameData::MAP_WIDTH, height: GameData::MAP_HEIGHT, grid: nil, monsters: []) ⇒ Map
constructor
A new instance of Map.
-
#load_grid!(grid) ⇒ Object
Given a grid from save data, load it into the @grid instance variable.
-
#load_map(grid, player) ⇒ Object
Set up the map grid using data loaded from a save file when loading the game.
-
#load_monsters! ⇒ Object
Load monsters from save data into @monsters and place them on tiles in @grid.
-
#move_monsters(player_coords) ⇒ Object
Call methods for each monster to determine the move it makes (if any) and process that movement.
-
#populate_monsters(player_level) ⇒ Object
Randomly populate monsters on the grid up to a fluctuating maximum population.
-
#post_combat(player, monster, outcome) ⇒ Object
When combat ends, call methods to update the map based on the outcome.
-
#process_combat_defeat(player) ⇒ Object
If player was defeated in combat, move them back to starting location (unless already there), swapping positions with any entity that is currently occupying that location.
-
#process_combat_victory(player, monster) ⇒ Object
If a monster was defeated in combat, remove it and repopulate monsters.
-
#process_movement(mover, destination) ⇒ Object
Given destination coords for movement, update the map, move the moving entity and return the destination tile.
-
#remove_monster(monster) ⇒ Object
Remove a monster from the map.
-
#setup_grid ⇒ Object
Populate the map grid with terrain tiles in a semi-random distribution of regions expanding from the centre of the map outwards.
-
#valid_move?(coords) ⇒ Boolean
Check if coords are a valid destination within the map (but not necessarily open for movement).
Constructor Details
#initialize(player: nil, width: GameData::MAP_WIDTH, height: GameData::MAP_HEIGHT, grid: nil, monsters: []) ⇒ Map
Returns a new instance of Map.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/terminal_hero/classes/map.rb', line 23 def initialize(player: nil, width: GameData::MAP_WIDTH, height: GameData::MAP_HEIGHT, grid: nil, monsters: []) # Set dimensions of map @width = width @height = height # Dictionary of map symbols @symbols = GameData::MAP_TILES # Array of monsters on the map @monsters = monsters if grid.nil? generate_map(player) else load_map(grid, player) end end |
Instance Attribute Details
#grid ⇒ Object (readonly)
Returns the value of attribute grid.
21 22 23 |
# File 'lib/terminal_hero/classes/map.rb', line 21 def grid @grid end |
#symbols ⇒ Object (readonly)
Returns the value of attribute symbols.
21 22 23 |
# File 'lib/terminal_hero/classes/map.rb', line 21 def symbols @symbols end |
Instance Method Details
#export ⇒ Object
Export all values required for map initialization to a hash, to be stored in a JSON save file
237 238 239 240 241 242 243 244 245 246 |
# File 'lib/terminal_hero/classes/map.rb', line 237 def export return { width: @width, height: @height, grid: @grid.map do |row| row.map(&:export) end, monsters: @monsters.map(&:export) } end |
#generate_grid_params ⇒ Object
Calculate relevant paramaters for setup_grid
110 111 112 113 114 115 116 117 118 |
# File 'lib/terminal_hero/classes/map.rb', line 110 def generate_grid_params # Create 2D array grid grid = [] @height.times { grid.push(Array.new(@width, false)) } # Return parameters for map generation - centrepoints, base radius of map # regions, and the maximum random variance from that radius return grid, @width / 2, @height / 2, @width / 8, @height / 8, [@width, @height].min / 16 end |
#generate_map(player) ⇒ Object
Generate a new map when starting a new game
41 42 43 44 45 46 47 48 |
# File 'lib/terminal_hero/classes/map.rb', line 41 def generate_map(player) # Fill map grid with terrain tiles @grid = setup_grid # Place the Player on the map @grid[player.coords[:y]][player.coords[:x]].entity = player # Populate the map with monsters populate_monsters(player.level) end |
#in_radius?(indices, centrepoints, radii, variance) ⇒ Boolean
Given indices, centrepoints, a radius, and a modification to the radius, determine whether indices fall inside the radius from the centrepoints. Used for generating terrain.
101 102 103 104 105 106 107 |
# File 'lib/terminal_hero/classes/map.rb', line 101 def in_radius?(indices, centrepoints, radii, variance) y_index, x_index = indices y_centre, x_centre = centrepoints v_radius, h_radius = radii ((y_centre - v_radius - variance)..(y_centre + v_radius + variance)).include?(y_index) && ((x_centre - h_radius - variance)..(x_centre + h_radius + variance)).include?(x_index) end |
#load_grid!(grid) ⇒ Object
Given a grid from save data, load it into the @grid instance variable
70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/terminal_hero/classes/map.rb', line 70 def load_grid!(grid) @grid = grid.map do |row| row.map do |tile| # Convert string values generated by JSON back to symbols tile[:color] = tile[:color].to_sym tile[:event] = tile[:event].to_sym unless tile[:event].nil? # Map each hash of tile data to a Tile Tile.new(**tile) end end end |
#load_map(grid, player) ⇒ Object
Set up the map grid using data loaded from a save file when loading the game
93 94 95 96 97 |
# File 'lib/terminal_hero/classes/map.rb', line 93 def load_map(grid, player) load_grid!(grid) load_monsters! @grid[player.coords[:y]][player.coords[:x]].entity = player end |
#load_monsters! ⇒ Object
Load monsters from save data into @monsters and place them on tiles in @grid
83 84 85 86 87 88 89 90 |
# File 'lib/terminal_hero/classes/map.rb', line 83 def load_monsters! @monsters.map! do |monster_data| monster_data[:event] = monster_data[:event].to_sym monster = Monster.new(**monster_data) @grid[monster.coords[:y]][monster.coords[:x]].entity = monster monster end end |
#move_monsters(player_coords) ⇒ Object
Call methods for each monster to determine the move it makes (if any) and process that movement. If a monster encounters the player, return its tile to allow triggering a combat event.
178 179 180 181 182 183 184 185 186 |
# File 'lib/terminal_hero/classes/map.rb', line 178 def move_monsters(player_coords) event_tile = nil @monsters.each do |monster| destination = monster.calc_destination(monster.choose_move(player_coords)) process_movement(monster, destination) event_tile = @grid[monster.coords[:y]][monster.coords[:x]] if destination == player_coords end return event_tile end |
#populate_monsters(player_level) ⇒ Object
Randomly populate monsters on the grid up to a fluctuating maximum population
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/terminal_hero/classes/map.rb', line 51 def populate_monsters(player_level) # 1/60 map tiles +/- 5 will be populated with monsters max_monsters = [(@width * @height / 80) + rand(-5..5), 1].max # Populate map until max population is reached, or number of iterations equals # number of map tiles (preventing infinite loop if valid tile not found) counter = 0 until @monsters.length >= max_monsters || counter >= @width * @height y = rand(1..(@height - 2)) x = rand(1..(@width - 2)) unless @grid[y][x].blocking monster = Monster.new(coords: { x: x, y: y }, level_base: player_level) @grid[y][x].entity = monster @monsters.push(monster) end counter += 1 end end |
#post_combat(player, monster, outcome) ⇒ Object
When combat ends, call methods to update the map based on the outcome
225 226 227 228 229 230 231 232 |
# File 'lib/terminal_hero/classes/map.rb', line 225 def post_combat(player, monster, outcome) case outcome when :victory process_combat_victory(player, monster) when :defeat process_combat_defeat(player) end end |
#process_combat_defeat(player) ⇒ Object
If player was defeated in combat, move them back to starting location (unless already there), swapping positions with any entity that is currently occupying that location
201 202 203 204 205 206 207 208 209 |
# File 'lib/terminal_hero/classes/map.rb', line 201 def process_combat_defeat(player) return if player.coords.values == GameData::DEFAULT_COORDS.values shifted_entity = @grid[GameData::DEFAULT_COORDS[:y]][GameData::DEFAULT_COORDS[:x]].entity player_location = player.coords @grid[GameData::DEFAULT_COORDS[:y]][GameData::DEFAULT_COORDS[:x]].entity = nil process_movement(player, GameData::DEFAULT_COORDS) process_movement(shifted_entity, player_location) unless shifted_entity.nil? end |
#process_combat_victory(player, monster) ⇒ Object
If a monster was defeated in combat, remove it and repopulate monsters
219 220 221 222 |
# File 'lib/terminal_hero/classes/map.rb', line 219 def process_combat_victory(player, monster) remove_monster(monster) populate_monsters(player.level) end |
#process_movement(mover, destination) ⇒ Object
Given destination coords for movement, update the map, move the moving entity and return the destination tile
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/terminal_hero/classes/map.rb', line 153 def process_movement(mover, destination) # If player destination is out of bounds, display and log error, then exit begin raise InvalidInputError if !valid_move?(destination) && mover.instance_of?(Player) rescue InvalidInputError => e DisplayController .(GameData::MESSAGES[:general_error] .call("Movement", e, Utils.log_error(e), msg: GameData::MESSAGES[:out_of_bounds_error])) exit end # Return nil if monster tries to move out of bounds return nil unless valid_move?(destination) # Process move and if destination is not blocked and return destination tile unless @grid[destination[:y]][destination[:x]].blocking @grid[mover.coords[:y]][mover.coords[:x]].entity = nil @grid[destination[:y]][destination[:x]].entity = mover mover.coords = destination end return @grid[destination[:y]][destination[:x]] end |
#remove_monster(monster) ⇒ Object
Remove a monster from the map
213 214 215 216 |
# File 'lib/terminal_hero/classes/map.rb', line 213 def remove_monster(monster) @grid[monster.coords[:y]][monster.coords[:x]].entity = nil @monsters.delete(monster) end |
#setup_grid ⇒ Object
Populate the map grid with terrain tiles in a semi-random distribution of regions expanding from the centre of the map outwards
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/terminal_hero/classes/map.rb', line 122 def setup_grid grid, h_cent, v_cent, h_rad, v_rad, max_variance = generate_grid_params variance = 0 # Populate the map grid with terrain tiles grid.each_with_index do |row, y| row.map!.with_index do |_square, x| # First and last row and column are edge tiles if y == 0 || y == @height - 1 || x == 0 || x == @width - 1 tile = Tile.new(**@symbols[:edge]) # Tiles inside base radius (after variance) are region 1 elsif in_radius?([y, x], [v_cent, h_cent], [v_rad, h_rad], variance) tile = Tile.new(**@symbols[:mountain]) # Tiles not in region 1 that are inside 2 * base radius are region 2 elsif in_radius?([y, x], [v_cent, h_cent], [v_rad * 2, h_rad * 2], variance) tile = Tile.new(**@symbols[:forest]) # Everything else is region 3 else tile = Tile.new(**@symbols[:plain]) end # Change the variance applied to radius so region boundaries are irregular variance = Utils.collar(0, variance + rand(-1..1), max_variance) tile end end return grid end |
#valid_move?(coords) ⇒ Boolean
Check if coords are a valid destination within the map (but not necessarily open for movement)
189 190 191 192 193 194 195 |
# File 'lib/terminal_hero/classes/map.rb', line 189 def valid_move?(coords) return false unless coords.is_a?(Hash) return false unless (0..(@width - 1)).include?(coords[:x]) return false unless (0..(@height - 1)).include?(coords[:y]) return true end |