ট্র্যাক
/
x86-64 Assembly
x86-64 Assembly
/
অনুশীলনী
/
ব্ল্যাকজ্যাক
ব্ল্যাকজ্যাক

ব্ল্যাকজ্যাক

লার্নিং অনুশীলনী

ভূমিকা

শর্তাধীন

RFLAGs

rflags নামে একটি বিশেষ রেজিস্টার আছে। এর বিটগুলো বিভিন্ন শর্তের জন্য ফ্ল্যাগের মতো কাজ করে।

এর মধ্যে কয়েকটি নিচে দেওয়া হলো:

name symbol bit
carry CF 0
zero ZF 6
sign SF 7
overflow OF 11

তুলনার ইনস্ট্রাকশন

rflags-এর ফ্ল্যাগগুলো সরাসরি পরিবর্তিত হয় না। বরং এগুলো বিভিন্ন ইনস্ট্রাকশন দ্বারা সেট হয়।

যেমন, অনেক পাটিগণিত বা বিটওয়াইজ অপারেশনের ফলাফল শূন্য হলে ZF সেট হয়।

শর্ত পরীক্ষা করার জন্য ব্যবহৃত সবচেয়ে কমন ইনস্ট্রাকশনগুলোর একটি হলো cmp। এটি দুটি অপারেন্ড নেয় এবং ফ্ল্যাগগুলো আপডেট করে, কিন্তু এর অপারেন্ডগুলো পরিবর্তন করে না।

CMP ইনস্ট্রাকশন

cmp ইনস্ট্রাকশনটি প্রথম অপারেন্ড থেকে দ্বিতীয় অপারেন্ড বিয়োগ করে এবং ফলাফল অনুযায়ী ফ্ল্যাগ সেট করে।

যদি A প্রথম অপারেন্ড হয় এবং B দ্বিতীয়টি হয়:

flag set when
CF A < B (unsigned)
ZF A == B
SF A < B (signed, no overflow)
OF overflow in signed subtraction

ব্রাঞ্চিং

ডিফল্টভাবে, x86-64-এ কোড উপর থেকে নিচে ক্রমানুসারে এক্সিকিউট হয়।

কিন্তু এমন অনেক পরিস্থিতি আছে যেখানে এই আচরণ পরিবর্তন করা প্রয়োজন। যেমন, কোনো শর্তের প্রতিক্রিয়ায় ভিন্ন একটি ইনস্ট্রাকশন সেট এক্সিকিউট করা।

উচ্চ-স্তরের ভাষায় এটি সাধারণত if...else-এর মতো শর্তাধীন অ্যাবস্ট্রাকশন দিয়ে করা হয়। তবে x86-64-এ সেগুলো নেই।

বরং, x86-64 এমন ইনস্ট্রাকশন দেয় যেগুলো কার্যকরভাবে এক্সিকিউশন কোডের অন্য একটি স্থানে স্থানান্তর করে। একে branching বলা হয়।

Note

আমরা ইতিমধ্যেই এমন দুটি ইনস্ট্রাকশন দেখেছি: call এবং ret।

কোনো ফাংশন কল করা হলে, এক্সিকিউশন কলার থেকে কল করা ফাংশনে স্থানান্তরিত হয়। আর ফিরে আসার সময়, এক্সিকিউশন আবার কলারের কাছে স্থানান্তরিত হয়।

কোনো ret না থাকলে, এক্সিকিউশন একটি ফাংশন থেকে পরেরটিতে ফলথ্রু হয়ে যায়। এটি কখনও কখনও কোড ফ্লো অপটিমাইজ করতে ব্যবহার করা যায়।

শর্তহীন জাম্প

jmp ইনস্ট্রাকশনটি শর্তহীনভাবে প্রোগ্রামের এক্সিকিউশন কোডের অন্য একটি বিন্দুতে স্থানান্তর করে। এর একমাত্র অপারেন্ড হলো একটি লেবেল, যেখানে সেই বিন্দুর অ্যাড্রেস থাকে যেখান থেকে এক্সিকিউশন চলবে।

যেমন, নিচের ফাংশনটি দেখুন:

fn:
    mov rax, 5
    jmp end

    add rax, 10
end:
    ret

fn কল করা হলে, এক্সিকিউশন mov rax, 5 থেকে শুরু হয়। এটি সেই মুহূর্তে rax-এর মান 5 করে দেয়।

পরের ইনস্ট্রাকশনটি jmp end, যা এক্সিকিউশনকে end লেবেলে স্থানান্তর করে।

end-এর পরে পরের ইনস্ট্রাকশনটি ret, যা এক্সিকিউশন আবার কলার ফাংশনে ফিরিয়ে দেয়।

লক্ষ্য করুন, add rax, 10 যেহেতু jmp end-এর পরে এবং end-এর আগে অবস্থিত, তাই এটি কখনও এক্সিকিউট হয় না। fn রিটার্ন করার সময় rax-এর মান 5।

শর্তাধীন জাম্প

jcc ইনস্ট্রাকশন পরিবারটি প্রোগ্রামের এক্সিকিউশন অন্য একটি বিন্দুতে স্থানান্তর করে কেবল তখনই, যখন একটি নির্দিষ্ট শর্ত পূরণ হয়। অন্যথায়, এক্সিকিউশন ক্রমানুসারে চলতে থাকে।

প্রতিটি শর্ত rflags-এর এক বা একাধিক ফ্ল্যাগের সাথে মিলে যায়। কিছু jcc ভ্যারিয়েন্ট পরীক্ষা করে যে একটি ফ্ল্যাগ সেট হয়েছে, আবার কিছু পরীক্ষা করে যে তা ক্লিয়ার হয়েছে।

jcc-এর cc আক্ষরিক নয়, বরং পরীক্ষিত ফ্ল্যাগের সাথে সম্পর্কিত নির্দিষ্ট সাফিক্সকে বোঝায়।

অনেক সাফিক্স আছে এবং তাদের অনেকগুলোই অন্য একটির মতো একই শর্ত পরীক্ষা করে। এর মধ্যে কিছু সরাসরি একটি ফ্ল্যাগকে বোঝায়, যাতে নির্দিষ্ট ফ্ল্যাগ সেট থাকলে ইনস্ট্রাকশনটি একটি লেবেলে জাম্প করে:

suffix jumps if
z ZF == 1
c CF == 1
s SF == 1
o OF == 1

আরও অনেক সাফিক্স বেছে নেওয়া হয়েছে cmp ইনস্ট্রাকশনে তাদের অর্থ বোঝাতে। যেমন:

instruction suffix jumps if
cmp A, B e A == B
cmp A, B l A < B (signed)
cmp A, B b A < B (unsigned)
cmp A, B g A > B (signed)
cmp A, B a A > B (unsigned)

l, b, g বা a-এর পরে e যোগ করে শর্তে সমতা অন্তর্ভুক্ত করা সম্ভব:

cmp rcx, r8
jge two      ; this jumps to 'two' if rcx is greater than, or equal to, r8 in a signed comparison
jbe two      ; this jumps to 'two' if rcx is lesser than, or equal to, r8 in an unsigned comparison

সব সাফিক্সের জন্যই এমন ভ্যারিয়েন্ট আছে যেগুলো বিপরীত আচরণ পরীক্ষা করে। এগুলোর সিনট্যাক্স একই, তবে সাফিক্সের আগে একটি n থাকে।

যেমন, ZF সেট না থাকলে jnz জাম্প করে। একইভাবে, A যদি B-এর চেয়ে বড় বা সমান না হয়, তখন jnae জাম্প করে (A ও B-কে আনসাইনড ইন্টিজার হিসেবে ব্যাখ্যা করা হয়)।

Note

কিছু সাফিক্স একই শর্তের অ্যালিয়াস। যেমন, jz (সাফিক্স z, ZF-এর জন্য) এবং je (সাফিক্স e, সমতার জন্য) দুটোই ZF সেট থাকলে জাম্প করে। এর কারণ হলো, cmp-এর ক্ষেত্রে বিয়োগের ফল শূন্য হলে ZF সেট হয়, যা দুটি অপারেন্ড সমান হওয়ার সাথে মিলে যায়।

তবে অন্য কিছু সাফিক্স ফ্ল্যাগের একটি সমন্বয় পরীক্ষা করে এবং একটি একক ফ্ল্যাগ সাফিক্স দিয়ে সরাসরি প্রতিস্থাপন করা যায় না।

আপনার তুলনার সেমান্টিকস ভালোভাবে বর্ণনা করে এমন সাফিক্স বেছে নিন।

লোকাল লেবেল

লেবেলগুলো পুরো সোর্স ফাইলে দৃশ্যমান, এগুলো কোনো ফাংশনের মধ্যে সীমাবদ্ধ নয়। তাই একটি লেবেলের নাম পুনরায় ব্যবহার করা অসম্ভব।

লোকাল লেবেলের আচরণ অনুকরণ করতে, NASM-এ আগে একটি পিরিয়ড (.) দিয়ে ঘোষণা করা লেবেলের জন্য একটি বিশেষ নোটেশন আছে। এই নোটেশনটি এমন একটি লেবেল সংজ্ঞায়িত করে যাতে পূর্বের ডট-ছাড়া লেবেলের নাম অন্তর্ভুক্ত থাকে:

section .text
fn1:
    ...
.example: ; this is fn1.example
    ...
    ret

fn2:
    ...
.example: ; this is fn2.example
    ...
    ret

কোডের যেকোনো জায়গা থেকে পুরো লেবেল নাম ব্যবহার করে এই লেবেলে জাম্প করা এখনও সম্ভব, যেমন jmp fn1.example।

তবে, ডট থেকে শুরু হওয়া লেবেলের অংশ ব্যবহার করে করা জাম্প উপরের ফাংশনের ভেতরের লেবেলে যাবে। যেমন, .example এমনভাবে আচরণ করে যেন এটি ফাংশনের লোকাল:

section .text
fn1:
    ...
.example:
    ...
    jmp .example ; this jumps to fn1.example

fn2:
    ...
.example:
    ...
    jmp .example ; this jumps to fn2.example

নির্দেশনা

এই অনুশীলনীতে আপনি ব্ল্যাকজ্যাক গেমের কিছু নিয়ম ইমপ্লিমেন্ট করবেন, যেমন গেমটি কীভাবে খেলা হয় আর কীভাবে স্কোর করা হয়।

Note

এই অনুশীলনীতে কার্ডগুলোকে সংখ্যা দিয়ে প্রকাশ করা হয়েছে; প্রতিটি সংখ্যা কার্ড তার সাংখ্যিক মান দিয়ে প্রকাশ করা হয়, আর জ্যাক, কুইন, কিং ও এইসকে যথাক্রমে ১১, ১২, ১৩ আর ১৪ দিয়ে চিহ্নিত করা হয় (গেমে জোকার ব্যবহার করা হয় না)।

এই প্রকাশভঙ্গি নিয়ে কাজ করা সহজ করতে ফাইলের শুরুতে কিছু কনস্ট্যান্ট ডিফাইন করা আছে; C2 থেকে C10 সংখ্যা কার্ডের জন্য, CJ জ্যাকের জন্য, CQ কুইনের জন্য, CK কিংয়ের জন্য আর CA এইসের জন্য।

এখানে একটি প্রমিত ফরাসি স্যুটের ৫২-তাসের ডেক ধরে নেওয়া হয়েছে, তবে বেশিরভাগ সংস্করণে খেলার জন্য একসাথে কয়েকটি ডেক মেশানো হয়।

Note

এই কনসেপ্টে উল্লিখিত নির্দেশনাগুলো হলো:

নির্দেশনা বিবরণ
cmp a, b a - b অনুযায়ী ফ্ল্যাগ সেট করে
jmp a কোড এখানে চলা বন্ধ করে লেবেল a-তে গিয়ে চলতে থাকে
jcc a cc-এর শর্ত পূরণ হলে কোড লেবেল a-তে চলতে থাকে

cmp a, b-এর পরে jcc-তে যেসব শর্ত যাচাই করা হয় সেগুলো হলো:

