import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/foundation.dart'; import 'dart:io'; class WordlePage extends StatefulWidget { @override _WordlePageState createState() => _WordlePageState(); } class _WordlePageState extends State with TickerProviderStateMixin { static const String TARGET_WORD = "HELLO"; static const int MAX_ATTEMPTS = 6; static const int WORD_LENGTH = 5; List> grid = List.generate(MAX_ATTEMPTS, (_) => List.filled(WORD_LENGTH, '')); List> gridStates = List.generate(MAX_ATTEMPTS, (_) => List.filled(WORD_LENGTH, LetterState.empty)); Map keyboardStates = {}; int currentRow = 0; int currentCol = 0; bool gameWon = false; bool gameLost = false; late List _flipControllers; late List> _flipAnimations; late FocusNode _focusNode; @override void initState() { super.initState(); _initializeAnimations(); _initializeKeyboardStates(); _focusNode = FocusNode(); // Request focus for keyboard input on web/desktop if (kIsWeb || (!kIsWeb && !Platform.isIOS && !Platform.isAndroid)) { WidgetsBinding.instance.addPostFrameCallback((_) { _focusNode.requestFocus(); }); } } void _initializeAnimations() { _flipControllers = List.generate( WORD_LENGTH, (index) => AnimationController( duration: Duration(milliseconds: 600), vsync: this, ), ); _flipAnimations = _flipControllers.map((controller) => Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: controller, curve: Curves.easeInOut) ) ).toList(); } void _initializeKeyboardStates() { for (int i = 65; i <= 90; i++) { keyboardStates[String.fromCharCode(i)] = LetterState.empty; } } @override void dispose() { for (var controller in _flipControllers) { controller.dispose(); } _focusNode.dispose(); super.dispose(); } void _addLetter(String letter) { if (currentCol < WORD_LENGTH && currentRow < MAX_ATTEMPTS && !gameWon && !gameLost) { setState(() { grid[currentRow][currentCol] = letter; currentCol++; }); HapticFeedback.lightImpact(); } } void _deleteLetter() { if (currentCol > 0 && !gameWon && !gameLost) { setState(() { currentCol--; grid[currentRow][currentCol] = ''; }); HapticFeedback.lightImpact(); } } Future _submitWord() async { if (currentCol == WORD_LENGTH && !gameWon && !gameLost) { String guess = grid[currentRow].join(''); // Animate the flip for (int i = 0; i < WORD_LENGTH; i++) { await Future.delayed(Duration(milliseconds: 100)); _flipControllers[i].forward(); } await Future.delayed(Duration(milliseconds: 300)); List newStates = _evaluateGuess(guess); setState(() { gridStates[currentRow] = newStates; _updateKeyboardStates(guess, newStates); }); // Reset animations for (var controller in _flipControllers) { controller.reset(); } if (guess == TARGET_WORD) { setState(() { gameWon = true; }); HapticFeedback.heavyImpact(); _showGameEndDialog(true); } else if (currentRow == MAX_ATTEMPTS - 1) { setState(() { gameLost = true; }); HapticFeedback.heavyImpact(); _showGameEndDialog(false); } else { setState(() { currentRow++; currentCol = 0; }); } } } List _evaluateGuess(String guess) { List states = List.filled(WORD_LENGTH, LetterState.absent); List targetLetters = TARGET_WORD.split(''); List 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 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; } } 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; 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: KeyboardListener( focusNode: _focusNode, onKeyEvent: _handleKeyEvent, child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, elevation: 0, automaticallyImplyLeading: false, 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: Column( children: [ Expanded( 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) { return Container( margin: EdgeInsets.all(2), width: 62, height: 62, child: AnimatedBuilder( animation: _flipAnimations[col], builder: (context, child) { // Check if this tile should be animating bool isCurrentRowAndAnimating = row == currentRow && _flipAnimations[col].value > 0; // If not animating, show the final state if (!isCurrentRowAndAnimating) { return Container( width: 62, height: 62, decoration: BoxDecoration( border: Border.all( color: _getBorderColor(row, col), width: 2, ), color: _getTileColor(row, col), ), child: Center( child: Text( grid[row][col], style: TextStyle( fontSize: 32, fontWeight: FontWeight.bold, color: _getTextColor(row, col), ), ), ), ); } // Animation is running - create flip effect double progress = _flipAnimations[col].value; double rotationX = progress * 3.14159; // Determine colors to show during animation bool showFinalColors = progress > 0.5; Color bgColor = showFinalColors ? _getTileColor(row, col) : Colors.white; Color textColor = showFinalColors ? _getTextColor(row, col) : Colors.black; return Container( width: 62, height: 62, decoration: BoxDecoration( border: Border.all( color: _getBorderColor(row, col), width: 2, ), color: Colors.white, ), child: Stack( children: [ // Animated background that flips Transform( alignment: Alignment.center, transform: Matrix4.identity() ..setEntry(3, 2, 0.001) ..rotateX(rotationX), child: Container( width: 62, height: 62, color: bgColor, ), ), // Text that always stays upright and visible Center( child: Text( grid[row][col], style: TextStyle( fontSize: 32, fontWeight: FontWeight.bold, color: textColor, ), ), ), ], ), ); }, ), ); }), ), ); }), ), ), ], ), ), ), ), // Keyboard Container( padding: EdgeInsets.all(8), child: _buildKeyboard(), ), ], ), ), ), ), ); } Widget _buildKeyboard() { List> 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); } } bool _handleKeyEvent(KeyEvent event) { if (event is KeyDownEvent) { if (event.logicalKey == LogicalKeyboardKey.enter) { _submitWord(); return true; } else if (event.logicalKey == LogicalKeyboardKey.backspace) { _deleteLetter(); return true; } else if (event.logicalKey.keyLabel.length == 1) { String key = event.logicalKey.keyLabel.toUpperCase(); if (key.codeUnitAt(0) >= 65 && key.codeUnitAt(0) <= 90) { _addLetter(key); return true; } } } return false; } 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 _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, }