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

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

Resolve "Normalize game architecture"

Closes #14

See merge request !14
parents e1e9abd6 34e7d262
No related branches found
No related tags found
1 merge request!14Resolve "Normalize game architecture"
Pipeline #5810 passed
Showing
with 669 additions and 10 deletions
assets/ui/placeholder.png

170 B

Improve/normalize game architecture.
Amélioration/normalisation de l'architecture du jeu.
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#db8616" stroke="#000" stroke-width="2"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#db1616" stroke="#000" stroke-width="2"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#6a78de" stroke="#000" stroke-width="2"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#be6ade" stroke="#000" stroke-width="2"/><rect x="8.5767" y="8.6213" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="22.27" y="8.6213" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="36.557" y="8.6213" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="50.844" y="8.6213" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="50.844" y="22.864" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="50.844" y="37.105" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="50.844" y="51.285" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="65.064" y="51.285" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="79.284" y="51.285" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="79.284" y="65.266" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="79.284" y="79.24" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="65.064" y="79.24" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="50.844" y="79.24" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="36.557" y="79.24" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="22.27" y="79.24" width="14.139" height="14.139" fill="#353ab0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="22.27" y="65.266" width="14.139" height="14.139" fill="#2d00e0" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><circle cx="29.34" cy="44.175" r="5.7602" fill="#209239" stroke="#1c6e24" stroke-linecap="round" stroke-width="4.2294"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#be6ade" stroke="#000" stroke-width="2"/><rect x="8.5767" y="8.6213" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="22.27" y="8.6213" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="36.557" y="8.6213" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="50.844" y="8.6213" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="50.844" y="22.864" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="50.844" y="37.105" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="50.844" y="51.285" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="65.064" y="51.285" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="79.284" y="51.285" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="79.284" y="65.266" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="79.284" y="79.24" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="65.064" y="79.24" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="50.844" y="79.24" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="36.557" y="79.24" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="22.27" y="79.24" width="14.139" height="14.139" fill="#727272" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><rect x="22.27" y="65.266" width="14.139" height="14.139" fill="#6f6f71" stroke="#fff" stroke-linecap="round" stroke-width="3.6305"/><circle cx="29.34" cy="44.175" r="5.7602" fill="#595959" stroke="#454545" stroke-linecap="round" stroke-width="4.2294"/></svg>
import 'package:snake/utils/tools.dart';
class DefaultGameSettings {
// available game parameters codes
static const String parameterCodeLevel = 'level';
static const String parameterCodeSize = 'size';
static const List<String> availableParameters = [
parameterCodeLevel,
parameterCodeSize,
];
// level: available values
static const String levelValueEasy = 'easy';
static const String levelValueMedium = 'medium';
static const String levelValueHard = 'hard';
static const String levelValueNightmare = 'nightmare';
static const List<String> allowedLevelValues = [
levelValueEasy,
levelValueMedium,
levelValueHard,
levelValueNightmare,
];
// level: default value
static const String defaultLevelValue = levelValueMedium;
// size: available values
static const String sizeValueSmall = '10x10';
static const String sizeValueMedium = '15x15';
static const String sizeValueLarge = '20x20';
static const String sizeValueExtraLarge = '30x30';
static const List<String> allowedSizeValues = [
sizeValueSmall,
sizeValueMedium,
sizeValueLarge,
sizeValueExtraLarge,
];
// size: default value
static const String defaultSizeValue = sizeValueMedium;
// available values from parameter code
static List<String> getAvailableValues(String parameterCode) {
switch (parameterCode) {
case parameterCodeLevel:
return DefaultGameSettings.allowedLevelValues;
case parameterCodeSize:
return DefaultGameSettings.allowedSizeValues;
}
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:snake/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 String skinValueRetro = 'retro';
static const List<String> allowedSkinValues = [
skinValueColors,
skinValueRetro,
];
// skin: default value
static const String defaultSkinValue = skinValueRetro;
// 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 = [
//
];
}
import 'package:flutter/material.dart';
import 'package:unicons/unicons.dart';
import 'package:snake/ui/screens/page_about.dart';
import 'package:snake/ui/screens/page_game.dart';
import 'package:snake/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;
}
import 'package:flutter/material.dart';
/// Colors from Tailwind CSS (v3.0) - June 2022
///
/// https://tailwindcss.com/docs/customizing-colors
const int _primaryColor = 0xFF6366F1;
const MaterialColor primarySwatch = MaterialColor(_primaryColor, <int, Color>{
50: Color(0xFFEEF2FF), // indigo-50
100: Color(0xFFE0E7FF), // indigo-100
200: Color(0xFFC7D2FE), // indigo-200
300: Color(0xFFA5B4FC), // indigo-300
400: Color(0xFF818CF8), // indigo-400
500: Color(_primaryColor), // indigo-500
600: Color(0xFF4F46E5), // indigo-600
700: Color(0xFF4338CA), // indigo-700
800: Color(0xFF3730A3), // indigo-800
900: Color(0xFF312E81), // indigo-900
});
const int _textColor = 0xFF64748B;
const MaterialColor textSwatch = MaterialColor(_textColor, <int, Color>{
50: Color(0xFFF8FAFC), // slate-50
100: Color(0xFFF1F5F9), // slate-100
200: Color(0xFFE2E8F0), // slate-200
300: Color(0xFFCBD5E1), // slate-300
400: Color(0xFF94A3B8), // slate-400
500: Color(_textColor), // slate-500
600: Color(0xFF475569), // slate-600
700: Color(0xFF334155), // slate-700
800: Color(0xFF1E293B), // slate-800
900: Color(0xFF0F172A), // slate-900
});
const Color errorColor = Color(0xFFDC2626); // red-600
final ColorScheme lightColorScheme = ColorScheme.light(
primary: primarySwatch.shade500,
secondary: primarySwatch.shade500,
onSecondary: Colors.white,
error: errorColor,
onSurface: textSwatch.shade500,
surface: textSwatch.shade50,
surfaceContainerHighest: Colors.white,
shadow: textSwatch.shade900.withOpacity(.1),
);
final ColorScheme darkColorScheme = ColorScheme.dark(
primary: primarySwatch.shade500,
secondary: primarySwatch.shade500,
onSecondary: Colors.white,
error: errorColor,
onSurface: textSwatch.shade300,
surface: const Color(0xFF262630),
surfaceContainerHighest: const Color(0xFF282832),
shadow: textSwatch.shade900.withOpacity(.2),
);
final ThemeData lightTheme = ThemeData(
colorScheme: lightColorScheme,
fontFamily: 'Nunito',
textTheme: TextTheme(
displayLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
displayMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
displaySmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
headlineLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
headlineMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
headlineSmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
titleLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
titleMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
titleSmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
bodyLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
bodyMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
bodySmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
labelLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
labelMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
labelSmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
),
);
final ThemeData darkTheme = lightTheme.copyWith(
colorScheme: darkColorScheme,
textTheme: TextTheme(
displayLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
displayMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
displaySmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
headlineLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
headlineMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
headlineSmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
titleLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
titleMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
titleSmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
bodyLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
bodyMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
bodySmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
labelLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
labelMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
labelSmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
),
);
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:snake/models/game/game.dart';
import 'package:snake/models/settings/settings_game.dart';
import 'package:snake/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
board: state.currentGame.board,
);
// game.dump();
updateState(game);
}
void startNewGame({
required GameSettings gameSettings,
required GlobalSettings globalSettings,
}) {
final Game newGame = Game.createNew(
// Settings
gameSettings: gameSettings,
globalSettings: globalSettings,
);
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();
}
@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,
];
}
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:snake/config/menu.dart';
class NavCubit extends HydratedCubit<int> {
NavCubit() : super(0);
void updateIndex(int index) {
if (Menu.isIndexAllowed(index)) {
emit(index);
} else {
goToGamePage();
}
}
void goToGamePage() {
emit(Menu.indexGame);
}
void goToSettingsPage() {
emit(Menu.indexSettings);
}
void goToAboutPage() {
emit(Menu.indexAbout);
}
@override
int fromJson(Map<String, dynamic> json) {
return Menu.indexGame;
}
@override
Map<String, dynamic>? toJson(int state) {
return <String, int>{'pageIndex': state};
}
}
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:snake/config/default_game_settings.dart';
import 'package:snake/models/settings/settings_game.dart';
part 'settings_game_state.dart';
class GameSettingsCubit extends HydratedCubit<GameSettingsState> {
GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault()));
void setValues({
String? level,
String? size,
}) {
emit(
GameSettingsState(
settings: GameSettings(
level: level ?? state.settings.level,
size: size ?? state.settings.size,
),
),
);
}
String getParameterValue(String code) {
switch (code) {
case DefaultGameSettings.parameterCodeLevel:
return GameSettings.getLevelValueFromUnsafe(state.settings.level);
case DefaultGameSettings.parameterCodeSize:
return GameSettings.getSizeValueFromUnsafe(state.settings.size);
}
return '';
}
void setParameterValue(String code, String value) {
final String level = code == DefaultGameSettings.parameterCodeLevel
? value
: getParameterValue(DefaultGameSettings.parameterCodeLevel);
final String size = code == DefaultGameSettings.parameterCodeSize
? value
: getParameterValue(DefaultGameSettings.parameterCodeSize);
setValues(
level: level,
size: size,
);
}
@override
GameSettingsState? fromJson(Map<String, dynamic> json) {
final String level = json[DefaultGameSettings.parameterCodeLevel] as String;
final String size = json[DefaultGameSettings.parameterCodeSize] as String;
return GameSettingsState(
settings: GameSettings(
level: level,
size: size,
),
);
}
@override
Map<String, dynamic>? toJson(GameSettingsState state) {
return <String, dynamic>{
DefaultGameSettings.parameterCodeLevel: state.settings.level,
DefaultGameSettings.parameterCodeSize: state.settings.size,
};
}
}
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:snake/config/default_global_settings.dart';
import 'package:snake/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:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
part 'theme_state.dart';
class ThemeCubit extends HydratedCubit<ThemeModeState> {
ThemeCubit() : super(const ThemeModeState());
void getTheme(ThemeModeState state) {
emit(state);
}
@override
ThemeModeState? fromJson(Map<String, dynamic> json) {
switch (json['themeMode']) {
case 'ThemeMode.dark':
return const ThemeModeState(themeMode: ThemeMode.dark);
case 'ThemeMode.light':
return const ThemeModeState(themeMode: ThemeMode.light);
case 'ThemeMode.system':
default:
return const ThemeModeState(themeMode: ThemeMode.system);
}
}
@override
Map<String, String>? toJson(ThemeModeState state) {
return <String, String>{'themeMode': state.themeMode.toString()};
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment