Skip to content

Commit

Permalink
Fully implemented Sleep mechanics
Browse files Browse the repository at this point in the history
Also moved Pokémon status/volatile status related tests to a separate test file
Shortened paralysisCheck by using a guard with an early return, instead of an if statement
  • Loading branch information
rhysm94 committed Apr 9, 2018
1 parent cd66c1c commit a627c2b
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 86 deletions.
8 changes: 8 additions & 0 deletions PokemonKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@
91EA2161206F859800C4B26D /* VolatileStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91EA215D206F859800C4B26D /* VolatileStatus.swift */; };
91EA2162206F859800C4B26D /* VolatileStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91EA215D206F859800C4B26D /* VolatileStatus.swift */; };
91EA2163206F859800C4B26D /* VolatileStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91EA215D206F859800C4B26D /* VolatileStatus.swift */; };
91EA5317207BE150005A62CC /* StatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91EA5316207BE150005A62CC /* StatusTests.swift */; };
91EA5318207BE150005A62CC /* StatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91EA5316207BE150005A62CC /* StatusTests.swift */; };
91EA5319207BE150005A62CC /* StatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91EA5316207BE150005A62CC /* StatusTests.swift */; };
91FF59C220472F9C00CBEBD5 /* PokemonKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91A0DB78204710C600D7B67A /* PokemonKitTests.swift */; };
91FF59C320472F9D00CBEBD5 /* PokemonKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91A0DB78204710C600D7B67A /* PokemonKitTests.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -232,6 +235,7 @@
91DF932020499BD100F6C67B /* GameKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameKit.framework; path = System/Library/Frameworks/GameKit.framework; sourceTree = SDKROOT; };
91DF932220499BD700F6C67B /* GameKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/System/Library/Frameworks/GameKit.framework; sourceTree = DEVELOPER_DIR; };
91EA215D206F859800C4B26D /* VolatileStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolatileStatus.swift; sourceTree = "<group>"; };
91EA5316207BE150005A62CC /* StatusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -426,6 +430,7 @@
isa = PBXGroup;
children = (
91A0DB78204710C600D7B67A /* PokemonKitTests.swift */,
91EA5316207BE150005A62CC /* StatusTests.swift */,
);
path = Sources;
sourceTree = "<group>";
Expand Down Expand Up @@ -867,6 +872,7 @@
91A0DBCB2047150F00D7B67A /* Random.swift in Sources */,
91A0DBC22047150F00D7B67A /* BattleAction.swift in Sources */,
91A0DBC12047150F00D7B67A /* Attack.swift in Sources */,
91EA5317207BE150005A62CC /* StatusTests.swift in Sources */,
91A0DBD02047150F00D7B67A /* Type.swift in Sources */,
91A0DBCA2047150F00D7B67A /* PokemonSpecies.swift in Sources */,
91A0DBCD2047150F00D7B67A /* Status.swift in Sources */,
Expand Down Expand Up @@ -895,6 +901,7 @@
91A0DBEF2047151000D7B67A /* Pokemon.swift in Sources */,
91A0DBE82047151000D7B67A /* BattleAction.swift in Sources */,
91A0DBF32047151000D7B67A /* Status.swift in Sources */,
91EA5318207BE150005A62CC /* StatusTests.swift in Sources */,
91A0DBEC2047151000D7B67A /* Nature.swift in Sources */,
91A0DBF42047151000D7B67A /* Terrain.swift in Sources */,
91A0DBF72047151000D7B67A /* Weather.swift in Sources */,
Expand Down Expand Up @@ -923,6 +930,7 @@
91A0DC152047151100D7B67A /* Pokemon.swift in Sources */,
91A0DC0E2047151100D7B67A /* BattleAction.swift in Sources */,
91A0DC192047151100D7B67A /* Status.swift in Sources */,
91EA5319207BE150005A62CC /* StatusTests.swift in Sources */,
91A0DC122047151100D7B67A /* Nature.swift in Sources */,
91A0DC1A2047151100D7B67A /* Terrain.swift in Sources */,
91A0DC1D2047151100D7B67A /* Weather.swift in Sources */,
Expand Down
34 changes: 25 additions & 9 deletions PokemonKit/Sources/BattleEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,19 +237,31 @@ public class BattleEngine: NSObject, GKGameModel {
}

func paralysisCheck() -> Bool {
if attacker.status == .paralysed {
let diceRoll = Random.shared.d3Roll()
if diceRoll == 1 {
view?.queue(action: .displayText("\(attacker) is paralysed"))
return false
} else {
return true
}
// Early return if Pokémon status isn't paralysed
guard attacker.status == .paralysed else { return true }

let diceRoll = Random.shared.d3Roll()
if diceRoll == 1 {
view?.queue(action: .displayText("\(attacker) is paralysed"))
return false
} else {
return true
}
}

func sleepCheck() -> Bool {
// Early return if Pokémon's status isn't asleep
guard case let .asleep(counter) = attacker.status else { return true }

if counter == 0 {
attacker.status = .healthy
view?.queue(action: .displayText("\(attacker) woke up!"))
return true
} else {
return false
}
}

func protectedCheck() -> Bool {
if defender.volatileStatus.contains(.protected) && !attack.breaksProtect {
view?.queue(action: .displayText("\(attacker.nickname) used \(attack.name)"))
Expand All @@ -275,7 +287,7 @@ public class BattleEngine: NSObject, GKGameModel {
}
}

let shouldAttack = [confusionCheck(), paralysisCheck(), protectedCheck(), hitCheck()].reduce(true) { $0 && $1 }
let shouldAttack = [confusionCheck(), sleepCheck(), paralysisCheck(), protectedCheck(), hitCheck()].reduce(true) { $0 && $1 }

func successfulDamage() {
// If the condition for a multi-turn move is matched, use it immediately (e.g. in the case of Solar Beam, if the weather is sunny)
Expand Down Expand Up @@ -375,6 +387,10 @@ public class BattleEngine: NSObject, GKGameModel {
view?.queue(action: .statusDamage(.badlyPoisoned, player.activePokemon, poisonDamage))
}

if case let .asleep(counter) = player.activePokemon.status {
player.activePokemon.status = .asleep(counter - 1)
}

player.activePokemon.volatileStatus.remove(.protected)
player.activePokemon.volatileStatus.remove(.flinch)

Expand Down
77 changes: 0 additions & 77 deletions PokemonKit/Tests/Sources/PokemonKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,6 @@ class PokemonKitTests: XCTestCase {
XCTAssertLessThanOrEqual(joe.activePokemon.currentHP, 17)
}

func testParalysisApplied() {
Random.shared = Random(seed: "willhit")

engine.addTurn(Turn(player: joe, action: .attack(attack: Pokedex.default.attacks["Thunder Wave"]!)))
engine.addTurn(Turn(player: rhys, action: .attack(attack: rhys.activePokemon.attacks[0])))

XCTAssert(rhys.activePokemon.status == .paralysed)
}

func testProteanMessage() {
let greninjaSpecies = PokemonSpecies(dexNum: 658, identifier: "greninja", name: "Greninja", typeOne: .water, typeTwo: .dark, stats: Stats(hp: 72, atk: 95, def: 67, spAtk: 103, spDef: 71, spd: 122), abilityOne: Ability(name: "Some", description: "Ability"), hiddenAbility: Ability(name: "Protean", description: "Changes Pokémon type to move type", activationMessage: Pokedex.activationMessage["Protean"]))
let greninja = Pokemon(species: greninjaSpecies, level: 100, ability: greninjaSpecies.hiddenAbility!, nature: .timid, effortValues: .empty, individualValues: .fullIVs, attacks: [])
Expand Down Expand Up @@ -273,14 +264,6 @@ class PokemonKitTests: XCTestCase {
XCTAssertEqual(rhys.activePokemon.statStages.atk, -2)
}

func testConfusedVolatileStatus() {
var confusion = VolatileStatus.confused(1)
confusion = confusion.turn()

XCTAssertEqual(VolatileStatus.confused(0), confusion)
XCTAssertNotEqual(VolatileStatus.confused(1), confusion)
}

func testGigaDrain() {

let beforeTurnHP = rhys.activePokemon.currentHP
Expand All @@ -300,66 +283,6 @@ class PokemonKitTests: XCTestCase {
XCTAssertEqual(effectiveness, Type.Effectiveness.superEffective)
}

func testConfusion() {
// Seed guaranteed to cause Joe's active Pokémon to hurt itself in its confusion
Random.shared = Random(seed: "confused")

joe.activePokemon.volatileStatus.insert(.confused(3))

engine.addTurn(Turn(player: joe, action: .attack(attack: joe.activePokemon.attacks[0])))
engine.addTurn(Turn(player: rhys, action: .attack(attack: tackle)))

// This is true when Joe's Pokémon hasn't been able to attack - e.g. due to confusion
XCTAssertEqual(rhys.activePokemon.currentHP, rhys.activePokemon.baseStats.hp)
}

func testConfusionWearsOff() {
Random.shared = Random(seed: "confused")

joe.activePokemon.volatileStatus.insert(.confused(1))

XCTAssertTrue(joe.activePokemon.volatileStatus.contains(.confused(1)))
XCTAssertFalse(joe.activePokemon.volatileStatus.contains(.confused(0)))

engine.addTurn(Turn(player: rhys, action: .attack(attack: tackle)))
engine.addTurn(Turn(player: joe, action: .attack(attack: joe.activePokemon.attacks[0])))

XCTAssertFalse(joe.activePokemon.volatileStatus.contains(.confused(1)))
XCTAssertTrue(joe.activePokemon.volatileStatus.contains(.confused(0)))

engine.addTurn(Turn(player: rhys, action: .attack(attack: tackle)))
engine.addTurn(Turn(player: joe, action: .attack(attack: tackle)))

var containsConfused = false
// Checks for *any* occurence of confusion in Pokémon's volatile status
for case let .confused(number) in joe.activePokemon.volatileStatus {
print(".confused(\(number))")
containsConfused = true
}

XCTAssertFalse(containsConfused)
}

func testConfusionAppliesOnce() {
Random.shared = Random(seed: "confused")

joe.activePokemon.volatileStatus.insert(.confused(2))

let confuseRay = Pokedex.default.attacks["Confuse Ray"]!

engine.addTurn(Turn(player: rhys, action: .attack(attack: confuseRay)))
engine.addTurn(Turn(player: joe, action: .attack(attack: joe.activePokemon.attacks[0])))

var confusionOccurences = 0

for case let .confused(number) in joe.activePokemon.volatileStatus {
print(".confused(\(number))")
confusionOccurences += 1
}

XCTAssertEqual(confusionOccurences, 1)
}

func testNotEffectiveDamage() {
let gengarSpecies = Pokedex.default.pokemon[93]
let gengar = Pokemon(species: gengarSpecies, level: 50, ability: gengarSpecies.abilityOne, nature: .modest, effortValues: Stats(hp: 0, atk: 0, def: 0, spAtk: 252, spDef: 6, spd: 252), individualValues: .fullIVs, attacks: [])
Expand Down
143 changes: 143 additions & 0 deletions PokemonKit/Tests/Sources/StatusTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//
// StatusTests.swift
// PokemonKit
//
// Created by Rhys Morgan on 09/04/2018.
// Copyright © 2018 Rhys Morgan. All rights reserved.
//

import XCTest

class StatusTests: XCTestCase {

var bulbasaur: Pokemon!
var pikachu: Pokemon!
let rhys = Player(name: "Rhys")
let joe = Player(name: "Joe")

var engine: BattleEngine!

let sludgeBomb = Pokedex.default.attacks["Sludge Bomb"]!
let gigaDrain = Pokedex.default.attacks["Giga Drain"]!
let thunderbolt = Pokedex.default.attacks["Thunderbolt"]!
let thunder = Pokedex.default.attacks["Thunder"]!
let tackle = Pokedex.default.attacks["Tackle"]!

override func setUp() {
super.setUp()

Random.shared = Random(seed: UUID().uuidString)

let bulbasaurSpecies = Pokedex.default.pokemon["bulbasaur"]!
bulbasaur = Pokemon(species: bulbasaurSpecies, level: 50, nature: .modest, effortValues: Stats(hp: 0, atk: 0, def: 4, spAtk: 252, spDef: 0, spd: 252), individualValues: .fullIVs, attacks: [sludgeBomb, gigaDrain])

let pikachuSpecies = Pokedex.default.pokemon["pikachu"]!
pikachu = Pokemon(species: pikachuSpecies, level: 50, nature: .timid, effortValues: Stats(hp: 0, atk: 0, def: 4, spAtk: 252, spDef: 0, spd: 252), individualValues: .fullIVs, attacks: [thunderbolt, thunder])

rhys.add(pokemon: bulbasaur)
joe.add(pokemon: pikachu)

engine = BattleEngine(playerOne: rhys, playerTwo: joe)
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}

func testParalysisApplied() {
Random.shared = Random(seed: "willhit")

engine.addTurn(Turn(player: joe, action: .attack(attack: Pokedex.default.attacks["Thunder Wave"]!)))
engine.addTurn(Turn(player: rhys, action: .attack(attack: rhys.activePokemon.attacks[0])))

XCTAssert(rhys.activePokemon.status == .paralysed)
}

func testConfusedVolatileStatus() {
var confusion = VolatileStatus.confused(1)
confusion = confusion.turn()

XCTAssertEqual(VolatileStatus.confused(0), confusion)
XCTAssertNotEqual(VolatileStatus.confused(1), confusion)
}

func testConfusion() {
// Seed guaranteed to cause Joe's active Pokémon to hurt itself in its confusion
Random.shared = Random(seed: "confused")

joe.activePokemon.volatileStatus.insert(.confused(3))

engine.addTurn(Turn(player: joe, action: .attack(attack: joe.activePokemon.attacks[0])))
engine.addTurn(Turn(player: rhys, action: .attack(attack: tackle)))

// This is true when Joe's Pokémon hasn't been able to attack - e.g. due to confusion
XCTAssertEqual(rhys.activePokemon.currentHP, rhys.activePokemon.baseStats.hp)
}

func testConfusionWearsOff() {
Random.shared = Random(seed: "confused")

joe.activePokemon.volatileStatus.insert(.confused(1))

XCTAssertTrue(joe.activePokemon.volatileStatus.contains(.confused(1)))
XCTAssertFalse(joe.activePokemon.volatileStatus.contains(.confused(0)))

engine.addTurn(Turn(player: rhys, action: .attack(attack: tackle)))
engine.addTurn(Turn(player: joe, action: .attack(attack: joe.activePokemon.attacks[0])))

XCTAssertFalse(joe.activePokemon.volatileStatus.contains(.confused(1)))
XCTAssertTrue(joe.activePokemon.volatileStatus.contains(.confused(0)))

engine.addTurn(Turn(player: rhys, action: .attack(attack: tackle)))
engine.addTurn(Turn(player: joe, action: .attack(attack: tackle)))

var containsConfused = false
// Checks for *any* occurence of confusion in Pokémon's volatile status
for case let .confused(number) in joe.activePokemon.volatileStatus {
print(".confused(\(number))")
containsConfused = true
}

XCTAssertFalse(containsConfused)
}

func testConfusionAppliesOnce() {
Random.shared = Random(seed: "confused")

joe.activePokemon.volatileStatus.insert(.confused(2))

let confuseRay = Pokedex.default.attacks["Confuse Ray"]!

engine.addTurn(Turn(player: rhys, action: .attack(attack: confuseRay)))
engine.addTurn(Turn(player: joe, action: .attack(attack: joe.activePokemon.attacks[0])))

var confusionOccurences = 0

for case let .confused(number) in joe.activePokemon.volatileStatus {
print(".confused(\(number))")
confusionOccurences += 1
}

XCTAssertEqual(confusionOccurences, 1)
}

func testSleepPreventsAttack() {
joe.activePokemon.status = .asleep(1)

engine.addTurn(Turn(player: rhys, action: .attack(attack: tackle)))
engine.addTurn(Turn(player: joe, action: .attack(attack: tackle)))

XCTAssertEqual(rhys.activePokemon.currentHP, rhys.activePokemon.baseStats.hp)
}

func testSleepExpires() {
joe.activePokemon.status = .asleep(0)

engine.addTurn(Turn(player: rhys, action: .attack(attack: tackle)))
engine.addTurn(Turn(player: joe, action: .attack(attack: tackle)))

XCTAssertEqual(joe.activePokemon.status, .healthy)
}
}

0 comments on commit a627c2b

Please sign in to comment.