ক্লাউড স্প্যানার গেম ডেভেলপমেন্টের সাথে শুরু হচ্ছে

1. ভূমিকা

ক্লাউড স্প্যানার হল একটি সম্পূর্ণরূপে পরিচালিত অনুভূমিকভাবে পরিমাপযোগ্য, বিশ্বব্যাপী বিতরণ করা, রিলেশনাল ডাটাবেস পরিষেবা যা কর্মক্ষমতা এবং উচ্চ প্রাপ্যতা ছাড়াই ACID লেনদেন এবং SQL শব্দার্থবিদ্যা প্রদান করে।

এই বৈশিষ্ট্যগুলি স্প্যানারকে গেমগুলির আর্কিটেকচারে একটি দুর্দান্ত ফিট করে তোলে যা বিশ্বব্যাপী প্লেয়ার বেস সক্ষম করতে চায় বা ডেটা সামঞ্জস্যের বিষয়ে উদ্বিগ্ন

এই ল্যাবে, আপনি দুটি Go পরিষেবা তৈরি করবেন যা খেলোয়াড়দের সাইন আপ করতে এবং খেলা শুরু করতে সক্ষম করতে একটি আঞ্চলিক স্প্যানার ডাটাবেসের সাথে ইন্টারঅ্যাক্ট করে।

413fdd57bb0b68bc.png

এরপরে আপনি Python লোড ফ্রেমওয়ার্ক Locust.io ব্যবহার করে সাইন আপ করা এবং গেম খেলার খেলোয়াড়দের অনুকরণ করতে ডেটা তৈরি করবেন। এবং তারপরে আপনি কতজন খেলোয়াড় খেলছেন তা নির্ধারণ করতে স্প্যানারকে জিজ্ঞাসা করবেন, এবং খেলোয়াড়দের খেলার খেলা বনাম খেলার খেলার কিছু পরিসংখ্যান।

অবশেষে, আপনি এই ল্যাবে তৈরি করা সংস্থানগুলি পরিষ্কার করবেন।

আপনি কি নির্মাণ করবেন

এই ল্যাবের অংশ হিসাবে, আপনি করবেন:

  • একটি স্প্যানার উদাহরণ তৈরি করুন
  • Go to handle player signup-এ লেখা একটি প্রোফাইল পরিষেবা স্থাপন করুন৷
  • গেমগুলিতে খেলোয়াড়দের বরাদ্দ করতে, বিজয়ী নির্ধারণ করতে এবং খেলোয়াড়দের গেমের পরিসংখ্যান আপডেট করতে Go-তে লেখা একটি ম্যাচমেকিং পরিষেবা স্থাপন করুন।

আপনি কি শিখবেন

  • একটি ক্লাউড স্প্যানার উদাহরণ কিভাবে সেটআপ করবেন
  • কিভাবে একটি গেম ডাটাবেস এবং স্কিমা তৈরি করতে হয়
  • ক্লাউড স্প্যানারের সাথে কাজ করার জন্য কীভাবে গো অ্যাপস স্থাপন করবেন
  • পঙ্গপাল ব্যবহার করে কীভাবে ডেটা তৈরি করবেন
  • গেম এবং প্লেয়ার সম্পর্কে প্রশ্নের উত্তর দিতে ক্লাউড স্প্যানারে কীভাবে ডেটা জিজ্ঞাসা করবেন।

আপনি কি প্রয়োজন হবে

  • একটি Google ক্লাউড প্রকল্প যা একটি বিলিং অ্যাকাউন্টের সাথে সংযুক্ত৷
  • একটি ওয়েব ব্রাউজার, যেমন ক্রোম বা ফায়ারফক্স

2. সেটআপ এবং প্রয়োজনীয়তা

একটি প্রকল্প তৈরি করুন

আপনার যদি ইতিমধ্যে একটি Google অ্যাকাউন্ট না থাকে (Gmail বা Google Apps), তাহলে আপনাকে অবশ্যই একটি তৈরি করতে হবে। Google ক্লাউড প্ল্যাটফর্ম কনসোলে সাইন-ইন করুন ( console.cloud.google.com ) এবং একটি নতুন প্রকল্প তৈরি করুন৷

আপনার যদি ইতিমধ্যে একটি প্রকল্প থাকে, তাহলে কনসোলের উপরের বাম দিকে প্রজেক্ট নির্বাচন পুল ডাউন মেনুতে ক্লিক করুন:

6c9406d9b014760.png

এবং একটি নতুন প্রকল্প তৈরি করতে ফলাফল ডায়ালগে 'নতুন প্রকল্প' বোতামে ক্লিক করুন:

949d83c8a4ee17d9.png

আপনার যদি ইতিমধ্যে একটি প্রকল্প না থাকে, তাহলে আপনার প্রথমটি তৈরি করতে আপনাকে এই মত একটি ডায়ালগ দেখতে হবে:

870a3cbd6541ee86.png

পরবর্তী প্রকল্প তৈরির ডায়ালগ আপনাকে আপনার নতুন প্রকল্পের বিশদ বিবরণ প্রবেশ করতে দেয়:

6a92c57d3250a4b3.png

প্রজেক্ট আইডিটি মনে রাখবেন, যা সমস্ত Google ক্লাউড প্রকল্প জুড়ে একটি অনন্য নাম (উপরের নামটি ইতিমধ্যে নেওয়া হয়েছে এবং আপনার জন্য কাজ করবে না, দুঃখিত!)। এটিকে পরে এই কোডল্যাবে PROJECT_ID হিসাবে উল্লেখ করা হবে৷

পরবর্তী, যদি আপনি ইতিমধ্যে এটি না করে থাকেন, তাহলে Google ক্লাউড সংস্থানগুলি ব্যবহার করতে এবং ক্লাউড স্প্যানার API সক্ষম করার জন্য আপনাকে বিকাশকারী কনসোলে বিলিং সক্ষম করতে হবে৷

15d0ef27a8fbab27.png

এই কোডল্যাবের মাধ্যমে চালানোর জন্য আপনার কয়েক ডলারের বেশি খরচ করা উচিত নয়, তবে আপনি যদি আরও সংস্থান ব্যবহার করার সিদ্ধান্ত নেন বা আপনি যদি সেগুলিকে চলমান রেখে দেন তবে এটি আরও বেশি হতে পারে (এই নথির শেষে "পরিষ্কার" বিভাগটি দেখুন)। Google ক্লাউড স্প্যানার মূল্য এখানে নথিভুক্ত করা হয়েছে।

Google ক্লাউড প্ল্যাটফর্মের নতুন ব্যবহারকারীরা $300 বিনামূল্যের ট্রায়ালের জন্য যোগ্য, যা এই কোডল্যাবটিকে সম্পূর্ণ বিনামূল্যে করতে হবে৷

গুগল ক্লাউড শেল সেটআপ

যদিও Google ক্লাউড এবং স্প্যানার আপনার ল্যাপটপ থেকে দূরবর্তীভাবে পরিচালিত হতে পারে, এই কোডল্যাবে আমরা Google ক্লাউড শেল ব্যবহার করব, ক্লাউডে চলমান একটি কমান্ড লাইন পরিবেশ।

এই ডেবিয়ান-ভিত্তিক ভার্চুয়াল মেশিনটি আপনার প্রয়োজনীয় সমস্ত বিকাশের সরঞ্জামগুলির সাথে লোড করা হয়েছে। এটি একটি ক্রমাগত 5GB হোম ডিরেক্টরি অফার করে এবং Google ক্লাউডে চলে, যা নেটওয়ার্ক কর্মক্ষমতা এবং প্রমাণীকরণকে ব্যাপকভাবে উন্নত করে। এর মানে হল যে এই কোডল্যাবের জন্য আপনার যা দরকার তা হল একটি ব্রাউজার (হ্যাঁ, এটি একটি Chromebook এ কাজ করে)।

  1. ক্লাউড কনসোল থেকে ক্লাউড শেল সক্রিয় করতে, কেবল ক্লাউড শেল সক্রিয় করুন ক্লিক করুন gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHKLJXOgOg39g ddS2A (পরিবেশের সাথে সংযোগ স্থাপন এবং সংযোগের জন্য এটি শুধুমাত্র কয়েক মুহূর্ত নিতে হবে)।

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvDu64GoM8 Rw

স্ক্রীন শট 2017-06-14 10.13.43 PM.png এ

একবার ক্লাউড শেলের সাথে সংযুক্ত হয়ে গেলে, আপনি দেখতে পাবেন যে আপনি ইতিমধ্যেই প্রমাণীকৃত এবং প্রকল্পটি ইতিমধ্যেই আপনার PROJECT_ID-তে সেট করা আছে৷

gcloud auth list

কমান্ড আউটপুট

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

কমান্ড আউটপুট

[core]
project = <PROJECT_ID>

যদি, কোন কারণে, প্রকল্পটি সেট করা না হয়, কেবল নিম্নলিখিত কমান্ডটি জারি করুন:

gcloud config set project <PROJECT_ID>

আপনার PROJECT_ID খুঁজছেন? সেটআপ ধাপে আপনি কোন আইডি ব্যবহার করেছেন তা দেখুন বা ক্লাউড কনসোল ড্যাশবোর্ডে দেখুন:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvOBziBc35650

ক্লাউড শেল ডিফল্টরূপে কিছু এনভায়রনমেন্ট ভেরিয়েবলও সেট করে, যা আপনার ভবিষ্যত কমান্ড চালানোর সময় কার্যকর হতে পারে।

echo $GOOGLE_CLOUD_PROJECT

কমান্ড আউটপুট

<PROJECT_ID>

কোডটি ডাউনলোড করুন

ক্লাউড শেলে, আপনি এই ল্যাবের কোডটি ডাউনলোড করতে পারেন। এটি v0.1.0 রিলিজের উপর ভিত্তি করে, তাই সেই ট্যাগটি পরীক্ষা করে দেখুন:

git clone https://github.com/cloudspannerecosystem/spanner-gaming-sample.git
cd spanner-gaming-sample/

# Check out v0.1.0 release
git checkout tags/v0.1.0 -b v0.1.0-branch

কমান্ড আউটপুট

Cloning into 'spanner-gaming-sample'...
*snip*
Switched to a new branch 'v0.1.0-branch'

পঙ্গপাল লোড জেনারেটর সেটআপ করুন

Locust হল একটি Python লোড টেস্টিং ফ্রেমওয়ার্ক যা REST API এন্ডপয়েন্ট পরীক্ষা করতে উপযোগী। এই কোডল্যাবে, 'জেনারেটর' ডিরেক্টরিতে আমাদের 2টি ভিন্ন লোড পরীক্ষা রয়েছে যা আমরা হাইলাইট করব:

  • authentication_server.py : প্লেয়ার তৈরি করার জন্য এবং সিঙ্গেল পয়েন্ট লুকআপ অনুকরণ করার জন্য একটি র্যান্ডম প্লেয়ার পাওয়ার কাজগুলি রয়েছে৷
  • match_server.py : গেম তৈরি এবং গেম বন্ধ করার কাজ রয়েছে। গেম তৈরি করা 100 র্যান্ডম প্লেয়ারকে বরাদ্দ করবে যারা বর্তমানে গেম খেলছে না। গেমগুলি বন্ধ করা হলে গেম_খেলানো এবং গেমস_জয় পরিসংখ্যান আপডেট হবে এবং সেই খেলোয়াড়দের ভবিষ্যতের গেমের জন্য বরাদ্দ করার অনুমতি দেবে।

ক্লাউড শেলে পঙ্গপাল চালানোর জন্য, আপনার পাইথন 3.7 বা উচ্চতর প্রয়োজন হবে। ক্লাউড শেল পাইথন 3.9 এর সাথে আসে, তাই সংস্করণটি যাচাই করা ছাড়া কিছুই করার নেই:

python -V

কমান্ড আউটপুট

Python 3.9.12

এখন, আপনি পঙ্গপালের জন্য প্রয়োজনীয়তা ইনস্টল করতে পারেন।

pip3 install -r requirements.txt

কমান্ড আউটপুট

Collecting locust==2.11.1
*snip*
Successfully installed ConfigArgParse-1.5.3 Flask-BasicAuth-0.2.0 Flask-Cors-3.0.10 brotli-1.0.9 gevent-21.12.0 geventhttpclient-2.0.2 greenlet-1.1.3 locust-2.11.1 msgpack-1.0.4 psutil-5.9.2 pyzmq-22.3.0 roundrobin-0.0.4 zope.event-4.5.0 zope.interface-5.4.0

এখন, PATH আপডেট করুন যাতে নতুন ইনস্টল করা পঙ্গপাল বাইনারি খুঁজে পাওয়া যায়:

PATH=~/.local/bin":$PATH"
which locust

কমান্ড আউটপুট

/home/<user>/.local/bin/locust

সারাংশ

এই ধাপে আপনি আপনার প্রকল্প সেট আপ করেছেন যদি আপনার কাছে ইতিমধ্যে একটি না থাকে, সক্রিয় ক্লাউড শেল, এবং এই ল্যাবের জন্য কোডটি ডাউনলোড করেন।

অবশেষে, আপনি ল্যাবে পরে লোড জেনারেশনের জন্য পঙ্গপাল সেট আপ করেছেন।

পরবর্তী আপ

এরপরে, আপনি ক্লাউড স্প্যানার ইনস্ট্যান্স এবং ডাটাবেস সেট আপ করবেন।

3. একটি স্প্যানার ইনস্ট্যান্স এবং ডাটাবেস তৈরি করুন

স্প্যানার উদাহরণ তৈরি করুন

এই ধাপে আমরা কোডল্যাবের জন্য আমাদের স্প্যানার ইনস্ট্যান্স সেট আপ করি। স্প্যানার এন্ট্রি জন্য অনুসন্ধান করুন 1a6580bd3d3e6783.png বাম উপরের হ্যামবার্গার মেনুতে 3129589f7bc9e5ce.png অথবা "/" টিপে স্প্যানার অনুসন্ধান করুন এবং "স্প্যানার" টাইপ করুন

36e52f8df8e13b99.png

পরবর্তী, ক্লিক করুন 95269e75bc8c3e4d.png এবং আপনার উদাহরণের জন্য cloudspanner-gaming ইনস্ট্যান্স নামটি প্রবেশ করান, একটি কনফিগারেশন নির্বাচন করে (একটি আঞ্চলিক উদাহরণ নির্বাচন করুন যেমন us-central1 ), এবং নোডের সংখ্যা সেট করে ফর্মটি পূরণ করুন। এই কোডল্যাবের জন্য আমাদের প্রয়োজন হবে মাত্র 500 processing units

সবশেষে, কিন্তু অন্তত নয়, "তৈরি করুন" এ ক্লিক করুন এবং কয়েক সেকেন্ডের মধ্যে আপনার হাতে একটি ক্লাউড স্প্যানার ইনস্ট্যান্স আছে।

4457c324c94f93e6.png

ডাটাবেস এবং স্কিমা তৈরি করুন

একবার আপনার দৃষ্টান্ত চালু হলে, আপনি ডাটাবেস তৈরি করতে পারেন। স্প্যানার একটি একক উদাহরণে একাধিক ডাটাবেসের জন্য অনুমতি দেয়।

ডাটাবেস হল যেখানে আপনি আপনার স্কিমা সংজ্ঞায়িত করেন। আপনি ডাটাবেসে কার অ্যাক্সেস আছে তা নিয়ন্ত্রণ করতে পারেন, কাস্টম এনক্রিপশন সেট আপ করতে পারেন, অপ্টিমাইজার কনফিগার করতে পারেন এবং ধরে রাখার সময়কাল সেট করতে পারেন।

বহু-আঞ্চলিক উদাহরণে, আপনি ডিফল্ট নেতা কনফিগার করতে পারেন। স্প্যানারে ডাটাবেস সম্পর্কে আরও পড়ুন

এই কোড-ল্যাবের জন্য, আপনি ডিফল্ট বিকল্পগুলির সাথে ডাটাবেস তৈরি করবেন এবং তৈরির সময় স্কিমা সরবরাহ করবেন।

এই ল্যাবটি দুটি টেবিল তৈরি করবে: প্লেয়ার এবং গেম

77651ac12e47fe2a.png

খেলোয়াড়রা সময়ের সাথে সাথে অনেক গেমে অংশগ্রহণ করতে পারে, কিন্তু একবারে শুধুমাত্র একটি খেলা। Games_played এবং games_won- এর মতো আকর্ষণীয় পরিসংখ্যানের উপর নজর রাখতে খেলোয়াড়দের JSON ডেটা টাইপ হিসাবে পরিসংখ্যানও থাকে। কারণ অন্যান্য পরিসংখ্যান পরে যোগ করা হতে পারে, এটি কার্যকরভাবে খেলোয়াড়দের জন্য একটি পরিকল্পনাবিহীন কলাম।

গেমগুলি স্প্যানারের ARRAY ডেটা টাইপ ব্যবহার করে অংশগ্রহণকারী খেলোয়াড়দের ট্র্যাক রাখে৷ গেমটি বন্ধ না হওয়া পর্যন্ত একটি গেমের বিজয়ী এবং সমাপ্ত বৈশিষ্ট্যগুলি পূরণ করা হয় না।

খেলোয়াড়ের বর্তমান_গেমটি একটি বৈধ খেলা তা নিশ্চিত করার জন্য একটি বিদেশী কী আছে।

এখন ইনস্ট্যান্স ওভারভিউতে 'Create Database' এ ক্লিক করে ডাটাবেস তৈরি করুন:

a820db6c4a4d6f2d.png

এবং তারপর বিস্তারিত পূরণ করুন. গুরুত্বপূর্ণ বিকল্প হল ডাটাবেসের নাম এবং উপভাষা। এই উদাহরণে, আমরা ডাটাবেস নমুনা-গেমের নাম দিয়েছি এবং Google স্ট্যান্ডার্ড SQL উপভাষা বেছে নিয়েছি।

স্কিমার জন্য, এই ডিডিএলটি বক্সে কপি করে পেস্ট করুন:

CREATE TABLE games (
  gameUUID STRING(36) NOT NULL,
  players ARRAY<STRING(36)> NOT NULL,
  winner STRING(36),
  created TIMESTAMP,
  finished TIMESTAMP,
) PRIMARY KEY(gameUUID);

CREATE TABLE players (
  playerUUID STRING(36) NOT NULL,
  player_name STRING(64) NOT NULL,
  email STRING(MAX) NOT NULL,
  password_hash BYTES(60) NOT NULL,
  created TIMESTAMP,
  updated TIMESTAMP,
  stats JSON,
  account_balance NUMERIC NOT NULL DEFAULT (0.00),
  is_logged_in BOOL,
  last_login TIMESTAMP,
  valid_email BOOL,
  current_game STRING(36),
  FOREIGN KEY (current_game) REFERENCES games (gameUUID),
) PRIMARY KEY(playerUUID);

CREATE UNIQUE INDEX PlayerAuthentication ON players(email) STORING (password_hash);

CREATE INDEX PlayerGame ON players(current_game);

CREATE UNIQUE INDEX PlayerName ON players(player_name);

তারপর, তৈরি বোতামে ক্লিক করুন এবং আপনার ডাটাবেস তৈরি হওয়ার জন্য কয়েক সেকেন্ড অপেক্ষা করুন।

ডাটাবেস তৈরি পৃষ্ঠাটি এইরকম হওয়া উচিত:

d39d358dc7d32939.png

এখন, কোড ল্যাবে পরে ব্যবহার করার জন্য আপনাকে ক্লাউড শেলে কিছু পরিবেশের ভেরিয়েবল সেট করতে হবে। তাই ইনস্ট্যান্স-আইডি নোট করুন এবং ক্লাউড শেল এ INSTANCE_ID এবং DATABASE_ID সেট করুন

f6f98848d3aea9c.png

export SPANNER_PROJECT_ID=$GOOGLE_CLOUD_PROJECT
export SPANNER_INSTANCE_ID=cloudspanner-gaming
export SPANNER_DATABASE_ID=sample-game

সারাংশ

এই ধাপে আপনি একটি স্প্যানার উদাহরণ এবং নমুনা-গেম ডাটাবেস তৈরি করেছেন। আপনি এই নমুনা গেমটি যে স্কিমা ব্যবহার করে তাও সংজ্ঞায়িত করেছেন।

পরবর্তী আপ

এর পরে, আপনি গেম খেলতে খেলোয়াড়দের সাইন আপ করার অনুমতি দেওয়ার জন্য প্রোফাইল পরিষেবা স্থাপন করবেন!

