Commit d1c30fd8 authored by Björn Eyselein's avatar Björn Eyselein
Browse files

Update Logik Versuche pro Karte

parent 8f349041
......@@ -21,6 +21,8 @@ interface CorrectionResult {
learnerSolution: Solution,
operations: EditOperation[],
answerSelection: AnswerSelectionResult
newTriesCount: number
maybeSampleSol: string | null
}
let correctionTextPar: JQuery<HTMLParagraphElement>;
......@@ -61,17 +63,25 @@ function readSolution(cardType: string): Solution | null {
}
function onCorrectionSuccess(result: CorrectionResult): void {
// console.info(JSON.stringify(result, null, 2));
console.info(JSON.stringify(result, null, 2));
correctionTextPar.text('Ihre Lösung war ' + (result.correct ? '' : 'nicht ') + 'korrekt.')
.removeClass(result.correct ? 'red-text' : 'green-text').addClass(result.correct ? 'green-text' : 'red-text');
let correctionText = 'Ihre Lösung war ' + (result.correct ? '' : 'nicht ') + 'korrekt.';
checkSolutionBtn.prop('disabled', result.correct);
if ((result.newTriesCount >= 2) && (result.maybeSampleSol != null)) {
correctionText += ` Die korrekte Lösung lautet '<code>${result.maybeSampleSol}</code>'.`;
}
correctionTextPar.html(correctionText).removeClass(result.correct ? 'red-text' : 'green-text').addClass(result.correct ? 'green-text' : 'red-text');
checkSolutionBtn.prop('disabled', result.correct || (result.newTriesCount >= 2));
if (result.correct) {
if (result.correct || result.newTriesCount >= 2) {
$('#nextFlashcardBtn').removeClass('disabled');
}
$('#triesSpan').text(result.newTriesCount);
switch (result.cardType) {
case 'Vocable':
case 'Text':
......
......@@ -83,15 +83,17 @@ class HomeController @Inject()(cc: ControllerComponents, protected val tableDefs
def learn(langId: Int, collId: Int, cardId: Int, isRepeating: Boolean): EssentialAction =
futureWithUserAndCompleteFlashcard(adminRightsRequired = false, langId, collId, cardId) { (user, _, _, completeFlashcard) =>
implicit request =>
val futureMaybeOldAnswer: Future[Option[UserAnsweredFlashcard]] = if (isRepeating) {
tableDefs.futureUserAnswerForFlashcard(user, completeFlashcard.flashcard)
} else Future.successful(None)
val futureMaybeOldAnswer: Future[Option[UserAnsweredFlashcard]] =
tableDefs.futureUserAnswerForFlashcard(user, completeFlashcard)
futureMaybeOldAnswer map { maybeOldAnswer =>
// val oldAnswerIsActive = maybeOldAnswer.exists(_.isActive)
// println(oldAnswerIsActive)
Ok(views.html.learn(user, completeFlashcard, maybeOldAnswer, isRepeating))
if (!isRepeating && maybeOldAnswer.isDefined) {
// TODO: Something went wrong, take next flashcard?
Redirect(routes.HomeController.startLearning(langId, collId, isRepeating))
} else {
Ok(views.html.learn(user, completeFlashcard, maybeOldAnswer, isRepeating))
}
}
}
......@@ -101,10 +103,23 @@ class HomeController @Inject()(cc: ControllerComponents, protected val tableDefs
request.body.asJson flatMap (json => JsonFormats.solutionFormat.reads(json).asOpt) match {
case None => Future(BadRequest("Could not read solution..."))
case Some(solution) =>
val correctionResult: CorrectionResult = Corrector.correct(completeFlashcard, solution)
tableDefs.futureInsertOrUpdateUserAnswer(user, completeFlashcard.flashcard, correctionResult.correct) map {
_ => Ok(JsonFormats.correctionResultWrites.writes(correctionResult))
val futurePreviousTries: Future[Int] = tableDefs.futureUserAnswerForFlashcard(user, completeFlashcard) map {
case None => 0
case Some(userAnsweredFlashcard) => userAnsweredFlashcard.tries
}
futurePreviousTries flatMap { previousTries =>
if (previousTries >= 2) {
???
} else {
val correctionResult: CorrectionResult = Corrector.correct(completeFlashcard, solution, previousTries)
tableDefs.futureInsertOrUpdateUserAnswer(user, completeFlashcard.flashcard, correctionResult.correct) map {
_ => Ok(JsonFormats.correctionResultWrites.writes(correctionResult))
}
}
}
}
}
......
......@@ -15,10 +15,14 @@ object Consts {
val idName : String = "id"
val indexName: String = "index"
val meaningName: String = "meaning"
val missingName: String = "missing"
val learnerSolutionName: String = "learnerSolution"
val maybeSampleSolName: String = "maybeSampleSol"
val meaningName : String = "meaning"
val missingName : String = "missing"
val nameName: String = "name"
val nameName : String = "name"
val newTriesCountName: String = "newTriesCount"
val operationsName : String = "operations"
val operationTypeName: String = "operationType"
......@@ -30,7 +34,6 @@ object Consts {
val repeatPwName: String = "repeatPw"
val selectedAnswersName: String = "selectedAnswers"
val learnerSolutionName: String = "learnerSolution"
val triesName: String = "tries"
......
......@@ -2,42 +2,59 @@ package model
import model.levenshtein.{EditOperation, Levenshtein}
final case class CorrectionResult(correct: Boolean, cardType: CardType, learnerSolution: Solution, operations: Seq[EditOperation], answersSelection: Option[AnswerSelectionResult])
final case class CorrectionResult(correct: Boolean, cardType: CardType, learnerSolution: Solution, operations: Seq[EditOperation],
answersSelection: Option[AnswerSelectionResult], newTriesCount: Int, maybeSampleSolution: Option[String] = None)
final case class AnswerSelectionResult(wrong: Seq[Int], correct: Seq[Int], missing: Seq[Int]) {
def isCorrect: Boolean = wrong.isEmpty && missing.isEmpty
}
object Corrector {
def matchAnswerIds(selectedIds: Seq[Int], correctIds: Seq[Int]): AnswerSelectionResult = AnswerSelectionResult(
private def matchAnswerIds(selectedIds: Seq[Int], correctIds: Seq[Int]): AnswerSelectionResult = AnswerSelectionResult(
wrong = selectedIds diff correctIds,
correct = selectedIds intersect correctIds,
missing = correctIds diff selectedIds
)
def correctTextualFlashcard(flashcard: Flashcard, solution: Solution): CorrectionResult = flashcard.meaning match {
private def correctTextualFlashcard(flashcard: CompleteFlashcard, solution: Solution, previousTriesCount: Int): Seq[EditOperation] = flashcard.meaning match {
case None => ???
case Some(meaning) =>
val editOperations: Seq[EditOperation] = Levenshtein.calculateBacktrace(solution.solution, meaning)
CorrectionResult(editOperations.isEmpty, flashcard.cardType, solution, editOperations, None)
case Some(meaning) => Levenshtein.calculateBacktrace(solution.solution, meaning)
}
def correctChoiceFlashcard(flashcard: Flashcard, answers: Seq[ChoiceAnswer], solution: Solution): CorrectionResult = {
private def correctChoiceFlashcard(flashcard: CompleteFlashcard, solution: Solution, previousTriesCount: Int): AnswerSelectionResult = {
val selectedAnswerIds: Seq[Int] = solution.selectedAnswers
val correctAnswerIds: Seq[Int] = answers.filter(_.correctness != Correctness.Wrong).map(_.id)
val correctAnswerIds: Seq[Int] = flashcard.choiceAnswers.filter(_.correctness != Correctness.Wrong).map(_.id)
val answerSelectionResult = matchAnswerIds(selectedAnswerIds, correctAnswerIds)
CorrectionResult(answerSelectionResult.isCorrect, flashcard.cardType, solution, Seq[EditOperation](), Some(answerSelectionResult))
matchAnswerIds(selectedAnswerIds, correctAnswerIds)
}
def correct(completeFlashcard: CompleteFlashcard, solution: Solution): CorrectionResult = completeFlashcard.flashcard.cardType match {
case CardType.Vocable | CardType.Text => correctTextualFlashcard(completeFlashcard.flashcard, solution)
case CardType.SingleChoice | CardType.MultipleChoice => correctChoiceFlashcard(completeFlashcard.flashcard, completeFlashcard.choiceAnswers, solution)
def correct(completeFlashcard: CompleteFlashcard, solution: Solution, previousTriesCount: Int): CorrectionResult = {
val (correct, editOps, ansSelection) = completeFlashcard.cardType match {
case CardType.Vocable | CardType.Text =>
val editOperations = correctTextualFlashcard(completeFlashcard, solution, previousTriesCount)
(editOperations.isEmpty, editOperations, None)
case CardType.SingleChoice | CardType.MultipleChoice =>
val answerSelectionResult = correctChoiceFlashcard(completeFlashcard, solution, previousTriesCount)
(answerSelectionResult.isCorrect, Seq[EditOperation](), Some(answerSelectionResult))
}
val newTriesCount: Int = if (correct) previousTriesCount else previousTriesCount + 1
val maybeSampleSolution: Option[String] = if (newTriesCount >= 2) {
completeFlashcard.meaning
} else None
CorrectionResult(correct, completeFlashcard.cardType, solution, editOps, ansSelection, newTriesCount, maybeSampleSolution)
}
}
\ No newline at end of file
......@@ -30,18 +30,20 @@ object JsonFormats {
(__ \ charName).writeNullable[Char]
) (unlift(EditOperation.unapply))
private val answerSelectionResultWrites: Writes[AnswerSelectionResult] = (
private implicit val answerSelectionResultWrites: Writes[AnswerSelectionResult] = (
(__ \ wrongName).write[Seq[Int]] and
(__ \ correctName).write[Seq[Int]] and
(__ \ missingName).write[Seq[Int]]
) (unlift(AnswerSelectionResult.unapply))
val correctionResultWrites: Writes[CorrectionResult] = (
def correctionResultWrites: Writes[CorrectionResult] = (
(__ \ correctName).write[Boolean] and
(__ \ cardTypeName).write[CardType] and
(__ \ learnerSolutionName).write[Solution](solutionFormat) and
(__ \ operationsName).write[Seq[EditOperation]] and
(__ \ answerSelectionName).writeNullable[AnswerSelectionResult](answerSelectionResultWrites)
(__ \ answerSelectionName).write[Option[AnswerSelectionResult]] and
(__ \ newTriesCountName).write[Int] and
(__ \ maybeSampleSolName).write[Option[String]]
) (unlift(CorrectionResult.unapply))
}
......@@ -71,7 +71,21 @@ final case class Flashcard(id: Int, collId: Int, langId: Int, cardType: CardType
final case class ChoiceAnswer(id: Int, cardId: Int, collId: Int, langId: Int, answer: String, correctness: Correctness)
final case class CompleteFlashcard(flashcard: Flashcard, choiceAnswers: Seq[ChoiceAnswer])
final case class CompleteFlashcard(flashcard: Flashcard, choiceAnswers: Seq[ChoiceAnswer]) {
def id: Int = flashcard.id
def collId: Int = flashcard.collId
def langId: Int = flashcard.langId
def cardType: CardType = flashcard.cardType
def question: String = flashcard.question
def meaning: Option[String] = flashcard.meaning
}
// User answered flashcard
......
......@@ -110,7 +110,7 @@ ON DUPLICATE KEY UPDATE date_answered = NOW(), correct = $correct,
// Queries - UserAnsweredFlashcard
def futureUserAnswerForFlashcard(user: User, flashcard: Flashcard): Future[Option[UserAnsweredFlashcard]] =
def futureUserAnswerForFlashcard(user: User, flashcard: CompleteFlashcard): Future[Option[UserAnsweredFlashcard]] =
db.run(usersAnsweredFlashcardsTQ.filter {
uaf => uaf.username === user.username && uaf.cardId === flashcard.id && uaf.collId === flashcard.collId && uaf.langId === flashcard.langId
}.result.headOption)
......
......@@ -24,13 +24,13 @@
<div class="card-panel">
<p>
@{
s"${completeCard.flashcard.langId}.${completeCard.flashcard.collId}.${completeCard.flashcard.id}"
}: @completeCard.flashcard.question
s"${completeCard.langId}.${completeCard.collId}.${completeCard.id}"
}: @completeCard.question
</p>
@completeCard.flashcard.cardType match {
@completeCard.cardType match {
case CardType.Vocable | CardType.Text => {
<p>Lösung: @completeCard.flashcard.meaning.getOrElse("")</p>
<p>Lösung: @completeCard.meaning.getOrElse("")</p>
}
case CardType.SingleChoice | CardType.MultipleChoice => {
<hr>
......
......@@ -12,7 +12,7 @@
}
@choiceInputType = @{
completeFlashcard.flashcard.cardType match {
completeFlashcard.cardType match {
case CardType.SingleChoice => "radio"
case CardType.MultipleChoice => "checkbox"
case _ => ""
......@@ -24,17 +24,17 @@
@helper.CSRF.formField
<div class="row">
<div class="col s12 l8 offset-l2" id="flashcardDiv" data-cardtype="@completeFlashcard.flashcard.cardType">
<div class="col s12 l8 offset-l2" id="flashcardDiv" data-cardtype="@completeFlashcard.cardType">
<div class="card-panel">
<h4 class="center-align">@completeFlashcard.flashcard.question</h4>
<h4 class="center-align">@completeFlashcard.question</h4>
</div>
<p class="center-align">
<code>
Stapel: @oldAnswer.map(_.bucketId).getOrElse("-"), Versuch: <span id="triesSpan">@oldAnswer.map(_.tries).getOrElse(0)</span></code>
Stapel: @oldAnswer.map(_.bucketId).getOrElse(1), Versuche: <span id="triesSpan">@oldAnswer.map(_.tries).getOrElse(0)</span></code>
</p>
@completeFlashcard.flashcard.cardType match {
@completeFlashcard.cardType match {
case CardType.Vocable | CardType.Text => {
<div class="row">
<div class="input-field col s12">
......@@ -58,11 +58,11 @@
<p id="correctionTextPar">&nbsp;</p>
<button class="btn btn-large waves-effect green" onclick="checkSolution();" id="checkSolutionBtn"
data-href="@routes.HomeController.checkSolution(completeFlashcard.flashcard.langId, completeFlashcard.flashcard.collId, completeFlashcard.flashcard.id)">
data-href="@routes.HomeController.checkSolution(completeFlashcard.langId, completeFlashcard.collId, completeFlashcard.id)">
Lösung testen
</button>
<a href="@routes.HomeController.startLearning(completeFlashcard.flashcard.langId, completeFlashcard.flashcard.collId, isRepeating)"
<a href="@routes.HomeController.startLearning(completeFlashcard.langId, completeFlashcard.collId, isRepeating)"
id="nextFlashcardBtn" class="btn btn-large waves-effect blue disabled">Weiter</a>
</div>
......
......@@ -210,7 +210,7 @@ from flashcards f
on uaf.card_id = f.id and uaf.coll_id = f.coll_id and uaf.lang_id = f.lang_id
join buckets b on uaf.bucket_id = b.id
where datediff(now(), date_answered) >= b.distance_days
or uaf.correct = false;
or (uaf.correct = false and uaf.tries < 2);
# --- !Downs
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment