7.6.1 Администратор экрана
Вначале было намерение написать администратор экрана на C (а не на С++), чтобы подчеркнуть разделение уровней реалзации. Это оказалось слишком утомительным, поэтому пришлось пойти на компромисс: используется стиль C (нет функций члнов, виртуальных функций, определяемых пользователем операций и т.п.), однако применяются конструкторы, надлежащим образом описываются и проверяются параметры функций и т.д. Оглядывясь назад, можно сказать, что администратор экрана очень пхож на C программу, которую потом модифицировали, чтобы вопользоваться средствами С++ не переписывая все полностью.
Экран представляется как двумерный массив символов, работу с которым осуществляют функции put_point() и put_line(), использующие при обращении с экраном структуру point:
// файл screen.h
const XMAX=40, YMAX=24;
struct point (* int x,y; point() (**) point(int a, int b) (* x=a; y=b; *) *);
overload put_point; extern void put_point(int a, int b); inline void put_point(point p) (* put_point(p.x,p.y); *)
overload put_line; extern void put_line(int, int, int, int); inline void put_line(point a, point b) (* put_line(a.x,a.y,b.x,b.y); *)
extern void screen_init(); extern void screen_refresh(); extern void screen_clear();
#include «stream.h»
Перед первым использованием функции put экран надо инциализировать с помощью screen_init(), а изменения в структре данных экрана отображаются на экране только после вызова screen_refresh(). Как увидит пользователь, это «обновление» («refresh») осуществляется просто посредством печати новой копии экрана под его предыдущим вариантом. Вот функции и оределения данных для экрана:
#include «screen.h» #include «stream.h»
enum color (* black='*', white=' ' *);
char screen[XMAX][YNAX];
void screen_init() (* for (int y=0; y«YMAX; y++) for (int x=0; x«XMAX; x++) screen[x][y] = white; *)
Точки печатаются, только если они есть на экране:
inline int on_screen(int a, int b) (* return 0«=a amp; amp; a«XMAX amp; amp; 0«=b amp; amp; b«YMAX; *)
void put_point(int a, int b) (* if (on_screen(a,b)) screen[a][b] = black; *)
Для рисования линий используется функция put_line():
void put_line(int x0, int y0, int x1, int y1) /* Строит линию от (x0,y0) до (x1,y1). Строится линия b(x-x0) + a(y-y0) = 0. Минимизирует abs(eps), где eps = 2*(b(x-x0)+ a(y-y0)). См. Newman and Sproulclass="underline" ``Principles of Interactive Computer Graphics'' McGraw-Hill, New York, 1979, pp 33-44. */ (* register dx = 1; int a = x1 – x0; if (a « 0) dx = -1, a = -a; register dy = 1; int b = y1 – y0;
if (b « 0) dy = -1, b = -b; int two_a = 2*a; int two_b = 2*b; int xcrit = -b + two_a; register eps = 0; for (;;) (* put_point(x0,y0); if(x0==x1 amp; amp; y0==y1) break; if(eps „= xcrit) x0 += dx, eps += two_b; if(eps“=a !! a«=b) y0 += dy, eps -= two_a; *) *)
Предоставляются функции для очистки экрана и его обноления:
void screen_clear() (* screen_init(); *) // очистка
void screen_refresh() // обновление (* for (int y=YMAX-1; 0«=y; y–) (* // сверху вниз for (int x=0; x«XMAX; x++) // слева направо cout.put(screen[x][y]); cout.put('\n'); *) *)
Функция ostream::put() применяется для печати символов как символов; ostream::operator««() печатает символы как млые целые. Пока что вы может представлять себе, что эти опрделения доступны только в откомпилированном виде, который вы изменить не можете.
7.6.2 Библиотека фигур
Нам нужно определить общее понятие фигуры (shape). Это надо сделать таким образом, чтобы оно использовалось (как бзовый класс) всеми конкретными фигурами (например, кругами и квадратами), и так, чтобы любой фигурой можно было манипулровать исключительно через интерфейс, предоставляемый классом shape:
struct shape (* shape() (* shape_list.append(this); *)
virtual point north() (*return point(0,0);*) // север virtual point south() (*return point(0,0);*) // юг virtual point east() (*return point(0,0);*) // восток virtual point neast() (*return point(0,0);*)//северо-восток virtual point seast() (*return point(0,0);*) // юго-восток
virtual void draw() (**); // нарисовать virtual void move(int, int) (**); // переместить *);
Идея состоит в том, что расположение фигуры задается с помощью move(), и фигура помещается на экран с помощью draw(). Фигуры можно располагать относительно друг друга, ипользуя понятие точки соприкосновения, и эти точки перечислются после точек компаса (сторон света). Каждая конкретная фигура определяет свой смысл этих точек, и каждая определяет способ, которым она рисуется. Для экономии места здесь на смом деле определяются только необходимые в этом примере строны света. Конструктор shape::shape() добавляет фигуру в список фигур shape_list. Этот список является gslist, то есть, одним из вариантов обобщенного односвязанного списка, определенного в #7.3.5. Он и соответствующий итератор были
сделаны так:
typedef shape* sp; declare(gslist,sp);
typedef gslist(sp) shape_lst; typedef gslist_iterator(sp) sp_iterator;
поэтому shape_list можно описать так:
shape_lst shape_list;
Линию можно построить либо по двум точкам, либо по точке и целому. В последнем случае создается горизонтальная линия, длину которой определяет целое. Знак целого указывает, каким концом является точка: левым или правым. Вот определение:
class line : public shape (* /* линия из 'w' в 'e' north() определяется как ``выше центра и на север как до самой северной точки'' */ point w,e; public: point north() (* return point((w.x+e.x)/2,e.y«w.y?w.y:e.y); *)
point south() (* return point((w.x+e.x)/2,e.y«w.y?e.y:w.y); *)
void move(int a, int b) (* w.x += a; w.y += b; e.x += a; e.x += b; *) void draw() (* put_line(w,e); *)
line(point a, point b) (* w = a; e = b; *) line(point a, int l) (* w = point(a.x+l-1,a.y); e = a; *) *);
Аналогично определяется прямоугольник rectangle:
class rectangle : public shape (* /* nw – n – ne ! ! ! ! w c e ! ! ! ! sw – s – se */ point sw,ne; public: point north() (* return point((sw.x+ne.x)/2,ne.y); *) point south() (* return point((sw.x+ne.x)/2,sw.y); *) point neast() (* return ne; *) point swest() (* return sw; *) void move (int a, int b) (* sw.x+=a; sw.y+=b; ne.x+=a; ne.y+=b; *) void draw(); rectangle(point, point); *);
Прямоугольник строится по двум точкам. Код усложняется из-за необходимости выяснять относительное положение этих тчек:
rectangle::rectangle(point a, point b); (* if (a.x «= b.x) (* (* sw = a; ne = b; *) else (* sw = point(a.x,b.y); ne = point(b.x,a.y); *) *) else (* if (a.y «= b.y) (* sw = point(b.x,a.y); ne = point(a.x,b.y); *) else (* sw = b; ne = a; *) *) *)
Чтобы построить прямоугольник, строятся четыре его строны:
void rectangle::draw(); (* point nw(sw.x,ne.y); point se(ne.x,sw.y); put_line(nw,ne); put_line(ne,se); put_line(se,sw); put_line(sw,nw); *)
Помимо определений фигур в библиотеке фигур содержатся функции для работы с ними. Например:
void shape_refresh(); // рисует все фигуры void stack(shape* p, shape* q); // ставит p на верх q
Чтобы справиться с нашим наивным экраном, нужна обновлющая функция. Она просто рисует все фигуры заново. Обратите внимание, что она совершенно не представляет, какие фигуры рисует:
void shape_refresh() (* screen_clear(); sl_iterator next(shape_list); shape* p; while ( p=next() ) p-»draw(); screen_refresh(); *)
И вот, наконец, настоящая сервисная функция (утилита). Она кладет одну фигуру на верх другой, задавая, что south() одной должен быть сразу над north() другой:
void stack(shape* q, shape* p) // ставит p на верх q (* point n = p-»north(); point s = q-»south(); q-»move(n.x-s.x,n.y-s.y+1);
*)
Теперь представим себе, что эта библиотека считается собственностью некой компании, которая продает программное обеспечение, и что они продают вам только заголовочный файл, содержащий определения фигур, и откомпилированный вариант оределений функций. И у вас все равно остается возможность оределять новые фигуры и использовать для ваших собственных фигур сервисные функции.
7.6.3 Прикладная программа
Прикладная программа чрезвычайно проста. Определяется новая фигура myshape (на печати она немного похожа на рожцу), а потом пишется главная программа, которая надевает на нее шляпу. Вначале описание myshape:
#include «shape.h»
class myshape : public rectangle (* line* l_eye; // левый глаз line* r_eye; // правый глаз line* mouth; // рот public: myshape(point, point); void draw(); void move(int, int); *);
Глаза и рот – отдельные и независимые объекты, которые создает конструктор myshape: