Browse Source

Now it comes to it. I've put this off for far too long.

Caleb Fangmeier 4 years ago
commit
c44e94cc39
78 changed files with 50446 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 2 0
      .idea/.gitignore
  3. 6 0
      .idea/BlackPycharmConfig.xml
  4. 13 0
      .idea/cocostrat.iml
  5. 7 0
      .idea/dictionaries/caleb.xml
  6. 7 0
      .idea/inspectionProfiles/profiles_settings.xml
  7. 7 0
      .idea/misc.xml
  8. 8 0
      .idea/modules.xml
  9. 7 0
      .idea/other.xml
  10. 7 0
      .idea/vcs.xml
  11. 4 0
      app/__init__.py
  12. BIN
      app/__pycache__/__init__.cpython-38.pyc
  13. BIN
      app/__pycache__/app.cpython-38.pyc
  14. BIN
      app/__pycache__/camera.cpython-38.pyc
  15. BIN
      app/__pycache__/economy.cpython-38.pyc
  16. BIN
      app/__pycache__/verkolst.cpython-38.pyc
  17. 152 0
      app/app.py
  18. 78 0
      app/camera.py
  19. 0 0
      app/colormap/__init__.py
  20. BIN
      app/colormap/__pycache__/__init__.cpython-38.pyc
  21. 23452 0
      app/colormap/colormap.c
  22. BIN
      app/colormap/colormap.cpython-38-x86_64-linux-gnu.so
  23. 146 0
      app/colormap/colormap.pyx
  24. 173 0
      app/economy.py
  25. 4 0
      app/nation.py
  26. 0 0
      app/trade/__init__.py
  27. BIN
      app/trade/__pycache__/__init__.cpython-38.pyc
  28. 25705 0
      app/trade/trade_utils.cpp
  29. BIN
      app/trade/trade_utils.cpython-38-x86_64-linux-gnu.so
  30. 99 0
      app/trade/trade_utils.pyx
  31. 0 0
      app/ui/__init__.py
  32. BIN
      app/ui/__pycache__/__init__.cpython-38.pyc
  33. BIN
      app/ui/__pycache__/map_overlay.cpython-38.pyc
  34. BIN
      app/ui/__pycache__/province_info.cpython-38.pyc
  35. BIN
      app/ui/__pycache__/status_bar.cpython-38.pyc
  36. 120 0
      app/ui/map_overlay.py
  37. 93 0
      app/ui/province_info.py
  38. 35 0
      app/ui/status_bar.py
  39. 30 0
      app/verkolst.py
  40. BIN
      assets/external/buildings.xcf
  41. BIN
      assets/external/buildings2.xcf
  42. BIN
      assets/gui/bottom_bar_1920_100_px.png
  43. BIN
      assets/gui/bottom_bar_1920_100_px.xcf
  44. BIN
      assets/gui/button_150_50_px.xcf
  45. BIN
      assets/gui/button_150_50_px_hover.png
  46. BIN
      assets/gui/button_150_50_px_normal.png
  47. BIN
      assets/gui/button_150_50_px_pressed.png
  48. BIN
      assets/gui/gold_40_40_px.png
  49. BIN
      assets/gui/gold_40_40_px.xcf
  50. BIN
      assets/gui/province_info_bg.png
  51. BIN
      assets/gui/province_info_bg.xcf
  52. 177 0
      assets/map/Verkolst.tmx
  53. BIN
      assets/map/buildings.png
  54. 4 0
      assets/map/buildings.tsx
  55. BIN
      assets/map/buildings.xcf
  56. BIN
      assets/map/nations.png
  57. 4 0
      assets/map/nations.tsx
  58. BIN
      assets/map/nations.xcf
  59. BIN
      assets/map/rivers.png
  60. 10 0
      assets/map/rivers.tsx
  61. BIN
      assets/map/rivers.xcf
  62. BIN
      assets/map/terrain.png
  63. 76 0
      assets/map/terrain.tsx
  64. BIN
      assets/map/terrain.xcf
  65. BIN
      assets/sound_effects/tap1.wav
  66. BIN
      assets/sound_effects/tap2.wav
  67. BIN
      assets/sprites/Dot.png
  68. BIN
      assets/sprites/Dot.xcf
  69. BIN
      assets/sprites/DotSmall.png
  70. BIN
      assets/sprites/DotSmall.xcf
  71. BIN
      assets/sprites/highlight_hex.png
  72. BIN
      assets/sprites/highlight_hex.xcf
  73. BIN
      assets/sprites/placeholder.png
  74. BIN
      assets/sprites/placeholder.xcf
  75. BIN
      assets/sprites/placeholder2.png
  76. BIN
      assets/sprites/placeholder2.xcf
  77. 3 0
      requirements.txt
  78. 15 0
      setup.py

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+cocos/
+build/

+ 2 - 0
.idea/.gitignore

@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml

+ 6 - 0
.idea/BlackPycharmConfig.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="BlackPycharmConfig">
+    <option name="executableName" value="/usr/bin/black" />
+  </component>
+</project>

+ 13 - 0
.idea/cocostrat.iml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/cocos" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+  <component name="PyDocumentationSettings">
+    <option name="renderExternalDocumentation" value="true" />
+  </component>
+</module>

+ 7 - 0
.idea/dictionaries/caleb.xml

@@ -0,0 +1,7 @@
+<component name="ProjectDictionaryState">
+  <dictionary name="caleb">
+    <words>
+      <w>verkolst</w>
+    </words>
+  </dictionary>
+</component>

+ 7 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,7 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="PROJECT_PROFILE" value="Default" />
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="ES6" />
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (cocostrat)" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/cocostrat.iml" filepath="$PROJECT_DIR$/.idea/cocostrat.iml" />
+    </modules>
+  </component>
+</project>

+ 7 - 0
.idea/other.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="PySciProjectComponent">
+    <option name="PY_SCI_VIEW" value="true" />
+    <option name="PY_SCI_VIEW_SUGGESTED" value="true" />
+  </component>
+</project>

+ 7 - 0
.idea/vcs.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/cocos" vcs="Git" />
+  </component>
+</project>

+ 4 - 0
app/__init__.py

@@ -0,0 +1,4 @@
+from pyglet.window import key, mouse
+keyboard_state = key.KeyStateHandler()
+mouse_state = mouse.MouseStateHandler()
+

BIN
app/__pycache__/__init__.cpython-38.pyc


BIN
app/__pycache__/app.cpython-38.pyc


BIN
app/__pycache__/camera.cpython-38.pyc


BIN
app/__pycache__/economy.cpython-38.pyc


BIN
app/__pycache__/verkolst.cpython-38.pyc


+ 152 - 0
app/app.py

