๐ The setup
You're building a character sheet for a tabletop RPG. The rules are strict: HP can never drop below 0 or exceed maxHp, your fullName should update when you change your first or last name, and nobody should be able to cheat XP into your character โ only the class itself hands out XP.
Below is the old-school version. Your job is to modernise it with PHP 8.4.
๐งฑ Starter code
hero.php โ starter
<?php
final class Character
{
public int $hp;
public int $maxHp = 10;
public int $xp = 0;
public function __construct(
public string $firstName,
public string $lastName,
public int $level = 1,
) {
$this->hp = $this->maxHp;
}
public function takeDamage(int $dmg): void
{
$this->hp = max(0, $this->hp - $dmg);
}
public function heal(int $amount): void
{
$this->hp = min($this->maxHp, $this->hp + $amount);
}
public function gainXp(int $amount): void
{
$this->xp += $amount;
while ($this->xp >= 100) {
$this->xp -= 100;
$this->level++;
$this->maxHp += 5;
}
}
}
$hero = new Character('Aria', 'Stormblade');
echo $hero->firstName . ' ' . $hero->lastName . PHP_EOL; // "Aria Stormblade"
$hero->takeDamage(4);
echo "HP: {$hero->hp}/{$hero->maxHp}" . PHP_EOL; // 6/10
$hero->hp = 9999; // ๐ฑ we can just... cheat?
echo "HP: {$hero->hp}" . PHP_EOL; // 9999 (bug!)
$hero->xp = 999_999; // ๐ฑ and cheat XP too?
echo "Level: {$hero->level}, XP: {$hero->xp}" . PHP_EOL;
๐ฏ Your challenges
- Virtual property. Add a
fullNameproperty with a get hook that returns"$firstName $lastName". It should always reflect the current first and last name. - Validated setter. Give
hpa set hook that clamps any assignment into the range[0, maxHp]. Once this works,takeDamage()andheal()become one-liners (or delete them โ who needs them). - Asymmetric visibility. Make
xppublic private(set)so it's readable from anywhere but only the class can change it. The final line of the starter should fail with a visibility error. - ๐ Bonus. Add an
isAlivevirtual property (get-only hook) that returnstruewhenhp > 0. - ๐ Bonus bonus. Add a
levelwith a set hook that also bumpsmaxHpby 5 per level gained (clean upgainXp()afterwards).
๐ก Hint โ property hook syntax
class Foo
{
// virtual get-only property
public string $fullName {
get => "$this->firstName $this->lastName";
}
// set hook with validation
public int $hp {
set(int $value) => max(0, min($this->maxHp, $value));
}
// asymmetric visibility
public private(set) int $xp = 0;
}
โ Show one possible solution
Answer both questions correctly to unlock the solution.
1. Which snippet correctly adds a set hook that clamps hp between 0 and maxHp?
2. You declare public private(set) int $xp = 0;. Which statement is true?
<?php
final class Character
{
public string $fullName {
get => "$this->firstName $this->lastName";
}
public int $hp {
set(int $value) => max(0, min($this->maxHp, $value));
}
public bool $isAlive {
get => $this->hp > 0;
}
public private(set) int $xp = 0;
public private(set) int $maxHp = 10;
public function __construct(
public string $firstName,
public string $lastName,
public int $level = 1,
) {
$this->hp = $this->maxHp;
}
public function gainXp(int $amount): void
{
$this->xp += $amount;
while ($this->xp >= 100) {
$this->xp -= 100;
$this->level++;
$this->maxHp += 5;
}
}
}
๐งช Try to break it: can you still overflow HP? What happens with negative damage? What if
maxHp changes while HP is above the new max?