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

Normalize game architecture

parent 69d54c5e
No related branches found
No related tags found
1 merge request!13Resolve "Normalize game architecture"
Pipeline #5690 passed
Showing
with 609 additions and 606 deletions
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:tetrisdual/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:tetrisdual/config/default_game_settings.dart';
import 'package:tetrisdual/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,
}) {
emit(
GameSettingsState(
settings: GameSettings(
gameType: gameType ?? state.settings.gameType,
),
),
);
}
String getParameterValue(String code) {
switch (code) {
case DefaultGameSettings.parameterCodeGameType:
return GameSettings.getGameTypeValueFromUnsafe(state.settings.gameType);
}
return '';
}
void setParameterValue(String code, String value) {
final String gameType = code == DefaultGameSettings.parameterCodeGameType
? value
: getParameterValue(DefaultGameSettings.parameterCodeGameType);
setValues(
gameType: gameType,
);
}
@override
GameSettingsState? fromJson(Map<String, dynamic> json) {
final String gameType = json[DefaultGameSettings.parameterCodeGameType] as String;
return GameSettingsState(
settings: GameSettings(
gameType: gameType,
),
);
}
@override
Map<String, dynamic>? toJson(GameSettingsState state) {
return <String, dynamic>{
DefaultGameSettings.parameterCodeGameType: state.settings.gameType,
};
}
}
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:tetrisdual/config/default_global_settings.dart';
import 'package:tetrisdual/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()};
}
}
part of 'theme_cubit.dart';
@immutable
class ThemeModeState extends Equatable {
const ThemeModeState({
this.themeMode,
});
final ThemeMode? themeMode;
@override
List<Object?> get props => <Object?>[
themeMode,
];
}
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:tetrisdual/entity/counter.dart';
import 'package:tetrisdual/layout/board_painter.dart';
import 'package:tetrisdual/provider/data.dart';
class Player {
Player(this.playerId);
int playerId;
int _score = 0;
int _currentTetrimino = 0;
final Counter _counter = Counter();
Widget buildTetriminoWidget(Data myProvider, double width) {
return GestureDetector(
onTapUp: (details) {
if (playerId == myProvider.getCurrentPlayer().playerId) {
pickRandomTetrimino();
myProvider.redraw();
}
},
child: CustomPaint(
size: Size(width, width),
willChange: false,
painter: BoardPainter(_currentTetrimino),
isComplex: true,
key: Key(_currentTetrimino.toString()),
),
);
}
Widget buildManagerWidget(Data myProvider) {
return Expanded(
child: Container(
margin: const EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: myProvider.currentPlayer == playerId
? [
_counter.buildCounterWidget(myProvider),
buildSubmitWidget(myProvider),
]
: [],
),
),
);
}
Widget buildSubmitWidget(Data myProvider) {
const double gainFontSize = 70;
const gainTestStyle = TextStyle(
fontFamily: 'Blocks',
fontSize: gainFontSize,
fontWeight: FontWeight.bold,
);
const submitIcon = Icon(
Icons.done_all,
color: Colors.orange,
size: gainFontSize / 2,
);
const topBorderBlack = BoxDecoration(
border: Border(
top: BorderSide(
color: Colors.black,
width: 2,
),
),
);
return Container(
decoration: topBorderBlack,
padding: const EdgeInsets.only(
top: 10,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'+${_counter.computePoints()}',
style: gainTestStyle,
textHeightBehavior: const TextHeightBehavior(
applyHeightToFirstAscent: false,
applyHeightToLastDescent: false,
),
),
const SizedBox(width: 10),
IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
icon: submitIcon,
onPressed: () {
_score = _score + _counter.computePoints();
_counter.reset();
myProvider.toggleCurrentPlayer();
},
),
const SizedBox(width: 10),
],
),
);
}
Widget buildPlayerBoard(Data myProvider, double screenWidth, bool isActive) {
final double tetriminoBlockWidth = screenWidth / 2.3;
final Color borderColor = isActive ? Colors.greenAccent : Colors.blueGrey;
const double borderWidth = 10;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
_score.toString(),
style: const TextStyle(
fontFamily: 'Blocks',
fontSize: 130,
fontWeight: FontWeight.bold,
),
textHeightBehavior: const TextHeightBehavior(
applyHeightToFirstAscent: false,
applyHeightToLastDescent: false,
),
),
Container(
decoration: BoxDecoration(
border: Border.all(
color: borderColor,
width: borderWidth,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
isActive
? buildTetriminoWidget(myProvider, tetriminoBlockWidth)
: SizedBox(
height: tetriminoBlockWidth,
),
buildManagerWidget(myProvider),
],
),
)
],
);
}
void pickRandomTetrimino() {
// ensure new tetrimino is not same as current one
int newTetrimino = _currentTetrimino;
while (newTetrimino == _currentTetrimino) {
newTetrimino = Random().nextInt(5) + 1;
}
_currentTetrimino = newTetrimino;
}
void resetTetrimino() {
_currentTetrimino = 0;
}
void submitPoints() {
_score = _score + _counter.computePoints();
_counter.reset();
}
}
import 'package:flutter/material.dart';
import 'package:tetrisdual/provider/data.dart';
class Board {
static Widget buildGameBoard(Data myProvider, double screenWidth) {
final Widget player1 = RotatedBox(
quarterTurns: 2,
child: myProvider
.getPlayer(1)
.buildPlayerBoard(myProvider, screenWidth, myProvider.currentPlayer == 1),
);
final Widget player2 = myProvider
.getPlayer(2)
.buildPlayerBoard(myProvider, screenWidth, myProvider.currentPlayer == 2);
final Widget togglePlayerWidget = GestureDetector(
onTapUp: (details) {
myProvider.toggleCurrentPlayer();
},
child: const Text(
'🔄',
style: TextStyle(
fontSize: 50,
),
textHeightBehavior: TextHeightBehavior(
applyHeightToFirstAscent: false,
applyHeightToLastDescent: false,
),
),
);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
player1,
togglePlayerWidget,
player2,
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:tetrisdual/layout/board.dart';
import 'package:tetrisdual/provider/data.dart';
import 'package:tetrisdual/utils/game_utils.dart';
class Game {
static Widget buildGameWidget(Data myProvider, double screenWidth) {
return !myProvider.isGameFinished
? Board.buildGameBoard(myProvider, screenWidth)
: Game.buildEndGameMessage(myProvider);
}
static TextButton buildQuitGameButton(Data myProvider) {
return TextButton(
child: const Image(
image: AssetImage('assets/icons/button_back.png'),
fit: BoxFit.fill,
),
onPressed: () => GameUtils.quitGame(myProvider),
);
}
static Container buildEndGameMessage(Data myProvider) {
const String decorationImageAssetName = 'assets/icons/game_fail.png';
final Widget decorationWidget = TextButton(
child: const Image(
image: AssetImage(decorationImageAssetName),
fit: BoxFit.fill,
),
onPressed: () {},
);
return Container(
margin: const EdgeInsets.all(2),
padding: const EdgeInsets.all(2),
child: Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: [
TableRow(
children: [
Column(
children: [decorationWidget],
),
Column(
children: [buildQuitGameButton(myProvider)],
),
Column(
children: [decorationWidget],
),
],
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:tetrisdual/provider/data.dart';
import 'package:tetrisdual/utils/game_utils.dart';
class Parameters {
static const double separatorHeight = 2.0;
static const double blockMargin = 3.0;
static const double blockPadding = 2.0;
static const Color buttonBackgroundColor = Colors.white;
static const Color buttonBorderColorActive = Colors.blue;
static const Color buttonBorderColorInactive = Colors.white;
static const double buttonBorderWidth = 10.0;
static const double buttonBorderRadius = 8.0;
static const double buttonPadding = 0.0;
static const double buttonMargin = 0.0;
static Widget buildParametersSelector(Data myProvider) {
final List<Widget> lines = [];
final List<String> parameters = myProvider.availableParameters;
for (int index = 0; index < parameters.length; index++) {
lines.add(buildParameterSelector(myProvider, parameters[index]));
lines.add(const SizedBox(height: separatorHeight));
}
final Widget buttonsBlock = buildStartNewGameButton(myProvider);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: separatorHeight),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: lines,
),
),
const SizedBox(height: separatorHeight),
Container(
child: buttonsBlock,
),
],
);
}
static Image buildImageWidget(String imageAssetCode) {
return Image(
image: AssetImage('assets/icons/$imageAssetCode.png'),
fit: BoxFit.fill,
);
}
static Container buildImageContainerWidget(String imageAssetCode) {
return Container(
child: buildImageWidget(imageAssetCode),
);
}
static Column buildDecorationImageWidget() {
return Column(
children: [
TextButton(
child: buildImageContainerWidget('placeholder'),
onPressed: () {},
),
],
);
}
static Container buildStartNewGameButton(Data myProvider) {
return Container(
margin: const EdgeInsets.all(blockMargin),
padding: const EdgeInsets.all(blockPadding),
child: Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: [
TableRow(
children: [
buildDecorationImageWidget(),
Column(
children: [
TextButton(
child: buildImageContainerWidget('button_start'),
onPressed: () => GameUtils.startNewGame(myProvider),
),
],
),
buildDecorationImageWidget(),
],
),
],
),
);
}
static Widget buildParameterSelector(Data myProvider, String parameterCode) {
final List<String> availableValues = myProvider.getParameterAvailableValues(parameterCode);
if (availableValues.length == 1) {
return const SizedBox(height: 0.0);
}
return Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: [
TableRow(
children: [
for (int index = 0; index < availableValues.length; index++)
Column(
children: [
_buildParameterButton(myProvider, parameterCode, availableValues[index])
],
),
],
),
],
);
}
static Widget _buildParameterButton(
Data myProvider,
String parameterCode,
String parameterValue,
) {
final String currentValue = myProvider.getParameterValue(parameterCode).toString();
final bool isActive = (parameterValue == currentValue);
final String imageAsset = '${parameterCode}_$parameterValue';
return TextButton(
child: Container(
margin: const EdgeInsets.all(buttonMargin),
padding: const EdgeInsets.all(buttonPadding),
decoration: BoxDecoration(
color: buttonBackgroundColor,
borderRadius: BorderRadius.circular(buttonBorderRadius),
border: Border.all(
color: isActive ? buttonBorderColorActive : buttonBorderColorInactive,
width: buttonBorderWidth,
),
),
child: buildImageWidget(imageAsset),
),
onPressed: () => myProvider.setParameterValue(parameterCode, parameterValue),
);
}
}
import 'dart:io';
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/services.dart';
import 'package:provider/provider.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:overlay_support/overlay_support.dart'; import 'package:hive/hive.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';
import 'package:tetrisdual/provider/data.dart'; import 'package:tetrisdual/config/theme.dart';
import 'package:tetrisdual/screens/home.dart'; import 'package:tetrisdual/cubit/game_cubit.dart';
import 'package:tetrisdual/cubit/nav_cubit.dart';
import 'package:tetrisdual/cubit/settings_game_cubit.dart';
import 'package:tetrisdual/cubit/settings_global_cubit.dart';
import 'package:tetrisdual/cubit/theme_cubit.dart';
import 'package:tetrisdual/ui/skeleton.dart';
void main() { void main() async {
// Initialize packages
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
final Directory tmpDir = await getTemporaryDirectory();
Hive.init(tmpDir.toString());
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: tmpDir,
);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((value) => runApp(const MyApp())); .then((value) => runApp(EasyLocalization(
path: 'assets/translations',
supportedLocales: const <Locale>[
Locale('en'),
Locale('fr'),
],
fallbackLocale: const Locale('en'),
useFallbackTranslations: true,
child: const MyApp(),
)));
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
...@@ -17,22 +44,56 @@ class MyApp extends StatelessWidget { ...@@ -17,22 +44,56 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChangeNotifierProvider( final List<String> assets = getImagesAssets();
create: (BuildContext context) => Data(), for (String asset in assets) {
child: Consumer<Data>(builder: (context, data, child) { precacheImage(AssetImage(asset), context);
return OverlaySupport( }
child: MaterialApp(
theme: ThemeData( return MultiBlocProvider(
primaryColor: Colors.blue, providers: [
visualDensity: VisualDensity.adaptivePlatformDensity, BlocProvider<NavCubit>(create: (context) => NavCubit()),
), BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()),
home: const Home(), BlocProvider<GameCubit>(create: (context) => GameCubit()),
routes: { BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
Home.id: (context) => const Home(), BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()),
}, ],
), child: BlocBuilder<ThemeCubit, ThemeModeState>(
); builder: (BuildContext context, ThemeModeState state) {
}), return MaterialApp(
title: 'Tetris',
home: const SkeletonScreen(),
// Theme stuff
theme: lightTheme,
darkTheme: darkTheme,
themeMode: state.themeMode,
// Localization stuff
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
);
},
),
); );
} }
List<String> getImagesAssets() {
final List<String> assets = [];
final List<String> gameImages = [
'button_back',
'button_delete_saved_game',
'button_resume_game',
'button_start',
'placeholder',
];
for (String image in gameImages) {
assets.add('assets/ui/$image.png');
}
return assets;
}
} }
class Counter {
// Current counter
bool touch = false; // Does this new tetrimino touch an other tetrimino of same player
int lines = 0; // Count lines fully filled by this new tetrimino
int holes = 0; // Count non fillable holes caused by this new tetrimino
// Points definitions
static const int base = 50;
static const int pointsIfTouch = 50;
static const int pointsPerLine = 60;
static const int pointsPerHole = -10;
int computePoints() {
return base + (touch ? pointsIfTouch : 0) + lines * pointsPerLine + holes * pointsPerHole;
}
void reset() {
touch = false;
lines = 0;
holes = 0;
}
@override
String toString() {
return '$Counter(${toJson()})';
}
Map<String, dynamic> toJson() {
return {
'touch': touch,
'lines': lines,
'holes': holes,
};
}
}
import 'package:tetrisdual/models/game/player.dart';
import 'package:tetrisdual/models/settings/settings_game.dart';
import 'package:tetrisdual/models/settings/settings_global.dart';
import 'package:tetrisdual/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
this.players = const [],
// Game data
this.currentPlayer = 0,
});
// Settings
final GameSettings gameSettings;
final GlobalSettings globalSettings;
// State
bool isRunning;
bool isStarted;
bool isFinished;
bool animationInProgress;
// Base data
List<Player> players;
// Game data
int currentPlayer;
factory Game.createEmpty() {
return Game(
// Settings
gameSettings: GameSettings.createDefault(),
globalSettings: GlobalSettings.createDefault(),
);
}
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
players: [
Player(1),
Player(2),
],
// Game data
currentPlayer: 0,
);
}
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(' players: $players');
printlog(' Game data');
printlog(' currentPlayer: $currentPlayer');
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
'players': players,
// Game data
'currentPlayer': currentPlayer,
};
}
}
import 'dart:math';
import 'package:tetrisdual/models/game/counter.dart';
class Player {
Player(this.playerId);
int playerId;
int score = 0;
int currentTetrimino = 0;
final Counter counter = Counter();
void pickRandomTetrimino() {
// ensure new tetrimino is not same as current one
int newTetrimino = currentTetrimino;
while (newTetrimino == currentTetrimino) {
newTetrimino = Random().nextInt(5) + 1;
}
currentTetrimino = newTetrimino;
}
void resetTetrimino() {
currentTetrimino = 0;
}
void submitPoints() {
score = score + counter.computePoints();
counter.reset();
}
@override
String toString() {
return '$Player(${toJson()})';
}
Map<String, dynamic> toJson() {
return {
'playerId': playerId,
'score': score,
'currentTetrimino': currentTetrimino,
'counter': counter,
};
}
}
import 'package:tetrisdual/config/default_game_settings.dart';
import 'package:tetrisdual/utils/tools.dart';
class GameSettings {
final String gameType;
GameSettings({
required this.gameType,
});
static String getGameTypeValueFromUnsafe(String gameType) {
if (DefaultGameSettings.allowedGameTypeValues.contains(gameType)) {
return gameType;
}
return DefaultGameSettings.defaultGameTypeValue;
}
factory GameSettings.createDefault() {
return GameSettings(
gameType: DefaultGameSettings.defaultGameTypeValue,
);
}
void dump() {
printlog('$GameSettings:');
printlog(' ${DefaultGameSettings.parameterCodeGameType}: $gameType');
printlog('');
}
@override
String toString() {
return '$GameSettings(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
DefaultGameSettings.parameterCodeGameType: gameType,
};
}
}
import 'package:tetrisdual/config/default_global_settings.dart';
import 'package:tetrisdual/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 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tetrisdual/entity/player.dart';
class Data extends ChangeNotifier {
// Configuration available parameters
final List<String> _availableParameters = [];
List<String> get availableParameters => _availableParameters;
// Application default configuration
// Application current configuration
String getParameterValue(String parameterCode) {
switch (parameterCode) {}
return '';
}
List<String> getParameterAvailableValues(String parameterCode) {
switch (parameterCode) {}
return [];
}
void setParameterValue(String parameterCode, String parameterValue) async {
switch (parameterCode) {}
final prefs = await SharedPreferences.getInstance();
prefs.setString(parameterCode, parameterValue);
}
// Game data
bool _gameIsRunning = false;
bool _gameIsFinished = false;
int _currentPlayer = 0;
List<Player?> _players = [null, null];
bool get isGameRunning => _gameIsRunning;
void updateGameIsRunning(bool gameIsRunning) {
_gameIsRunning = gameIsRunning;
notifyListeners();
}
bool get isGameFinished => _gameIsFinished;
void updateGameIsFinished(bool gameIsFinished) {
_gameIsFinished = gameIsFinished;
notifyListeners();
}
int get currentPlayer => _currentPlayer;
void toggleCurrentPlayer() {
if (_currentPlayer == 0) {
// start game
_currentPlayer = 1;
} else {
// Reset current player tetrimino
getCurrentPlayer().resetTetrimino();
// 1 -> 2 ; 2 -> 1
_currentPlayer = 3 - _currentPlayer;
}
// Pick new tetrimino
getCurrentPlayer().pickRandomTetrimino();
notifyListeners();
}
void enableRandomPlayer() {
_currentPlayer = Random().nextInt(2) + 1;
toggleCurrentPlayer();
}
Player getPlayer(int playerId) {
final int playerIndex = playerId - 1;
Player? player = _players[playerIndex];
// Create new player if none
if (null == player) {
player = Player(playerId);
_players[playerIndex] = player;
}
return player;
}
Player getCurrentPlayer() {
return getPlayer(currentPlayer);
}
void redraw() {
notifyListeners();
}
void resetGame() {
_gameIsRunning = false;
_gameIsFinished = false;
_players = [Player(1), Player(2)];
notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:overlay_support/overlay_support.dart';
import 'package:tetrisdual/layout/game.dart';
import 'package:tetrisdual/layout/parameters.dart';
import 'package:tetrisdual/provider/data.dart';
import 'package:tetrisdual/utils/game_utils.dart';
class Home extends StatefulWidget {
const Home({super.key});
static const String id = 'home';
@override
HomeState createState() => HomeState();
}
class HomeState extends State<Home> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final Data myProvider = Provider.of<Data>(context);
final double screenWidth = MediaQuery.of(context).size.width;
final List<Widget> menuActions = [];
if (myProvider.isGameRunning) {
menuActions.add(TextButton(
child: const Image(
image: AssetImage('assets/icons/button_back.png'),
fit: BoxFit.fill,
),
onPressed: () => toast('Long press to quit game...'),
onLongPress: () => GameUtils.quitGame(myProvider),
));
}
return Scaffold(
appBar: AppBar(
actions: menuActions,
),
body: myProvider.isGameRunning
? Game.buildGameWidget(myProvider, screenWidth)
: Parameters.buildParametersSelector(myProvider),
);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment