Otras personas ya han sugerido mi idea inicial, el método de matriz, pero además de consolidar las declaraciones if, puede evitar algo de lo que tiene asegurándose de que los argumentos proporcionados estén en el rango esperado y utilizando retornos en el lugar (algunos códigos Los estándares que he visto imponen un punto de salida para las funciones, pero he encontrado que los retornos múltiples son muy útiles para evitar la codificación de flechas y, con la prevalencia de excepciones en Java, no tiene mucho sentido hacer cumplir estrictamente dicha regla de todos modos ya que cualquier excepción no detectada lanzada dentro del método es un posible punto de salida de todos modos). La posibilidad de anidar declaraciones de cambio es una posibilidad, pero para el pequeño rango de valores que está comprobando aquí, encuentro si las declaraciones son más compactas y no es probable que generen una gran diferencia de rendimiento,
public int fightMath(int one, int two) {
if (one > 3 || one < 0 || two > 3 || two < 0) {
throw new IllegalArgumentException("Result is undefined for arguments outside the range [0, 3]");
}
if (one <= 1) {
if (two <= 1) return 0;
if (two - one == 2) return 1;
return 2; // two can only be 3 here, no need for an explicit conditional
}
// one >= 2
if (two >= 2) return 3;
if (two == 1) return 1;
return 2; // two can only be 0 here
}
Esto termina siendo menos legible de lo que podría ser debido a la irregularidad de partes del mapeo input-> result. En su lugar, prefiero el estilo de matriz debido a su simplicidad y a cómo puede configurar la matriz para que tenga sentido visualmente (aunque eso está en parte influenciado por mis recuerdos de los mapas de Karnaugh):
int[][] results = {{0, 0, 1, 2},
{0, 0, 2, 1},
{2, 1, 3, 3},
{2, 1, 3, 3}};
Actualización: dada su mención de bloqueo / golpe, aquí hay un cambio más radical en la función que utiliza tipos enumerados de propiedad / retención de atributos para las entradas y el resultado y también modifica un poco el resultado para dar cuenta del bloqueo, lo que debería dar como resultado Función legible.
enum MoveType {
ATTACK,
BLOCK;
}
enum MoveHeight {
HIGH,
LOW;
}
enum Move {
// Enum members can have properties/attributes/data members of their own
ATTACK_HIGH(MoveType.ATTACK, MoveHeight.HIGH),
ATTACK_LOW(MoveType.ATTACK, MoveHeight.LOW),
BLOCK_HIGH(MoveType.BLOCK, MoveHeight.HIGH),
BLOCK_LOW(MoveType.BLOCK, MoveHeight.LOW);
public final MoveType type;
public final MoveHeight height;
private Move(MoveType type, MoveHeight height) {
this.type = type;
this.height = height;
}
/** Makes the attack checks later on simpler. */
public boolean isAttack() {
return this.type == MoveType.ATTACK;
}
}
enum LandedHit {
NEITHER,
PLAYER_ONE,
PLAYER_TWO,
BOTH;
}
LandedHit fightMath(Move one, Move two) {
// One is an attack, the other is a block
if (one.type != two.type) {
// attack at some height gets blocked by block at same height
if (one.height == two.height) return LandedHit.NEITHER;
// Either player 1 attacked or player 2 attacked; whoever did
// lands a hit
if (one.isAttack()) return LandedHit.PLAYER_ONE;
return LandedHit.PLAYER_TWO;
}
// both attack
if (one.isAttack()) return LandedHit.BOTH;
// both block
return LandedHit.NEITHER;
}
Ni siquiera tiene que cambiar la función en sí si desea agregar bloques / ataques de más alturas, solo las enumeraciones; Sin embargo, agregar tipos adicionales de movimientos probablemente requerirá la modificación de la función. Además, EnumSet
s podría ser más extensible que usar enumeraciones adicionales como propiedades de la enumeración principal, por ejemplo, EnumSet<Move> attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...);
y luego en attacks.contains(move)
lugar de hacerlo move.type == MoveType.ATTACK
, aunque el uso de EnumSet
s probablemente será un poco más lento que las comprobaciones de igualdad directa.
Para el caso donde un bloque exitoso resulta en un contador, puede reemplazarlo if (one.height == two.height) return LandedHit.NEITHER;
con
if (one.height == two.height) {
// Successful block results in a counter against the attacker
if (one.isAttack()) return LandedHit.PLAYER_TWO;
return LandedHit.PLAYER_ONE;
}
Además, reemplazar algunas de las if
declaraciones con el uso del operador ternario ( boolean_expression ? result_if_true : result_if_false
) podría hacer que el código sea más compacto (por ejemplo, el código en el bloque anterior se volvería return one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;
), pero eso puede dar lugar a líneas más difíciles de leer, por lo que no No lo recomiendo para ramificaciones más complejas.