Initial commit

This commit is contained in:
nono 2025-06-19 13:38:17 +02:00
commit d0dff4834a
53 changed files with 3156 additions and 0 deletions

43
.gitignore vendored Normal file
View file

@ -0,0 +1,43 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

30
.metadata Normal file
View file

@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: android
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

16
README.md Normal file
View file

@ -0,0 +1,16 @@
# wordle
Wordle
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

28
analysis_options.yaml Normal file
View file

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

13
android/.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

44
android/app/build.gradle Normal file
View file

@ -0,0 +1,44 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
android {
namespace = "fr.achevalm.wordle"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "fr.achevalm.wordle"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.debug
}
}
}
flutter {
source = "../.."
}

View file

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View file

@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="wordle"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View file

@ -0,0 +1,5 @@
package fr.achevalm.wordle
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

18
android/build.gradle Normal file
View file

@ -0,0 +1,18 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View file

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip

25
android/settings.gradle Normal file
View file

@ -0,0 +1,25 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.0" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
}
include ":app"

File diff suppressed because one or more lines are too long

1
assets/french_words.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
devtools_options.yaml Normal file
View file

@ -0,0 +1,4 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
- provider: true

36
lib/LetterColoration.dart Normal file
View file

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
class LetterColoration {
static List<Color> getColorFeedback(
String guess, String targetWord, bool submitted) {
if (!submitted) {
return List.filled(targetWord.length, Colors.grey[800]!);
}
List<Color> feedbackColors =
List.filled(targetWord.length, Colors.grey[700]!);
List<bool> targetMatched = List.filled(targetWord.length, false);
for (int i = 0; i < guess.length; i++) {
if (guess[i] == targetWord[i]) {
feedbackColors[i] = Colors.green;
targetMatched[i] = true;
}
}
for (int i = 0; i < guess.length; i++) {
if (feedbackColors[i] == Colors.green) {
continue;
}
for (int j = 0; j < targetWord.length; j++) {
if (!targetMatched[j] && guess[i] == targetWord[j]) {
feedbackColors[i] = Colors.orange;
targetMatched[j] = true;
break;
}
}
}
return feedbackColors;
}
}

View file

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wordle/db/classic_game.dart';
import 'db/classic_game_provider.dart';
import 'game_screen.dart';
class ClassicGameHistoryScreen extends StatelessWidget {
const ClassicGameHistoryScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Classic Game History'),
),
body: FutureBuilder<List<DbClassicGame>>(
future: Provider.of<DbClassicGameProvider>(context, listen: false)
.getAllClassicGames(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text('No classic games found.'));
} else {
final List<DbClassicGame> games = snapshot.data!;
return ListView.builder(
itemCount: games.length,
itemBuilder: (context, index) {
final game = games[index];
return Card(
margin:
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: ListTile(
title: Text('Game on ${game.date.toString()}'),
subtitle: Text(
'Word: ${game.board.correctWord}, Max Guesses: ${game.board.maxGuesses}, Has Won: ${game.board.hasWon ? "Yes" : "No"}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text(
game.board.correctWord.toUpperCase()),
),
body: Center(
child: GameScreen(
guesses: game.board.guesses,
maxGuesses: game.board.maxGuesses,
correctWord: game.board.correctWord,
readOnly: true,
),
),
)));
},
),
);
},
);
}
},
),
);
}
}

View file

@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
import 'package:wordle/game_screen.dart';
import 'package:wordle/validated_number_input.dart';
import 'package:wordle/word_loader.dart';
class ClassicGame extends StatefulWidget {
const ClassicGame({super.key});
@override
_ClassicGameState createState() => _ClassicGameState();
}
class _ClassicGameState extends State<ClassicGame> {
int selectedWordLength = 5;
int selectedMaxGuesses = 6;
bool isWordLengthValid = true;
bool isMaxGuessesValid = true;
late String correctWord;
void _resetDefaults() {
setState(() {
selectedWordLength = 5;
selectedMaxGuesses = 6;
});
}
bool get _canStartGame => isWordLengthValid && isMaxGuessesValid;
Future<void> _startGame() async {
String? word = await WordLoader.getWordOfLength(selectedWordLength);
if (word == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'No words available for the selected length. Please choose another length.'),
),
);
return;
} else {
correctWord = word;
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: const Text("Classic"),
),
body: Center(
child: GameScreen(
maxGuesses: selectedMaxGuesses,
correctWord: correctWord,
))),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Game Options"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
"Word Length",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8.0),
ValidatedNumberInput(
defaultValue: selectedWordLength,
minValue: 5,
maxValue: 25,
onValueChanged: (value) {
setState(() {
selectedWordLength = value;
});
},
onValidityChanged: (isValid) {
setState(() {
isWordLengthValid = isValid;
});
},
),
const SizedBox(height: 24.0),
const Text(
"Number of Guesses",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8.0),
ValidatedNumberInput(
defaultValue: selectedMaxGuesses,
minValue: 2,
onValueChanged: (value) {
setState(() {
selectedMaxGuesses = value;
});
},
onValidityChanged: (isValid) {
setState(() {
isMaxGuessesValid = isValid;
});
},
),
const SizedBox(height: 32.0),
ElevatedButton(
onPressed: _canStartGame ? _startGame : null,
child: const Text("Start Game"),
),
const SizedBox(height: 16.0),
ElevatedButton(
onPressed: _resetDefaults,
child: const Text("Reset to Default Values"),
),
],
),
),
);
}
}

