You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
7.3 KiB
Go
285 lines
7.3 KiB
Go
package valueobjects
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"knowfoolery/backend/shared/types"
|
|
)
|
|
|
|
// AttemptResult represents the outcome of a single answer attempt
|
|
type AttemptResult struct {
|
|
result types.AttemptResult
|
|
isCorrect bool
|
|
pointsAwarded int
|
|
hintWasUsed bool
|
|
timeTaken types.Duration
|
|
playerAnswer string
|
|
correctAnswer string
|
|
similarity float64 // For fuzzy matches
|
|
isFuzzyMatch bool // True if matched via fuzzy logic
|
|
}
|
|
|
|
// NewAttemptResult creates a new AttemptResult
|
|
func NewAttemptResult(
|
|
isCorrect bool,
|
|
pointsAwarded int,
|
|
hintWasUsed bool,
|
|
timeTaken types.Duration,
|
|
playerAnswer string,
|
|
correctAnswer string,
|
|
similarity float64,
|
|
isFuzzyMatch bool,
|
|
) *AttemptResult {
|
|
var result types.AttemptResult
|
|
if isCorrect {
|
|
result = types.AttemptResultCorrect
|
|
} else {
|
|
result = types.AttemptResultIncorrect
|
|
}
|
|
|
|
return &AttemptResult{
|
|
result: result,
|
|
isCorrect: isCorrect,
|
|
pointsAwarded: pointsAwarded,
|
|
hintWasUsed: hintWasUsed,
|
|
timeTaken: timeTaken,
|
|
playerAnswer: playerAnswer,
|
|
correctAnswer: correctAnswer,
|
|
similarity: similarity,
|
|
isFuzzyMatch: isFuzzyMatch,
|
|
}
|
|
}
|
|
|
|
// NewCorrectAttemptResult creates a result for a correct answer
|
|
func NewCorrectAttemptResult(
|
|
pointsAwarded int,
|
|
hintWasUsed bool,
|
|
timeTaken types.Duration,
|
|
playerAnswer string,
|
|
correctAnswer string,
|
|
similarity float64,
|
|
isFuzzyMatch bool,
|
|
) *AttemptResult {
|
|
return NewAttemptResult(
|
|
true,
|
|
pointsAwarded,
|
|
hintWasUsed,
|
|
timeTaken,
|
|
playerAnswer,
|
|
correctAnswer,
|
|
similarity,
|
|
isFuzzyMatch,
|
|
)
|
|
}
|
|
|
|
// NewIncorrectAttemptResult creates a result for an incorrect answer
|
|
func NewIncorrectAttemptResult(
|
|
hintWasUsed bool,
|
|
timeTaken types.Duration,
|
|
playerAnswer string,
|
|
correctAnswer string,
|
|
similarity float64,
|
|
) *AttemptResult {
|
|
return NewAttemptResult(
|
|
false,
|
|
types.PointsIncorrect,
|
|
hintWasUsed,
|
|
timeTaken,
|
|
playerAnswer,
|
|
correctAnswer,
|
|
similarity,
|
|
false, // Incorrect answers are never fuzzy matches
|
|
)
|
|
}
|
|
|
|
// NewTimeoutAttemptResult creates a result for a timeout
|
|
func NewTimeoutAttemptResult(correctAnswer string) *AttemptResult {
|
|
return &AttemptResult{
|
|
result: types.AttemptResultTimeout,
|
|
isCorrect: false,
|
|
pointsAwarded: types.PointsIncorrect,
|
|
hintWasUsed: false,
|
|
timeTaken: types.NewDuration(0),
|
|
playerAnswer: "",
|
|
correctAnswer: correctAnswer,
|
|
similarity: 0.0,
|
|
isFuzzyMatch: false,
|
|
}
|
|
}
|
|
|
|
// Result returns the attempt result type
|
|
func (ar *AttemptResult) Result() types.AttemptResult {
|
|
return ar.result
|
|
}
|
|
|
|
// IsCorrect returns true if the attempt was correct
|
|
func (ar *AttemptResult) IsCorrect() bool {
|
|
return ar.isCorrect
|
|
}
|
|
|
|
// IsIncorrect returns true if the attempt was incorrect
|
|
func (ar *AttemptResult) IsIncorrect() bool {
|
|
return !ar.isCorrect && ar.result != types.AttemptResultTimeout
|
|
}
|
|
|
|
// IsTimeout returns true if the attempt timed out
|
|
func (ar *AttemptResult) IsTimeout() bool {
|
|
return ar.result == types.AttemptResultTimeout
|
|
}
|
|
|
|
// PointsAwarded returns the points awarded for this attempt
|
|
func (ar *AttemptResult) PointsAwarded() int {
|
|
return ar.pointsAwarded
|
|
}
|
|
|
|
// HintWasUsed returns true if a hint was used for this attempt
|
|
func (ar *AttemptResult) HintWasUsed() bool {
|
|
return ar.hintWasUsed
|
|
}
|
|
|
|
// TimeTaken returns the time taken for this attempt
|
|
func (ar *AttemptResult) TimeTaken() types.Duration {
|
|
return ar.timeTaken
|
|
}
|
|
|
|
// PlayerAnswer returns the answer provided by the player
|
|
func (ar *AttemptResult) PlayerAnswer() string {
|
|
return ar.playerAnswer
|
|
}
|
|
|
|
// CorrectAnswer returns the correct answer
|
|
func (ar *AttemptResult) CorrectAnswer() string {
|
|
return ar.correctAnswer
|
|
}
|
|
|
|
// Similarity returns the similarity score (0-1) between player and correct answer
|
|
func (ar *AttemptResult) Similarity() float64 {
|
|
return ar.similarity
|
|
}
|
|
|
|
// IsFuzzyMatch returns true if the answer was matched via fuzzy logic
|
|
func (ar *AttemptResult) IsFuzzyMatch() bool {
|
|
return ar.isFuzzyMatch
|
|
}
|
|
|
|
// IsExactMatch returns true if the answer was an exact match
|
|
func (ar *AttemptResult) IsExactMatch() bool {
|
|
return ar.isCorrect && !ar.isFuzzyMatch
|
|
}
|
|
|
|
// GetMatchType returns a string describing the match type
|
|
func (ar *AttemptResult) GetMatchType() string {
|
|
switch {
|
|
case ar.IsTimeout():
|
|
return "timeout"
|
|
case ar.IsIncorrect():
|
|
return "incorrect"
|
|
case ar.IsExactMatch():
|
|
return "exact_match"
|
|
case ar.IsFuzzyMatch():
|
|
return "fuzzy_match"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// GetResultDescription returns a human-readable description of the result
|
|
func (ar *AttemptResult) GetResultDescription() string {
|
|
switch {
|
|
case ar.IsTimeout():
|
|
return "Time ran out"
|
|
case ar.IsIncorrect():
|
|
return "Incorrect answer"
|
|
case ar.IsExactMatch():
|
|
if ar.hintWasUsed {
|
|
return "Correct answer with hint (exact match)"
|
|
}
|
|
return "Correct answer (exact match)"
|
|
case ar.IsFuzzyMatch():
|
|
if ar.hintWasUsed {
|
|
return "Correct answer with hint (close match)"
|
|
}
|
|
return "Correct answer (close match)"
|
|
default:
|
|
return "Unknown result"
|
|
}
|
|
}
|
|
|
|
// GetScoreExplanation returns an explanation of the scoring for this attempt
|
|
func (ar *AttemptResult) GetScoreExplanation() string {
|
|
switch {
|
|
case ar.IsTimeout():
|
|
return "No points awarded - time expired"
|
|
case ar.IsIncorrect():
|
|
return "No points awarded - incorrect answer"
|
|
case ar.hintWasUsed:
|
|
return "1 point awarded - correct answer with hint"
|
|
default:
|
|
return "2 points awarded - correct answer without hint"
|
|
}
|
|
}
|
|
|
|
// Equals checks if two AttemptResults are equal
|
|
func (ar *AttemptResult) Equals(other *AttemptResult) bool {
|
|
if other == nil {
|
|
return false
|
|
}
|
|
|
|
return ar.result == other.result &&
|
|
ar.isCorrect == other.isCorrect &&
|
|
ar.pointsAwarded == other.pointsAwarded &&
|
|
ar.hintWasUsed == other.hintWasUsed &&
|
|
ar.playerAnswer == other.playerAnswer &&
|
|
ar.correctAnswer == other.correctAnswer &&
|
|
ar.similarity == other.similarity &&
|
|
ar.isFuzzyMatch == other.isFuzzyMatch
|
|
}
|
|
|
|
// IsWorthPoints returns true if this attempt result awards points
|
|
func (ar *AttemptResult) IsWorthPoints() bool {
|
|
return ar.pointsAwarded > 0
|
|
}
|
|
|
|
// GetTimeTakenFormatted returns the time taken formatted for display
|
|
func (ar *AttemptResult) GetTimeTakenFormatted() string {
|
|
if ar.IsTimeout() {
|
|
return "timeout"
|
|
}
|
|
|
|
seconds := ar.timeTaken.Seconds()
|
|
if seconds < 1 {
|
|
return "< 1s"
|
|
}
|
|
|
|
return fmt.Sprintf("%.1fs", seconds)
|
|
}
|
|
|
|
// AttempResultSummary provides a summary of the attempt result
|
|
type AttemptResultSummary struct {
|
|
MatchType string `json:"match_type"`
|
|
IsCorrect bool `json:"is_correct"`
|
|
PointsAwarded int `json:"points_awarded"`
|
|
HintWasUsed bool `json:"hint_was_used"`
|
|
TimeTaken string `json:"time_taken"`
|
|
PlayerAnswer string `json:"player_answer"`
|
|
CorrectAnswer string `json:"correct_answer"`
|
|
Similarity float64 `json:"similarity"`
|
|
ResultDescription string `json:"result_description"`
|
|
ScoreExplanation string `json:"score_explanation"`
|
|
}
|
|
|
|
// ToSummary converts the attempt result to a summary structure
|
|
func (ar *AttemptResult) ToSummary() *AttemptResultSummary {
|
|
return &AttemptResultSummary{
|
|
MatchType: ar.GetMatchType(),
|
|
IsCorrect: ar.IsCorrect(),
|
|
PointsAwarded: ar.PointsAwarded(),
|
|
HintWasUsed: ar.HintWasUsed(),
|
|
TimeTaken: ar.GetTimeTakenFormatted(),
|
|
PlayerAnswer: ar.PlayerAnswer(),
|
|
CorrectAnswer: ar.CorrectAnswer(),
|
|
Similarity: ar.Similarity(),
|
|
ResultDescription: ar.GetResultDescription(),
|
|
ScoreExplanation: ar.GetScoreExplanation(),
|
|
}
|
|
} |