Zbyt wiele stwierdzeń „jeśli”?

263

Poniższy kod działa tak, jak powinienem, ale jest brzydki, przesadny lub zawiera wiele innych rzeczy. Patrzyłem na formuły i próbowałem napisać kilka rozwiązań, ale ostatecznie otrzymałem podobną liczbę instrukcji.

Czy istnieje jakiś wzór matematyczny, który przydałby mi się w tym przypadku, czy 16, jeśli stwierdzenia są dopuszczalne?

Aby wyjaśnić kod, jest to rodzaj gry opartej na jednoczesnych turach. Dwóch graczy ma po cztery przyciski akcji, a wyniki pochodzą z tablicy (0-3), ale zmienne „one” i „two” mogą być przypisał cokolwiek, jeśli to pomoże. Wynik jest następujący: 0 = żadna wygrana, 1 = wygrana p1, 2 = wygrana p2, 3 = oba wygrane.

public int fightMath(int one, int two) {

    if(one == 0 && two == 0) { result = 0; }
    else if(one == 0 && two == 1) { result = 0; }
    else if(one == 0 && two == 2) { result = 1; }
    else if(one == 0 && two == 3) { result = 2; }
    else if(one == 1 && two == 0) { result = 0; }
    else if(one == 1 && two == 1) { result = 0; }
    else if(one == 1 && two == 2) { result = 2; }
    else if(one == 1 && two == 3) { result = 1; }
    else if(one == 2 && two == 0) { result = 2; }
    else if(one == 2 && two == 1) { result = 1; }
    else if(one == 2 && two == 2) { result = 3; }
    else if(one == 2 && two == 3) { result = 3; }
    else if(one == 3 && two == 0) { result = 1; }
    else if(one == 3 && two == 1) { result = 2; }
    else if(one == 3 && two == 2) { result = 3; }
    else if(one == 3 && two == 3) { result = 3; }

    return result;
}
TomFirth
źródło
1
@waqaslam: - Czy to może pomóc w przełączeniu instrukcji Java do obsługi dwóch zmiennych?
Rahul Tripathi
9
Z pewnością jest tu jakaś logika, którą można uogólnić, a nie brutalnie wymusić? Z pewnością jest jakaś funkcja, f(a, b)która daje odpowiedź w ogólnym przypadku? Nie wyjaśniłeś logiki obliczeń, dlatego wszystkie odpowiedzi są tylko pomadką na świni. Zacznę od poważnego przemyślenia logiki programu, używanie intflag do akcji jest bardzo przestarzałe. enummogą zawierać logikę i mają charakter opisowy, dzięki czemu możesz pisać swój kod w bardziej nowoczesny sposób.
Boris the Spider
Po przeczytaniu odpowiedzi @ Steve Benett zawartych w alternatywnym pytaniu podanym powyżej, mogę założyć, że nie ma prostej odpowiedzi na formułę, ponieważ jest ona zasadniczo taka sama jak baza danych. Próbowałem wyjaśnić w pierwotnym pytaniu, że tworzyłem prostą grę (wojownik), a użytkownicy mają do wyboru 4 przyciski: blockHigh (0), blockLow (1), attackHigh (2) i attackLow (3). Liczby te są przechowywane w tablicy, dopóki nie będą potrzebne. Później są używane przez funkcję „fightMath ()”, która wywołuje wybory playerOne przeciwko playerTwos, aby dać wynik. Brak rzeczywistego wykrywania kolizji.
TomFirth
9
Jeśli masz odpowiedź, opublikuj ją jako taką. Dalsza dyskusja w komentarzach jest trudna do naśladowania, zwłaszcza gdy w grę wchodzi kod. Jeśli chcesz porozmawiać o tym, czy to pytanie powinno zostać przeniesione do Code Review, możesz na ten temat omówić Meta .
George Stocker
1
Co rozumiesz przez „to samo co baza danych”? Jeśli te wartości znajdują się w bazie danych, wyciągnij je stamtąd. W przeciwnym razie, jeśli to naprawdę jest tak skomplikowane, zostawiłbym to tak, jak masz, i dodawałem komentarze logiki biznesowej po każdej linii, aby ludzie rozumieli, co się dzieje. To jest lepsze (dla mnie) długie i wyraźne - ktoś w przyszłości może zrozumieć, co się dzieje. Jeśli umieścisz go na mapie lub spróbujesz zaoszczędzić 8 linii kodu, pozytywna strona jest naprawdę niewielka, a zmniejszenie jest większe: sprawiasz, że jest to coraz bardziej mylące dla kogoś, kto pewnego dnia musi przeczytać twój kod.
skaz

Odpowiedzi:

600

Jeśli nie możesz opracować formuły, możesz użyć tabeli dla tak ograniczonej liczby wyników:

final int[][] result = new int[][] {
  { 0, 0, 1, 2 },
  { 0, 0, 2, 1 },
  { 2, 1, 3, 3 },
  { 1, 2, 3, 3 }
};
return result[one][two];
laalto
źródło
7
Jest to interesujące, ponieważ wcześniej nie widziałem tego rozwiązania. Nie jestem pewien, czy rozumiem wynik zwrotu, ale z przyjemnością to przetestuję.
TomFirth
4
Nie potrzebujesz twierdzenia, Java i tak wyrzuci, IndexOutOfBoundsExceptionjeśli jeden lub więcej indeksów jest poza zakresem.
JAB
43
@JoeHarper Jeśli chcesz czegoś łatwego do odczytania, w pierwszej kolejności nie będziesz używać magicznych liczb, a będziesz mieć komentarz wyjaśniający mapowanie. W tej chwili wolę tę wersję od oryginalnej, ale dla czegoś, co można utrzymać w dłuższej perspektywie, zastosowałbym podejście obejmujące wyliczone typy lub przynajmniej nazwane stałe.
JAB
13
@JoeHarper „Teoretycznie” to jedno, „praktycznie” to drugie. Oczywiście staram się używać opisowego nazewnictwa (z wyjątkiem konwencji i/ j/ kdla zmiennych pętli), nazwanych stałych, porządkowania kodu w czytelny sposób itp., Ale kiedy nazwy zmiennych i funkcji zaczynają zajmować więcej niż 20 znaków każdy, co znajduję, prowadzi do mniej czytelnego kodu. Moje zwykłe podejście polega na wypróbowaniu zrozumiałego, ale zwięzłego kodu z komentarzami tu i tam, aby wyjaśnić, dlaczego kod ma taką strukturę (a nie jak). Umieszczając dlaczego w nazwach po prostu zaśmieca wszystko.
JAB
13
Podoba mi się to rozwiązanie tego konkretnego problemu, ponieważ wyniki faktycznie są podyktowane macierzą wyników.
Próbowanie
201

