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

Create minimal playable game

parent 3c8627f7
No related branches found
No related tags found
1 merge request!2Resolve "Create minimal playable game"
Pipeline #3284 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:flutter/material.dart';
import 'package:solitaire_game/entities/tile.dart';
import 'package:solitaire_game/provider/data.dart'; import 'package:solitaire_game/provider/data.dart';
class Board { class Board {
static Container buildGameBoard(Data myProvider) { static Container buildGameBoard(Data myProvider) {
return Container( return Container(
margin: EdgeInsets.all(4),
padding: EdgeInsets.all(4),
child: Column( child: Column(
children: [ children: [
buildGameTileset(myProvider), buildGameTileset(myProvider),
...@@ -15,19 +14,26 @@ class Board { ...@@ -15,19 +14,26 @@ class Board {
} }
static Table buildGameTileset(Data myProvider) { 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( return Table(
defaultColumnWidth: IntrinsicColumnWidth(), defaultColumnWidth: IntrinsicColumnWidth(),
children: [ children: [
for (var row = 0; row < boardSize; row++) for (var row = 0; row < board.length; row++)
TableRow( TableRow(
children: [ children: [
for (var col = 0; col < boardSize; col++) for (var col = 0; col < board[row].length; col++)
Column( TableCell(
children: [ child: board[row][col] != null
Text('[' + col.toString() + ',' + row.toString() + "]"), ? (board[row][col]?.render(myProvider) ?? Container())
], : boardTileWithoutHole,
), ),
], ],
), ),
......
...@@ -2,6 +2,7 @@ import 'dart:convert'; ...@@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:solitaire_game/entities/tile.dart';
class Data extends ChangeNotifier { class Data extends ChangeNotifier {
// Configuration available values // Configuration available values
...@@ -22,6 +23,9 @@ class Data extends ChangeNotifier { ...@@ -22,6 +23,9 @@ class Data extends ChangeNotifier {
// Game data // Game data
bool _assetsPreloaded = false; bool _assetsPreloaded = false;
bool _gameIsRunning = false; bool _gameIsRunning = false;
List<List<Tile?>> _board = [];
int _boardSize = 0;
double _tileSize = 0;
String _currentState = ''; String _currentState = '';
void updateParameterSkin(String parameterSkin) { void updateParameterSkin(String parameterSkin) {
...@@ -63,8 +67,24 @@ class Data extends ChangeNotifier { ...@@ -63,8 +67,24 @@ class Data extends ChangeNotifier {
String get currentState => _currentState; String get currentState => _currentState;
String computeCurrentGameState() { 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 = { var currentState = {
'skin': _parameterSkin, 'skin': _parameterSkin,
'boardValues': boardValues,
}; };
return json.encode(currentState); return json.encode(currentState);
...@@ -113,6 +133,32 @@ class Data extends ChangeNotifier { ...@@ -113,6 +133,32 @@ class Data extends ChangeNotifier {
_assetsPreloaded = assetsPreloaded; _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 gameIsRunning => _gameIsRunning;
bool get isGameFinished => !_gameIsRunning; bool get isGameFinished => !_gameIsRunning;
void updateGameIsRunning(bool gameIsRunning) { void updateGameIsRunning(bool gameIsRunning) {
......
...@@ -40,7 +40,9 @@ class _HomeState extends State<Home> { ...@@ -40,7 +40,9 @@ class _HomeState extends State<Home> {
gameImages.forEach((image) => assets.add('assets/icons/' + image + '.png')); gameImages.forEach((image) => assets.add('assets/icons/' + image + '.png'));
List skinImages = [ List skinImages = [
'empty', 'board',
'hole',
'peg',
]; ];
myProvider.availableSkinValues.forEach((skin) => skinImages myProvider.availableSkinValues.forEach((skin) => skinImages
...@@ -59,6 +61,8 @@ class _HomeState extends State<Home> { ...@@ -59,6 +61,8 @@ class _HomeState extends State<Home> {
myProvider.updateAssetsPreloaded(true); myProvider.updateAssetsPreloaded(true);
} }
myProvider.updateTileSize((MediaQuery.of(context).size.width - 20) / myProvider.boardSize);
List<Widget> menuActions = []; List<Widget> menuActions = [];
if (myProvider.gameIsRunning) { 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/provider/data.dart';
import 'package:solitaire_game/utils/board_utils.dart';
class GameUtils { class GameUtils {
static Future<void> quitGame(Data myProvider) async { static Future<void> quitGame(Data myProvider) async {
...@@ -8,7 +10,7 @@ class GameUtils { ...@@ -8,7 +10,7 @@ class GameUtils {
static Future<void> startNewGame(Data myProvider) async { static Future<void> startNewGame(Data myProvider) async {
print('Starting game'); print('Starting game');
myProvider.resetGame(); BoardUtils.createNewBoard(myProvider);
myProvider.updateGameIsRunning(true); myProvider.updateGameIsRunning(true);
} }
...@@ -22,6 +24,8 @@ class GameUtils { ...@@ -22,6 +24,8 @@ class GameUtils {
if (savedState.isNotEmpty) { if (savedState.isNotEmpty) {
try { try {
myProvider.setParameterValue('skin', savedState['skin']); myProvider.setParameterValue('skin', savedState['skin']);
myProvider.updateBoard(
BoardUtils.createBoardFromSavedState(myProvider, savedState['boardValues']));
myProvider.updateGameIsRunning(true); myProvider.updateGameIsRunning(true);
} catch (e) { } catch (e) {
...@@ -36,4 +40,66 @@ class GameUtils { ...@@ -36,4 +40,66 @@ class GameUtils {
startNewGame(myProvider); 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