নির্দেশনা কখন জাম্প করে
je a == b
jl a < b (সাইনড)
jg a > b (সাইনড)
jb a < b (আনসাইনড)
ja a > b (আনসাইনড)
jle a <= b (সাইনড)
jge a >= b (আনসাইনড)
jbe a <= b (আনসাইনড)
jae a >= b (আনসাইনড)
jne a != b
jnl !(a < b) (সাইনড)
jng !(a > b) (সাইনড)
jnb !(a < b) (আনসাইনড)
jna !(a > b) (আনসাইনড)
jnle !(a <= b) (সাইনড)
jnge !(a >= b) (সাইনড)
jnbe !(a <= b) (আনসাইনড)
jnae !(a >= b) (আনসাইনড)

1. একটি কার্ডের মান নির্ণয় করুন

ব্ল্যাকজ্যাক-এ একটি CA-এর মান হাতের উপর নির্ভর করে ১ বা ১১ হয় (এ বিষয়ে পরে আরও বলা আছে)। ফেস কার্ডগুলো (CJ, CQ আর CK) ১০ পয়েন্টে গণ্য হয়, আর বাকি যেকোনো কার্ডের মান তার সাংখ্যিক মানের সমান।

card প্যারামিটারসহ value_of_card ফাংশনটি ডিফাইন করুন, যেখানে card হলো একটি কার্ড নির্দেশকারী একটি সংখ্যা। ফাংশনটি অবশ্যই পাস করা কার্ডটির সাংখ্যিক মান রিটার্ন করবে। যেহেতু একটি CA একাধিক মান (১ বা ১১) নিতে পারে, আমরা আপাতত CA-এর মান ১ ধরে নিচ্ছি। পরে আপনি একটি বিদ্যমান হাতের ভিত্তিতে CA-এর মান নির্ণয় করার একটি ফাংশন ইমপ্লিমেন্ট করবেন।

value_of_card(13)
// => 10

value_of_card(4)
// => 4

value_of_card(14)
// => 1

2. কোন কার্ডের মান বেশি তা নির্ধারণ করুন

card_one আর card_two প্যারামিটারসহ higher_card ফাংশনটি ডিফাইন করুন, যেখানে এগুলো দুটি সংখ্যা আর প্রত্যেকে একটি করে কার্ড নির্দেশ করে। স্কোরিংয়ের জন্য CJ, CQ আর CK-এর মান সবই ১০। ফাংশনটি রিটার্ন করবে স্কোরিংয়ের জন্য কোন কার্ডের মান বেশি। দুটি কার্ডের মান সমান হলে দুটোই রিটার্ন করুন।

একটি CA একাধিক মান নিতে পারে, তাই এই কাজে আমরা এর মান ১ ধরে নেব।

higher_card(13, 11)
// => {13, 11}

higher_card(4, 6)
// => 6

>>> higher_card(13, 14)
// => 13
Note

একটি ফাংশন থেকে দুটি ইন্টিজার রিটার্ন করতে হলে rax আর rdx দুটি রেজিস্টারই ব্যবহার করা উচিত:

returning_two_values:
    mov rax, rdi
    mov rdx, rsi
    ret

শুধু একটি কার্ড রিটার্ন করা হলে rdx-কে 0 সেট করতে হবে।

3. একটি এইসের মান নির্ণয় করুন

আগে বলা হয়েছে, হাতের উপর নির্ভর করে একটি CA-এর মান হয় ১ নয়তো ১১ পয়েন্ট। ব্ল্যাকজ্যাক-এর নিয়ম হলো, হাতের স্কোর সর্বোচ্চ করতে CA-এর মান এমনভাবে বাছাই করতে হবে যাতে ২১ ছাড়িয়ে না যায় ("বাস্ট" হয়ে না যায়)।

card_one আর card_two প্যারামিটারসহ value_of_ace ফাংশনটি ডিফাইন করুন; এগুলো দুটি সংখ্যা, যা শেষ CA পাওয়ার আগে হাতে থাকা এক জোড়া কার্ড নির্দেশ করে। আপনার ফাংশনটি রিটার্ন করবে, আসন্ন CA-কে ১ আর ১১-এর মধ্যে কোন মান দেওয়া হবে। মনে রাখবেন: নতুন হাতের (CA সহ) মান যতটা সম্ভব বেশি হতে হবে, কিন্তু ২১ ছাড়িয়ে যাওয়া যাবে না।

ইঙ্গিত: হাতে আগে থেকেই একটি CA থাকলে আসন্ন CA-এর মান হবে ১।

value_of_ace(6, 13)
// => 1

value_of_ace(7, 3)
// => 11

4. হাতটি ব্ল্যাকজ্যাক কি না তা নির্ধারণ করুন

খেলোয়াড় প্রথম দুটি কার্ড হিসেবে একটি CA আর একটি দশ-কার্ড (C10, CJ, CQ বা CK) পেলে তার স্কোর হয় ২১। এই হাতটিকে বলা হয় ব্ল্যাকজ্যাক।

card_one আর card_two প্যারামিটারসহ is_blackjack ফাংশনটি ডিফাইন করুন; এগুলো দুটি সংখ্যা, যা এক জোড়া কার্ড নির্দেশ করে। দুই কার্ডের হাতটি ব্ল্যাকজ্যাক হলে ফাংশনটি ১ রিটার্ন করবে, নাহলে ০। মানগুলো নিয়ে কাজ করা সহজ করতে ফাইলের শুরুতে TRUE আর FALSE দুটি কনস্ট্যান্ট ডিফাইন করা আছে, যেগুলো যথাক্রমে ১ আর ০-এর সমতুল্য।

নোট: স্কোরের গণনা নানা ভাবে করা যায়। তবে সম্ভব হলে আমরা চাই আপনি কার্ডের মান যোগ না করে হাতে একটি CA আর একটি দশ-কার্ড আছে কি না তা যাচাই করুন।

is_blackjack(14, 13)
// => 1

is_blackjack(10, 9)
// => 0

5. জোড়া ভাগ করা

খেলোয়াড়ের প্রথম দুটি কার্ডের মান একই হলে (যেমন দুটি C6-এর হাত, বা একটি CQ আর একটি CK-এর হাত), খেলোয়াড় চাইলে সেগুলোকে দুটি আলাদা হাত হিসেবে ধরতে পারেন। একে বলা হয় "জোড়া ভাগ করা"।

card_one আর card_two প্যারামিটারসহ can_split_pairs ফাংশনটি ডিফাইন করুন; এগুলো দুটি সংখ্যা, যা এক জোড়া কার্ড নির্দেশ করে। দুই কার্ডের হাতটি দুটি হাতে ভাগ করা গেলে ফাংশনটি ১ রিটার্ন করবে, নাহলে ০। মানগুলো নিয়ে কাজ করা সহজ করতে ফাইলের শুরুতে TRUE আর FALSE দুটি কনস্ট্যান্ট ডিফাইন করা আছে, যেগুলো যথাক্রমে ১ আর ০-এর সমতুল্য।

can_split_pair(12, 13)
// => 1

can_split_pair(10, 14)
// => 0

6. ডাবল ডাউন করা

শুরুতে দেওয়া দুটি কার্ডের মোট মান ৯, ১০ বা ১১ পয়েন্ট হলে খেলোয়াড় নিজের মূল বাজির সমান একটি অতিরিক্ত বাজি রাখতে পারেন। একে বলা হয় "ডাবল ডাউন"।

card_one আর card_two প্যারামিটারসহ can_double_down ফাংশনটি ডিফাইন করুন; এগুলো দুটি সংখ্যা, যা এক জোড়া কার্ড নির্দেশ করে। দুই কার্ডের হাতটি খেলোয়াড়কে "ডাবল ডাউন" করার সুযোগ দিলে ফাংশনটি ১ রিটার্ন করবে, নাহলে ০। মানগুলো নিয়ে কাজ করা সহজ করতে ফাইলের শুরুতে TRUE আর FALSE দুটি কনস্ট্যান্ট ডিফাইন করা আছে, যেগুলো যথাক্রমে ১ আর ০-এর সমতুল্য।

can_double_down(14, 9)
// => 1

can_double_down(10, 2)
// => 0
GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
x86-64 Assembly Exercism

ব্ল্যাকজ্যাক শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, x86-64 Assembly ট্র্যাকের 22টি কনসেপ্ট130টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।