Про синхронізації процесів в середовищі Windows, C / C + +, Програмування, статті

– Запасайтеся, дияволи, трунами, зараз стріляти буду.
М.
Зощенко. Нервові люди

Завдання Майхілла – ще один (поряд із завданням RS-тригера [3]) приклад вирішення
нетривіальних проблем створення складних систем. Впоравшись з нею, ми навчимося
організовувати взаємодію паралельно працюючих компонентів складних
програмних комплексів в жорстких умовах.

Алгоритм поведінки і автоматна модель стрілка


На перший окрик: “Хто йде?” – Він став жартувати,
На постріл
в повітря закричав: “Кінчай дурити!”
Я трохи забарився і, не вступаючи в
суперечка,
Чінарік виплюнув – і вистрілив в упор.
В. Висоцький

У задачі Майхілла необхідно визначити, як потрібно діяти стрільцям,
побудованим в шеренгу, щоб одночасно відкрити стрілянину, якщо команда
“Вогонь!” (Або “Плі!”) Подається крайнього в шерензі, а обмін інформацією
дозволяється тільки між сусідами.

З відомих рішень даної задачі своєю простотою і, головне, “автоматним”
підходом залучає наведене в роботі [2]. Воно полягає в тому, що кожен
стрілець повинен керуватися таким набором вказівок.

1. Якщо ти лівофланговий і отримав наказ “Шеренга, пли!”, То запам’ятай число 1
– Свій порядковий номер – і рівно через секунду повідом його сусідові справа.

2. Якщо ти неправофланговий і сусід зліва повідомив тобі число V, запам’ятай число
V +1 – свій порядковий номер – і рівно через секунду повідом його сусідові справа.

3. Якщо ти правофланговий і сусід зліва повідомив тобі число n-1, то рівно
через секунду відповідай йому: “Готовий!” і приступай до зворотного рахунку в думці: n, n-1,
n-2, …, відраховуючи по одному числу в секунду.

4. Якщо ти не правофланговий і сусід праворуч доповів тобі: “Готовий!”, То рівно
через секунду приступай до зворотного рахунку в думці: V, V-1, V-2, …, де V – твій
порядковий номер, відраховуючи по одному числу в секунду. При цьому, якщо V> 1,
тобто якщо ти не лівофланговий, то рівно через секунду після отримання повідомлення
від сусіда праворуч доповіси: “Готовий!” сусідові зліва.

5. Дорахував до нуля, стріляй!

Аналогічні вказівки даються, коли наказ отримано правофланговим.

Нескладно показати, що рішення не залежить від обраного часового інтервалу.
Більш того, цей інтервал може бути і “плаваючим” – лише б він був однаковим
для всіх стрільців на кожному кроці рахунку. Завдяки даній властивості алгоритм
легко реалізувати в рамках мережевої автоматної моделі.

На малюнку зображений КА, що моделює поведінку стрілка. Стрeлкі відповідають
синхронизирующей інформації, яка надходить до стрілку від його сусідів зліва і
справа. Предикати і дії автомата можна умовно описати таким чином:

/ / Предикати

x1 Стан сусіда зліва “Вогонь!”?

/ / Команда “Вогонь”;

x2 Стан сусіда справа “Готовий!”?

x3 Свій номер не дорівнює нулю?

/ / Дії

y1 Встановити свій номер: взяти номер

сусіда зліва

і збільшити на 1

y2 Зменшити свій номер на 1

y3 Провести постріл

До речі, ці рядки в подальшому можна перетворити на коментарі до операторів
“Автоматної програми”.

Програмна модель стрільця


Маючи алгоритм розв’язання задачі та модель поведінки стрілка, можна приступити до
програмування. Заголовний файл і реалізація класу “Стрілець” (Rifleman)
показані в лістингу 1.

У класу CRifleman, породженого з автоматного класу LFsaAppl, є три
предиката, три дії і таблиця переходів автомата. Перебуваючи в початковому
стані “Сон” (“солдат спить – служба йде”), стрілець чекає команди “Вогонь!”,
якої відповідає однойменне внутрішній стан сусіда зліва (адреса
сусіда знаходиться в покажчику pFsaLeftMan). Аналіз такої ситуації в автоматі
виконує предикат x1.

При надходженні команди “Вогонь!” автомат виконує дію y1 і переходить в
стан “Вогонь”. Дія y1 присвоює автомату номер на одиницю більше, ніж
у сусіда ліворуч, а стан “Вогонь” сигналізує сусідові справа про те, що дана
команда відкрити стрілянину.

Перебуваючи в стані “Вогонь”, стрілок очікує, щоб сусід справа (адреса
якого зберігається в покажчику pFsaRightMan) перейшов у стан “Готовий”,
визначаючи відповідний момент по істинності предиката x2. Коли це
трапляється, він сам переходить в стан “Готовий” і виконує дію y2, т. е.
зменшує на одиницю свій номер.

Потім починається автономна робота стрілка – зменшення на одиницю свого
номера при кожному такті роботи автомата. При рівності номера нулю автомат
переходить в стан “Постріл”. При цьому виконується дію y3.

Стан “Постріл” стане сигналом для кулі, яка почне свій “нищівний
політ “від одного кордону вікна до іншого (нагадаємо, що в якості кулі ми
використовуємо м’ячик зі статті [1]). Про роль дій y3, y4, y5 і предиката x4 буде
розказано в розділі про стрілянину чергами.

Програмна модель командира


У формулюванні завдання немає ні слова про те, звідки береться команда. Будемо
вважати, що її подає командир і що він робить це, переходячи у внутрішнє
стан “Вогонь”. Заголовний файл і реалізація методів для класу “Командир”
(Officer) представлені в лістингу 2.

Алгоритм функціонування командира простий (але роль його важлива!): Це
циклічні переходи зі стану “Сон” в стан “Вогонь”. З “Сну” командира
можна вивести належним йому методом SetCommand.

Перекуємо м’ячі на кулі


Тепер перетворимо м’ячик в кулю, надавши йому новий алгоритм поведінки. Для цього
введемо в конструктор м’ячика параметр – адреса таблиці переходів. Відзначте,
до речі, що ми міняємо алгоритм роботи об’єкта, не змінюючи його методів, – прийом,
майже неможливий у звичайному програмуванні.

Крім того, як об’єкту деякого контейнера бібліотеки STL, класу
необхідно додати перевантажені оператори ==,! =,> і <.

Для зв’язку зі стрільцем введені посилання і метод, що дозволяє її встановити.
Аналіз внутрішнього стану стрілка, до якого “прикріплена” куля, при
настанні стану “Постріл” виконує предикат x1. Предикат x2 визначає
умова досягнення кулею кордону вікна. Дія y4 введено для установки кулі в
вихідну позицію у вікні відображення. Метод SetCenter поміщає колишній м’ячик (нині
– Кулю) в задану точку, а метод SetMove задає крок переміщення по координатним
осях (див. лістинг 3).

У початковому стані st куля очікує події “Постріл”. Коли воно
відбувається, куля вилітає і переходить у стан b1. У цьому стані вона
перебуває до тих пір, поки не досягне межі вікна, а потім повертається в
стан st і чекає наступного пострілу (така собі куля-бумеранг).

Разом весело крокувати …


Отже, всі “суспільство” в зборі. Є куля, стрілок і командир. Їх потрібно
об’єднати в ланцюг – єдину систему, здатну виконати поставлену задачу.
Дамо класу “Ланцюг” ім’я CchainShot (лістинг 4).

Даний клас створює об’єкт “Командир”, а також масиви об’єктів “Стрілець” і
“Куля” – відповідно IArrayRifleman і IArrayBullet. Зв’язки між породженими
об’єктами організовує метод SetLink.

Метод OnSize повинен викликатися відразу після створення ланцюга стрільців. Він служить
для завдання розмірів м’ячиків-куль, установки їх початкового положення і
визначення кроків переміщення по координатних осях за один такт автоматного
часу.

Методи GetAddrRifleman і GetAddrBullet повертають адреси стрільців і куль по
їх номерами з масивів. При організації зв’язків для кожного стрільця шукається куля з
таким же номером, як у нього.

Організація зв’язків полягає у привласненні значень вказівниками, що входять до
склад об’єктів “Стрілець” і “Куля”. При цьому для першого стрільця сусідом зліва
є командир, а для останнього покажчик на сусіда праворуч має значення
Null.

Батарея, вогонь!

Коли зрозуміли наказ –
Виконуйте цю ж мить!
Л. Філатов.
Про Федота-стрільця, удалого молодца

Об’єкт “Ланцюг стрільців” створюється в тілі методу OnCreate класу CFireView. При
виклику методу OnSize викликається однойменний метод об’єкта “Ланцюг стрільців”,
виконує початкову настройку ланцюга.

За допомогою редактора ресурсів Visual C + + введемо в основне меню програми
команди для відкриття вогню і керування швидкістю руху куль. Програмний код
методів, пов’язаних з цими пунктами меню, приведений в лістингу 5.

Зверніть увагу на те, що автоматні моделі, включаючи і моделі стрільців,
відразу ж після створення в методі OnCreate починають працювати. А управління
швидкістю руху куль реалізовано за допомогою механізму керування швидкістю
роботи мережевої автоматною середовища (методи OnFast і OnSlow). Об’єкт TNetFsa
створюється в основному класі програми CFireApp (докладніше див [1], розділ
“Редагування основного класу програми”).

Ось куля пролетіла, і ага, або А чергами слабo,?!


В основному варіанті програми стрілки випускають по одній пулі. А як зробити,
щоб вони були готові стріляти в будь-який момент? Або, по-іншому, як організувати
стрілянину чергами?

Виявляється, для цього достатньо внести в класи “Стрілець” і “Куля” зовсім
невеликі зміни. Повністю нові класи наведено в прикладі, що додається до
електронної версії цієї статті тут же ми розглянемо лише міркування, що лежать
в основі реалізації “автоматної” стрільби.

По-перше, куля повинна бути випущена точно в потрібний момент. Більш за все для
цього підходить дію y3 стрілка, що дозволяє створити динамічний
об’єкт-кулю. Нехай сам стрілець після пострілу переходить до очікування нової
команди. Тоді дію y3 придбає вид, показаний в лістингу 6.

По-друге, об’єкту “Стрілець” слід передати адресу вікна відображення, який
він, в свою чергу, передасть об’єкту “Куля” (див. лістинг 6).

І по-третє, необхідно надати пулі здатність розпізнавати постріл в
автоматичному режимі, що можна зробити, привласнюючи їй номер, рівний нулю.
Крім того, така куля повинна самознищуватися, досягнувши кордону вікна.

Таблиця переходів для даного варіанту алгоритму приведена в лістингу 7.
Підкреслимо, що при переході КА в стан “00” автоматний об’єкт віддаляється з
мережевий автоматною середовища.

Проробивши описані зміни, можна, нарешті, стріляти чергами. Щоб
випустити кілька куль поспіль, потрібно перш, ніж перша куля досягне кордону
вікна, дати необхідну кількість разів команду Fire в меню програми Command.

Меню дозволяє також задати швидкість польоту кулі, вибравши пункт Slow або Fast
(За умовчанням діє Fast). В остаточному варіанті кулі, що входять в масив,
мають вигляд відомих м’ячиків, а “автоматні” – стилізованих під кулю еліпсів.

“Розбір польотів”


Гримнув постріл в тиші,
Замайорів воронячу зграю,
На війні як на війні

Іноді стріляють.
А. Розенбаум

Отже, використовуючи мінімум понять і коштів, ми за два кроки (перший – у роботі
[1], другий – тут) перетворили вельми обмежений за своїми можливостями
вихідний приклад, запропонований програмістами Microsoft, у більш цікавий,
вирішальний до того ж вельми актуальну і не таку вже просту проблему
синхронізації паралельних процесів.

Крім того, завдання Майхілла по духу ближче розробникам ігрових програм,
які часто використовують модель КА для опису поведінки персонажів [4]. Їм в
Цього разу особливу увагу і уклін!

Отже, вирішуючи завдання Майхілла, ми:


Куля може бути “дурною”, а може володіти “інтелектом” ПТУРСa. Так, можна
різко підвищити ефективність ураження цілей стрілками, навчивши їх стріляти ще й
“Віялом”. Поведінка стрільців можна ускладнити, змусивши їх пересуватися,
взаємодіяти, стріляти одиночними і чергами. При розборі прикладу зверніть
увагу на те, як реалізується формування паузи між кулями за допомогою
“Автоматної затримки” CFDelay. Затримка – ще один варіант використання
автоматних підпрограм.

У прикладі стрільба чергою організовується за допомогою додаткових дій
стрілка y4, y5 і предиката x4, а властивість класу стрілка nLengthQueue визначає
довжину черги з куль. Необхідні зміни внесено і до таблиці переходів кулі
і стрілка.

Можливі й інші варіанти розвитку прикладу – були б час і бажання (і
замовники, звичайно!). З часом буває туго, але одна з цілей FSA-бібліотеки –
допомогти його зберегти. Щасти вам полювання (з автоматами) в “програмних джунглях” і
до нових зустрічей!


Лістинг 1


Програмна модель стрілка

extern LArc RiflemanTBL[];
class CRifleman : public LFsaAppl
{
public:
int GetNumber();
void SetNumber(int n);
void SetLink(CRifleman *pFsaLeft,
CRifleman
*pFsaRigtht);
CRifleman *pFsaRightMan;
CRifleman *pFsaLeftMan;
CRifleman();
CRifleman(int n, CWnd* pW, LArc
*pTBL=RiflemanTBL);
virtual ~CRifleman();
bool operator==(const CRifleman
&var) const;
bool operator<(const CRifleman
&var) const;
bool operator!=(const CRifleman
&var) const;
bool operator>(const CRifleman
&var) const;
protected:
CWnd* pParentWnd;
CFireApp * pApp; / / покажчик на об’єкт
/ / Основного класу програми
int x1(); // Is fire?
int x2(); // Is ready?
int x3(); // Number is equal to zero? Shot!
int x4(); //
void y1(); // To place number.
void y2(); // To reduce number by unit.
void y3(); // Gunshot
void y4(); //
void y5(); //
int nNumber;
int nSaveNumber;
int nLengthQueue; // Length of queue.
int nCurrentQueue; //
};
typedef vector<CRifleman*>
TIArrayRifleman;
typedef vector<CRifleman*>:
:iterator TIIteratorRifleman;
extern LArc RiflemanTBL[];
CRifleman::CRifleman():LFsaAppl() { }
CRifleman::CRifleman(int n, CWnd* pW,
LArc* pTBL):
LFsaAppl(pTBL)
{
pParentWnd = pW;
pFsaRightMan = NULL;
pFsaLeftMan = NULL;
nNumber = n;
nLengthQueue = 5;
nCurrentQueue = nLengthQueue;
if (pParentWnd)
{
pApp = (CFireApp*)AfxGetApp();
FLoad(pApp->pNetFsa,1);
}
}
bool CRifleman::operator==(const CRifleman
&var) const
{
if (nNumber==var.nNumber) return true;
else return false;
}
void CRifleman::SetLink(CRifleman
* pFsaLeft, CRifleman *
pFsaRigtht)
{
pFsaRightMan = pFsaRigtht;
pFsaLeftMan = pFsaLeft;
}
LArc RiflemanTBL[] = {
LArc (“Сон”, “Вогонь”, “x1”, “y1”),
LArc (“Вогонь”, “Готовий”, “x2”, “y2”),
LArc (“Готовий”, “Готовий”, “x3”, “y2”),
LArc (“Готовий”, “Постріл”, “^ x3”, “y3y4”),
LArc (“Постріл”, “Постріл”, “x4”, “y3y5”),
LArc (“Постріл”, “Сон”, “^ x4”, “-“),
LArc()
};
int CRifleman::x1()
{
if (!pFsaLeftMan) return false;
return string((pFsaLeftMan)-
> FGetState ()) == “Вогонь”;
}
int CRifleman::x2()
{
if (!pFsaRightMan) return true;
else return string((pFsaRightMan)-
>FGetState()) ==
“Готовий”;
}
int CRifleman::x3() { return nNumber; }
int CRifleman::x4() { return nCurrentQueue; }
void CRifleman::y1()
{
int n = pFsaLeftMan->GetNumber();
SetNumber(n+1);
}
void CRifleman::y2() { nNumber-; }
void CRifleman::y3() { }
void CRifleman::y4()
{
nCurrentQueue = nLengthQueue;
}
/ / Формування затримки між пострілами
void CRifleman::y5()
{
CFDelay *pCFDelay;
pCFDelay = new CFDelay(200);
pCFDelay->FCall(this);
nCurrentQueue-;
}

Листинг 2


Модель командира

class COfficer : public CRifleman
{
public:
COfficer();
virtual ~COfficer();
void SetCommand();
protected:
CFireApp *pApp; //
int x1(); // Is fire?
void y1();
bool bCommandFire;
};
extern LArc OfficerTBL[];
COfficer::COfficer():CRifleman
(0,NULL,OfficerTBL)
{
bCommandFire = false;
pApp = (CFireApp*)AfxGetApp(); //
FLoad (pApp-> pNetFsa, 1); / / підключити
об’єкт
до КА-мережі
}
COfficer::~COfficer() { }
LArc OfficerTBL[] = {
LArc (“Сон”, “Вогонь”, “x1”, “y1”),
LArc (“Вогонь”, “Сон”, “-“, “-“),
LArc()
};
int COfficer::x1() { return bCommandFire; }
void COfficer::y1() { bCommandFire = false; }
void COfficer::SetCommand() { bCommandFire =
true; }

Листинг 3


Модель кулі

extern LArc BulletTBL[];
class CBullet : public TBounce
{
public:
void SetAddrMan (LFsaAppl
*pFsaAppl);
CBullet();
CBullet(CWnd* pW, int nNum,
CSize sz=CSize(10,10),
LArc *pTBL=BulletTBL);
virtual ~CBullet();
void SetCenter(int x, int y);
void SetMove(int cx, int cy);
protected:
int x1();
int x2();
int x3();
void y4();
protected:
LFsaAppl *pFsaShot;
};
typedef vector<CBullet*>
TIArrayBullet;
typedef vector<CBullet*>:
:iterator
TIIteratorBullet;
CBullet::CBullet(CWnd* pW, int nNum,
CSize sz, LArc *pTBL)
:TBounce(pW, nNum, sz, pTBL)
{
pFsaShot = NULL;
}
CBullet::CBullet():TBounce()
{ pFsaShot = NULL; }
CBullet::~CBullet() { }
void CBullet::SetAddrMan(LFsaAppl
* pFsaAppl) { pFsaShot =
pFsaAppl; }
//
LArc BulletTBL[] = {
LArc("st","b1", "x1", "y4"),
LArc("b1","b1", "^x2", "y1"),
LArc("b1","st", "x2", "y4"),
LArc()
};
int CBullet::x1()
{
if (!pFsaShot) return false;
return string((pFsaShot)-
> FGetState ()) == “постріл”;
}
int CBullet::x2()
{
return m_ptCenter.y + m_sizeRadius.cy >=
rcClient.bottom;
}
int CBullet::x3()
{
return nNumBounce;
}
void CBullet::y4() { SetCenter(0,10); }
void CBullet::SetCenter(int x, int y)
{
if (y) m_ptCenter.y = y;
if (x) m_ptCenter.x = x;
}
void CBullet::SetMove(int cx, int cy)
{
m_sizeMove.cx = cx;
m_sizeMove.cy = cy;
}

Лістинг 4


Модель ланцюга стрільців

