Moved move activation from the game engine to the move itself and added the ability to switch out

This commit is contained in:
James Daly 2019-05-29 22:20:47 -04:00
parent 8ff9bdd938
commit 32c6913187
7 changed files with 237 additions and 178 deletions

View File

@ -23,8 +23,8 @@ object Game {
val movepool2 = IndexedSeq(Move("Absorb")) val movepool2 = IndexedSeq(Move("Absorb"))
val p1 = TrainerID("Jaeda", Gender.Female, 0) val p1 = TrainerID("Jaeda", Gender.Female, 0)
val p2 = TrainerID("Wild Monster", Gender.Male, 0) val p2 = TrainerID("Wild Monster", Gender.Male, 0)
val party1 = new Party(p1, new Monster(new StorageMon("Allied Mon", Gene.randomGene(null, form1), form1, Statistic.emptyEvs, movepool1)), IndexedSeq()) val party1 = new Party(p1, new MonsterPtr(new Monster(new StorageMon("Allied Mon", Gene.randomGene(null, form1), form1, Statistic.emptyEvs, movepool1))), IndexedSeq())
val party2 = new Party(p2, new Monster(new StorageMon("Wild Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2)), IndexedSeq( val party2 = new Party(p2, new MonsterPtr(new Monster(new StorageMon("Wild Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))), IndexedSeq(
new Monster(new StorageMon("Sideboard Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2)) new Monster(new StorageMon("Sideboard Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))
)) ))
val engine = new BattleEngine(party1, party2) val engine = new BattleEngine(party1, party2)

View File

@ -3,7 +3,7 @@ package mon.battle
import mon.stat._ import mon.stat._
import mon.stat.Statistic.Speed import mon.stat.Statistic.Speed
case class Action(user : Monster, move : Move, target : Monster) extends Comparable[Action] { case class Action(user : MonsterPtr, move : MoveTurn, target : MonsterPtr) extends Comparable[Action] {
override def compareTo(other : Action) = { override def compareTo(other : Action) = {
if (move.prior == other.move.prior) { if (move.prior == other.move.prior) {
other.user(Speed) compareTo user(Speed) other.user(Speed) compareTo user(Speed)

View File

@ -15,11 +15,11 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
} }
def playTurn() = { def playTurn() = {
val playerMove = player.pollAction val playerAction = player.pollAction(enemy)
val playerTarget = player.pollTarget(playerMove, player.lead, enemy) val enemyAction = enemy.pollAction(player)
val enemyMove = enemy.pollAction println(enemyAction)
val enemyTarget = enemy.pollTarget(enemyMove, enemy.lead, player)
val actions = Seq(Action(player.lead, playerMove, playerTarget), Action(enemy.lead, enemyMove, enemyTarget)) val actions = Seq(playerAction, enemyAction)
val queue = rng.shuffle(actions).sorted // Shuffle to randomize in the event of a tie val queue = rng.shuffle(actions).sorted // Shuffle to randomize in the event of a tie
queue.foreach(useMove) queue.foreach(useMove)
val eotQueue = Seq(player.lead, enemy.lead).sortBy(_(Speed)) val eotQueue = Seq(player.lead, enemy.lead).sortBy(_(Speed))
@ -28,7 +28,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
if (!player.lead.isAlive) { if (!player.lead.isAlive) {
if (player.canFight) { if (player.canFight) {
val replace = player.pollReplacement(enemy) val replace = player.pollReplacement(enemy)
player.swapIn(replace) player.switchIn(replace)
} else { } else {
println(s"${player.trainer} has lost!") println(s"${player.trainer} has lost!")
} }
@ -36,7 +36,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
if (!enemy.lead.isAlive) { if (!enemy.lead.isAlive) {
if (enemy.canFight) { if (enemy.canFight) {
val replace = enemy.pollReplacement(player) val replace = enemy.pollReplacement(player)
enemy.swapIn(replace) enemy.switchIn(replace)
} else { } else {
println(s"${enemy.trainer} has lost!") println(s"${enemy.trainer} has lost!")
} }
@ -51,117 +51,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
val move = action.move val move = action.move
val target = action.target val target = action.target
if (user.isAlive) { if (user.isAlive) {
println(s"$user used $move.") move.useMove(user, target)
if (attackRoll(user, move, target)) {
if (move.pow > 0 || move.powCallback != null) {
val dmg = damageRoll(user, move, target)
val actualDmg = target.takeDamage(dmg)
if (dmg == actualDmg) {
println(s"$target takes $actualDmg damage!")
} else {
println(s"$target takes $actualDmg (+${dmg - actualDmg} overkill) damage!")
}
if (move.drain > 0.frac) {
val healing = Math.max(move.drain * actualDmg, 1)
user.recoverDamage(healing)
println(s"$user recovered $healing damage.")
} else if (move.drain < 0.frac) {
val recoil = Math.max(-move.drain * actualDmg, 1)
user.takeDamage(recoil)
println(s"$user took $recoil damage from recoil!")
}
if (!user.isAlive) {
println(s"$user fainted!")
}
if (!target.isAlive) {
println(s"$target fainted!")
}
}
applyBoosts(target, move.boosts)
applyStatus(target, move.status)
if (move.selfEffect != null) {
applyEffect(user, move.selfEffect)
}
if (move.secondary != null && rng.chance(move.secondary.chance.%%)) {
applyEffect(target, move.secondary)
}
// TODO : Support moves
// TODO : Multiparty
} else {
println("Missed!")
}
}
}
def attackRoll(user: Monster, move: Move, target: Monster) = {
if (move.accuracy == 0) {
true
} else {
val acc = user.boosts.getOrElse(Accuracy, 0) - target.boosts.getOrElse(Evasion, 0)
val mod = if (acc > 0) Fraction(3 + acc, 3) else Fraction(3, 3 - acc)
val chance = move.accuracy.%% * mod
rng.chance(chance)
}
}
def damageRoll(user: Monster, move: Move, target: Monster) = {
val atkStat = if (move.mvType == MoveType.Physical) PAtk else MAtk
val defStat = if (move.mvType == MoveType.Physical) PDef else MDef
// TODO : Fixed damage
val pow = if (move.powCallback != null) move.powCallback(user, target) else move.pow
val baseDmg = (2 * user.level / 5 + 2) * pow * user(atkStat) / (target(defStat) * 50) + 2
val effectiveness = target.effectiveness(move.element)
val multiplier = effectiveness * critMultiplier(user, move, target)
if (effectiveness > 1.0) {
println("It's super effective!")
} else if (effectiveness < 1.0) {
println("It's not very effective.")
}
val maxDmg = (baseDmg * multiplier).toInt
val minDmg = (17 \ 20) * maxDmg
rng.nextInt(minDmg, maxDmg)
}
def critMultiplier(user : Monster, move : Move, target: Monster) = {
// Percentage chance is different from Pokemon
val stage = move.crit
val chance = (1 << stage) \ 16 // Doubles per stage
if (rng.chance(chance)) {
println("A critical hit!")
1.5
} else {
1.0
}
}
def applyEffect(target: Monster, effect: Secondary) {
applyBoosts(target, effect.boosts)
applyStatus(target, effect.status)
}
def applyStatus(target: Monster, status: Status) {
if (target.isAlive) {
if (status != null && target.status == None) {
// apply status
target.status = Some(status)
status.onStart.map(_(target))
}
}
}
def applyBoosts(target: Monster, boosts: Map[Stat, Int]) {
if (target.isAlive) {
boosts.foreach {
case (s, b) => {
target.applyBoost(s, b)
if (b > 0) {
println(s"$target's $s rose!")
} else if (b < 0) {
println(s"$target's $s fell!")
}
}
}
} }
} }
} }

View File

@ -0,0 +1,6 @@
package mon.stat
class MonsterPtr(var mon : Monster) {
override def toString : String = mon.toString()
}

View File

@ -6,7 +6,9 @@ import scala.tools.reflect.ToolBox
import com.fasterxml.jackson.module.scala.JsonScalaEnumeration import com.fasterxml.jackson.module.scala.JsonScalaEnumeration
import scala.io.Source import scala.io.Source
import scala.util.Random
import Statistic._
import mon.util._ import mon.util._
object MoveType extends Enumeration { object MoveType extends Enumeration {
@ -19,7 +21,28 @@ object Target extends Enumeration {
} }
class TargetType extends TypeReference[Target.type] class TargetType extends TypeReference[Target.type]
abstract class Move { trait MoveTurn {
def name: String
def prior: Int
def target: Target
def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit
}
class SwitchOut(val party: Party, val replacement: Monster) extends MoveTurn {
def name = "Swap"
def prior = +6
def target = Target.Self
def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit = {
println(s"${party.trainer} withdrew $user!")
party.switchIn(replacement)
println(s"${party.trainer} sent out $replacement")
}
override def toString: String = s"Swap -> $replacement"
}
abstract class Move extends MoveTurn {
val name: String val name: String
val desc: String val desc: String
val mvType: MoveType val mvType: MoveType
@ -43,6 +66,120 @@ abstract class Move {
// zPower, zMoveEffect, zMoveBoost // zPower, zMoveEffect, zMoveBoost
override def useMove(user: Monster, target: Monster)(implicit rng: Random) = {
println(s"$user used $name.")
if (attackRoll(user, target)) {
if (pow > 0 || powCallback != null) {
val dmg = damageRoll(user, target)
val actualDmg = target.takeDamage(dmg)
if (dmg == actualDmg) {
println(s"$target takes $actualDmg damage!")
} else {
println(s"$target takes $actualDmg (+${dmg - actualDmg} overkill) damage!")
}
if (drain > 0.frac) {
val healing = Math.max(drain * actualDmg, 1)
user.recoverDamage(healing)
println(s"$user recovered $healing damage.")
} else if (drain < 0.frac) {
val recoil = Math.max(-drain * actualDmg, 1)
user.takeDamage(recoil)
println(s"$user took $recoil damage from recoil!")
}
if (!user.isAlive) {
println(s"$user fainted!")
}
if (!target.isAlive) {
println(s"$target fainted!")
}
}
applyBoosts(target, boosts)
applyStatus(target, status)
if (selfEffect != null) {
applyEffect(user, selfEffect)
}
if (secondary != null && rng.chance(secondary.chance.%%)) {
applyEffect(target, secondary)
}
// TODO : Support moves
// TODO : Multiparty
} else {
println("Missed!")
}
}
def attackRoll(user: Monster, target: Monster)(implicit rng: Random) = {
if (accuracy == 0) {
true
} else {
val acc = user.boosts.getOrElse(Accuracy, 0) - target.boosts.getOrElse(Evasion, 0)
val mod = if (acc > 0) Fraction(3 + acc, 3) else Fraction(3, 3 - acc)
val chance = accuracy.%% * mod
rng.chance(chance)
}
}
def damageRoll(user: Monster, target: Monster)(implicit rng: Random) = {
val atkStat = if (mvType == MoveType.Physical) PAtk else MAtk
val defStat = if (mvType == MoveType.Physical) PDef else MDef
// TODO : Fixed damage
val actualPow = if (powCallback != null) powCallback(user, target) else pow
val baseDmg = (2 * user.level / 5 + 2) * actualPow * user(atkStat) / (target(defStat) * 50) + 2
val effectiveness = target.effectiveness(element)
val multiplier = effectiveness * critMultiplier(user, target)
if (effectiveness > 1.0) {
println("It's super effective!")
} else if (effectiveness < 1.0) {
println("It's not very effective.")
}
val maxDmg = (baseDmg * multiplier).toInt
val minDmg = (17 \ 20) * maxDmg
rng.nextInt(minDmg, maxDmg)
}
def critMultiplier(user: Monster, target: Monster)(implicit rng: Random) = {
// Percentage chance is different from Pokemon
val stage = crit
val chance = (1 << stage) \ 16 // Doubles per stage
if (rng.chance(chance)) {
println("A critical hit!")
1.5
} else {
1.0
}
}
def applyEffect(target: Monster, effect: Secondary) {
applyBoosts(target, effect.boosts)
applyStatus(target, effect.status)
}
def applyStatus(target: Monster, status: Status) {
if (target.isAlive) {
if (status != null && target.status == None) {
// apply status
target.status = Some(status)
status.onStart.map(_(target))
}
}
}
def applyBoosts(target: Monster, boosts: Map[Stat, Int]) {
if (target.isAlive) {
boosts.foreach {
case (s, b) => {
target.applyBoost(s, b)
if (b > 0) {
println(s"$target's $s rose!")
} else if (b < 0) {
println(s"$target's $s fell!")
}
}
}
}
}
override def toString = { override def toString = {
name name
} }

View File

@ -2,12 +2,24 @@ package mon.stat
import scala.util.Random import scala.util.Random
import mon.battle.rngDice import mon.battle.Action
import mon.stat.Target._ import mon.stat.Target._
class Party(val trainer : TrainerID, var lead : Monster, var sideboard : IndexedSeq[Monster]) { class Party(val trainer: TrainerID, val lead: MonsterPtr, var sideboard: IndexedSeq[Monster]) {
def pollAction(implicit rng : Random) : Move = rng.pick(lead.base.moves) def pollAction(them: Party)(implicit rng: Random): Action = {
def pollTarget(move : Move, user : Monster, them: Party)(implicit rng : Random) : Monster = { if (hasBackup && shouldSwitch(them)) {
val sub = pollReplacement(them: Party)
val move = new SwitchOut(this, sub)
Action(lead, move, lead)
} else {
val move = pollMove
val target = pollTarget(move, lead, them)
Action(lead, move, target)
}
}
def pollMove(implicit rng: Random): MoveTurn = rng.pick(lead.base.moves)
def pollTarget(move: MoveTurn, user: MonsterPtr, them: Party)(implicit rng: Random): MonsterPtr = {
if (move.target == Self) { if (move.target == Self) {
user user
} else { } else {
@ -19,13 +31,21 @@ class Party(val trainer : TrainerID, var lead : Monster, var sideboard : Indexed
sideboard.filter(_.isAlive).head sideboard.filter(_.isAlive).head
} }
def swapIn(mon : Monster) = { def switchIn(mon: Monster) = {
val index = sideboard.indexOf(mon) val index = sideboard.indexOf(mon)
sideboard = sideboard.updated(index, lead) sideboard = sideboard.updated(index, lead.mon)
lead = mon lead.mon = mon
}
def shouldSwitch(them: Party)(implicit rng: Random): Boolean = {
true
} }
def canFight: Boolean = { def canFight: Boolean = {
lead.isAlive || sideboard.exists(_.isAlive) lead.isAlive || hasBackup
}
def hasBackup: Boolean = {
sideboard.exists(_.isAlive)
} }
} }

View File

@ -3,6 +3,10 @@ package mon
import org.json4s.DefaultFormats import org.json4s.DefaultFormats
import org.json4s.ext.EnumNameSerializer import org.json4s.ext.EnumNameSerializer
import scala.util.Random
import mon.util.Dice
package object stat { package object stat {
type Stat = Statistic.Value type Stat = Statistic.Value
type MoveType = MoveType.Value type MoveType = MoveType.Value
@ -10,4 +14,6 @@ package object stat {
type Target = Target.Value type Target = Target.Value
implicit val formats = DefaultFormats + new EnumNameSerializer(MoveType) + new EnumNameSerializer(Gender) implicit val formats = DefaultFormats + new EnumNameSerializer(MoveType) + new EnumNameSerializer(Gender)
implicit def rngDice(rng : Random) = new Dice(rng)
implicit def ptrToMonster(ptr : MonsterPtr) : Monster = ptr.mon
} }