Last active
June 3, 2024 23:57
-
-
Save KDCinfo/263cbdad3af99c86b81014f745ef9b02 to your computer and use it in GitHub Desktop.
Working on a `botPlay` for Tic Tac Tuples.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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