Caputchin
تطوير لعبة مخصّصة

ابنِ لعبة مستضافة ذاتيًّا

بنهاية هذا الدرس سيكون لديك لعبة Caputchin حقيقية: حزمة JavaScript تعمل في إطار iframe المعزول الخاص بالأداة، وتكلّم المضيف عبر SDK اللعبة، وتبلّغ أثرًا يستطيع الخادم إعادة تشغيله. بخلاف الوضع اليدوي، تستطيع لعبة مستضافة ذاتيًّا حراسة مفتاح موقع، لأن جولتها قابلة لإعادة التشغيل. يغطّي هذا الدرس بناء اللعبة واستضافتها؛ إعادة التشغيل والحراسة يغطّي الأداة التي تشغّل البوّابة فعلًا.

اقرأ شغّل لعبتك الخاصة لمكان هذا. تحتاج إلى مكان لاستضافة ملفّ JavaScript ساكن (أي CDN أو مضيف ساكن) والأداة على صفحتك.

شكل لعبة مستضافة ذاتيًّا

اللعبة المستضافة ذاتيًّا حزمة JavaScript واحدة مكتفية بذاتها:

  1. تستورد دالة register من @caputchin/game-sdk وتسجّل مصنع لعبة واحدًا.
  2. تعرض في الحاوية التي يعطيها الأداة للمصنع، مستخدمةً اللغة والمظهر والإعداد المحلولة الممرَّرة معها.
  3. تُلعَب حتميًّا تحت بذرة كل جولة، مسجّلةً اللعب كـ أثر.
  4. عند الفوز، تنادي bridge.pass({ trace }) بذلك السجلّ.

تحمّل الأداة حزمتك في إطار iframe خاصتها وتستدعي مصنعك؛ ونداء المصنع هو إشارة البدء (لا حدث بدء منفصل لتنتظره).

1. سجّل مصنع لعبة

ثبّت الـ SDK وناد register مرة بمصنعك. لا تمرّر البيان؛ يحلّ الخادم التهيئات ويسلّمها للمصنع.

import { register } from "@caputchin/game-sdk";

register((container, bridge, ctx) => {
  // container — a DOM element inside the sandboxed iframe; render into it.
  // bridge    — push-only channel to the host (pass / error / setSize).
  // ctx       — the per-round context (seed + resolved locale/skin/config).

  const cleanup = startGame(container, bridge, ctx);

  // Return an optional cleanup function; the widget calls it on unmount.
  return cleanup;
});
npm install @caputchin/game-sdk

2. اقرأ سياق كل جولة

وسيط المصنع الثالث، ctx، يحمل كل ما تحتاجه لعبتك لهذا الزائر:

  • ctx.seed — بذرة إعادة تشغيل كل جولة. اشتقّ كل العشوائية من هذه. null حين تعمل اللعبة خارج جلسة متحقَّق منها.
  • ctx.locale — كائن اللغة المحلول: ctx.locale._lang إضافةً إلى مفاتيح نصوصك المترجَمة.
  • ctx.skin — كائن المظهر المحلول: ctx.skin._theme (دائمًا light أو dark) إضافةً إلى مفاتيح ألوانك وأصولك.
  • ctx.config — إعداد اللعب المحلول (أو null).

هذه التهيئات المحلولة لهذا الزائر، منتَجةً من Caputchin من مخطّطك وتهيئاتك المعرَّفة في لوحة التحكّم. تقرأ مفاتيحك الخاصة منها؛ لا تحلّ أي شيء بنفسك.

إن احتاج تخطيطك حجمًا صريحًا، أخبِر المضيف مرة بعد أول رسم لك:

bridge.setSize(360, 480);

3. اجعل اللعب حتميًّا

هذه القاعدة التي تجعل لعبة قابلة للحراسة: يجب أن تكون اللعبة حتمية بإعطاء البذرة ومُدخَلات اللاعب. لا تنادِ Math.random() ولا تقرأ ساعة الحائط لأي شيء يؤثّر في الحصيلة؛ اشتقّ كل خيار عشوائي من ctx.seed. يعيد الخادم تشغيل منطقك تحت البذرة نفسها والأثر المسجَّل نفسه ويجب أن يبلغ الحُكم نفسه.

// A tiny seeded PRNG; feed it ctx.seed so the server reproduces the run.
function makeRng(seed) {
  let s = hashToInt(seed);
  return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}

سجّل المُدخَلات التي تقود الحصيلة (أيّ أهداف أصابها اللاعب، بالترتيب، بالتوقيت إن كان يهمّ) أثناء لعبك. سلسِل ذلك السجلّ إلى نصّ: ذلك النصّ أثرك.

4. انجح بالأثر

حين يفوز الزائر، سلّم المضيف الأثر. pass يأخذ كائنًا بنصّ trace واحد:

function onWin(traceString) {
  bridge.pass({ trace: traceString });
}

الأثر معتم على المنصّة: إنه أيّ نصّ تعرّفه لعبتك وحدها، بحيث يُنتج منطقك، مدموجًا مع البذرة، النتيجة. لا تبلّغ اللعبة نتيجة هنا؛ والتسجيل، إن وُجِد، شأنك الخاص داخل الإطار.

حقيقتان إضافيتان عن الجسر:

  • أول pass يصرف الجولة؛ وجولة فاشلة أو مهجورة يُشار إليها ببساطة بـ عدم نداء pass (لا طريقة fail على هذا الجسر).
  • bridge.error({ code, message }) لإخفاق داخلي للّعبة (فشل تحميل أصل، أو استثناء)، لا خسارة لاعب. يُظهر حدث error للمضيف.

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

5. احزم للعزل

تحمّل الأداة رابط سكربت واحد بالضبط، فيجب أن يكون كل شيء في ذلك الملفّ الواحد. أعدّ مُجمِّعك (esbuild، أو rollup، أو vite، أو webpack؛ أيٌّ منها) لمخرَج واحد مكتفٍ بذاته:

  • ضمّن الأصول كروابط data (الصور النقطية، والأصوات، والخطوط)؛
  • عطّل تقسيم الكود؛
  • إن استخدمت WASM، ضمّنه كـ base64 وأنشئه من بايتات.

أنماط لا تعمل داخل العزل، لأن الإطار بأصل معتم بـ CSP صارم:

  • fetch('./sprite.png') نسبيّ المسار — لا مسار للجلب منه؛
  • import('./chunk.js') ديناميكي — الرابط الثاني محجوب؛
  • new Worker('./worker.js') — ولّد العمّال من روابط Blob مضمَّنة بدلًا؛
  • جلب CDN خارجي وقت التشغيل — connect-src يحجبه.

6. استضف الحزمة ووجّه الأداة إليها

قدّم الملفّ المبنيّ من مضيفك الساكن عبر https (‏http loopback مسموح للتطوير المحلّي)، ثم وجّه الأداة إليه:

<caputchin-game
  sitekey="cpt_pub_..."
  game-src="https://cdn.example.com/my-game/game.js"
></caputchin-game>

تحمّل الأداة حزمتك في إطار iframe المعزول، وتستدعي مصنعك بالسياق، وتنتظر pass خاصتك. عند هذه النقطة تعمل اللعبة وتتحقّق، لكنها ليست مسموحًا لها بعد بـ حراسة مفتاح.

7. اجعلها قابلة للحراسة

لاستخدام لعبتك المستضافة ذاتيًّا كبوّابة تحقّق، يلزم شيئان إضافيان، كلاهما مغطًّى تاليًا:

  1. سجّلها كلعبة مخصّصة على لوحة التحكّم (معرّف تختاره) وعرّف مخطّط حقولها وتهيئاتها كي يحمل السياق locale/skin/config حقيقيًّا.
  2. ارفع أداة إعادة تشغيل: نسخة بلا واجهة من منطق لعبتك يشغّلها الخادم لإعادة اشتقاق الحُكم من البذرة والأثر. انظر إعادة التشغيل والحراسة.

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

انظر أيضًا

في هذه الصفحة