يا جماعة الخير، السلام عليكم ورحمة الله. اسمي أبو عمر، وقصتي اليوم بترجع لسنوات طويلة، لما كنت لسا “فرخ” في عالم البرمجة وتطوير الألعاب. كنت شغال على لعبة استراتيجية بسيطة، زي ألعاب زمان، والمفروض إنه جنودي يلاقوا طريقهم من نقطة “ألف” لنقطة “باء” على الخريطة عشان يهاجموا العدو.
في البداية، قلت لحالي “شو القصة يعني؟ بسيطة!”. كتبت كود بسيط يخلي الجنود يتحركوا خطوة خطوة باتجاه الهدف. والنتيجة؟ كارثة! كارثة بكل معنى الكلمة. الجنود كانوا يمشوا ويخبطوا في الحيطان والجبال زي الأعمى اللي مضيع عصايته. أحياناً كانوا يلفوا في دوائر، وأحياناً يختاروا أطول طريق ممكن يوصلهم للعدو، لدرجة إنه العدو كان يخلص قهوته وشايه وهم لسا ما وصلوا. شعرت بإحباط شديد، حسيت إنه لعبتي رح تضل مجرد فكرة فاشلة وشخصياتي “هبايل” ما عليهم عتب.
في ليلة من الليالي، وأنا ببحث وبقرأ في منتديات المطورين القديمة، وقعت عيني على اسم غريب: “A* Algorithm”. قرأت عنها، وكل ما أقرأ سطر، كانت عيوني تلمع أكثر. حسيت كأني لقيت الكنز المفقود. طبقتها في اليوم التالي، وشفت جنودي لأول مرة بيتحركوا بذكاء، بيتحاشوا العوائق، وبيختاروا أقصر الطرق وأذكاها. منظرهم وهم بيتحركوا بتناسق وفعالية كان زي الموسيقى لعيوني. من يومها، صارت خوارزمية A* هي “سلاحي السري” في أي مشروع بيحتاج لتحديد مسار ذكي. اليوم، بدي أشارككم هالسلاح، ونفهم مع بعض كيف هالسحر بيشتغل.
لماذا تفشل الطرق التقليدية؟ (الاستكشاف الأعمى)
قبل ما نحكي عن البطل A*، لازم نفهم ليش الطرق الثانية كانت “عمياء” ومُحبِطة. المشكلة الأساسية في إيجاد المسار (Pathfinding) هي البحث في مساحة كبيرة من الاحتمالات. تخيلها كمتاهة ضخمة.
خوارزمية البحث بالعرض أولاً (BFS – Breadth-First Search)
هاي الخوارزمية بتشتغل زي لما تكب كاسة مي على الأرض. المي بتنتشر في كل الاتجاهات بنفس المسافة. الـ BFS بتستكشف كل الجيران القريبين، بعدين جيران الجيران، وهكذا. هي بتضمنلك أقصر طريق من ناحية “عدد الخطوات”، لكنها غبية جداً. ما بتعرف وين الهدف أصلاً! بتضلها تبحث في كل الاتجاهات، حتى لو كان الهدف قدامها مباشرة. هذا إهدار كبير للموارد والوقت، خصوصاً في الخرائط الكبيرة.
خوارزمية دَيكسترا (Dijkstra’s Algorithm)
هاي الخوارزمية أذكى بشوي من اللي قبلها. ديكسترا ما بتتعامل مع كل الخطوات بنفس الأهمية، بل بتعطي “تكلفة” لكل خطوة. مثلاً، المشي في الوحل ممكن يكلف 5 نقاط، بينما المشي على الشارع يكلف نقطة واحدة. ديكسترا دائماً بتختار المسار اللي تكلفته “التراكمية” أقل حتى الآن.
مشكلتها؟ إنها لسا ما عندها “بصيرة” أو “حدس” تجاه الهدف. هي بتعرف أرخص طريق وصلتله “لهلأ”، بس ما عندها أي فكرة عن المسافة المتبقية للهدف. فممكن تضلها تستكشف مسارات رخيصة لكنها بتبعدها عن الهدف النهائي.
البطل المنتظر: خوارزمية A* (A-Star)
وهنا يأتي دور A*. هاي الخوارزمية هي المزيج العبقري بين دقة ديكسترا والبصيرة أو الحدس الذكي. هي بتوازن بين “ما قطعناه حتى الآن” و “ما نتوقع أن نقطعه للوصول”.
المعادلة السحرية: f(n) = g(n) + h(n)
هاي المعادلة هي قلب A* النابض. خلينا نفصصها حبة حبة:
g(n): هاي هي “تكلفة الماضي”. بتمثل التكلفة الفعلية للمسار من نقطة البداية إلى النقطة الحالية (n). هاي بالضبط نفس فكرة خوارزمية ديكسترا. “قديش كلفنا المشوار لهون”.h(n): هاي هي “تكلفة المستقبل المقدرة” أو الـ “Heuristic”. هي تخمين ذكي ومدروس للتكلفة من النقطة الحالية (n) إلى نقطة النهاية. هذا هو الجزء اللي بيعطي A* بصيرتها. “قديش بنتوقع يكلفنا المشوار من هون للآخر”.f(n): هي “التكلفة الإجمالية المقدرة”. هي مجموع التكلفتين السابقتين. خوارزمية A* دائماً بتختار النقطة اللي عندها أقل قيمةf(n)عشان تستكشفها.
ببساطة، A* بتفضل المسارات اللي مش بس رخيصة لحد الآن (g منخفضة)، لكنها كمان “بتبشر بالخير” وبتقربنا من الهدف (h منخفضة).
كيف تعمل A* خطوة بخطوة؟
عشان نفهمها صح، خلينا نستخدم مفهومين أساسيين:
- القائمة المفتوحة (Open List): قائمة بالعقد (المربعات) اللي اكتشفناها وممكن تكون جزء من المسار، بس لسا ما فحصناها بالكامل. بنرتبها دائماً حسب أقل قيمة
f(n). - القائمة المغلقة (Closed List): قائمة بالعقد اللي فحصناها وخلصنا منها، ومستحيل نرجع نزورها مرة ثانية.
والخطوات هي كالتالي:
- ضع عقدة البداية في القائمة المفتوحة.
- طالما القائمة المفتوحة مش فاضية، كرر التالي:
- اختر العقدة اللي عندها أقل قيمة
fمن القائمة المفتوحة. خلينا نسميها “العقدة الحالية”. - انقل “العقدة الحالية” من القائمة المفتوحة إلى القائمة المغلقة.
- إذا كانت “العقدة الحالية” هي عقدة الهدف، مبروك! لقد وجدت المسار. (عليك الآن تتبع العقد الآباء للخلف للحصول على المسار الكامل).
- وإلا، لكل “عقدة مجاورة” للعقدة الحالية:
- إذا كانت العقدة المجاورة جداراً أو موجودة في القائمة المغلقة، تجاهلها.
- احسب قيم
g,h,fللعقدة المجاورة. - إذا كانت العقدة المجاورة موجودة في القائمة المفتوحة ووجدنا مساراً أفضل إليها (قيمة
gأقل)، فحدّث قيمتها. - إذا لم تكن في القائمة المفتوحة، أضفها إلى القائمة المفتوحة.
- اختر العقدة اللي عندها أقل قيمة
- إذا انتهت الحلقة والقائمة المفتوحة فارغة، فهذا يعني أنه لا يوجد مسار.
مثال كود (بايثون مبسط)
هذا مثال توضيحي بسيط جداً لكيف ممكن تبني الخوارزمية بلغة بايثون. ركز على الفكرة أكثر من التفاصيل الدقيقة للتطبيق.
class Node():
"""عقدة تمثل مربع في الخريطة"""
def __init__(self, parent=None, position=None):
self.parent = parent
self.position = position
self.g = 0 # التكلفة من البداية
self.h = 0 # التكلفة المقدرة للنهاية
self.f = 0 # المجموع
def __eq__(self, other):
return self.position == other.position
def astar(maze, start, end):
"""ترجع قائمة بالإحداثيات كمسار من البداية للنهاية"""
# إنشاء عقد البداية والنهاية
start_node = Node(None, start)
end_node = Node(None, end)
# تهيئة القائمتين المفتوحة والمغلقة
open_list = []
closed_list = []
# إضافة عقدة البداية
open_list.append(start_node)
# بدء الحلقة
while len(open_list) > 0:
# الحصول على العقدة الحالية (ذات أقل قيمة f)
current_node = open_list[0]
current_index = 0
for index, item in enumerate(open_list):
if item.f < current_node.f:
current_node = item
current_index = index
# إزالة العقدة الحالية من القائمة المفتوحة وإضافتها للمغلقة
open_list.pop(current_index)
closed_list.append(current_node)
# هل وصلنا للهدف؟
if current_node == end_node:
path = []
current = current_node
while current is not None:
path.append(current.position)
current = current.parent
return path[::-1] # عكس المسار ليكون من البداية للنهاية
# البحث في الجيران
children = []
for new_position in [(0, -1), (0, 1), (-1, 0), (1, 0)]: # الجيران الأربعة
# الحصول على إحداثيات العقدة
node_position = (current_node.position[0] + new_position[0], current_node.position[1] + new_position[1])
# التأكد من أن الإحداثيات ضمن حدود الخريطة
if node_position[0] > (len(maze) - 1) or node_position[0] (len(maze[len(maze)-1]) -1) or node_position[1] open_node.g]) > 0:
continue
# إضافة الجار للقائمة المفتوحة
open_list.append(child)
نصائح عملية من خبرة أبو عمر
بعد سنوات من التعامل مع A*، تعلمت كم شغلة بتفرق في الأداء والنتيجة:
اختر الـ Heuristic الصح
“الـ Heuristic هو بوصلة الخوارزمية، لو بوصلتك خربانة رح تتوه.”
اختيار دالة التقدير (h) هو أهم قرار. يجب أن تكون “مقبولة” (Admissible)، يعني لا تبالغ أبداً في تقدير المسافة المتبقية. لازم تكون متفائلة أو واقعية، بس مش متشائمة. أشهر دالتين للخرائط الشبكية:
- مسافة مانهاتن (Manhattan Distance): مناسبة جداً لو الحركة مسموحة فقط أفقياً وعمودياً (زي التاكسي في شوارع مانهاتن). حسابها:
|x1 - x2| + |y1 - y2|. - المسافة الإقليدية (Euclidean Distance): مناسبة لو الحركة مسموحة في كل الاتجاهات (قطرياً). حسابها:
sqrt((x1-x2)^2 + (y1-y2)^2). هي أدق، لكن حساب الجذر التربيعي أبطأ.
تحسين الأداء في الخرائط الضخمة
في الخرائط الكبيرة جداً، حتى A* ممكن تصير بطيئة وتستهلك ذاكرة كبيرة. هنا ممكن تلجأ لحلول متقدمة:
- شبكات الملاحة (Navigation Meshes): بدلاً من التعامل مع آلاف المربعات الصغيرة، قسم خريطتك لمضلعات كبيرة (مناطق يمكن المشي فيها). A* هنا ستبحث عن مسار بين هذه المضلعات، وهو أسرع بكثير.
- خوارزميات متقدمة مثل (Jump Point Search – JPS): هي نسخة محسنة من A* على الخرائط الشبكية، تقوم بـ “القفز” فوق العديد من المربعات غير المهمة، مما يقلل بشكل كبير من عدد العقد التي يجب فحصها.
A* ليست فقط للألعاب
صحيح أن شهرتها جاءت من الألعاب، لكن A* أداة قوية جداً في مجالات أخرى. أي مشكلة يمكن تمثيلها على شكل “رسم بياني” (Graph) من نقاط وتوصيلات بينها، يمكن لـ A* أن تجد الحل الأمثل فيها. من تخطيط مسارات الروبوتات في المستودعات، إلى إيجاد أقصر طريق في تطبيقات الخرائط، وحتى في حل الألغاز المعقدة.
الخلاصة: من الفوضى إلى الذكاء 😉
رحلتي مع A* علمتني درساً مهماً: أحياناً، الحل لمشكلة معقدة لا يكمن في كتابة كود أكثر، بل في اختيار الخوارزمية الأذكى. خوارزمية A* حولت شخصياتي من كائنات “بتتخبط بالحيطان” إلى وحدات ذكية وفعالة تتحرك بهدف ووعي.
نصيحتي لكل مبرمج ومطور: لا تستهينوا أبداً بأساسيات علوم الحاسوب والخوارزميات. هي الأدوات الحقيقية اللي بتميز المبرمج العادي عن المبرمج الخبير. A* هي واحدة من أروع هذه الأدوات، وهي ليست مجرد كود، بل هي طريقة تفكير تجمع بين الدقة والحدس.
فلا تيأس أبداً إذا واجهتك مشكلة تبدو مستحيلة، فغالباً ما يكون هناك خوارزمية جميلة وذكية مثل A* تنتظر منك أن تكتشفها. بالتوفيق في مشاريعكم!