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

Add animations on board game

parent 50a369ca
No related branches found
No related tags found
1 merge request!47Resolve "Add animations on board"
Pipeline #4878 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 {
final GameData newGame = GameData.createRandom(boardSize); static const boardSize = 6;
gameCubit.updateGameState(newGame);
} 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);
gameCubit.updateGameState(newGame);
}
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(() {});
}
class _GamePageState extends State<GamePage> {
@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(
? GameBoardWidget( child: gameState.game != null
gameData: gameState.game!, ? GameBoardWidget(
size: Size(boardWidgetWidth, boardWidgetHeight), gameData: gameState.game!,
) size: Size(boardWidgetWidth, boardWidgetHeight),
: SizedBox.shrink(), animations: animations,
)
: 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,64 +3,64 @@ import 'package:flutter/material.dart'; ...@@ -3,64 +3,64 @@ 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,
child: cellContent, child: cellContent,
), ),
); );
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