Ponieważ twój zestaw danych jest tak mały, możesz spakować wszystko w 1 długą liczbę całkowitą i przekształcić go w formułę

public int fightMath(int one,int two)
{
   return (int)(0xF9F66090L >> (2*(one*4 + two)))%4;
}

Bardziej wariant bitowy:

Wykorzystuje to fakt, że wszystko jest wielokrotnością liczby 2

public int fightMath(int one,int two)
{
   return (0xF9F66090 >> ((one << 3) | (two << 1))) & 0x3;
}

Geneza magicznej stałej

Co mogę powiedzieć? Świat potrzebuje magii, czasem możliwość czegoś wymaga jej stworzenia.

Istotą funkcji, która rozwiązuje problem OP, jest mapa od 2 liczb (jedna, dwie), dziedzina {0,1,2,3} do zakresu {0,1,2,3}. Każda z odpowiedzi dotyczyła sposobu wdrożenia tej mapy.

Ponadto w wielu odpowiedziach można zobaczyć powtórzenie problemu jako mapę 1 2-cyfrowej podstawy 4 liczby N (jeden, dwa), gdzie jedna to cyfra 1, dwie to cyfra 2, a N = 4 * jeden + dwa; N = {0,1,2, ..., 15} - szesnaście różnych wartości, to ważne. Wyjściem funkcji jest jedna 1-cyfrowa liczba podstawowa 4 {0,1,2,3} - 4 różne wartości, również ważne.

Teraz 1-cyfrowy numer podstawowy 4 można wyrazić jako 2-cyfrowy numer podstawowy 2; {0,1,2,3} = {00,01,10,11}, więc każde wyjście może być zakodowane tylko za pomocą 2 bitów. Z góry możliwe jest tylko 16 różnych wyjść, więc 16 * 2 = 32 bity to wszystko, co jest konieczne do zakodowania całej mapy; to wszystko może zmieścić się w 1 liczbie całkowitej.

Stała M jest kodowaniem mapy m, gdzie m (0) jest kodowany w bitach M [0: 1], m (1) jest kodowany w bitach M [2: 3], a m (n) jest kodowany w bitach M [n * 2: n * 2 + 1].

Pozostaje tylko indeksowanie i zwracanie prawej części stałej, w tym przypadku możesz przesunąć M w prawo 2 * N razy i wziąć 2 najmniej znaczące bity, czyli (M >> 2 * N) i 0x3. Wyrażenia (jedno << 3) i (dwa << 1) po prostu mnożą rzeczy, zauważając, że 2 * x = x << 1 i 8 * x = x << 3.

waTeim
źródło
79
sprytne, ale nikt inny nie czytając kodu nie będzie w stanie go zrozumieć.
Aran Mulholland
106
Myślę, że to bardzo zła praktyka. Nikt inny oprócz autora nie zrozumie tego. Chcesz spojrzeć na fragment kodu i szybko go zrozumieć. Ale to tylko strata czasu.
Balázs Németh
14
W tej sprawie jestem z @ BalázsMáriaNémeth. Choć bardzo imponujące, powinieneś kodować agresywnych psychopatów!
OrhanC1
90
Wszyscy downvoters uważają, że jest to ohydny zapach kodu. Wszyscy zwycięzcy myślą tak samo, ale podziwiają spryt za tym. +1 (Nigdy nie używaj tego kodu)
usr
4
Cóż za piękny przykład pisania tylko kodu !
lealand
98

Nie podoba mi się żadne z przedstawionych rozwiązań oprócz JAB. Żadne z pozostałych nie ułatwia odczytania kodu i zrozumienia, co jest obliczane .

Oto jak napisałbym ten kod - znam tylko C #, a nie Java, ale otrzymujesz obraz:

const bool t = true;
const bool f = false;
static readonly bool[,] attackResult = {
    { f, f, t, f }, 
    { f, f, f, t },
    { f, t, t, t },
    { t, f, t, t }
};
[Flags] enum HitResult 
{ 
    Neither = 0,
    PlayerOne = 1,
    PlayerTwo = 2,
    Both = PlayerOne | PlayerTwo
}
static HitResult ResolveAttack(int one, int two)
{
    return 
        (attackResult[one, two] ? HitResult.PlayerOne : HitResult.Neither) | 
        (attackResult[two, one] ? HitResult.PlayerTwo : HitResult.Neither);
}    

Teraz jest o wiele bardziej jasne, co jest tutaj obliczane: podkreśla to, że obliczamy, kto zostanie trafiony jakim atakiem, i zwracamy oba wyniki.

Jednak może być jeszcze lepiej; ta tablica boolowska jest nieco nieprzejrzysta. Podoba mi się podejście do wyszukiwania tabel, ale chciałbym napisać to w taki sposób, aby było jasne, jaka jest zamierzona semantyka gry. Oznacza to, że zamiast „zero ataku i obrona jednego nie daje żadnego trafienia”, zamiast tego znajdź sposób, aby kod wyraźniej oznaczał „atak niskim kopnięciem i obrona bloku niskim skutkiem braku trafienia”. Spraw, by kod odzwierciedlał logikę biznesową gry.

Eric Lippert
źródło
66
Nonsens. Większość średnio doświadczonych programów będzie w stanie docenić udzieloną tu radę i zastosować styl kodowania do własnego języka. Pytanie brzmiało, jak uniknąć ciągu ifs. To pokazuje jak.
GreenAsJade
6
@ user3414693: Wiem, że jest to pytanie Java. Jeśli dokładnie przeczytasz odpowiedź, stanie się jasna. Jeśli uważasz, że moja odpowiedź jest nierozsądna, zachęcam do napisania własnej odpowiedzi, którą lubisz bardziej.
Eric Lippert,
1
@EricLippert Lubię też rozwiązanie JAB. IMHO, typ enum w C # pozostawia wiele do życzenia. Nie podąża za filozofią sukcesu, którą spełniają pozostałe funkcje. Np. Stackoverflow.com/a/847353/92414 Czy zespół c # ma jakiś plan stworzenia nowego typu wyliczenia (aby uniknąć uszkodzenia istniejącego kodu), który jest lepiej zaprojektowany?
SolutionYogi
@SolutionYogi: Nie bardzo lubię wyliczenia w języku C #, chociaż są takie, jakie są z dobrych powodów historycznych. (Głównie ze względu na kompatybilność z istniejącymi wyliczeniami COM.) Nie jestem świadomy żadnych planów dodania nowego sprzętu do wyliczeń w C # 6.
Eric Lippert
3
@SList nie, komentarze nie są wyświetlane. OP zrobił dokładnie to, co należy zrobić; konwertuj komentarze na czysty kod. Zobacz np. Kod Steve'a McConnella Complete stevemcconnell.com/cccntnt.htm
djechlin
87

Możesz utworzyć macierz, która zawiera wyniki

int[][] results = {{0, 0, 1, 2}, {0, 0, 2, 1},{2, 1, 3, 3},{2, 1, 3, 3}};

Kiedy chcesz uzyskać wartość, skorzystasz

public int fightMath(int one, int two) {
  return this.results[one][two]; 
}
djm.im
źródło
69

Inne osoby już zasugerowały mój początkowy pomysł, metodę matrycową, ale oprócz konsolidacji instrukcji if można uniknąć części tego, co masz, upewniając się, że dostarczone argumenty znajdują się w oczekiwanym zakresie i używając zwrotów w miejscu (niektóre kodowanie standardy, które widziałem, egzekwują jeden punkt wyjścia dla funkcji, ale zauważyłem, że wielokrotne zwroty są bardzo przydatne do unikania kodowania strzałek, a przy powszechności wyjątków w Javie i tak nie ma sensu ścisłe egzekwowanie takiej reguły ponieważ każdy niewyłapany wyjątek zgłoszony w metodzie jest możliwym punktem wyjścia). Zagnieżdżanie instrukcji przełączania jest możliwe, ale dla małego zakresu wartości, które tu sprawdzasz, stwierdzam, czy instrukcje są bardziej zwarte i raczej nie spowodują dużej różnicy wydajności,

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
}

W efekcie jest to mniej czytelne, niż mogłoby to wynikać z nieregularności części mapowania danych wejściowych i wyników. Zamiast tego preferuję styl matrycy ze względu na jego prostotę i sposób, w jaki można ustawić matrycę, aby miała sens wizualny (choć częściowo na to wpływ mają moje wspomnienia map Karnaugh):

int[][] results = {{0, 0, 1, 2},
                   {0, 0, 2, 1},
                   {2, 1, 3, 3},
                   {2, 1, 3, 3}};

Aktualizacja: Biorąc pod uwagę Twoją wzmiankę o blokowaniu / trafianiu, oto bardziej radykalna zmiana funkcji, która korzysta z wyliczonych typów własności / posiadania atrybutów dla danych wejściowych i wyniku, a także nieco modyfikuje wynik w celu uwzględnienia blokowania, co powinno skutkować większym funkcja czytelna.

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;
}

Nie musisz nawet zmieniać samej funkcji, jeśli chcesz dodawać bloki / ataki o większej wysokości, tylko wyliczenia; dodanie dodatkowych rodzajów ruchów prawdopodobnie będzie wymagać modyfikacji funkcji. Ponadto, EnumSets może być bardziej rozszerzalne niż użycie dodatkowych wyliczeń jako właściwości głównego wyliczenia, np. EnumSet<Move> attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...);A następnie attacks.contains(move)zamiast move.type == MoveType.ATTACK, chociaż użycie EnumSets będzie prawdopodobnie nieco wolniejsze niż bezpośrednie sprawdzanie równości.


Dla przypadku, gdy udany blok skutkuje w liczniku, można zastąpić if (one.height == two.height) return LandedHit.NEITHER;z

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;
}

Również zastąpienie niektórych ifinstrukcji użyciem operatora trójskładnikowego ( boolean_expression ? result_if_true : result_if_false) może uczynić kod bardziej zwartym (na przykład kod w poprzednim bloku stałby się return one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;), ale może to prowadzić do trudniejszych do odczytania onelinerów, więc nie Polecam go do bardziej skomplikowanych rozgałęzień.

UKŁUCIE
źródło
Na pewno przyjrzę się temu, ale mój obecny kod pozwala mi użyć wartości int onei twobyć ponownie użyty jako punkty początkowe w moim arkuszu sprite. Chociaż nie wymaga to dużo dodatkowego kodu, aby to umożliwić.
TomFirth
2
@ TomFirth84 Istnieje EnumMapklasa, której można użyć do odwzorowania wyliczeń na przesunięcia liczb całkowitych (możesz także użyć wartości porządkowych członków wyliczenia bezpośrednio, np. Move.ATTACK_HIGH.ordinal()Byłby 0, Move.ATTACK_LOW.ordinal()byłby 1itp., Ale jest to bardziej kruche / mniej elastyczne niż jawnie kojarzenie każdego członka z wartością jako dodawanie wartości wyliczeniowych pomiędzy istniejącymi pomniejszyłoby liczbę, co nie byłoby tak w przypadku EnumMap.)
JAB
7
Jest to najbardziej czytelne rozwiązanie, ponieważ tłumaczy kod na coś, co ma znaczenie dla osoby czytającej kod.
David Stanley,
Twój kod, przynajmniej ten wykorzystujący wyliczenia, jest niepoprawny. Według instrukcji if w PO udany blok prowadzi do trafienia atakującego. Ale +1 za znaczący kod.
Taemyr
2
Możesz nawet dodać attack(against)metodę do Movewyliczenia, zwracając HIT, gdy ruch jest udanym atakiem, POWRÓT, gdy ruch jest zablokowanym atakiem, i NIC, gdy nie jest atakiem. W ten sposób możesz zaimplementować go ogólnie ( public boolean attack(Move other) { if this.isAttack() return (other.isAttack() || other.height != this.height) ? HIT : BACKFIRE; return NOTHING; }) i zastąpić go w razie potrzeby konkretnymi ruchami (słabe ruchy, które może zablokować każdy blok, ataki, które nigdy nie odpalają itp.)
przepisano
50

Dlaczego nie skorzystać z tablicy?

Zacznę od początku. Widzę wzór, wartości zaczynają się od 0 do 3 i chcesz złapać wszystkie możliwe wartości. To jest twój stół:

