Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dev.hylfrd.farmhelper.feature.jacob;

import java.util.Objects;

/** Immutable, normalized evidence for one crop count within one explicit contest identity. */
public record JacobContestEvidence(
JacobContestIdentity contest,
JacobCrop crop,
long collectedCount,
long sequence
) {
public JacobContestEvidence {
Objects.requireNonNull(contest, "contest");
Objects.requireNonNull(crop, "crop");
if (collectedCount < 0L) {
throw new IllegalArgumentException("collected count must be non-negative");
}
if (sequence < 0L) {
throw new IllegalArgumentException("sequence must be non-negative");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dev.hylfrd.farmhelper.feature.jacob;

/** Caller-assigned identity that prevents one contest's count from leaking into another. */
public record JacobContestIdentity(long value) {
public JacobContestIdentity {
if (value <= 0L) {
throw new IllegalArgumentException("contest identity must be positive");
}
}
}
59 changes: 59 additions & 0 deletions src/main/java/dev/hylfrd/farmhelper/feature/jacob/JacobCrop.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package dev.hylfrd.farmhelper.feature.jacob;

import java.util.Locale;
import java.util.Objects;
import java.util.Optional;

/** Crop names recognized by the fixed upstream Jacob scoreboard vocabulary. */
public enum JacobCrop {
WHEAT,
CARROT,
POTATO,
NETHER_WART,
SUGAR_CANE,
MUSHROOM,
MELON,
PUMPKIN,
COCOA_BEANS,
CACTUS;

/**
* Converts one cleaned server line using the same token families as upstream
* {@code GameStateHandler#convertCrop(String)}.
*/
public static Optional<JacobCrop> fromLine(String line) {
Objects.requireNonNull(line, "line");
String upper = line.toUpperCase(Locale.ROOT);
if (upper.contains("WHEAT")) {
return Optional.of(WHEAT);
}
if (upper.contains("CARROT")) {
return Optional.of(CARROT);
}
if (upper.contains("POTATO")) {
return Optional.of(POTATO);
}
if (upper.contains("NETHER") || upper.contains("WART")) {
return Optional.of(NETHER_WART);
}
if (upper.contains("SUGAR") || upper.contains("CANE")) {
return Optional.of(SUGAR_CANE);
}
if (upper.contains("MUSHROOM")) {
return Optional.of(MUSHROOM);
}
if (upper.contains("MELON")) {
return Optional.of(MELON);
}
if (upper.contains("PUMPKIN")) {
return Optional.of(PUMPKIN);
}
if (upper.contains("COCOA") || upper.contains("BEAN")) {
return Optional.of(COCOA_BEANS);
}
if (upper.contains("CACTUS")) {
return Optional.of(CACTUS);
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dev.hylfrd.farmhelper.feature.jacob;

/** Privacy-safe reasons why Jacob evidence cannot produce a trusted threshold decision. */
public enum JacobEvidenceIssue {
NONE,
SOURCE_UNKNOWN,
INCOMPLETE,
MALFORMED,
UNKNOWN_FORMAT,
OVERFLOW,
INPUT_LIMIT,
CONFLICT,
THRESHOLD_UNAVAILABLE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package dev.hylfrd.farmhelper.feature.jacob;

import dev.hylfrd.farmhelper.runtime.snapshot.Observation;

import java.util.Objects;

/** Immutable tri-state result at the cleaned-text observation boundary. */
public record JacobEvidenceParseResult(
JacobContestIdentity contest,
long sequence,
Observation<JacobContestEvidence> evidence,
JacobEvidenceIssue issue
) {
public JacobEvidenceParseResult {
Objects.requireNonNull(contest, "contest");
Objects.requireNonNull(evidence, "evidence");
Objects.requireNonNull(issue, "issue");
if (sequence < 0L) {
throw new IllegalArgumentException("sequence must be non-negative");
}
if (evidence.isPresent()) {
JacobContestEvidence value = evidence.get();
if (!value.contest().equals(contest) || value.sequence() != sequence) {
throw new IllegalArgumentException("evidence identity does not match parse context");
}
if (issue != JacobEvidenceIssue.NONE) {
throw new IllegalArgumentException("present evidence cannot have an issue");
}
} else if (evidence.isAbsent()) {
if (issue != JacobEvidenceIssue.NONE) {
throw new IllegalArgumentException("absent evidence cannot have an issue");
}
} else if (issue == JacobEvidenceIssue.NONE) {
throw new IllegalArgumentException("unknown evidence must have an issue");
}
}

public static JacobEvidenceParseResult present(JacobContestEvidence evidence) {
Objects.requireNonNull(evidence, "evidence");
return new JacobEvidenceParseResult(
evidence.contest(), evidence.sequence(), Observation.present(evidence), JacobEvidenceIssue.NONE);
}

public static JacobEvidenceParseResult absent(JacobContestIdentity contest, long sequence) {
return new JacobEvidenceParseResult(contest, sequence, Observation.absent(), JacobEvidenceIssue.NONE);
}

public static JacobEvidenceParseResult unknown(
JacobContestIdentity contest,
long sequence,
JacobEvidenceIssue issue
) {
if (issue == null || issue == JacobEvidenceIssue.NONE) {
throw new IllegalArgumentException("unknown evidence requires a non-NONE issue");
}
return new JacobEvidenceParseResult(contest, sequence, Observation.unknown(), issue);
}
}
Loading
Loading