I stumbled my way through some Rust yesterday and today to make a simple ASCII art API. It can draw lines, circles, and a canvas.
use std::collections::HashMap;
#[derive(PartialEq, PartialOrd, Debug)]
struct Point(u32, u32);
#[derive(PartialEq, PartialOrd, Debug)]
struct Dimension(u32, u32);
#[derive(PartialEq, PartialOrd, Debug)]
struct Rectangle(Point, Point);
#[derive(PartialEq, PartialOrd, Debug, Hash, Eq)]
struct Coordinate(u32, u32);
#[derive(Clone, Copy, Debug)]
enum Shape {
Canvas,
Circle,
HorizontalLine,
VerticalLine,
DiagonalLineLeftToRight,
DiagonalLineRightToLeft,
}
fn canvas_index_to_coords(i: u32, num: u32) -> Coordinate {
if i < num { Coordinate(i, 0) }
else { Coordinate(i % num, i / num) }
}
fn write(coords: &Coordinate, chr: char, num: u32) {
if coords.0 == num - 1 { println!("{}", chr); }
else { print!("{} ", chr); }
}
fn combine(a: HashMap<Coordinate, Shape>, b: HashMap<Coordinate, Shape>) -> HashMap<Coordinate, Shape> {
let mut combined = HashMap::new();
for (key, val) in a {
combined.insert(key, val);
}
for (key, val) in b {
combined.insert(key, val);
}
combined
}
fn canvas(size: Dimension) -> HashMap<Coordinate, Shape> {
let mut canvas_coords = HashMap::new();
for i in 0..(size.0 * size.1) {
canvas_coords.insert(canvas_index_to_coords(i, size.0), Shape::Canvas);
}
canvas_coords
}
fn circle(radius: u32, point: Point) -> HashMap<Coordinate, Shape> {
let x0 = point.0;
let y0 = point.1;
let mut x = radius;
let mut y = 0;
let mut err: i32 = 0;
let mut coords = HashMap::new();
while x >= y {
coords.insert(Coordinate(x0 + x, y0 + y), Shape::Circle);
coords.insert(Coordinate(x0 + y, y0 + x), Shape::Circle);
coords.insert(Coordinate(x0 - y, y0 + x), Shape::Circle);
coords.insert(Coordinate(x0 - x, y0 + y), Shape::Circle);
coords.insert(Coordinate(x0 - x, y0 - y), Shape::Circle);
coords.insert(Coordinate(x0 - y, y0 - x), Shape::Circle);
coords.insert(Coordinate(x0 + y, y0 - x), Shape::Circle);
coords.insert(Coordinate(x0 + x, y0 - y), Shape::Circle);
y += 1;
err += 1 + 2 * y as i32;
if 2 * (err - x as i32) + 1 > 0
{
x -= 1;
err += 1 - 2 * x as i32;
}
}
coords
}
fn line_shape(rectangle: Rectangle) -> Shape {
let x0 = (rectangle.0).0;
let y0 = (rectangle.0).1;
let x1 = (rectangle.1).0;
let y1 = (rectangle.1).1;
if x0 != x1 && y0 > y1 { Shape::DiagonalLineLeftToRight }
else if x0 != x1 && y0 < y1 { Shape::DiagonalLineRightToLeft }
else if y0 == y1 { Shape::HorizontalLine }
else { Shape::VerticalLine }
}
fn line(rectangle: Rectangle) -> HashMap<Coordinate, Shape> {
let x0 = (rectangle.0).0 as i32;
let y0 = (rectangle.0).1 as i32;
let x1 = (rectangle.1).0 as i32;
let y1 = (rectangle.1).1 as i32;
let dx = ((x1 - x0)).abs();
let sx: i32 = if x0 < x1 { 1 } else { -1 };
let dy = ((y1 - y0)).abs();
let sy: i32 = if y0 < y1 { 1 } else { -1 };
let tmp = if dx > dy { dx } else { -dy };
let mut err = tmp / 2;
let mut e2;
let mut x0_m = x0;
let mut y0_m = y0;
let mut coords = HashMap::new();
let line_shape = line_shape(rectangle);
loop {
coords.insert(Coordinate(x0_m as u32, y0_m as u32), line_shape);
if x0_m == x1 as i32 && y0_m == y1 as i32 {
break;
}
e2 = err;
if e2 > -dx {
err -= dy;
x0_m += sx;
}
if e2 < dy {
err += dx;
y0_m += sy;
}
}
coords
}
fn draw(num: u32, coords: HashMap<Coordinate, Shape>) {
let mut vec = Vec::new();
for (key, value) in &coords {
vec.push((key, value));
}
vec.sort_by_key(|&(coord, _)| coord.0);
vec.sort_by_key(|&(coord, _)| (coord.1 as i32) * -1);
for (coord, shape) in vec {
match shape {
&Shape::Canvas => write(coord, ' ', num),
&Shape::Circle => write(coord, 'o', num),
&Shape::HorizontalLine => write(coord, '-', num),
&Shape::VerticalLine => write(coord, '|', num),
&Shape::DiagonalLineLeftToRight => write(coord, '\\', num),
&Shape::DiagonalLineRightToLeft => write(coord, '/', num),
}
}
}
fn main() {
let num = 10;
let canvas_size = Dimension(num, num);
let point_1 = Point(2, 2);
let point_2 = Point(3, 4);
let point_3 = Point(7, 7);
let rectangle = Rectangle(Point(0, 0), Point(0, 9));
draw(num, combine(canvas(canvas_size), combine(circle(1, point_3), combine(circle(1, point_2), combine(circle(1, point_1), line(rectangle))))));
}
I'm sure there are some obvious errors here, like bounds checking when drawing the canvas, but I would be more interested in feedback of general functional style (never worked with a FP language) and also references/copy, which I'm not sure I got correctly. Also the line/circle algorithms are really hard to build nicely in FP I think.