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

Merge branch '48-add-animations-on-board' into 'master'

Resolve "Add animations on board"

Closes #48

See merge request !47
parents 50a369ca 00dba30b
No related branches found
No related tags found
1 merge request!47Resolve "Add animations on board"
Pipeline #4884 passed
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
app.versionName=1.0.39 app.versionName=1.0.40
app.versionCode=40 app.versionCode=41
...@@ -2,15 +2,21 @@ import 'dart:convert'; ...@@ -2,15 +2,21 @@ import 'dart:convert';
import 'dart:math'; import 'dart:math';
class GameDataItem { class GameDataItem {
final int value; final int? value;
const GameDataItem({ const GameDataItem({
required this.value, required this.value,
}); });
factory GameDataItem.fromValue(int? value) {
return GameDataItem(
value: value,
);
}
factory GameDataItem.fromJson(Map<String, dynamic>? json) { factory GameDataItem.fromJson(Map<String, dynamic>? json) {
return GameDataItem( return GameDataItem(
value: (json?['value'] != null) ? (json?['value'] as int) : 0, value: (json?['value'] != null) ? (json?['value'] as int) : null,
); );
} }
...@@ -26,10 +32,12 @@ class GameDataItem { ...@@ -26,10 +32,12 @@ class GameDataItem {
} }
class GameData { class GameData {
final bool isReady;
final int boardSize; final int boardSize;
final List<List<GameDataItem>> board; final List<List<GameDataItem>> board;
const GameData({ const GameData({
required this.isReady,
required this.boardSize, required this.boardSize,
required this.board, required this.board,
}); });
...@@ -40,7 +48,7 @@ class GameData { ...@@ -40,7 +48,7 @@ class GameData {
for (var y = 0; y < boardSize; y++) { for (var y = 0; y < boardSize; y++) {
final List<GameDataItem> line = []; final List<GameDataItem> line = [];
for (var x = 0; x < boardSize; x++) { for (var x = 0; x < boardSize; x++) {
final GameDataItem item = new GameDataItem(value: 0); final GameDataItem item = new GameDataItem(value: null);
line.add(item); line.add(item);
} }
...@@ -48,6 +56,7 @@ class GameData { ...@@ -48,6 +56,7 @@ class GameData {
} }
return GameData( return GameData(
isReady: true,
boardSize: boardSize, boardSize: boardSize,
board: cells, board: cells,
); );
...@@ -71,13 +80,27 @@ class GameData { ...@@ -71,13 +80,27 @@ class GameData {
} }
return GameData( return GameData(
isReady: true,
boardSize: boardSize, boardSize: boardSize,
board: cells, board: cells,
); );
} }
GameDataItem getCell(int x, int y) {
return this.board[y][x];
}
int? getCellValue(int x, int y) {
return this.getCell(x, y).value;
}
void updateCellValue(int x, int y, int? value) {
this.board[y][x] = new GameDataItem.fromValue(value);
}
factory GameData.fromJson(Map<String, dynamic>? json) { factory GameData.fromJson(Map<String, dynamic>? json) {
return GameData( return GameData(
isReady: (json?['isReady'] != null) ? (json?['isReady'] as bool) : false,
boardSize: (json?['boardSize'] != null) ? (json?['boardSize'] as int) : 0, boardSize: (json?['boardSize'] != null) ? (json?['boardSize'] as int) : 0,
board: (json?['board'] != null) ? (json?['board'] as List<List<GameDataItem>>) : [], board: (json?['board'] != null) ? (json?['board'] as List<List<GameDataItem>>) : [],
); );
...@@ -85,6 +108,7 @@ class GameData { ...@@ -85,6 +108,7 @@ class GameData {
Map<String, dynamic>? toJson() { Map<String, dynamic>? toJson() {
return <String, dynamic>{ return <String, dynamic>{
'isReady': this.isReady,
'boardSize': this.boardSize, 'boardSize': this.boardSize,
'board': this.board, 'board': this.board,
}; };
......
...@@ -58,6 +58,6 @@ class CellPainter extends CustomPainter { ...@@ -58,6 +58,6 @@ class CellPainter extends CustomPainter {
@override @override
bool shouldRepaint(CustomPainter oldDelegate) { bool shouldRepaint(CustomPainter oldDelegate) {
return false; return true;
} }
} }
...@@ -13,12 +13,89 @@ class GamePage extends StatefulWidget { ...@@ -13,12 +13,89 @@ class GamePage extends StatefulWidget {
State<GamePage> createState() => _GamePageState(); State<GamePage> createState() => _GamePageState();
} }
void createNewGame(GameCubit gameCubit, int boardSize) { class _GamePageState extends State<GamePage> with TickerProviderStateMixin {
static const boardSize = 6;
List<List<Animation<double>?>> animations = List.generate(
boardSize,
(i) => List.generate(
boardSize,
(i) => null,
),
);
void resetAnimations() {
animations = List.generate(
boardSize,
(i) => List.generate(
boardSize,
(i) => null,
),
);
}
void createNewGame(GameCubit gameCubit) {
final GameData newGame = GameData.createRandom(boardSize); final GameData newGame = GameData.createRandom(boardSize);
gameCubit.updateGameState(newGame); gameCubit.updateGameState(newGame);
} }
class _GamePageState extends State<GamePage> { void removeCell(GameCubit gameCubit, int x, int y) {
final GameData newGame = gameCubit.state.game ?? GameData.createRandom(boardSize);
// "remove" cell
newGame.updateCellValue(x, y, null);
setState(() {});
// "move down" cells
final controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 750),
)..addListener(() {
if (mounted) {
setState(() {});
}
});
if (mounted) {
setState(() {});
}
Animation<double> animation = Tween(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
curve: Curves.bounceOut,
parent: controller,
))
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
for (var i = 0; i < y; i++) {
newGame.updateCellValue(x, (y - i), newGame.getCellValue(x, (y - i) - 1));
}
newGame.updateCellValue(x, 0, null);
resetAnimations();
setState(() {});
controller.dispose();
}
});
for (var i = 0; i < y; i++) {
animations[(y - i) - 1][x] = animation;
}
controller.forward().orCancel;
}
void updateCellValue(GameCubit gameCubit, int x, int y, int value) {
final GameData newGame = gameCubit.state.game ?? GameData.createRandom(boardSize);
newGame.updateCellValue(x, y, value);
gameCubit.updateGameState(newGame);
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<GameCubit, GameState>( return BlocBuilder<GameCubit, GameState>(
...@@ -29,19 +106,36 @@ class _GamePageState extends State<GamePage> { ...@@ -29,19 +106,36 @@ class _GamePageState extends State<GamePage> {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
if (gameState.game?.isReady != true) {
createNewGame(gameCubit);
}
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
gameState.game != null GestureDetector(
child: gameState.game != null
? GameBoardWidget( ? GameBoardWidget(
gameData: gameState.game!, gameData: gameState.game!,
size: Size(boardWidgetWidth, boardWidgetHeight), size: Size(boardWidgetWidth, boardWidgetHeight),
animations: animations,
) )
: SizedBox.shrink(), : SizedBox.shrink(),
onTapUp: (details) {
double xTap = details.localPosition.dx;
double yTap = details.localPosition.dy;
int x = (xTap / boardWidgetWidth * boardSize).toInt();
int y = (yTap / boardWidgetHeight * boardSize).toInt();
print('[' + x.toString() + ',' + y.toString() + ']');
removeCell(gameCubit, x, y);
},
),
IconButton( IconButton(
onPressed: () { onPressed: () {
createNewGame(gameCubit, boardSize); createNewGame(gameCubit);
}, },
icon: Icon(UniconsSolid.star), icon: Icon(UniconsSolid.star),
color: Colors.white, color: Colors.white,
......
...@@ -3,49 +3,48 @@ import 'package:flutter/material.dart'; ...@@ -3,49 +3,48 @@ import 'package:flutter/material.dart';
import 'package:random/models/game_data.dart'; import 'package:random/models/game_data.dart';
import 'package:random/ui/painters/cell_painter.dart'; import 'package:random/ui/painters/cell_painter.dart';
class GameBoardWidget extends StatefulWidget { class GameBoardWidget extends StatelessWidget {
const GameBoardWidget({ const GameBoardWidget({
super.key, super.key,
required this.gameData, required this.gameData,
required this.size, required this.size,
required this.animations,
}); });
final GameData gameData; final GameData gameData;
final Size size; final Size size;
final List<List<Animation<double>?>> animations;
@override
State<GameBoardWidget> createState() => _GameBoardWidgetState();
}
class _GameBoardWidgetState extends State<GameBoardWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final widgetWidth = widget.size.width; final widgetWidth = this.size.width;
final widgetHeight = widget.size.height; final widgetHeight = this.size.height;
final rowsCount = widget.gameData.board.length; final rowsCount = this.gameData.board.length;
final columnsCount = widget.gameData.board[0].length; final columnsCount = this.gameData.board[0].length;
print('counts: rows=' + rowsCount.toString() + ' / columns=' + columnsCount.toString());
final cellWidth = widgetWidth / columnsCount; final cellWidth = widgetWidth / columnsCount;
final cellHeight = widgetHeight / rowsCount; final cellHeight = widgetHeight / rowsCount;
print('cell: width=' + cellWidth.toString() + ' / height=' + cellHeight.toString());
final List<Widget> cells = []; final List<Widget> cells = [];
for (var y = 0; y < rowsCount; y++) { for (var y = 0; y < rowsCount; y++) {
for (var x = 0; x < columnsCount; x++) { for (var x = 0; x < columnsCount; x++) {
final GameDataItem item = widget.gameData.board[y][x]; final GameDataItem item = this.gameData.board[y][x];
int? value = item.value;
if (value != null) {
final Animation<double>? translation = this.animations[y][x];
final Widget cellContent = CustomPaint( final Widget cellContent = CustomPaint(
size: Size(cellWidth, cellHeight), size: Size(cellWidth, cellHeight),
willChange: false, willChange: false,
painter: CellPainter(value: item.value), painter: CellPainter(value: value),
); );
final Widget cellWidget = Positioned( final Widget cellWidget = Positioned(
left: (x * cellWidth).toDouble(), left: (x * cellWidth).toDouble(),
top: (y * cellHeight).toDouble(), top: ((y + (translation?.value ?? 0)) * cellHeight).toDouble(),
child: Container( child: Container(
width: cellWidth, width: cellWidth,
height: cellHeight, height: cellHeight,
...@@ -56,11 +55,12 @@ class _GameBoardWidgetState extends State<GameBoardWidget> { ...@@ -56,11 +55,12 @@ class _GameBoardWidgetState extends State<GameBoardWidget> {
cells.add(cellWidget); cells.add(cellWidget);
} }
} }
}
return Container( return Container(
width: widgetWidth, width: widgetWidth,
height: widgetHeight, height: widgetHeight,
color: Colors.grey, color: Colors.black,
child: Stack( child: Stack(
children: cells, children: cells,
), ),
......
...@@ -3,7 +3,7 @@ description: A random application, for testing purpose only. ...@@ -3,7 +3,7 @@ description: A random application, for testing purpose only.
publish_to: 'none' publish_to: 'none'
version: 1.0.39+40 version: 1.0.40+41
environment: environment:
sdk: '^3.0.0' sdk: '^3.0.0'
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment