Altre persone hanno già suggerito la mia idea iniziale, il metodo matrix, ma oltre a consolidare le istruzioni if puoi evitare parte di ciò che hai assicurandoti che gli argomenti forniti siano nell'intervallo previsto e utilizzando i rendimenti sul posto (alcuni codici gli standard che ho visto impongono un punto di uscita per le funzioni, ma ho scoperto che i ritorni multipli sono molto utili per evitare la codifica delle frecce e con la prevalenza delle eccezioni in Java non ha molto senso applicare rigorosamente una regola del genere poiché qualsiasi eccezione non rilevata generata all'interno del metodo costituisce comunque un possibile punto di uscita). Le istruzioni switch di nidificazione sono una possibilità, ma per il piccolo intervallo di valori che stai verificando qui trovo se le istruzioni sono più compatte e non rischiano di determinare una grande differenza di prestazioni,
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
}
Questo finisce per essere meno leggibile di quanto potrebbe altrimenti essere dovuto all'irregolarità di parti della mappatura input-> result. Preferisco invece lo stile matrice grazie alla sua semplicità e al modo in cui puoi impostare la matrice in modo che abbia senso visivamente (anche se questo è in parte influenzato dai miei ricordi delle mappe di Karnaugh):
int[][] results = {{0, 0, 1, 2},
{0, 0, 2, 1},
{2, 1, 3, 3},
{2, 1, 3, 3}};
Aggiornamento: data la tua menzione di blocco / colpire, ecco una modifica più radicale alla funzione che utilizza tipi enumerati di proprietà / possesso di attributi per gli input e il risultato e modifica anche un po 'il risultato per tenere conto del blocco, che dovrebbe tradursi in un funzione leggibile.
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;
}
Non devi nemmeno cambiare la funzione stessa se vuoi aggiungere blocchi / attacchi di più altezze, solo gli enum; l'aggiunta di ulteriori tipi di mosse richiederà probabilmente una modifica della funzione. Inoltre, EnumSet
s potrebbe essere più estensibile dell'utilizzo di enumerazioni extra come proprietà dell'enum principale, ad esempio EnumSet<Move> attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...);
e quindi attacks.contains(move)
piuttosto che move.type == MoveType.ATTACK
, sebbene l'uso di EnumSet
s sarà probabilmente leggermente più lento dei controlli di uguale diretto.
Nel caso in cui un blocco riuscito comporti un contatore, è possibile sostituirlo 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;
}
Inoltre, la sostituzione di alcune delle if
dichiarazioni con l'uso dell'operatore ternario ( boolean_expression ? result_if_true : result_if_false
) potrebbe rendere il codice più compatto (ad esempio, il codice nel blocco precedente diventerebbe return one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;
), ma ciò può portare a oneliners più difficili da leggere, quindi non vorrei lo consiglio per ramificazioni più complesse.