#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
The program accept inputs of the form (x,y) – (x,y), (x,y) – (x,y), …
where (x,y) are the coordinates of the line segments.
The program accept an arbitrary number of line segments.
The coordinate range is between 0 and 19 on x and 0 to 9 on y, and the output
is a 20x10 array of ASCII characters.
The program be able to handle any number of lines from any coordinates.
"""
import re
import argparse
POINT_PATTERN = r'\((\d+) *, *(\d+)\)'
LINE_PATTERN = r'{point} *- *{point}'.format(point=POINT_PATTERN)
def parse(input_string, pattern):
"""
parse start of the string using a pattern and return the remainings
:param input_string: a sting
:param pattern: a regex pattern
:return: the matched part and the remaining
:raises: ValueError
"""
if not pattern.startswith('^'):
pattern = '^' + pattern
match = re.search(pattern, input_string)
if not match:
raise ValueError('"{}" is invalid.'.format(input_string))
return match, input_string[len(match.group()):]
def parse_to_integers(input_string):
"""
parse input
:param input_string: input string
:return: a list of (x0, y0, x1, y1)
:raises: ValueError
"""
result = []
matched, remaining = parse(input_string, LINE_PATTERN)
result.append(tuple(map(int, matched.groups())))
while remaining:
matched, remaining = parse(
remaining, ' *, *{}'.format(LINE_PATTERN))
result.append(tuple(map(int, matched.groups())))
return result
def validate_coordinates(canvas_width, canvas_height, x0, y0, x1, y1):
"""
check if the points are not out of the canvas
"""
conditions = [
(0 <= x0 < canvas_width),
(0 <= x1 < canvas_width),
(0 <= y0 < canvas_height),
(0 <= y1 < canvas_height),
]
if not all(conditions):
raise ValueError('({}, {}) - ({}, {}) is out of canvas'.format(
x0, y0, x1, y1
))
return x0, y0, x1, y1
def find_dots(x0, y0, x1, y1):
"""
find dots for for a line on a canvas
"""
adj = x0 - x1
opp = y0 - y1
if adj == 0:
return {y: [x0] for y in range(min(y0, y1), max(y0, y1) + 1)}
if opp == 0:
return {y0: range(min(x0, x1), max(x0, x1) + 1)}
tan = opp / float(adj)
result = {}
if abs(tan) < 1:
points = sorted([(x0, y0), (x1, y1)], key=lambda pt: pt[0])
for x in range(points[0][0], points[1][0] + 1):
y = int(round(points[0][1] + tan * (x - points[0][0])))
result.setdefault(y, []).append(x)
return result
else:
points = sorted([(x0, y0), (x1, y1)], key=lambda pt: pt[1])
for y in range(points[0][1], points[1][1] + 1):
x = int(round(points[0][0] + (y - points[0][1]) / tan))
result.setdefault(y, []).append(x)
return result
def merge_by_row(dots_list):
"""
merge a list of dots by row
:return: a dictionay mapping row to columns
"""
def _merge(x, y):
for k, v in x.items():
x[k] = sorted(list(set(v + y.pop(k, []))))
x.update(y)
return x
return reduce(_merge, dots_list, {})
def render_row(row, canvas_width, symbol, blank):
"""
get the string representation of a row
"""
chars = [symbol if i in row else blank for i in range(canvas_width)]
return ''.join(chars)
def argument_parser():
"""
argumment parser
"""
parser = argparse.ArgumentParser(
description='ascii line art generator')
description = (
'input represeting lines in the format of'
' (x,y) – (x,y), (x,y) – (x,y), …')
parser.add_argument('input', help=description)
parser.add_argument(
'--width', type=int, default=20,
help='width of the canvas[default=20].')
parser.add_argument(
'--height', type=int, default=10,
help='height of the canvas[default=10].')
parser.add_argument(
'--blank', default=' ',
help='symbol representing blank pixel[default=" "].')
parser.add_argument(
'--symbol', default='x',
help='symbol representing a pixel[default="x"].')
return parser
def main(argv=None):
args = argument_parser().parse_args(argv)
print('draw on a {} X {} canvas'.format(args.width, args.height))
try:
lines = [
validate_coordinates(args.width, args.height, *ints)
for ints in parse_to_integers(args.input)
]
except ValueError as exc:
print(exc)
return
dots_list = [find_dots(*line) for line in lines]
rows = merge_by_row(dots_list)
rendered = [
render_row(rows.get(y, []), args.width, args.symbol, args.blank)
for y in range(args.height)
]
print('\n'.join(rendered))
if __name__ == '__main__':
main()