Към съдържанието
wannacode

RPG 1: героите

Проблем

Пет раси, един отряд

В света на Арканум пет раси се борят за земите му. Всяка има свои герои:

Раса Герой С какво се отличава
хора паладин средна щета, лекува се
елфи амазонка бърза, удря по-силно от хората, отскача от удари
орки варварин голяма щета, поема по-малко щета, изпада в ярост
джуджета некромант малка щета, черпи сила от мъртвите
кървави елфи вещица средна щета, възстановява се със силата на стихиите

Искаш отряд от петимата и един цикъл, който ги показва и ги пуска в бой. Ако всяка раса е отделен клас без нищо общо, ще имаш пет масива, пет цикъла и пет копия на един и същ код за живота и щетата. А шестата раса ще иска шесто копие.

Идеята

Общото е в Character

Всеки герой има име, живот, сила и раса. Всеки поема щета, може да е жив или мъртъв и може да покаже какъв е. Това е общото и стои в родителя:

abstract class Character {
protected String name;
protected int health;
protected int power;
protected String race;
public Character(String name, int health, int power, String race) {
this.name = name;
this.health = health;
this.power = power;
this.race = race;
}
public String getName() {
return name;
}
public int getHealth() {
return health;
}
public int getPower() {
return power;
}
public void takeDamage(int amount) {
health = Math.max(health - amount, 0);
}
public boolean isAlive() {
return health > 0;
}
public void showInfo() {
System.out.println(name + ", " + race + ": живот " + health + ", сила " + power);
}
public abstract String useAbility();
}

Различно е само умението. Затова useAbility() е абстрактен: всяка раса трябва да го напише. А Character е абстрактен клас, защото герой „просто персонаж“ няма.

Умението връща текст за случилото се, вместо да печата. Така кой какво вижда на екрана решава играта, а не героят.

По-точно, за любопитните
  • Полетата са protected: наследниците ги променят, а отвън се четат само с getter-и.
  • В старото упражнение имаше getter и setter за всяко поле. Тук няма начин животът да се смени отвън. Той се мени само през takeDamage и уменията, затова никой не може да направи героя безсмъртен с един ред.
  • Math.max(health - amount, 0) пази живота да не падне под 0.
  • Името Character вече има клас в Java: java.lang.Character, за символите. Свой клас със същото име в твоя файл го закрива. Работи, но ако ти трябва и стандартният, пиши го с цялото име: java.lang.Character.isDigit(c).

Виж го

Първият герой

Паладинът казва в конструктора каква е расата и с колко живот и сила започва. Всичко друго взима от Character. Пише само умението си:

class Paladin extends Character {
public Paladin(String name) {
super(name, 120, 14, "човек");
}
@Override
public String useAbility() {
health += 20;
return name + " се лекува: +20 живот";
}
}
Character hero = new Paladin("Ардан");
hero.showInfo();
hero.takeDamage(30);
hero.showInfo();
System.out.println(hero.useAbility());
hero.showInfo();
Изход
Ардан, човек: живот 120, сила 14
Ардан, човек: живот 90, сила 14
Ардан се лекува: +20 живот
Ардан, човек: живот 110, сила 14

Променливата е от тип Character, а обектът е Paladin. hero.useAbility() вика метода на паладина.

Идеята

Всяка раса по своему

Варваринът поема с една пета по-малко щета. Той презаписва takeDamage и вика версията на родителя с по-малкото число:

class Barbarian extends Character {
public Barbarian(String name) {
super(name, 110, 20, "орк");
}
@Override
public void takeDamage(int amount) {
super.takeDamage(amount - amount / 5);
}
@Override
public String useAbility() {
power += power / 2;
return name + " изпада в ярост: сила " + power;
}
}

Амазонката помни дали се е приготвила да отскочи. Ако е, следващият удар е наполовина и отскокът свършва:

class Amazon extends Character {
private boolean dodging;
public Amazon(String name) {
super(name, 100, 18, "елф");
}
@Override
public void takeDamage(int amount) {
if (dodging) {
amount = amount / 2;
dodging = false;
}
super.takeDamage(amount);
}
@Override
public String useAbility() {
dodging = true;
return name + " се готви да отскочи: следващият удар е наполовина";
}
}

И двамата ползват super.takeDamage: проверката, че животът не пада под 0, остава на едно място.

Пробвай

Колко живот остава?

Какво ще отпечата програмата? Character е от горната карта.

class Amazon extends Character {
private boolean dodging;
public Amazon(String name) {
super(name, 100, 18, "елф");
}
@Override
public void takeDamage(int amount) {
if (dodging) {
amount = amount / 2;
dodging = false;
}
super.takeDamage(amount);
}
@Override
public String useAbility() {
dodging = true;
return name + " се готви да отскочи: следващият удар е наполовина";
}
}
Character lira = new Amazon("Лира");
lira.useAbility();
lira.takeDamage(30);
lira.takeDamage(30);
System.out.println(lira.getHealth());
Отговор

Виж го

Отрядът

Некромантът и вещицата пишат само конструктор и умение:

class Necromancer extends Character {
public Necromancer(String name) {
super(name, 90, 10, "джудже");
}
@Override
public String useAbility() {
power += 3;
return name + " черпи сила от мъртвите: +3 сила";
}
}
class Witch extends Character {
public Witch(String name) {
super(name, 95, 16, "кървав елф");
}
@Override
public String useAbility() {
health += 15;
return name + " призовава стихиите: +15 живот";
}
}