4. প্রোফাইল পরিষেবা স্থাপন করুন

পরিষেবা ওভারভিউ

প্রোফাইল পরিষেবা হল গো-তে লেখা একটি REST API যা জিন ফ্রেমওয়ার্কের সুবিধা দেয়।

4fce45ee6c858b3e.png

এই API-এ, খেলোয়াড়রা গেম খেলতে সাইন আপ করতে পারে। এটি একটি সাধারণ POST কমান্ড দ্বারা তৈরি করা হয়েছে যা একটি প্লেয়ারের নাম, ইমেল এবং পাসওয়ার্ড গ্রহণ করে। পাসওয়ার্ডটি bcrypt দিয়ে এনক্রিপ্ট করা হয় এবং হ্যাশ ডাটাবেসে সংরক্ষণ করা হয়।

ইমেল একটি অনন্য শনাক্তকারী হিসাবে বিবেচিত হয়, যখন player_name গেমের প্রদর্শনের উদ্দেশ্যে ব্যবহার করা হয়।

এই API বর্তমানে লগইন পরিচালনা করে না, তবে এটি বাস্তবায়ন করা একটি অতিরিক্ত অনুশীলন হিসাবে আপনার উপর ছেড়ে দেওয়া যেতে পারে।

প্রোফাইল পরিষেবার জন্য ./src/golang/profile-service/main.go ফাইলটি নিম্নরূপ দুটি প্রাথমিক শেষ বিন্দু প্রকাশ করে:

func main() {
   configuration, _ := config.NewConfig()

   router := gin.Default()
   router.SetTrustedProxies(nil)

   router.Use(setSpannerConnection(configuration))

   router.POST("/players", createPlayer)
   router.GET("/players", getPlayerUUIDs)
   router.GET("/players/:id", getPlayerByID)

   router.Run(configuration.Server.URL())
}

এবং সেই শেষ পয়েন্টগুলির কোড প্লেয়ার মডেলের দিকে যাবে।

func getPlayerByID(c *gin.Context) {
   var playerUUID = c.Param("id")

   ctx, client := getSpannerConnection(c)

   player, err := models.GetPlayerByUUID(ctx, client, playerUUID)
   if err != nil {
       c.IndentedJSON(http.StatusNotFound, gin.H{"message": "player not found"})
       return
   }

   c.IndentedJSON(http.StatusOK, player)
}

func createPlayer(c *gin.Context) {
   var player models.Player

   if err := c.BindJSON(&player); err != nil {
       c.AbortWithError(http.StatusBadRequest, err)
       return
   }

   ctx, client := getSpannerConnection(c)
   err := player.AddPlayer(ctx, client)
   if err != nil {
       c.AbortWithError(http.StatusBadRequest, err)
       return
   }

   c.IndentedJSON(http.StatusCreated, player.PlayerUUID)
}

পরিষেবাটি প্রথম জিনিসগুলির মধ্যে একটি হল স্প্যানার সংযোগ সেট করা৷ এটি পরিষেবার জন্য সেশন পুল তৈরি করতে পরিষেবা স্তরে প্রয়োগ করা হয়।

func setSpannerConnection() gin.HandlerFunc {
   ctx := context.Background()
   client, err := spanner.NewClient(ctx, configuration.Spanner.URL())

   if err != nil {
       log.Fatal(err)
   }

   return func(c *gin.Context) {
       c.Set("spanner_client", *client)
       c.Set("spanner_context", ctx)
       c.Next()
   }
}

প্লেয়ার এবং প্লেয়ারস্ট্যাটগুলি নিম্নরূপ সংজ্ঞায়িত করা হয়েছে:

type Player struct {
   PlayerUUID      string `json:"playerUUID" validate:"omitempty,uuid4"`
   Player_name     string `json:"player_name" validate:"required_with=Password Email"`
   Email           string `json:"email" validate:"required_with=Player_name Password,email"`
   // not stored in DB
   Password        string `json:"password" validate:"required_with=Player_name Email"` 
   // stored in DB
   Password_hash   []byte `json:"password_hash"`                                       
   created         time.Time
   updated         time.Time
   Stats           spanner.NullJSON `json:"stats"`
   Account_balance big.Rat          `json:"account_balance"`
   last_login      time.Time
   is_logged_in    bool
   valid_email     bool
   Current_game    string `json:"current_game" validate:"omitempty,uuid4"`
}

type PlayerStats struct {
   Games_played spanner.NullInt64 `json:"games_played"`
   Games_won    spanner.NullInt64 `json:"games_won"`
}

প্লেয়ার যোগ করার ফাংশনটি একটি ReadWrite লেনদেনের মধ্যে একটি DML সন্নিবেশের সুবিধা দেয়, কারণ প্লেয়ার যোগ করা ব্যাচ সন্নিবেশের পরিবর্তে একটি একক বিবৃতি। ফাংশন এই মত দেখায়:

func (p *Player) AddPlayer(ctx context.Context, client spanner.Client) error {
   // Validate based on struct validation rules
   err := p.Validate()
   if err != nil {
       return err
   }

   // take supplied password+salt, hash. Store in user_password
   passHash, err := hashPassword(p.Password)

   if err != nil {
       return errors.New("Unable to hash password")
   }

   p.Password_hash = passHash

   // Generate UUIDv4
   p.PlayerUUID = generateUUID()

   // Initialize player stats
   emptyStats := spanner.NullJSON{Value: PlayerStats{
       Games_played: spanner.NullInt64{Int64: 0, Valid: true},
       Games_won:    spanner.NullInt64{Int64: 0, Valid: true},
   }, Valid: true}

   // insert into spanner
   _, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
       stmt := spanner.Statement{
           SQL: `INSERT players (playerUUID, player_name, email, password_hash, created, stats) VALUES
                   (@playerUUID, @playerName, @email, @passwordHash, CURRENT_TIMESTAMP(), @pStats)
           `,
           Params: map[string]interface{}{
               "playerUUID":   p.PlayerUUID,
               "playerName":   p.Player_name,
               "email":        p.Email,
               "passwordHash": p.Password_hash,
               "pStats":       emptyStats,
           },
       }

       _, err := txn.Update(ctx, stmt)
       return err
   })
   if err != nil {
       return err
   }
   // return empty error on success
   return nil
}

একটি প্লেয়ারকে তাদের UUID এর উপর ভিত্তি করে পুনরুদ্ধার করতে, একটি সাধারণ রিড জারি করা হয়। এটি প্লেয়ার প্লেয়ার ইউইউআইডি, প্লেয়ার_নাম, ইমেল এবং পরিসংখ্যান পুনরুদ্ধার করে।

func GetPlayerByUUID(ctx context.Context, client spanner.Client, uuid string) (Player, error) {
   row, err := client.Single().ReadRow(ctx, "players",
       spanner.Key{uuid}, []string{"playerUUID", "player_name", "email", "stats"})
   if err != nil {
       return Player{}, err
   }

   player := Player{}
   err = row.ToStruct(&player)

   if err != nil {
       return Player{}, err
   }
   return player, nil
}

