Jump to content

struggling graduate


busby

Recommended Posts

  • Replies 50
  • Created
  • Last Reply

Top Posters In This Topic

I suppose I might as well post the solution I made yesterday as well. I didn't bother creating a web front end, so run it from a terminal...

 

<?php
class Player
{
    private $name;
    private $health;
    private $attack;
    private $defence;
    private $speed;
    private $evade;
    private $maxHealth;

    public function __construct($name, $health, $attack, $defence, $speed, $evade)
    {
        $this->name = $name;
        $this->health = $this->maxHealth = $health;
        $this->attack = $attack;
        $this->defence = $defence;
        $this->speed = $speed;
        $this->evade = $evade;
    }

    public function isAlive()
    {
        return $this->health > 0;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getHealth()
    {
        return $this->health;
    }

    public function getMaxHealth()
    {
        return $this->maxHealth;
    }

    public function getAttack()
    {
        return $this->attack;
    }

    public function getDefence()
    {
        return $this->defence;
    }

    public function getSpeed()
    {
        return $this->speed;
    }

    public function getEvade()
    {
        return $this->evade / 100;
    }

    public function attack(Player $opponent)
    {
        if (!$opponent->doEvadeRoll($this)) {
            $opponent->wound($this->getAttack() - $opponent->getDefence(), $opponent);
        }
    }

    public function wound($hp, Player $opponent)
    {
        $this->health = max(0, $this->health - $hp);
    }

    public function heal($hp)
    {
        $this->health = min($this->getMaxHealth(), $this->health + $hp);
    }

    public function doEvadeRoll(Player $opponent)
    {
        return mt_rand(1, 100) <= $this->evade;
    }

    public function __toString()
    {
        return sprintf('%s (%d)', $this->getName(), $this->getHealth());
    }
}

class Ninja extends Player
{
    public function __construct($name)
    {
        parent::__construct($name,
            mt_rand(40, 60),
            mt_rand(60, 70),
            mt_rand(20, 30),
            mt_rand(90, 100),
            mt_rand(30, 100)
        );
    }

    public function getAttack()
    {
        $attack = parent::getAttack();
        if (mt_rand(1, 100) <= 5) {
            $attack *= 2;
        }
        return $attack;
    }
}

class Samurai extends Player
{
    public function __construct($name)
    {
        parent::__construct($name,
            mt_rand(60, 100),
            mt_rand(75, 80),
            mt_rand(35, 40),
            mt_rand(60, 80),
            mt_rand(30, 40)
        );
    }

    public function doEvadeRoll(Player $opponent)
    {
        $evaded = parent::doEvadeRoll($opponent);
        if ($evaded && mt_rand(1, 100) <= 10) {
            $this->heal(10);
        }

        return $evaded;
    }
}

class Brawler extends Player
{
    public function __construct($name)
    {
        parent::__construct($name,
            mt_rand(90, 100),
            mt_rand(65, 75),
            mt_rand(40, 50),
            mt_rand(40, 65),
            mt_rand(30, 35)
        );
    }

    public function getDefence()
    {
        $def = parent::getDefence();
        if ($this->getHealth()/$this->getMaxHealth() < 0.2) {
            $def += 10;
        }
        return $def;
    }
}

class Game
{
    private $player1;
    private $player2;
    private $maxRounds;
    private $rounds;

    public function __construct(Player $player1, Player $player2, $maxRounds = 30)
    {
        $this->player1 = $player1;
        $this->player2 = $player2;
        $this->maxRounds = $maxRounds;
        $this->rounds = 0;
    }

    public function gameOver()
    {
        return $this->rounds == $this->maxRounds || !$this->player1->isAlive() || !$this->player2->isAlive();
    }

    public function getWinner()
    {
        if (!$this->gameOver()) {
            throw new Exception('Game not over yet.');
        }

        if (!$this->player2->isAlive()) {
            return $this->player1;
        }
        else if (!$this->player1->isAlive()) {
            return $this->player2;
        }
        else {
            return null; // draw
        }
    }

    private function comparePlayers(Player $a, Player $b)
    {
        $s1 = $a->getSpeed();
        $s2 = $b->getSpeed();
        if ($s1 === $s2) {
            return $a->getDefence() - $b->getDefence();
        }
        else {
            return $s1 - $s2;
        }
    }

    public function doRound()
    {
        if ($this->gameOver()) {
            throw new Exception('Cannot do more rounds when game is over.');
        }

        ++$this->rounds;

        $players = array($this->player1, $this->player2);
        usort($players, array($this, 'comparePlayers'));

        $players[0]->attack($players[1]);
        $players[1]->attack($players[0]);
    }

    public function getRound()
    {
        return $this->rounds + 1;
    }
}

$classes = array('Ninja', 'Samurai', 'Brawler');
list($p1, $p2) = array_rand($classes, 2);

$player1 = new $classes[$p1]($classes[$p1]);
$player2 = new $classes[$p2]($classes[$p2]);

$game = new Game($player1, $player2, 30);

while (!$game->gameOver()) {
    echo 'Round ' . $game->getRound() . ':' . PHP_EOL;
    echo $player1 . PHP_EOL;
    echo $player2 . PHP_EOL;
    echo PHP_EOL;
    $game->doRound();
}

$winner = $game->getWinner();
if ($winner === null) {
    echo 'Draw.';
}
else {
    echo 'Winner: ' . $winner->getName();
}
echo PHP_EOL;

 

I estimate that I used ~40 minutes.

Link to comment
Share on other sites

I typed it as I read the task description:

 

class BattleRecorder
{
  private static $instance;
  
  private function __construct() {}
  private function __clone() {}
  
  public static function get() {
    if (is_null(self::$instance)) {
      self::$instance = new self();
    }
    return self::$instance;
  }
  
  public function record($msg) {
    echo $msg, PHP_EOL;
  }
}

abstract class Warrior
{
  public $name;
  public $health;
  public $attack;
  public $defence;
  public $speed;
  public $evade;
  
  public $turns;
  public $recorder;
  
  public static function get($warrior, $name) {
    $className = ucfirst(strtolower($warrior));
    return new $className($name);
  }
  
  public function __construct($name) {
    $this->name = $name;
    
    $this->_init();
  }
  
  public function isDefeated() {
    return !$this->isAlive() || !$this->hasTurnsLeft();
  }
  
  public function isAlive() {
    return $this->health > 0;
  }
  
  public function hasTurnsLeft() {
    return $this->turns > 0;
  }
  
  public function evadedAttack() {
    return $this->evade == mt_rand(1, 100) / 100;
  }
  
  public abstract function attack(Warrior $w);
  public abstract function defend($attack);
  
  protected abstract function _init();
}

class Ninja extends Warrior
{
  public function attack(Warrior $w) {
    --$this->turns;
    
    $damage = $this->attack * (0.05 == mt_rand(1,100) / 100 ? 2 : 1);
    
    $this->recorder->record($this->name . ' dealt ' . $damage . ' damage.');
    
    $w->defend($damage);
  }
  
  public function defend($attack) {
    if ($this->evadedAttack()) {
      $this->recorder->record($this->name . ' evaded the attack.');
      return;
    }
    
    $damage = $attack - $this->defence;
    if ($damage > 0)
      $this->health -= $damage;
      
    $this->recorder->record($this->name . ' defended, but lost ' . $damage . ' health.');
  }
  
  protected function _init() {
    $this->health = mt_rand(40, 60);
    $this->attack = mt_rand(60, 70);
    $this->defence = mt_rand(20, 30);
    $this->speed = mt_rand(90, 100);
    $this->evade = mt_rand(30, 50) / 100;
  }
}

class Arena
{
  public static function fight(Warrior $w1, Warrior $w2) {
    $w1->turns = 30;
    $w1->recorder = BattleRecorder::get();
    
    $w2->turns = 30;
    $w2->recorder = BattleRecorder::get();
    
    list($w1, $w2) = self::drawFirstBlood($w1, $w2);
    while (!$w1->isDefeated() && !$w2->isDefeated()) {
      $w1->attack($w2);
      $w2->attack($w1);
    }
    
    if ($w1->isAlive() && $w2->isAlive())
      BattleRecorder::get()->record('draw!');
    elseif ($w1->isAlive())
      BattleRecorder::get()->record($w1->name . ' won!');
    else
      BattleRecorder::get()->record($w2->name . ' won!');
  }
  
  protected static function drawFirstBlood(Warrior $w1, Warrior $w2) {
    $r = array($w1, $w2);
    if ($w1->speed > $w2->speed) {
      $w1->attack($w2);
      $r = array($w2, $w1);
    } else if ($w1->speed == $w2->speed) {
      if ($w1->defence < $w2->defence) {
        $w1->attack($w2);
        $r = array($w2, $w1);
      } else {
        $w2->attack($w1);
      }
    } else {
      $w2->attack($w1);
    }
    return $r;
  }
}

 

Might not be a good example as for best practices :) Took me around 40 minutes I think.

 

Call like:

 

Arena::fight(
  Warrior::get('Ninja', 'Foo'),
  Warrior::get('Ninja', 'Bar')
);

Link to comment
Share on other sites

The spec defines how to determine who goes first initially. The way it is worded...yes, technically doing it before the battle loops will work, and be within spec.  But then each warrior has a stat-changing ability that is triggered based on the battle.  Technically there are currently no speed-changing abilities defined in this spec, so technically you are within spec.  But the point of OOP is to be able to think ahead and expand.  It makes more sense to code the abilities in a way that any stat can be changed for whatever reason.  For instance, say I want to give ninjas a 10% chance of a speed boost, that would affect whether or not they go first.  But that stat change won't work if you are determining who will go first only once, before the first battle.  So I don't think it was "misinterpreted" per se..just that IMO it is better to code with the mentality of "xyz is in the spec...I could technically do it one way, but if I do it this other way, it will make it easier for me to add more things later".  I've never really sat down and formerly learned OOP but I think that's what's called keeping your coupling loose or something.

Link to comment
Share on other sites

Not sure if that was at me, or what. But anyway, at first I interpreted it as the stats determine who goes first, as in starts off the entire battle, from which point it just goes back and forth between the two. In which case a change in stats during the battle wouldn't matter.

Link to comment
Share on other sites

Technically there are currently no speed-changing abilities defined in this spec, so technically you are within spec.

 

The brawler gets a defence boost when on low health. Defence was the secondary stat to check when determining who goes first.

Link to comment
Share on other sites

Not sure if that was at me, or what. But anyway, at first I interpreted it as the stats determine who goes first, as in starts off the entire battle, from which point it just goes back and forth between the two. In which case a change in stats during the battle wouldn't matter.

 

