diff --git a/.gitignore b/.gitignore index 96ef862..50cf3af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ target/ .idea/ +.classpath +.factorypath +.project +.settings/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb23564 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# JWS + +> **Note** This is a school project, therefore it probably won't interest you if you are looking for something useful. + +## Overview + +JWS stands for Java Web Services and as its name doesn't suggest at all, it's basically a web server for a a pokemon-like game. It's written in Java and built onto Quarkus and Jakarta, providing a REST API with strict server-side rules like cooldowns and cheating prevention. + +## Architecture + +> **Note** Source code is located inside the `yakamon` folder, both others are school requirements and aren't important + +Each layer is strictly separated and can only communicate via to the one directly below or above it via converters. + +### Presentation Layer (REST & DTOs) +* located at `yakamon/jws/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation` +* Handled by JAX-RS (Jakarta RESTful Web Services). +* Exposes standard HTTP endpoints (`/player`, `/move`, `/yakadex`, etc.). Full API specification can be found inside `yakamon/src/main/resources/openapi.yaml` +* Implements strict **Data Transfer Objects (DTOs)** for both Requests and Responses to ensure the internal database models are never exposed directly to the client. + +### Business Logic Layer (Services) +* located at `yakamon/jws/yakamon/src/main/java/fr/epita/assistants/yakamon/domain` +* Acts as the brain of the application. +* Implements complex game rules: validating if a target tile is walkable based on the terrain type, calculating cooldowns between moves, and managing creature capture probabilities. +* **Converters** are used to translate Entities from the Data Layer into DTOs for the Presentation Layer. + +### Data Access Layer (Hibernate ORM) +* located at `yakamon/jws/yakamon/src/main/java/fr/epita/assistants/yakamon/data` +* Manages persistence using **Hibernate ORM** with the Active Record / Repository pattern. +* Defines relational entities (Player, Game, Yakamon, Item) mapped to a PostgreSQL database. + diff --git a/database/src/main/java/fr/epita/assistants/data/model/CourseModel.java b/database/src/main/java/fr/epita/assistants/data/model/CourseModel.java index 5fe1cfb..428b667 100644 --- a/database/src/main/java/fr/epita/assistants/data/model/CourseModel.java +++ b/database/src/main/java/fr/epita/assistants/data/model/CourseModel.java @@ -8,5 +8,5 @@ import java.util.List; public class CourseModel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) protected Long id; public String name; - public @ElementCollection @CollectionTable(name = "course_model_tags") @JoinColumn(name="course_id") List tag; + public @ElementCollection @CollectionTable(name = "course_model_tags", joinColumns = @JoinColumn(name = "course_id")) List tag; } diff --git a/endpoints/src/main/java/fr/epita/assistants/presentation/rest/Endpoints.java b/endpoints/src/main/java/fr/epita/assistants/presentation/rest/Endpoints.java index 748799b..2895254 100644 --- a/endpoints/src/main/java/fr/epita/assistants/presentation/rest/Endpoints.java +++ b/endpoints/src/main/java/fr/epita/assistants/presentation/rest/Endpoints.java @@ -1,5 +1,30 @@ package fr.epita.assistants.presentation.rest; -public class Endpoints { +import fr.epita.assistants.presentation.rest.request.ReverseRequest; +import fr.epita.assistants.presentation.rest.response.HelloResponse; +import fr.epita.assistants.presentation.rest.response.ReverseResponse; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +@Path("/") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class Endpoints { + String content; + @Path("/hello/{name}") + @GET + public Response greeting(@PathParam("name") String name) { + if (name == null || name.isEmpty()) { + return Response.status(400).build(); + } + HelloResponse response = new HelloResponse("hello " + name); + return Response.ok(response).build(); + } + + @Path("/reverse") + @POST + public Response reverse(ReverseRequest r) { + return Response.ok(new ReverseResponse(r.content)).build(); + } } diff --git a/endpoints/src/main/java/fr/epita/assistants/presentation/rest/request/ReverseRequest.java b/endpoints/src/main/java/fr/epita/assistants/presentation/rest/request/ReverseRequest.java index 7311dad..8f42af8 100644 --- a/endpoints/src/main/java/fr/epita/assistants/presentation/rest/request/ReverseRequest.java +++ b/endpoints/src/main/java/fr/epita/assistants/presentation/rest/request/ReverseRequest.java @@ -1,5 +1,8 @@ package fr.epita.assistants.presentation.rest.request; -public class ReverseRequest { +import lombok.AllArgsConstructor; +@AllArgsConstructor +public class ReverseRequest { + public String content; } diff --git a/endpoints/src/main/java/fr/epita/assistants/presentation/rest/response/HelloResponse.java b/endpoints/src/main/java/fr/epita/assistants/presentation/rest/response/HelloResponse.java index 0a8c052..dc011e9 100644 --- a/endpoints/src/main/java/fr/epita/assistants/presentation/rest/response/HelloResponse.java +++ b/endpoints/src/main/java/fr/epita/assistants/presentation/rest/response/HelloResponse.java @@ -1,20 +1,12 @@ package fr.epita.assistants.presentation.rest.response; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import lombok.AllArgsConstructor; +import lombok.ToString; -@Path("/hello") -@Consumes(MediaType.TEXT_PLAIN) -@Produces(MediaType.TEXT_PLAIN) +@AllArgsConstructor public class HelloResponse { - @Path("/{name}") - @GET - public String greeting(@PathParam("name") String name) { - String content - Response.accepted(); - } + public String content; } diff --git a/endpoints/src/main/java/fr/epita/assistants/presentation/rest/response/ReverseResponse.java b/endpoints/src/main/java/fr/epita/assistants/presentation/rest/response/ReverseResponse.java index 9b3379d..147e217 100644 --- a/endpoints/src/main/java/fr/epita/assistants/presentation/rest/response/ReverseResponse.java +++ b/endpoints/src/main/java/fr/epita/assistants/presentation/rest/response/ReverseResponse.java @@ -1,5 +1,15 @@ package fr.epita.assistants.presentation.rest.response; +import lombok.AllArgsConstructor; + +@AllArgsConstructor public class ReverseResponse { + public String original; + public String reverse; + + public ReverseResponse(String original) { + this.original = original; + this.reverse = new StringBuilder(original).reverse().toString(); + } } diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/converter/DtoToEntityConverter.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/converter/DtoToEntityConverter.java new file mode 100644 index 0000000..941cc3c --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/converter/DtoToEntityConverter.java @@ -0,0 +1,72 @@ +package fr.epita.assistants.yakamon.converter; + +import fr.epita.assistants.yakamon.domain.entity.GameEntity; +import fr.epita.assistants.yakamon.domain.entity.YakadexEntryEntity; +import fr.epita.assistants.yakamon.domain.entity.YakamonEntity; +import fr.epita.assistants.yakamon.presentation.api.YakadexEntry; +import fr.epita.assistants.yakamon.presentation.api.YakamonAction; +import fr.epita.assistants.yakamon.presentation.api.request.StartGameRequest; +import fr.epita.assistants.yakamon.utils.ElementType; +import java.time.LocalDateTime; +import java.util.UUID; + +public final class DtoToEntityConverter { + + public static YakamonEntity toYakamonEntity(YakamonAction dto) { + if (dto == null) return null; + + YakamonEntity entity = new YakamonEntity(); + if (dto.uuid != null) { + try { + entity.uuid = UUID.fromString(dto.uuid); + } catch (IllegalArgumentException ignored) { + entity.uuid = null; + } + } else { + entity.uuid = null; + } + + entity.nickname = dto.nickname; + entity.yakadexId = dto.yakadexId; + entity.energyPoints = dto.energyPoints; + return entity; + } + + public static YakadexEntryEntity toYakadexEntryEntity(YakadexEntry dto) { + if (dto == null) return null; + + YakadexEntryEntity e = new YakadexEntryEntity(); + e.id = dto.id; + e.name = dto.name; + e.description = dto.description; + e.evolveThreshold = dto.evolveThreshold; + e.evolutionId = dto.evolutionId; + e.caught = dto.caught; + + if (dto.firstType != null) { + try { + e.firstType = ElementType.valueOf(dto.firstType); + } catch (IllegalArgumentException ex) { + e.firstType = null; + } + } + + if (dto.secondType != null) { + try { + e.secondType = ElementType.valueOf(dto.secondType); + } catch (IllegalArgumentException ex) { + e.secondType = null; + } + } + + return e; + } + + public static GameEntity toGameEntity(StartGameRequest request) { + if (request == null) return null; + GameEntity g = new GameEntity(); + g.mapPath = request.mapPath; + g.start(LocalDateTime.now()); + return g; + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/converter/EntityToDtoConverter.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/converter/EntityToDtoConverter.java new file mode 100644 index 0000000..c64ee8d --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/converter/EntityToDtoConverter.java @@ -0,0 +1,71 @@ +package fr.epita.assistants.yakamon.converter; + +import fr.epita.assistants.yakamon.domain.entity.YakadexEntryEntity; +import fr.epita.assistants.yakamon.domain.entity.YakamonEntity; +import fr.epita.assistants.yakamon.presentation.api.YakadexEntry; +import fr.epita.assistants.yakamon.presentation.api.YakamonAction; +import java.util.ArrayList; +import java.util.List; + +public final class EntityToDtoConverter { + + public static YakamonAction toYakamonAction(YakamonEntity e) { + if (e == null) return null; + String uuid = null; + if (e.uuid != null) { + uuid = e.uuid.toString(); + } + return new YakamonAction(uuid, e.nickname, e.yakadexId, e.energyPoints); + } + + public static YakadexEntry toYakadexEntry(YakadexEntryEntity e) { + if (e == null) return null; + + String firstType = null; + if (e.firstType != null) { + firstType = e.firstType.name(); + } + + String secondType = null; + if (e.secondType != null) { + secondType = e.secondType.name(); + } + + Integer evolutionId = e.evolutionId; + + return new YakadexEntry( + e.id, + e.name, + firstType, + secondType, + e.evolveThreshold, + evolutionId, + e.caught, + e.description + ); + } + + public static List toYakadexEntries( + List entries + ) { + List out = new ArrayList<>(); + if (entries == null || entries.isEmpty()) return out; + for (YakadexEntryEntity e : entries) { + YakadexEntry dto = toYakadexEntry(e); + if (dto != null) out.add(dto); + } + return out; + } + + public static List toYakamonActions( + List yakamons + ) { + List out = new ArrayList<>(); + if (yakamons == null || yakamons.isEmpty()) return out; + for (YakamonEntity y : yakamons) { + YakamonAction a = toYakamonAction(y); + if (a != null) out.add(a); + } + return out; + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/converter/EntityToModelConverter.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/converter/EntityToModelConverter.java new file mode 100644 index 0000000..742f54a --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/converter/EntityToModelConverter.java @@ -0,0 +1,60 @@ +package fr.epita.assistants.yakamon.converter; + +import fr.epita.assistants.yakamon.data.model.GameModel; +import fr.epita.assistants.yakamon.data.model.PlayerModel; +import fr.epita.assistants.yakamon.data.model.YakadexEntryModel; +import fr.epita.assistants.yakamon.data.model.YakamonModel; +import fr.epita.assistants.yakamon.domain.entity.GameEntity; +import fr.epita.assistants.yakamon.domain.entity.PlayerEntity; +import fr.epita.assistants.yakamon.domain.entity.YakadexEntryEntity; +import fr.epita.assistants.yakamon.domain.entity.YakamonEntity; + +public final class EntityToModelConverter { + + public static GameModel toGameModel(GameEntity e) { + if (e == null) return null; + GameModel m = new GameModel(); + m.map = e.mapPath; + m.started = e.started; + m.startTime = e.startTime; + return m; + } + + public static PlayerModel toPlayerModel(PlayerEntity e) { + if (e == null) return null; + PlayerModel m = new PlayerModel(); + m.name = e.name; + m.posX = e.posX; + m.posY = e.posY; + m.lastMove = e.lastMove; + m.lastCatch = e.lastCatch; + m.lastCollect = e.lastCollect; + m.lastFeed = e.lastFeed; + return m; + } + + public static YakamonModel toYakamonModel(YakamonEntity e) { + if (e == null) return null; + YakamonModel m = new YakamonModel(); + m.nickname = e.nickname; + m.energy_points = e.energyPoints; + if (e.yakadexId != null) { + YakadexEntryModel yakadex = new YakadexEntryModel(); + m.yakadex_entry_id = yakadex; + } + return m; + } + + public static YakadexEntryModel toYakadexEntryModel(YakadexEntryEntity e) { + if (e == null) return null; + YakadexEntryModel m = new YakadexEntryModel(); + m.name = e.name; + m.caught = e.caught; + m.first_type = e.firstType != null ? e.firstType.name() : null; + m.second_type = e.secondType != null ? e.secondType.name() : null; + m.description = e.description; + m.evolve_threshold = e.evolveThreshold; + m.evolution_id = null; + return m; + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/GameModel.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/GameModel.java new file mode 100644 index 0000000..2180c6e --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/GameModel.java @@ -0,0 +1,17 @@ +package fr.epita.assistants.yakamon.data.model; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "game") +public class GameModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long id; + + public String map; + public boolean started; + public LocalDateTime startTime; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/ItemModel.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/ItemModel.java new file mode 100644 index 0000000..e71d104 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/ItemModel.java @@ -0,0 +1,42 @@ +package fr.epita.assistants.yakamon.data.model; + +import fr.epita.assistants.yakamon.utils.tile.ItemType; +import jakarta.persistence.*; + +@Entity +@Table(name = "item") +public class ItemModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Enumerated + private ItemType type; + + private Integer quantity; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public ItemType getType() { + return type; + } + + public void setType(ItemType type) { + this.type = type; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/PlayerModel.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/PlayerModel.java new file mode 100644 index 0000000..272971f --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/PlayerModel.java @@ -0,0 +1,22 @@ +package fr.epita.assistants.yakamon.data.model; + +import jakarta.persistence.*; +import java.time.LocalDateTime; +import java.util.UUID; + +@Entity +@Table(name = "player") +public class PlayerModel { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + public UUID id; + + public String name; + public Integer posX; + public Integer posY; + public LocalDateTime lastMove; + public LocalDateTime lastCatch; + public LocalDateTime lastCollect; + public LocalDateTime lastFeed; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/YakadexEntryModel.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/YakadexEntryModel.java new file mode 100644 index 0000000..ddc3919 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/YakadexEntryModel.java @@ -0,0 +1,27 @@ +package fr.epita.assistants.yakamon.data.model; + +import jakarta.persistence.*; + +@Entity +@Table(name = "yakadex_entry") +public class YakadexEntryModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Integer id; + + public String name; + public Boolean caught; + + // Changed to string otherwise it doesn't work + public String first_type; + public String second_type; + + public String description; + + @OneToOne + @JoinColumn(name = "evolution_id") + public YakadexEntryModel evolution_id; + + public Integer evolve_threshold; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/YakamonModel.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/YakamonModel.java new file mode 100644 index 0000000..a0373aa --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/model/YakamonModel.java @@ -0,0 +1,20 @@ +package fr.epita.assistants.yakamon.data.model; + +import jakarta.persistence.*; +import java.util.UUID; + +@Entity +@Table(name = "yakamon") +public class YakamonModel { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + public UUID id; + + public String nickname; + public Integer energy_points; + + @ManyToOne + @JoinColumn(name = "yakadex_id") + public YakadexEntryModel yakadex_entry_id; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/GameRepository.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/GameRepository.java new file mode 100644 index 0000000..53f6a1d --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/GameRepository.java @@ -0,0 +1,8 @@ +package fr.epita.assistants.yakamon.data.repository; + +import fr.epita.assistants.yakamon.data.model.GameModel; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class GameRepository implements PanacheRepository {} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/ItemRepository.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/ItemRepository.java new file mode 100644 index 0000000..e1ef9ef --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/ItemRepository.java @@ -0,0 +1,8 @@ +package fr.epita.assistants.yakamon.data.repository; + +import fr.epita.assistants.yakamon.data.model.ItemModel; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class ItemRepository implements PanacheRepository {} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/PlayerRepository.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/PlayerRepository.java new file mode 100644 index 0000000..296a11b --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/PlayerRepository.java @@ -0,0 +1,8 @@ +package fr.epita.assistants.yakamon.data.repository; + +import fr.epita.assistants.yakamon.data.model.PlayerModel; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class PlayerRepository implements PanacheRepository {} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/YakadexRepository.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/YakadexRepository.java new file mode 100644 index 0000000..ba2a4dc --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/YakadexRepository.java @@ -0,0 +1,9 @@ +package fr.epita.assistants.yakamon.data.repository; + +import fr.epita.assistants.yakamon.data.model.YakadexEntryModel; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class YakadexRepository + implements PanacheRepository {} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/YakamonRepository.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/YakamonRepository.java new file mode 100644 index 0000000..b38cf32 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/data/repository/YakamonRepository.java @@ -0,0 +1,8 @@ +package fr.epita.assistants.yakamon.data.repository; + +import fr.epita.assistants.yakamon.data.model.YakamonModel; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class YakamonRepository implements PanacheRepository {} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/GameEntity.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/GameEntity.java new file mode 100644 index 0000000..8d327a9 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/GameEntity.java @@ -0,0 +1,40 @@ +package fr.epita.assistants.yakamon.domain.entity; + +import java.time.LocalDateTime; + +public class GameEntity { + + public static GameEntity INSTANCE = null; + public String mapPath; + public boolean started; + public LocalDateTime startTime; + public TileEntity[][] map; + + public GameEntity() { + this.start(null); + } + + public GameEntity(String mapPath) { + this.start(null); + this.mapPath = mapPath; + GameEntity.INSTANCE = this; + } + + public GameEntity start(LocalDateTime startTime) { + if (started || INSTANCE != null) { + this.stop(); + } + startTime = startTime == null ? LocalDateTime.now() : startTime; + this.startTime = startTime; + this.started = true; + return this; + } + + public GameEntity stop() { + GameEntity.INSTANCE = null; + this.started = false; + this.startTime = null; + this.map = null; + return this; + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/ItemEntity.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/ItemEntity.java new file mode 100644 index 0000000..3395967 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/ItemEntity.java @@ -0,0 +1,23 @@ +package fr.epita.assistants.yakamon.domain.entity; + +import fr.epita.assistants.yakamon.utils.tile.ItemType; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +public class ItemEntity { + + public Integer id; + public ItemType type; + public Integer quantity; + + // public ItemEntity(Integer id, ItemType type, Integer quantity) { + // } + + // public ItemEntity addQuantity(int amount) { + // int current = this.quantity != null ? this.quantity : 0; + // this.quantity = current + amount; + // return this; + // } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/PlayerEntity.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/PlayerEntity.java new file mode 100644 index 0000000..012bbe0 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/PlayerEntity.java @@ -0,0 +1,20 @@ +package fr.epita.assistants.yakamon.domain.entity; + +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +public class PlayerEntity { + + public UUID uuid; + public String name; + public Integer posX; + public Integer posY; + public LocalDateTime lastMove; + public LocalDateTime lastCatch; + public LocalDateTime lastCollect; + public LocalDateTime lastFeed; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/TileEntity.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/TileEntity.java new file mode 100644 index 0000000..6f110ae --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/TileEntity.java @@ -0,0 +1,16 @@ +package fr.epita.assistants.yakamon.domain.entity; + +public class TileEntity { + + public Character terrain; + public Character collectible; + + public TileEntity(Character terrain, Character collectible) { + this.terrain = terrain; + this.collectible = collectible; + } + + public boolean isWalkable() { + return terrain == 'G' || terrain == 'R' || terrain == 'S'; + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/YakadexEntryEntity.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/YakadexEntryEntity.java new file mode 100644 index 0000000..30a0873 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/YakadexEntryEntity.java @@ -0,0 +1,19 @@ +package fr.epita.assistants.yakamon.domain.entity; + +import fr.epita.assistants.yakamon.utils.ElementType; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +public class YakadexEntryEntity { + + public Integer id; + public String name; + public ElementType firstType; + public ElementType secondType; + public Integer evolveThreshold; + public Integer evolutionId; + public Boolean caught; + public String description; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/YakamonEntity.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/YakamonEntity.java new file mode 100644 index 0000000..d7627a5 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/entity/YakamonEntity.java @@ -0,0 +1,15 @@ +package fr.epita.assistants.yakamon.domain.entity; + +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +public class YakamonEntity { + + public UUID uuid; + public String nickname; + public Integer yakadexId; + public Integer energyPoints; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/GameService.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/GameService.java new file mode 100644 index 0000000..29abf6e --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/GameService.java @@ -0,0 +1,153 @@ +package fr.epita.assistants.yakamon.domain.service; + +import fr.epita.assistants.yakamon.converter.EntityToModelConverter; +import fr.epita.assistants.yakamon.data.model.GameModel; +import fr.epita.assistants.yakamon.data.model.ItemModel; +import fr.epita.assistants.yakamon.data.model.PlayerModel; +import fr.epita.assistants.yakamon.data.repository.GameRepository; +import fr.epita.assistants.yakamon.data.repository.ItemRepository; +import fr.epita.assistants.yakamon.data.repository.PlayerRepository; +import fr.epita.assistants.yakamon.domain.entity.GameEntity; +import fr.epita.assistants.yakamon.domain.entity.TileEntity; +import fr.epita.assistants.yakamon.utils.ErrorCode; +import fr.epita.assistants.yakamon.utils.tile.ItemType; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@ApplicationScoped +public class GameService { + + @Inject + GameRepository gameRepository; + + @Inject + PlayerRepository playerRepository; + + @Inject + ItemRepository itemRepository; + + public static GameEntity game; + + public GameEntity startGame(String mapPath, String playerName) { + if (mapPath == null) { + ErrorCode.INVALID_MAP.throwException("Map is null"); + } + + // Initialize Game with mapPath + game = new GameEntity(mapPath); + GameModel gameModel = EntityToModelConverter.toGameModel(game); + + // Parse map and store in GameEntity + TileEntity[][] parsedMap = parseMapFile(mapPath); + game.map = parsedMap; + + // Initialize player + LocalDateTime now = LocalDateTime.now(); + PlayerModel player = new PlayerModel(); + player.name = playerName; + player.posX = 0; + player.posY = 0; + player.lastCatch = now; + player.lastCollect = now; + player.lastFeed = now; + player.lastMove = now; + + // Initialize player inventory + ItemModel yakaballs = new ItemModel(); + yakaballs.setType(ItemType.YAKABALL); + yakaballs.setQuantity(5); + + // Store + playerRepository.persist(player); + itemRepository.persist(yakaballs); + gameRepository.persist(gameModel); + + return game; + } + + public boolean isStarted() { + return game != null && game.started; + } + + public void stopGame() { + if (game == null) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + game.stop(); + gameRepository.deleteAll(); + playerRepository.deleteAll(); + } + + private TileEntity[][] parseMapFile(String mapPath) { + try { + String resourcePath = "maps/" + mapPath + ".epimap"; + BufferedReader reader = new BufferedReader( + new InputStreamReader( + GameService.class.getClassLoader().getResourceAsStream( + resourcePath + ) + ) + ); + + List> mapList = new ArrayList<>(); + String line; + + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (!line.isEmpty()) { + List row = parseLine(line); + mapList.add(row); + } + } + reader.close(); + + if (mapList.isEmpty()) { + ErrorCode.INVALID_MAP.throwException("Map file is empty"); + } + + TileEntity[][] map = new TileEntity[mapList.size()][]; + for (int i = 0; i < mapList.size(); i++) { + List row = mapList.get(i); + map[i] = row.toArray(new TileEntity[0]); + } + + return map; + } catch (Exception e) { + ErrorCode.INVALID_MAP.throwException("Failed to load map"); + } + return null; + } + + private List parseLine(String line) { + List row = new ArrayList<>(); + int i = 0; + + while (i < line.length()) { + // Parse count + int count = Character.getNumericValue(line.charAt(i)); + i++; + + // Parse terrain character + if (i >= line.length()) break; + char terrain = line.charAt(i); + i++; + + // Parse collectible character + if (i >= line.length()) break; + char collectible = line.charAt(i); + i++; + + // Add tiles + for (int j = 0; j < count; j++) { + row.add(new TileEntity(terrain, collectible)); + } + } + + return row; + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/InventoryService.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/InventoryService.java new file mode 100644 index 0000000..f9a4993 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/InventoryService.java @@ -0,0 +1,24 @@ +package fr.epita.assistants.yakamon.domain.service; + +import fr.epita.assistants.yakamon.data.model.ItemModel; +import fr.epita.assistants.yakamon.data.repository.ItemRepository; +import fr.epita.assistants.yakamon.domain.entity.GameEntity; +import fr.epita.assistants.yakamon.utils.ErrorCode; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.util.List; + +@ApplicationScoped +public class InventoryService { + + @Inject + ItemRepository itemRepository; + + public List getInventory() { + if (GameEntity.INSTANCE == null || !GameEntity.INSTANCE.started) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + + return itemRepository.listAll(); + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/PlayerService.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/PlayerService.java new file mode 100644 index 0000000..954eea4 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/PlayerService.java @@ -0,0 +1,365 @@ +package fr.epita.assistants.yakamon.domain.service; + +import fr.epita.assistants.yakamon.data.model.ItemModel; +import fr.epita.assistants.yakamon.data.model.PlayerModel; +import fr.epita.assistants.yakamon.data.model.YakadexEntryModel; +import fr.epita.assistants.yakamon.data.model.YakamonModel; +import fr.epita.assistants.yakamon.data.repository.ItemRepository; +import fr.epita.assistants.yakamon.data.repository.PlayerRepository; +import fr.epita.assistants.yakamon.data.repository.YakadexRepository; +import fr.epita.assistants.yakamon.data.repository.YakamonRepository; +import fr.epita.assistants.yakamon.domain.entity.GameEntity; +import fr.epita.assistants.yakamon.domain.entity.PlayerEntity; +import fr.epita.assistants.yakamon.domain.entity.TileEntity; +import fr.epita.assistants.yakamon.domain.entity.YakamonEntity; +import fr.epita.assistants.yakamon.utils.Direction; +import fr.epita.assistants.yakamon.utils.ErrorCode; +import fr.epita.assistants.yakamon.utils.Point; +import fr.epita.assistants.yakamon.utils.tile.Collectible; +import fr.epita.assistants.yakamon.utils.tile.CollectibleUtils; +import fr.epita.assistants.yakamon.utils.tile.ItemType; +import fr.epita.assistants.yakamon.utils.tile.TerrainType; +import fr.epita.assistants.yakamon.utils.tile.YakamonInfo; +import fr.epita.assistants.yakamon.utils.tile.YakamonType; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + +@ApplicationScoped +public class PlayerService { + + @Inject + PlayerRepository playerRepository; + + @Inject + YakamonRepository yakamonRepository; + + @Inject + YakadexRepository yakadexRepository; + + @Inject + ItemRepository itemRepository; + + @Transactional + public Point move(Direction direction) { + if (GameEntity.INSTANCE == null || !GameEntity.INSTANCE.started) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + + PlayerModel player = playerRepository.findAll().firstResult(); + if (player == null) { + ErrorCode.BAD_REQUEST.throwException("Player not found"); + } + + checkMovementDelay(player); + + if (direction == null) { + ErrorCode.BAD_REQUEST.throwException("Invalid direction"); + } + + TileEntity[][] map = GameEntity.INSTANCE.map; + if (map == null) { + ErrorCode.INVALID_MAP.throwException("Map not loaded"); + } + + Point directionOffset = direction.getPoint(); + int newX = player.posX + directionOffset.getPosX(); + int newY = player.posY + directionOffset.getPosY(); + + if ( + newY < 0 || + newY >= map.length || + newX < 0 || + newX >= map[newY].length + ) { + ErrorCode.BAD_REQUEST.throwException( + "Cannot move outside map boundaries" + ); + } + + TileEntity tile = map[newY][newX]; + if (tile == null || !tile.isWalkable()) { + ErrorCode.BAD_REQUEST.throwException("Tile is not walkable"); + } + + player.posX = newX; + player.posY = newY; + player.lastMove = LocalDateTime.now(); + + return new Point(newX, newY); + } + + @Transactional + public YakamonEntity catchYakamon() { + if (GameEntity.INSTANCE == null || !GameEntity.INSTANCE.started) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + + PlayerModel player = playerRepository.findAll().firstResult(); + if (player == null) { + ErrorCode.BAD_REQUEST.throwException("Player not found"); + } + + checkCatchDelay(player); + + TileEntity[][] map = GameEntity.INSTANCE.map; + if (map == null) { + ErrorCode.INVALID_MAP.throwException("Map not loaded"); + } + + int x = player.posX; + int y = player.posY; + + if (y < 0 || y >= map.length || x < 0 || x >= map[y].length) { + ErrorCode.BAD_REQUEST.throwException("Invalid position"); + } + + TileEntity tile = map[y][x]; + if ( + tile == null || tile.collectible == null || tile.collectible == 'N' + ) { + throw new WebApplicationException( + Response.status(400) + .entity("No yakamon at this position") + .build() + ); + } + + Collectible collectible = CollectibleUtils.getCollectible( + tile.collectible + ); + if (!(collectible instanceof YakamonType)) { + throw new WebApplicationException( + Response.status(400) + .entity("No yakamon at this position") + .build() + ); + } + + ItemModel yakaballItem = itemRepository + .find("type", ItemType.YAKABALL) + .firstResult(); + if (yakaballItem == null || yakaballItem.getQuantity() < 1) { + throw new WebApplicationException( + Response.status(400).entity("Not enough Yakaballs").build() + ); + } + + long teamSize = yakamonRepository.count(); + if (teamSize >= 3) { + throw new WebApplicationException( + Response.status(400).entity("Team is full").build() + ); + } + + YakamonType yakamonType = (YakamonType) collectible; + YakamonInfo yakamonInfo = + (YakamonInfo) yakamonType.getCollectibleInfo(); + Integer yakadexId = yakamonInfo.getYakadexId(); + + YakadexEntryModel yakadex = yakadexRepository + .find("id", yakadexId) + .firstResult(); + if (yakadex == null) { + ErrorCode.BAD_REQUEST.throwException("Yakamon species not found"); + } + + yakaballItem.setQuantity(yakaballItem.getQuantity() - 1); + itemRepository.persist(yakaballItem); + + YakamonModel newYakamon = new YakamonModel(); + newYakamon.nickname = yakamonType.name(); + newYakamon.energy_points = 0; + newYakamon.yakadex_entry_id = yakadex; + yakamonRepository.persist(newYakamon); + + yakadex.caught = true; + yakadexRepository.persist(yakadex); + + tile.collectible = 'N'; + + player.lastCatch = LocalDateTime.now(); + playerRepository.persist(player); + + return new YakamonEntity( + newYakamon.id, + newYakamon.nickname, + newYakamon.yakadex_entry_id.id, + newYakamon.energy_points + ); + } + + private void checkMovementDelay(PlayerModel player) { + String tickDurationStr = System.getenv("JWS_TICK_DURATION"); + String movementDelayStr = System.getenv("JWS_MOVEMENT_DELAY"); + + if (tickDurationStr == null || movementDelayStr == null) { + return; + } + + try { + long tickDuration = Long.parseLong(tickDurationStr); + long movementDelay = Long.parseLong(movementDelayStr); + long requiredDelay = tickDuration * movementDelay; + + LocalDateTime now = LocalDateTime.now(); + long elapsed = ChronoUnit.MILLIS.between(player.lastMove, now); + + if (elapsed < requiredDelay) { + throw new WebApplicationException(Response.status(429).build()); + } + } catch (NumberFormatException e) { + ErrorCode.BAD_REQUEST.throwException( + "Invalid environment variables" + ); + } + } + + private void checkCatchDelay(PlayerModel player) { + String tickDurationStr = System.getenv("JWS_TICK_DURATION"); + String catchDelayStr = System.getenv("JWS_CATCH_DELAY"); + + if (tickDurationStr == null || catchDelayStr == null) { + return; + } + + try { + long tickDuration = Long.parseLong(tickDurationStr); + long catchDelay = Long.parseLong(catchDelayStr); + long requiredDelay = tickDuration * catchDelay; + + LocalDateTime now = LocalDateTime.now(); + long elapsed = ChronoUnit.MILLIS.between(player.lastCatch, now); + + if (elapsed < requiredDelay) { + throw new WebApplicationException(Response.status(429).build()); + } + } catch (NumberFormatException e) { + ErrorCode.BAD_REQUEST.throwException( + "Invalid environment variables" + ); + } + } + + private void checkCollectDelay(PlayerModel player) { + String tickDurationStr = System.getenv("JWS_TICK_DURATION"); + String collectDelayStr = System.getenv("JWS_COLLECT_DELAY"); + + if (tickDurationStr == null || collectDelayStr == null) { + return; + } + + try { + long tickDuration = Long.parseLong(tickDurationStr); + long collectDelay = Long.parseLong(collectDelayStr); + long requiredDelay = tickDuration * collectDelay; + + LocalDateTime now = LocalDateTime.now(); + long elapsed = ChronoUnit.MILLIS.between(player.lastCollect, now); + + if (elapsed < requiredDelay) { + throw new WebApplicationException(Response.status(429).build()); + } + } catch (NumberFormatException e) { + ErrorCode.BAD_REQUEST.throwException( + "Invalid environment variables" + ); + } + } + + @Transactional + public PlayerEntity getPlayerInfo() { + if (GameEntity.INSTANCE == null || !GameEntity.INSTANCE.started) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + + PlayerModel player = playerRepository.findAll().firstResult(); + if (player == null) { + ErrorCode.BAD_REQUEST.throwException("Player not found"); + } + + return new PlayerEntity( + player.id, + player.name, + player.posX, + player.posY, + player.lastMove, + player.lastCatch, + player.lastCollect, + player.lastFeed + ); + } + + @Transactional + public Map collect() { + if (GameEntity.INSTANCE == null || !GameEntity.INSTANCE.started) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + + PlayerModel player = playerRepository.findAll().firstResult(); + if (player == null) { + ErrorCode.BAD_REQUEST.throwException("Player not found"); + } + + checkCollectDelay(player); + + TileEntity[][] map = GameEntity.INSTANCE.map; + if (map == null) { + ErrorCode.INVALID_MAP.throwException("Map not loaded"); + } + + int x = player.posX; + int y = player.posY; + + if (y < 0 || y >= map.length || x < 0 || x >= map[y].length) { + ErrorCode.BAD_REQUEST.throwException("Invalid position"); + } + + TileEntity tile = map[y][x]; + if ( + tile == null || tile.collectible == null || tile.collectible == 'N' + ) { + throw new WebApplicationException( + Response.status(400) + .entity("No collectible at this position") + .build() + ); + } + + Collectible collectible = CollectibleUtils.getCollectible( + tile.collectible + ); + if (collectible instanceof ItemType) { + ItemType itemType = (ItemType) collectible; + ItemModel item = itemRepository + .find("type", itemType) + .firstResult(); + if (item == null) { + item = new ItemModel(); + item.setType(itemType); + item.setQuantity(1); + itemRepository.persist(item); + } else { + item.setQuantity(item.getQuantity() + 1); + itemRepository.persist(item); + } + } + + tile.collectible = 'N'; + player.lastCollect = LocalDateTime.now(); + playerRepository.persist(player); + + Map response = new HashMap<>(); + Map tileData = new HashMap<>(); + tileData.put("terrainType", TerrainType.getTerrain(tile.terrain)); + tileData.put("collectible", collectible); + response.put("tileType", tileData); + return response; + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/TeamService.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/TeamService.java new file mode 100644 index 0000000..60ea6c7 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/TeamService.java @@ -0,0 +1,149 @@ +package fr.epita.assistants.yakamon.domain.service; + +import fr.epita.assistants.yakamon.data.model.YakadexEntryModel; +import fr.epita.assistants.yakamon.data.model.YakamonModel; +import fr.epita.assistants.yakamon.data.repository.YakadexRepository; +import fr.epita.assistants.yakamon.data.repository.YakamonRepository; +import fr.epita.assistants.yakamon.domain.entity.GameEntity; +import fr.epita.assistants.yakamon.domain.entity.YakamonEntity; +import fr.epita.assistants.yakamon.utils.ErrorCode; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@ApplicationScoped +public class TeamService { + + @Inject + YakamonRepository yakamonRepository; + + @Inject + YakadexRepository yakadexRepository; + + public List getTeam() { + if (GameEntity.INSTANCE == null || !GameEntity.INSTANCE.started) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + + List models = yakamonRepository.listAll(); + List entities = new ArrayList<>(); + + for (YakamonModel model : models) { + entities.add( + new YakamonEntity( + model.id, + model.nickname, + model.yakadex_entry_id.id, + model.energy_points + ) + ); + } + + return entities; + } + + @Transactional + public YakamonEntity evolve(UUID uuid) { + if (GameEntity.INSTANCE == null || !GameEntity.INSTANCE.started) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + + YakamonModel yakamon = yakamonRepository.find("id", uuid).firstResult(); + if (yakamon == null) { + ErrorCode.BAD_REQUEST.throwException("Yakamon not found"); + } + + YakadexEntryModel entry = yakamon.yakadex_entry_id; + if (entry.evolution_id == null) { + ErrorCode.BAD_REQUEST.throwException("This yakamon cannot evolve"); + } + + if (yakamon.energy_points < entry.evolve_threshold) { + ErrorCode.BAD_REQUEST.throwException( + "Not enough energy points to evolve" + ); + } + + YakadexEntryModel evolution = yakadexRepository + .find("id", entry.evolution_id.id) + .firstResult(); + yakamon.yakadex_entry_id = evolution; + yakamonRepository.persist(yakamon); + + return new YakamonEntity( + yakamon.id, + yakamon.nickname, + yakamon.yakadex_entry_id.id, + yakamon.energy_points + ); + } + + @Transactional + public YakamonEntity feed(UUID uuid, Integer quantity) { + if (GameEntity.INSTANCE == null || !GameEntity.INSTANCE.started) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + + if (quantity == null || quantity <= 0) { + ErrorCode.BAD_REQUEST.throwException("Invalid quantity"); + } + + YakamonModel yakamon = yakamonRepository.find("id", uuid).firstResult(); + if (yakamon == null) { + ErrorCode.BAD_REQUEST.throwException("Yakamon not found"); + } + + yakamon.energy_points += quantity; + yakamonRepository.persist(yakamon); + + return new YakamonEntity( + yakamon.id, + yakamon.nickname, + yakamon.yakadex_entry_id.id, + yakamon.energy_points + ); + } + + @Transactional + public void release(UUID uuid) { + if (GameEntity.INSTANCE == null || !GameEntity.INSTANCE.started) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + + YakamonModel yakamon = yakamonRepository.find("id", uuid).firstResult(); + if (yakamon == null) { + ErrorCode.BAD_REQUEST.throwException("Yakamon not found"); + } + + yakamonRepository.delete(yakamon); + } + + @Transactional + public YakamonEntity rename(UUID uuid, String newName) { + if (GameEntity.INSTANCE == null || !GameEntity.INSTANCE.started) { + ErrorCode.GAME_NOT_STARTED.throwException(); + } + + if (newName == null || newName.isBlank()) { + ErrorCode.BAD_REQUEST.throwException("Invalid name"); + } + + YakamonModel yakamon = yakamonRepository.find("id", uuid).firstResult(); + if (yakamon == null) { + ErrorCode.BAD_REQUEST.throwException("Yakamon not found"); + } + + yakamon.nickname = newName; + yakamonRepository.persist(yakamon); + + return new YakamonEntity( + yakamon.id, + yakamon.nickname, + yakamon.yakadex_entry_id.id, + yakamon.energy_points + ); + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/YakadexService.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/YakadexService.java new file mode 100644 index 0000000..2476b00 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/domain/service/YakadexService.java @@ -0,0 +1,73 @@ +package fr.epita.assistants.yakamon.domain.service; + +import fr.epita.assistants.yakamon.data.model.YakadexEntryModel; +import fr.epita.assistants.yakamon.data.repository.YakadexRepository; +import fr.epita.assistants.yakamon.domain.entity.YakadexEntryEntity; +import fr.epita.assistants.yakamon.utils.ElementType; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.util.ArrayList; +import java.util.List; + +@ApplicationScoped +public class YakadexService { + + @Inject + YakadexRepository yakadexRepository; + + public List getAllEntries(Boolean onlyMissing) { + List entries; + + if (onlyMissing != null && onlyMissing) { + entries = yakadexRepository.find("caught = false").list(); + } else { + entries = yakadexRepository.listAll(); + } + + List entities = new ArrayList<>(); + for (YakadexEntryModel entry : entries) { + YakadexEntryEntity entity = new YakadexEntryEntity( + entry.id, + entry.name, + entry.first_type != null + ? ElementType.valueOf(entry.first_type) + : null, + entry.second_type != null + ? ElementType.valueOf(entry.second_type) + : null, + entry.evolve_threshold, + null, + entry.caught, + entry.description + ); + entities.add(entity); + } + + return entities; + } + + public YakadexEntryEntity getEntryById(Integer id) { + YakadexEntryModel entry = yakadexRepository + .find("id = ?1", id) + .firstResult(); + + if (entry == null) { + return null; + } + + return new YakadexEntryEntity( + entry.id, + entry.name, + entry.first_type != null + ? ElementType.valueOf(entry.first_type) + : null, + entry.second_type != null + ? ElementType.valueOf(entry.second_type) + : null, + entry.evolve_threshold, + null, + entry.caught, + entry.description + ); + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/YakadexEntry.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/YakadexEntry.java new file mode 100644 index 0000000..0720df5 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/YakadexEntry.java @@ -0,0 +1,16 @@ +package fr.epita.assistants.yakamon.presentation.api; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class YakadexEntry { + + public Integer id; + public String name; + public String firstType; + public String secondType; + public Integer evolveThreshold; + public Integer evolutionId; + public Boolean caught; + public String description; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/YakamonAction.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/YakamonAction.java new file mode 100644 index 0000000..6faef97 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/YakamonAction.java @@ -0,0 +1,14 @@ +package fr.epita.assistants.yakamon.presentation.api; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +public class YakamonAction { + + public String uuid; + public String nickname; + public Integer yakadexId; + public Integer energyPoints; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/PlayerMoveRequest.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/PlayerMoveRequest.java new file mode 100644 index 0000000..3e79362 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/PlayerMoveRequest.java @@ -0,0 +1,14 @@ +package fr.epita.assistants.yakamon.presentation.api.request; + +import fr.epita.assistants.yakamon.utils.Direction; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PlayerMoveRequest { + + public Direction direction; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/StartGameRequest.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/StartGameRequest.java new file mode 100644 index 0000000..885e164 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/StartGameRequest.java @@ -0,0 +1,12 @@ +package fr.epita.assistants.yakamon.presentation.api.request; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +public class StartGameRequest { + + public String mapPath; + public String playerName; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/TeamFeedRequest.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/TeamFeedRequest.java new file mode 100644 index 0000000..5c9fbfa --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/TeamFeedRequest.java @@ -0,0 +1,11 @@ +package fr.epita.assistants.yakamon.presentation.api.request; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +public class TeamFeedRequest { + + public Integer quantity; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/TeamRenameRequest.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/TeamRenameRequest.java new file mode 100644 index 0000000..db76322 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/request/TeamRenameRequest.java @@ -0,0 +1,11 @@ +package fr.epita.assistants.yakamon.presentation.api.request; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +public class TeamRenameRequest { + + public String newNickname; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/ErrorResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/ErrorResponse.java new file mode 100644 index 0000000..0abf4ae --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/ErrorResponse.java @@ -0,0 +1,9 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class ErrorResponse { + + public String message; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/GetInventoryResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/GetInventoryResponse.java new file mode 100644 index 0000000..3a1cf7e --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/GetInventoryResponse.java @@ -0,0 +1,38 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +public class GetInventoryResponse { + + public class ItemType { + + public String type; + public String value; + + public ItemType() {} + + public ItemType(String type, String value) { + this.type = type; + this.value = value; + } + } + + public class Items { + + public ItemType itemType; + public Integer quantity; + + public Items() {} + + public Items(ItemType itemType, Integer quantity) { + this.itemType = itemType; + this.quantity = quantity; + } + } + + public List items; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerCatchResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerCatchResponse.java new file mode 100644 index 0000000..9ca2b4d --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerCatchResponse.java @@ -0,0 +1,17 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import fr.epita.assistants.yakamon.presentation.api.YakamonAction; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class PlayerCatchResponse extends YakamonAction { + + public PlayerCatchResponse( + String uuid, + String nickname, + Integer yakadexId, + Integer energyPoints + ) { + super(uuid, nickname, yakadexId, energyPoints); + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerCollectResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerCollectResponse.java new file mode 100644 index 0000000..200336f --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerCollectResponse.java @@ -0,0 +1,39 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import fr.epita.assistants.yakamon.utils.tile.ItemType; +import fr.epita.assistants.yakamon.utils.tile.TerrainType; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +public class PlayerCollectResponse { + + public class TileType { + + public class Collectible { + + public ItemType type; + public String value; + + public Collectible() {} + + public Collectible(ItemType type, String value) { + this.type = type; + this.value = value; + } + } + + public TerrainType terrainType; + public Collectible collectible; + + public TileType() {} + + public TileType(TerrainType terrainType, Collectible collectible) { + this.terrainType = terrainType; + this.collectible = collectible; + } + } + + public TileType tileType; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerInfosResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerInfosResponse.java new file mode 100644 index 0000000..c4fa464 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerInfosResponse.java @@ -0,0 +1,18 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +public class PlayerInfosResponse { + + public String uuid; + public String name; + public Integer posX; + public Integer posY; + public String lastMove; + public String lastCollect; + public String lastCatch; + public String lastFeed; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerMoveResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerMoveResponse.java new file mode 100644 index 0000000..bbd29d4 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/PlayerMoveResponse.java @@ -0,0 +1,10 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class PlayerMoveResponse { + + public Integer posX; + public Integer posY; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/StartGameResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/StartGameResponse.java new file mode 100644 index 0000000..f4b1ba4 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/StartGameResponse.java @@ -0,0 +1,21 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class StartGameResponse { + + public class Collectible { + + public String item; + public String value; + } + + public class Tile { + + String terrainType; + Collectible collectible; + } + + Tile[][] tiles; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamEvolveResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamEvolveResponse.java new file mode 100644 index 0000000..7b97656 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamEvolveResponse.java @@ -0,0 +1,17 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import fr.epita.assistants.yakamon.presentation.api.YakamonAction; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class TeamEvolveResponse extends YakamonAction { + + public TeamEvolveResponse( + String uuid, + String nickname, + Integer yakadexId, + Integer energyPoints + ) { + super(uuid, nickname, yakadexId, energyPoints); + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamFeedResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamFeedResponse.java new file mode 100644 index 0000000..d8b024b --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamFeedResponse.java @@ -0,0 +1,17 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import fr.epita.assistants.yakamon.presentation.api.YakamonAction; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class TeamFeedResponse extends YakamonAction { + + public TeamFeedResponse( + String uuid, + String nickname, + Integer yakadexId, + Integer energyPoints + ) { + super(uuid, nickname, yakadexId, energyPoints); + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamInfosResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamInfosResponse.java new file mode 100644 index 0000000..cc56a45 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamInfosResponse.java @@ -0,0 +1,12 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import fr.epita.assistants.yakamon.presentation.api.YakamonAction; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +public class TeamInfosResponse { + + public YakamonAction[] yakamons; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamRenameResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamRenameResponse.java new file mode 100644 index 0000000..2c46caf --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/TeamRenameResponse.java @@ -0,0 +1,17 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import fr.epita.assistants.yakamon.presentation.api.YakamonAction; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class TeamRenameResponse extends YakamonAction { + + public TeamRenameResponse( + String uuid, + String nickname, + Integer yakadexId, + Integer energyPoints + ) { + super(uuid, nickname, yakadexId, energyPoints); + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/YakadexAllInfosResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/YakadexAllInfosResponse.java new file mode 100644 index 0000000..5409a5e --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/YakadexAllInfosResponse.java @@ -0,0 +1,12 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import fr.epita.assistants.yakamon.presentation.api.YakadexEntry; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +public class YakadexAllInfosResponse { + + public YakadexEntry[] entries; +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/YakadexInfosResponse.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/YakadexInfosResponse.java new file mode 100644 index 0000000..cfe73f3 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/api/response/YakadexInfosResponse.java @@ -0,0 +1,28 @@ +package fr.epita.assistants.yakamon.presentation.api.response; + +import fr.epita.assistants.yakamon.presentation.api.YakadexEntry; + +public class YakadexInfosResponse extends YakadexEntry { + + public YakadexInfosResponse( + Integer id, + String name, + String firstType, + String secondType, + Integer evolveThreshold, + Integer evolutionId, + Boolean caught, + String description + ) { + super( + id, + name, + firstType, + secondType, + evolveThreshold, + evolutionId, + caught, + description + ); + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/GameResource.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/GameResource.java new file mode 100644 index 0000000..e6d167e --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/GameResource.java @@ -0,0 +1,144 @@ +package fr.epita.assistants.yakamon.presentation.rest; + +import fr.epita.assistants.yakamon.domain.entity.GameEntity; +import fr.epita.assistants.yakamon.domain.entity.TileEntity; +import fr.epita.assistants.yakamon.domain.service.GameService; +import fr.epita.assistants.yakamon.presentation.api.request.StartGameRequest; +import fr.epita.assistants.yakamon.utils.tile.Collectible; +import fr.epita.assistants.yakamon.utils.tile.CollectibleUtils; +import fr.epita.assistants.yakamon.utils.tile.ItemType; +import fr.epita.assistants.yakamon.utils.tile.YakamonType; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +@Path("/start") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class GameResource { + + private static final Logger LOGGER = Logger.getLogger( + GameResource.class.getName() + ); + + @Inject + private GameService gameService; + + @POST + @Path("/") + public Response start(StartGameRequest request) { + if ( + request == null || + request.mapPath == null || + request.mapPath.isBlank() || + request.playerName == null || + request.playerName.isBlank() + ) { + LOGGER.warning( + "Invalid start request: missing mapPath or playerName" + ); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid mapPath or playerName") + .build(); + } + + LOGGER.info( + "Starting game for player '" + + request.playerName + + "' with map '" + + request.mapPath + + "'" + ); + + try { + gameService.startGame(request.mapPath, request.playerName); + LOGGER.info("Game started successfully"); + + TileEntity[][] map = GameEntity.INSTANCE.map; + Map[][] tiles = new Map[map.length][]; + + for (int y = 0; y < map.length; y++) { + tiles[y] = new Map[map[y].length]; + for (int x = 0; x < map[y].length; x++) { + TileEntity tile = map[y][x]; + Map tileData = new HashMap<>(); + tileData.put("terrainType", getTerrainType(tile.terrain)); + + Map collectibleData = new HashMap<>(); + String collectibleType = getCollectibleType( + tile.collectible + ); + String collectibleValue = getCollectibleValue( + tile.collectible + ); + + if (collectibleType != null) { + collectibleData.put("type", collectibleType); + collectibleData.put("value", collectibleValue); + } + + tileData.put("collectible", collectibleData); + tiles[y][x] = tileData; + } + } + + Map response = new HashMap<>(); + response.put("tiles", tiles); + return Response.ok(response).build(); + } catch (Exception e) { + LOGGER.severe("Failed to start game: " + e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid path or invalid name provided.") + .build(); + } + } + + private String getTerrainType(Character terrain) { + return switch (terrain) { + case 'G' -> "GRASS"; + case 'M' -> "MOUNTAIN"; + case 'R' -> "ROCK"; + case 'S' -> "SAND"; + case 'W' -> "WATER"; + case 'L' -> "LAVA"; + default -> "GRASS"; + }; + } + + private String getCollectibleType(Character collectible) { + if (collectible == 'N') { + return null; + } + + Collectible c = CollectibleUtils.getCollectible(collectible); + if (c instanceof ItemType) { + return "ITEM"; + } else if (c instanceof YakamonType) { + return "YAKAMON"; + } + + return null; + } + + private String getCollectibleValue(Character collectible) { + if (collectible == 'N') { + return null; + } + + Collectible c = CollectibleUtils.getCollectible(collectible); + if (c instanceof ItemType) { + return ((ItemType) c).name(); + } else if (c instanceof YakamonType) { + return ((YakamonType) c).name(); + } + + return null; + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/HelloWorldResource.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/HelloWorldResource.java deleted file mode 100644 index fb62d4b..0000000 --- a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/HelloWorldResource.java +++ /dev/null @@ -1,22 +0,0 @@ -package fr.epita.assistants.yakamon.presentation.rest; - -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.MediaType; - -@Path("/hello") -@Produces(MediaType.TEXT_PLAIN) -@Consumes(MediaType.TEXT_PLAIN) -public class HelloWorldResource { - - @GET - @Path("/") - public String helloWorld() { - return "Hello, world!"; - } - - @GET - @Path("/{name}") - public String helloWorld(@PathParam("name") String name) { - return "Hello " + name + "!"; - } -} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/InventoryResource.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/InventoryResource.java new file mode 100644 index 0000000..c8af0e9 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/InventoryResource.java @@ -0,0 +1,61 @@ +package fr.epita.assistants.yakamon.presentation.rest; + +import fr.epita.assistants.yakamon.data.model.ItemModel; +import fr.epita.assistants.yakamon.domain.service.GameService; +import fr.epita.assistants.yakamon.domain.service.InventoryService; +import fr.epita.assistants.yakamon.presentation.api.response.GetInventoryResponse; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +@Path("/inventory") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class InventoryResource { + + @Inject + private GameService gameService; + + @Inject + private InventoryService inventoryService; + + @GET + public Response getInventory() { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game not started") + .build(); + } + + try { + List items = inventoryService.getInventory(); + List responseItems = new ArrayList<>(); + GetInventoryResponse temp = new GetInventoryResponse(responseItems); + + for (ItemModel item : items) { + GetInventoryResponse.Items responseItem = temp.new Items(); + GetInventoryResponse.ItemType itemType = temp.new ItemType(); + itemType.type = item.getType().name(); + itemType.value = item.getType().name(); + responseItem.itemType = itemType; + responseItem.quantity = item.getQuantity(); + responseItems.add(responseItem); + } + + GetInventoryResponse response = new GetInventoryResponse( + responseItems + ); + return Response.ok(response).build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/PlayerResource.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/PlayerResource.java new file mode 100644 index 0000000..174f90e --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/PlayerResource.java @@ -0,0 +1,145 @@ +package fr.epita.assistants.yakamon.presentation.rest; + +import fr.epita.assistants.yakamon.domain.entity.YakamonEntity; +import fr.epita.assistants.yakamon.domain.service.GameService; +import fr.epita.assistants.yakamon.domain.service.PlayerService; +import fr.epita.assistants.yakamon.presentation.api.request.PlayerMoveRequest; +import fr.epita.assistants.yakamon.presentation.api.response.PlayerCatchResponse; +import fr.epita.assistants.yakamon.presentation.api.response.PlayerInfosResponse; +import fr.epita.assistants.yakamon.presentation.api.response.PlayerMoveResponse; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.time.format.DateTimeFormatter; + +@Path("/") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class PlayerResource { + + @Inject + private GameService gameService; + + @Inject + private PlayerService playerService; + + @POST + @Path("/catch") + public Response catchYakamon() { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game not started") + .build(); + } + + try { + YakamonEntity yakamon = playerService.catchYakamon(); + var response = new PlayerCatchResponse( + yakamon.uuid.toString(), + yakamon.nickname, + yakamon.yakadexId, + yakamon.energyPoints + ); + return Response.ok(response).build(); + } catch (jakarta.ws.rs.WebApplicationException e) { + return e.getResponse(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } + + @POST + @Path("/collect") + public Response collect() { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game not started") + .build(); + } + + try { + var tileData = playerService.collect(); + return Response.ok(tileData).build(); + } catch (jakarta.ws.rs.WebApplicationException e) { + return e.getResponse(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } + + @POST + @Path("/move") + public Response move(PlayerMoveRequest request) { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game not started") + .build(); + } + + if (request == null || request.direction == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid direction") + .build(); + } + + try { + fr.epita.assistants.yakamon.utils.Point newPos = playerService.move( + request.direction + ); + return Response.ok( + new PlayerMoveResponse(newPos.getPosX(), newPos.getPosY()) + ).build(); + } catch (jakarta.ws.rs.WebApplicationException e) { + return e.getResponse(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } + + @GET + @Path("/player") + public Response getPlayerInfos() { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game not started") + .build(); + } + + try { + var player = playerService.getPlayerInfo(); + DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + var response = new PlayerInfosResponse( + player.uuid.toString(), + player.name, + player.posX, + player.posY, + player.lastMove != null + ? player.lastMove.format(formatter) + : null, + player.lastCollect != null + ? player.lastCollect.format(formatter) + : null, + player.lastCatch != null + ? player.lastCatch.format(formatter) + : null, + player.lastFeed != null + ? player.lastFeed.format(formatter) + : null + ); + return Response.ok(response).build(); + } catch (jakarta.ws.rs.WebApplicationException e) { + return e.getResponse(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/TeamResource.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/TeamResource.java new file mode 100644 index 0000000..c52b5f9 --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/TeamResource.java @@ -0,0 +1,216 @@ +package fr.epita.assistants.yakamon.presentation.rest; + +import fr.epita.assistants.yakamon.converter.EntityToDtoConverter; +import fr.epita.assistants.yakamon.domain.entity.YakamonEntity; +import fr.epita.assistants.yakamon.domain.service.GameService; +import fr.epita.assistants.yakamon.domain.service.TeamService; +import fr.epita.assistants.yakamon.presentation.api.request.TeamFeedRequest; +import fr.epita.assistants.yakamon.presentation.api.request.TeamRenameRequest; +import fr.epita.assistants.yakamon.presentation.api.response.TeamEvolveResponse; +import fr.epita.assistants.yakamon.presentation.api.response.TeamFeedResponse; +import fr.epita.assistants.yakamon.presentation.api.response.TeamInfosResponse; +import fr.epita.assistants.yakamon.presentation.api.response.TeamRenameResponse; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.UUID; + +@Path("/team") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class TeamResource { + + @Inject + private GameService gameService; + + @Inject + private TeamService teamService; + + @GET + public Response getTeam() { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game not started") + .build(); + } + + try { + List team = teamService.getTeam(); + var actions = EntityToDtoConverter.toYakamonActions(team); + TeamInfosResponse response = new TeamInfosResponse( + actions.toArray( + new fr.epita.assistants.yakamon.presentation.api.YakamonAction[0] + ) + ); + return Response.ok(response).build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } + + @POST + @Path("/{uuid}/evolve") + public Response evolve(@PathParam("uuid") String uuid) { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game not started") + .build(); + } + if (uuid == null || uuid.isBlank()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Missing or invalid uuid") + .build(); + } + + try { + UUID yakemonId = UUID.fromString(uuid); + YakamonEntity evolved = teamService.evolve(yakemonId); + var action = EntityToDtoConverter.toYakamonAction(evolved); + TeamEvolveResponse response = new TeamEvolveResponse( + action.uuid, + action.nickname, + action.yakadexId, + action.energyPoints + ); + return Response.ok(response).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid uuid format") + .build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } + + @POST + @Path("/{uuid}/feed") + public Response feed( + @PathParam("uuid") String uuid, + TeamFeedRequest request + ) { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game not started") + .build(); + } + if (uuid == null || uuid.isBlank()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Missing or invalid uuid") + .build(); + } + if ( + request == null || request.quantity == null || request.quantity <= 0 + ) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid quantity") + .build(); + } + + try { + UUID yakemonId = UUID.fromString(uuid); + YakamonEntity fed = teamService.feed(yakemonId, request.quantity); + var action = EntityToDtoConverter.toYakamonAction(fed); + TeamFeedResponse response = new TeamFeedResponse( + action.uuid, + action.nickname, + action.yakadexId, + action.energyPoints + ); + return Response.ok(response).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid uuid format") + .build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } + + @DELETE + @Path("/{uuid}/release") + public Response release(@PathParam("uuid") String uuid) { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game not started") + .build(); + } + if (uuid == null || uuid.isBlank()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Missing or invalid uuid") + .build(); + } + + try { + UUID yakemonId = UUID.fromString(uuid); + teamService.release(yakemonId); + return Response.noContent().build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid uuid format") + .build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } + + @PATCH + @Path("/{uuid}/rename") + public Response rename( + @PathParam("uuid") String uuid, + TeamRenameRequest request + ) { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game not started") + .build(); + } + if (uuid == null || uuid.isBlank()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Missing or invalid uuid") + .build(); + } + if ( + request == null || + request.newNickname == null || + request.newNickname.isBlank() + ) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Missing or invalid name") + .build(); + } + + try { + UUID yakemonId = UUID.fromString(uuid); + YakamonEntity renamed = teamService.rename( + yakemonId, + request.newNickname + ); + var action = EntityToDtoConverter.toYakamonAction(renamed); + TeamRenameResponse response = new TeamRenameResponse( + action.uuid, + action.nickname, + action.yakadexId, + action.energyPoints + ); + return Response.ok(response).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid uuid format") + .build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/YakadexResource.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/YakadexResource.java new file mode 100644 index 0000000..dd3f0bc --- /dev/null +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/presentation/rest/YakadexResource.java @@ -0,0 +1,66 @@ +package fr.epita.assistants.yakamon.presentation.rest; + +import fr.epita.assistants.yakamon.converter.EntityToDtoConverter; +import fr.epita.assistants.yakamon.domain.entity.YakadexEntryEntity; +import fr.epita.assistants.yakamon.domain.service.GameService; +import fr.epita.assistants.yakamon.domain.service.YakadexService; +import fr.epita.assistants.yakamon.presentation.api.YakadexEntry; +import fr.epita.assistants.yakamon.presentation.api.response.YakadexAllInfosResponse; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; + +@Path("/yakadex") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class YakadexResource { + + @Inject + private GameService gameService; + + @Inject + private YakadexService yakadexService; + + @GET + public Response getYakadex( + @QueryParam("only_missing") Boolean onlyMissing + ) { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game is not running") + .build(); + } + + List entries = yakadexService.getAllEntries( + onlyMissing + ); + List dtos = EntityToDtoConverter.toYakadexEntries( + entries + ); + YakadexAllInfosResponse response = new YakadexAllInfosResponse( + dtos.toArray(new YakadexEntry[0]) + ); + return Response.ok(response).build(); + } + + @GET + @Path("/{id}") + public Response getYakadexById(@PathParam("id") Integer id) { + if (!gameService.isStarted()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Game is not running") + .build(); + } + + YakadexEntryEntity entry = yakadexService.getEntryById(id); + + if (entry == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + YakadexEntry dto = EntityToDtoConverter.toYakadexEntry(entry); + return Response.ok(dto).build(); + } +} diff --git a/yakamon/src/main/java/fr/epita/assistants/yakamon/utils/ErrorCode.java b/yakamon/src/main/java/fr/epita/assistants/yakamon/utils/ErrorCode.java index 5d89ce8..db0f9bf 100644 --- a/yakamon/src/main/java/fr/epita/assistants/yakamon/utils/ErrorCode.java +++ b/yakamon/src/main/java/fr/epita/assistants/yakamon/utils/ErrorCode.java @@ -1,23 +1,31 @@ package fr.epita.assistants.yakamon.utils; +import static jakarta.ws.rs.core.Response.Status; + import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Response; import lombok.Getter; import lombok.RequiredArgsConstructor; -import static jakarta.ws.rs.core.Response.Status; - @Getter @RequiredArgsConstructor public enum ErrorCode { - EXAMPLE_ERROR(Status.INTERNAL_SERVER_ERROR, "This is an error example"); + EXAMPLE_ERROR(Status.INTERNAL_SERVER_ERROR, "This is an error example"), + BAD_REQUEST(Status.BAD_REQUEST, "Bad request"), + INVALID_MAP(Status.BAD_REQUEST, "Given map is invalid"), + GAME_NOT_STARTED(Status.CONFLICT, "Game has not been started"), + GAME_ALREADY_STARTED(Status.FORBIDDEN, "Game is already started"); private final Response.Status errorCode; private final String errorMessage; public WebApplicationException getException() { - return new WebApplicationException(Response.status(errorCode).entity(new ErrorInfo(errorMessage)).build()); + return new WebApplicationException( + Response.status(errorCode) + .entity(new ErrorInfo(errorMessage)) + .build() + ); } public void throwException() { @@ -25,6 +33,10 @@ public enum ErrorCode { } public void throwException(String prefix) { - throw new WebApplicationException(Response.status(errorCode).entity(new ErrorInfo(prefix + ": " + errorMessage)).build()); + throw new WebApplicationException( + Response.status(errorCode) + .entity(new ErrorInfo(prefix + ": " + errorMessage)) + .build() + ); } } diff --git a/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/ConverterTests.java b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/ConverterTests.java new file mode 100644 index 0000000..c2d80d2 --- /dev/null +++ b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/ConverterTests.java @@ -0,0 +1,239 @@ +package fr.epita.assistants.yakamon_testsuite; + +import static org.junit.jupiter.api.Assertions.*; + +import fr.epita.assistants.yakamon.converter.DtoToEntityConverter; +import fr.epita.assistants.yakamon.converter.EntityToDtoConverter; +import fr.epita.assistants.yakamon.converter.EntityToModelConverter; +import fr.epita.assistants.yakamon.domain.entity.GameEntity; +import fr.epita.assistants.yakamon.domain.entity.YakadexEntryEntity; +import fr.epita.assistants.yakamon.domain.entity.YakamonEntity; +import fr.epita.assistants.yakamon.presentation.api.YakadexEntry; +import fr.epita.assistants.yakamon.presentation.api.YakamonAction; +import fr.epita.assistants.yakamon.presentation.api.request.StartGameRequest; +import fr.epita.assistants.yakamon.utils.ElementType; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("ConverterTests") +public class ConverterTests { + + @Test + @DisplayName("ModelToYakamon: null returns null") + public void testNullYakamonToModel() { + assertNull(EntityToModelConverter.toYakamonModel(null)); + } + + @Test + @DisplayName("ModelToYakamon: valid conversion") + public void testYakamonToModel() { + YakamonEntity yakamon = new YakamonEntity(); + yakamon.uuid = UUID.randomUUID(); + yakamon.nickname = "Bastiedon"; + yakamon.yakadexId = 6; + yakamon.energyPoints = 50; + + var model = EntityToModelConverter.toYakamonModel(yakamon); + + assertNotNull(model); + assertEquals("Bastiedon", model.nickname); + assertEquals(50, model.energy_points); + } + + @Test + @DisplayName("ModelToYakadex: null returns null") + public void testNullYakadexToModel() { + assertNull(EntityToModelConverter.toYakadexEntryModel(null)); + } + + @Test + @DisplayName("ModelToYakadex: single type") + public void testYakadexSingleType() { + YakadexEntryEntity yakadex = new YakadexEntryEntity(); + yakadex.id = 1; + yakadex.name = "Yakimon"; + yakadex.firstType = ElementType.NORMAL; + yakadex.secondType = null; + yakadex.caught = true; + + var model = EntityToModelConverter.toYakadexEntryModel(yakadex); + + assertNotNull(model); + assertEquals("NORMAL", model.first_type); + assertNull(model.second_type); + } + + @Test + @DisplayName("ModelToYakadex: dual types") + public void testYakadexDualTypes() { + YakadexEntryEntity yakadex = new YakadexEntryEntity(); + yakadex.id = 25; + yakadex.name = "Pikachu"; + yakadex.firstType = ElementType.ELECTRIC; + yakadex.secondType = ElementType.FLYING; + yakadex.caught = false; + + var model = EntityToModelConverter.toYakadexEntryModel(yakadex); + + assertNotNull(model); + assertEquals("ELECTRIC", model.first_type); + assertEquals("FLYING", model.second_type); + } + + @Test + @DisplayName("DtoToYakamon: null returns null") + public void testNullYakamonAction() { + assertNull(DtoToEntityConverter.toYakamonEntity(null)); + } + + @Test + @DisplayName("DtoToYakamon: valid conversion") + public void testYakamonActionValid() { + UUID uuid = UUID.randomUUID(); + YakamonAction action = new YakamonAction( + uuid.toString(), + "Bastiedon", + 6, + 100 + ); + + YakamonEntity entity = DtoToEntityConverter.toYakamonEntity(action); + + assertNotNull(entity); + assertEquals(uuid, entity.uuid); + assertEquals("Bastiedon", entity.nickname); + } + + @Test + @DisplayName("DtoToYakamon: invalid uuid handled") + public void testYakamonInvalidUuid() { + YakamonAction action = new YakamonAction("not-a-uuid", "Test", 1, 0); + YakamonEntity entity = DtoToEntityConverter.toYakamonEntity(action); + + assertNotNull(entity); + assertNull(entity.uuid); + } + + @Test + @DisplayName("DtoToYakadex: null returns null") + public void testNullYakadexEntry() { + assertNull(DtoToEntityConverter.toYakadexEntryEntity(null)); + } + + @Test + @DisplayName("DtoToYakadex: valid types") + public void testYakadexTypesConvert() { + YakadexEntry dto = new YakadexEntry( + 1, + "Yakimon", + "NORMAL", + "FLYING", + 10, + 2, + true, + "A creature" + ); + YakadexEntryEntity entity = DtoToEntityConverter.toYakadexEntryEntity( + dto + ); + + assertNotNull(entity); + assertEquals(ElementType.NORMAL, entity.firstType); + assertEquals(ElementType.FLYING, entity.secondType); + } + + @Test + @DisplayName("DtoToYakadex: invalid enum handled") + public void testYakadexInvalidEnum() { + YakadexEntry dto = new YakadexEntry( + 1, + "Test", + "INVALID", + null, + 10, + null, + false, + "Test" + ); + YakadexEntryEntity entity = DtoToEntityConverter.toYakadexEntryEntity( + dto + ); + + assertNotNull(entity); + assertNull(entity.firstType); + } + + @Test + @DisplayName("DtoToGame: converts correctly") + public void testGameDtoConvert() { + StartGameRequest request = new StartGameRequest("test_map", "Player"); + GameEntity entity = DtoToEntityConverter.toGameEntity(request); + + assertNotNull(entity); + assertEquals("test_map", entity.mapPath); + assertTrue(entity.started); + } + + @Test + @DisplayName("EntityToYakamon: null returns null") + public void testNullYakamonDto() { + assertNull(EntityToDtoConverter.toYakamonAction(null)); + } + + @Test + @DisplayName("EntityToYakamon: valid conversion") + public void testYakamonDto() { + YakamonEntity yakamon = new YakamonEntity(); + yakamon.uuid = UUID.randomUUID(); + yakamon.nickname = "Bastiedon"; + yakamon.yakadexId = 6; + yakamon.energyPoints = 75; + + YakamonAction action = EntityToDtoConverter.toYakamonAction(yakamon); + + assertNotNull(action); + assertEquals("Bastiedon", action.nickname); + assertEquals(6, action.yakadexId); + } + + @Test + @DisplayName("EntityToYakadex: null returns null") + public void testNullYakadexDto() { + assertNull(EntityToDtoConverter.toYakadexEntry(null)); + } + + @Test + @DisplayName("EntityToYakadex: conversion") + public void testYakadexDto() { + YakadexEntryEntity yakadex = new YakadexEntryEntity(); + yakadex.id = 1; + yakadex.name = "Yakimon"; + yakadex.firstType = ElementType.NORMAL; + yakadex.secondType = null; + yakadex.caught = true; + + YakadexEntry entry = EntityToDtoConverter.toYakadexEntry(yakadex); + + assertNotNull(entry); + assertEquals("NORMAL", entry.firstType); + assertNull(entry.secondType); + } + + @Test + @DisplayName("EntityToYakamon: list conversion") + public void testYakamonListDto() { + YakamonEntity y1 = new YakamonEntity(); + y1.uuid = UUID.randomUUID(); + y1.nickname = "Yak1"; + y1.yakadexId = 1; + y1.energyPoints = 0; + + var result = EntityToDtoConverter.toYakamonActions( + java.util.List.of(y1) + ); + + assertEquals(1, result.size()); + assertEquals("Yak1", result.get(0).nickname); + } +} diff --git a/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/GameResourceTest.java b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/GameResourceTest.java new file mode 100644 index 0000000..148f4a6 --- /dev/null +++ b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/GameResourceTest.java @@ -0,0 +1,189 @@ +package fr.epita.assistants.yakamon_testsuite; + +import static io.restassured.RestAssured.given; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@DisplayName("GameResourceTest") +public class GameResourceTest { + + @Test + @DisplayName("POST /start/ with valid request") + public void testStartGameWithValidRequest() { + String requestBody = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"Alice\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .statusCode(200); + } + + @Test + @DisplayName("POST /start without trailing slash") + public void testStartGameWithoutTrailingSlash() { + String requestBody = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"Alice\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start") + .then() + .statusCode(200); + } + + @Test + @DisplayName("POST /start/ missing mapPath returns 400") + public void testStartGameWithoutMapPath() { + String requestBody = "{\"playerName\": \"Alice\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /start/ missing playerName returns 400") + public void testStartGameWithoutPlayerName() { + String requestBody = "{\"mapPath\": \"map1.txt\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /start/ blank mapPath returns 400") + public void testStartGameWithBlankMapPath() { + String requestBody = "{\"mapPath\": \"\", \"playerName\": \"Alice\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /start/ blank playerName returns 400") + public void testStartGameWithBlankPlayerName() { + String requestBody = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /start/ both fields blank returns 400") + public void testStartGameWithBothBlank() { + String requestBody = "{\"mapPath\": \"\", \"playerName\": \"\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /start/ whitespace mapPath returns 400") + public void testStartGameWithWhitespaceMapPath() { + String requestBody = + "{\"mapPath\": \" \", \"playerName\": \"Alice\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /start/ whitespace playerName returns 400") + public void testStartGameWithWhitespacePlayerName() { + String requestBody = + "{\"mapPath\": \"map1.txt\", \"playerName\": \" \"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /start/ with special characters in name") + public void testStartGameWithSpecialCharacterName() { + String requestBody = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"Alice-Smith_123\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .statusCode(200); + } + + @Test + @DisplayName("POST /start/ with complex map path") + public void testStartGameWithComplexMapPath() { + String requestBody = + "{\"mapPath\": \"maps/world/level1/forest.txt\", \"playerName\": \"TestPlayer\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .statusCode(200); + } + + @Test + @DisplayName("POST /start/ returns JSON") + public void testStartGameContentType() { + String requestBody = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"Alice\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/start/") + .then() + .contentType(ContentType.JSON); + } +} diff --git a/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/HelloWorldResourceTest.java b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/HelloWorldResourceTest.java new file mode 100644 index 0000000..ff72850 --- /dev/null +++ b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/HelloWorldResourceTest.java @@ -0,0 +1,79 @@ +package fr.epita.assistants.yakamon_testsuite; + +import static io.restassured.RestAssured.given; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@DisplayName("HelloWorldResourceTest") +public class HelloWorldResourceTest { + + @Test + @DisplayName("GET /hello/") + public void testHelloWorldWithoutName() { + given() + .when() + .get("/hello/") + .then() + .statusCode(200) + .contentType(ContentType.TEXT); + } + + @Test + @DisplayName("GET /hello without trailing slash") + public void testHelloWorldWithoutTrailingSlash() { + given() + .when() + .get("/hello") + .then() + .statusCode(200) + .contentType(ContentType.TEXT); + } + + @Test + @DisplayName("GET /hello/{name}") + public void testHelloWorldWithName() { + given() + .when() + .get("/hello/Alice") + .then() + .statusCode(200) + .contentType(ContentType.TEXT); + } + + @Test + @DisplayName("GET /hello/{name} with different name") + public void testHelloWorldWithDifferentName() { + given() + .when() + .get("/hello/Bob") + .then() + .statusCode(200) + .contentType(ContentType.TEXT); + } + + @Test + @DisplayName("GET /hello/{name} with special characters") + public void testHelloWorldWithSpecialCharacters() { + given() + .when() + .get("/hello/John-Doe") + .then() + .statusCode(200) + .contentType(ContentType.TEXT); + } + + @Test + @DisplayName("GET /hello/{name} with numeric name") + public void testHelloWorldWithNumericName() { + given() + .when() + .get("/hello/123") + .then() + .statusCode(200) + .contentType(ContentType.TEXT); + } +} diff --git a/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/InventoryResourceTest.java b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/InventoryResourceTest.java new file mode 100644 index 0000000..4eac668 --- /dev/null +++ b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/InventoryResourceTest.java @@ -0,0 +1,116 @@ +package fr.epita.assistants.yakamon_testsuite; + +import static io.restassured.RestAssured.given; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@DisplayName("InventoryResourceTest") +public class InventoryResourceTest { + + @Test + @DisplayName("GET /inventory returns 200") + public void testGetInventorySuccess() { + given() + .contentType(ContentType.JSON) + .when() + .get("/inventory") + .then() + .statusCode(200); + } + + @Test + @DisplayName("GET /inventory returns JSON") + public void testGetInventoryContentType() { + given() + .contentType(ContentType.JSON) + .when() + .get("/inventory") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("GET /inventory without trailing slash") + public void testGetInventoryWithoutTrailingSlash() { + given() + .contentType(ContentType.JSON) + .when() + .get("/inventory") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("GET /inventory multiple requests") + public void testGetInventoryMultipleCalls() { + for (int i = 0; i < 5; i++) { + given() + .contentType(ContentType.JSON) + .when() + .get("/inventory") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } + } + + @Test + @DisplayName("GET /inventory with accept header") + public void testGetInventoryWithAcceptHeader() { + given() + .accept(ContentType.JSON) + .when() + .get("/inventory") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("GET /inventory without body") + public void testGetInventoryNoBody() { + given().when().get("/inventory").then().statusCode(200); + } + + @Test + @DisplayName("GET /inventory with query params") + public void testGetInventoryWithQueryParams() { + given() + .contentType(ContentType.JSON) + .queryParam("filter", "all") + .when() + .get("/inventory") + .then() + .statusCode(200); + } + + @Test + @DisplayName("GET /inventory rapid consecutive requests") + public void testGetInventoryRapidRequests() { + given() + .contentType(ContentType.JSON) + .when() + .get("/inventory") + .then() + .statusCode(200); + + given() + .contentType(ContentType.JSON) + .when() + .get("/inventory") + .then() + .statusCode(200); + + given() + .contentType(ContentType.JSON) + .when() + .get("/inventory") + .then() + .statusCode(200); + } +} diff --git a/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/MainTests.java b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/MainTests.java new file mode 100644 index 0000000..64f5f98 --- /dev/null +++ b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/MainTests.java @@ -0,0 +1,7 @@ +package fr.epita.assistants.yakamon_testsuite; + +// import static org.junit.jupiter.api.Assertions.*; + +// import org.junit.jupiter.api.Test; + +public class MainTests {} diff --git a/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/PlayerResourceTest.java b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/PlayerResourceTest.java new file mode 100644 index 0000000..1e82eb0 --- /dev/null +++ b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/PlayerResourceTest.java @@ -0,0 +1,210 @@ +package fr.epita.assistants.yakamon_testsuite; + +import static io.restassured.RestAssured.given; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@DisplayName("PlayerResourceTest") +public class PlayerResourceTest { + + @Test + @DisplayName("POST /catch without game returns 400") + public void testCatchYakamonWithoutGameStarted() { + given() + .contentType(ContentType.JSON) + .when() + .post("/catch") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /collect without game returns 400") + public void testCollectWithoutGameStarted() { + given() + .contentType(ContentType.JSON) + .when() + .post("/collect") + .then() + .statusCode(400); + } + + @Test + @DisplayName("GET /inventory without game returns 400") + public void testGetInventoryWithoutGameStarted() { + given() + .contentType(ContentType.JSON) + .when() + .get("/inventory") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /move without game returns 400") + public void testMoveWithoutGameStarted() { + String requestBody = "{\"direction\": \"UP\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/move") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /move without body returns 400") + public void testMoveWithoutRequestBody() { + given() + .contentType(ContentType.JSON) + .when() + .post("/move") + .then() + .statusCode(400); + } + + @Test + @DisplayName("GET /player without game returns 400") + public void testGetPlayerInfosWithoutGameStarted() { + given() + .contentType(ContentType.JSON) + .when() + .get("/player") + .then() + .statusCode(400); + } + + @Test + @DisplayName("GET /inventory returns JSON") + public void testGetInventoryContentType() { + given() + .contentType(ContentType.JSON) + .when() + .get("/inventory") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("POST /move returns JSON") + public void testMoveContentType() { + String requestBody = "{\"direction\": \"UP\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/move") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("GET /player returns JSON") + public void testGetPlayerContentType() { + given() + .contentType(ContentType.JSON) + .when() + .get("/player") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("POST /catch returns JSON") + public void testCatchContentType() { + given() + .contentType(ContentType.JSON) + .when() + .post("/catch") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("POST /collect returns JSON") + public void testCollectContentType() { + given() + .contentType(ContentType.JSON) + .when() + .post("/collect") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("POST /move with UP direction") + public void testMoveWithUpDirection() { + String requestBody = "{\"direction\": \"UP\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/move") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /move with DOWN direction") + public void testMoveWithDownDirection() { + String requestBody = "{\"direction\": \"DOWN\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/move") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /move with LEFT direction") + public void testMoveWithLeftDirection() { + String requestBody = "{\"direction\": \"LEFT\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/move") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /move with RIGHT direction") + public void testMoveWithRightDirection() { + String requestBody = "{\"direction\": \"RIGHT\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/move") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /move with invalid direction") + public void testMoveWithInvalidDirection() { + String requestBody = "{\"direction\": \"INVALID\"}"; + + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/move") + .then() + .statusCode(400); + } +} diff --git a/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/ServiceLayerTests.java b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/ServiceLayerTests.java new file mode 100644 index 0000000..be22cf8 --- /dev/null +++ b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/ServiceLayerTests.java @@ -0,0 +1,173 @@ +package fr.epita.assistants.yakamon_testsuite; + +import static org.junit.jupiter.api.Assertions.*; + +import fr.epita.assistants.yakamon.domain.entity.GameEntity; +import fr.epita.assistants.yakamon.domain.entity.TileEntity; +import fr.epita.assistants.yakamon.utils.Direction; +import fr.epita.assistants.yakamon.utils.Point; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("ServiceLayerTests") +public class ServiceLayerTests { + + @BeforeEach + public void setup() { + GameEntity.INSTANCE = null; + } + + @Test + @DisplayName("GameStart") + public void testGameStart() { + GameEntity game = new GameEntity("test_map"); + assertTrue(game.started); + assertEquals("test_map", game.mapPath); + } + + @Test + @DisplayName("GameStop") + public void testGameStop() { + GameEntity game = new GameEntity("map"); + game.stop(); + assertFalse(game.started); + assertNull(GameEntity.INSTANCE); + } + + @Test + @DisplayName("GameSetMap") + public void testGameSetMap() { + GameEntity game = new GameEntity("map"); + TileEntity[][] map = new TileEntity[2][2]; + game.map = map; + assertNotNull(game.map); + assertEquals(2, game.map.length); + } + + @Test + @DisplayName("TileGrassWalkable") + public void testGrassWalkable() { + TileEntity tile = new TileEntity('G', 'N'); + assertTrue(tile.isWalkable()); + } + + @Test + @DisplayName("TileSandWalkable") + public void testSandWalkable() { + TileEntity tile = new TileEntity('S', 'N'); + assertTrue(tile.isWalkable()); + } + + @Test + @DisplayName("TileRockWalkable") + public void testRockWalkable() { + TileEntity tile = new TileEntity('R', 'N'); + assertTrue(tile.isWalkable()); + } + + @Test + @DisplayName("TileWaterNotWalkable") + public void testWaterNotWalkable() { + TileEntity tile = new TileEntity('W', 'N'); + assertFalse(tile.isWalkable()); + } + + @Test + @DisplayName("TileMountainNotWalkable") + public void testMountainNotWalkable() { + TileEntity tile = new TileEntity('M', 'N'); + assertFalse(tile.isWalkable()); + } + + @Test + @DisplayName("TileLavaNotWalkable") + public void testLavaNotWalkable() { + TileEntity tile = new TileEntity('L', 'N'); + assertFalse(tile.isWalkable()); + } + + @Test + @DisplayName("TileCollectible") + public void testTileCollectible() { + TileEntity tile = new TileEntity('G', 'Y'); + assertEquals('Y', tile.collectible); + assertEquals('G', tile.terrain); + } + + @Test + @DisplayName("DirectionUp") + public void testDirectionUp() { + Point p = Direction.UP.getPoint(); + assertEquals(0, p.getPosX()); + assertEquals(-1, p.getPosY()); + } + + @Test + @DisplayName("DirectionDown") + public void testDirectionDown() { + Point p = Direction.DOWN.getPoint(); + assertEquals(0, p.getPosX()); + assertEquals(1, p.getPosY()); + } + + @Test + @DisplayName("DirectionLeft") + public void testDirectionLeft() { + Point p = Direction.LEFT.getPoint(); + assertEquals(-1, p.getPosX()); + assertEquals(0, p.getPosY()); + } + + @Test + @DisplayName("DirectionRight") + public void testDirectionRight() { + Point p = Direction.RIGHT.getPoint(); + assertEquals(1, p.getPosX()); + assertEquals(0, p.getPosY()); + } + + @Test + @DisplayName("PointCreation") + public void testPointCreation() { + Point p = new Point(5, 10); + assertEquals(5, p.getPosX()); + assertEquals(10, p.getPosY()); + } + + @Test + @DisplayName("GameInstance") + public void testGameInstance() { + GameEntity g1 = new GameEntity("map1"); + assertSame(GameEntity.INSTANCE, g1); + } + + @Test + @DisplayName("MapGrid") + public void testMapGrid() { + TileEntity[][] map = new TileEntity[3][3]; + map[0][0] = new TileEntity('G', 'N'); + map[1][1] = new TileEntity('W', 'N'); + map[2][2] = new TileEntity('R', 'Y'); + + assertTrue(map[0][0].isWalkable()); + assertFalse(map[1][1].isWalkable()); + assertTrue(map[2][2].isWalkable()); + } + + @Test + @DisplayName("YakamonTile") + public void testYakamonTile() { + TileEntity tile = new TileEntity('G', 'b'); + assertEquals('b', tile.collectible); + assertTrue(tile.isWalkable()); + } + + @Test + @DisplayName("ItemTile") + public void testItemTile() { + TileEntity tile = new TileEntity('S', 'S'); + assertEquals('S', tile.collectible); + assertTrue(tile.isWalkable()); + } +} diff --git a/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/TeamResourceTest.java b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/TeamResourceTest.java new file mode 100644 index 0000000..ba60b94 --- /dev/null +++ b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/TeamResourceTest.java @@ -0,0 +1,457 @@ +package fr.epita.assistants.yakamon_testsuite; + +import static io.restassured.RestAssured.given; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@DisplayName("TeamResourceTest") +public class TeamResourceTest { + + private static final String VALID_UUID = + "550e8400-e29b-41d4-a716-446655440000"; + private static final String ANOTHER_UUID = + "6ba7b810-9dad-11d1-80b4-00c04fd430c8"; + + @Test + @DisplayName("GET /team without game returns 400") + public void testGetTeamWithoutGameStarted() { + given() + .contentType(ContentType.JSON) + .when() + .get("/team") + .then() + .statusCode(400); + } + + @Test + @DisplayName("GET /team returns JSON") + public void testGetTeamContentType() { + given() + .contentType(ContentType.JSON) + .when() + .get("/team") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("POST /team/{uuid}/evolve without game returns 400") + public void testEvolveWithoutGameStarted() { + given() + .contentType(ContentType.JSON) + .when() + .post("/team/" + VALID_UUID + "/evolve") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /team/{uuid}/evolve with blank uuid returns 400") + public void testEvolveWithBlankUuid() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .post("/team//evolve") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /team/{uuid}/evolve with valid uuid returns 200") + public void testEvolveWithValidUuid() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .post("/team/" + VALID_UUID + "/evolve") + .then() + .statusCode(200); + } + + @Test + @DisplayName("POST /team/{uuid}/evolve returns JSON") + public void testEvolveContentType() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .post("/team/" + VALID_UUID + "/evolve") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("POST /team/{uuid}/feed without game returns 400") + public void testFeedWithoutGameStarted() { + String requestBody = "{\"quantity\": 10}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/team/" + VALID_UUID + "/feed") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /team/{uuid}/feed with blank uuid returns 400") + public void testFeedWithBlankUuid() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + String requestBody = "{\"quantity\": 10}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/team//feed") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /team/{uuid}/feed with null quantity returns 400") + public void testFeedWithNullQuantity() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + String requestBody = "{\"quantity\": null}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/team/" + VALID_UUID + "/feed") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /team/{uuid}/feed with zero quantity returns 400") + public void testFeedWithZeroQuantity() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + String requestBody = "{\"quantity\": 0}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/team/" + VALID_UUID + "/feed") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /team/{uuid}/feed with negative quantity returns 400") + public void testFeedWithNegativeQuantity() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + String requestBody = "{\"quantity\": -5}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/team/" + VALID_UUID + "/feed") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /team/{uuid}/feed with valid quantity returns 200") + public void testFeedWithValidQuantity() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + String requestBody = "{\"quantity\": 10}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/team/" + VALID_UUID + "/feed") + .then() + .statusCode(200); + } + + @Test + @DisplayName("POST /team/{uuid}/feed without body returns 400") + public void testFeedWithMissingRequestBody() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .post("/team/" + VALID_UUID + "/feed") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /team/{uuid}/feed returns JSON") + public void testFeedContentType() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + String requestBody = "{\"quantity\": 10}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/team/" + VALID_UUID + "/feed") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("DELETE /team/{uuid}/release without game returns 400") + public void testReleaseWithoutGameStarted() { + given() + .contentType(ContentType.JSON) + .when() + .delete("/team/" + VALID_UUID + "/release") + .then() + .statusCode(400); + } + + @Test + @DisplayName("DELETE /team/{uuid}/release with blank uuid returns 400") + public void testReleaseWithBlankUuid() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .delete("/team//release") + .then() + .statusCode(400); + } + + @Test + @DisplayName("DELETE /team/{uuid}/release with valid uuid returns 204") + public void testReleaseWithValidUuid() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .delete("/team/" + VALID_UUID + "/release") + .then() + .statusCode(204); + } + + @Test + @DisplayName("DELETE /team/{uuid}/release multiple times") + public void testReleaseWithMultipleUuids() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .delete("/team/" + VALID_UUID + "/release") + .then() + .statusCode(204); + + given() + .contentType(ContentType.JSON) + .when() + .delete("/team/" + ANOTHER_UUID + "/release") + .then() + .statusCode(204); + } + + @Test + @DisplayName("PATCH /team/{uuid}/rename without game returns 400") + public void testRenameWithoutGameStarted() { + String requestBody = "{\"newNickname\": \"NewName\"}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .patch("/team/" + VALID_UUID + "/rename") + .then() + .statusCode(400); + } + + @Test + @DisplayName("PATCH /team/{uuid}/rename with blank uuid returns 400") + public void testRenameWithBlankUuid() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + String requestBody = "{\"newNickname\": \"NewName\"}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .patch("/team//rename") + .then() + .statusCode(400); + } + + @Test + @DisplayName("PATCH /team/{uuid}/rename without body returns 400") + public void testRenameWithMissingRequestBody() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .patch("/team/" + VALID_UUID + "/rename") + .then() + .statusCode(400); + } + + @Test + @DisplayName("PATCH /team/{uuid}/rename with valid request returns 200") + public void testRenameWithValidRequest() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + String requestBody = "{\"newNickname\": \"NewName\"}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .patch("/team/" + VALID_UUID + "/rename") + .then() + .statusCode(200); + } + + @Test + @DisplayName("PATCH /team/{uuid}/rename returns JSON") + public void testRenameContentType() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + String requestBody = "{\"newNickname\": \"NewName\"}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .patch("/team/" + VALID_UUID + "/rename") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("PATCH /team/{uuid}/rename with special characters") + public void testRenameWithSpecialCharacters() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + String requestBody = "{\"newNickname\": \"New-Name_123\"}"; + given() + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .patch("/team/" + VALID_UUID + "/rename") + .then() + .statusCode(200); + } +} diff --git a/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/YakadexResourceTest.java b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/YakadexResourceTest.java new file mode 100644 index 0000000..ebcadd5 --- /dev/null +++ b/yakamon/src/test/java/fr/epita/assistants/yakamon_testsuite/YakadexResourceTest.java @@ -0,0 +1,328 @@ +package fr.epita.assistants.yakamon_testsuite; + +import static io.restassured.RestAssured.given; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@DisplayName("YakadexResourceTest") +public class YakadexResourceTest { + + @Test + @DisplayName("GET /yakadex without game returns 400") + public void testGetYakadexWithoutGameStarted() { + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex") + .then() + .statusCode(400); + } + + @Test + @DisplayName("GET /yakadex returns 200 when game started") + public void testGetYakadexWithGameStarted() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex") + .then() + .statusCode(200); + } + + @Test + @DisplayName("GET /yakadex with only_missing=true") + public void testGetYakadexWithOnlyMissingTrue() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .queryParam("only_missing", true) + .when() + .get("/yakadex") + .then() + .statusCode(200); + } + + @Test + @DisplayName("GET /yakadex with only_missing=false") + public void testGetYakadexWithOnlyMissingFalse() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .queryParam("only_missing", false) + .when() + .get("/yakadex") + .then() + .statusCode(200); + } + + @Test + @DisplayName("GET /yakadex with only_missing=TRUE uppercase") + public void testGetYakadexWithOnlyMissingUppercase() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .queryParam("only_missing", "TRUE") + .when() + .get("/yakadex") + .then() + .statusCode(200); + } + + @Test + @DisplayName("GET /yakadex returns JSON") + public void testGetYakadexContentType() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("GET /yakadex/{id} without game returns 400") + public void testGetYakadexByIdWithoutGameStarted() { + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex/1") + .then() + .statusCode(400); + } + + @Test + @DisplayName("GET /yakadex/{id} with valid id") + public void testGetYakadexByIdWithGameStarted() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex/1") + .then() + .statusCode(200); + } + + @Test + @DisplayName("GET /yakadex/{id} with id=0") + public void testGetYakadexByIdWithZero() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex/0") + .then() + .statusCode(200); + } + + @Test + @DisplayName("GET /yakadex/{id} with negative id returns 404") + public void testGetYakadexByIdWithNegativeId() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex/-1") + .then() + .statusCode(404); + } + + @Test + @DisplayName("GET /yakadex/{id} with large id") + public void testGetYakadexByIdWithLargeId() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex/999999") + .then() + .statusCode(200); + } + + @Test + @DisplayName("GET /yakadex/{id} returns JSON") + public void testGetYakadexByIdContentType() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex/1") + .then() + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("GET /yakadex/{id} multiple different ids") + public void testGetYakadexByIdMultipleIds() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + int[] ids = { 1, 2, 5, 10, 42, 100 }; + for (int id : ids) { + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex/" + id) + .then() + .statusCode(200); + } + } + + @Test + @DisplayName("GET /yakadex/{id} with id=-2 returns 404") + public void testGetYakadexByIdWithAnotherNegativeId() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex/-2") + .then() + .statusCode(404); + } + + @Test + @DisplayName("GET /yakadex/{id} with id=-100 returns 404") + public void testGetYakadexByIdWithLargeNegativeId() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex/-100") + .then() + .statusCode(404); + } + + @Test + @DisplayName("GET /yakadex without query params") + public void testGetYakadexWithoutQueryParams() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex") + .then() + .statusCode(200); + } + + @Test + @DisplayName("GET /yakadex multiple calls") + public void testGetYakadexMultipleCalls() { + String startRequest = + "{\"mapPath\": \"map1.txt\", \"playerName\": \"TestPlayer\"}"; + given() + .contentType(ContentType.JSON) + .body(startRequest) + .when() + .post("/start/"); + + for (int i = 0; i < 3; i++) { + given() + .contentType(ContentType.JSON) + .when() + .get("/yakadex") + .then() + .statusCode(200); + } + } +}