Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoSign in
Socket

board

Package Overview
Dependencies
Maintainers
2
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

board - pypi Package Compare versions

Comparing version
0.0.0.post0
to
1.0
+109
-24
board.egg-info/PKG-INFO

@@ -1,10 +0,11 @@

Metadata-Version: 1.1
Metadata-Version: 1.2
Name: board
Version: 0.0.0.post0
Version: 1.0
Summary: Standard Board mechanism for Dojo tasks
Home-page: https://github.com/graingert/dojo-board
Author: Thomas Grainger
Author-email: board@graingert.co.uk
Home-page: https://github.com/tjguk/dojo-board
Author: Tim Golden
Author-email: mail@timgolden.me.uk
Maintainer: Tim Golden
Maintainer-email: mail@timgolden.me.uk
License: unlicensed
Description-Content-Type: UNKNOWN
Description: Board Game for Python Dojos

@@ -59,3 +60,5 @@ ===========================

infinite size. (So if you have, say, 3 infinite dimensions, you have
the basis for a Minecraft layout). Dimensions are zero-based.
the basis for a Minecraft layout). Dimensions are zero-based and
negative indexes operate as they usually do in Python: working from
the end of the dimension backwards.

@@ -113,3 +116,2 @@ Cells on the board are accessed by item access, eg board[1, 2] or

To read, write and empty the data at a board position, use indexing::

@@ -124,2 +126,5 @@

b1[-1, -1] = "*"
print(b1[2, 2]) # "*"
del b1[0, 0]

@@ -149,15 +154,2 @@ print(b1[0, 0]) # <Empty>

To get a crude view of the contents of the board, use .dump::
b1 = board.Board((3, 3))
b1.populate("abcdef")
b1.dump()
To get a grid view of a 2-dimensional board, use .draw::
b1 = board.Board((3, 3))
b1.populate("OX XXOO ")
b1.draw()
To populate the board from an arbitrary iterator, use .populate::

@@ -189,3 +181,3 @@

The length of the board is the product of its dimension lengths. If any
dimension is infinite, the board length is infinte. NB to find the
dimension is infinite, the board length is infinite. NB to find the
amount of data on the board, use lendata::

@@ -208,2 +200,11 @@

For the common case of slicing a board around its occupied space,
use .occupied_board::
b1 = board.Board((3, 3))
b1.populate("abcd")
b1.draw()
b2 = b1.occupied_board()
b2.draw()
To test whether a position is on any edge of the board, use .is_edge::

@@ -222,3 +223,3 @@

EXPERIMENTAL: To iterate over all the coords in the rectangular space between
To iterate over all the coords in the rectangular space between
two corners, use .itercoords::

@@ -229,3 +230,3 @@

EXPERIMENTAL: To iterate over all the on-board positions from one point in a
To iterate over all the on-board positions from one point in a
particular direction, use .iterline::

@@ -240,2 +241,41 @@

or .iterlinedata to generate the data at each point::
b1 = board.Board((3, 3))
b1.populate("ABCDEFGHJ")
start_from = 1, 1
direction = 1, 0
list(b1.iterlinedata(start_from, direction)) # ['A', 'D', 'G']
Both iterline and iterdata can take a maximum number of steps, eg for
games like Connect 4 or Battleships::
b1 = board.Board((8, 8))
#
# Draw a Battleship
#
b1.populate("BBBB", b1.iterline((2, 2), (1, 0)))
As a convenience for games which need to look for a run of so many
things, the .run_of_n method combines iterline with data to yield
every possible line on the board which is of a certain length along
with its data::
b1 = board.Board((3, 3))
b1[0, 0] = 'X'
b1[1, 1] = 'O'
b1[0, 1] = 'X'
for line, data in b1.runs_of_n(3):
if all(d == "O" for d in data):
print("O wins")
break
elif all(d == "X" for d in data):
print("X wins")
break
To iterate over the corners of the board, use .corners::
b1 = board.Board((3, 3))
corners() # [(0, 0), (0, 2), (2, 0), (2, 2)]
Properties

@@ -261,2 +301,47 @@ ----------

Display the Board
-----------------
To get a crude view of the contents of the board, use .dump::
b1 = board.Board((3, 3))
b1.populate("abcdef")
b1.dump()
To get a grid view of a 2-dimensional board, use .draw::
b1 = board.Board((3, 3))
b1.populate("OX XXOO ")
b1.draw()
If you don't want the borders drawn, eg because you're using the board
to render ASCII art, pass use_borders=False::
b1 = board.Board((8, 8))
for coord in b1.iterline((0, 0), (1, 1)):
b1[coord] = "*"
for coord in b1.iterline((7, 0), (-1, 1)):
b1[coord] = "*"
b1.draw(use_borders=False)
To render to an image using Pillow (which isn't a hard dependency) use paint.
The default renderer treats the data items as text and renders then, scaled
to fit, into each cell. This works, obviously, for things like Noughts & Crosses
assuming that you store something like "O" and "X". But it also works for
word searches and even simple battleships where the data items are objects
whose __str__ returns blank (for undiscovered), "+" for a single hit, and "*"
for a destroyed vessel::
b1 = board.Board((3, 3))
b1[0, 0] = "X"
b1[1, 1] = "O"
b1[0, 2] = "X"
b1.paint("board.png")
# ... and now look at board.png
The text painting is achieved internally by means of a callback called
text_sprite. An alternative ready-cooked callback for paint() is
imagefile_sprite. This looks for a .png file in the current directory
(or another; you can specify).
Local and Global coordinates

@@ -263,0 +348,0 @@ ----------------------------

+303
-49

@@ -1,6 +0,8 @@

# -*- coding: utf-8 -*-
# -*- coding: utf-8-*- # Encoding cookie added by Mu Editor
"""Board -- an n-dimensional board with support for iteration, containership and slicing
Boards can have any number of dimensions, any of which can be infinite. Boards
can be sliced [:1, :2], returning a linked-copy, or copied (.copy), returning a snapshot copy.
can be sliced [:1, :2], returning a linked-copy, or copied (.copy), returning a
snapshot copy.

@@ -10,5 +12,6 @@ Boards can be iterated over for coordinates or data (.iterdata). There are also

the bounding box of occupied data (.occupied), all the coordinates in a space
in n-dimensions (.itercoords) and other.
in n-dimensions (.itercoords) and others.
"""
# testing
#

@@ -30,3 +33,9 @@ # The semantics of 3.x range are broadly equivalent

import itertools
import io
try:
from PIL import Image, ImageDraw, ImageFont
except ImportError:
Image = None
class _Infinity(int):

@@ -61,2 +70,3 @@

return False
__nonzero__ = __bool__

@@ -155,2 +165,57 @@ Empty = _Empty()

def _centred_coord(outer_size, inner_size):
"""Given an outer and an inner size, calculate the top-left coordinates
which the inner image should position at to be centred within the outer
image
"""
outer_w, outer_h = outer_size
inner_w, inner_h = inner_size
return round((outer_w - inner_w) / 2), round((outer_h - inner_h) / 2)
def text_sprite(font_name="arial", colour="#0000ff"):
"""Text sprite generator callback from Board.paint
Convert the object to text of approximately the right size for
the cell being painted. Typically this will be used for one or
two letter objects, but it will work for any object which can
meaningfully be converted to text
"""
def _text_sprite(obj, size):
#
# Very roughly, one point is three quarters of
# a pixel. We pick a point size which will fill
# the smaller edge of the cell (if it's not square)
#
point_size = round(min(size) * 0.75)
#
# Create a new transparent image to hold the
# text. Draw the text into it in blue, centred,
# using the font requested, and return the resulting image
#
image = Image.new("RGBA", size, (255, 255, 255, 0))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("%s.ttf" % font_name, point_size)
text = str(obj)
draw.text(_centred_coord(size, font.getsize(text)), text, font=font, fill=colour)
return image
return _text_sprite
def imagefile_sprite(directory=".", extension=".png"):
"""Image sprite generator callback for Board.paint
Given the text form of an object, look for an image file in the
stated directory [default: current] and return it, scaled to size.
"""
def _imagefile_sprite(obj, size):
image = Image.open(os.path.join(directory, "%s%s" % (obj, extension)))
image.thumbnail(size)
return image
return _imagefile_sprite
class Board(object):

@@ -166,6 +231,6 @@ """Board - represent a board of n dimensions, each possibly infinite.

b[2, 2] = "*"
print(b[2, 2])
b.draw()
"""
class BoardError(BaseException): pass
class BoardError(Exception): pass
class InvalidDimensionsError(BoardError): pass

@@ -179,2 +244,6 @@ class OutOfBoundsError(BoardError): pass

raise self.InvalidDimensionsError("The board must have at least one dimension")
try:
iter(dimension_sizes)
except TypeError:
raise self.InvalidDimensionsError("Dimensions must be iterable (eg a tuple), not {}".format(type(dimension_sizes).__name__))
if any(d <= 0 for d in dimension_sizes):

@@ -192,2 +261,3 @@ raise self.InvalidDimensionsError("Each dimension must be >= 1")

self._offset_from_global = _offset_from_global or tuple(0 for _ in self.dimensions)
self._sprite_cache = {}

@@ -256,2 +326,4 @@ def __repr__(self):

def _is_in_bounds(self, coord):
"""Is a given coordinate within the space of this board?
"""
if len(coord) != len(self.dimensions):

@@ -264,2 +336,5 @@ raise self.InvalidDimensionsError(

def _check_in_bounds(self, coord):
"""If a given coordinate is not within the space of this baord, raise
an OutOfBoundsError
"""
if not self._is_in_bounds(coord):

@@ -269,7 +344,11 @@ raise self.OutOfBoundsError("{} is out of bounds for {}".format(coord, self))

def __contains__(self, coord):
"""Implement <coord> in <board>
"""
return self._is_in_bounds(coord)
def __iter__(self):
"""Iterator over all combinations of coordinates. If you need
data, use iterdata.
"""Implement for <coord> in <board>
Iterate over all combinations of coordinates. If you need data,
use iterdata().
"""

@@ -301,3 +380,5 @@ # If all the dimensions are finite (the simplest and most common

def iterdata(self):
"""Generate the list of data in local coordinate terms.
"""Implement: for (<coord>, <data>) in <board>
Generate the list of data in local coordinate terms.
"""

@@ -314,3 +395,3 @@ for gcoord, value in self._data.items():

def iterline(self, coord, vector):
def iterline(self, coord, vector, max_steps=None):
"""Generate coordinates starting at the given one and moving

@@ -326,6 +407,27 @@ in the direction of the vector until the edge of the board is

raise InvalidDimensionsError()
n_steps = 0
while self._is_in_bounds(coord):
yield coord
n_steps += 1
if max_steps is not None and n_steps == max_steps:
break
coord = tuple(c + v for (c, v) in zip(coord, vector))
def iterlinedata(self, coord, vector, max_steps=None):
"""Use .iterline to generate the data starting at the given
coordinate and moving in the direction of the vector until
the edge of the board is reached or the maximum number of
steps has been taken (if specified).
This could be used, eg, to see whether you have a battleship
or a word in a word-search
"""
for coord in self.iterline(coord, vector, max_steps):
yield self[coord]
def corners(self):
dimension_bounds = [(0, len(d) -1 if d.is_finite else Infinity) for d in self.dimensions]
return list(itertools.product(*dimension_bounds))
def copy(self, with_data=True):

@@ -365,4 +467,9 @@ """Return a new board with the same dimensionality as the present one.

def __setitem__(self, coord, value):
coord = self._normalised_coord(coord)
self._data[coord] = value
if all(isinstance(c, (int, long)) for c in coord):
coord = self._normalised_coord(coord)
self._data[coord] = value
#~ elif all(isinstance(i, (int, long, slice)) for i in item):
#~ return self._slice(item)
else:
raise TypeError("{} can only be indexed by int or slice".format(self.__class__.__name__))

@@ -453,2 +560,9 @@ def __delitem__(self, coord):

def occupied_board(self):
"""Return a sub-board containing only the portion of this board
which contains data.
"""
(x0, y0), (x1, y1) = self.occupied()
return self[x0:x1+1, y0:y1+1]
def itercoords(self, coord1, coord2):

@@ -463,29 +577,60 @@ """Iterate over the coordinates in between the two coordinates.

#
# TODO: what happens if the coords are reversed?
#
for coord in itertools.product(*(range(i1, 1 + i2) for (i1, i2) in zip(coord1, coord2))):
for coord in itertools.product(*(range(i1, 1 + i2) for (i1, i2) in zip(*sorted([coord1, coord2])))):
yield coord
def neighbours(self, coord):
"""For a given coordinate, yield each of its nearest
neighbours along all dimensions.
def neighbours(self, coord, include_diagonals=True):
"""Iterate over all the neighbours of a coordinate
For a given coordinate, yield each of its nearest neighbours along
all dimensions, including diagonal neighbours if requested (the default)
"""
gcoord = self._normalised_coord(coord)
offsets = itertools.product(*[(-1, 0, 1) for d in self.dimensions])
for offset in offsets:
if all(o == 0 for o in offsets):
continue
#
# Diagonal offsets have no zero component
#
if include_diagonals or any(o == 0 for o in offset):
neighbour = tuple(c + o for (c, o) in zip(coord, offset))
if self._is_in_bounds(neighbour):
yield neighbour
def runs_of_n(self, n, ignore_reversals=True):
"""Iterate over all dimensions to yield runs of length n
Yield each run of n cells as a tuple of coordinates and a tuple
of data. If ignore_reversals is True (the default) then don't
yield the same line in the opposite direction.
This is useful for, eg, noughts and crosses, battleship or connect 4
where the game engine has to detect a line of somethings in a row.
"""
all_zeroes = tuple(0 for _ in self.dimensions)
all_offsets = itertools.product(*[(-1, 0, 1) for d in self.dimensions])
offsets = [o for o in all_offsets if o != all_zeroes]
already_seen = set()
#
# Find the bounding box for all coordinates surrounding coord.
# Then produce every coordinate in that space, selecting only
# those which fall onto the local board.
# This is brute force: running for every cell and looking in every
# direction. We check later whether we've run off the board (as
# the resulting line will fall short). We might do some kind of
# pre-check here, but we have to check against every direction
# of every dimension, which would complicate this code
#
mins = [x - 1 for x in coord]
maxs = [x + 1 for x in coord]
gcoords = set(c for c in itertools.product(*zip(mins, gcoord, maxs)) if self._is_in_bounds(c))
#
# ... and remove the coordinate itself
#
gcoords.remove(coord)
for cell in iter(self):
for direction in offsets:
line = tuple(self.iterline(cell, direction, n))
if len(line) == n:
if line in already_seen:
continue
already_seen.add(line)
#
# Most of the time you don't want the same line twice,
# once in each direction.
#
if ignore_reversals:
already_seen.add(line[::-1])
for g in gcoords:
yield self._from_global(g)
yield line, [self[c] for c in line]

@@ -502,9 +647,22 @@ def is_edge(self, coord):

def populate(self, iterable):
"""Populate the entire board from an iterable
def is_corner(self, coord):
"""Determine whether a position is on any corner of the board
The iterable can be shorter or longer than the board. The two
are zipped together so the population will stop when the shorter
is exhausted.
Infinite dimensions only have a lower edge (zero); finite dimensions
have a lower and an upper edge.
"""
self._check_in_bounds(coord)
dimension_bounds = ((0, len(d) - 1 if d.is_finite else 0) for d in self.dimensions)
return all(c in bounds for (c, bounds) in zip(coord, dimension_bounds))
def populate(self, iterable, coord_iterable=None):
"""Populate all or part of the board from an iterable
The population iterable can be shorter or longer than the board
iterable. The two are zipped together so the population will stop
when the shorter is exhausted.
If no iterable is supplied for cooordinates, the whole board is
populated.
This is a convenience method both to assist testing and also for,

@@ -515,25 +673,121 @@ eg, games like Boggle or word-searches where the board must start

supplied.
With a coordinate iterable this could be used, for example, to combine
iterline and a list of objects to populate data on a Battleships board.
"""
for coord, value in zip(self, iter(iterable)):
if coord_iterable is None:
board_iter = iter(self)
else:
board_iter = iter(coord_iterable)
for coord, value in zip(board_iter, iter(iterable)):
self[coord] = value
def draw(self):
for line in self.drawn():
def draw(self, callback=str, use_borders=True):
"""Draw the board in a very simple text layout
By default data items are rendered as strings. If a different callback
is supplied, it is called with the data item and should return a string.
The idea is that items can be "hidden" from the board, or rendered
differently according to some state. Think of Battleships where the
same object can be hidden, revealed, or sunk.
"""
for line in self.drawn(callback, use_borders):
print(line)
def drawn(self):
if len(self.dimensions) != 2 or any(d.is_infinite for d in self.dimensions):
def drawn(self, callback=str, use_borders=True):
if len(self.dimensions) != 2 or self.has_infinite_dimensions:
raise self.BoardError("Can only draw a finite 2-dimensional board")
data = dict((coord, str(v)) for (coord, v) in self.iterdata())
cell_width = len(max((str(v) for v in data.values()), key=len))
corner, hedge, vedge = "+", "-", "|"
divider = (corner + (hedge * cell_width)) * len(self.dimensions[0]) + corner
data = dict((coord, callback(v)) for (coord, v) in self.iterdata())
if data:
cell_w = len(max((v for v in data.values()), key=len))
else:
cell_w = 1
if use_borders:
corner, hedge, vedge = "+", "-", "|"
else:
corner = hedge = vedge = ""
divider = (corner + (hedge * cell_w)) * len(self.dimensions[0]) + corner
yield divider
if use_borders: yield divider
for y in self.dimensions[1]:
yield vedge + vedge.join(data.get((x, y), "").center(cell_width) for x in self.dimensions[0]) + vedge
yield divider
yield vedge + vedge.join(data.get((x, y), "").center(cell_w) for x in self.dimensions[0]) + vedge
if use_borders: yield divider
def painted(self, callback, size, background_colour, use_borders):
if not Image:
raise NotImplementedError("Painting is not available unless Pillow is installed")
if len(self.dimensions) != 2 or self.has_infinite_dimensions:
raise self.BoardError("Can only paint a finite 2-dimensional board")
#
# Construct a board of the requested size, containing
# cells sized equally to fit within the size for each
# of the two dimensions. Keep the border between them
# proportional to the overall image size
#
n_wide = len(self.dimensions[0])
n_high = len(self.dimensions[1])
image = Image.new("RGBA", size)
if use_borders:
h_border = image.height / 80
v_border = image.width / 80
else:
h_border = v_border = 0
draw = ImageDraw.Draw(image)
drawable_w = image.width - (1 + n_wide) * h_border
cell_w = round(drawable_w / n_wide)
drawable_h = image.height - (1 + n_high) * v_border
cell_h = round(drawable_h / n_high)
for (x, y) in self:
obj = self[x, y]
#
# If the cell is empty: draw nothing
# Try to fetch the relevant sprite from the cache
# If the sprite is not cached, generate and cache it
# If the sprite is larger than the cell, crop it to the correct
# size, maintaining its centre
#
if obj is Empty:
sprite = None
else:
try:
sprite = self._sprite_cache[obj]
except KeyError:
sprite = self._sprite_cache[obj] = callback(obj, (cell_w, cell_h))
if sprite.width > cell_w or sprite.height > cell_h:
box_x = (sprite.width - cell_w) / 2
box_y = (sprite.height - cell_h) / 2
sprite = sprite.crop((box_x, box_y, cell_w, cell_h))
#
# Draw the cell and any sprite within it
#
cell_x = round(h_border + ((cell_w + h_border) * x))
cell_y = round(v_border + ((cell_h + v_border) * y))
draw.rectangle((cell_x, cell_y, cell_x + cell_w, cell_y + cell_h), fill=background_colour)
if sprite:
x_offset, y_offset = _centred_coord((cell_w, cell_h), sprite.size)
image.alpha_composite(sprite, (cell_x + x_offset, cell_y + y_offset))
#
# Return the whole image as PNG-encoded bytes
#
f = io.BytesIO()
image.save(f, "PNG")
return f.getvalue()
def paint(self, filepath, callback=text_sprite(), size=(800, 800), background_colour="#ffffcc", use_borders=True):
with open(filepath, "wb") as f:
f.write(self.painted(callback, size, background_colour, use_borders))
def cornerposts(dimensions):
for d in dimensions:
yield 0
if d.is_finite:
yield len(d)
if __name__ == '__main__':
pass
pass
+109
-24

@@ -1,10 +0,11 @@

Metadata-Version: 1.1
Metadata-Version: 1.2
Name: board
Version: 0.0.0.post0
Version: 1.0
Summary: Standard Board mechanism for Dojo tasks
Home-page: https://github.com/graingert/dojo-board
Author: Thomas Grainger
Author-email: board@graingert.co.uk
Home-page: https://github.com/tjguk/dojo-board
Author: Tim Golden
Author-email: mail@timgolden.me.uk
Maintainer: Tim Golden
Maintainer-email: mail@timgolden.me.uk
License: unlicensed
Description-Content-Type: UNKNOWN
Description: Board Game for Python Dojos

@@ -59,3 +60,5 @@ ===========================

infinite size. (So if you have, say, 3 infinite dimensions, you have
the basis for a Minecraft layout). Dimensions are zero-based.
the basis for a Minecraft layout). Dimensions are zero-based and
negative indexes operate as they usually do in Python: working from
the end of the dimension backwards.

@@ -113,3 +116,2 @@ Cells on the board are accessed by item access, eg board[1, 2] or

To read, write and empty the data at a board position, use indexing::

@@ -124,2 +126,5 @@

b1[-1, -1] = "*"
print(b1[2, 2]) # "*"
del b1[0, 0]

@@ -149,15 +154,2 @@ print(b1[0, 0]) # <Empty>

To get a crude view of the contents of the board, use .dump::
b1 = board.Board((3, 3))
b1.populate("abcdef")
b1.dump()
To get a grid view of a 2-dimensional board, use .draw::
b1 = board.Board((3, 3))
b1.populate("OX XXOO ")
b1.draw()
To populate the board from an arbitrary iterator, use .populate::

@@ -189,3 +181,3 @@

The length of the board is the product of its dimension lengths. If any
dimension is infinite, the board length is infinte. NB to find the
dimension is infinite, the board length is infinite. NB to find the
amount of data on the board, use lendata::

@@ -208,2 +200,11 @@

For the common case of slicing a board around its occupied space,
use .occupied_board::
b1 = board.Board((3, 3))
b1.populate("abcd")
b1.draw()
b2 = b1.occupied_board()
b2.draw()
To test whether a position is on any edge of the board, use .is_edge::

@@ -222,3 +223,3 @@

EXPERIMENTAL: To iterate over all the coords in the rectangular space between
To iterate over all the coords in the rectangular space between
two corners, use .itercoords::

@@ -229,3 +230,3 @@

EXPERIMENTAL: To iterate over all the on-board positions from one point in a
To iterate over all the on-board positions from one point in a
particular direction, use .iterline::

@@ -240,2 +241,41 @@

or .iterlinedata to generate the data at each point::
b1 = board.Board((3, 3))
b1.populate("ABCDEFGHJ")
start_from = 1, 1
direction = 1, 0
list(b1.iterlinedata(start_from, direction)) # ['A', 'D', 'G']
Both iterline and iterdata can take a maximum number of steps, eg for
games like Connect 4 or Battleships::
b1 = board.Board((8, 8))
#
# Draw a Battleship
#
b1.populate("BBBB", b1.iterline((2, 2), (1, 0)))
As a convenience for games which need to look for a run of so many
things, the .run_of_n method combines iterline with data to yield
every possible line on the board which is of a certain length along
with its data::
b1 = board.Board((3, 3))
b1[0, 0] = 'X'
b1[1, 1] = 'O'
b1[0, 1] = 'X'
for line, data in b1.runs_of_n(3):
if all(d == "O" for d in data):
print("O wins")
break
elif all(d == "X" for d in data):
print("X wins")
break
To iterate over the corners of the board, use .corners::
b1 = board.Board((3, 3))
corners() # [(0, 0), (0, 2), (2, 0), (2, 2)]
Properties

@@ -261,2 +301,47 @@ ----------

Display the Board
-----------------
To get a crude view of the contents of the board, use .dump::
b1 = board.Board((3, 3))
b1.populate("abcdef")
b1.dump()
To get a grid view of a 2-dimensional board, use .draw::
b1 = board.Board((3, 3))
b1.populate("OX XXOO ")
b1.draw()
If you don't want the borders drawn, eg because you're using the board
to render ASCII art, pass use_borders=False::
b1 = board.Board((8, 8))
for coord in b1.iterline((0, 0), (1, 1)):
b1[coord] = "*"
for coord in b1.iterline((7, 0), (-1, 1)):
b1[coord] = "*"
b1.draw(use_borders=False)
To render to an image using Pillow (which isn't a hard dependency) use paint.
The default renderer treats the data items as text and renders then, scaled
to fit, into each cell. This works, obviously, for things like Noughts & Crosses
assuming that you store something like "O" and "X". But it also works for
word searches and even simple battleships where the data items are objects
whose __str__ returns blank (for undiscovered), "+" for a single hit, and "*"
for a destroyed vessel::
b1 = board.Board((3, 3))
b1[0, 0] = "X"
b1[1, 1] = "O"
b1[0, 2] = "X"
b1.paint("board.png")
# ... and now look at board.png
The text painting is achieved internally by means of a callback called
text_sprite. An alternative ready-cooked callback for paint() is
imagefile_sprite. This looks for a .png file in the current directory
(or another; you can specify).
Local and Global coordinates

@@ -263,0 +348,0 @@ ----------------------------

+102
-18

@@ -50,3 +50,5 @@ Board Game for Python Dojos

infinite size. (So if you have, say, 3 infinite dimensions, you have
the basis for a Minecraft layout). Dimensions are zero-based.
the basis for a Minecraft layout). Dimensions are zero-based and
negative indexes operate as they usually do in Python: working from
the end of the dimension backwards.

@@ -104,3 +106,2 @@ Cells on the board are accessed by item access, eg board[1, 2] or

To read, write and empty the data at a board position, use indexing::

@@ -115,2 +116,5 @@

b1[-1, -1] = "*"
print(b1[2, 2]) # "*"
del b1[0, 0]

@@ -140,15 +144,2 @@ print(b1[0, 0]) # <Empty>

To get a crude view of the contents of the board, use .dump::
b1 = board.Board((3, 3))
b1.populate("abcdef")
b1.dump()
To get a grid view of a 2-dimensional board, use .draw::
b1 = board.Board((3, 3))
b1.populate("OX XXOO ")
b1.draw()
To populate the board from an arbitrary iterator, use .populate::

@@ -180,3 +171,3 @@

The length of the board is the product of its dimension lengths. If any
dimension is infinite, the board length is infinte. NB to find the
dimension is infinite, the board length is infinite. NB to find the
amount of data on the board, use lendata::

@@ -199,2 +190,11 @@

For the common case of slicing a board around its occupied space,
use .occupied_board::
b1 = board.Board((3, 3))
b1.populate("abcd")
b1.draw()
b2 = b1.occupied_board()
b2.draw()
To test whether a position is on any edge of the board, use .is_edge::

@@ -213,3 +213,3 @@

EXPERIMENTAL: To iterate over all the coords in the rectangular space between
To iterate over all the coords in the rectangular space between
two corners, use .itercoords::

@@ -220,3 +220,3 @@

EXPERIMENTAL: To iterate over all the on-board positions from one point in a
To iterate over all the on-board positions from one point in a
particular direction, use .iterline::

@@ -231,2 +231,41 @@

or .iterlinedata to generate the data at each point::
b1 = board.Board((3, 3))
b1.populate("ABCDEFGHJ")
start_from = 1, 1
direction = 1, 0
list(b1.iterlinedata(start_from, direction)) # ['A', 'D', 'G']
Both iterline and iterdata can take a maximum number of steps, eg for
games like Connect 4 or Battleships::
b1 = board.Board((8, 8))
#
# Draw a Battleship
#
b1.populate("BBBB", b1.iterline((2, 2), (1, 0)))
As a convenience for games which need to look for a run of so many
things, the .run_of_n method combines iterline with data to yield
every possible line on the board which is of a certain length along
with its data::
b1 = board.Board((3, 3))
b1[0, 0] = 'X'
b1[1, 1] = 'O'
b1[0, 1] = 'X'
for line, data in b1.runs_of_n(3):
if all(d == "O" for d in data):
print("O wins")
break
elif all(d == "X" for d in data):
print("X wins")
break
To iterate over the corners of the board, use .corners::
b1 = board.Board((3, 3))
corners() # [(0, 0), (0, 2), (2, 0), (2, 2)]
Properties

@@ -252,2 +291,47 @@ ----------

Display the Board
-----------------
To get a crude view of the contents of the board, use .dump::
b1 = board.Board((3, 3))
b1.populate("abcdef")
b1.dump()
To get a grid view of a 2-dimensional board, use .draw::
b1 = board.Board((3, 3))
b1.populate("OX XXOO ")
b1.draw()
If you don't want the borders drawn, eg because you're using the board
to render ASCII art, pass use_borders=False::
b1 = board.Board((8, 8))
for coord in b1.iterline((0, 0), (1, 1)):
b1[coord] = "*"
for coord in b1.iterline((7, 0), (-1, 1)):
b1[coord] = "*"
b1.draw(use_borders=False)
To render to an image using Pillow (which isn't a hard dependency) use paint.
The default renderer treats the data items as text and renders then, scaled
to fit, into each cell. This works, obviously, for things like Noughts & Crosses
assuming that you store something like "O" and "X". But it also works for
word searches and even simple battleships where the data items are objects
whose __str__ returns blank (for undiscovered), "+" for a single hit, and "*"
for a destroyed vessel::
b1 = board.Board((3, 3))
b1[0, 0] = "X"
b1[1, 1] = "O"
b1[0, 2] = "X"
b1.paint("board.png")
# ... and now look at board.png
The text painting is achieved internally by means of a callback called
text_sprite. An alternative ready-cooked callback for paint() is
imagefile_sprite. This looks for a .png file in the current directory
(or another; you can specify).
Local and Global coordinates

@@ -254,0 +338,0 @@ ----------------------------

@@ -0,0 +0,0 @@ [bdist_wheel]

+4
-4

@@ -9,3 +9,3 @@ from setuptools import setup

name='board',
version='0.0.0.post0',
version='1.0',
description='Standard Board mechanism for Dojo tasks',

@@ -15,6 +15,6 @@ long_description=readme + '\n\n' + history,

author_email='mail@timgolden.me.uk',
maintainer='Thomas Grainger',
maintainer_email='board@graingert.co.uk',
maintainer='Tim Golden',
maintainer_email='mail@timgolden.me.uk',
license="unlicensed",
url='https://github.com/graingert/dojo-board',
url='https://github.com/tjguk/dojo-board',
py_modules=['board'],

@@ -21,0 +21,0 @@ classifiers=[