Jak mogę inkrementować zmienną bez przekraczania maksymalnej wartości?


89

Pracuję nad prostym programem do gier wideo dla szkoły i stworzyłem metodę, w której gracz otrzymuje 15 punktów zdrowia, jeśli ta metoda zostanie wywołana. Muszę utrzymywać zdrowie na maksymalnym poziomie 100 i przy mojej ograniczonej zdolności programowania w tym momencie robię coś takiego.

public void getHealed(){
    if(health <= 85)
        health += 15;
    else if(health == 86)
        health += 14;
    else if(health == 87)
    health += 13; 
}// this would continue so that I would never go over 100

Rozumiem, że moja składnia nie jest idealna, ale moje pytanie brzmi: jaki może być lepszy sposób na zrobienie tego, ponieważ muszę zrobić podobnie z punktami obrażeń i nie zejść poniżej 0.

Nazywa się to arytmetyką nasycenia .


7
Na marginesie, wybrałbym inną nazwę dla tej metody. Według standardu Java (i większości innych języków, które znam), nazwa metody zaczynająca się od „get” powinna zwracać wartość i niczego nie zmieniać. Podobnie nazwy metod zaczynające się od „set” powinny zmieniać wartość i zwykle nic nie zwracają.
Darrel Hoffman,

Odpowiedzi:


226

Po prostu bym to zrobił. Zasadniczo zajmuje minimum od 100 (maksymalne zdrowie) do tego, jakie byłoby zdrowie z 15 dodatkowymi punktami. Zapewnia, że ​​zdrowie użytkownika nie przekracza 100.

public void getHealed() {
    health = Math.min(health + 15, 100);
}

Aby upewnić się, że hitpoints nie spadają poniżej zera, można zastosować podobną funkcję: Math.max.

public void takeDamage(int damage) {
    if(damage > 0) {
        health = Math.max(health - damage, 0);
    }
}

71

po prostu dodaj 15 do zdrowia, więc:

health += 15;
if(health > 100){
    health = 100;
}

Jednak, jak zauważyłem nijakie, czasami w przypadku wielowątkowości (wielu bloków kodu wykonywanych jednocześnie) stan zdrowia przekraczający 100 w dowolnym momencie może powodować problemy, a wielokrotna zmiana właściwości kondycji może być również zła. W takim przypadku możesz to zrobić, jak wspomniano w innych odpowiedziach.

if(health + 15 > 100) {
    health = 100;
} else {
    health += 15;
}

9
Nie powinno nigdy pozwolić na to, by się skończyło, może to wprowadzić nowe problemy, takie jak stan rasy, w którym zakłada się, że zdrowie postaci wynosi co najwyżej zdefiniowany maksymalny poziom zdrowia (100). Sądzę, że mało prawdopodobne w przypadku tego poziomu projektu, ale powinienem na początku egzekwować dobre praktyki.
nijaki

6
@bland Gdyby ktoś użył tego podejścia, sposobem uniknięcia takiego stanu wyścigu byłoby użycie zmiennej tymczasowej do przechowywania nowej wartości zdrowia, a następnie ustawienie zdrowia na tę nową wartość zdrowia w jednym miejscu, z synchronizacją, jeśli to konieczne.
Bob,

13
@bland: Warunki wyścigu są istotne tylko w przypadku wielowątkowości. A jeśli robi wielowątkowość (w co bardzo wątpię) , rozwiązaniem byłoby zablokowanie wszystkich dostępów healthlub upewnienie się, że healthdostęp do nich jest możliwy tylko z jednego wątku. Ograniczenie „nigdy nie powinno pozwolić, aby zdrowie przekroczyło 100” nie jest realistyczne.
BlueRaja - Danny Pflughoeft

@ BlueRaja-DannyPflughoeft Stwierdziłem, że jest to mało prawdopodobne dla tego projektu. Ogólnie zamki nie będą zawsze w użyciu, a jeśli masz maksymalną wartość, należy jej ściśle przestrzegać, gry lub w inny sposób. Przykład: Jeśli zdarzenie jest powiązane ze zmianą właściwości i używa wartości procentowej, to teraz ogromnie wypaczyłeś to przetwarzanie, a także spowodowałeś dwukrotne wywołanie. Staram się tutaj zachować ogólność i upewnić się, że OP się uczy - czuję, że ta odpowiedź, chociaż działa, jest zbyt wąska i konkretna dla ucznia, ponieważ zmusi go do nie myślenia o szerszej perspektywie.
nijaki

@Bob, myślę, że to niepotrzebne do czegoś tak prostego.
Chłodnica matematyczna

45

