Merge branch 'temp' into main

This commit is contained in:
Linloir 2022-03-11 15:03:28 +08:00
commit 38ccdfa476
25 changed files with 49715 additions and 340770 deletions

13866
assets/All.txt Normal file

File diff suppressed because it is too large Load Diff

4615
assets/CET4.txt Normal file

File diff suppressed because it is too large Load Diff

8028
assets/CET46.txt Normal file

File diff suppressed because it is too large Load Diff

2273
assets/CET6.txt Normal file

File diff suppressed because it is too large Load Diff

5987
assets/COCA-Slim.txt Normal file

File diff suppressed because it is too large Load Diff

4343
assets/GRE Slim.txt Normal file

File diff suppressed because it is too large Load Diff

3469
assets/HighSchool.txt Normal file

File diff suppressed because it is too large Load Diff

1520
assets/TOEFL Slim.txt Normal file

File diff suppressed because it is too large Load Diff

4516
assets/TOEFL.txt Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-03-05 20:56:05
* @LastEditTime : 2022-03-08 21:47:38
* @LastEditTime : 2022-03-11 14:54:04
* @Description : The display widget of the wordle game
*/
@ -12,7 +12,10 @@ import './event_bus.dart';
import 'dart:math' as math;
class WordleDisplayWidget extends StatefulWidget {
const WordleDisplayWidget({Key? key}) : super(key: key);
const WordleDisplayWidget({Key? key, required this.wordLen, required this.maxChances}) : super(key: key);
final int wordLen;
final int maxChances;
@override
State<WordleDisplayWidget> createState() => _WordleDisplayWidgetState();
@ -28,7 +31,7 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
void _validationAnimation(List<int> validation) async {
onAnimation = true;
bool result = true;
for(int i = 0; i < 5 && onAnimation; i++) {
for(int i = 0; i < widget.wordLen && onAnimation; i++) {
setState((){
inputs[r][i]["State"] = validation[i];
});
@ -44,7 +47,7 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
onAnimation = false;
r++;
c = 0;
if(r == 6 || result == true) {
if(r == widget.maxChances || result == true) {
mainBus.emit(event: "ValidationEnds", args: result);
acceptInput = false;
}
@ -61,8 +64,8 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
c = 0;
onAnimation = false;
acceptInput = true;
for(int i = 0; i < 6; i++) {
for(int j = 0; j < 5; j++) {
for(int i = 0; i < widget.maxChances; i++) {
for(int j = 0; j < widget.wordLen; j++) {
inputs[i][j]["Letter"] = "";
inputs[i][j]["State"] = 0;
}
@ -74,9 +77,9 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
void initState() {
super.initState();
inputs = [
for(int i = 0; i < 6; i++)
for(int i = 0; i < widget.maxChances; i++)
[
for(int j = 0; j < 5; j++)
for(int j = 0; j < widget.wordLen; j++)
{
"Letter": "",
"State": 0,
@ -111,17 +114,17 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
child: Align(
alignment: Alignment.center,
child: AspectRatio(
aspectRatio: 5 / 6,
aspectRatio: widget.wordLen / widget.maxChances,
child: Column(
//Column(
children: [
for(int i = 0; i < 6; i++)
for(int i = 0; i < widget.maxChances; i++)
Expanded(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (int j = 0; j < 5; j++)
for (int j = 0; j < widget.wordLen; j++)
AnimatedBuilder(
animation: inputs[i][j]["InputAnimationController"],
builder: (context, child) {
@ -206,7 +209,7 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
),
onNotification: (noti) {
if(noti.type == InputType.singleCharacter) {
if(r < 6 && c < 5 && !onAnimation && acceptInput) {
if(r < widget.maxChances && c < widget.wordLen && !onAnimation && acceptInput) {
setState((){
inputs[r][c]["Letter"] = noti.msg;
inputs[r][c]["State"] = 3;

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-03-05 20:41:41
* @LastEditTime : 2022-03-09 13:10:14
* @LastEditTime : 2022-03-11 14:54:15
* @Description : Offline page
*/
@ -14,14 +14,19 @@ import 'package:wordle/display_pannel.dart';
import 'package:wordle/instruction_pannel.dart';
import 'package:wordle/popper_generator.dart';
class OfflinePage extends StatefulWidget {
const OfflinePage({Key? key}) : super(key: key);
class GamePage extends StatefulWidget {
const GamePage({Key? key, required this.database, required this.wordLen, required this.maxChances, required this.gameMode}) : super(key: key);
final Map<String, List<String>> database;
final int wordLen;
final int maxChances;
final int gameMode;
@override
State<OfflinePage> createState() => _OfflinePageState();
State<GamePage> createState() => _GamePageState();
}
class _OfflinePageState extends State<OfflinePage> with TickerProviderStateMixin{
class _GamePageState extends State<GamePage> with TickerProviderStateMixin{
late AnimationController _controller;
void _onGameEnd(dynamic args) {
@ -68,7 +73,7 @@ class _OfflinePageState extends State<OfflinePage> with TickerProviderStateMixin
fontWeight: FontWeight.bold,
fontSize: 20.0,
),
title: const Text('WORDLE OFFLINE'),
title: const Text('WORDLE'),
centerTitle: true,
//iconTheme: const IconThemeData(color: Colors.black),
actions: [
@ -118,8 +123,12 @@ class _OfflinePageState extends State<OfflinePage> with TickerProviderStateMixin
],
),
body: Container(
child: const ValidationProvider(
child: WordleDisplayWidget(),
child: ValidationProvider(
database: widget.database,
wordLen: widget.wordLen,
maxChances: widget.maxChances,
gameMode: widget.gameMode,
child: WordleDisplayWidget(wordLen: widget.wordLen, maxChances: widget.maxChances),
),
color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[850] : Colors.white,
),

View File

@ -1,79 +1,58 @@
/*
* @Author : Linloir
* @Date : 2022-03-06 15:03:57
* @LastEditTime : 2022-03-07 15:55:48
* @LastEditTime : 2022-03-11 13:56:33
* @Description : Word generator
*/
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
abstract class Words {
static Set<String> dataBase = <String>{};
static Set<String> dictionary = <String>{};
static int _length = 5;
static String _cache = "";
//static Map<String, String> explainations = {};
static Future<bool> importWordsDatabase({int length = 5}) async {
//explainations.clear();
if(length != _length || dataBase.isEmpty || dictionary.isEmpty){
_length = length;
dataBase.clear();
_cache = "";
try {
var data = await rootBundle.loadString('assets/popular.txt');
LineSplitter.split(data).forEach((line) {
if(line.length == length && dataBase.lookup(line.substring(0,length - 1)) == null && dataBase.lookup(line.substring(0, length - 2)) == null) {
dataBase.add(line.toLowerCase());
}
_cache = line;
});
} catch (e) {
throw "Failed loading question database";
}
try {
var data = await rootBundle.loadString('assets/unixWords.txt');
LineSplitter.split(data).forEach((line) {
if(line.length == length && dictionary.lookup(line.substring(0,length - 1)) == null && dictionary.lookup(line.substring(0, length - 2)) == null) {
dictionary.add(line.toLowerCase());
}
_cache = line;
});
} catch (e) {
throw "Failed loading validation database";
}
}
if(dataBase.isEmpty) {
throw "Empty question database";
}
else if(dictionary.isEmpty) {
throw "Empty validation database";
}
return true;
}
static Future<String> generateWord() async {
int bound = dataBase.length;
if(bound == 0) {
try{
await Words.importWordsDatabase(length: _length);
} catch (e) {
return 'crash';
}
bound = dataBase.length;
}
var rng = Random();
String res = dataBase.elementAt(rng.nextInt(bound));
if(dictionary.lookup(res) == null) {
dictionary.add(res);
}
return res;
}
static bool isWordValidate(String word) {
return dictionary.lookup(word.toLowerCase()) != null;
}
Future<Set<String>> generateDictionary() async {
// Directory documentRoot = await getApplicationDocumentsDirectory();
// String dicPath = documentRoot.path + Platform.pathSeparator + dicName + ".txt";
// File dicFile = File(dicPath);
String dicContents = await rootBundle.loadString("assets/All.txt");
Set<String> database = {};
LineSplitter.split(dicContents).forEach((line) {
database.add(line.toUpperCase());
});
return database;
}
Future<Map<String, List<String>>> generateQuestionSet({required String dicName, required int wordLen}) async {
// Directory documentRoot = await getApplicationDocumentsDirectory();
// String dicPath = documentRoot.path + Platform.pathSeparator + dicName + ".txt";
// File dicFile = File(dicPath);
String dicContents = await rootBundle.loadString("assets/" + dicName + ".txt");
Map<String, List<String>> database = {};
LineSplitter.split(dicContents).forEach((line) {
var vowelStart = line.indexOf('[');
var vowelEnd = line.indexOf(']');
var word = "";
var vowel = "";
var explain = "";
word = line.substring(0, vowelStart == -1 ? null : vowelStart).trim().toUpperCase();
if(vowelStart != -1){
vowel = line.substring(vowelStart, vowelEnd + 1).trim();
explain = line.substring(vowelEnd + 1).trim();
}
if(wordLen == -1 || word.length == wordLen) {
database[word] = [vowel, explain];
}
});
return database;
}
Future<String> fetchOnlineWord() async {
await Future.delayed(const Duration(seconds: 2));
return "BINGO";
}
String getNextWord(Map<String, List<String>> database) {
var rng = Random();
return database.keys.elementAt(rng.nextInt(database.length));
}

27
lib/group_shared.dart Normal file
View File

@ -0,0 +1,27 @@
/*
* @Author : Linloir
* @Date : 2022-03-10 10:35:47
* @LastEditTime : 2022-03-10 13:06:37
* @Description :
*/
import 'package:flutter/material.dart';
class SelectNotification extends Notification {
const SelectNotification({required this.selection});
final int selection;
}
class GroupSharedData extends InheritedWidget {
const GroupSharedData({Key? key, required child, required this.selected}) : super(key: key, child: child);
final int selected;
static GroupSharedData? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<GroupSharedData>();
@override
bool updateShouldNotify(GroupSharedData oldWidget) {
return oldWidget.selected != selected;
}
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-03-05 20:55:53
* @LastEditTime : 2022-03-07 08:07:14
* @LastEditTime : 2022-03-11 11:05:10
* @Description : Input pannel class
*/
@ -30,9 +30,11 @@ class _InputPannelWidgetState extends State<InputPannelWidget> {
}
void _onAnimationStops(dynamic args) {
_cache.forEach((key, value) {
setState(() {
_keyState[key] = value;
setState(() {
_cache.forEach((key, value) {
if(_keyState[key] != 1){
_keyState[key] = value;
}
});
});
}

70
lib/loading_page.dart Normal file
View File

@ -0,0 +1,70 @@
/*
* @Author : Linloir
* @Date : 2022-03-11 09:51:50
* @LastEditTime : 2022-03-11 14:54:17
* @Description : Game Page
*/
import 'package:flutter/material.dart';
import 'package:wordle/game_page.dart';
import 'package:wordle/generator.dart';
import 'package:wordle/validation_provider.dart';
class LoadingPage extends StatefulWidget {
const LoadingPage({Key? key, required this.dicName, required this.wordLen, required this.maxChances, required this.gameMode}) : super(key: key);
final String dicName;
final int wordLen;
final int maxChances;
final int gameMode;
@override
State<LoadingPage> createState() => _LoadingPageState();
}
class _LoadingPageState extends State<LoadingPage> {
Future<Map<String, List<String>>> _loadDatabase({required String dicName, required int wordLen, required int gameMode}) async {
var dataBase = await generateQuestionSet(dicName: dicName, wordLen: wordLen);
if(ValidationProvider.validationDatabase.isEmpty) {
ValidationProvider.validationDatabase = await generateDictionary();
}
return dataBase;
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _loadDatabase(dicName: widget.dicName, wordLen: widget.wordLen, gameMode: widget.wordLen),
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.done) {
return GamePage(database: snapshot.data as Map<String, List<String>>, wordLen: widget.wordLen, maxChances: widget.maxChances, gameMode: widget.gameMode);
}
else {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 50.0,
height: 50.0,
child: CircularProgressIndicator(
color: Colors.teal[400],
strokeWidth: 4.0,
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: Text('Loading Libraries', style: TextStyle(color: Colors.teal[400], fontWeight: FontWeight.bold, fontSize: 25.0),),
),
]
),
),
);
}
},
);
}
}

View File

@ -1,15 +1,41 @@
/*
* @Author : Linloir
* @Date : 2022-03-05 20:21:34
* @LastEditTime : 2022-03-07 14:40:28
* @LastEditTime : 2022-03-11 14:56:28
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:wordle/event_bus.dart';
import './offline.dart';
import './generator.dart';
import 'package:wordle/loading_page.dart';
import 'package:wordle/single_selection.dart';
import 'package:wordle/selection_group.dart';
import 'package:wordle/scroll_behav.dart';
import 'package:path_provider/path_provider.dart';
import 'package:wordle/instruction_pannel.dart';
import 'dart:io';
import 'dart:math';
void main() {
Future<void> loadSettings() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String documentsPath = documentsDirectory.path + Platform.pathSeparator;
File settings = File(documentsPath + "settings.txt");
if(!(await settings.exists())) {
var defaultSettings = "5\nCET4\nLight";
settings.writeAsString(defaultSettings);
}
List<String> dicBooks = ["validation.txt", "CET4.txt"];
for(String dicName in dicBooks) {
if(!(await File(documentsPath + dicName).exists())) {
//Copy file
ByteData data = await rootBundle.load("assets/CET4.txt");
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await File(documentsPath + dicName).writeAsBytes(bytes, flush: true);
}
}
}
void main(){
runApp(const MyApp());
}
@ -33,6 +59,7 @@ class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
//loadSettings();
mainBus.onBus(event: "ToggleTheme", onEvent: _onThemeChange);
}
@ -45,6 +72,7 @@ class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
scrollBehavior: MyScrollBehavior(),
title: 'Wordle',
theme: ThemeData(
primarySwatch: Colors.grey,
@ -52,7 +80,6 @@ class _MyAppState extends State<MyApp> {
),
routes: {
"/": (context) => const HomePage(),
"/Offline": (context) => const OfflinePage(),
},
initialRoute: "/",
);
@ -67,137 +94,352 @@ class HomePage extends StatefulWidget {
}
class _HomePageState extends State<HomePage> {
bool _ignorance = false;
//bool _ignorance = false;
Future<void> readSettings() async {
return;
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Words.importWordsDatabase(),
future: readSettings(),
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.done || _ignorance) {
if(snapshot.hasData || _ignorance){
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: AspectRatio(
aspectRatio: 16 / 9,
child: OutlinedButton(
child: Padding(
padding: const EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
child: Align(
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'OFFLINE PLAYGROUND',
style: TextStyle(
color: Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.grey[850]!,
fontSize: 20.0,
fontWeight: FontWeight.bold
),
),
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 8.0),
// child: Container(
// width: 80.0,
// height: 8.0,
// decoration: BoxDecoration(
// color: Colors.grey[800],
// borderRadius: BorderRadius.circular(3.5),
// ),
// ),
// ),
Text(
'Play Wordle game offline using local word database',
style: TextStyle(
color: Colors.grey[500],
fontSize: 12.0,
),
),
],
if(snapshot.connectionState == ConnectionState.done) {
var mode = Theme.of(context).brightness;
return Scaffold(
body: Align(
alignment: Alignment.center,
child: Container(
constraints: const BoxConstraints(maxWidth: 960.0),
child: Column(
children: [
Container(
constraints: const BoxConstraints(minHeight: 100.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding: const EdgeInsets.only(left: 30.0, bottom: 10.0),
child: Text('WORDLE', style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.w300, color: mode == Brightness.light ? Colors.grey[850]! : Colors.white)),
),
),
),
style: ButtonStyle(
shape: MaterialStateProperty.all<OutlinedBorder?>(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
padding: MaterialStateProperty.all<EdgeInsets?>(const EdgeInsets.all(0)),
),
onPressed: () async {
Navigator.of(context).pushNamed("/Offline");
//Future.delayed(const Duration(milliseconds: 10)).then((e) => mainBus.emit(event: "NewGame", args: []));
}
)
const Spacer(),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(right: 30.0, bottom: 10.0),
child: Row(
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 750),
reverseDuration: const Duration(milliseconds: 750),
switchInCurve: Curves.bounceOut,
switchOutCurve: Curves.bounceIn,
transitionBuilder: (child, animation) {
var rotateAnimation = Tween<double>(begin: 0, end: 2 * pi).animate(animation);
var opacAnimation = Tween<double>(begin: 0, end: 1).animate(animation);
return AnimatedBuilder(
animation: rotateAnimation,
builder: (context, child) {
return Transform(
transform: Matrix4.rotationZ(rotateAnimation.status == AnimationStatus.reverse ? 2 * pi - rotateAnimation.value : rotateAnimation.value),
alignment: Alignment.center,
child: Opacity(
opacity: opacAnimation.value,
child: child,
),
);
},
child: child,
);
},
child: IconButton(
key: ValueKey(mode),
icon: mode == Brightness.light ? const Icon(Icons.dark_mode_outlined) : const Icon(Icons.dark_mode),
onPressed: () => mainBus.emit(event: "ToggleTheme", args: []),
),
),
IconButton(
icon: const Icon(Icons.help_outline_outlined),
//color: Colors.black,
onPressed: (){
showInstructionDialog(context: context);
},
),
]
)
),
)
],
),
),
)
const Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: OfflinePage(),
),
),
],
),
),
);
}
else {
return Container(
color: Colors.black38,
child: AlertDialog(
title: const Text('Failure'),
content: Text(snapshot.error as String),
actions: [
TextButton(
child: const Text('Retry'),
onPressed: () => setState(() {}),
),
TextButton(
child: const Text('Ignore'),
onPressed: () {
setState((){
_ignorance = true;
});
},
),
],
),
);
}
),
);
}
else {
return Scaffold(
body: Center(
return const Text('');
}
},
);
}
}
class OfflinePage extends StatefulWidget {
const OfflinePage({Key? key}) : super(key: key);
@override
State<OfflinePage> createState() => _OfflinePageState();
}
class _OfflinePageState extends State<OfflinePage> {
int wordLen = 5;
int maxChances = 6;
int dicBookIndex = 0;
var dicBook = [["All", "Full wordlist", "A", Colors.indigo], ["HighSchool", "HighSchool wordlist", "H", Colors.amber],
["CET4", "CET4 wordlist", "4", Colors.green[400]], ["CET6", "CET6 wordlist", "6", Colors.teal[400]],
["CET4 + 6", "CET4&6 wordlist", "46", Colors.teal[600]], ["TOEFL Slim", "TOEFL without CET4&6", "T", Colors.blue[400]],
["TOEFL", "Full TOEFL wordlist", "T", Colors.cyan[400]], ["GRE Slim", "GRE without CET4&6", "G", Colors.pink[200]]];
late final List<Widget> dicBookSelections;
var wordLenSelectionColors = [Colors.green[300], Colors.green[500], Colors.teal[300], Colors.teal[500], Colors.pink[300]];
var chancesSelectionColors = [Colors.deepOrange[600], Colors.orange[400], Colors.cyan, Colors.blue[400], Colors.blue[600], Colors.teal[400], Colors.teal[600], Colors.green[700]];
@override
void initState() {
super.initState();
dicBookSelections = [
for(int i = 0; i < dicBook.length; i++)
generateSelectionBox(
id: i,
width: 190,
height: 240,
padding: const EdgeInsets.fromLTRB(15.0, 25.0, 15.0, 0.0),
color: dicBook[i][3]! as Color,
primaryText: dicBook[i][0] as String,
primaryTextSize: 20.0,
secondaryText: dicBook[i][1] as String,
secondaryTextSize: 14.0,
decorationText: dicBook[i][2] as String,
alignment: Alignment.topLeft,
)
];
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
height: 150.0,
decoration: BoxDecoration(
color: wordLenSelectionColors[wordLen - 4]!.withOpacity(0.2),
borderRadius: BorderRadius.circular(10.0),
),
alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(10.0),
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 60.0,
width: 60.0,
child: CircularProgressIndicator(
strokeWidth: 6.0,
color: Colors.grey[850],
Padding(
padding: const EdgeInsets.fromLTRB(15.0, 10.0, 0.0, 10.0),
child: Text(
'Word Length',
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
color: wordLenSelectionColors[wordLen - 4]!,
),
),
),
Padding(
padding: const EdgeInsets.all(30.0),
child: Text('Loading library',
style: TextStyle(
color: Colors.grey[850]!,
fontSize: 16.0,
fontWeight: FontWeight.bold,
SelectionGroupProvider(
defaultSelection: 5,
onChanged: (sel) => setState(() => wordLen = sel),
selections: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for(int i = 4; i <= 8; i++)
generateSelectionBox(
id: i,
width: 80.0,
height: 80.0,
color: wordLenSelectionColors[i - 4]!,
primaryText: '$i',
primaryTextSize: 25.0,
),
],
),
),
),
],
),
),
);
}
},
Container(
width: double.infinity,
height: 150.0,
decoration: BoxDecoration(
color: chancesSelectionColors[maxChances - 1]!.withOpacity(0.2),
borderRadius: BorderRadius.circular(10.0),
),
alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(10.0),
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(15.0, 10.0, 0.0, 10.0),
child: Text(
'Max Attempts',
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
color: chancesSelectionColors[maxChances - 1]!,
),
),
),
SelectionGroupProvider(
defaultSelection: 6,
onChanged: (sel) => setState(() => maxChances = sel),
selections: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for(int i = 1; i <= 8; i++)
generateSelectionBox(
id: i,
width: 80.0,
height: 80.0,
color: chancesSelectionColors[i - 1]!,
primaryText: '$i',
primaryTextSize: 25.0,
),
],
),
),
),
],
),
),
Container(
width: double.infinity,
decoration: BoxDecoration(
color: (dicBook[dicBookIndex][3]! as Color).withOpacity(0.2),
borderRadius: BorderRadius.circular(10.0),
),
margin: const EdgeInsets.all(10.0),
padding: const EdgeInsets.all(15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(15.0, 10.0, 0.0, 10.0),
child: Text(
'Word List',
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
color: (dicBook[dicBookIndex][3]! as Color),
),
),
),
SelectionGroupProvider(
defaultSelection: 0,
onChanged: (sel) => setState(() => dicBookIndex = sel),
selections: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: dicBookSelections,
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.teal.withOpacity(0.2),
borderRadius: BorderRadius.circular(10.0),
),
margin: const EdgeInsets.all(10.0),
child: InkWell(
onTap: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return LoadingPage(dicName: dicBook[dicBookIndex][0] as String, wordLen: wordLen, maxChances: maxChances, gameMode: 0);
}));
},
borderRadius: BorderRadius.circular(10.0),
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 30.0),
child: const Text(
'Start Normal',
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
color: Colors.teal,
),
),
),
),
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.pink[200]!.withOpacity(0.2),
borderRadius: BorderRadius.circular(10.0),
),
margin: const EdgeInsets.all(10.0),
child: InkWell(
onTap: (){},
borderRadius: BorderRadius.circular(10.0),
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 30.0),
child: Text(
'Start Hard',
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
color: Colors.pink[400],
),
),
),
),
),
),
],
),
),
],
),
),
);
// Scaffold(
// body: Center(
// child: ElevatedButton(
// child: const Text('Offline'),
// onPressed: () => Navigator.of(context).pushNamed("/Offline"),
// )
// )
// );
}
}

17
lib/scroll_behav.dart Normal file
View File

@ -0,0 +1,17 @@
/*
* @Author : Linloir
* @Date : 2022-03-10 22:07:28
* @LastEditTime : 2022-03-10 22:07:29
* @Description : Scroll behavior
*/
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
class MyScrollBehavior extends ScrollBehavior {
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}

52
lib/selection_group.dart Normal file
View File

@ -0,0 +1,52 @@
/*
* @Author : Linloir
* @Date : 2022-03-09 22:33:34
* @LastEditTime : 2022-03-10 13:48:48
* @Description :
*/
import 'package:flutter/material.dart';
import './group_shared.dart';
class SelectionGroupProvider extends StatefulWidget {
const SelectionGroupProvider({
Key? key,
required this.selections,
required this.onChanged,
this.defaultSelection = 0,
}) : super(key: key);
final Widget selections;
final int defaultSelection;
final void Function(int) onChanged;
@override
State<SelectionGroupProvider> createState() => _SelectionGroupProviderState();
}
class _SelectionGroupProviderState extends State<SelectionGroupProvider> {
int selected = 0;
@override
void initState() {
super.initState();
selected = widget.defaultSelection;
}
@override
Widget build(BuildContext context) {
return GroupSharedData(
child: NotificationListener<SelectNotification>(
child: widget.selections,
onNotification: (noti) {
setState(() {
selected = noti.selection;
widget.onChanged(selected);
});
return true;
},
),
selected: selected,
);
}
}

362
lib/single_selection.dart Normal file
View File

@ -0,0 +1,362 @@
/*
* @Author : Linloir
* @Date : 2022-03-09 22:33:41
* @LastEditTime : 2022-03-11 14:01:14
* @Description : Single Selection Box
*/
import 'package:flutter/material.dart';
import 'dart:math';
import './group_shared.dart';
class SingleSelectionBox extends StatefulWidget {
const SingleSelectionBox({
Key? key,
required this.id,
required this.unselectedChild,
required this.selectedChild,
//These two are displayed by animation widget so not include in the decoration parameter
required this.selectedBackgroundColor,
required this.unselectedBackgroundColor,
this.shadowBlurRadius = 8,
this.shadowSpreadRadius = 0,
this.unselectedShadowColor = Colors.transparent,
this.selectedShadowColor = Colors.transparent,
this.borderRadius = 0.0,
this.borderColor = Colors.transparent,
this.borderWidth = 0.0,
}):
super(key: key);
final int id;
final Widget unselectedChild;
final Widget selectedChild;
final Color selectedBackgroundColor;
final Color unselectedBackgroundColor;
final double shadowBlurRadius;
final double shadowSpreadRadius;
final Color unselectedShadowColor;
final Color selectedShadowColor;
final double borderRadius;
final Color borderColor;
final double borderWidth;
@override
State<SingleSelectionBox> createState() => _SingleSelectionBoxState();
}
class _SingleSelectionBoxState extends State<SingleSelectionBox> with TickerProviderStateMixin{
bool selected = false;
//Record last pressed shift from center point
double pressedX = 0;
double pressedY = 0;
//Grow animation controller
late final AnimationController _scaleController;
late final AnimationController _opacController;
late final AnimationController _shrinkController;
@override
void initState() {
super.initState();
_scaleController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 750),
);
_opacController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 750),
);
_shrinkController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
if(context.findAncestorWidgetOfExactType<GroupSharedData>()!.selected == widget.id) {
_scaleController.value = _scaleController.upperBound;
selected = true;
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
var newData = context.findAncestorWidgetOfExactType<GroupSharedData>()!;
if(newData.selected != widget.id && selected) {
_opacController.forward();
selected = false;
}
}
@override
Widget build(BuildContext context) {
var data = GroupSharedData.of(context)!;
//Use gesture detector to detect the position pressed for animations
return LayoutBuilder(
builder:(context, constraints) {
var decoration = BoxDecoration(
boxShadow: [BoxShadow(
color: data.selected == widget.id ? widget.selectedShadowColor : widget.unselectedShadowColor,
blurRadius: widget.shadowBlurRadius,
spreadRadius: widget.shadowSpreadRadius,
)],
color: widget.unselectedBackgroundColor,
border: widget.borderWidth > 0 ? Border.all(
color: data.selected == widget.id ? widget.selectedBackgroundColor : widget.borderColor,
width: widget.borderWidth
) : null,
borderRadius: BorderRadius.circular(widget.borderRadius),
);
return GestureDetector(
//Stack best describes the relation between the animation and the container box
child: AnimatedBuilder(
animation: _shrinkController,
builder: (context, child) {
var _shrinkAnimation = Tween<double>(begin: 1, end: 0.9).animate(CurvedAnimation(
parent: _shrinkController,
curve: Curves.elasticOut,
reverseCurve: Curves.elasticIn,
));
return Transform.scale(
scale: _shrinkAnimation.value,
alignment: Alignment.center,
child: child,
);
},
child: Stack(
alignment: Alignment.center,
children: [
//Container
Positioned.fill(
child: AnimatedContainer(
duration: const Duration(milliseconds: 750),
decoration: decoration,
alignment: Alignment.center,
),
),
//Animation ( similar to ink well effect )
Positioned.fill(
child: AnimatedBuilder(
animation: _scaleController,
child: AnimatedBuilder(
animation: _opacController,
builder: (context, child) {
num topLeftSquredDis = pow(pressedX, 2) + pow(pressedY, 2);
num topRightSquredDis = pow(constraints.maxWidth - pressedX, 2) + pow(pressedY, 2);
num botLeftSquredDis = pow(pressedX, 2) + pow(constraints.maxHeight - pressedY, 2);
num botRightSquredDis = pow(constraints.maxWidth - pressedX, 2) + pow(constraints.maxHeight - pressedY, 2);
num radius = sqrt(max(max(topLeftSquredDis, topRightSquredDis), max(botLeftSquredDis, botRightSquredDis)));
var _opacityAnimation = Tween<double>(begin: 1, end: 0).animate(_opacController);
return OverflowBox(
maxHeight: radius * 2,
maxWidth: radius * 2,
child: SizedBox(
child: Opacity(
opacity: _opacityAnimation.value,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.selectedBackgroundColor,
),
),
),
width: radius * 2,
height: radius * 2,
),
);
},
),
builder: (context, child) {
var _scaleAnimation = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(
parent: _scaleController,
curve: Curves.easeInOutCubic,
));
return ClipRRect(
child: Transform.translate(
child: Transform.scale(
scale: _scaleAnimation.value,
child: child,
),
offset: Offset(pressedX - constraints.maxWidth / 2, pressedY - constraints.maxHeight / 2),
),
clipBehavior: Clip.antiAlias,
borderRadius: BorderRadius.circular(widget.borderRadius),
);
},
)
),
Positioned(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 750),
child: data.selected == widget.id ? widget.selectedChild : widget.unselectedChild,
),
),
],
),
),
onTapUp: (detail) {
setState(() {
selected = true;
//Notify the group manager
SelectNotification(selection: widget.id).dispatch(context);
//Record the coordinate and start the animation
pressedX = detail.localPosition.dx;
pressedY = detail.localPosition.dy;
_opacController.reset();
_scaleController.reset();
_scaleController.forward();
_shrinkController.reverse();
});
},
onTapDown: ((detail) {
_shrinkController.value = _shrinkController.upperBound;
}),
onTapCancel: (() {
_shrinkController.reverse();
}),
);
}
);
}
}
Widget generateSelectionBox({
required int id,
required double width,
required double height,
required Color color,
required String primaryText,
required double primaryTextSize,
String? secondaryText,
double? secondaryTextSize,
String? decorationText,
Alignment alignment = Alignment.center,
EdgeInsets padding = EdgeInsets.zero,
}) {
return Container(
width: width,
height: height,
padding: const EdgeInsets.all(10.0),
child: SingleSelectionBox(
id: id,
unselectedBackgroundColor: Colors.white.withOpacity(0.6),
selectedBackgroundColor: color,
//unselectedShadowColor: Colors.grey[400]!,
//selectedShadowColor: color,
borderRadius: 10.0,
borderWidth: 0.0,
unselectedChild: Stack(
key: const ValueKey(0),
children: [
Positioned.fill(
child: Align(
alignment: alignment,
child: Padding(
padding: padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Text(primaryText, style: TextStyle(
color: Colors.grey[850],
fontWeight: FontWeight.bold,
fontSize: primaryTextSize,
),),
),
if(secondaryText != null)
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Text(secondaryText, style: TextStyle(
color: Colors.grey[850]!.withOpacity(0.8),
fontSize: secondaryTextSize,
),),
),
],
),
),
),
),
if(decorationText != null)
Positioned.fill(
child: ClipRRect(
child: Align(
alignment: Alignment.bottomRight,
child: Transform.translate(
offset: const Offset(15, 35),
child: Text(
decorationText,
style: TextStyle(
color: color.withOpacity(0.3),
fontSize: 120.0,
fontWeight: FontWeight.bold,
),
)
),
),
borderRadius: BorderRadius.circular(10.0),
)
)
],
),
selectedChild: Stack(
key: const ValueKey(1),
children: [
Positioned.fill(
child: Align(
alignment: alignment,
child: Padding(
padding: padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Text(primaryText, style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: primaryTextSize,
),),
),
if(secondaryText != null)
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Text(secondaryText, style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: secondaryTextSize,
),),
),
],
),
),
),
),
if(decorationText != null)
Positioned.fill(
child: ClipRRect(
child: Align(
alignment: Alignment.bottomRight,
child: Transform.translate(
offset: const Offset(15, 35),
child: Text(
decorationText,
style: TextStyle(
color: Colors.white.withOpacity(0.3),
fontSize: 120.0,
fontWeight: FontWeight.bold,
),
)
),
),
borderRadius: BorderRadius.circular(10.0),
)
)
],
),
),
);
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-03-05 21:40:51
* @LastEditTime : 2022-03-08 22:19:08
* @LastEditTime : 2022-03-11 14:54:02
* @Description : Validation Provider class
*/
@ -19,9 +19,15 @@ class InputNotification extends Notification {
}
class ValidationProvider extends StatefulWidget {
const ValidationProvider({Key? key, required this.child}) : super(key: key);
static Set<String> validationDatabase = <String>{};
const ValidationProvider({Key? key, required this.child, required this.database, required this.wordLen, required this.maxChances, required this.gameMode}) : super(key: key);
final Widget child;
final Map<String, List<String>> database;
final int wordLen;
final int maxChances;
final int gameMode;
@override
State<ValidationProvider> createState() => _ValidationProviderState();
@ -38,13 +44,14 @@ class _ValidationProviderState extends State<ValidationProvider> {
_newGame();
}
void _newGame() async {
void _newGame() {
curAttempt = "";
curAttemptCount = 0;
acceptInput = true;
answer = await Words.generateWord();
answer = answer.toUpperCase();
//print(answer);
answer = getNextWord(widget.database);
if(!ValidationProvider.validationDatabase.contains(answer)){
ValidationProvider.validationDatabase.add(answer);
}
letterMap = {};
answer.split('').forEach((c) {
letterMap[c] ??= 0;
@ -73,7 +80,7 @@ class _ValidationProviderState extends State<ValidationProvider> {
builder: (context) {
return AlertDialog(
title: const Text('Result'),
content: Text(result ? "Won" : "Lost, answer is $answer"),
content: Text(result ? "Won" : "Lost, answer is ${answer.toLowerCase()}"),
actions: [
TextButton(
child: const Text('Back'),
@ -113,25 +120,25 @@ class _ValidationProviderState extends State<ValidationProvider> {
child: widget.child,
onNotification: (noti) {
if(noti.type == InputType.inputConfirmation) {
if(curAttempt.length < 5) {
if(curAttempt.length < widget.wordLen) {
//Not enough
return true;
}
else {
//Check validation
if(Words.isWordValidate(curAttempt)) {
if(ValidationProvider.validationDatabase.lookup(curAttempt) != null) {
//Generate map
Map<String, int> leftWordMap = Map.from(letterMap);
var positionValRes = <int>[for(int i = 0; i < 5; i++) -1];
var positionValRes = <int>[for(int i = 0; i < widget.wordLen; i++) -1];
var letterValRes = <String, int>{};
for(int i = 0; i < 5; i++) {
for(int i = 0; i < widget.wordLen; i++) {
if(curAttempt[i] == answer[i]) {
positionValRes[i] = 1;
leftWordMap[curAttempt[i]] = leftWordMap[curAttempt[i]]! - 1;
letterValRes[curAttempt[i]] = 1;
}
}
for(int i = 0; i < 5; i++) {
for(int i = 0; i < widget.wordLen; i++) {
if(curAttempt[i] != answer[i] && leftWordMap[curAttempt[i]] != null && leftWordMap[curAttempt[i]]! > 0) {
positionValRes[i] = 2;
leftWordMap[curAttempt[i]] = leftWordMap[curAttempt[i]]! - 1;
@ -179,7 +186,7 @@ class _ValidationProviderState extends State<ValidationProvider> {
}
}
else{
if(acceptInput && curAttempt.length < 5) {
if(acceptInput && curAttempt.length < widget.wordLen) {
curAttempt += noti.msg;
}
}

View File

@ -57,6 +57,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.2"
flutter:
dependency: "direct main"
description: flutter
@ -109,6 +123,76 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
path_provider:
dependency: "direct main"
description:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.9"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.12"
path_provider_ios:
dependency: transitive
description:
name: path_provider_ios
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.4"
sky_engine:
dependency: transitive
description: flutter
@ -170,5 +254,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0+1"
sdks:
dart: ">=2.16.1 <3.0.0"
flutter: ">=2.8.0"

View File

@ -34,6 +34,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
path_provider: ^2.0.9
dev_dependencies:
flutter_test:
@ -62,9 +63,14 @@ flutter:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- assets/unixWords.txt
- assets/ospd.txt
- assets/popular.txt
- 'assets/All.txt'
- 'assets/CET4.txt'
- 'assets/CET6.txt'
- 'assets/CET46.txt'
- 'assets/GRE Slim.txt'
- 'assets/TOEFL Slim.txt'
- 'assets/TOEFL.txt'
- 'assets/HighSchool.txt'
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.