mirror of
https://forgejo.allbyte.fr/nono/flutter_wordle
synced 2026-03-14 21:15:45 +01:00
272 lines
7.3 KiB
Dart
272 lines
7.3 KiB
Dart
import 'package:diacritic/diacritic.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:wordle/db/board.dart';
|
|
import 'package:wordle/db/classic_game.dart';
|
|
import 'package:wordle/db/classic_game_provider.dart';
|
|
import 'package:wordle/word_loader.dart';
|
|
|
|
import 'guess_row.dart';
|
|
import 'letter_keyboard.dart';
|
|
|
|
class GameData {
|
|
final String correctWord;
|
|
final int maxGuesses;
|
|
final List<String> guesses;
|
|
final bool hasWon;
|
|
|
|
const GameData({
|
|
required this.correctWord,
|
|
required this.maxGuesses,
|
|
required this.guesses,
|
|
required this.hasWon,
|
|
});
|
|
}
|
|
|
|
class GameScreen extends StatefulWidget {
|
|
final int maxGuesses;
|
|
final List<String>? guesses;
|
|
final String correctWord;
|
|
final bool readOnly;
|
|
final void Function(GameData data)? onGameEnd;
|
|
|
|
const GameScreen({
|
|
super.key,
|
|
required this.maxGuesses,
|
|
required this.correctWord,
|
|
this.guesses,
|
|
this.readOnly = false,
|
|
this.onGameEnd,
|
|
});
|
|
|
|
@override
|
|
_GameScreenState createState() => _GameScreenState();
|
|
}
|
|
|
|
class _GameScreenState extends State<GameScreen> {
|
|
late List<String> guesses;
|
|
String currentGuess = "";
|
|
int currentRowIndex = 0;
|
|
bool gameEnded = false;
|
|
String? _errorMessage;
|
|
late ScrollController _scrollController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_scrollController = ScrollController();
|
|
|
|
if (widget.readOnly) {
|
|
guesses = widget.guesses ?? [];
|
|
currentRowIndex = guesses.length;
|
|
gameEnded = true;
|
|
} else {
|
|
guesses = List.generate(widget.maxGuesses, (_) => "");
|
|
}
|
|
}
|
|
|
|
void _resetGame() {
|
|
setState(() {
|
|
guesses = List.generate(widget.maxGuesses, (_) => "");
|
|
currentRowIndex = 0;
|
|
currentGuess = "";
|
|
gameEnded = false;
|
|
_errorMessage = null;
|
|
});
|
|
}
|
|
|
|
void onLetterPressed(String letter) {
|
|
if (currentGuess.length < widget.correctWord.length && !gameEnded) {
|
|
setState(() {
|
|
currentGuess += letter;
|
|
guesses[currentRowIndex] = currentGuess;
|
|
_errorMessage = null;
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_scrollToCurrentGuess();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
void onBackspacePressed() {
|
|
if (currentGuess.isNotEmpty && !gameEnded) {
|
|
setState(() {
|
|
currentGuess = currentGuess.substring(0, currentGuess.length - 1);
|
|
guesses[currentRowIndex] = currentGuess;
|
|
_errorMessage = null;
|
|
});
|
|
}
|
|
}
|
|
|
|
void onSubmitGuess() async {
|
|
if (currentGuess.length == widget.correctWord.length && !gameEnded) {
|
|
bool isValidWord = await WordLoader.isValidWord(currentGuess);
|
|
if (!isValidWord) {
|
|
setState(() {
|
|
_errorMessage = 'Invalid word. Please enter a valid word.';
|
|
});
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
guesses[currentRowIndex] = currentGuess;
|
|
|
|
if (currentGuess.toUpperCase() ==
|
|
removeDiacritics(widget.correctWord.toUpperCase())) {
|
|
gameEnded = true;
|
|
_handleGameEnd(GameData(
|
|
correctWord: widget.correctWord,
|
|
maxGuesses: widget.maxGuesses,
|
|
guesses: guesses,
|
|
hasWon: true));
|
|
} else if (currentRowIndex == widget.maxGuesses - 1) {
|
|
gameEnded = true;
|
|
_handleGameEnd(GameData(
|
|
correctWord: widget.correctWord,
|
|
maxGuesses: widget.maxGuesses,
|
|
guesses: guesses,
|
|
hasWon: false));
|
|
} else {
|
|
currentRowIndex++;
|
|
currentGuess = "";
|
|
_errorMessage = null;
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_scrollToCurrentGuess();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> saveGame(GameData data) async {
|
|
final game = DbClassicGame(
|
|
date: DateTime.now(),
|
|
board: DbBoard(
|
|
correctWord: data.correctWord,
|
|
maxGuesses: data.maxGuesses,
|
|
guesses: data.guesses,
|
|
hasWon: data.hasWon),
|
|
);
|
|
|
|
final gameProvider =
|
|
Provider.of<DbClassicGameProvider>(context, listen: false);
|
|
|
|
await gameProvider.insertClassicGame(game);
|
|
}
|
|
|
|
void _handleGameEnd(GameData data) {
|
|
if (widget.onGameEnd != null) {
|
|
widget.onGameEnd!(data);
|
|
} else {
|
|
saveGame(data);
|
|
showDefaultEndGameDialog(
|
|
data.hasWon ? "You Win!" : "Game Over",
|
|
data.hasWon
|
|
? "Congratulations, you guessed the correct word!"
|
|
: "You've used all your guesses. The correct word was \"${widget.correctWord}\".",
|
|
"Return to the main screen",
|
|
);
|
|
}
|
|
_resetGame();
|
|
}
|
|
|
|
Future<void> _scrollToCurrentGuess() async {
|
|
double rowHeight = 70.0;
|
|
double offset = (currentRowIndex * rowHeight) -
|
|
(MediaQuery.of(context).size.height / 2) +
|
|
(rowHeight / 2);
|
|
|
|
offset = offset < 0 ? 0 : offset;
|
|
|
|
_scrollController.animateTo(
|
|
offset,
|
|
duration: const Duration(milliseconds: 500),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
}
|
|
|
|
void showDefaultEndGameDialog(String title, String message, String btnText) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
title: Text(title),
|
|
content: Text(message),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: Text(btnText),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_scrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: ListView.builder(
|
|
controller: _scrollController,
|
|
itemCount: guesses.length,
|
|
itemBuilder: (context, index) {
|
|
return GuessRow(
|
|
guess: guesses[index],
|
|
correctWord: widget.correctWord,
|
|
evaluateColors: index < currentRowIndex || widget.readOnly,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
if (_errorMessage != null) ...[
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
|
child: Text(
|
|
_errorMessage!,
|
|
style: const TextStyle(color: Colors.red, fontSize: 16),
|
|
),
|
|
),
|
|
],
|
|
if (!widget.readOnly) ...[
|
|
ElevatedButton(
|
|
onPressed:
|
|
currentGuess.length == widget.correctWord.length && !gameEnded
|
|
? onSubmitGuess
|
|
: null,
|
|
style: ElevatedButton.styleFrom(
|
|
foregroundColor: Colors.white,
|
|
backgroundColor:
|
|
currentGuess.length == widget.correctWord.length && !gameEnded
|
|
? const Color(0xFF4F545C)
|
|
: const Color(0xFF2C2F33),
|
|
),
|
|
child: const Text("Submit Guess"),
|
|
),
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
|
child: LetterKeyboard(
|
|
onLetterPressed: onLetterPressed,
|
|
onBackspacePressed: onBackspacePressed,
|
|
),
|
|
),
|
|
]
|
|
],
|
|
);
|
|
}
|
|
}
|