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

Merge branch '76-normalize-game-architecture' into 'master'

Resolve "Normalize game architecture"

Closes #76

See merge request !77
parents 5ba8b349 2921cc1c
No related branches found
No related tags found
1 merge request!77Resolve "Normalize game architecture"
Pipeline #5802 passed
Showing
with 798 additions and 506 deletions
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:unicons/unicons.dart'; import 'package:unicons/unicons.dart';
import 'package:wordguessing/ui/screens/about_page.dart'; import 'package:wordguessing/ui/screens/page_about.dart';
import 'package:wordguessing/ui/screens/game_page.dart'; import 'package:wordguessing/ui/screens/page_game.dart';
import 'package:wordguessing/ui/screens/settings_page.dart'; import 'package:wordguessing/ui/screens/page_settings.dart';
class MenuItem { class MenuItem {
final String code;
final Icon icon; final Icon icon;
final Widget page; final Widget page;
const MenuItem({ const MenuItem({
required this.code,
required this.icon, required this.icon,
required this.page, required this.page,
}); });
} }
class Menu { class Menu {
static List<MenuItem> items = [ static const indexGame = 0;
const MenuItem( static const menuItemGame = MenuItem(
code: 'bottom_nav_home',
icon: Icon(UniconsLine.home), icon: Icon(UniconsLine.home),
page: GamePage(), page: PageGame(),
), );
const MenuItem(
code: 'bottom_nav_settings', static const indexSettings = 1;
static const menuItemSettings = MenuItem(
icon: Icon(UniconsLine.setting), icon: Icon(UniconsLine.setting),
page: SettingsPage(), page: PageSettings(),
), );
const MenuItem(
code: 'bottom_nav_about', static const indexAbout = 2;
static const menuItemAbout = MenuItem(
icon: Icon(UniconsLine.info_circle), icon: Icon(UniconsLine.info_circle),
page: AboutPage(), page: PageAbout(),
), );
];
static Widget getPageWidget(int pageIndex) { static Map<int, MenuItem> items = {
return Menu.items.elementAt(pageIndex).page; indexGame: menuItemGame,
indexSettings: menuItemSettings,
indexAbout: menuItemAbout,
};
static bool isIndexAllowed(int pageIndex) {
return items.keys.contains(pageIndex);
} }
static List<BottomNavigationBarItem> getMenuItems() { static Widget getPageWidget(int pageIndex) {
return Menu.items return items[pageIndex]?.page ?? menuItemGame.page;
.map((MenuItem item) => BottomNavigationBarItem(
icon: item.icon,
label: tr(item.code),
))
.toList();
} }
static int itemsCount = Menu.items.length; static int itemsCount = Menu.items.length;
......
...@@ -39,11 +39,9 @@ final ColorScheme lightColorScheme = ColorScheme.light( ...@@ -39,11 +39,9 @@ final ColorScheme lightColorScheme = ColorScheme.light(
secondary: primarySwatch.shade500, secondary: primarySwatch.shade500,
onSecondary: Colors.white, onSecondary: Colors.white,
error: errorColor, error: errorColor,
background: textSwatch.shade200,
onBackground: textSwatch.shade500,
onSurface: textSwatch.shade500, onSurface: textSwatch.shade500,
surface: textSwatch.shade50, surface: textSwatch.shade50,
surfaceVariant: Colors.white, surfaceContainerHighest: Colors.white,
shadow: textSwatch.shade900.withOpacity(.1), shadow: textSwatch.shade900.withOpacity(.1),
); );
...@@ -52,11 +50,9 @@ final ColorScheme darkColorScheme = ColorScheme.dark( ...@@ -52,11 +50,9 @@ final ColorScheme darkColorScheme = ColorScheme.dark(
secondary: primarySwatch.shade500, secondary: primarySwatch.shade500,
onSecondary: Colors.white, onSecondary: Colors.white,
error: errorColor, error: errorColor,
background: const Color(0xFF171724),
onBackground: textSwatch.shade400,
onSurface: textSwatch.shade300, onSurface: textSwatch.shade300,
surface: const Color(0xFF262630), surface: const Color(0xFF262630),
surfaceVariant: const Color(0xFF282832), surfaceContainerHighest: const Color(0xFF282832),
shadow: textSwatch.shade900.withOpacity(.2), shadow: textSwatch.shade900.withOpacity(.2),
); );
...@@ -192,5 +188,3 @@ final ThemeData darkTheme = lightTheme.copyWith( ...@@ -192,5 +188,3 @@ final ThemeData darkTheme = lightTheme.copyWith(
), ),
), ),
); );
final ThemeData appTheme = darkTheme;
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:wordguessing/models/game/game.dart';
import 'package:wordguessing/models/settings/settings_game.dart';
import 'package:wordguessing/models/settings/settings_global.dart';
part 'game_state.dart';
class GameCubit extends HydratedCubit<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
word: state.currentGame.word,
otherWords: state.currentGame.otherWords,
images: state.currentGame.images,
// Game data
recentWordsKeys: state.currentGame.recentWordsKeys,
questionsCount: state.currentGame.questionsCount,
goodAnswers: state.currentGame.goodAnswers,
wrongAnswers: state.currentGame.wrongAnswers,
);
// game.dump();
updateState(game);
}
void startNewGame({
required GameSettings gameSettings,
required GlobalSettings globalSettings,
}) {
final Game newGame = Game.createNew(
// Settings
gameSettings: gameSettings,
globalSettings: globalSettings,
);
updateState(newGame);
nextWord();
newGame.dump();
updateState(newGame);
refresh();
}
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 nextWord() {
state.currentGame.pickNewWord();
refresh();
}
void checkWord(word) {
if (state.currentGame.word.key == word.key) {
state.currentGame.goodAnswers++;
nextWord();
} else {
state.currentGame.wrongAnswers++;
}
refresh();
}
@override
GameState? fromJson(Map<String, dynamic> json) {
final Game currentGame = json['currentGame'] as Game;
return GameState(
currentGame: currentGame,
);
}
@override
Map<String, dynamic>? toJson(GameState state) {
return <String, dynamic>{
'currentGame': state.currentGame.toJson(),
};
}
}
part of 'game_cubit.dart';
@immutable
class GameState extends Equatable {
const GameState({
required this.currentGame,
});
final Game currentGame;
@override
List<dynamic> get props => <dynamic>[
currentGame,
];
}
...@@ -2,26 +2,32 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; ...@@ -2,26 +2,32 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:wordguessing/config/menu.dart'; import 'package:wordguessing/config/menu.dart';
class BottomNavCubit extends HydratedCubit<int> { class NavCubit extends HydratedCubit<int> {
BottomNavCubit() : super(0); NavCubit() : super(0);
void updateIndex(int index) { void updateIndex(int index) {
if (isIndexAllowed(index)) { if (Menu.isIndexAllowed(index)) {
emit(index); emit(index);
} else { } else {
goToHomePage(); goToGamePage();
} }
} }
bool isIndexAllowed(int index) { void goToGamePage() {
return (index >= 0) && (index < Menu.itemsCount); emit(Menu.indexGame);
} }
void goToHomePage() => emit(0); void goToSettingsPage() {
emit(Menu.indexSettings);
}
void goToAboutPage() {
emit(Menu.indexAbout);
}
@override @override
int fromJson(Map<String, dynamic> json) { int fromJson(Map<String, dynamic> json) {
return 0; return Menu.indexGame;
} }
@override @override
......
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:wordguessing/config/default_game_settings.dart';
import 'package:wordguessing/models/settings/settings_game.dart';
part 'settings_game_state.dart';
class GameSettingsCubit extends HydratedCubit<GameSettingsState> {
GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault()));
void setValues({
String? gameType,
String? lang,
}) {
emit(
GameSettingsState(
settings: GameSettings(
gameType: gameType ?? state.settings.gameType,
lang: lang ?? state.settings.lang,
),
),
);
}
String getParameterValue(String code) {
switch (code) {
case DefaultGameSettings.parameterCodeGameType:
return GameSettings.getGameTypeValueFromUnsafe(state.settings.gameType);
case DefaultGameSettings.parameterCodeLangValue:
return GameSettings.getLangValueFromUnsafe(state.settings.lang);
}
return '';
}
void setParameterValue(String code, String value) {
final String gameType = code == DefaultGameSettings.parameterCodeGameType
? value
: getParameterValue(DefaultGameSettings.parameterCodeGameType);
final String lang = code == DefaultGameSettings.parameterCodeLangValue
? value
: getParameterValue(DefaultGameSettings.parameterCodeLangValue);
setValues(
gameType: gameType,
lang: lang,
);
}
@override
GameSettingsState? fromJson(Map<String, dynamic> json) {
final String gameType = json[DefaultGameSettings.parameterCodeGameType] as String;
final String lang = json[DefaultGameSettings.parameterCodeLangValue] as String;
return GameSettingsState(
settings: GameSettings(
gameType: gameType,
lang: lang,
),
);
}
@override
Map<String, dynamic>? toJson(GameSettingsState state) {
return <String, dynamic>{
DefaultGameSettings.parameterCodeGameType: state.settings.gameType,
DefaultGameSettings.parameterCodeLangValue: state.settings.lang,
};
}
}
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:wordguessing/config/default_global_settings.dart';
import 'package:wordguessing/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,
];
}
import 'package:wordguessing/data/game_data.dart'; import 'package:wordguessing/data/game_data.dart';
import 'package:wordguessing/models/word.dart'; import 'package:wordguessing/models/data/word.dart';
import 'package:wordguessing/utils/tools.dart'; import 'package:wordguessing/utils/tools.dart';
class FetchDataHelper { class FetchDataHelper {
...@@ -38,7 +38,7 @@ class FetchDataHelper { ...@@ -38,7 +38,7 @@ class FetchDataHelper {
} }
} }
List<Word> getWords(String lang, int count) { List<Word> getWords({required String lang, required int count}) {
if (_words.isEmpty || lang != _lang) { if (_words.isEmpty || lang != _lang) {
init(lang); init(lang);
} }
......
...@@ -2,20 +2,23 @@ import 'dart:io'; ...@@ -2,20 +2,23 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:wordguessing/config/default_game_settings.dart';
import 'package:wordguessing/config/theme.dart'; import 'package:wordguessing/config/theme.dart';
import 'package:wordguessing/cubit/bottom_nav_cubit.dart'; import 'package:wordguessing/cubit/game_cubit.dart';
import 'package:wordguessing/cubit/nav_cubit.dart';
import 'package:wordguessing/cubit/settings_game_cubit.dart';
import 'package:wordguessing/cubit/settings_global_cubit.dart';
import 'package:wordguessing/cubit/theme_cubit.dart'; import 'package:wordguessing/cubit/theme_cubit.dart';
import 'package:wordguessing/provider/data.dart';
import 'package:wordguessing/ui/skeleton.dart'; import 'package:wordguessing/ui/skeleton.dart';
void main() async { void main() async {
/// Initialize packages // Initialize packages
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized(); await EasyLocalization.ensureInitialized();
final Directory tmpDir = await getTemporaryDirectory(); final Directory tmpDir = await getTemporaryDirectory();
...@@ -24,8 +27,8 @@ void main() async { ...@@ -24,8 +27,8 @@ void main() async {
storageDirectory: tmpDir, storageDirectory: tmpDir,
); );
runApp( SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
EasyLocalization( .then((value) => runApp(EasyLocalization(
path: 'assets/translations', path: 'assets/translations',
supportedLocales: const <Locale>[ supportedLocales: const <Locale>[
Locale('en'), Locale('en'),
...@@ -34,8 +37,7 @@ void main() async { ...@@ -34,8 +37,7 @@ void main() async {
fallbackLocale: const Locale('en'), fallbackLocale: const Locale('en'),
useFallbackTranslations: true, useFallbackTranslations: true,
child: const MyApp(), child: const MyApp(),
), )));
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
...@@ -43,17 +45,21 @@ class MyApp extends StatelessWidget { ...@@ -43,17 +45,21 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<String> assets = getImagesAssets();
for (String asset in assets) {
precacheImage(AssetImage(asset), context);
}
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), BlocProvider<NavCubit>(create: (context) => NavCubit()),
BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()), BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()),
BlocProvider<GameCubit>(create: (context) => GameCubit()),
BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()),
], ],
child: BlocBuilder<ThemeCubit, ThemeModeState>( child: BlocBuilder<ThemeCubit, ThemeModeState>(
builder: (BuildContext context, ThemeModeState state) { builder: (BuildContext context, ThemeModeState state) {
return ChangeNotifierProvider(
create: (BuildContext context) => Data(),
child: Consumer<Data>(
builder: (context, data, child) {
return MaterialApp( return MaterialApp(
title: 'Jeux de mots et lettres', title: 'Jeux de mots et lettres',
home: const SkeletonScreen(), home: const SkeletonScreen(),
...@@ -72,7 +78,29 @@ class MyApp extends StatelessWidget { ...@@ -72,7 +78,29 @@ class MyApp extends StatelessWidget {
}, },
), ),
); );
}), }
);
List<String> getImagesAssets() {
final List<String> assets = [];
final List<String> gameImages = [
'button_back',
'button_delete_saved_game',
'button_resume_game',
'button_start',
'game_fail',
'game_win',
'placeholder',
];
for (String gameType in DefaultGameSettings.allowedGameTypeValues) {
gameImages.add('${DefaultGameSettings.parameterCodeGameType}_$gameType');
}
for (String image in gameImages) {
assets.add('assets/ui/$image.png');
}
return assets;
} }
} }
...@@ -9,16 +9,24 @@ class Word { ...@@ -9,16 +9,24 @@ class Word {
required this.images, required this.images,
}); });
Map<String, dynamic> toJson() { factory Word.createEmpty() {
return { return const Word(
'key': key, key: '',
'text': text, text: '',
'images': images.toString(), images: [],
}; );
} }
@override @override
String toString() { String toString() {
return toJson().toString(); return '$Word(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
'key': key,
'text': text,
'images': images,
};
} }
} }
import 'package:wordguessing/config/default_game_settings.dart';
import 'package:wordguessing/data/fetch_data_helper.dart';
import 'package:wordguessing/models/data/word.dart';
import 'package:wordguessing/models/settings/settings_game.dart';
import 'package:wordguessing/models/settings/settings_global.dart';
import 'package:wordguessing/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.word,
required this.otherWords,
required this.images,
// Game data
this.recentWordsKeys = const [],
this.questionsCount = 0,
this.goodAnswers = 0,
this.wrongAnswers = 0,
});
// Settings
final GameSettings gameSettings;
final GlobalSettings globalSettings;
// State
bool isRunning;
bool isStarted;
bool isFinished;
bool animationInProgress;
// Base data
Word word;
List<Word> otherWords;
List<Word> images;
// Game data
List<String> recentWordsKeys;
int questionsCount;
int goodAnswers;
int wrongAnswers;
factory Game.createEmpty() {
return Game(
// Settings
gameSettings: GameSettings.createDefault(),
globalSettings: GlobalSettings.createDefault(),
// Base data
word: Word.createEmpty(),
otherWords: [],
images: [],
// Game data
recentWordsKeys: [],
);
}
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
word: Word.createEmpty(),
otherWords: [],
images: [],
// Game data
recentWordsKeys: [],
);
}
bool get canBeResumed => isStarted && !isFinished;
bool get gameWon => isRunning && isStarted && isFinished;
void updateWord(Word newWord) {
word = newWord;
if (newWord.key != '') {
recentWordsKeys.insert(0, newWord.key);
recentWordsKeys = recentWordsKeys.take(DefaultGameSettings.recentWordsCount).toList();
}
}
void pickNewWord() {
switch (gameSettings.gameType) {
case DefaultGameSettings.gameTypeValuePickImage:
pickDataForGamePickImage();
break;
case DefaultGameSettings.gameTypeValuePickWord:
pickDataForGamePickWord();
break;
default:
}
questionsCount++;
}
bool isRecentlyPicked(String key) {
return recentWordsKeys.contains(key);
}
void pickDataForGamePickImage() {
Word word;
int attempts = 0;
do {
final List<Word> words = FetchDataHelper().getWords(
lang: gameSettings.lang,
count: DefaultGameSettings.itemsCount,
);
word = words.take(1).toList()[0];
if ((words.length == DefaultGameSettings.itemsCount) && !isRecentlyPicked(word.key)) {
updateWord(word);
final List<Word> pickedImages = [];
for (var element in words) {
pickedImages.add(element);
}
images = pickedImages;
}
attempts++;
} while (word != word && attempts < 10);
}
void pickDataForGamePickWord() {
Word word;
int attempts = 0;
do {
final List<Word> words = FetchDataHelper().getWords(
lang: gameSettings.lang,
count: DefaultGameSettings.itemsCount,
);
word = words.take(1).toList()[0];
if ((words.length >= DefaultGameSettings.itemsCount) && !isRecentlyPicked(word.key)) {
updateWord(word);
otherWords = words.skip(1).toList();
images = [word];
}
attempts++;
} while (word != word && attempts < 10);
}
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(' word: $word');
printlog(' otherWords: $otherWords');
printlog(' images: $images');
printlog(' Game data');
printlog(' recentWordsKeys: $recentWordsKeys');
printlog(' questionsCount: $questionsCount');
printlog(' goodAnswers: $goodAnswers');
printlog(' wrongAnswers: $wrongAnswers');
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
'word': word.toJson(),
'otherWords': otherWords,
'images': images,
// Game data
'recentWordsKeys': recentWordsKeys,
'questionsCount': questionsCount,
'goodAnswers': goodAnswers,
'wrongAnswers': wrongAnswers,
};
}
}
import 'package:wordguessing/config/default_game_settings.dart';
import 'package:wordguessing/utils/tools.dart';
class GameSettings {
final String gameType;
final String lang;
GameSettings({
required this.gameType,
required this.lang,
});
static String getGameTypeValueFromUnsafe(String gameType) {
if (DefaultGameSettings.allowedGameTypeValues.contains(gameType)) {
return gameType;
}
return DefaultGameSettings.defaultGameTypeValue;
}
static String getLangValueFromUnsafe(String lang) {
if (DefaultGameSettings.allowedLangValues.contains(lang)) {
return lang;
}
return DefaultGameSettings.defaultLangValue;
}
factory GameSettings.createDefault() {
return GameSettings(
gameType: DefaultGameSettings.defaultGameTypeValue,
lang: DefaultGameSettings.defaultLangValue,
);
}
void dump() {
printlog('$GameSettings:');
printlog(' ${DefaultGameSettings.parameterCodeGameType}: $gameType');
printlog(' ${DefaultGameSettings.parameterCodeLangValue}: $lang');
printlog('');
}
@override
String toString() {
return '$GameSettings(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
DefaultGameSettings.parameterCodeGameType: gameType,
DefaultGameSettings.parameterCodeLangValue: lang,
};
}
}
import 'package:wordguessing/config/default_global_settings.dart';
import 'package:wordguessing/utils/tools.dart';
class GlobalSettings {
String skin;
GlobalSettings({
required this.skin,
});
static String getSkinValueFromUnsafe(String skin) {
if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) {
return skin;
}
return DefaultGlobalSettings.defaultSkinValue;
}
factory GlobalSettings.createDefault() {
return GlobalSettings(
skin: DefaultGlobalSettings.defaultSkinValue,
);
}
void dump() {
printlog('$GlobalSettings:');
printlog(' ${DefaultGlobalSettings.parameterCodeSkin}: $skin');
printlog('');
}
@override
String toString() {
return '$GlobalSettings(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
DefaultGlobalSettings.parameterCodeSkin: skin,
};
}
}
import 'package:flutter/foundation.dart';
import 'package:wordguessing/models/word.dart';
enum GameType { pickWord, pickImage }
class Data extends ChangeNotifier {
GameType? _gameType;
// Language
String _lang = '';
// randomization
Word? _word;
List<Word> _otherWords = [];
List<Word> _images = [];
final int _recentWordsCount = 20;
List<String> _recentWordsKeys = [];
// game data
int _questionsCount = 0;
int _goodAnswers = 0;
int _wrongAnswers = 0;
GameType? get gameType => _gameType;
void updateGameType(GameType gameType) {
_gameType = gameType;
notifyListeners();
}
void resetGameType() {
_gameType = null;
notifyListeners();
}
String get lang => _lang;
void updateLang(String value) {
_lang = value;
notifyListeners();
}
Word? get word => _word;
void updateWord(Word? newWord) {
_word = newWord;
if ((newWord != null) && (newWord.key != '')) {
_recentWordsKeys.insert(0, newWord.key);
_recentWordsKeys = _recentWordsKeys.take(_recentWordsCount).toList();
}
notifyListeners();
}
bool isRecentlyPicked(String word) {
return _recentWordsKeys.contains(word);
}
List<Word> get otherWords => _otherWords;
void updateOtherWords(List<Word> words) {
_otherWords = words;
notifyListeners();
}
List<Word> get images => _images;
void updateImages(List<Word> images) {
_images = images;
notifyListeners();
}
void resetGame() {
resetGameType();
updateLang('');
updateQuestionsCount(0);
updateGoodAnswers(0);
updateWrongAnswers(0);
updateWord(null);
updateOtherWords([]);
updateImages([]);
notifyListeners();
}
int get questionsCount => _questionsCount;
void updateQuestionsCount(int value) {
_questionsCount = value;
notifyListeners();
}
int get goodAnswers => _goodAnswers;
void updateGoodAnswers(int value) {
_goodAnswers = value;
notifyListeners();
}
int get wrongAnswers => _wrongAnswers;
void updateWrongAnswers(int value) {
_wrongAnswers = value;
notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wordguessing/cubit/game_cubit.dart';
import 'package:wordguessing/models/game/game.dart';
import 'package:wordguessing/ui/widgets/actions/button_game_quit.dart';
class GameEndWidget extends StatelessWidget {
const GameEndWidget({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<GameCubit, GameState>(
builder: (BuildContext context, GameState gameState) {
final Game currentGame = gameState.currentGame;
final Image decorationImage = Image(
image: AssetImage(
currentGame.gameWon ? 'assets/ui/game_win.png' : 'assets/ui/game_fail.png'),
fit: BoxFit.fill,
);
return Container(
margin: const EdgeInsets.all(2),
padding: const EdgeInsets.all(2),
child: Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: [
TableRow(
children: [
Column(
children: [decorationImage],
),
Column(
children: [
currentGame.animationInProgress == true
? decorationImage
: const QuitGameButton()
],
),
Column(
children: [decorationImage],
),
],
),
],
),
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:wordguessing/ui/widgets/game/score_bar.dart';
class GameTopWidget extends StatelessWidget {
const GameTopWidget({super.key});
@override
Widget build(BuildContext context) {
return const ScoreBarWidget();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wordguessing/provider/data.dart';
class GameAbstract extends StatelessWidget {
const GameAbstract({super.key});
final int countWords = 4;
void startGame(Data myProvider, String lang) {
myProvider.updateLang(lang);
nextWord(myProvider);
}
void nextWord(Data myProvider) {
pickData(myProvider);
myProvider.updateQuestionsCount(myProvider.questionsCount + 1);
}
void pickData(Data myProvider) {}
void checkWord(Data myProvider, word) {
if (myProvider.word?.key == word.key) {
myProvider.updateGoodAnswers(myProvider.goodAnswers + 1);
nextWord(myProvider);
} else {
myProvider.updateWrongAnswers(myProvider.wrongAnswers + 1);
}
}
Widget buildScoreItemContainer(String text, Color blockColor) {
// Darken block color to get border color
const double amount = 0.2;
final hsl = HSLColor.fromColor(blockColor);
final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
final Color borderColor = hslDark.toColor();
return Container(
margin: const EdgeInsets.symmetric(horizontal: 15),
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: blockColor,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: borderColor,
width: 4,
),
),
child: Text(
text,
style: const TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
);
}
String getGoodAnswersString(Data myProvider) {
final int count = myProvider.questionsCount - 1;
return '👍 ${myProvider.goodAnswers}/$count';
}
String getWrongAnswersString(Data myProvider) {
final int count = myProvider.questionsCount - 1;
return '🚩 ${myProvider.wrongAnswers}/$count';
}
Widget buildScoreContainer(Data myProvider) {
return Container(
padding: const EdgeInsets.all(5),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildScoreItemContainer(
getGoodAnswersString(myProvider),
Colors.green,
),
const SizedBox(width: 20),
buildScoreItemContainer(
getWrongAnswersString(myProvider),
Colors.orange,
),
],
),
);
}
Widget buildStartGameBlock(Data myProvider) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.teal,
backgroundColor: Colors.teal,
padding: const EdgeInsets.all(35),
),
child: const Text(
"🇫🇷",
style: TextStyle(
fontSize: 60,
color: Colors.black,
),
),
onPressed: () => startGame(myProvider, 'fr'),
),
const SizedBox(height: 15),
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.teal,
backgroundColor: Colors.teal,
padding: const EdgeInsets.all(35),
),
child: const Text(
"🇬🇧",
style: TextStyle(
fontSize: 60,
color: Colors.black,
),
),
onPressed: () => startGame(myProvider, 'en'),
),
],
);
}
List<Widget> buildPageContent(Data myProvider) {
return <Widget>[];
}
Widget buildPage(BuildContext context) {
final Data myProvider = Provider.of<Data>(context);
return SizedBox.expand(
child: FittedBox(
fit: BoxFit.contain,
alignment: Alignment.center,
child: SizedBox(
height: (MediaQuery.of(context).size.height),
width: (MediaQuery.of(context).size.width),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: buildPageContent(myProvider),
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return buildPage(context);
}
}
import 'package:flutter/material.dart';
import 'package:wordguessing/data/fetch_data_helper.dart';
import 'package:wordguessing/models/word.dart';
import 'package:wordguessing/provider/data.dart';
import 'package:wordguessing/ui/games/abstract_game.dart';
class GamePickImagePage extends GameAbstract {
const GamePickImagePage({super.key});
@override
pickData(Data myProvider) {
Word word;
int attempts = 0;
do {
final List<Word> words = FetchDataHelper().getWords(myProvider.lang, countWords);
word = words.take(1).toList()[0];
if ((words.length == countWords) && !myProvider.isRecentlyPicked(word.key)) {
myProvider.updateWord(word);
final List<Word> images = [];
for (var element in words) {
images.add(element);
}
myProvider.updateImages(images);
}
attempts++;
} while (myProvider.word != word && attempts < 10);
}
Widget buildImageContainer(Data myProvider, Word word) {
const double imageSize = 130;
String imageAsset = 'assets/placeholder.png';
if ((word.images.isNotEmpty)) {
imageAsset = 'assets/images/${word.images[0]}';
}
return TextButton(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.blue.shade200,
width: 8,
),
),
margin: const EdgeInsets.all(2),
child: Image(
image: AssetImage(imageAsset),
width: imageSize,
height: imageSize,
fit: BoxFit.fill,
),
),
onPressed: () {
checkWord(myProvider, word);
},
);
}
Widget buildImageItemsBlock(Data myProvider) {
final List<Word> images = myProvider.images;
if (images.length != countWords) {
return const SizedBox.shrink();
}
images.sort((a, b) => a.key.compareTo(b.key));
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildImageContainer(myProvider, images[0]),
buildImageContainer(myProvider, images[1]),
],
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildImageContainer(myProvider, images[2]),
buildImageContainer(myProvider, images[3]),
],
)
],
);
}
Widget buildTextItemBlock(Data myProvider) {
final Word? word = myProvider.word;
if (word == null) {
return const SizedBox.shrink();
}
return Container(
decoration: BoxDecoration(
color: Colors.blue.shade800,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: Colors.white,
width: 6,
),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(
word.text,
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
);
}
@override
List<Widget> buildPageContent(Data myProvider) {
return <Widget>[
buildTextItemBlock(myProvider),
const SizedBox(height: 2),
((myProvider.word == null) || (myProvider.word?.key == ''))
? buildStartGameBlock(myProvider)
: buildScoreContainer(myProvider),
const SizedBox(height: 2),
buildImageItemsBlock(myProvider),
];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment