Battles in Kallisor

//initiate variables.
var herbPower = 10;
var herbCost = 25;
var numberBattles = 0;
var monstersFought = [];
var monsters = GetMonsterList();

//Welcome the player to the game
var display = "Welcome to Battles in Kallisor.";
display += "\r\rIn this simulation, you will battle against a random creature. ";
display += "If you win, you get some gold and maybe an item or two. ";
display += "If you lose, it's game over.";
display += "\r\rHerbs restore about " + herbPower + " Hit Points. You have a limited supply.";
display += "\r\rIn this version, there are " + monsters.length + " types of monsters you may battle.";

Show(display);

var hero = GetHero();

var stillPlaying = true;
while (stillPlaying) 
{
  //choose a random monster based on hero level
  var monster;
  
  if (hero.level > monsters.length && Math.random() < 0.3)
  {
    monster = MakeRandomMonster(hero);
  }
  
  else
  {
  var maxRange = Math.min(monsters.length, hero.level + 2)
  var rand = Math.floor(Math.random() * maxRange);

  //this creates a copy of the source monster so the original data doesn't get overwritten
  monster = CloneMonster(monsters[rand]);
  }
  
  
  
  //keep a tally of battles and monsters fought
  //technically numbersBattles will be equal to monstersFought.length
  //but they're different ideas so we're keeping different variables
  //in the future, if we change monstersFought to only track which kind of monster instead of each monster, then we'd still want the numberBattles counter anyway
  numberBattles++;
  monstersFought.push(monster.Name);

//let the player know they've been attacked
  Show("You are attacked by a " + monster.Name + "!");

//Let the monster have a chance to strike first based on a random factor that's based on the difference between the monster's stats and yours
var monsterFirst = false;
var monsterRun = false;
var monSum = monster.HPMax + monster.attack + monster.defense;
var heroSum = hero.HPMax + hero.attack + monster.defense;
var statDifference = monSum - heroSum;
var chance = Math.floor(Math.random() * 500)
if (chance > 0 && chance < statDifference)
{
  monsterFirst = true;
  Show("The " + monster.Name + " surprises you and attacks first!");
}
if (chance < 0 && Math.abs(chance) < statDifference)
{
  monsterRun = true;
}


//this starts a battle loop
//each time through the loop, the player will act, then the monster will act
  var inBattle = true;
  while (inBattle) 
  {
    if (!monsterFirst)
{    //This starts a loop to get input from the user. We're using a loop here in case the user enters an unexpected value; we can ask again for input until we have a valid response
    var getCommand = true;
    while (getCommand) 
    {
      //This sets up the battle dialogue. If we had this on a web page, we would do this differently. We'd have a stats section, etc. instead of putting it all in like this.
      display = "You are battling one " + monster.Name;
      
      if (hero.sightScope)
      {
        display += " (HP: " + monster.HP + ")";
      }
      
      display += ".\r\r";
      display += "Your HP: " + hero.HP + "/" + hero.HPMax;
      display += "\rYour herbs: " + hero.herbs;

      display += "\r\rWould you like to:\r";
      display += "(f)ight\r";
      
      //This if statement only shows the herb option if you actually have herbs.
      if (hero.herbs >= 1) 
      {
        display += "(u)se an herb\r";
      }
      
      display += "(r)un away\r";
      display += "(q)uit\r";

      var action = prompt(display);
      action = action.substring(0, 1).toLowerCase();

      if (action === "f" ||
        ((action === "u" || action === "h") && hero.herbs >= 1) ||
        action === "r" ||
        action === "q") 
      {
        getCommand = false;
      }
    }

    var monsterWillAttack = true;

    //initiate the fight command
    if (action === "f") 
    {
      DoAttack(hero, monster);
      if (monster.HP <= 0) 
      {
        monsterWillAttack = false;
        inBattle = false;
        AwardPrizes(hero, monster);
      }
    } 
    //this allows the player to use herbs with 'u' or 'h' but only if they have herbs
    else if ((action === "u" || action === "h") && hero.herbs >= 1) 
    {
      var herbHeal = UseHerb(hero);
      Show("The herb healed you " + herbHeal + " points. Your HP is now " + hero.HP + ".");
    } 
    //get ready to try to run
    else if (action === "r") 
    {
      Show("You try to run away from the " + monster.Name + ".");
      if (RunFromBattle(hero)) 
      {
        Show("You successfully escaped that battle!");
        monsterWillAttack = false;
        inBattle = false;
      } 
      else 
      {
        Show("You were not able to run away.");
      }
    } 
    //quit the game
    else if (action === "q") 
    {
      monsterWillAttack = false;
      inBattle = false;
      stillPlaying = false;
      break;
    }
}
monsterFirst = false;

    //Monster fights back
    if (monsterWillAttack) 
    {
      if (monster.HP < (monster.HPMax / 2) && (Math.random() * 1000) < monster.herbs) 
      {
        UseHerb(monster);
        Show("The " + monster.Name + " used an herb to heal.");
      } 
      else if (monsterRun && (Math.random() * 500 < 10))
      {
        Show("The monster ran away!");
        inBattle = false;
      }
      else
      {
        DoAttack(monster, hero);
        if (hero.HP <= 0) //oh no, you died
        {
          inBattle = false;
          stillPlaying = false;
        }
      }
    } // from MonsterFightsBack
  } // from (while (inBattle)
  if (stillPlaying)
  {
    BuyHerb(hero);
  }
} // from (while (stillPlaying))

if (hero.HP <= 0) 
{
  display = "You have died.\r\r";
} 
else 
{
  display = "You live to battle another day.\r\r";
}

display += hero.Name + "'s final stats:\r";
display += "HP: " + hero.HP + "/" + hero.HPMax;
display += "\rUnused Herbs: " + hero.herbs;

display += "\rTotal experience points: " + hero.exp;
display += "\rTotal gold accumulated: " + hero.gold;
display += "\rAttack power: " + hero.attack;
display += "\rDefense power: " + hero.defense;

display += "\r\rNumber of battles fought: " + numberBattles;
display += "\rMonsters battled:\r";
display += monstersFought;

display += "\r\rBe sure to play again!";
Show(display);

//End of Game



function AwardPrizes(h, m) 
{
  var display = "The " + m.Name + " was defeated.\r\r";
  display += h.Name + " earns " + m.exp + " experience points and " + m.gold + " gold.\r\r";
  h.exp += m.exp;
  h.gold += m.gold;

  //see if you find an herb from the monster
  if ((Math.random() * 100) < m.herbs) 
  {
    display += h.Name + " found an herb!";
    h.herbs++;
  }

  Show(display);

  //see if you get stronger
  LevelUp(hero);

  
  //let's have after-battle recovery
  var healUp = Math.floor(Math.random() * (hero.HPMax - hero.HP));
  hero.HP += healUp;
  hero.HP = Math.min(hero.HP, hero.HPMax);
  
  if (healUp > 1)
  {
    Show("You get some rest after the battle and recover " + healUp + " hit points.");
  }
  else if (healUp === 1)
  {
    Show("You get a little rest after the battle and recover " + healUp + " hit point.");
  }
}


function DoAttack(attacker, defender) 
{
  var display = "";

  var attName = attacker.Name;
  var defName = defender.Name;

  if (attacker.Name === monster.Name) 
  {
    attName = "The " + attacker.Name;
  } 
  else 
  {
    defName = "the " + defender.Name;
  }

  display += attName + " attacks! \r\r";

  var range = 5;
  var att = attacker.attack + Math.floor(Math.random() * range) - (Math.floor(range / 2));
  var def = defender.defense + Math.floor(Math.random() * range) - (Math.floor(range / 2));

  var damage = Math.max(0, (att - def))

  if (damage === 0) 
  {
    damage = Math.floor(Math.random() * 2);
  }

  if (damage > 0) 
  {
    display += attName + " does " + damage + " point";

    if (damage > 1) 
    {
      display += "s";
    }
    
    display += " of damage to " + defName + "!";
    defender.HP -= damage;
    defender.HP = Math.max(0, defender.HP);
  } 
  else 
  {
    display += attName + " misses! No damage is scored."
  }
  Show(display);
}

function BuyHerb(h)
{
  while (h.gold >= herbCost)
  {
    display = "An herb costs " + herbCost + " gold. \r\r";
    display += "You currently have " + h.herbs + " herb";
    if (h.herbs !== 0)
    {
      display += "s"
    }
    display += " and " + h.gold + " gold pieces.\r\r";
    display += "Do you want to buy an herb?";
    display += "\r\r(y)es \r\r(n)o ";
    
    var getInput = true;
    while (getInput)
    {
      var userInput = prompt(display);
      userInput = userInput.substring(0,1).toLowerCase();
      if (userInput !== "y" && userInput !== "n") 
      {
        continue;
      }
      if (userInput === "n")
      {
        getInput = false;
        return;
      }
      if (userInput === "y")
      {
        getInput = false;
        h.gold -= herbCost;
        h.herbs++;
      }
    }
  }
}


function UseHerb(h) 
{
  var range = 5;
  var herbHeal = Math.floor(Math.random() * range) - (Math.floor(range / 2));
  herbHeal = herbPower + herbHeal;

  h.HP += herbHeal;
  h.HP = Math.min(h.HP, h.HPMax);
  h.herbs--;

  return herbHeal;
}


function RunFromBattle(h) 
{
  var runChecker = Math.floor(Math.random() * 100);
  return (runChecker < h.runRate);
}


function Show(text, alrt = true) 
{
  console.log(text)
  if (alrt) {
    alert(text);
  }
}


function GetHero() 
{
  var n = prompt("What is your character's name?", "Dariak");
  //var n = "Dariak";

  var h = 
  {
    Name: n,
    HPMax: 20,
    herbs: 2,
    gold: 0,
    exp: 0,
    attack: 5,
    defense: 3,
    runRate: 25,
    level: 1,
    sightScope: false
  };

  h.HP = h.HPMax;

  return h;
}


//Decide if the hero levels up!
function LevelUp(h)
{
  var text = "";
  var levelChanged = true;
  var HPInc = 0;
  var attInc = 0;
  var defInc = 0;
  var totalAttInc = 0;
  var totalDefInc = 0;
  
  while (levelChanged)
  {
    levelChanged = false; //we want the loop to run at least once (instead of using a do/while loop)
    
    switch (h.level)
    {
      case 1:
        {
          if (h.exp > 5)
          {
            levelChanged = true;
            HPInc = 2;
            attInc = 1;
            defInc = 1;
          }
          break;
        }
        case 2:
        {
          if (h.exp > 15)
          {
            levelChanged = true;
            HPInc = 4;
            attInc = 1;
            defInc = 1;
            h.sightScope = true;
          }
          break;
        }
        case 3:
        {
          if (h.exp > 30)
          {
            levelChanged = true;
            HPInc = 5;
            attInc = 2;
            defInc = 1;
          }
          break;
        }
        case 4:
        {
          if (h.exp > 50)
          {
            levelChanged = true;
            HPInc = 10;
            attInc = 1;
            defInc = 2;
          }
          break;
        }
        default:  //keep growing if specific levels are not set
        {
          if (h.exp > (15 * h.level))
          {
            levelChanged = true;
            HPInc = h.level * 3;
            attInc = (h.level + 1) % 3;
            defInc = (h.level + 0) % 3;
            break;
          }
        }
        
    }
    
    //totalAttInc += attInc;
    //totalDefInc += defInc;
    
    if (levelChanged)
    {
      h.level++;
      h.attack += attInc;
      h.defense += defInc;
      h.HPMax += HPInc;
      h.HP += HPInc; //give some HP for levelling up
      text = "\r\rCongratulations! You leveled up!\r\r";
      text += "You're now level " + h.level + ".\r\r";
      text += "Your hit points increased by " + HPInc + " and is now " + h.HPMax + ".\r";
      text += "Your attack power increased by " + attInc + " and is now " + h.attack + ".\r";
      text += "Your defense power increased by " + defInc + " and is now " + h.defense + ".\r";
      if (h.sightScope)
      {
        text += "\r\rYou've learned a lot about your foes. You can now see their remaining HP.";
      }
      Show(text);
    }
  }
  return;
}

//This creates a copy of the monster so the original monster data is not overwritten
function CloneMonster(m) 
{
  var cm = {
    Name: m.Name,
    HPMax: m.HPMax,
    HP: m.HPMax,
    herbs: m.herbs,
    gold: m.gold,
    exp: m.exp,
    attack: m.attack,
    defense: m.defense
  }
  return cm;
}

function PlusMinusHalf(number)
{
  var newNumber = 0;
  newNumber = Math.floor(Math.random() * number) - (number / 2);
  newNumber = number + newNumber;
  newNumber = Math.floor(newNumber);
  return newNumber;
}

function MakeRandomMonster(h)
{
  var monHP = PlusMinusHalf(h.HPMax);
  var monHerbs = h.level * 5;
  var monAttack = PlusMinusHalf(h.attack);
  var monDefense = PlusMinusHalf(h.defense);
  var monExp = Math.ceil(Math.random() * h.level + PlusMinusHalf(h.level));
  var monGold = Math.ceil(Math.random() * h.level + PlusMinusHalf(h.level));
  var monName = GenerateRandomName();
  
  var mon = 
  {
    Name: monName,
    HPMax: monHP,
    herbs: monHerbs,
    gold: monGold,
    exp: monExp,
    attack: monAttack,
    defense: monDefense
  };
  return mon;
}

function GenerateRandomName()
{
  var makeName = "";
  var nameSnippets = ["dra", "con", "us", "mon", "ty", "mer", "lin", 
  "ti", "ger", "max", "bus", "ter", "bud", "dy", "med", "usa"];
  
  var rand = Math.ceil(Math.random() * 4) + 2;
  var rand2;
  
  for (var i = 0; i < rand; i++)
  {
    rand2 = Math.floor(Math.random() * nameSnippets.length);
    makeName += nameSnippets[rand2];
  }
  return makeName;
}

//This sets the list of monsters with all their stats.
function GetMonsterList() 
{
  var m = [];

  var mon =           ////to add a monster, copy from this line down...
  {
    Name: "squirret", //monster's name
    HPMax: 5,         //monster's Max and starting hit points
    herbs: 10,        //likelihood of monster using an herb 
    gold: 2,          //amount of gold won for defeating monster
    exp: 2,           //amount of experience for defeating monster
    attack: 2,        //monster's attack strength
    defense: 2        //monster's defense rate
  };
  m.push(mon);        ////...to here to add a monster

  mon = 
  {
    Name: "swallomer",
    HPMax: 10,
    herbs: 25,
    gold: 3,
    exp: 3,
    attack: 4,
    defense: 3
  };
  m.push(mon);

  mon = 
  {
    Name: "gleese",
    HPMax: 12,
    herbs: 30,
    gold: 6,
    exp: 6,
    attack: 3,
    defense: 6
  };
  m.push(mon);

  mon = 
  {
    Name: "rodia",
    HPMax: 14,
    herbs: 20,
    gold: 8,
    exp: 7,
    attack: 5,
    defense: 8
  };
  m.push(mon);

  mon = 
  {
    Name: "eaglon",
    HPMax: 18,
    herbs: 50,
    gold: 10,
    exp: 8,
    attack: 5,
    defense: 8
  };
  m.push(mon);

  mon = 
  {
    Name: "lupino",
    HPMax: 25,
    herbs: 50,
    gold: 15,
    exp: 11,
    attack: 8,
    defense: 9
  };
  m.push(mon);

  mon = 
  {
    Name: "tigroar",
    HPMax: 28,
    herbs: 60,
    gold: 18,
    exp: 10,
    attack: 9,
    defense: 10
  };
  m.push(mon);


  //after all the monsters have been added, send them onward
  return m;
}