Skip to content

Commit

Permalink
Extract Minimap._iter_points() into a function
Browse files Browse the repository at this point in the history
Using this function, the minimap can be generated in another process
without an instance of `Minimap`, thus solving the problem of resolving
the use of a `Dummy` object for rendering in an even better way.
  • Loading branch information
MasterofJOKers committed Oct 16, 2016
1 parent bdb8462 commit 67edb7a
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 98 deletions.
15 changes: 3 additions & 12 deletions horizons/gui/modules/singleplayermenu.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from horizons.extscheduler import ExtScheduler
from horizons.gui.modules import AIDataSelection, PlayerDataSelection
from horizons.gui.util import load_uh_widget
from horizons.gui.widgets.minimap import Minimap
from horizons.gui.widgets.minimap import Minimap, iter_minimap_points
from horizons.gui.windows import Window
from horizons.savegamemanager import SavegameManager
from horizons.scenario import InvalidScenarioFileFormat, ScenarioEventHandler
Expand Down Expand Up @@ -604,15 +604,6 @@ def _get_selected_map(self):
return scenario[language_index][1]


class _DataMinimap(Minimap):
def __init__(self, position, world):
"""Initialize only the very basics needed for `Minimap.get_data()`"""
self.use_rotation = False
self.location = position
self.world = world
self._update_world_to_minimap_ratio()


def generate_random_minimap(size, parameters):
"""Called as subprocess, calculates minimap data and passes it via string via stdout"""
# called as standalone basically, so init everything we need
Expand All @@ -638,9 +629,9 @@ def generate_random_minimap(size, parameters):
map_file = generate_random_map(*parameters)
world = load_raw_world(map_file)
location = Rect.init_from_topleft_and_size_tuples((0, 0), size)
minimap = _DataMinimap(location, world=world)

# communicate via stdout. Sometimes the process seems to print more information, therefore
# we add markers around our data so it's easier for the caller to get to the data.
data = minimap.get_data()
args = (location, world, Minimap.COLORS['island'], Minimap.COLORS['water'])
data = [(x, y, r, g, b) for (x, y), (r, g, b) in iter_minimap_points(*args)]
print('DATA', json.dumps(data), 'ENDDATA')
174 changes: 88 additions & 86 deletions horizons/gui/widgets/minimap.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,78 @@
from horizons.util.shapes import Circle, Point, Rect


def get_world_to_minimap_ratio(world_dimensions, minimap_dimensions):
"""Compute the pixel of the world needed for one pixel on the minimap.
Returns a tuple for x and y, in case they differ.
Accepts two tuples of (width, height) to compute the ratio on.
"""
return tuple(float(w) / m for w, m in zip(world_dimensions, minimap_dimensions))


def iter_minimap_points(location, world, island_color, water_color, area=None):
"""Return an iterator over the pixels of a minimap of the given world.
For every pixel, a tuple ((x, y), (r, g, b)) is returned. These are the x and y
coordinated and the color of the pixel in RGB.
If `area` is set, it's supposed to be a part of `location`, that is to be
returned.
"""
if area is None:
area = location

# calculate which area of the real map is mapped to which pixel on the minimap
world_dimensions = (world.map_dimensions.width, world.map_dimensions.height)
minimap_dimensions = (location.width, location.height)
pixel_per_coord_x, pixel_per_coord_y = get_world_to_minimap_ratio(world_dimensions, minimap_dimensions)

# calculate values here so we don't have to do it in the loop
pixel_per_coord_x_half_as_int = int(pixel_per_coord_x/2)
pixel_per_coord_y_half_as_int = int(pixel_per_coord_y/2)

world_min_x = world.min_x
world_min_y = world.min_y
full_map = world.full_map

# loop through map coordinates, assuming (0, 0) is the origin of the minimap
# this facilitates calculating the real world coords
for x in xrange(area.left - location.left, area.left + area.width - location.left):
for y in xrange(area.top - location.top, area.top + area.height - location.top):
"""
This code should be here, but since python can't do inlining, we have to inline
ourselves for performance reasons
covered_area = Rect.init_from_topleft_and_size(
int(x * pixel_per_coord_x)+world_min_x,
int(y * pixel_per_coord_y)+world_min_y),
int(pixel_per_coord_x), int(pixel_per_coord_y))
real_map_point = covered_area.center
"""
# use center of the rect that the pixel covers
real_map_x = int(x * pixel_per_coord_x) + world_min_x + pixel_per_coord_x_half_as_int
real_map_y = int(y * pixel_per_coord_y) + world_min_y + pixel_per_coord_y_half_as_int
real_map_coords = (real_map_x, real_map_y)

# check what's at the covered_area
if real_map_coords in full_map:
# this pixel is an island
tile = full_map[real_map_coords]
settlement = tile.settlement
if settlement is None:
# island without settlement
if tile.id <= 0:
color = water_color
else:
color = island_color
else:
# pixel belongs to a player
color = settlement.owner.color.to_tuple()
else:
color = water_color

yield ((x, y), color)


class Minimap(object):
"""A basic minimap.
Expand Down Expand Up @@ -190,11 +262,6 @@ def draw(self):
ExtScheduler().add_new_object(self._timed_update, self,
self.SHIP_DOT_UPDATE_INTERVAL, -1)

def get_data(self):
"""Returns a list representing the minimap data"""
return [(point.x, point.y, r, g, b)
for point, (r, g, b) in self._iter_points()]

def draw_data(self, data):
"""Display data from dump_data"""
# only icon mode for now
Expand Down Expand Up @@ -467,79 +534,6 @@ def cleanup():

return True

def _iter_points(self, where=None):
"""Returns an iterator over the points.
This can be used to only get the points without having to use any
rendering.
"""
if where is None:
where = self.location

# calculate which area of the real map is mapped to which pixel on the minimap
pixel_per_coord_x, pixel_per_coord_y = self._world_to_minimap_ratio

# calculate values here so we don't have to do it in the loop
pixel_per_coord_x_half_as_int = int(pixel_per_coord_x/2)
pixel_per_coord_y_half_as_int = int(pixel_per_coord_y/2)

world_min_x = self.world.min_x
world_min_y = self.world.min_y
island_col = self.COLORS["island"]
water_col = self.COLORS["water"]
location_left = self.location.left
location_top = self.location.top

fife_point = fife.Point(0, 0)

use_rotation = self._get_rotation_setting()
full_map = self.world.full_map

# loop through map coordinates, assuming (0, 0) is the origin of the minimap
# this facilitates calculating the real world coords
for x in xrange(where.left-self.location.left, where.left+where.width-self.location.left):
for y in xrange(where.top-self.location.top, where.top+where.height-self.location.top):

"""
This code should be here, but since python can't do inlining, we have to inline
ourselves for performance reasons
covered_area = Rect.init_from_topleft_and_size(
int(x * pixel_per_coord_x)+world_min_x,
int(y * pixel_per_coord_y)+world_min_y),
int(pixel_per_coord_x), int(pixel_per_coord_y))
real_map_point = covered_area.center
"""
# use center of the rect that the pixel covers
real_map_x = int(x * pixel_per_coord_x) + world_min_x + pixel_per_coord_x_half_as_int
real_map_y = int(y * pixel_per_coord_y) + world_min_y + pixel_per_coord_y_half_as_int
real_map_coords = (real_map_x, real_map_y)

# check what's at the covered_area
if real_map_coords in full_map:
# this pixel is an island
tile = full_map[real_map_coords]
settlement = tile.settlement
if settlement is None:
# island without settlement
if tile.id <= 0:
color = water_col
else:
color = island_col
else:
# pixel belongs to a player
color = settlement.owner.color.to_tuple()
else:
color = water_col

if use_rotation:
# inlined _get_rotated_coords
rot_x, rot_y = self._rotate((location_left + x, location_top + y), self._rotations)
fife_point.set(rot_x - location_left, rot_y - location_top)
else:
fife_point.set(x, y)

yield (fife_point, color)

def _recalculate(self, where=None):
"""Calculate which pixel of the minimap should display what and draw it
@param where: Rect of minimap coords. Defaults to self.location
Expand All @@ -552,9 +546,21 @@ def _recalculate(self, where=None):
if where is None:
rt.removeAll(render_name)

location_left = self.location.left
location_top = self.location.top
draw_point = rt.addPoint
fife_point = fife.Point(0, 0)
use_rotation = self._get_rotation_setting()

for fife_point, color in self._iter_points(where):
for (x, y), color in iter_minimap_points(self.location, self.world,
self.COLORS["island"], self.COLORS["water"], where):
if use_rotation:
# inlined _get_rotated_coords
rot_x, rot_y = self._rotate((location_left + x, location_top + y), self._rotations)
fife_point.set(rot_x - location_left, rot_y - location_top)
else:
fife_point.set(x, y)
fife_point.set(x, y)
draw_point(render_name, fife_point, *color)

def _timed_update(self, force=False):
Expand Down Expand Up @@ -737,13 +743,9 @@ def _rotate(self, tup, rotations):
return (new_x, new_y)

def _update_world_to_minimap_ratio(self):
world_height = self.world.map_dimensions.height
world_width = self.world.map_dimensions.width
minimap_height = self.location.height
minimap_width = self.location.width
pixel_per_coord_x = float(world_width) / minimap_width
pixel_per_coord_y = float(world_height) / minimap_height
self._world_to_minimap_ratio = (pixel_per_coord_x, pixel_per_coord_y)
world_dimensions = (self.world.map_dimensions.width, self.world.map_dimensions.height)
minimap_dimensions = (self.location.width, self.location.height)
self._world_to_minimap_ratio = get_world_to_minimap_ratio(world_dimensions, minimap_dimensions)

def _world_coords_to_minimap_coords(self, tup):
"""Calculates which pixel in the minimap contains a coord in the real map.
Expand Down

0 comments on commit 67edb7a

Please sign in to comment.