Commit d2e991fd authored by Gerson Sunyé's avatar Gerson Sunyé
Browse files

Initial implementation of Game component

parent 0b04889a
namespace java fr.univnantes.alma.core namespace java fr.univnantes.alma.thrift
namespace js core namespace js core
exception InvalidOperationException { exception InvalidOperationException {
1: i32 code, 1: i32 code,
2: string description 2: string description
} }
exception GameNotFound {
1: i32 code,
2: string description
}
include "common.thrift" include "common.thrift"
namespace java fr.univnantes.alma.thrift
service GameClientService { service GameClientService {
bool ping() throws (1:common.InvalidOperationException e) bool ping() throws (1:common.InvalidOperationException e)
} }
include "common.thrift" include "common.thrift"
struct CrossPlatformResource { namespace java fr.univnantes.alma.thrift
1: i32 id,
2: string name, struct JoinRequest {
3: optional string salutation 1: string name
} }
service GameServerService { service GameServerService {
CrossPlatformResource get(1:i32 id) throws (1:common.InvalidOperationException e), i32 createGame(i32 numberOfPlayers)
void save(1:CrossPlatformResource resource) throws (1:common.InvalidOperationException e),
list <CrossPlatformResource> getList() throws (1:common.InvalidOperationException e), i32 join(i32 gameId, JoinRequest request) throws (1:common.GameNotFound e)
bool ping() throws (1:common.InvalidOperationException e) void start(i32 gameId) throws (1:common.GameNotFound e)
} }
= Conception préliminaire = Conception préliminaire
== Création d'une partie
.Join game
[plantuml]
....
participant "__one:Player__" as player1
participant "__two:Player__" as player2
participant "__three:Player__" as player3
participant "__four:Player__" as player4
participant "__five:Player__" as player5
participant "__six:Player__" as player6
participant "__game:GameServer__" as game
player1 -> game : id := createGame(6)
par
player1 -> game : playerId := join(id, one)
player2 -> game : playerId := join(id, two)
player3 -> game : playerId := join(id, three)
player4 -> game : playerId := join(id, four)
player5 -> game : playerId := join(id, five)
player6 -> game : playerId := join(id, six)
end
game -> game : start()
....
.Questions ouveres (à faire)
- Les joeurs ne peuvent pas envcoyer juste une `id`, ils doivent aussi envoyer une adress/port pour que le serveur puisse les contacter
- Le serveur doit prévenir les joueurs du début de la partie.
.L'interface GameServer
[plantuml]
....
interface GameServer {
createGame(numberOfPlayers : Integer): Integer
join(gameId : Integer): Integer
satrt()
}
....
= Conception détaillée = Conception détaillée
== Game Server
.GameSever
[plantuml]
....
interface GameServer {
createGame(numberOfPlayers : Integer): Integer
join(gameId : Integer): Integer
start()
}
package game {
class "GameServerController" as controller {
createGame(numberOfPlayers : Integer): Integer
join(gameId : Integer): Integer
start()
}
class "Game" as game {
id : Integer {id}
numberOfPlayers : Integer
}
class "Player" as player {
id : Integer {id}
}
GameServer <|-- controller
controller *- "[*] games" game : \t\t\t
controller *-- "[0..7] players" player
}
note right of game: Uncompleted!
....
[plantuml]
....
state Game {
[*] --> Created
Created -> Started : start()
Started -> Phase1 : \t
Phase1 --> [*]
}
note right of Game : Uncompleted!
....
=== Opérations du GameServerController
.Create Game
[source,OCL]
----
GameServerController::createGame(numberOfPlayers : Integer): Integer
pre:
numberOfPlayers > 1 and numberOfPlayers <= 7
post:
self.games -> exists(each | each.isOclNew())and
game.oclinState(Created)
----
.Join Game
[source,OCL]
----
GameServerController::join(gameId : Integer): Integer
pre:
self.games -> exists(each | each.id = gameId)
post:
let game = self.games->select(id = gameId)->first() in
game.players->exists(each| each.isOclNew())
----
.Start Game
[plantuml]
----
partition GameServerController::start() {
start
while (enough players?)
:JoinRequest<
:handleRequest();
endwhile
:Game Start>
stop
}
----
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="CheckStyle-IDEA-Module">
<option name="configuration">
<map />
</option>
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version> <version>2.4.0</version>
<relativePath></relativePath> <relativePath/>
</parent> </parent>
<groupId>fr.univnantes.alma</groupId> <groupId>fr.univnantes.alma</groupId>
...@@ -21,21 +21,32 @@ ...@@ -21,21 +21,32 @@
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>fr.univnantes.alma</groupId>
<artifactId>not-alone-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId> <artifactId>spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.atlanmod.commons</groupId>
<artifactId>commons-core</artifactId>
<version>1.0.6</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
</dependencies> </dependencies>
......
package fr.univnantes.alma; package fr.univnantes.alma;
import fr.univnantes.alma.thrift.GameServerService;
import fr.univnantes.alma.handler.GameServiceHandler;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TServlet;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@SpringBootApplication import javax.servlet.Servlet;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class NotAloneApplication { public class NotAloneApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(NotAloneApplication.class, args); SpringApplication.run(NotAloneApplication.class, args);
} }
@Bean
public TProtocolFactory tProtocolFactory() {
return new TBinaryProtocol.Factory();
}
@Bean
public ServletRegistrationBean gameServer(TProtocolFactory protocolFactory, GameServiceHandler handler) {
TServlet tServlet = new TServlet(new GameServerService.Processor<GameServiceHandler>(handler), protocolFactory);
return new ServletRegistrationBean(tServlet, "/api");
}
} }
package fr.univnantes.alma.common;
public class GameJoinRequest {
}
package fr.univnantes.alma.common;
import fr.univnantes.alma.common.GameJoinRequest;
public interface GameService {
/**
* Creates a game for a number of players
* @param expectedPlayers The number of expected players, between 2 and 7
*
* @return an int, the game identification
*/
int createGame(int expectedPlayers);
/**
*
* @param gameId
* @param request
* @return
*/
int join(int gameId, GameJoinRequest request);
void start(int gameId) throws InterruptedException;
}
package fr.univnantes.alma.game;
import fr.univnantes.alma.common.GameJoinRequest;
import org.atlanmod.commons.log.Log;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class Game {
/**
* Stores arriving registrations.
*/
private final BlockingQueue<GameJoinRequest> requests;
/**
* Counter used to increment player identifications.
*/
private final AtomicInteger idCounter = new AtomicInteger(0);
private final AtomicInteger expectedPlayers;
public Game(int expectedPlayers) {
this.expectedPlayers = new AtomicInteger(expectedPlayers);
this.requests = new ArrayBlockingQueue<GameJoinRequest>(expectedPlayers);
}
public int join(GameJoinRequest request) {
int id = idCounter.getAndIncrement();
requests.offer(request);
return id;
}
public void start() {
Thread t = new Thread(() -> {
this.waitForPlayers();
Log.info("We can start !");
}
);
t.start();
}
private void waitForPlayers() {
Log.info("Waiting for request. Expecting {0} players.", expectedPlayers);
GameJoinRequest request;
while (requests.size() < expectedPlayers.intValue()) {
try {
request = requests.take();
this.handleRequest(request);
} catch (InterruptedException e) {
Log.error(e);
}
}
}
private void handleRequest(GameJoinRequest request) {
}
}
package fr.univnantes.alma.game;
import fr.univnantes.alma.common.GameJoinRequest;
import fr.univnantes.alma.common.GameService;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import static org.atlanmod.commons.Preconditions.checkArgument;
@Component
public class GameServiceController implements GameService {
private final AtomicInteger idCounter = new AtomicInteger(0);
private final Map<Integer, Game> games = new HashMap<>();
@Override
public int createGame(int expectedPlayers) {
checkArgument(expectedPlayers > 1 && expectedPlayers <= 7, "A game must have between 2 and 7 players");
int newId = idCounter.incrementAndGet();
Game newGame = new Game(expectedPlayers);
games.put(newId, newGame);
return newId;
}
@Override
public int join(int gameId, GameJoinRequest request) {
Game game = games.get(gameId);
return game.join(request);
}
@Override
public void start(int gameId) throws InterruptedException {
Game game = games.get(gameId);
game.start();
}
}
package fr.univnantes.alma.handler;
import fr.univnantes.alma.common.GameJoinRequest;
import fr.univnantes.alma.thrift.GameNotFound;
import fr.univnantes.alma.thrift.GameServerService;
import fr.univnantes.alma.thrift.JoinRequest;
import fr.univnantes.alma.common.GameService;
import org.apache.thrift.TException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class GameServiceHandler implements GameServerService.Iface {
@Autowired
GameService service;
@Override
public int createGame(int numberOfPlayers) throws TException {
return service.createGame(numberOfPlayers);
}
@Override
public int join(int gameId, JoinRequest request) throws TException {
return service.join(gameId, new GameJoinRequest());
}
@Override
public void start(int gameId) throws GameNotFound, TException {
try {
service.start(gameId);
} catch (InterruptedException e) {
throw new GameNotFound();
}
}
}
package fr.univnantes.alma;
import fr.univnantes.alma.thrift.GameServerService;
import fr.univnantes.alma.thrift.JoinRequest;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransport;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NotAloneApplication.class)
class NotAloneApplicationTest {
@Autowired
protected TProtocolFactory protocolFactory;
@LocalServerPort
protected int port;
protected GameServerService.Iface client;
@BeforeEach
public void setUp() throws Exception {
TTransport transport = new THttpClient("http://localhost:" + port + "/api");
TProtocol protocol = protocolFactory.getProtocol(transport);
client = new GameServerService.Client(protocol);
}
@Test
public void testCreateGame() throws TException {
int id = client.createGame(4);
assertThat(id).isGreaterThan(0);
}
@Test
public void testJoinGame() throws TException, InterruptedException {
int id = client.createGame(4);
client.join(id, new JoinRequest("one"));
client.join(id, new JoinRequest("two"));
client.join(id, new JoinRequest("three"));
client.join(id, new JoinRequest("four"));
client.start(id);
Thread.sleep(1000);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment