2025, Nov 01 08:32
ग्रिड-आधारित जल प्रवाह में पक्षपात क्यों आता है और उसे निष्पक्ष बनाने के आसान तरीके
ग्रिड-आधारित जल प्रवाह सिमुलेशन में निर्णय-वृक्ष से पैदा दिशात्मक पक्षपात का समाधान जानें: सममित नियम, निष्पक्ष टाई-ब्रेकर, डबल-बफरिंग, परीक्षणयोग्य कोड.
ग्रिड-आधारित जल प्रवाह में दिशात्मक पक्षपात को खत्म करना
किसी टाइल ग्रिड में पानी के प्रवाह का सिमुलेशन करते समय, शर्तों की जाँच का हल्का-सा क्रम भी नतीजों को झुका सकता है. एक आम संकेत यह है कि पानी एक क्षैतिज दिशा में बहकता रहता है, जबकि नियम सममित होने चाहिए. नीचे ऐसा वास्तविक पैटर्न है जो इस तरह का पक्षपात पैदा करता है, और उसे इस तरह रीफैक्टर करने का तरीका भी, ताकि चयन निष्पक्ष रहे और रखरखाव आसान बना रहे.
समस्या वाला निर्णय-वृक्ष
मुख्य विचार सरल है: हर फ्रेम में, एक टाइल पात्रता नियमों के आधार पर पास की एक ही टाइल की ओर पानी की थोड़ी मात्रा धकेलने की कोशिश करती है. आस-पड़ोस को हर दिशा के लिए दो-आइटम संरचना से दर्शाया गया है, जहाँ इंडेक्स 0 पड़ोसी टाइल ऑब्जेक्ट रखता है और इंडेक्स 1 यह बताता है कि वह पड़ोसी “ऊँचा” है या नहीं (True होने पर पात्र नहीं). नीचे दिया गया कोड एक गहराई से नेस्टेड निर्णय-वृक्ष दिखाता है, जो पहले दक्षिण को प्राथमिकता देने की कोशिश करता है, फिर पश्चिम और पूर्व को अतिरिक्त टाई-ब्रेकर के साथ आँकता है, और अंत में उत्तर को जाँचता है.
import random
def disperse_liquid_step(self):
# self.<dir>_ref[0] = पड़ोसी टाइल ऑब्जेक्ट
# self.<dir>_ref[1] = अनुमत ऊँचाई से अधिक होने की जाँच
target_cell = self
if self.water > FLOW:
# दक्षिण मौजूद है
if self.south_ref != None:
# दक्षिण ऊँचा नहीं है
if self.south_ref[1] != True:
# दक्षिण भरा हुआ नहीं है
if self.south_ref[0].water < (100-FLOW):
target_cell = self.south_ref[0]
# दक्षिण भरा हुआ है
else:
# दक्षिण भरा हुआ है, पश्चिम मौजूद है
if self.west_ref != None:
# पश्चिम ऊँचा नहीं है
if self.west_ref[1] != True:
# पश्चिम भरा हुआ नहीं है
if self.west_ref[0].water < (100 - FLOW):
# पूर्व की जाँच करें
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा है
if self.east_ref[1] == True:
target_cell = self.west_ref[0]
# पूर्व ऊँचा नहीं है
else:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water <= (100 - FLOW):
# पूर्व में self से कम पानी है
if self.east_ref[0].water < self.water:
# पश्चिम में self से कम पानी है
if self.west_ref[0].water < self.water:
if self.east_ref[0].water > self.west_ref[0].water:
target_cell = self.west_ref[0]
elif self.east_ref[0].water < self.west_ref[0].water:
target_cell = self.east_ref[0]
else:
target_cell = random.choice([self.east_ref[0], self.west_ref[0]])
# पश्चिम self के बराबर या उससे अधिक
else:
target_cell = self.east_ref[0]
# पूर्व self के बराबर या उससे अधिक
else:
target_cell = self.west_ref[0]
# पूर्व मौजूद नहीं है
else:
target_cell = self.west_ref[0]
# पश्चिम भरा हुआ है
else:
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा नहीं है
if self.east_ref[1] != True:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100-FLOW):
target_cell = self.east_ref
# पूर्व भरा हुआ है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व ऊँचा है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व मौजूद नहीं है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पश्चिम ऊँचा है
else:
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा नहीं है
if self.east_ref[1] != True:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100-FLOW):
target_cell = self.east_ref
# पूर्व भरा हुआ है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व ऊँचा है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व मौजूद नहीं है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# दक्षिण भरा हुआ है, पश्चिम मौजूद नहीं है
else:
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा नहीं है
if self.east_ref[1] != True:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100-FLOW):
target_cell = self.east_ref
# पूर्व भरा हुआ है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व ऊँचा है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व मौजूद नहीं है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# दक्षिण ऊँचा है
else:
# पश्चिम मौजूद है
if self.west_ref != None:
# पश्चिम ऊँचा नहीं है
if self.west_ref[1] != True:
# पश्चिम भरा हुआ नहीं है
if self.west_ref[0].water < (100 - FLOW):
# पूर्व की जाँच करें
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा है
if self.east_ref[1] == True:
target_cell = self.west_ref[0]
# पूर्व ऊँचा नहीं है
else:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100 - FLOW):
# पूर्व में self से कम पानी है
if self.east_ref[0].water < self.water:
# पश्चिम में self से कम पानी है
if self.west_ref[0].water < self.water:
if self.east_ref[0].water > self.west_ref[0].water:
target_cell = self.west_ref[0]
elif self.east_ref[0].water < self.west_ref[0].water:
target_cell = self.east_ref[0]
else:
target_cell = random.choice([self.east_ref[0], self.west_ref[0]])
# पश्चिम self के बराबर या उससे अधिक
else:
target_cell = self.east_ref[0]
# पूर्व self के बराबर या उससे अधिक
else:
target_cell = self.west_ref[0]
# पूर्व मौजूद नहीं है
else:
target_cell = self.west_ref[0]
# पश्चिम भरा हुआ है
else:
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा नहीं है
if self.east_ref[1] != True:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100-FLOW):
target_cell = self.east_ref
# पूर्व भरा हुआ है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व ऊँचा है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व मौजूद नहीं है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पश्चिम ऊँचा है
else:
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा नहीं है
if self.east_ref[1] != True:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100-FLOW):
target_cell = self.east_ref
# पूर्व भरा हुआ है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व ऊँचा है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व मौजूद नहीं है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पश्चिम मौजूद नहीं है
else:
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा नहीं है
if self.east_ref[1] != True:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100-FLOW):
target_cell = self.east_ref
# पूर्व भरा हुआ है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व ऊँचा है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व मौजूद नहीं है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# दक्षिण मौजूद नहीं है
else:
# पश्चिम मौजूद है
if self.west_ref != None:
# पश्चिम ऊँचा नहीं है
if self.west_ref[1] != True:
# पश्चिम भरा हुआ नहीं है
if self.west_ref[0].water < (100 - FLOW):
# पूर्व की जाँच करें
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा है
if self.east_ref[1] == True:
target_cell = self.west_ref[0]
# पूर्व ऊँचा नहीं है
else:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100 - FLOW):
# पूर्व में self से कम पानी है
if self.east_ref[0].water < self.water:
# पश्चिम में self से कम पानी है
if self.west_ref[0].water < self.water:
if self.east_ref[0].water > self.west_ref[0].water:
target_cell = self.west_ref[0]
elif self.east_ref[0].water < self.west_ref[0].water:
target_cell = self.east_ref[0]
else:
target_cell = random.choice([self.east_ref[0], self.west_ref[0]])
# पश्चिम self के बराबर या उससे अधिक
else:
target_cell = self.east_ref[0]
# पूर्व self के बराबर या उससे अधिक
else:
target_cell = self.west_ref[0]
# पूर्व मौजूद नहीं है
else:
target_cell = self.west_ref[0]
# पश्चिम भरा हुआ है
else:
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा नहीं है
if self.east_ref[1] != True:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100-FLOW):
target_cell = self.east_ref
# पूर्व भरा हुआ है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व ऊँचा है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व मौजूद नहीं है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पश्चिम ऊँचा है
else:
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा नहीं है
if self.east_ref[1] != True:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100-FLOW):
target_cell = self.east_ref
# पूर्व भरा हुआ है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व ऊँचा है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व मौजूद नहीं है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पश्चिम मौजूद नहीं है
else:
# पूर्व मौजूद है
if self.east_ref != None:
# पूर्व ऊँचा नहीं है
if self.east_ref[1] != True:
# पूर्व भरा हुआ नहीं है
if self.east_ref[0].water < (100-FLOW):
target_cell = self.east_ref
# पूर्व भरा हुआ है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व ऊँचा है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
# पूर्व मौजूद नहीं है
else:
# उत्तर मौजूद है
if self.north_ref != None:
# उत्तर ऊँचा नहीं है
if self.north_ref[1] != True:
# उत्तर भरा हुआ नहीं है
if self.north_ref[0].water < (100 - FLOW):
target_cell = self.north_ref
वास्तव में क्या हो रहा है
निर्णय-वृक्ष का स्वरूप मायने रखता है. यदि एक क्षैतिज दिशा का मूल्यांकन दूसरी से पहले हो, तो जब दोनों ही योग्य हों, वही दिशा जीत जाती है—क्योंकि शुरुआती जाँचें बाकी शाखाओं को शॉर्ट-सर्किट कर देती हैं. दूसरे शब्दों में, तुलना और शाखाबंदी का क्रम एक प्राथमिकता बना सकता है, भले ही आपका इरादा “निष्पक्ष” चयन का हो. यही वजह है कि ऊपर जैसा नेस्टेड ट्री कई बार पूर्व को चुन लेता है: कुछ शाखाओं में पश्चिम की बारी तभी आती है जब पूर्व कई शर्तों पर असफल हो जाता है, जबकि पूर्व की जाँच पहले ही हो चुकी होती है. केवल यह क्रमात्मक प्रभाव ही आपके सिमुलेशन को तिरछा कर सकता है.
एक और व्यावहारिक पहलू: जब आप टाइलों पर इटरेट करते हुए पानी को तुरंत अपडेट करते हैं, तो शुरुआती टाइलें स्थिति बदल देती हैं और इससे बाद की टाइलों के फैसलों में पक्षपात आ सकता है. प्रति-फ्रेम बफर का उपयोग करें—पहले हर टाइल के लिए तय करें कि पानी कहाँ जाना चाहिए, फिर सभी ट्रांसफर लागू करें—ताकि इटरेशन का क्रम परिणाम को प्रभावित न करे.
सरल और परीक्षणयोग्य फ्लो नियम
अनजाने पक्षपात से बचने का भरोसेमंद तरीका है कि दिशा-विशेष जाँचों को अलग करें, सभी योग्य विकल्प समेटें, और चयन एक ही स्थान पर करें. इससे नियम सममित रहते हैं, शाखाएँ कम होती हैं, और बिना किसी दिशा को डिफ़ॉल्ट प्राथमिकता दिए टाई-ब्रेकर जोड़ना आसान हो जाता है. नीचे ठीक ऐसा ही करने का एक संक्षिप्त तरीका दिया है. आस-पड़ोस का कॉन्वेंशन पहले जैसा ही है: हर दिशा में इंडेक्स 0 पड़ोसी टाइल ऑब्जेक्ट रखता है और इंडेक्स 1 “ऊँचा” होने की जाँच है.
import random
FLOW = 10 # न्यूनतम स्थानांतरित की जाने वाली मात्रा
class Cell:
def __init__(self, level=0, raised=False):
self.level = level
self.raised = raised
self.north_ref = None
self.south_ref = None
self.east_ref = None
self.west_ref = None
def can_drain_into(self, ref):
return ref and not ref[1] and ref[0].level < (100 - FLOW)
def decide_flow(self):
target = self
if self.level <= FLOW:
return target
# यदि योग्य हो तो तुरंत दक्षिण को प्राथमिकता
if self.can_drain_into(self.south_ref):
return self.south_ref[0]
# क्षैतिज विकल्पों को सममित रूप से इकट्ठा करें
lateral = []
if self.can_drain_into(self.west_ref):
lateral.append(self.west_ref[0])
if self.can_drain_into(self.east_ref):
lateral.append(self.east_ref[0])
# जब पश्चिम और पूर्व दोनों वैध हों, तब टाई-ब्रेकिंग
if self.west_ref and self.east_ref:
west_ok = self.west_ref[0] in lateral
east_ok = self.east_ref[0] in lateral
if west_ok and east_ok:
w_amt = self.west_ref[0].level
e_amt = self.east_ref[0].level
if w_amt < self.level or e_amt < self.level:
if w_amt < e_amt:
return self.west_ref[0]
elif e_amt < w_amt:
return self.east_ref[0]
else:
return random.choice([self.east_ref[0], self.west_ref[0]])
else:
return self.west_ref[0] # मूल लॉजिक की तरह डिफॉल्ट शाखा
# यदि केवल एक क्षैतिज विकल्प हो, तो उसी का उपयोग करें
if lateral:
return random.choice(lateral)
# अंत में, उत्तर पर विचार करें
if self.can_drain_into(self.north_ref):
return self.north_ref[0]
return target
यह क्यों काम करता है
दिशा-विशेष जाँचों को छोटे-छोटे प्रेडिकेट्स में बाँटना और उम्मीदवारों के जुटाए हुए सेट से निर्णय लेना, ब्रांचिंग क्रम से पैदा होने वाली छिपी प्राथमिकता को हटा देता है. जब पश्चिम और पूर्व दोनों वैध हों, तो कोड उनके जल-स्तर की तुलना सुसंगत तरीके से करता है और बराबरी होने पर निष्पक्ष यादृच्छिक चयन करता है. क्योंकि नियम-समुच्चय में दक्षिण और उत्तर की भूमिकाएँ अलग हैं, उन्हें स्पष्ट रूप से संभालने पर भी कोई छिपा पक्षपात वापस नहीं आता. यह तरीका समझने और परीक्षण करने में आसान है, क्योंकि हर शर्त एक ही बार व्यक्त होती है और अंतिम चयन केंद्रीकृत रहता है.
व्यावहारिक बातें
पात्रता जाँचों को चयन से अलग रखें, ताकि आप उन्हें अलग-थलग समझ सकें और if/else के क्रम में अनचाही प्राथमिकता घुसने से रोका जा सके. यदि आप ग्रिड पर इटरेट करते हुए पानी को इन-प्लेस अपडेट करते हैं, तो पहले सभी टाइलों के फैसले बफर करें और उसके बाद ही बदलाव लागू करें; इससे इटरेशन का क्रम प्रवाह को किसी एक दिशा में धकेल नहीं पाएगा. छोटे हेल्पर फ़ंक्शनों की ओर रीफैक्टर करने से न केवल शाखाबंदी की गहराई घटती है, बल्कि नियम विस्तार योग्य और डिबग करने में आसान हो जाते हैं.
निष्कर्ष
ग्रिड सिमुलेशन में दिशात्मक पक्षपात अक्सर नियमों से नहीं, बल्कि निर्णय-वृक्ष की संरचना से उपजता है. प्रति-दिशा जाँचों को छोटे हेल्पर्स में बदलकर, सभी वैध लक्ष्यों को समेटकर, और स्पष्ट टाई-ब्रेकर या यादृच्छिकता से चयन करके, आप सिमुलेशन को निष्पक्ष, सममित और संभालने में आसान रखते हैं. और यदि आपका लूप टाइलों को तुरंत अपडेट करता है, तो प्रति-फ्रेम बफर जोड़ें—गणना को अनुप्रयोग से अलग करें—ताकि प्रवाह कहीं अधिक स्वाभाविक दिखे.