Сега петимата влизат в един масив от тип Character, а един цикъл ги кара да ползват умението си и да поемат удар от 30:

Character[] party = {
new Paladin("Ардан"),
new Amazon("Лира"),
new Barbarian("Грок"),
new Necromancer("Торин"),
new Witch("Морена"),
};
for (Character hero : party) {
System.out.println(hero.useAbility());
hero.takeDamage(30);
hero.showInfo();
}
Изход
Ардан се лекува: +20 живот
Ардан, човек: живот 110, сила 14
Лира се готви да отскочи: следващият удар е наполовина
Лира, елф: живот 85, сила 18
Грок изпада в ярост: сила 30
Грок, орк: живот 86, сила 30
Торин черпи сила от мъртвите: +3 сила
Торин, джудже: живот 60, сила 13
Морена призовава стихиите: +15 живот
Морена, кървав елф: живот 80, сила 16

Цикълът не знае кой е от коя раса. Всеки герой сам решава какво прави умението му и колко щета поема. Амазонката губи 15, варваринът 24, останалите 30. Шестата раса ще влезе в същия масив, без цикълът да се промени.

Character[] party = {
new Paladin("Ардан"),
new Amazon("Лира"),
new Barbarian("Грок"),
new Necromancer("Торин"),
new Witch("Морена"),
};
for (Character hero : party) {
System.out.println(hero.useAbility());
hero.takeDamage(30);
hero.showInfo();
}
Стъпка по стъпка, с думи
  1. Масивът е от тип Character, а вътре са 5 обекта от 5 различни класа.
  2. hero сочи Ардан, обект Paladin. Изпълнява се useAbility() на Paladin. takeDamage е общият от Character: 140 − 30 = 110.
  3. hero сочи Лира, обект Amazon. Изпълнява се useAbility() на Amazon. Amazon презаписва takeDamage: от удара 30 поема само 15 и остават 85.
  4. hero сочи Грок, обект Barbarian. Изпълнява се useAbility() на Barbarian. Barbarian презаписва takeDamage: от удара 30 поема само 24 и остават 86.
  5. hero сочи Торин, обект Necromancer. Изпълнява се useAbility() на Necromancer. takeDamage е общият от Character: 90 − 30 = 60.
  6. hero сочи Морена, обект Witch. Изпълнява се useAbility() на Witch. takeDamage е общият от Character: 110 − 30 = 80.
  7. Цикълът е един и не знае расите. Кой метод се изпълнява, решава обектът, към който сочи hero.

Пробвай

Намери грешката

Програмата спира още при първия удар срещу варварина. На кой ред е грешката?

Избери реда с грешката.

Пробвай

Кое липсва?

Какво трябва да пише на празното място, за да е задължително всяка раса да има умение?
abstract class Character {
    protected String name;
    protected int health;
    protected int power;
    protected String race;

    public Character(String name, int health, int power, String race) {
        this.name = name;
        this.health = health;
        this.power = power;
        this.race = race;
    }

    public String getName() {
        return name;
    }

    public int getHealth() {
        return health;
    }

    public int getPower() {
        return power;
    }

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

    public boolean isAlive() {
        return health > 0;
    }

    public void showInfo() {
        System.out.println(name + ", " + race + ": живот " + health + ", сила " + power);
    }

    public  String useAbility();
}

Упражнение

Още герои

Реши задачите в редактора си. Започни от класовете в урока.

1. Шести герой. Добави друид от горския народ: 105 живот, 12 сила. Умението му дава +8 живот и +2 сила. Не пипай Character.

Изход
Ели, горски народ: живот 105, сила 12
Ели се свързва с гората: +8 живот, +2 сила
Ели, горски народ: живот 113, сила 14

2. Списък на отряда. Напиши метод showAll(Character[] party), който показва героите с номера и накрая общия им живот. Пусни го за отряда от урока.

Изход
1. Ардан, човек: живот 120, сила 14
2. Лира, елф: живот 100, сила 18
3. Грок, орк: живот 110, сила 20
4. Торин, джудже: живот 90, сила 10
5. Морена, кървав елф: живот 95, сила 16
Общо живот: 515

3. Кой е най-силен? Всеки от отряда ползва умението си веднъж. Напиши метод Character strongest(Character[] party), който връща героя с най-голяма сила.

Изход
Най-силен: Грок със сила 30

Още задачи

  • Отряд от входа. Прочети пет реда „раса име“, например „паладин Ардан“, и създай героите в масив. За непозната раса кажи това и прочети следващия ред.
  • Ярост с мярка. Ярост след ярост прави варварина безкрайно силен. Нека умението вдига силата само първия път, а после казва, че вече е в ярост.
  • Кой е най-здрав? Като задача 3, но по живот, след като всеки е поел удар от 25.

Тест

Провери се

1. Защо Character е абстрактен?
2. Масивът е Character[]. Кой takeDamage се вика за варварина в него?
3. Амазонката отскача, после я удрят с 40 и после пак с 40. Колко живот губи?
4. Barbarian презаписва takeDamage. Как вика версията на Character?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·