ডিফল্টরূপে, পরিবেশের ভেরিয়েবল ব্যবহার করে পরিষেবাটি কনফিগার করা হয়। ./src/golang/profile-service/config/config.go ফাইলের প্রাসঙ্গিক বিভাগটি দেখুন।

func NewConfig() (Config, error) {
   *snip*
   // Server defaults
   viper.SetDefault("server.host", "localhost")
   viper.SetDefault("server.port", 8080)

   // Bind environment variable override
   viper.BindEnv("server.host", "SERVICE_HOST")
   viper.BindEnv("server.port", "SERVICE_PORT")
   viper.BindEnv("spanner.project_id", "SPANNER_PROJECT_ID")
   viper.BindEnv("spanner.instance_id", "SPANNER_INSTANCE_ID")
   viper.BindEnv("spanner.database_id", "SPANNER_DATABASE_ID")

   *snip*

   return c, nil
}

আপনি দেখতে পাচ্ছেন যে স্থানীয় হোস্ট:8080-এ পরিষেবা চালানোর জন্য ডিফল্ট আচরণ।

এই তথ্য দিয়ে পরিষেবাটি চালানোর সময় এসেছে।

প্রোফাইল পরিষেবা চালান

Go কমান্ড ব্যবহার করে পরিষেবাটি চালান। এটি নির্ভরতা ডাউনলোড করবে এবং পোর্ট 8080 এ চলমান পরিষেবাটি স্থাপন করবে:

cd ~/spanner-gaming-sample/src/golang/profile-service
go run . &

কমান্ড আউটপুট:

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /players                  --> main.createPlayer (4 handlers)
[GIN-debug] GET    /players                  --> main.getPlayerUUIDs (4 handlers)
[GIN-debug] GET    /players/:id              --> main.getPlayerByID (4 handlers)
[GIN-debug] GET    /players/:id/stats        --> main.getPlayerStats (4 handlers)
[GIN-debug] Listening and serving HTTP on localhost:8080

একটি কার্ল কমান্ড জারি করে পরিষেবাটি পরীক্ষা করুন:

curl http://localhost:8080/players \
    --include \
    --header "Content-Type: application/json" \
    --request "POST" \
    --data '{"email": "test@gmail.com","password": "s3cur3P@ss","player_name": "Test Player"}'

কমান্ড আউটপুট:

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: <date> 18:55:08 GMT
Content-Length: 38

"506a1ab6-ee5b-4882-9bb1-ef9159a72989"

সারাংশ

এই ধাপে, আপনি প্রোফাইল পরিষেবা স্থাপন করেছেন যা খেলোয়াড়দের আপনার গেম খেলতে সাইন আপ করার অনুমতি দেয় এবং আপনি একটি নতুন প্লেয়ার তৈরি করতে একটি POST api কল ইস্যু করে পরিষেবাটি পরীক্ষা করেছেন৷

পরবর্তী পদক্ষেপ

পরবর্তী ধাপে, আপনি ম্যাচ মেকিং পরিষেবা স্থাপন করবেন।

5. ম্যাচ মেকিং পরিষেবা স্থাপন করুন

পরিষেবা ওভারভিউ

ম্যাচ মেকিং সার্ভিস হল গো-তে লেখা একটি REST API যা জিন ফ্রেমওয়ার্কের সুবিধা দেয়।

9aecd571df0dcd7c.png

এই API তে, গেম তৈরি এবং বন্ধ করা হয়। যখন একটি গেম তৈরি করা হয়, 10 জন খেলোয়াড় যারা বর্তমানে একটি গেম খেলছেন না তাদের গেমটিতে নিয়োগ করা হয়।

যখন একটি খেলা বন্ধ হয়ে যায়, তখন একজন বিজয়ী এলোমেলোভাবে নির্বাচিত হয় এবং প্রতিটি খেলোয়াড়ের পরিসংখ্যান গেম_খেলানো এবং গেমস_জয় এর জন্য সমন্বয় করা হয়। এছাড়াও, প্রতিটি খেলোয়াড়কে আপডেট করা হয় যে তারা আর খেলছে না এবং ভবিষ্যতের গেম খেলতেও উপলব্ধ রয়েছে।

ম্যাচমেকিং পরিষেবার জন্য ./src/golang/matchmaking-service/main.go ফাইলটি প্রোফাইল পরিষেবার মতো একই সেটআপ এবং কোড অনুসরণ করে, তাই এটি এখানে পুনরাবৃত্তি করা হয়নি৷ এই পরিষেবাটি নিম্নরূপ দুটি প্রাথমিক শেষ পয়েন্ট প্রকাশ করে:

func main() {
   router := gin.Default()
   router.SetTrustedProxies(nil)

   router.Use(setSpannerConnection())

   router.POST("/games/create", createGame)
   router.PUT("/games/close", closeGame)

   router.Run(configuration.Server.URL())
}

এই পরিষেবাটি একটি গেম স্ট্রাকট প্রদান করে, সেইসাথে প্লেয়ার এবং প্লেয়ারস্ট্যাটগুলিকে স্লিমড ডাউন করে:

type Game struct {
   GameUUID string           `json:"gameUUID"`
   Players  []string         `json:"players"`
   Winner   string           `json:"winner"`
   Created  time.Time        `json:"created"`
   Finished spanner.NullTime `json:"finished"`
}

type Player struct {
   PlayerUUID   string           `json:"playerUUID"`
   Stats        spanner.NullJSON `json:"stats"`
   Current_game string           `json:"current_game"`
}

type PlayerStats struct {
   Games_played int `json:"games_played"`
   Games_won    int `json:"games_won"`
}

একটি গেম তৈরি করতে, ম্যাচমেকিং পরিষেবাটি 100 জন খেলোয়াড়ের একটি এলোমেলো নির্বাচন দখল করে যারা বর্তমানে একটি গেম খেলছে না।

স্প্যানার মিউটেশনগুলিকে গেম তৈরি করতে এবং খেলোয়াড়দের বরাদ্দ করার জন্য বেছে নেওয়া হয়, যেহেতু মিউটেশনগুলি বড় পরিবর্তনের জন্য DML এর চেয়ে বেশি কার্যকরী।

