diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 8c883829..10fcb72d 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -7,6 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 3C07CB1F2BC27FDB00207C38 /* SystemManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C07CB1E2BC27FDB00207C38 /* SystemManagerTests.swift */; }; + 3C07CB212BC287DD00207C38 /* GameModuleTestsUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C07CB202BC287DD00207C38 /* GameModuleTestsUtils.swift */; }; + 3C07CB232BC28A2500207C38 /* EntityManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C07CB222BC28A2500207C38 /* EntityManagerTest.swift */; }; + 3C07CB252BC2903000207C38 /* TFEntityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C07CB242BC2903000207C38 /* TFEntityTests.swift */; }; + 3C07CB272BC2990E00207C38 /* TFComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C07CB262BC2990E00207C38 /* TFComponentTests.swift */; }; + 3C07CB2E2BC2A49200207C38 /* EventManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C07CB2C2BC2A49200207C38 /* EventManagerTests.swift */; }; + 3C07CB2F2BC2A49200207C38 /* EventTestsUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C07CB2D2BC2A49200207C38 /* EventTestsUtils.swift */; }; 3C0B608D2BB2B84000FFECB4 /* ContactComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0B608C2BB2B84000FFECB4 /* ContactComponent.swift */; }; 3C3CBDF72BB81D970001B8A9 /* TFCameraNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3CBDF62BB81D970001B8A9 /* TFCameraNode.swift */; }; 3C3CBDF92BB821500001B8A9 /* TFScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3CBDF82BB821500001B8A9 /* TFScene.swift */; }; @@ -205,6 +212,13 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 3C07CB1E2BC27FDB00207C38 /* SystemManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemManagerTests.swift; sourceTree = ""; }; + 3C07CB202BC287DD00207C38 /* GameModuleTestsUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameModuleTestsUtils.swift; sourceTree = ""; }; + 3C07CB222BC28A2500207C38 /* EntityManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityManagerTest.swift; sourceTree = ""; }; + 3C07CB242BC2903000207C38 /* TFEntityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFEntityTests.swift; sourceTree = ""; }; + 3C07CB262BC2990E00207C38 /* TFComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFComponentTests.swift; sourceTree = ""; }; + 3C07CB2C2BC2A49200207C38 /* EventManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventManagerTests.swift; sourceTree = ""; }; + 3C07CB2D2BC2A49200207C38 /* EventTestsUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventTestsUtils.swift; sourceTree = ""; }; 3C0B608C2BB2B84000FFECB4 /* ContactComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactComponent.swift; sourceTree = ""; }; 3C3CBDF62BB81D970001B8A9 /* TFCameraNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFCameraNode.swift; sourceTree = ""; }; 3C3CBDF82BB821500001B8A9 /* TFScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFScene.swift; sourceTree = ""; }; @@ -798,6 +812,8 @@ BA443D462BB05C6E009F0FFB /* MoveEventTests.swift */, BA443D482BB05EF0009F0FFB /* RemoveEventTests.swift */, BA443D4A2BB05F73009F0FFB /* RequestSpawnEventTests.swift */, + 3C07CB2C2BC2A49200207C38 /* EventManagerTests.swift */, + 3C07CB2D2BC2A49200207C38 /* EventTestsUtils.swift */, ); path = EventTests; sourceTree = ""; @@ -1036,6 +1052,11 @@ isa = PBXGroup; children = ( BAFFB94F2BB12F9D00D8301F /* GameEngineTests.swift */, + 3C07CB1E2BC27FDB00207C38 /* SystemManagerTests.swift */, + 3C07CB222BC28A2500207C38 /* EntityManagerTest.swift */, + 3C07CB242BC2903000207C38 /* TFEntityTests.swift */, + 3C07CB262BC2990E00207C38 /* TFComponentTests.swift */, + 3C07CB202BC287DD00207C38 /* GameModuleTestsUtils.swift */, ); path = GameModuleTests; sourceTree = ""; @@ -1404,12 +1425,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3C07CB252BC2903000207C38 /* TFEntityTests.swift in Sources */, + 3C07CB2F2BC2A49200207C38 /* EventTestsUtils.swift in Sources */, + 3C07CB232BC28A2500207C38 /* EntityManagerTest.swift in Sources */, + 3C07CB1F2BC27FDB00207C38 /* SystemManagerTests.swift in Sources */, 52DF5FC22BA32B2600135367 /* TowerForgeTests.swift in Sources */, + 3C07CB2E2BC2A49200207C38 /* EventManagerTests.swift in Sources */, BA443D472BB05C6E009F0FFB /* MoveEventTests.swift in Sources */, 52DF5FE42BA3391200135367 /* TFTexturesTests.swift in Sources */, + 3C07CB272BC2990E00207C38 /* TFComponentTests.swift in Sources */, BAFFB9502BB12F9D00D8301F /* GameEngineTests.swift in Sources */, BA443D4B2BB05F73009F0FFB /* RequestSpawnEventTests.swift in Sources */, BA443D452BB05A3C009F0FFB /* SpawnEventTests.swift in Sources */, + 3C07CB212BC287DD00207C38 /* GameModuleTestsUtils.swift in Sources */, BA443D492BB05EF0009F0FFB /* RemoveEventTests.swift in Sources */, BA443D422BAD9885009F0FFB /* DamageEventTests.swift in Sources */, ); diff --git a/TowerForge/TowerForge/GameModule/Events/EventTransformation.swift b/TowerForge/TowerForge/GameModule/Events/EventTransformation.swift index c6b9707b..d5e48a62 100644 --- a/TowerForge/TowerForge/GameModule/Events/EventTransformation.swift +++ b/TowerForge/TowerForge/GameModule/Events/EventTransformation.swift @@ -7,7 +7,7 @@ import Foundation -protocol EventTransformation: Identifiable { +protocol EventTransformation: Identifiable, AnyObject { var id: UUID { get} func transformEvent(event: TFEvent) -> TFEvent } diff --git a/TowerForge/TowerForge/GameModule/Events/TFEvent.swift b/TowerForge/TowerForge/GameModule/Events/TFEvent.swift index 3e0a760b..3edb7ed2 100644 --- a/TowerForge/TowerForge/GameModule/Events/TFEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/TFEvent.swift @@ -26,9 +26,6 @@ extension TFEventTypeWrapper: Hashable { protocol TFEvent { var timestamp: TimeInterval { get } var entityId: UUID { get } - - /// Execute method returns an optional EventOutput as not all event executions - /// will return an EventOutput func execute(in target: EventTarget) -> EventOutput } diff --git a/TowerForge/TowerForge/GameModule/GameWorld.swift b/TowerForge/TowerForge/GameModule/GameWorld.swift index c25cbb91..924e34a5 100644 --- a/TowerForge/TowerForge/GameModule/GameWorld.swift +++ b/TowerForge/TowerForge/GameModule/GameWorld.swift @@ -20,9 +20,10 @@ class GameWorld { private var grid: Grid private var renderer: Renderer? private let worldBounds: CGRect + private var popup: StatePopupNode unowned var delegate: SceneManagerDelegate? - unowned var statePopupDelegate: StatePopupDelegate? + unowned var statePopupDelegate: StatePopupDelegate? { didSet { popup.delegate = statePopupDelegate } } init(scene: GameScene?, screenSize: CGRect, mode: Mode, gameRoom: GameRoom? = nil, currentPlayer: GamePlayer? = nil) { @@ -33,6 +34,7 @@ class GameWorld { selectionNode = UnitSelectionNode() powerUpSelectionNode = PowerUpSelectionNode(eventManager: gameEngine.eventManager) grid = Grid(screenSize: worldBounds) + popup = StatePopupNode() renderer = Renderer(target: self, scene: scene) setUp() @@ -62,6 +64,7 @@ class GameWorld { setUpGameEngine() setUpSelectionNode() setUpPowerUpNode() + setUpStatePopupNode() } private func setUpScene() { @@ -93,15 +96,19 @@ class GameWorld { } } - func presentStatePopup() { - let popup = StatePopupNode() - popup.delegate = statePopupDelegate - // TODO: Refactor this + private func setUpStatePopupNode() { popup.zPosition = 10_000 popup.name = "popup" popup.position = CGPoint(x: 0, y: 0) + } + + func presentStatePopup() { scene?.add(node: popup, staticOnScreen: true) } + + func removeStatePopup() { + scene?.remove(node: popup) + } } extension GameWorld: Renderable { diff --git a/TowerForge/TowerForge/GameModule/Systems/GameSystems/PositionSystem.swift b/TowerForge/TowerForge/GameModule/Systems/GameSystems/PositionSystem.swift index b33ad6e2..c1f1eda1 100644 --- a/TowerForge/TowerForge/GameModule/Systems/GameSystems/PositionSystem.swift +++ b/TowerForge/TowerForge/GameModule/Systems/GameSystems/PositionSystem.swift @@ -7,7 +7,6 @@ import Foundation import UIKit -import QuartzCore class PositionSystem: TFSystem { var isActive = true @@ -27,7 +26,6 @@ class PositionSystem: TFSystem { continue } if playerComponent.player == .ownPlayer && positionComponent.position.x > GameWorld.worldSize.width { - // TODO: Change UIScreen handleOutOfGame(entity: entity) } else if playerComponent.player == .oppositePlayer && positionComponent.position.x < UIScreen.main.bounds.minX { diff --git a/TowerForge/TowerForge/GameViewController.swift b/TowerForge/TowerForge/GameViewController.swift index a7661b6f..44009cc8 100644 --- a/TowerForge/TowerForge/GameViewController.swift +++ b/TowerForge/TowerForge/GameViewController.swift @@ -84,7 +84,7 @@ extension GameViewController: SceneManagerDelegate { // Present the scene gameScene.sceneManagerDelegate = self gameScene.updateDelegate = self - gameScene.statePopupDelegate = self + gameScene.statePopupDelegate = self showScene(scene: gameScene) setUpGameWorld(scene: gameScene) } @@ -106,5 +106,6 @@ extension GameViewController: StatePopupDelegate { func onResume() { isPaused = false + gameWorld?.removeStatePopup() } } diff --git a/TowerForge/TowerForge/Networking/RoomNetwork/GameRoom.swift b/TowerForge/TowerForge/Networking/RoomNetwork/GameRoom.swift index abc9e73d..a436319b 100644 --- a/TowerForge/TowerForge/Networking/RoomNetwork/GameRoom.swift +++ b/TowerForge/TowerForge/Networking/RoomNetwork/GameRoom.swift @@ -59,11 +59,12 @@ class GameRoom { private func postRoomDataToFirebase(completion: ((_ err: Any?, _ result: String?) -> Void)?) { let roomRef = FirebaseDatabaseReference(.Rooms).child(roomName) - roomRef.updateChildValues(["roomName": roomName ]) { err, snap in + let roomId = UUID().uuidString + roomRef.updateChildValues(["roomId": roomId ]) { err, _ in if err != nil { completion?(err, nil) } else { - completion?(nil, snap.key) + completion?(nil, roomId) } } } @@ -156,7 +157,7 @@ class GameRoom { } print("Finding the room and making it") let room = GameRoom(roomName: roomName, roomState: .waitingForPlayers) - room.roomId = snap.key + room.roomId = snap.childSnapshot(forPath: "roomId").value as? RoomId completion(room) } } diff --git a/TowerForge/TowerForge/Nodes/StatePopupNode.swift b/TowerForge/TowerForge/Nodes/StatePopupNode.swift index 541d378f..1900665a 100644 --- a/TowerForge/TowerForge/Nodes/StatePopupNode.swift +++ b/TowerForge/TowerForge/Nodes/StatePopupNode.swift @@ -16,14 +16,14 @@ protocol StatePopupDelegate: AnyObject { class StatePopupNode: TFSpriteNode { - var delegate: StatePopupDelegate? + var delegate: StatePopupDelegate? { didSet { setupNode() } } init() { super.init(color: .clear, size: CGSize(width: 700, height: 300)) - setupNode() } func setupNode() { + self.isUserInteractionEnabled = true let background = TFSpriteNode(imageName: "square-button", size: self.size) // TODO: Refactor into a constant @@ -38,11 +38,13 @@ class StatePopupNode: TFSpriteNode { height: 200), imageNamed: "play-button") resumeButton.name = "resumeButton" + resumeButton.isUserInteractionEnabled = true let menuButton = TFButtonNode(action: TFButtonDelegate(onTouchBegan: { [weak self] in self?.delegate?.onMenu() }, onTouchEnded: {}), size: CGSize(width: 200, height: 200), imageNamed: "menu-button") menuButton.name = "menuButton" + menuButton.isUserInteractionEnabled = true // TODO: Refactor the position into a constants resumeButton.position = CGPoint(x: background.position.x + 150, y: background.position.y) @@ -55,7 +57,6 @@ class StatePopupNode: TFSpriteNode { self.add(child: background) self.add(child: menuButton) self.add(child: resumeButton) - } } diff --git a/TowerForge/TowerForge/Scenes/GameScene.swift b/TowerForge/TowerForge/Scenes/GameScene.swift index 91419eaa..bfc9368e 100644 --- a/TowerForge/TowerForge/Scenes/GameScene.swift +++ b/TowerForge/TowerForge/Scenes/GameScene.swift @@ -66,8 +66,10 @@ class GameScene: SKScene { override func update(_ currentTime: TimeInterval) { if statePopupDelegate?.isPaused ?? false { + lastUpdatedTimeInterval = currentTime return } + if lastUpdatedTimeInterval == TimeInterval(0) { lastUpdatedTimeInterval = currentTime } @@ -98,6 +100,10 @@ extension GameScene: TFScene { func remove(node: TFNode) { node.node.removeFromParent() + + if let name = node.name { + cameraNode?.removeChild(withName: name) + } } func contains(node: TFNode) -> Bool { diff --git a/TowerForge/TowerForgeTests/EventTests/EventManagerTests.swift b/TowerForge/TowerForgeTests/EventTests/EventManagerTests.swift new file mode 100644 index 00000000..8d95c2cc --- /dev/null +++ b/TowerForge/TowerForgeTests/EventTests/EventManagerTests.swift @@ -0,0 +1,123 @@ +// +// EventManagerTests.swift +// TowerForgeTests +// +// Created by Zheng Ze on 7/4/24. +// + +import XCTest +@testable import TowerForge + +final class EventManagerTests: XCTestCase { + func test_initialize() { + let eventManager = EventManager() + XCTAssertEqual(eventManager.eventQueue.count, 0) + XCTAssertEqual(eventManager.eventTransformations.count, 0) + XCTAssertEqual(eventManager.eventHandler.count, 0) + XCTAssertNil(eventManager.remoteEventManager) + } + + func test_intialiseWithNetworking() { + let eventManager = EventManager(roomId: "testRoom", + currentPlayer: GamePlayer(userPlayerId: "test", userName: "test")) + XCTAssertEqual(eventManager.eventQueue.count, 0) + XCTAssertEqual(eventManager.eventTransformations.count, 0) + XCTAssertEqual(eventManager.eventHandler.count, 0) + XCTAssertNotNil(eventManager.remoteEventManager) + } + + func test_addEvents_eventsShouldGetAdded() { + let eventManager = EventManager() + XCTAssertEqual(eventManager.eventQueue.count, 0) + + let event = TestEvent() + eventManager.add(event) + + XCTAssertEqual(eventManager.eventQueue.count, 1) + XCTAssertIdentical(eventManager.eventQueue[0] as AnyObject, event) + } + + func test_addEventTransformation_transformationAdded() { + let eventManager = EventManager() + XCTAssertEqual(eventManager.eventTransformations.count, 0) + + let eventTransformation = TestEventTransformation() + eventManager.addTransformation(eventTransformation: eventTransformation) + + XCTAssertEqual(eventManager.eventTransformations.count, 1) + XCTAssertIdentical(eventManager.eventTransformations[0], eventTransformation) + } + + func test_removeEventTransformation_transformationRemoved() { + let eventManager = EventManager() + XCTAssertEqual(eventManager.eventTransformations.count, 0) + + let eventTransformation = TestEventTransformation() + eventManager.addTransformation(eventTransformation: eventTransformation) + + XCTAssertEqual(eventManager.eventTransformations.count, 1) + XCTAssertIdentical(eventManager.eventTransformations[0], eventTransformation) + + eventManager.removeTransformation(eventTransformation: eventTransformation) + + XCTAssertEqual(eventManager.eventTransformations.count, 0) + } + + func test_addRemoteEventWithoutNetworking_eventUnpackedImmediately() { + let eventManager = EventManager() + XCTAssertEqual(eventManager.eventQueue.count, 0) + + let event = TestRemoteEvent() + eventManager.add(event) + + XCTAssertEqual(eventManager.eventQueue.count, 1) + XCTAssertTrue(eventManager.eventQueue[0] is TestEvent) + } + + func test_addRemoteEventWithNetworking_eventNotUnpackedImmediately() { + let eventManager = EventManager(roomId: "testRoom", + currentPlayer: GamePlayer(userPlayerId: "test", userName: "test")) + XCTAssertEqual(eventManager.eventQueue.count, 0) + + let event = TestRemoteEvent() + eventManager.add(event) + + XCTAssertEqual(eventManager.eventQueue.count, 0) + } + + func test_executeEvents_eventsShouldGetExecuted() { + let eventA = TestEvent() + let eventB = TestEvent() + let eventC = TestEvent() + let eventManager = EventManager() + + eventManager.add(eventA) + eventManager.add(eventB) + eventManager.add(eventC) + + XCTAssertFalse(eventA.didExecute) + XCTAssertFalse(eventB.didExecute) + XCTAssertFalse(eventC.didExecute) + + eventManager.executeEvents(in: TestEventTarget()) + + XCTAssertTrue(eventA.didExecute) + XCTAssertTrue(eventB.didExecute) + XCTAssertTrue(eventC.didExecute) + } + + func test_executeEventWithTransformation_eventTransformed() { + let event = TestEvent() + let transformation = TestEventTransformation() + let eventManager = EventManager() + + eventManager.addTransformation(eventTransformation: transformation) + eventManager.add(event) + + XCTAssertFalse(transformation.didTransformEvent) + + eventManager.executeEvents(in: TestEventTarget()) + + XCTAssertTrue(transformation.didTransformEvent) + } +} diff --git a/TowerForge/TowerForgeTests/EventTests/EventTestsUtils.swift b/TowerForge/TowerForgeTests/EventTests/EventTestsUtils.swift new file mode 100644 index 00000000..45649bfe --- /dev/null +++ b/TowerForge/TowerForgeTests/EventTests/EventTestsUtils.swift @@ -0,0 +1,48 @@ +// +// EventTestsUtils.swift +// TowerForgeTests +// +// Created by Zheng Ze on 7/4/24. +// + +import Foundation +@testable import TowerForge + +class TestEvent: TFEvent { + var timestamp = Date().timeIntervalSince1970 + var entityId = UUID() + private(set) var didExecute = false + + func execute(in target: any TowerForge.EventTarget) -> TowerForge.EventOutput { + didExecute = true + return EventOutput() + } +} + +class TestRemoteEvent: TFRemoteEvent { + var type = "" + var timeStamp = Date().timeIntervalSince1970 + var source = "" + private(set) var didUnpack = false + + func unpack(into eventManager: TowerForge.EventManager, for gamePlayer: TowerForge.UserPlayerId) { + didUnpack = true + eventManager.add(TestEvent()) + } +} + +class TestEventTransformation: EventTransformation { + var id = UUID() + private(set) var didTransformEvent = false + + func transformEvent(event: any TowerForge.TFEvent) -> any TowerForge.TFEvent { + didTransformEvent = true + return event + } +} + +class TestEventTarget: EventTarget { + func system(ofType type: T.Type) -> T? { + TestSystemA() as? T + } +} diff --git a/TowerForge/TowerForgeTests/GameModuleTests/EntityManagerTest.swift b/TowerForge/TowerForgeTests/GameModuleTests/EntityManagerTest.swift new file mode 100644 index 00000000..38a52dd2 --- /dev/null +++ b/TowerForge/TowerForgeTests/GameModuleTests/EntityManagerTest.swift @@ -0,0 +1,131 @@ +// +// EntityManagerTest.swift +// TowerForgeTests +// +// Created by Zheng Ze on 7/4/24. +// + +import XCTest +@testable import TowerForge + +final class EntityManagerTest: XCTestCase { + func test_initializeEntityManager_containNoEntity() { + let entityManager = EntityManager() + XCTAssertEqual(entityManager.entities.count, 0) + } + + func test_addEntity_entityIsAdded() { + let entityManager = EntityManager() + XCTAssertEqual(entityManager.entities.count, 0) + + let entity = TestEntity() + entityManager.add(entity) + XCTAssertEqual(entityManager.entities.count, 1) + XCTAssertIdentical(entityManager.entity(with: entity.id), entity) + } + + func test_addEntityMoreThanOnce_entityOnlyAddedOnce() { + let entityManager = EntityManager() + XCTAssertEqual(entityManager.entities.count, 0) + + let entity = TestEntity() + entityManager.add(entity) + XCTAssertEqual(entityManager.entities.count, 1) + XCTAssertIdentical(entityManager.entity(with: entity.id), entity) + + entityManager.add(entity) + XCTAssertEqual(entityManager.entities.count, 1) + } + + func test_removeEntity_entityIsRemoved() { + let entityManager = EntityManager() + XCTAssertEqual(entityManager.entities.count, 0) + + let entityA = TestEntity() + entityManager.add(entityA) + XCTAssertEqual(entityManager.entities.count, 1) + XCTAssertIdentical(entityManager.entity(with: entityA.id), entityA) + + entityManager.removeEntity(with: entityA.id) + XCTAssertEqual(entityManager.entities.count, 0) + XCTAssertNil(entityManager.entity(with: entityA.id)) + } + + func test_removeEntityWhenEntityIsNotPresent_noEntityIsRemoved() { + let entityManager = EntityManager() + XCTAssertEqual(entityManager.entities.count, 0) + + let entityA = TestEntity() + entityManager.add(entityA) + XCTAssertEqual(entityManager.entities.count, 1) + XCTAssertIdentical(entityManager.entity(with: entityA.id), entityA) + + let entityB = TestEntity() + + XCTAssertNil(entityManager.entity(with: entityB.id)) + + entityManager.removeEntity(with: entityB.id) + XCTAssertEqual(entityManager.entities.count, 1) + XCTAssertEqual(entityManager.entities.count, 1) + } + + func test_componentOfEntity_componentIsRetrieved() { + let entityManager = EntityManager() + XCTAssertEqual(entityManager.entities.count, 0) + + let entity = TestEntity() + let component = TestComponentA() + entity.addComponent(component) + entityManager.add(entity) + + XCTAssertEqual(entityManager.entities.count, 1) + XCTAssertIdentical(entityManager.entity(with: entity.id), entity) + + XCTAssertIdentical(entityManager.component(ofType: TestComponentA.self, of: entity.id), component) + } + + func test_unownedComponent_componentIsNotRetrieved() { + let entityManager = EntityManager() + XCTAssertEqual(entityManager.entities.count, 0) + + let entity = TestEntity() + entityManager.add(entity) + + XCTAssertEqual(entityManager.entities.count, 1) + XCTAssertIdentical(entityManager.entity(with: entity.id), entity) + + XCTAssertNil(entityManager.component(ofType: TestComponentA.self, of: entity.id)) + } + + func test_entitiesWithComponentsAdded_componentsRetrieved() { + let entityManager = EntityManager() + XCTAssertEqual(entityManager.entities.count, 0) + + let entityA = TestEntity() + let entityB = TestEntity() + let componentA = TestComponentA() + let componentB = TestComponentA() + entityA.addComponent(componentA) + entityB.addComponent(componentB) + + entityManager.add(entityA) + entityManager.add(entityB) + + XCTAssertEqual(entityManager.entities.count, 2) + XCTAssertEqual(entityManager.components(ofType: TestComponentA.self).count, 2) + } + + func test_entitiesWithNoComponentsAdded_noComponentsRetrieved() { + let entityManager = EntityManager() + XCTAssertEqual(entityManager.entities.count, 0) + + let entityA = TestEntity() + let entityB = TestEntity() + + entityManager.add(entityA) + entityManager.add(entityB) + + XCTAssertEqual(entityManager.entities.count, 2) + XCTAssertEqual(entityManager.components(ofType: TestComponentA.self).count, 0) + } +} diff --git a/TowerForge/TowerForgeTests/GameModuleTests/GameEngineTests.swift b/TowerForge/TowerForgeTests/GameModuleTests/GameEngineTests.swift index 02919cc8..fa49d9c1 100644 --- a/TowerForge/TowerForgeTests/GameModuleTests/GameEngineTests.swift +++ b/TowerForge/TowerForgeTests/GameModuleTests/GameEngineTests.swift @@ -15,22 +15,22 @@ final class GameEngineTests: XCTestCase { let emptySystemManager = SystemManager() let emptyEventManager = EventManager() - // After adding the playerInfo and team components (2 each) -// let entityCount = emptyEntityManager.entities.count + 4 -// -// let gameEngine = GameEngine() -// -// XCTAssertEqual(gameEngine.entities.count, -// entityCount, -// "The GameEngine's entity manager must have the same count as the empty entity manager") -// -// XCTAssertEqual(gameEngine.systemManager.systems.count, -// emptySystemManager.systems.count, -// "The GameEngine's system manager must have the same count as the empty system manager") -// -// XCTAssertEqual(gameEngine.eventManager.eventQueue.count, -// emptyEventManager.eventQueue.count, -// "The GameEngine's event manager must have the same count as the empty event manager") + // After adding team components (2) + let entityCount = emptyEntityManager.entities.count + 2 + + let gameEngine = GameEngine() + + XCTAssertEqual(gameEngine.entities.count, + entityCount, + "The GameEngine's entity manager must contain 2 entities on init, 1 team entity for each player") + + XCTAssertEqual(gameEngine.systemManager.systems.count, + emptySystemManager.systems.count, + "The GameEngine's system manager must have the same count as the empty system manager") + + XCTAssertEqual(gameEngine.eventManager.eventQueue.count, + emptyEventManager.eventQueue.count, + "The GameEngine's event manager must have the same count as the empty event manager") } } diff --git a/TowerForge/TowerForgeTests/GameModuleTests/GameModuleTestsUtils.swift b/TowerForge/TowerForgeTests/GameModuleTests/GameModuleTestsUtils.swift new file mode 100644 index 00000000..0bf3b494 --- /dev/null +++ b/TowerForge/TowerForgeTests/GameModuleTests/GameModuleTestsUtils.swift @@ -0,0 +1,65 @@ +// +// GameModuleTestsUtils.swift +// TowerForgeTests +// +// Created by Zheng Ze on 7/4/24. +// + +import Foundation +@testable import TowerForge + +class TestComponentA: TFComponent { + private(set) var didUpdate = false + private(set) var didAddEntity = false + private(set) var didRemoveEntity = false + + override func didAddToEntity(_ entity: TFEntity) { + super.didAddToEntity(entity) + didAddEntity = true + } + + override func willRemoveFromEntity() { + super.willRemoveFromEntity() + didRemoveEntity = true + } +} + +class TestComponentB: TFComponent { + private(set) var didUpdate = false + private(set) var didAddEntity = false + private(set) var didRemoveEntity = false + + override func didAddToEntity(_ entity: TFEntity) { + super.didAddToEntity(entity) + didAddEntity = true + } + + override func willRemoveFromEntity() { + super.willRemoveFromEntity() + didRemoveEntity = true + } +} + +class TestEntity: TFEntity {} + +class TestSystemA: TFSystem { + var isActive = true + var entityManager = EntityManager() + var eventManager = EventManager() + private(set) var didUpdate = false + + func update(within time: CGFloat) { + didUpdate = true + } +} + +class TestSystemB: TFSystem { + var isActive = true + var entityManager = EntityManager() + var eventManager = EventManager() + private(set) var didUpdate = false + + func update(within time: CGFloat) { + didUpdate = true + } +} diff --git a/TowerForge/TowerForgeTests/GameModuleTests/SystemManagerTests.swift b/TowerForge/TowerForgeTests/GameModuleTests/SystemManagerTests.swift new file mode 100644 index 00000000..5f0fe061 --- /dev/null +++ b/TowerForge/TowerForgeTests/GameModuleTests/SystemManagerTests.swift @@ -0,0 +1,121 @@ +// +// SystemManagerTests.swift +// TowerForgeTests +// +// Created by Zheng Ze on 7/4/24. +// + +import XCTest +@testable import TowerForge + +final class SystemManagerTests: XCTestCase { + func test_initializeSystemManager_noSystemsAdded() { + let systemManager = SystemManager() + XCTAssertEqual(systemManager.systems.values.count, 0) + } + + func test_addSystem_systemIsAdded() { + let systemManager = SystemManager() + + XCTAssertEqual(systemManager.systems.values.count, 0) + + let system = TestSystemA() + systemManager.add(system: system) + + XCTAssertEqual(systemManager.systems.values.count, 1) + XCTAssertNotNil(systemManager.system(ofType: TestSystemA.self)) + XCTAssertIdentical(systemManager.system(ofType: TestSystemA.self), system) + } + + func test_addMultipleDifferentSystems_allSystemsAreAdded() { + let systemManager = SystemManager() + + XCTAssertEqual(systemManager.systems.values.count, 0) + + let systemA = TestSystemA() + systemManager.add(system: systemA) + + XCTAssertEqual(systemManager.systems.values.count, 1) + XCTAssertNotNil(systemManager.system(ofType: TestSystemA.self)) + XCTAssertIdentical(systemManager.system(ofType: TestSystemA.self), systemA) + + let systemB = TestSystemB() + systemManager.add(system: systemB) + + XCTAssertEqual(systemManager.systems.values.count, 2) + XCTAssertNotNil(systemManager.system(ofType: TestSystemA.self)) + XCTAssertIdentical(systemManager.system(ofType: TestSystemA.self), systemA) + XCTAssertNotNil(systemManager.system(ofType: TestSystemB.self)) + XCTAssertIdentical(systemManager.system(ofType: TestSystemB.self), systemB) + } + + func test_addMultipleSystemOfSameType_onlyFirstSystemAdded() { + let systemManager = SystemManager() + + XCTAssertEqual(systemManager.systems.values.count, 0) + + let systemA = TestSystemA() + systemManager.add(system: systemA) + + XCTAssertEqual(systemManager.systems.values.count, 1) + XCTAssertNotNil(systemManager.system(ofType: TestSystemA.self)) + XCTAssertIdentical(systemManager.system(ofType: TestSystemA.self), systemA) + + let systemB = TestSystemA() + systemManager.add(system: systemB) + + XCTAssertEqual(systemManager.systems.values.count, 1) + XCTAssertNotNil(systemManager.system(ofType: TestSystemA.self)) + XCTAssertIdentical(systemManager.system(ofType: TestSystemA.self), systemA) + XCTAssertNotIdentical(systemManager.system(ofType: TestSystemA.self), systemB) + } + + func test_deleteSystem_systemIsDeleted() { + let systemManager = SystemManager() + + XCTAssertEqual(systemManager.systems.values.count, 0) + + let system = TestSystemA() + systemManager.add(system: system) + + XCTAssertEqual(systemManager.systems.values.count, 1) + XCTAssertNotNil(systemManager.system(ofType: TestSystemA.self)) + XCTAssertIdentical(systemManager.system(ofType: TestSystemA.self), system) + + systemManager.remove(ofType: TestSystemA.self) + + XCTAssertEqual(systemManager.systems.values.count, 0) + XCTAssertNil(systemManager.system(ofType: TestSystemA.self)) + } + + func test_deleteSystemWhenSystemOfSameTypeNotInSystemManager_noSytemsDeleted() { + let systemManager = SystemManager() + + XCTAssertEqual(systemManager.systems.values.count, 0) + + let system = TestSystemA() + systemManager.add(system: system) + + XCTAssertEqual(systemManager.systems.values.count, 1) + XCTAssertNotNil(systemManager.system(ofType: TestSystemA.self)) + XCTAssertIdentical(systemManager.system(ofType: TestSystemA.self), system) + + systemManager.remove(ofType: TestSystemB.self) + + XCTAssertEqual(systemManager.systems.values.count, 1) + XCTAssertNotNil(systemManager.system(ofType: TestSystemA.self)) + XCTAssertIdentical(systemManager.system(ofType: TestSystemA.self), system) + } + + func test_updateCalledInSystemManager_systemsUpdateIsCalled() { + let systemManager = SystemManager() + let system = TestSystemA() + systemManager.add(system: system) + + XCTAssertFalse(system.didUpdate) + + systemManager.update(10) + + XCTAssertTrue(system.didUpdate) + } +} diff --git a/TowerForge/TowerForgeTests/GameModuleTests/TFComponentTests.swift b/TowerForge/TowerForgeTests/GameModuleTests/TFComponentTests.swift new file mode 100644 index 00000000..e0cf9105 --- /dev/null +++ b/TowerForge/TowerForgeTests/GameModuleTests/TFComponentTests.swift @@ -0,0 +1,44 @@ +// +// TFComponentTests.swift +// TowerForgeTests +// +// Created by Zheng Ze on 7/4/24. +// + +import XCTest +@testable import TowerForge + +final class TFComponentTests: XCTestCase { + func test_initialise_shouldContainsIdAndNoEntity() { + let component = TFComponent() + + XCTAssertNotNil(component.id) + XCTAssertNil(component.entity) + } + + func test_addToEntity_shouldContainReferenceToEntity() { + let entity = TestEntity() + let component = TFComponent() + + XCTAssertNil(component.entity) + + entity.addComponent(component) + + XCTAssertNotNil(component.entity) + } + + func test_removeFromEntity_shouldNotContainReferenceToEntity() { + let entity = TestEntity() + let component = TFComponent() + + XCTAssertNil(component.entity) + + entity.addComponent(component) + + XCTAssertNotNil(component.entity) + + entity.removeComponent(ofType: TFComponent.self) + + XCTAssertNil(component.entity) + } +} diff --git a/TowerForge/TowerForgeTests/GameModuleTests/TFEntityTests.swift b/TowerForge/TowerForgeTests/GameModuleTests/TFEntityTests.swift new file mode 100644 index 00000000..7f947fdf --- /dev/null +++ b/TowerForge/TowerForgeTests/GameModuleTests/TFEntityTests.swift @@ -0,0 +1,148 @@ +// +// TFEntityTests.swift +// TowerForgeTests +// +// Created by Zheng Ze on 7/4/24. +// + +import XCTest +@testable import TowerForge + +final class TFEntityTests: XCTestCase { + func test_initializeEntity_shouldContainNoComponents() { + let entity = TestEntity() + XCTAssertNotNil(entity.id) + XCTAssertEqual(entity.components.count, 0) + } + + func test_addComponent_componentAdded() { + let entity = TestEntity() + let component = TestComponentA() + + XCTAssertEqual(entity.components.count, 0) + + entity.addComponent(component) + XCTAssertEqual(entity.components.count, 1) + XCTAssertIdentical(entity.component(ofType: TestComponentA.self), component) + XCTAssertTrue(entity.hasComponent(ofType: TestComponentA.self)) + } + + func test_addMultipleComponentOfDifferentTypes_allComponentsAdded() { + let entity = TestEntity() + let componentA = TestComponentA() + let componentB = TestComponentB() + + XCTAssertEqual(entity.components.count, 0) + XCTAssertNil(entity.component(ofType: TestComponentA.self)) + XCTAssertNil(entity.component(ofType: TestComponentB.self)) + XCTAssertFalse(entity.hasComponent(ofType: TestComponentA.self)) + XCTAssertFalse(entity.hasComponent(ofType: TestComponentB.self)) + + entity.addComponent(componentA) + XCTAssertEqual(entity.components.count, 1) + XCTAssertIdentical(entity.component(ofType: TestComponentA.self), componentA) + XCTAssertNil(entity.component(ofType: TestComponentB.self)) + XCTAssertTrue(entity.hasComponent(ofType: TestComponentA.self)) + XCTAssertFalse(entity.hasComponent(ofType: TestComponentB.self)) + + entity.addComponent(componentB) + + XCTAssertEqual(entity.components.count, 2) + XCTAssertIdentical(entity.component(ofType: TestComponentA.self), componentA) + XCTAssertIdentical(entity.component(ofType: TestComponentB.self), componentB) + XCTAssertTrue(entity.hasComponent(ofType: TestComponentA.self)) + XCTAssertTrue(entity.hasComponent(ofType: TestComponentB.self)) + } + + func test_addMultipleComponentOfSameType_onlyFirstAdded() { + let entity = TestEntity() + let componentA = TestComponentA() + let componentB = TestComponentA() + + XCTAssertEqual(entity.components.count, 0) + + entity.addComponent(componentA) + XCTAssertEqual(entity.components.count, 1) + XCTAssertIdentical(entity.component(ofType: TestComponentA.self), componentA) + XCTAssertNotIdentical(entity.component(ofType: TestComponentA.self), componentB) + XCTAssertTrue(entity.hasComponent(ofType: TestComponentA.self)) + + entity.addComponent(componentB) + + XCTAssertEqual(entity.components.count, 1) + XCTAssertIdentical(entity.component(ofType: TestComponentA.self), componentA) + XCTAssertNotIdentical(entity.component(ofType: TestComponentA.self), componentB) + XCTAssertTrue(entity.hasComponent(ofType: TestComponentA.self)) + } + + func test_removeComponent_componentIsRemoved() { + let entity = TestEntity() + let component = TestComponentA() + + XCTAssertEqual(entity.components.count, 0) + XCTAssertNil(entity.component(ofType: TestComponentA.self)) + XCTAssertFalse(entity.hasComponent(ofType: TestComponentA.self)) + + entity.addComponent(component) + + XCTAssertEqual(entity.components.count, 1) + XCTAssertIdentical(entity.component(ofType: TestComponentA.self), component) + XCTAssertTrue(entity.hasComponent(ofType: TestComponentA.self)) + + entity.removeComponent(ofType: TestComponentA.self) + + XCTAssertEqual(entity.components.count, 0) + XCTAssertNil(entity.component(ofType: TestComponentA.self)) + XCTAssertFalse(entity.hasComponent(ofType: TestComponentA.self)) + } + + func test_removeComponentThatDoesNotExist_noComponentRemoved() { + let entity = TestEntity() + let component = TestComponentA() + + XCTAssertEqual(entity.components.count, 0) + XCTAssertNil(entity.component(ofType: TestComponentA.self)) + XCTAssertNil(entity.component(ofType: TestComponentB.self)) + XCTAssertFalse(entity.hasComponent(ofType: TestComponentA.self)) + XCTAssertFalse(entity.hasComponent(ofType: TestComponentB.self)) + + entity.addComponent(component) + + XCTAssertEqual(entity.components.count, 1) + XCTAssertIdentical(entity.component(ofType: TestComponentA.self), component) + XCTAssertNil(entity.component(ofType: TestComponentB.self)) + XCTAssertTrue(entity.hasComponent(ofType: TestComponentA.self)) + XCTAssertFalse(entity.hasComponent(ofType: TestComponentB.self)) + + entity.removeComponent(ofType: TestComponentB.self) + + XCTAssertEqual(entity.components.count, 1) + XCTAssertIdentical(entity.component(ofType: TestComponentA.self), component) + XCTAssertNil(entity.component(ofType: TestComponentB.self)) + XCTAssertTrue(entity.hasComponent(ofType: TestComponentA.self)) + XCTAssertFalse(entity.hasComponent(ofType: TestComponentB.self)) + } + + func test_addComponent_shouldCallDidAddToEntityOnComponent() { + let entity = TestEntity() + let component = TestComponentA() + + XCTAssertFalse(component.didAddEntity) + + entity.addComponent(component) + + XCTAssertTrue(component.didAddEntity) + } + + func test_removeComponent_shouldCallWillRemoveEntityOnComponent() { + let entity = TestEntity() + let component = TestComponentA() + entity.addComponent(component) + + XCTAssertFalse(component.didRemoveEntity) + + entity.removeComponent(ofType: TestComponentA.self) + + XCTAssertTrue(component.didRemoveEntity) + } +}