class CChainShot
{
public:
CChainShot(CWnd *pW);
virtual ~CChainShot();
void SetLink();
void SetCommand();
void OnSize(int cx, int cy);
CRifleman* GetAddrRifleman(int n);
CBullet* GetAddrBullet(int n);
protected:
CWnd *pWnd;
COfficer *pCOfficer;
TIArrayRifleman IArrayRifleman;
TIArrayBullet IArrayBullet;
};
CChainShot::CChainShot(CWnd *pW)
{
pWnd = pW;
pCOfficer = new COfficer();
for (int i=1; i<=4; i++) {
IArrayRifleman.push_back(new CRifleman(i,pWnd));
IArrayBullet.push_back(new CBullet(pWnd,i));
}
SetLink();
}
CChainShot::~CChainShot()
{
if (pCOfficer) delete pCOfficer;
TIIteratorRifleman iterRifleman =
IArrayRifleman.begin();
while (iterRifleman != IArrayRifleman.end())
delete *iterRifleman++;
IArrayRifleman.erase(IArrayRifleman.begin(),
IArrayRifleman.end());
TIIteratorBullet iterBullet = IArrayBullet
.begin();
while (iterBullet!=IArrayBullet.end()) delete
*iterBullet++;
IArrayBullet.erase(IArrayBullet.begin()
,IArrayBullet.end());
}
void CChainShot::SetCommand()
{
if (pCOfficer) pCOfficer->SetCommand();
}
CRifleman* CChainShot::GetAddrRifleman(int n)
{
CRifleman* currentRifleman=NULL;
CRifleman vs(n, NULL);
TIIteratorRifleman iterRifleman
= IArrayRifleman.begin();
while (iterRifleman != IArrayRifleman.end()) {
currentRifleman= *iterRifleman++;
if (*currentRifleman==vs) break;
}
return currentRifleman;
}
CBullet* CChainShot::GetAddrBullet(int n)
{
CBullet* currentBullet=NULL;
CBullet vs(NULL, n);
if (!IArrayBullet.empty()) {
TIIteratorBullet iterBullet = IArrayBullet
.begin();
while (iterBullet != IArrayBullet.end()) {
currentBullet= *iterBullet++;
if (*currentBullet==vs) break;
}
}
return currentBullet;
}
void CChainShot::SetLink()
{
LFsaAppl *currentRifleman;
TIIteratorRifleman iterRifleman =
IArrayRifleman.begin();
int n =1;
CRifleman *pFsaLeft = NULL;
CRifleman *pFsaRight = NULL;
while (iterRifleman != IArrayRifleman.end())
{
if (n==1)
{
currentRifleman= *iterRifleman++;
((CRifleman*)currentRifleman)-
>SetNumber(n);
n++;
pFsaLeft = pCOfficer;
pFsaRight= *iterRifleman++;
((CRifleman*)pFsaRight)->SetNumber(n);
n++;
((CRifleman*)currentRifleman)->
SetLink(pFsaLeft, pFsaRight);
}
else
{
pFsaLeft = currentRifleman;
if (iterRifleman != IArrayRifleman.end())
{
currentRifleman = pFsaRight;
pFsaRight= *iterRifleman++;
((CRifleman*)pFsaRight)->SetNumber(n);
n++;
((CRifleman*)currentRifleman)->
SetLink(pFsaLeft, pFsaRight);
}
}
}
pFsaLeft = currentRifleman;
currentRifleman = pFsaRight;
pFsaRight= NULL;
((CRifleman*)currentRifleman)-
>SetLink(pFsaLeft,
pFsaRight);
TIIteratorBullet iterBullet =
IArrayBullet.begin();
while (iterBullet != IArrayBullet.end()) {
CBullet* currentBullet= *iterBullet++;
CRifleman* pRf=GetAddrRifleman
(currentBullet->GetNum());
currentBullet->SetAddrMan(pRf);
}
}
void CChainShot::OnSize(int cx, int cy)
{
int n=1;
CBullet* currentBullet;
TIIteratorBullet iterBullet =
IArrayBullet.begin();
while (iterBullet != IArrayBullet.end()) {
currentBullet= *iterBullet++;
currentBullet->Size(CSize(cx/n,cy/n));
currentBullet->SetCenter(400/n-20,10);
currentBullet->SetMove(0,1);
// currentBullet->SizeBounce(CSize(20,20));
n++;
}
}

Лістинг 5


Об’єкт вікна-відображення

void CFireView::OnFire()
{
pChainShot->SetCommand();
}
int CFireView::OnCreate(LPCREATESTRUCT
lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
pChainShot = new CChainShot(this);
CFireApp *pApp = (CFireApp*)AfxGetApp(); //
pApp-> pNetFsa-> go_task (); / / запуск
КА-об’єкта
return 0;
}
void CFireView::OnSize(UINT nType,
int cx, int cy)
{
pChainShot->OnSize(cx, cy);
CView::OnSize(nType, cx, cy);
}
void CFireView::OnFast()
{
CFireApp *pApp = (CFireApp*)AfxGetApp();
pApp->lCountTime=0;
}
void CFireView::OnSlow()
{
CFireApp *pApp = (CFireApp*)AfxGetApp();
pApp->lCountTime=1000;
}

Лістинг 6


Дія y3 моделі стрілка

void CRifleman::y3()
{
CRect r;
pParentWnd->GetClientRect(r);
CSize cz = r.Size();
int x1, y1;
x1=cz.cx/nSaveNumber;
y1= cz.cy/nSaveNumber;
CBullet *currentBullet =
new CBullet(pParentWnd, 0);
/ / Завдання початкового положення
кулі та її розмірів
currentBullet->SetCenter(x1-50,10);
currentBullet->SetMove(0,3);//
інтервал між кулями
// currentBullet->SetMove(nCurrentQueue,3);
/ / Стрілянина
/ / Віялом
currentBullet->SizeBounce(CSize(2,5));
/ / Передача адреси стрілка нової пулі
currentBullet->SetAddrMan(this);
// currentBullet->FCall(this);
}