// Create a new game and assign players
// Players that are not currently playing a game are eligble to be selected for the new game
// Current implementation allows for less than numPlayers to be placed in a game
func (g *Game) CreateGame(ctx context.Context, client spanner.Client) error {
   // Initialize game values
   g.GameUUID = generateUUID()

   numPlayers := 10

   // Create and assign
   _, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
       var m []*spanner.Mutation

       // get players
       query := fmt.Sprintf("SELECT playerUUID FROM (SELECT playerUUID FROM players WHERE current_game IS NULL LIMIT 10000) TABLESAMPLE RESERVOIR (%d ROWS)", numPlayers)
       stmt := spanner.Statement{SQL: query}
       iter := txn.Query(ctx, stmt)

       playerRows, err := readRows(iter)
       if err != nil {
           return err
       }

       var playerUUIDs []string

       for _, row := range playerRows {
           var pUUID string
           if err := row.Columns(&pUUID); err != nil {
               return err
           }

           playerUUIDs = append(playerUUIDs, pUUID)
       }

       // Create the game
       gCols := []string{"gameUUID", "players", "created"}
       m = append(m, spanner.Insert("games", gCols, []interface{}{g.GameUUID, playerUUIDs, time.Now()}))

       // Update players to lock into this game
       for _, p := range playerUUIDs {
           pCols := []string{"playerUUID", "current_game"}
           m = append(m, spanner.Update("players", pCols, []interface{}{p, g.GameUUID}))
       }

       txn.BufferWrite(m)

       return nil
   })

   if err != nil {
       return err
   }

   return nil
}

প্লেয়ারদের এলোমেলো নির্বাচন GoogleSQL-এর টেবিলস্পেস রিজার্ভর ক্ষমতা ব্যবহার করে SQL দিয়ে করা হয়।

একটি খেলা বন্ধ করা একটু বেশি জটিল। এতে গেমের খেলোয়াড়দের মধ্যে একটি এলোমেলো বিজয়ী নির্বাচন করা, গেমটি শেষ হওয়ার সময় চিহ্নিত করা এবং প্রতিটি খেলোয়াড়ের পরিসংখ্যান গেম_খেলানো এবং গেমস_ওয়ান এর জন্য আপডেট করা জড়িত।

এই জটিলতা এবং পরিবর্তনের পরিমাণের কারণে, মিউটেশনগুলি আবার গেমটি বন্ধ করার জন্য বেছে নেওয়া হয়।

func determineWinner(playerUUIDs []string) string {
   if len(playerUUIDs) == 0 {
       return ""
   }

   var winnerUUID string

   rand.Seed(time.Now().UnixNano())
   offset := rand.Intn(len(playerUUIDs))
   winnerUUID = playerUUIDs[offset]
   return winnerUUID
}

// Given a list of players and a winner's UUID, update players of a game
// Updating players involves closing out the game (current_game = NULL) and
// updating their game stats. Specifically, we are incrementing games_played.
// If the player is the determined winner, then their games_won stat is incremented.
func (g Game) updateGamePlayers(ctx context.Context, players []Player, txn *spanner.ReadWriteTransaction) error {
   for _, p := range players {
       // Modify stats
       var pStats PlayerStats
       json.Unmarshal([]byte(p.Stats.String()), &pStats)

       pStats.Games_played = pStats.Games_played + 1

       if p.PlayerUUID == g.Winner {
           pStats.Games_won = pStats.Games_won + 1
       }
       updatedStats, _ := json.Marshal(pStats)
       p.Stats.UnmarshalJSON(updatedStats)

       // Update player
       // If player's current game isn't the same as this game, that's an error
       if p.Current_game != g.GameUUID {
           errorMsg := fmt.Sprintf("Player '%s' doesn't belong to game '%s'.", p.PlayerUUID, g.GameUUID)
           return errors.New(errorMsg)
       }

       cols := []string{"playerUUID", "current_game", "stats"}
       newGame := spanner.NullString{
           StringVal: "",
           Valid:     false,
       }

       txn.BufferWrite([]*spanner.Mutation{
           spanner.Update("players", cols, []interface{}{p.PlayerUUID, newGame, p.Stats}),
       })
   }

   return nil
}

// Closing game. When provided a Game, choose a random winner and close out the game.
// A game is closed by setting the winner and finished time.
// Additionally all players' game stats are updated, and the current_game is set to null to allow
// them to be chosen for a new game.
func (g *Game) CloseGame(ctx context.Context, client spanner.Client) error {
   // Close game
   _, err := client.ReadWriteTransaction(ctx,
       func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
           // Get game players
           playerUUIDs, players, err := g.getGamePlayers(ctx, txn)

           if err != nil {
               return err
           }

           // Might be an issue if there are no players!
           if len(playerUUIDs) == 0 {
               errorMsg := fmt.Sprintf("No players found for game '%s'", g.GameUUID)
               return errors.New(errorMsg)
           }

           // Get random winner
           g.Winner = determineWinner(playerUUIDs)

           // Validate game finished time is null
           row, err := txn.ReadRow(ctx, "games", spanner.Key{g.GameUUID}, []string{"finished"})
           if err != nil {
               return err
           }

           if err := row.Column(0, &g.Finished); err != nil {
               return err
           }

           // If time is not null, then the game is already marked as finished. 
           // That's an error.
           if !g.Finished.IsNull() {
               errorMsg := fmt.Sprintf("Game '%s' is already finished.", g.GameUUID)
               return errors.New(errorMsg)
           }

           cols := []string{"gameUUID", "finished", "winner"}
           txn.BufferWrite([]*spanner.Mutation{
               spanner.Update("games", cols, []interface{}{g.GameUUID, time.Now(), g.Winner}),
           })

           // Update each player to increment stats.games_played 
           // (and stats.games_won if winner), and set current_game 
           // to null so they can be chosen for a new game
           playerErr := g.updateGamePlayers(ctx, players, txn)
           if playerErr != nil {
               return playerErr
           }

           return nil
       })

   if err != nil {
       return err
   }

   return nil
}

পরিষেবার জন্য ./src/golang/matchmaking-service/config/config.go- তে বর্ণিত পরিবেশের ভেরিয়েবলের মাধ্যমে কনফিগারেশন আবার পরিচালনা করা হয়।

   // Server defaults
   viper.SetDefault("server.host", "localhost")
   viper.SetDefault("server.port", 8081)

   // Bind environment variable override
   viper.BindEnv("server.host", "SERVICE_HOST")
   viper.BindEnv("server.port", "SERVICE_PORT")
   viper.BindEnv("spanner.project_id", "SPANNER_PROJECT_ID")
   viper.BindEnv("spanner.instance_id", "SPANNER_INSTANCE_ID")
   viper.BindEnv("spanner.database_id", "SPANNER_DATABASE_ID")

প্রোফাইল-পরিষেবার সাথে দ্বন্দ্ব এড়াতে, এই পরিষেবাটি স্থানীয় হোস্ট: 8081- এ ডিফল্টরূপে চলে।

এই তথ্য দিয়ে, এখন ম্যাচমেকিং পরিষেবা চালানোর সময় এসেছে।

ম্যাচ মেকিং সার্ভিস চালান

Go কমান্ড ব্যবহার করে পরিষেবাটি চালান। এটি পোর্ট 8082-এ চলমান পরিষেবা স্থাপন করবে৷ এই পরিষেবাটির প্রোফাইল-পরিষেবার মতো একই নির্ভরতা রয়েছে, তাই নতুন নির্ভরতাগুলি ডাউনলোড করা হবে না৷

cd ~/spanner-gaming-sample/src/golang/matchmaking-service
go run . &

কমান্ড আউটপুট:

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /games/create             --> main.createGame (4 handlers)
[GIN-debug] PUT    /games/close              --> main.closeGame (4 handlers)
[GIN-debug] Listening and serving HTTP on localhost:8081

একটি খেলা তৈরি করুন

একটি গেম তৈরি করতে পরিষেবাটি পরীক্ষা করুন। প্রথমে, ক্লাউড শেলে একটি নতুন টার্মিনাল খুলুন:

90eceac76a6bb90b.png

তারপর, নিম্নলিখিত কার্ল কমান্ড ইস্যু করুন:

curl http://localhost:8081/games/create \
    --include \
    --header "Content-Type: application/json" \
    --request "POST"

কমান্ড আউটপুট:

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: <date> 19:38:45 GMT
Content-Length: 38

"f45b0f7f-405b-4e67-a3b8-a624e990285d"

খেলা বন্ধ করুন

curl http://localhost:8081/games/close \
    --include \
    --header "Content-Type: application/json" \
    --data '{"gameUUID": "f45b0f7f-405b-4e67-a3b8-a624e990285d"}' \
    --request "PUT"

কমান্ড আউটপুট:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: <date> 19:43:58 GMT
Content-Length: 38

"506a1ab6-ee5b-4882-9bb1-ef9159a72989"

সারাংশ

এই ধাপে, আপনি গেম তৈরি করা এবং সেই গেমটিতে খেলোয়াড়দের বরাদ্দ করার জন্য ম্যাচমেকিং-পরিষেবা স্থাপন করেছেন। এই পরিষেবাটি একটি গেম বন্ধ করাও পরিচালনা করে, যা একটি এলোমেলো বিজয়ী বাছাই করে এবং গেম_প্লেড এবং গেমস_ওয়ানের জন্য সমস্ত গেম খেলোয়াড়ের পরিসংখ্যান আপডেট করে।

পরবর্তী পদক্ষেপ

এখন যেহেতু আপনার পরিষেবাগুলি চলছে, এখন খেলোয়াড়দের সাইন আপ করার এবং গেম খেলার সময়!

6. খেলা শুরু করুন

এখন যেহেতু প্রোফাইল এবং ম্যাচমেকিং পরিষেবাগুলি চলছে, আপনি প্রদত্ত পঙ্গপাল জেনারেটর ব্যবহার করে লোড তৈরি করতে পারেন৷

পঙ্গপাল জেনারেটর চালানোর জন্য একটি ওয়েব-ইন্টারফেস অফার করে, কিন্তু এই ল্যাবে আপনি কমান্ড লাইন ( -হেডলেস বিকল্প) ব্যবহার করবেন।

খেলোয়াড়দের সাইন আপ করুন

প্রথমত, আপনি খেলোয়াড় তৈরি করতে চাইবেন।

./generators/authentication_server.py ফাইলে প্লেয়ার তৈরি করার জন্য পাইথন কোডটি দেখতে এইরকম:

class PlayerLoad(HttpUser):
   def on_start(self):
       global pUUIDs
       pUUIDs = []

   def generatePlayerName(self):
       return ''.join(random.choices(string.ascii_lowercase + string.digits, k=32))

   def generatePassword(self):
       return ''.join(random.choices(string.ascii_lowercase + string.digits, k=32))

   def generateEmail(self):
       return ''.join(random.choices(string.ascii_lowercase + string.digits, k=32) + ['@'] +
           random.choices(['gmail', 'yahoo', 'microsoft']) + ['.com'])

   @task
   def createPlayer(self):
       headers = {"Content-Type": "application/json"}
       data = {"player_name": self.generatePlayerName(), "email": self.generateEmail(), "password": self.generatePassword()}

       with self.client.post("/players", data=json.dumps(data), headers=headers, catch_response=True) as response:
           try:
               pUUIDs.append(response.json())
           except json.JSONDecodeError:
               response.failure("Response could not be decoded as JSON")
           except KeyError:
               response.failure("Response did not contain expected key 'gameUUID'")

প্লেয়ারের নাম, ইমেল এবং পাসওয়ার্ড এলোমেলোভাবে তৈরি করা হয়।

সফলভাবে সাইন আপ করা খেলোয়াড়দের রিড লোড জেনারেট করার জন্য একটি দ্বিতীয় টাস্ক দ্বারা পুনরুদ্ধার করা হবে।

   @task(5)
   def getPlayer(self):
       # No player UUIDs are in memory, reschedule task to run again later.
       if len(pUUIDs) == 0:
           raise RescheduleTask()

       # Get first player in our list, removing it to avoid contention from concurrent requests
       pUUID = pUUIDs[0]
       del pUUIDs[0]

       headers = {"Content-Type": "application/json"}

       self.client.get(f"/players/{pUUID}", headers=headers, name="/players/[playerUUID]")

নিম্নলিখিত কমান্ডটি ./generators/authentication_server.py ফাইলটিকে কল করে যা 30s ( t=30s) এর জন্য একটি সময়ে দুটি থ্রেডের একযোগে নতুন প্লেয়ার তৈরি করবে (u=2):

cd ~/spanner-gaming-sample
locust -H http://127.0.0.1:8080 -f ./generators/authentication_server.py --headless -u=2 -r=2 -t=30s

খেলোয়াড়রা গেমে যোগ দেয়

এখন আপনি খেলোয়াড়দের সাইন আপ করেছেন, তারা গেম খেলা শুরু করতে চায়!

./generators/match_server.py ফাইলে গেম তৈরি এবং বন্ধ করার জন্য পাইথন কোডটি এইরকম দেখাচ্ছে:

from locust import HttpUser, task
from locust.exception import RescheduleTask

import json

class GameMatch(HttpUser):
   def on_start(self):
       global openGames
       openGames = []

   @task(2)
   def createGame(self):
       headers = {"Content-Type": "application/json"}

       # Create the game, then store the response in memory of list of open games.
       with self.client.post("/games/create", headers=headers, catch_response=True) as response:
           try:
               openGames.append({"gameUUID": response.json()})
           except json.JSONDecodeError:
               response.failure("Response could not be decoded as JSON")
           except KeyError:
               response.failure("Response did not contain expected key 'gameUUID'")


   @task
   def closeGame(self):
       # No open games are in memory, reschedule task to run again later.
       if len(openGames) == 0:
           raise RescheduleTask()

       headers = {"Content-Type": "application/json"}

       # Close the first open game in our list, removing it to avoid 
       # contention from concurrent requests
       game = openGames[0]
       del openGames[0]

       data = {"gameUUID": game["gameUUID"]}
       self.client.put("/games/close", data=json.dumps(data), headers=headers)

যখন এই জেনারেটর চালানো হয়, এটি 2:1 অনুপাতে গেম খুলবে এবং বন্ধ করবে (খোলা: বন্ধ)। এই কমান্ডটি 10 ​​সেকেন্ডের জন্য জেনারেটর চালাবে (-t=10s) :

locust -H http://127.0.0.1:8081 -f ./generators/match_server.py --headless -u=1 -r=1 -t=10s

সারাংশ

এই ধাপে, আপনি গেম খেলতে সাইন আপ করা খেলোয়াড়দের সিমুলেট করেছেন এবং তারপর ম্যাচমেকিং পরিষেবা ব্যবহার করে খেলোয়াড়দের গেম খেলার জন্য সিমুলেশন চালিয়েছেন। এই সিমুলেশনগুলি আমাদের পরিষেবার REST এপিআই-তে অনুরোধ জারি করার জন্য Locust Python ফ্রেমওয়ার্কের সাহায্য করেছে।

প্লেয়ার তৈরি এবং গেম খেলতে ব্যয় করা সময়, সেইসাথে সমসাময়িক ব্যবহারকারীর সংখ্যা ( -u) পরিবর্তন করতে নির্দ্বিধায়।

পরবর্তী পদক্ষেপ

সিমুলেশনের পরে, আপনি স্প্যানারকে জিজ্ঞাসা করে বিভিন্ন পরিসংখ্যান পরীক্ষা করতে চাইবেন।

7. গেমের পরিসংখ্যান পুনরুদ্ধার করুন

এখন যেহেতু আমরা সিমুলেটেড খেলোয়াড়দের সাইন আপ করতে এবং গেম খেলতে সক্ষম হয়েছি, আপনার পরিসংখ্যান পরীক্ষা করা উচিত।

এটি করতে, স্প্যানারকে ক্যোয়ারী অনুরোধ ইস্যু করতে ক্লাউড কনসোল ব্যবহার করুন।

b5e3154c6f7cb0cf.png

খোলা বনাম বন্ধ গেম চেক করা হচ্ছে

একটি ক্লোজড গেম হল এমন একটি যেটির সমাপ্ত টাইমস্ট্যাম্পটি পপুলেটেড থাকে, যখন একটি খোলা গেমটি NULL হয়ে শেষ হয়ে যায় । খেলা বন্ধ হলে এই মান সেট করা হয়।

সুতরাং এই ক্যোয়ারীটি আপনাকে পরীক্ষা করবে যে কতগুলি গেম খোলা আছে এবং কতগুলি বন্ধ রয়েছে:

SELECT Type, NumGames FROM
(SELECT "Open Games" as Type, count(*) as NumGames FROM games WHERE finished IS NULL
UNION ALL
SELECT "Closed Games" as Type, count(*) as NumGames FROM games WHERE finished IS NOT NULL
)

ফলাফল:

Type

NumGames

Open Games

0

Closed Games

175

খেলোয়াড়দের খেলা বনাম না খেলার পরিমাণ পরীক্ষা করা হচ্ছে

একজন খেলোয়াড় একটি গেম খেলছে যদি তাদের বর্তমান_গেম কলাম সেট করা থাকে। অন্যথায়, তারা বর্তমানে একটি খেলা খেলছে না.

তাই বর্তমানে কতজন খেলোয়াড় খেলছেন এবং খেলছেন না তা তুলনা করতে, এই প্রশ্নটি ব্যবহার করুন:

SELECT Type, NumPlayers FROM
(SELECT "Playing" as Type, count(*) as NumPlayers FROM players WHERE current_game IS NOT NULL
UNION ALL
SELECT "Not Playing" as Type, count(*) as NumPlayers FROM players WHERE current_game IS NULL
)

ফলাফল:

Type

NumPlayers

Playing

0

Not Playing

310

শীর্ষ বিজয়ীদের নির্ধারণ করুন

যখন একটি খেলা বন্ধ হয়, খেলোয়াড়দের মধ্যে একজনকে এলোমেলোভাবে বিজয়ী হওয়ার জন্য নির্বাচিত করা হয়। গেমটি বন্ধ করার সময় সেই খেলোয়াড়ের গেমস-জয় পরিসংখ্যান বৃদ্ধি পায়।

SELECT playerUUID, stats
FROM players
WHERE CAST(JSON_VALUE(stats, "$.games_won") AS INT64)>0
LIMIT 10;

ফলাফল:

প্লেয়ার ইউইউআইডি

পরিসংখ্যান

07e247c5-f88e-4bca-a7bc-12d2485f2f2b

{"games_played":49,"games_won":1}

09b72595-40af-4406-a000-2fb56c58fe92

{"games_played":56,"games_won":1}

1002385b-02a0-462b-a8e7-05c9b27223aa

{"games_played":66,"games_won":1}

13ec3770-7ae3-495f-9b53-6322d8e8d6c3

{"games_played":44,"games_won":1}

15513852-3f2a-494f-b437-fe7125d15f1b

{"games_played":49,"games_won":1}

17faec64-4f77-475c-8df8-6ab026cf6698

{"games_played":50,"games_won":1}

1abfcb27-037d-446d-bb7a-b5cd17b5733d

{"games_played":63,"games_won":1}

2109a33e-88bd-4e74-a35c-a7914d9e3bde

{"games_played":56,"games_won":2}

222e37d9-06b0-4674-865d-a0e5fb80121e

{"games_played":60,"games_won":1}

22ced15c-0da6-4fd9-8cb2-1ffd233b3c56

{"games_played":50,"games_won":1}

সারাংশ

এই ধাপে, আপনি স্প্যানারকে জিজ্ঞাসা করতে ক্লাউড কনসোল ব্যবহার করে খেলোয়াড় এবং গেমের বিভিন্ন পরিসংখ্যান পর্যালোচনা করেছেন।

পরবর্তী পদক্ষেপ

পরবর্তী, এটা পরিষ্কার করার সময়!

8. পরিষ্কার করা (ঐচ্ছিক)

পরিষ্কার করতে, শুধু ক্লাউড কনসোলের ক্লাউড স্প্যানার বিভাগে যান এবং "ক্লাউড স্প্যানার ইনস্ট্যান্স সেটআপ করুন" নামে কোডল্যাব ধাপে তৈরি করা 'ক্লাউডস্প্যানার-গেমিং' উদাহরণটি মুছুন।

9. অভিনন্দন!

অভিনন্দন, আপনি সফলভাবে স্প্যানারে একটি নমুনা গেম স্থাপন করেছেন৷

এরপর কি?

এই ল্যাবে, আপনাকে গোলং ড্রাইভার ব্যবহার করে স্প্যানারের সাথে কাজ করার বিভিন্ন বিষয়ের সাথে পরিচয় করিয়ে দেওয়া হয়েছে। এটি আপনাকে সমালোচনামূলক ধারণাগুলি বোঝার জন্য একটি ভাল ভিত্তি দিতে হবে যেমন:

  • স্কিমা ডিজাইন
  • ডিএমএল বনাম মিউটেশন
  • গোলং এর সাথে কাজ করছি

আপনার গেমের ব্যাকএন্ড হিসাবে স্প্যানারের সাথে কাজ করার আরেকটি উদাহরণের জন্য ক্লাউড স্প্যানার গেম ট্রেডিং পোস্ট কোডল্যাবটি দেখতে ভুলবেন না!