From 2976e3feddd6b4fca0dc61656196c2e67d977614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr> Date: Tue, 3 Sep 2024 16:02:03 +0200 Subject: [PATCH] Add "naive" AI player --- android/gradle.properties | 4 +- .../metadata/android/en-US/changelogs/10.txt | 1 + .../metadata/android/fr-FR/changelogs/10.txt | 1 + lib/config/default_game_settings.dart | 12 +++- lib/cubit/game_cubit.dart | 14 +++++ lib/models/game/game.dart | 20 ++++++ lib/robot/robot_player.dart | 20 ++++++ lib/ui/parameters/parameter_painter.dart | 63 +++++++++++++++++++ lib/ui/widgets/game/game_player.dart | 5 +- pubspec.yaml | 2 +- 10 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/10.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/10.txt create mode 100644 lib/robot/robot_player.dart diff --git a/android/gradle.properties b/android/gradle.properties index 4bb5439..6bf54a6 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.9 -app.versionCode=9 +app.versionName=0.0.10 +app.versionCode=10 diff --git a/fastlane/metadata/android/en-US/changelogs/10.txt b/fastlane/metadata/android/en-US/changelogs/10.txt new file mode 100644 index 0000000..b69d84e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/10.txt @@ -0,0 +1 @@ +Add (naive) AI player. diff --git a/fastlane/metadata/android/fr-FR/changelogs/10.txt b/fastlane/metadata/android/fr-FR/changelogs/10.txt new file mode 100644 index 0000000..24f4483 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/10.txt @@ -0,0 +1 @@ +Ajout d'une IA (naîve) comme joueur. diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart index 8ffc2a1..668711f 100644 --- a/lib/config/default_game_settings.dart +++ b/lib/config/default_game_settings.dart @@ -8,12 +8,18 @@ class DefaultGameSettings { ]; // game mode: available values - static const String gameModeDual = 'dual'; + static const String gameModeHumanVsHuman = 'human-vs-human'; + static const String gameModeHumanVsRobot = 'human-vs-robot'; + static const String gameModeRobotVsHuman = 'robot-vs-human'; + static const String gameModeRobotVsRobot = 'robot-vs-robot'; static const List<String> allowedGameModeValues = [ - gameModeDual, + gameModeHumanVsHuman, + gameModeHumanVsRobot, + gameModeRobotVsHuman, + gameModeRobotVsRobot, ]; // items count: default value - static const String defaultGameModeValue = gameModeDual; + static const String defaultGameModeValue = gameModeHumanVsHuman; // available values from parameter code static List<String> getAvailableValues(String parameterCode) { diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart index 0cc2123..77cbf85 100644 --- a/lib/cubit/game_cubit.dart +++ b/lib/cubit/game_cubit.dart @@ -7,6 +7,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:awale/models/game/game.dart'; import 'package:awale/models/settings/settings_game.dart'; import 'package:awale/models/settings/settings_global.dart'; +import 'package:awale/robot/robot_player.dart'; import 'package:awale/utils/tools.dart'; part 'game_state.dart'; @@ -59,6 +60,8 @@ class GameCubit extends HydratedCubit<GameState> { updateState(newGame); refresh(); + + robotPlay(); } void quitGame() { @@ -80,6 +83,17 @@ class GameCubit extends HydratedCubit<GameState> { void toggleCurrentPlayer() { state.currentGame.currentPlayer = 1 - state.currentGame.currentPlayer; refresh(); + + robotPlay(); + } + + void robotPlay() async { + if (!state.currentGame.isFinished && !state.currentGame.isCurrentPlayerHuman()) { + final int pickedCell = RobotPlayer.pickCell(state.currentGame); + await Future.delayed(const Duration(milliseconds: 500)); + + tapOnCell(pickedCell); + } } void tapOnCell(int cellIndex) async { diff --git a/lib/models/game/game.dart b/lib/models/game/game.dart index bb6ed6c..269a01d 100644 --- a/lib/models/game/game.dart +++ b/lib/models/game/game.dart @@ -1,3 +1,4 @@ +import 'package:awale/config/default_game_settings.dart'; import 'package:awale/models/game/board.dart'; import 'package:awale/models/settings/settings_game.dart'; import 'package:awale/models/settings/settings_global.dart'; @@ -137,6 +138,25 @@ class Game { return false; } + bool isPlayerHuman(int playerIndex) { + switch (gameSettings.gameMode) { + case DefaultGameSettings.gameModeHumanVsHuman: + return true; + case DefaultGameSettings.gameModeHumanVsRobot: + return (playerIndex == 0); + case DefaultGameSettings.gameModeRobotVsHuman: + return (playerIndex == 1); + case DefaultGameSettings.gameModeRobotVsRobot: + return false; + default: + } + return true; + } + + bool isCurrentPlayerHuman() { + return isPlayerHuman(currentPlayer); + } + void dump() { printlog(''); printlog('## Current game dump:'); diff --git a/lib/robot/robot_player.dart b/lib/robot/robot_player.dart new file mode 100644 index 0000000..b14b503 --- /dev/null +++ b/lib/robot/robot_player.dart @@ -0,0 +1,20 @@ +import 'package:awale/models/game/game.dart'; + +class RobotPlayer { + static pickCell(Game currentGame) { + List<int> allowedMoves = []; + + for (int cellIndex = 0; cellIndex < currentGame.board.cells.length; cellIndex++) { + if (currentGame.isCurrentPlayerHouse(cellIndex) && + currentGame.isMoveAllowed(cellIndex)) { + allowedMoves.add(cellIndex); + } + } + + allowedMoves.shuffle(); + + final int pickedCellIndex = allowedMoves[0]; + + return pickedCellIndex; + } +} diff --git a/lib/ui/parameters/parameter_painter.dart b/lib/ui/parameters/parameter_painter.dart index 5e70ef5..1a23f6b 100644 --- a/lib/ui/parameters/parameter_painter.dart +++ b/lib/ui/parameters/parameter_painter.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:awale/config/default_game_settings.dart'; import 'package:awale/models/settings/settings_game.dart'; import 'package:awale/models/settings/settings_global.dart'; import 'package:awale/utils/tools.dart'; @@ -40,6 +41,9 @@ class ParameterPainter extends CustomPainter { // content switch (code) { + case DefaultGameSettings.parameterCodeGameMode: + paintGameModeParameterItem(value, canvas, canvasSize); + break; default: printlog('Unknown parameter: $code/$value'); paintUnknownParameterItem(value, canvas, canvasSize); @@ -87,4 +91,63 @@ class ParameterPainter extends CustomPainter { ), ); } + + void paintGameModeParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + String text = ''; + Color baseColor = Colors.grey; + + switch (value) { + case DefaultGameSettings.gameModeHumanVsHuman: + text = '🧑 🧑'; + baseColor = Colors.green; + break; + case DefaultGameSettings.gameModeHumanVsRobot: + text = '🧑 🤖'; + baseColor = Colors.pink; + break; + case DefaultGameSettings.gameModeRobotVsHuman: + text = '🤖 🧑'; + baseColor = Colors.pink; + break; + case DefaultGameSettings.gameModeRobotVsRobot: + text = '🤖 🤖'; + baseColor = Colors.brown; + break; + default: + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3; + + paint.color = baseColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + final textSpan = TextSpan( + text: text, + style: const TextStyle( + color: Colors.black, + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } } diff --git a/lib/ui/widgets/game/game_player.dart b/lib/ui/widgets/game/game_player.dart index 2806e67..d7d484d 100644 --- a/lib/ui/widgets/game/game_player.dart +++ b/lib/ui/widgets/game/game_player.dart @@ -25,8 +25,9 @@ class GamePlayerWidget extends StatelessWidget { final bool isCurrentPlayer = (currentGame.currentPlayer == playerIndex); final int seedsCount = isCurrentPlayer ? currentGame.currentHand : 0; - final Color baseColor = - isCurrentPlayer ? Colors.pink : Theme.of(context).colorScheme.surface; + final Color baseColor = isCurrentPlayer + ? (currentGame.isPlayerHuman(playerIndex) ? Colors.pink : Colors.grey) + : Theme.of(context).colorScheme.surface; return Container( margin: const EdgeInsets.all(2), diff --git a/pubspec.yaml b/pubspec.yaml index bf3eaab..656ed76 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Awale game publish_to: "none" -version: 0.0.9+9 +version: 0.0.10+10 environment: sdk: "^3.0.0" -- GitLab