From c5bc3578e24d785989a58fc28c54f4acdb8027bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr> Date: Tue, 2 Jan 2024 16:03:28 +0100 Subject: [PATCH] Create minimal playable game --- android/gradle.properties | 4 +- assets/translations/en.json | 5 ++ assets/translations/fr.json | 5 ++ .../metadata/android/en-US/changelogs/12.txt | 1 + .../metadata/android/fr-FR/changelogs/12.txt | 1 + lib/cubit/game_cubit.dart | 39 +++++++++++ lib/cubit/game_state.dart | 19 +++++ lib/cubit/settings_state.dart | 2 +- lib/main.dart | 2 + lib/models/move.dart | 37 ++++++++++ lib/models/twister_color.dart | 25 +++++++ lib/models/twister_member.dart | 25 +++++++ lib/ui/screens/home.dart | 3 +- lib/ui/skeleton.dart | 7 +- lib/ui/widgets/game.dart | 45 ++++++++++++ lib/ui/widgets/show_move.dart | 69 +++++++++++++++++++ lib/utils/color_extensions.dart | 33 +++++++++ pubspec.yaml | 2 +- 18 files changed, 316 insertions(+), 8 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/12.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/12.txt create mode 100644 lib/cubit/game_cubit.dart create mode 100644 lib/cubit/game_state.dart create mode 100644 lib/models/move.dart create mode 100644 lib/models/twister_color.dart create mode 100644 lib/models/twister_member.dart create mode 100644 lib/ui/widgets/game.dart create mode 100644 lib/ui/widgets/show_move.dart create mode 100644 lib/utils/color_extensions.dart diff --git a/android/gradle.properties b/android/gradle.properties index f0be9fb..d9abd55 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=0.0.11 -app.versionCode=11 +app.versionName=0.0.12 +app.versionCode=12 diff --git a/assets/translations/en.json b/assets/translations/en.json index 435402a..a170877 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,6 +1,11 @@ { "app_name": "Twister", + "left_hand": "left hand", + "right_hand": "right hand", + "left_foot": "left foot", + "right_foot": "right foot", + "bottom_nav_game": "Game", "bottom_nav_settings": "Settings", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 45fb897..05bbe10 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,6 +1,11 @@ { "app_name": "Twister", + "left_hand": "main gauche", + "right_hand": "main droite", + "left_foot": "pied gauche", + "right_foot": "pied droit", + "bottom_nav_game": "Jeu", "bottom_nav_settings": "Réglages", diff --git a/fastlane/metadata/android/en-US/changelogs/12.txt b/fastlane/metadata/android/en-US/changelogs/12.txt new file mode 100644 index 0000000..c4767c3 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/12.txt @@ -0,0 +1 @@ +Add minimal playable game. diff --git a/fastlane/metadata/android/fr-FR/changelogs/12.txt b/fastlane/metadata/android/fr-FR/changelogs/12.txt new file mode 100644 index 0000000..90a1f5e --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/12.txt @@ -0,0 +1 @@ +Création du jeu minimal mais jouable. diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart new file mode 100644 index 0000000..ddc7353 --- /dev/null +++ b/lib/cubit/game_cubit.dart @@ -0,0 +1,39 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:twister/models/move.dart'; + +part 'game_state.dart'; + +class GameCubit extends HydratedCubit<GameState> { + GameCubit() : super(const GameState()); + + Move getMove() { + return state.move ?? Move.createNull(); + } + + void setValues({ + Move? move, + }) { + emit(GameState( + move: move ?? state.move, + )); + } + + @override + GameState? fromJson(Map<String, dynamic> json) { + Move move = json['move'] as Move; + + return GameState( + move: move, + ); + } + + @override + Map<String, dynamic>? toJson(GameState state) { + return <String, dynamic>{ + 'move': state.move?.toJson(), + }; + } +} diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart new file mode 100644 index 0000000..f859fd5 --- /dev/null +++ b/lib/cubit/game_state.dart @@ -0,0 +1,19 @@ +part of 'game_cubit.dart'; + +@immutable +class GameState extends Equatable { + const GameState({ + this.move, + }); + + final Move? move; + + @override + List<dynamic> get props => <dynamic>[ + move, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'move': move, + }; +} diff --git a/lib/cubit/settings_state.dart b/lib/cubit/settings_state.dart index 3811631..6048256 100644 --- a/lib/cubit/settings_state.dart +++ b/lib/cubit/settings_state.dart @@ -14,6 +14,6 @@ class SettingsState extends Equatable { ]; Map<String, dynamic> get values => <String, dynamic>{ - 'discoveriesDaysCount': timerValue, + 'timerValue': timerValue, }; } diff --git a/lib/main.dart b/lib/main.dart index d30fd3e..04f3952 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:twister/config/theme.dart'; import 'package:twister/cubit/bottom_nav_cubit.dart'; +import 'package:twister/cubit/game_cubit.dart'; import 'package:twister/cubit/settings_cubit.dart'; import 'package:twister/ui/skeleton.dart'; @@ -44,6 +45,7 @@ class MyApp extends StatelessWidget { return MultiBlocProvider( providers: [ BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), + BlocProvider<GameCubit>(create: (context) => GameCubit()), BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()), ], child: MaterialApp( diff --git a/lib/models/move.dart b/lib/models/move.dart new file mode 100644 index 0000000..b415b2b --- /dev/null +++ b/lib/models/move.dart @@ -0,0 +1,37 @@ +import 'package:twister/models/twister_color.dart'; +import 'package:twister/models/twister_member.dart'; + +class Move { + final TwisterColor? color; + final TwisterMember? member; + + Move({ + required this.color, + required this.member, + }); + + factory Move.createNull() { + return Move( + color: null, + member: null, + ); + } + + factory Move.pickRandom() { + return Move( + color: TwisterColor.pickRandom(), + member: TwisterMember.pickRandom(), + ); + } + + String toString() { + return 'Move(' + this.toJson().toString() + ')'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'color': this.color.toString(), + 'member': this.member.toString(), + }; + } +} diff --git a/lib/models/twister_color.dart b/lib/models/twister_color.dart new file mode 100644 index 0000000..eb8c21b --- /dev/null +++ b/lib/models/twister_color.dart @@ -0,0 +1,25 @@ +import 'dart:math'; + +enum TwisterAllowedColors { + blue, + green, + red, + yellow, +} + +class TwisterColor { + final TwisterAllowedColors value; + + TwisterColor({ + required this.value, + }); + + factory TwisterColor.pickRandom() { + int random = Random().nextInt(TwisterAllowedColors.values.length); + return TwisterColor(value: TwisterAllowedColors.values[random]); + } + + String toString() { + return this.value.toString(); + } +} diff --git a/lib/models/twister_member.dart b/lib/models/twister_member.dart new file mode 100644 index 0000000..875f7d5 --- /dev/null +++ b/lib/models/twister_member.dart @@ -0,0 +1,25 @@ +import 'dart:math'; + +enum TwisterAllowedMembers { + leftHand, + rightHand, + leftFoot, + rightFoot, +} + +class TwisterMember { + final TwisterAllowedMembers value; + + TwisterMember({ + required this.value, + }); + + factory TwisterMember.pickRandom() { + int random = Random().nextInt(TwisterAllowedMembers.values.length); + return TwisterMember(value: TwisterAllowedMembers.values[random]); + } + + String toString() { + return this.value.toString(); + } +} diff --git a/lib/ui/screens/home.dart b/lib/ui/screens/home.dart index d5ba4df..dbda139 100644 --- a/lib/ui/screens/home.dart +++ b/lib/ui/screens/home.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:twister/ui/widgets/game.dart'; import 'package:unicons/unicons.dart'; class ScreenHome extends StatelessWidget { @@ -16,7 +17,7 @@ class ScreenHome extends StatelessWidget { physics: const BouncingScrollPhysics(), children: <Widget>[ const SizedBox(height: 8), - const Text('HOME'), + const Game(), const SizedBox(height: 36), ], ), diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index ca33599..a9fae72 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -18,7 +18,7 @@ class _SkeletonScreenState extends State<SkeletonScreen> { @override Widget build(BuildContext context) { List<Widget> pageNavigation = <Widget>[ - const ScreenHome(), + ScreenHome(), const ScreenSettings(), ]; @@ -28,8 +28,9 @@ class _SkeletonScreenState extends State<SkeletonScreen> { body: BlocBuilder<BottomNavCubit, int>( builder: (BuildContext context, int state) { return AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: pageNavigation.elementAt(state)); + duration: const Duration(milliseconds: 300), + child: pageNavigation.elementAt(state), + ); }, ), backgroundColor: Theme.of(context).colorScheme.background, diff --git a/lib/ui/widgets/game.dart b/lib/ui/widgets/game.dart new file mode 100644 index 0000000..1d4a181 --- /dev/null +++ b/lib/ui/widgets/game.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:twister/cubit/game_cubit.dart'; +import 'package:twister/models/move.dart'; +import 'package:twister/ui/widgets/show_move.dart'; + +class Game extends StatefulWidget { + const Game({super.key}); + + @override + State<Game> createState() => _GameState(); +} + +class _GameState extends State<Game> { + Move move = Move.createNull(); + + Widget pickNewMove() { + return BlocBuilder<GameCubit, GameState>(builder: (BuildContext context, GameState state) { + return TextButton( + onPressed: () { + BlocProvider.of<GameCubit>(context).setValues( + move: Move.pickRandom(), + ); + }, + child: Icon(UniconsSolid.refresh), + ); + }); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (context, gameState) { + return Column( + children: [ + ShowMove(move: gameState.move ?? Move.createNull()), + pickNewMove(), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/show_move.dart b/lib/ui/widgets/show_move.dart new file mode 100644 index 0000000..adead4b --- /dev/null +++ b/lib/ui/widgets/show_move.dart @@ -0,0 +1,69 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +import 'package:twister/models/move.dart'; +import 'package:twister/models/twister_color.dart'; +import 'package:twister/models/twister_member.dart'; +import 'package:twister/utils/color_extensions.dart'; + +class ShowMove extends StatelessWidget { + const ShowMove({super.key, required this.move}); + + final Move move; + + Color getColor(Move move) { + switch (move.color?.value) { + case TwisterAllowedColors.blue: + return Colors.blue; + case TwisterAllowedColors.green: + return Colors.green; + case TwisterAllowedColors.red: + return Colors.red; + case TwisterAllowedColors.yellow: + return Colors.yellow; + default: + return Colors.grey; + } + } + + Widget getWidget(Move move) { + TextStyle style = TextStyle( + color: Colors.black, + fontSize: 30, + fontWeight: FontWeight.bold, + ); + + switch (move.member?.value) { + case TwisterAllowedMembers.leftHand: + return Text(tr('left_hand'), style: style); + case TwisterAllowedMembers.rightHand: + return Text(tr('right_hand'), style: style); + case TwisterAllowedMembers.leftFoot: + return Text(tr('left_foot'), style: style); + case TwisterAllowedMembers.rightFoot: + return Text(tr('right_foot'), style: style); + default: + return Text('?', style: style); + } + } + + @override + Widget build(BuildContext context) { + Color color = getColor(move); + + return Container( + child: Padding( + padding: EdgeInsets.all(20), + child: getWidget(move), + ), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.all(Radius.circular(50)), + border: Border.all( + color: color.darken(20), + width: 10, + ), + ), + ); + } +} diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart new file mode 100644 index 0000000..4e55e33 --- /dev/null +++ b/lib/utils/color_extensions.dart @@ -0,0 +1,33 @@ +import 'dart:ui'; + +extension ColorExtension on Color { + Color darken([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = 1 - percent / 100; + return Color.fromARGB( + alpha, + (red * value).round(), + (green * value).round(), + (blue * value).round(), + ); + } + + Color lighten([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = percent / 100; + return Color.fromARGB( + alpha, + (red + ((255 - red) * value)).round(), + (green + ((255 - green) * value)).round(), + (blue + ((255 - blue) * value)).round(), + ); + } + + Color avg(Color other) { + final red = (this.red + other.red) ~/ 2; + final green = (this.green + other.green) ~/ 2; + final blue = (this.blue + other.blue) ~/ 2; + final alpha = (this.alpha + other.alpha) ~/ 2; + return Color.fromARGB(alpha, red, green, blue); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index ac2c248..1a01090 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: twister game companion publish_to: 'none' -version: 0.0.11+11 +version: 0.0.12+12 environment: sdk: '^3.0.0' -- GitLab