94 lines
3.6 KiB
Dart
94 lines
3.6 KiB
Dart
import 'models.dart';
|
||
|
||
class ReviewCheckResult {
|
||
const ReviewCheckResult({required this.complete, required this.message});
|
||
|
||
final bool complete;
|
||
final String message;
|
||
}
|
||
|
||
/// Checks only the minimum information a learner needs to produce in an A0
|
||
/// review. It intentionally accepts common contractions and variable slot
|
||
/// values instead of comparing an answer with one fixed sentence.
|
||
class ReviewFeedback {
|
||
const ReviewFeedback._();
|
||
|
||
static ReviewCheckResult check(ReviewItem item, String input) {
|
||
final text = input.toLowerCase().replaceAll('’', "'");
|
||
final tokens = RegExp(
|
||
r"[a-z]+(?:'[a-z]+)?",
|
||
).allMatches(text).map((match) => match.group(0)!).toSet();
|
||
bool has(String token) => tokens.contains(token);
|
||
bool phrase(String value) => text.contains(value);
|
||
bool hasAny(Iterable<String> values) => values.any(has);
|
||
final introduction =
|
||
phrase("i'm") || phrase('i am') || phrase('my name is');
|
||
final itIs = phrase("it's") || phrase('it is');
|
||
final numberWords = RegExp(
|
||
r'\b(zero|one|two|three|four|five|six|seven|eight|nine|ten)\b',
|
||
).allMatches(text).length;
|
||
|
||
final complete = switch (item.id) {
|
||
'A0-P01' => introduction && tokens.length >= 2,
|
||
'A0-P02' => (phrase("what's your name") || phrase('what is your name')),
|
||
'A0-P03' => phrase('nice to meet you'),
|
||
'A0-P04' => phrase('how do you spell'),
|
||
'A0-P05' => phrase('how are you'),
|
||
'A0-P06' => introduction && hasAny(['good', 'okay', 'tired']),
|
||
'A0-P07' =>
|
||
phrase("what's your phone number") ||
|
||
phrase('what is your phone number'),
|
||
'A0-P08' =>
|
||
(phrase('my number is') || numberWords >= 3) && numberWords >= 3,
|
||
'A0-P09' => phrase("what's this") || phrase('what is this'),
|
||
'A0-P10' => itIs && hasAny(['book', 'pen', 'bag', 'key']),
|
||
'A0-P11' => phrase('where are you from'),
|
||
'A0-P12' => introduction && has('from') && tokens.length >= 3,
|
||
'A0-P13' => phrase('this is my') && tokens.length >= 4,
|
||
'A0-P14' => phrase('what day is it'),
|
||
'A0-P15' =>
|
||
itIs &&
|
||
hasAny([
|
||
'monday',
|
||
'tuesday',
|
||
'wednesday',
|
||
'thursday',
|
||
'friday',
|
||
'saturday',
|
||
'sunday',
|
||
]),
|
||
'A0-P16' => phrase('what time is it'),
|
||
'A0-P17' =>
|
||
itIs &&
|
||
(has('clock') ||
|
||
has("o'clock") ||
|
||
has('oclock') ||
|
||
phrase("o'clock")),
|
||
'A0-P18' => introduction && has('like') && tokens.length >= 3,
|
||
'A0-P19' => phrase('do you like'),
|
||
'A0-P20' =>
|
||
phrase('please say that again') || phrase('please speak slowly'),
|
||
_ when item.id.startsWith('A0-W') => tokens.contains(
|
||
item.target.toLowerCase(),
|
||
),
|
||
_ => tokens.length >= 2,
|
||
};
|
||
return ReviewCheckResult(
|
||
complete: complete,
|
||
message: complete ? '表达已完成,可以进入下一次复习安排。' : _hint(item.id),
|
||
);
|
||
}
|
||
|
||
static String _hint(String id) => switch (id) {
|
||
'A0-P01' => '用 I’m / I am 或 My name is 介绍一个名字。',
|
||
'A0-P02' => '试着问:What’s your name?',
|
||
'A0-P04' => '问对方怎样拼写:How do you spell …?',
|
||
'A0-P08' => '说出至少三个英文数字,也可用 My number is … 开头。',
|
||
'A0-P10' => '用 It’s / It is 加上一个物品。',
|
||
'A0-P12' => '用 I’m from … 说出一个地点。',
|
||
'A0-P17' => '用 It’s … o’clock 说整点时间。',
|
||
'A0-P18' => '用 I like … 说一项喜好。',
|
||
_ => '再补充本次目标中的关键英文词或句型。',
|
||
};
|
||
}
|