Blackjack

Blackjack

Exercício de aprendizagem

Introdução

Comparações

O Python suporta os seguintes operadores de comparação básicos:

Operador Operação Descrição
> "maior que" a > b é True se a for estritamente maior, em valor, do que b
< "menor que" a < b é True se a for estritamente menor, em valor, do que b
== "igual a" a == b é True se a for estritamente igual a b em valor
>= "maior ou igual a" a >= b é True se a > b OU a == b em valor
<= "menor ou igual a" a <= b é True se a < b ou a == b em valor
!= "diferente de" a != b é True se a == b for False
is "identidade" a is b é True se e somente se a e b forem o mesmo objeto
is not "identidade negada" a is not b é True se a e b não forem o mesmo objeto
in "teste de pertença" a in b é True se a for membro, subconjunto ou elemento de b
not in "teste de pertença negado" a not in b é True se a não for membro, subconjunto ou elemento de b

Todos têm a mesma prioridade (que é superior à das operações Boolean, mas inferior à das operações aritméticas ou bit a bit).

Comparação entre tipos de dados diferentes

Os objetos de tipos diferentes (exceto os tipos numéricos) nunca são considerados iguais por predefinição. Instâncias não idênticas de uma class também não serão consideradas iguais, a menos que a class defina métodos especiais de comparação avançada que personalizem o comportamento de comparação predefinido do object. A personalização através de rich comparisons será abordada num exercício seguinte. Para mais detalhes (muitos) sobre este tema, consulta Comparações de valores na documentação do Python.

Os tipos numéricos são (na sua maioria) uma exceção a esta regra de correspondência de tipos. Um integer pode ser considerado igual a um float (ou um octal igual a um hexadecimal), desde que os tipos possam ser convertidos implicitamente para efeitos de comparação.

Para os outros tipos numéricos da biblioteca padrão do Python (complexos, decimais, frações), os operadores de comparação estão definidos onde "fazem sentido" (onde a conversão implícita não altera o resultado), mas lançam um TypeError se os objetos subjacentes não puderem ser convertidos com exatidão para efeitos de comparação. Para mais informações sobre as regras que o Python usa para a conversão numérica, consulta conversões aritméticas na documentação do Python.

>>> import fractions

# A string cannot be converted to an int.
>>> 17 == '17'
False

# An int can be converted to float for comparison.
>>> 17 == 17.0
True

# The fraction 6/3 can be converted to the int 2
# The int 2 can be converted to 0b10 in binary.
>>> 6/3 == 0b10
True

# An int can be converted to a complex number with a 0 imaginary part.
>>> 17 == complex(17)
True

# The fraction 2/5 can be converted to the float 0.4
>>> 0.4 == 2/5
True

>>> complex(2/5, 1/2) == complex(0.4, 0.5)
True

Qualquer comparação ordenada de um número com um tipo NaN (not a number) é False. Um efeito secundário confuso da definição de NaN do Python é que NaN nunca é considerado igual a NaN.

>>> x = float('NaN')

>>> 3 < x
False

>>> x < 3
False

# NaN never compares equal to NaN
>>> x == x
False

Comparar strings

Ao contrário dos números, as strings (str) são comparadas lexicograficamente, usando os respetivos pontos de código Unicode (o resultado de passar cada ponto de código da str à função incorporada ord(), que devolve um int). Se todos os pontos de código de ambas as strings coincidirem e estiverem pela mesma ordem, as duas strings são consideradas iguais. Esta comparação é feita aos pares: primeiro com primeiro, segundo com segundo, etc. No Python 3.x, str e bytes não podem ser diretamente coagidos ou comparados.

>>> 'Python' > 'Rust'
False

>>> 'Python' > 'JavaScript'
True

# Examples with Mandarin.
# hello < goodbye
>>> '你好' < '再见'
True

# ord() of first characters
>>> ord('你'), ord('再')
(20320, 20877)

# ord() of second characters
>>> ord('好'), ord('见')
(22909, 35265)

# And with Korean words.
# Pretty < beautiful.
>>> '예쁜' < '아름다운'
False

>>> ord('예'), ord('아')
(50696, 50500)

Encadeamento de comparações

Os operadores de comparação podem ser encadeados arbitrariamente, ou seja, podem ser usados em qualquer combinação e com qualquer extensão. Repara que a avaliação de uma expressão ocorre de left para right.

Por exemplo, x < y <= z é equivalente a x < y and y <= z, exceto que y é avaliado apenas uma vez. Em ambos os casos, z não é avaliado de todo quando se verifica que x < y é False. A isto chama-se frequentemente short-circuit evaluation: a avaliação termina se o valor de verdade da expressão já tiver sido determinado.

O Short circuiting é suportado por vários operadores boolean, por funções e também pelo encadeamento de comparações em Python. Ao contrário de muitas outras linguagens de programação, incluindo C, C++, C# e Java, as expressões encadeadas como a < b < c em Python têm uma interpretação matemática e uma precedência convencionais.

>>> x = 2
>>> y = 5
>>> z = 10

>>> x < y < z
True

>>> x < y > z
False

>>> x > y < z
False

Comparar a identidade de objetos

Os operadores is e is not testam a identidade de um objeto, em oposição ao seu valor. A identidade de um objeto nunca muda após a sua criação e pode ser obtida com a função id().

<apple> is <orange> é True se e somente se id(<apple>) == id(<orange>). <apple> is not <orange> devolve o inverso.

Devido ao seu estatuto de singleton, None e NotImplemented devem ser sempre comparados com itens usando is e is not. Consulta a documentação de referência do Python sobre comparações de valores e o PEP8 para mais detalhes sobre esta convenção.

>>> my_fav_numbers = [1, 2, 3]

>>> your_fav_numbers = my_fav_numbers

>>> my_fav_numbers is your_fav_numbers
True

# The returned id will differ by system and python version.
>>> id(my_fav_numbers)
4517478208

# your_fav_numbers is only an alias pointing to the original my_fav_numbers object.
# Assigning a new name does not create a new object.
>>> id(your_fav_numbers)
4517478208


>>> my_fav_numbers is not your_fav_numbers
False

>>> my_fav_numbers is not None
True

>>> my_fav_numbers is NotImplemented
False

Comparações de pertença

Os operadores in e not in testam a pertença. <fish> in <soup> é True se <fish> for um membro de <soup> (se <fish> for um subconjunto de <soup> ou estiver contido em <soup>), e é False caso contrário. <fish> not in <soup> devolve a negação, ou o oposto de <fish> in <soup>.

Para os tipos string e bytes, <name> in <fullname> é True se e somente se <name> for uma substring de <fullname>.

# A set of lucky numbers.
>>> lucky_numbers = {11, 22, 33}
>>> 22 in lucky_numbers
True

>>> 44 in lucky_numbers
False

# A dictionary of employee information.
>>> employee = {'name': 'John Doe', 
                'id': 67826, 'age': 33, 
                'title': 'ceo'}

# Checking for the membership of certain keys.
>>> 'age' in employee
True

>>> 33 in employee
False

>>> 'lastname' not in employee
True

# Checking for substring membership
>>> name = 'Super Batman'
>>> 'Bat' in name
True

>>> 'Batwoman' in name
False

Instruções

Neste exercício vais implementar algumas regras do Blackjack, como a forma como o jogo se joga e se pontua.

Nota: Neste exercício, A significa ás, J significa valete, Q significa dama e K significa rei. Os jokers são descartados. Assume-se um baralho padrão de 52 cartas com naipes franceses, mas na maioria das versões misturam-se vários baralhos para jogar.

1. Calcula o valor de uma carta

No Blackjack, cabe a cada jogador decidir se um ás vale 1 ou 11 pontos (mais sobre isto mais adiante). As figuras (J, Q, K) valem 10 pontos e qualquer outra carta vale o valor do seu "pip" (numérico).

Define a função value_of_card(<card>) com o parâmetro card. A função deve devolver o valor numérico da string da carta recebida. Como um ás pode assumir vários valores (1 ou 11), por agora esta função deve fixar o valor de um ás em 1. Mais adiante vais implementar uma função que determina o valor de um ás, dada uma mão existente.

>>> value_of_card('K')
10

>>> value_of_card('4')
4

>>> value_of_card('A')
1

2. Determina qual carta tem o valor mais alto

Define a função higher_card(<card_one>, <card_two>) com os parâmetros card_one e card_two. Para efeitos de pontuação, o valor de J, Q ou K é 10. A função deve devolver qual das cartas tem o valor mais alto para pontuação. Se as duas cartas tiverem o mesmo valor, devolve as duas. Podes devolver as duas cartas usando uma vírgula na instrução return:

# Using a comma in a return creates a Tuple.  Tuples will be covered in a later exercise.
>>> def returning_two_values(value_one, value_two):
        return value_one, value_two

>>> returning_two_values('K', '3')
('K', '3')

Um ás pode assumir vários valores, por isso nesta tarefa vamos fixar as cartas A no valor 1.

>>> higher_card('K', '10')
('K', '10')

>>> higher_card('4', '6')
'6'

>>> higher_card('K', 'A')
'K'

3. Calcula o valor de um ás

Como já foi referido, um ás pode valer ou 1 ou 11 pontos. Os jogadores tentam aproximar-se o mais possível de 21 pontos, sem passar acima de 21 ("rebentar").

Define a função value_of_ace(<card_one>, <card_two>) com os parâmetros card_one e card_two, que são um par de cartas que já estão na mão antes de receberes um ás. A tua função terá de decidir se o próximo ás vai valer 1 ou 11, e devolver esse valor. Lembra-te: o valor da mão com o ás tem de ser o mais alto possível sem passar de 21.

Dica: se já tivermos um ás na mão, o valor do próximo ás seria 1.

>>> value_of_ace('6', 'K')
1

>>> value_of_ace('7', '3')
11

4. Determina uma mão "Natural" ou "Blackjack"

Se um jogador receber um ás (A) e uma carta de dez (10, K, Q ou J) como as suas duas primeiras cartas, fica com uma pontuação de 21. Isto é conhecido como uma mão de blackjack.

Define a função is_blackjack(<card_one>, <card_two>) com os parâmetros card_one e card_two, que são um par de cartas. Determina se a mão de duas cartas é um blackjack e devolve o boolean True se for, e False caso contrário.

Nota: O cálculo da pontuação pode ser feito de várias formas. Mas, se possível, gostaríamos que verificasses se há um ás e uma carta de dez na mão (ou numa determinada posição), em vez de somar os valores da mão.

>>> is_blackjack('A', 'K')
True

>>> is_blackjack('10', '9')
False

5. Dividir pares

Se as duas primeiras cartas de uma mão tiverem o mesmo valor (por exemplo, dois seis ou um Q e um K), o jogador pode optar por tratá-las como duas mãos separadas. Isto é conhecido como "dividir pares".

Define a função can_split_pairs(<card_one>, <card_two>) com os parâmetros card_one e card_two, que são um par de cartas. Determina se esta mão de duas cartas pode ser dividida em dois pares. Se a mão puder ser dividida, devolve o boolean True; caso contrário, devolve False.

>>> can_split_pairs('Q', 'K')
True

>>> can_split_pairs('10', 'A')
False

6. Dobrar a aposta

Quando as duas cartas iniciais somam 9, 10 ou 11 pontos, o jogador pode fazer uma aposta adicional igual à aposta original. Isto é conhecido como "dobrar a aposta".

Define a função can_double_down(<card_one>, <card_two>) com os parâmetros card_one e card_two, que são um par de cartas. Determina se a mão de duas cartas pode ser "dobrada" e devolve o boolean True se puder, e False caso contrário.

>>> can_double_down('A', '9')
True

>>> can_double_down('10', '2')
False
Editar via GitHub A ligação abre numa nova janela ou separador
Python Exercism

Estás pronto para começar Blackjack?

Inscreve-te no Exercism para aprenderes e dominares Python com 17 conceitos146 exercícios, e mentoria humana real, tudo grátis.