Nie potrzebujesz osobnego przypadku dla każdego z intpowyższych 85. Wystarczy mieć jedną else, więc jeśli zdrowie jest już 86lub wyższe, po prostu ustaw je bezpośrednio na 100.

if(health <= 85)
    health += 15;
else
    health = 100;

25
Trochę za dużo magicznych liczb jak dla mnie (nawet biorąc pod uwagę dozwolone 100) - przy zmianie 15 na 16 trzeba by dostosować 85. Czy zmiana 85 na przynajmniej 100 - 15(lub 100 -HEALED_HEALTH) nie byłaby poprawą?
Maciej Piechotka

37

Myślę, że idiomatycznym, zorientowanym obiektowo sposobem na zrobienie tego jest posiadanie setHealthw Characterklasie. Implementacja tej metody będzie wyglądać następująco:

public void setHealth(int newValue) {
    health = Math.max(0, Math.min(100, newValue))
}

Zapobiega to spadkowi zdrowia poniżej 0 lub wyższym niż 100, niezależnie od tego, co ustawisz.


Twoja getHealed()implementacja może wyglądać tak:

public void getHealed() {
    setHealth(getHealth() + 15);
}

To, czy metoda ma Charactermieć sens, to zadanie getHealed()pozostawione czytelnikowi :)


5
+1: To świetny sposób na zrobienie tego w sposób obiektowy! Jedyne, co mógłbym zasugerować (i prawdopodobnie pozostawiłoby to czytelnikowi), to prawdopodobnie mieć dwie metody ( heal(int hp)i damage(int hp)), z których każda wywołuje twoją setHealth(int newValue)metodę.
Chris Forrence,

18
Co jest złego w wywoływaniu funkcji bibliotecznych? Jako nazwane funkcje wyrażają intencję wyraźniej niż zbiór logiki warunkowej.
erickson

2
Również wiele funkcji bibliotecznych (i najprawdopodobniej te) jest naprawdę wbudowanych i w ogóle nie wykonuje żadnego wywołania.
kriss

2
@Matteo Zła odpowiedź - najprawdopodobniej funkcja biblioteki robi dokładnie to samo wewnętrznie, więc po co się powtarzać i zanieczyszczać kod? NIE używanie funkcji bibliotecznych nie jest zgodne z podstawowymi zasadami DRY.
NickG,

2
@Matteo to nie jest, aby uniknąć if. Ma to na celu uniknięcie możliwości strzelenia sobie w stopę. Jeśli jest zbyt rozwlekły, po prostu użyj importu statycznego. Wtedy wygląda to tak: health = max(0, min(100, newValue)) Jeśli to nadal jest dla Ciebie nieczytelne, wyodrębnij je do metody o nazwie, clampaby wiersz wyglądał następująco:health = clamp(0, 100, newValue)
Daniel Kaplan,

14

Mam zamiar zaoferować fragment kodu wielokrotnego użytku, nie jest to najmniejszy, ale można go używać z dowolną ilością, więc nadal warto go powiedzieć

health += amountToHeal;
if (health >= 100) 
{ 
    health = 100;
}

Możesz także zmienić 100 na zmienną maxHealth, jeśli chcesz dodać statystyki do gry, którą tworzysz, więc cała metoda może wyglądać mniej więcej tak

private int maxHealth = 100;
public void heal(int amountToHeal)
{
    health += amountToHeal;
    if (health >= maxHealth) 
    { 
        health = maxHealth;
    }
}

EDYTOWAĆ

Dodatkowe informacje

Możesz zrobić to samo, gdy gracz zostanie uszkodzony, ale nie potrzebujesz minHealth, ponieważ i tak byłoby to 0. Robiąc to w ten sposób, będziesz w stanie uszkodzić i leczyć dowolne ilości za pomocą tego samego kodu.


1
minHealthmoże być negatywne, powiedzmy, w D&D ... :)
JYelton

1
Zdecydowanie najlepsza odpowiedź tutaj.
Glitch Desire,

@JYelton, tak, powiedziałbyś po prostu (zdrowie <= 0) w instrukcji if. Możesz sobie z tym poradzić, jak chcesz, jeśli chcesz, aby miały życie tylko minus 1 z lifeCount lub po prostu jeśli stracą, to poradzi sobie z tym. Jeśli chcesz, możesz nawet sprawić, że zaczną nowe życie z ilością -HP, jaką mieli. Możesz wkleić ten kod praktycznie w dowolnym miejscu, a on wykona zadanie dobrze, o to chodzi w tej odpowiedzi.
5tar-Kaster


4

Zrobiłbym metodę statyczną w klasie pomocniczej. W ten sposób zamiast powtarzać kod dla każdej wartości, która musi mieścić się w pewnych granicach, możesz mieć jedną uniwersalną metodę. Przyjąłby dwie wartości określające min i maks, oraz trzecią wartość do ustalenia w tym zakresie.

