From 744cef3d3c006cd5ae7b0a11299e0f3080912dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr> Date: Thu, 2 Jun 2022 10:43:49 +0200 Subject: [PATCH] Create minimal playable game --- android/gradle.properties | 4 +- assets/skins/default_0.png | Bin 0 -> 251 bytes assets/skins/images_0.png | Bin 0 -> 251 bytes icons/build_game_icons.sh | 1 + icons/skins/default/0.svg | 2 + icons/skins/images/0.svg | 2 + lib/entities/cell.dart | 62 ++++++++++++++++++++ lib/layout/board.dart | 41 ++++++++++++++ lib/layout/game.dart | 39 +++++++++++-- lib/main.dart | 3 +- lib/provider/data.dart | 63 ++++++++++++++++++++- lib/utils/board_utils.dart | 113 +++++++++++++++++++++++++++++++++++++ lib/utils/game_utils.dart | 5 +- pubspec.yaml | 1 + 14 files changed, 327 insertions(+), 9 deletions(-) create mode 100644 assets/skins/default_0.png create mode 100644 assets/skins/images_0.png create mode 100644 icons/skins/default/0.svg create mode 100644 icons/skins/images/0.svg create mode 100644 lib/entities/cell.dart create mode 100644 lib/layout/board.dart create mode 100644 lib/utils/board_utils.dart diff --git a/android/gradle.properties b/android/gradle.properties index bc2d95e..818e87b 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=0.0.1 -app.versionCode=1 +app.versionName=0.0.2 +app.versionCode=2 diff --git a/assets/skins/default_0.png b/assets/skins/default_0.png new file mode 100644 index 0000000000000000000000000000000000000000..604bda78fcc44f91482257e4580b5d84dd1cbfd9 GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6EX)iH3=2LLnlLaha29w(7Beu&M1U~k*%{Bz zFfcGkmbgZg1m~xflqVLYGL)B>>t*I;7bhncr0V4trO$q6BgMeLz#rff;u;VTP+woa zV#SKJYuEn&|6kZE?L7knL$0TbV@SoEw|5vh859Iq4!%xc<?i4;@^7Zk#|MXV8Z$R| t{y(OdqdIf@29Qe_$OH}l8$NCI{VrF3-f*|Q-ODbJF`lk|F6*2UngC<sMK}Ne literal 0 HcmV?d00001 diff --git a/assets/skins/images_0.png b/assets/skins/images_0.png new file mode 100644 index 0000000000000000000000000000000000000000..604bda78fcc44f91482257e4580b5d84dd1cbfd9 GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6EX)iH3=2LLnlLaha29w(7Beu&M1U~k*%{Bz zFfcGkmbgZg1m~xflqVLYGL)B>>t*I;7bhncr0V4trO$q6BgMeLz#rff;u;VTP+woa zV#SKJYuEn&|6kZE?L7knL$0TbV@SoEw|5vh859Iq4!%xc<?i4;@^7Zk#|MXV8Z$R| t{y(OdqdIf@29Qe_$OH}l8$NCI{VrF3-f*|Q-ODbJF`lk|F6*2UngC<sMK}Ne literal 0 HcmV?d00001 diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh index 52ab3a2..9877717 100755 --- a/icons/build_game_icons.sh +++ b/icons/build_game_icons.sh @@ -33,6 +33,7 @@ AVAILABLE_SKINS=" # Images per skin SKIN_IMAGES=" + 0 1 2 3 diff --git a/icons/skins/default/0.svg b/icons/skins/default/0.svg new file mode 100644 index 0000000..b69eb37 --- /dev/null +++ b/icons/skins/default/0.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#fff" stroke="#505050" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.6"/></svg> diff --git a/icons/skins/images/0.svg b/icons/skins/images/0.svg new file mode 100644 index 0000000..b69eb37 --- /dev/null +++ b/icons/skins/images/0.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#fff" stroke="#505050" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.6"/></svg> diff --git a/lib/entities/cell.dart b/lib/entities/cell.dart new file mode 100644 index 0000000..57d927a --- /dev/null +++ b/lib/entities/cell.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + +import '../provider/data.dart'; +import '../utils/board_utils.dart'; + +class Cell { + int value; + + Cell( + @required this.value, + ); + + Container widget(Data myProvider, int row, int col) { + String imageAsset = this.getImageAssetName(myProvider); + + return Container( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 100), + transitionBuilder: (Widget child, Animation<double> animation) { + return ScaleTransition(child: child, scale: animation); + }, + child: Image( + image: AssetImage(imageAsset), + fit: BoxFit.fill, + key: ValueKey<int>(imageAsset.hashCode), + ), + ), + ); + } + + Container widgetFillBoardWithColor(Data myProvider) { + String imageAsset = this.getImageAssetName(myProvider); + + return Container( + margin: EdgeInsets.all(2), + decoration: BoxDecoration( + border: Border.all( + color: Colors.black, + width: 4, + ), + ), + child: GestureDetector( + child: Image( + image: AssetImage(imageAsset), + fit: BoxFit.fill + ), + onTap: () { + BoardUtils.fillBoardFromFirstCell(myProvider, this.value); + if (BoardUtils.checkBoardIsSolved(myProvider)) { + myProvider.updateGameWon(true); + } + }, + ) + ); + } + + String getImageAssetName(Data myProvider) { + int cellValue = this.value; + return 'assets/skins/' + myProvider.skin + '_' + cellValue.toString() + '.png'; + } + +} diff --git a/lib/layout/board.dart b/lib/layout/board.dart new file mode 100644 index 0000000..f8ae151 --- /dev/null +++ b/lib/layout/board.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +import '../provider/data.dart'; + +class Board { + + static Container buildGameBoard(Data myProvider) { + return Container( + margin: EdgeInsets.all(4), + padding: EdgeInsets.all(4), + child: Column( + children: [ + buildGameTileset(myProvider), + ], + ), + ); + } + + static Table buildGameTileset(Data myProvider) { + int boardSize = myProvider.boardSize; + List cells = myProvider.cells; + + return Table( + defaultColumnWidth: IntrinsicColumnWidth(), + children: [ + for (var row = 0; row < boardSize; row++) + TableRow(children: [ + for (var col = 0; col < boardSize; col++) + Column(children: [ + cells[row][col].widget( + myProvider, + row, + col + ) + ]), + ]), + ] + ); + } + +} diff --git a/lib/layout/game.dart b/lib/layout/game.dart index f479032..b2720f6 100644 --- a/lib/layout/game.dart +++ b/lib/layout/game.dart @@ -2,6 +2,8 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../entities/cell.dart'; +import '../layout/board.dart'; import '../provider/data.dart'; import '../utils/game_utils.dart'; @@ -16,21 +18,50 @@ class Game { crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( - child: Text('❇️'), + child: Board.buildGameBoard(myProvider), ), SizedBox(height: 2), Container( - height: 150, - width: double.maxFinite, child: gameIsFinished ? Game.buildEndGameMessage(myProvider) - : Text('❇️'), + : Game.buildSelectColorBar(myProvider), ), ], ), ); } + static Container buildSelectColorBar(Data myProvider) { + List cells = myProvider.cells; + + int maxValue = myProvider.colorsCount; + + return Container( + margin: EdgeInsets.all(2), + padding: EdgeInsets.all(2), + + child: Table( + defaultColumnWidth: IntrinsicColumnWidth(), + children: [ + TableRow( + children: [ + for ( + int value = 1; + value <= maxValue; + value++ + ) + Column( + children: [ + Cell(value).widgetFillBoardWithColor(myProvider) + ] + ), + ] + ), + ] + ), + ); + } + static FlatButton buildRestartGameButton(Data myProvider) { return FlatButton( child: Container( diff --git a/lib/main.dart b/lib/main.dart index f4e8d27..7f44866 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,7 +29,8 @@ class MyApp extends StatelessWidget { routes: { Home.id: (context) => Home(), }, - )); + ), + ); }), ); } diff --git a/lib/provider/data.dart b/lib/provider/data.dart index cd696e5..c421301 100644 --- a/lib/provider/data.dart +++ b/lib/provider/data.dart @@ -19,10 +19,17 @@ class Data extends ChangeNotifier { // Game data bool _gameIsRunning = false; bool _gameWon = false; + int _boardSize = 0; + int _colorsCount = 0; + List _cells = []; String get level => _level; void updateLevel(String level) { _level = level; + + updateBoardSize(getBoardSizeFromLevel(level)); + updateColorsCount(getColorsCountFromLevel(level)); + notifyListeners(); } @@ -39,6 +46,7 @@ class Data extends ChangeNotifier { case 'skin': { return _skin; } break; } + return ''; } List getParameterAvailableValues(String parameterCode) { @@ -68,6 +76,55 @@ class Data extends ChangeNotifier { setParameterValue('skin', prefs.getString('skin') ?? _skinDefault); } + int get boardSize => _boardSize; + void updateBoardSize(int boardSize) { + _boardSize = boardSize; + } + + int get colorsCount => _colorsCount; + void updateColorsCount(int colorsCount) { + _colorsCount = colorsCount; + } + + int getBoardSizeFromLevel(String level) { + switch(level) { + case 'easy': { return 6; } + break; + case 'normal': { return 10; } + break; + case 'hard': { return 14; } + break; + case 'nightmare': { return 20; } + break; + } + return 8; + } + + int getColorsCountFromLevel(String level) { + switch(level) { + case 'easy': { return 4; } + break; + case 'normal': { return 5; } + break; + case 'hard': { return 6; } + break; + case 'nightmare': { return 7; } + break; + } + return 4; + } + + List get cells => _cells; + void updateCells(List cells) { + _cells = cells; + notifyListeners(); + } + + updateCellValue(int col, int row, int value) { + _cells[row][col].value = value; + notifyListeners(); + } + bool get gameIsRunning => _gameIsRunning; void updateGameIsRunning(bool gameIsRunning) { _gameIsRunning = gameIsRunning; @@ -75,10 +132,14 @@ class Data extends ChangeNotifier { } bool isGameFinished() { - return false; + return _gameWon; } bool get gameWon => _gameWon; + void updateGameWon(bool gameWon) { + _gameWon = gameWon; + notifyListeners(); + } void resetGame() { _gameIsRunning = false; diff --git a/lib/utils/board_utils.dart b/lib/utils/board_utils.dart new file mode 100644 index 0000000..61848e3 --- /dev/null +++ b/lib/utils/board_utils.dart @@ -0,0 +1,113 @@ +import 'dart:math'; + +import '../entities/cell.dart'; +import '../provider/data.dart'; + +class BoardUtils { + + static printGrid(List cells) { + String stringValues = '01234567'; + print(''); + print('-------'); + for (var rowIndex = 0; rowIndex < cells.length; rowIndex++) { + String row = ''; + for (var colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { + row += stringValues[cells[rowIndex][colIndex].value]; + } + print(row); + } + print('-------'); + print(''); + } + + static createNewBoard(Data myProvider) { + int boardSize = myProvider.boardSize; + int maxValue = myProvider.colorsCount; + + var rand = new Random(); + + List grid = []; + for (var rowIndex = 0; rowIndex < boardSize; rowIndex++) { + List row = []; + for (var colIndex = 0; colIndex < boardSize; colIndex++) { + int value = 1 + rand.nextInt(maxValue); + row.add(Cell(value)); + } + grid.add(row); + } + printGrid(grid); + + myProvider.resetGame(); + myProvider.updateCells(grid); + } + + static fillBoardFromFirstCell(Data myProvider, int value) { + List cellsToFill = BoardUtils.getSiblingFillableCells(myProvider, 0, 0, [[0, 0]]); + + for (var cellIndex = 0; cellIndex < cellsToFill.length; cellIndex++) { + myProvider.updateCellValue(cellsToFill[cellIndex][1], cellsToFill[cellIndex][0], value); + } + } + + static List getSiblingFillableCells(Data myProvider, row, col, siblingCells) { + List cells = myProvider.cells; + int boardSize = myProvider.boardSize; + + int referenceValue = cells[row][col].value; + + for (var deltaRow = -1; deltaRow <= 1; deltaRow++) { + for (var deltaCol = -1; deltaCol <= 1; deltaCol++) { + if (deltaCol == 0 || deltaRow == 0) { + int candidateRow = row + deltaRow; + int candidateCol = col + deltaCol; + + if ( + (candidateRow >= 0 && candidateRow < boardSize) + && + (candidateCol >= 0 && candidateCol < boardSize) + ) { + if (cells[candidateRow][candidateCol].value == referenceValue) { + bool alreadyFound = false; + for (var index = 0; index < siblingCells.length; index++) { + if ( + (siblingCells[index][0] == candidateRow) + && + (siblingCells[index][1] == candidateCol) + ) { + alreadyFound = true; + } + } + if (!alreadyFound) { + siblingCells.add([candidateRow, candidateCol]); + siblingCells = getSiblingFillableCells(myProvider, candidateRow, candidateCol, siblingCells); + } + } + } + } + } + } + + return siblingCells; + } + + static bool checkBoardIsSolved(Data myProvider) { + List cells = myProvider.cells; + int boardSize = myProvider.boardSize; + + // check grid is fully completed and does not contain conflict + int previousValue = cells[0][0].value; + for (var row = 0; row < boardSize; row++) { + for (var col = 0; col < boardSize; col++) { + if (cells[row][col].value == 0 || cells[row][col].value != previousValue) { + return false; + } + previousValue = cells[row][col].value; + } + } + + print('-> ok grid solved!'); + + return true; + } + +} diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart index 58e9792..a7005ec 100644 --- a/lib/utils/game_utils.dart +++ b/lib/utils/game_utils.dart @@ -1,4 +1,5 @@ import '../provider/data.dart'; +import '../utils/board_utils.dart'; class GameUtils { @@ -9,8 +10,10 @@ class GameUtils { static Future<void> startGame(Data myProvider) async { print('Starting game'); print('- level: ' + myProvider.level); + print('- size: ' + myProvider.boardSize.toString()); + print('- colors: ' + myProvider.colorsCount.toString()); - myProvider.resetGame(); + BoardUtils.createNewBoard(myProvider); myProvider.updateGameIsRunning(true); } diff --git a/pubspec.yaml b/pubspec.yaml index 56bfd6d..c16bdce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,3 +21,4 @@ flutter: uses-material-design: true assets: - assets/icons/ + - assets/skins/ -- GitLab