Р. ЛАФОРЕ ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В C++ (4-Е ИЗДАНИЕ) (часть13) онлайн

Листинг программы ELEV

Мы разделили программу на четыре файла. Два из них, ELEV.H и ELEV.CPP, могут поставляться, например, производителем программного обеспечения лифтов. Затем это ПО может купить строительная компания, заинтересованная в проек- тировании лифтовой системы здания. (Приводимая здесь программа не имеет надлежащего сертификата, поэтому лучше не пытаться использовать ее для ра- боты с реальными лифтами.) Затем инженеры могут написать другую пару фай- лов, ELEV_APP.H и ELEV_APP.CPP, в первом из которых описаны характеристики высотного здания. Действительно нужно использовать разные файлы, потому что эти характеристики должны быть доступны методам класса лифтов, и про- стейшим решением для этого является именно включение ELEV_APP.H в ELEV.H. В файле ELEV_APP.CPP инициализируются лифты, затем в определенные моменты времени вызываются функции работы лифтов, что создает более точную карти- ну в реальном времени.

Описатель класса

В файле ELEV.H содержится описатель класса elevator. Массив указателей на лиф- ты, car_list[], позволяет каждой кабинке опрашивать все другие, выясняя их мес- тонахождение и направление движения.

Листинг 13.4. Заголовочный файл ELEV.H

// elev.h

// заголовочный файл для лифтов – содержит объявления  //классов

 

#include "elev_app.h"  //поставляется клиентом                        

                       //(застройщиком)

#include "msoftcon.h"  //для консольной графики

#include <iostream>

#include <iomanip>    //для setw()

#include <conio.h>     //для вывода на экран

#include <stdlib.h>    //для itoa()

#include <process.h>    //для exit()

using namespace std;

 

enum direction { UP, DN, STOP };

const int LOAD_TIME =   3;   //время посадки/высадки

                             //(В tick'ах)

const int SPACING =    7;    //расстояние между                          

                             //изображениями кабинок

const int BUF_LENGTH =  80;  //длина буфера рабочей строки

 

class building;       //будущее объявление

///////////////////////////////////////////////////////////

class elevator

  {

  private:

  building* ptrBuilding;  //указатель на building

 

Листинг 13.4 (продолжение)

  const int car_number;   //номер лифта (от 0 до nc-1)

  int current_floor;      //где мы? (от 0 до nf-1)

  int old_floor;          //куда едем? (от 0 до nf-1)

  direction current_dir;         //в каком направлении?

  bool destination[NUM_FLOORS];  //выбирается пассажирами

  int loading_timer;      //ненулевое во время посадки

  int unloading_timer;    //ненулевое во время высадки

 

  public:

  elevator(building*, int);  //конструктор

  void car_tick1();          //метроном-1 для каждой кабинки

  void car_tick2();          //метроном-2 для каждой кабинки

  void car_display();        //рисование лифта

  void dests_display() const;//вывод запросов

  void decide();         //принятие решения

  void move();           //движение лифта

  void get_destinations();  //получение номеров конечных этажей                   

  int get_floor() const;            //получение текущего этажа

  direction get_direction() const;  //получение текущего направления

  };

///////////////////////////////////////////////////////////

class building

  {

  private:

  elevator* car_list[NUM_CARS];  //указатель на кабинки

  int num_cars;                  //уже созданные лифты

                                 //массив кнопок «вверх», «вниз»

  bool floor_request[2][NUM_FLOORS];//false=вверх, true=вниз

 

  public:

  building();              //конструктор

  ~building();             //деструктор

  void master_tick();      //рассылка временных меток всем                

                           //лифтам

  int get_cars_floor(const int) const; //поиск лифта

            //выяснение направления движения

  direction get_cars_dir(const int) const; 

            //проверка запроса с этажа

  bool get_floor_req(const int, const int) const;

            //установка запроса с этажа

  void set_floor_req(const int, const int, const bool);

  void record_floor_reqs();  //получение запроса с этажа

  void show_floor_reqs() const;  //вывод запроса

  };

///////////////////////////////////////////////////////////

 

Методы

В файле ELEV.CPP содержатся определения класса elevator и методы и данные класса building. Функции building инициализируют систему, устанавливают мет- роном, получают и выводят запросы. Функции elevator инициализируют с помо- щью конструктора отдельные лифты, устанавливают два метронома для каждой кабинки, выводят изображение лифтов, их направления, принимают решения, двигают лифты и получают номера конечных этажей от пользователя.

Листинг 13.5. Листинг определений классов

// elev.cpp

// содержит определения данных и методов класса

 

#include "elev.h"               //включить объявление класса

///////////////////////////////////////////////////////////

//        определения функций для класса building   

///////////////////////////////////////////////////////////

building::building()            //конструктор

  {

  char ustring[BUF_LENGTH];     //строка для номеров этажей

 

  init_graphics();              //инициализация графики

  clear_screen();               //очистка экрана

  num_cars = 0;

  for(int k=0; k<NUM_CARS; k++) //создать лифты

    {

    car_list[k] = new elevator(this, num_cars);

    num_cars++;

    }

  for(int j=0; j<NUM_FLOORS; j++)    //для каждого этажа

    {

    set_cursor_pos(3, NUM_FLOORS-j); //вывести номер этажа

    itoa(j+1, ustring, 10);          //на экран

    cout << setw(3) << ustring;

    floor_request[UP][j] = false;    //запросов еще не было

    floor_request[DN][j] = false;

    }

  }  //конец конструктора

//---------------------------------------------------------

building::~building()           //деструктор

  {

  for(int k=0; k<NUM_CARS; k++) //удалить лифты

    delete car_list[k];

  }

//---------------------------------------------------------

void building::master_tick()    //метроном

  {

  int j;

  show_floor_reqs();            //вывести запросы с этажей

  for(j=0; j<NUM_CARS; j++)     //для каждого лифта

    car_list[j]->car_tick1();   //послать временную метку 1

  for(j=0; j<NUM_CARS; j++)     //для каждого лифта

    car_list[j]->car_tick2();   //послать временную метку 2

  }  //конец master_tick()

//---------------------------------------------------------

void building::show_floor_reqs() const //вывод запросов

  {

  for(int j=0; j<NUM_FLOORS; j++)

    {

    set_cursor_pos(SPACING, NUM_FLOORS-j);

    if(floor_request[UP][j]==true)

      cout << '\x1E';           //стрелка вверх

    else

      cout << ' ';

    set_cursor_pos(SPACING+3, NUM_FLOORS-j);

Листинг 13.5 (продолжение)

    if(floor_request[DN][j]==true)

      cout << '\x1F';           //стрелка вниз

    else

      cout << ' ';

    }

  }  //конец show_floor_reqs()

//---------------------------------------------------------

//record_floor_reqs() – получение запросов снаружи

void building::record_floor_reqs()

  {

  char ch = 'x';                //рабочий символ для ввода

  char ustring[BUF_LENGTH];     //рабочая строка ввода

  int iFloor;          //этаж, с которого был запрос

  char chDirection;    //'u' или 'd' для выбора направления

 

  set_cursor_pos(1,22);         //низ экрана

  cout << "Нажмите [Enter] для вызова лифта: ";

  if( !kbhit() )                //ожидание нажатия (должен быть CR,              

                                //возврат каретки)

    return;

  cin.ignore(10, '\n');

  if(ch=='\x1B')      //при нажатии escape выход из программы

    exit(0);

  set_cursor_pos(1,22); clear_line();  //очистить от старого

                                       //текста

  set_cursor_pos(1,22);         //низ экрана

  cout << "На каком Вы этаже? ";

  cin.get(ustring, BUF_LENGTH); //получить этаж

  cin.ignore(10, '\n');         //съесть символы, включая

                                //разделитель

  iFloor = atoi(ustring);       //преобразовать в int

 

  cout << "В каком направлении будете двигаться (u/d): ";

  cin.get(chDirection);         //(избежать повтора новых

                                //строк)

  cin.ignore(10, '\n');         //съесть символы, включая разделитель

 

  if(chDirection=='u' || chDirection=='U')

    floor_request[UP][iFloor-1] = true;  //запрос «вверх»

  if(chDirection=='d' || chDirection=='D')

    floor_request[DN][iFloor-1] = true;  //запрос «вниз»

  set_cursor_pos(1,22); clear_line();//стереть старый текст

  set_cursor_pos(1,23); clear_line();

  set_cursor_pos(1,24); clear_line();

  }  //end record_floor_reqs()

//---------------------------------------------------------

//get_floor_req() – проверка наличия запросов

bool building::get_floor_req(const int dir,

                    const int floor) const

  {

  return floor_request[dir][floor];

  }

//---------------------------------------------------------

//set_floor_req() – установить запрос

void building::set_floor_req(const int dir, const int floor,

                 const bool updown)

{

  floor_request[dir][floor] = updown;

  }

//---------------------------------------------------------

//get_cars_floor() – найти кабинку

int building::get_cars_floor(const int carNo) const

  {

  return car_list[carNo]->get_floor();

  }

//---------------------------------------------------------

//get_cars_dir() – определение направления

direction building::get_cars_dir(const int carNo) const

  {

  return car_list[carNo]->get_direction();

  }

//---------------------------------------------------------

 

///////////////////////////////////////////////////////////

//        определения функций класса elevator

///////////////////////////////////////////////////////////

                                //конструктор

elevator::elevator(building* ptrB, int nc) :

                     ptrBuilding(ptrB), car_number(nc)

  {

  current_floor = 0;            //начать с 0 (для пользователя - 1)

  old_floor = 0;                //запомнить предыдущий этаж

  current_dir = STOP;           //вначале стоит на месте

  for(int j=0; j<NUM_FLOORS; j++)//пассажиры еще не

    destination[j] = false;      //  нажимали кнопки

  loading_timer = 0;            //еще не началась посадка

  unloading_timer = 0;          //еще не началась высадка

                       }        //конец конструктора

//---------------------------------------------------------

int elevator::get_floor() const//получение текущего этажа

  {

  return current_floor;

  }

//---------------------------------------------------------

direction elevator::get_direction() const  //получение

  {                                        //текущего направления

  return current_dir;

  }

//---------------------------------------------------------

void elevator::car_tick1()      //метроном-1 для каждого лифта

  {

  car_display();                //нарисовать кабинку

  dests_display();              //нарисовать конечные этажи

  if(loading_timer)             //счет времени посадки

    --loading_timer;

  if(unloading_timer)           //счет времени высадки

    --unloading_timer;

  decide();                     //принятие решения

  }  //конец car_tick()

//---------------------------------------------------------

//все лифты должны знать, что делать, до начала движения

void elevator::car_tick2()      //метроном-2 для каждого лифта

  {

Листинг 13.5 (продолжение)

   move();                       //двигать лифт, если нужно

  }

//---------------------------------------------------------

void elevator::car_display()    //вывод образа лифта

  {

  set_cursor_pos(SPACING+(car_number+1)*SPACING, NUM_FLOORS-old_floor);

  cout << "  ";                 //стереть со старой позиции

  set_cursor_pos(SPACING-1+(car_number+1)*SPACING,

                          NUM_FLOORS-current_floor);

    switch(loading_timer)

    {

      case 3:

      cout << "\x01\xDB \xDB "; //лифт с открытыми дверями

      break;                    //слева - мордочка

    case 2:

      cout << " \xDB\x01\xDB "; //мордочка в дверях

      get_destinations();       //получить конечный этаж

      break;

    case 1:

      cout << " \xDB\xDB\xDB "; //нарисовать с закрытыми

      break;                    //дверями без мордочки

    case 0:

      cout << " \xDB\xDB\xDB ";  //двери закрыты,

      break;                     //мордочки нет (по умолчанию)

    }

  set_cursor_pos(SPACING+(car_number+1)*SPACING,

                        NUM_FLOORS-current_floor);

  switch(unloading_timer)

    {

    case 3:

      cout << "\xDB\x01\xDB ";  //двери открыты,

      break;                    //мордочка слева

    case 2:

      cout << "\xDB \xDB\x01";  //двери открыты,

      break;                    //мордочка справа

    case 1:

      cout << "\xDB\xDB\xDB ";  //двери закрыты

      break;                    //мордочки нет

    case 0:

      cout << "\xDB\xDB\xDB ";  //двери закрыты,

      break;                    //мордочки нет (по умолчанию)

    }

  old_floor = current_floor;    //запомнить старый этаж

  }  //конец car_display()

//---------------------------------------------------------

void elevator::dests_display() const //вывести конечные

  {      //  этажи, выбранные кнопками

  for(int j=0; j<NUM_FLOORS; j++)    //  внутри лифта

    {

    set_cursor_pos(SPACING-2+(car_number+1)*SPACING, NUM_FLOORS-j);

    if( destination[j] == true )

      cout << '\xFE';                //маленький квадратик

    else

      cout << ' ';                   //пробел

    }

  }  //конец dests_display()

//---------------------------------------------------------

void elevator::decide()         //принятие решения

  {

  int j;

//флаги показывают, сверху или снизу запросы или назначения

  bool destins_above, destins_below;   //конечные пункты

  bool requests_above, requests_below; //запросы

  //ближайший запрос снизу и сверху

  int nearest_higher_req = 0;

  int nearest_lower_req = 0;

  //флаги указывают, есть ли другие лифты, движущиеся в том

  //же направлении между нами и ближайшим запросом с этажа //(ЗЭ)

  bool car_between_up, car_between_dn;

  //флаги указывают, есть ли лифты противоположного

  // направления с другой стороны от ближайшего ЗЭ

  bool car_opposite_up, car_opposite_dn;

  //этаж и направление другого лифта

  int ofloor;                   //этаж

  direction odir;               //направление

 

  //убедиться, что мы не слишком высоко или низко

  if( (current_floor==NUM_FLOORS-1 && current_dir==UP)

    || (current_floor==0 && current_dir==DN) )

    current_dir = STOP;

 

  //если этаж назначения – текущий, начать высадку

  if( destination[current_floor]==true )

    {

    destination[current_floor] = false; //удалить это

                                        // назначение

    if( !unloading_timer)       //высадка

      unloading_timer = LOAD_TIME;

    return;

    }

  //если есть запрос «вверх» с этого этажа и если

  //мы едем вверх или стоим, произвести посадку на борт

  if( (ptrBuilding->get_floor_req(UP, current_floor) &&

      current_dir != DN) )

    {

    current_dir = UP;           //(если была остановка)

    //удалить ЗЭ в данном направлении движения

    ptrBuilding->set_floor_req(current_dir,

                      current_floor, false);

    if( !loading_timer)         //посадка

Листинг 13.5 (продолжение)

      loading_timer = LOAD_TIME;

    return;

    }

  //проверка других назначений или ЗЭ

  //расстояние считать до ближайшего запроса

  destins_above = destins_below = false;

  requests_above = requests_below = false;

  for(j=current_floor+1; j<NUM_FLOORS; j++)

    {                           //проверять верхние этажи

    if( destination[j] )        //если они - назначения

      destins_above = true;     //установить флаг

    if( ptrBuilding->get_floor_req(UP, j) ||

       ptrBuilding->get_floor_req(DN, j) )

      {                         //если ЗЭ

      requests_above = true;    //установить флаг

      if( !nearest_higher_req ) //если еще не установлен

        nearest_higher_req = j; //установить ближайший ЗЭ

      }

    }

  for(j=current_floor-1; j>=0; j--) //проверка нижних этажей

    {

    if(destination[j] )         //если назначения

      destins_below = true;     //установить флаг

    if( ptrBuilding->get_floor_req(UP, j) ||

       ptrBuilding->get_floor_req(DN, j) )

      {                         //если запросы

      requests_below = true;    //установить флаг

      if( !nearest_lower_req )  //если еще не установлен

        nearest_lower_req = j;  //установить ближайший ЗЭ

      }

    }

  //если нет запросов сверху/снизу, остановиться

  if( !destins_above && !requests_above &&

     !destins_below && !requests_below)

     {

     current_dir = STOP;

     return;

     }

  //если есть назначение, а мы стоим или движемся к нему,

  //начать/продолжать движение

  if( destins_above && (current_dir==STOP || current_dir==UP) )

    {

    current_dir = UP;

    return;

    }

  if( destins_below && (current_dir==STOP || current_dir==DN) )

    {

    current_dir = DN;

    return;

    }

  //проверка, есть ли другие лифты, (a) того же направления

  //между нами и ближайшим ЗЭ, или

  //(b) встречного направления с другой стороны ЗЭ

  car_between_up = car_between_dn = false;

  car_opposite_up = car_opposite_dn = false;

  for(j=0; j<NUM_CARS; j++)     //проверить каждый лифт

    {

    if(j != car_number)         //если это не наш лифт

      {                         //получить его этаж

      ofloor = ptrBuilding->get_cars_floor(j);  //и

      odir = ptrBuilding->get_cars_dir(j); //направление

      //если едет вверх, и ЗЭ вверху

      if( (odir==UP || odir==STOP) && requests_above )

     //если он при этом между нами и ЗЭ

        if( (ofloor > current_floor

           && ofloor <= nearest_higher_req)

        //или там же, но его номер ниже

          || (ofloor==current_floor && j < car_number) )

          car_between_up = true;

      //если он едет вниз, и ЗЭ внизу

      if( (odir==DN || odir==STOP) && requests_below )

        //если он снизу, но над ближайшим ЗЭ

        if( (ofloor < current_floor

           && ofloor >= nearest_lower_req)

          //или на том же этаже, но с меньшим номером

          || (ofloor==current_floor && j < car_number))

          car_between_dn = true;

      //если идет вверх, а ЗЭ снизу

      if( (odir==UP || odir==STOP) && requests_below )

        //и он ниже ЗЭ и ближе к нему, чем мы

        if(nearest_lower_req >= ofloor

          && nearest_lower_req - ofloor

            < current_floor - nearest_lower_req)

          car_opposite_up = true;

      //если идет вниз, а ЗЭ сверху

      if( (odir==DN || odir==STOP) && requests_above )

        //и он над ЗЭ и ближе к нему, чем мы

        if(ofloor >= nearest_higher_req

          && ofloor - nearest_higher_req

            < nearest_higher_req - current_floor)

          car_opposite_dn = true;

      }  //конец if(не для нашего лифта)

    }  //конец for(для каждого лифта)

  //если идем вверх или остановились, а ЗЭ над нами

  //и между нами и ЗЭ нет идущих вверх лифтов

  //или идущих вниз над ЗЭ и ближе к нему, чем мы, тогда

  //ехать вверх

  if( (current_dir==UP || current_dir==STOP)

     && requests_above && !car_between_up && !car_opposite_dn )

    {

    current_dir = UP;

    return;

    }

  //если идем вниз или остановились, и снизу есть ЗЭ,

  //и нет лифтов, идущих вниз, между нами и ЗЭ

  //или под ЗЭ, идущих вверх и ближе нас к ЗЭ

  if( (current_dir==DN || current_dir==STOP)

     && requests_below && !car_between_dn && !car_opposite_up )

    {

    current_dir = DN;

    return;

Листинг 13.5 (продолжение)

void elevator::move()

  {                             //если посадка или высадка,

  if(loading_timer || unloading_timer) //не двигаться

    return;

  if(current_dir==UP)           //если идем вверх, идти вверх

    current_floor++;

  else if(current_dir==DN)      //если идем вниз, идти вниз

    current_floor--;

  }  //end move()

//---------------------------------------------------------

void elevator::get_destinations() //остановка, получение

                                  //пунктов назначения

  {

  char ustring[BUF_LENGTH];       //входной рабочий буфер

  int dest_floor;                 //этаж назначения

 

  set_cursor_pos(1,22);clear_line();//удалить верхнюю строку

  set_cursor_pos(1, 22);

  cout << "Лифт " << (car_number+1)

      << " остановился на этаже " << (current_floor+1)

      << "\nЭтаж назначения (0 для окончания ввода)";

  for(int j=1; j<NUM_FLOORS; j++)   //получить запросы этажей

    {           //максимум; обычно меньше

    set_cursor_pos(1, 24);

    cout << "Этаж назначения " << j << ": ";

 

    cin.get(ustring, BUF_LENGTH);   //(во избежание дублирования

                                    //пустых строк)

    cin.ignore(10, '\n');       //съесть символы, включая

                                //ограничитель

    dest_floor = atoi(ustring);

    set_cursor_pos(1,24); clear_line(); //стереть старую

                                        //строку

    if(dest_floor==0)           //если больше нет запросов

      {        //стереть нижние три строки

      set_cursor_pos(1,22); clear_line();   

      set_cursor_pos(1,23); clear_line();

      set_cursor_pos(1,24); clear_line();

      return;

      }

    --dest_floor;                  //начинать с 0, а не 1

    if(dest_floor==current_floor)  //выбрать текущий этаж

      { --j; continue; }           //  забыть его

    //если мы остановились, первый запрос выбирает

                                   //направление движения

    if(j==1 && current_dir==STOP)

      current_dir = (dest_floor < current_floor) ? DN : UP;

    destination[dest_floor] = true;//записать выбор

    dests_display();               //вывести этажи назначения

    }

  }  //конец get_destinations()

//---------------------------------------------------------

Прикладная программа

Следующие два файла, ELEV_APP.H и ELEV_APP.CPP, созданы некими проектиров- щиками лифтовой системы конкретного здания. Соответственно, им нужно адап- тировать ПО для своих целей. Для этого в файле ELEV_APP.H введены две кон- станты — число этажей и число лифтов.

Листинг 13.6. Заголовочный файл ELEV_APP.H

// elev_app.h

// Устанавливает характеристики конкретного здания

const int NUM_FLOORS = 20;  //число этажей const int NUM_CARS = 4;     //число кабинок лифтов

В файле ELEV_APP.CPP инициализируются данные класса building и создается набор объектов типа elevator с помощью new (впрочем, можно было использовать и массив). Затем в цикле вызываются методы master_tick() и get_floor_request(). Функция wait() (объявленная в MSOFTCON.H или BORLACON.H) замедляет процесс для удобства восприятия человеком. Когда пользователь отвечает на запрос программы, таймер (время в программе, в противоположность пользователь- скому времени) останавливается.

Листинг 13.7. Программа ELEV_APP

// elev_app.cpp

// клиентский файл

 

#include "elev.h"              //для объявлений классов

 

int main()

{

  building theBuilding;

  while(true)

    {

    theBuilding.master_tick(); //послать временные метки

                               //всем лифтам

    wait(1000);                //пауза

                //получить запросы этажей от пользователей

    theBuilding.record_floor_reqs();

    }

  return 0;

}

 

Стратегия работы лифтов

Встраивание интеллекта необходимого уровня в лифтовые системы — не такая простая задача, как кажется на первый взгляд. Процесс принятия решения отра- батывается функцией decide(), состоящей из определенного набора правил. Эти правила организованы в порядке их приоритета. Если применяется одно из них, то выполняется некое действие, при этом правила более низких уровней не отра- батываются. Приведем упрощенный вариант такой обработки:

1. Если лифт вот-вот врежется в дно шахты или пробьет ее крышу, навер- ное, следует остановиться.

Если данный этаж — это этаж назначения, высадить пассажиров.

Если обнаружен на данном этаже запрос «вверх», едем вверх, загружаем пассажиров.

Если обнаружен на данном этаже запрос «вниз», едем вниз, загружаем пассажиров.

Если нет запросов этажей или этажей назначения ни снизу, ни сверху, ос- тановиться.

Если этажи назначения сверху, едем вверх.

Если этажи назначения снизу, едем вниз.

Если стоим или движемся вверх, есть запрос с более высокого этажа, и нет при этом лифтов, движущихся вверх, между нами и этажом запроса или над ним, а также движущихся вниз и находящихся ближе к нему, чем мы, то едем вверх.

Если стоим или движемся вниз, есть запрос с более низкого этажа, и нет при этом лифтов, движущихся вниз, между нами и этажом запроса или под ним, а также движущихся вверх и находящихся ближе к нему, чем мы, то едем вниз.

10. Если ничего никому от нас не нужно, останавливаемся.

 

Правила 8 и 9 довольно сложны. Они предназначены для исключения вы- полнения одного и того же запроса несколькими лифтами сразу. Тем не менее результаты не всегда идеальны. В некоторых ситуациях лифты медлят с при- нятием решения, очень опасаясь сделать то, что могут сделать за них другие. Но в реальности в этот момент другие лифты не движутся к той же цели, а, напри- мер, отвечают на свои запросы. Чтобы улучшить стратегию работы системы, не- обходимо заставить различать функцию decide() запросы «вверх» и «вниз» во время проверки относительного местонахождения ЗЭ (запроса с этажа). Но это еще больше усложнило бы и без того слишком длинную функцию, поэтому ос- тавляем возможность дальнейших усовершенствований читателю.

 

22