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

Merge branch '2-add-minimal-gameplay' into 'master'

Resolve "Add minimal gameplay"

Closes #2

See merge request !2
parents 2a8b85ad 1ac87837
No related branches found
No related tags found
1 merge request!2Resolve "Add minimal gameplay"
Pipeline #1951 passed
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'game_board.dart';
import 'game_board_scorer.dart';
class MoveSearchArgs {
MoveSearchArgs(
{required this.board, required this.player, required this.numPlies});
final GameBoard board;
final PieceType player;
final int numPlies;
}
/// A move and its score. Used by the minimax algorithm.
class ScoredMove {
final int score;
final Position move;
const ScoredMove(this.score, this.move);
}
// The [compute] function requires a top-level method as its first argument.
// This is that method for [MoveFinder].
Position? _findNextMove(MoveSearchArgs args) {
final bestMove = _performSearchPly(
args.board, args.player, args.player, args.numPlies - 1);
return bestMove?.move;
}
// This is a recursive implementation of minimax, an algorithm so old it has
// its own Wikipedia page: https://wikipedia.org/wiki/Minimax.
ScoredMove? _performSearchPly(
GameBoard board,
PieceType scoringPlayer,
PieceType player,
int pliesRemaining,
) {
final availableMoves = board.getMovesForPlayer(player);
if (availableMoves.isEmpty) {
return null;
}
var score = (scoringPlayer == player)
? GameBoardScorer.minScore
: GameBoardScorer.maxScore;
ScoredMove? bestMove;
for (var i = 0; i < availableMoves.length; i++) {
final newBoard =
board.updateForMove(availableMoves[i].x, availableMoves[i].y, player);
if (pliesRemaining > 0 &&
newBoard.getMovesForPlayer(getOpponent(player)).isNotEmpty) {
// Opponent has next turn.
score = _performSearchPly(
newBoard,
scoringPlayer,
getOpponent(player),
pliesRemaining - 1,
)?.score ??
0;
} else if (pliesRemaining > 0 &&
newBoard.getMovesForPlayer(player).isNotEmpty) {
// Opponent has no moves; player gets another turn.
score = _performSearchPly(
newBoard,
scoringPlayer,
player,
pliesRemaining - 1,
)?.score ??
0;
} else {
// Game is over or the search has reached maximum depth.
score = GameBoardScorer(newBoard).getScore(scoringPlayer);
}
if (bestMove == null ||
(score > bestMove.score && scoringPlayer == player) ||
(score < bestMove.score && scoringPlayer != player)) {
bestMove =
ScoredMove(score, Position(availableMoves[i].x, availableMoves[i].y));
}
}
return bestMove;
}
/// The [MoveFinder] class exists to provide its [findNextMove] method, which
/// uses the minimax algorithm to look for the best available move on
/// [initialBoard] a [GameBoardScorer] to provide the heuristic.
class MoveFinder {
final GameBoard initialBoard;
MoveFinder(this.initialBoard);
/// Searches the tree of possible moves on [initialBoard] to a depth of
/// [numPlies], looking for the best possible move for [player]. Because the
/// actual work is done in an isolate, a [Future] is used as the return value.
Future<Position?> findNextMove(PieceType player, int numPlies) {
return compute(
_findNextMove,
MoveSearchArgs(
board: initialBoard,
player: player,
numPlies: numPlies,
),
);
}
}
import 'package:flutter/foundation.dart';
class Data extends ChangeNotifier {
// Configuration available values
List _availableDifficultyLevels = ['easy', 'medium', 'hard'];
List get availableDifficultyLevels => _availableDifficultyLevels;
// Application default configuration
String _level = 'medium';
String _size = '8x8';
String _skin = 'default';
// Game data
bool _gameRunning = false;
int _sizeVertical = null;
int _sizeHorizontal = null;
List _cells = [];
String get level => _level;
void updateLevel(String level) {
_level = level;
notifyListeners();
}
String get size => _size;
int get sizeVertical => _sizeVertical;
int get sizeHorizontal => _sizeHorizontal;
void updateSize(String size) {
_size = size;
_sizeHorizontal = int.parse(_size.split('x')[0]);
_sizeVertical = int.parse(_size.split('x')[1]);
notifyListeners();
}
String get skin => _skin;
void updateSkin(String skin) {
_skin = skin;
notifyListeners();
}
getParameterValue(String parameterCode) {
switch(parameterCode) {
case 'difficulty': { return _level; }
break;
}
}
List getParameterAvailableValues(String parameterCode) {
switch(parameterCode) {
case 'difficulty': { return _availableDifficultyLevels; }
break;
}
}
setParameterValue(String parameterCode, String parameterValue) {
switch(parameterCode) {
case 'difficulty': { updateLevel(parameterValue); }
break;
}
}
List get cells => _cells;
void updateCells(List cells) {
_cells = cells;
notifyListeners();
}
bool get gameRunning => _gameRunning;
void updateGameRunning(bool gameRunning) {
_gameRunning = gameRunning;
notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../provider/data.dart';
class Home extends StatelessWidget {
static const String id = 'home';
@override
Widget build(BuildContext context) {
Data myProvider = Provider.of<Data>(context);
return Scaffold(
appBar: AppBar(
title: new Text('Reversi'),
actions: [
],
leading: IconButton(
icon: Image.asset('assets/icons/application.png'),
onPressed: () { },
),
),
body: SafeArea(
child: Center(
child: Text('🎮')
),
)
);
}
}
import 'package:flutter/widgets.dart';
import 'game_board.dart';
abstract class Styling {
// **** GRADIENTS AND COLORS ****
static const Map<PieceType, String> assetImageCodes = {
PieceType.black: 'black',
PieceType.white: 'white',
PieceType.empty: 'empty',
};
static const BorderSide boardCellBorderSide = BorderSide(
color: Color(0xff108010),
width: 1.0,
);
static const BoxDecoration boardCellDecoration = BoxDecoration(
color: Color(0xff50bb50),
border: Border(
bottom: boardCellBorderSide,
top: boardCellBorderSide,
left: boardCellBorderSide,
right: boardCellBorderSide,
),
);
static const BoxDecoration mainWidgetDecoration = BoxDecoration(
color: Color(0xffffffff)
);
static const thinkingColor = Color(0xff2196f3);
// **** ANIMATIONS ****
static const Duration thinkingFadeDuration = Duration(milliseconds: 500);
static const pieceFlipDuration = Duration(milliseconds: 300);
// **** SIZES ****
static const thinkingSize = 10.0;
// **** BOXES ****
static const BorderSide activePlayerIndicatorBorder = BorderSide(
color: Color(0xff2196f3),
width: 10.0,
);
static const BorderSide inactivePlayerIndicatorBorder = BorderSide(
color: Color(0x00000000),
width: 10.0,
);
static const activePlayerIndicator = BoxDecoration(
borderRadius: const BorderRadius.all(const Radius.circular(10.0)),
border: Border(
bottom: activePlayerIndicatorBorder,
top: activePlayerIndicatorBorder,
left: activePlayerIndicatorBorder,
right: activePlayerIndicatorBorder,
),
);
static const inactivePlayerIndicator = BoxDecoration(
borderRadius: const BorderRadius.all(const Radius.circular(10.0)),
border: Border(
bottom: inactivePlayerIndicatorBorder,
top: inactivePlayerIndicatorBorder,
left: inactivePlayerIndicatorBorder,
right: inactivePlayerIndicatorBorder,
),
);
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'styling.dart';
/// This is a self-animated progress spinner, only instead of spinning it
/// moves five little circles in a horizontal arrangement.
class ThinkingIndicator extends ImplicitlyAnimatedWidget {
final Color color;
final double height;
final bool visible;
ThinkingIndicator({
this.color = const Color(0xffffffff),
this.height = 10.0,
this.visible = true,
Key? key,
}) : super(
duration: Styling.thinkingFadeDuration,
key: key,
);
@override
ImplicitlyAnimatedWidgetState createState() => _ThinkingIndicatorState();
}
class _ThinkingIndicatorState
extends AnimatedWidgetBaseState<ThinkingIndicator> {
Tween<double>? _opacityTween;
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: SizedBox(
height: widget.height,
child: Opacity(
opacity: _opacityTween!.evaluate(animation!),
child: _opacityTween!.evaluate(animation!) != 0
? _AnimatedCircles(
color: widget.color,
height: widget.height,
)
: null,
),
),
);
}
@override
void forEachTween(visitor) {
_opacityTween = visitor(
_opacityTween,
widget.visible ? 1.0 : 0.0,
(dynamic value) => Tween<double>(begin: value as double),
) as Tween<double>?;
}
}
class _AnimatedCircles extends StatefulWidget {
final Color color;
final double height;
const _AnimatedCircles({
required this.color,
required this.height,
Key? key,
}) : super(key: key);
@override
_AnimatedCirclesState createState() => _AnimatedCirclesState();
}
class _AnimatedCirclesState extends State<_AnimatedCircles>
with SingleTickerProviderStateMixin {
late Animation<double> _thinkingAnimation;
late AnimationController _thinkingController;
@override
void initState() {
super.initState();
_thinkingController = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this)
..addStatusListener((status) {
// This bit ensures that the animation reverses course rather than
// stopping.
if (status == AnimationStatus.completed) _thinkingController.reverse();
if (status == AnimationStatus.dismissed) _thinkingController.forward();
});
_thinkingAnimation = Tween(begin: 0.0, end: widget.height).animate(
CurvedAnimation(parent: _thinkingController, curve: Curves.easeOut));
_thinkingController.forward();
}
@override
void dispose() {
_thinkingController.dispose();
super.dispose();
}
Widget _buildCircle() {
return Container(
width: widget.height,
height: widget.height,
decoration: BoxDecoration(
border: Border.all(
color: widget.color,
width: 2.0,
),
borderRadius: BorderRadius.all(const Radius.circular(5.0)),
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _thinkingAnimation,
builder: (context, child) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildCircle(),
SizedBox(width: _thinkingAnimation.value),
_buildCircle(),
SizedBox(width: _thinkingAnimation.value),
_buildCircle(),
SizedBox(width: _thinkingAnimation.value),
_buildCircle(),
SizedBox(width: _thinkingAnimation.value),
_buildCircle(),
],
);
},
);
}
}
...@@ -7,7 +7,7 @@ packages: ...@@ -7,7 +7,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.7.0" version: "2.8.1"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
...@@ -50,6 +50,20 @@ packages: ...@@ -50,6 +50,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.2"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
...@@ -60,6 +74,18 @@ packages: ...@@ -60,6 +74,18 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
...@@ -88,6 +114,48 @@ packages: ...@@ -88,6 +114,48 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.3"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
...@@ -95,6 +163,48 @@ packages: ...@@ -95,6 +163,48 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.0.0" version: "5.0.0"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
...@@ -141,7 +251,7 @@ packages: ...@@ -141,7 +251,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.1" version: "0.4.2"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
...@@ -156,6 +266,20 @@ packages: ...@@ -156,6 +266,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.5"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
sdks: sdks:
dart: ">=2.12.0 <3.0.0" dart: ">=2.13.0 <3.0.0"
flutter: ">=1.16.0" flutter: ">=2.0.0"
name: reversi name: simpler_reversi
description: A reversi game application. description: A reversi game application.
publish_to: 'none' publish_to: 'none'
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
provider: ^5.0.0 provider: ^5.0.0
shared_preferences: ^2.0.6
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment