Skip to content
Snippets Groups Projects
Commit c7667f5f authored by Benoît Harrault's avatar Benoît Harrault
Browse files

Normalize game architecture

parent 6c63633d
No related branches found
No related tags found
1 merge request!24Resolve "Normalize game architecture"
Pipeline #5685 passed
Showing
with 596 additions and 145 deletions
Improve/normalize game architecture.
Amélioration/normalisation de l'architecture du jeu.
......@@ -4,7 +4,7 @@ class TwisterColors {
static const Color grey = Color.fromARGB(255, 0xF0, 0xE2, 0xE7); // F0E2E7
static const Color blue = Color.fromARGB(255, 0x3F, 0x84, 0xE5); // 3F84E5
static const Color green = Color.fromARGB(255, 0x3F, 0x78, 0x4C); // 3F784C
static const Color red = Color.fromARGB(255, 0xB2, 0x0D, 0x30); // B20D30
static const Color yellow = Color.fromARGB(255, 0xC1, 0x78, 0x17); // C17817
static const Color green = Color.fromARGB(255, 0x1D, 0xA2, 0x3C); // 1DA23C
static const Color red = Color.fromARGB(255, 0xE0, 0x0D, 0x3B); // E00D3B
static const Color yellow = Color.fromARGB(255, 0xE8, 0xD9, 0x17); // E8D917
}
import 'package:twister/utils/tools.dart';
class DefaultGameSettings {
// available global parameters codes
static const String parameterCodeTimerValue = 'timer';
static const List<String> availableParameters = [
parameterCodeTimerValue,
];
// timer value: available values
static const String timerValueMedium = 'medium';
static const List<String> allowedTimerValues = [
timerValueMedium,
];
// timer value: default value
static const String defaultTimerValue = timerValueMedium;
// available values from parameter code
static List<String> getAvailableValues(String parameterCode) {
switch (parameterCode) {
case parameterCodeTimerValue:
return DefaultGameSettings.allowedTimerValues;
}
printlog('Did not find any available value for game parameter "$parameterCode".');
return [];
}
// parameters displayed with assets (instead of painter)
static List<String> displayedWithAssets = [
//
];
}
import 'package:twister/utils/tools.dart';
class DefaultGlobalSettings {
// available global parameters codes
static const String parameterCodeSkin = 'skin';
static const List<String> availableParameters = [
parameterCodeSkin,
];
// skin: available values
static const String skinValueColors = 'colors';
static const List<String> allowedSkinValues = [
skinValueColors,
];
// skin: default value
static const String defaultSkinValue = skinValueColors;
// available values from parameter code
static List<String> getAvailableValues(String parameterCode) {
switch (parameterCode) {
case parameterCodeSkin:
return DefaultGlobalSettings.allowedSkinValues;
}
printlog('Did not find any available value for global parameter "$parameterCode".');
return [];
}
// parameters displayed with assets (instead of painter)
static List<String> displayedWithAssets = [
//
];
}
class DefaultSettings {
static const int defaultTimerValue = 20;
static const List<int> allowedTimerValues = [
10,
defaultTimerValue,
30,
];
}
import 'package:flutter/material.dart';
import 'package:unicons/unicons.dart';
import 'package:twister/ui/screens/page_about.dart';
import 'package:twister/ui/screens/page_game.dart';
import 'package:twister/ui/screens/page_settings.dart';
class MenuItem {
final Icon icon;
final Widget page;
const MenuItem({
required this.icon,
required this.page,
});
}
class Menu {
static const indexGame = 0;
static const menuItemGame = MenuItem(
icon: Icon(UniconsLine.home),
page: PageGame(),
);
static const indexSettings = 1;
static const menuItemSettings = MenuItem(
icon: Icon(UniconsLine.setting),
page: PageSettings(),
);
static const indexAbout = 2;
static const menuItemAbout = MenuItem(
icon: Icon(UniconsLine.info_circle),
page: PageAbout(),
);
static Map<int, MenuItem> items = {
indexGame: menuItemGame,
indexSettings: menuItemSettings,
indexAbout: menuItemAbout,
};
static bool isIndexAllowed(int pageIndex) {
return items.keys.contains(pageIndex);
}
static Widget getPageWidget(int pageIndex) {
return items[pageIndex]?.page ?? menuItemGame.page;
}
static int itemsCount = Menu.items.length;
}
......@@ -39,11 +39,9 @@ final ColorScheme lightColorScheme = ColorScheme.light(
secondary: primarySwatch.shade500,
onSecondary: Colors.white,
error: errorColor,
background: textSwatch.shade200,
onBackground: textSwatch.shade500,
onSurface: textSwatch.shade500,
surface: textSwatch.shade50,
surfaceVariant: Colors.white,
surfaceContainerHighest: Colors.white,
shadow: textSwatch.shade900.withOpacity(.1),
);
......@@ -52,11 +50,9 @@ final ColorScheme darkColorScheme = ColorScheme.dark(
secondary: primarySwatch.shade500,
onSecondary: Colors.white,
error: errorColor,
background: const Color(0xFF171724),
onBackground: textSwatch.shade400,
onSurface: textSwatch.shade300,
surface: const Color(0xFF262630),
surfaceVariant: const Color(0xFF282832),
surfaceContainerHighest: const Color(0xFF282832),
shadow: textSwatch.shade900.withOpacity(.2),
);
......@@ -192,5 +188,3 @@ final ThemeData darkTheme = lightTheme.copyWith(
),
),
);
final ThemeData appTheme = darkTheme;
import 'package:audioplayers/audioplayers.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:twister/models/move.dart';
import 'package:twister/models/game/game.dart';
import 'package:twister/models/game/move.dart';
import 'package:twister/models/settings/settings_game.dart';
import 'package:twister/models/settings/settings_global.dart';
part 'game_state.dart';
class GameCubit extends HydratedCubit<GameState> {
GameCubit() : super(const GameState());
GameCubit()
: super(GameState(
currentGame: Game.createEmpty(),
));
void updateState(Game game) {
emit(GameState(
currentGame: game,
));
}
void refresh() {
final Game game = Game(
// Settings
gameSettings: state.currentGame.gameSettings,
globalSettings: state.currentGame.globalSettings,
// State
isRunning: state.currentGame.isRunning,
isStarted: state.currentGame.isStarted,
isFinished: state.currentGame.isFinished,
animationInProgress: state.currentGame.animationInProgress,
// Base data
move: state.currentGame.move,
// Game data
history: state.currentGame.history,
);
// game.dump();
Move getMove() {
return state.move ?? Move.createNull();
updateState(game);
}
void setValues({
Move? move,
void startNewGame({
required GameSettings gameSettings,
required GlobalSettings globalSettings,
}) {
List<Move> history = state.history ?? [];
if (move != null) {
history.add(move);
final Game newGame = Game.createNew(
// Settings
gameSettings: gameSettings,
globalSettings: globalSettings,
);
newGame.dump();
updateState(newGame);
refresh();
}
emit(GameState(
move: move ?? state.move,
history: history,
));
void quitGame() {
state.currentGame.isRunning = false;
refresh();
}
void resumeSavedGame() {
state.currentGame.isRunning = true;
refresh();
}
void deleteSavedGame() {
state.currentGame.isRunning = false;
state.currentGame.isFinished = true;
refresh();
}
void setMove(Move move) {
state.currentGame.isStarted = true;
state.currentGame.move = move;
state.currentGame.history.add(move);
refresh();
}
void deleteHistory() {
List<Move> history = [];
emit(GameState(
move: state.move,
history: history,
));
state.currentGame.history = [];
state.currentGame.isStarted = false;
refresh();
}
void setAnimationIsRunning(bool isRunning) {
state.currentGame.animationInProgress = isRunning;
refresh();
}
void pickNewMove() {
Move newMove = Move.pickRandom();
setMove(newMove);
final player = AudioPlayer();
player.play(AssetSource(newMove.toSoundAsset()));
}
@override
GameState? fromJson(Map<String, dynamic> json) {
Move move = json['move'] as Move;
final Game currentGame = json['currentGame'] as Game;
return GameState(
move: move,
currentGame: currentGame,
);
}
@override
Map<String, dynamic>? toJson(GameState state) {
return <String, dynamic>{
'move': state.move?.toJson(),
'currentGame': state.currentGame.toJson(),
};
}
}
......@@ -3,21 +3,13 @@ part of 'game_cubit.dart';
@immutable
class GameState extends Equatable {
const GameState({
this.move,
this.history,
required this.currentGame,
});
final Move? move;
final List<Move>? history;
final Game currentGame;
@override
List<dynamic> get props => <dynamic>[
move,
history,
currentGame,
];
Map<String, dynamic> get values => <String, dynamic>{
'move': move,
'history': history,
};
}
import 'package:hydrated_bloc/hydrated_bloc.dart';
class BottomNavCubit extends HydratedCubit<int> {
BottomNavCubit() : super(0);
import 'package:twister/config/menu.dart';
int pagesCount = 2;
class NavCubit extends HydratedCubit<int> {
NavCubit() : super(0);
void updateIndex(int index) {
if (isIndexAllowed(index)) {
if (Menu.isIndexAllowed(index)) {
emit(index);
} else {
goToHomePage();
goToGamePage();
}
}
bool isIndexAllowed(int index) {
return (index >= 0) && (index < pagesCount);
void goToGamePage() {
emit(Menu.indexGame);
}
void goToHomePage() => emit(0);
void goToSettingsPage() {
emit(Menu.indexSettings);
}
void goToAboutPage() {
emit(Menu.indexAbout);
}
@override
int? fromJson(Map<String, dynamic> json) {
return 0;
int fromJson(Map<String, dynamic> json) {
return Menu.indexGame;
}
@override
......
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:twister/config/default_settings.dart';
part 'settings_state.dart';
class SettingsCubit extends HydratedCubit<SettingsState> {
SettingsCubit() : super(const SettingsState());
int getTimerValue() {
return state.timerValue ?? DefaultSettings.defaultTimerValue;
}
void setValues({
int? timerValue,
}) {
emit(SettingsState(
timerValue: timerValue ?? state.timerValue,
));
}
@override
SettingsState? fromJson(Map<String, dynamic> json) {
int timerValue = json['timerValue'] as int;
return SettingsState(
timerValue: timerValue,
);
}
@override
Map<String, dynamic>? toJson(SettingsState state) {
return <String, dynamic>{
'timerValue': state.timerValue ?? DefaultSettings.defaultTimerValue,
};
}
}
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:twister/config/default_game_settings.dart';
import 'package:twister/models/settings/settings_game.dart';
part 'settings_game_state.dart';
class GameSettingsCubit extends HydratedCubit<GameSettingsState> {
GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault()));
void setValues({
String? timerValue,
}) {
emit(
GameSettingsState(
settings: GameSettings(
timerValue: timerValue ?? state.settings.timerValue,
),
),
);
}
String getParameterValue(String code) {
switch (code) {
case DefaultGameSettings.parameterCodeTimerValue:
return GameSettings.getTimerValueFromUnsafe(state.settings.timerValue);
}
return '';
}
void setParameterValue(String code, String value) {
final String timerValue = code == DefaultGameSettings.parameterCodeTimerValue
? value
: getParameterValue(DefaultGameSettings.parameterCodeTimerValue);
setValues(
timerValue: timerValue,
);
}
@override
GameSettingsState? fromJson(Map<String, dynamic> json) {
final String timerValue = json[DefaultGameSettings.parameterCodeTimerValue] as String;
return GameSettingsState(
settings: GameSettings(
timerValue: timerValue,
),
);
}
@override
Map<String, dynamic>? toJson(GameSettingsState state) {
return <String, dynamic>{
DefaultGameSettings.parameterCodeTimerValue: state.settings.timerValue,
};
}
}
part of 'settings_game_cubit.dart';
@immutable
class GameSettingsState extends Equatable {
const GameSettingsState({
required this.settings,
});
final GameSettings settings;
@override
List<dynamic> get props => <dynamic>[
settings,
];
}
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:twister/config/default_global_settings.dart';
import 'package:twister/models/settings/settings_global.dart';
part 'settings_global_state.dart';
class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> {
GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault()));
void setValues({
String? skin,
}) {
emit(
GlobalSettingsState(
settings: GlobalSettings(
skin: skin ?? state.settings.skin,
),
),
);
}
String getParameterValue(String code) {
switch (code) {
case DefaultGlobalSettings.parameterCodeSkin:
return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin);
}
return '';
}
void setParameterValue(String code, String value) {
final String skin = (code == DefaultGlobalSettings.parameterCodeSkin)
? value
: getParameterValue(DefaultGlobalSettings.parameterCodeSkin);
setValues(
skin: skin,
);
}
@override
GlobalSettingsState? fromJson(Map<String, dynamic> json) {
final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String;
return GlobalSettingsState(
settings: GlobalSettings(
skin: skin,
),
);
}
@override
Map<String, dynamic>? toJson(GlobalSettingsState state) {
return <String, dynamic>{
DefaultGlobalSettings.parameterCodeSkin: state.settings.skin,
};
}
}
part of 'settings_global_cubit.dart';
@immutable
class GlobalSettingsState extends Equatable {
const GlobalSettingsState({
required this.settings,
});
final GlobalSettings settings;
@override
List<dynamic> get props => <dynamic>[
settings,
];
}
part of 'settings_cubit.dart';
@immutable
class SettingsState extends Equatable {
const SettingsState({
this.timerValue,
});
final int? timerValue;
@override
List<dynamic> get props => <dynamic>[
timerValue,
];
Map<String, dynamic> get values => <String, dynamic>{
'timerValue': timerValue,
};
}
......@@ -2,20 +2,22 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
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/cubit/nav_cubit.dart';
import 'package:twister/cubit/settings_game_cubit.dart';
import 'package:twister/cubit/settings_global_cubit.dart';
import 'package:twister/cubit/theme_cubit.dart';
import 'package:twister/ui/skeleton.dart';
void main() async {
/// Initialize packages
// Initialize packages
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
final Directory tmpDir = await getTemporaryDirectory();
......@@ -24,8 +26,8 @@ void main() async {
storageDirectory: tmpDir,
);
runApp(
EasyLocalization(
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((value) => runApp(EasyLocalization(
path: 'assets/translations',
supportedLocales: const <Locale>[
Locale('en'),
......@@ -34,8 +36,7 @@ void main() async {
fallbackLocale: const Locale('en'),
useFallbackTranslations: true,
child: const MyApp(),
),
);
)));
}
class MyApp extends StatelessWidget {
......@@ -43,12 +44,18 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final List<String> assets = getImagesAssets();
for (String asset in assets) {
precacheImage(AssetImage(asset), context);
}
return MultiBlocProvider(
providers: [
BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()),
BlocProvider<GameCubit>(create: (context) => GameCubit()),
BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()),
BlocProvider<NavCubit>(create: (context) => NavCubit()),
BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()),
BlocProvider<GameCubit>(create: (context) => GameCubit()),
BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()),
],
child: BlocBuilder<ThemeCubit, ThemeModeState>(
builder: (BuildContext context, ThemeModeState state) {
......@@ -67,7 +74,32 @@ class MyApp extends StatelessWidget {
locale: context.locale,
debugShowCheckedModeBanner: false,
);
}),
},
),
);
}
List<String> getImagesAssets() {
final List<String> assets = [];
const List<String> gameImages = [
'button_back',
'button_delete_saved_game',
'button_resume_game',
'button_start',
'game_end',
'move-blank',
'move-left-foot',
'move-left-hand',
'move-right-foot',
'move-right-hand',
'placeholder',
];
for (String image in gameImages) {
assets.add('assets/ui/$image.png');
}
return assets;
}
}
import 'package:twister/models/game/move.dart';
import 'package:twister/models/settings/settings_game.dart';
import 'package:twister/models/settings/settings_global.dart';
import 'package:twister/utils/tools.dart';
class Game {
Game({
// Settings
required this.gameSettings,
required this.globalSettings,
// State
this.isRunning = false,
this.isStarted = false,
this.isFinished = false,
this.animationInProgress = false,
// Base data
required this.move,
// Game data
required this.history,
});
// Settings
final GameSettings gameSettings;
final GlobalSettings globalSettings;
// State
bool isRunning;
bool isStarted;
bool isFinished;
bool animationInProgress;
// Base data
Move? move;
// Game data
List<Move> history;
factory Game.createEmpty() {
return Game(
// Settings
gameSettings: GameSettings.createDefault(),
globalSettings: GlobalSettings.createDefault(),
// Base data
move: Move.createEmpty(),
// Game data
history: [],
);
}
factory Game.createNew({
GameSettings? gameSettings,
GlobalSettings? globalSettings,
}) {
final GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault();
final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault();
return Game(
// Settings
gameSettings: newGameSettings,
globalSettings: newGlobalSettings,
// State
isRunning: true,
// Base data
move: Move.createEmpty(),
// Game data
history: [],
);
}
bool get canBeResumed => isStarted && !isFinished;
void dump() {
printlog('');
printlog('## Current game dump:');
printlog('');
printlog('$Game:');
printlog(' Settings');
gameSettings.dump();
globalSettings.dump();
printlog(' State');
printlog(' isRunning: $isRunning');
printlog(' isStarted: $isStarted');
printlog(' isFinished: $isFinished');
printlog(' animationInProgress: $animationInProgress');
printlog(' Base data');
printlog(' move: $move');
printlog(' Game data');
printlog(' history: $history');
printlog('');
}
@override
String toString() {
return '$Game(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
// Settings
'gameSettings': gameSettings.toJson(),
'globalSettings': globalSettings.toJson(),
// State
'isRunning': isRunning,
'isStarted': isStarted,
'isFinished': isFinished,
'animationInProgress': animationInProgress,
// Base data
'move': move,
// Game data
'history': history,
};
}
}
import 'package:easy_localization/easy_localization.dart';
import 'package:twister/models/twister_color.dart';
import 'package:twister/models/twister_member.dart';
import 'package:twister/models/game/twister_color.dart';
import 'package:twister/models/game/twister_member.dart';
class Move {
final TwisterColor? color;
......@@ -11,7 +12,7 @@ class Move {
required this.member,
});
factory Move.createNull() {
factory Move.createEmpty() {
return Move(
color: null,
member: null,
......@@ -34,7 +35,7 @@ class Move {
@override
String toString() {
return 'Move(${toJson()})';
return '$Move(${toJson()})';
}
String toSoundAsset() {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment