Skip to content

משחק החיים + מצלמת רשת + שיידרטוי

Posted on:February 3, 2022 at 07:29 PM

Game of Life

Click here for the english version

👾 Try it Live! 👾

Table of contents

Open Table of contents

מיצבים לברנים 🔥

באופן כללי, לי (ולנתי) יש בקלוג אינסופי של רעיונות למיצבים אז חשבנו לשם שינוי ללכת על משהו קל להשגה: להריץ את משחק החיים של קונווי (Conway’s Game of Life) .

משחק החיים - החוקים

משחק החיים הוא משחק עם מעט מאוד חוקים, ולכן קל לממש את הליבה שלו:

  • 🏨 כל משבצת מוקפת ב-8 משבצות, לכן לכל תא יכולים להיות עד 8 שכנים.
  • ⬜⬅⬛ אם התא חי, ויש לו שכן אחד או שאין לו שכנים כלל, הוא מת מבדידות.
  • ⬜⬅⬛ אם יש לתא יותר משלושה שכנים, הוא מת מצפיפות.
  • ⬛⬅⬜ אם התא מת, ויש לו בדיוק שלושה שכנים, הוא הופך להיות חי (“נולד”).
  • ⬜⬅⬜ אחרת (שני שכנים, או שלושה שכנים והתא חי) מצבו לא משתנה.

המימוש הקלאסי הוא עם קונבולוציה עם גרעין (kernel) בגודל 3X3:

[[1, 1, 1],
 [1, 0, 1],
 [1, 1, 1]]

קונבולוציה עם הקרנל סוכמת את השכנים, ואז נותר רק לממש את החוקים של לידה והשרדות, המוות זו ברירת המחדל של כל שאר התאים. אני ממש אהבתי את פשטות המימוש של ArrayFire.

אינטראקטיביות

ואז, כמובן שהתחלנו למרכב את הרעיון, רצינו אינטרקאטיביות, והתחלנו לחשוב על ממשק עם ג’ויסטיק וכפתורים, וניסינו להבין איך בכלל פורסים כזה משחק בשטח. הגדרנו stretch goal - אם סיימנו עם הכפתורים, עושים אינטגרציה למצלמה. ישבנו כמה שעות לתכנת ולא הצלחנו להתקדם לשום מקום משמעותי השעה הייתה 1 בלילה והיינו קצת אבודים…

יותר מזל משכל

ברגע של מזל מוחלט, נזכרתי בכלי מגניב שנקרא Shadertoy. באתר שלהם ניתן ליצור שיידרים (עוד פרטים בהמשך) עם סביבת פיתוח מינימלית, ואינטגרציה עם כל קלט שתרצו: מצלמה, מיקרופון, שעון ועוד. היינו כל כך תקועים עם המימוש שלנו, שפשוט אמרנו fuck it, ושברנו את הראש איך כותבים את GLSL.

אז מה לעזאזל זה שיידר ? WTF Is A Shader

בהפשטה וחסר דיוק משווע, שיידר דו מימדי זה שם לחתיכת קוד שיודעת לחשב צבע של פיקסל. השיידר מקבל כמה אינפוטים, קורדינטות, באפרים, זמן, ועוד כמה דברים, ומחשב את הערך של פיקסל בודד. למה זה טוב? כי במקום לכתוב תוכנה שבה אנחנו מחשבים את כל הפיקסלים אחד אחרי השני, אנחנו יכולים לתת למעבד הגרפי שלנו להריץ את השיידר על מלא פיקסלים במקביל. כל עוד כתבנו שיידר שפוי, אין חשיבות לאיזה פיקסל מעובד קודם, וזה נותן לנו הרבה חופש, וייתרונות בביצועים של התוכנה שלנו. בעיקר, ברגע שמתרגלים לכתוב קוד שנוגע רק בפיקסל בודד כל פעם, אנחנו נותנים לruntime להחליט בשבילנו איך לעשות את השאר נכון.

“Never bet against the compiler.” — Someone on the Internet

איך מחברים תמונה למשחק החיים

אמ;לק - אלגוריתם סף (Threshold) - הופכים את התמונה לבינארית. אחרי שהיא בינארית, כל פיקסל מהווה תא חי או מת במשחק החיים

image thresholding

def threshold(image: np.ndarray, val) -> np.ndarray:
    return image - val >= 0

אז כזה, רק בשיידר, וכדי לתפוס קצוות יותר אפשר לעשות עיבוד תמונה נוסף כדי לחדד

def gaussian_kernel(kernel_size=21, nsig=3):
    x = np.linspace(-nsig, nsig, kernel_size+1)
    kern1d = np.diff(scipy.stats.norm.cdf(x))
    kern2d = np.outer(kern1d, kern1d)
    return kern2d/kern2d.sum()

def gaussian_blur(image: np.ndarray, kernel_size) -> np.ndarray:
    kernel = gaussian_kernel(kernel_size)
    return scipy.signal.convolve2d(image, kernel, 'same')

def sharpen(image: np.ndarray, kernel_size) -> np.ndarray:
    return image - gaussian_blur(image) >= 0

נו, אז מה נסגר עם זה?

נכון, נכון, היה כאן איפשהו מיצב, לקח לנו בערך שעה לכתוב הכל, shadertoy עשו אינטגרציה מדהימה והכל עבד בלי חיכוך. בשטח לקחנו את blink64 (מחשב השטח שלי, שאולי יקבל כאן פוסט מתישהו), חיברנו למקרן ומצלמת רשת, והמיצב רץ במשך יומיים באמצע המדבר.

היה מגניב ממש, אנשים נהנו, ואנחנו קצת פישלנו ולא צילמנו כלום בשטח

¯_(ツ)_/¯