From 840adcc4a489557c28012ded3364bfbfafda48c3 Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Sun, 6 Mar 2022 00:03:12 +0800 Subject: [PATCH] Imply event bus --- LICENSE | 21 ++++++ README.md | 4 +- lib/display_pannel.dart | 83 ++++++++++++++++++++++ lib/event_bus.dart | 45 ++++++++++++ lib/input_pannel.dart | 6 ++ lib/main.dart | 110 ++++++----------------------- lib/offline.dart | 52 ++++++++++++++ lib/validation_provider.dart | 130 +++++++++++++++++++++++++++++++++++ 8 files changed, 360 insertions(+), 91 deletions(-) create mode 100644 LICENSE create mode 100644 lib/display_pannel.dart create mode 100644 lib/event_bus.dart create mode 100644 lib/input_pannel.dart create mode 100644 lib/offline.dart create mode 100644 lib/validation_provider.dart diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..01deb0d --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md index 952f13e..f32c615 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# Flutter wordle -The wordle game, but in Flutter. +# Flutter-Wordle +A wordle game written by flutter \ No newline at end of file diff --git a/lib/display_pannel.dart b/lib/display_pannel.dart new file mode 100644 index 0000000..b114c9e --- /dev/null +++ b/lib/display_pannel.dart @@ -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 createState() => _WordleDisplayWidgetState(); +} + +class _WordleDisplayWidgetState extends State { + int r = 0; + int c = 0; + var inputs = >[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( + 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; + }, + ); + } +} diff --git a/lib/event_bus.dart b/lib/event_bus.dart new file mode 100644 index 0000000..d9d8db6 --- /dev/null +++ b/lib/event_bus.dart @@ -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 = >{}; + + void onBus({required String event, required EventCallBack onEvent}) { + _eventMap[event] ??= []; + _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]![i](args); + } + } + } +} diff --git a/lib/input_pannel.dart b/lib/input_pannel.dart new file mode 100644 index 0000000..62a8cda --- /dev/null +++ b/lib/input_pannel.dart @@ -0,0 +1,6 @@ +/* + * @Author : Linloir + * @Date : 2022-03-05 20:55:53 + * @LastEditTime : 2022-03-05 20:55:53 + * @Description : + */ diff --git a/lib/main.dart b/lib/main.dart index 202509b..b399f27 100644 --- a/lib/main.dart +++ b/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 createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - 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: [ - 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"), + ) + ) ); } -} +} \ No newline at end of file diff --git a/lib/offline.dart b/lib/offline.dart new file mode 100644 index 0000000..b9efdcd --- /dev/null +++ b/lib/offline.dart @@ -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 createState() => _OfflinePageState(); +} + +class _OfflinePageState extends State { + @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(), + ), + ], + ), + ); + } +} diff --git a/lib/validation_provider.dart b/lib/validation_provider.dart new file mode 100644 index 0000000..9f6a597 --- /dev/null +++ b/lib/validation_provider.dart @@ -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 createState() => _ValidationProviderState(); +} + +class _ValidationProviderState extends State { + String answer = "WORDLE"; + Set letterSet = {'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( + 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; + }, + ); + } +}