0 & 0 = 0
0 & 1 = 0
0 & 2 = 1
0 & 3 = 2
1 & 0 = 0
1 & 1 = 0
1 & 2 = 2
1 & 3 = 1
2 & 0 = 2
2 & 1 = 1
2 & 2 = 3
2 & 3 = 3
3 & 0 = 2
3 & 1 = 1
3 & 2 = 3
3 & 3 = 3

patrząc na ten sam plik binarny tabeli, widzimy następujące wyniki:

00 & 00 = 00
00 & 01 = 00
00 & 10 = 01
00 & 11 = 10
01 & 00 = 00
01 & 01 = 00
01 & 10 = 10
01 & 11 = 01
10 & 00 = 10
10 & 01 = 01
10 & 10 = 11
10 & 11 = 11
11 & 00 = 10
11 & 01 = 01
11 & 10 = 11
11 & 11 = 11

Być może już widzisz jakiś wzorzec, ale kiedy połączę wartość pierwszą i drugą, widzę, że używasz wszystkich wartości 0000, 0001, 0010, ..... 1110 i 1111. Teraz połączmy wartość pierwszą i drugą, aby utworzyć jeden 4-bitowa liczba całkowita.

0000 = 00
0001 = 00
0010 = 01
0011 = 10
0100 = 00
0101 = 00
0110 = 10
0111 = 01
1000 = 10
1001 = 01
1010 = 11
1011 = 11
1100 = 10
1101 = 01
1110 = 11
1111 = 11

Kiedy tłumaczymy to z powrotem na wartości dziesiętne, widzimy bardzo możliwą tablicę wartości, w której jeden i dwa połączone mogą być użyte jako indeks:

0 = 0
1 = 0
2 = 1
3 = 2
4 = 0
5 = 0
6 = 2
7 = 1
8 = 2
9 = 1
10 = 3
11 = 3
12 = 2
13 = 1
14 = 3
15 = 3

Tablica jest wtedy {0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 2, 1, 3, 3} , gdzie jej indeks to po prostu jeden i dwa razem.

Nie jestem programistą Java, ale możesz pozbyć się wszystkich instrukcji if i zapisać to w następujący sposób:

int[] myIntArray = {0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 2, 1, 3, 3};
result = myIntArray[one * 4 + two]; 

Nie wiem, czy przesunięcie bitów o 2 jest szybsze niż mnożenie. Ale warto spróbować.

dj bazzie wazzie
źródło
2
Przesunięcie bitowe o wartości 2 jest prawie zdecydowanie szybsze niż pomnożenie 4. W najlepszym razie pomnożenie przez 4 rozpoznałoby, że 4 to 2 ^ 2 i samo wykonało przesunięcie bitowe (potencjalnie przetłumaczone przez kompilator). Szczerze mówiąc, dla mnie zmiana jest bardziej czytelna.
Cruncher
Podoba mi się twoje podejście! Zasadniczo spłaszcza macierz 4x4 do 16-elementowej tablicy.
Cameron Tinker
6
W dzisiejszych czasach, jeśli się nie mylę, kompilator bez wątpienia rozpozna, że ​​mnożymy przez potęgę dwóch i odpowiednio go zoptymalizujemy. Zatem dla ciebie programista, zmiana bitów i mnożenie powinny mieć dokładnie taką samą wydajność.
Tanner Swett
24

To używa trochę bitmagii (już to robisz, trzymając dwa bity informacji (niski / wysoki i atak / blok) w jednej liczbie całkowitej):

Nie uruchomiłem go, wpisałem tylko tutaj, proszę dwukrotnie sprawdzić. Pomysł na pewno działa. EDYCJA: Jest teraz testowany dla każdego wejścia, działa dobrze.

public int fightMath(int one, int two) {
    if(one<2 && two<2){ //both players blocking
        return 0; // nobody hits
    }else if(one>1 && two>1){ //both players attacking
        return 3; // both hit
    }else{ // some of them attack, other one blocks
        int different_height = (one ^ two) & 1; // is 0 if they are both going for the same height - i.e. blocker wins, and 1 if height is different, thus attacker wins
        int attacker = one>1?1:0; // is 1 if one is the attacker, two is the blocker, and 0 if one is the blocker, two is the attacker
        return (attacker ^ different_height) + 1;
    }
}

Czy powinienem zasugerować rozdzielenie dwóch bitów informacji na osobne zmienne? Kod oparty głównie na operacjach bitowych takich jak ten powyżej jest zwykle bardzo trudny do utrzymania.

elias
źródło
2
Zgadzam się z tym rozwiązaniem, wygląda bardzo podobnie do tego, co miałem na myśli w moim komentarzu do głównego pytania. Wolałbym podzielić go na osobne zmienne, co ułatwiłoby dodanie środkowego ataku, na przykład w przyszłości.
Joh
2
Właśnie poprawiłem kilka błędów w powyższym kodzie po przetestowaniu, teraz działa dobrze. Idąc dalej drogą manipulatora bitami, wymyśliłem również rozwiązanie jednoliniowe, które nadal nie jest tak mistyczne jak maska ​​bitowa w innych odpowiedziach, ale wciąż jest wystarczająco trudne, by zmarnować twój umysł:return ((one ^ two) & 2) == 0 ? (one & 2) / 2 * 3 : ((one & 2) / 2 ^ ((one ^ two) & 1)) + 1;
Elias
1
To najlepsza odpowiedź, ponieważ każdy czytający ją programista zrozumie magię, jaka kryje się za wszystkimi tymi magicznymi liczbami.
bezmax
20

Szczerze mówiąc, każdy ma swój własny styl kodu. Nie sądziłbym, że wpłynie to zbytnio na wydajność. Jeśli rozumiesz to lepiej niż przy użyciu wersji skrzynki przełączników, kontynuuj korzystanie z tego.

Możesz zagnieżdżać ifs, więc potencjalnie może wystąpić niewielki wzrost wydajności ostatniego sprawdzania, ponieważ nie przejdzie tak wielu instrukcji if. Ale w kontekście podstawowego kursu java prawdopodobnie nie przyniesie to korzyści.

else if(one == 3 && two == 3) { result = 3; }

Więc zamiast ...

if(one == 0 && two == 0) { result = 0; }
else if(one == 0 && two == 1) { result = 0; }
else if(one == 0 && two == 2) { result = 1; }
else if(one == 0 && two == 3) { result = 2; }

Zrobiłbyś ...

if(one == 0) 
{ 
    if(two == 0) { result = 0; }
    else if(two == 1) { result = 0; }
    else if(two == 2) { result = 1; }
    else if(two == 3) { result = 2; }
}

I po prostu sformatuj go tak, jak wolisz.

Nie poprawia to wyglądu kodu, ale moim zdaniem może go nieco przyspieszyć.

Joe Harper
źródło
3
Nie wiem, czy to naprawdę dobra praktyka, ale w tym przypadku prawdopodobnie użyłbym zagnieżdżonych instrukcji switch. Zajmie to więcej miejsca, ale byłoby naprawdę jasne.
dyesdyes
To też by działało, ale myślę, że to kwestia preferencji. Właściwie wolę, jeśli instrukcje, ponieważ praktycznie mówią, co robi kod. Nie odkładając oczywiście opinii, cokolwiek Ci odpowiada :). Poproś jednak o alternatywną sugestię!
Joe Harper
12

Zobaczmy, co wiemy

1: twoje odpowiedzi są symetryczne dla P1 (gracz pierwszy) i P2 (gracz drugi). Ma to sens w grze walki, ale jest również coś, co możesz wykorzystać, aby poprawić swoją logikę.

2: 3 uderzenia 0 uderzeń 2 uderzenia 1 uderzenia 3. Jedynymi przypadkami nieobjętymi tymi przypadkami są kombinacje 0 vs 1 i 2 vs 3. Innymi słowy, unikalna tabela zwycięstwa wygląda następująco: 0 uderzeń 2, 1 uderzeń 3, 2 uderzenia 1, 3 uderzenia 0.

3: Jeśli 0/1 zmierzy się ze sobą, wówczas nastąpi remis bez trafień, ale jeśli 2/3 pójdzie przeciwko sobie, oba trafią

Po pierwsze, zbudujmy funkcję jednokierunkową, która mówi nam, czy wygraliśmy:

// returns whether we beat our opponent
public boolean doesBeat(int attacker, int defender) {
  int[] beats = {2, 3, 1, 0};
  return defender == beats[attacker];
}

Następnie możemy użyć tej funkcji do skomponowania końcowego wyniku:

// returns the overall fight result
// bit 0 = one hits
// bit 1 = two hits
public int fightMath(int one, int two)
{
  // Check to see whether either has an outright winning combo
  if (doesBeat(one, two))
    return 1;

  if (doesBeat(two, one))
    return 2;

  // If both have 0/1 then its hitless draw but if both have 2/3 then they both hit.
  // We can check this by seeing whether the second bit is set and we need only check
  // one's value as combinations where they don't both have 0/1 or 2/3 have already
  // been dealt with 
  return (one & 2) ? 3 : 0;
}

Chociaż jest to prawdopodobnie bardziej skomplikowane i prawdopodobnie wolniejsze niż wyszukiwanie tabel oferowane w wielu odpowiedziach, uważam, że jest to doskonała metoda, ponieważ faktycznie zawiera logikę twojego kodu i opisuje go każdemu, kto czyta twój kod. Myślę, że dzięki temu jest to lepsze wdrożenie.

(Minęło trochę czasu, odkąd zrobiłem jakąkolwiek Javę, więc przepraszam, jeśli składnia jest wyłączona, mam nadzieję, że nadal jest zrozumiała, jeśli źle ją popełniam)

Nawiasem mówiąc, 0-3 wyraźnie coś znaczy ; nie są to wartości arbitralne, więc warto je nazwać.

Jack Aidley
źródło
11

Mam nadzieję, że poprawnie rozumiem logikę. Co powiesz na coś takiego:

public int fightMath (int one, int two)
{
    int oneHit = ((one == 3 && two != 1) || (one == 2 && two != 0)) ? 1 : 0;
    int twoHit = ((two == 3 && one != 1) || (two == 2 && one != 0)) ? 2 : 0;

    return oneHit+twoHit;
}

Sprawdzanie jednego wysokiego trafienia lub jednego niskiego trafienia nie jest blokowane i to samo dla gracza drugiego.

Edycja: Algorytm nie został w pełni zrozumiany, „trafienie” przyznawane podczas blokowania, którego nie zdawałem sobie sprawy (Thx elias):

public int fightMath (int one, int two)
{
    int oneAttack = ((one == 3 && two != 1) || (one == 2 && two != 0)) ? 1 : (one >= 2) ? 2 : 0;
    int twoAttack = ((two == 3 && one != 1) || (two == 2 && one != 0)) ? 2 : (two >= 2) ? 1 : 0;

    return oneAttack | twoAttack;
}
Chris
źródło
Sposób na znalezienie świetnego wyniku!
Czad
1
Podoba mi się to podejście, ale obawiam się, że w tym rozwiązaniu całkowicie brakuje możliwości trafienia przez zablokowanie ataku (np. Jeśli jeden = 0, a drugi = 2, zwraca 0, ale oczekuje się 1 zgodnie ze specyfikacją). Być może możesz popracować nad tym, aby zrobić to dobrze, ale nie jestem pewien, czy wynikowy kod byłby nadal tak elegancki, ponieważ oznacza to, że linie będą się nieco wydłużać.
elias
Nie zdawałem sobie sprawy, że „blok” został przyznany za blok. Dzięki za zwrócenie na to uwagi. Dostosowane za pomocą bardzo prostej poprawki.
Chris
10

Nie mam doświadczenia z Javą, więc mogą występować literówki. Proszę traktować kod jako pseudo-kod.

Poszedłbym z prostym przełącznikiem. W tym celu potrzebujesz oceny pojedynczego numeru. Jednak w tym przypadku, ponieważ 0 <= one < 4 <= 9i 0 <= two < 4 <= 9, możemy przekonwertować obie inty na proste int, mnożąc oneprzez 10 i dodając two. Następnie użyj przełącznika w wynikowej liczbie w następujący sposób:

public int fightMath(int one, int two) {
    // Convert one and two to a single variable in base 10
    int evaluate = one * 10 + two;

    switch(evaluate) {
        // I'd consider a comment in each line here and in the original code
        // for clarity
        case 0: result = 0; break;
        case 1: result = 0; break;
        case 1: result = 0; break;
        case 2: result = 1; break;
        case 3: result = 2; break;
        case 10: result = 0; break;
        case 11: result = 0; break;
        case 12: result = 2; break;
        case 13: result = 1; break;
        case 20: result = 2; break;
        case 21: result = 1; break;
        case 22: result = 3; break;
        case 23: result = 3; break;
        case 30: result = 1; break;
        case 31: result = 2; break;
        case 32: result = 3; break;
        case 33: result = 3; break;
    }

    return result;
}

Jest jeszcze jedna krótka metoda, którą chcę wskazać jako kod teoretyczny. Jednak nie użyłbym tego, ponieważ ma dodatkową złożoność, z którą normalnie nie chcesz sobie poradzić. Dodatkowa złożoność wynika z podstawy 4 , ponieważ liczenie wynosi 0, 1, 2, 3, 10, 11, 12, 13, 20, ...

public int fightMath(int one, int two) {
    // Convert one and two to a single variable in base 4
    int evaluate = one * 4 + two;

    allresults = new int[] { 0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 1, 2, 3, 3 };

    return allresults[evaluate];
}

Naprawdę tylko dodatkowa uwaga, na wypadek, gdyby coś mi brakowało w Javie. W PHP zrobiłbym:

function fightMath($one, $two) {
    // Convert one and two to a single variable in base 4
    $evaluate = $one * 10 + $two;

    $allresults = array(
         0 => 0,  1 => 0,  2 => 1,  3 => 2,
        10 => 0, 11 => 0, 12 => 2, 13 => 1,
        20 => 2, 21 => 1, 22 => 3, 23 => 3,
        30 => 1, 31 => 2, 32 => 3, 33 => 3 );

    return $allresults[$evaluate];
}
Francisco Presencia
źródło
Java nie ma lamdów przed 8. wersją
Kirill Gamazkov
1
To. Dla tak małej liczby danych wejściowych użyłbym przełącznika o wartości złożonej (chociaż może być bardziej czytelny przy mnożniku większym niż 10, takim jak 100 lub 1000).
Medinoc
7

Ponieważ wolisz zagnieżdżone ifwarunki warunkowe, oto inny sposób.
Zauważ, że nie używa on resultczłonka i nie zmienia żadnego stanu.

public int fightMath(int one, int two) {
    if (one == 0) {
      if (two == 0) { return 0; }
      if (two == 1) { return 0; }
      if (two == 2) { return 1; }
      if (two == 3) { return 2; }
    }   
    if (one == 1) {
      if (two == 0) { return 0; }
      if (two == 1) { return 0; }
      if (two == 2) { return 2; }
      if (two == 3) { return 1; }
    }
    if (one == 2) {
      if (two == 0) { return 2; }
      if (two == 1) { return 1; }
      if (two == 2) { return 3; }
      if (two == 3) { return 3; }
    }
    if (one == 3) {
      if (two == 0) { return 1; }
      if (two == 1) { return 2; }
      if (two == 2) { return 3; }
      if (two == 3) { return 3; }
    }
    return DEFAULT_RESULT;
}
Nick Dandoulakis
źródło
dlaczego nie masz nic innego?
FDinoff,
3
@FDinoff Mógłbym użyć elsełańcuchów, ale to nie miało znaczenia.
Nick Dandoulakis
1
Wiem, że jest to banalne, ale czy dodawanie łańcuchów else nie byłoby wykonywane szybciej? w 3 z 4 przypadków? Zawsze mam zwyczaj pisania kodu, aby wykonać go tak szybko, jak to możliwe, nawet jeśli jest to tylko kilka cykli.
Brandon Bearden,
2
@BrandonBearden tutaj nie zrobią żadnej różnicy (zakładając, że dane wejściowe są zawsze w zakresie 0..3). Kompilator prawdopodobnie i tak zoptymalizuje kod. Jeśli mamy długą serię else ifinstrukcji, możemy przyspieszyć kod za pomocą switchlub tabel przeglądowych.
Nick Dandoulakis,
Jak to się dzieje? Jeśli one==0uruchomi kod, będzie musiał sprawdzić, czy one==1wtedy, one==2a jeśli w końcu jeszcze one==3- Jeśli byłyby jeszcze inne, gdyby tam był, nie wykonałby trzech ostatnich kontroli, ponieważ zamknąłby instrukcję po pierwszym dopasowaniu. I tak, możesz dalej optymalizować, używając instrukcji switch zamiast if (one...instrukcji, a następnie używając innego przełącznika w obudowie one's. To jednak nie moje pytanie.
Brandon Bearden
6

Wypróbuj z obudową przełącznika. ..

Zajrzyj tutaj lub tutaj, aby uzyskać więcej informacji na ten temat

switch (expression)
{ 
  case constant:
        statements;
        break;
  [ case constant-2:
        statements;
        break;  ] ...
  [ default:
        statements;
        break;  ] ...
}

Możesz dodać do niego wiele warunków (nie jednocześnie), a nawet mieć domyślną opcję której żadne inne przypadki nie zostały spełnione.

PS: Tylko jeśli jeden warunek ma być spełniony ..

Jeśli jednocześnie wystąpią 2 warunki .. Nie sądzę, aby można było użyć przełącznika. Ale możesz zmniejszyć swój kod tutaj.

Instrukcja Java przełączania wielu przypadków

Nevin Madhukar K.
źródło
6

Pierwszą rzeczą, która przyszła mi do głowy, była zasadniczo ta sama odpowiedź udzielona przez Francisco Presencia, ale nieco zoptymalizowana:

public int fightMath(int one, int two)
{
    switch (one*10 + two)
    {
    case  0:
    case  1:
    case 10:
    case 11:
        return 0;
    case  2:
    case 13:
    case 21:
    case 30:
        return 1;
    case  3:
    case 12:
    case 20:
    case 31:
        return 2;
    case 22:
    case 23:
    case 32:
    case 33:
        return 3;
    }
}

Możesz dalej go zoptymalizować, ustawiając ostatni przypadek (dla 3) jako domyślny:

    //case 22:
    //case 23:
    //case 32:
    //case 33:
    default:
        return 3;

Zaletą tej metody jest to, że łatwiej jest zobaczyć, które wartości onei twoktóre wartości zwracają, niż niektóre inne sugerowane metody.

David R. Tribble
źródło
To jest wariant innej mojej odpowiedzi tutaj .
David R Tribble
6
((two&2)*(1+((one^two)&1))+(one&2)*(2-((one^two)&1)))/2
Dawood ibn Kareem
źródło
4
Jak długo to trwało?
mbatchkarov
2
@mbatchkarov Około 10 minut czytania innych odpowiedzi, a następnie 10 minut bazgrania ołówkiem i papierem.
Dawood ibn Kareem
7
Byłbym naprawdę smutny, gdybym musiał to utrzymać.
Meryovi,
uhmmm ... Wystąpił błąd: zaginąłeś; --unicorns
Alberto
Zgadzam się z @Meryovi, rekwizyty za zwięzłość, ale okropne jak kod APL
Ed Griebel
3

Gdy rysuję tabelę między jednym a dwoma, a wynikiem, widzę jeden wzór,

if(one<2 && two <2) result=0; return;

Powyższe ograniczyłoby co najmniej 3, jeśli stwierdzenia. Nie widzę ustalonego wzorca ani nie jestem w stanie wiele wyciągnąć z podanego kodu - ale jeśli taka logika może zostać wyprowadzona, zmniejszyłoby to liczbę instrukcji if.

Mam nadzieję że to pomoże.

AnonNihcas
źródło
3

Dobrym pomysłem byłoby zdefiniowanie reguł jako tekstu, wówczas łatwiej będzie uzyskać prawidłową formułę. Jest to wyodrębnione z ładnej reprezentacji tablicy Laalto:

{ 0, 0, 1, 2 },
{ 0, 0, 2, 1 },
{ 2, 1, 3, 3 },
{ 1, 2, 3, 3 }

A oto kilka ogólnych uwag, ale powinieneś opisać je w kategoriach:

if(one<2) // left half
{
    if(two<2) // upper left half
    {
        result = 0; //neither hits
    }
    else // lower left half
    {
        result = 1+(one+two)%2; //p2 hits if sum is even
    }
}
else // right half
{
    if(two<2) // upper right half
    {
        result = 1+(one+two+1)%2; //p1 hits if sum is even
    }
    else // lower right half
    {
        return 3; //both hit
    }
}

Można oczywiście zmniejszyć to do mniejszej ilości kodu, ale ogólnie dobrym pomysłem jest zrozumienie tego, co piszesz, niż znalezienie kompaktowego rozwiązania.

if((one<2)&&(two<2)) result = 0; //top left
else if((one>1)&&(two>1)) result = 3; //bottom right
else result = 1+(one+two+((one>1)?1:0))%2; //no idea what that means

Wyjaśnienie skomplikowanych trafień p1 / p2 byłoby świetne, wygląda interesująco!

Marcellus
źródło
3

Najkrótsze i wciąż czytelne rozwiązanie:

static public int fightMath(int one, int two)
{
    if (one < 2 && two < 2) return 0;
    if (one > 1 && two > 1) return 3;
    int n = (one + two) % 2;
    return one < two ? 1 + n : 2 - n;
}

lub nawet krócej:

static public int fightMath(int one, int two)
{
    if (one / 2 == two / 2) return (one / 2) * 3;
    return 1 + (one + two + one / 2) % 2;
}

Nie zawiera żadnych „magicznych” liczb;) Mam nadzieję, że to pomaga.

PW
źródło
2
Takie formuły uniemożliwią zmianę (aktualizację) wyniku kombinacji w późniejszym terminie. Jedynym sposobem byłoby przerobienie całej formuły.
SNag
2
@SNag: Zgadzam się z tym. Najbardziej elastycznym rozwiązaniem jest użycie tablicy 2D. Ale autor tego postu chciał formuły, a ta jest najlepsza, jaką można uzyskać tylko za pomocą prostych instrukcji i matematyki.
PW
2

static int val(int i, int u){ int q = (i & 1) ^ (u & 1); return ((i >> 1) << (1 ^ q))|((u >> 1) << q); }

użytkownik1837841
źródło
1

Osobiście lubię kaskadować operatorów trójskładnikowych:

int result = condition1
    ? result1
    : condition2
    ? result2
    : condition3
    ? result3
    : resultElse;

Ale w twoim przypadku możesz użyć:

final int[] result = new int[/*16*/] {
    0, 0, 1, 2,
    0, 0, 2, 1,
    2, 1, 3, 3,
    1, 2, 3, 3
};

public int fightMath(int one, int two) {
    return result[one*4 + two];
}

Lub możesz zauważyć wzór w bitach:

one   two   result

section 1: higher bits are equals =>
both result bits are equals to that higher bits

00    00    00
00    01    00
01    00    00
01    01    00
10    10    11
10    11    11
11    10    11
11    11    11

section 2: higher bits are different =>
lower result bit is inverse of lower bit of 'two'
higher result bit is lower bit of 'two'

00    10    01
00    11    10
01    10    10
01    11    01
10    00    10
10    01    01
11    00    01
11    01    10

Możesz więc użyć magii:

int fightMath(int one, int two) {
    int b1 = one & 2, b2 = two & 2;
    if (b1 == b2)
        return b1 | (b1 >> 1);

    b1 = two & 1;

    return (b1 << 1) | (~b1);
}
Kirill Gamazkov
źródło
1

Oto dość zwięzła wersja, podobna do odpowiedzi JAB . Wykorzystuje mapę do przechowywania, która porusza się triumfalnie nad innymi.

public enum Result {
  P1Win, P2Win, BothWin, NeitherWin;
}

public enum Move {
  BLOCK_HIGH, BLOCK_LOW, ATTACK_HIGH, ATTACK_LOW;

  static final Map<Move, List<Move>> beats = new EnumMap<Move, List<Move>>(
      Move.class);

  static {
    beats.put(BLOCK_HIGH, new ArrayList<Move>());
    beats.put(BLOCK_LOW, new ArrayList<Move>());
    beats.put(ATTACK_HIGH, Arrays.asList(ATTACK_LOW, BLOCK_LOW));
    beats.put(ATTACK_LOW, Arrays.asList(ATTACK_HIGH, BLOCK_HIGH));
  }

  public static Result compare(Move p1Move, Move p2Move) {
    boolean p1Wins = beats.get(p1Move).contains(p2Move);
    boolean p2Wins = beats.get(p2Move).contains(p1Move);

    if (p1Wins) {
      return (p2Wins) ? Result.BothWin : Result.P1Win;
    }
    if (p2Wins) {
      return (p1Wins) ? Result.BothWin : Result.P2Win;
    }

    return Result.NeitherWin;
  }
} 

Przykład:

System.out.println(Move.compare(Move.ATTACK_HIGH, Move.BLOCK_LOW));

Wydruki:

P1Win
Duncan Jones
źródło
Poleciłbym static final Map<Move, List<Move>> beats = new java.util.EnumMap<>();zamiast tego, powinien być nieco bardziej wydajny.
JAB
@JAB Tak, dobry pomysł. Zawsze zapominam, że ten typ istnieje. I ... jak dziwnie jest je zbudować!
Duncan Jones
1

Użyłbym mapy, HashMap lub TreeMap

Zwłaszcza jeśli parametry nie są w formularzu 0 <= X < N

Jak zestaw losowych liczb całkowitych dodatnich ..

Kod

public class MyMap
{
    private TreeMap<String,Integer> map;

    public MyMap ()
    {
        map = new TreeMap<String,Integer> ();
    }

    public void put (int key1, int key2, Integer value)
    {
        String key = (key1+":"+key2);

        map.put(key, new Integer(value));
    }

    public Integer get (int key1, int key2)
    {
        String key = (key1+":"+key2);

        return map.get(key);
    }
}
Khaled.K
źródło
1

Dzięki @Joe Harper, kiedy skończyłem, używając odmiany jego odpowiedzi. Aby jeszcze bardziej zmniejszyć, ponieważ 2 wyniki na 4 były takie same, zmniejszyłem ją jeszcze bardziej.

W pewnym momencie mogę do tego wrócić, ale jeśli nie będzie poważnego oporu spowodowanego wieloma stwierdzeniami if, zatrzymam to na razie. Przeanalizuję macierz tabel i przejdę dalej do rozwiązań instrukcji.

public int fightMath(int one, int two) {
  if (one === 0) {
    if (two === 2) { return 1; }
    else if(two === 3) { return 2; }
    else { return 0; }
  } else if (one === 1) {
    if (two === 2) { return 2; }
    else if (two === 3) { return 1; }
    else { return 0; }
  } else if (one === 2) {
    if (two === 0) { return 2; }
    else if (two === 1) { return 1; }
    else { return 3; }
  } else if (one === 3) {
    if (two === 0) { return 1; }
    else if (two === 1) { return 2; }
    else { return 3; }
  }
}
TomFirth
źródło
13
Jest to w rzeczywistości mniej czytelne niż oryginał i nie zmniejsza liczby instrukcji if ...
Chad
@Chad Pomysł polegał na zwiększeniu szybkości procesu i chociaż wygląda to przerażająco, łatwo go zaktualizować, jeśli w przyszłości będę dodawał więcej działań. Mówiąc to, używam teraz poprzedniej odpowiedzi, której wcześniej nie do końca rozumiałem.
TomFirth,
3
@ TomFirth84 Czy istnieje powód, dla którego nie przestrzegasz odpowiednich konwencji kodowania instrukcji if?
ylun.ca
@ylun: Zmniejszyłem wiersze przed wklejeniem go na SO, nie ze względu na czytelność, ale ze względu na czystą przestrzeń na spam. Na tej stronie są różne praktyki i niestety jest to sposób, w jaki się nauczyłem i czuję się swobodnie.
TomFirth
2
@ TomFirth84 Nie sądzę, że można to łatwo zaktualizować, liczba linii rośnie podobnie jak iloczyn liczby dozwolonych wartości.
Andrew Lazarus,
0
  1. Użyj stałych lub wyliczeń, aby kod był bardziej czytelny
  2. Spróbuj podzielić kod na więcej funkcji
  3. Spróbuj użyć symetrii problemu

Oto sugestia, jak może to wyglądać, ale używanie ints jest nadal trochę brzydkie:

static final int BLOCK_HIGH = 0;
static final int BLOCK_LOW = 1;
static final int ATTACK_HIGH = 2;
static final int ATTACK_LOW = 3;

public static int fightMath(int one, int two) {
    boolean player1Wins = handleAttack(one, two);
    boolean player2Wins = handleAttack(two, one);
    return encodeResult(player1Wins, player2Wins); 
}



private static boolean handleAttack(int one, int two) {
     return one == ATTACK_HIGH && two != BLOCK_HIGH
        || one == ATTACK_LOW && two != BLOCK_LOW
        || one == BLOCK_HIGH && two == ATTACK_HIGH
        || one == BLOCK_LOW && two == ATTACK_LOW;

}

private static int encodeResult(boolean player1Wins, boolean player2Wins) {
    return (player1Wins ? 1 : 0) + (player2Wins ? 2 : 0);
}

Lepiej byłoby użyć typu strukturalnego dla danych wejściowych i wyjściowych. Dane wejściowe faktycznie mają dwa pola: pozycję i typ (blok lub atak). Dane wyjściowe mają również dwa pola: player1Wins i player2Wins. Kodowanie tego w jedną liczbę całkowitą utrudnia odczytanie kodu.

class PlayerMove {
    PlayerMovePosition pos;
    PlayerMoveType type;
}

enum PlayerMovePosition {
    HIGH,LOW
}

enum PlayerMoveType {
    BLOCK,ATTACK
}

class AttackResult {
    boolean player1Wins;
    boolean player2Wins;

    public AttackResult(boolean player1Wins, boolean player2Wins) {
        this.player1Wins = player1Wins;
        this.player2Wins = player2Wins;
    }
}

AttackResult fightMath(PlayerMove a, PlayerMove b) {
    return new AttackResult(isWinningMove(a, b), isWinningMove(b, a));
}

boolean isWinningMove(PlayerMove a, PlayerMove b) {
    return a.type == PlayerMoveType.ATTACK && !successfulBlock(b, a)
            || successfulBlock(a, b);
}

boolean successfulBlock(PlayerMove a, PlayerMove b) {
    return a.type == PlayerMoveType.BLOCK 
            && b.type == PlayerMoveType.ATTACK 
            && a.pos == b.pos;
}

Niestety Java nie jest zbyt dobra w wyrażaniu tego rodzaju typów danych.

peq
źródło
-2

Zamiast tego zrób coś takiego

   public int fightMath(int one, int two) {
    return Calculate(one,two)

    }


    private int Calculate(int one,int two){

    if (one==0){
        if(two==0){
     //return value}
    }else if (one==1){
   // return value as per condtiion
    }

    }
onkar
źródło
4
Właśnie utworzyłeś funkcję prywatną, która jest opakowana przez funkcję publiczną. Dlaczego nie zaimplementować tego w funkcji publicznej?
Martin Ueding
5
I nie zmniejszyłeś liczby instrukcji if.
Czad