diff options
30 files changed, 333 insertions, 340 deletions
diff --git a/api/build.gradle b/api/build.gradle index 188c417..02fbbdb 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -37,6 +37,10 @@ dependencies { // In our simple use case, the logger gets automatically configured by simply existing. implementation 'org.slf4j:slf4j-simple:+' + // Reference the domain subproject. + implementation project(':domain') + + implementation 'com.google.code.gson:gson:2.8.7' // Use JUnit Jupiter API for testing. diff --git a/api/src/main/java/akkamon/api/AkkamonMessageEngine.java b/api/src/main/java/akkamon/api/AkkamonMessageEngine.java deleted file mode 100644 index 2bbe0c8..0000000 --- a/api/src/main/java/akkamon/api/AkkamonMessageEngine.java +++ /dev/null @@ -1,6 +0,0 @@ -package akkamon.api; - -public interface AkkamonMessageEngine { - // broadcasts position info to WebSocket Clients - void broadCastGridPosition(); -} diff --git a/api/src/main/java/akkamon/api/AkkamonSession.java b/api/src/main/java/akkamon/api/AkkamonSession.java deleted file mode 100644 index 048071c..0000000 --- a/api/src/main/java/akkamon/api/AkkamonSession.java +++ /dev/null @@ -1,4 +0,0 @@ -package akkamon.api; - -public interface AkkamonSession { -} diff --git a/api/src/main/java/akkamon/api/EventSocket.java b/api/src/main/java/akkamon/api/EventSocket.java index 092d763..7b9619a 100644 --- a/api/src/main/java/akkamon/api/EventSocket.java +++ b/api/src/main/java/akkamon/api/EventSocket.java @@ -1,8 +1,10 @@ package akkamon.api; +import akkamon.domain.AkkamonSession; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketAdapter; +import java.io.IOException; import java.util.concurrent.CountDownLatch; @@ -45,4 +47,13 @@ public class EventSocket extends WebSocketAdapter implements AkkamonSession { System.out.println("Awaiting closure from remote"); closureLatch.await(); } + + @Override + public void send(String event) { + try { + getRemote().sendString(event); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/api/src/main/java/akkamon/api/MessagingEngine.java b/api/src/main/java/akkamon/api/MessagingEngine.java index bbab97e..4919b32 100644 --- a/api/src/main/java/akkamon/api/MessagingEngine.java +++ b/api/src/main/java/akkamon/api/MessagingEngine.java @@ -1,28 +1,84 @@ package akkamon.api; -import akkamon.api.models.GameState; +import akka.actor.typed.ActorRef; +import akka.actor.typed.ActorSystem; +import akkamon.api.models.Event; +import akkamon.api.models.HeartBeatEvent; +import akkamon.domain.AkkamonMessageEngine; +import akkamon.domain.AkkamonNexus; +import akkamon.domain.AkkamonSession; import com.google.gson.Gson; import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class MessagingEngine implements AkkamonMessageEngine { - private HashMap<String, AkkamonSession> akkamonSessions = new HashMap<>(); - private static MessagingEngine instance; + private Map<String, AkkamonSession> trainerIdToAkkamonSessions = new HashMap<>(); private Gson gson = new Gson(); + private ActorRef<AkkamonNexus.Command> system; + public MessagingEngine() { + this.system = ActorSystem.create(AkkamonNexus.create(this), "akkamon-system"); + + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2); + executor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + heartBeat(); + } + }, 0, 200, TimeUnit.MILLISECONDS); + + } + + private void heartBeat() { + if (!trainerIdToAkkamonSessions.isEmpty()) { + for (Map.Entry<String, AkkamonSession> entry : trainerIdToAkkamonSessions.entrySet()) { + // System.out.println("heartbeat to " + entry.getKey()); + entry.getValue().send(gson.toJson(new HeartBeatEvent())); + } + } } @Override public void broadCastGridPosition() { } + @Override + public void registerTrainerSession(String trainerId, AkkamonSession session) { + trainerIdToAkkamonSessions.put(trainerId, session); + } + + @Override + public void removeTrainerSession(String trainerId, AkkamonSession session) { + + } + void incoming(AkkamonSession session, String message) { + Event event = gson.fromJson(message, Event.class); + switch (event.type) { + case TRAINER_REGISTRATION: + String trainerId = String.valueOf(trainerIdToAkkamonSessions.size()); + String sceneId = "AkkamonStartScene"; + + system.tell(new AkkamonNexus.RequestTrainerRegistration( + trainerId, + sceneId, + session, + system + )); + break; + case HEART_BEAT: + System.out.println("My heart goes boom skip!"); + break; + } } - private void updatePositions(GameState gameState) { + private void updatePositions() { } diff --git a/api/src/main/java/akkamon/api/models/Event.java b/api/src/main/java/akkamon/api/models/Event.java index 50fc849..8517b06 100644 --- a/api/src/main/java/akkamon/api/models/Event.java +++ b/api/src/main/java/akkamon/api/models/Event.java @@ -1,14 +1,6 @@ package akkamon.api.models; -import org.eclipse.jetty.server.Authentication; - public class Event { - public String type; - public GameState gameState; - public User user; + public EventType type; - public Event(String type, GameState gameState) { - this.type = type; - this.gameState = gameState; - } } diff --git a/api/src/main/java/akkamon/api/models/EventType.java b/api/src/main/java/akkamon/api/models/EventType.java new file mode 100644 index 0000000..1ca2ef2 --- /dev/null +++ b/api/src/main/java/akkamon/api/models/EventType.java @@ -0,0 +1,11 @@ +package akkamon.api.models; + +import com.google.gson.annotations.SerializedName; + +public enum EventType { + @SerializedName("PlayerRegistrationEvent") + TRAINER_REGISTRATION, + + @SerializedName("HeartBeat") + HEART_BEAT +} diff --git a/api/src/main/java/akkamon/api/models/GameState.java b/api/src/main/java/akkamon/api/models/GameState.java deleted file mode 100644 index d849fe5..0000000 --- a/api/src/main/java/akkamon/api/models/GameState.java +++ /dev/null @@ -1,33 +0,0 @@ -package akkamon.api.models; - -import akkamon.domain.Trainer; - -import java.util.HashMap; -import java.util.Map; - -public class GameState { - public Player currentPlayer; - public HashMap<String, Player> remotePlayers = new HashMap<>(); - - - public void setCurrentPlayer(String name, HashMap<String, Trainer> trainers) { - Trainer trainer = trainers.get(name); - Position position = new Position(trainer.getX(), trainer.getY()); - currentPlayer = new Player(name, position); - } - - public void setRemotePlayers(HashMap<String, Trainer> trainers) { - for (Map.Entry<String, Trainer> trainer: trainers.entrySet()) { - if (trainer.getValue().getName().equals(currentPlayer.name)) { - continue; - } - - String name = trainer.getKey(); - Trainer obj = trainer.getValue(); - remotePlayers.put(name, new Player(name, new Position( - obj.getX(), - obj.getY() - ))); - } - } -} diff --git a/api/src/main/java/akkamon/api/models/HeartBeatEvent.java b/api/src/main/java/akkamon/api/models/HeartBeatEvent.java new file mode 100644 index 0000000..1b3dd0e --- /dev/null +++ b/api/src/main/java/akkamon/api/models/HeartBeatEvent.java @@ -0,0 +1,5 @@ +package akkamon.api.models; + +public class HeartBeatEvent extends Event { + public EventType type = EventType.HEART_BEAT; +} diff --git a/api/src/main/java/akkamon/api/models/Player.java b/api/src/main/java/akkamon/api/models/Player.java deleted file mode 100644 index 3aebb4d..0000000 --- a/api/src/main/java/akkamon/api/models/Player.java +++ /dev/null @@ -1,13 +0,0 @@ -package akkamon.api.models; - -import java.util.HashMap; - -public class Player { - public String name; - public Position position; - - public Player(String name, Position position) { - this.name = name; - this.position = position; - } -} diff --git a/api/src/main/java/akkamon/api/models/Position.java b/api/src/main/java/akkamon/api/models/Position.java deleted file mode 100644 index 3dd3402..0000000 --- a/api/src/main/java/akkamon/api/models/Position.java +++ /dev/null @@ -1,11 +0,0 @@ -package akkamon.api.models; - -public class Position { - public float x; - public float y; - - public Position(float x, float y) { - this.x = x; - this.y = y; - } -} diff --git a/api/src/main/java/akkamon/api/models/User.java b/api/src/main/java/akkamon/api/models/User.java deleted file mode 100644 index 5f26ea0..0000000 --- a/api/src/main/java/akkamon/api/models/User.java +++ /dev/null @@ -1,11 +0,0 @@ -package akkamon.api.models; - -public class User { - public String name; - public String password; - - public User(String name, String password) { - this.name = name; - this.password = password; - } -} diff --git a/client/src/GameState.ts b/client/src/GameState.ts index a36827d..a226864 100644 --- a/client/src/GameState.ts +++ b/client/src/GameState.ts @@ -1,7 +1,6 @@ -import Player from './player'; -import type { Event } from './events'; +import type { Player } from './player'; -export default class GameState { +export class GameState { static instance: GameState; @@ -30,13 +29,4 @@ export default class GameState { } - withoutSprite() { - let spriteLess: GameState = new GameState(); - spriteLess.currentPlayer = new Player({ - name: this.currentPlayer!.name, - position: this.currentPlayer!.position - }); - // spriteLess.remotePlayers = this.remotePlayers; - return spriteLess; - } } diff --git a/client/src/app.ts b/client/src/app.ts index 21b210e..79b0814 100644 --- a/client/src/app.ts +++ b/client/src/app.ts @@ -1,5 +1,5 @@ // import Phaser from 'phaser'; -import AkkamonStartScene from './game'; +import AkkamonStartScene from './scene'; import { Client } from './client'; const serviceUrl = 'ws://localhost:8080'; diff --git a/client/src/client.ts b/client/src/client.ts index b40f250..aeb9845 100644 --- a/client/src/client.ts +++ b/client/src/client.ts @@ -1,6 +1,8 @@ import type AkkamonSession from './session'; import { Socket } from './socket'; -import type { +import { + EventType, + HeartBeatReplyEvent, AkkamonEvent } from './events'; @@ -25,11 +27,13 @@ export class Client // console.log("-> client is handling incoming event:"); // console.log(event); switch (event.type) { - + case EventType.HEART_BEAT: + this.send(new HeartBeatReplyEvent()); + break; } } - out(event: AkkamonEvent) { + send(event: AkkamonEvent) { // console.log("-> client is now sending out message:"); // console.log(event) if (this.session) { diff --git a/client/src/events.ts b/client/src/events.ts index d8ccf02..7b58ddd 100644 --- a/client/src/events.ts +++ b/client/src/events.ts @@ -1,15 +1,21 @@ import Phaser from 'phaser'; -import type Player from './player'; -import type GameState from './GameState'; +import type { Player } from './player'; +import type { GameState } from './GameState'; import type { Direction } from './Direction'; +export enum EventType { + HEART_BEAT = "HeartBeat", + PLAYER_REGISTRATION = "PlayerRegistrationEvent", + GRID_MOVE_START = "GridMoveStartEvent" +} + export interface AkkamonEvent { - type: string + type: EventType } export class PlayerRegistrationEvent implements AkkamonEvent { - public type: string = "PlayerRegistrationEvent"; + public type: EventType = EventType.PLAYER_REGISTRATION; constructor( ) { } @@ -17,10 +23,17 @@ export class PlayerRegistrationEvent implements AkkamonEvent { export class GridMoveStartEvent implements AkkamonEvent { - public type: string = "GridMoveStartEvent"; + public type: EventType = EventType.GRID_MOVE_START; constructor( public direction: Direction ) { } } +export class HeartBeatReplyEvent implements AkkamonEvent { + + public type: EventType = EventType.HEART_BEAT; + + constructor( + ) { } +} diff --git a/client/src/game.ts b/client/src/game.ts deleted file mode 100644 index 6413aa0..0000000 --- a/client/src/game.ts +++ /dev/null @@ -1,136 +0,0 @@ -import Phaser from 'phaser'; -import type Player from './player'; -import Client from './client'; -import GameState from './GameState'; -import PlayerSprite from './sprite'; -import { GridControls } from './GridControls'; -import { GridPhysics } from './GridPhysics'; -import { Direction } from './Direction'; - - -type RemotePlayerStates = { - [name: string]: Player -} - -export default class AkkamonStartScene extends Phaser.Scene -{ - - static readonly TILE_SIZE = 32; - - private gridPhysics?: GridPhysics - private gridControls?: GridControls - - directionToAnimation: { - [key in Direction]: string - } = { - [Direction.UP]: "misa-back-walk", - [Direction.DOWN]: "misa-front-walk", - [Direction.LEFT]: "misa-left-walk", - [Direction.RIGHT]: "misa-right-walk", - [Direction.NONE]: "misa-front-walk" - } - - remotePlayerSprites: {[name: string]: PlayerSprite} = {}; - spawnPoint: Phaser.Types.Tilemaps.TiledObject | undefined; - - - constructor () - { - super('akkamonStartScene'); - } - - preload () - { - this.load.image("tiles", "assets/tilesets/akkamon-demo-extruded.png"); - // load from json! - this.load.tilemapTiledJSON("map", "assets/tilemaps/akkamon-demo-tilemap.json"); - - // An atlas is a way to pack multiple images together into one texture. I'm using it to load all - // the player animations (walking left, walking right, etc.) in one image. For more info see: - - // https://labs.phaser.io/view.html?src=src/animation/texture%20atlas%20animation.js - // If you don't use an atlas, you can do the same thing with a spritesheet, see: - // https://labs.phaser.io/view.html?src=src/animation/single%20sprite%20sheet.js - this.load.atlas("atlas", - "assets/atlas/atlas.png", - "assets/atlas/atlas.json"); - } - - - create () - { - const map = this.make.tilemap({ key: "map" }); - // Parameters are the name you gave the tileset in Tiled and then the key of the tileset image in - // Phaser's cache (i.e. the name you used in preload) - const tileset = map.addTilesetImage("akkamon-demo-extruded", "tiles"); - // Parameters: layer name (or index) from Tiled, tileset, x, y - const belowLayer = map.createLayer("Below Player", tileset, 0, 0); - const worldLayer = map.createLayer("World", tileset, 0, 0); - const aboveLayer = map.createLayer("Above Player", tileset, 0, 0); - // By default, everything gets depth sorted on the screen in the order we created things. Here, we - // want the "Above Player" layer to sit on top of the player, so we explicitly give it a depth. - // Higher depths will sit on top of lower depth objects. - aboveLayer.setDepth(10); - - this.spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point"); - - //this.createPlayerAnimation(Direction.UP); - - - // Create a sprite with physics enabled via the physics system. The image used for the sprite has - // a bit of whitespace, so I'm using setSize & setOffset to control the size of the player's body. - - let player = new PlayerSprite({ - scene: this, - tilePos: new Phaser.Math.Vector2( - Math.floor(this.spawnPoint.x! / AkkamonStartScene.TILE_SIZE), - Math.floor(this.spawnPoint.y! / AkkamonStartScene.TILE_SIZE), - ), - texture: this.textures.get("atlas"), - frame: "misa-front", - player: GameState.getInstance().currentPlayer!, - }); - - this.add.existing(player); - this.gridPhysics = new GridPhysics(player, map); - this.gridControls = new GridControls( - this.input, - this.gridPhysics - ); - - this.createPlayerAnimation(Direction.LEFT, 0, 3); - this.createPlayerAnimation(Direction.RIGHT, 0, 3); - this.createPlayerAnimation(Direction.UP, 0, 3); - this.createPlayerAnimation(Direction.DOWN, 0, 3); - - // Phaser supports multiple cameras, but you can access the default camera like this: - const camera = this.cameras.main; - camera.startFollow(player); - camera.roundPixels = true; - camera.setBounds(0, 0, map.widthInPixels, map.heightInPixels); - - } - - - update(time: number, delta: number) { - this.gridControls!.update(); - this.gridPhysics!.update(delta); - } - - private createPlayerAnimation(direction: Direction, start: number, end: number) { - this.anims.create({ - key: direction, // "misa-left-walk", - frames: this.anims.generateFrameNames("atlas", { prefix: this.directionToAnimation[direction] + ".", start: start, end: end, zeroPad: 3 }), - frameRate: 10, - repeat: -1 - }); - -// anims.create({ -// key: "misa-left-walk", -// frames: anims.generateFrameNames("atlas", { prefix: "misa-left-walk.", start: 0, end: 3, zeroPad: 3 }), -// frameRate: 10, -// repeat: -1 - - } - -} diff --git a/client/src/player.ts b/client/src/player.ts index 1be85dd..93040a4 100644 --- a/client/src/player.ts +++ b/client/src/player.ts @@ -11,7 +11,7 @@ type Input = { cursors: Phaser.Types.Input.Keyboard.CursorKeys, } -export default class Player +export class Player { name: string position: {x:number, y: number} diff --git a/client/src/scene.ts b/client/src/scene.ts index 4ac2a3f..76ac029 100644 --- a/client/src/scene.ts +++ b/client/src/scene.ts @@ -1 +1,136 @@ -import type Player from './player'; +import Phaser from 'phaser'; +import type { Player } from './player'; +import { Client } from './client'; +import { GameState } from './GameState'; +import { PlayerSprite } from './sprite'; +import { GridControls } from './GridControls'; +import { GridPhysics } from './GridPhysics'; +import { Direction } from './Direction'; + + +type RemotePlayerStates = { + [name: string]: Player +} + +export default class AkkamonStartScene extends Phaser.Scene +{ + + static readonly TILE_SIZE = 32; + + private gridPhysics?: GridPhysics + private gridControls?: GridControls + + directionToAnimation: { + [key in Direction]: string + } = { + [Direction.UP]: "misa-back-walk", + [Direction.DOWN]: "misa-front-walk", + [Direction.LEFT]: "misa-left-walk", + [Direction.RIGHT]: "misa-right-walk", + [Direction.NONE]: "misa-front-walk" + } + + remotePlayerSprites: {[name: string]: PlayerSprite} = {}; + spawnPoint: Phaser.Types.Tilemaps.TiledObject | undefined; + + + constructor () + { + super('akkamonStartScene'); + } + + preload () + { + this.load.image("tiles", "assets/tilesets/akkamon-demo-extruded.png"); + // load from json! + this.load.tilemapTiledJSON("map", "assets/tilemaps/akkamon-demo-tilemap.json"); + + // An atlas is a way to pack multiple images together into one texture. I'm using it to load all + // the player animations (walking left, walking right, etc.) in one image. For more info see: + + // https://labs.phaser.io/view.html?src=src/animation/texture%20atlas%20animation.js + // If you don't use an atlas, you can do the same thing with a spritesheet, see: + // https://labs.phaser.io/view.html?src=src/animation/single%20sprite%20sheet.js + this.load.atlas("atlas", + "assets/atlas/atlas.png", + "assets/atlas/atlas.json"); + } + + + create () + { + const map = this.make.tilemap({ key: "map" }); + // Parameters are the name you gave the tileset in Tiled and then the key of the tileset image in + // Phaser's cache (i.e. the name you used in preload) + const tileset = map.addTilesetImage("akkamon-demo-extruded", "tiles"); + // Parameters: layer name (or index) from Tiled, tileset, x, y + const belowLayer = map.createLayer("Below Player", tileset, 0, 0); + const worldLayer = map.createLayer("World", tileset, 0, 0); + const aboveLayer = map.createLayer("Above Player", tileset, 0, 0); + // By default, everything gets depth sorted on the screen in the order we created things. Here, we + // want the "Above Player" layer to sit on top of the player, so we explicitly give it a depth. + // Higher depths will sit on top of lower depth objects. + aboveLayer.setDepth(10); + + this.spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point"); + + //this.createPlayerAnimation(Direction.UP); + + + // Create a sprite with physics enabled via the physics system. The image used for the sprite has + // a bit of whitespace, so I'm using setSize & setOffset to control the size of the player's body. + + let player = new PlayerSprite({ + scene: this, + tilePos: new Phaser.Math.Vector2( + Math.floor(this.spawnPoint.x! / AkkamonStartScene.TILE_SIZE), + Math.floor(this.spawnPoint.y! / AkkamonStartScene.TILE_SIZE), + ), + texture: this.textures.get("atlas"), + frame: "misa-front", + player: GameState.getInstance().currentPlayer!, + }); + + this.add.existing(player); + this.gridPhysics = new GridPhysics(player, map); + this.gridControls = new GridControls( + this.input, + this.gridPhysics + ); + + this.createPlayerAnimation(Direction.LEFT, 0, 3); + this.createPlayerAnimation(Direction.RIGHT, 0, 3); + this.createPlayerAnimation(Direction.UP, 0, 3); + this.createPlayerAnimation(Direction.DOWN, 0, 3); + + // Phaser supports multiple cameras, but you can access the default camera like this: + const camera = this.cameras.main; + camera.startFollow(player); + camera.roundPixels = true; + camera.setBounds(0, 0, map.widthInPixels, map.heightInPixels); + + } + + + update(time: number, delta: number) { + this.gridControls!.update(); + this.gridPhysics!.update(delta); + } + + private createPlayerAnimation(direction: Direction, start: number, end: number) { + this.anims.create({ + key: direction, // "misa-left-walk", + frames: this.anims.generateFrameNames("atlas", { prefix: this.directionToAnimation[direction] + ".", start: start, end: end, zeroPad: 3 }), + frameRate: 10, + repeat: -1 + }); + +// anims.create({ +// key: "misa-left-walk", +// frames: anims.generateFrameNames("atlas", { prefix: "misa-left-walk.", start: 0, end: 3, zeroPad: 3 }), +// frameRate: 10, +// repeat: -1 + + } + +} diff --git a/client/src/socket.ts b/client/src/socket.ts index cb584a1..8e2b9ef 100644 --- a/client/src/socket.ts +++ b/client/src/socket.ts @@ -18,12 +18,12 @@ export class Socket extends WebSocket implements AkkamonSession console.log("this is the websocket"); console.log(this); console.log("logging in the session to the server"); - client.out(new PlayerRegistrationEvent()); + client.send(new PlayerRegistrationEvent()); } this.onmessage = function incomingMessage(this: WebSocket, ev: MessageEvent) { - // console.log("received message from the server!"); - // console.log("-> " + ev.data); + //console.log("received message from the server!"); + console.log("-> " + ev.data); // console.log("calling client.in:"); client.in(ev.data); } diff --git a/client/src/sprite.ts b/client/src/sprite.ts index cf25ba0..e173131 100644 --- a/client/src/sprite.ts +++ b/client/src/sprite.ts @@ -1,5 +1,5 @@ import Phaser from 'phaser'; -import AkkamonStartScene from './game'; +import AkkamonStartScene from './scene'; import type Player from './player'; import type { Direction } from './Direction'; @@ -15,7 +15,7 @@ interface AkkamonPlayerSprite extends Phaser.GameObjects.Sprite { player: Player } -export default class PlayerSprite extends Phaser.GameObjects.Sprite implements AkkamonPlayerSprite { +export class PlayerSprite extends Phaser.GameObjects.Sprite implements AkkamonPlayerSprite { player: Player; tilePos: Phaser.Math.Vector2; diff --git a/domain/build.gradle b/domain/build.gradle index 4b7063a..efe3079 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -26,10 +26,6 @@ dependencies { implementation 'ch.qos.logback:logback-classic:1.2.3' implementation 'junit:junit:4.12' - // Reference the domain subproject. - implementation project(':api') - - testImplementation "com.typesafe.akka:akka-actor-testkit-typed_${versions.ScalaBinary}" testImplementation "com.typesafe.akka:akka-stream-testkit_${versions.ScalaBinary}" diff --git a/domain/src/main/java/akkamon/domain/Akkamon.java b/domain/src/main/java/akkamon/domain/Akkamon.java deleted file mode 100644 index 602a6fe..0000000 --- a/domain/src/main/java/akkamon/domain/Akkamon.java +++ /dev/null @@ -1,6 +0,0 @@ -package akkamon.domain; - -public interface Akkamon { - void newPlayerConnected(String name, String password); - void updateTrainerPosition(String name, float x, float y); -} diff --git a/domain/src/main/java/akkamon/domain/AkkamonImpl.java b/domain/src/main/java/akkamon/domain/AkkamonImpl.java deleted file mode 100644 index 534abf2..0000000 --- a/domain/src/main/java/akkamon/domain/AkkamonImpl.java +++ /dev/null @@ -1,41 +0,0 @@ -package akkamon.domain; - -import java.util.HashMap; - -public class AkkamonImpl implements Akkamon { - - private static AkkamonImpl instance; - - private static HashMap<String, Trainer> dummyTrainersCollection = new HashMap<>(); - - public static AkkamonImpl getInstance() { - if (instance == null) { - instance = new AkkamonImpl(); - } - return instance; - } - - @Override - public void newPlayerConnected(String name, String password) { - // switch (dummyTrainersCollection.size()) { - // case 0: - // dummyTrainersCollection.put("Ash", new Trainer("Ash")); - // break; - // case 1: - // dummyTrainersCollection.put("Misty", new Trainer("Misty")); - // break; - // } - } - - public void updateTrainerPosition(String name, float x, float y) { - // Trainer trainer = dummyTrainersCollection.get(name); - // trainer.newPosition(x, y); - } - - public HashMap<String, Trainer> getDummyTrainersCollection() { - return dummyTrainersCollection; - } - - - -} diff --git a/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java b/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java new file mode 100644 index 0000000..fd22ce1 --- /dev/null +++ b/domain/src/main/java/akkamon/domain/AkkamonMessageEngine.java @@ -0,0 +1,10 @@ +package akkamon.domain; + +public interface AkkamonMessageEngine { + // broadcasts position info to WebSocket Clients + void broadCastGridPosition(); + + void registerTrainerSession(String trainerId, AkkamonSession session); + + void removeTrainerSession(String trainerId, AkkamonSession session); +} diff --git a/domain/src/main/java/akkamon/domain/AkkamonNexus.java b/domain/src/main/java/akkamon/domain/AkkamonNexus.java index a64e751..acae59d 100644 --- a/domain/src/main/java/akkamon/domain/AkkamonNexus.java +++ b/domain/src/main/java/akkamon/domain/AkkamonNexus.java @@ -18,20 +18,32 @@ public class AkkamonNexus extends AbstractBehavior<AkkamonNexus.Command> { public static class RequestTrainerRegistration implements AkkamonNexus.Command, SceneTrainerGroup.Command { public String trainerId; public String sceneId; - public ActorRef<TrainerRegistered> replyTo; - - public RequestTrainerRegistration(String trainerId, String sceneId, ActorRef<TrainerRegistered> replyTo) { + public AkkamonSession session; + public ActorRef<Command> replyTo; + + public RequestTrainerRegistration( + String trainerId, + String sceneId, + AkkamonSession session, + ActorRef<Command> replyTo + ) { this.trainerId = trainerId; this.sceneId = sceneId; + this.session = session; this.replyTo = replyTo; } } - public static class TrainerRegistered { - private ActorRef<Trainer.Command> trainer; + public static class TrainerRegistered implements Command { + private String trainerId; + private AkkamonSession session; - public TrainerRegistered(ActorRef<Trainer.Command> trainer) { - this.trainer = trainer; + public TrainerRegistered( + String trainerId, + AkkamonSession session + ) { + this.trainerId = trainerId; + this.session = session; } } @@ -40,16 +52,17 @@ public class AkkamonNexus extends AbstractBehavior<AkkamonNexus.Command> { } } - - public static Behavior<AkkamonNexus.Command> create() { - return Behaviors.setup(AkkamonNexus::new); + public static Behavior<AkkamonNexus.Command> create(AkkamonMessageEngine messagingEngine) { + return Behaviors.setup(context -> new AkkamonNexus(context, messagingEngine)); } + private AkkamonMessageEngine messageEngine; private Map<String, ActorRef<SceneTrainerGroup.Command>> sceneIdToActor = new HashMap<>(); - public AkkamonNexus(ActorContext<Command> context) { + public AkkamonNexus(ActorContext<Command> context, AkkamonMessageEngine msgEngine) { super(context); - getContext().getLog().info("AkkamonNexus is spinning"); + this.messageEngine = msgEngine; + getContext().getLog().info("AkkamonNexus is up and running, waiting eagerly for your messages!"); } @Override @@ -59,13 +72,26 @@ public class AkkamonNexus extends AbstractBehavior<AkkamonNexus.Command> { RequestTrainerRegistration.class, this::onTrainerRegistration ) + .onMessage( + TrainerRegistered.class, + this::onTrainerRegistered + ) .build(); } + private AkkamonNexus onTrainerRegistered(TrainerRegistered reply) { + // TODO test when registration fails? + getContext().getLog().info("Adding {} to Live AkkamonSessions in Messaging Engine", reply.trainerId); + messageEngine.registerTrainerSession(reply.trainerId, reply.session); + return this; + } + private AkkamonNexus onTrainerRegistration(RequestTrainerRegistration registrationRequest) { String sceneId = registrationRequest.sceneId; String trainerId = registrationRequest.trainerId; + getContext().getLog().info("Nexus received registration request for {} in {}", trainerId, sceneId); + ActorRef<SceneTrainerGroup.Command> sceneTrainerGroup = sceneIdToActor.get(sceneId); if (sceneTrainerGroup != null) { sceneTrainerGroup.tell(registrationRequest); diff --git a/domain/src/main/java/akkamon/domain/AkkamonSession.java b/domain/src/main/java/akkamon/domain/AkkamonSession.java new file mode 100644 index 0000000..b04aaab --- /dev/null +++ b/domain/src/main/java/akkamon/domain/AkkamonSession.java @@ -0,0 +1,5 @@ +package akkamon.domain; + +public interface AkkamonSession { + void send(String event); +} diff --git a/domain/src/main/java/akkamon/domain/SceneTrainerGroup.java b/domain/src/main/java/akkamon/domain/SceneTrainerGroup.java index fb9456a..e6bd8ce 100644 --- a/domain/src/main/java/akkamon/domain/SceneTrainerGroup.java +++ b/domain/src/main/java/akkamon/domain/SceneTrainerGroup.java @@ -54,7 +54,10 @@ public class SceneTrainerGroup extends AbstractBehavior<SceneTrainerGroup.Comman if (this.sceneId.equals(registrationRequest.sceneId)) { ActorRef<Trainer.Command> trainerActor = trainerIdToActor.get(registrationRequest.trainerId); if (trainerActor != null) { - registrationRequest.replyTo.tell(new AkkamonNexus.TrainerRegistered(trainerActor)); + registrationRequest.replyTo.tell(new AkkamonNexus.TrainerRegistered( + registrationRequest.trainerId, + registrationRequest.session + )); } else { getContext().getLog().info("Creating trainer actor for {}", registrationRequest.trainerId); trainerActor = @@ -63,7 +66,10 @@ public class SceneTrainerGroup extends AbstractBehavior<SceneTrainerGroup.Comman getContext() .watchWith(trainerActor, new SceneTrainerGroup.TrainerOffline(trainerActor, sceneId, registrationRequest.trainerId)); trainerIdToActor.put(registrationRequest.trainerId, trainerActor); - registrationRequest.replyTo.tell(new AkkamonNexus.TrainerRegistered(trainerActor)); + registrationRequest.replyTo.tell(new AkkamonNexus.TrainerRegistered( + registrationRequest.trainerId, + registrationRequest.session + )); } } else { getContext() diff --git a/domain/src/test/java/akkamon/domain/AkkamonImplTest.java b/domain/src/test/java/akkamon/domain/AkkamonImplTest.java deleted file mode 100644 index 90c4f44..0000000 --- a/domain/src/test/java/akkamon/domain/AkkamonImplTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package akkamon.domain; - -import org.junit.Test; - -import static junit.framework.TestCase.assertNotNull; - -class AkkamonImplTest { - - class getInstance_behaviour { - @Test - void given_there_is_no_instance_yet_when_getInstance_is_called_then_give_class_property_instance() { - assertNotNull(AkkamonImpl.getInstance()); - } - } - -}
\ No newline at end of file diff --git a/domain/src/test/java/akkamon/domain/AkkamonNexusTest.java b/domain/src/test/java/akkamon/domain/AkkamonNexusTest.java index 5150b1c..d83bdcb 100644 --- a/domain/src/test/java/akkamon/domain/AkkamonNexusTest.java +++ b/domain/src/test/java/akkamon/domain/AkkamonNexusTest.java @@ -13,15 +13,21 @@ public class AkkamonNexusTest { @Test public void given_a_registration_request_when_no_scene_or_trainer_exists_in_the_system_then_create_scene_and_trainer_and_reply() { - TestProbe<AkkamonNexus.TrainerRegistered> probe = - testKit.createTestProbe(AkkamonNexus.TrainerRegistered.class); + TestProbe<AkkamonNexus.Command> probe = + testKit.createTestProbe(AkkamonNexus.Command.class); ActorRef<SceneTrainerGroup.Command> sceneTrainerGroupActor = testKit.spawn(SceneTrainerGroup.create("start")); - sceneTrainerGroupActor.tell(new AkkamonNexus.RequestTrainerRegistration("ash", "start", probe.getRef())); + // TODO use mockito to mock AkkamonSessions - probe.expectMessageClass(AkkamonNexus.TrainerRegistered.class); + // sceneTrainerGroupActor.tell(new AkkamonNexus.RequestTrainerRegistration( + // "ash", + // "start", + // probe.getRef() + // )); + + // probe.expectMessageClass(AkkamonNexus.TrainerRegistered.class); } |
