]>
Commit | Line | Data |
---|---|---|
223e47cc LB |
1 | // Copyright 2012 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
b7449926 XL |
11 | // run-pass |
12 | #![allow(non_snake_case)] | |
13 | ||
1a4d82fc JJ |
14 | // ASCII art shape renderer. Demonstrates traits, impls, operator overloading, |
15 | // non-copyable struct, unit testing. To run execute: rustc --test shapes.rs && | |
16 | // ./shapes | |
17 | ||
18 | // Rust's std library is tightly bound to the language itself so it is | |
19 | // automatically linked in. However the extra library is designed to be | |
20 | // optional (for code that must run on constrained environments like embedded | |
21 | // devices or special environments like kernel code) so it must be explicitly | |
22 | // linked in. | |
23 | ||
24 | // Extern mod controls linkage. Use controls the visibility of names to modules | |
25 | // that are already linked in. Using WriterUtil allows us to use the write_line | |
26 | // method. | |
27 | ||
28 | use std::fmt; | |
29 | use std::iter::repeat; | |
30 | use std::slice; | |
223e47cc LB |
31 | |
32 | // Represents a position on a canvas. | |
c34b1796 | 33 | #[derive(Copy, Clone)] |
223e47cc | 34 | struct Point { |
c34b1796 AL |
35 | x: isize, |
36 | y: isize, | |
223e47cc LB |
37 | } |
38 | ||
39 | // Represents an offset on a canvas. (This has the same structure as a Point. | |
40 | // but different semantics). | |
c34b1796 | 41 | #[derive(Copy, Clone)] |
970d7e83 | 42 | struct Size { |
c34b1796 AL |
43 | width: isize, |
44 | height: isize, | |
223e47cc LB |
45 | } |
46 | ||
c34b1796 | 47 | #[derive(Copy, Clone)] |
970d7e83 | 48 | struct Rect { |
223e47cc LB |
49 | top_left: Point, |
50 | size: Size, | |
51 | } | |
52 | ||
223e47cc | 53 | // Contains the information needed to do shape rendering via ASCII art. |
970d7e83 | 54 | struct AsciiArt { |
c34b1796 AL |
55 | width: usize, |
56 | height: usize, | |
1a4d82fc JJ |
57 | fill: char, |
58 | lines: Vec<Vec<char> > , | |
223e47cc LB |
59 | |
60 | // This struct can be quite large so we'll disable copying: developers need | |
1a4d82fc | 61 | // to either pass these structs around via references or move them. |
223e47cc LB |
62 | } |
63 | ||
64 | impl Drop for AsciiArt { | |
1a4d82fc | 65 | fn drop(&mut self) {} |
223e47cc LB |
66 | } |
67 | ||
68 | // It's common to define a constructor sort of function to create struct instances. | |
69 | // If there is a canonical constructor it is typically named the same as the type. | |
970d7e83 | 70 | // Other constructor sort of functions are typically named from_foo, from_bar, etc. |
c34b1796 | 71 | fn AsciiArt(width: usize, height: usize, fill: char) -> AsciiArt { |
c1a9b12d SL |
72 | // Build a vector of vectors containing blank characters for each position in |
73 | // our canvas. | |
74 | let lines = vec![vec!['.'; width]; height]; | |
223e47cc LB |
75 | |
76 | // Rust code often returns values by omitting the trailing semi-colon | |
77 | // instead of using an explicit return statement. | |
78 | AsciiArt {width: width, height: height, fill: fill, lines: lines} | |
79 | } | |
80 | ||
81 | // Methods particular to the AsciiArt struct. | |
970d7e83 | 82 | impl AsciiArt { |
c34b1796 AL |
83 | fn add_pt(&mut self, x: isize, y: isize) { |
84 | if x >= 0 && x < self.width as isize { | |
85 | if y >= 0 && y < self.height as isize { | |
223e47cc | 86 | // Note that numeric types don't implicitly convert to each other. |
c34b1796 AL |
87 | let v = y as usize; |
88 | let h = x as usize; | |
223e47cc LB |
89 | |
90 | // Vector subscripting will normally copy the element, but &v[i] | |
91 | // will return a reference which is what we need because the | |
92 | // element is: | |
93 | // 1) potentially large | |
94 | // 2) needs to be modified | |
95 | let row = &mut self.lines[v]; | |
96 | row[h] = self.fill; | |
97 | } | |
98 | } | |
99 | } | |
100 | } | |
101 | ||
1a4d82fc | 102 | // Allows AsciiArt to be converted to a string using the libcore ToString trait. |
223e47cc | 103 | // Note that the %s fmt! specifier will not call this automatically. |
c34b1796 | 104 | impl fmt::Display for AsciiArt { |
1a4d82fc | 105 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
223e47cc | 106 | // Convert each line into a string. |
1a4d82fc JJ |
107 | let lines = self.lines.iter() |
108 | .map(|line| line.iter().cloned().collect()) | |
109 | .collect::<Vec<String>>(); | |
223e47cc LB |
110 | |
111 | // Concatenate the lines together using a new-line. | |
c1a9b12d | 112 | write!(f, "{}", lines.join("\n")) |
223e47cc LB |
113 | } |
114 | } | |
115 | ||
116 | // This is similar to an interface in other languages: it defines a protocol which | |
117 | // developers can implement for arbitrary concrete types. | |
970d7e83 | 118 | trait Canvas { |
223e47cc LB |
119 | fn add_point(&mut self, shape: Point); |
120 | fn add_rect(&mut self, shape: Rect); | |
121 | ||
122 | // Unlike interfaces traits support default implementations. | |
123 | // Got an ICE as soon as I added this method. | |
970d7e83 | 124 | fn add_points(&mut self, shapes: &[Point]) { |
85aaf69f | 125 | for pt in shapes {self.add_point(*pt)}; |
223e47cc LB |
126 | } |
127 | } | |
128 | ||
129 | // Here we provide an implementation of the Canvas methods for AsciiArt. | |
130 | // Other implementations could also be provided (e.g. for PDF or Apple's Quartz) | |
131 | // and code can use them polymorphically via the Canvas trait. | |
132 | impl Canvas for AsciiArt { | |
970d7e83 | 133 | fn add_point(&mut self, shape: Point) { |
223e47cc LB |
134 | self.add_pt(shape.x, shape.y); |
135 | } | |
136 | ||
970d7e83 | 137 | fn add_rect(&mut self, shape: Rect) { |
223e47cc | 138 | // Add the top and bottom lines. |
85aaf69f | 139 | for x in shape.top_left.x..shape.top_left.x + shape.size.width { |
223e47cc LB |
140 | self.add_pt(x, shape.top_left.y); |
141 | self.add_pt(x, shape.top_left.y + shape.size.height - 1); | |
142 | } | |
143 | ||
144 | // Add the left and right lines. | |
85aaf69f | 145 | for y in shape.top_left.y..shape.top_left.y + shape.size.height { |
223e47cc LB |
146 | self.add_pt(shape.top_left.x, y); |
147 | self.add_pt(shape.top_left.x + shape.size.width - 1, y); | |
148 | } | |
149 | } | |
150 | } | |
151 | ||
152 | // Rust's unit testing framework is currently a bit under-developed so we'll use | |
153 | // this little helper. | |
970d7e83 LB |
154 | pub fn check_strs(actual: &str, expected: &str) -> bool { |
155 | if actual != expected { | |
1a4d82fc | 156 | println!("Found:\n{}\nbut expected\n{}", actual, expected); |
223e47cc LB |
157 | return false; |
158 | } | |
159 | return true; | |
160 | } | |
161 | ||
162 | ||
970d7e83 | 163 | fn test_ascii_art_ctor() { |
223e47cc | 164 | let art = AsciiArt(3, 3, '*'); |
85aaf69f | 165 | assert!(check_strs(&art.to_string(), "...\n...\n...")); |
223e47cc LB |
166 | } |
167 | ||
168 | ||
970d7e83 | 169 | fn test_add_pt() { |
223e47cc LB |
170 | let mut art = AsciiArt(3, 3, '*'); |
171 | art.add_pt(0, 0); | |
172 | art.add_pt(0, -10); | |
173 | art.add_pt(1, 2); | |
85aaf69f | 174 | assert!(check_strs(&art.to_string(), "*..\n...\n.*.")); |
223e47cc LB |
175 | } |
176 | ||
177 | ||
970d7e83 | 178 | fn test_shapes() { |
223e47cc LB |
179 | let mut art = AsciiArt(4, 4, '*'); |
180 | art.add_rect(Rect {top_left: Point {x: 0, y: 0}, size: Size {width: 4, height: 4}}); | |
181 | art.add_point(Point {x: 2, y: 2}); | |
85aaf69f | 182 | assert!(check_strs(&art.to_string(), "****\n*..*\n*.**\n****")); |
223e47cc LB |
183 | } |
184 | ||
185 | pub fn main() { | |
186 | test_ascii_art_ctor(); | |
187 | test_add_pt(); | |
188 | test_shapes(); | |
189 | } |