42
lib/db/board.dart Normal file
View file

@ -0,0 +1,42 @@
const String columnId = 'id';
const String tableBoard = 'board';
const String columnCorrectWord = 'correctWord';
const String columnMaxGuesses = 'maxGuesses';
const String columnGuesses = 'guesses';
const String columnHasWon = 'hasWon';
class DbBoard {
int? id;
String correctWord;
int maxGuesses;
List<String> guesses;
bool hasWon;
DbBoard({
this.id,
required this.correctWord,
required this.maxGuesses,
required this.guesses,
required this.hasWon,
});
Map<String, Object?> toMap() {
return {
columnCorrectWord: correctWord,
columnMaxGuesses: maxGuesses,
columnGuesses: guesses.join(","),
columnHasWon: hasWon ? 1 : 0,
if (id != null) columnId: id,
};
}
factory DbBoard.fromMap(Map<String, Object?> map) {
return DbBoard(
id: map[columnId] as int?,
correctWord: map[columnCorrectWord] as String,
maxGuesses: map[columnMaxGuesses] as int,
guesses: (map[columnGuesses] as String).split(","),
hasWon: (map[columnHasWon] as int) == 1,
);
}
}

37
lib/db/classic_game.dart Normal file
View file

@ -0,0 +1,37 @@
import 'dart:convert';
import 'board.dart';
const String tableClassicHistory = 'classic_game_history';
const String columnBoardId = 'board_id';
const String columnId = 'id';
const String columnDate = 'date';
// DbClassicGame class
class DbClassicGame {
int? id;
DateTime date;
DbBoard board;
DbClassicGame({
this.id,
required this.date,
required this.board,
});
Map<String, Object?> toMap() {
return {
if (id != null) columnId: id,
columnDate: date.toIso8601String(),
'board': jsonEncode(board.toMap()),
};
}
factory DbClassicGame.fromMap(Map<String, Object?> map, DbBoard board) {
return DbClassicGame(
id: map[columnId] as int?,
date: DateTime.parse(map[columnDate] as String),
board: board,
);
}
}

View file

@ -0,0 +1,48 @@
import 'package:flutter/cupertino.dart';
import 'package:sqflite/sqflite.dart';
import 'board.dart';
import 'classic_game.dart';
class DbClassicGameProvider extends ChangeNotifier {
Database? _db;
void initialize(Database db) {
_db = db;
}
Future<void> insertClassicGame(DbClassicGame game) async {
if (_db == null) throw Exception('Database has not been initialized');
int boardId = await _db!.insert('board', game.board.toMap());
game.board.id = boardId;
await _db!.insert('classic_game_history', {
'date': game.date.toIso8601String(),
'board_id': boardId,
});
notifyListeners();
}
Future<List<DbClassicGame>> getAllClassicGames() async {
if (_db == null) throw Exception('Database has not been initialized');
final List<Map<String, Object?>> gameMaps =
await _db!.query('classic_game_history');
List<DbClassicGame> games = [];
for (var gameMap in gameMaps) {
final boardId = gameMap['board_id'] as int;
final List<Map<String, Object?>> boardMaps =
await _db!.query('board', where: 'id = ?', whereArgs: [boardId]);
if (boardMaps.isNotEmpty) {
final board = DbBoard.fromMap(boardMaps.first);
games.add(DbClassicGame(
id: gameMap['id'] as int?,
date: DateTime.parse(gameMap['date'] as String),
board: board,
));
}
}
return games;
}
}

35
lib/db/duel_game.dart Normal file
View file

@ -0,0 +1,35 @@
import 'board.dart';
class DbDuelGame {
int? id;
DateTime date;
int winner; // (0 for player 1, 1 for player 2)
int numberOfTurns;
int player1score;
int player2score;
List<DbBoard> player1games;
List<DbBoard> player2games;
DbDuelGame({
this.id,
required this.date,
required this.winner,
required this.numberOfTurns,
required this.player1score,
required this.player2score,
required this.player1games,
required this.player2games,
});
// Convert to a Map for inserting into SQLite (only metadata)
Map<String, Object?> toMap() {
return {
'date': date.toIso8601String(),
'winner': winner,
'numberOfTurns': numberOfTurns,
'player1score': player1score,
'player2score': player2score,
if (id != null) 'id': id,
};
}
}

