Skip to content
Merged
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
28 changes: 24 additions & 4 deletions lib/screens/practice/scrambledword_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ class _ScrambledWordScreenState extends State<ScrambledWordScreen> {
// Statistics tracking
int _correctWithoutHint = 0;
int _correctWithHint = 0;
int _correctWithSolve = 0;
List<bool> _hintUsed = [];
List<bool> _solveUsed = [];

/// Checks if a word can be meaningfully scrambled
bool _canBeScrambled(String word) {
Expand All @@ -56,6 +58,7 @@ class _ScrambledWordScreenState extends State<ScrambledWordScreen> {
_quizEntries = _quizEntries.sublist(0, widget.count);
}
_hintUsed = List.filled(_quizEntries.length, false);
_solveUsed = List.filled(_quizEntries.length, false);
_setupCurrentWord();
}

Expand Down Expand Up @@ -109,11 +112,22 @@ class _ScrambledWordScreenState extends State<ScrambledWordScreen> {
});
}

void _solveWord() {
setState(() {
final target = _quizEntries[_current].target.trim();
_userOrder = target.split('');
_solveUsed[_current] = true;
_checkCorrect();
});
}

void _nextWord() {
setState(() {
// Update statistics for the current word
if (_isCorrect) {
if (_hintUsed[_current]) {
if (_solveUsed[_current]) {
_correctWithSolve++;
} else if (_hintUsed[_current]) {
_correctWithHint++;
} else {
_correctWithoutHint++;
Expand Down Expand Up @@ -161,7 +175,7 @@ class _ScrambledWordScreenState extends State<ScrambledWordScreen> {
}

if (_current >= _quizEntries.length) {
int totalCorrect = _correctWithoutHint + _correctWithHint;
int totalCorrect = _correctWithoutHint + _correctWithHint + _correctWithSolve;
double percent = totalCorrect > 0 ? (_correctWithoutHint / totalCorrect) * 100 : 0;

return Scaffold(
Expand All @@ -172,8 +186,9 @@ class _ScrambledWordScreenState extends State<ScrambledWordScreen> {
children: [
Text('Exercise complete', style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 16),
Text('Correct without hint: $_correctWithoutHint', style: const TextStyle(color: correctTextC, fontSize: 18)),
Text('Correct with hint: $_correctWithHint', style: const TextStyle(color: Colors.blue, fontSize: 18)),
Text('Found without hint: $_correctWithoutHint', style: const TextStyle(color: correctTextC, fontSize: 18)),
Text('Found with hint: $_correctWithHint', style: const TextStyle(color: Colors.blue, fontSize: 18)),
Text('Solved automatically: $_correctWithSolve', style: const TextStyle(color: Colors.orange, fontSize: 18)),
const SizedBox(height: 8),
Text('Score: ${percent.toStringAsFixed(1)}%', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
const SizedBox(height: 24),
Expand Down Expand Up @@ -296,6 +311,11 @@ class _ScrambledWordScreenState extends State<ScrambledWordScreen> {
),
],
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _solveWord,
child: const Text('Solve'),
),
],
const SizedBox(height: 24),
if (_isCorrect || _showHint)
Expand Down
202 changes: 158 additions & 44 deletions lib/screens/practice/wordsearch_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:unicode_data/unicode_data.dart';

// Color definitions for WordSearchScreen
const Color foundC = Colors.greenAccent;
const Color solvedC = Colors.orangeAccent;
const Color selectedC = Colors.yellowAccent;
const Color borderC = Colors.blueGrey;
const Color defaultC = Colors.white;
Expand Down Expand Up @@ -41,13 +42,15 @@ class _WordSearchScreenState extends State<WordSearchScreen> {
late int _gridSize;
late List<_PlacedWord> _placedWords;
Set<int> _foundWordIndexes = {};
Set<int> _solvedWordIndexes = {};
List<_FoundWord> _foundWords = [];
int? _selectStartRow;
int? _selectStartCol;
int? _selectEndRow;
int? _selectEndCol;
bool _showSourceHints = true;
late List<List<bool>> _foundMatrix;
late List<List<bool>> _solvedMatrix;

@override
void initState() {
Expand All @@ -73,12 +76,14 @@ class _WordSearchScreenState extends State<WordSearchScreen> {
_placedWords = [];
_grid = _generateGrid(_gridSize, wordList, widget.readingDirection, actuallyPlaced: _placedWords);
_foundWordIndexes.clear();
_solvedWordIndexes.clear();
_foundWords.clear();
_selectStartRow = null;
_selectStartCol = null;
_selectEndRow = null;
_selectEndCol = null;
_foundMatrix = List.generate(_gridSize, (_) => List.filled(_gridSize, false));
_solvedMatrix = List.generate(_gridSize, (_) => List.filled(_gridSize, false));
}

List<List<String>> _generateGrid(int gridSize, List<String> wordList, TextDirection direction, {List<_PlacedWord>? actuallyPlaced}) {
Expand Down Expand Up @@ -319,6 +324,20 @@ class _WordSearchScreenState extends State<WordSearchScreen> {
}
}

void _markWordSolved(_PlacedWord word) {
final start = word.start;
final end = word.end;
if (word.isHorizontal) {
for (int c = start[1]; c <= end[1]; c++) {
_solvedMatrix[start[0]][c] = true;
}
} else {
for (int r = start[0]; r <= end[0]; r++) {
_solvedMatrix[r][start[1]] = true;
}
}
}

void _checkSelection() {
if (_selectStartRow == null || _selectStartCol == null || _selectEndRow == null || _selectEndCol == null) return;
final start = [_selectStartRow!, _selectStartCol!];
Expand Down Expand Up @@ -393,9 +412,9 @@ class _WordSearchScreenState extends State<WordSearchScreen> {
}
}
final selectedWord = selected.join();
// Check if matches any placed word and not already found
// Check if matches any placed word and not already found or solved
for (int i = 0; i < _placedWords.length; i++) {
if (_foundWordIndexes.contains(i)) continue;
if (_foundWordIndexes.contains(i) || _solvedWordIndexes.contains(i)) continue;
if (_placedWords[i].word == selectedWord) {
setState(() {
_foundWordIndexes.add(i);
Expand All @@ -405,6 +424,7 @@ class _WordSearchScreenState extends State<WordSearchScreen> {
start: _placedWords[i].start,
end: _placedWords[i].end,
isHorizontal: _placedWords[i].isHorizontal,
isSolved: false,
));
_markWordFound(_placedWords[i]);
_selectStartRow = null;
Expand All @@ -418,6 +438,23 @@ class _WordSearchScreenState extends State<WordSearchScreen> {
// Keep the yellow highlight until a new selection is started
}

void _solveWord(int wordIndex) {
if (_foundWordIndexes.contains(wordIndex) || _solvedWordIndexes.contains(wordIndex)) return;
final placedWord = _placedWords[wordIndex];
setState(() {
_solvedWordIndexes.add(wordIndex);
_foundWords.add(_FoundWord(
index: wordIndex,
word: placedWord.word,
start: placedWord.start,
end: placedWord.end,
isHorizontal: placedWord.isHorizontal,
isSolved: true,
));
_markWordSolved(placedWord);
});
}

List<int> _getSelectedCells() {
// If only start cell is set, highlight it
if (_selectStartRow != null && _selectStartCol != null && (_selectEndRow == null || _selectEndCol == null)) {
Expand Down Expand Up @@ -477,6 +514,10 @@ class _WordSearchScreenState extends State<WordSearchScreen> {
return false;
}

bool _isCellSolved(int row, int col) {
return _solvedMatrix[row][col];
}

bool _isCellSelected(int row, int col) {
if (_selectStartRow == null || _selectStartCol == null) return false;
if (_selectEndRow == null || _selectEndCol == null) {
Expand Down Expand Up @@ -520,6 +561,7 @@ class _WordSearchScreenState extends State<WordSearchScreen> {
final row = index ~/ _gridSize;
final col = index % _gridSize;
final isSelected = _isCellSelected(row, col);
final isSolved = _isCellSolved(row, col);
final isFound = _foundMatrix[row][col];
return GestureDetector(
onTap: () => _onCellTap(row, col),
Expand All @@ -531,9 +573,11 @@ class _WordSearchScreenState extends State<WordSearchScreen> {
border: Border.all(color: borderC),
color: isSelected
? selectedC
: isFound
? foundC
: defaultC,
: isSolved
? solvedC
: isFound
? foundC
: defaultC,
),
child: Center(
child: Text(
Expand Down Expand Up @@ -574,57 +618,126 @@ class _WordSearchScreenState extends State<WordSearchScreen> {
const SizedBox(height: 8),
Wrap(
spacing: 12,
runSpacing: 12,
children: _showSourceHints
? _selectedEntries.asMap().entries.map((entry) {
final idx = entry.key;
final word = entry.value;
final foundIdx = _foundWords.indexWhere((fw) => fw.word == word.target.trim().toUpperCase());
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Chip(
label: EntrySourceWidget(
entry: word,
style: const TextStyle(fontSize: 12),
vocabulary: widget.vocabulary,
imageSize: ImageSize.medium,
),
),
if (foundIdx != -1)
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Chip(
label: Text(word.target),
backgroundColor: foundC,
final foundWord = _foundWords.firstWhere(
(fw) => fw.word == word.target.trim().toUpperCase(),
orElse: () => _FoundWord(
index: -1,
word: '',
start: [0, 0],
end: [0, 0],
isHorizontal: true,
isSolved: false,
),
);
final isFound = foundWord.index != -1;
final isSolved = isFound && foundWord.isSolved;
final isManuallyFound = isFound && !foundWord.isSolved;
// Find the placed word index
final placedWordIdx = _placedWords.indexWhere((pw) => pw.word == word.target.trim().toUpperCase());
final canSolve = placedWordIdx != -1 && !_foundWordIndexes.contains(placedWordIdx) && !_solvedWordIndexes.contains(placedWordIdx);
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Chip(
label: EntrySourceWidget(
entry: word,
style: const TextStyle(fontSize: 12),
vocabulary: widget.vocabulary,
imageSize: ImageSize.medium,
),
),
],
if (isFound)
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Chip(
label: Text(word.target),
backgroundColor: isSolved ? solvedC : foundC,
),
)
else if (canSolve)
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: ElevatedButton(
onPressed: () => _solveWord(placedWordIdx),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
minimumSize: const Size(0, 24),
),
child: const Text('Solve', style: TextStyle(fontSize: 12)),
),
),
],
),
);
}).toList()
: _selectedEntries.map((e) {
final found = _foundWords.any((fw) => fw.word == e.target.trim().toUpperCase());
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Chip(
label: Text(e.target),
backgroundColor: found ? foundC : null,
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: EntrySourceWidget(
entry: e,
style: TextStyle(fontSize: 12, color: hintTextC),
vocabulary: widget.vocabulary,
imageSize: ImageSize.small,
: _selectedEntries.asMap().entries.map((entry) {
final idx = entry.key;
final e = entry.value;
final foundWord = _foundWords.firstWhere(
(fw) => fw.word == e.target.trim().toUpperCase(),
orElse: () => _FoundWord(
index: -1,
word: '',
start: [0, 0],
end: [0, 0],
isHorizontal: true,
isSolved: false,
),
);
final isFound = foundWord.index != -1;
final isSolved = isFound && foundWord.isSolved;
// Find the placed word index
final placedWordIdx = _placedWords.indexWhere((pw) => pw.word == e.target.trim().toUpperCase());
final canSolve = placedWordIdx != -1 && !_foundWordIndexes.contains(placedWordIdx) && !_solvedWordIndexes.contains(placedWordIdx);
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Chip(
label: Text(e.target),
backgroundColor: isFound ? (isSolved ? solvedC : foundC) : null,
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: EntrySourceWidget(
entry: e,
style: TextStyle(fontSize: 12, color: hintTextC),
vocabulary: widget.vocabulary,
imageSize: ImageSize.small,
),
),
),
],
if (!isFound && canSolve)
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: ElevatedButton(
onPressed: () => _solveWord(placedWordIdx),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
minimumSize: const Size(0, 24),
),
child: const Text('Solve', style: TextStyle(fontSize: 12)),
),
),
],
),
);
}).toList(),
),
const SizedBox(height: 24),
Text('Found: ${_foundWords.length} / ${_selectedEntries.length}', style: Theme.of(context).textTheme.titleLarge),
Text(
_foundWords.length == _selectedEntries.length
? 'Found all!'
: 'Found: ${_foundWords.length} / ${_selectedEntries.length}',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 12),
// Show found words in full at the bottom
ElevatedButton(
Expand Down Expand Up @@ -654,5 +767,6 @@ class _FoundWord {
final List<int> start;
final List<int> end;
final bool isHorizontal;
_FoundWord({required this.index, required this.word, required this.start, required this.end, required this.isHorizontal});
final bool isSolved;
_FoundWord({required this.index, required this.word, required this.start, required this.end, required this.isHorizontal, required this.isSolved});
}