diff --git a/back/src/Controller/position.js b/back/position-test.js similarity index 100% rename from back/src/Controller/position.js rename to back/position-test.js diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index aa5dfdc9..06e0cb11 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -87,7 +87,8 @@ export class IoSocketController{ //Hydrate and manage error hydrateMessageReceive(message : string) : MessageUserPosition | Error{ try { - return new MessageUserPosition(message); + let data = JSON.parse(message); + return new MessageUserPosition(data); }catch (err) { //TODO log error return new Error(err); diff --git a/back/src/Controller/PositionController.ts b/back/src/Controller/PositionController.ts index 0ffdd02f..f22f0917 100644 --- a/back/src/Controller/PositionController.ts +++ b/back/src/Controller/PositionController.ts @@ -1 +1,130 @@ -// TODO \ No newline at end of file +import {MessageUserPosition} from "_Model/Websocket/MessageUserPosition"; +import {Distance} from "_Model/Distance"; + +export class PositionController { + static readonly MIN_DISTANCE = 12; + static readonly MAX_PER_GROUP = 3; + + constructor () + { + // Injecter socket ? + } + + getDistancesBetweenAllUsers(users: MessageUserPosition[]): Distance[] + { + let i = 0; + let distances: Distance[] = []; + users.forEach(function(user1, key1) { + users.forEach(function(user2, key2) { + if(key1 < key2) { + distances[i] = { + distance: PositionController.computeDistance(user1, user2), + user1: user1, + user2: user2 + }; + i++; + } + }); + }); + + distances.sort(PositionController.compareDistances); + + return distances; + } + + createGroups(distances: Distance[], nbOfUsers: number, Oldgroups: MessageUserPosition[][]): MessageUserPosition[][] + { + // TODO : detect in existing groups if a user must be removed from the group + let alreadyInAGroup: any[string] = []; + let groups: MessageUserPosition[][] = []; + + let roomId = 0; + for(let i = 0; i < distances.length; i++) { + let dist = distances[i]; + + if(dist.distance <= PositionController.MIN_DISTANCE) { + if(typeof groups[roomId] === 'undefined') { + groups[roomId] = []; + } + + if(groups[roomId].indexOf(dist.user1) === -1 && typeof alreadyInAGroup[dist.user1.userId] === 'undefined') { + if(groups[roomId].length > 1) { + // if group is not empty we check current user can be added in the group according to its distance to the others already in it + for(let j = 0; j < groups[roomId].length; j++) { + let userTotest = groups[roomId][j]; + if(PositionController.computeDistance(dist.user1, userTotest) <= PositionController.MIN_DISTANCE) { + groups[roomId].push(dist.user1); + alreadyInAGroup[dist.user1.userId] = true; + break; + } + } + } else { + groups[roomId].push(dist.user1); + alreadyInAGroup[dist.user1.userId] = true; + } + } + + if(groups[roomId].length === PositionController.MAX_PER_GROUP) { + roomId++; // on créé un nouveau groupe + if(roomId > (nbOfUsers / PositionController.MAX_PER_GROUP)) { + console.log('There is no room left for user ID : ' + dist.user2.userId + ' !'); + break; + } + continue; + } + + if(groups[roomId].indexOf(dist.user2) === -1 && typeof alreadyInAGroup[dist.user2.userId] === 'undefined') { + if(groups[roomId].length > 1) { + // if group is not empty we check current user can be added in the group according to its distance to the others already in it + for(let j = 0; j < groups[roomId].length; j++) { + let userTotest = groups[roomId][j]; + if(PositionController.computeDistance(dist.user2, userTotest) <= PositionController.MIN_DISTANCE) { + groups[roomId].push(dist.user2); + alreadyInAGroup[dist.user2.userId] = true; + break; + } + } + } else { + groups[roomId].push(dist.user2); + alreadyInAGroup[dist.user2.userId] = true; + } + } + } + } + return groups; + } + + // FIXME + checkGroupDistance (groups: MessageUserPosition[][]) + { + for(let i = 0; i < groups.length; i++) { + let group = groups[i]; + group.forEach((user1, key1) => { + group.forEach((user2, key2) => { + if(key1 < key2) { + let distance = PositionController.computeDistance(user1, user2); + if(distance > PositionController.MIN_DISTANCE) { + // TODO : message a user1 et user2 + } + } + }); + }); + } + } + + private static computeDistance(user1: MessageUserPosition, user2: MessageUserPosition): number + { + return Math.sqrt(Math.pow(user2.position.x - user1.position.x, 2) + Math.pow(user2.position.y - user1.position.y, 2)); + } + + private static compareDistances(distA: Distance, distB: Distance): number + { + if (distA.distance < distB.distance) { + return -1; + } + if (distA.distance > distB.distance) { + return 1; + } + return 0; + } +} \ No newline at end of file diff --git a/back/src/Model/Distance.ts b/back/src/Model/Distance.ts new file mode 100644 index 00000000..0abde384 --- /dev/null +++ b/back/src/Model/Distance.ts @@ -0,0 +1,7 @@ +import {MessageUserPosition} from "../Model/Websocket/MessageUserPosition"; + +export interface Distance { + distance: number, + user1: MessageUserPosition, + user2: MessageUserPosition, +} \ No newline at end of file diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts new file mode 100644 index 00000000..208615da --- /dev/null +++ b/back/src/Model/Group.ts @@ -0,0 +1,18 @@ +import {MessageUserPosition} from "./Websocket/MessageUserPosition"; +export class Group { + static readonly MAX_PER_GROUP = 4; + + users: MessageUserPosition[]; + + constructor(users: MessageUserPosition[]) { + this.users = users; + } + + getUsers(): MessageUserPosition[] { + return this.users; + } + + isFull(): boolean { + return this.users.length >= Group.MAX_PER_GROUP; + } +} \ No newline at end of file diff --git a/back/src/Model/UserInterface.ts b/back/src/Model/UserInterface.ts new file mode 100644 index 00000000..c75d3f3a --- /dev/null +++ b/back/src/Model/UserInterface.ts @@ -0,0 +1,7 @@ +import { Group } from "./Group"; +import { PointInterface } from "./Websocket/PointInterface"; + +export interface Userinteface { + group: Group, + pointInterface: PointInterface +} \ No newline at end of file diff --git a/back/src/Model/Websocket/Group.ts b/back/src/Model/Websocket/Group.ts new file mode 100644 index 00000000..1daa8d66 --- /dev/null +++ b/back/src/Model/Websocket/Group.ts @@ -0,0 +1,18 @@ +import {MessageUserPosition} from "./MessageUserPosition"; +export class Group { + static readonly MAX_PER_GROUP = 4; + + users: MessageUserPosition[]; + + constructor(users: MessageUserPosition[]) { + this.users = users; + } + + getUsers(): MessageUserPosition[] { + return this.users; + } + + isFull(): boolean { + return this.users.length >= Group.MAX_PER_GROUP; + } +} \ No newline at end of file diff --git a/back/src/Model/Websocket/Message.ts b/back/src/Model/Websocket/Message.ts index 8e6f2c9a..d726968f 100644 --- a/back/src/Model/Websocket/Message.ts +++ b/back/src/Model/Websocket/Message.ts @@ -2,8 +2,7 @@ export class Message { userId: string; roomId: string; - constructor(message: string) { - let data = JSON.parse(message); + constructor(data: any) { if(!data.userId || !data.roomId){ throw Error("userId or roomId cannot be null"); } diff --git a/back/src/Model/Websocket/MessageUserPosition.ts b/back/src/Model/Websocket/MessageUserPosition.ts index 493f1457..2e79f5ef 100644 --- a/back/src/Model/Websocket/MessageUserPosition.ts +++ b/back/src/Model/Websocket/MessageUserPosition.ts @@ -24,9 +24,8 @@ export class Point implements PointInterface{ export class MessageUserPosition extends Message{ position: PointInterface; - constructor(message: string) { - super(message); - let data = JSON.parse(message); + constructor(data: any) { + super(data); this.position = new Point(data.position.x, data.position.y); } diff --git a/back/src/Model/World.ts b/back/src/Model/World.ts new file mode 100644 index 00000000..e9bd5474 --- /dev/null +++ b/back/src/Model/World.ts @@ -0,0 +1,46 @@ +import {MessageUserPosition} from "./Websocket/MessageUserPosition"; +import {PointInterface} from "./Websocket/PointInterface"; +import {Group} from "./Group"; + +export class World { + // Users, sorted by ID + private users: Map; + private groups: Group[] + private connectCallback: (user1: string, user2: string) => void; + private disconnectCallback: (user1: string, user2: string) => void; + + constructor(connectCallback: (user1: string, user2: string) => void, disconnectCallback: (user1: string, user2: string) => void) + { + this.users = new Map(); + this.groups = []; + this.connectCallback = connectCallback; + this.disconnectCallback = disconnectCallback; + } + + public join(userPosition: MessageUserPosition): void { + this.users.set(userPosition.userId, userPosition.position); + } + + public updatePosition(userPosition: MessageUserPosition): void { + if(typeof userPosition.userId === 'undefined') { + throw new Error('unkown id'); + } + //this.users.get(userPosition.userId).x; + + // TODO: compute distance between peers. + + // Is the user in a group? + + // Is the user leaving the group? (is the user at more than max distance of each player) + + // Should we split the group? (is each player reachable from the current player?) + // This is needed if + // A <==> B <==> C <===> D + // becomes A <==> B <=====> C <> D + // If C moves right, the distance between B and C is too great and we must form 2 groups + + // If the user is in no group + // is there someone in a group close enough and with room in the group? + } + +} \ No newline at end of file diff --git a/back/tests/MessageTest.ts b/back/tests/MessageTest.ts index 69d57fce..070ddf57 100644 --- a/back/tests/MessageTest.ts +++ b/back/tests/MessageTest.ts @@ -3,27 +3,27 @@ import {Message} from "../src/Model/Websocket/Message"; describe("Message Model", () => { it("should find userId and roomId", () => { - let message = JSON.stringify({userId: "test1", roomId: "test2"}); + let message = {userId: "test1", roomId: "test2"}; let messageObject = new Message(message); expect(messageObject.userId).toBe("test1"); expect(messageObject.roomId).toBe("test2"); }) it("should expose a toJson method", () => { - let message = JSON.stringify({userId: "test1", roomId: "test2"}); + let message = {userId: "test1", roomId: "test2"}; let messageObject = new Message(message); expect(messageObject.toJson()).toEqual({userId: "test1", roomId: "test2"}); }) it("should find throw error when no userId", () => { - let message = JSON.stringify({roomId: "test2"}); + let message = {roomId: "test2"}; expect(() => { let messageObject = new Message(message); }).toThrow(new Error("userId or roomId cannot be null")); }) it("should find throw error when no roomId", () => { - let message = JSON.stringify({userId: "test1"}); + let message = {userId: "test1"}; expect(() => { let messageObject = new Message(message); }).toThrow(new Error("userId or roomId cannot be null")); diff --git a/back/tests/WorldTest.ts b/back/tests/WorldTest.ts new file mode 100644 index 00000000..7b8c167b --- /dev/null +++ b/back/tests/WorldTest.ts @@ -0,0 +1,39 @@ +import "jasmine"; +import {Message} from "../src/Model/Websocket/Message"; +import {World} from "../src/Model/World"; +import {MessageUserPosition, Point} from "../src/Model/Websocket/MessageUserPosition"; + +describe("World", () => { + it("should connect user1 and user2", () => { + let connectCalled: boolean = false; + let connect = (user1: string, user2: string): void => { + connectCalled = true; + } + let disconnect = (user1: string, user2: string): void => { + + } + + let world = new World(connect, disconnect); + + world.join(new MessageUserPosition({ + userId: "foo", + roomId: 1, + position: new Point(100, 100) + })); + + world.join(new MessageUserPosition({ + userId: "bar", + roomId: 1, + position: new Point(500, 100) + })); + + world.updatePosition(new MessageUserPosition({ + userId: "bar", + roomId: 1, + position: new Point(101, 100) + })); + + expect(connectCalled).toBe(true); + + }) +}) \ No newline at end of file