810 lines
29 KiB
Dart
810 lines
29 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'dart:io';
|
|
import '../../services/puzzle_service.dart';
|
|
import '../../models/puzzle_models.dart';
|
|
|
|
class WordlePage extends StatefulWidget {
|
|
final String? targetWord;
|
|
final DailyChallenge? dailyChallenge;
|
|
|
|
const WordlePage({Key? key, this.targetWord, this.dailyChallenge})
|
|
: super(key: key);
|
|
|
|
@override
|
|
_WordlePageState createState() => _WordlePageState();
|
|
}
|
|
|
|
class _WordlePageState extends State<WordlePage> with TickerProviderStateMixin {
|
|
late String TARGET_WORD;
|
|
static const int MAX_ATTEMPTS = 6;
|
|
static const int WORD_LENGTH = 5;
|
|
DateTime? gameStartTime;
|
|
|
|
List<List<String>> grid = List.generate(
|
|
MAX_ATTEMPTS,
|
|
(_) => List.filled(WORD_LENGTH, ''),
|
|
);
|
|
List<List<LetterState>> gridStates = List.generate(
|
|
MAX_ATTEMPTS,
|
|
(_) => List.filled(WORD_LENGTH, LetterState.empty),
|
|
);
|
|
Map<String, LetterState> keyboardStates = {};
|
|
|
|
int currentRow = 0;
|
|
int currentCol = 0;
|
|
int animatingRow = -1;
|
|
bool gameWon = false;
|
|
bool gameLost = false;
|
|
|
|
late AnimationController _revealController;
|
|
late List<Animation<double>> _tileAnimations;
|
|
late FocusNode _focusNode;
|
|
Set<LogicalKeyboardKey> _pressedKeys = <LogicalKeyboardKey>{};
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
TARGET_WORD = widget.targetWord ?? "HELLO";
|
|
gameStartTime = DateTime.now();
|
|
_initializeAnimations();
|
|
_initializeKeyboardStates();
|
|
_focusNode = FocusNode();
|
|
_loadGameState();
|
|
|
|
// Request focus for keyboard input on web/desktop
|
|
if (kIsWeb || (!kIsWeb && !Platform.isIOS && !Platform.isAndroid)) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_focusNode.requestFocus();
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _loadGameState() async {
|
|
final gameState = await PuzzleService.loadGameState();
|
|
if (gameState != null && gameState.targetWord == TARGET_WORD) {
|
|
setState(() {
|
|
// Restore game state
|
|
for (int i = 0; i < gameState.guesses.length; i++) {
|
|
if (i < MAX_ATTEMPTS && gameState.guesses[i].isNotEmpty) {
|
|
for (int j = 0; j < WORD_LENGTH; j++) {
|
|
if (j < gameState.guesses[i].length) {
|
|
grid[i][j] = gameState.guesses[i][j];
|
|
}
|
|
}
|
|
// Evaluate the guess to set proper states
|
|
List<LetterState> states = _evaluateGuess(gameState.guesses[i]);
|
|
gridStates[i] = states;
|
|
_updateKeyboardStates(gameState.guesses[i], states);
|
|
}
|
|
}
|
|
currentRow = gameState.currentAttempt;
|
|
currentCol = 0;
|
|
gameWon = gameState.isWon;
|
|
gameLost = gameState.isGameComplete && !gameState.isWon;
|
|
if (gameState.solveTime != null) {
|
|
gameStartTime = gameState.solveTime;
|
|
}
|
|
});
|
|
|
|
// If game is already completed, show the completed state
|
|
if (gameState.isGameComplete) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_showCompletedGameUI();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _saveGameState() async {
|
|
final guesses = <String>[];
|
|
for (int i = 0; i <= currentRow && i < MAX_ATTEMPTS; i++) {
|
|
if (grid[i].any((cell) => cell.isNotEmpty)) {
|
|
guesses.add(grid[i].join(''));
|
|
}
|
|
}
|
|
|
|
final gameState = GameState(
|
|
targetWord: TARGET_WORD,
|
|
guesses: guesses,
|
|
currentAttempt: currentRow,
|
|
isGameComplete: gameWon || gameLost,
|
|
isWon: gameWon,
|
|
solveTime: gameWon ? DateTime.now() : null,
|
|
gameDate: PuzzleService.getTodayDateString(),
|
|
);
|
|
|
|
await PuzzleService.saveGameState(gameState);
|
|
}
|
|
|
|
void _initializeAnimations() {
|
|
_revealController = AnimationController(
|
|
duration: Duration(milliseconds: 1500),
|
|
vsync: this,
|
|
);
|
|
|
|
_tileAnimations = List.generate(
|
|
WORD_LENGTH,
|
|
(index) => Tween<double>(begin: 0.0, end: 1.0).animate(
|
|
CurvedAnimation(
|
|
parent: _revealController,
|
|
curve: Interval(
|
|
index * 0.1,
|
|
(index * 0.1) + 0.5,
|
|
curve: Curves.elasticOut,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _initializeKeyboardStates() {
|
|
for (int i = 65; i <= 90; i++) {
|
|
keyboardStates[String.fromCharCode(i)] = LetterState.empty;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_revealController.dispose();
|
|
_focusNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _addLetter(String letter) {
|
|
if (currentCol < WORD_LENGTH &&
|
|
currentRow < MAX_ATTEMPTS &&
|
|
!gameWon &&
|
|
!gameLost) {
|
|
setState(() {
|
|
grid[currentRow][currentCol] = letter;
|
|
currentCol++;
|
|
});
|
|
_saveGameState();
|
|
HapticFeedback.lightImpact();
|
|
}
|
|
}
|
|
|
|
void _deleteLetter() {
|
|
if (currentCol > 0 && !gameWon && !gameLost) {
|
|
setState(() {
|
|
currentCol--;
|
|
grid[currentRow][currentCol] = '';
|
|
});
|
|
_saveGameState();
|
|
HapticFeedback.lightImpact();
|
|
}
|
|
}
|
|
|
|
Future<void> _submitWord() async {
|
|
if (currentCol == WORD_LENGTH && !gameWon && !gameLost) {
|
|
String guess = grid[currentRow].join('');
|
|
List<LetterState> newStates = _evaluateGuess(guess);
|
|
|
|
// Set the states and start animating the current row
|
|
setState(() {
|
|
gridStates[currentRow] = newStates;
|
|
_updateKeyboardStates(guess, newStates);
|
|
animatingRow = currentRow;
|
|
});
|
|
|
|
// Start the reveal animation
|
|
_revealController.reset();
|
|
await _revealController.forward();
|
|
|
|
// Clear the animating row and move to next row
|
|
setState(() {
|
|
animatingRow = -1;
|
|
});
|
|
|
|
if (guess == TARGET_WORD) {
|
|
setState(() {
|
|
gameWon = true;
|
|
});
|
|
await _saveGameState();
|
|
await _submitAttempt(currentRow + 1, true);
|
|
HapticFeedback.heavyImpact();
|
|
_showCompletedGameUI();
|
|
} else if (currentRow == MAX_ATTEMPTS - 1) {
|
|
setState(() {
|
|
gameLost = true;
|
|
});
|
|
await _saveGameState();
|
|
await _submitAttempt(MAX_ATTEMPTS, false);
|
|
HapticFeedback.heavyImpact();
|
|
_showCompletedGameUI();
|
|
} else {
|
|
setState(() {
|
|
currentRow++;
|
|
currentCol = 0;
|
|
});
|
|
await _saveGameState();
|
|
}
|
|
}
|
|
}
|
|
|
|
List<LetterState> _evaluateGuess(String guess) {
|
|
List<LetterState> states = List.filled(WORD_LENGTH, LetterState.absent);
|
|
List<String> targetLetters = TARGET_WORD.split('');
|
|
List<bool> used = List.filled(WORD_LENGTH, false);
|
|
|
|
// First pass: check for correct positions
|
|
for (int i = 0; i < WORD_LENGTH; i++) {
|
|
if (guess[i] == targetLetters[i]) {
|
|
states[i] = LetterState.correct;
|
|
used[i] = true;
|
|
}
|
|
}
|
|
|
|
// Second pass: check for present letters
|
|
for (int i = 0; i < WORD_LENGTH; i++) {
|
|
if (states[i] != LetterState.correct) {
|
|
for (int j = 0; j < WORD_LENGTH; j++) {
|
|
if (!used[j] && guess[i] == targetLetters[j]) {
|
|
states[i] = LetterState.present;
|
|
used[j] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return states;
|
|
}
|
|
|
|
void _updateKeyboardStates(String guess, List<LetterState> states) {
|
|
for (int i = 0; i < guess.length; i++) {
|
|
String letter = guess[i];
|
|
LetterState currentState = keyboardStates[letter] ?? LetterState.empty;
|
|
LetterState newState = states[i];
|
|
|
|
if (currentState == LetterState.correct) continue;
|
|
if (currentState == LetterState.present && newState == LetterState.absent)
|
|
continue;
|
|
|
|
keyboardStates[letter] = newState;
|
|
}
|
|
}
|
|
|
|
Future<void> _submitAttempt(int attempts, bool solved) async {
|
|
try {
|
|
await PuzzleService.submitAttempt(attempts: attempts, solved: solved);
|
|
} catch (e) {
|
|
print('Failed to submit attempt: $e');
|
|
}
|
|
}
|
|
|
|
void _showCompletedGameUI() {
|
|
// Remove the old dialog and show a new completed game state
|
|
// This will be handled in the UI by showing a different keyboard/message area
|
|
}
|
|
|
|
void _goToLeaderboard() {
|
|
Navigator.pop(
|
|
context,
|
|
); // Go back to puzzles page which will show leaderboard option
|
|
}
|
|
|
|
void _showGameEndDialog(bool won) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
title: Text(won ? 'Congratulations!' : 'Game Over'),
|
|
content: Text(
|
|
won ? 'You solved today\'s puzzle!' : 'The word was: $TARGET_WORD',
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
_resetGame();
|
|
},
|
|
child: Text(
|
|
'Play Again',
|
|
style: TextStyle(color: Color(0xFF6A4C93)),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
void _resetGame() {
|
|
setState(() {
|
|
grid = List.generate(MAX_ATTEMPTS, (_) => List.filled(WORD_LENGTH, ''));
|
|
gridStates = List.generate(
|
|
MAX_ATTEMPTS,
|
|
(_) => List.filled(WORD_LENGTH, LetterState.empty),
|
|
);
|
|
currentRow = 0;
|
|
currentCol = 0;
|
|
animatingRow = -1;
|
|
gameWon = false;
|
|
gameLost = false;
|
|
_initializeKeyboardStates();
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return PopScope(
|
|
canPop: false,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
// Prevent mobile keyboard from showing up
|
|
FocusScope.of(context).unfocus();
|
|
// Only request focus on web/desktop platforms
|
|
if (kIsWeb || (!kIsWeb && !Platform.isIOS && !Platform.isAndroid)) {
|
|
_focusNode.requestFocus();
|
|
}
|
|
},
|
|
child: RawKeyboardListener(
|
|
focusNode: _focusNode,
|
|
onKey: _handleRawKeyEvent,
|
|
child: Scaffold(
|
|
backgroundColor: Colors.white,
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.white,
|
|
elevation: 0,
|
|
leading: IconButton(
|
|
icon: Icon(Icons.arrow_back, color: Color(0xFF6A4C93)),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
centerTitle: true,
|
|
title: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'وصال',
|
|
style: TextStyle(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w200,
|
|
fontFamily: 'Blaka',
|
|
color: Color(0xFF6A4C93),
|
|
),
|
|
),
|
|
SizedBox(width: 8),
|
|
Text(
|
|
'Daily Challenge',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
bottom: PreferredSize(
|
|
preferredSize: Size.fromHeight(1),
|
|
child: Container(height: 1, color: Colors.grey[200]),
|
|
),
|
|
),
|
|
body: SafeArea(
|
|
child: LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
double screenHeight = constraints.maxHeight;
|
|
double keyboardHeight = (gameWon || gameLost) ? 200 : 200; // Estimated heights
|
|
double availableHeight = screenHeight - keyboardHeight;
|
|
|
|
return Column(
|
|
children: [
|
|
// Game Grid Section
|
|
Expanded(
|
|
child: Container(
|
|
height: availableHeight,
|
|
child: Center(
|
|
child: Container(
|
|
constraints: BoxConstraints(maxWidth: 350),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
// Game Grid
|
|
Container(
|
|
padding: EdgeInsets.all(16),
|
|
child: Column(
|
|
children: List.generate(MAX_ATTEMPTS, (row) {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 2),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: List.generate(WORD_LENGTH, (col) {
|
|
bool shouldAnimate =
|
|
row == animatingRow &&
|
|
gridStates[row][col] !=
|
|
LetterState.empty;
|
|
|
|
return Container(
|
|
margin: EdgeInsets.all(2),
|
|
width: 62,
|
|
height: 62,
|
|
child: shouldAnimate
|
|
? AnimatedBuilder(
|
|
animation: _tileAnimations[col],
|
|
builder: (context, child) {
|
|
double scale =
|
|
0.8 +
|
|
(0.2 *
|
|
_tileAnimations[col]
|
|
.value);
|
|
return Transform.scale(
|
|
scale: scale,
|
|
child: Container(
|
|
width: 62,
|
|
height: 62,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color:
|
|
_getBorderColor(
|
|
row,
|
|
col,
|
|
),
|
|
width: 2,
|
|
),
|
|
color:
|
|
_getAnimatedTileColor(
|
|
row,
|
|
col,
|
|
_tileAnimations[col]
|
|
.value,
|
|
),
|
|
borderRadius:
|
|
BorderRadius.circular(
|
|
4,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
grid[row][col],
|
|
style: TextStyle(
|
|
fontSize: 32,
|
|
fontWeight:
|
|
FontWeight.bold,
|
|
color: _getAnimatedTextColor(
|
|
row,
|
|
col,
|
|
_tileAnimations[col]
|
|
.value,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
)
|
|
: Container(
|
|
width: 62,
|
|
height: 62,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color: _getBorderColor(
|
|
row,
|
|
col,
|
|
),
|
|
width: 2,
|
|
),
|
|
color: _getTileColor(
|
|
row,
|
|
col,
|
|
),
|
|
borderRadius:
|
|
BorderRadius.circular(4),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
grid[row][col],
|
|
style: TextStyle(
|
|
fontSize: 32,
|
|
fontWeight:
|
|
FontWeight.bold,
|
|
color: _getTextColor(
|
|
row,
|
|
col,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
// Spacer between grid and keyboard
|
|
SizedBox(height: 16),
|
|
// Keyboard or completion message section
|
|
Container(
|
|
constraints: BoxConstraints(
|
|
minHeight: 200,
|
|
maxHeight: 250,
|
|
),
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16),
|
|
child: (gameWon || gameLost)
|
|
? _buildCompletionMessage()
|
|
: _buildKeyboard(),
|
|
),
|
|
// Bottom padding to ensure keyboard isn't at the very bottom
|
|
SizedBox(height: MediaQuery.of(context).padding.bottom + 8),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCompletionMessage() {
|
|
String message;
|
|
String buttonText = 'Go to Leaderboard';
|
|
Color messageColor;
|
|
IconData messageIcon;
|
|
|
|
if (gameWon) {
|
|
if (widget.dailyChallenge?.solved == true &&
|
|
widget.dailyChallenge?.solveTime != null) {
|
|
// Already completed today
|
|
message =
|
|
'You solved this at ${PuzzleService.formatSolveTime(widget.dailyChallenge!.solveTime!)} in ${widget.dailyChallenge!.attempts} attempts!';
|
|
} else {
|
|
// Just completed now
|
|
message = 'You got it!\nSolved in ${currentRow + 1} attempts!';
|
|
}
|
|
messageColor = Colors.green;
|
|
messageIcon = Icons.celebration;
|
|
} else {
|
|
if (widget.dailyChallenge?.attempted == true) {
|
|
// Already attempted today
|
|
message =
|
|
'You attempted this challenge but didn\'t solve it in ${widget.dailyChallenge!.attempts} attempts.\nThe word was: $TARGET_WORD';
|
|
} else {
|
|
// Just failed now
|
|
message = 'Better luck next time!\nThe word was: $TARGET_WORD';
|
|
}
|
|
messageColor = Colors.orange;
|
|
messageIcon = Icons.sentiment_neutral;
|
|
}
|
|
|
|
return Column(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(24),
|
|
margin: EdgeInsets.symmetric(horizontal: 16),
|
|
decoration: BoxDecoration(
|
|
color: messageColor.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: messageColor.withOpacity(0.3), width: 2),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Icon(messageIcon, color: messageColor, size: 48),
|
|
SizedBox(height: 16),
|
|
Text(
|
|
message,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 24),
|
|
ElevatedButton(
|
|
onPressed: _goToLeaderboard,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Color(0xFF6A4C93),
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(25),
|
|
),
|
|
elevation: 4,
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.leaderboard, size: 20),
|
|
SizedBox(width: 8),
|
|
Text(
|
|
buttonText,
|
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildKeyboard() {
|
|
List<List<String>> keyboardLayout = [
|
|
['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
|
|
['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'],
|
|
['ENTER', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '⌫'],
|
|
];
|
|
|
|
return Column(
|
|
children: keyboardLayout.map((row) {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 2),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: row.map((key) {
|
|
bool isSpecial = key == 'ENTER' || key == '⌫';
|
|
double width = isSpecial ? 65 : 35;
|
|
|
|
return Container(
|
|
margin: EdgeInsets.symmetric(horizontal: 1.5),
|
|
width: width,
|
|
height: 58,
|
|
child: Material(
|
|
color: _getKeyColor(key),
|
|
borderRadius: BorderRadius.circular(4),
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(4),
|
|
onTap: () => _handleKeyTap(key),
|
|
child: Container(
|
|
alignment: Alignment.center,
|
|
child: Text(
|
|
key,
|
|
style: TextStyle(
|
|
fontSize: isSpecial ? 12 : 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: _getKeyTextColor(key),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
);
|
|
}).toList(),
|
|
);
|
|
}
|
|
|
|
void _handleKeyTap(String key) {
|
|
if (key == 'ENTER') {
|
|
_submitWord();
|
|
} else if (key == '⌫') {
|
|
_deleteLetter();
|
|
} else {
|
|
_addLetter(key);
|
|
}
|
|
}
|
|
|
|
void _handleRawKeyEvent(RawKeyEvent event) {
|
|
if (event is RawKeyDownEvent) {
|
|
if (event.logicalKey == LogicalKeyboardKey.enter) {
|
|
_submitWord();
|
|
} else if (event.logicalKey == LogicalKeyboardKey.backspace) {
|
|
_deleteLetter();
|
|
} else if (event.character != null && event.character!.isNotEmpty) {
|
|
String key = event.character!.toUpperCase();
|
|
if (key.codeUnitAt(0) >= 65 && key.codeUnitAt(0) <= 90) {
|
|
_addLetter(key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Color _getBorderColor(int row, int col) {
|
|
if (row >= MAX_ATTEMPTS || col >= WORD_LENGTH) return Colors.grey[300]!;
|
|
if (row < currentRow || (row == currentRow && gameWon)) {
|
|
return Colors.transparent;
|
|
} else if (row == currentRow && col < currentCol) {
|
|
return Colors.grey[600]!;
|
|
} else {
|
|
return Colors.grey[300]!;
|
|
}
|
|
}
|
|
|
|
Color _getTileColor(int row, int col) {
|
|
if (row >= MAX_ATTEMPTS || col >= WORD_LENGTH) return Colors.white;
|
|
if (row >= currentRow && !gameWon) return Colors.white;
|
|
|
|
switch (gridStates[row][col]) {
|
|
case LetterState.correct:
|
|
return Color(0xFF6AAE7C);
|
|
case LetterState.present:
|
|
return Color(0xFFC9B037);
|
|
case LetterState.absent:
|
|
return Color(0xFF787C7E);
|
|
default:
|
|
return Colors.white;
|
|
}
|
|
}
|
|
|
|
Color _getTextColor(int row, int col) {
|
|
if (row >= MAX_ATTEMPTS || col >= WORD_LENGTH) return Colors.black;
|
|
if (row >= currentRow && !gameWon) return Colors.black;
|
|
return gridStates[row][col] == LetterState.empty
|
|
? Colors.black
|
|
: Colors.white;
|
|
}
|
|
|
|
Color _getAnimatedTileColor(int row, int col, double animationValue) {
|
|
if (row >= MAX_ATTEMPTS || col >= WORD_LENGTH) return Colors.white;
|
|
if (row != animatingRow || gridStates[row][col] == LetterState.empty) {
|
|
return _getTileColor(row, col);
|
|
}
|
|
|
|
// Show color based on animation progress
|
|
Color targetColor;
|
|
switch (gridStates[row][col]) {
|
|
case LetterState.correct:
|
|
targetColor = Color(0xFF6AAE7C);
|
|
break;
|
|
case LetterState.present:
|
|
targetColor = Color(0xFFC9B037);
|
|
break;
|
|
case LetterState.absent:
|
|
targetColor = Color(0xFF787C7E);
|
|
break;
|
|
default:
|
|
targetColor = Colors.white;
|
|
}
|
|
|
|
return Color.lerp(Colors.white, targetColor, animationValue) ??
|
|
Colors.white;
|
|
}
|
|
|
|
Color _getAnimatedTextColor(int row, int col, double animationValue) {
|
|
if (row >= MAX_ATTEMPTS || col >= WORD_LENGTH) return Colors.black;
|
|
if (row != animatingRow || gridStates[row][col] == LetterState.empty) {
|
|
return _getTextColor(row, col);
|
|
}
|
|
|
|
// Text color changes with animation
|
|
return Color.lerp(Colors.black, Colors.white, animationValue) ??
|
|
Colors.black;
|
|
}
|
|
|
|
Color _getKeyColor(String key) {
|
|
if (key == 'ENTER' || key == '⌫') {
|
|
return Colors.grey[300]!;
|
|
}
|
|
|
|
LetterState state = keyboardStates[key] ?? LetterState.empty;
|
|
switch (state) {
|
|
case LetterState.correct:
|
|
return Color(0xFF6AAE7C);
|
|
case LetterState.present:
|
|
return Color(0xFFC9B037);
|
|
case LetterState.absent:
|
|
return Color(0xFF787C7E);
|
|
default:
|
|
return Colors.grey[200]!;
|
|
}
|
|
}
|
|
|
|
Color _getKeyTextColor(String key) {
|
|
if (key == 'ENTER' || key == '⌫') {
|
|
return Colors.black;
|
|
}
|
|
|
|
LetterState state = keyboardStates[key] ?? LetterState.empty;
|
|
return state == LetterState.empty ? Colors.black : Colors.white;
|
|
}
|
|
}
|
|
|
|
enum LetterState { empty, absent, present, correct }
|