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

Merge branch '2-add-minimal-game-design' into 'master'

Resolve "Add minimal game design"

Closes #2

See merge request !2
parents 4e118578 9db7eb18
No related branches found
No related tags found
1 merge request!2Resolve "Add minimal game design"
Pipeline #5919 passed
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
app.versionName=0.0.1 app.versionName=0.0.2
app.versionCode=1 app.versionCode=2
Add minimal playable game.
Ajout d'un jeu jouable minimal.
...@@ -31,13 +31,13 @@ class GameCubit extends HydratedCubit<GameState> { ...@@ -31,13 +31,13 @@ class GameCubit extends HydratedCubit<GameState> {
isStarted: state.currentGame.isStarted, isStarted: state.currentGame.isStarted,
isFinished: state.currentGame.isFinished, isFinished: state.currentGame.isFinished,
animationInProgress: state.currentGame.animationInProgress, animationInProgress: state.currentGame.animationInProgress,
boardAnimated: state.currentGame.boardAnimated,
// Base data // Base data
board: state.currentGame.board, board: state.currentGame.board,
// Game data // Game data
currentPlayer: state.currentGame.currentPlayer,
scores: state.currentGame.scores, scores: state.currentGame.scores,
); );
// game.dump(); game.dump();
updateState(game); updateState(game);
} }
...@@ -74,16 +74,84 @@ class GameCubit extends HydratedCubit<GameState> { ...@@ -74,16 +74,84 @@ class GameCubit extends HydratedCubit<GameState> {
refresh(); refresh();
} }
// FIXME: should be removed void toggleCurrentPlayer() {
void doSomething() { state.currentGame.currentPlayer = 1 - state.currentGame.currentPlayer;
printlog('shoud do something!');
refresh(); refresh();
} }
// FIXME: should be removed void tapOnCell(int cellIndex) {
void logSomething([dynamic yolo]) { printlog('tapOnCell: $cellIndex');
printlog('logSomething: $yolo');
if (!state.currentGame.isCurrentPlayerHouse(cellIndex)) {
printlog('not allowed');
return;
}
if (state.currentGame.board.cells[cellIndex] == 0) {
printlog('empty cell');
return;
}
if (!state.currentGame.isMoveAllowed(cellIndex)) {
printlog('not allowed (need to give at least one seed to other player)');
return;
}
state.currentGame.animationInProgress = true;
refresh();
final int lastCellIndex = animateSeedsDistribution(cellIndex);
animateSeedsEarning(lastCellIndex);
toggleCurrentPlayer();
state.currentGame.animationInProgress = false;
refresh();
}
int animateSeedsDistribution(int sourceCellIndex) {
printlog('animateSeedsDistribution / sourceCellIndex: $sourceCellIndex');
final int seedsCount = state.currentGame.board.cells[sourceCellIndex];
// empty source cell
state.currentGame.board.cells[sourceCellIndex] = 0;
printlog('animateSeedsDistribution / empty source cell');
refresh(); refresh();
int cellIndex = sourceCellIndex;
for (int i = 0; i < seedsCount; i++) {
cellIndex = state.currentGame.getNextCellIndex(cellIndex, sourceCellIndex);
state.currentGame.board.cells[cellIndex] += 1;
refresh();
}
refresh();
return cellIndex;
}
void animateSeedsEarning(int lastCellIndex) {
printlog('animateSeedsEarning / lastCellIndex: $lastCellIndex');
if (state.currentGame.isOpponentHouse(lastCellIndex)) {
final int seedsCount = state.currentGame.board.cells[lastCellIndex];
printlog('found $seedsCount seed(s) on final house.');
if ([2, 3].contains(seedsCount)) {
printlog('ok will earn these seeds.');
state.currentGame.board.cells[lastCellIndex] = 0;
state.currentGame.scores[state.currentGame.currentPlayer] += seedsCount;
refresh();
// (recursively) check previous cells
animateSeedsEarning(state.currentGame.getPreviousCellIndex(lastCellIndex));
}
}
} }
@override @override
......
...@@ -26,7 +26,20 @@ class Board { ...@@ -26,7 +26,20 @@ class Board {
void dump() { void dump() {
printlog(''); printlog('');
printlog(' $Board:'); printlog(' $Board:');
printlog(' cells: $cells');
const List<List<int>> indexes = [
[11, 10, 9, 8, 7, 6],
[0, 1, 2, 3, 4, 5],
];
for (List<int> line in indexes) {
String row = ' ';
for (int index in line) {
row += '[${cells[index].toString().padLeft(2, ' ')}]';
}
printlog(row);
}
printlog(''); printlog('');
} }
......
...@@ -3,14 +3,6 @@ import 'package:awale/models/settings/settings_game.dart'; ...@@ -3,14 +3,6 @@ import 'package:awale/models/settings/settings_game.dart';
import 'package:awale/models/settings/settings_global.dart'; import 'package:awale/models/settings/settings_global.dart';
import 'package:awale/utils/tools.dart'; import 'package:awale/utils/tools.dart';
typedef MovingTile = String;
typedef Move = Board;
typedef Player = String;
typedef ConflictsCount = List<List<int>>;
typedef AnimatedBoard = List<List<bool>>;
typedef AnimatedBoardSequence = List<AnimatedBoard>;
typedef Word = String;
class Game { class Game {
Game({ Game({
// Settings // Settings
...@@ -22,12 +14,12 @@ class Game { ...@@ -22,12 +14,12 @@ class Game {
this.isStarted = false, this.isStarted = false,
this.isFinished = false, this.isFinished = false,
this.animationInProgress = false, this.animationInProgress = false,
this.boardAnimated = const [],
// Base data // Base data
required this.board, required this.board,
// Game data // Game data
required this.currentPlayer,
required this.scores, required this.scores,
}); });
...@@ -40,12 +32,12 @@ class Game { ...@@ -40,12 +32,12 @@ class Game {
bool isStarted; bool isStarted;
bool isFinished; bool isFinished;
bool animationInProgress; bool animationInProgress;
AnimatedBoard boardAnimated;
// Base data // Base data
final Board board; final Board board;
// Game data // Game data
int currentPlayer;
List<int> scores; List<int> scores;
factory Game.createNull() { factory Game.createNull() {
...@@ -56,6 +48,7 @@ class Game { ...@@ -56,6 +48,7 @@ class Game {
// Base data // Base data
board: Board.createNull(), board: Board.createNull(),
// Game data // Game data
currentPlayer: 0,
scores: [0, 0], scores: [0, 0],
); );
} }
...@@ -80,16 +73,57 @@ class Game { ...@@ -80,16 +73,57 @@ class Game {
globalSettings: newGlobalSettings, globalSettings: newGlobalSettings,
// State // State
isRunning: true, isRunning: true,
boardAnimated: [],
// Base data // Base data
board: Board.createNew(cells: cells), board: Board.createNew(cells: cells),
// Game data // Game data
currentPlayer: 0,
scores: [0, 0], scores: [0, 0],
); );
} }
bool get canBeResumed => isStarted && !isFinished; bool get canBeResumed => isStarted && !isFinished;
int getNextCellIndex(int cellIndex, int firstCellIndex) {
final int nextCellIndex = (cellIndex + 1) % board.cells.length;
if (nextCellIndex == firstCellIndex) {
return getNextCellIndex(nextCellIndex, firstCellIndex);
}
return nextCellIndex;
}
int getPreviousCellIndex(int cellIndex) {
return (cellIndex - 1) % board.cells.length;
}
bool isCurrentPlayerHouse(int cellIndex) {
const allowedCellIndexes = [
[0, 1, 2, 3, 4, 5],
[6, 7, 8, 9, 10, 11],
];
return allowedCellIndexes[currentPlayer].contains(cellIndex);
}
bool isOpponentHouse(int cellIndex) {
return !isCurrentPlayerHouse(cellIndex);
}
// Ensure move is allowed, from cell seeds count
bool isMoveAllowed(int cellIndex) {
final int seedsCount = board.cells[cellIndex];
int finalCellIndex = cellIndex;
for (int i = 0; i < seedsCount; i++) {
finalCellIndex = getNextCellIndex(finalCellIndex, cellIndex);
if (isOpponentHouse(finalCellIndex)) {
return true;
}
}
return false;
}
void dump() { void dump() {
printlog(''); printlog('');
printlog('## Current game dump:'); printlog('## Current game dump:');
...@@ -103,7 +137,6 @@ class Game { ...@@ -103,7 +137,6 @@ class Game {
printlog(' isStarted: $isStarted'); printlog(' isStarted: $isStarted');
printlog(' isFinished: $isFinished'); printlog(' isFinished: $isFinished');
printlog(' animationInProgress: $animationInProgress'); printlog(' animationInProgress: $animationInProgress');
printlog('board:');
board.dump(); board.dump();
printlog(' Game data'); printlog(' Game data');
printlog(' scores: $scores'); printlog(' scores: $scores');
...@@ -125,7 +158,6 @@ class Game { ...@@ -125,7 +158,6 @@ class Game {
'isStarted': isStarted, 'isStarted': isStarted,
'isFinished': isFinished, 'isFinished': isFinished,
'animationInProgress': animationInProgress, 'animationInProgress': animationInProgress,
'boardAnimated': boardAnimated,
// Base data // Base data
'board': board.toJson(), 'board': board.toJson(),
// Game data // Game data
......
...@@ -4,9 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; ...@@ -4,9 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:awale/cubit/game_cubit.dart'; import 'package:awale/cubit/game_cubit.dart';
import 'package:awale/models/game/game.dart'; import 'package:awale/models/game/game.dart';
import 'package:awale/ui/widgets/game/game_board.dart'; import 'package:awale/ui/widgets/game/game_board.dart';
import 'package:awale/ui/widgets/game/game_bottom.dart';
import 'package:awale/ui/widgets/game/game_end.dart'; import 'package:awale/ui/widgets/game/game_end.dart';
import 'package:awale/ui/widgets/game/game_top.dart';
class GameLayout extends StatelessWidget { class GameLayout extends StatelessWidget {
const GameLayout({super.key}); const GameLayout({super.key});
...@@ -24,11 +22,9 @@ class GameLayout extends StatelessWidget { ...@@ -24,11 +22,9 @@ class GameLayout extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const GameTopWidget(),
const SizedBox(height: 8), const SizedBox(height: 8),
const GameBoardWidget(), const GameBoardWidget(),
const SizedBox(height: 8), const SizedBox(height: 8),
const GameBottomWidget(),
const Expanded(child: SizedBox.shrink()), const Expanded(child: SizedBox.shrink()),
currentGame.isFinished ? const GameEndWidget() : const SizedBox.shrink(), currentGame.isFinished ? const GameEndWidget() : const SizedBox.shrink(),
], ],
......
...@@ -3,6 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; ...@@ -3,6 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:awale/cubit/game_cubit.dart'; import 'package:awale/cubit/game_cubit.dart';
import 'package:awale/models/game/game.dart'; import 'package:awale/models/game/game.dart';
import 'package:awale/ui/widgets/game/game_house.dart';
import 'package:awale/ui/widgets/game/game_player.dart';
import 'package:awale/ui/widgets/game/game_score.dart';
class GameBoardWidget extends StatelessWidget { class GameBoardWidget extends StatelessWidget {
const GameBoardWidget({super.key}); const GameBoardWidget({super.key});
...@@ -13,9 +16,89 @@ class GameBoardWidget extends StatelessWidget { ...@@ -13,9 +16,89 @@ class GameBoardWidget extends StatelessWidget {
child: BlocBuilder<GameCubit, GameState>( child: BlocBuilder<GameCubit, GameState>(
builder: (BuildContext context, GameState gameState) { builder: (BuildContext context, GameState gameState) {
final Game currentGame = gameState.currentGame; final Game currentGame = gameState.currentGame;
final Color borderColor = Theme.of(context).colorScheme.onSurface;
// FIXME: should be implemented Widget getHouseContent(int cellIndex) {
return Text(currentGame.toString()); final bool isTapAllowed = currentGame.isCurrentPlayerHouse(cellIndex);
return GestureDetector(
onTap: () {
if (isTapAllowed && !currentGame.animationInProgress) {
BlocProvider.of<GameCubit>(context).tapOnCell(cellIndex);
}
},
child: GameHouseWidget(
seedsCount: currentGame.board.cells[cellIndex],
active: isTapAllowed,
),
);
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GamePlayerWidget(
active: currentGame.currentPlayer == 0,
),
Container(
margin: const EdgeInsets.all(2),
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: borderColor,
borderRadius: BorderRadius.circular(2),
border: Border.all(
color: borderColor,
width: 2,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GameScoreWidget(
score: currentGame.scores[0],
),
Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: [
TableRow(children: [
getHouseContent(0),
getHouseContent(11),
]),
TableRow(children: [
getHouseContent(1),
getHouseContent(10),
]),
TableRow(children: [
getHouseContent(2),
getHouseContent(9),
]),
TableRow(children: [
getHouseContent(3),
getHouseContent(8),
]),
TableRow(children: [
getHouseContent(4),
getHouseContent(7),
]),
TableRow(children: [
getHouseContent(5),
getHouseContent(6),
]),
],
),
GameScoreWidget(
score: currentGame.scores[1],
)
],
),
),
GamePlayerWidget(
active: currentGame.currentPlayer == 1,
),
],
);
}, },
), ),
); );
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:awale/cubit/game_cubit.dart';
import 'package:awale/models/game/game.dart';
class GameBottomWidget extends StatelessWidget {
const GameBottomWidget({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<GameCubit, GameState>(
builder: (BuildContext context, GameState gameState) {
final Game currentGame = gameState.currentGame;
// FIXME: should be implemented
return Text(currentGame.board.toString());
},
);
}
}
import 'package:flutter/material.dart';
class GameHouseWidget extends StatelessWidget {
const GameHouseWidget({
super.key,
required this.seedsCount,
required this.active,
});
final int seedsCount;
final bool active;
@override
Widget build(BuildContext context) {
final Color borderColor = active
? Theme.of(context).colorScheme.onSecondary
: Theme.of(context).colorScheme.onTertiary;
return AspectRatio(
aspectRatio: 1,
child: Container(
margin: const EdgeInsets.all(2),
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: borderColor,
borderRadius: BorderRadius.circular(2),
border: Border.all(
color: borderColor,
width: 2,
),
),
width: 50,
child: Text(
seedsCount.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
),
);
}
}
import 'package:flutter/material.dart';
class GamePlayerWidget extends StatelessWidget {
const GamePlayerWidget({
super.key,
required this.active,
});
final bool active;
@override
Widget build(BuildContext context) {
final Color baseColor = active ? Colors.pink : Theme.of(context).colorScheme.surface;
return Container(
margin: const EdgeInsets.all(2),
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: baseColor,
borderRadius: BorderRadius.circular(2),
border: Border.all(
color: baseColor,
width: 2,
),
),
width: 100,
height: 100,
child: const SizedBox.shrink(),
);
}
}
import 'package:flutter/material.dart';
class GameScoreWidget extends StatelessWidget {
const GameScoreWidget({
super.key,
required this.score,
});
final int score;
@override
Widget build(BuildContext context) {
final Color borderColor = Theme.of(context).colorScheme.onPrimary;
return Container(
margin: const EdgeInsets.all(2),
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: borderColor,
borderRadius: BorderRadius.circular(2),
border: Border.all(
color: borderColor,
width: 2,
),
),
width: 100,
child: Text(
score.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 50,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:awale/cubit/game_cubit.dart';
import 'package:awale/models/game/game.dart';
class GameTopWidget extends StatelessWidget {
const GameTopWidget({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<GameCubit, GameState>(
builder: (BuildContext context, GameState gameState) {
final Game currentGame = gameState.currentGame;
// FIXME: should be implemented
return Text(currentGame.scores.toString());
},
);
}
}
...@@ -3,7 +3,7 @@ description: Awale game ...@@ -3,7 +3,7 @@ description: Awale game
publish_to: "none" publish_to: "none"
version: 0.0.1+1 version: 0.0.2+2
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