1. ভূমিকা
ক্লাউড স্প্যানার একটি সম্পূর্ণরূপে পরিচালিত অনুভূমিকভাবে স্কেলেবল, বিশ্বব্যাপী বিতরণযোগ্য, রিলেশনাল ডাটাবেস পরিষেবা যা কর্মক্ষমতা এবং উচ্চ প্রাপ্যতা ত্যাগ না করেই ACID লেনদেন এবং SQL শব্দার্থবিদ্যা প্রদান করে।
এই বৈশিষ্ট্যগুলি স্প্যানারকে এমন গেমগুলির আর্কিটেকচারে দুর্দান্তভাবে ফিট করে তোলে যারা বিশ্বব্যাপী খেলোয়াড় বেস সক্ষম করতে চায় বা ডেটা সামঞ্জস্য নিয়ে উদ্বিগ্ন।
এই ল্যাবে, আপনি দুটি Go পরিষেবা তৈরি করবেন যা একটি আঞ্চলিক স্প্যানার ডাটাবেসের সাথে ইন্টারঅ্যাক্ট করবে যাতে খেলোয়াড়রা সাইন আপ করতে এবং খেলা শুরু করতে পারে।

এরপর আপনি পাইথন লোড ফ্রেমওয়ার্ক Locust.io ব্যবহার করে ডেটা তৈরি করবেন যাতে খেলোয়াড়রা সাইন আপ করে গেমটি খেলছে এবং খেলছে তা অনুকরণ করতে পারেন। এবং তারপরে আপনি স্প্যানারকে জিজ্ঞাসা করবেন কতজন খেলোয়াড় খেলছে তা নির্ধারণ করতে এবং খেলোয়াড়দের জিতে যাওয়া বনাম খেলা গেম সম্পর্কে কিছু পরিসংখ্যান জানতে।
অবশেষে, আপনি এই ল্যাবে তৈরি করা সম্পদগুলি পরিষ্কার করবেন।
তুমি কী তৈরি করবে
এই ল্যাবের অংশ হিসেবে, আপনি:
- একটি স্প্যানার ইনস্ট্যান্স তৈরি করুন
- প্লেয়ার সাইনআপ পরিচালনা করতে Go লেখা একটি প্রোফাইল পরিষেবা স্থাপন করুন।
- গেমগুলিতে খেলোয়াড়দের নিয়োগ করতে, বিজয়ী নির্ধারণ করতে এবং খেলোয়াড়দের গেমের পরিসংখ্যান আপডেট করতে Go তে লেখা একটি ম্যাচমেকিং পরিষেবা স্থাপন করুন।
তুমি কি শিখবে
- ক্লাউড স্প্যানার ইনস্ট্যান্স কীভাবে সেটআপ করবেন
- কিভাবে একটি গেম ডাটাবেস এবং স্কিমা তৈরি করবেন
- ক্লাউড স্প্যানারের সাথে কাজ করার জন্য Go অ্যাপগুলি কীভাবে স্থাপন করবেন
- Locust ব্যবহার করে কীভাবে ডেটা তৈরি করবেন
- গেম এবং খেলোয়াড়দের সম্পর্কে প্রশ্নের উত্তর দেওয়ার জন্য ক্লাউড স্প্যানারে ডেটা কীভাবে অনুসন্ধান করবেন।
তোমার যা লাগবে
- একটি Google ক্লাউড প্রকল্প যা একটি বিলিং অ্যাকাউন্টের সাথে সংযুক্ত।
- একটি ওয়েব ব্রাউজার, যেমন ক্রোম বা ফায়ারফক্স ।
2. সেটআপ এবং প্রয়োজনীয়তা
একটি প্রকল্প তৈরি করুন
যদি আপনার ইতিমধ্যেই একটি Google অ্যাকাউন্ট (Gmail বা Google Apps) না থাকে, তাহলে আপনাকে অবশ্যই একটি তৈরি করতে হবে। Google Cloud Platform কনসোলে ( console.cloud.google.com ) সাইন-ইন করুন এবং একটি নতুন প্রকল্প তৈরি করুন।
যদি আপনার ইতিমধ্যেই একটি প্রকল্প থাকে, তাহলে কনসোলের উপরের বাম দিকে প্রকল্প নির্বাচন পুল ডাউন মেনুতে ক্লিক করুন:

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

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

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

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

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


