Black Jack

Black Jack

सीखने का अभ्यास

परिचय

कंडीशनल

RFLAGs

rflags नाम का एक विशेष रजिस्टर होता है। इसके बिट विभिन्न शर्तों के लिए फ्लैग की तरह काम करते हैं।

इनमें से कुछ नीचे दिए गए हैं:

नाम प्रतीक बिट
carry CF 0
zero ZF 6
sign SF 7
overflow OF 11

तुलना के निर्देश

rflags के फ्लैग सीधे नहीं बदले जाते। इसके बजाय, उन्हें कई अलग-अलग निर्देश सेट करते हैं।

उदाहरण के लिए, जब परिणाम शून्य होता है, तो कई अंकगणितीय या बिटवाइज़ ऑपरेशन ZF को सेट करते हैं।

शर्तों की जाँच के लिए सबसे आम निर्देशों में से एक है cmp। यह दो ऑपरेंड लेता है और फ्लैग अपडेट करता है, लेकिन अपने ऑपरेंड नहीं बदलता।

CMP निर्देश

cmp निर्देश पहले ऑपरेंड में से दूसरा ऑपरेंड घटाता है और परिणाम के अनुसार फ्लैग सेट करता है।

यदि A पहला ऑपरेंड है और B दूसरा:

फ्लैग कब सेट होता है
CF A < B (अनसाइन्ड)
ZF A == B
SF A < B (साइन्ड, कोई ओवरफ्लो नहीं)
OF साइन्ड घटाव में ओवरफ्लो

ब्रांचिंग

डिफ़ॉल्ट रूप से, 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 शब्दशः नहीं है, बल्कि उस विशेष प्रत्यय की ओर संकेत करता है जो जाँचे जाने वाले फ्लैग से जुड़ा होता है।

कई प्रत्यय हैं, और उनमें से कई वही शर्त जाँचते हैं जो कोई दूसरा प्रत्यय जाँचता है। इनमें से कुछ सीधे किसी फ्लैग की ओर संकेत करते हैं, जिससे निर्देश किसी लेबल पर जंप करता है यदि वह विशेष फ्लैग सेट हो:

प्रत्यय कब जंप करता है
z ZF == 1
c CF == 1
s SF == 1
o OF == 1

कई अन्य इस तरह चुने गए हैं कि वे cmp निर्देश में अपने अर्थ की ओर संकेत करें। उदाहरण के लिए:

निर्देश प्रत्यय कब जंप करता है
cmp A, B e A == B
cmp A, B l A < B (साइन्ड)
cmp A, B b A < B (अनसाइन्ड)
cmp A, B g A > B (साइन्ड)
cmp A, B a A > B (अनसाइन्ड)

शर्त में समानता शामिल करने के लिए 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 होता है।

