feat: Wordle interaction
This commit is contained in:
parent
7167b4c34c
commit
171f760d4e
@ -22,4 +22,4 @@ Frontend je napsaný ve Vanilla Javě. Config soubor se automaticky ukládá v n
|
||||
|
||||
- Unix-like operační systémy: `~/.config/Chronos/config.json`
|
||||
- Windows: `C:\Users\<username>\AppData\Chronos\config.json`
|
||||
- MaxOS: `~/Library/Application Support/Chronos/config.json`
|
||||
- MacOS: `~/Library/Application Support/Chronos/config.json`
|
||||
|
@ -0,0 +1,13 @@
|
||||
package cz.jzitnik.chronos.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package cz.jzitnik.chronos.controllers;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import cz.jzitnik.chronos.entities.Item;
|
||||
import cz.jzitnik.chronos.entities.Message;
|
||||
import cz.jzitnik.chronos.entities.MessageType;
|
||||
|
@ -25,6 +25,7 @@ public class Interaction {
|
||||
@JsonIgnore
|
||||
private Character character;
|
||||
|
||||
@Lob
|
||||
@JsonIgnore
|
||||
private String memory;
|
||||
|
||||
|
@ -2,5 +2,6 @@ package cz.jzitnik.chronos.interactions;
|
||||
|
||||
public enum Interaction {
|
||||
Farmer,
|
||||
Cashier
|
||||
Cashier,
|
||||
Librarian
|
||||
}
|
@ -4,6 +4,7 @@ import cz.jzitnik.chronos.entities.Player;
|
||||
import cz.jzitnik.chronos.entities.Character;
|
||||
import cz.jzitnik.chronos.interactions.list.Hangman;
|
||||
import cz.jzitnik.chronos.interactions.list.RockPaperScissors;
|
||||
import cz.jzitnik.chronos.interactions.list.wordle.Wordle;
|
||||
import cz.jzitnik.chronos.payload.responses.InteractionResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -12,9 +13,10 @@ import org.springframework.stereotype.Service;
|
||||
public class InteractionService {
|
||||
@Autowired
|
||||
private RockPaperScissors rockPaperScissors;
|
||||
|
||||
@Autowired
|
||||
private Hangman hangman;
|
||||
@Autowired
|
||||
private Wordle wordle;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Function3<T, U, V, W> {
|
||||
@ -25,6 +27,7 @@ public class InteractionService {
|
||||
return switch (interaction) {
|
||||
case Farmer -> rockPaperScissors::play;
|
||||
case Cashier -> hangman::play;
|
||||
case Librarian -> wordle::play;
|
||||
};
|
||||
}
|
||||
}
|
@ -59,7 +59,6 @@ public class Hangman implements InteractionPlayer {
|
||||
try {
|
||||
return objectMapper.readValue(memory, HangmanMemory.class);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
throw new RuntimeException("Somebody just fucked up db."); // If this happens idk what is wrong with my life
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,234 @@
|
||||
package cz.jzitnik.chronos.interactions.list.wordle;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import cz.jzitnik.chronos.entities.Item;
|
||||
import cz.jzitnik.chronos.entities.ItemType;
|
||||
import cz.jzitnik.chronos.entities.Player;
|
||||
import cz.jzitnik.chronos.entities.Character;
|
||||
import cz.jzitnik.chronos.interactions.InteractionPlayer;
|
||||
import cz.jzitnik.chronos.payload.responses.InteractionResponse;
|
||||
import cz.jzitnik.chronos.repository.CharacterRepository;
|
||||
import cz.jzitnik.chronos.services.ItemService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class Wordle implements InteractionPlayer {
|
||||
@Autowired
|
||||
private CharacterRepository characterRepository;
|
||||
@Autowired
|
||||
private ItemService itemService;
|
||||
@Autowired
|
||||
private WordleService wordleService;
|
||||
|
||||
private static final int ATTEMPTS = 6; // Max attempts for Wordle
|
||||
|
||||
public enum FeedbackType {
|
||||
NOT_IN_WORD,
|
||||
WRONG_POSITION,
|
||||
CORRECT_POSITION
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
public static class CharacterFeedback {
|
||||
private char character;
|
||||
private FeedbackType feedbackType;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
public static class GuessFeedback {
|
||||
private String guess;
|
||||
private List<CharacterFeedback> feedback;
|
||||
}
|
||||
|
||||
// Memory
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
private static class WordleMemory {
|
||||
private String word;
|
||||
private List<GuessFeedback> feedbackHistory;
|
||||
private int attemptsRemaining;
|
||||
}
|
||||
|
||||
private static WordleMemory readMemory(String memory) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
try {
|
||||
return objectMapper.readValue(memory, WordleMemory.class);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
throw new RuntimeException("Error reading memory from DB.");
|
||||
}
|
||||
}
|
||||
|
||||
private static String writeMemory(WordleMemory memory) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
try {
|
||||
return objectMapper.writeValueAsString(memory);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Response
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
private static class WordleResponse {
|
||||
public enum Type {
|
||||
GAME_CREATED,
|
||||
GAME_WON,
|
||||
GAME_LOST,
|
||||
GAME_CONTINUE,
|
||||
}
|
||||
|
||||
private Type type;
|
||||
private List<GuessFeedback> feedbackHistory;
|
||||
private int attemptsRemaining;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResponse play(Player player, Character character, String data) {
|
||||
if (character.getInteractionData().getPlayer() == null) {
|
||||
// New game
|
||||
try {
|
||||
return initGame(player, character);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Game already exists
|
||||
if (!character.getInteractionData().getPlayer().getId().equals(player.getId())) {
|
||||
return new InteractionResponse(false, "already_playing", new ArrayList<>());
|
||||
}
|
||||
|
||||
try {
|
||||
return makeGuess(player, character, data);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private InteractionResponse initGame(Player player, Character character) throws JsonProcessingException {
|
||||
var interactionData = character.getInteractionData();
|
||||
interactionData.setPlayer(player);
|
||||
|
||||
interactionData.setMemory(writeMemory(new WordleMemory(wordleService.fetchWordleSolution(), new ArrayList<>(), ATTEMPTS)));
|
||||
|
||||
characterRepository.save(character);
|
||||
|
||||
var response = new WordleResponse(WordleResponse.Type.GAME_CREATED, new ArrayList<>(), ATTEMPTS);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
return new InteractionResponse(true, objectMapper.writeValueAsString(response), new ArrayList<>());
|
||||
}
|
||||
|
||||
private InteractionResponse makeGuess(Player player, Character character, String data) throws JsonProcessingException {
|
||||
var interactionMemory = readMemory(character.getInteractionData().getMemory());
|
||||
|
||||
if (data.equals("get_data")) {
|
||||
var response = new WordleResponse(WordleResponse.Type.GAME_CONTINUE, interactionMemory.getFeedbackHistory(), interactionMemory.getAttemptsRemaining());
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return new InteractionResponse(false, objectMapper.writeValueAsString(response), new ArrayList<>());
|
||||
}
|
||||
|
||||
String word = interactionMemory.getWord();
|
||||
List<GuessFeedback> feedbackHistory = interactionMemory.getFeedbackHistory();
|
||||
|
||||
if (data.length() != 5) {
|
||||
return new InteractionResponse(false, "invalid_input", new ArrayList<>());
|
||||
}
|
||||
|
||||
if (feedbackHistory.stream().anyMatch(f -> f.getGuess().equals(data))) {
|
||||
return new InteractionResponse(false, "already_guessed", new ArrayList<>());
|
||||
}
|
||||
|
||||
List<CharacterFeedback> feedback = generateFeedback(word, data);
|
||||
feedbackHistory.add(new GuessFeedback(data, feedback));
|
||||
interactionMemory.setFeedbackHistory(feedbackHistory);
|
||||
|
||||
interactionMemory.setAttemptsRemaining(interactionMemory.getAttemptsRemaining() - 1);
|
||||
|
||||
if (data.equals(word)) {
|
||||
var item = new Item(ItemType.KEY_FRAGMENT, player);
|
||||
itemService.addItem(player, item);
|
||||
|
||||
character.setInteractedWith(true);
|
||||
characterRepository.save(character);
|
||||
|
||||
var items = new ArrayList<Item>();
|
||||
items.add(item);
|
||||
|
||||
var response = new WordleResponse(WordleResponse.Type.GAME_WON, feedbackHistory, interactionMemory.getAttemptsRemaining());
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return new InteractionResponse(true, objectMapper.writeValueAsString(response), items);
|
||||
}
|
||||
|
||||
if (interactionMemory.getAttemptsRemaining() == 0) {
|
||||
character.setInteractedWith(true);
|
||||
characterRepository.save(character);
|
||||
|
||||
var response = new WordleResponse(WordleResponse.Type.GAME_LOST, feedbackHistory, interactionMemory.getAttemptsRemaining());
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return new InteractionResponse(false, objectMapper.writeValueAsString(response), new ArrayList<>());
|
||||
}
|
||||
|
||||
character.getInteractionData().setMemory(writeMemory(interactionMemory));
|
||||
characterRepository.save(character);
|
||||
|
||||
var response = new WordleResponse(WordleResponse.Type.GAME_CONTINUE, feedbackHistory, interactionMemory.getAttemptsRemaining());
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return new InteractionResponse(false, objectMapper.writeValueAsString(response), new ArrayList<>());
|
||||
}
|
||||
|
||||
private List<CharacterFeedback> generateFeedback(String word, String guess) {
|
||||
List<CharacterFeedback> feedback = new ArrayList<>();
|
||||
boolean[] matched = new boolean[word.length()];
|
||||
|
||||
// First pass: Check for correct positions
|
||||
for (int i = 0; i < word.length(); i++) {
|
||||
if (guess.charAt(i) == word.charAt(i)) {
|
||||
feedback.add(new CharacterFeedback(guess.charAt(i), FeedbackType.CORRECT_POSITION));
|
||||
matched[i] = true;
|
||||
} else {
|
||||
feedback.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: Check for incorrect positions
|
||||
for (int i = 0; i < word.length(); i++) {
|
||||
if (feedback.get(i) != null) continue;
|
||||
|
||||
char guessChar = guess.charAt(i);
|
||||
boolean found = false;
|
||||
for (int j = 0; j < word.length(); j++) {
|
||||
if (!matched[j] && word.charAt(j) == guessChar) {
|
||||
feedback.set(i, new CharacterFeedback(guessChar, FeedbackType.WRONG_POSITION));
|
||||
matched[j] = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
feedback.set(i, new CharacterFeedback(guessChar, FeedbackType.NOT_IN_WORD));
|
||||
}
|
||||
}
|
||||
|
||||
return feedback;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package cz.jzitnik.chronos.interactions.list.wordle;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class WordleResponse {
|
||||
private String solution;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cz.jzitnik.chronos.interactions.list.wordle;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Service
|
||||
public class WordleService {
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
public String fetchWordleSolution() {
|
||||
LocalDate today = LocalDate.now();
|
||||
String formattedDate = today.toString(); // Format: YYYY-MM-DD
|
||||
|
||||
String url = "https://www.nytimes.com/svc/wordle/v2/" + formattedDate + ".json";
|
||||
|
||||
WordleResponse response = restTemplate.getForObject(url, WordleResponse.class);
|
||||
|
||||
return response != null ? response.getSolution() : "ahead"; // Idk another word than ahead
|
||||
}
|
||||
}
|
@ -69,9 +69,31 @@ public class InitGameService {
|
||||
shop_characters.add(cashier);
|
||||
shop.setCharacters(shop_characters);
|
||||
|
||||
// Knihovna
|
||||
var library = new Room(
|
||||
"Knihovna",
|
||||
game
|
||||
);
|
||||
var library_characters = new ArrayList<Character>();
|
||||
var librarian = new Character(
|
||||
"Knihovník",
|
||||
library,
|
||||
"Ahoj já jsem knihovník a budeš se mnou hrát wordle."
|
||||
);
|
||||
librarian.setInteraction(Interaction.Librarian);
|
||||
librarian.setInteractionData(new cz.jzitnik.chronos.entities.Interaction(
|
||||
"Tak si zahrajeme wordle.",
|
||||
"Se mnou někdo již hrál wordle. Další fragment klíče nemám.",
|
||||
librarian
|
||||
));
|
||||
library_characters.add(librarian);
|
||||
library.setCharacters(library_characters);
|
||||
|
||||
|
||||
rooms.add(outside);
|
||||
rooms.add(stodola);
|
||||
rooms.add(shop);
|
||||
rooms.add(library);
|
||||
|
||||
game.setRooms(rooms);
|
||||
|
||||
|
@ -2,5 +2,6 @@ package cz.jzitnik.api.types;
|
||||
|
||||
public enum Interaction {
|
||||
Farmer,
|
||||
Cashier
|
||||
Cashier,
|
||||
Librarian
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package cz.jzitnik.game.interactions;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import cz.jzitnik.api.ApiService;
|
||||
import cz.jzitnik.api.requests.InteractionRequest;
|
||||
|
@ -20,7 +20,6 @@ public class Interactions {
|
||||
|
||||
Cli.type(character, interactionData.getStartText());
|
||||
|
||||
// "Object _ =" is just for java compiler to ensure that all interactions are covered.
|
||||
switch (character.getInteraction()) {
|
||||
case Farmer -> {
|
||||
var options = new String[]{"Kámen", "Nůžky", "Papír"};
|
||||
@ -42,6 +41,10 @@ public class Interactions {
|
||||
Hangman hangman = new Hangman(character, apiService, playerKey);
|
||||
hangman.play();
|
||||
}
|
||||
case Librarian -> {
|
||||
Wordle wordle = new Wordle(character, apiService, playerKey);
|
||||
wordle.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
136
frontend/src/main/java/cz/jzitnik/game/interactions/Wordle.java
Normal file
136
frontend/src/main/java/cz/jzitnik/game/interactions/Wordle.java
Normal file
@ -0,0 +1,136 @@
|
||||
package cz.jzitnik.game.interactions;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import cz.jzitnik.api.ApiService;
|
||||
import cz.jzitnik.api.requests.InteractionRequest;
|
||||
import cz.jzitnik.api.responses.InteractionResponse;
|
||||
import cz.jzitnik.api.types.Character;
|
||||
import cz.jzitnik.utils.Cli;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.Console;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class Wordle {
|
||||
private Character character;
|
||||
private ApiService apiService;
|
||||
private String playerKey;
|
||||
|
||||
@Getter
|
||||
private static class WordleResponse {
|
||||
public enum Type {
|
||||
GAME_CREATED,
|
||||
GAME_WON,
|
||||
GAME_LOST,
|
||||
GAME_CONTINUE,
|
||||
}
|
||||
|
||||
private Type type;
|
||||
private int attemptsRemaining;
|
||||
private List<GuessFeedback> feedbackHistory;
|
||||
}
|
||||
|
||||
@Getter
|
||||
private static class GuessFeedback {
|
||||
private String guess;
|
||||
private List<CharacterFeedback> feedback;
|
||||
}
|
||||
|
||||
@Getter
|
||||
private static class CharacterFeedback {
|
||||
private char character;
|
||||
private FeedbackType feedbackType;
|
||||
}
|
||||
|
||||
public enum FeedbackType {
|
||||
NOT_IN_WORD,
|
||||
WRONG_POSITION,
|
||||
CORRECT_POSITION
|
||||
}
|
||||
|
||||
public void play() throws IOException {
|
||||
var response = apiService.isInteracting(playerKey, character.getId()).execute();
|
||||
var interacting = response.body().getData().get();
|
||||
|
||||
if (!interacting) {
|
||||
init();
|
||||
} else {
|
||||
// Get data where we left off
|
||||
var body = apiService.interact(playerKey, new InteractionRequest("get_data", character.getId())).execute().body();
|
||||
var data = body.getData().get();
|
||||
gameContinue(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void init() throws IOException {
|
||||
var body = apiService.interact(playerKey, new InteractionRequest("", character.getId())).execute().body();
|
||||
var data = body.getData().get();
|
||||
gameContinue(data);
|
||||
}
|
||||
|
||||
private void gameContinue(InteractionResponse data) throws IOException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
var wordleData = objectMapper.readValue(data.getResponseText(), WordleResponse.class);
|
||||
|
||||
switch (wordleData.getType()) {
|
||||
case GAME_WON -> {
|
||||
Cli.type(character, "Vyhrál jsi! Slovo bylo: " + getCurrentWord(wordleData.getFeedbackHistory()));
|
||||
Cli.gotItems(data.getItems());
|
||||
}
|
||||
case GAME_LOST -> Cli.type(character, "Prohrál jsi! Bohužel jste neuhodl slovo. Fragment klíče nedostanete.");
|
||||
case GAME_CONTINUE, GAME_CREATED -> {
|
||||
System.out.println("Zbývající pokusy: " + wordleData.getAttemptsRemaining());
|
||||
displayFeedback(wordleData.getFeedbackHistory());
|
||||
|
||||
var guess = askForWord();
|
||||
var response = apiService.interact(playerKey, new InteractionRequest(guess, character.getId())).execute();
|
||||
var body = response.body().getData().get();
|
||||
|
||||
gameContinue(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String askForWord() {
|
||||
Console console = System.console();
|
||||
|
||||
String word = console.readLine("Zadejte slovo (5 písmen): ");
|
||||
|
||||
if (word.length() != 5) {
|
||||
Cli.error("Neplatný vstup! Slovo musí mít 5 písmen.");
|
||||
return askForWord();
|
||||
}
|
||||
|
||||
return word.toLowerCase();
|
||||
}
|
||||
|
||||
private void displayFeedback(List<GuessFeedback> feedbackHistory) {
|
||||
for (var feedback : feedbackHistory) {
|
||||
System.out.print(feedback.getGuess() + " -> ");
|
||||
|
||||
for (var charFeedback : feedback.getFeedback()) {
|
||||
String color = switch (charFeedback.getFeedbackType()) {
|
||||
case CORRECT_POSITION -> Cli.Colors.GREEN;
|
||||
case WRONG_POSITION -> Cli.Colors.YELLOW;
|
||||
case NOT_IN_WORD -> "";
|
||||
};
|
||||
|
||||
System.out.print(color + charFeedback.getCharacter() + Cli.Colors.RESET);
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
private String getCurrentWord(List<GuessFeedback> feedbackHistory) {
|
||||
for (var feedback : feedbackHistory) {
|
||||
if (feedback.getFeedback().stream().allMatch(f -> f.getFeedbackType() == FeedbackType.CORRECT_POSITION)) {
|
||||
return feedback.getGuess();
|
||||
}
|
||||
}
|
||||
return "???";
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user