feat: Wordle interaction
This commit is contained in:
@ -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);
|
||||
|
||||
|
Reference in New Issue
Block a user