@@ -0,0 +1,152 @@
+import datetime
+
+import pyglet as pyg
+import pyglet.window.key as key
+import pyglet.window.mouse as mouse
+
+import cocos
+from cocos.director import director
+
+from .camera import camera
+from .verkolst import verkolst
+from . import keyboard_state, mouse_state
+
+
+class CocostratUI(cocos.layer.Layer):
+    is_event_handler = True
+
+    def __init__(self):
+        from .ui.status_bar import StatusBar
+        from .ui.province_info import ProvinceInfo
+        import app.ui.map_overlay as overlays
+        super().__init__()
+        self.province_info = ProvinceInfo()
+        self.province_info.position = (director.window.width+450, director.window.height)
+        self.add(self.province_info)
+        # self.add_step_button()
+
+        self.status_bar = StatusBar()
+        self.add(self.status_bar)
+
+        self.hl_province = None
+        self.hl_province_sprite = cocos.sprite.Sprite('highlight_hex.png',
+                                                      position=(100, 100))
+        self.hl_province_sprite.visible = False
+
+        self.overlays = [
+            overlays.FoodOverlay(self, verkolst['overlay']),
+            overlays.PopsOverlay(self, verkolst['overlay'], 'lc'),
+            overlays.PopsOverlay(self, verkolst['overlay'], 'mc'),
+            overlays.PopsOverlay(self, verkolst['overlay'], 'uc'),
+            overlays.TradeDistanceOverlay(self, verkolst['overlay']),
+        ]
+
+        self.click_sound = pyg.media.load('assets/sound_effects/tap1.wav', streaming=False)
+        self.knock_sound = pyg.media.load('assets/sound_effects/tap2.wav', streaming=False)
+
+        self.on_turn_start()
+
+    def on_turn_start(self):
+        self.status_bar.on_turn_start()
+        self.province_info.on_turn_start()
+        for overlay in self.overlays:
+            overlay.update()
+
+    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
+        if buttons & mouse.MIDDLE:
+            x1, y1 = camera.screen_to_world(x-dx, y-dy)
+            x2, y2 = camera.screen_to_world(x, y)
+            d_world = (x2-x1, y2-y1)
+            cam = camera.cam_position
+            camera.cam_position = (cam[0] - d_world[0], cam[1] - d_world[1])
+
+    def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
+        if abs(scroll_y) == 1:
+            camera.zoom(scroll_y)
+
+    def on_mouse_press(self, x, y, buttons, modifiers):
+        if buttons & mouse.LEFT:
+            x, y = camera.screen_to_world(x, y)
+            self.hl_province = verkolst['terrain'].get_at_pixel(x, y)
+            if self.hl_province:
+                self.click_sound.play()
+                self.hl_province_sprite.position = self.hl_province.center
+                self.hl_province_sprite.visible = True
+            self.province_info.set_province(self.hl_province)
+            for overlay in self.overlays:
+                overlay.update()
+        elif buttons & mouse.RIGHT:
+            if self.hl_province is not None:
+                self.knock_sound.play()
+                self.hl_province = None
+                self.hl_province_sprite.visible = False
+                self.province_info.set_province(None)
+                for overlay in self.overlays:
+                    overlay.update()
+
+    def toggle_overlay(self, overlay_idx):
+        for idx, overlay in enumerate(self.overlays):
+            if idx == overlay_idx:
+                overlay.toggle()
+            else:
+                overlay.disable()
+        verkolst['overlay'].visible = any(overlay.enabled for overlay in self.overlays)
+
+    def on_key_press(self, symbol, modifiers):
+        if symbol == key.F1:
+            verkolst.process_turn()
+            self.on_turn_start()
+        elif symbol == key.F2:
+            self.toggle_overlay(0)
+        elif symbol == key.F3:
+            self.toggle_overlay(1)
+        elif symbol == key.F4:
+            self.toggle_overlay(2)
+        elif symbol == key.F5:
+            self.toggle_overlay(3)
+        elif symbol == key.F6:
+            self.toggle_overlay(4)
+
+
+def main():
+    from os.path import dirname, abspath, split, join
+    import pyglet.image
+    from pyglet import gl
+    pyglet.image.Texture.default_mag_filter = gl.GL_NEAREST
+    pyglet.image.Texture.default_min_filter = gl.GL_LINEAR
+
+    # Must specify full path for Pycharm debugger to work... ugh
+    base_dir = split(dirname(abspath(__file__)))[0]
+    pyg.resource.path = [
+        join(base_dir, 'assets/map/'),
+        join(base_dir, 'assets/sprites/'),
+        join(base_dir, 'assets/gui/'),
+        join(base_dir, 'assets/sound_effects/')
+    ]
+    pyg.resource.reindex()
+    # print(pyg.resource._default_loader._index)
+
+    director.init(width=1920, height=1080, autoscale=True, resizable=True,
+                  audio_backend='pyglet')
+    director.window.push_handlers(keyboard_state)
+    director.window.push_handlers(mouse_state)
+
+    verkolst.init('Verkolst.tmx')
+
+    camera.init()
+    camera.add(verkolst['terrain'])
+    camera.add(verkolst['rivers'])
+    camera.add(verkolst['buildings'])
+    camera.add(verkolst['nations'])
+    camera.add(verkolst['overlay'])
+
+    ui = CocostratUI()
+    camera.overlay_layer.add(ui.hl_province_sprite)
+
+    main_scene = cocos.scene.Scene(camera, ui)
+    director.run(main_scene)
+
+
+if __name__ == '__main__':
+    main()
+

+ 78 - 0
app/camera.py

@@ -0,0 +1,78 @@
+import math
+import cocos
+import pyglet.window.key as key
+from app import keyboard_state
+
+
+class RTSCameraMotion(cocos.actions.Action):
+    scale_levels = [0.5, 0.8, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0]
+
+    def __init__(self):
+        super().__init__()
+
+    def step(self, dt):
+        position = getattr(self.target, 'position', (0, 0))
+        speed = getattr(self.target, 'speed', (0, 0))
+        force = getattr(self.target, 'force', 10000)
+        mu_k = getattr(self.target, 'mu_k', 9)
+        scale_request_idx = getattr(self.target, 'scale_request_idx', 3)
+
+        # Handle Acceleration
+        speed = (speed[0] + (keyboard_state[key.RIGHT] - keyboard_state[key.LEFT]) * dt * force,
+                 speed[1] + (keyboard_state[key.UP] - keyboard_state[key.DOWN]) * dt * force)
+        # Handle Damping
+        ds = (-mu_k*speed[0]*dt, -mu_k*speed[1]*dt)
+        speed = (speed[0]+ds[0] if abs(ds[0]) < abs(speed[0]) else 0,
+                 speed[1]+ds[1] if abs(ds[1]) < abs(speed[1]) else 0)
+        # Update position and speed
+        self.target.speed = speed
+        self.target.position = (position[0] + speed[0]*dt, position[1] + speed[1]*dt)
+
+        if scroller := getattr(self.target, 'scroller'):
+            scroller.set_focus(self.target.x, self.target.y)
+            scroller.scale += 5*(self.scale_levels[scale_request_idx] - scroller.scale)*dt
+
+            # Keep object in bounds as calculated by the scroller
+            if self.target.x != scroller.restricted_fx:
+                self.target.position = (scroller.restricted_fx, self.target.position[1])
+                self.target.speed = (0.0, self.target.speed[1])
+            if self.target.y != scroller.restricted_fy:
+                self.target.position = (self.target.position[0], scroller.restricted_fy)
+                self.target.speed = (self.target.speed[1], 0.0)
+
+
+class RTSCamera(cocos.layer.ScrollingManager):
+
+    def __init__(self):
+        pass
+
+    def init(self):
+        super().__init__()
+
+        self.cam = cocos.cocosnode.CocosNode()
+        self.cam.scroller = self
+        self.cam_layer = cocos.layer.ScrollableLayer()
+        self.cam_layer.add(self.cam)
+        self.cam_motion = RTSCameraMotion()
+        self.cam.do(self.cam_motion)
+        self.add(self.cam_layer)
+
+        self.overlay_layer = cocos.layer.ScrollableLayer()
+        self.add(self.overlay_layer, z=1)
+
+
+    @property
+    def cam_position(self):
+        return self.cam.position
+
+    @cam_position.setter
+    def cam_position(self, new_position):
+        self.cam.position = new_position
+
+    def zoom(self, delta):
+        scale_request_idx_new = getattr(self.cam, 'scale_request_idx', 3) + delta
+        if 0 <= scale_request_idx_new < len(self.cam_motion.scale_levels):
+            self.cam.scale_request_idx = scale_request_idx_new
+
+
+camera = RTSCamera()

