181 lines
5.7 KiB
Rust
181 lines
5.7 KiB
Rust
//@ run-pass
|
|
#![allow(unused_imports)]
|
|
#![allow(non_snake_case)]
|
|
|
|
// ASCII art shape renderer. Demonstrates traits, impls, operator overloading,
|
|
// non-copyable struct, unit testing. To run execute: rustc --test shapes.rs &&
|
|
// ./shapes
|
|
|
|
// Rust's std library is tightly bound to the language itself so it is
|
|
// automatically linked in. However the extra library is designed to be
|
|
// optional (for code that must run on constrained environments like embedded
|
|
// devices or special environments like kernel code) so it must be explicitly
|
|
// linked in.
|
|
|
|
// Extern mod controls linkage. Use controls the visibility of names to modules
|
|
// that are already linked in. Using WriterUtil allows us to use the write_line
|
|
// method.
|
|
|
|
use std::fmt;
|
|
use std::iter::repeat;
|
|
use std::slice;
|
|
|
|
// Represents a position on a canvas.
|
|
#[derive(Copy, Clone)]
|
|
struct Point {
|
|
x: isize,
|
|
y: isize,
|
|
}
|
|
|
|
// Represents an offset on a canvas. (This has the same structure as a Point.
|
|
// but different semantics).
|
|
#[derive(Copy, Clone)]
|
|
struct Size {
|
|
width: isize,
|
|
height: isize,
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
struct Rect {
|
|
top_left: Point,
|
|
size: Size,
|
|
}
|
|
|
|
// Contains the information needed to do shape rendering via ASCII art.
|
|
struct AsciiArt {
|
|
width: usize,
|
|
height: usize,
|
|
fill: char,
|
|
lines: Vec<Vec<char> > ,
|
|
|
|
// This struct can be quite large so we'll disable copying: developers need
|
|
// to either pass these structs around via references or move them.
|
|
}
|
|
|
|
impl Drop for AsciiArt {
|
|
fn drop(&mut self) {}
|
|
}
|
|
|
|
// It's common to define a constructor sort of function to create struct instances.
|
|
// If there is a canonical constructor it is typically named the same as the type.
|
|
// Other constructor sort of functions are typically named from_foo, from_bar, etc.
|
|
fn AsciiArt(width: usize, height: usize, fill: char) -> AsciiArt {
|
|
// Build a vector of vectors containing blank characters for each position in
|
|
// our canvas.
|
|
let lines = vec![vec!['.'; width]; height];
|
|
|
|
// Rust code often returns values by omitting the trailing semi-colon
|
|
// instead of using an explicit return statement.
|
|
AsciiArt {width: width, height: height, fill: fill, lines: lines}
|
|
}
|
|
|
|
// Methods particular to the AsciiArt struct.
|
|
impl AsciiArt {
|
|
fn add_pt(&mut self, x: isize, y: isize) {
|
|
if x >= 0 && x < self.width as isize {
|
|
if y >= 0 && y < self.height as isize {
|
|
// Note that numeric types don't implicitly convert to each other.
|
|
let v = y as usize;
|
|
let h = x as usize;
|
|
|
|
// Vector subscripting will normally copy the element, but &v[i]
|
|
// will return a reference which is what we need because the
|
|
// element is:
|
|
// 1) potentially large
|
|
// 2) needs to be modified
|
|
let row = &mut self.lines[v];
|
|
row[h] = self.fill;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Allows AsciiArt to be converted to a string using the libcore ToString trait.
|
|
// Note that the %s fmt! specifier will not call this automatically.
|
|
impl fmt::Display for AsciiArt {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
// Convert each line into a string.
|
|
let lines = self.lines.iter()
|
|
.map(|line| line.iter().cloned().collect())
|
|
.collect::<Vec<String>>();
|
|
|
|
// Concatenate the lines together using a new-line.
|
|
write!(f, "{}", lines.join("\n"))
|
|
}
|
|
}
|
|
|
|
// This is similar to an interface in other languages: it defines a protocol which
|
|
// developers can implement for arbitrary concrete types.
|
|
trait Canvas {
|
|
fn add_point(&mut self, shape: Point);
|
|
fn add_rect(&mut self, shape: Rect);
|
|
|
|
// Unlike interfaces traits support default implementations.
|
|
// Got an ICE as soon as I added this method.
|
|
fn add_points(&mut self, shapes: &[Point]) { //~ WARN method `add_points` is never used
|
|
for pt in shapes {self.add_point(*pt)};
|
|
}
|
|
}
|
|
|
|
// Here we provide an implementation of the Canvas methods for AsciiArt.
|
|
// Other implementations could also be provided (e.g., for PDF or Apple's Quartz)
|
|
// and code can use them polymorphically via the Canvas trait.
|
|
impl Canvas for AsciiArt {
|
|
fn add_point(&mut self, shape: Point) {
|
|
self.add_pt(shape.x, shape.y);
|
|
}
|
|
|
|
fn add_rect(&mut self, shape: Rect) {
|
|
// Add the top and bottom lines.
|
|
for x in shape.top_left.x..shape.top_left.x + shape.size.width {
|
|
self.add_pt(x, shape.top_left.y);
|
|
self.add_pt(x, shape.top_left.y + shape.size.height - 1);
|
|
}
|
|
|
|
// Add the left and right lines.
|
|
for y in shape.top_left.y..shape.top_left.y + shape.size.height {
|
|
self.add_pt(shape.top_left.x, y);
|
|
self.add_pt(shape.top_left.x + shape.size.width - 1, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rust's unit testing framework is currently a bit under-developed so we'll use
|
|
// this little helper.
|
|
pub fn check_strs(actual: &str, expected: &str) -> bool {
|
|
if actual != expected {
|
|
println!("Found:\n{}\nbut expected\n{}", actual, expected);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
fn test_ascii_art_ctor() {
|
|
let art = AsciiArt(3, 3, '*');
|
|
assert!(check_strs(&art.to_string(), "...\n...\n..."));
|
|
}
|
|
|
|
|
|
fn test_add_pt() {
|
|
let mut art = AsciiArt(3, 3, '*');
|
|
art.add_pt(0, 0);
|
|
art.add_pt(0, -10);
|
|
art.add_pt(1, 2);
|
|
assert!(check_strs(&art.to_string(), "*..\n...\n.*."));
|
|
}
|
|
|
|
|
|
fn test_shapes() {
|
|
let mut art = AsciiArt(4, 4, '*');
|
|
art.add_rect(Rect {top_left: Point {x: 0, y: 0}, size: Size {width: 4, height: 4}});
|
|
art.add_point(Point {x: 2, y: 2});
|
|
assert!(check_strs(&art.to_string(), "****\n*..*\n*.**\n****"));
|
|
}
|
|
|
|
pub fn main() {
|
|
test_ascii_art_ctor();
|
|
test_add_pt();
|
|
test_shapes();
|
|
}
|