איך בנינו מולטיפלייר 1v1 בזמן אמת עם Cloudflare Durable Objects
15 בפברואר 2026 · 10 דק׳ קריאה
כשהתחלתי לבנות את PriceGuess, זה היה משחק סולו פשוט. מוצר אחד ביום, 6 ניחושים, נקודה. אבל מהר מאוד הבנתי שמה שעושה משחקים כאלה ממכרים הוא תחרות. לשחק לבד זה כיף, לשחק נגד מישהו — זה אדרנלין.
הבעיה: Cloudflare Workers הם Stateless
הדבר הראשון שצריך להבין על Cloudflare Workers הוא שהם stateless — אין להם זיכרון שנשמר בין בקשות. כל בקשה שמגיעה ל-Worker היא "נולדת מחדש". אבל למשחק 1v1 בזמן אמת אנחנו צריכים שני דברים קריטיים: חיבור רציף (WebSocket) ומצב משותף (state) שנגיש לשני השחקנים.
כאן נכנסים Durable Objects של Cloudflare. בקצרה, Durable Object הוא מופע יחיד שחי בנקודה אחת ברשת, יש לו זיכרון שמחזיק מעמד, ואפשר לחבר אליו WebSockets. כל "חדר" של 1v1 הוא Durable Object אחד.
הארכיטקטורה: Service Binding
אחד הדברים שלקח לי הכי הרבה זמן להבין: ב-Cloudflare אי אפשר להריץ Durable Objects באותו Worker שמריץ את אפליקציית Astro. צריך Worker נפרד. הפתרון הוא Service Binding — מנגנון שמאפשר ל-Worker אחד "לקרוא" ל-Worker אחר כאילו הם פונקציות מקומיות, בלי HTTP roundtrip.
כלומר, כש-API Route ב-Astro צריך ליצור חדר חדש, הוא קורא ל-Service Binding שמעביר את הבקשה ל-Worker הנפרד שמנהל את ה-Durable Objects. מבחוץ זה נראה חלק, אבל היו כמה רגעים שהתייאשתי מזה.
מה עשיתי לא נכון (רשימה חלקית)
- השתמשתי ב-
new_classesבמקוםnew_sqlite_classes— בחשבון חינמי של Cloudflare חייבים להשתמש ב-SQLite migration class. גיליתי את זה רק אחרי שעות של "Error: Durable Object class not found". - ניסיתי לפתוח WebSocket מהדפדפן ישירות ל-DO — זה לא עובד. ה-WebSocket חייב לעבור דרך ה-Worker שמנהל את ה-Service Binding, ושם אני מעביר את הבקשה ל-DO.
- שכחתי שה-state נמחק כשה-DO "ישן" — Durable Objects עוברים eviction אחרי חוסר פעילות. המשמעות: אם שני שחקנים ממתינים 30 דקות בלי פעולה, כל ה-state של המשחק נעלם. הפתרון: שמירה ב-SQLite Storage של ה-DO.
- סדר הדיפלוי חשוב — אם מדפלייים את אפליקציית Astro לפני ה-DO Worker, ה-Service Binding נכשל כי הוא מנסה להתחבר ל-Worker שלא קיים עדיין.
מנגנון ה-Matchmaking
שחקן שרוצה משחק אקראי שולח בקשה ל-API. ה-API בודק ב-D1 אם יש חדר פתוח שמחכה לשחקן שני. אם כן — מצרף אותו לחדר. אם לא — יוצר חדר חדש ומגדיר טיימר של 10 שניות. אם אף אחד לא מצטרף תוך 10 שניות, ה-DO מייצר "בוט" שמשחק עם עיכובים אקראיים כדי שירגיש אנושי.
הבוט הוא אחד הדברים שהכי גאה בהם. הוא לא משתמש בנתונים אמיתיים — פשוט מנחש בטווח סביר סביב המחיר האמיתי, עם קצת רנדומליות. הניחושים שלו "מרגישים" כמו שחקן אמיתי.
מערכת הדירוג (ELO)
למשחקים מדורגים, ייבאתי את מערכת ELO הקלאסית מהשחמט. כל שחקן מתחיל עם 1000 נקודות. ניצחון נגד שחקן חזק נותן יותר נקודות, הפסד נגד שחקן חלש גורע יותר. הוספתי גם מערכת ליגות — ברונזה, כסף, זהב, פלטינום, יהלום — שנותנת לשחקנים תחושת התקדמות.
הגורם K שבחרתי הוא 32 (הסטנדרט בשחמט מהיר), מה שאומר שהדירוג מגיב מהר לתוצאות. זה מתאים למשחק כמו שלנו שבו רוצים שהנגות ירגישו חזק.
הטיפ שלי למי שרוצה לבנות מולטיפלייר
תתחילו קטן. הגרסה הראשונה של ה-1v1 תמכה רק ב"קלאסי" — סיבוב אחד, 6 ניחושים, מי שמנחש ראשון מנצח. רק אחרי שזה עבד חלק הוספתי Best of 3, Best of 5, Picture Match, ו-Ranked. כל מצב חדש נבנה כ"הרחבה" של הלוגיקה הבסיסית, לא כשכתוב מאפס.