مثال آلة محدودة. آلات الحالة المحدودة، كيفية البرمجة دون مشاكل. العمليات مع الآلات

نظرية الأتمتة هي فرع من الرياضيات المنفصلة التي تدرس نماذج محولات المعلومات المنفصلة. مثل هذه المحولات هي أجهزة حقيقية (أجهزة كمبيوتر، كائنات حية) وأجهزة وهمية (نظريات بديهية، آلات رياضية). في الأساس، يمكن وصف آلة الحالة المحدودة بأنها جهاز م ، لها قنوات إدخال وإخراج، وفي كل لحظة من اللحظات المنفصلة من الزمن، تسمى لحظات الساعة، تكون في إحدى الحالات النهائية.

عن طريق قناة الإدخال في كل لحظة من الزمن ر =1، 2، ... إلى الجهاز م تصل إشارات الإدخال (من مجموعة محدودة من الإشارات). يتم ضبط قانون تغيير الحالة في المرة القادمة اعتمادًا على إشارة الإدخال وحالة الجهاز في الوقت الحالي. تعتمد إشارة الخرج على الحالة وإشارة الدخل في الوقت الحالي (الشكل 1).

آلة الحالة المحدودة هي نموذج رياضي لأجهزة معالجة المعلومات المنفصلة الحقيقية.

آلة الدولة يسمى النظام أ= (X , س , ي , , )، أين X , س , ي هي مجموعات محدودة تعسفية غير فارغة، و و - وظائف منها:

    مجموعة من X ={أ 1 , ..., أ م ) يسمى أبجدية الإدخال ، وعناصره هي إشارات الإدخال ، تسلسلها موجود بكلمات شائعة ;

    مجموعة من س ={س 1 , ..., س ن ) يسمى العديد من الدول الإنسان الآلي وعناصره - تنص على ;

    مجموعة من ي ={ب 1 , ..., ب ص ) يسمى الأبجدية الإخراج ، عناصرها هي إشارات الإخراج ، تسلسلاتها هي كلمات الخروج ;

    وظيفة : X س س مُسَمًّى وظيفة الانتقال ;

    وظيفة :X س ي مُسَمًّى وظيفة الإخراج .

هكذا، (س , س )س , (س , س )ي ل  س X , س س .

ترتبط آلة الحالة المحدودة بجهاز وهمي يعمل على النحو التالي. يمكن أن يكون في واحدة من العديد من الولايات س ، إدراك الإشارات من مجموعة متنوعة من X وإصدار إشارات من مجموعة متنوعة من ي .

2. طرق تحديد آلة الحالة المحدودة

هناك عدة طرق متكافئة لتعريف الأوتوماتا المجردة، يمكن تسمية ثلاث منها: مجدول , هندسي و وظيفي .

2.1 المهمة الجدولية للآلة

يترتب على تعريف الإنسان الآلي أنه يمكن دائمًا تحديده من خلال جدول يحتوي على مدخلين ت خطوط و ص الأعمدة، حيث عند تقاطع العمود س والسلاسل أ هي قيم الدالة (أ أنا , س ي ), (أ أنا , س ي ).

س

أ

س 1

س ي

س ن

أ 1

(أ 1 , س 1), (أ 1 , س 1)

(أ 1 , س ي ), (أ 1 , س ي )

(أ 1 , س ن ), (أ 1 , س ن )

أ أنا

(أ أنا , س 1), (أ أنا , س 1)

(أ أنا , س ي ), (أ أنا , س ي )

(أ أنا , س ن ), (أ أنا , س ن )

أ م

(أ م , س 1), (أ م , س 1)

(أ م , س ي ), (أ م , س ي )

(أ م , س ن ), (أ م , س ن )

2.2. تحديد إنسان آلي باستخدام مخطط مور

هناك طريقة أخرى لتعريف آلة الحالة المحدودة وهي بيانياً، أي باستخدام الرسم البياني. يتم تصوير الإنسان على شكل رسم بياني موجه ز(س , د ) مع العديد من القمم س والعديد من الأقواس د ={(س ي , (أ أنا , س ي ))| س ي س , أ أنا X )، بينما القوس ( س ي , (أ أنا , س ي )) تم وضع علامة على الزوج ( أ أنا , (أ أنا , س ي )). وهكذا، بهذه الطريقة، يتم تصوير حالات الآلة بواسطة دوائر تُكتب فيها رموز الحالة س ي (ي = 1, …, ن ). من كل دائرة يتم تنفيذها ت الأسهم (الحواف الموجهة) واحد لواحد المقابلة لأحرف أبجدية الإدخال X ={أ 1 , ..., أ م ). السهم المقابل للحرف أ أنا X ويترك الدائرة س ي س ، وينسب إلى الزوج ( أ أنا , (أ أنا , س ي ))، وهذا السهم يؤدي إلى الدائرة المقابلة (أ أنا , س ي ).

الرسم الناتج يسمى الرسم البياني الآلي أو، مخطط مور . بالنسبة للآلات غير المعقدة، تكون هذه الطريقة أكثر وضوحًا من مجدول.

في هذه المقالة، يشير مصطلح "آلة الحالة المحدودة" إلى خوارزمية يمكن أن تكون في واحدة من عدد صغير من الحالات. "الحالة" هي حالة معينة تحدد علاقة معينة بين إشارات الإدخال والإخراج، بالإضافة إلى إشارات الإدخال والحالات اللاحقة. سوف يلاحظ القارئ الذكي على الفور أن آلات الحالة المحدودة الموصوفة في هذه المقالة هي آلات ميلي. آلة ميلي هي آلة ذات حالة محدودة حيث تكون المخرجات عبارة عن وظائف للحالة الحالية وإشارة الإدخال، على عكس آلة مور حيث تكون المخرجات وظائف للحالة فقط. في كلتا الحالتين، الحالة اللاحقة هي دالة للحالة الحالية وإشارة الدخل.

دعونا نلقي نظرة على مثال لآلة الحالة المحدودة البسيطة. تخيل أنه في سلسلة نصية تحتاج إلى التعرف على تسلسل الأحرف "//". ويبين الشكل 1 كيف يتم ذلك باستخدام آلة الحالة. لا ينتج عن الظهور الأول للشرطة المائلة إشارة خرج، ولكنه يتسبب في انتقال الجهاز إلى الحالة الثانية. إذا لم يجد الجهاز في الحالة الثانية شرطة مائلة، فإنه يعود إلى الحالة الأولى، لأنه يتطلب وجود خطين مائلين على التوالي. إذا تم العثور على الشرطة المائلة الثانية، يصدر الجهاز إشارة "جاهز".

معرفة ما يحتاجه العميل.

إنشاء مخطط انتقال الدولة

قم بتشفير "الهيكل العظمي" لجهاز الحالة دون تفصيل عمليات الفرع.

تأكد من أن التحولات تعمل بشكل صحيح.

كن محددًا بشأن تفاصيل الانتقال.

خذ الاختبار.

مثال آلة الدولة

دعونا نلقي نظرة على مثال أكثر إثارة للاهتمام لآلة الحالة - وهو برنامج يتحكم في سحب وتمديد جهاز هبوط الطائرة. على الرغم من أن معظم الطائرات تقوم بهذا الإجراء باستخدام آلية التحكم الكهروهيدروليكية (ببساطة لأنه لا يوجد كمبيوتر على متن الطائرة)، إلا أنه في بعض الحالات، كما هو الحال في المركبات الجوية بدون طيار، فإن الأمر يستحق استخدام التحكم البرمجي.

أولاً، دعونا نلقي نظرة على المعدات. يتكون جهاز الهبوط للطائرة من جهاز مقدمة، وجهاز هبوط رئيسي أيسر، وجهاز هبوط رئيسي أيمن. يتم تشغيلها بواسطة آلية هيدروليكية. تعمل المضخة الهيدروليكية التي تعمل بالكهرباء على توفير الضغط لمشغل الطاقة. باستخدام البرنامج، يتم تشغيل المضخة أو إيقاف تشغيلها. يقوم الكمبيوتر بضبط موضع صمام الاتجاه - "لأسفل" أو "لأعلى" - للسماح بالضغط برفع أو خفض جهاز الهبوط. يحتوي كل دعامة من دعامات الهيكل على مفتاح حد: يتم إغلاق أحدهما إذا تم رفع الهيكل، والآخر - إذا كان مقفلاً في الوضع السفلي. لتحديد ما إذا كانت الطائرة على الأرض أم لا، يتم إغلاق مفتاح الحد الموجود على دعامة ترس المقدمة عندما يكون وزن الطائرة على ترس المقدمة. تتكون أدوات التحكم الخاصة بالطيار من ذراع جهاز الهبوط العلوي/السفلي وثلاثة مصابيح (واحد لكل ساق) يمكن إطفاؤها، باللون الأخضر (وضعية الأسفل)، أو الأحمر (وضعية الانطلاق).

الآن دعنا ننتقل إلى تطوير آلة الحالة المحدودة. الخطوة الأولى والأكثر صعوبة هي فهم التوقعات الحقيقية للعميل. إحدى مزايا آلة الحالة المحدودة هي أنها تجبر المبرمج على التفكير في جميع الحالات المحتملة، ونتيجة لذلك، يتلقى جميع المعلومات المطلوبة من العميل. لماذا أعتبر هذه المرحلة الأصعب؟ وكم مرة تم إعطاؤك وصفًا مشابهًا للمهمة: لا تسحب جهاز الهبوط إذا كانت الطائرة على الأرض.

بالطبع، هذا مهم، لكن العميل يعتقد أن هذا هو المكان الذي ينتهي فيه كل شيء. وماذا عن بقية الحالات؟ هل يكفي سحب جهاز الهبوط لحظة إقلاع الطائرة من الأرض؟ ماذا لو ارتدت الطائرة على نتوء على المدرج؟ ماذا لو قام الطيار بتحريك عصا التروس إلى الوضع العلوي أثناء ركن السيارة، ونتيجة لذلك، يبدأ في الإقلاع؟ هل يجب رفع جهاز الهبوط في هذه الحالة؟

إحدى مزايا التفكير من حيث آلة الحالة هي أنه يمكنك رسم مخطط انتقال الحالة بسرعة على لوحة العرض، أمام العميل مباشرة، والمشي خلال العملية بأكملها معه. يتم قبول التعيين التالي لانتقال الحالة: "الحدث الذي تسبب في التحول"/"إشارة الخرج نتيجة للانتقال". لو قمنا فقط بتطوير ما طلبه العميل في البداية ("لا تسحب جهاز الهبوط إذا كانت الطائرة على الأرض")، لكنا قد استلمنا الآلة الموضحة في الشكل 2.

عند إنشاء مخطط انتقال الحالة (أو أي خوارزمية أخرى)، ضع ما يلي في الاعتبار:

تعمل أجهزة الكمبيوتر بسرعة كبيرة مقارنة بالمعدات الميكانيكية

قد لا يعرف المهندس الميكانيكي الذي يشرح ما يجب القيام به الكثير عن أجهزة الكمبيوتر والخوارزميات مثلك. وهذا أيضًا أمر إيجابي، وإلا فلن تكون هناك حاجة إليك!

كيف سيتصرف برنامجك إذا انكسر جزء ميكانيكي أو كهربائي؟

يظهر الشكل 3 آلة حالة تعتمد على ما يطلبه العميل فعليًا. هنا نريد منع جهاز هبوط الطائرة من التراجع حتى تصبح بالتأكيد في الهواء. للقيام بذلك، بعد فتح مفتاح الهبوط، تنتظر الآلة لعدة ثوان. نريد أيضًا الاستجابة للحافة المرتفعة لرافعة الطيار بدلاً من المستوى، مما سيتجنب المشاكل إذا قام شخص ما بتحريك الرافعة أثناء وقوف الطائرة. إن سحب أو تمديد جهاز الهبوط يستغرق بضع ثوان، ويجب أن نكون مستعدين للموقف الذي سيغير فيه الطيار رأيه خلال هذه العملية ويحرك الرافعة في الاتجاه المعاكس. لاحظ أيضًا أنه إذا هبطت الطائرة مرة أخرى ونحن في حالة "انتظار الإقلاع"، فسيتم إعادة تشغيل المؤقت ولن يتراجع جهاز الهبوط إلا إذا كانت الطائرة في الهواء لمدة ثانيتين.

تنفيذ آلة الدولة المحدودة

القائمة 1 هي تطبيقي لآلة الحالة الموضحة في الشكل 3. دعونا نناقش بعض تفاصيل الكود.

/*القائمة 1*/

تعداد typedef(GEAR_DOWN = 0، WTG_FOR_TKOFF، RAISING_GEAR، GEAR_UP، LOWERING_GEAR) نوع_الحالة؛

/*يحتوي هذا المصفوفة على مؤشرات للوظائف التي يتم استدعاؤها في حالات معينة*/

فارغ(*state_table)() = (GearDown, WtgForTakeoff, RaisingGear, GearUp, LoweringGear);

State_Type curr_state؛

تهيئةLdgGearSM();

/*قلب الآلة هو هذه الحلقة التي لا نهاية لها. وظيفة المقابلة

الحالة الحالية، يتم استدعاؤها مرة واحدة لكل تكرار */

بينما (1) {

State_table();

DecrementTimer();

فارغتهيئةLdgGearSM( فارغ )

curr_state = GEAR_DOWN;

/*إيقاف المعدات وإطفاء الأنوار وما إلى ذلك*/

فارغجير داون( فارغ )

/* انتقل إلى حالة الانتظار إذا كانت الطائرة

لم يكن على الأرض وتلقى الأمر برفع جهاز الهبوط*/

لو((gear_lever == UP) && (prev_gear_lever == DOWN) && (squat_switch == UP)) (

curr_state = WTG_FOR_TKOFF;

prev_gear_lever = gear_lever;

فارغرايزينج جير( فارغ )

لو((nosegear_is_up == MADE) && (leftgear_is_up == MADE) && (rtgear_is_up == MADE)) (

curr_state = GEAR_UP;

/*إذا غيّر الطيار قراره، انتقل إلى حالة "جهاز الهبوط السفلي"*/

لو(gear_lever == للأسفل) (

curr_state = LOWERING_GEAR;

فارغاستعد( فارغ )

/*إذا قام الطيار بتحريك الرافعة إلى الوضع "لأسفل"،

نذهب إلى حالة "خفض جهاز الهبوط"*/

لو(gear_lever == للأسفل) (

curr_state = LOWERING_GEAR;

فارغوتغفورتاكيوف( فارغ )

/* انتظر قبل رفع جهاز الهبوط.*/

لو(مؤقت<= 0.0) {

curr_state = RAISING_GEAR;

/*إذا لمسنا مرة أخرى أو غير الطيار رأيه، ابدأ من جديد*/

لو((squat_switch == DOWN) || (gear_lever == DOWN)) (

curr_state = GEAR_DOWN;

/* لا أريد أن أطلب منه تبديل الرافعة مرة أخرى

كان هذا مجرد ارتداد.*/

prev_gear_lever = DOWN;

فارغخفض العتاد( فارغ )

لو(gear_lever == UP) (

curr_state = RAISING_GEAR;

لو((nosegear_is_down == MADE) && (leftgear_is_down == MADE) &&(rtgear_is_down == MADE)) (

curr_state = GEAR_DOWN;

أولاً، قد تلاحظ أن وظيفة كل حالة يتم تنفيذها بواسطة دالة C منفصلة. بالطبع، سيكون من الممكن تنفيذ إنسان آلي باستخدام بيان التبديل مع حالة منفصلة لكل حالة، ولكن هذا يمكن أن يؤدي إلى وظيفة طويلة جدًا (10-20 سطرًا من التعليمات البرمجية لكل حالة لكل حالة من الحالات 20-30) . يمكن أن يؤدي هذا أيضًا إلى حدوث أخطاء إذا قمت بتغيير الكود في المراحل النهائية من الاختبار. ربما لم تنسَ أبدًا بيان الاستراحة في نهاية القضية، لكن مثل هذه الحالات حدثت لي. لن ينتهي الأمر أبدًا بالرمز الخاص بحالة ما في الرمز الخاص بحالة أخرى إذا كان لديك وظيفة منفصلة لكل حالة.

لتجنب استخدام عبارة التبديل، أستخدم مصفوفة من المؤشرات لتوضيح الوظائف، وأعلن أن المتغير المستخدم كمؤشر للمصفوفة هو من النوع enum.

للتبسيط، يتم تمثيل أجهزة الإدخال/الإخراج المسؤولة عن قراءة حالة المفاتيح وتشغيل المضخات وإيقافها وما إلى ذلك كمتغيرات بسيطة. من المفترض أن تكون هذه المتغيرات بمثابة "عناوين سحرية" مرتبطة بالأجهزة من خلال وسائل غير مرئية.

الشيء الآخر الواضح هو أن الكود لا يهم حقًا في هذه المرحلة. إنه ببساطة ينتقل من دولة إلى أخرى. وهذه خطوة وسيطة مهمة ولا ينبغي تجاهلها. بالمناسبة، سيكون من الجيد إضافة عبارات الطباعة بين توجيهات الترجمة الشرطية (#ifdef DEBUG .. #endif) التي من شأنها طباعة الحالة الحالية وقيم إشارات الإدخال.

مفتاح النجاح يكمن في الكود الذي يسبب انتقال الحالة، أي. يحدد أن إدخال البيانات قد حدث.

إذا مر الكود عبر جميع الحالات بشكل صحيح، فإن الخطوة التالية هي كتابة "ملء" الكود، أي ما ينتج إشارة الخرج بالضبط. تذكر أن كل انتقال له إشارة دخل (الحدث الذي تسبب فيه) وإشارة خرج (جهاز الإدخال/الإخراج، كما في مثالنا). غالبًا ما يكون من المفيد التقاط ذلك في شكل جدول انتقال الحالة.

في جدول انتقال الحالة، يوجد صف واحد لكل انتقال حالة.

عند برمجة جهاز الحالة، حاول الحفاظ على قوتها - وهو وجود تطابق واضح بين متطلبات العميل والرمز. قد يكون من الضروري إخفاء تفاصيل الأجهزة في طبقة وظيفية أخرى، على سبيل المثال، لجعل كود آلة الحالة يبدو مثل جدول انتقال الحالة ومخطط انتقال الحالة قدر الإمكان. يساعد هذا التناظر في منع الأخطاء ويشرح سبب كون أجهزة الحالة جزءًا مهمًا من ترسانة مبرمجي الأنظمة المدمجة. بالطبع، يمكنك تحقيق نفس التأثير باستخدام مربعات الاختيار وعدد لا حصر له من عبارات if المتداخلة، ولكن هذا من شأنه أن يجعل من الصعب جدًا تتبع الكود ومقارنته برغبات العميل.

يقوم مقتطف الكود الموجود في القائمة 2 بتوسيع وظيفة RaisingGear(). لاحظ أن كود الدالة RaisingGear() يهدف إلى عكس صفين من جدول الانتقال لحالة RaisingGear.

فارغرايزينج جير( فارغ )

/*بعد رفع جميع المفاتيح، ننتقل إلى حالة "رفع الهيكل"*/

لو((nosegear_is_up == MADE) && (leftgear_is_up == MADE) && (rtgear_is_up == MADE)) (

Pump_motor = OFF؛

gear_lights = EXTINGUISH;

curr_state = GEAR_UP;

/*إذا غيّر الطيار رأيه، ابدأ في سحب جهاز الهبوط*/

لو(gear_lever == للأسفل) (

Pump_direction = DOWN;

curr_state = GEAR_LOWERING;

تذكر أن تتجنب الظروف الكامنة. تحدث الحالة المخفية عندما تحاول، بسبب الكسل، إضافة حالة فرعية شرطية بدلاً من إضافة حالة معينة. على سبيل المثال، إذا كانت التعليمات البرمجية الخاصة بك تعالج نفس إشارة الإدخال بطرق مختلفة (أي تؤدي إلى انتقالات مختلفة للحالة) اعتمادًا على الوضع، فهي حالة مخفية. في هذه الحالة، أود أن أتساءل عما إذا كان ينبغي تقسيم هذه الدولة إلى قسمين؟ إن استخدام الحالات المخفية يلغي فائدة استخدام آلة الحالة.

كممارسة، يمكنك تمديد آلة الحالة التي نظرنا إليها للتو عن طريق إضافة مهلة إلى دورة سحب أو تمديد جهاز الهبوط، لأن... لا يريد المهندس الميكانيكي أن تعمل المضخة الهيدروليكية لمدة أطول من 60 ثانية. إذا انتهت الدورة، فيجب تنبيه الطيار من خلال تبديل الضوء الأخضر والأحمر، ويجب أن يكون قادرًا على تحريك الرافعة مرة أخرى للمحاولة مرة أخرى. قد ترغب أيضًا في سؤال مهندس ميكانيكي افتراضي عن تأثير عكس اتجاه المضخة أثناء تشغيلها على المضخة، لأنه يحدث في حالتين عندما يغير الطيار رأيه. وبطبيعة الحال، سيقول الميكانيكي أنه سلبي. إذن كيف يمكنك تعديل آلة الحالة لإيقاف المضخة بسرعة عند تغيير الاتجاه؟

اختبار آلة الدولة

إن جمال خوارزميات التشفير كآلات حالة هو أن خطة الاختبار تكتب نفسها تلقائيًا تقريبًا. كل ما عليك فعله هو المرور بكل مرحلة انتقالية للحالة. عادةً ما أقوم بذلك مع وجود علامة في يدي، مع شطب الأسهم الموجودة على مخطط انتقال الحالة أثناء اجتياز الاختبار. هذه طريقة جيدة لتجنب "الحالات المخفية" - حيث يتم تفويتها في الاختبارات أكثر من الحالات المحددة.

وهذا يتطلب قدرًا كبيرًا من الصبر والكثير من القهوة، حيث أنه حتى آلة الدولة متوسطة الحجم يمكن أن تحتوي على ما يصل إلى 100 عملية انتقال مختلفة. بالمناسبة، يعد عدد التحولات طريقة رائعة لقياس مدى تعقيد النظام. يتم تحديد الأخير وفقًا لمتطلبات العميل، وتجعل آلة الحالة نطاق الاختبار واضحًا. مع اتباع نهج أقل تنظيمًا، قد يكون مقدار الاختبار المطلوب مثيرًا للإعجاب، ولكنك ببساطة لن تعرف ذلك.

من السهل جدًا استخدام عبارات الطباعة في التعليمات البرمجية الخاصة بك والتي تعرض الحالة الحالية وقيم إشارات الإدخال والإخراج. يتيح لك ذلك ملاحظة ما تعبر عنه القاعدة الذهبية لاختبار البرمجيات بسهولة: تأكد من أن البرنامج يفعل ما هو مقصود منه، وكذلك أنه لا يفعل أي شيء غير ضروري. بمعنى آخر، هل تحصل فقط على المخرجات التي تتوقعها، وما الذي يحدث بعد ذلك؟ هل هناك أي تحولات "صعبة" للدولة، أي؟ الدول التي تمرر بشكل عشوائي لتكرار واحد فقط للحلقة؟ هل تتغير المخرجات عندما لا تتوقع ذلك؟ من الناحية المثالية، يجب أن تشبه مخرجات printfs بشكل ملحوظ جدول انتقال الحالة.

أخيرًا - وهذا ينطبق على أي برنامج مضمن، وليس فقط البرامج المعتمدة على أجهزة الدولة - كن حذرًا للغاية في المرة الأولى التي تقوم فيها بتشغيل البرنامج على أجهزة حقيقية. من السهل جدًا أن تخطئ في قطبية الإشارة - "أوه، اعتقدت أن 1 يعني جهاز الهبوط لأعلى و0 يعني جهاز الهبوط للأسفل." في كثير من الحالات، كان مساعد الأجهزة الخاص بي يستخدم "مفتاح التشغيل السريع" مؤقتًا لحماية المكونات القيمة حتى يتأكد من أن برنامجي كان يحرك الأمور في الاتجاه الصحيح.

يطلق

عند تلبية جميع متطلبات العميل، يمكنني تشغيل آلة حالة ذات تعقيد مماثل في غضون يومين. دائمًا ما تفعل الآلات ما أريد. أصعب شيء بالطبع هو فهم ما يريده العميل بالضبط والتأكد من أن العميل نفسه يعرف ما يريد. هذا الأخير يستغرق وقتا أطول بكثير!

مارتن جوميز هو مبرمج في مختبر الفيزياء التطبيقية بجامعة جونز هوبكنز. تشارك في تطوير البرمجيات لدعم رحلات المركبات الفضائية البحثية. عملت في مجال تطوير الأنظمة المدمجة لمدة 17 عاما. يحمل مارتن بكالوريوس العلوم في هندسة الطيران وماجستير العلوم في الهندسة الكهربائية من جامعة كورنيل.

تتناول المقالة أجهزة الحالة المحدودة البسيطة وتنفيذها في لغة C++ باستخدام بنيات التبديل وجداول وقت التشغيل ومكتبة Boost Statechart.

مقدمة

بشكل تقريبي، فإن آلة الحالة المحدودة، من خلال عيون المستخدم، هي صندوق أسود يمكن نقل شيء ما إليه وتلقي شيء ما من هناك. هذا تجريد مريح للغاية يسمح لك بإخفاء خوارزمية معقدة، كما أن أجهزة الحالة المحدودة فعالة جدًا أيضًا.

يتم تصوير آلات الحالة المحدودة في شكل رسوم بيانية تتكون من حالات وانتقالات. اسمحوا لي أن أشرح بمثال بسيط:

كما خمنت على الأرجح، هذا هو مخطط حالة المصباح الكهربائي. تتم الإشارة إلى الحالة الأولية بدائرة سوداء، والانتقالات بالأسهم، ويتم تمييز بعض الأسهم - وهذه هي الأحداث التي ينتقل بعدها الجهاز إلى حالة أخرى. لذلك، مباشرة من الحالة الأولية، نجد أنفسنا في الدولة الإضائة مطفئة- المصباح لا يضيء . إذا قمت بالضغط على الزر، فسوف يغير الجهاز حالته ويتبع السهم المحدد اضغط الزر، في حالة الضوء يعمل- المصباح مضاء. يمكنك الانتقال من هذه الحالة، مرة أخرى باتباع السهم، بعد الضغط على الزر، إلى الحالة الإضائة مطفئة.

تُستخدم الجداول الانتقالية أيضًا على نطاق واسع:

التطبيق العملي للآلات الأوتوماتيكية

تستخدم آلات الحالة المحدودة على نطاق واسع في البرمجة. على سبيل المثال، من المريح جدًا أن نتخيل تشغيل جهاز على شكل آلة أوتوماتيكية. وهذا سيجعل الكود أبسط وأسهل للتجربة والصيانة.

تُستخدم أيضًا آلات الحالة المحدودة لكتابة جميع أنواع المحللين اللغويين ومحللي النصوص، وبمساعدتهم يمكنك البحث بشكل فعال عن السلاسل الفرعية، كما تتم ترجمة التعبيرات العادية إلى آلة الحالة المحدودة.

على سبيل المثال، سنقوم بتنفيذ جهاز آلي لحساب الأرقام والكلمات في النص. في البداية، دعونا نتفق على أن الرقم سيتم اعتباره سلسلة من الأرقام من 0 إلى 9 بطول عشوائي، محاطًا بأحرف مسافات بيضاء (مسافة، علامة تبويب، تغذية سطر). سيتم اعتبار الكلمة عبارة عن سلسلة ذات طول عشوائي تتكون من أحرف ومحاطة أيضًا بأحرف مسافات بيضاء.

دعونا نلقي نظرة على الرسم البياني:

من الحالة الأولية نصل إلى الحالة يبدأ. نتحقق من الحرف الحالي وإذا كان حرفًا فنذهب إلى الحالة كلمةعلى طول السهم المميز بـ خطاب. تفترض هذه الحالة أننا نفكر حاليًا في كلمة ما، وتحليل الرموز الأخرى إما أن يؤكد هذا الافتراض أو يدحضه. لذا، فكر في الحرف التالي، إذا كان حرفًا، فإن الحالة لا تتغير (لاحظ السهم الدائري المميز بـ خطاب). إذا كان الحرف ليس حرفا، ولكنه يتوافق مع حرف مسافة بيضاء، فهذا يعني أن الفرضية كانت صحيحة ووجدنا كلمة (نتبع السهم) فضاءإلى دولة يبدأ). إذا لم يكن الحرف حرفًا ولا مسافة، فقد أخطأنا في الافتراض والتسلسل الذي ندرسه ليس كلمة (اتبع السهم مجهولإلى دولة يتخطى).

قادر يتخطىنحن هناك حتى تتم مواجهة حرف مسافة بيضاء. بعد اكتشاف الفجوة، اتبع السهم فضاءإلى دولة يبدأ. يعد ذلك ضروريًا لتخطي السطر الذي لا يتطابق مع نمط البحث الخاص بنا تمامًا.

بعد دخول الدولة يبدأ، تتكرر دورة البحث من البداية. فرع التعرف على الأرقام يشبه فرع التعرف على الكلمات.

إنسان آلي باستخدام تعليمات التبديل

الأول هو الحالات المحتملة:

وبعد ذلك نكرر الخط، وندخل الرمز الحالي إلى الجهاز. الإنسان الآلي نفسه عبارة عن تعليمات تبديل تقوم أولاً بالانتقال إلى قسم الحالة الحالية. يوجد داخل القسم بناء if-else، والذي، اعتمادًا على الحدث (الرمز الوارد)، يغير الحالة الحالية:

const size_t length = text. length(); for (size_t i = 0 ; i ! = length; ++ i) ( const char current = text[ i] ; Switch (state) ( case State_Start: if (std:: isdigit (current) ) (state = State_Number; ) وإلا إذا (std:: isalpha (current)) (state = State_Word;) فاصل؛ الحالة State_Number: if (std:: isspace (current) ) (

نحن هنا ننظر إلى الرسم البياني - الوضع الحالي رقم، حدث فضاء(تم العثور على حرف مسافة)، وهو ما يعني العثور على الرقم:

FoundNumber(); الحالة = State_Start؛ ) وإلا إذا (! std::isdigit(current) ) (state = State_Skip;) فاصل; case State_Word: if (std:: isspace (current) ) ( FoundWord() ;state = State_Start; ) else if (! std:: isalpha (current)) ) (state = State_Skip; ) استراحة ; case State_Skip: if (std::isspace (current) ) (state = State_Start;) فاصل; ))

حصيلة:

كفاءة عالية

سهولة التنفيذ للخوارزميات البسيطة

- صعوبة الصيانة

تفسير وقت التشغيل

فكرة هذا النهج بسيطة - تحتاج إلى إنشاء جدول انتقالي، وتعبئته، وبعد ذلك، عند حدوث حدث ما، ابحث عن الحالة التالية في الجدول وقم بإجراء الانتقال:

تعداد الأحداث ( Event_Space، Event_Digit، Event_Letter، Event_Unknown ) ؛ عملية باطلة (حدث الأحداث) ؛ ... انتقال البنية ( States BaseState_; Events Event_; States TargetState_; ) ; void AddTransition(States fromState, Events events, States toState); ...typedef std::vector< transition>TransitionTable; TransitionTable Transitions_; الدول CurrentState_؛

ملء الجدول:

AddTransition(State_Start, Event_Digit, State_Number) ؛ AddTransition(State_Start, Event_Letter, State_Word) ؛ AddTransition(State_Number, Event_Space, State_Start) ؛ AddTransition(State_Number, Event_Letter, State_Skip) ؛ AddTransition(State_Number, Event_Unknown, State_Skip) ; AddTransition(State_Word, Event_Space, State_Start) ؛ AddTransition(State_Word, Event_Digit, State_Skip) ; AddTransition(State_Word, Event_Unknown, State_Skip) ; AddTransition(State_Skip, Event_Space, State_Start) ؛

اتضح بشكل واضح تماما. سيكون ثمن الوضوح هو تشغيل الجهاز بشكل أبطأ، وهو ما لا يهم في كثير من الأحيان.

حتى يتمكن الجهاز، عند حدوث أحداث معينة، من إخطار بعض التعليمات البرمجية، يمكنك إضافته إلى البنية بمعلومات حول الانتقال ( انتقال) مؤشر الوظيفة ( فعل)، والتي سوف تسمى:

typedef void (DynamicMachine::* Action) () ؛ انتقال البنية (states BaseState_; Events Event_; States TargetState_; Action Action_;) ; ... void AddTransition(States fromState, Events events, States toState, Action action) ؛ ...AddTransition(State_Number, Event_Space, State_Start, & DynamicMachine::FoundNumber) ؛

حصيلة:

المرونة والرؤية

أسهل للصيانة

- أداء أقل مقارنة بوحدات التبديل

تفسير وقت التنفيذ. تحسين السرعة

هل من الممكن الجمع بين الرؤية والسرعة؟ من غير المرجح أن يكون من الممكن صنع آلة أوتوماتيكية بنفس كفاءة الآلة الأوتوماتيكية القائمة على كتل المفاتيح، ولكن من الممكن سد الفجوة. للقيام بذلك، تحتاج إلى إنشاء مصفوفة من الجدول، بحيث للحصول على معلومات حول الانتقال، لا تحتاج إلى البحث، ولكن قم بإجراء التحديد حسب الحالة ورقم الحدث:

يتم تحقيق النتيجة على حساب استهلاك الذاكرة.

حصيلة:

المرونة والرؤية

فعال

- استهلاك الذاكرة (على الأرجح غير مهم)

تعزيز مخطط الحالة

ناقشنا عدة طرق لتنفيذ آلة الحالة بنفسك. من أجل التغيير، أقترح التفكير في إنشاء مكتبة لبناء الآلات من Boost. هذه المكتبة قوية جدًا، حيث تسمح لك الإمكانيات المتوفرة ببناء أجهزة ذات حالة محدودة ومعقدة للغاية. سوف ننظر في الأمر بسرعة كبيرة.

لذا، أولاً نقوم بتعريف الأحداث:

أحداث مساحة الاسم (رقم الهيكل: Boost::statechart::event< Digit>( ) ؛ الرسالة الهيكلية: Boost::statechart::event< Letter>( ) ؛ مساحة البناء: Boost::statechart::event< Space>( ) ؛ البنية غير معروفة: Boost::statechart::event< Unknown> { } ; }

الجهاز نفسه (لاحظ أن معلمة القالب الثاني هي الحالة الأولية):

آلة الهيكل: Boost::statechart::state_machine< Machine, States:: Start > { } ;

والدول نفسها. داخل الحالات، من الضروري تحديد النوع الذي يصف التحولات ( تفاعلات)، وإذا كان هناك العديد من التحولات، فقم بإدراجها في قائمة نوع Boost::mpl::list. معلمة القالب الثاني simple_state- اكتب وصف الجهاز. يتم وصف التحولات بواسطة معلمات قالب الانتقال، زوج الحدث - الحالة التالية:

حالات مساحة الاسم (بداية الهيكل: Boost::statechart::simple_state< Start, Machine> < boost:: statechart :: transition < Events:: Digit , States:: Number >< Events:: Letter , States:: Word >> ردود الفعل؛ ) ; رقم الهيكل: Boost::statechart::simple_state< Number, Machine>( تعزيز typedef::mpl::list< boost:: statechart :: transition < Events:: Space , States:: Start >, Boost::statechart::transition< Events:: Letter , States:: Skip >, Boost::statechart::transition< Events:: Unknown , States:: Skip >> ردود الفعل؛ ) ; كلمة البناء: Boost::statechart::simple_state< Word, Machine>( تعزيز typedef::mpl::list< boost:: statechart :: transition < Events:: Space , States:: Start >, Boost::statechart::transition< Events:: Digit , States:: Skip >, Boost::statechart::transition< Events:: Unknown , States:: Skip >> ردود الفعل؛ ) ; تخطي البنية: Boost::statechart::simple_state< Skip, Machine>( تعزيز typedef::statechart::transition< Events:: Space , States:: Start >تفاعلات؛ ) ; )

تم بناء الجهاز، كل ما تبقى هو تهيئته ويمكنك استخدامه:

آلة الآلة Machine.initiate(); ...machine.process_event(Events::Space());

حصيلة:

المرونة وقابلية التوسع

- كفاءة

أداء

لقد كتبت برنامج اختبار للتحقق من أداء الآلات المبنية. قمت بتشغيل نص يصل حجمه إلى 17 ميجابايت عبر الأجهزة. وإليكم نتائج الجولة:

تحميل النص طول النص: 17605548 بايت 0.19 ثانية كلمات تشغيل BoostMachine: 998002، الأرقام: 6816 0.73 ثانية كلمات تشغيل DynamicMachine: 998002، الأرقام: 6816 0.56 ثانية كلمات تشغيل FastDynamicMachine: 998002، الأرقام: 6816 0.29 ثانية تشغيل SimpleMach كلمات: 998002، أرقام: 6816 0.20 ثانية

ما بقي دون فحص

لا يزال هناك العديد من تطبيقات أجهزة الحالة المحدودة التي تركت مكشوفة (أوصي بـ http://www.rsdn.ru/article/alg/Static_Finite_State_Machine.xml و http://www.rsdn.ru/article/alg/FiniteStateMachine.xml) والمولدات التي تبني الآلات من الأوصاف، ومكتبة Meta State Machine من Boost الإصدار 1.44.0، بالإضافة إلى الأوصاف الرسمية لآلات الحالة المحدودة. يمكن للقارئ الفضولي أن يتعرف على كل ما سبق بنفسه.

آلة الحالة المحدودة هي نموذج مجرد يحتوي على عدد محدود من حالات شيء ما. يستخدم لتمثيل والتحكم في تدفق تنفيذ أي أوامر. تعتبر آلة الحالة مثالية لتطبيق الذكاء الاصطناعي في الألعاب، مما ينتج حلاً أنيقًا دون كتابة تعليمات برمجية ضخمة ومعقدة. في هذه المقالة سوف نلقي نظرة على النظرية ونتعلم أيضًا كيفية استخدام آلة الحالة البسيطة والمبنية على المكدس.

لقد نشرنا بالفعل سلسلة من المقالات حول كتابة الذكاء الاصطناعي باستخدام آلة الحالة المحدودة. إذا لم تكن قد قرأت هذه السلسلة بعد، يمكنك ذلك الآن:

ما هي آلة الدولة المحدودة؟

آلة الحالة المحدودة (أو ببساطة FSM - آلة الحالة المحدودة) هي نموذج حاسوبي يعتمد على آلة الحالة الافتراضية. يمكن لدولة واحدة فقط أن تكون نشطة في كل مرة. ولذلك، من أجل تنفيذ أي إجراءات، يجب على الجهاز تغيير حالته.

تُستخدم أجهزة الحالة عادةً لتنظيم وتمثيل تدفق تنفيذ شيء ما. وهذا مفيد بشكل خاص عند تطبيق الذكاء الاصطناعي في الألعاب. على سبيل المثال، لكتابة "عقل" العدو: تمثل كل حالة نوعًا من الإجراء (الهجوم، المراوغة، وما إلى ذلك).

يمكن تمثيل آلة الحالة المحدودة كرسم بياني، تكون رؤوسه حالات، وحوافها عبارة عن انتقالات بينها. تحتوي كل حافة على ملصق يشير إلى متى يجب أن يحدث الانتقال. على سبيل المثال، في الصورة أعلاه يمكنك أن ترى أن الجهاز سيغير حالة "التجوال" إلى حالة "الهجوم"، بشرط أن يكون اللاعب قريبًا.

تخطيط الدول وتحولاتها

يبدأ تنفيذ آلة الحالة المحدودة بتحديد حالاتها والانتقالات بينها. تخيل آلة حالة تصف تصرفات نملة تحمل أوراق الشجر إلى عش النمل:

نقطة البداية هي حالة "العثور على الورقة"، والتي تظل نشطة حتى تعثر النملة على الورقة. وعندما يحدث هذا، ستتغير الولاية إلى "العودة إلى المنزل". ستظل هذه الحالة نفسها نشطة حتى تصل نملتنا إلى عش النمل. بعد ذلك، تتغير الحالة مرة أخرى إلى "العثور على ورقة الشجر".

إذا كانت حالة "العثور على الورقة" نشطة، ولكن مؤشر الماوس بالقرب من النملة، فستتغير الحالة إلى "الهرب". بمجرد أن تصبح النملة على مسافة آمنة كافية من مؤشر الفأرة، ستتغير الحالة مرة أخرى إلى "العثور على ورقة".

يرجى ملاحظة أنه عند التوجه إلى المنزل أو بعيدًا عن المنزل، لن تخاف النملة من مؤشر الفأرة. لماذا؟ ولكن لأنه لا يوجد انتقال المقابلة.

تنفيذ آلة الحالة المحدودة البسيطة

يمكن تنفيذ آلة الحالة المحدودة باستخدام فئة واحدة. دعنا نسميها ولايات ميكرونيزيا الموحدة. والفكرة هي تنفيذ كل حالة كأسلوب أو وظيفة. سنستخدم أيضًا خاصية activeState لتحديد الحالة النشطة.

فئة عامة FSM (خاص var activeState:Function؛ // مؤشر إلى الحالة النشطة للجهاز وظيفة عامة FSM() () وظيفة عامة setState(state:Function) :void ( activeState = State;) تحديث الوظيفة العامة () :void ( إذا ( activeState != null) ( activeState(); ) ) )

كل دولة هي وظيفة. علاوة على ذلك، بحيث سيتم استدعاؤه في كل مرة يتم فيها تحديث إطار اللعبة. كما ذكرنا سابقًا، سيقوم activeState بتخزين مؤشر إلى وظيفة الحالة النشطة.

يجب استدعاء طريقة التحديث () لفئة FSM في كل إطار من اللعبة. وهي بدورها ستستدعي وظيفة الحالة النشطة حاليًا.

ستقوم طريقة setState () بتعيين الحالة النشطة الجديدة. علاوة على ذلك، فإن كل وظيفة تحدد حالة معينة من الإنسان الآلي لا تنتمي بالضرورة إلى فئة FSM - وهذا يجعل فئتنا أكثر عالمية.

باستخدام آلة الدولة

دعونا ننفذ الذكاء الاصطناعي الخاص بالنمل. لقد أظهرنا أعلاه بالفعل مجموعة من حالاتها والتحولات بينها. دعونا نوضحها مرة أخرى، ولكن هذه المرة سنركز على الكود.

يتم تمثيل نملتنا بطبقة النمل، التي لديها مجال دماغي. هذا مجرد مثال لفئة FSM.

فئة عامة Ant (موضع var العام:Vector3D؛ سرعة var العامة:Vector3D؛ دماغ var العام:FSM؛ الوظيفة العامة Ant(posX:Number, posY:Number) (position = new Vector3D(posX, posY); velocity = new Vector3D( -1, -1); Brain = new FSM(); // ابدأ بالعثور على ورقة. Brain.setState(findLeaf); ) /** * الحالة "findLeaf". * تجبر النملة على البحث عن الأوراق. */ الوظيفة العامة findLeaf( ) :void ( ) /** * الحالة "goHome". * تجعل النملة تذهب إلى عش النمل. */ public function goHome() :void ( ) /** * الحالة "runAway". * القوات النملة تهرب من مؤشر الفأرة * / public function runAway() :void ( ) public function update():void ( // تحديث جهاز الحالة. ستستدعي هذه الوظيفة // وظيفة الحالة النشطة: findLeaf() , goHome() أو runAway(). Brain.update( ); // تطبيق السرعة على حركة النملة. moveBasedOnVelocity(); ) (...) )

تحتوي فئة Ant أيضًا على خصائص السرعة والموضع. سيتم استخدام هذه المتغيرات لحساب الحركة باستخدام طريقة أويلر. يتم استدعاء وظيفة التحديث () في كل مرة يتم فيها تحديث إطار اللعبة.

يوجد أدناه تطبيق لكل طريقة، بدءًا من findLeaf() - الحالة المسؤولة عن العثور على الأوراق.

الوظيفة العامة findLeaf() :void ( // تحرك النملة إلى الورقة. velocity = new Vector3D(Game.instance.leaf.x - Position.x, Game.instance.leaf.y - Position.y); if (distance (لعبة .instance.leaf، هذا)<= 10) { // Муравей только что подобрал листок, время // возвращаться домой! brain.setState(goHome); } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Курсор мыши находится рядом. Бежим! // Меняем состояние автомата на runAway() brain.setState(runAway); } }

حالة goHome() - تُستخدم لجعل النملة تعود إلى المنزل.

الوظيفة العامة goHome() :void ( // ينقل النملة إلى المنزل velocity = new Vector3D(Game.instance.home.x - Position.x, Game.instance.home.y - location.y); if (distance( Game.instance.home, this)<= 10) { // Муравей уже дома. Пора искать новый лист. brain.setState(findLeaf); } }

وأخيرًا، يتم استخدام حالة runAway() عند الابتعاد عن مؤشر الماوس.

الوظيفة العامة runAway() :void ( // تحرك النملة بعيدًا عن سرعة المؤشر = new Vector3D(position.x - Game.mouse.x,position.y - Game.mouse.y); // هل لا يزال المؤشر قريبًا ? if ( distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) ( // لا، إنه بعيد بالفعل. حان الوقت للعودة للبحث عن الورقة. Brain.setState(findLeaf); ) )

تحسين ولايات ميكرونيزيا الموحدة: إنسان آلي قائم على المكدس

تخيل أن نملة في طريقها إلى المنزل تحتاج أيضًا إلى الهروب من مؤشر الماوس. وهذا ما ستبدو عليه دول ولايات ميكرونيزيا الموحدة:

يبدو وكأنه تغيير تافه. لا، هذا التغيير يخلق مشكلة بالنسبة لنا. تخيل أن الوضع الحالي هو "الهروب". إذا ابتعد مؤشر الفأر عن النملة فماذا تفعل: اذهب إلى المنزل أم ابحث عن ورقة شجر؟

الحل لهذه المشكلة هو آلة الحالة القائمة على المكدس. على عكس FSM البسيط الذي قمنا بتنفيذه أعلاه، يستخدم هذا النوع من FSM مكدسًا لإدارة الحالات. توجد الحالة النشطة في الجزء العلوي من المكدس، وتحدث التحولات عند إضافة/إزالة الحالات من المكدس.

وهنا عرض توضيحي مرئي لتشغيل آلة الحالة القائمة على المكدس:

تنفيذ FSM على أساس المكدس

يمكن تنفيذ آلة الحالة هذه بنفس طريقة تنفيذ آلة بسيطة. سيكون الاختلاف هو استخدام مجموعة من المؤشرات للحالات المطلوبة. لم نعد بحاجة إلى خاصية activeState، لأن سيشير الجزء العلوي من المكدس بالفعل إلى الحالة النشطة.

فئة عامة StackFSM (خاص var stack:Array؛ الوظيفة العامة StackFSM() ( this.stack = new Array();) تحديث الوظيفة العامة() :void ( varcurrentStateFunction:Function = getCurrentState(); if (currentStateFunction != null) ( currentStateFunction(); ) ) الوظيفة العامة popState() : الوظيفة ( return stack.pop(); ) الوظيفة العامة PushState(state:Function) :void ( if (getCurrentState() != State) ( stack.push(state) ; ) ) الوظيفة العامة getCurrentState() : الوظيفة ( return stack.length > 0 ? stack : null; ) )

لاحظ أنه تم استبدال طريقة setState() بـ PushState() (إضافة حالة جديدة إلى أعلى المكدس) وpopState() (إزالة حالة في أعلى المكدس).

باستخدام FSM القائم على المكدس

من المهم ملاحظة أنه عند استخدام آلة حالة قائمة على المكدس، تكون كل حالة مسؤولة عن إزالتها من المكدس عندما لا تكون هناك حاجة إليها. على سبيل المثال، يجب أن تقوم حالة الهجوم () بإزالة نفسها من المكدس إذا تم تدمير العدو بالفعل.

فئة عامة Ant ( (...) public var Brain:StackFSM; public function Ant(posX:Number, posY:Number) ((...) Brain = new StackFSM(); // ابدأ بالبحث عن دماغ الورقة. PushState( findLeaf); (...) ) /** * الحالة "findLeaf". * يجبر النملة على البحث عن أوراق الشجر */ public function findLeaf() :void ( // ينقل النملة إلى الورقة. velocity = new Vector3D(Game.instance. leaf.x - Position.x, Game.instance.leaf.y - Position.y); if (distance(Game.instance.leaf, هذا)<= 10) { //Муравей только что подобрал листок, время // возвращаться домой! brain.popState(); // removes "findLeaf" from the stack. brain.pushState(goHome); // push "goHome" state, making it the active state. } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Курсор мыши рядом. Надо бежать! // Состояние "runAway" добавляется перед "findLeaf", что означает, // что состояние "findLeaf" вновь будет активным при завершении состояния "runAway". brain.pushState(runAway); } } /** * Состояние "goHome". * Заставляет муравья идти в муравейник. */ public function goHome() :void { // Перемещает муравья к дому velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y); if (distance(Game.instance.home, this) <= 10) { // Муравей уже дома. Пора искать новый лист. brain.popState(); // removes "goHome" from the stack. brain.pushState(findLeaf); // push "findLeaf" state, making it the active state } if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) { // Курсор мыши рядом. Надо бежать! // Состояние "runAway" добавляется перед "goHome", что означает, // что состояние "goHome" вновь будет активным при завершении состояния "runAway". brain.pushState(runAway); } } /** * Состояние "runAway". * Заставляет муравья убегать от курсора мыши. */ public function runAway() :void { // Перемещает муравья подальше от курсора velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y); // Курсор все еще рядом? if (distance(Game.mouse, this) >MOUSE_THREAT_RADIUS) ( // لا، إنه بعيد بالفعل. حان الوقت للعودة للبحث عن الأوراق. Brain.popState(); ) ) (...))

خاتمة

من المؤكد أن أجهزة الحالة مفيدة في تنفيذ منطق الذكاء الاصطناعي في الألعاب. ويمكن تمثيلها بسهولة كرسم بياني، مما يسمح للمطور برؤية جميع الخيارات الممكنة.

يعد تنفيذ آلة الدولة بوظائف الدولة تقنية بسيطة ولكنها قوية. يمكن تنفيذ نسج الحالة الأكثر تعقيدًا باستخدام FSM.