Skip to content

[Black Jack]: Comparisons Concept and Concept Exercise Finalization #2844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
476 changes: 277 additions & 199 deletions concepts/comparisons/about.md

Large diffs are not rendered by default.

80 changes: 57 additions & 23 deletions concepts/comparisons/introduction.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,57 @@
# Introduction

A [comparison operator][comparisons] in Python (_also called a Python relational operator_), looks at the values of two operands and returns `True` or `False` based on whether the `comparison` condition is met.
The most common comparison operators are `"<"`, `">"`, `"=="`, `">="`, `"<="`, and `"!="`.
They all have the same priority (which is higher than that of the Boolean operations)

## Comparison Chaining

Comparisons can be chained arbitrarily, e.g., `x < y <= z` is equivalent to `x < y` `and` `y <= z`, except that `y` is evaluated only once (but in both cases `z` is _not_ evaluated at all when `x < y` is found to be `False`).
This is also called `short-circuit` evaluation which means the execution is stopped if the truth value of the expression has already been determined.
Note that the evaluation of expression takes place from left to right.
In python, short circuiting is supported by various boolean operators, functions and, in this case, comparison chaining.

## Comparison of different data types

Since everything in Python is an `object`, things can get interesting when objects of different types are compared.
For example, the `str` value of a number is considered completely different from the `integer` or `floating-point` value.
However, an `integer` **can** be considered equal to a `float`, as they are both numeric types that Python can implicitly convert to compare.
For other numeric types, comparison operators are defined where they "make sense", but throw a `TypeError` if the underlying objects cannot be converted for comparison.
For more information on the rules that python uses for numeric conversion, see [arithmetic conversions][arithmetic conversions] in the Python documentation.

[comparisons]: https://docs.python.org/3/library/stdtypes.html?
[arithmetic conversions]: https://docs.python.org/3/reference/expressions.html?highlight=number%20conversion#arithmetic-conversions
# Introduction

A [comparison operator][comparisons] in Python (_also called a Python relational operator_), looks at the _values_ of two [operands][operand] and returns a boolean `True` or `False` if the `comparison` condition is or is not met.

The table below shows the most common Python comparison operators:

| Operator | Operation | Description |
| -------- | -------------------------- | ------------------------------------------------------------------------- |
| `>` | "greater than" | `a > b` is `True` if `a` is **strictly** greater in value than `b` |
| `<` | "less than" | `a < b` is `True` if `a` is **strictly** less in value than `b` |
| `==` | "equal to" | `a == b` is `True` if `a` is **strictly** equal to `b` in value |
| `>=` | "greater than or equal to" | `a >= b` is `True` if `a > b` OR `a == b` in value |
| `<=` | "less than or equal to" | `a <= b` is `True` if `a < b` or `a == b` in value |
| `!=` | "not equal to" | `a != b` is `True` if `a == b` is `False` |
| `is` | "identity" | `a is b` is `True` if **_and only if_** `a` and `b` are the same _object_ |
| `is not` | "negated identity" | `a is not b` is `True` if `a` and `b` are **not** the same _object_ |
| `in` | "containment test" | `a in b` is `True` if `a` is member, subset, or element of `b` |
| `not in` | "negated containment test" | `a not in b` is `True` if `a` is not a member, subset, or element of `b` |

They all have the same priority (_which is higher than that of [Boolean operations][boolean operations], but lower than that of arithmetic or bitwise operations_).

## Comparison between different data types

Objects that are different types (_except numeric types_) never compare equal by default.
Non-identical instances of a `class` will also _**not**_ compare as equal unless the `class` defines special methods that customize the default `object` comparison behavior.

Numeric types are (mostly) an exception to this type matching rule.
An `integer` **can** be considered equal to a `float` (_or an [`octal`][octal] equal to a [`hexadecimal`][hex]_), as long as the types can be implicitly converted for comparison.

For the other numeric types ([complex][complex numbers], [decimal][decimal numbers], [fractions][rational numbers]), comparison operators are defined where they "make sense" (_where implicit conversion does not change the outcome_), but throw a `TypeError` if the underlying objects cannot be accurately converted for comparison.

## Comparing object identity

The operators `is` and `is not` test for object [_identity_][object identity], as opposed to object _value_.
An object's identity never changes after creation and can be found by using the [`id()`][id function] function.

`<apple> is <orange>` evaluates to `True` if _**and only if**_ `id(<apple>)` == `id(<orange>)`.
`<apple> is not <orange>` yields the inverse.

## Membership comparisons

The operators `in` and `not in` test for _membership_.
`<fish> in <soup>` evaluates to `True` if `<fish>` is a member of `<soup>` (_if `<fish>` is a subset of or is contained within `<soup>`_), and evaluates `False` otherwise.
`<fish> not in <soup>` returns the negation, or _opposite of_ `<fish> in <soup>`.

For string and bytes types, `<name> in <fullname>` is `True` _**if and only if**_ `<name>` is a substring of `<fullname>`.

[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
[comparisons]: https://docs.python.org/3/library/stdtypes.html?highlight=comparisons#comparisons
[complex numbers]: https://docs.python.org/3/library/functions.html#complex
[decimal numbers]: https://docs.python.org/3/library/decimal.html
[hex]: https://docs.python.org/3/library/functions.html?highlight=hex#hex
[id function]: https://docs.python.org/3/library/functions.html#id
[object identity]: https://docs.python.org/3/reference/datamodel.html
[octal]: https://docs.python.org/3/library/functions.html?#oct
[operand]: https://www.computerhope.com/jargon/o/operand.htm
[rational numbers]: https://docs.python.org/3/library/fractions.html
4 changes: 2 additions & 2 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@
"name": "Black Jack",
"uuid": "e1a6075e-0c5e-48cd-8c50-69140961a06a",
"concepts": ["comparisons"],
"prerequisites": ["basics"],
"status": "wip"
"prerequisites": ["basics", "bools", "conditionals"],
"status": "beta"
},
{
"slug": "pretty-leaflet",
Expand Down
42 changes: 27 additions & 15 deletions exercises/concept/black-jack/.docs/hints.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,42 @@

## 1. Calculate the value of a card

- You can use the equality comparison operator `==` to determine specific cards, e.g. `card == 'J'`.
- You can use the [`int` constructor][int constructor] to get an integer number from an integer literal, e.g. `int(card)`.
- You can use the equality comparison operator `==` to determine if a card is an ace card: `card == 'A'`.
- You can use the containment operator `in` to determine if a substring is contained inside a string: `'Q' in 'KJQ'`.
- You can use the [`int` constructor][int constructor] to convert a `str` of an `int` to an `int`: `int('13')`.

## 2. Calculate the value of an ace
## 2. Determine which card has a higher value

- You can use the order comparison operator `>` to decide the appropriate course of action, e.g. `hand_value + 11 > 21`.
- Once you have defined the `value_of_card` function, you can call it from other functions.
- You can use the value comparison operators `>` and `<` to determine if specific cards are _greater than_ or _less than_ a given value: `3 < 12`.
- You can use the equality comparison operator `==` to determine if two values are equal to one another.

## 3. Determine Blackjack
## 3. Calculate the value of an ace

- You can use the [`if`/`elif`/`else` syntax][if syntax] to handle different combinations of cards.
- Once you have defined the `value_of_card` function, you can call it from other functions.
- You can use the order comparison operator `>` to decide the appropriate course of action here.

## 4. Determine Blackjack

- Remember, you can use the [`if`/`elif`/`else` syntax][if syntax] to handle different combinations of cards.
- You can chain BOTH comparison operators and boolean operators _arbitrarily_: `y < z < x` or `(y or z) and (x or z)`
- You can reuse the already implemented `value_of_card` function.

## 4. Splitting pairs
## 5. Splitting pairs

- You can reuse the already implemented `value_of_card` function.
- You can handle the `A` case (when at least one of the cards in an ace) separately.

## 5. Doubling down
## 6. Doubling down

- You can chain comparison operators, e.g. `9 <= hand_value <= 11`.
- You can use the [conditional expression][conditional expression] (sometimes called a "ternary operator")
to shorten simple `if`/`else` statements, e.g. `1 if card == 'A' else value_of_card(card)`.
- An `A` scored at 11 will never allow doubling down if there are two cards in the hand.
- Given the first point, you _should_ be able to reuse the already implemented `value_of_card` function.
- You can chain comparison operators _arbitrarily_: `y < z < x`.
- You can use the [conditional expression][conditional expression] (_sometimes called a "ternary operator"_)
to shorten simple `if`/`else` statements: `13 if letter == 'M' else 3`.

[python comparisons tutorial]: https://docs.python.org/3/reference/expressions.html#comparisons
[python comparisons examples]: https://www.tutorialspoint.com/python/comparison_operators_example.htm
[int constructor]: https://docs.python.org/3/library/functions.html#int
[if syntax]: https://docs.python.org/3/tutorial/controlflow.html#if-statements
[conditional expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions
[if syntax]: https://docs.python.org/3/tutorial/controlflow.html#if-statements
[int constructor]: https://docs.python.org/3/library/functions.html#int
[python comparisons examples]: https://www.tutorialspoint.com/python/comparison_operators_example.htm
[python comparisons tutorial]: https://docs.python.org/3/reference/expressions.html#comparisons
99 changes: 69 additions & 30 deletions exercises/concept/black-jack/.docs/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,89 +3,128 @@
In this exercise you are going to implement some rules of [Blackjack][blackjack],
such as the way the game is played and scored.

**Note** : In this exercise, _A_ means ace, _J_ means jack, _Q_ means queen, and _K_ means king cards.
A [standard 52-card deck][standard_deck] is assumed.
**Note** : In this exercise, _`A`_ means ace, _`J`_ means jack, _`Q`_ means queen, and _`K`_ means king.
Jokers are discarded.
A [standard French-suited 52-card deck][standard_deck] is assumed, but in most versions, several decks are shuffled together for play.

## 1. Calculate the value of a card

In Blackjack, it is up to each individual player if an ace is worth 1 or 11 points.
Face cards (_J_, _Q_, _K_) are worth 10 points and any other card is worth its pip (numerical) value.
In Blackjack, it is up to each individual player if an ace is worth 1 or 11 points (_more on that later_).
Face cards (`J`, `Q`, `K`) are scored at 10 points and any other card is worth its "pip" (_numerical_) value.

Define the `value_of_card` function with a parameter `card`.
The value of _J_, _Q_ or _K_ is 10.
Otherwise, return the numerical value of a card.
An ace can take on multiple values so let's ignore _A_ for the time being.
Define the `value_of_card(<card>)` function with parameter `card`.
The function should return the _numerical value_ of the passed-in card string.
Since an ace can take on multiple values (1 **or** 11), this function should fix the value of an ace card at 1 for the time being.
Later on, you will implement a function to determine the value of an ace card, give an existing hand.

```python
>>> value_of_card('K')
10

>>> value_of_card('4')
4

>>> value_of_card('A')
1
```

## 2. Calculate the value of an ace
## 2. Determine which card has a higher value

As mentioned before, an ace can be worth _either_ 1 or 11 points.
At the same time, players are trying to get as close to 21 as possible, without going _over_ 21.
Define the `higher_card(<card_one>, <card_two>)` function having parameters `card_one` and `card_two`.
For scoring purposes, the value of `J`, `Q` or `K` is 10.
The function should return which card has the higher value for scoring.
If both cards have an equal value, return both.
Returning both cards can be done by using a comma in the `return` statement:

Define the `value_of_ace(<hand_value>)` function with a parameter `hand_value`,
which is the total hand value before getting an ace card.
You now have to decide if the upcoming ace card will be scored as a 1 or 11.
The value of the hand with the ace needs to be as high as possible _without_ going over 21.
```python
# 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')
```

An ace can take on multiple values, so we will fix `A` cards to a value of 1 for this task.

```python
>>> value_of_ace(19)
>>> higher_card('K', '10')
('K', '10')

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

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

## 3. Calculate the value of an ace

As mentioned before, an ace can be worth _either_ 1 **or** 11 points.
Players try to get as close as possible to a score of 21, without going _over_ 21 (_going "bust"_).

Define the `value_of_ace(<card_one>, <card_two>)` function with parameters `card_one` and `card_two`, which are a pair of cards already in the hand _before_ getting an ace card.
Your function will have to decide if the upcoming ace will get a value of 1 or a value of 11, and return that value.
Remember: the value of the hand with the ace needs to be as high as possible _without_ going over 21.

```python
>>> value_of_ace('6', `K`)
1
>>> value_of_ace(7)

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

## 3. Determine Blackjack
## 4. Determine a "Natural" or "Blackjack" Hand

If the first two cards are an ace and a ten-card, giving a count of 21 in two cards, it is known as blackjack.
If the first two cards a player is dealt are an ace (`A`) and a ten-card (10, `K`, `Q` or `J`), giving a score of 21 in two cards, the hand is considered a `natural` or `blackjack`.

Define the `is_blackjack(<card_one>, <card_two>)` function with parameters `card_one` and `card_two`, which are a pair of cards.
Determine if the two-card hand is a blackjack.
Determine if the two-card hand is a `blackjack`, and return the boolean `True` if it is, `False` otherwise.

**Note** : This calculation can be done in many ways.
If possible, check if there is an ace and a ten-card in the hand.
**Note** : The score _calculation_ can be done in many ways.
But if possible, we'd like you to check if there is an ace and a ten-card **_in_** the hand (or at a certain position), as opposed to _summing_ the hand values.

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

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

## 4. Splitting pairs
## 5. Splitting pairs

If the first two cards are of the same value,
such as two sixes, or a _Q_ and _K_ a player may choose to treat them as two separate hands.
If the players first two cards are of the same value, such as two sixes, or a `Q` and `K` a player may choose to treat them as two separate hands.
This is known as "splitting pairs".

Define the `can_split_pairs(<card_one>, <card_two>)` function with parameters `card_one` and `card_two`, which are a pair of cards.
Determine if this two-card hand can be split into two pairs.
If the hand can be split, return the boolean `True` otherwise, return `False`

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

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

## 5. Doubling down
## 6. Doubling down

When the original two cards dealt total 9, 10, or 11 points
a player can place an additional bet equal to the original bet.
When the original two cards dealt total 9, 10, or 11 points, a player can place an additional bet equal to their original bet.
This is known as "doubling down".

Define the `can_double_down(<card_one>, <card_two>)` function with parameters `card_one` and `card_two`, which are a pair of cards.
Determine if the two-card hand can be "doubled down".
Determine if the two-card hand can be "doubled down", and return the boolean `True` if it can, `False` otherwise.

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

>>> can_double_down('10', '2')
False
```

[blackjack]: https://en.wikipedia.org/wiki/Blackjack
[blackjack]: https://bicyclecards.com/how-to-play/blackjack/
[standard_deck]: https://en.wikipedia.org/wiki/Standard_52-card_deck
Loading