diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index 7b2f9115a9bd412af67c699117004fdd370b0dfc..e06408205830beb6b8e5c86f52a01b8e20644f53 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/launch_image.png b/android/app/src/main/res/mipmap-hdpi/launch_image.png
index 7b2f9115a9bd412af67c699117004fdd370b0dfc..e06408205830beb6b8e5c86f52a01b8e20644f53 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/launch_image.png and b/android/app/src/main/res/mipmap-hdpi/launch_image.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index a8cd7531476f32d181bb840246e414425e86edd0..b54ec718c3d74369a3b9863d698f9348cff22fcb 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/launch_image.png b/android/app/src/main/res/mipmap-mdpi/launch_image.png
index a8cd7531476f32d181bb840246e414425e86edd0..b54ec718c3d74369a3b9863d698f9348cff22fcb 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/launch_image.png and b/android/app/src/main/res/mipmap-mdpi/launch_image.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 0f30a0494c2d105d5d8dafa39425c800e8e00f01..2129702d44bd65a022ec7a59ff5e187453d59c8a 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/launch_image.png b/android/app/src/main/res/mipmap-xhdpi/launch_image.png
index 0f30a0494c2d105d5d8dafa39425c800e8e00f01..2129702d44bd65a022ec7a59ff5e187453d59c8a 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/launch_image.png and b/android/app/src/main/res/mipmap-xhdpi/launch_image.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 3773eeeef8f29cf1f1303448894b6a473c42116d..12a2adfad532918958782914cc20f2cc24e6909e 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/launch_image.png b/android/app/src/main/res/mipmap-xxhdpi/launch_image.png
index 3773eeeef8f29cf1f1303448894b6a473c42116d..12a2adfad532918958782914cc20f2cc24e6909e 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/launch_image.png and b/android/app/src/main/res/mipmap-xxhdpi/launch_image.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 2227e093b294ce65a443702c16c5b20e5b833e24..0ce12a293f8bf4c6eaf3550de76c4f6758d78e03 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png b/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png
index 2227e093b294ce65a443702c16c5b20e5b833e24..0ce12a293f8bf4c6eaf3550de76c4f6758d78e03 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png and b/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png differ
diff --git a/android/gradle.properties b/android/gradle.properties
index bc2d95e8567abcfd41c26ebeb95fced48f43e773..818e87b23b224ced309ae5c147e5ed827826e237 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,5 +1,5 @@
 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
diff --git a/assets/skins/default_empty.png b/assets/skins/default_board.png
similarity index 100%
rename from assets/skins/default_empty.png
rename to assets/skins/default_board.png
diff --git a/assets/skins/default_hole.png b/assets/skins/default_hole.png
new file mode 100644
index 0000000000000000000000000000000000000000..d6c839077a02b3d3e14593e6d986ce6a844f9190
Binary files /dev/null and b/assets/skins/default_hole.png differ
diff --git a/assets/skins/default_peg.png b/assets/skins/default_peg.png
new file mode 100644
index 0000000000000000000000000000000000000000..5ba2cc9f0887a67bd4643ef7df647eb809482115
Binary files /dev/null and b/assets/skins/default_peg.png differ
diff --git a/fastlane/metadata/android/en-US/changelogs/2.txt b/fastlane/metadata/android/en-US/changelogs/2.txt
new file mode 100644
index 0000000000000000000000000000000000000000..57e563eca79a526fa6986226ae4e802a84e72a7e
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/2.txt
@@ -0,0 +1 @@
+Create minimal playable game
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png
index bc10e1eae9d63710ab0f26b439b493557e938c00..5ee834dd7c8c76e334fecf70b10fece0f9d0d185 100644
Binary files a/fastlane/metadata/android/en-US/images/icon.png and b/fastlane/metadata/android/en-US/images/icon.png differ
diff --git a/fastlane/metadata/android/fr-FR/changelogs/2.txt b/fastlane/metadata/android/fr-FR/changelogs/2.txt
new file mode 100644
index 0000000000000000000000000000000000000000..35f530831b09bef219422e8ab9a11edb20600b62
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/2.txt
@@ -0,0 +1 @@
+Création du jeu minimal jouable
\ No newline at end of file
diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh
index e0233acb66be46c189858591517645f46ca30d4c..aef865033c1d8e95a8b2bc654752c85ea9826e04 100755
--- a/icons/build_game_icons.sh
+++ b/icons/build_game_icons.sh
@@ -36,7 +36,9 @@ AVAILABLE_SKINS="
 
 # Images per skin
 SKIN_IMAGES="
-  empty
+  board
+  hole
+  peg
 "
 
 #######################################################
