Μπλακ Τζακ

Μπλακ Τζακ

Άσκηση εκμάθησης

Εισαγωγή

Συνθήκες

RFLAGs

Υπάρχει ένας ειδικός καταχωρητής που ονομάζεται rflags. Τα bit του λειτουργούν σαν σημαίες για διάφορες συνθήκες.

Μερικές από αυτές αναφέρονται παρακάτω:

όνομα σύμβολο bit
μεταφορά CF 0
μηδέν ZF 6
πρόσημο SF 7
υπερχείλιση OF 11

Εντολές σύγκρισης

Οι σημαίες στο rflags δεν τροποποιούνται άμεσα. Αντίθετα, ορίζονται από πολλές διαφορετικές εντολές.

Για παράδειγμα, το ZF ορίζεται από πολλές αριθμητικές πράξεις ή πράξεις bitwise, όταν το αποτέλεσμα είναι μηδέν.

Μία από τις πιο συνηθισμένες εντολές που χρησιμοποιούνται για τον έλεγχο συνθηκών είναι η 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, δεν εκτελείται ποτέ. Η τιμή του rax όταν επιστρέφει η fn είναι 5.

Άλμα υπό συνθήκη

Η οικογένεια εντολών jcc μεταφέρει την εκτέλεση του προγράμματος σε άλλο σημείο μόνο αν ικανοποιείται μια συγκεκριμένη συνθήκη. Διαφορετικά, η εκτέλεση συνεχίζεται διαδοχικά.

Κάθε συνθήκη αντιστοιχεί σε μία ή περισσότερες σημαίες στο rflags. Κάποιες παραλλαγές της jcc ελέγχουν αν μια σημαία είναι ορισμένη, άλλες ελέγχουν αν είναι καθαρισμένη.

Το cc στο jcc δεν είναι κυριολεκτικό, αλλά παραπέμπει στο συγκεκριμένο επίθημα που σχετίζεται με τη σημαία που ελέγχεται.

Υπάρχουν πολλά επιθήματα και πολλά από αυτά ελέγχουν την ίδια συνθήκη με κάποιο άλλο. Κάποια από αυτά παραπέμπουν απευθείας σε μια σημαία, ώστε η εντολή να κάνει άλμα σε μια ετικέτα αν η συγκεκριμένη σημαία είναι ορισμένη:

επίθημα κάνει άλμα αν
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 (χωρίς πρόσημο)

Είναι δυνατό να προσθέσεις e μετά από l, b, g ή a, για να συμπεριλάβεις την ισότητα στη συνθήκη:

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, για το equal) κάνουν και τα δύο άλμα όταν το 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

Οδηγίες

Σε αυτή την άσκηση θα υλοποιήσεις μερικούς κανόνες του Blackjack, όπως τον τρόπο με τον οποίο παίζεται και βαθμολογείται το παιχνίδι.

Note

Σε αυτή την άσκηση, τα φύλλα αντιπροσωπεύονται από αριθμούς· κάθε φύλλο με αριθμό αντιπροσωπεύεται από την αριθμητική του τιμή, ενώ οι βαλέδες, οι ντάμες, οι ρήγες και οι άσσοι ξεχωρίζουν από τους αριθμούς 11, 12, 13 και 14 αντίστοιχα (οι τζόκερ δεν χρησιμοποιούνται στο παιχνίδι).

Για να είναι πιο εύκολη η εργασία με αυτή την αναπαράσταση, μερικές σταθερές ορίζονται στην αρχή του αρχείου: τα C2 έως C10 είναι για τα φύλλα με αριθμό, το CJ για τον βαλέ, το CQ για τη ντάμα, το CK για τον ρήγα και το CA για τον άσσο.

Θεωρείται πως χρησιμοποιείται μια τυποποιημένη τράπουλα 52 φύλλων με γαλλικά χρώματα, αλλά στις περισσότερες εκδοχές ανακατεύονται μαζί αρκετές τράπουλες για το παιχνίδι.

Note

Αυτές είναι οι εντολές που αναφέρονται σε αυτή την έννοια:

Εντολή Περιγραφή
cmp a, b ορίζει τις σημαίες ανάλογα με το a - b
jmp a ο κώδικας σταματά να εκτελείται εδώ και συνεχίζει στην ετικέτα a
jcc a ο κώδικας συνεχίζει στην ετικέτα a αν ικανοποιείται η συνθήκη στο cc

Αυτές είναι οι συνθήκες που ελέγχονται σε μια jcc μετά από μια cmp a, b:

Εντολή Μετάβαση όταν
je a == b
jl a < b (signed)
jg a > b (signed)
jb a < b (unsigned)
ja a > b (unsigned)
jle a <= b (signed)
jge a >= b (unsigned)
jbe a <= b (unsigned)
jae a >= b (unsigned)
jne a != b
jnl !(a < b) (signed)
jng !(a > b) (signed)
jnb !(a < b) (unsigned)
jna !(a > b) (unsigned)
jnle !(a <= b) (signed)
jnge !(a >= b) (signed)
jnbe !(a <= b) (unsigned)
jnae !(a >= b) (unsigned)

1. Υπολόγισε την τιμή ενός φύλλου

Στο Blackjack, η τιμή ενός 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 πόντους, ανάλογα με το χέρι. Οι κανόνες του Blackjack απαιτούν οι τιμές των CA να επιλέγονται έτσι ώστε να μεγιστοποιούν το σκορ του χεριού χωρίς όμως να ξεπερνούν το 21 (να "μπουστάρει" το χέρι).

Όρισε τη συνάρτηση value_of_ace με παραμέτρους card_one και card_two, που είναι δύο αριθμοί που αντιπροσωπεύουν ένα ζευγάρι φύλλων που βρίσκονται ήδη στο χέρι πριν πάρεις το τελευταίο CA. Η συνάρτηση πρέπει να επιστρέφει ποια τιμή, 1 ή 11, θα αποδοθεί στο CA που έρχεται. Θυμήσου: η τιμή του νέου χεριού (με το CA) πρέπει να είναι όσο το δυνατόν μεγαλύτερη χωρίς να ξεπερνά το 21.

Υπόδειξη: αν έχουμε ήδη ένα CA στο χέρι, τότε η τιμή για το CA που έρχεται θα είναι 1.

value_of_ace(6, 13)
// => 1

value_of_ace(7, 3)
// => 11

4. Προσδιόρισε αν το χέρι είναι blackjack

Αν ένας παίκτης πάρει ένα CA και ένα δεκάρι (C10, CJ, CQ ή CK) ως τα δύο πρώτα του φύλλα, τότε έχει σκορ 21. Αυτό το χέρι είναι γνωστό ως blackjack.

Όρισε τη συνάρτηση is_blackjack με παραμέτρους card_one και card_two, που είναι δύο αριθμοί που αντιπροσωπεύουν ένα ζευγάρι φύλλων. Η συνάρτηση πρέπει να επιστρέφει 1 αν το χέρι των δύο φύλλων είναι blackjack, αλλιώς 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

Έτοιμος να ξεκινήσεις την άσκηση Μπλακ Τζακ;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις x86-64 Assembly με 22 έννοιες130 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.