Package: hexlogic
HexLogicHexLogic aims to be a Python package, without dependencies outside of the
built-in library, providing fully documented functions to deal with the
relations between objects on a hexagon tiled grid. Including conversion from
„hexagonal“ to „pixel“ coordinates and pathfinding with varying movement cost.
As well as various operations like line-drawing.Is there a requirement for Hexlogic?Although there have been various attempts at bundling the functionalities,
outlined by Amit Patel on redblobgames.com and providing them on the Python
Package Index, there is currently no package available, that provides all
the following features:documentationtype-hinteddrop-in usabilityincludes pathfinding algorithmsnot an OOP only implementationOn design choicesDrop-in usability can be achieved using an object-oriented approach, as Python allows for multiple inheritance. Despite that opting for a function based approach, has the benefit, of only having to add the coordinates to the objects positioned on the hexagonal grid, which ultimately keeps the separation between the different building blocks of code cleaner and sidesteps possible conflicts between parent classes.The file started out as a part of a Pygame-CE project, as a result, the design is built around the idea of positioning units, tiles etc. being childclasses of pygame.sprite.Sprite on a map.A lesson learned from this ongoing project, is that with increasing scope of the project it becomes desirable to catch errors early, which is the reason for the divergence from the minimalistic pythonic approach to code."breadth_first_search" and "dijkstras_algorithm" were included, albeit being inferior to "a_star_algorithm", as I plan on using a modified functionality of each for my projects AI and/or a game mechanicPrerequisitesPython 3.9 or newerInstallationPython Package IndexpipinstallhexlogicManually (Github)gitclonehttps://github.com/MaximilianHauser/HexLogic.gitUsageThese functions assume you're using objects to represent tiles, units, items,
etc on a map that is made up of hexagonal shaped tiles, with a flat side as top.
It furthermore assumes you're using a 3-dimensional cartesian coordinate system,
that assigns the coordinates along axis parallel to the orientation of the sides
of each hexagon. Like in the example given below. When using Pygame-CE it is
recommended to use square images and draw a hexagon on them. An example of a
64x64 tile is provided, it's intended to be used with the transparency colorcode
set to (255, 0, 255). Currently the package does not support drawing hexagons.+s \         / -r
         \   _   /                   A: ( q=0, r=0, s=0 )
         _ / B \ _                   B: ( q=0, r=-1, s=1 )
       / G \ _ / C \                 C: ( q=1, r=-1, s=0 )
 -q __ \ _ / A \ _ / __ +q           D: ( q=1, r=0, s=-1 )
       / F \ _ / D \                 E: ( q=0, r=1, s=-1 )
       \ _ / E \ _ /                 F: ( q=-1, r=1, s=0 )
           \ _ /                     G: ( q=-1, r=0, s=1 )
        /        \
    +r /          \ -sContains all hextile logic, specified as logic handling the relationship