diff --git a/icons/icon.svg b/icons/icon.svg
index a4609114d8f74aff540032078873cc124c7b6633..f215ae5833909c330705d8c78cdadef35a4ca975 100644
--- a/icons/icon.svg
+++ b/icons/icon.svg
@@ -75,50 +75,38 @@
         </g>
         <path d="m24.549 1119.5c0.66325 0 1.1979-0.5346 1.1979-1.1979v-0.3333c0 0.6632-0.53461 1.1978-1.1979 1.1978h-20.354c-0.66325 0-1.1979-0.5346-1.1979-1.1978v0.3333c0 0.6633 0.53461 1.1979 1.1979 1.1979z" fill="#1a237e" opacity=".2"/>
     </g>
-    <g transform="translate(.059003 -.23356)" fill="#fffffe" stroke="#fffffd" stroke-linecap="round" stroke-linejoin="round" stroke-width=".66394">
-        <ellipse cx="12.739" cy="8.1361" rx=".32" ry=".32"/>
-        <ellipse cx="14.314" cy="8.1361" rx=".32" ry=".32"/>
-        <ellipse cx="15.89" cy="8.1361" rx=".32" ry=".32"/>
-        <ellipse cx="12.739" cy="9.7538" rx=".32" ry=".32"/>
-        <ellipse cx="14.314" cy="9.7538" rx=".32" ry=".32"/>
-        <ellipse cx="15.89" cy="9.7538" rx=".32" ry=".32"/>
-        <ellipse cx="12.739" cy="11.372" rx=".32" ry=".32"/>
-        <ellipse cx="14.314" cy="11.372" rx=".32" ry=".32"/>
-        <ellipse cx="15.89" cy="11.372" rx=".32" ry=".32"/>
-        <ellipse cx="9.5883" cy="12.989" rx=".32" ry=".32"/>
-        <ellipse cx="8.0129" cy="12.989" rx=".32" ry=".32"/>
-        <ellipse cx="19.041" cy="12.989" rx=".32" ry=".32"/>
-        <ellipse cx="20.616" cy="12.989" rx=".32" ry=".32"/>
-        <ellipse cx="11.164" cy="12.989" rx=".32" ry=".32"/>
-        <ellipse cx="12.739" cy="12.989" rx=".32" ry=".32"/>
-        <ellipse cx="14.314" cy="12.989" rx=".32" ry=".32"/>
-        <ellipse cx="15.89" cy="12.989" rx=".32" ry=".32"/>
-        <ellipse cx="17.465" cy="12.989" rx=".32" ry=".32"/>
-        <ellipse cx="9.5883" cy="14.607" rx=".32" ry=".32"/>
-        <ellipse cx="8.0129" cy="14.607" rx=".32" ry=".32"/>
-        <ellipse cx="19.041" cy="14.607" rx=".32" ry=".32"/>
-        <ellipse cx="20.616" cy="14.607" rx=".32" ry=".32"/>
-        <ellipse cx="11.164" cy="14.607" rx=".32" ry=".32"/>
-        <ellipse cx="12.739" cy="14.607" rx=".32" ry=".32"/>
-        <ellipse cx="15.89" cy="14.607" rx=".32" ry=".32"/>
-        <ellipse cx="17.465" cy="14.607" rx=".32" ry=".32"/>
-        <ellipse cx="9.5883" cy="16.225" rx=".32" ry=".32"/>
-        <ellipse cx="8.0129" cy="16.225" rx=".32" ry=".32"/>
-        <ellipse cx="19.041" cy="16.225" rx=".32" ry=".32"/>
-        <ellipse cx="20.616" cy="16.225" rx=".32" ry=".32"/>
-        <ellipse cx="11.164" cy="16.225" rx=".32" ry=".32"/>
-        <ellipse cx="12.739" cy="16.225" rx=".32" ry=".32"/>
-        <ellipse cx="14.314" cy="16.225" rx=".32" ry=".32"/>
-        <ellipse cx="15.89" cy="16.225" rx=".32" ry=".32"/>
-        <ellipse cx="17.465" cy="16.225" rx=".32" ry=".32"/>
-        <ellipse cx="12.739" cy="17.843" rx=".32" ry=".32"/>
-        <ellipse cx="14.314" cy="17.843" rx=".32" ry=".32"/>
-        <ellipse cx="15.89" cy="17.843" rx=".32" ry=".32"/>
-        <ellipse cx="12.739" cy="21.078" rx=".32" ry=".32"/>
-        <ellipse cx="14.314" cy="21.078" rx=".32" ry=".32"/>
-        <ellipse cx="15.89" cy="21.078" rx=".32" ry=".32"/>
-        <ellipse cx="12.739" cy="19.46" rx=".32" ry=".32"/>
-        <ellipse cx="14.314" cy="19.46" rx=".32" ry=".32"/>
-        <ellipse cx="15.89" cy="19.46" rx=".32" ry=".32"/>
+    <g transform="matrix(1.3242 0 0 1.3242 -4.6596 -4.6595)" fill="#fffffe" stroke="#fffffd" stroke-linecap="round" stroke-linejoin="round" stroke-width=".66394">
+        <circle cx="12.798" cy="9.5202" r=".32"/>
+        <circle cx="14.373" cy="9.5202" r=".32"/>
+        <circle cx="15.949" cy="9.5202" r=".32"/>
+        <circle cx="12.798" cy="11.138" r=".32"/>
+        <circle cx="14.373" cy="11.138" r=".32"/>
+        <circle cx="15.949" cy="11.138" r=".32"/>
+        <circle cx="9.6473" cy="12.755" r=".32"/>
+        <circle cx="19.1" cy="12.755" r=".32"/>
+        <circle cx="11.223" cy="12.755" r=".32"/>
+        <circle cx="12.798" cy="12.755" r=".32"/>
+        <circle cx="14.373" cy="12.755" r=".32"/>
+        <circle cx="15.949" cy="12.755" r=".32"/>
+        <circle cx="17.524" cy="12.755" r=".32"/>
+        <circle cx="9.6473" cy="14.373" r=".32"/>
+        <circle cx="19.1" cy="14.373" r=".32"/>
+        <circle cx="11.223" cy="14.373" r=".32"/>
+        <circle cx="12.798" cy="14.373" r=".32"/>
+        <circle cx="15.949" cy="14.373" r=".32"/>
+        <circle cx="17.524" cy="14.373" r=".32"/>
+        <circle cx="9.6473" cy="15.991" r=".32"/>
+        <circle cx="19.1" cy="15.991" r=".32"/>
+        <circle cx="11.223" cy="15.991" r=".32"/>
+        <circle cx="12.798" cy="15.991" r=".32"/>
+        <circle cx="14.373" cy="15.991" r=".32"/>
+        <circle cx="15.949" cy="15.991" r=".32"/>
+        <circle cx="17.524" cy="15.991" r=".32"/>
+        <circle cx="12.798" cy="17.609" r=".32"/>
+        <circle cx="14.373" cy="17.609" r=".32"/>
+        <circle cx="15.949" cy="17.609" r=".32"/>
+        <circle cx="12.798" cy="19.226" r=".32"/>
+        <circle cx="14.373" cy="19.226" r=".32"/>
+        <circle cx="15.949" cy="19.226" r=".32"/>
     </g>
 </svg>
diff --git a/icons/skins/default/empty.svg b/icons/skins/default/board.svg
similarity index 100%
rename from icons/skins/default/empty.svg
rename to icons/skins/default/board.svg
diff --git a/icons/skins/default/hole.svg b/icons/skins/default/hole.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0a7a1add764c2db361052df50d08f22ce97fa7c3
--- /dev/null
+++ b/icons/skins/default/hole.svg
@@ -0,0 +1,2 @@
+<?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>
diff --git a/icons/skins/default/peg.svg b/icons/skins/default/peg.svg
new file mode 100644
index 0000000000000000000000000000000000000000..d7e72c5f2fee9b4d91424ad13c4b7d363f728e5c
--- /dev/null
+++ b/icons/skins/default/peg.svg
@@ -0,0 +1,2 @@
+<?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>
diff --git a/lib/entities/tile.dart b/lib/entities/tile.dart
new file mode 100644
index 0000000000000000000000000000000000000000..75b694bc51bd2576ccd31109610315323ee00d09
--- /dev/null
+++ b/lib/entities/tile.dart
@@ -0,0 +1,84 @@
+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,
+    );
+  }
+}
diff --git a/lib/layout/board.dart b/lib/layout/board.dart
index e10ed6100fd859b72ce72d1ca8ecd01157d3fb42..1843cc9906f48768a5fb67b5d868d4b061201595 100644
--- a/lib/layout/board.dart
+++ b/lib/layout/board.dart
@@ -1,11 +1,10 @@
 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,
                 ),
             ],
           ),
diff --git a/lib/provider/data.dart b/lib/provider/data.dart
index a9512a9c3148cc529d35a19d9d154bc44e7630ce..61e1e394c07c8598db1e2dddddbe2ba07c03dc64 100644
--- a/lib/provider/data.dart
+++ b/lib/provider/data.dart
@@ -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) {
diff --git a/lib/screens/home.dart b/lib/screens/home.dart
index be742886eccb32a42338920ba1f288ad6cc5c8d5..32c3b5e10f8ae5c406fa5d8d2d96115d44358ce8 100644
--- a/lib/screens/home.dart
+++ b/lib/screens/home.dart
@@ -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) {
diff --git a/lib/utils/board_utils.dart b/lib/utils/board_utils.dart
new file mode 100644
index 0000000000000000000000000000000000000000..5f60126993ddea1db46d2e9f72e7d4d4e4b60b17
--- /dev/null
+++ b/lib/utils/board_utils.dart
@@ -0,0 +1,84 @@
+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);
+  }
+}
diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart
index de8996e50ccf4d4751747fa7763b7e8dfd1b3040..c618e456ea8f3a9cdc662dee00dec690c355c399 100644
--- a/lib/utils/game_utils.dart
+++ b/lib/utils/game_utils.dart
@@ -1,4 +1,6 @@
+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);
+  }
 }