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

Add minimal playable game

parent 4e118578
No related branches found
No related tags found
1 merge request!2Resolve "Add minimal game design"
Pipeline #5916 passed
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
Add minimal playable game.
Ajout d'un jeu jouable minimal.
......@@ -31,13 +31,13 @@ class GameCubit extends HydratedCubit<GameState> {
isStarted: state.currentGame.isStarted,
isFinished: state.currentGame.isFinished,
animationInProgress: state.currentGame.animationInProgress,
boardAnimated: state.currentGame.boardAnimated,
// Base data
board: state.currentGame.board,
// Game data
currentPlayer: state.currentGame.currentPlayer,
scores: state.currentGame.scores,
);
// game.dump();
game.dump();
updateState(game);
}
......@@ -74,16 +74,84 @@ class GameCubit extends HydratedCubit<GameState> {
refresh();
}
// FIXME: should be removed
void doSomething() {
printlog('shoud do something!');
void toggleCurrentPlayer() {
state.currentGame.currentPlayer = 1 - state.currentGame.currentPlayer;
refresh();
}
// FIXME: should be removed
void logSomething([dynamic yolo]) {
printlog('logSomething: $yolo');
void tapOnCell(int cellIndex) {
printlog('tapOnCell: $cellIndex');
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();
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
......
......@@ -26,7 +26,20 @@ class Board {
void dump() {
printlog('');
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('');
}
......
......@@ -3,14 +3,6 @@ import 'package:awale/models/settings/settings_game.dart';
import 'package:awale/models/settings/settings_global.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 {
Game({
// Settings
......@@ -22,12 +14,12 @@ class Game {
this.isStarted = false,
this.isFinished = false,
this.animationInProgress = false,
this.boardAnimated = const [],
// Base data
required this.board,
// Game data
required this.currentPlayer,
required this.scores,
});
......@@ -40,12 +32,12 @@ class Game {
bool isStarted;
bool isFinished;
bool animationInProgress;
AnimatedBoard boardAnimated;
// Base data
final Board board;
// Game data
int currentPlayer;
List<int> scores;
factory Game.createNull() {
......@@ -56,6 +48,7 @@ class Game {
// Base data
board: Board.createNull(),
// Game data
currentPlayer: 0,
scores: [0, 0],
);
}
......@@ -80,16 +73,57 @@ class Game {
globalSettings: newGlobalSettings,
// State
isRunning: true,
boardAnimated: [],
// Base data
board: Board.createNew(cells: cells),
// Game data
currentPlayer: 0,
scores: [0, 0],
);
}
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() {
printlog('');
printlog('## Current game dump:');
......@@ -103,7 +137,6 @@ class Game {
printlog(' isStarted: $isStarted');
printlog(' isFinished: $isFinished');
printlog(' animationInProgress: $animationInProgress');
printlog('board:');
board.dump();
printlog(' Game data');
printlog(' scores: $scores');
......@@ -125,7 +158,6 @@ class Game {
'isStarted': isStarted,
'isFinished': isFinished,
'animationInProgress': animationInProgress,
'boardAnimated': boardAnimated,
// Base data
'board': board.toJson(),
// Game data
......
......@@ -4,9 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:awale/cubit/game_cubit.dart';
import 'package:awale/models/game/game.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_top.dart';
class GameLayout extends StatelessWidget {
const GameLayout({super.key});
......@@ -24,11 +22,9 @@ class GameLayout extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const GameTopWidget(),
const SizedBox(height: 8),
const GameBoardWidget(),
const SizedBox(height: 8),
const GameBottomWidget(),
const Expanded(child: SizedBox.shrink()),
currentGame.isFinished ? const GameEndWidget() : const SizedBox.shrink(),
],
......
......@@ -3,6 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:awale/cubit/game_cubit.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 {
const GameBoardWidget({super.key});
......@@ -13,9 +16,89 @@ class GameBoardWidget extends StatelessWidget {
child: BlocBuilder<GameCubit, GameState>(
builder: (BuildContext context, GameState gameState) {
final Game currentGame = gameState.currentGame;
final Color borderColor = Theme.of(context).colorScheme.onSurface;
// FIXME: should be implemented
return Text(currentGame.toString());
Widget getHouseContent(int cellIndex) {
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
publish_to: "none"
version: 0.0.1+1
version: 0.0.2+2
environment:
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