class HelperClass
{
    // Some other methods

    public static int clamp( int min, int max, int value )
    {
        if( value > max )
            return max;
        else if( value < min )
            return min;
        else
            return value;
    }
}

W twoim przypadku zadeklarowałbyś gdzieś swoje minimalne i maksymalne zdrowie.

final int HealthMin = 0;
final int HealthMax = 100;

Następnie wywołaj funkcję przekazującą twoje minimalne, maksymalne i dostosowane zdrowie.

health = HelperClass.clamp( HealthMin, HealthMax, health + 15 );

3

Wiem, że to projekt szkolny, ale jeśli chciałbyś później rozszerzyć swoją grę i móc ulepszyć swoją moc leczniczą, napisz następującą funkcję:

public void getHealed(healthPWR) {
    health = Math.min(health + healthPWR, 100);
}

i wywołaj funkcję:

getHealed(15);
getHealed(25);

...itp...

Ponadto możesz stworzyć swoje maksymalne HP, tworząc zmienną, która nie jest lokalna dla funkcji. Ponieważ nie wiem, jakiego języka używasz, nie pokażę przykładu, ponieważ może on mieć niewłaściwą składnię.


2

Może to?

public void getHealed()
{
  if (health <= 85)
  {
    health += 15;
  } else
  {
    health = 100;
  }
}

2

Jeśli chcesz być bezczelny i zmieścić swój kod w jednej linii, możesz użyć operatora trójskładnikowego :

health += (health <= 85) ? 15 : (100 - health);

Zauważ, że niektórzy ludzie będą krzywo patrzeć na tę składnię z powodu (prawdopodobnie) złej czytelności!


4
Uważam, że jest health = (health <= 85)?(health+15):100bardziej czytelny (jeśli naprawdę chcesz użyć operatora trójskładnikowego)
Matteo

1

Wierzę, że to wystarczy

if (health >= 85) health = 100;
else health += 15;

Wyjaśnienie:

  • Jeśli przerwa na leczenie wynosi 15 lub mniej, zdrowie osiągnie 100.

  • W przeciwnym razie, jeśli różnica jest większa niż 15, doda 15 do zdrowia.

Na przykład: jeśli zdrowie wyniesie 83, wyniesie 98, ale nie 100.


Czy możesz wyjaśnić, jak to działa, aby rozwiązać pytanie zgłaszającego?
Ro Yo Mi

Jeśli przerwa do leczenia wynosi 15 lub mniej zdrowia, wyniesie 100, w przeciwnym razie, jeśli przerwa jest większa niż 15, doda 15 do zdrowia. więc na przykład zdrowie będzie 83 zmieniło się na 98, ale nie 100. Jeśli masz jakieś szczególne obawy, daj mi znać, dziękuję za komentarz.
MarmiK

&& health < 100Warunek jest niepotrzebna. Jeśli jest to 100, zostanie ustawione na 100, bez zmian. Jedynym powodem, dla którego by to było potrzebne, jest to, że w jakiś sposób można uzyskać> 100 i nie chcemy, aby uzdrowienie zmniejszyło cię z powrotem do 100.
Darrel Hoffman

@DarrelHoffman Myślę, że masz rację, jeśli gra nie zapewnia dodatkowego zdrowia 100+ to masz rację, poprawiłem odpowiedź :) dziękuję
MarmiK

1

Gdybym chciał być bezpieczny wątkowo, zrobiłbym to w ten sposób, zamiast używać zsynchronizowanego bloku.

Atomowa metoda compareAndSet daje taki sam wynik, jak zsynchronizowana bez narzutu.

AtomicInteger health = new AtomicInteger();

public void addHealth(int value)
{
    int original = 0;
    int newValue = 0;
    do
    {
        original = health.get();
        newValue = Math.min(100, original + value);
    }
    while (!health.compareAndSet(original, newValue));
}

1

Najprostszy sposób przy użyciu operatora modułu.

zdrowie = (zdrowie + 50)% 100;

zdrowie nigdy nie osiągnie ani nie przekroczy 100.


Ale jeśli wykonasz tę operację, gdy healthosiągniesz 100, skończysz z 50 punktami zdrowia.
Parziphal

0
   private int health;
    public void Heal()
    {
        if (health > 85)
            health = 100;
        else
            health += 15;
    }
    public void Damage()
    {
        if (health < 15)
            health = 0;
        else
            health -= 15;
    }

jeśli zamierzasz tworzyć funkcje, powinieneś przynajmniej ustawić 15 jako parametr :)
Jordan

@Jordan: To zależy od kontekstu, w którym piszę kod. :-)
pocałuj mnie w pachę
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.