একবার ক্লাউড শেলের সাথে সংযুক্ত হয়ে গেলে, আপনি দেখতে পাবেন যে আপনি ইতিমধ্যেই প্রমাণিত এবং প্রকল্পটি ইতিমধ্যেই আপনার 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 খুঁজছেন? সেটআপ ধাপে আপনি কোন আইডি ব্যবহার করেছেন তা দেখুন অথবা ক্লাউড কনসোল ড্যাশবোর্ডে এটি দেখুন:

ক্লাউড শেল ডিফল্টরূপে কিছু পরিবেশ ভেরিয়েবল সেট করে, যা ভবিষ্যতের কমান্ড চালানোর সময় কার্যকর হতে পারে।
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 : গেম তৈরি এবং গেম বন্ধ করার কাজগুলি অন্তর্ভুক্ত করে। গেম তৈরি করলে ১০০ জন র্যান্ডম খেলোয়াড় নিয়োগ করা হবে যারা বর্তমানে গেম খেলছে না। গেম বন্ধ করলে গেম_প্লেড এবং গেম_ওন পরিসংখ্যান আপডেট হবে এবং সেই খেলোয়াড়দের ভবিষ্যতের গেমে নিয়োগ করার অনুমতি দেওয়া হবে।
ক্লাউড শেলে Locust চালানোর জন্য, আপনার Python 3.7 বা তার উচ্চতর সংস্করণের প্রয়োজন হবে। ক্লাউড শেল Python 3.9 এর সাথে আসে, তাই সংস্করণটি যাচাই করা ছাড়া আর কিছুই করার নেই:
python -V
কমান্ড আউটপুট
Python 3.9.12
এখন, আপনি Locust-এর জন্য প্রয়োজনীয়তাগুলি ইনস্টল করতে পারেন।
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
সারাংশ
এই ধাপে আপনি আপনার প্রকল্পটি সেট আপ করেছেন, যদি আপনার ইতিমধ্যেই একটি না থাকে, তাহলে ক্লাউড শেল সক্রিয় করেছেন এবং এই ল্যাবের কোড ডাউনলোড করেছেন।
অবশেষে, আপনি ল্যাবে পরে লোড জেনারেশনের জন্য Locust সেট আপ করেন।
পরবর্তী
এরপর, আপনি ক্লাউড স্প্যানার ইনস্ট্যান্স এবং ডাটাবেস সেট আপ করবেন।
৩. একটি স্প্যানার ইনস্ট্যান্স এবং ডাটাবেস তৈরি করুন
স্প্যানার ইনস্ট্যান্স তৈরি করুন
এই ধাপে আমরা কোডল্যাবের জন্য আমাদের স্প্যানার ইন্সট্যান্স সেট আপ করি। স্প্যানার এন্ট্রিটি অনুসন্ধান করুন।
বাম দিকের উপরের হ্যামবার্গার মেনুতে
অথবা "/" টিপে "Spanner" টাইপ করে Spanner অনুসন্ধান করুন।

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

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

খেলোয়াড়রা সময়ের সাথে সাথে অনেক খেলায় অংশগ্রহণ করতে পারে, কিন্তু একবারে কেবল একটি খেলায়। খেলোয়াড়দের কাছে JSON ডেটা টাইপ হিসেবে stats থাকে যাতে games_played এবং games_won এর মতো আকর্ষণীয় পরিসংখ্যান ট্র্যাক করা যায়। যেহেতু অন্যান্য পরিসংখ্যান পরে যোগ করা হতে পারে, এটি কার্যকরভাবে খেলোয়াড়দের জন্য একটি স্কিমালেস কলাম।
গেমগুলি স্প্যানারের ARRAY ডেটা টাইপ ব্যবহার করে অংশগ্রহণকারী খেলোয়াড়দের ট্র্যাক রাখে। গেমটি শেষ না হওয়া পর্যন্ত কোনও গেমের বিজয়ী এবং সমাপ্ত বৈশিষ্ট্যগুলি পূরণ করা হয় না।
খেলোয়াড়ের current_game একটি বৈধ খেলা কিনা তা নিশ্চিত করার জন্য একটি বিদেশী কী আছে।
এখন ইনস্ট্যান্স ওভারভিউতে 'Create Database' এ ক্লিক করে ডাটাবেস তৈরি করুন:

এবং তারপর বিস্তারিত তথ্য পূরণ করুন। গুরুত্বপূর্ণ বিকল্পগুলি হল ডাটাবেসের নাম এবং উপভাষা। এই উদাহরণে, আমরা ডাটাবেসের নমুনা-খেলার নামকরণ করেছি এবং গুগল স্ট্যান্ডার্ড SQL উপভাষাটি বেছে নিয়েছি।
স্কিমার জন্য, এই DDL টি কপি করে বক্সে পেস্ট করুন:
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);
তারপর, তৈরি করুন বোতামে ক্লিক করুন এবং আপনার ডাটাবেস তৈরি হওয়ার জন্য কয়েক সেকেন্ড অপেক্ষা করুন।
ডাটাবেস তৈরির পৃষ্ঠাটি দেখতে এরকম হওয়া উচিত:

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

export SPANNER_PROJECT_ID=$GOOGLE_CLOUD_PROJECT
export SPANNER_INSTANCE_ID=cloudspanner-gaming
export SPANNER_DATABASE_ID=sample-game
সারাংশ
এই ধাপে আপনি একটি স্প্যানার ইনস্ট্যান্স এবং স্যাম্পল-গেম ডাটাবেস তৈরি করেছেন। আপনি এই স্যাম্পল গেমটি যে স্কিমা ব্যবহার করে তাও সংজ্ঞায়িত করেছেন।
পরবর্তী
এরপর, আপনি প্রোফাইল পরিষেবাটি স্থাপন করবেন যাতে খেলোয়াড়রা গেমটি খেলতে সাইন আপ করতে পারে!
৪. প্রোফাইল পরিষেবা স্থাপন করুন
পরিষেবার ওভারভিউ
প্রোফাইল পরিষেবাটি Go তে লেখা একটি REST API যা জিন ফ্রেমওয়ার্ককে কাজে লাগায়।

এই API-তে, খেলোয়াড়রা গেম খেলতে সাইন আপ করতে পারে। এটি একটি সাধারণ POST কমান্ড দ্বারা তৈরি করা হয় যা খেলোয়াড়ের নাম, ইমেল এবং পাসওয়ার্ড গ্রহণ করে। পাসওয়ার্ডটি bcrypt দিয়ে এনক্রিপ্ট করা হয় এবং হ্যাশটি ডাটাবেসে সংরক্ষণ করা হয়।
ইমেলটি একটি অনন্য শনাক্তকারী হিসেবে বিবেচিত হয়, যেখানে প্লেয়ার_নামটি গেমের প্রদর্শনের উদ্দেশ্যে ব্যবহৃত হয়।
এই 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-এর উপর ভিত্তি করে তাদের পুনরুদ্ধার করতে, একটি সহজ পঠন জারি করা হয়। এটি প্লেয়ারের playerUUID, player_name, email, এবং পরিসংখ্যান পুনরুদ্ধার করে।
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
}
আপনি দেখতে পাচ্ছেন যে ডিফল্ট আচরণ হল localhost: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 কমান্ড জারি করে পরিষেবাটি পরীক্ষা করুন:
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 কল জারি করে পরিষেবাটি পরীক্ষা করেছেন।
পরবর্তী পদক্ষেপ
পরবর্তী ধাপে, আপনি ম্যাচ-মেকিং পরিষেবাটি স্থাপন করবেন।
৫. ম্যাচ-মেকিং পরিষেবা স্থাপন করুন
পরিষেবার ওভারভিউ
ম্যাচ-মেকিং পরিষেবাটি Go ভাষায় লেখা একটি REST API যা জিন ফ্রেমওয়ার্ককে কাজে লাগায়।

এই API-তে, গেম তৈরি এবং বন্ধ করা হয়। যখন একটি গেম তৈরি করা হয়, তখন 10 জন খেলোয়াড় যারা বর্তমানে একটি গেম খেলছেন না তাদের গেমটিতে নিযুক্ত করা হয়।
যখন একটি খেলা বন্ধ থাকে, তখন এলোমেলোভাবে একজন বিজয়ী নির্বাচন করা হয় এবং প্রতিটি খেলোয়াড়ের games_played এবং games_won এর পরিসংখ্যান সমন্বয় করা হয়। এছাড়াও, প্রতিটি খেলোয়াড়কে আপডেট করা হয় যাতে বোঝা যায় যে তারা আর খেলছে না এবং ভবিষ্যতের খেলাগুলি খেলার জন্য উপলব্ধ।
ম্যাচমেকিং পরিষেবার জন্য ./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"`
}
একটি গেম তৈরি করার জন্য, ম্যাচমেকিং পরিষেবাটি এমন ১০০ জন খেলোয়াড়ের একটি এলোমেলো নির্বাচন সংগ্রহ করে যারা বর্তমানে কোনও গেম খেলছে না।
গেমটি তৈরি এবং খেলোয়াড়দের নিয়োগের জন্য স্প্যানার মিউটেশন বেছে নেওয়া হয়, কারণ বড় পরিবর্তনের জন্য মিউটেশনগুলি 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 এর TABLESPACE RESERVOIR ক্ষমতা ব্যবহার করে SQL দিয়ে প্লেয়ারদের এলোমেলো নির্বাচন করা হয়।
একটি খেলা বন্ধ করা একটু জটিল। এর মধ্যে রয়েছে খেলার খেলোয়াড়দের মধ্যে থেকে একজন এলোমেলো বিজয়ী নির্বাচন করা, খেলা শেষ হওয়ার সময় চিহ্নিত করা এবং games_played এবং games_won এর জন্য প্রতিটি খেলোয়াড়ের পরিসংখ্যান আপডেট করা।
এই জটিলতা এবং পরিবর্তনের পরিমাণের কারণে, খেলাটি বন্ধ করার জন্য আবার মিউটেশন বেছে নেওয়া হয়।
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")
প্রোফাইল-সার্ভিসের সাথে দ্বন্দ্ব এড়াতে, এই পরিষেবাটি ডিফল্টরূপে localhost: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
একটি খেলা তৈরি করুন
একটি গেম তৈরি করতে পরিষেবাটি পরীক্ষা করুন। প্রথমে, ক্লাউড শেলে একটি নতুন টার্মিনাল খুলুন:

তারপর, নিম্নলিখিত curl কমান্ডটি জারি করুন:
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"
সারাংশ
এই ধাপে, আপনি গেম তৈরি এবং সেই গেমে খেলোয়াড়দের নিয়োগের জন্য ম্যাচমেকিং-সার্ভিস স্থাপন করেছেন। এই পরিষেবাটি একটি গেম বন্ধ করার কাজও পরিচালনা করে, যা একজন র্যান্ডম বিজয়ী নির্বাচন করে এবং games_played এবং games_won এর জন্য সমস্ত গেম খেলোয়াড়ের পরিসংখ্যান আপডেট করে।
পরবর্তী পদক্ষেপ
এখন যেহেতু আপনার পরিষেবাগুলি চালু আছে, খেলোয়াড়দের সাইন আপ করার এবং গেম খেলার সময় এসেছে!
৬. খেলা শুরু করুন
এখন যেহেতু প্রোফাইল এবং ম্যাচমেকিং পরিষেবাগুলি চলছে, আপনি প্রদত্ত পঙ্গপাল জেনারেটর ব্যবহার করে লোড তৈরি করতে পারেন।
জেনারেটর চালানোর জন্য Locust একটি ওয়েব-ইন্টারফেস অফার করে, কিন্তু এই ল্যাবে আপনি কমান্ড লাইন ( –হেডলেস বিকল্প) ব্যবহার করবেন।
খেলোয়াড়দের সাইন আপ করুন
প্রথমত, আপনি খেলোয়াড় তৈরি করতে চাইবেন।
./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 API-তে অনুরোধ জারি করার জন্য Locust Python ফ্রেমওয়ার্ককে কাজে লাগিয়েছে।
খেলোয়াড় তৈরি এবং গেম খেলার জন্য ব্যয় করা সময়, সেইসাথে সমসাময়িক ব্যবহারকারীর সংখ্যা ( -u) পরিবর্তন করতে দ্বিধা করবেন না।
পরবর্তী পদক্ষেপ
সিমুলেশনের পরে, আপনি স্প্যানারকে জিজ্ঞাসা করে বিভিন্ন পরিসংখ্যান পরীক্ষা করতে চাইবেন।
৭. খেলার পরিসংখ্যান পুনরুদ্ধার করুন
এখন যেহেতু আমরা এমন খেলোয়াড়দের তৈরি করেছি যারা সাইন আপ করতে এবং গেম খেলতে সক্ষম, আপনার পরিসংখ্যান পরীক্ষা করা উচিত।
এটি করার জন্য, স্প্যানারকে কোয়েরি অনুরোধ ইস্যু করতে ক্লাউড কনসোল ব্যবহার করুন।

খোলা বনাম বন্ধ খেলা পরীক্ষা করা হচ্ছে
একটি বন্ধ খেলা হল এমন একটি খেলা যাতে সমাপ্ত টাইমস্ট্যাম্পটি পূরণ করা থাকে, যখন একটি খোলা খেলাটি 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
)
ফলাফল:
| |
| |
| |
খেলছেন বনাম খেলছেন না এমন খেলোয়াড়ের সংখ্যা পরীক্ষা করা
একজন খেলোয়াড় যদি তাদের current_game কলাম সেট করা থাকে তাহলে সে একটি খেলা খেলছে। অন্যথায়, সে বর্তমানে কোনও খেলা খেলছে না।
তাই বর্তমানে কতজন খেলোয়াড় খেলছে আর কতজন খেলছে না তার তুলনা করতে, এই কোয়েরিটি ব্যবহার করুন:
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
)
ফলাফল:
| |
| |
| |
শীর্ষ বিজয়ীদের নির্ধারণ করুন
যখন একটি খেলা বন্ধ করা হয়, তখন খেলোয়াড়দের মধ্যে থেকে একজনকে এলোমেলোভাবে বিজয়ী হিসেবে নির্বাচিত করা হয়। খেলা বন্ধ করার সময় সেই খেলোয়াড়ের games_won পরিসংখ্যান বৃদ্ধি করা হয়।
SELECT playerUUID, stats
FROM players
WHERE CAST(JSON_VALUE(stats, "$.games_won") AS INT64)>0
LIMIT 10;
ফলাফল:
প্লেয়ারইউইউআইডি | পরিসংখ্যান |
07e247c5-f88e-4bca-a7bc-12d2485f2f2b | {"গেমস_খেলেছে":49,"গেমস_জিতেছে":1} |
০৯বি৭২৫৯৫-৪০এএফ-৪৪০৬-এ০০০-২এফবি৫৬সি৫৮এফই৯২ | {"গেমস_খেলেছে":56,"গেমস_জিতেছে":1} |
১০০২৩৮৫বি-০২এ০-৪৬২বি-এ৮ই৭-০৫সি৯বি২৭২২৩এএ | {"গেমস_খেলেছে":66,"গেমস_জিতেছে":1} |
13ec3770-7ae3-495f-9b53-6322d8e8d6c3 | {"গেমস_খেলেছে":44,"গেমস_জিতেছে":1} |
15513852-3f2a-494f-b437-fe7125d15f1b | {"গেমস_খেলেছে":49,"গেমস_জিতেছে":1} |
১৭faec64-4f77-475c-8df8-6ab026cf6698 | {"গেমস_খেলেছে":৫০,"গেমস_জিতেছে":১} |
1abfcb27-037d-446d-bb7a-b5cd17b5733d | {"গেমস_খেলেছে":63,"গেমস_জিতেছে":1} |
2109a33e-88bd-4e74-a35c-a7914d9e3bde | {"গেমস_খেলেছে":56,"গেমস_জিতেছে":2} |
222e37d9-06b0-4674-865d-a0e5fb80121e | {"গেমস_খেলেছে":60,"গেমস_জিতেছে":1} |
22ced15c-0da6-4fd9-8cb2-1ffd233b3c56 | {"গেমস_খেলেছে":৫০,"গেমস_জিতেছে":১} |
সারাংশ
এই ধাপে, আপনি স্প্যানারকে জিজ্ঞাসা করার জন্য ক্লাউড কনসোল ব্যবহার করে খেলোয়াড় এবং গেমের বিভিন্ন পরিসংখ্যান পর্যালোচনা করেছেন।
পরবর্তী পদক্ষেপ
এরপর, পরিষ্কার করার সময়!
৮. পরিষ্কার করা (ঐচ্ছিক)
পরিষ্কার করার জন্য, ক্লাউড কনসোলের ক্লাউড স্প্যানার বিভাগে যান এবং "সেটআপ এ ক্লাউড স্প্যানার ইনস্ট্যান্স" নামক কোডল্যাব ধাপে আমরা যে 'ক্লাউডস্প্যানার-গেমিং' ইনস্ট্যান্সটি তৈরি করেছি তা মুছে ফেলুন।
9. অভিনন্দন!
অভিনন্দন, আপনি স্প্যানারে একটি নমুনা গেম সফলভাবে স্থাপন করেছেন।
এরপর কী?
এই ল্যাবে, গোল্যাং ড্রাইভার ব্যবহার করে স্প্যানারের সাথে কাজ করার বিভিন্ন বিষয়ের সাথে আপনাকে পরিচয় করিয়ে দেওয়া হয়েছে। এটি আপনাকে গুরুত্বপূর্ণ ধারণাগুলি বোঝার জন্য আরও ভাল ভিত্তি দেবে যেমন:
- স্কিমা ডিজাইন
- ডিএমএল বনাম মিউটেশন
- গোলং-এর সাথে কাজ করা
আপনার গেমের ব্যাকএন্ড হিসেবে স্প্যানারের সাথে কাজ করার আরেকটি উদাহরণের জন্য ক্লাউড স্প্যানার গেম ট্রেডিং পোস্ট কোডল্যাবটি অবশ্যই দেখে নিন!