From 5e205dfe09d67dc20bf2eeec560e3943d463dd0c Mon Sep 17 00:00:00 2001 From: Serin Delaunay Date: Wed, 14 Jun 2017 20:49:50 +0100 Subject: [PATCH 01/10] Move map data structure into class Remove IPython artefacts from .py files --- Wangview/Display.ipynb | 18 ++++---- Wangview/Display.py | 16 ++++--- Wangview/Hypergraph.py | 10 ----- Wangview/MapGrid.ipynb | 100 +++++++++++++++++++++++++++++++++++++++++ Wangview/MapGrid.py | 58 ++++++++++++++++++++++++ WangviewIPython.ipynb | 21 ++++++--- WangviewIPython.py | 16 +------ 7 files changed, 193 insertions(+), 46 deletions(-) create mode 100644 Wangview/MapGrid.ipynb create mode 100644 Wangview/MapGrid.py diff --git a/Wangview/Display.ipynb b/Wangview/Display.ipynb index 65793e7..9c5d98c 100644 --- a/Wangview/Display.ipynb +++ b/Wangview/Display.ipynb @@ -10,13 +10,13 @@ "source": [ "from bearlibterminal import terminal as blt\n", "import json\n", - "from collections import deque\n", "from itertools import product\n", "import random\n", "from os import path\n", "from .Tileset import Tileset\n", "from .Hypergraph import Hypergraph\n", - "from .FPSLimiter import FPSLimiter" + "from .FPSLimiter import FPSLimiter\n", + "from .MapGrid import MapGrid" ] }, { @@ -142,9 +142,10 @@ " \"\"\"\n", " terrain_iter = self.hypergraph.generate_lines(\n", " self.terrain_width, self.terrain_height)\n", - " terrain_deque_iter = (deque(line, self.terrain_width)\n", - " for line in terrain_iter)\n", - " self.terrain_map = deque(terrain_deque_iter, self.terrain_height)\n", + " self.terrain_map = MapGrid(self.terrain_width, self.terrain_height)\n", + " for line in terrain_iter:\n", + " line_list = list(line)\n", + " self.terrain_map.add_line(line, True, False)\n", " def init_tile_map(self):\n", " \"\"\"\n", " Generates a grid of unicode codepoints specifying graphical tiles,\n", @@ -155,14 +156,15 @@ " tile_iter = ((self.select_tile(self.get_tile_corners(x,y))\n", " for x in range(self.tile_width))\n", " for y in range(self.tile_height))\n", - " tile_deque_iter = (deque(line, self.tile_width) for line in tile_iter)\n", - " self.tile_map = deque(tile_deque_iter, self.tile_height)\n", + " self.tile_map = MapGrid(self.tile_width, self.tile_height)\n", + " for line in tile_iter:\n", + " self.tile_map.add_line(line, True, False)\n", " def get_tile_corners(self, x, y):\n", " \"\"\"\n", " Returns a generator which iterates over the terrain values in positions\n", " [(x,y), (x,y+1), (x+1, y), (x+1, y+1)]\n", " \"\"\"\n", - " return (self.terrain_map[y][x]\n", + " return (self.terrain_map[x,y]\n", " for (x,y) in\n", " product((x,x+1),(y,y+1)))\n", " def select_tile(self, corners):\n", diff --git a/Wangview/Display.py b/Wangview/Display.py index 54905b1..8e62664 100644 --- a/Wangview/Display.py +++ b/Wangview/Display.py @@ -2,13 +2,13 @@ # coding: utf-8 from bearlibterminal import terminal as blt import json -from collections import deque from itertools import product import random from os import path from .Tileset import Tileset from .Hypergraph import Hypergraph from .FPSLimiter import FPSLimiter +from .MapGrid import MapGrid class Display(object): """ @@ -125,9 +125,10 @@ def init_terrain_map(self): """ terrain_iter = self.hypergraph.generate_lines( self.terrain_width, self.terrain_height) - terrain_deque_iter = (deque(line, self.terrain_width) - for line in terrain_iter) - self.terrain_map = deque(terrain_deque_iter, self.terrain_height) + self.terrain_map = MapGrid(self.terrain_width, self.terrain_height) + for line in terrain_iter: + line_list = list(line) + self.terrain_map.add_line(line, True, False) def init_tile_map(self): """ Generates a grid of unicode codepoints specifying graphical tiles, @@ -138,14 +139,15 @@ def init_tile_map(self): tile_iter = ((self.select_tile(self.get_tile_corners(x,y)) for x in range(self.tile_width)) for y in range(self.tile_height)) - tile_deque_iter = (deque(line, self.tile_width) for line in tile_iter) - self.tile_map = deque(tile_deque_iter, self.tile_height) + self.tile_map = MapGrid(self.tile_width, self.tile_height) + for line in tile_iter: + self.tile_map.add_line(line, True, False) def get_tile_corners(self, x, y): """ Returns a generator which iterates over the terrain values in positions [(x,y), (x,y+1), (x+1, y), (x+1, y+1)] """ - return (self.terrain_map[y][x] + return (self.terrain_map[x,y] for (x,y) in product((x,x+1),(y,y+1))) def select_tile(self, corners): diff --git a/Wangview/Hypergraph.py b/Wangview/Hypergraph.py index 72c9a83..f5c64af 100644 --- a/Wangview/Hypergraph.py +++ b/Wangview/Hypergraph.py @@ -1,14 +1,8 @@ # coding: utf-8 - -# In[ ]: - from functools import reduce import random - -# In[ ]: - class Hypergraph(object): """ Stores data specifying which terrains can be present in a single tile, @@ -120,9 +114,6 @@ def generate_lines(self, width, height): line = self.generate_line(width, line) yield line - -# In[ ]: - if __name__ == '__main__': # A quick test that the class is working correctly th = Hypergraph({'a':[['a','b'],['c','a']], @@ -130,4 +121,3 @@ def generate_lines(self, width, height): 'c':[['b','c'],['c','a']]}) for line in th.generate_lines(10,10): print(''.join(line)), - diff --git a/Wangview/MapGrid.ipynb b/Wangview/MapGrid.ipynb new file mode 100644 index 0000000..dfe9f31 --- /dev/null +++ b/Wangview/MapGrid.ipynb @@ -0,0 +1,100 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from collections import deque" + ] + }, + { + "cell_type": "code", + "execution_count": 290, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class MapGrid(object):\n", + " \"\"\"Stores terrain and tile information for display.\"\"\"\n", + " def __init__(self, *size):\n", + " self._columns, self._rows = (0, 0)\n", + " self._columns_max, self._rows_max = size\n", + " self._grid = deque(maxlen=self._rows_max)\n", + " def full(self):\n", + " \"\"\"Returns True if the MapGrid is at full capacity, False otherwise.\"\"\"\n", + " return self._rows == self._rows_max and self._columns == self._columns_max\n", + " def _make_row(self, sequence):\n", + " \"\"\"Converts a sequence into a row object compatible with the MapGrid.\"\"\"\n", + " return deque(sequence, maxlen=self._columns_max)\n", + " def iter_border(self, horizontal, top_left):\n", + " \"\"\"Returns an iterator which yields values from one of the MapGrid's four borders.\"\"\"\n", + " if horizontal:\n", + " return self.row(0 if top_left else self._rows-1)\n", + " else:\n", + " return self.column(0 if top_left else self._columns-1)\n", + " def add_line(self, line, horizontal, top_left):\n", + " \"\"\"Appends a row or column to the MapGrid, rotating or growing as necessary.\"\"\"\n", + " line = list(line)\n", + " append = deque.appendleft if top_left else deque.append\n", + " if horizontal:\n", + " if self._columns == 0:\n", + " assert len(line) <= self._columns_max, \"first row too long\"\n", + " self._columns = len(line)\n", + " else:\n", + " assert len(line) == self._columns, \"wrong length row added\"\n", + " self._rows = min(self._rows+1, self._rows_max)\n", + " append(self._grid, self._make_row(line))\n", + " else:\n", + " if self._rows == 0:\n", + " assert len(line) <= self._rows_max, \"first column too long\"\n", + " self._rows = len(line)\n", + " for _ in range(len(line)):\n", + " self._grid.append(self._make_row([]))\n", + " else:\n", + " assert len(line) == self._rows, \"wrong length column added\"\n", + " self._columns = min(self._columns+1, self._columns_max)\n", + " for terrain, row in zip(line, self._grid):\n", + " append(row,terrain)\n", + " def row(self, y):\n", + " return iter(self._grid[y])\n", + " def column(self, x):\n", + " return (line[x] for line in self._grid)\n", + " def __iter__(self):\n", + " for row in self._grid:\n", + " yield iter(row)\n", + " def __getitem__(self, xy):\n", + " x,y = xy\n", + " return self._grid[y][x]\n", + " def __str__(self):\n", + " \"\"\"Returns a string representation of the MapGrid's contents.\"\"\"\n", + " return '\\n'.join(' '.join(map(str,row)) for row in self._grid)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:python36]", + "language": "python", + "name": "conda-env-python36-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Wangview/MapGrid.py b/Wangview/MapGrid.py new file mode 100644 index 0000000..fa6a950 --- /dev/null +++ b/Wangview/MapGrid.py @@ -0,0 +1,58 @@ + +# coding: utf-8 +from collections import deque + +class MapGrid(object): + """Stores terrain and tile information for display.""" + def __init__(self, *size): + self._columns, self._rows = (0, 0) + self._columns_max, self._rows_max = size + self._grid = deque(maxlen=self._rows_max) + def full(self): + """Returns True if the MapGrid is at full capacity, False otherwise.""" + return self._rows == self._rows_max and self._columns == self._columns_max + def _make_row(self, sequence): + """Converts a sequence into a row object compatible with the MapGrid.""" + return deque(sequence, maxlen=self._columns_max) + def iter_border(self, horizontal, top_left): + """Returns an iterator which yields values from one of the MapGrid's four borders.""" + if horizontal: + return self.row(0 if top_left else self._rows-1) + else: + return self.column(0 if top_left else self._columns-1) + def add_line(self, line, horizontal, top_left): + """Appends a row or column to the MapGrid, rotating or growing as necessary.""" + line = list(line) + append = deque.appendleft if top_left else deque.append + if horizontal: + if self._columns == 0: + assert len(line) <= self._columns_max, "first row too long" + self._columns = len(line) + else: + assert len(line) == self._columns, "wrong length row added" + self._rows = min(self._rows+1, self._rows_max) + append(self._grid, self._make_row(line)) + else: + if self._rows == 0: + assert len(line) <= self._rows_max, "first column too long" + self._rows = len(line) + for _ in range(len(line)): + self._grid.append(self._make_row([])) + else: + assert len(line) == self._rows, "wrong length column added" + self._columns = min(self._columns+1, self._columns_max) + for terrain, row in zip(line, self._grid): + append(row,terrain) + def row(self, y): + return iter(self._grid[y]) + def column(self, x): + return (line[x] for line in self._grid) + def __iter__(self): + for row in self._grid: + yield iter(row) + def __getitem__(self, xy): + x,y = xy + return self._grid[y][x] + def __str__(self): + """Returns a string representation of the MapGrid's contents.""" + return '\n'.join(' '.join(map(str,row)) for row in self._grid) diff --git a/WangviewIPython.ipynb b/WangviewIPython.ipynb index d69788b..c2703e4 100644 --- a/WangviewIPython.ipynb +++ b/WangviewIPython.ipynb @@ -11,18 +11,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 42, "metadata": { "collapsed": true }, @@ -33,7 +42,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 43, "metadata": { "collapsed": false }, @@ -44,13 +53,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 45, "metadata": { "collapsed": false }, "outputs": [], "source": [ - "w = Display('../Wangscape/Wangscape/example3/output')\n", + "w = Display('../Wangscape/doc/examples/example3/output')\n", "w.run()" ] } diff --git a/WangviewIPython.py b/WangviewIPython.py index 58cddf1..8592af3 100644 --- a/WangviewIPython.py +++ b/WangviewIPython.py @@ -1,27 +1,13 @@ # coding: utf-8 - # Use this notebook to run Wangview while developing with IPython. # # The `autoreload` extension will allow you to test changes to the code without restarting the kernel. - -# In[ ]: - get_ipython().magic('load_ext autoreload') - -# In[ ]: - get_ipython().magic('autoreload 2') - -# In[ ]: - from Wangview.Display import Display - -# In[ ]: - -w = Display('../Wangscape/Wangscape/example3/output') +w = Display('../Wangscape/doc/examples/example3/output') w.run() - From ae73c7821cfd9293ac652a3b34304dcf261935db Mon Sep 17 00:00:00 2001 From: Serin Delaunay Date: Wed, 14 Jun 2017 21:22:52 +0100 Subject: [PATCH 02/10] Add MapGrid docstrings --- Wangview/MapGrid.ipynb | 4 ++++ Wangview/MapGrid.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Wangview/MapGrid.ipynb b/Wangview/MapGrid.ipynb index dfe9f31..7302cff 100644 --- a/Wangview/MapGrid.ipynb +++ b/Wangview/MapGrid.ipynb @@ -61,13 +61,17 @@ " for terrain, row in zip(line, self._grid):\n", " append(row,terrain)\n", " def row(self, y):\n", + " \"\"\"Returns an iterator for the specified row.\"\"\"\n", " return iter(self._grid[y])\n", " def column(self, x):\n", + " \"\"\"Returns an iterator for the specified column.\"\"\"\n", " return (line[x] for line in self._grid)\n", " def __iter__(self):\n", + " \"\"\"Returns an iterator of row iterators.\"\"\"\n", " for row in self._grid:\n", " yield iter(row)\n", " def __getitem__(self, xy):\n", + " \"\"\"Returns the value at the given coordinates.\"\"\"\n", " x,y = xy\n", " return self._grid[y][x]\n", " def __str__(self):\n", diff --git a/Wangview/MapGrid.py b/Wangview/MapGrid.py index fa6a950..c498d5e 100644 --- a/Wangview/MapGrid.py +++ b/Wangview/MapGrid.py @@ -44,13 +44,17 @@ def add_line(self, line, horizontal, top_left): for terrain, row in zip(line, self._grid): append(row,terrain) def row(self, y): + """Returns an iterator for the specified row.""" return iter(self._grid[y]) def column(self, x): + """Returns an iterator for the specified column.""" return (line[x] for line in self._grid) def __iter__(self): + """Returns an iterator of row iterators.""" for row in self._grid: yield iter(row) def __getitem__(self, xy): + """Returns the value at the given coordinates.""" x,y = xy return self._grid[y][x] def __str__(self): From 59928c13c3d3747ac615b36f15dd1f027fa4e946 Mon Sep 17 00:00:00 2001 From: Serin Delaunay Date: Sun, 27 Aug 2017 11:57:14 +0100 Subject: [PATCH 03/10] Use argparse --- Wangview.ipynb | 19 ++++++++++++++----- Wangview.py | 22 ++++++++++++---------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/Wangview.ipynb b/Wangview.ipynb index e64f40a..8c1afcb 100644 --- a/Wangview.ipynb +++ b/Wangview.ipynb @@ -4,12 +4,13 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": false }, "outputs": [], "source": [ "from Wangview.Display import Display\n", - "import sys" + "import sys\n", + "import argparse" ] }, { @@ -21,12 +22,20 @@ "outputs": [], "source": [ "if __name__ == '__main__':\n", - " import sys\n", + " parser = argparse.ArgumentParser(description=\"Render random landscapes using Wangscape tiles\")\n", + " parser.add_argument(\"path\", type=str, default=\".\", help=\"Path to the folder containing the tileset and metadata\")\n", + " parser.add_argument(\"--tile-groups\", type=str, default=\"tile_groups.json\", help=\"Name of the tile groups JSON file\")\n", + " parser.add_argument(\"--terrain-hypergraph\", type=str, default=\"terrain_hypergraph.json\", help=\"Name of the terrain hypergraph JSON file\")\n", + " parser.add_argument(\"--tileset-data\", type=str, default=\"tilesets.json\", help=\"Name of the tileset data JSON file\")\n", + " # parser.add_argument(\"--fps\", type=int, default=30, help=\"Maximum frames per second\")\n", + " # parser.add_argument(\"--map\", type=str, default=None, help=\"Name of a JSON file with a fixed map\")\n", + " args = parser.parse_args()\n", + " print(args)\n", " try:\n", - " w = Display(*sys.argv[1:])\n", + " w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data)\n", " w.run()\n", " except (IndexError, FileNotFoundError):\n", - " print('Usage: Wangview.py [path [tile_groups.json [terrain_hypergraph.json [tileset_data.json]]]]\\n')\n", + " parser.print_help()\n", " raise" ] } diff --git a/Wangview.py b/Wangview.py index 825502f..8e76961 100644 --- a/Wangview.py +++ b/Wangview.py @@ -1,20 +1,22 @@ # coding: utf-8 - -# In[ ]: - from Wangview.Display import Display import sys - - -# In[ ]: +import argparse if __name__ == '__main__': - import sys + parser = argparse.ArgumentParser(description="Render random landscapes using Wangscape tiles") + parser.add_argument("path", type=str, default=".", help="Path to the folder containing the tileset and metadata") + parser.add_argument("--tile-groups", type=str, default="tile_groups.json", help="Name of the tile groups JSON file") + parser.add_argument("--terrain-hypergraph", type=str, default="terrain_hypergraph.json", help="Name of the terrain hypergraph JSON file") + parser.add_argument("--tileset-data", type=str, default="tilesets.json", help="Name of the tileset data JSON file") + # parser.add_argument("--fps", type=int, default=30, help="Maximum frames per second") + # parser.add_argument("--map", type=str, default=None, help="Name of a JSON file with a fixed map") + args = parser.parse_args() + print(args) try: - w = Display(*sys.argv[1:]) + w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data) w.run() except (IndexError, FileNotFoundError): - print('Usage: Wangview.py [path [tile_groups.json [terrain_hypergraph.json [tileset_data.json]]]]\n') + parser.print_help() raise - From 0407d554f928349330272bdb18ec83851599e550 Mon Sep 17 00:00:00 2001 From: Serin Delaunay Date: Sun, 27 Aug 2017 12:05:54 +0100 Subject: [PATCH 04/10] Use FPS argument --- Wangview.ipynb | 4 ++-- Wangview.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Wangview.ipynb b/Wangview.ipynb index 8c1afcb..bb4019f 100644 --- a/Wangview.ipynb +++ b/Wangview.ipynb @@ -27,12 +27,12 @@ " parser.add_argument(\"--tile-groups\", type=str, default=\"tile_groups.json\", help=\"Name of the tile groups JSON file\")\n", " parser.add_argument(\"--terrain-hypergraph\", type=str, default=\"terrain_hypergraph.json\", help=\"Name of the terrain hypergraph JSON file\")\n", " parser.add_argument(\"--tileset-data\", type=str, default=\"tilesets.json\", help=\"Name of the tileset data JSON file\")\n", - " # parser.add_argument(\"--fps\", type=int, default=30, help=\"Maximum frames per second\")\n", + " parser.add_argument(\"--fps\", type=int, default=30, help=\"Maximum frames per second\")\n", " # parser.add_argument(\"--map\", type=str, default=None, help=\"Name of a JSON file with a fixed map\")\n", " args = parser.parse_args()\n", " print(args)\n", " try:\n", - " w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data)\n", + " w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps)\n", " w.run()\n", " except (IndexError, FileNotFoundError):\n", " parser.print_help()\n", diff --git a/Wangview.py b/Wangview.py index 8e76961..a7e3935 100644 --- a/Wangview.py +++ b/Wangview.py @@ -10,12 +10,12 @@ parser.add_argument("--tile-groups", type=str, default="tile_groups.json", help="Name of the tile groups JSON file") parser.add_argument("--terrain-hypergraph", type=str, default="terrain_hypergraph.json", help="Name of the terrain hypergraph JSON file") parser.add_argument("--tileset-data", type=str, default="tilesets.json", help="Name of the tileset data JSON file") - # parser.add_argument("--fps", type=int, default=30, help="Maximum frames per second") + parser.add_argument("--fps", type=int, default=30, help="Maximum frames per second") # parser.add_argument("--map", type=str, default=None, help="Name of a JSON file with a fixed map") args = parser.parse_args() print(args) try: - w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data) + w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps) w.run() except (IndexError, FileNotFoundError): parser.print_help() From d9a4173ed89e5c5ae757c9b1e7c115ea308335be Mon Sep 17 00:00:00 2001 From: Serin Delaunay Date: Sun, 27 Aug 2017 13:31:19 +0100 Subject: [PATCH 05/10] Get map size from command line --- Wangview.ipynb | 3 ++- Wangview.py | 3 ++- Wangview/Display.ipynb | 12 +++++++----- Wangview/Display.py | 12 +++++++----- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Wangview.ipynb b/Wangview.ipynb index bb4019f..2872f81 100644 --- a/Wangview.ipynb +++ b/Wangview.ipynb @@ -28,11 +28,12 @@ " parser.add_argument(\"--terrain-hypergraph\", type=str, default=\"terrain_hypergraph.json\", help=\"Name of the terrain hypergraph JSON file\")\n", " parser.add_argument(\"--tileset-data\", type=str, default=\"tilesets.json\", help=\"Name of the tileset data JSON file\")\n", " parser.add_argument(\"--fps\", type=int, default=30, help=\"Maximum frames per second\")\n", + " parser.add_argument(\"--size\", type=int, nargs=2, metavar=(\"x\",\"y\"), default=(30,20), help=\"Size of the map display\")\n", " # parser.add_argument(\"--map\", type=str, default=None, help=\"Name of a JSON file with a fixed map\")\n", " args = parser.parse_args()\n", " print(args)\n", " try:\n", - " w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps)\n", + " w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps, args.size)\n", " w.run()\n", " except (IndexError, FileNotFoundError):\n", " parser.print_help()\n", diff --git a/Wangview.py b/Wangview.py index a7e3935..a8653a4 100644 --- a/Wangview.py +++ b/Wangview.py @@ -11,11 +11,12 @@ parser.add_argument("--terrain-hypergraph", type=str, default="terrain_hypergraph.json", help="Name of the terrain hypergraph JSON file") parser.add_argument("--tileset-data", type=str, default="tilesets.json", help="Name of the tileset data JSON file") parser.add_argument("--fps", type=int, default=30, help="Maximum frames per second") + parser.add_argument("--size", type=int, nargs=2, metavar=("x","y"), default=(30,20), help="Size of the map display") # parser.add_argument("--map", type=str, default=None, help="Name of a JSON file with a fixed map") args = parser.parse_args() print(args) try: - w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps) + w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps, args.size) w.run() except (IndexError, FileNotFoundError): parser.print_help() diff --git a/Wangview/Display.ipynb b/Wangview/Display.ipynb index 9c5d98c..d3f30f5 100644 --- a/Wangview/Display.ipynb +++ b/Wangview/Display.ipynb @@ -38,11 +38,13 @@ " fn_tile_groups='tile_groups.json',\n", " fn_terrain_hypergraph='terrain_hypergraph.json',\n", " fn_tileset_data='tilesets.json',\n", - " fps=30):\n", + " fps=30,\n", + " size=(30,20)):\n", + " # fixed_map=None):\n", " # Initialise file path and metadata\n", " self.rel_path = rel_path\n", " with open(path.join(rel_path, fn_tileset_data),'r') as f:\n", - " self.init_tilesets(json.load(f))\n", + " self.init_tilesets(json.load(f), size)\n", " with open(path.join(rel_path,fn_tile_groups),'r') as f:\n", " self.init_tile_groups(json.load(f))\n", " with open(path.join(rel_path, fn_terrain_hypergraph),'r') as f:\n", @@ -90,7 +92,7 @@ " \"\"\"\n", " self.tile_groups = {tuple(k.split('.')):self.simplify_tile_group(v)\n", " for (k,v) in raw_groups.items()}\n", - " def init_tilesets(self, raw_tileset_data):\n", + " def init_tilesets(self, raw_tileset_data, size):\n", " \"\"\"\n", " Converts the data in the `tilesets.json` metadata file\n", " into a format suitable for Wangview,\n", @@ -106,8 +108,8 @@ " self.resolution = resolution\n", " # Initialise bearlibterminal\n", " blt.open()\n", - " config_string = \"window: size=30x20, cellsize={0}x{1}, title='Wangview'\".format(\n", - " self.resolution[0], self.resolution[1])\n", + " config_string = \"window: size={0}x{1}, cellsize={2}x{3}, title='Wangview'\".format(\n", + " size[0], size[1], self.resolution[0], self.resolution[1])\n", " blt.set(config_string)\n", " # Start tile unicode blocks in private space\n", " tileset_offset_counter = 0xE000\n", diff --git a/Wangview/Display.py b/Wangview/Display.py index 8e62664..53b938b 100644 --- a/Wangview/Display.py +++ b/Wangview/Display.py @@ -21,11 +21,13 @@ def __init__(self, fn_tile_groups='tile_groups.json', fn_terrain_hypergraph='terrain_hypergraph.json', fn_tileset_data='tilesets.json', - fps=30): + fps=30, + size=(30,20)): + # fixed_map=None): # Initialise file path and metadata self.rel_path = rel_path with open(path.join(rel_path, fn_tileset_data),'r') as f: - self.init_tilesets(json.load(f)) + self.init_tilesets(json.load(f), size) with open(path.join(rel_path,fn_tile_groups),'r') as f: self.init_tile_groups(json.load(f)) with open(path.join(rel_path, fn_terrain_hypergraph),'r') as f: @@ -73,7 +75,7 @@ def init_tile_groups(self, raw_groups): """ self.tile_groups = {tuple(k.split('.')):self.simplify_tile_group(v) for (k,v) in raw_groups.items()} - def init_tilesets(self, raw_tileset_data): + def init_tilesets(self, raw_tileset_data, size): """ Converts the data in the `tilesets.json` metadata file into a format suitable for Wangview, @@ -89,8 +91,8 @@ def init_tilesets(self, raw_tileset_data): self.resolution = resolution # Initialise bearlibterminal blt.open() - config_string = "window: size=30x20, cellsize={0}x{1}, title='Wangview'".format( - self.resolution[0], self.resolution[1]) + config_string = "window: size={0}x{1}, cellsize={2}x{3}, title='Wangview'".format( + size[0], size[1], self.resolution[0], self.resolution[1]) blt.set(config_string) # Start tile unicode blocks in private space tileset_offset_counter = 0xE000 From d0f5412cb5519f377b52abe3927b6d9969764d60 Mon Sep 17 00:00:00 2001 From: Serin Delaunay Date: Sun, 27 Aug 2017 14:27:17 +0100 Subject: [PATCH 06/10] Display fixed map --- Wangview.ipynb | 4 ++-- Wangview.py | 4 ++-- Wangview/Display.ipynb | 33 +++++++++++++++++++++++---------- Wangview/Display.py | 33 +++++++++++++++++++++++---------- 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/Wangview.ipynb b/Wangview.ipynb index 2872f81..1dfa77f 100644 --- a/Wangview.ipynb +++ b/Wangview.ipynb @@ -29,11 +29,11 @@ " parser.add_argument(\"--tileset-data\", type=str, default=\"tilesets.json\", help=\"Name of the tileset data JSON file\")\n", " parser.add_argument(\"--fps\", type=int, default=30, help=\"Maximum frames per second\")\n", " parser.add_argument(\"--size\", type=int, nargs=2, metavar=(\"x\",\"y\"), default=(30,20), help=\"Size of the map display\")\n", - " # parser.add_argument(\"--map\", type=str, default=None, help=\"Name of a JSON file with a fixed map\")\n", + " parser.add_argument(\"--fixed-map\", type=str, default=None, help=\"Name of a JSON file with a fixed map\")\n", " args = parser.parse_args()\n", " print(args)\n", " try:\n", - " w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps, args.size)\n", + " w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps, args.size, args.fixed_map)\n", " w.run()\n", " except (IndexError, FileNotFoundError):\n", " parser.print_help()\n", diff --git a/Wangview.py b/Wangview.py index a8653a4..b02895f 100644 --- a/Wangview.py +++ b/Wangview.py @@ -12,11 +12,11 @@ parser.add_argument("--tileset-data", type=str, default="tilesets.json", help="Name of the tileset data JSON file") parser.add_argument("--fps", type=int, default=30, help="Maximum frames per second") parser.add_argument("--size", type=int, nargs=2, metavar=("x","y"), default=(30,20), help="Size of the map display") - # parser.add_argument("--map", type=str, default=None, help="Name of a JSON file with a fixed map") + parser.add_argument("--fixed-map", type=str, default=None, help="Name of a JSON file with a fixed map") args = parser.parse_args() print(args) try: - w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps, args.size) + w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps, args.size, args.fixed_map) w.run() except (IndexError, FileNotFoundError): parser.print_help() diff --git a/Wangview/Display.ipynb b/Wangview/Display.ipynb index d3f30f5..7d3f953 100644 --- a/Wangview/Display.ipynb +++ b/Wangview/Display.ipynb @@ -39,16 +39,22 @@ " fn_terrain_hypergraph='terrain_hypergraph.json',\n", " fn_tileset_data='tilesets.json',\n", " fps=30,\n", - " size=(30,20)):\n", - " # fixed_map=None):\n", + " size=(30,20),\n", + " fn_fixed_map=None):\n", " # Initialise file path and metadata\n", + " self.size = size\n", " self.rel_path = rel_path\n", " with open(path.join(rel_path, fn_tileset_data),'r') as f:\n", - " self.init_tilesets(json.load(f), size)\n", + " self.init_tilesets(json.load(f))\n", " with open(path.join(rel_path,fn_tile_groups),'r') as f:\n", " self.init_tile_groups(json.load(f))\n", " with open(path.join(rel_path, fn_terrain_hypergraph),'r') as f:\n", " self.hypergraph = Hypergraph(json.load(f))\n", + " if fn_fixed_map is not None:\n", + " with open(path.join(rel_path, fn_fixed_map),'r') as f:\n", + " self.fixed_map = json.load(f)\n", + " else:\n", + " self.fixed_map = None\n", " # Initialise geometry info\n", " self.terminal_width = blt.state(blt.TK_WIDTH)\n", " self.terminal_height = blt.state(blt.TK_HEIGHT)\n", @@ -92,7 +98,7 @@ " \"\"\"\n", " self.tile_groups = {tuple(k.split('.')):self.simplify_tile_group(v)\n", " for (k,v) in raw_groups.items()}\n", - " def init_tilesets(self, raw_tileset_data, size):\n", + " def init_tilesets(self, raw_tileset_data):\n", " \"\"\"\n", " Converts the data in the `tilesets.json` metadata file\n", " into a format suitable for Wangview,\n", @@ -109,7 +115,7 @@ " # Initialise bearlibterminal\n", " blt.open()\n", " config_string = \"window: size={0}x{1}, cellsize={2}x{3}, title='Wangview'\".format(\n", - " size[0], size[1], self.resolution[0], self.resolution[1])\n", + " self.size[0], self.size[1], self.resolution[0], self.resolution[1])\n", " blt.set(config_string)\n", " # Start tile unicode blocks in private space\n", " tileset_offset_counter = 0xE000\n", @@ -141,13 +147,20 @@ " to generate a grid of terrain values,\n", " and formats the result as a deque of deques.\n", " Stores the result in self.terrain_map.\n", + " If fixed_map is defined, a fixed map is used instead.\n", " \"\"\"\n", - " terrain_iter = self.hypergraph.generate_lines(\n", - " self.terrain_width, self.terrain_height)\n", " self.terrain_map = MapGrid(self.terrain_width, self.terrain_height)\n", - " for line in terrain_iter:\n", - " line_list = list(line)\n", - " self.terrain_map.add_line(line, True, False)\n", + " if self.fixed_map is not None:\n", + " assert len(self.fixed_map) == self.terrain_height\n", + " for line in self.fixed_map:\n", + " assert len(line) == self.terrain_width\n", + " self.terrain_map.add_line(line, True, False)\n", + " else:\n", + " terrain_iter = self.hypergraph.generate_lines(\n", + " self.terrain_width, self.terrain_height)\n", + " for line in terrain_iter:\n", + " line_list = list(line)\n", + " self.terrain_map.add_line(line, True, False)\n", " def init_tile_map(self):\n", " \"\"\"\n", " Generates a grid of unicode codepoints specifying graphical tiles,\n", diff --git a/Wangview/Display.py b/Wangview/Display.py index 53b938b..b64a51b 100644 --- a/Wangview/Display.py +++ b/Wangview/Display.py @@ -22,16 +22,22 @@ def __init__(self, fn_terrain_hypergraph='terrain_hypergraph.json', fn_tileset_data='tilesets.json', fps=30, - size=(30,20)): - # fixed_map=None): + size=(30,20), + fn_fixed_map=None): # Initialise file path and metadata + self.size = size self.rel_path = rel_path with open(path.join(rel_path, fn_tileset_data),'r') as f: - self.init_tilesets(json.load(f), size) + self.init_tilesets(json.load(f)) with open(path.join(rel_path,fn_tile_groups),'r') as f: self.init_tile_groups(json.load(f)) with open(path.join(rel_path, fn_terrain_hypergraph),'r') as f: self.hypergraph = Hypergraph(json.load(f)) + if fn_fixed_map is not None: + with open(path.join(rel_path, fn_fixed_map),'r') as f: + self.fixed_map = json.load(f) + else: + self.fixed_map = None # Initialise geometry info self.terminal_width = blt.state(blt.TK_WIDTH) self.terminal_height = blt.state(blt.TK_HEIGHT) @@ -75,7 +81,7 @@ def init_tile_groups(self, raw_groups): """ self.tile_groups = {tuple(k.split('.')):self.simplify_tile_group(v) for (k,v) in raw_groups.items()} - def init_tilesets(self, raw_tileset_data, size): + def init_tilesets(self, raw_tileset_data): """ Converts the data in the `tilesets.json` metadata file into a format suitable for Wangview, @@ -92,7 +98,7 @@ def init_tilesets(self, raw_tileset_data, size): # Initialise bearlibterminal blt.open() config_string = "window: size={0}x{1}, cellsize={2}x{3}, title='Wangview'".format( - size[0], size[1], self.resolution[0], self.resolution[1]) + self.size[0], self.size[1], self.resolution[0], self.resolution[1]) blt.set(config_string) # Start tile unicode blocks in private space tileset_offset_counter = 0xE000 @@ -124,13 +130,20 @@ def init_terrain_map(self): to generate a grid of terrain values, and formats the result as a deque of deques. Stores the result in self.terrain_map. + If fixed_map is defined, a fixed map is used instead. """ - terrain_iter = self.hypergraph.generate_lines( - self.terrain_width, self.terrain_height) self.terrain_map = MapGrid(self.terrain_width, self.terrain_height) - for line in terrain_iter: - line_list = list(line) - self.terrain_map.add_line(line, True, False) + if self.fixed_map is not None: + assert len(self.fixed_map) == self.terrain_height + for line in self.fixed_map: + assert len(line) == self.terrain_width + self.terrain_map.add_line(line, True, False) + else: + terrain_iter = self.hypergraph.generate_lines( + self.terrain_width, self.terrain_height) + for line in terrain_iter: + line_list = list(line) + self.terrain_map.add_line(line, True, False) def init_tile_map(self): """ Generates a grid of unicode codepoints specifying graphical tiles, From abd172a6ff6f2573009bf10e0bebaba2e9cc642e Mon Sep 17 00:00:00 2001 From: Serin Delaunay Date: Tue, 29 Aug 2017 21:06:21 +0100 Subject: [PATCH 07/10] Refactor and add file output * Extract TileGroups class * Extract TilesetInformation class * Extract display as OutputBLT * Add OutputPillow as alternative * Alter initialisation of some classes --- Wangview.ipynb | 30 ++-- Wangview.py | 30 ++-- Wangview/Display.ipynb | 226 +++++++++++++++++++++++++++--- Wangview/Display.py | 215 +++++++++++++++++++++++++--- Wangview/Hypergraph.ipynb | 34 ++++- Wangview/Hypergraph.py | 32 ++++- Wangview/MapGrid.ipynb | 4 +- Wangview/MapGrid.py | 4 +- Wangview/OutputBLT.ipynb | 81 +++++++++++ Wangview/OutputBLT.py | 39 ++++++ Wangview/OutputPillow.ipynb | 81 +++++++++++ Wangview/OutputPillow.py | 39 ++++++ Wangview/TileGroups.ipynb | 90 ++++++++++++ Wangview/TileGroups.py | 48 +++++++ Wangview/Tileset.py | 7 - Wangview/TilesetInformation.ipynb | 112 +++++++++++++++ Wangview/TilesetInformation.py | 70 +++++++++ 17 files changed, 1069 insertions(+), 73 deletions(-) create mode 100644 Wangview/OutputBLT.ipynb create mode 100644 Wangview/OutputBLT.py create mode 100644 Wangview/OutputPillow.ipynb create mode 100644 Wangview/OutputPillow.py create mode 100644 Wangview/TileGroups.ipynb create mode 100644 Wangview/TileGroups.py create mode 100644 Wangview/TilesetInformation.ipynb create mode 100644 Wangview/TilesetInformation.py diff --git a/Wangview.ipynb b/Wangview.ipynb index 1dfa77f..46249ae 100644 --- a/Wangview.ipynb +++ b/Wangview.ipynb @@ -22,18 +22,28 @@ "outputs": [], "source": [ "if __name__ == '__main__':\n", - " parser = argparse.ArgumentParser(description=\"Render random landscapes using Wangscape tiles\")\n", - " parser.add_argument(\"path\", type=str, default=\".\", help=\"Path to the folder containing the tileset and metadata\")\n", - " parser.add_argument(\"--tile-groups\", type=str, default=\"tile_groups.json\", help=\"Name of the tile groups JSON file\")\n", - " parser.add_argument(\"--terrain-hypergraph\", type=str, default=\"terrain_hypergraph.json\", help=\"Name of the terrain hypergraph JSON file\")\n", - " parser.add_argument(\"--tileset-data\", type=str, default=\"tilesets.json\", help=\"Name of the tileset data JSON file\")\n", - " parser.add_argument(\"--fps\", type=int, default=30, help=\"Maximum frames per second\")\n", - " parser.add_argument(\"--size\", type=int, nargs=2, metavar=(\"x\",\"y\"), default=(30,20), help=\"Size of the map display\")\n", - " parser.add_argument(\"--fixed-map\", type=str, default=None, help=\"Name of a JSON file with a fixed map\")\n", + " parser = argparse.ArgumentParser(description='Render random landscapes using Wangscape tiles')\n", + " parser.add_argument('path', type=str, default='.', help='Path to the folder containing the tileset and metadata')\n", + " parser.add_argument('--tile-groups', type=str, default='tile_groups.json', help='Name of the tile groups JSON file')\n", + " parser.add_argument('--terrain-hypergraph', type=str, default='terrain_hypergraph.json', help='Name of the terrain hypergraph JSON file')\n", + " parser.add_argument('--tileset-data', type=str, default='tilesets.json', help='Name of the tileset data JSON file')\n", + " parser.add_argument('--fps', type=int, default=30, help='Maximum frames per second')\n", + " map_parser = parser.add_mutually_exclusive_group()\n", + " map_parser.add_argument('--size', type=int, dest='map_mode', nargs=2, metavar=('x','y'), help='Size of the map display')\n", + " map_parser.add_argument('--fixed-map', type=str, dest='map_mode', help='Name of a JSON file with a fixed map')\n", + " output_parser = parser.add_argument_group()\n", + " output_parser.add_argument('--display', dest='output_mode', action='append_const', const=True, help='Display maps in a GUI interface')\n", + " output_parser.add_argument('--output', dest='output_mode', action='append', type=str, help='Write an image to file')\n", + " parser.set_defaults(map_mode=[30,20], output_mode=[])\n", " args = parser.parse_args()\n", - " print(args)\n", " try:\n", - " w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps, args.size, args.fixed_map)\n", + " w = Display(rel_path = args.path,\n", + " fn_tile_groups = args.tile_groups,\n", + " fn_terrain_hypergraph = args.terrain_hypergraph,\n", + " fn_tileset_data = args.tileset_data,\n", + " fps = args.fps,\n", + " map_mode = args.map_mode,\n", + " output_mode = args.output_mode)\n", " w.run()\n", " except (IndexError, FileNotFoundError):\n", " parser.print_help()\n", diff --git a/Wangview.py b/Wangview.py index b02895f..9589f85 100644 --- a/Wangview.py +++ b/Wangview.py @@ -5,18 +5,28 @@ import argparse if __name__ == '__main__': - parser = argparse.ArgumentParser(description="Render random landscapes using Wangscape tiles") - parser.add_argument("path", type=str, default=".", help="Path to the folder containing the tileset and metadata") - parser.add_argument("--tile-groups", type=str, default="tile_groups.json", help="Name of the tile groups JSON file") - parser.add_argument("--terrain-hypergraph", type=str, default="terrain_hypergraph.json", help="Name of the terrain hypergraph JSON file") - parser.add_argument("--tileset-data", type=str, default="tilesets.json", help="Name of the tileset data JSON file") - parser.add_argument("--fps", type=int, default=30, help="Maximum frames per second") - parser.add_argument("--size", type=int, nargs=2, metavar=("x","y"), default=(30,20), help="Size of the map display") - parser.add_argument("--fixed-map", type=str, default=None, help="Name of a JSON file with a fixed map") + parser = argparse.ArgumentParser(description='Render random landscapes using Wangscape tiles') + parser.add_argument('path', type=str, default='.', help='Path to the folder containing the tileset and metadata') + parser.add_argument('--tile-groups', type=str, default='tile_groups.json', help='Name of the tile groups JSON file') + parser.add_argument('--terrain-hypergraph', type=str, default='terrain_hypergraph.json', help='Name of the terrain hypergraph JSON file') + parser.add_argument('--tileset-data', type=str, default='tilesets.json', help='Name of the tileset data JSON file') + parser.add_argument('--fps', type=int, default=30, help='Maximum frames per second') + map_parser = parser.add_mutually_exclusive_group() + map_parser.add_argument('--size', type=int, dest='map_mode', nargs=2, metavar=('x','y'), help='Size of the map display') + map_parser.add_argument('--fixed-map', type=str, dest='map_mode', help='Name of a JSON file with a fixed map') + output_parser = parser.add_argument_group() + output_parser.add_argument('--display', dest='output_mode', action='append_const', const=True, help='Display maps in a GUI interface') + output_parser.add_argument('--output', dest='output_mode', action='append', type=str, help='Write an image to file') + parser.set_defaults(map_mode=[30,20], output_mode=[]) args = parser.parse_args() - print(args) try: - w = Display(args.path, args.tile_groups, args.terrain_hypergraph, args.tileset_data, args.fps, args.size, args.fixed_map) + w = Display(rel_path = args.path, + fn_tile_groups = args.tile_groups, + fn_terrain_hypergraph = args.terrain_hypergraph, + fn_tileset_data = args.tileset_data, + fps = args.fps, + map_mode = args.map_mode, + output_mode = args.output_mode) w.run() except (IndexError, FileNotFoundError): parser.print_help() diff --git a/Wangview/Display.ipynb b/Wangview/Display.ipynb index 7d3f953..09566ff 100644 --- a/Wangview/Display.ipynb +++ b/Wangview/Display.ipynb @@ -11,12 +11,12 @@ "from bearlibterminal import terminal as blt\n", "import json\n", "from itertools import product\n", - "import random\n", "from os import path\n", - "from .Tileset import Tileset\n", "from .Hypergraph import Hypergraph\n", "from .FPSLimiter import FPSLimiter\n", - "from .MapGrid import MapGrid" + "from .MapGrid import MapGrid\n", + "from .TilesetInformation import TilesetInformation\n", + "from .TileGroups import TileGroups" ] }, { @@ -31,6 +31,189 @@ " \"\"\"\n", " Stores Wangscape output metadata in a suitable format,\n", " stores terrain and tile grids,\n", + " and interfaces with bearlibterminal to draw a scene,\n", + " or with pillow to write one to file.\n", + " \"\"\"\n", + " def __init__(self,\n", + " rel_path='.',\n", + " fn_tile_groups='tile_groups.json',\n", + " fn_terrain_hypergraph='terrain_hypergraph.json',\n", + " fn_tileset_data='tilesets.json',\n", + " fps=30,\n", + " map_mode=[30,20],\n", + " output_mode=[]):\n", + " # Initialise base path\n", + " self.rel_path = rel_path\n", + " # Read map size or fixed map and prepare storage\n", + " self.init_map(map_mode)\n", + " self.init_output(output_mode)\n", + " self.init_tilesets(path.join(rel_path, fn_tileset_data))\n", + " self.tile_groups = TileGroups.from_file(self.tilesets,\n", + " path.join(rel_path, fn_tile_groups))\n", + " self.hypergraph = Hypergraph.from_file(path.join(rel_path, fn_terrain_hypergraph))\n", + " self.update_map()\n", + " # Throttle framerate\n", + " self.fps_limiter = FPSLimiter(fps)\n", + "\n", + " def init_map(self, map_mode):\n", + " if type(map_mode) is str:\n", + " # Initialise fixed map and use its size\n", + " self.use_fixed_map = True\n", + " fn_fixed_map = map_mode\n", + " self.init_fixed_map(fn_fixed_map)\n", + " else:\n", + " # Use random maps of the given size\n", + " self.use_fixed_map = False\n", + " size = tuple(map_mode)\n", + " self.init_sizes(size)\n", + " self.terrain_map = MapGrid(self.terrain_map_width, self.terrain_map_height)\n", + " self.tile_map = MapGrid(self.tile_map_width, self.tile_map_height)\n", + " \n", + " def init_fixed_map(self, fn_fixed_map):\n", + " with open(path.join(self.rel_path, fn_fixed_map),'r') as f:\n", + " fixed_map_data = json.load(f)\n", + " self.fixed_map = fixed_map_data[\"map\"]\n", + " self.init_sizes(fixed_map_data[\"size\"])\n", + " \n", + " def init_sizes(self, size):\n", + " # The size of the scene in tiles\n", + " self.display_width = size[0]\n", + " self.display_height = size[1]\n", + " # Tiles are not aligned with display boundaries,\n", + " # so one extra row and column is required\n", + " self.tile_map_width = self.display_width + 1\n", + " self.tile_map_height = self.display_height + 1\n", + " # Terrain values are specified in the corners of the graphical tiles,\n", + " # so another extra row and column is required\n", + " self.terrain_map_width = self.tile_map_width + 1\n", + " self.terrain_map_height = self.tile_map_height + 1\n", + " \n", + " def add_blt_output(self):\n", + " from .OutputBLT import OutputBLT\n", + " self.outputs.append(OutputBLT())\n", + " self.use_blt = True\n", + " \n", + " def add_pillow_output(self, filename):\n", + " from .OutputPillow import OutputPillow\n", + " self.outputs.append(OutputPillow(filename))\n", + " self.use_pillow = True\n", + " \n", + " def init_output(self, output_mode):\n", + " self.outputs = []\n", + " for item in output_mode:\n", + " if item is True:\n", + " self.add_blt_output()\n", + " else:\n", + " self.add_pillow_output(item)\n", + " if self.outputs == []:\n", + " self.add_blt_output()\n", + " for output in self.outputs:\n", + " output.set_size(self.display_width, self.display_height)\n", + " \n", + " def init_tilesets(self, filename):\n", + " \"\"\"\n", + " Converts the data in the `tilesets.json` metadata file\n", + " into a format suitable for Wangview,\n", + " stores it in self.tilesets,\n", + " and loads tilesets into bearlibterminal.\n", + " \"\"\"\n", + " self.tilesets = TilesetInformation(self.outputs)\n", + " self.tilesets.set_rel_path(self.rel_path)\n", + " self.tilesets.load_tilesets(filename)\n", + " \n", + " def update_map(self):\n", + " if self.use_fixed_map:\n", + " self.terrain_map.empty()\n", + " for line in self.fixed_map:\n", + " self.terrain_map.add_line(line, True, False)\n", + " else:\n", + " self.generate_terrain_map()\n", + " self.generate_tile_map()\n", + " def generate_terrain_map(self):\n", + " self.terrain_map.empty()\n", + " for line in self.hypergraph.generate_lines(self.terrain_map_width,\n", + " self.terrain_map_height):\n", + " self.terrain_map.add_line(line, True, False)\n", + " \n", + " def generate_tile_map(self):\n", + " tile_iter = ((self.tile_groups.select_tile(self.get_tile_corners(x,y))\n", + " for x in range(self.tile_map_width))\n", + " for y in range(self.tile_map_height))\n", + " self.tile_map.empty()\n", + " for line in tile_iter:\n", + " self.tile_map.add_line(line, True, False)\n", + " \n", + " def get_tile_corners(self, x, y):\n", + " \"\"\"\n", + " Returns a generator which iterates over the terrain values in positions\n", + " [(x,y), (x,y+1), (x+1, y), (x+1, y+1)]\n", + " \"\"\"\n", + " return (self.terrain_map[x,y]\n", + " for (x,y) in\n", + " product((x,x+1),(y,y+1)))\n", + " \n", + " def draw_iter(self):\n", + " \"\"\"Yields cell coordinates, offset, and character for each tile to be drawn\"\"\"\n", + " for y, line in enumerate(self.tile_map):\n", + " # Corner Wang tiles are offset by a quarter of a tile in each dimension.\n", + " # Odd resolutions have the pixel at (0,0) moved by (x//2, y//2) in output tiles,\n", + " # So this reverse translation is correct.\n", + " dy = -(self.tilesets.tile_height//2)\n", + " if y == self.tile_map_height-1:\n", + " # The terminal ignores characters put outside its range,\n", + " # so one row must be drawn using composition\n", + " y -= 1\n", + " dy += self.tilesets.tile_height\n", + " for x, c in enumerate(line):\n", + " dx = -(self.tilesets.tile_width//2)\n", + " if x == self.tile_map_width-1:\n", + " # One column must also be drawn using composition\n", + " x -= 1\n", + " dx += self.tilesets.tile_width\n", + " yield (x,y,dx,dy,c)\n", + " \n", + " def draw(self):\n", + " \"\"\"\n", + " Draws every tile to the outputs.\n", + " See also: `draw_iter.`\n", + " \"\"\"\n", + " for output in self.outputs:\n", + " output.clear()\n", + " for draw_args in self.draw_iter():\n", + " for output in self.outputs:\n", + " output.put_ext(*draw_args)\n", + " for output in self.outputs:\n", + " output.refresh()\n", + " def run(self):\n", + " \"\"\"\n", + " Draws the scene to the terminal and refreshes repeatedly.\n", + " Quits on pressing Esc or closing the window.\n", + " Creates a new scene on pressing Space.\n", + " \"\"\"\n", + " while True:\n", + " self.fps_limiter.wait()\n", + " self.draw()\n", + " signals = [output.signal() for output in self.outputs]\n", + " if all(signal == 'stop' for signal in signals):\n", + " break\n", + " if 'next' in signals:\n", + " self.update_map()\n", + " for output in self.outputs:\n", + " output.close()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class NotDisplay(object):\n", + " \"\"\"\n", + " Stores Wangscape output metadata in a suitable format,\n", + " stores terrain and tile grids,\n", " and interfaces with bearlibterminal to draw a scene.\n", " \"\"\"\n", " def __init__(self,\n", @@ -39,22 +222,30 @@ " fn_terrain_hypergraph='terrain_hypergraph.json',\n", " fn_tileset_data='tilesets.json',\n", " fps=30,\n", - " size=(30,20),\n", - " fn_fixed_map=None):\n", - " # Initialise file path and metadata\n", - " self.size = size\n", + " map_mode=[30,20],\n", + " output_mode=[]):\n", + " # Initialise base path\n", " self.rel_path = rel_path\n", + " # Initialise fixed map and use its size, if it exists\n", + " if type(map_mode) is str:\n", + " fn_fixed_map = map_mode\n", + " self.init_fixed_map(fn_fixed_map)\n", + " else:\n", + " size = tuple(map_mode)\n", + " self.init_sizes(size)\n", " with open(path.join(rel_path, fn_tileset_data),'r') as f:\n", " self.init_tilesets(json.load(f))\n", " with open(path.join(rel_path,fn_tile_groups),'r') as f:\n", " self.init_tile_groups(json.load(f))\n", " with open(path.join(rel_path, fn_terrain_hypergraph),'r') as f:\n", " self.hypergraph = Hypergraph(json.load(f))\n", - " if fn_fixed_map is not None:\n", - " with open(path.join(rel_path, fn_fixed_map),'r') as f:\n", - " self.fixed_map = json.load(f)\n", - " else:\n", - " self.fixed_map = None\n", + " # Select terrain values\n", + " self.init_terrain_map()\n", + " # Select tile values based on terrain values\n", + " self.init_tile_map()\n", + " # Throttle framerate\n", + " self.fps_limiter = FPSLimiter(fps)\n", + " def init_sizes(self, size):\n", " # Initialise geometry info\n", " self.terminal_width = blt.state(blt.TK_WIDTH)\n", " self.terminal_height = blt.state(blt.TK_HEIGHT)\n", @@ -66,12 +257,6 @@ " # so another extra row and column is required\n", " self.terrain_width = self.terminal_width+2\n", " self.terrain_height = self.terminal_height+2\n", - " # Select terrain values\n", - " self.init_terrain_map()\n", - " # Select tile values based on terrain values\n", - " self.init_tile_map()\n", - " # Throttle framerate\n", - " self.fps_limiter = FPSLimiter(fps)\n", " def simplify_tile(self, tile):\n", " \"\"\"\n", " Converts a full specification of a tile's location in a tileset\n", @@ -141,6 +326,11 @@ " blt.set(config_string)\n", " # Insert the next tileset's tiles at the correct unicode codepoint\n", " tileset_offset_counter += rx*ry\n", + " def init_fixed_map(self, fn_fixed_map):\n", + " with open(path.join(rel_path, fn_fixed_map),'r') as f:\n", + " fixed_map_data = json.load(f)\n", + " self.fixed_map = fixed_map[\"map\"]\n", + " self.size = tuple(fixed_map[\"size\"])\n", " def init_terrain_map(self):\n", " \"\"\"\n", " Calls Hypergraph.generate_lines\n", diff --git a/Wangview/Display.py b/Wangview/Display.py index b64a51b..ae94544 100644 --- a/Wangview/Display.py +++ b/Wangview/Display.py @@ -3,14 +3,188 @@ from bearlibterminal import terminal as blt import json from itertools import product -import random from os import path -from .Tileset import Tileset from .Hypergraph import Hypergraph from .FPSLimiter import FPSLimiter from .MapGrid import MapGrid +from .TilesetInformation import TilesetInformation +from .TileGroups import TileGroups class Display(object): + """ + Stores Wangscape output metadata in a suitable format, + stores terrain and tile grids, + and interfaces with bearlibterminal to draw a scene, + or with pillow to write one to file. + """ + def __init__(self, + rel_path='.', + fn_tile_groups='tile_groups.json', + fn_terrain_hypergraph='terrain_hypergraph.json', + fn_tileset_data='tilesets.json', + fps=30, + map_mode=[30,20], + output_mode=[]): + # Initialise base path + self.rel_path = rel_path + # Read map size or fixed map and prepare storage + self.init_map(map_mode) + self.init_output(output_mode) + self.init_tilesets(path.join(rel_path, fn_tileset_data)) + self.tile_groups = TileGroups.from_file(self.tilesets, + path.join(rel_path, fn_tile_groups)) + self.hypergraph = Hypergraph.from_file(path.join(rel_path, fn_terrain_hypergraph)) + self.update_map() + # Throttle framerate + self.fps_limiter = FPSLimiter(fps) + + def init_map(self, map_mode): + if type(map_mode) is str: + # Initialise fixed map and use its size + self.use_fixed_map = True + fn_fixed_map = map_mode + self.init_fixed_map(fn_fixed_map) + else: + # Use random maps of the given size + self.use_fixed_map = False + size = tuple(map_mode) + self.init_sizes(size) + self.terrain_map = MapGrid(self.terrain_map_width, self.terrain_map_height) + self.tile_map = MapGrid(self.tile_map_width, self.tile_map_height) + + def init_fixed_map(self, fn_fixed_map): + with open(path.join(self.rel_path, fn_fixed_map),'r') as f: + fixed_map_data = json.load(f) + self.fixed_map = fixed_map_data["map"] + self.init_sizes(fixed_map_data["size"]) + + def init_sizes(self, size): + # The size of the scene in tiles + self.display_width = size[0] + self.display_height = size[1] + # Tiles are not aligned with display boundaries, + # so one extra row and column is required + self.tile_map_width = self.display_width + 1 + self.tile_map_height = self.display_height + 1 + # Terrain values are specified in the corners of the graphical tiles, + # so another extra row and column is required + self.terrain_map_width = self.tile_map_width + 1 + self.terrain_map_height = self.tile_map_height + 1 + + def add_blt_output(self): + from .OutputBLT import OutputBLT + self.outputs.append(OutputBLT()) + self.use_blt = True + + def add_pillow_output(self, filename): + from .OutputPillow import OutputPillow + self.outputs.append(OutputPillow(filename)) + self.use_pillow = True + + def init_output(self, output_mode): + self.outputs = [] + for item in output_mode: + if item is True: + self.add_blt_output() + else: + self.add_pillow_output(item) + if self.outputs == []: + self.add_blt_output() + for output in self.outputs: + output.set_size(self.display_width, self.display_height) + + def init_tilesets(self, filename): + """ + Converts the data in the `tilesets.json` metadata file + into a format suitable for Wangview, + stores it in self.tilesets, + and loads tilesets into bearlibterminal. + """ + self.tilesets = TilesetInformation(self.outputs) + self.tilesets.set_rel_path(self.rel_path) + self.tilesets.load_tilesets(filename) + + def update_map(self): + if self.use_fixed_map: + self.terrain_map.empty() + for line in self.fixed_map: + self.terrain_map.add_line(line, True, False) + else: + self.generate_terrain_map() + self.generate_tile_map() + def generate_terrain_map(self): + self.terrain_map.empty() + for line in self.hypergraph.generate_lines(self.terrain_map_width, + self.terrain_map_height): + self.terrain_map.add_line(line, True, False) + + def generate_tile_map(self): + tile_iter = ((self.tile_groups.select_tile(self.get_tile_corners(x,y)) + for x in range(self.tile_map_width)) + for y in range(self.tile_map_height)) + self.tile_map.empty() + for line in tile_iter: + self.tile_map.add_line(line, True, False) + + def get_tile_corners(self, x, y): + """ + Returns a generator which iterates over the terrain values in positions + [(x,y), (x,y+1), (x+1, y), (x+1, y+1)] + """ + return (self.terrain_map[x,y] + for (x,y) in + product((x,x+1),(y,y+1))) + + def draw_iter(self): + """Yields cell coordinates, offset, and character for each tile to be drawn""" + for y, line in enumerate(self.tile_map): + # Corner Wang tiles are offset by a quarter of a tile in each dimension. + # Odd resolutions have the pixel at (0,0) moved by (x//2, y//2) in output tiles, + # So this reverse translation is correct. + dy = -(self.tilesets.tile_height//2) + if y == self.tile_map_height-1: + # The terminal ignores characters put outside its range, + # so one row must be drawn using composition + y -= 1 + dy += self.tilesets.tile_height + for x, c in enumerate(line): + dx = -(self.tilesets.tile_width//2) + if x == self.tile_map_width-1: + # One column must also be drawn using composition + x -= 1 + dx += self.tilesets.tile_width + yield (x,y,dx,dy,c) + + def draw(self): + """ + Draws every tile to the outputs. + See also: `draw_iter.` + """ + for output in self.outputs: + output.clear() + for draw_args in self.draw_iter(): + for output in self.outputs: + output.put_ext(*draw_args) + for output in self.outputs: + output.refresh() + def run(self): + """ + Draws the scene to the terminal and refreshes repeatedly. + Quits on pressing Esc or closing the window. + Creates a new scene on pressing Space. + """ + while True: + self.fps_limiter.wait() + self.draw() + signals = [output.signal() for output in self.outputs] + if all(signal == 'stop' for signal in signals): + break + if 'next' in signals: + self.update_map() + for output in self.outputs: + output.close() + +class NotDisplay(object): """ Stores Wangscape output metadata in a suitable format, stores terrain and tile grids, @@ -22,22 +196,30 @@ def __init__(self, fn_terrain_hypergraph='terrain_hypergraph.json', fn_tileset_data='tilesets.json', fps=30, - size=(30,20), - fn_fixed_map=None): - # Initialise file path and metadata - self.size = size + map_mode=[30,20], + output_mode=[]): + # Initialise base path self.rel_path = rel_path + # Initialise fixed map and use its size, if it exists + if type(map_mode) is str: + fn_fixed_map = map_mode + self.init_fixed_map(fn_fixed_map) + else: + size = tuple(map_mode) + self.init_sizes(size) with open(path.join(rel_path, fn_tileset_data),'r') as f: self.init_tilesets(json.load(f)) with open(path.join(rel_path,fn_tile_groups),'r') as f: self.init_tile_groups(json.load(f)) with open(path.join(rel_path, fn_terrain_hypergraph),'r') as f: self.hypergraph = Hypergraph(json.load(f)) - if fn_fixed_map is not None: - with open(path.join(rel_path, fn_fixed_map),'r') as f: - self.fixed_map = json.load(f) - else: - self.fixed_map = None + # Select terrain values + self.init_terrain_map() + # Select tile values based on terrain values + self.init_tile_map() + # Throttle framerate + self.fps_limiter = FPSLimiter(fps) + def init_sizes(self, size): # Initialise geometry info self.terminal_width = blt.state(blt.TK_WIDTH) self.terminal_height = blt.state(blt.TK_HEIGHT) @@ -49,12 +231,6 @@ def __init__(self, # so another extra row and column is required self.terrain_width = self.terminal_width+2 self.terrain_height = self.terminal_height+2 - # Select terrain values - self.init_terrain_map() - # Select tile values based on terrain values - self.init_tile_map() - # Throttle framerate - self.fps_limiter = FPSLimiter(fps) def simplify_tile(self, tile): """ Converts a full specification of a tile's location in a tileset @@ -124,6 +300,11 @@ def init_tilesets(self, raw_tileset_data): blt.set(config_string) # Insert the next tileset's tiles at the correct unicode codepoint tileset_offset_counter += rx*ry + def init_fixed_map(self, fn_fixed_map): + with open(path.join(rel_path, fn_fixed_map),'r') as f: + fixed_map_data = json.load(f) + self.fixed_map = fixed_map["map"] + self.size = tuple(fixed_map["size"]) def init_terrain_map(self): """ Calls Hypergraph.generate_lines diff --git a/Wangview/Hypergraph.ipynb b/Wangview/Hypergraph.ipynb index 9448ecb..9f3869f 100644 --- a/Wangview/Hypergraph.ipynb +++ b/Wangview/Hypergraph.ipynb @@ -9,7 +9,8 @@ "outputs": [], "source": [ "from functools import reduce\n", - "import random" + "import random\n", + "import json" ] }, { @@ -25,7 +26,27 @@ " Stores data specifying which terrains can be present in a single tile,\n", " and uses that data to generate random terrain grids.\n", " \"\"\"\n", - " def __init__(self, raw_hypergraph):\n", + " def __init__(self):\n", + " self.data = {}\n", + " \n", + " @classmethod\n", + " def from_data(cls, raw_hypergraph):\n", + " h = cls()\n", + " h.init_hypergraph(raw_hypergraph)\n", + " return h\n", + " \n", + " @classmethod\n", + " def from_file(cls, filename):\n", + " h = cls()\n", + " h.load(filename)\n", + " return h\n", + " \n", + " def load(self, filename):\n", + " with open(filename, 'r') as f:\n", + " raw_hypergraph = json.load(f)\n", + " self.init_hypergraph(raw_hypergraph)\n", + " \n", + " def init_hypergraph(self, raw_hypergraph):\n", " # Input data is a dict of lists of lists.\n", " # Convert it to a dict of frozensets of frozensets.\n", " self.data = {k: frozenset(map(frozenset,v))\n", @@ -36,6 +57,7 @@ " return reduce(lambda x,y: x.union(y),\n", " options,\n", " frozenset())\n", + " \n", " def terrain_options(self, *terrains):\n", " \"\"\"\n", " Returns a frozenset containing the terrains which\n", @@ -53,6 +75,7 @@ " combine = lambda options, terrain: filter(lambda clique: terrain in clique, options)\n", " # Take the union of the remaining cliques\n", " return self.flatten_options(reduce(combine, seq, start))\n", + " \n", " def terrain_options_2(self, t_left=[], t_up=[]):\n", " \"\"\"\n", " Returns a frozenset containing the terrains which can legally be placed\n", @@ -121,6 +144,7 @@ " # Set L to the value just inserted\n", " t_left = [new_line[-1]]\n", " return new_line\n", + " \n", " def generate_lines(self, width, height):\n", " \"\"\"Yields each line of a grid of terrain values satisfying the ajdacency constraints\"\"\"\n", " # Yield a line with no constraints from a preceding line\n", @@ -142,9 +166,9 @@ "source": [ "if __name__ == '__main__':\n", " # A quick test that the class is working correctly\n", - " th = Hypergraph({'a':[['a','b'],['c','a']],\n", - " 'b':[['a','b'],['b','c']],\n", - " 'c':[['b','c'],['c','a']]})\n", + " th = Hypergraph.from_data({'a':[['a','b'],['c','a']],\n", + " 'b':[['a','b'],['b','c']],\n", + " 'c':[['b','c'],['c','a']]})\n", " for line in th.generate_lines(10,10):\n", " print(''.join(line)), " ] diff --git a/Wangview/Hypergraph.py b/Wangview/Hypergraph.py index f5c64af..5e0f100 100644 --- a/Wangview/Hypergraph.py +++ b/Wangview/Hypergraph.py @@ -2,13 +2,34 @@ # coding: utf-8 from functools import reduce import random +import json class Hypergraph(object): """ Stores data specifying which terrains can be present in a single tile, and uses that data to generate random terrain grids. """ - def __init__(self, raw_hypergraph): + def __init__(self): + self.data = {} + + @classmethod + def from_data(cls, raw_hypergraph): + h = cls() + h.init_hypergraph(raw_hypergraph) + return h + + @classmethod + def from_file(cls, filename): + h = cls() + h.load(filename) + return h + + def load(self, filename): + with open(filename, 'r') as f: + raw_hypergraph = json.load(f) + self.init_hypergraph(raw_hypergraph) + + def init_hypergraph(self, raw_hypergraph): # Input data is a dict of lists of lists. # Convert it to a dict of frozensets of frozensets. self.data = {k: frozenset(map(frozenset,v)) @@ -19,6 +40,7 @@ def flatten_options(options): return reduce(lambda x,y: x.union(y), options, frozenset()) + def terrain_options(self, *terrains): """ Returns a frozenset containing the terrains which @@ -36,6 +58,7 @@ def terrain_options(self, *terrains): combine = lambda options, terrain: filter(lambda clique: terrain in clique, options) # Take the union of the remaining cliques return self.flatten_options(reduce(combine, seq, start)) + def terrain_options_2(self, t_left=[], t_up=[]): """ Returns a frozenset containing the terrains which can legally be placed @@ -104,6 +127,7 @@ def generate_line(self, width, previous_line=None): # Set L to the value just inserted t_left = [new_line[-1]] return new_line + def generate_lines(self, width, height): """Yields each line of a grid of terrain values satisfying the ajdacency constraints""" # Yield a line with no constraints from a preceding line @@ -116,8 +140,8 @@ def generate_lines(self, width, height): if __name__ == '__main__': # A quick test that the class is working correctly - th = Hypergraph({'a':[['a','b'],['c','a']], - 'b':[['a','b'],['b','c']], - 'c':[['b','c'],['c','a']]}) + th = Hypergraph.from_data({'a':[['a','b'],['c','a']], + 'b':[['a','b'],['b','c']], + 'c':[['b','c'],['c','a']]}) for line in th.generate_lines(10,10): print(''.join(line)), diff --git a/Wangview/MapGrid.ipynb b/Wangview/MapGrid.ipynb index 7302cff..2589269 100644 --- a/Wangview/MapGrid.ipynb +++ b/Wangview/MapGrid.ipynb @@ -22,8 +22,10 @@ "class MapGrid(object):\n", " \"\"\"Stores terrain and tile information for display.\"\"\"\n", " def __init__(self, *size):\n", - " self._columns, self._rows = (0, 0)\n", " self._columns_max, self._rows_max = size\n", + " self.empty()\n", + " def empty(self):\n", + " self._columns, self._rows = (0, 0)\n", " self._grid = deque(maxlen=self._rows_max)\n", " def full(self):\n", " \"\"\"Returns True if the MapGrid is at full capacity, False otherwise.\"\"\"\n", diff --git a/Wangview/MapGrid.py b/Wangview/MapGrid.py index c498d5e..318b2ff 100644 --- a/Wangview/MapGrid.py +++ b/Wangview/MapGrid.py @@ -5,8 +5,10 @@ class MapGrid(object): """Stores terrain and tile information for display.""" def __init__(self, *size): - self._columns, self._rows = (0, 0) self._columns_max, self._rows_max = size + self.empty() + def empty(self): + self._columns, self._rows = (0, 0) self._grid = deque(maxlen=self._rows_max) def full(self): """Returns True if the MapGrid is at full capacity, False otherwise.""" diff --git a/Wangview/OutputBLT.ipynb b/Wangview/OutputBLT.ipynb new file mode 100644 index 0000000..8da0291 --- /dev/null +++ b/Wangview/OutputBLT.ipynb @@ -0,0 +1,81 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from bearlibterminal import terminal as blt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class OutputBLT(object):\n", + " def __init__(self):\n", + " pass\n", + " def set_size(self, width, height):\n", + " self.width = width\n", + " self.height = height\n", + " def set_resolution(self, tile_width, tile_height):\n", + " self.tile_width = tile_width\n", + " self.tile_height = tile_height\n", + " def initialise(self):\n", + " blt.open()\n", + " blt.composition(True)\n", + " config_string = \"window: size={0}x{1}, cellsize={2}x{3}, title='Wangview'\".format(\n", + " self.width, self.height, self.tile_width, self.tile_height)\n", + " blt.set(config_string)\n", + " def load_tileset(self, offset, filename):\n", + " config_string = \"0x{0:x}: {1}, size={2}x{3}\".format(\n", + " offset, filename, self.tile_width, self.tile_height)\n", + " blt.set(config_string)\n", + " def put_ext(self, x,y,dx,dy,c):\n", + " blt.put_ext(x,y,dx,dy,c)\n", + " def refresh(self):\n", + " blt.refresh()\n", + " def clear(self):\n", + " blt.clear()\n", + " def signal(self):\n", + " while blt.has_input():\n", + " kp = blt.read()\n", + " if kp in [blt.TK_CLOSE, blt.TK_ESCAPE]:\n", + " return 'stop'\n", + " if kp == blt.TK_SPACE:\n", + " return 'next'\n", + " return None\n", + " def close(self):\n", + " blt.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda root]", + "language": "python", + "name": "conda-root-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Wangview/OutputBLT.py b/Wangview/OutputBLT.py new file mode 100644 index 0000000..3d9a422 --- /dev/null +++ b/Wangview/OutputBLT.py @@ -0,0 +1,39 @@ + +# coding: utf-8 +from bearlibterminal import terminal as blt + +class OutputBLT(object): + def __init__(self): + pass + def set_size(self, width, height): + self.width = width + self.height = height + def set_resolution(self, tile_width, tile_height): + self.tile_width = tile_width + self.tile_height = tile_height + def initialise(self): + blt.open() + blt.composition(True) + config_string = "window: size={0}x{1}, cellsize={2}x{3}, title='Wangview'".format( + self.width, self.height, self.tile_width, self.tile_height) + blt.set(config_string) + def load_tileset(self, offset, filename): + config_string = "0x{0:x}: {1}, size={2}x{3}".format( + offset, filename, self.tile_width, self.tile_height) + blt.set(config_string) + def put_ext(self, x,y,dx,dy,c): + blt.put_ext(x,y,dx,dy,c) + def refresh(self): + blt.refresh() + def clear(self): + blt.clear() + def signal(self): + while blt.has_input(): + kp = blt.read() + if kp in [blt.TK_CLOSE, blt.TK_ESCAPE]: + return 'stop' + if kp == blt.TK_SPACE: + return 'next' + return None + def close(self): + blt.close() diff --git a/Wangview/OutputPillow.ipynb b/Wangview/OutputPillow.ipynb new file mode 100644 index 0000000..474de10 --- /dev/null +++ b/Wangview/OutputPillow.ipynb @@ -0,0 +1,81 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import PIL\n", + "from PIL import ImageFile\n", + "from PIL import Image" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class OutputPillow(object):\n", + " def __init__(self, filename):\n", + " self._tiles = {}\n", + " self.filename = filename\n", + " def set_size(self, width, height):\n", + " self.width = width\n", + " self.height = height\n", + " def set_resolution(self, tile_width, tile_height):\n", + " self.tile_width = tile_width\n", + " self.tile_height = tile_height\n", + " def initialise(self):\n", + " self.clear()\n", + " def load_tileset(self, offset, filename):\n", + " tileset = Image.open(filename)\n", + " tileset_width = tileset.width//self.tile_width\n", + " tileset_height = tileset.height//self.tile_height\n", + " for y in range(tileset_height):\n", + " for x in range(tileset_width):\n", + " square = (x*self.tile_width, y*self.tile_height,\n", + " (x+1)*self.tile_width, (y+1)*self.tile_height)\n", + " tile_offset = offset + y*tileset_width + x\n", + " self._tiles[tile_offset] = tileset.crop(square)\n", + " def put_ext(self, x,y,dx,dy,c):\n", + " self.image.paste(self._tiles[c], (x*self.tile_width + dx, y*self.tile_height + dy))\n", + " def refresh(self):\n", + " self.image.save(self.filename)\n", + " def clear(self):\n", + " self.image = Image.new('RGBA', (self.width*self.tile_width,\n", + " self.height*self.tile_height))\n", + " def signal(self):\n", + " return 'stop'\n", + " def close(self):\n", + " pass" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda root]", + "language": "python", + "name": "conda-root-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Wangview/OutputPillow.py b/Wangview/OutputPillow.py new file mode 100644 index 0000000..4db2a5c --- /dev/null +++ b/Wangview/OutputPillow.py @@ -0,0 +1,39 @@ + +# coding: utf-8 +import PIL +from PIL import ImageFile +from PIL import Image + +class OutputPillow(object): + def __init__(self, filename): + self._tiles = {} + self.filename = filename + def set_size(self, width, height): + self.width = width + self.height = height + def set_resolution(self, tile_width, tile_height): + self.tile_width = tile_width + self.tile_height = tile_height + def initialise(self): + self.clear() + def load_tileset(self, offset, filename): + tileset = Image.open(filename) + tileset_width = tileset.width//self.tile_width + tileset_height = tileset.height//self.tile_height + for y in range(tileset_height): + for x in range(tileset_width): + square = (x*self.tile_width, y*self.tile_height, + (x+1)*self.tile_width, (y+1)*self.tile_height) + tile_offset = offset + y*tileset_width + x + self._tiles[tile_offset] = tileset.crop(square) + def put_ext(self, x,y,dx,dy,c): + self.image.paste(self._tiles[c], (x*self.tile_width + dx, y*self.tile_height + dy)) + def refresh(self): + self.image.save(self.filename) + def clear(self): + self.image = Image.new('RGBA', (self.width*self.tile_width, + self.height*self.tile_height)) + def signal(self): + return 'stop' + def close(self): + pass diff --git a/Wangview/TileGroups.ipynb b/Wangview/TileGroups.ipynb new file mode 100644 index 0000000..d270017 --- /dev/null +++ b/Wangview/TileGroups.ipynb @@ -0,0 +1,90 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import json\n", + "import random" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class TileGroups(object):\n", + " def __init__(self, tilesets):\n", + " self.tilesets = tilesets\n", + " self.tile_groups = {}\n", + " \n", + " @classmethod\n", + " def from_data(cls, tilesets, raw_groups):\n", + " tg = cls(tilesets)\n", + " tg.init_tile_groups(raw_groups)\n", + " return tg\n", + " \n", + " @classmethod\n", + " def from_file(cls, tilesets, filename):\n", + " tg = cls(tilesets)\n", + " tg.load(filename)\n", + " return tg\n", + " \n", + " def load(self, filename):\n", + " with open(filename, 'r') as f:\n", + " raw_tile_groups = json.load(f)\n", + " self.init_tile_groups(raw_tile_groups)\n", + " \n", + " def simplify_tile_group(self, tile_group):\n", + " \"\"\"Converts every tile in a group into a unicode codepoint\"\"\"\n", + " return [self.tilesets.simplify_tile(tile) for tile in tile_group]\n", + " \n", + " def init_tile_groups(self, raw_tile_groups):\n", + " \"\"\"\n", + " Converts the data the `tile_groups.json` metadata file\n", + " into a format suitable for Wangview,\n", + " and stores it in self.tile_groups.\n", + " Example input:\n", + " {\"g.g.g.g\": [{\"filename\": \"g.s.png\", \"x\":0, \"y\":0},\n", + " {\"filename\": \"v.g.png\", \"x\":96,\"y\":96}]}\n", + " output:\n", + " {(\"g\", \"g\", \"g\", \"g\"): [0xe000, 0xe01f]}\n", + " \"\"\"\n", + " self.tile_groups = {tuple(k.split('.')):self.simplify_tile_group(v)\n", + " for (k,v) in raw_tile_groups.items()}\n", + " \n", + " def select_tile(self, corners):\n", + " \"\"\"Selects a random tile that has the specified terrain values in its corners\"\"\"\n", + " return random.choice(self.tile_groups[tuple(corners)])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda root]", + "language": "python", + "name": "conda-root-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Wangview/TileGroups.py b/Wangview/TileGroups.py new file mode 100644 index 0000000..c200f8d --- /dev/null +++ b/Wangview/TileGroups.py @@ -0,0 +1,48 @@ + +# coding: utf-8 +import json +import random + +class TileGroups(object): + def __init__(self, tilesets): + self.tilesets = tilesets + self.tile_groups = {} + + @classmethod + def from_data(cls, tilesets, raw_groups): + tg = cls(tilesets) + tg.init_tile_groups(raw_groups) + return tg + + @classmethod + def from_file(cls, tilesets, filename): + tg = cls(tilesets) + tg.load(filename) + return tg + + def load(self, filename): + with open(filename, 'r') as f: + raw_tile_groups = json.load(f) + self.init_tile_groups(raw_tile_groups) + + def simplify_tile_group(self, tile_group): + """Converts every tile in a group into a unicode codepoint""" + return [self.tilesets.simplify_tile(tile) for tile in tile_group] + + def init_tile_groups(self, raw_tile_groups): + """ + Converts the data the `tile_groups.json` metadata file + into a format suitable for Wangview, + and stores it in self.tile_groups. + Example input: + {"g.g.g.g": [{"filename": "g.s.png", "x":0, "y":0}, + {"filename": "v.g.png", "x":96,"y":96}]} + output: + {("g", "g", "g", "g"): [0xe000, 0xe01f]} + """ + self.tile_groups = {tuple(k.split('.')):self.simplify_tile_group(v) + for (k,v) in raw_tile_groups.items()} + + def select_tile(self, corners): + """Selects a random tile that has the specified terrain values in its corners""" + return random.choice(self.tile_groups[tuple(corners)]) diff --git a/Wangview/Tileset.py b/Wangview/Tileset.py index 724aba0..8dec237 100644 --- a/Wangview/Tileset.py +++ b/Wangview/Tileset.py @@ -1,12 +1,5 @@ # coding: utf-8 - -# In[ ]: - from collections import namedtuple - -# In[ ]: - Tileset = namedtuple('Tileset',['filename','offset','width','height','clique']) - diff --git a/Wangview/TilesetInformation.ipynb b/Wangview/TilesetInformation.ipynb new file mode 100644 index 0000000..94ec96f --- /dev/null +++ b/Wangview/TilesetInformation.ipynb @@ -0,0 +1,112 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from .Tileset import Tileset\n", + "\n", + "import json\n", + "from os import path" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class TilesetInformation(object):\n", + " def __init__(self, outputs=[]):\n", + " self.outputs = list(outputs)\n", + " \n", + " def set_rel_path(self, rel_path):\n", + " self.rel_path = rel_path\n", + " \n", + " def set_resolution(self, tile_width, tile_height):\n", + " self.tile_width = tile_width\n", + " self.tile_height = tile_height\n", + " for output in self.outputs:\n", + " output.set_resolution(tile_width, tile_height)\n", + " output.initialise()\n", + " \n", + " def load_tilesets(self, filename):\n", + " with open(filename, 'r') as f:\n", + " raw_tileset_data = json.load(f)\n", + " self.init_tilesets(raw_tileset_data)\n", + " \n", + " def init_tilesets(self, raw_tileset_data):\n", + " first_tileset = True\n", + " for tileset in raw_tileset_data:\n", + " self.load_tileset(tileset, first_tileset)\n", + " first_tileset = False\n", + " \n", + " def load_tileset(self, tileset, is_first):\n", + " # All tilesets specify tile resolution\n", + " resolution = tileset['resolution']\n", + " if is_first:\n", + " # Use the first tileset's resolution\n", + " self.set_resolution(*resolution)\n", + " # Start tile unicode blocks in private space\n", + " self.tileset_offset_counter = 0xE000\n", + " # Initialise converted metadata\n", + " self.tilesets = {}\n", + " # Validate tileset's resolution\n", + " assert tuple(resolution) == (self.tile_width, self.tile_height)\n", + " # Calculate the number of tiles the tileset has in each axis\n", + " rx = tileset['x']//self.tile_width\n", + " ry = tileset['y']//self.tile_height\n", + " # Add the tileset's entry to the tileset container\n", + " filename = tileset['filename']\n", + " self.tilesets[filename] = Tileset(\n", + " filename, self.tileset_offset_counter,\n", + " rx,ry, tuple(tileset['terrains']))\n", + " # Load the tileset in the output modules\n", + " for output in self.outputs:\n", + " output.load_tileset(self.tileset_offset_counter,\n", + " path.join(self.rel_path, filename))\n", + " # Insert the next tileset's tiles at the correct unicode codepoint\n", + " self.tileset_offset_counter += rx*ry\n", + " \n", + " def simplify_tile(self, tile):\n", + " \"\"\"\n", + " Converts a full specification of a tile's location in a tileset\n", + " into the unicode codepoint where it will have been loaded.\n", + " See also: `init_tilesets()`\n", + " \"\"\"\n", + " tileset = self.tilesets[tile['filename']]\n", + " return (tileset.offset +\n", + " tileset.width*tile['y']//self.tile_height +\n", + " tile['x']//self.tile_width)\n", + " " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda root]", + "language": "python", + "name": "conda-root-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Wangview/TilesetInformation.py b/Wangview/TilesetInformation.py new file mode 100644 index 0000000..9d35680 --- /dev/null +++ b/Wangview/TilesetInformation.py @@ -0,0 +1,70 @@ + +# coding: utf-8 +from .Tileset import Tileset + +import json +from os import path + +class TilesetInformation(object): + def __init__(self, outputs=[]): + self.outputs = list(outputs) + + def set_rel_path(self, rel_path): + self.rel_path = rel_path + + def set_resolution(self, tile_width, tile_height): + self.tile_width = tile_width + self.tile_height = tile_height + for output in self.outputs: + output.set_resolution(tile_width, tile_height) + output.initialise() + + def load_tilesets(self, filename): + with open(filename, 'r') as f: + raw_tileset_data = json.load(f) + self.init_tilesets(raw_tileset_data) + + def init_tilesets(self, raw_tileset_data): + first_tileset = True + for tileset in raw_tileset_data: + self.load_tileset(tileset, first_tileset) + first_tileset = False + + def load_tileset(self, tileset, is_first): + # All tilesets specify tile resolution + resolution = tileset['resolution'] + if is_first: + # Use the first tileset's resolution + self.set_resolution(*resolution) + # Start tile unicode blocks in private space + self.tileset_offset_counter = 0xE000 + # Initialise converted metadata + self.tilesets = {} + # Validate tileset's resolution + assert tuple(resolution) == (self.tile_width, self.tile_height) + # Calculate the number of tiles the tileset has in each axis + rx = tileset['x']//self.tile_width + ry = tileset['y']//self.tile_height + # Add the tileset's entry to the tileset container + filename = tileset['filename'] + self.tilesets[filename] = Tileset( + filename, self.tileset_offset_counter, + rx,ry, tuple(tileset['terrains'])) + # Load the tileset in the output modules + for output in self.outputs: + output.load_tileset(self.tileset_offset_counter, + path.join(self.rel_path, filename)) + # Insert the next tileset's tiles at the correct unicode codepoint + self.tileset_offset_counter += rx*ry + + def simplify_tile(self, tile): + """ + Converts a full specification of a tile's location in a tileset + into the unicode codepoint where it will have been loaded. + See also: `init_tilesets()` + """ + tileset = self.tilesets[tile['filename']] + return (tileset.offset + + tileset.width*tile['y']//self.tile_height + + tile['x']//self.tile_width) + From c4c165a12e4ce67ac5f73897b27f37810b040946 Mon Sep 17 00:00:00 2001 From: Serin Delaunay Date: Tue, 29 Aug 2017 21:25:43 +0100 Subject: [PATCH 08/10] Add short versions of command line arguments Change help text for --output. --- Wangview.ipynb | 16 ++++++++-------- Wangview.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Wangview.ipynb b/Wangview.ipynb index 46249ae..d0e014f 100644 --- a/Wangview.ipynb +++ b/Wangview.ipynb @@ -24,16 +24,16 @@ "if __name__ == '__main__':\n", " parser = argparse.ArgumentParser(description='Render random landscapes using Wangscape tiles')\n", " parser.add_argument('path', type=str, default='.', help='Path to the folder containing the tileset and metadata')\n", - " parser.add_argument('--tile-groups', type=str, default='tile_groups.json', help='Name of the tile groups JSON file')\n", - " parser.add_argument('--terrain-hypergraph', type=str, default='terrain_hypergraph.json', help='Name of the terrain hypergraph JSON file')\n", - " parser.add_argument('--tileset-data', type=str, default='tilesets.json', help='Name of the tileset data JSON file')\n", - " parser.add_argument('--fps', type=int, default=30, help='Maximum frames per second')\n", + " parser.add_argument('-g', '--tile-groups', metavar='TILE_GROUPS.json', type=str, default='tile_groups.json', help='Name of the tile groups JSON file')\n", + " parser.add_argument('-c', '--terrain-hypergraph', metavar='TERRAIN_HYPERGRAPH.json', type=str, default='terrain_hypergraph.json', help='Name of the terrain hypergraph JSON file')\n", + " parser.add_argument('-t', '--tileset-data', metavar='TILESET_DATA.json', type=str, default='tilesets.json', help='Name of the tileset data JSON file')\n", + " parser.add_argument('-f', '--fps', metavar='FPS', type=int, default=30, help='Maximum frames per second')\n", " map_parser = parser.add_mutually_exclusive_group()\n", - " map_parser.add_argument('--size', type=int, dest='map_mode', nargs=2, metavar=('x','y'), help='Size of the map display')\n", - " map_parser.add_argument('--fixed-map', type=str, dest='map_mode', help='Name of a JSON file with a fixed map')\n", + " map_parser.add_argument('-s', '--size', type=int, dest='map_mode', nargs=2, metavar=('X','Y'), help='Size of the map display')\n", + " map_parser.add_argument('-m', '--fixed-map', metavar='FIXED_MAP.json', type=str, dest='map_mode', help='Name of a JSON file with a fixed map')\n", " output_parser = parser.add_argument_group()\n", - " output_parser.add_argument('--display', dest='output_mode', action='append_const', const=True, help='Display maps in a GUI interface')\n", - " output_parser.add_argument('--output', dest='output_mode', action='append', type=str, help='Write an image to file')\n", + " output_parser.add_argument('-d', '--display', dest='output_mode', action='append_const', const=True, help='Display maps in a GUI interface')\n", + " output_parser.add_argument('-o', '--output', metavar='OUTPUT.png', dest='output_mode', action='append', type=str, help='Name of the image file to write the scene to')\n", " parser.set_defaults(map_mode=[30,20], output_mode=[])\n", " args = parser.parse_args()\n", " try:\n", diff --git a/Wangview.py b/Wangview.py index 9589f85..0b37e23 100644 --- a/Wangview.py +++ b/Wangview.py @@ -7,16 +7,16 @@ if __name__ == '__main__': parser = argparse.ArgumentParser(description='Render random landscapes using Wangscape tiles') parser.add_argument('path', type=str, default='.', help='Path to the folder containing the tileset and metadata') - parser.add_argument('--tile-groups', type=str, default='tile_groups.json', help='Name of the tile groups JSON file') - parser.add_argument('--terrain-hypergraph', type=str, default='terrain_hypergraph.json', help='Name of the terrain hypergraph JSON file') - parser.add_argument('--tileset-data', type=str, default='tilesets.json', help='Name of the tileset data JSON file') - parser.add_argument('--fps', type=int, default=30, help='Maximum frames per second') + parser.add_argument('-g', '--tile-groups', metavar='TILE_GROUPS.json', type=str, default='tile_groups.json', help='Name of the tile groups JSON file') + parser.add_argument('-c', '--terrain-hypergraph', metavar='TERRAIN_HYPERGRAPH.json', type=str, default='terrain_hypergraph.json', help='Name of the terrain hypergraph JSON file') + parser.add_argument('-t', '--tileset-data', metavar='TILESET_DATA.json', type=str, default='tilesets.json', help='Name of the tileset data JSON file') + parser.add_argument('-f', '--fps', metavar='FPS', type=int, default=30, help='Maximum frames per second') map_parser = parser.add_mutually_exclusive_group() - map_parser.add_argument('--size', type=int, dest='map_mode', nargs=2, metavar=('x','y'), help='Size of the map display') - map_parser.add_argument('--fixed-map', type=str, dest='map_mode', help='Name of a JSON file with a fixed map') + map_parser.add_argument('-s', '--size', type=int, dest='map_mode', nargs=2, metavar=('X','Y'), help='Size of the map display') + map_parser.add_argument('-m', '--fixed-map', metavar='FIXED_MAP.json', type=str, dest='map_mode', help='Name of a JSON file with a fixed map') output_parser = parser.add_argument_group() - output_parser.add_argument('--display', dest='output_mode', action='append_const', const=True, help='Display maps in a GUI interface') - output_parser.add_argument('--output', dest='output_mode', action='append', type=str, help='Write an image to file') + output_parser.add_argument('-d', '--display', dest='output_mode', action='append_const', const=True, help='Display maps in a GUI interface') + output_parser.add_argument('-o', '--output', metavar='OUTPUT.png', dest='output_mode', action='append', type=str, help='Name of the image file to write the scene to') parser.set_defaults(map_mode=[30,20], output_mode=[]) args = parser.parse_args() try: From 7c30b52e2e7ef9a0f87fd03e3a4adf246c6f48b0 Mon Sep 17 00:00:00 2001 From: Serin Delaunay Date: Tue, 29 Aug 2017 21:27:10 +0100 Subject: [PATCH 09/10] Use relative path for output image --- Wangview/Display.ipynb | 2 +- Wangview/Display.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Wangview/Display.ipynb b/Wangview/Display.ipynb index 09566ff..6f49854 100644 --- a/Wangview/Display.ipynb +++ b/Wangview/Display.ipynb @@ -95,7 +95,7 @@ " \n", " def add_pillow_output(self, filename):\n", " from .OutputPillow import OutputPillow\n", - " self.outputs.append(OutputPillow(filename))\n", + " self.outputs.append(OutputPillow(path.join(self.rel_path, filename)))\n", " self.use_pillow = True\n", " \n", " def init_output(self, output_mode):\n", diff --git a/Wangview/Display.py b/Wangview/Display.py index ae94544..3ff19a4 100644 --- a/Wangview/Display.py +++ b/Wangview/Display.py @@ -78,7 +78,7 @@ def add_blt_output(self): def add_pillow_output(self, filename): from .OutputPillow import OutputPillow - self.outputs.append(OutputPillow(filename)) + self.outputs.append(OutputPillow(path.join(self.rel_path, filename))) self.use_pillow = True def init_output(self, output_mode): From 5e29109ea2010d58cace48c91298de6f7f2a5628 Mon Sep 17 00:00:00 2001 From: Serin Delaunay Date: Wed, 30 Aug 2017 16:24:02 +0100 Subject: [PATCH 10/10] Add Pillow to requirements.txt Allow updated BLT --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6a36111..b426d37 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -bearlibterminal==0.15.2 +bearlibterminal>=0.15.2 +Pillow>=4.2.1