View file

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:wordle/db/board.dart';
import 'package:wordle/db/duel_game.dart';
class DbDuelGameProvider extends ChangeNotifier {
Database? _db;
void initialize(Database db) {
_db = db;
}
Future<void> insert(DbDuelGame duelGame) async {
if (_db == null) {
throw Exception("Database has not been initialized");
}
// Insert the duel game metadata
int duelGameId = await _db!.insert('duel_game_history', duelGame.toMap());
// Insert each board for player 1 and player 2
for (DbBoard board in duelGame.player1games) {
await _db!.insert('duel_game_boards', {
'duel_game_id': duelGameId,
'player': 1,
...board.toMap(),
});
}
for (DbBoard board in duelGame.player2games) {
await _db!.insert('duel_game_boards', {
'duel_game_id': duelGameId,
'player': 2,
...board.toMap(),
});
}
notifyListeners();
}
Future<List<DbDuelGame>> getAllDuelGames() async {
if (_db == null) {
throw Exception("Database has not been initialized");
}
// Query duel games
List<Map<String, dynamic>> duelGameMaps =
await _db!.query('duel_game_history');
List<DbDuelGame> duelGames = [];
// For each duel game, retrieve the boards associated with it
for (var duelGameMap in duelGameMaps) {
int duelGameId = duelGameMap['id'];
// Query boards associated with this duel game
List<Map<String, dynamic>> boardMaps = await _db!.query(
'duel_game_boards',
where: 'duel_game_id = ?',
whereArgs: [duelGameId],
);
// Split boards into player 1 and player 2
List<DbBoard> player1games = [];
List<DbBoard> player2games = [];
for (var boardMap in boardMaps) {
DbBoard board = DbBoard.fromMap(boardMap);
if (boardMap['player'] == 1) {
player1games.add(board);
} else {
player2games.add(board);
}
}
// Create the DbDuelGame object
DbDuelGame duelGame = DbDuelGame(
id: duelGameMap['id'],
date: DateTime.parse(duelGameMap['date']),
winner: duelGameMap['winner'],
numberOfTurns: duelGameMap['numberOfTurns'],
player1score: duelGameMap['player1score'],
player2score: duelGameMap['player2score'],
player1games: player1games,
player2games: player2games,
);
duelGames.add(duelGame);
}
return duelGames;
}
}

40
lib/db/survival_game.dart Normal file
View file

@ -0,0 +1,40 @@
import 'dart:convert';
import 'board.dart';
const String tableSurvivalHistory = 'survival_game_history';
class DbSurvivalGame {
int? id;
DateTime date;
int maxLevelReached;
List<DbBoard> boards;
DbSurvivalGame({
this.id,
required this.date,
required this.maxLevelReached,
required this.boards,
});
Map<String, Object?> toMap() {
return {
'date': date.toIso8601String(),
'maxLevelReached': maxLevelReached,
'boards': jsonEncode(boards.map((board) => board.toMap()).toList()),
if (id != null) columnId: id,
};
}
factory DbSurvivalGame.fromMap(Map<String, Object?> map) {
List<dynamic> boardsJson = jsonDecode(map['boards'] as String);
return DbSurvivalGame(
id: map[columnId] as int?,
date: DateTime.parse(map['date'] as String),
maxLevelReached: map['maxLevelReached'] as int,
boards: boardsJson
.map((item) => DbBoard.fromMap(Map<String, Object?>.from(item)))
.toList(),
);
}
}

View file

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:wordle/db/survival_game.dart';
import 'board.dart';
class DbSurvivalGameProvider extends ChangeNotifier {
Database? _db;
void initialize(Database db) {
_db = db;
}
Future<void> insertSurvivalGame(DbSurvivalGame game) async {
if (_db == null) throw Exception('Database has not been initialized');
// Insert survival game
int survivalGameId = await _db!.insert('survival_game_history', {
'date': game.date.toIso8601String(),
'maxLevelReached': game.maxLevelReached,
});
// Insert boards associated with the survival game
for (var board in game.boards) {
int boardId = await _db!.insert('board', board.toMap());
await _db!.insert('survival_game_boards', {
'survival_game_id': survivalGameId,
'board_id': boardId,
});
}
notifyListeners();
}
Future<List<DbSurvivalGame>> getAllSurvivalGames() async {
if (_db == null) throw Exception('Database has not been initialized');
final List<Map<String, Object?>> gameMaps =
await _db!.query('survival_game_history');
List<DbSurvivalGame> games = [];
for (var gameMap in gameMaps) {
int survivalGameId = gameMap['id'] as int;
List<DbBoard> boards = [];
// Fetch boards associated with the survival game
final List<Map<String, Object?>> boardMaps = await _db!.query(
'survival_game_boards',
where: 'survival_game_id = ?',
whereArgs: [survivalGameId]);
for (var boardMap in boardMaps) {
final List<Map<String, Object?>> boardDetails = await _db!
.query('board', where: 'id = ?', whereArgs: [boardMap['board_id']]);
if (boardDetails.isNotEmpty) {
boards.add(DbBoard.fromMap(boardDetails.first));
}
}
games.add(DbSurvivalGame(
id: survivalGameId,
date: DateTime.parse(gameMap['date'] as String),
maxLevelReached: gameMap['maxLevelReached'] as int,
boards: boards,
));
}
return games;
}
}

View file

