diff --git a/build.gradle b/build.gradle index d2b2c29..59ec1d1 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.2' + classpath 'com.android.tools.build:gradle:3.5.3' classpath 'com.mobidevelop.robovm:robovm-gradle-plugin:2.3.7' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" diff --git a/core/src/me/msoucy/ptures/controller/Engine.kt b/core/src/me/msoucy/ptures/controller/Engine.kt new file mode 100644 index 0000000..413bab9 --- /dev/null +++ b/core/src/me/msoucy/ptures/controller/Engine.kt @@ -0,0 +1,119 @@ +package me.msoucy.ptures.controller + +import me.msoucy.ptures.model.Creature +import me.msoucy.ptures.model.KnockedOut +import me.msoucy.ptures.model.Skill +import me.msoucy.ptures.model.Target +import me.msoucy.ptures.model.Team +import me.msoucy.ptures.view.BattleView +import me.msoucy.ptures.view.SkillChoice + +sealed class BattleType(vararg val teams: Team) { + init { + assert(teams.isNotEmpty()) + } + + abstract val numGroups: Int +} + +sealed class IndividualBattle(vararg teams: Team) : BattleType(*teams) { + override val numGroups: Int + get() = teams.size +} + +class SingleBattle(vararg teams: Team) : IndividualBattle(*teams) +class DoubleBattle(vararg teams: Team) : IndividualBattle(*teams) +class TripleBattle(vararg teams: Team) : IndividualBattle(*teams) +class TeamBattle(override val numGroups: Int, vararg teams: Team) : BattleType(*teams) +class RaidBattle(val boss: Creature, vararg teams: Team) : BattleType(*teams) { + override val numGroups: Int + get() = 2 +} + + +class Engine(private val battle: BattleType) { + + private var currentCreature = 0 + + private val creatures get() = battle.teams.flatMap { it.activeCreatures } + + val attacker: Creature + get() { + return creatures[currentCreature].creature + } + + private val forcedSkills = mutableMapOf() + + private val activeCreatures + get() = creatures + .filter { !it.creature.hasStatus() } + .sortedBy { it.creature.spd } + + fun resolveTurn(view: BattleView) { + forcedSkills.clear() + // All preconditions + for (i in activeCreatures.indices) { + val creature = creatures[i] + for (status in creature.creature.statuses) { + status.onTurnStart(this, creature.creature) + } + } + // Get the moves each creature will use this turn + val moves = activeCreatures.indices.map { i -> + val forcedSkill = forcedSkills[creatures[i].creature] + if (forcedSkill != null) { + SkillChoice(forcedSkill, creatures[nextOpponent(i)].creature) + } else { + creatures[i].chooseSkill(activeCreatures.map { it.creature }) + } + } + // Resolve each move + for (i in activeCreatures.indices) { + // Resolve move + val (skill, target) = moves[i] + currentCreature = i + for (step in skill.damageSteps) { + for (targetCreature in getTargetList(step.target, target)) { + if (attacker.hits(targetCreature, step)) { + targetCreature.apply(step, attacker) + step.applyStatus(targetCreature) + } + } + } + for (step in skill.postSteps) { + for (targetCreature in getTargetList(step.target, target)) { + step.apply(targetCreature) + } + } + } + // All post conditions + for (creatureView in activeCreatures) { + val creature = creatureView.creature + for (status in creature.statuses) { + status.onTurnEnd(this) + } + } + } + + private fun getTargetList(target: Target, selected: Creature): List { + return when (target) { + Target.Self -> listOf(attacker) + Target.Selected -> listOf(selected) + Target.Others -> creatures.filter { it.creature != attacker }.map { it.creature } + Target.Opponents -> creatures.filter { it.playerId != creatures[currentCreature].playerId }.map { it.creature } + Target.All -> creatures.map { it.creature } + } + } + + private fun nextOpponent(idx: Int): Int { + var nextIdx = idx + 1 + while (nextIdx != idx) { + if (creatures[nextIdx].playerId != creatures[idx].playerId) { + return nextIdx + } + nextIdx = (nextIdx + 1) % creatures.size + } + // There are no opponents... so use the "none" index + return -1 + } +} \ No newline at end of file diff --git a/core/src/me/msoucy/ptures/model/Engine.kt b/core/src/me/msoucy/ptures/model/Engine.kt deleted file mode 100644 index 4bc9feb..0000000 --- a/core/src/me/msoucy/ptures/model/Engine.kt +++ /dev/null @@ -1,91 +0,0 @@ -package me.msoucy.ptures.model - -import me.msoucy.ptures.view.BattleView - -class Engine(private vararg val creatures : Pair) { - init { - assert(creatures.isNotEmpty()) - } - - private var currentCreature = 0 - - val attacker : Creature get() { - return creatures[currentCreature].first - } - - val forcedSkills = mutableMapOf() - - val activeCreatures get() = creatures.withIndex() - .filter { (_, c) -> !c.first.hasStatus() } - .sortedBy { (_, c) -> c.first.spd } - .map { (i, _) -> i} - - fun resolveTurn(view : BattleView) { - forcedSkills.clear() - // All preconditions - for (i in activeCreatures) { - val creature = creatures[i] - for (status in creature.first.statuses) { - status.onTurnStart(this, creature.first) - } - } - // Get the moves each creature will use this turn - val moves = activeCreatures.map { i -> - if (creatures[i].first in forcedSkills) { - Pair(forcedSkills[creatures[i].first], nextOpponent(i)) - } else { - TODO("Fill in view here") - } - } - // Resolve each move - for (i in activeCreatures) { - // Resolve move - val (skill, target) = moves[i] - currentCreature = i - for (step in skill.damageSteps) { - for (t in getTargetList(step.target, target)) { - val (targetCreature, _) = creatures[t] - if (attacker.hits(targetCreature, step)) { - targetCreature.apply(step, attacker) - step.applyStatus(targetCreature) - } - } - } - for(step in skill.postSteps) { - for (t in getTargetList(step.target, target)) { - val (targetCreature, _) = creatures[t] - step.apply(targetCreature) - } - } - } - // All post conditions - for (i in activeCreatures) { - val creature = creatures[i] - for (status in creature.first.statuses) { - status.onTurnEnd(this) - } - } - } - - private fun getTargetList(target : Target, selected : Int) : List { - return when(target) { - Target.Self -> listOf(currentCreature) - Target.Selected -> if (selected == -1) { listOf() } else { listOf(selected) } - Target.Others -> creatures.indices.filter { it != currentCreature } - Target.Opponents -> creatures.indices.filter { creatures[it].second != creatures[currentCreature].second } - Target.All -> creatures.indices.toList() - } - } - - private fun nextOpponent(idx : Int) : Int { - var nextIdx = idx + 1 - while (nextIdx != idx) { - if (creatures[nextIdx].second != creatures[idx].second) { - return nextIdx - } - nextIdx = (nextIdx + 1) % creatures.size - } - // There are no opponents... so use the "none" index - return -1 - } -} \ No newline at end of file diff --git a/core/src/me/msoucy/ptures/model/Skills.kt b/core/src/me/msoucy/ptures/model/Skills.kt index 9ef6c13..48cbe61 100644 --- a/core/src/me/msoucy/ptures/model/Skills.kt +++ b/core/src/me/msoucy/ptures/model/Skills.kt @@ -22,14 +22,14 @@ enum class Attribute { Wood } -class Damage(val power : Int, val attribute : Attribute = Attribute.Neutral) { +class Damage(val power: Int, val attribute: Attribute = Attribute.Neutral) { var target = Target.Selected var accuracy = 100 - var status : () -> Status? = {null} + var status: () -> Status? = { null } var statusChance = 0 - fun applyStatus(creature : Creature) { + fun applyStatus(creature: Creature) { val appStatus = status() if (appStatus != null && random(100) < statusChance) { creature.addStatus(appStatus) @@ -37,34 +37,42 @@ class Damage(val power : Int, val attribute : Attribute = Attribute.Neutral) { } } -class StatusApplier(val target : Target, val apply : (Creature) -> Unit) +class StatusApplier(val target: Target, val apply: (Creature) -> Unit) @SkillMarker -sealed class Skill(open val name : String, val attribute : Attribute) { +open class Skill(open val name: String, val attribute: Attribute) { val damageSteps = mutableListOf() val postSteps = mutableListOf() - fun damage(power : Int, block : Damage.() -> Unit = {}) { + fun damage(power: Int, block: Damage.() -> Unit = {}) { val d = Damage(power, attribute) d.block() damageSteps.add(d) } - fun addStatus(status : Status, target : Target = Target.Selected) { - postSteps.add (StatusApplier(target) { c -> c.addStatus(status) }) + fun addStatus(status: Status, target: Target = Target.Selected) { + postSteps.add(StatusApplier(target) { c -> c.addStatus(status) }) } - fun removeStatus(status : Status, target : Target = Target.Selected) { - postSteps.add (StatusApplier(target) { c -> c.removeStatus(status) }) + fun removeStatus(status: Status, target: Target = Target.Selected) { + postSteps.add(StatusApplier(target) { c -> c.removeStatus(status) }) } + + // Assume that the first damage step is the one that provides the target + val target: Target + get() = if (damageSteps.isNotEmpty()) { + damageSteps[0].target + } else { + Target.Self + } } -class AdvancedSkill(name : String, attribute : Attribute) : Skill(name, attribute) { - override val name : String get() = super.name + "+" - val baseName : String get() = super.name +class AdvancedSkill(name: String, attribute: Attribute) : Skill(name, attribute) { + override val name: String get() = super.name + "+" + val baseName: String get() = super.name } -fun skill(name : String, attribute : Attribute = Attribute.Neutral, block : Skill.() -> Unit) : Skill { +fun skill(name: String, attribute: Attribute = Attribute.Neutral, block: Skill.() -> Unit): Skill { val ret = Skill(name, attribute) ret.block() return ret diff --git a/core/src/me/msoucy/ptures/model/Status.kt b/core/src/me/msoucy/ptures/model/Status.kt index f93cf15..695296e 100644 --- a/core/src/me/msoucy/ptures/model/Status.kt +++ b/core/src/me/msoucy/ptures/model/Status.kt @@ -1,5 +1,7 @@ package me.msoucy.ptures.model +import me.msoucy.ptures.controller.Engine + sealed class Status { open fun onTurnStart(engine : Engine, creature : Creature) { diff --git a/core/src/me/msoucy/ptures/model/Team.kt b/core/src/me/msoucy/ptures/model/Team.kt new file mode 100644 index 0000000..585538d --- /dev/null +++ b/core/src/me/msoucy/ptures/model/Team.kt @@ -0,0 +1,11 @@ +package me.msoucy.ptures.model + +import me.msoucy.ptures.view.PlayerView + +class Team(var view : PlayerView) { + + var creatures = mutableListOf() + var selectedCreatures = mutableListOf(0) + + val activeCreatures get() = selectedCreatures.map { view.creatureViewFor(creatures[it]) } +} \ No newline at end of file diff --git a/core/src/me/msoucy/ptures/view/TextView.kt b/core/src/me/msoucy/ptures/view/TextView.kt index e3bbccb..67a586b 100644 --- a/core/src/me/msoucy/ptures/view/TextView.kt +++ b/core/src/me/msoucy/ptures/view/TextView.kt @@ -4,21 +4,21 @@ import me.msoucy.ptures.model.Creature import me.msoucy.ptures.model.Skill import me.msoucy.ptures.model.VisibleStatus -class SkillViewText(val skill : Skill) : SkillView() { +class SkillViewText(skill : Skill) : SkillView(skill) { override fun display() { println(skill.name) } override fun displayEnumerated(idx: Int) { - println("${idx}: ${skill.name}") + println("${idx+1}: ${skill.name}") } } -class CreatureViewText(val creature : Creature) : CreatureView() { +class CreatureViewText(playerId : Int, creature : Creature) : CreatureView(playerId, creature) { private val skillViews = creature.skills.map { SkillViewText(it) } - override val skillChoice : Skill get() { + private fun chooseSkillName() : Skill { println("Skills:") println("=======") skillViews.forEachIndexed { index, skillView -> @@ -33,10 +33,21 @@ class CreatureViewText(val creature : Creature) : CreatureView() { idx = tmpIdx } } - return creature.skills[idx] } + private fun chooseTarget(skill : Skill, possibleTargets : List) : Creature { + return creature + } + + override fun chooseSkill(possibleTargets : List) : SkillChoice { + + val skill = chooseSkillName() + val target = chooseTarget(skill, possibleTargets) + + return SkillChoice(skill, target) + } + override fun displayName() { println(creature.name) } @@ -52,4 +63,8 @@ class CreatureViewText(val creature : Creature) : CreatureView() { println(it.label) } } +} + +class PlayerViewText(playerId : Int) : PlayerView(playerId) { + override fun creatureViewFor(creature: Creature) = CreatureViewText(playerId, creature) } \ No newline at end of file diff --git a/core/src/me/msoucy/ptures/view/ViewBase.kt b/core/src/me/msoucy/ptures/view/ViewBase.kt index 70d8219..115d59e 100644 --- a/core/src/me/msoucy/ptures/view/ViewBase.kt +++ b/core/src/me/msoucy/ptures/view/ViewBase.kt @@ -1,21 +1,27 @@ package me.msoucy.ptures.view +import me.msoucy.ptures.model.Creature import me.msoucy.ptures.model.Skill -interface SkillView { - fun display() - fun displayEnumerated(idx : Int) +data class SkillChoice (val skill : Skill, val target : Creature) + +abstract class SkillView(val skill : Skill) { + abstract fun display() + abstract fun displayEnumerated(idx : Int) } -interface CreatureView { +abstract class CreatureView(val playerId : Int, val creature: Creature) { - val skillChoice : Skill + abstract fun chooseSkill(possibleTargets : List) : SkillChoice - fun displayName() - fun displaySkills() - fun displayStatuses() + abstract fun displayName() + abstract fun displaySkills() + abstract fun displayStatuses() } -interface BattleView { - // +abstract class BattleView { +} + +abstract class PlayerView(val playerId : Int) { + abstract fun creatureViewFor(creature: Creature) : CreatureView } \ No newline at end of file