Note that the box in the lower right corner of the screen has a discontinuous path.
Each movement has associated with it a duration which gives the number of milliseconds it takes for a box to move from the first point to the second. Duration gives a box's speed; the longer the duration, the slower the box moves (allowing for movements of different rates). The duration may change from movement to movement, but stays the same within a movement.
All boxes with movements begin moving at the same time; each box continues to move until it reaches the end of its movement chain, at which point it stops moving for the rest of the animation. When all boxes stop moving, the animation ends.
Keeping track of and implementing box movements is the first major problem that needs to be solved. One possible approach is to take the term "script" literally and precompute all the necessary actions before the executing any of them. This approach is possible because the animation can be delayed until the entire script is available. (Alternatively, this approach has the disadvantage that the entire script must be available before the animation can begin.)
Another, more object-oriented, approach to box movements is to have each box be responsible for keeping track of its movements; an animation script would then be a set of the boxes in the animation. The code below uses the second approach.
In keeping with the object-oriented approach, a box also needs to able to draw itself. As was the case with box motion, it is helpful to understand drawing before trying to implement it. The two interesting features of box drawing are that higher-level boxes have to be drawn on top of lower-level boxes, and that boxes at the same level have to be drawn merged.
To implement overwriting, it may be helpful to think in analogies. If each box were made of paper, the first feature can be implemented by laying down the lower-level boxes before the higher-level boxes; the higher-level boxes automatically appear on top of the lower-level boxes. This technique can be simulated on a terminal screen by drawing the lower-level boxes before the higher-level boxes. This technique requires that the whole box be drawn, including the empty space inside the box; it is by writing the empty part of the higher-level box that the parts of the lower-level boxes showing through the higher-level box are eliminated.
Merging is a bit more tricky to implement because the boxes at the same level don't have a natural order. In fact, order doesn't help at all here; some box has to be drawn last, and that box's edges will be visible inside the other boxes at the same level. Fortunately, a little bit of thought resolves the dilemma: write all the box edges before writing any of the box middles.
One point to note about both drawing implementations is that they assume the screen is blank to start. This is wasteful in time if only one box moves; it would be quicker to draw only the moved box (and, because of overlapping and merging, possibly all boxes in the same pile as the moved box). The great advantage to tabula-risa drawing is that it's simpler to implement than is incremental drawing; where permitted, and in the absence of any compelling and concrete evidence to the contrary, it's better to go with the simpler approach.
And our reward for adopting tabula-risa drawing is immediate: because all boxes are redrawn each time, the low-level animation script needs no explicit drawing instructions if the interpreter calls the drawing routines after each set of boxes have been moved in a particular instant.
The overall shape of the animation program is now set: an animation script consists of a set of box objects; each box object is responsible for its motions in the animation. The script is animated by some code the cycles through the boxes in the animation script.
There are many other design details to be worked out, but these are either simple or local enough to deal with as they come up during implementation.
<main()
>= (U->)
int main(int argc, char * argv[]) {
if (argc != 1) {
std::cerr << "Command format is \"" << argv[0] << "\".\n";
return EXIT_FAILURE;
}
animate_boxes(std::cout, read_script(std::cin));
}
Definesmain
(links are to index).
Of these questions, the first one is, except for a few minor details, straightforward and won't be discussed further here. The second question requires much more information then we have right now to be answered well, so we'll delay it. That leaves the third question.
At heart, animation is a simple, three step process:
This translates into the code
<the animation loop>= (U->) void animate_boxes(std::ostream & os, animation_script boxes) { unsigned time = 0, next_step_time; while (true) { draw_boxes(os, boxes); if (!boxes.nearest_step_time(next_step_time)) break; usleep((next_step_time - time)*1000); time = next_step_time; boxes.step(); } }
Definesanimate_boxes
(links are to index).
animate_boxes()
in front of us, we can begin to answer the
animation-script questions. In particular, we now know that an animation
script should support the following operations:
step()
, which moves the boxes from their current position to
their next position in the animation.
nearest_step_time()
, which figures out how long the current
picture should be displayed.
Notice that the animation script is not responsible for drawing the boxes, at
least in animate_boxes()
. This is a design decision made to let the same
animation script support different drawing styles.
Given the discussion in the Design Section, overlapping boxes can be handled by
drawing the boxes in increasing level order starting from an empty screen. The
only tricky bit to this is finding the subset of boxes that have the same
level. If the animation list keeps the boxes in ascending order by level, this
is fairly easy (and is even easier once we learn about the
std::equal_range()
generic algorithm).
<box drawing>= (U->) static void draw_boxes(std::ostream & screen, const animation_script & boxes) { screen::clear(screen); unsigned start = 0; while (start < boxes.size()) { unsigned end = start + 1; const unsigned level = boxes.ordered_by_level(start).b().level; while ((end < boxes.size()) and (level == boxes.ordered_by_level(end).b().level)) end++; draw_level(screen, boxes, start, end); start = end; } }
Definesdraw_boxes
(links are to index).
<same-level box drawing>= (U->) static void draw_level( std::ostream & screen, const animation_script & boxes, unsigned start, unsigned end) { unsigned i; for (i = start; i < end; i++) boxes.ordered_by_level(i).draw(screen); for (i = start; i < end; i++) boxes.ordered_by_level(i).clear_interior(screen); }
Definesdraw_level
(links are to index).
<animation-list public method declarations>= (U->) // Find the step time nearest to now and store it in the given reference; // return true iff there is a nearest step time. bool nearest_step_time(unsigned &) const; // Return the cel that's in the given position when the cels are ordered by // box-level number. animation_cel ordered_by_level(unsigned) const; // Move the boxes one step in the animation. void step(void); // Return the number of animation cels in this list. unsigned size(void) const { return cels.size(); }
ordered_by_level()
is easiest implemented by a list, as is
nearest_step_time()
, although perhaps not the same list as
ordered_by_level()
. ordered_by_level()
also suggests that the list
elements be boxes, because boxes have levels. However, step()
suggests
that the list elements be box movements because they contain the box references
and velocity information needed to move a box. nearest_tep_time()
, on the
other hand, suggests that list elements need to contain more than just boxes
and movements because neither of these indicates when the next move occurs.
Given these complex and conflicting requirements, it seems best to assume that
an animation-script list element is data structure, call it animation_cel
.
The details about an animation_cel
can be gleaned from the animation-list
methods' implementation.
There need to be at least two animation-cel lists: one ordered by levels and the other ordered by next-move times. Making copies of each animation cel for each list is wasteful and, more importantly, raises the problem of keeping the two copies consistent. The level-ordered list will be stable - a box doesn't change its level - so having the time-ordered list point into the level-ordered list is a possibility. However, pointing into a container is a bad idea, because it involves a knowledge of container internals. Using iterators is a better choice, but iterator invalidation could be a problem.
If the level-ordered list were a list container, then the iterator approach would be acceptable because list container's good iterator-invalidation properties. On the other hand, if the level-ordered list were a vector, then the time-ordered list could contain vector indices. Because vectors are more space-efficient than are list containers, and because indices are much simpler than are iterators, the level-ordered list will be a vector.
<animation-script data structures>= (U->) std::vector<animation_cel> cels; std::vector<unsigned> level_order; std::vector<unsigned> step_time_order;
Definescels
,level_order
,step_time_order
(links are to index).
ordered_by_level()
is simple:
<animation-script public method definitions>= (U->) [D->] animation_cel animation_script:: ordered_by_level(unsigned i) const { return cels.at(level_order.at(i)); }
Definesanimation_script::ordered_by_level
(links are to index).
nearest_step_time
:
<animation-script public method definitions>+= (U->) [<-D->] bool animation_script:: nearest_step_time(unsigned & time) const { if (step_time_order.empty()) return false; time = cels[step_time_order[0]].get_time(); return true; }
Definesanimation_script::nearest_step_time
(links are to index).
step()
method,
animation_script::step()
has a simple implementation: remember the step
time at the head of the list; this is the current step-time. As long as the
animation cel at the head of the list has the same step time, move the cel and
re-order the list. If the cel at the list head has no more moves, remove it
from the list.
This implementation is inefficient because it resorts the list after every change, rather than waiting until all changes have been made. However, if this turns out to be a problem, the code can be reworked to make it more efficient while keeping the same interface.
<animation-script public method definitions>+= (U->) [<-D] void animation_script:: step(void) { if (!step_time_order.empty()) { const unsigned step_time = cels[step_time_order[0]].get_time(); do { if (!cels[step_time_order[0]].step()) { step_time_order[0] = step_time_order.back(); step_time_order.pop_back(); } fix_step_time_order(); } while (!step_time_order.empty() and (step_time == cels[step_time_order[0]].get_time())); } }
Definesanimation_script::step
(links are to index).
<aanim.cc
>= #include <iostream> #include <cstdlib> #include "read-script.h" #include "animate-boxes.h" <main()
>
Definesaanim.cc
,main
(links are to index).
<animate-boxes.cc
>=
#include <unistd.h>
#include "animate-boxes.h"
#include "screen.h"
// Deja vu c++ style.
static void draw_boxes(std::ostream &, const animation_script &);
static void draw_level(std::ostream &, const animation_script &, unsigned, unsigned);
<the animation loop>
<box drawing>
<same-level box drawing>
Definesanimate-boxes.cc
(links are to index).
<animate-boxes.h
>=
#ifndef _animate_boxes_h_defined_
#define _animate_boxes_h_defined_
#include "animation-script.h"
#include <ostream>
extern void animate_boxes(std::ostream &, animation_script);
#endif
Definesanimate-boxes.h
(links are to index).
<animation-cel.h
>=
#ifndef _animation_cel_h_defined_
#define _animation_cel_h_defined_
#include <ostream>
#include <vector>
#include "box.h"
class animation_cel {
public:
// Add the given movement to the animation path for this cel.
void add_movement(const movement &);
// Create an animation cell with the given box and initial animation path.
animation_cel(const box & b, const movement & m);
// Return a constant reference to the box associated with this cel.
const box & b(void) const { return bx; }
// Erase the box's interior.
void clear_interior(std::ostream &) const;
// Draw the box to the given output stream;
void draw(std::ostream &) const;
// Return the next time at which this box should move.
unsigned get_time(void) const {
return (time + 500)/1000;
}
// Return true iff this animation has at least one move left.
bool moves_left(void) const;
// Move the cel to the next step in the animation; return true if the cel
// is still part of the animation.
bool step(void);
private:
// The box associated with this animation cell.
box bx;
// The coordinates of the upper-left corner of the box, in units of
// fractional characters.
double ul_c, ul_r;
// The amount a box moves in a step along the rows and columns, in units
// of fractional characters.
double delta_c, delta_r;
// The time the next step should be taken, and the amount of delay
// between steps, both in microseconds.
unsigned delta_t, time;
// The number of steps left in the current movement.
unsigned steps;
// The sequence of movements this box should traverse; next is the index
// of the next movement to use once the current movement is done.
std::vector<movement> paths;
unsigned next;
void set_deltas();
};
#endif
Definesanimation-cel.h
(links are to index).
<animation-cel.cc
>=
#include <cassert>
#include "animation-cel.h"
#include "screen.h"
// Deja vu c++ style.
static inline int rnd(double x) { return static_cast<int>(x + 0.5); }
void animation_cel::
add_movement(const movement & m) {
paths.push_back(m);
if (steps == 0) {
assert(next == paths.size() - 1);
set_deltas();
}
}
animation_cel::
animation_cel(const box & b, const movement & m) :
bx(b), time(0), steps(0), next(0) {
add_movement(m);
}
void animation_cel::
clear_interior(std::ostream & screen) const {
const int
c = rnd(ul_c) + 1,
r = rnd(ul_r);
for (unsigned i = 1; i < bx.height - 1; i++)
screen::put(screen, r + i, c, bx.middle);
}
void animation_cel::
draw(std::ostream & screen) const {
const int
c = rnd(ul_c),
r = rnd(ul_r);
screen::color(screen, bx.color);
screen::put(screen, r, c, bx.top);
for (unsigned i = 1; i < bx.height - 1; i++)
screen::put(screen, r + i, c, bx.sides);
screen::put(screen, r + bx.height - 1, c, bx.top);
}
bool animation_cel::
moves_left(void) const {
return (next < paths.size()) or (steps > 0);
}
void animation_cel::
set_deltas(void) {
// The current movement is complete, go on to the next one (which must exist)
// and reset the box location and deltas.
const movement & m = paths.at(next++);
const int
c = m.end_c - m.start_c,
r = m.end_r - m.start_r;
steps = std::max(std::abs(r), std::abs(c));
if (steps > 1) {
delta_c = double(c)/double(steps);
delta_r = double(r)/double(steps);
delta_t = (1000*m.time)/steps;
ul_c = m.start_c;
ul_r = m.start_r;
}
else {
delta_c = 0.0;
delta_r = 0.0;
delta_t = 1000*m.time;
ul_c = m.end_c;
ul_r = m.end_r;
}
if (time == 0)
time = delta_t;
}
bool animation_cel::
step(void) {
if (steps == 0)
if (next == paths.size())
return false;
else
set_deltas();
assert(steps > 0);
ul_c += delta_c;
ul_r += delta_r;
time += delta_t;
steps--;
return true;
}
Definesanimation-cel.cc
(links are to index).
<animation-script.cc
>=
#include <cassert>
#include <iostream>
#include "animation-script.h"
void animation_script::
add_box(const box & b, const movement & m) {
assert(find(b.name) < 0);
animation_cel cel(b, m);
cels.push_back(cel);
const unsigned new_cel_idx = cels.size() - 1;
level_order.push_back(new_cel_idx);
unsigned i = new_cel_idx;
while ((i > 0) and (cels.at(level_order[i - 1]).b().level > b.level)) {
level_order[i] = level_order[i - 1];
i--;
}
level_order[i] = new_cel_idx;
step_time_order.push_back(new_cel_idx);
fix_step_time_order();
}
bool animation_script::
add_movement(const std::string & name, const movement & m) {
const int i = find(name);
if (i < 0)
return false;
else {
cels[i].add_movement(m);
return true;
}
}
int animation_script::
find(const std::string & name) const {
// Return the index of the animation cel associated with the box having the
// given name.
for (unsigned i = cels.size(); i > 0; i--)
if (cels[i - 1].b().name == name)
return i - 1;
return -1;
}
void animation_script::
fix_step_time_order(void) {
// Sort the time-step list into increasing step-time order.
const unsigned cel_cnt = step_time_order.size();
if (cel_cnt > 1)
for (unsigned i = 0; i < cel_cnt - 1; i++) {
unsigned min = i;
for (unsigned j = i + 1; j < cel_cnt; j++)
if (cels.at(step_time_order.at(min)).get_time() >
cels.at(step_time_order.at(j)).get_time())
min = j;
std::swap(step_time_order.at(min), step_time_order.at(i));
}
}
<animation-script public method definitions>
Definesanimation-script.cc
(links are to index).
<animation-script.h
>=
#ifndef _animation_script_h_defined_
#define _animation_script_h_defined_
#include <string>
#include <vector>
#include "movement.h"
#include "box.h"
#include "animation-cel.h"
class animation_script {
public:
// Add the given box with the given movement to this list.
void add_box(const box &, const movement &);
// Add the given move directive to the box in this list having the given
// name.
bool add_movement(const std::string &, const movement &);
<animation-list public method declarations>
private:
<animation-script data structures>
int find(const std::string &) const;
void fix_step_time_order(void);
};
#endif
Definesanimation-script.h
(links are to index).
<box.h
>=
#ifndef _box_h_defined_
#define _box_h_defined_
#include <string>
#include <vector>
#include "movement.h"
class box {
public:
// Create a box with the given name, dimensions, level, and color.
box(const std::string &, int, int, int, const std::string &);
// This box's name.
std::string name;
// This box's height and width in characters.
unsigned height, width;
// The strings to draw the top (and bottom) and sides of a box, as well as
// to blank out the middle of the box.
std::string top, sides, middle;
// The box's level.
unsigned level;
// The box's color.
std::string color;
};
#endif
Definesbox.h
(links are to index).
<box.cc
>=
#include <cassert>
#include "box.h"
#include "color.h"
// Deja vu, c++ style;
static std::string gen_row(unsigned, const std::string &, char);
box::
box(const std::string & name, int h, int w, int l, const std::string & c) :
name(name),
height(h), width(w),
top(gen_row(w, "+", '-')),
sides(gen_row(w, "|", ' ')),
middle(gen_row(w, "", ' ')),
level(l), color(c) {
assert(h >= 0);
assert(w >= 0);
assert(h + w > 0);
assert(l >= 0);
assert(is_color(c));
}
static std::string
gen_row(unsigned width, const std::string & end, char middle) {
// Generate a string width characters wide that starts and ends with the
// character end and has the character middle in between.
if (width < 1) return std::string("");
if (width < 2) return end;
return end + std::string(width - 2, middle) + end;
}
Definesbox.cc
(links are to index).
<color routine declarations>= (U->) // Return true iff the given string is a legal color name. extern bool is_color(const std::string &); // Return the foreground color code corresponding to the given color name. // Die with an error message if the color name is illegal. extern unsigned color_code(const std::string &);
<color.cc
>=
#include <cassert>
#include "color.h"
// A color has a name and a code.
struct
color {
const std::string name;
const unsigned code;
};
// The set of supported colors.
static const color
colors[] = {
{"black", 0},
{"red", 1},
{"green", 2},
{"yellow", 3},
{"blue", 4},
{"magenta", 5},
{"cyan", 6},
{"white", 7},
};
// How many colors are there?
static const unsigned
color_cnt = sizeof(colors)/sizeof(color);
// Deja vu, c++ style.
static int find(const std::string &);
unsigned
color_code(const std::string & c) {
const int i = find(c);
assert(i >= 0);
return 30 + colors[i].code;
}
static int
find(const std::string & str) {
// Return the index of the given color in the color set or -1 if there's
// no such color in the set.
for (unsigned i = 0; i < color_cnt; i++)
if (colors[i].name == str)
return i;
return -1;
}
bool
is_color(const std::string & str) {
return find(str) != -1;
}
Definescolor.cc
(links are to index).
<color.h
>=
#ifndef _color_h_defined_
#define _color_h_defined_
#include <string>
<color routine declarations>
#endif
Definescolor.h
(links are to index).
<movement.h
>=
#ifndef _movement_h_defined_
#define _movement_h_defined_
class movement {
public:
// Create a movement starting at (start_r, start_c) and ending at
// (end_r, end_c), and taking time msecs to get there.
movement(
unsigned start_r, unsigned start_c,
unsigned end_r, unsigned end_c,
unsigned time) :
start_r(start_r), start_c(start_c),
end_r(end_r), end_c(end_c),
time(time)
{ }
// This movement starts at (start_r, start_c) and ends at (end_r, end_c).
unsigned start_r, start_c, end_r, end_c;
// How many msecs this movement takes.
unsigned time;
};
#endif
Definesmovement.h
(links are to index).
<read-script.cc
>=
#include <cstdlib>
#include <vector>
#include <cassert>
#include "read-script.h"
#include "strings.h"
#include "movement.h"
#include "box.h"
#include "color.h"
// The list of all boxes declared.
static std::vector<box> boxes_defined;
static int
find_box(const std::string & name) {
// Return the index of the box having the given name or -1 if there's no
// such box.
for (unsigned i = 0; i < boxes_defined.size(); i++)
if (boxes_defined[i].name == name)
return i;
return -1;
}
static void
add_box(const box & b) {
// Add the given box to the list of defined boxes; die if the box is
// already defined.
assert(find_box(b.name) == -1);
boxes_defined.push_back(b);
}
// Deja vu, c++ style.
static box parse_box(strings pieces);
static movement parse_move(const strings & pieces);
static void
do_box(strings pieces) {
// Declare the box described by the box statement in pieces.
if (find_box(pieces[1]) != -1) {
std::cerr << "Input error: repeated box name \"" << pieces[1] << "\".";
exit(EXIT_FAILURE);
}
add_box(parse_box(pieces));
}
static void
do_move(animation_script & boxes, const strings & pieces) {
// Add the move statement in pieces to the appropriate box in boxes.
const movement m = parse_move(pieces);
if (boxes.add_movement(pieces[1], m))
return;
const int i = find_box(pieces[1]);
if (i < 0) {
std::cerr << "Input error: Undefined box name \"" << pieces[1] << "\" in a move statement.\n";
exit(EXIT_FAILURE);
}
boxes.add_box(boxes_defined[i], m);
}
static box
parse_box(strings pieces) {
// Parse the box statement given in pieces and return the corresponding box.
assert(pieces[0] == "box");
const unsigned pcnt = pieces.size();
if ((pcnt < 4) or (pcnt > 6)) {
std::cerr << "Input error: A box statement has " << pcnt << " word"
<< (pcnt == 1 ? "" : "s") << " but should have from 4 to 6 words.\n";
exit(EXIT_FAILURE);
}
if (!is_color(pieces[pcnt - 1]))
pieces.push_back(std::string("black"));
if (pieces.size() == 5)
pieces.insert(pieces.end() - 1, std::string("0"));
else if (!is_int(pieces[4])) {
std::cerr << "Input error: A box statement level is \"" << pieces[4]
<< "\", should be a non-negative integer.\n";
exit(EXIT_FAILURE);
}
if (pieces.size() != 6) {
std::cerr << "Input error: malformed box statement.\n";
exit(EXIT_FAILURE);
}
return box(pieces[1], stoi(pieces[2]), stoi(pieces[3]), stoi(pieces[4]), pieces[5]);
}
static movement
parse_move(const strings & pieces) {
// Parse the move statement given in pieces and return the corresponding
// movement.
assert(pieces[0] == "move");
const unsigned pcnt = pieces.size();
if (pcnt != 7) {
std::cerr << "Input error: A move statement has " << pcnt << " word"
<< (pcnt == 1 ? "" : "s") << " but should have 7 words.\n";
exit(EXIT_FAILURE);
}
return movement(stoi(pieces[2]), stoi(pieces[3]), stoi(pieces[4]), stoi(pieces[5]), stoi(pieces[6]));
}
animation_script
read_script(std::istream & is) {
// Read the animation script from the input stream is, returning the
// resulting animation list.
animation_script boxes;
std::string line;
while (read_nonblank_line(is, line)) {
const strings pieces = split(line);
if (pieces[0] == "box")
do_box(pieces);
else if (pieces[0] == "move")
do_move(boxes, pieces);
else {
std::cerr << "Input error: \"" << pieces[0]
<< "\" is an unrecognized animation statement.\n";
exit(EXIT_FAILURE);
}
}
return boxes;
}
Definesread-script.cc
(links are to index).
<read-script.h
>=
#ifndef _read_script_h_defined_
#define _read_script_h_defined_
#include "animation-script.h"
#include <iostream>
extern animation_script read_script(std::istream &);
#endif
Definesread-script.h
(links are to index).
<screen.h
>=
#ifndef _screen_h_defined_
#define _screen_h_defined_
#include <ostream>
#include <string>
namespace screen {
// Send to the given output stream the escape sequences to clear the screen
// and position the cursor at (1, 1).
void clear(std::ostream &);
// Send to the given output stream the escape sequences to set the foreground
// color to the given color; die if the color's unrecognized.
void color(std::ostream &, const std::string &);
// Send to the given output stream the escape sequences that display the given
// string on the screen starting at the given (r, c) location.
void put(std::ostream &, unsigned, unsigned, const std::string &);
}
#endif
Definesscreen.h
(links are to index).
<screen.cc
>=
#include "screen.h"
#include "color.h"
using std::ostream;
using std::string;
const char * const esc = "\033[";
static ostream &
move(ostream & os, unsigned r, unsigned c) {
// Send to the given output stream the escape sequence to position the cursor
// at (r, c); return the given output stream.
return os << esc << r << ';' << c << 'H';
}
namespace screen {
void clear(ostream & os) {
os << esc << "2J";
move(os, 1, 1) << std::flush;
}
void color(ostream & os, const string & str) {
os << esc << color_code(str) << 'm' << std::flush;
}
void put(ostream & os, unsigned r, unsigned c, const string & str) {
move(os, r, c) << str << std::flush;
}
}
Definesscreen.cc
(links are to index).
<strings.h
>=
#ifndef _strings_h_defined_
#define _strings_h_defined_
#include <vector>
#include <string>
#include <istream>
// A vector of strings.
typedef std::vector<std::string> strings;
// A string index.
typedef std::string::size_type str_indx;
// The end-of-string marker.
const str_indx eostr = std::string::npos;
// Split a string into non-empty words using space-character delimiters.
extern strings split(const std::string &);
// Convert the given string into an integer; die if the string isn't an integer.
extern int stoi(const std::string &);
// Return true if the given string is an integer.
extern bool is_int(const std::string &);
// Read into the given string the next non-blank line from the given input
// stream; return true iff a string was read.
extern bool read_nonblank_line(std::istream &, std::string &);
#endif
Definesstrings.h
(links are to index).
<strings.cc
>=
#include <cstdlib>
#include "strings.h"
typedef const std::string & cstr_ref;
bool
is_int(cstr_ref str) {
return (str.size() > 0) and
(str.find_first_not_of("0123456789") == eostr);
}
bool
read_nonblank_line(std::istream & is, std::string & line) {
// Read a non-blank line from the input stream is;
while (getline(is, line))
if (line.find_first_not_of(" \t") != eostr)
return true;
return false;
}
strings
split(cstr_ref str) {
strings pieces;
const char * spaces = " \t\n";
str_indx start = 0;
while (true) {
start = str.find_first_not_of(spaces, start);
if (start == eostr) break;
const str_indx end = str.find_first_of(spaces, start + 1);
pieces.push_back(str.substr(start, end - start));
start = end;
}
return pieces;
}
int
stoi(cstr_ref str) {
return std::atoi(str.c_str());
}
#ifdef TESTING_STRINGS
// g++ -g -o strings-test -ansi -pedantic -Wall -DTESTING_STRINGS strings.cc && ./strings-test
#include <cassert>
int main() {
strings pieces = split(" \t \n");
assert(pieces.empty());
pieces = split(" red\twhite\nblue ");
assert(pieces.size() == 3);
assert(pieces[0] == "red");
assert(pieces[1] == "white");
assert(pieces[2] == "blue");
pieces = split("hello");
assert(pieces.size() == 1);
assert(pieces[0] == "hello");
assert(stoi("0") == 0);
assert(stoi("-1") == -1);
assert(stoi("1") == 1);
assert(is_int("1"));
assert(is_int("123"));
assert(!is_int("alpha"));
}
#endif
Definesstrings.cc
(links are to index).
aanim.cc
>: D1
animate-boxes.cc
>: D1
animate-boxes.h
>: D1
animation-cel.cc
>: D1
animation-cel.h
>: D1
animation-script.cc
>: D1
animation-script.h
>: D1
box.cc
>: D1
box.h
>: D1
color.cc
>: D1
color.h
>: D1
main()
>: D1, U2
movement.h
>: D1
read-script.cc
>: D1
read-script.h
>: D1
screen.cc
>: D1
screen.h
>: D1
strings.cc
>: D1
strings.h
>: D1
This page last modified on 4 January 2003.