mirror of
https://github.com/Linloir/Flutter-Wordle.git
synced 2025-12-17 06:18:12 +08:00
Imply event bus
This commit is contained in:
parent
cbb587a126
commit
840adcc4a4
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 霖落
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,2 +1,2 @@
|
||||
# Flutter wordle
|
||||
The wordle game, but in Flutter.
|
||||
# Flutter-Wordle
|
||||
A wordle game written by flutter
|
||||
83
lib/display_pannel.dart
Normal file
83
lib/display_pannel.dart
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:56:05
|
||||
* @LastEditTime : 2022-03-05 23:51:16
|
||||
* @Description : The display widget of the wordle game
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wordle/validation_provider.dart';
|
||||
import './event_bus.dart';
|
||||
|
||||
class WordleDisplayWidget extends StatefulWidget {
|
||||
const WordleDisplayWidget({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<WordleDisplayWidget> createState() => _WordleDisplayWidgetState();
|
||||
}
|
||||
|
||||
class _WordleDisplayWidgetState extends State<WordleDisplayWidget> {
|
||||
int r = 0;
|
||||
int c = 0;
|
||||
var inputs = <List<String>>[for(int i = 0; i < 6; i++) [for(int j = 0; j < 5; j++) "A"]];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
mainBus.onBus(
|
||||
event: "Attempt",
|
||||
onEvent: (args) {
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NotificationListener<InputNotification>(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
for(int r = 0; r < 6; r++)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
for(int c = 0; c < 5; c++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: Container(
|
||||
constraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
inputs[r][c],
|
||||
style: TextStyle(
|
||||
fontSize: 50.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.grey[600]!,
|
||||
width: 3.0,
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
onNotification: (noti) {
|
||||
if(noti.type == InputType.singleCharacter) {
|
||||
if(r <= 6 && c <= 5) {
|
||||
setState((){
|
||||
inputs[r].add(noti.msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
45
lib/event_bus.dart
Normal file
45
lib/event_bus.dart
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 21:50:33
|
||||
* @LastEditTime : 2022-03-05 23:15:18
|
||||
* @Description : Event bus class
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef EventCallBack = void Function(dynamic args);
|
||||
|
||||
final EventBus mainBus = EventBus();
|
||||
|
||||
class EventBus {
|
||||
EventBus._internal();
|
||||
static final EventBus _bus = EventBus._internal();
|
||||
factory EventBus() => _bus;
|
||||
|
||||
final _eventMap = <String, List<EventCallBack>>{};
|
||||
|
||||
void onBus({required String event, required EventCallBack onEvent}) {
|
||||
_eventMap[event] ??= <EventCallBack>[];
|
||||
_eventMap[event]!.add(onEvent);
|
||||
}
|
||||
|
||||
void offBus({required String event, EventCallBack? callBack}) {
|
||||
if(callBack == null) {
|
||||
_eventMap.remove(event);
|
||||
}
|
||||
else{
|
||||
_eventMap[event]?.remove(callBack);
|
||||
}
|
||||
}
|
||||
|
||||
void emit({required String event, dynamic args}) {
|
||||
// _eventMap[event]?.forEach((func) {
|
||||
// func(args);
|
||||
// });
|
||||
if(_eventMap[event] != null) {
|
||||
for(int i = _eventMap[event]!.length - 1; i >= 0; i--) {
|
||||
_eventMap[event];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
lib/input_pannel.dart
Normal file
6
lib/input_pannel.dart
Normal file
@ -0,0 +1,6 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:55:53
|
||||
* @LastEditTime : 2022-03-05 20:55:53
|
||||
* @Description :
|
||||
*/
|
||||
108
lib/main.dart
108
lib/main.dart
@ -1,4 +1,11 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:21:34
|
||||
* @LastEditTime : 2022-03-05 20:46:33
|
||||
* @Description :
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import './offline.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
@ -11,105 +18,30 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
title: 'Wordle',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// Try running your application with "flutter run". You'll see the
|
||||
// application has a blue toolbar. Then, without quitting the app, try
|
||||
// changing the primarySwatch below to Colors.green and then invoke
|
||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// is not restarted.
|
||||
primarySwatch: Colors.blue,
|
||||
primarySwatch: Colors.blue
|
||||
),
|
||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
routes: {
|
||||
"/": (context) => const HomePage(),
|
||||
"/Offline": (context) => const OfflinePage(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({Key? key, required this.title}) : super(key: key);
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Invoke "debug painting" (press "p" in the console, choose the
|
||||
// "Toggle Debug Paint" action from the Flutter Inspector in Android
|
||||
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
|
||||
// to see the wireframe for each widget.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
child: ElevatedButton(
|
||||
child: const Text('Offline'),
|
||||
onPressed: () => Navigator.of(context).pushNamed("/Offline"),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
52
lib/offline.dart
Normal file
52
lib/offline.dart
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:41:41
|
||||
* @LastEditTime : 2022-03-05 23:22:41
|
||||
* @Description : Offline page
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import './display_pannel.dart';
|
||||
|
||||
class OfflinePage extends StatefulWidget {
|
||||
const OfflinePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<OfflinePage> createState() => _OfflinePageState();
|
||||
}
|
||||
|
||||
class _OfflinePageState extends State<OfflinePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0.0,
|
||||
shadowColor: Colors.transparent,
|
||||
toolbarHeight: 80.0,
|
||||
titleTextStyle: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
title: const Text('WORDLE OFFLINE'),
|
||||
centerTitle: true,
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.history),
|
||||
color: Colors.black,
|
||||
onPressed: (){},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: WordleDisplayWidget(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
130
lib/validation_provider.dart
Normal file
130
lib/validation_provider.dart
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 21:40:51
|
||||
* @LastEditTime : 2022-03-05 23:06:28
|
||||
* @Description : Validation Provider class
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import './event_bus.dart';
|
||||
|
||||
enum InputType { singleCharacter, inputConfirmation }
|
||||
|
||||
class InputNotification extends Notification {
|
||||
const InputNotification({required this.type, required this.msg});
|
||||
|
||||
final InputType type;
|
||||
final String msg;
|
||||
}
|
||||
|
||||
class ValidationProvider extends StatefulWidget {
|
||||
const ValidationProvider({Key? key, required this.child}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<ValidationProvider> createState() => _ValidationProviderState();
|
||||
}
|
||||
|
||||
class _ValidationProviderState extends State<ValidationProvider> {
|
||||
String answer = "WORDLE";
|
||||
Set<String> letterSet = <String>{'W', 'O', 'R', 'D', 'L', 'E'};
|
||||
String curAttempt = "";
|
||||
int curAttemptCount = 0;
|
||||
bool acceptInput = true;
|
||||
|
||||
void _newGame(String answer) {
|
||||
this.answer = answer;
|
||||
letterSet.clear();
|
||||
letterSet.addAll(answer.split(''));
|
||||
curAttempt = "";
|
||||
curAttemptCount = 0;
|
||||
acceptInput = true;
|
||||
}
|
||||
|
||||
void _onGameWin() {
|
||||
acceptInput = false;
|
||||
_showResult(true);
|
||||
}
|
||||
|
||||
void _onGameLoose() {
|
||||
acceptInput = false;
|
||||
_showResult(false);
|
||||
}
|
||||
|
||||
void _showResult(bool result) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Result'),
|
||||
content: Text(result ? "Won" : "Lost"),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
bool _isWordValidate(String word) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState(){
|
||||
super.initState();
|
||||
mainBus.onBus(event: "NewGame", onEvent: (args) => _newGame(args as String));
|
||||
mainBus.onBus(event: "Result", onEvent: (args) => args as bool ? _onGameWin() : _onGameLoose());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NotificationListener<InputNotification>(
|
||||
child: widget.child,
|
||||
onNotification: (noti) {
|
||||
if(noti.type == InputType.inputConfirmation){
|
||||
if(curAttempt.length < 5) {
|
||||
//Not enough
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
//Check validation
|
||||
if(_isWordValidate(curAttempt)) {
|
||||
//emit current attempt
|
||||
mainBus.emit(
|
||||
event: "Attempt",
|
||||
args: {
|
||||
"Attempt": curAttempt,
|
||||
"AttemptCount": curAttemptCount,
|
||||
}
|
||||
);
|
||||
curAttempt = "";
|
||||
curAttemptCount++;
|
||||
}
|
||||
else{
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Info'),
|
||||
content: const Text('Not a word!'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('OK'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(acceptInput && curAttempt.length < 5) {
|
||||
curAttempt += noti.msg;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user