+ 0 - 0
app/colormap/__init__.py


BIN
app/colormap/__pycache__/__init__.cpython-38.pyc


File diff suppressed because it is too large
+ 23452 - 0
app/colormap/colormap.c


BIN
app/colormap/colormap.cpython-38-x86_64-linux-gnu.so


+ 146 - 0
app/colormap/colormap.pyx

@@ -0,0 +1,146 @@
+# New matplotlib colormaps by Nathaniel J. Smith, Stefan van der Walt,
+# and (in the case of viridis) Eric Firing.
+#
+# This file and the colormaps in it are released under the CC0 license /
+# public domain dedication. We would appreciate credit if you use or
+# redistribute these colormaps, but do not impose any legal restrictions.
+#
+# To the extent possible under law, the persons who associated CC0 with
+# mpl-colormaps have waived all copyright and related or neighboring rights
+# to mpl-colormaps.
+#
+# You should have received a copy of the CC0 legalcode along with this
+# work.  If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+
+__all__ = ['apply_colormap', 'apply_colormap_single']
+
+cdef float _magma_data[16][3]
+_magma_data[0][:] = [0.001462, 0.000466, 0.013866]
+_magma_data[1][:] = [0.039608, 0.031090, 0.133515]
+_magma_data[2][:] = [0.113094, 0.065492, 0.276784]
+_magma_data[3][:] = [0.211718, 0.061992, 0.418647]
+_magma_data[4][:] = [0.316654, 0.071690, 0.485380]
+_magma_data[5][:] = [0.414709, 0.110431, 0.504662]
+_magma_data[6][:] = [0.512831, 0.148179, 0.507648]
+_magma_data[7][:] = [0.613617, 0.181811, 0.498536]
+_magma_data[8][:] = [0.716387, 0.214982, 0.475290]
+_magma_data[9][:] = [0.816914, 0.255895, 0.436461]
+_magma_data[10][:] = [0.904281, 0.319610, 0.388137]
+_magma_data[11][:] = [0.960949, 0.418323, 0.359630]
+_magma_data[12][:] = [0.986700, 0.535582, 0.382210]
+_magma_data[13][:] = [0.996096, 0.653659, 0.446213]
+_magma_data[14][:] = [0.996898, 0.769591, 0.534892]
+_magma_data[15][:] = [0.992440, 0.884330, 0.640099]
+
+
+cdef float _inferno_data[16][3]
+_inferno_data[0][:] = [0.001462, 0.000466, 0.013866]
+_inferno_data[1][:] = [0.042253, 0.028139, 0.141141]
+_inferno_data[2][:] = [0.129285, 0.047293, 0.290788]
+_inferno_data[3][:] = [0.238273, 0.036621, 0.396353]
+_inferno_data[4][:] = [0.341500, 0.062325, 0.429425]
+_inferno_data[5][:] = [0.441207, 0.099338, 0.431594]
+_inferno_data[6][:] = [0.540920, 0.134729, 0.415123]
+_inferno_data[7][:] = [0.640135, 0.171438, 0.381065]
+_inferno_data[8][:] = [0.735683, 0.215906, 0.330245]
+_inferno_data[9][:] = [0.822386, 0.275197, 0.266085]
+_inferno_data[10][:] = [0.894305, 0.353399, 0.193584]
+_inferno_data[11][:] = [0.946965, 0.449191, 0.115272]
+_inferno_data[12][:] = [0.978422, 0.557937, 0.034931]
+_inferno_data[13][:] = [0.987874, 0.675267, 0.065257]
+_inferno_data[14][:] = [0.974638, 0.797692, 0.206332]
+_inferno_data[15][:] = [0.947594, 0.917399, 0.410665]
+
+cdef float _plasma_data[16][3]
+_plasma_data[0][:] = [0.050383, 0.029803, 0.527975]
+_plasma_data[1][:] = [0.193374, 0.018354, 0.590330]
+_plasma_data[2][:] = [0.299855, 0.009561, 0.631624]
+_plasma_data[3][:] = [0.399411, 0.000859, 0.656133]
+_plasma_data[4][:] = [0.494877, 0.011990, 0.657865]
+_plasma_data[5][:] = [0.584391, 0.068579, 0.632812]
+_plasma_data[6][:] = [0.665129, 0.138566, 0.585582]
+_plasma_data[7][:] = [0.736019, 0.209439, 0.527908]
+_plasma_data[8][:] = [0.798216, 0.280197, 0.469538]
+_plasma_data[9][:] = [0.853319, 0.351553, 0.413734]
+_plasma_data[10][:] = [0.901807, 0.425087, 0.359688]
+_plasma_data[11][:] = [0.942598, 0.502639, 0.305816]
+_plasma_data[12][:] = [0.973416, 0.585761, 0.251540]
+_plasma_data[13][:] = [0.991365, 0.675355, 0.198453]
+_plasma_data[14][:] = [0.993033, 0.771720, 0.154808]
+_plasma_data[15][:] = [0.974443, 0.874622, 0.144061]
+
+cdef float _viridis_data[16][3]
+_viridis_data[0][:] = [0.267004, 0.004874, 0.329415]
+_viridis_data[1][:] = [0.282327, 0.094955, 0.417331]
+_viridis_data[2][:] = [0.278826, 0.175490, 0.483397]
+_viridis_data[3][:] = [0.258965, 0.251537, 0.524736]
+_viridis_data[4][:] = [0.229739, 0.322361, 0.545706]
+_viridis_data[5][:] = [0.199430, 0.387607, 0.554642]
+_viridis_data[6][:] = [0.172719, 0.448791, 0.557885]
+_viridis_data[7][:] = [0.149039, 0.508051, 0.557250]
+_viridis_data[8][:] = [0.127568, 0.566949, 0.550556]
+_viridis_data[9][:] = [0.120638, 0.625828, 0.533488]
+_viridis_data[10][:] = [0.157851, 0.683765, 0.501686]
+_viridis_data[11][:] = [0.246070, 0.738910, 0.452024]
+_viridis_data[12][:] = [0.369214, 0.788888, 0.382914]
+_viridis_data[13][:] = [0.515992, 0.831158, 0.294279]
+_viridis_data[14][:] = [0.678489, 0.863742, 0.189503]
+_viridis_data[15][:] = [0.845561, 0.887322, 0.099702]
+
+import numpy as np
+cimport cython
+
+cmaps = {}
+for (name, data) in (
+        ('magma', _magma_data),
+        ('inferno', _inferno_data),
+        ('plasma', _plasma_data),
+        ('viridis', _viridis_data)
+):
+
+    cmaps[name] = np.array(data, dtype=np.float32)*255
+
+magma = cmaps['magma']
+inferno = cmaps['inferno']
+plasma = cmaps['plasma']
+viridis = cmaps['viridis']
+
+
+@cython.boundscheck(False)
+@cython.wraparound(False)
+cdef map_many(int[:, :] idxs, float[:, :, :] dst, float[:,:] cmap):
+    cdef int height = idxs.shape[0]
+    cdef int width = idxs.shape[1]
+    for i in range(height):
+        for j in range(width):
+            dst[i][j][0] = cmap[idxs[i][j]][0]
+            dst[i][j][1] = cmap[idxs[i][j]][1]
+            dst[i][j][2] = cmap[idxs[i][j]][2]
+
+
+def apply_colormap(input_: np.ndarray, colormap: str, min_: float=None, max_: float=None):
+
+    if max_ is None:
+        max_ = np.max(input_)
+    if min_ is None:
+        min_ = np.min(input_)
+    range_ = max_ - min_
+    if range_ == 0:
+        idxs = np.zeros((input_.shape[0], input_.shape[1]), dtype=np.int32)
+    else:
+        normalized = 15*(input_ - min_) / range_
+        idxs = normalized.astype(np.int32)
+        np.clip(idxs, 0, 15, out=idxs)
+    colors = np.zeros(shape=(input_.shape[0], input_.shape[1], 3), dtype=np.float32)
+    map_many(idxs, colors, cmaps[colormap])
+    return colors
+
+def apply_colormap_single(input_: np.ndarray, colormap: str, min_: float=None, max_: float=None):
+    range_ = max_ - min_
+    if range_ == 0:
+        idx = 0
+    else:
+        idx = int(15*(input_ - min_) / range_)
+    idx = np.clip(idx, 0, 15)
+    color = tuple(cmaps[colormap][idx])
+    return color

