Skip to content

Instantly share code, notes, and snippets.

@KDCinfo
Last active June 3, 2024 23:57
Show Gist options
  • Save KDCinfo/263cbdad3af99c86b81014f745ef9b02 to your computer and use it in GitHub Desktop.
Save KDCinfo/263cbdad3af99c86b81014f745ef9b02 to your computer and use it in GitHub Desktop.
Working on a `botPlay` for Tic Tac Tuples.
/// Open source multi-player dynamic (3-5) grid tic-tac-toe
/// https://github.com/KDCinfo/dev-play/tree/main/tictactoe
/// This is a **Work In Progress**
/// Just wanted to capture a snapshot of it.
/// Update 2024-06-02:
/// - Extracted out `runBotPlay` as a static method into its own abstract class.
/// - Only 'Rows' is done (albeit untested yet)
/// Update 2024-06-03:
/// - All rows work except last available cell.
import 'package:dev_play_tictactuple/src/app_constants.dart';
import 'package:dev_play_tictactuple/src/data/models/models.dart';
import 'package:dev_play_tictactuple/src/data/service_repositories/service_repositories.dart';
class ScorebookRepository {
ScorebookRepository({super.key});
void updateGame(ScorebookData newScorebookData) {
if (checkRows != null || checkCols != null || checkDiags != null || noMorePlays) {
// ...
} else if (newScorebookData.currentGame.currentPlayerIndex == 1 &&
newScorebookData
.currentGame.players[newScorebookData.currentGame.currentPlayerIndex].playerType ==
const PlayerTypeBot()) {
// Check if:
// - `currentPlayerIndex == 1` (2nd player)
// - player 2 `playerType` is a bot
newScorebookDataTmp = BotPlay.runBotPlay(
newScorebookData: newScorebookData,
playTurn: ({
required GameData currentGame,
required int tileIndex,
}) =>
playTurn,
);
// } else { ...
}
abstract class BotPlay {
///
/// Find a tile to play from a list of 3 tuples; rows, cols, and diags with an `edgeSize` of [3-5].
///
static int runBotPlay({
required Map<MatchTupleEnum, Map<int, List<int>>> filledAllRows,
}) {
final countDownRows = filledAllRows[MatchTupleEnum.row] ?? {}; // Map<int, List<int>>
// // [Group]: [playerId, playerId] // -2 = empty tile
// // [0]: [ 0, 1, 1, 0, 1] // List<int>
// // [1]: [-2, -2, 1, 0, -2]
// // [2]: [ 0, 1, -2, 0, 1]
// // [3]: [ 0, 1, 1, -2, 1]
// // [4]: [ 0, 1, 1, 0, 1]
// final countDownCols = fullMap[MatchTupleEnum.column] ?? {};
// final countDownDiags = fullMap[MatchTupleEnum.diagonal] ?? {};
/// BotPlayTilePlayData
/// - MatchTupleEnum.row, .col, .diag
/// - playerId
/// - maxCount
/// - groupIndex
/// - tileToPlay
final tupleDataLists = <MatchTupleEnum, BotPlayTilePlayData>{
MatchTupleEnum.row: const BotPlayTilePlayData(matchTupleEnum: MatchTupleEnum.row),
MatchTupleEnum.column: const BotPlayTilePlayData(matchTupleEnum: MatchTupleEnum.column),
MatchTupleEnum.diagonal: const BotPlayTilePlayData(matchTupleEnum: MatchTupleEnum.diagonal),
};
/// The `edgeSize` is the number of rows or columns.
final edgeSize = countDownRows.values.first.length;
// countDownRows.entries.map((rowGroup) {
countDownRows.forEach(
(rowGroup, groupList) {
// Note: A `playerId` of `-2` represents an empty tile.
//
// rowGroup.key // 0 // `groupIndex`
// rowGroup.value // [0, 1, 1, -2, 1]
//
/// This will allow for this tile, and future marker tiles,
/// and now player tiles, to all look to update the `tupleDataLists`.
var markerHitStartCompares = false;
/// <playerId, currentLongestCount>
final prevIdCount = <int, int>{};
var loopCount = 0;
/// Check for empty tiles in each row.
final rowHasEmptyTile = groupList.any((playerId) => playerId == -2);
final rowHasAllEmptyTiles = groupList.every((playerId) => playerId == -2);
/// If there are no empty tiles, there's nowhere to play and nothing to check for.
if (!rowHasAllEmptyTiles && rowHasEmptyTile) {
// for (final checkPlayerId in rowGroup.value) {
for (final checkPlayerId in groupList) {
// key: int currentTileIndex
// value: List<int> checkPlayerId
/// When the first marker is hit; we can then begin
/// performing checks on whether to update `tupleDataLists`.
if (checkPlayerId == -2 && !markerHitStartCompares) {
markerHitStartCompares = true;
}
if (prevIdCount.isEmpty) {
// There's no need to store empty tiles.
if (checkPlayerId != -2) {
prevIdCount.addAll({checkPlayerId: 1});
tupleDataLists[MatchTupleEnum.row] = tupleDataLists[MatchTupleEnum.row]!.copyWith(
// matchTupleEnum: MatchTupleEnum.row,
playerId: prevIdCount.keys.first,
tilesPlayedCount: prevIdCount.values.first,
groupIndex: rowGroup,
// tileIndexToPlay: -1,
);
} else {
tupleDataLists[MatchTupleEnum.row] = tupleDataLists[MatchTupleEnum.row]!.copyWith(
// matchTupleEnum: MatchTupleEnum.row,
// playerId: prevIdCount.keys.first,
// tilesPlayedCount: prevIdCount.values.first,
groupIndex: rowGroup,
tileIndexToPlay: loopCount,
);
}
} else {
/// This first code block is for cases where a marker isn't the
/// first cell, and it has to look at the sequential tiles 'before'
/// the current tile, which is the `prevIdCount` before the update below.
///
/// So before updating the `prevIdCount`, if this is an empty
/// tile (`-2`), we need to check the previous `prevIdCount`
/// to see if it qualifies for updating `tupleDataLists`.
if (checkPlayerId == -2) {
if (prevIdCount.values.first >=
tupleDataLists[MatchTupleEnum.row]!.tilesPlayedCount) {
//
tupleDataLists[MatchTupleEnum.row] = tupleDataLists[MatchTupleEnum.row]!.copyWith(
// matchTupleEnum: MatchTupleEnum.row,
playerId: prevIdCount.keys.first,
tilesPlayedCount: prevIdCount.values.first,
groupIndex: rowGroup,
tileIndexToPlay: loopCount + (rowGroup * edgeSize),
);
}
}
/// Update the running `prevIdCount`.
if (prevIdCount.isNotEmpty && prevIdCount.keys.contains(checkPlayerId)) {
final tempNewMax = prevIdCount[checkPlayerId]! + 1;
prevIdCount[checkPlayerId] = tempNewMax;
} else {
// There's no need to store empty tiles.
if (checkPlayerId != -2) {
prevIdCount
..clear()
..addAll({checkPlayerId: 1});
}
}
/// Now that `prevIdCount` has been updated with
/// this current tile, we need to check if the new
/// data qualifies it for updating `tupleDataLists`.
if (markerHitStartCompares && checkPlayerId != -2) {
// final prevTileIdEmpty = tupleDataLists[MatchTupleEnum.row]!.playerId == -2;
// if (prevTileIdEmpty || prevIdCount.values.first >=
if (prevIdCount.values.first >=
tupleDataLists[MatchTupleEnum.row]!.tilesPlayedCount) {
//
tupleDataLists[MatchTupleEnum.row] = tupleDataLists[MatchTupleEnum.row]!.copyWith(
// matchTupleEnum: MatchTupleEnum.row,
playerId: prevIdCount.keys.first,
tilesPlayedCount: prevIdCount.values.first,
groupIndex: rowGroup,
// tileIndexToPlay: loopCount,
);
}
} else if (checkPlayerId == -2) {
if (prevIdCount.values.first >=
tupleDataLists[MatchTupleEnum.row]!.tilesPlayedCount) {
//
tupleDataLists[MatchTupleEnum.row] = tupleDataLists[MatchTupleEnum.row]!.copyWith(
// matchTupleEnum: MatchTupleEnum.row,
// playerId: prevIdCount.keys.first,
// tilesPlayedCount: prevIdCount.values.first,
groupIndex: rowGroup,
// tileIndexToPlay: loopCount,
tileIndexToPlay: loopCount + (rowGroup * edgeSize),
);
}
}
}
loopCount++;
}
}
// return rowGroup;
},
); // End of `.map`
final tileIndexToPlay = tupleDataLists[MatchTupleEnum.row]!.tileIndexToPlay;
return tileIndexToPlay > -1 ? tileIndexToPlay : 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment