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

Merge branch '2-create-minimal-playable-game' into 'master'

Resolve "Create minimal playable game"

Closes #2

See merge request !2
parents 3c8627f7 38ebed07
No related branches found
No related tags found
1 merge request!2Resolve "Create minimal playable game"
Pipeline #3287 passed
<?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="#c5c5c5" stroke="#505050" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.6"/><circle cx="50" cy="50" r="12.853" fill="#333" stroke="#797979" stroke-linecap="round" stroke-linejoin="round" stroke-width=".914"/></svg>
<?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"/><circle cx="50" cy="50" r="28.385" fill="#b98212" stroke="#745517" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.0228"/></svg>
import 'package:flutter/material.dart';
import 'package:solitaire_game/provider/data.dart';
import 'package:solitaire_game/utils/game_utils.dart';
class Tile {
int currentRow;
int currentCol;
bool hasPeg;
Tile(
this.currentRow,
this.currentCol,
this.hasPeg,
);
Widget render(Data myProvider) {
List<Widget> stack = [
this.hole(myProvider),
];
if (this.hasPeg) {
stack.add(this.draggable(myProvider));
}
return Stack(
alignment: Alignment.center,
children: stack,
);
}
Widget hole(Data myProvider) {
String image = 'assets/skins/' + myProvider.parameterSkin + '_hole.png';
return DragTarget<List<int>>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Container(
child: Image(
image: AssetImage(image),
width: myProvider.tileSize,
height: myProvider.tileSize,
fit: BoxFit.fill,
),
);
},
onAccept: (List<int> source) {
List<int> target = [this.currentCol, this.currentRow];
// print('(drag) Pick from ' + source.toString() + ' and drop on ' + target.toString());
if (GameUtils.isMoveAllowed(myProvider, source, target)) {
GameUtils.move(myProvider, source, target);
}
},
);
}
Widget draggable(Data myProvider) {
return Draggable<List<int>>(
data: [this.currentCol, this.currentRow],
// Widget when draggable is stationary
child: this.peg(myProvider),
// Widget when draggable is being dragged
feedback: this.peg(myProvider),
// Widget to display on original place when being dragged
childWhenDragging: Container(),
);
}
Widget peg(Data myProvider) {
String image = 'assets/skins/' + myProvider.parameterSkin + '_peg.png';
return Image(
image: AssetImage(image),
width: myProvider.tileSize,
height: myProvider.tileSize,
fit: BoxFit.fill,
);
}
}
import 'package:flutter/material.dart';
import 'package:solitaire_game/entities/tile.dart';
import 'package:solitaire_game/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),
......@@ -15,19 +14,26 @@ class Board {
}
static Table buildGameTileset(Data myProvider) {
int boardSize = 9;
List<List<Tile?>> board = myProvider.board;
Widget boardTileWithoutHole = Image(
image: AssetImage('assets/skins/' + myProvider.parameterSkin + '_board.png'),
width: myProvider.tileSize,
height: myProvider.tileSize,
fit: BoxFit.fill,
);
return Table(
defaultColumnWidth: IntrinsicColumnWidth(),
children: [
for (var row = 0; row < boardSize; row++)
for (var row = 0; row < board.length; row++)
TableRow(
children: [
for (var col = 0; col < boardSize; col++)
Column(
children: [
Text('[' + col.toString() + ',' + row.toString() + "]"),
],
for (var col = 0; col < board[row].length; col++)
TableCell(
child: board[row][col] != null
? (board[row][col]?.render(myProvider) ?? Container())
: boardTileWithoutHole,
),
],
),
......
......@@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solitaire_game/entities/tile.dart';
class Data extends ChangeNotifier {
// Configuration available values
......@@ -22,6 +23,9 @@ class Data extends ChangeNotifier {
// Game data
bool _assetsPreloaded = false;
bool _gameIsRunning = false;
List<List<Tile?>> _board = [];
int _boardSize = 0;
double _tileSize = 0;
String _currentState = '';
void updateParameterSkin(String parameterSkin) {
......@@ -63,8 +67,24 @@ class Data extends ChangeNotifier {
String get currentState => _currentState;
String computeCurrentGameState() {
String boardValues = '';
String textBoard = ' ';
String textHole = '·';
String textPeg = 'o';
for (var rowIndex = 0; rowIndex < _board.length; rowIndex++) {
for (var colIndex = 0; colIndex < _board[rowIndex].length; colIndex++) {
String cellValue = textBoard;
if (_board[rowIndex][colIndex] != null) {
cellValue = (_board[rowIndex][colIndex]?.hasPeg ?? false) ? textPeg : textHole;
}
boardValues += cellValue;
}
}
var currentState = {
'skin': _parameterSkin,
'boardValues': boardValues,
};
return json.encode(currentState);
......@@ -113,6 +133,32 @@ class Data extends ChangeNotifier {
_assetsPreloaded = assetsPreloaded;
}
double get tileSize => _tileSize;
void updateTileSize(double tileSize) {
_tileSize = tileSize;
}
int get boardSize => _boardSize;
void updateBoardSize(int boardSize) {
_boardSize = boardSize;
}
List<List<Tile?>> get board => _board;
void updateBoard(List<List<Tile?>> board) {
_board = board;
updateBoardSize(board.length);
notifyListeners();
}
updatePegValue(int row, int col, bool hasPeg) {
if (_board[row][col] != null) {
_board[row][col]?.hasPeg = hasPeg;
saveCurrentGameState();
notifyListeners();
}
}
bool get gameIsRunning => _gameIsRunning;
bool get isGameFinished => !_gameIsRunning;
void updateGameIsRunning(bool gameIsRunning) {
......
......@@ -40,7 +40,9 @@ class _HomeState extends State<Home> {
gameImages.forEach((image) => assets.add('assets/icons/' + image + '.png'));
List skinImages = [
'empty',
'board',
'hole',
'peg',
];
myProvider.availableSkinValues.forEach((skin) => skinImages
......@@ -59,6 +61,8 @@ class _HomeState extends State<Home> {
myProvider.updateAssetsPreloaded(true);
}
myProvider.updateTileSize((MediaQuery.of(context).size.width - 20) / myProvider.boardSize);
List<Widget> menuActions = [];
if (myProvider.gameIsRunning) {
......
import 'dart:math';
import 'package:solitaire_game/entities/tile.dart';
import 'package:solitaire_game/provider/data.dart';
class BoardUtils {
static printGrid(List cells) {
String textBoard = ' ';
String textHole = '·';
String textPeg = 'o';
print('');
print('-------');
for (var rowIndex = 0; rowIndex < cells.length; rowIndex++) {
String row = '';
for (var colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) {
String textCell = textBoard;
Tile? tile = cells[rowIndex][colIndex];
if (tile != null) {
textCell = tile.hasPeg ? textPeg : textHole;
}
row += textCell;
}
print(row);
}
print('-------');
print('');
}
static List<List<Tile?>> createBoardFromSavedState(Data myProvider, String savedBoard) {
List<List<Tile?>> board = [];
int boardSize = pow((savedBoard.length), 1 / 2).round();
myProvider.updateBoardSize(boardSize);
String textBoard = ' ';
String textPeg = 'o';
int index = 0;
for (var rowIndex = 0; rowIndex < boardSize; rowIndex++) {
List<Tile?> row = [];
for (var colIndex = 0; colIndex < boardSize; colIndex++) {
String stringValue = savedBoard[index++];
if (stringValue == textBoard) {
row.add(null);
} else {
row.add(Tile(rowIndex, colIndex, (stringValue == textPeg)));
}
}
board.add(row);
}
return board;
}
static createNewBoard(Data myProvider) {
List<String> templateEnglish = [
' ooo ',
' ooo ',
'ooooooo',
'ooo·ooo',
'ooooooo',
' ooo ',
' ooo ',
];
List<List<Tile?>> grid = [];
int row = 0;
templateEnglish.forEach((String line) {
List<Tile?> gridLine = [];
int col = 0;
line.split("").forEach((String tileCode) {
gridLine.add(tileCode == ' ' ? null : new Tile(row, col, (tileCode == 'o')));
col++;
});
row++;
grid.add(gridLine);
});
printGrid(grid);
myProvider.resetGame();
myProvider.updateBoard(grid);
}
}
import 'package:solitaire_game/entities/tile.dart';
import 'package:solitaire_game/provider/data.dart';
import 'package:solitaire_game/utils/board_utils.dart';
class GameUtils {
static Future<void> quitGame(Data myProvider) async {
......@@ -8,7 +10,7 @@ class GameUtils {
static Future<void> startNewGame(Data myProvider) async {
print('Starting game');
myProvider.resetGame();
BoardUtils.createNewBoard(myProvider);
myProvider.updateGameIsRunning(true);
}
......@@ -22,6 +24,8 @@ class GameUtils {
if (savedState.isNotEmpty) {
try {
myProvider.setParameterValue('skin', savedState['skin']);
myProvider.updateBoard(
BoardUtils.createBoardFromSavedState(myProvider, savedState['boardValues']));
myProvider.updateGameIsRunning(true);
} catch (e) {
......@@ -36,4 +40,66 @@ class GameUtils {
startNewGame(myProvider);
}
}
static bool isMoveAllowed(Data myProvider, List<int> source, List<int> target) {
List<List<Tile?>> board = myProvider.board;
int sourceCol = source[0];
int sourceRow = source[1];
int targetCol = target[0];
int targetRow = target[1];
// ensure source exists and has a peg
if (board[sourceRow][sourceCol] == null || board[sourceRow][sourceCol]?.hasPeg == false) {
print('move forbidden: source peg does not exist');
return false;
}
// ensure target exists and is empty
if (board[targetRow][targetCol] == null || board[targetRow][targetCol]?.hasPeg == true) {
print('move forbidden: target does not exist or already with a peg');
return false;
}
// ensure source and target are in the same line/column
if ((targetCol != sourceCol) && (targetRow != sourceRow)) {
print('move forbidden: source and target are not in the same line or column');
return false;
}
// ensure source and target are separated by exactly one tile
if (((targetCol == sourceCol) && ((targetRow - sourceRow).abs() != 2)) ||
((targetRow == sourceRow) && ((targetCol - sourceCol).abs() != 2))) {
print('move forbidden: source and target must be separated by exactly one tile');
return false;
}
// ensure middle tile exists and has a peg
int middleRow = (sourceRow + ((targetRow - sourceRow) / 2)).round();
int middleCol = (sourceCol + ((targetCol - sourceCol) / 2)).round();
if (board[middleRow][middleCol] == null || board[middleRow][middleCol]?.hasPeg == false) {
print('move forbidden: tile between source and target does not contain a peg');
return false;
}
// ok, move is allowed
return true;
}
static void move(Data myProvider, List<int> source, List<int> target) {
print('Move from ' + source.toString() + ' to ' + target.toString());
int sourceCol = source[0];
int sourceRow = source[1];
int targetCol = target[0];
int targetRow = target[1];
int middleRow = (sourceRow + ((targetRow - sourceRow) / 2)).round();
int middleCol = (sourceCol + ((targetCol - sourceCol) / 2)).round();
// remove peg from source
myProvider.updatePegValue(sourceRow, sourceCol, false);
// put peg in target
myProvider.updatePegValue(targetRow, targetCol, true);
// remove peg from middle tile
myProvider.updatePegValue(middleRow, middleCol, false);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment