Advanced Programming II, Fall 2002

Programming Assignment 2 - An Example Solution


Table of Contents

Introduction

Design

Before solving the problem, it would be helpful to understand it. An animation script defines a bunch of boxes and associates with some of the boxes movements from one point to another along a straight line. A box with several movements associated with it has the movements chained together in the top-to-bottom order the movements are given in the script. After an animation script has been read and processed, it will have created one or more boxes, each with an associated path of movements.

an animation script

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.

Implementation

The program needs to read in an animation script and then animate it.
<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));
  }
Defines main (links are to index).

With that out of the way, three questions immediately present themselves as possible next steps:

  1. How are animation scripts read?

  2. What is an animation script?

  3. How are scripts animated?

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:

  1. Show the current picture for some period of time.

  2. Figure out what the next picture should be, usually by slightly modifying the current picture.

  3. Create a new current picture.

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();
    }
  }
Defines animate_boxes (links are to index).

With 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:

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;
    }
  }
Defines draw_boxes (links are to index).

The Design Section discussion of overlapping boxes on the same level also leads to some simple code for merging such boxes.
<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);
  }
Defines draw_level (links are to index).

And that's about it for animation.

The Animation Script

The animation description has used several operations that should be provided by the animation-script data structure:
<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;
Defines cels, level_order, step_time_order (links are to index).

With these data types, 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));
  }
Defines animation_script::ordered_by_level (links are to index).

as is 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;
  }
Defines animation_script::nearest_step_time (links are to index).

Because it relies on the animation-cel's 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()));
    }
  }
Defines animation_script::step (links are to index).

There are a few details left before the animation script is finished, such as adding cels to an animation script, but these are straightforward and most easily described by the code itself.

Files

Main

<aanim.cc>=

#include <iostream>
#include <cstdlib>
#include "read-script.h"
#include "animate-boxes.h"

<main()>

Defines aanim.cc, main (links are to index).

Box Animation

<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>

Defines animate-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
Defines animate-boxes.h (links are to index).

Animation Cel

<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
Defines animation-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;
  }
Defines animation-cel.cc (links are to index).

The Animation Script

<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>
Defines animation-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
Defines animation-script.h (links are to index).

Boxes

<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
Defines box.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;
  }
Defines box.cc (links are to index).

Colors

<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;  
  }
Defines color.cc (links are to index).

<color.h>=

#ifndef _color_h_defined_
#define _color_h_defined_

#include <string>

<color routine declarations>

#endif
Defines color.h (links are to index).

Movements

<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
Defines movement.h (links are to index).

Reading the Script

<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;
  }
Defines read-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
Defines read-script.h (links are to index).

The Screen

<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
Defines screen.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;
    }
  }
Defines screen.cc (links are to index).

Strings

<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
Defines strings.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
Defines strings.cc (links are to index).

Index


This page last modified on 4 January 2003.