Initial commit

This commit is contained in:
2024-12-16 08:41:04 +01:00
commit 7fac24d073
31 changed files with 1166 additions and 0 deletions

View File

@ -0,0 +1,13 @@
package cz.jzitnik.chronos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ChronosApplication {
public static void main(String[] args) {
SpringApplication.run(ChronosApplication.class, args);
}
}

View File

@ -0,0 +1,27 @@
package cz.jzitnik.chronos.controllers;
import cz.jzitnik.chronos.entities.Game;
import cz.jzitnik.chronos.payload.responses.UnifiedResponse;
import cz.jzitnik.chronos.repository.GameRepository;
import cz.jzitnik.chronos.services.GameService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/game")
public class GameController {
@Autowired
private GameService gameService;
@Autowired
private GameRepository gameRepository;
@PostMapping("/new")
public UnifiedResponse<Game, Error> createGame() {
var game = gameService.createGame();
gameRepository.save(game);
return UnifiedResponse.success(game);
}
}

View File

@ -0,0 +1,47 @@
package cz.jzitnik.chronos.controllers;
import cz.jzitnik.chronos.payload.errors.NotFoundError;
import cz.jzitnik.chronos.payload.requests.PlayerNameRequest;
import cz.jzitnik.chronos.payload.responses.UnifiedResponse;
import cz.jzitnik.chronos.repository.GameRepository;
import cz.jzitnik.chronos.repository.PlayerRepository;
import cz.jzitnik.chronos.services.GameService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/game/players")
public class PlayerController {
@Autowired
private GameRepository gameRepository;
@Autowired
private PlayerRepository playerRepository;
@Autowired
private GameService gameService;
@PostMapping("/new")
public ResponseEntity<UnifiedResponse<String, Error>> newPlayer(@RequestParam String gameKey, @RequestBody PlayerNameRequest playerNameRequest) {
var gameOptional = gameRepository.findByGameKey(gameKey);
if (gameOptional.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
UnifiedResponse.failure(new NotFoundError("Game with key " + gameKey + " was not found!"))
);
}
var game = gameOptional.get();
var player = gameService.addPlayer(game, playerNameRequest.getName());
gameRepository.save(game);
return ResponseEntity.ok(
UnifiedResponse.success(player.getPlayerKey())
);
}
}

View File

@ -0,0 +1,43 @@
package cz.jzitnik.chronos.entities;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@Entity
@NoArgsConstructor
public class Character {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "charowner", cascade = CascadeType.ALL)
@JsonManagedReference(value = "character-inventory")
private List<Item> inventory = new ArrayList<>();
@Enumerated(EnumType.STRING)
private Interaction interaction;
@ManyToOne
@JsonBackReference(value = "room-characters")
private Room room;
private String dialog;
public Character(String name, Room room, String dialog) {
this.name = name;
this.room = room;
this.dialog = dialog;
}
}

View File

@ -0,0 +1,35 @@
package cz.jzitnik.chronos.entities;
import jakarta.persistence.*;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@Entity
@NoArgsConstructor
public class Game {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String gameKey;
@OneToMany(mappedBy = "game", cascade = CascadeType.ALL)
@JsonManagedReference(value = "game-players")
private List<Player> players = new ArrayList<>();
@OneToMany(mappedBy = "game", cascade = CascadeType.ALL)
@JsonManagedReference(value = "game-rooms")
private List<Room> rooms = new ArrayList<>();
public void addPlayer(Player player) {
this.players.add(player);
}
}

View File

@ -0,0 +1,5 @@
package cz.jzitnik.chronos.entities;
public enum Interaction {
Farmer
}

View File

@ -0,0 +1,37 @@
package cz.jzitnik.chronos.entities;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Entity
@NoArgsConstructor
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
private ItemType itemType;
@ManyToOne
@JsonBackReference(value = "player-inventory")
private Player owner;
@ManyToOne
@JsonBackReference(value = "character-inventory")
private Character charowner;
@ManyToOne
@JsonBackReference(value = "room-items")
private Room room;
public Item(ItemType itemType, Character charowner) {
this.itemType = itemType;
this.charowner = charowner;
}
}

View File

@ -0,0 +1,6 @@
package cz.jzitnik.chronos.entities;
public enum ItemType {
KEY_FRAGMENT,
LUCK_POTION
}

View File

@ -0,0 +1,46 @@
package cz.jzitnik.chronos.entities;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@Entity
@NoArgsConstructor
public class Player {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
@JsonManagedReference(value = "player-inventory")
@Size(max = 3)
private List<Item> inventory = new ArrayList<>();
@ManyToOne
@JsonBackReference(value = "game-players")
private Game game;
@ManyToOne
@Column(name = "current_room")
private Room currentRoom;
@Column(unique = true, nullable = false)
@JsonIgnore
private String playerKey;
public Player(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,45 @@
package cz.jzitnik.chronos.entities;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Room {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "room", cascade = CascadeType.ALL)
@JsonManagedReference(value = "room-items")
private List<Item> items = new ArrayList<>();
@OneToMany(mappedBy = "room", cascade = CascadeType.ALL)
@JsonManagedReference(value = "room-characters")
private List<Character> characters = new ArrayList<>();
@ManyToOne
@JsonBackReference(value = "game-rooms")
private Game game;
@OneToMany(mappedBy = "current_room", cascade = CascadeType.ALL)
@JsonIgnore
private List<Player> players = new ArrayList<>();
public Room(String name, Game game) {
this.name = name;
this.game = game;
}
}

View File

@ -0,0 +1,12 @@
package cz.jzitnik.chronos.payload.errors;
public class NotFoundError extends Error {
public NotFoundError() {
super("Not Found Error");
}
public NotFoundError(String message) {
super(message);
}
}

View File

@ -0,0 +1,12 @@
package cz.jzitnik.chronos.payload.requests;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class PlayerNameRequest {
private String name;
}

View File

@ -0,0 +1,37 @@
package cz.jzitnik.chronos.payload.responses;
import lombok.Getter;
import lombok.Setter;
import java.util.Optional;
public class UnifiedResponse<T, U> {
@Getter
private final Optional<T> data;
@Getter
private final Optional<U> error;
@Getter
private final Boolean success;
private UnifiedResponse(Optional<T> data, Optional<U> error, Boolean success) {
this.data = data;
this.error = error;
this.success = success;
}
public static <T, U> UnifiedResponse<T, U> success(T data) {
return new UnifiedResponse<>(Optional.ofNullable(data), Optional.empty(), true);
}
// Static factory method for error response
public static <T, U> UnifiedResponse<T, U> failure(U error) {
return new UnifiedResponse<>(Optional.empty(), Optional.ofNullable(error), false);
}
// Static factory method for empty response
public static <T, U> UnifiedResponse<T, U> empty() {
return new UnifiedResponse<>(Optional.empty(), Optional.empty(), false);
}
}

View File

@ -0,0 +1,13 @@
package cz.jzitnik.chronos.repository;
import cz.jzitnik.chronos.entities.Game;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface GameRepository extends JpaRepository<Game, Long> {
boolean existsByGameKey(String gameKey);
Optional<Game> findByGameKey(String gameKey);
}

View File

@ -0,0 +1,10 @@
package cz.jzitnik.chronos.repository;
import cz.jzitnik.chronos.entities.Player;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PlayerRepository extends JpaRepository<Player, Long> {
boolean existsByPlayerKey(String playerKey);
}

View File

@ -0,0 +1,76 @@
package cz.jzitnik.chronos.services;
import cz.jzitnik.chronos.entities.Game;
import cz.jzitnik.chronos.entities.Player;
import cz.jzitnik.chronos.repository.GameRepository;
import cz.jzitnik.chronos.repository.PlayerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.security.SecureRandom;
@Service
public class GameService {
@Autowired
private GameRepository gameRepository;
@Autowired
private PlayerRepository playerRepository;
@Autowired
private InitGameService initGameService;
private static final int KEY_LENGTH = 10;
private static final String CHAR_POOL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
public String generateKey() {
SecureRandom random = new SecureRandom();
StringBuilder gameKey = new StringBuilder(KEY_LENGTH);
for (int i = 0; i < KEY_LENGTH; i++) {
int index = random.nextInt(CHAR_POOL.length());
gameKey.append(CHAR_POOL.charAt(index));
}
return gameKey.toString();
}
public String generateUniqueGameKey() {
var key = generateKey();
if (gameRepository.existsByGameKey(key)) {
return generateUniqueGameKey();
}
return key;
}
public String generateUniquePlayerKey() {
var key = generateKey();
if (playerRepository.existsByPlayerKey(key)) {
return generateUniquePlayerKey();
}
return key;
}
public Game createGame() {
Game newGame = new Game();
newGame.setGameKey(generateUniqueGameKey());
initGameService.initGame(newGame);
return gameRepository.save(newGame);
}
public Player addPlayer(Game game, String playerName) {
var player = new Player(playerName);
player.setPlayerKey(generateUniquePlayerKey());
player.setGame(game);
game.addPlayer(player);
return player;
}
}

View File

@ -0,0 +1,52 @@
package cz.jzitnik.chronos.services;
import cz.jzitnik.chronos.entities.*;
import cz.jzitnik.chronos.entities.Character;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Service
public class InitGameService {
public void initGame(Game game) {
var rooms = new ArrayList<Room>();
// Outside
var outside = new Room(
"Outside", // Name
game
);
var outside_characters = new ArrayList<Character>();
var elf_items = new ArrayList<Item>();
var elf = new Character(
"Skřítek",
outside,
"Ahoj já jsem skřítek... Můj dialog doplnit"
);
elf_items.add(new Item(ItemType.LUCK_POTION, elf));
elf.setInventory(elf_items);
outside_characters.add(elf);
outside.setCharacters(outside_characters);
// Stodola
var stodola = new Room(
"Stodola",
game
);
var stodola_characters = new ArrayList<Character>();
var farmar = new Character(
"Farmář",
stodola,
"Ahoj já jsem farmář a budeš se mnou hrát kámen nůžky papír."
);
farmar.setInteraction(Interaction.Farmer);
stodola_characters.add(farmar);
stodola.setCharacters(stodola_characters);
rooms.add(outside);
rooms.add(stodola);
game.setRooms(rooms);
}
}

View File

@ -0,0 +1,9 @@
spring.application.name=chronos
# DB
spring.datasource.url=${DATASOURCE_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.default_schema=app

View File

@ -0,0 +1,13 @@
package cz.jzitnik.chronos;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ChronosApplicationTests {
@Test
void contextLoads() {
}
}