between cartesian coordinates and cube coordinates, for the purpose of defining
the relative position of hexagon tiles on the screen. In addition it provides
calculations in regards to hextile map related formulas and algorithms.Custom Errors:ConstraintViolation(ValueError):Custom Error to be raised when a logical constraint is violated.Classes:RectCoords(namedtuple("RectCoords", "x y")):Coordinates in a rectangular cartesian coordinate system.HexCoords(namedtuple("HexCoords", "q r s")):Coordinates in a three-dimensional cartesian coordinate system, limited by the constraint q + r + s = 0.GraphMatrix(tile_grp:set|list):Creates a GraphMatrix object, containing a directed, weighted graph, from the
objects or coordinates contained in tile_grp, organized in a Dictionary.Functions and Methods:float_to_int(num_in:int|float) -> int|float:Returns an Integer if passed an Integer or if passed a Float with its decimal being zero. Returns a Float if passed a Float, with a non zero decimal.tuple_or_object(tuple_or_object:object|tuple|RectCoords|HexCoords, expected_len:2|3, return_coords_obj:bool=False) -> tuple|RectCoords|HexCoords:Returns a Tuple or a Namedtuple of predefined length, when passed an Object or a Tuple.linint(a:int|float, b:int|float, t:int|float) -> int|float:Linear interpolation returns point at t of distance between a and b.rect_linint(xy_a:object|tuple|RectCoords, xy_b:object|tuple|RectCoords, t:int|float, return_coords_obj:bool=False) -> tuple|RectCoords:Linear interpolation returns point at t of distance between a and b on a cartesian coordinates system.cube_linint(obj_a:object|tuple|HexCoords, obj_b:object|tuple|HexCoords, t:int|float, return_coords_obj:bool=False) -> tuple|HexCoords:Returns the hextile coordinates of a point situated at t part of the way from obj_a to obj_b.round_container(container:dict|list|set|tuple|RectCoords, d:int=0) -> dict|list|set|tuple|RectCoords:Rounds each number in a container to the specified decimal, if None is specified to the nearest Integer.round_hex(qrs:tuple|HexCoords, return_coords_obj:bool=False) -> tuple|HexCoords:Rounds each of the coordinates to the nearest Integer.get_xy(obj:object, return_coords_obj:bool=False) -> tuple|RectCoords:Returns values of attributes x and y of obj as Tuple or RectCoords.set_xy(obj:object, x:int|float, y:int|float) -> None:Set x and y attribute of obj to specified values.get_qrs(obj:object, return_coords_obj:bool=False) -> tuple|HexCoords:Returns values of attributes q, r, s of obj.set_qrs(obj:object, q:int|float, r:int|float, s:int|float) -> None:Set q r and s attribute of obj to specified values.hex_to_pixel(qrs:object|tuple|HexCoords, tile_width:int=64, tile_height:int=64, return_coords_obj:bool=False) -> tuple|RectCoords:Converts cube coordinates to pixel coordinates.pixel_to_hex(xy:object|tuple|RectCoords, tile_width:int=64, tile_height:int=64, return_coords_obj:bool=False) -> tuple|HexCoords:Converts pixel coordinates to cube coordinates.neighbors(qrs:object|tuple|HexCoords) -> set:Return a List of coordinates of neighboring hexagons.distance(obj_a:object|tuple|HexCoords, obj_b:object|tuple|HexCoords) -> int|float:Returns distance from one Object to another in a cube coordinate system.in_range(obj:object|tuple|HexCoords, n:int) -> set:Returns a Set containing the cube coordinates of every hexagon in distance n from obj.line_draw(obj_a:object|tuple|HexCoords, obj_b:object|tuple|HexCoords) -> tuple:Draws a line from one hexagon to another, returns a Tuple containing the hexagons with the center closest to the line.dist_lim_flood_fill(start_obj:object|tuple|HexCoords, n:int, obj_grp:list|set, movement_var:str=None) -> set:All cube coordinates within n distance from an Object, factoring in movement_var (variable if 0 blocks object traversability).GraphMatrix.update_entry(self, from_coord:object|tuple|HexCoords, to_coord:object|tuple|HexCoords, movement_cost:int|float) -> None:Add or update a one-directional entry in the adjacency matrix.GraphMatrix.del_entry(self, from_coord:object|tuple|HexCoords, to_coord:object|tuple|HexCoords) -> None:Delete a one-directional entry in the adjacency matrix. Does not raise an Error or Warning if no entry matching the input exists.GraphMatrix.connected(self, from_coord:object|tuple|HexCoords) -> set:Return all connected coordinates. Returns None, in case of there aren't being any.GraphMatrix.get_movement_cost(self, from_coord:object|tuple|HexCoords, to_coord:object|tuple|HexCoords) -> int|float:Get the movement cost from one Object or coordinate to another.GraphMatrix.breadth_first_search(start:object|tuple|HexCoords, goal:object|tuple|HexCoords, test_accessibility:bool=False) -> list:Algorithm for searching a tree data structure for a node that satisfies a given property.GraphMatrix.dijkstras_algorithm(start:object|tuple|HexCoords, goal:object|tuple|HexCoords, test_accessibility:bool=False) -> list:Supports weighted movement cost.GraphMatrix.a_star_algorithm(start:object|tuple|HexCoords, goal:object|tuple|HexCoords, test_accessibility:bool=False) -> list:Modified version of Dijkstra’s Algorithm that is optimized for a single destination. It prioritizes paths that seem to be leading closer to a goal.To DoList of issues to be solved before the package will be released at version 1.0eliminate dependency on NumPy and Pandas, by replacing the interim solution for graph matrix storage with built-in typesadd example.pyidentify and test for edge casesmodify round_container to work for infinitely nested containersadd a setthat is ?automatically? updated, containing all coordinates which are linked in GraphMatrixwrite a working testgrp_teardownreplace "block_var" artifacts with movement_cost = 0 -> tile = blocked for "dist_lim_flood_fill"add pathfinding functions as methods to GraphMatrixnegative movement_cost for blocking movement instead of 0 ?refactor code to be organized around one class handling all the logic affecting objects in passed container (like observer.py etc.), keep functions only implementation ???find out why and fix imports in test not working (RectCoords instead of hl.RectCoords etc.)add a raise error, if a goal or start is passed to pathfinding, which is inaccessible (has negative movement_cost)add a "if not connected functionality" to pathfinding algorithmsupdate TestTestgrpGenerator unittest, block_var to movement_costadd a mechanic to GraphMatrix methods del_entry and update_entry that update GraphMatrix.matrix_coordsadd List and Dictionary as a return option for functions, which offered only Tuple and RectCoords/HexCoordsupdate remaining unittests to test for dictionary and list outputs, as well as modified GraphMatrix pathfindingReferencesredblobgames.com (Amit Patel):HexagonsHexagons Implementation GuideHexagons Generated CodePathfindingPathfinding Implementation GuideNumPy Style Guide:syntax and best practices for docstringsOther packages on the Python Package Index offering hexagonal grid functionalities:Prior to deciding to make Hexlogic a package available on PyPI, I researched other available
implementations to check if it had already been implemented in the way envisioned and on the
other hand to learn from other people's work. Below is a list of the packages found which are thematically
closest to my own. Check them out! Depending on what you're trying to do some of them might be better
suited for your needs.HexPexhttps://pypi.org/project/hexpex/OOP implementation based on single coordinatecube and axial coordinates, directions (facing up)distances, neighbors, range, rings, rotation, spiralHexutilhttps://pypi.org/project/hexutil/OOP implementation based on single coordinatecomprehensive implementation of "pointy side up" hexagonal game focused map logicincluding pathfinding and field of viewpygame-pguhttps://pypi.org/project/pygame-pgu/a COMPREHENSIVE collection of handy modules and scripts for PyGameHexGridhttps://pypi.org/project/hexgrid/"Settlers of Catan" - grid, complete with tile, edge and node coordinatesUsed by JSettlers2, described in "Thomas, Robert S. 2003. Real-time Decision Making for Adversarial Environments Using a Plan-based Heuristic. PhD thesis, Northwestern University"