Yes, you're supposed to let whoever has more speed (failing that, less defense...failing that...unspecified (I just flip a coin at that point)) attack first.  And technically it doesn't say "check speed before every round," and technically there are no speed-altering abilities in the spec.  So it's within one's rights to write it exactly like that: check stats initially, do all 30 rounds based on initial stat check (..except for defense...see Dan's point, which does apply)

 

HOWEVER... there are specs for abilities that cause stats to change, which does affect each round of the battle. 

 

Ninja: "With each attack there is a 5% chance of doubling the attack strength."  This very explicitly states that an altered stat should affect each fighting round.

 

Samurai: "When a samurai evades an attack there is a 10% chance of regaining 10 health" What would be the point in increasing health if it's not supposed to be applied to subsequent rounds of the fight?

 

Brawler: "When a brawler’s health falls to less than 20% their defence increases by 10" What would be the point in increasing defense if that increased defense is not going to be used in subsequent rounds?

 

The specs state to alter specific stats for specific warriors, to be applied to each (subsequent) round of a fight.  If you stuck to the exact current specs, it does not say to do anything with speed, so it is "safe" to check speed before the rounds begin, and then never check it again. 

 

But if you look at the specs in a broader term of "script should be able to alter stats for warriors and be applied to subsequent rounds," then you are fucking yourself on the speed stat.  If tomorrow you are tasked with the following:

 

Ninja: "Each attack there is a 10% chance of increasing speed by 20%." 

 

Since the current spec is check speed to determine who hits first, this new ability would give ninjas an increased chance to hit first on each round.  But since you don't do a speed check every round, you're going to have to recode what you did, to accommodate this new ability.  Isn't that the whole point of Object Oriented Programming?  To write code in more abstract terms so that there is less work involved in adding new stuff later on? 

 

Technically there are currently no speed-changing abilities defined in this spec, so technically you are within spec.

 

The brawler gets a defence boost when on low health. Defence was the secondary stat to check when determining who goes first.

 

Yeah you're right...forgot about that (I did it in my code..just forgot about it when posting that... but that just further proves my point in this post.

Link to comment
Share on other sites

HOWEVER... there are specs for abilities that cause stats to change, which does affect each round of the battle. 

Yeah, sorry, when I said "In which case a change in stats during the battle wouldn't matter." I meant with respect to determining who goes first. I wasn't ever arguing with that.

 

edit: Nevermind.

Link to comment
Share on other sites

Ninja: "With each attack there is a 5% chance of doubling the attack strength."  This very explicitly states that an altered stat should affect each fighting round.

 

I interpreted that as: every time a ninja attacks there's a 5% chance that it does twice as much damage as it would normally do, not that the attack stat of the ninja doubles permanently.

 

edit:

 

Looking over the code it seems that Daniel and ignace interpreted it that way (the way I did) as well.

 

I interpret that as for each round, 5% chance that the $attack stat doubles.  This doesn't necessarily double the actual damage done, because damage done is $attack - $defense.    So IOW I interpret it as

 

($attack * 2) - $defense

 

not

 

($attack - $defense) * 2

 

I guess that interpretation is a matter of opinion though... but I too interpret that it is  something to do each round, and shouldn't be a permanent stat increase.  So my code in principle basically goes like this:

 

current stats

$ninja->attack = 50;

$warrior->defense = 50;

 

pre-battle:

rand 1,100..if > 95 (5% chance) then $ninja->attack = $ninja->attack * 2; // evaluate true for demo

 

battle:

$damage = $ninja->attack - $warrior->defense; // 100 - 50

$warrior->health = $warrior->health - $damage;

 

post-battle:

$ninja->attack = $ninja->attack / 2;

 

 

 

 

Link to comment
Share on other sites

I edited my post, but you quoted me before that.

 

I was actually talking about something else: I thought that you permanently increased the attack stat of the ninja, but upon closer inspection of your code I realized that you didn't, so I removed my comment about that.

 

But what you posted on was another good point. The specifications are too ambiguous.

Link to comment
Share on other sites

But what you posted on was another good point. The specifications are too ambiguous.

 

IMO, if it were me doing the interview,  it would have earned the OP brownie points (and more time) if he had gone back with a list of questions to clarify details (and perhaps also point out flaws in the "game mechanics" itself, depending on the employer...the warriors are pretty unbalanced...). 

Link to comment
Share on other sites

The OP needs advice on how to overcome his "fresh graduate struggles as a web developer" in general, and not solve his specific problem of implementing a game.

 

The brilliant suggested implementations/solutions of his exam interests me though.

 

However, there ought to be a separate category for these sort of discussions, say, "PHP Games". Its purpose is to be a discussion of ideas of possible cool games, then implement it in PHP. While there is an existing category for PHP scripts, this will be different in the sense that it will devote itself to game programming in PHP.

 

I don't know, just a suggestion.

Link to comment
Share on other sites

The OP needs advice on how to overcome his "fresh graduate struggles as a web developer" in general, and not solve his specific problem of implementing a game.

 

The brilliant suggested implementations/solutions of his exam interests me though.

 

However, there ought to be a separate category for these sort of discussions, say, "PHP Games". Its purpose is to be a discussion of ideas of possible cool games, then implement it in PHP. While there is an existing category for PHP scripts, this will be different in the sense that it will devote itself to game programming in PHP.

 

I don't know, just a suggestion.

 

Ok.

Link to comment
Share on other sites

I really like how the OP actually posted a fun exercise, we all posted our solution in either OO or procedural, and now we are all discussing each others solution. I think we should do this more, allow the entire community to participate and defend their design/decisions. With the greater goal of everyone learning new things from each others code/comments.

Link to comment
Share on other sites

I really like how the OP actually posted a fun exercise, we all posted our solution in either OO or procedural, and now we are all discussing each others solution. I think we should do this more, allow the entire community to participate and defend their design/decisions. With the greater goal of everyone learning new things from each others code/comments.

 

I agree, I was thinking the same thing. I think it would be fun and great way to learn from each other and could ultimately lead to a nice library of examples.

Link to comment
Share on other sites

I really like how the OP actually posted a fun exercise, we all posted our solution in either OO or procedural, and now we are all discussing each others solution. I think we should do this more, allow the entire community to participate and defend their design/decisions. With the greater goal of everyone learning new things from each others code/comments.

 

Thus, I would recommend a separate and new forum category. And  it shall be devoted to discussions about Game Programming.

 

The fun really is in conceptualizing and implementing games, either new or old, complex or simple games. Also, Game Programming has a deep theoretical aspect, and it can enhance existing skills (as compared to developing emails or the next blog/cms application).

 

 

Link to comment
Share on other sites

I think if there was a dedicated forum for this kind of thing then it would just end up inactive after a short while. It's been interesting and got people involved this time, but I think that's purely down to the fact that it's rare.. Like Christmas. Have that everyday and the novelty would soon ware out. Perhaps creating the odd thread here and there in Misc. would be better?

Link to comment
Share on other sites

I think if there was a dedicated forum for this kind of thing then it would just end up inactive after a short while. It's been interesting and got people involved this time, but I think that's purely down to the fact that it's rare.. Like Christmas. Have that everyday and the novelty would soon ware out.

 

I agree though.

Link to comment
Share on other sites

Regardless, to address ebmigue's other point, I don't think there's much we can do for the OP.  He's obviously deficient in many areas, and nothing short of getting good resources (which we have at least one sticky topic for, if not more) and writing code will help him.  Maybe he should go back to school for a couple years at a technical college, but since he just graduated, that's probably not a viable option.

 

With no disrespect intended to the OP, he's at beginning hobbyist level right now and is essentially asking us to make him into a competent entry level programmer.  That's outside the scope of what we do, and not realistic at all anyway.  We're more than willing to help him (or anyone) with specific questions about specific code, but we're not in the business of mentoring.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.