@ -0,0 +1,95 @@
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class WordleDatabase {
static final WordleDatabase _instance = WordleDatabase._internal();
Database? _db;
factory WordleDatabase() {
return _instance;
}
WordleDatabase._internal();
Future<Database> get database async {
if (_db != null) {
return _db!;
}
_db = await _initializeDatabase();
return _db!;
}
Future<Database> _initializeDatabase() async {
// Get the default database path
String path = join(await getDatabasesPath(), 'wordle_game.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
// Create tables for each game mode
await db.execute('''
CREATE TABLE board (
id INTEGER PRIMARY KEY AUTOINCREMENT,
correctWord TEXT NOT NULL,
maxGuesses INTEGER NOT NULL,
guesses TEXT NOT NULL,
hasWon INTEGER NOT NULL
)
''');
await db.execute('''
CREATE TABLE classic_game_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
board_id INTEGER NOT NULL,
FOREIGN KEY (board_id) REFERENCES board (id) ON DELETE CASCADE
)
''');
await db.execute('''
CREATE TABLE duel_game_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
winner INTEGER NOT NULL CHECK (winner IN (0, 1)),
numberOfTurns INTEGER NOT NULL,
player1score INTEGER NOT NULL,
player2score INTEGER NOT NULL
)
''');
await db.execute('''
CREATE TABLE duel_game_boards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
duel_game_id INTEGER NOT NULL,
player INTEGER NOT NULL CHECK (player IN (1, 2)),
correctWord TEXT NOT NULL,
maxGuesses INTEGER NOT NULL,
guesses TEXT NOT NULL,
hasWon INTEGER NOT NULL,
FOREIGN KEY (duel_game_id) REFERENCES duel_game_history (id)
)
''');
await db.execute('''
CREATE TABLE survival_game_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
maxLevelReached INTEGER NOT NULL
)
''');
await db.execute('''
CREATE TABLE survival_game_boards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
survival_game_id INTEGER NOT NULL,
board_id INTEGER NOT NULL,
FOREIGN KEY (survival_game_id) REFERENCES survival_game_history (id) ON DELETE CASCADE,
FOREIGN KEY (board_id) REFERENCES board (id) ON DELETE CASCADE
)
''');
},
);
}
}

142
lib/duel_game_history.dart Normal file
View file

@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wordle/db/duel_game.dart';
import 'db/duel_game_provider.dart';
import 'game_screen.dart';
class DuelGameHistoryScreen extends StatelessWidget {
const DuelGameHistoryScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Duel Game History'),
),
body: FutureBuilder<List<DbDuelGame>>(
future: Provider.of<DbDuelGameProvider>(context, listen: false)
.getAllDuelGames(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text('No duel games found.'));
} else {
final List<DbDuelGame> games = snapshot.data!;
return ListView.builder(
itemCount: games.length,
itemBuilder: (context, index) {
final game = games[index];
return Card(
margin:
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: ListTile(
title: Text('Game on ${game.date.toString()}'),
subtitle: Text(
'Player 1 Score: ${game.player1score}, Player 2 Score: ${game.player2score}, Winner: Player ${game.winner + 1}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title:
Text('Duel Game on ${game.date.toString()}'),
),
body: ListView(
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text('Player 1 Games',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold)),
),
...game.player1games.map((board) => Card(
margin: const EdgeInsets.symmetric(
vertical: 8, horizontal: 16),
child: ListTile(
title: Text(
'Word: ${board.correctWord}, Max Guesses: ${board.maxGuesses}, Has Won: ${board.hasWon ? "Yes" : "No"}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text(board.correctWord
.toUpperCase()),
),
body: Center(
child: GameScreen(
guesses: board.guesses,
maxGuesses:
board.maxGuesses,
correctWord:
board.correctWord,
readOnly: true,
),
),
),
),
);
},
),
)),
const Padding(
padding: EdgeInsets.all(16.0),
child: Text('Player 2 Games',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold)),
),
...game.player2games.map((board) => Card(
margin: const EdgeInsets.symmetric(
vertical: 8, horizontal: 16),
child: ListTile(
title: Text(
'Word: ${board.correctWord}, Max Guesses: ${board.maxGuesses}, Has Won: ${board.hasWon ? "Yes" : "No"}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text(board.correctWord
.toUpperCase()),
),
body: Center(
child: GameScreen(
guesses: board.guesses,
maxGuesses:
board.maxGuesses,
correctWord:
board.correctWord,
readOnly: true,
),
),
),
),
);
},
),
)),
],
),
),
),
);
},
),
);
},
);
}
},
),
);
}
}

217
lib/duel_game_screen.dart Normal file
View file

@ -0,0 +1,217 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wordle/db/duel_game.dart';
import 'package:wordle/db/duel_game_provider.dart';
import 'package:wordle/word_loader.dart';
import 'db/board.dart';
import 'game_screen.dart';
class DuelGameScreen extends StatefulWidget {
final int numberOfTurns;
const DuelGameScreen({super.key, required this.numberOfTurns});
@override
_DuelGameScreenState createState() => _DuelGameScreenState();
}
class _DuelGameScreenState extends State<DuelGameScreen> {
final _wordController = TextEditingController();
String? _errorMessage;
int _currentPlayer = 1;
final List<String> _playerWords = [];
int _player1Score = 0;
int _player2Score = 0;
int _turnsLeft;
bool _isGameStarted = false;
String _currentCorrectWord = "";
final List<DbBoard> _player1Games = [];
final List<DbBoard> _player2Games = [];
_DuelGameScreenState() : _turnsLeft = 0;
@override
void initState() {
super.initState();
_turnsLeft = widget.numberOfTurns;
}
void _submitWord() async {
String enteredWord = _wordController.text.trim().toUpperCase();
bool validWord = await WordLoader.isValidWord(enteredWord);
if (validWord) {
setState(() {
_playerWords.add(enteredWord);
_wordController.clear();
_errorMessage = null;
if (_playerWords.length == 2) {
_currentPlayer = 1;
_startGame();
} else {
_currentPlayer = 2;
}
});
} else {
setState(() {
_errorMessage = "Invalid word. Please enter a valid word.";
});
}
}
void _startGame() {
setState(() {
_isGameStarted = true;
_turnsLeft--;
_playTurn();
});
}
void _playTurn() {
setState(() {
_currentCorrectWord =
_currentPlayer == 1 ? _playerWords[1] : _playerWords[0];
});
}
void _onGuessSubmitted(GameData data) async {
DbBoard currentBoard = DbBoard(
correctWord: data.correctWord,
maxGuesses: data.maxGuesses,
guesses: data.guesses,
hasWon: data.hasWon,
);
if (_currentPlayer == 1) {
_player1Games.add(currentBoard);
} else {
_player2Games.add(currentBoard);
}
await _showTurnEndDialog(data.hasWon);
setState(() {
if (_currentPlayer == 1) {
if (data.hasWon) _player1Score += _playerWords[0].length;
_currentPlayer = 2;
_playTurn();
} else {
if (data.hasWon) _player2Score += _playerWords[1].length;
_currentPlayer = 1;
if (_turnsLeft > 0) {
_turnsLeft--;
_playerWords.clear();
_isGameStarted = false;
} else {
_saveGameHistory();
_showEndGameDialog();
}
}
});
}
void _saveGameHistory() async {
final duelGame = DbDuelGame(
date: DateTime.now(),
winner: _player1Score > _player2Score ? 0 : 1,
numberOfTurns: widget.numberOfTurns,
player1score: _player1Score,
player2score: _player2Score,
player1games: _player1Games,
player2games: _player2Games,
);
final provider = Provider.of<DbDuelGameProvider>(context, listen: false);
await provider.insert(duelGame);
}
Future<void> _showTurnEndDialog(bool hasWon) async {
String message = hasWon
? "Congratulations, Player $_currentPlayer! You guessed the correct word."
: "Player $_currentPlayer, you did not guess the correct word.";
await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Turn Over'),
content: Text(message),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("OK"),
),
],
);
},
);
}
Future<void> _showEndGameDialog() async {
await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Game Over'),
content: Text(
'The duel has ended. Player 1 score: $_player1Score, Player 2 score: $_player2Score. Thanks for playing!'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
},
child: const Text("Return to Main Menu"),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Player $_currentPlayer - Enter a Word'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: !_isGameStarted
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Player $_currentPlayer, enter the word you want the other player to guess:',
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
TextField(
controller: _wordController,
decoration: InputDecoration(
labelText: 'Enter a word',
border: const OutlineInputBorder(),
errorText: _errorMessage,
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _submitWord,
child: const Text('Submit Word'),
),
],
)
: GameScreen(
maxGuesses: 6,
correctWord: _currentCorrectWord,
readOnly: false,
onGameEnd: _onGuessSubmitted,
),
),
);
}
}

View file

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'duel_game_screen.dart';
class DuelSetupScreen extends StatefulWidget {
const DuelSetupScreen({super.key});
@override
_DuelSetupScreenState createState() => _DuelSetupScreenState();
}
class _DuelSetupScreenState extends State<DuelSetupScreen> {
int? _numberOfTurns;
String? _errorMessage;
void _submitTurns() {
if (_numberOfTurns != null && _numberOfTurns! > 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DuelGameScreen(numberOfTurns: _numberOfTurns!),
),
);
} else {
setState(() {
_errorMessage = "Please enter a valid number of turns.";
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Duel Setup'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Enter the number of turns for the duel:',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
TextField(
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
_numberOfTurns = int.tryParse(value);
});
},
decoration: InputDecoration(
labelText: 'Number of turns',
border: const OutlineInputBorder(),
errorText: _errorMessage,
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _submitTurns,
child: const Text('Submit Turns'),
),
],
),
),
);
}
}

33
lib/game_mode_row.dart Normal file
View file

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
class GameModeRow extends StatelessWidget {
final VoidCallback historyCallback;
final VoidCallback playCallback;
final String label;
const GameModeRow({
super.key,
required this.historyCallback,
required this.playCallback,
required this.label,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.menu_book),
iconSize: 32,
onPressed: historyCallback,
),
const SizedBox(width: 16.0),
ElevatedButton(
onPressed: playCallback,
child: Text(label),
),
],
);
}
}

272
lib/game_screen.dart Normal file
View file

@ -0,0 +1,272 @@
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,
),
),
]
],
);
}
}

54
lib/guess_row.dart Normal file
View file

@ -0,0 +1,54 @@
import 'package:diacritic/diacritic.dart';
import 'package:flutter/material.dart';
import 'LetterColoration.dart';
class GuessRow extends StatelessWidget {
final String guess;
final String correctWord;
final bool evaluateColors;
const GuessRow({
super.key,
required this.guess,
required this.correctWord,
required this.evaluateColors,
});
@override
Widget build(BuildContext context) {
int wordLength = correctWord.length;
double screenWidth = MediaQuery.of(context).size.width;
double totalMargin = 2 * wordLength * 2;
double boxSize = (screenWidth - 32 - totalMargin) / wordLength;
double fontSize = boxSize * 0.6;
String normalizedCorrectWord = removeDiacritics(correctWord).toUpperCase();
List<Color> colors = LetterColoration.getColorFeedback(
guess, normalizedCorrectWord, evaluateColors);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(wordLength, (index) {
String letter = guess.length > index ? guess[index] : '';
return _buildLetterTile(letter, colors[index], boxSize, fontSize);
}),
);
}
Widget _buildLetterTile(String letter, Color color, boxSize, fontSize) {
return Container(
width: boxSize,
height: boxSize,
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 2),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(8.0),
),
alignment: Alignment.center,
child: Text(
letter.toUpperCase(),
style: TextStyle(fontSize: fontSize, color: Colors.white),
),
);
}
}

64
lib/letter_keyboard.dart Normal file
View file

@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
class LetterKeyboard extends StatelessWidget {
final Function(String) onLetterPressed;
final Function() onBackspacePressed;
const LetterKeyboard({
super.key,
required this.onLetterPressed,
required this.onBackspacePressed,
});
@override
Widget build(BuildContext context) {
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
final List<String> firstRow = letters.substring(0, 10).split('');
final List<String> secondRow = letters.substring(10, 20).split('');
final List<String> thirdRow = letters.substring(20).split('') + [""];
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildKeyboardRow(firstRow),
_buildKeyboardRow(secondRow),
_buildKeyboardRow(thirdRow),
],
);
}
Widget _buildKeyboardRow(List<String> keys) {
return Wrap(
alignment: WrapAlignment.center,
spacing: 4.0,
runSpacing: 4.0,
children: keys.map((key) {
return Material(
color: Colors.transparent,
child: Container(
width: 28,
height: 28,
margin: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: const Color(0xFF4F545C),
borderRadius: BorderRadius.circular(4),
),
child: InkWell(
onTap: () =>
key == "" ? onBackspacePressed() : onLetterPressed(key),
child: Center(
child: Text(
key,
style: const TextStyle(
fontSize: 14,
color: Colors.white,
),
),
),
),
),
);
}).toList(),
);
}
}

55
lib/main.dart Normal file
View file

@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wordle/db/classic_game_provider.dart';
import 'package:wordle/db/duel_game_provider.dart';
import 'package:wordle/db/survival_game_provider.dart';
import 'db/wordle_database.dart';
import 'main_menu.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final wordleDatabase = WordleDatabase();
final db = await wordleDatabase.database;
runApp(MultiProvider(
providers: [
ChangeNotifierProvider<DbClassicGameProvider>(
create: (_) {
final provider = DbClassicGameProvider();
provider.initialize(db);
return provider;
},
),
ChangeNotifierProvider<DbDuelGameProvider>(
create: (_) {
final provider = DbDuelGameProvider();
provider.initialize(db);
return provider;
},
),
ChangeNotifierProvider<DbSurvivalGameProvider>(
create: (_) {
final provider = DbSurvivalGameProvider();
provider.initialize(db);
return provider;
},
),
],
child: const MyApp(),
));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wordle App',
theme: ThemeData.dark(),
home: const MainMenu(),
);
}
}

176
lib/main_menu.dart Normal file
View file

@ -0,0 +1,176 @@
import 'package:country_flags/country_flags.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:wordle/classic_game_history.dart';
import 'package:wordle/classic_options_screen.dart';
import 'package:wordle/duel_game_history.dart';
import 'package:wordle/duel_setup_screen.dart';
import 'package:wordle/survival_game_history.dart';
import 'package:wordle/survival_game_screen.dart';
import 'game_mode_row.dart';
class MainMenu extends StatefulWidget {
const MainMenu({super.key});
@override
_MainMenuState createState() => _MainMenuState();
}
class _MainMenuState extends State<MainMenu> {
String _selectedLanguage = 'english';
@override
void initState() {
super.initState();
_loadLanguage();
}
void _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('selectedLanguage') ?? 'english';
});
}
void _setLanguage(String language) async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = language;
prefs.setString('selectedLanguage', _selectedLanguage);
});
}
String getCountryCode() {
switch (_selectedLanguage) {
case 'french':
return 'FR';
case 'spanish':
return 'ES';
case 'english':
default:
return 'GB';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Wordle Game'),
actions: [
PopupMenuButton<String>(
icon: CountryFlag.fromCountryCode(
getCountryCode(),
height: 24,
width: 24,
shape: const RoundedRectangle(6),
),
onSelected: (String value) {
_setLanguage(value);
},
itemBuilder: (BuildContext context) => [
PopupMenuItem(
value: 'english',
child: Row(
children: [
CountryFlag.fromCountryCode('GB', height: 20, width: 20),
const SizedBox(width: 8),
const Text('English'),
],
),
),
PopupMenuItem(
value: 'french',
child: Row(
children: [
CountryFlag.fromCountryCode('FR', height: 20, width: 20),
const SizedBox(width: 8),
const Text('Français'),
],
),
),
PopupMenuItem(
value: 'spanish',
child: Row(
children: [
CountryFlag.fromCountryCode('ES', height: 20, width: 20),
const SizedBox(width: 8),
const Text('Español'),
],
),
),
],
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GameModeRow(
historyCallback: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ClassicGameHistoryScreen(),
),
);
},
playCallback: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ClassicGame(),
),
);
},
label: "Classic Mode",
),
const SizedBox(height: 16.0),
GameModeRow(
historyCallback: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DuelGameHistoryScreen(),
),
);
},
playCallback: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DuelSetupScreen(),
),
);
},
label: "Duel Mode",
),
const SizedBox(height: 16.0),
GameModeRow(
historyCallback: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SurvivalGameHistoryScreen(),
),
);
},
playCallback: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SurvivalGameScreen(),
),
);
},
label: "Survival Mode",
),
],
),
),
);
}
}

View file

@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wordle/db/survival_game.dart';
import 'db/survival_game_provider.dart';
import 'game_screen.dart';
class SurvivalGameHistoryScreen extends StatelessWidget {
const SurvivalGameHistoryScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Survival Game History'),
),
body: FutureBuilder<List<DbSurvivalGame>>(
future: Provider.of<DbSurvivalGameProvider>(context, listen: false)
.getAllSurvivalGames(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text('No survival history available.'));
}
final histories = snapshot.data!;
return ListView.builder(
itemCount: histories.length,
itemBuilder: (context, index) {
final history = histories[index];
return Card(
margin: const EdgeInsets.all(8.0),
child: ListTile(
title: Text('Date: ${history.date.toLocal()}'),
subtitle: Text('Max Level: ${history.maxLevelReached}'),
onTap: () => _showHistoryDetails(context, history),
),
);
},
);
},
),
);
}
void _showHistoryDetails(BuildContext context, DbSurvivalGame history) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text(
'Survival Game Details (Level ${history.maxLevelReached})'),
),
body: ListView(
padding: const EdgeInsets.all(16.0),
children: [
Text('Date: ${history.date.toLocal()}'),
const SizedBox(height: 10),
Text('Max Level Reached: ${history.maxLevelReached}'),
const SizedBox(height: 10),
const Text('Words Guessed:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
...history.boards.map((board) => Card(
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ListTile(
title: Text(
'Word: ${board.correctWord}, Max Guesses: ${board.maxGuesses}, Has Won: ${board.hasWon ? "Yes" : "No"}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text(board.correctWord.toUpperCase()),
),
body: Center(
child: GameScreen(
guesses: board.guesses,
maxGuesses: board.maxGuesses,
correctWord: board.correctWord,
readOnly: true,
),
),
),
),
);
},
),
)),
],
),
),
),
);
}
}

View file

@ -0,0 +1,112 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wordle/word_loader.dart';
import 'db/board.dart';
import 'db/survival_game.dart';
import 'db/survival_game_provider.dart';
import 'game_screen.dart';
class SurvivalGameScreen extends StatefulWidget {
const SurvivalGameScreen({super.key});
@override
SurvivalGameScreenState createState() => SurvivalGameScreenState();
}
class SurvivalGameScreenState extends State<SurvivalGameScreen> {
late Future<String?> futureWord;
int _currentLvl = 1;
int wordLength = 5;
int maxGuesses = 6;
List<DbBoard> boards = [];
@override
void initState() {
super.initState();
_loadWord();
}
void _loadWord() {
futureWord = WordLoader.getWordOfLength(wordLength);
}
void _nextLvl(GameData data) {
if (data.hasWon) {
setState(() {
boards.add(DbBoard(
correctWord: data.correctWord,
maxGuesses: data.maxGuesses,
guesses: data.guesses,
hasWon: data.hasWon,
));
_currentLvl++;
wordLength++;
if (_currentLvl % 2 == 0) maxGuesses++;
_loadWord();
});
} else {
_saveGameHistory();
Navigator.of(context).pop();
}
}
void _saveGameHistory() async {
final survivalHistory = DbSurvivalGame(
date: DateTime.now(),
maxLevelReached: _currentLvl,
boards: boards,
);
final provider =
Provider.of<DbSurvivalGameProvider>(context, listen: false);
await provider.insertSurvivalGame(survivalHistory);
}
void showEndGameDialog(GameData data) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => AlertDialog(
title: Text(data.hasWon ? "Congrats!" : "You lost"),
content: Text("The word was '${data.correctWord}'"),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
_nextLvl(data);
},
child: Text(data.hasWon ? "Next" : "Back to main menu"),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Survival - Level $_currentLvl'),
),
body: FutureBuilder<String?>(
future: futureWord,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (snapshot.hasData) {
return GameScreen(
maxGuesses: maxGuesses,
correctWord: snapshot.data!,
readOnly: false,
onGameEnd: showEndGameDialog,
);
} else {
return const Center(child: Text('Unable to load the word.'));
}
},
),
);
}
}

View file

@ -0,0 +1,147 @@
import 'package:flutter/material.dart';
class ValidatedNumberInput extends StatefulWidget {
final int? minValue;
final int? maxValue;
final ValueChanged<int>? onValueChanged;
final int? defaultValue;
final ValueChanged<bool>? onValidityChanged;
const ValidatedNumberInput({
super.key,
this.minValue,
this.maxValue,
this.onValueChanged,
this.defaultValue,
this.onValidityChanged,
});
@override
_ValidatedNumberInputState createState() => _ValidatedNumberInputState();
}
class _ValidatedNumberInputState extends State<ValidatedNumberInput> {
late int currentValue;
late TextEditingController _controller;
bool isValid = true;
@override
void initState() {
super.initState();
currentValue = widget.defaultValue ?? 0;
_controller = TextEditingController(text: currentValue.toString());
_validateInput(currentValue);
}
@override
void didUpdateWidget(covariant ValidatedNumberInput oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.defaultValue != null && widget.defaultValue != currentValue) {
setState(() {
currentValue = widget.defaultValue!;
_controller.text = currentValue.toString();
_validateInput(currentValue);
});
}
}
void _incrementValue() {
setState(() {
currentValue++;
_controller.text = currentValue.toString();
_validateInput(currentValue);
widget.onValueChanged?.call(currentValue);
});
}
void _decrementValue() {
setState(() {
currentValue--;
_controller.text = currentValue.toString();
_validateInput(currentValue);
widget.onValueChanged?.call(currentValue);
});
}
void _updateValueFromText(String value) {
String sanitizedValue = value.replaceAll(RegExp(r'[^0-9]'), '');
int? newValue = int.tryParse(sanitizedValue);
if (newValue != null) {
setState(() {
currentValue = newValue;
_controller.text = currentValue.toString();
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
_validateInput(currentValue);
widget.onValueChanged?.call(currentValue);
});
} else {
_controller.text = currentValue.toString();
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
}
}
void _validateInput(int value) {
int minValue = widget.minValue ?? double.negativeInfinity.toInt();
int maxValue = widget.maxValue ?? double.maxFinite.toInt();
bool newIsValid = value >= minValue && value <= maxValue;
if (newIsValid != isValid) {
isValid = newIsValid;
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.onValidityChanged?.call(isValid);
});
}
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
color: Colors.grey[800],
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: isValid ? Colors.grey : Colors.red,
width: 2.0,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: _decrementValue,
constraints: const BoxConstraints(),
padding: EdgeInsets.zero,
),
SizedBox(
width: 60,
child: TextFormField(
controller: _controller,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 8.0),
),
onChanged: (value) {
_updateValueFromText(value);
},
),
),
IconButton(
icon: const Icon(Icons.add),
onPressed: _incrementValue,
constraints: const BoxConstraints(),
padding: EdgeInsets.zero,
),
],
),
);
}
}

62
lib/word_loader.dart Normal file
View file

@ -0,0 +1,62 @@
import 'dart:convert';
import 'dart:math';
import 'package:diacritic/diacritic.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WordLoader {
static final Map<String, List<String>> _cachedWordLists = {};
static Future<List<String>> loadWordList(String language) async {
if (_cachedWordLists.containsKey(language)) {
return _cachedWordLists[language]!;
}
try {
String assetPath = "assets/${language}_words.json";
String jsonString = await rootBundle.loadString(assetPath);
List<dynamic> wordsJson = json.decode(jsonString);
List<String> words = wordsJson.cast<String>();
_cachedWordLists[language] = words;
return words;
} catch (e) {
print("Error loading word list for language '$language': $e");
return [];
}
}
static Future<bool> isValidWord(String word) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var language = prefs.getString('selectedLanguage') ?? "english";
List<String> words = await WordLoader.loadWordList(language);
List<String> normalizedWords =
words.map((w) => removeDiacritics(w).toUpperCase()).toList();
return normalizedWords.contains(word.toUpperCase());
}
static List<String> _filterWordsByLength(List<String> words, int length) {
return words
.where((word) => word.length == length && !word.contains("-"))
.toList();
}
static String? _selectRandomWord(List<String> words) {
if (words.isEmpty) {
return null;
}
final random = Random();
return words[random.nextInt(words.length)];
}
static Future<String?> getWordOfLength(int length) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var language = prefs.getString('selectedLanguage') ?? "english";
List<String> words = await loadWordList(language);
List<String> filteredWords = _filterWordsByLength(words, length);
String? word = _selectRandomWord(filteredWords);
print(word);
return word;
}
}

514
pubspec.lock Normal file
View file

@ -0,0 +1,514 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
args:
dependency: transitive
description:
name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
url: "https://pub.dev"
source: hosted
version: "2.6.0"
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
country_flags:
dependency: "direct main"
description:
name: country_flags
sha256: "0c2d9f0673ad0a5ec148f5b264c9e7338208b83000336bb4db8d189cd50df2bb"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.8"
diacritic:
dependency: "direct main"
description:
name: diacritic
sha256: "12981945ec38931748836cd76f2b38773118d0baef3c68404bdfde9566147876"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: transitive
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
jovial_misc:
dependency: transitive
description:
name: jovial_misc
sha256: f6e64f789ee311025bb367be9c9afe9759f76dd8209070b7f38e735b5f529eb1
url: "https://pub.dev"
source: hosted
version: "0.8.5"
jovial_svg:
dependency: transitive
description:
name: jovial_svg
sha256: adbc985f89a9e9c601d29aebb9fc17dd0a5db05b67af7e6c21da91eeb13dacb7
url: "https://pub.dev"
source: hosted
version: "1.1.23"
js:
dependency: transitive
description:
name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev"
source: hosted
version: "0.7.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.16.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev"
source: hosted
version: "3.9.1"
provider:
dependency: "direct main"
description:
name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "6.1.2"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
sqflite:
dependency: "direct main"
description:
name: sqflite
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sqflite_android:
dependency: transitive
description:
name: sqflite_android
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "4468b24876d673418a7b7147e5a08a715b4998a7ae69227acafaab762e0e5490"
url: "https://pub.dev"
source: hosted
version: "2.5.4+5"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: "96a698e2bc82bd770a4d6aab00b42396a7c63d9e33513a56945cbccb594c2474"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sqflite_platform_interface:
dependency: transitive
description:
name: sqflite_platform_interface
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
url: "https://pub.dev"
source: hosted
version: "3.3.0+3"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.4"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "15.0.0"
web:
dependency: transitive
description:
name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
sdks:
dart: ">=3.7.0-0 <4.0.0"
flutter: ">=3.24.0"

96
pubspec.yaml Normal file
View file

@ -0,0 +1,96 @@
name: wordle
description: "Wordle"
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: ^3.5.4
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
sqflite: ^2.4.1
shared_preferences: ^2.3.3
country_flags: ^3.0.0
diacritic: ^0.1.6
provider: ^6.1.2
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- assets/english_words.json
- assets/french_words.json
- assets/spanish_words.json
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package