Лістинг 7


Таблиця переходів “автоматної” кулі

LArc BulletTBL[] = {
LArc("st","b1", "x1x3", "y4"),
LArc("st","b1", "^x3", "y4"),
LArc("b1","b1", "^x2", "y1"),
LArc("b1","st", "x2x3", "y4"),
LArc("b1","00", "x2^x3","-"),
LArc()
};

Коментарі:
Все це звичайно цікаво але є кілька зауважень.
1) Це абстрактна
модель поведінки представлена ​​в термінах деякої аналогії
2) В силу п.1 ця
модель, як і всяка абстракція за своєю суттю не позбавлена ​​недоліків, оскільки
не враховує умов конкретної реалізації.
3) В деяких специфічних
умовах дана реалізація можливо і буде оптимальною, але завдання реалізує
ці умови буде тільки одна з нескінченної кількості можливих.

Тепер
більш зрозумілою мовою.
Приклад 1. Нехай дійсно терміни “шеренга”,
“Солдат” “постріл” та інші означають те що ми зазвичай звикли під цими словами
розуміти. Тоді умова “через одну секунду сказати” або відповісти означає що у
солдата є доступ до таймера: (ну скажімо наручні годинники з секундною стрілкою або
електронні, або загальні на всіх на часовий вежі яку прекрасно видно всім –
не суть важливо). Тоді ця реалізація не буде оптимальною як за
ефективності:

(Крайній у шерензі може відразу ж сказати час коли
потрібно відкрити стрілянину, приплюсувавши до поточного часу деякий завідомо
достатній проміжок часу, необхідний для розповсюдження команди по
шерензі),
так і за часом:

(
-Крайній зліва говорить поточний час
і тут же відзначає його,
-По шерензі поширюється поточний час,

-Крайній праворуч вираховує проміжок часу витрачений на
поширення сигналу, віднімаючи з поточного часу, передане йому час,
плюсує цю різницю знову до поточного і передає в зворотному порядку.
– Таким
чином необхідність затримки в 1 сек відпадає.).

Приклад 2. Нехай ми
маємо деяке число потоків, в яких визначені TCP-гнізда / Требуется
одночасно відправити з цих гнізд запит на з’єднання з віддаленим гніздом.
Оскільки всі потоки мають доступ до одного глобального таймеру (годинам) то все
що для цього потрібно – встановити в деякій глобальної змінної час
коли необхідно почати з’єднання. Потоки періодично будуть перевіряти цю
змінну і:
1) Якщо нуль то чекати
2) Якщо не нуль але більше ніж значення
глобального таймера – чекати
3) Якщо не нуль але менше – приступити до
встановлення з’єднання.

Приклад 3. Модифікований приклад 2, де замість
потоків виступають різні комп’ютери в мережі із запущеною на них відповідної
програмою. У цьому випадку стає відчутною реальністю дуже небезпечна
ситуація що якщо хоч одна ланка в ланцюжку, побудованої повністю по
представленій схемі і що називається буквально, перестане функціонувати –
це поставить під загрозу всю процедуру “розстрілу”. Якщо ж піти повністю від
даної абстракції, взявши за основу тільки кінцеве завдання, напрошується
кілька інших (різних в залежності від необхідної точності) шляхів вирішення
завдання стрільців. Можна як припускати що таймери на всіх комп’ютерах йдуть
синхронно (в рамках необхідної точності), так і взяти за основу зовнішній таймер (в
рамках необхідної точності тут допускається що топологія такої мережі буде
“Зірка” з таймером в центрі), ну і так далі аж до релятивістської теорії
відносності:))

P.S. Багато написав однако. Оглянувся ось і сам
здивувався:). А між тим все можна було б просто висловити все одним
предложженіем: “Розум – це Будда, а припинення умоглядного мислення – це
шлях. “(С) Робер Шеклі” Варіанти вибору “;)
Не слід переоцінювати
значення абстракцій в процесі аналізу конкретних завдань.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*