https://github.com/dfleta/yatzy-refactoring-kata
Yatzy refactoring kata
https://github.com/dfleta/yatzy-refactoring-kata
Last synced: 5 months ago
JSON representation
Yatzy refactoring kata
- Host: GitHub
- URL: https://github.com/dfleta/yatzy-refactoring-kata
- Owner: dfleta
- Created: 2022-01-13T12:17:49.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2025-01-09T11:02:52.000Z (over 1 year ago)
- Last Synced: 2025-01-09T12:20:54.000Z (over 1 year ago)
- Language: Python
- Size: 29.3 KB
- Stars: 0
- Watchers: 2
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Yatzy Refactoring Kata
**Kata readme by [Emily Bache](https://github.com/emilybache)** Thanks!! :clap::clap:
[Emily Bache kata repo](https://github.com/emilybache/Yatzy-Refactoring-Kata)
*Designed by Jon Jagger. It's available in his Cyber-Dojo. [Blog post](http://jonjagger.blogspot.co.uk/2012/05/yahtzee-cyber-dojo-refactoring-in-java.html)*
## Kata: Yatzy rules
The game of Yatzy is a simple dice game. Each player
rolls five six-sided dice. They can re-roll some or all
of the dice up to three times (including the original roll).
For example, suppose a players rolls:
3,4,5,5,2
They hold (-,-,5,5,-) and re-roll (3,4,-,-,2):
5,1,5,5,3
They hold (5,-,5,5,-) and re-roll (-,1,-,-,3):
5,6,5,5,2
The player then places the roll in a category, such as ones,
twos, fives, pair, two pairs etc (see below). If the roll is
compatible with the category, the player gets a score for the
roll according to the rules. If the roll is not compatible
with the category, the player scores zero for the roll.
For example, suppose a player scores 5,6,5,5,2 in the fives
category they would score 15 (three fives). The score for
that go is then added to their total and the category cannot
be used again in the remaining goes for that game.
A full game consists of one go for each category. Thus, for
their last go in a game, a player must choose their only
remaining category.
Your task is to score a GIVEN roll in a GIVEN category.
You do NOT have to program the random dice rolling.
The game is NOT played by letting the computer choose the
highest scoring category for a given roll.
## Kata: Yatzy Categories and Scoring Rules
### Chance
The player scores the sum of all dice, no matter what they read.
For example:
- 1,1,3,3,6 placed on "chance" scores 14 (1+1+3+3+6)
- 4,5,5,6,1 placed on "chance" scores 21 (4+5+5+6+1)
### Yatzy
If all dice have the same number, the player scores 50 points.
For example:
- 1,1,1,1,1 placed on "yatzy" scores 50
- 1,1,1,2,1 placed on "yatzy" scores 0
### Ones, Twos, Threes, Fours, Fives, Sixes
The player scores the sum of the dice that reads one, two, three, four, five or six, respectively.For example:
- 1,1,2,4,4 placed on "fours" scores 8 (4+4)
- 2,3,2,5,1 placed on "twos" scores 4 (2+2)
- 3,3,3,4,5 placed on "ones" scores 0
### Pair
The player scores the sum of the two highest matching dice.
For example, when placed on "pair":
- 3,3,3,4,4 scores 8 (4+4)
- 1,1,6,2,6 scores 12 (6+6)
- 3,3,3,4,1 scores 6 (3+3)
- 3,3,3,3,1 scores 6 (3+3)
### Two pairs
If there are two pairs of dice with the same number, the player scores the sum of these dice. For example, when placed on "two pairs":
- 1,1,2,3,3 scores 8 (1+1+3+3)
- 1,1,2,3,4 scores 0
- 1,1,2,2,2 scores 6 (1+1+2+2)
### Three of a kind
If there are three dice with the same number, the player
scores the sum of these dice.
For example, when placed on "three of a kind":
- 3,3,3,4,5 scores 9 (3+3+3)
- 3,3,4,5,6 scores 0
- 3,3,3,3,1 scores 9 (3+3+3)
### Four of a kind
If there are four dice with the same number, the player scores the sum of these dice.
For example, when placed on "four of a kind":
- 2,2,2,2,5 scores 8 (2+2+2+2)
- 2,2,2,5,5 scores 0
- 2,2,2,2,2 scores 8 (2+2+2+2)
### Small straight
When placed on "small straight", if the dice read
1,2,3,4,5,
the player scores 15 (the sum of all the dice).
### Large straight
When placed on "large straight", if the dice read
2,3,4,5,6,
the player scores 20 (the sum of all the dice).
### Full house
If the dice are two of a kind and three of a kind, the player scores the sum of all the dice.
For example, when placed on "full house":
- 1,1,2,2,2 scores 8 (1+1+2+2+2)
- 2,2,3,3,4 scores 0
- 4,4,4,4,4 scores 0
---
## Métodos de clase en Python: `@staticmethod` vs `@classmethod`
Lee el código en [`yatzy_refactored.py`](./src/yatzy_refactored.py)
### Objetivo 🎯
Explicar, con ejemplos prácticos sacados de `src/yatzy_refactored.py`, cuándo y por qué usar `@staticmethod` y `@classmethod` en Python.
---
### Resumen breve ✅
- `@staticmethod`: función ligada a la clase por SRP; **no recibe** ni `self` ni `cls`. Útil para operaciones puramente funcionales relacionadas con la clase que no necesitan acceder al estado de una instancia o de la clase.
- `@classmethod`: método que recibe la **clase** como primer argumento (`cls`). Útil cuando el comportamiento necesita acceder o modificar datos de clase, o cuando queremos que el método sea heredable por subclases.
---
### Diferencias 🔍
- Signatura: `def f(*args)` vs `def f(cls, *args)`
- Acceso: no puede usar atributos de clase (`staticmethod`) ↔ usa `cls` para acceder/alterar atributos o invocar otros métodos de clase
- Heredabilidad: `classmethod` respeta la clase que llama; `staticmethod` es independiente de la clase que la contiene
---
### Ejemplos del proyecto 🔧
#### 1- `@staticmethod` — función pura relacionada con la lógica del juego
```python
@staticmethod
def chance(*dice):
return sum(dice)
@staticmethod
def yatzy(*dice):
return Yatzy.FIFTY if len(set(dice)) == 1 else Yatzy.ZERO
```
Por qué usamos `staticmethod` aquí:
- Estas funciones calculan un resultado a partir de los argumentos (`dice`) y **no necesitan** acceder a la instancia (`self`) ni a la clase (`cls`).
- Son utilitarias: su comportamiento no cambia si la clase se hereda.
#### 2- `@classmethod` — necesita conocer la clase o usar otros métodos de clase
```python
@classmethod
def pair(cls, *dice):
PAIR = Pips.TWO.value
pip = cls.__biggest_pip_repeated(dice, PAIR)
return pip * PAIR if pip else Yatzy.ZERO
@classmethod
def small_straight(cls, *dice):
return cls.chance(*dice) if not Pips.minus(Pips.SIX) - set(dice) else Yatzy.ZERO
```
Por qué usamos `classmethod` aquí:
- `pair` y `small_straight` llaman a otros métodos que pertenecen a la clase (por ejemplo `cls.__biggest_pip_repeated`, `cls.chance`). Usar `cls` permite que una subclase que reimplemente esos métodos conserve el comportamiento correcto.
- `classmethod` facilita la extensibilidad y la reutilización por herencia.
#### 3- Método de instancia
```python
def fours(self):
return self.__sum_dice_equals(Pips.FOUR.value)
```
Este método usa `self.dice`, el estado de una instancia u objeto de la clase, por eso NO puede ser `@staticmethod` ni `@classmethod`.
---
### Buenas prácticas y recomendaciones 💡
- Usa `@staticmethod` para utilidades que no requieren ni `cls` ni `self` (pure functions relacionadas conceptualmente con la clase).
- Usa `@classmethod` cuando el método **debe conocer la clase** que lo invoca (para acceder a atributos de clase, construir instancias, o apoyar herencia).
- Preferir `cls.CONSTANT` dentro de `@classmethod` para mantener compatibilidad con subclases.
- Evitar mezclar responsabilidades: si el método necesita estado de instancia, debe ser un método de instancia.
---
### Preguntas tipo examen (con respuestas) ✅
1. ¿Qué recibe siempre un `@classmethod` como primer argumento? — `cls` (la clase que llama).
2. ¿Es `@staticmethod` heredable por una subclase? — Sí, pero no obtiene ninguna referencia a la subclase a menos que se le pase explícitamente.
3. Verdadero/Falso: Un `@classmethod` puede llamar a otros `@classmethod` usando `cls`. — Verdadero.
---
### Conexión con `Pips` (enum) 🧩
`Pips.reversedValues()` y `Pips.minus()` están definidos como `@classmethod` en el `Enum`: eso tiene sentido porque operan sobre los miembros de la clase `Enum` y su comportamiento debe ser coherente con la clase, no con una instancia concreta.
---
### Referencia `cls`
`cls` es el primer parámetro obligatorio de un `@classmethod`: recibe la clase que invoca el método, no una instancia de la clase (`self`).
Permite acceder a atributos y otros métodos de la misma clase, y respeta la herencia (si una subclase llama, cls apunta a esa subclase).
### Ejemplos en `yatzy_refactored.py`
```python
@classmethod
def pair(cls, *dice):
PAIR = Pips.TWO.value
pip = cls.__biggest_pip_repeated(dice, PAIR)
return pip * PAIR if pip else cls.ZERO
```
`@classmethod def pair(cls, *dice)` calcula la suma de los dos mayores dados iguales, sino devuelve cero.
Reutiliza el método de clase `cls.__biggest_pip_repeated` que devuelve los dos mayores dados que coinciden. Este método encapsula una responsabilidad que es reutilizada por otros métodos de la clase. Tiene visibilidad *privada*.
Para acceder a este método de clase, usamos la referencia `cls` desde los métodos que lo invocan.
Además, necesitamos acceder al valor `ZERO`, una constante de la clase, pues es utilizada por otros métodos de la clase para las computaciones de los dados. La refencia a esa constante en la clase es `cls.ZERO`.
Si una subclase redefine `__biggest_pip_repeated`, cls garantiza que se use la versión de la subclase.
El *helpers* privado `__biggest_pip_repeated` acepta `cls` para poder operar sobre la clase (y sus posibles subclases) al filtrar pips.
### Tipos de referencias
`self` → instancia (estado concreto).
`cls` → clase: configuración/versión actual de la clase, útil para herencia.
Sin `self` ni `cls` → `@staticmethod` o función pura vinculada solo conceptualmente a la clase.
---
### Prompt
`#file:yatzy_refactored.py #file:test_yatzy_from_scratch.py #file:pips.py`
He resuelto el kata Yatzy sobre refactorización.
Utilizo este kata como introducción a la programación orientada a objetos en Python y, en particular, a los distintos tipos de métodos de clase: `staticmethod` y `classmethod`.
Redacta un fichero markdown para mi alumnado de formación profesional de desarrollo de aplicaciones multiplataforma en el módulo de programación donde expliques los conceptos de programación Python y orientada a objetos sobre los métodos de clase `staticmethod` y `classmethod` utilizando los métodos del fichero #file:yatzy_refactored.py como ejemplo.
cls
El manual de referencia que usamos en el aula es Learning Python de Mark Lutz.