+ 173 - 0
app/economy.py

@@ -0,0 +1,173 @@
+import json
+import pickle
+from os.path import join
+from itertools import product
+import numpy as np
+
+
+class Resources:
+    def __init__(self, rows=10, cols=10):
+        self.shape = shape = (rows, cols)
+        self.lc_pops = np.zeros(shape, dtype=np.float32)
+        self.mc_pops = np.zeros(shape, dtype=np.float32)
+        self.uc_pops = np.zeros(shape, dtype=np.float32)
+
+        self.lc_starvation = np.zeros(shape, dtype=np.bool)
+        self.mc_starvation = np.zeros(shape, dtype=np.bool)
+        self.uc_starvation = np.zeros(shape, dtype=np.bool)
+        self.any_starvation = np.zeros(shape, dtype=np.bool)
+
+        self.food_rate = np.zeros(shape, dtype=np.float32)
+        self.food_production_capacity = np.zeros(shape, dtype=np.float32)
+        self.food_production = np.zeros(shape, dtype=np.float32)
+        self.food_consumption = np.zeros(shape, dtype=np.float32)
+        self.food_stored = np.zeros(shape, dtype=np.float32)
+        self.food_stored_capacity = np.zeros(shape, dtype=np.float32)
+
+        self.trade_distance = np.zeros(shape, dtype=np.float32)
+        self.trade_range = np.full(shape, 5)
+        self.trade_value = np.zeros(shape, dtype=np.float32)
+
+        self.export_partner = np.full((*shape, 2), 0)
+
+    def init(self, verkolst):
+        self.shape = (verkolst.width, verkolst.height)
+        self.lc_pops = np.full(self.shape, 25, dtype=np.float32)
+        self.mc_pops = np.full(self.shape, 0, dtype=np.float32)
+        self.uc_pops = np.full(self.shape, 0, dtype=np.float32)
+
+        self.lc_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
+        self.mc_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
+        self.uc_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
+        self.any_starvation = np.full_like(self.lc_pops, False, dtype=np.bool)
+
+        # Lets calculate the base food production
+        terrain = verkolst["terrain"]
+
+        food_rate = np.full(self.shape, 0, dtype=np.float32)
+        food_capacity = np.full(self.shape, 0, dtype=np.float32)
+        trade_distance = np.full(self.shape, 0, dtype=np.float32)
+        for i, j in product(range(self.shape[0]), range(self.shape[1])):
+            food_rate[i][j] = terrain.cells[i][j].tile.properties["food_rate"]
+            food_capacity[i][j] = terrain.cells[i][j].tile.properties["food_capacity"]
+            trade_distance[i][j] = terrain.cells[i][j].tile.properties["trade_distance"]
+
+        self.food_rate = food_rate
+        self.food_production_capacity = food_capacity
+        self.food_production = np.full_like(self.lc_pops, 0)
+        self.update_food_production()
+        self.food_consumption = 5 * self.uc_pops + 2 * self.mc_pops + 1 * self.lc_pops
+        self.food_stored = 2 * self.food_production
+        self.food_stored_capacity = np.full_like(self.lc_pops, 5000)
+
+        self.trade_distance = trade_distance
+        self.trade_range = np.full_like(self.lc_pops, 5)
+        self.update_trade_value()
+
+        self.export_partner = np.full((*self.shape, 2), 0)
+        self.update_export_partners()
+
+    def load(self, save_dir):
+        with open(join(save_dir, "pops.json"), "r") as f:
+            obs = json.load(f)
+            self.lc_pops = pickle.loads(obs["lc_pops"])
+            self.mc_pops = pickle.loads(obs["mc_pops"])
+            self.uc_pops = pickle.loads(obs["uc_pops"])
+
+    def dump(self, save_dir):
+        with open(join(save_dir, "pops.json"), "w") as f:
+            json.dump(
+                {
+                    "lc_pops": self.lc_pops.dumps(),
+                    "mc_pops": self.mc_pops.dumps(),
+                    "uc_pops": self.uc_pops.dumps(),
+                },
+                f,
+            )
+
+    def update_food_production(self):
+        (self.food_rate * np.floor(self.lc_pops)).clip(
+            min=0, max=self.food_production_capacity, out=self.food_production
+        )
+
+    def update_trade_value(self):
+        self.trade_value = self.lc_pops*1 + self.mc_pops*5 + self.uc_pops*20
+
+    def update_export_partners(self):
+        from app.trade.trade_utils import update_export_partner
+        update_export_partner(self.trade_range, self.trade_distance, self.trade_value, self.export_partner)
+
+    def eat_food(self):
+        # First, add any produced food to the stockpile
+        self.food_stored += self.food_production
+
+        # Next eat the food from uc->mc->lc marking starvation as it occurs
+        self.food_stored -= np.floor(self.uc_pops) * 5
+        self.uc_starvation = self.food_stored <= 0
+
+        self.food_stored -= np.floor(self.mc_pops) * 2
+        self.mc_starvation = self.food_stored <= 0
+
+        self.food_stored -= np.floor(self.lc_pops) * 1
+        self.lc_starvation = self.food_stored <= 0
+
+        self.any_starvation = np.logical_or(
+            self.lc_starvation, np.logical_or(self.mc_starvation, self.uc_starvation)
+        )
+
+        # Finally, clip the amount of food stored to between 0 and the food storage capacity
+        self.food_stored.clip(
+            min=0, max=self.food_stored_capacity, out=self.food_stored
+        )
+
+    def redistribute_classes(self):
+        total_pops = self.lc_pops + self.mc_pops + self.uc_pops
+        current_proportions = np.stack([self.lc_pops, self.mc_pops, self.uc_pops]) / (
+            total_pops
+        )
+        target_proportions = np.stack([
+            np.full_like(self.lc_pops, 0.8),
+            np.full_like(self.lc_pops, 0.15),
+            np.full_like(self.lc_pops, 0.05),
+        ])
+        target_proportions[0, self.any_starvation] = 1
+        target_proportions[1, self.any_starvation] = 0
+        target_proportions[2, self.any_starvation] = 0
+
+        new_proportions = current_proportions + (target_proportions - current_proportions) * 0.1
+        self.lc_pops = total_pops * new_proportions[0]
+        self.mc_pops = total_pops * new_proportions[1]
+        self.uc_pops = total_pops * new_proportions[2]
+
+    def make_babies(self):
+
+        food_stored_proportion = self.food_stored / self.food_stored_capacity
+
+        repro_rate = 1 + 0.10 * food_stored_proportion
+        repro_rate[self.any_starvation] = 1.0
+        repro_rate[self.lc_starvation] = 0.95
+        np.multiply(self.lc_pops, repro_rate, out=self.lc_pops, casting="unsafe")
+
+        repro_rate = 1 + 0.08 * food_stored_proportion
+        repro_rate[self.any_starvation] = 1.0
+        repro_rate[self.mc_starvation] = 0.95
+        np.multiply(self.mc_pops, repro_rate, out=self.mc_pops, casting="unsafe")
+
+        repro_rate = 1 + 0.06 * food_stored_proportion
+        repro_rate[self.any_starvation] = 1.0
+        repro_rate[self.uc_starvation] = 0.95
+        np.multiply(self.uc_pops, repro_rate, out=self.uc_pops, casting="unsafe")
+
+        self.food_consumption = (
+            5 * np.floor(self.uc_pops)
+            + 2 * np.floor(self.mc_pops)
+            + 1 * np.floor(self.lc_pops)
+        )
+
+    def process_turn(self):
+        self.eat_food()
+        self.redistribute_classes()
+        self.make_babies()
+        self.update_food_production()
+        self.update_trade_value()
+        self.update_export_partners()

+ 4 - 0
app/nation.py

@@ -0,0 +1,4 @@
+
+
+class Nation:
+    pass

+ 0 - 0
app/trade/__init__.py


BIN
app/trade/__pycache__/__init__.cpython-38.pyc


File diff suppressed because it is too large
+ 25705 - 0
app/trade/trade_utils.cpp


BIN
app/trade/trade_utils.cpython-38-x86_64-linux-gnu.so


+ 99 - 0
app/trade/trade_utils.pyx

@@ -0,0 +1,99 @@
+# distutils: language=c++
+cimport cython
+import numpy as np
+cimport numpy as np
+
+from libcpp.queue cimport priority_queue
+from libcpp.pair cimport pair
+
+ctypedef pair[float, int] step
+ctypedef priority_queue[step] pp_t
+
+
+@cython.boundscheck(False)
+@cython.wraparound(False)
+cdef hex_neighbors(const int i, const int j, int[:,:] out):
+    cdef int ii, jj
+    out[0][0], out[0][1] = i, j+1  # UU
+    out[1][0], out[1][1] = i, j-1  # DD
+    if i % 2 == 0:  # even rows
+        out[2][0], out[2][1] = i+1, j    # UR
+        out[3][0], out[3][1] = i+1, j-1  # DR
+        out[4][0], out[4][1] = i-1, j-1  # DL
+        out[5][0], out[5][1] = i-1, j    # UL
+    else:  # odd rows
+        out[2][0], out[2][1] = i+1, j+1  # UR
+        out[3][0], out[3][1] = i+1, j    # DR
+        out[4][0], out[4][1] = i-1, j    # DL
+        out[5][0], out[5][1] = i-1, j+1  # UL
+
+@cython.boundscheck(True)
+@cython.wraparound(False)
+@cython.cdivision(True)
+cdef _trade_distance(int init_i, int init_j, float trade_range, const float[:, :] trade_traversal_cost,
+                     const float[:, :] trade_value, float[:, :] distance):
+    """
+    Function needs following properties:
+      1. Returns an array for the map where
+        a) Shows trade distance where distance < trade_range
+        b) Otherwise, filled with -1
+      2. Returns the province that is the export partner (if one exists)
+    """
+
+    cdef int width = trade_traversal_cost.shape[0]
+    cdef int height = trade_traversal_cost.shape[1]
+    cdef pp_t pp
+    cdef step top
+    cdef int i, j, ii, jj
+    cdef int exp_i = -1, exp_j = -1
+    cdef float exp_val = 0
+    cdef float dist, dist_tmp
+    cdef int[:, :] neighbors = np.zeros((6,2), dtype=np.int32)
+
+    cdef char[:, :] visited = np.full((trade_traversal_cost.shape[0], trade_traversal_cost.shape[1]),
+                                      False, dtype=np.int8)
+    distance[init_i][init_j] = 0.0
+    pp.push(step(0.0, init_j*width+init_i))
+
+    while not pp.empty():
+        top = pp.top()
+        i = top.second % width
+        j = top.second // width
+        dist = -top.first
+        pp.pop()
+
+        if trade_value[i][j] > exp_val:
+            exp_i, exp_j = i, j
+            exp_val = trade_value[i][j]
+
+        hex_neighbors(i, j, neighbors)
+        for idx in range(6):
+            ii = neighbors[idx][0]
+            jj = neighbors[idx][1]
+            if 0 <= ii < width and 0 <= jj < height and not visited[ii][jj]:
+                visited[ii][jj] = True
+                dist_tmp = dist + trade_traversal_cost[ii][jj]
+
+                if dist_tmp <= trade_range:
+                    distance[ii][jj] = dist_tmp
+                    pp.push(step(-dist_tmp, jj*width+ii))
+    return exp_i, exp_j
+
+
+def trade_distance(int i, int j, float trade_range, trade_traversal_cost: np.ndarray, trade_value: np.ndarray):
+    distance = np.full_like(trade_traversal_cost, -1)
+    exp = _trade_distance(i, j, trade_range, trade_traversal_cost, trade_value, distance)
+    return exp, distance
+
+cpdef update_export_partner(trade_range: np.ndarray, trade_distance: np.ndarray, trade_value: np.ndarray, export_partner: np.ndarray):
+    cdef int width = trade_range.shape[0]
+    cdef int height = trade_range.shape[1]
+
+    cdef float[:, :] distance
+    for i in range(width):
+        for j in range(height):
+            distance = np.full_like(trade_range, -1)
+            (exp_i, exp_j) = _trade_distance(i, j, trade_range[i, j], trade_distance, trade_value, distance)
+            export_partner[i, j] = [exp_i, exp_j]
+
+

+ 0 - 0
app/ui/__init__.py


BIN
app/ui/__pycache__/__init__.cpython-38.pyc


BIN
app/ui/__pycache__/map_overlay.cpython-38.pyc


BIN
app/ui/__pycache__/province_info.cpython-38.pyc


BIN
app/ui/__pycache__/status_bar.cpython-38.pyc


+ 120 - 0
app/ui/map_overlay.py

@@ -0,0 +1,120 @@
+from itertools import product
+import numpy as np
+from app.verkolst import verkolst
+from app.colormap.colormap import apply_colormap
+from app.trade.trade_utils import trade_distance
+
+
+class Overlay:
+
+    def __init__(self, ui, hexmap_layer, opacity=120):
+        self._ui = ui
+        self._overlay = hexmap_layer
+        self._enabled = False
+        self._opacity = opacity
+
+    def enable(self):
+        self._enabled = True
+        self.update()
+
+    def disable(self):
+        self._enabled = False
+
+    def toggle(self):
+        if self._enabled:
+            self.disable()
+        else:
+            self.enable()
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    def _recalculate(self):
+        pass
+
+    def update(self):
+        if not self._enabled:
+            return
+        self._recalculate()
+        for i, j in product(range(verkolst.width), range(verkolst.height)):
+            self._overlay.set_cell_color(i, j, self.get_cell_color(i, j))
+            self._overlay.set_cell_opacity(i, j, self.get_cell_opacity(i, j))
+
+    def get_cell_color(self, i, j):
+        raise NotImplementedError()
+
+    def get_cell_opacity(self, i, j):
+        return self._opacity
+
+
+class FoodOverlay(Overlay):
+
+    def __init__(self, ui, hexmap_layer):
+        super().__init__(ui, hexmap_layer)
+
+    def get_cell_color(self, i, j):
+        res = verkolst.resources
+        if res.any_starvation[i, j]:
+            return 255, 0, 0  # Red
+        elif res.food_stored[i, j]/res.food_stored_capacity[i, j] < 0.25:
+            return 255, 153, 51  # Orange
+        elif res.food_stored[i, j] < res.food_stored_capacity[i, j]:
+            return 51, 204, 51  # Green
+        else:
+            return 0, 0, 255  # Blue
+
+
+class PopsOverlay(Overlay):
+
+    def __init__(self, ui, hexmap_layer, class_):
+        super().__init__(ui, hexmap_layer, opacity=240)
+        self._class = class_
+        self._colors = None
+
+    def _recalculate(self):
+        pops = {
+            'lc': verkolst.resources.lc_pops,
+            'mc': verkolst.resources.mc_pops,
+            'uc': verkolst.resources.uc_pops,
+        }[self._class]
+        self._colors = apply_colormap(pops, 'magma', min_=0)
+
+    def get_cell_color(self, i, j):
+        return self._colors[i, j]
+
+
+class TradeDistanceOverlay(Overlay):
+
+    def __init__(self, ui, hexmap_layer):
+        super().__init__(ui, hexmap_layer, opacity=180)
+        self._colors = None
+        self.trade_distance = np.full_like(verkolst.resources.lc_pops, 0)
+        self.exp_partner = None
+
+    def _recalculate(self):
+        if self._ui.hl_province is not None:
+            self.exp_partner, self.trade_distance = trade_distance(self._ui.hl_province.i, self._ui.hl_province.j, 10,
+                                                                   verkolst.resources.trade_distance,
+                                                                   verkolst.resources.trade_value)
+        else:
+            self.trade_distance[:, :] = 0.0
+        self._colors = apply_colormap(self.trade_distance, 'viridis', min_=0)
+
+    def get_cell_color(self, i, j):
+        if self._ui.hl_province is None:
+            return 0, 0, 0
+        elif (i, j) == self.exp_partner:
+            return 255, 204, 0  # Gold
+        else:
+            return self._colors[i, j]
+
+    def get_cell_opacity(self, i, j):
+        if self._ui.hl_province is None:
+            return 0
+        elif self.trade_distance[i, j] < 0:  # out of range
+            return 0
+        elif (i, j) == self.exp_partner:
+            return 255
+        else:
+            return self._opacity

+ 93 - 0
app/ui/province_info.py

@@ -0,0 +1,93 @@
+import cocos
+from cocos.director import director
+from app.verkolst import verkolst
+
+
+class ProvinceInfo(cocos.layer.Layer):
+
+    is_event_handler = False
+
+    def __init__(self):
+        super().__init__()
+        self.position = (director.window.width+450, director.window.height)
+
+        self.province = None
+
+        background = cocos.sprite.Sprite('province_info_bg.png', position=(0, 0), anchor=(450, 300))
+        self.add(background)
+
+        self.terrain_type_label = cocos.text.Label("-", font_size=10, position=(-450+30, -30))
+        self.add(self.terrain_type_label)
+
+        self.lc_pops_label = cocos.text.HTMLLabel("-", position=(-450+30, -45))
+        self.add(self.lc_pops_label)
+
+        self.mc_pops_label = cocos.text.HTMLLabel("-", position=(-450+30, -60))
+        self.add(self.mc_pops_label)
+
+        self.uc_pops_label = cocos.text.HTMLLabel("-", position=(-450+30, -75))
+        self.add(self.uc_pops_label)
+
+        self.food_production_label = cocos.text.Label("-", font_size=10, position=(-450+30, -90))
+        self.add(self.food_production_label)
+
+        self.food_consumption_label = cocos.text.Label("-", font_size=10, position=(-450+30, -105))
+        self.add(self.food_consumption_label)
+
+        self.food_rate_label = cocos.text.Label("-", font_size=10, position=(-450+30, -120))
+        self.add(self.food_rate_label)
+
+        self.food_stored_label = cocos.text.Label("-", font_size=10, position=(-450+30, -135))
+        self.add(self.food_stored_label)
+
+        self.trade_value_label = cocos.text.Label("-", font_size=10, position=(-450+30, -150))
+        self.add(self.trade_value_label)
+
+        self.export_partner_label = cocos.text.Label("-", font_size=10, position=(-450+30, -165))
+        self.add(self.export_partner_label)
+
+    def set_province(self, province):
+        if self.province is None and province is not None:
+            self.do(cocos.actions.MoveTo((director.window.width, director.window.height), 0.1))
+        elif self.province is not None and province is None:
+            self.do(cocos.actions.MoveTo((director.window.width+450, director.window.height), 0.1))
+
+        self.province = province
+        self._render()
+
+    def _render(self):
+        if self.province is not None:
+            self.terrain_type_label.element.text = (f'{self.province.tile.properties["name"]}'
+                                                    f' ({self.province.i}, {self.province.j})')
+            i, j = self.province.i, self.province.j
+            resources = verkolst.resources
+
+            pops_tot = resources.lc_pops[i][j] + resources.mc_pops[i][j] + resources.uc_pops[i][j]
+
+            def _fmt(label, number, starving):
+                if starving[i][j]:
+                    hl_color = "#B22222"  # Red
+                elif resources.any_starvation[i][j]:
+                    hl_color = "#DAA520"  # Yellow
+                else:
+                    hl_color = "#FFFFF0"  # Off White
+                return (f'<font color="#FFFFF0">{label}: </font><font color="{hl_color}">{int(number[i][j]):d}</font>'
+                        f'<font color="#FFFFF0"> - {100*number[i][j]/pops_tot:.1f}%</font>'
+                        )
+
+            self.lc_pops_label.element.text = _fmt('LC', resources.lc_pops, resources.lc_starvation)
+            self.mc_pops_label.element.text = _fmt('MC', resources.mc_pops, resources.mc_starvation)
+            self.uc_pops_label.element.text = _fmt('UC', resources.uc_pops, resources.uc_starvation)
+
+            self.food_production_label.element.text = f"Food production: {resources.food_production[i][j]:.1f}"
+            self.food_consumption_label.element.text = f"Food consumption: {resources.food_consumption[i][j]:.1f}"
+            self.food_rate_label.element.text = f"Food production rate: {resources.food_rate[i][j]:.1f}"
+            self.food_stored_label.element.text = f"Food stored: {resources.food_stored[i][j]:.1f}"
+
+            self.trade_value_label.element.text = f"Trade value: {resources.trade_value[i][j]:.1f}"
+
+            self.export_partner_label.element.text = f"Exporting to: ({resources.export_partner[i][j][0]:.1f}, {resources.export_partner[i][j][1]:.1f})"
+
+    def on_turn_start(self):
+        self._render()
+

+ 35 - 0
app/ui/status_bar.py

@@ -0,0 +1,35 @@
+import datetime
+import cocos
+
+from app.verkolst import verkolst
+
+
+class StatusBar(cocos.layer.Layer):
+
+    def __init__(self):
+        super().__init__()
+
+        background = cocos.sprite.Sprite('bottom_bar_1920_100_px.png',
+                                         position=(0, 0),
+                                         anchor=(0, 0))
+        self.add(background)
+        gold_icon = cocos.sprite.Sprite('gold_40_40_px.png',
+                                        position=(100, 4),
+                                        anchor=(0, 0))
+        gold_count = cocos.text.Label('1238',
+                                      position=(140, 12),
+                                      font_size=20,
+                                      )
+        self.add(gold_icon)
+        self.add(gold_count)
+
+        self.date_label = cocos.text.Label('-',
+                                           position=(1920-5, 12),
+                                           font_size=20,
+                                           anchor_x="right")
+        self.add(self.date_label)
+
+    def on_turn_start(self):
+        month = datetime.datetime.strftime(verkolst.date, '%B')
+        self.date_label.element.text = f'{month} {verkolst.date.day}, {verkolst.date.year}'
+

+ 30 - 0
app/verkolst.py

@@ -0,0 +1,30 @@
+import datetime
+import cocos
+from .economy import Resources
+
+
+class Verkolst:
+
+    def __init__(self):
+        self.map = None
+        self.date = None
+        self.resources = Resources()
+        self.height = 0
+        self.width = 0
+
+    def init(self, tmx_source):
+        self.map = cocos.tiles.load('Verkolst.tmx')
+        self.height = len(self['terrain'].cells[0])
+        self.width = len(self['terrain'].cells)
+        self.date = datetime.date(1278, 1, 1)
+        self.resources.init(self)
+
+    def process_turn(self):
+        self.date += datetime.timedelta(days=1)
+        self.resources.process_turn()
+
+    def __getitem__(self, item):
+        return self.map[item]
+
+
+verkolst = Verkolst()

BIN
assets/external/buildings.xcf


BIN
assets/external/buildings2.xcf


BIN
assets/gui/bottom_bar_1920_100_px.png


BIN
assets/gui/bottom_bar_1920_100_px.xcf


BIN
assets/gui/button_150_50_px.xcf


BIN
assets/gui/button_150_50_px_hover.png


BIN
assets/gui/button_150_50_px_normal.png


BIN
assets/gui/button_150_50_px_pressed.png


BIN
assets/gui/gold_40_40_px.png


BIN
assets/gui/gold_40_40_px.xcf


BIN
assets/gui/province_info_bg.png


BIN
assets/gui/province_info_bg.xcf


+ 177 - 0
assets/map/Verkolst.tmx

@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.2" tiledversion="1.3.2" orientation="hexagonal" renderorder="right-down" compressionlevel="0" width="50" height="30" tilewidth="115" tileheight="100" infinite="0" hexsidelength="62" staggeraxis="x" staggerindex="even" nextlayerid="7" nextobjectid="1">
+ <tileset firstgid="1" source="terrain.tsx"/>
+ <tileset firstgid="26" source="rivers.tsx"/>
+ <tileset firstgid="51" source="buildings.tsx"/>
+ <tileset firstgid="76" source="nations.tsx"/>
+ <layer id="1" name="terrain" width="50" height="30">
+  <data encoding="csv">
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,7,7,6,6,6,6,6,6,6,6,5,5,5,5,5,5,5,11,11,11,3,11,11,11,11,3,3,3,6,6,6,5,5,3,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,7,3,6,6,6,6,6,6,6,6,5,5,5,5,5,11,11,11,3,3,11,11,11,11,6,6,3,6,6,6,5,5,3,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,7,3,3,6,6,6,6,6,6,6,6,6,5,5,5,5,11,11,11,3,11,11,11,11,11,6,6,6,6,6,6,6,5,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,7,4,3,3,3,3,6,6,6,6,6,6,6,6,5,5,5,11,11,11,11,11,11,11,11,6,6,6,3,6,6,6,5,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,7,4,4,4,3,3,3,6,6,6,6,6,6,6,6,6,5,11,11,11,11,11,6,11,11,11,6,6,3,3,6,6,5,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,7,4,4,4,3,3,3,3,3,3,3,3,3,3,5,5,6,5,11,11,11,11,11,11,11,11,6,6,3,3,6,6,6,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,4,4,4,4,4,3,3,3,4,3,3,3,3,3,3,5,3,5,5,11,3,11,11,6,11,11,6,3,3,3,3,6,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,7,7,7,4,4,4,4,4,4,4,4,4,4,3,3,3,3,3,3,3,3,3,5,5,3,5,6,5,3,3,6,3,3,3,6,
+2,2,2,2,2,2,2,2,2,2,7,2,7,7,7,7,4,4,4,4,4,4,4,4,4,4,4,4,4,3,7,7,7,3,3,3,3,5,5,5,5,5,6,3,3,6,3,6,3,6,
+2,2,2,2,2,2,2,2,2,7,7,7,3,7,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,7,7,2,7,7,3,4,3,4,5,5,5,5,3,3,3,3,3,3,3,
+2,2,2,2,2,2,2,2,2,7,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,7,7,2,2,2,2,7,4,4,4,5,5,5,3,3,6,3,3,3,3,3,
+2,2,2,2,2,2,2,2,2,7,7,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,7,7,2,2,2,7,7,4,4,4,4,5,5,5,3,3,6,6,3,3,3,
+2,2,2,2,2,2,2,2,2,2,2,7,7,3,8,3,8,3,8,4,4,4,4,4,4,4,4,4,3,7,7,7,7,7,7,7,4,4,4,4,5,5,5,5,3,6,6,6,3,3,
+2,2,2,2,2,2,2,2,2,2,2,7,7,3,3,8,3,8,8,3,3,3,3,4,3,4,3,4,3,3,3,3,3,7,7,7,7,4,3,3,3,5,3,3,3,3,6,6,3,3,
+2,2,2,2,2,2,2,2,2,2,2,7,7,3,3,3,5,3,3,8,8,3,3,3,3,3,3,3,3,3,3,3,5,3,5,7,7,5,3,3,3,3,3,3,3,3,6,6,3,3,
+2,2,2,2,2,2,2,2,2,2,2,2,7,3,3,6,6,5,3,8,3,8,8,8,3,3,3,3,3,5,5,5,5,5,5,5,5,5,3,3,3,3,3,3,3,3,6,6,6,3,
+2,2,2,2,2,2,2,2,2,2,2,2,7,7,3,6,6,5,5,3,8,8,8,8,3,3,3,3,3,5,5,5,5,5,5,5,5,5,3,3,3,3,3,3,3,3,6,6,6,6,
+2,2,2,2,2,2,2,2,2,2,2,2,7,7,3,3,6,5,5,3,3,3,3,3,3,3,3,3,3,3,5,5,5,5,5,5,3,3,3,3,3,3,3,3,3,3,3,6,6,6,
+2,2,2,2,2,2,2,2,2,2,2,2,2,7,7,3,5,6,5,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,6,6,3,6,6,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,3,5,6,6,5,5,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,6,6,3,3,6,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,3,5,5,5,6,5,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,6,3,3,6,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,7,7,5,5,5,5,5,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,6,11,3,3,
+2,2,2,2,2,2,2,2,2,2,2,2,2,7,7,7,7,7,7,5,5,5,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,6,3,
+2,2,2,2,2,2,7,2,2,2,2,2,2,7,7,7,7,7,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,6,
+2,2,2,2,7,7,7,2,2,2,7,2,7,7,7,7,7,7,7,7,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,6,
+2,2,2,7,7,7,3,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,
+2,2,2,7,7,3,3,3,7,7,7,7,7,7,3,7,3,7,7,7,7,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,
+2,2,2,2,7,7,7,7,7,7,3,3,3,7,7,7,7,7,7,7,7,7,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11,
+2,2,2,2,2,7,2,7,7,7,7,7,7,7,2,7,2,7,2,7,2,7,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,3,11,11,11,11,11,11,11,11,
+2,2,2,2,2,2,2,2,2,7,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11,11,11,11,11,11,11,11,11
+</data>
+ </layer>
+ <layer id="2" name="rivers" width="50" height="30">
+  <data encoding="csv">
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,41,41,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,41,41,41,41,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,41,41,0,0,0,41,36,40,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,41,41,0,41,41,41,39,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,26,0,40,0,41,41,40,0,0,41,41,36,0,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,35,0,26,26,36,41,37,31,41,0,38,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,41,28,28,26,37,41,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,41,41,38,34,41,38,36,41,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,41,41,41,41,34,41,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,41,41,41,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,41,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+</data>
+ </layer>
+ <layer id="3" name="buildings" width="50" height="30">
+  <data encoding="csv">
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,51,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+</data>
+ </layer>
+ <layer id="5" name="nations" width="50" height="30" visible="0" opacity="0.41">
+  <data encoding="csv">
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,0,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,0,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,0,0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,76,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,76,76,76,76,76,0,0,0,0,0,0,0,0,76,76,76,76,76,76,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,76,0,76,76,76,76,76,76,76,76,0,0,0,0,0,0,0,0,76,76,76,76,76,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,76,76,76,76,0,76,76,0,76,76,76,76,0,76,0,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,76,76,76,76,76,76,76,76,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,76,76,76,76,76,76,76,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,76,76,76,76,76,76,76,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,76,76,76,0,76,76,76,76,76,76,76,76,76,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,76,76,76,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,76,76,76,76,0,0,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+</data>
+ </layer>
+ <layer id="6" name="overlay" width="50" height="30" visible="0">
+  <data encoding="csv">
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,
+91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91
+</data>
+ </layer>
+</map>

BIN
assets/map/buildings.png


+ 4 - 0
assets/map/buildings.tsx

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tileset version="1.2" tiledversion="1.3.1" name="buildings" tilewidth="115" tileheight="100" spacing="4" tilecount="25" columns="5">
+ <image source="buildings.png" width="591" height="516"/>
+</tileset>

BIN
assets/map/buildings.xcf


BIN
assets/map/nations.png


+ 4 - 0
assets/map/nations.tsx

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tileset version="1.2" tiledversion="1.3.2" name="nations" tilewidth="115" tileheight="100" spacing="4" tilecount="25" columns="5">
+ <image source="nations.png" width="591" height="516"/>
+</tileset>

BIN
assets/map/nations.xcf


BIN
assets/map/rivers.png


+ 10 - 0
assets/map/rivers.tsx

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tileset version="1.2" tiledversion="1.3.1" name="rivers" tilewidth="116" tileheight="101" spacing="6" tilecount="25" columns="5">
+ <grid orientation="orthogonal" width="118" height="106"/>
+ <image source="rivers.png" width="604" height="529"/>
+ <tile id="0">
+  <properties>
+   <property name="sides" value="TL-BR"/>
+  </properties>
+ </tile>
+</tileset>

BIN
assets/map/rivers.xcf


BIN
assets/map/terrain.png


+ 76 - 0
assets/map/terrain.tsx

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tileset version="1.2" tiledversion="1.3.2" name="terrain" tilewidth="115" tileheight="100" spacing="4" tilecount="25" columns="5">
+ <image source="terrain.png" width="591" height="516"/>
+ <tile id="0">
+  <properties>
+   <property name="food_capacity" type="float" value="0"/>
+   <property name="food_rate" type="float" value="0"/>
+   <property name="name" value="null"/>
+   <property name="trade_distance" type="float" value="0"/>
+  </properties>
+ </tile>
+ <tile id="1">
+  <properties>
+   <property name="food_capacity" type="float" value="0"/>
+   <property name="food_rate" type="float" value="0"/>
+   <property name="name" value="water_deep"/>
+   <property name="trade_distance" type="float" value="2"/>
+  </properties>
+ </tile>
+ <tile id="2">
+  <properties>
+   <property name="food_capacity" type="float" value="1000"/>
+   <property name="food_rate" type="float" value="1.5"/>
+   <property name="name" value="grassland"/>
+   <property name="trade_distance" type="float" value="1"/>
+  </properties>
+ </tile>
+ <tile id="3">
+  <properties>
+   <property name="food_capacity" type="float" value="2000"/>
+   <property name="food_rate" type="float" value="2"/>
+   <property name="name" value="fertile_grassland"/>
+   <property name="trade_distance" type="float" value="1"/>
+  </properties>
+ </tile>
+ <tile id="4">
+  <properties>
+   <property name="food_capacity" type="float" value="500"/>
+   <property name="food_rate" type="float" value="1.1"/>
+   <property name="name" value="mountains"/>
+   <property name="trade_distance" type="float" value="10"/>
+  </properties>
+ </tile>
+ <tile id="5">
+  <properties>
+   <property name="food_capacity" type="float" value="500"/>
+   <property name="food_rate" type="float" value="1.1"/>
+   <property name="name" value="forest_coniferous"/>
+   <property name="trade_distance" type="float" value="5"/>
+  </properties>
+ </tile>
+ <tile id="6">
+  <properties>
+   <property name="food_capacity" type="float" value="1000"/>
+   <property name="food_rate" type="float" value="1.5"/>
+   <property name="name" value="water_shallow"/>
+   <property name="trade_distance" type="float" value="0.5"/>
+  </properties>
+ </tile>
+ <tile id="7">
+  <properties>
+   <property name="food_capacity" type="float" value="5000"/>
+   <property name="food_rate" type="float" value="5"/>
+   <property name="name" value="fields"/>
+   <property name="trade_distance" type="float" value="1"/>
+  </properties>
+ </tile>
+ <tile id="10">
+  <properties>
+   <property name="food_capacity" type="float" value="500"/>
+   <property name="food_rate" type="float" value="1.1"/>
+   <property name="name" value="forest_deciduous"/>
+   <property name="trade_distance" type="float" value="5"/>
+  </properties>
+ </tile>
+</tileset>

BIN
assets/map/terrain.xcf


BIN
assets/sound_effects/tap1.wav


BIN
assets/sound_effects/tap2.wav


BIN
assets/sprites/Dot.png


BIN
assets/sprites/Dot.xcf


BIN
assets/sprites/DotSmall.png


BIN
assets/sprites/DotSmall.xcf


BIN
assets/sprites/highlight_hex.png


BIN
assets/sprites/highlight_hex.xcf


BIN
assets/sprites/placeholder.png


BIN
assets/sprites/placeholder.xcf


BIN
assets/sprites/placeholder2.png


BIN
assets/sprites/placeholder2.xcf


+ 3 - 0
requirements.txt

@@ -0,0 +1,3 @@
+cocos2d==0.6.8
+numpy==1.18.1
+cython==0.29.15

+ 15 - 0
setup.py

@@ -0,0 +1,15 @@
+from distutils.core import setup
+from Cython.Build import cythonize
+import numpy as np
+
+ext_options = dict(
+    compiler_directives={'profile': False, 'language_level': 3},
+    annotate=False,
+)
+setup(
+    ext_modules=cythonize([
+        'app/colormap/colormap.pyx',
+        'app/trade/trade_utils.pyx',
+    ], **ext_options),
+    include_dirs=[np.get_include()],
+)