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
exception InvalidOperationException {
1: i32 code,
2: string description
}
exception GameNotFound {
1: i32 code,
2: string description
}
include "common.thrift"
namespace java fr.univnantes.alma.thrift
service GameClientService {
bool ping() throws (1:common.InvalidOperationException e)
}
include "common.thrift"
struct CrossPlatformResource {
1: i32 id,
2: string name,
3: optional string salutation
namespace java fr.univnantes.alma.thrift
struct JoinRequest {
1: string name
}
service GameServerService {
CrossPlatformResource get(1:i32 id) throws (1:common.InvalidOperationException e),
void save(1:CrossPlatformResource resource) throws (1:common.InvalidOperationException e),
i32 createGame(i32 numberOfPlayers)
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
== 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
== 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
......@@ -6,8 +6,8 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath></relativePath>
<version>2.4.0</version>
<relativePath/>
</parent>
<groupId>fr.univnantes.alma</groupId>
......@@ -21,21 +21,32 @@
</properties>
<dependencies>
<dependency>
<groupId>fr.univnantes.alma</groupId>
<artifactId>not-alone-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
......
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.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 static void main(String[] 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