उदाहरण के लिए, jnz तब जंप करता है जब ZF सेट न हो। इसी तरह, jnae तब जंप करता है जब A, B से >= न हो (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

इस अभ्यास में पत्तों को संख्याओं से दर्शाया गया है। हर संख्या वाले पत्ते को उसकी संख्यात्मक वैल्यू से दर्शाया जाता है, जबकि गुलाम, बेगम, बादशाह और इक्के को क्रमशः 11, 12, 13 और 14 से पहचाना जाता है (खेल में जोकर का इस्तेमाल नहीं होता)।

इस प्रतिनिधित्व के साथ काम करना आसान बनाने के लिए फाइल के शुरू में कुछ कॉन्स्टेंट परिभाषित किए गए हैं। C2 से C10 संख्या वाले पत्तों के लिए हैं, CJ गुलाम के लिए, CQ बेगम के लिए, CK बादशाह के लिए और CA इक्के के लिए।

यहाँ मान लिया जाता है कि ताश की फ्रेंच सूट वाली मानक 52-पत्तों की गड्डी इस्तेमाल होती है, लेकिन ज़्यादातर संस्करणों में खेलने के लिए कई गड्डियाँ एक साथ फेंटी जाती हैं।

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 की वैल्यू 1 या 11 होती है, जो हाथ में मौजूद पत्तों पर निर्भर करती है (इस पर आगे बात करेंगे)। तस्वीर वाले पत्ते (CJ, CQ और CK) 10 अंक के होते हैं, और बाकी हर पत्ता अपनी संख्यात्मक वैल्यू के बराबर होता है।

value_of_card फंक्शन बनाइए जिसका पैरामीटर card है, यानी किसी पत्ते को दर्शाने वाली एक संख्या। यह फंक्शन उस पत्ते की संख्यात्मक वैल्यू लौटाता है जो इसे दिया गया है। चूँकि एक CA कई वैल्यू ले सकता है (1 या 11), अभी के लिए हम CA की वैल्यू 1 तय करते हैं। आगे आप एक फंक्शन बनाएँगे जो मौजूदा हाथ को देखकर CA की वैल्यू तय करेगा।

value_of_card(13)
// => 10

value_of_card(4)
// => 4

value_of_card(14)
// => 1

2. यह तय कीजिए कि किस पत्ते की वैल्यू ज़्यादा है

higher_card फंक्शन बनाइए जिसके पैरामीटर card_one और card_two हैं, यानी दो संख्याएँ जो दो पत्तों को दर्शाती हैं। अंक गिनने के लिए CJ, CQ और CK की वैल्यू 10 होती है। यह फंक्शन वह पत्ता लौटाता है जिसकी अंक गिनने के लिए वैल्यू ज़्यादा होती है। अगर दोनों पत्तों की वैल्यू बराबर हो, तो दोनों लौटाइए।

CA कई वैल्यू ले सकता है, इसलिए इस काम के लिए हम उसकी वैल्यू 1 तय करते हैं।

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 की वैल्यू 1 या 11 अंक होती है। ब्लैकजैक के नियमों के मुताबिक CA की वैल्यू ऐसी चुनी जाती है कि हाथ का स्कोर ज़्यादा से ज़्यादा हो, मगर 21 से ऊपर न चला जाए (यानी "बस्ट" न हो जाए)।

value_of_ace फंक्शन बनाइए जिसके पैरामीटर card_one और card_two हैं। ये दो संख्याएँ उन दो पत्तों को दर्शाती हैं जो अंतिम CA मिलने से पहले हाथ में पहले से मौजूद हैं। आपका फंक्शन लौटाता है कि आने वाले CA को कौन-सी वैल्यू, 1 या 11, असाइन की जाएगी। याद रखिए: नए हाथ (जिसमें CA शामिल है) की वैल्यू जितनी ज़्यादा हो सके उतनी होनी चाहिए, मगर 21 से ऊपर न जाए।

संकेत: अगर हाथ में पहले से एक CA है, तो आने वाले CA की वैल्यू 1 होगी।

value_of_ace(6, 13)
// => 1

value_of_ace(7, 3)
// => 11

4. यह तय कीजिए कि हाथ ब्लैकजैक है या नहीं

अगर किसी खिलाड़ी को पहले दो पत्तों के रूप में एक CA और एक दस वाला पत्ता (C10, CJ, CQ या CK) मिलता है, तो खिलाड़ी का स्कोर 21 होता है। इस हाथ को ब्लैकजैक कहा जाता है।

is_blackjack फंक्शन बनाइए जिसके पैरामीटर card_one और card_two हैं, यानी दो संख्याएँ जो दो पत्तों को दर्शाती हैं। अगर दो पत्तों वाला हाथ ब्लैकजैक है तो यह फंक्शन 1 लौटाता है, वरना 0। वैल्यू के साथ काम करना आसान बनाने के लिए फाइल के शुरू में TRUE और FALSE कॉन्स्टेंट परिभाषित किए गए हैं, जो क्रमशः 1 और 0 के बराबर हैं।

ध्यान दें: स्कोर की गणना कई तरीकों से की जा सकती है। लेकिन हो सके तो हम चाहेंगे कि आप पत्तों की वैल्यू जोड़ने के बजाय यह जाँचें कि हाथ में कोई CA है और कोई दस वाला पत्ता भी है।

is_blackjack(14, 13)
// => 1

is_blackjack(10, 9)
// => 0

5. जोड़े अलग करना

अगर किसी खिलाड़ी के पहले दो पत्तों की वैल्यू एक जैसी हो (जैसे दो C6 का हाथ, या एक CQ और एक CK का हाथ), तो खिलाड़ी चाहे तो उन्हें दो अलग-अलग हाथों की तरह मान सकता है। इसे "जोड़े अलग करना" कहा जाता है।

can_split_pairs फंक्शन बनाइए जिसके पैरामीटर card_one और card_two हैं, यानी दो संख्याएँ जो दो पत्तों को दर्शाती हैं। अगर दो पत्तों वाले हाथ को दो हाथों में बाँटा जा सकता है तो यह फंक्शन 1 लौटाता है, वरना 0। वैल्यू के साथ काम करना आसान बनाने के लिए फाइल के शुरू में TRUE और FALSE कॉन्स्टेंट परिभाषित किए गए हैं, जो क्रमशः 1 और 0 के बराबर हैं।

can_split_pair(12, 13)
// => 1

can_split_pair(10, 14)
// => 0

6. दाँव दोगुना करना

जब शुरू में बाँटे गए दो पत्तों का जोड़ 9, 10 या 11 अंक हो, तो खिलाड़ी अपने शुरुआती दाँव के बराबर एक अतिरिक्त दाँव लगा सकता है। इसे "दाँव दोगुना करना" कहा जाता है।

can_double_down फंक्शन बनाइए जिसके पैरामीटर card_one और card_two हैं, यानी दो संख्याएँ जो दो पत्तों को दर्शाती हैं। अगर दो पत्तों वाला हाथ खिलाड़ी को "दाँव दोगुना करने" की सुविधा देता है तो यह फंक्शन 1 लौटाता है, वरना 0। वैल्यू के साथ काम करना आसान बनाने के लिए फाइल के शुरू में TRUE और FALSE कॉन्स्टेंट परिभाषित किए गए हैं, जो क्रमशः 1 और 0 के बराबर हैं।

can_double_down(14, 9)
// => 1

can_double_down(10, 2)
// => 0
GitHub के ज़रिए संपादित करें यह लिंक एक नई विंडो या टैब में खुलता है
x86-64 Assembly Exercism

Black Jack शुरू करने के लिए तैयार हैं?

Exercism पर साइन अप कीजिए और x86-64 Assembly को 22 कॉन्सेप्ट130 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।