Outras pessoas já sugeriram minha ideia inicial, o método da matriz, mas, além de consolidar as instruções if, você pode evitar parte do que possui, certificando-se de que os argumentos fornecidos estejam no intervalo esperado e usando retornos no local (algumas codificações padrões que vi impor um ponto de saída para funções, mas descobri que vários retornos são muito úteis para evitar a codificação de seta e com a prevalência de exceções em Java, não há muito sentido em impor estritamente essa regra de qualquer maneira como qualquer exceção não capturada lançada dentro do método é um possível ponto de saída). Aninhar instruções de chave é uma possibilidade, mas, para o pequeno intervalo de valores que você está verificando aqui, acho que as instruções são mais compactas e provavelmente não resultam em muita diferença de desempenho,
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
}
Isso acaba sendo menos legível do que poderia ser devido à irregularidade de partes do mapeamento de entrada-> resultado. Eu prefiro o estilo da matriz devido à sua simplicidade e como você pode configurá-la para fazer sentido visualmente (embora isso seja em parte influenciado pelas minhas memórias dos mapas de Karnaugh):
int[][] results = {{0, 0, 1, 2},
{0, 0, 2, 1},
{2, 1, 3, 3},
{2, 1, 3, 3}};
Atualização: dada a sua menção de bloqueio / batida, aqui está uma mudança mais radical na função que utiliza tipos enumerados apropriados / contendo atributos para entradas e o resultado e também modifica um pouco o resultado para levar em conta o bloqueio, o que deve resultar em mais função legível.
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;
}
Você nem precisa alterar a função se quiser adicionar bloqueios / ataques de mais alturas, apenas as enumerações; adicionar tipos adicionais de movimentos provavelmente exigirá modificação da função. Além disso, EnumSet
s pode ser mais extensível do que usar enumerações extras como propriedades da enumeração principal, por exemplo, EnumSet<Move> attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...);
e então em attacks.contains(move)
vez de move.type == MoveType.ATTACK
, embora usar EnumSet
s provavelmente seja um pouco mais lento do que as verificações diretas de igual.
No caso de um bloco bem-sucedido resultar em um contador, você pode substituir if (one.height == two.height) return LandedHit.NEITHER;
por
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;
}
Além disso, a substituição de algumas if
instruções pelo uso do operador ternário ( boolean_expression ? result_if_true : result_if_false
) poderia tornar o código mais compacto (por exemplo, o código no bloco anterior se tornaria return one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;
), mas isso pode levar a oneliners mais difíceis de ler, então eu não faria ' não recomendo para ramificações mais complexas.