Skip to content

Commit

Permalink
v0.10.2 QLessDice - continuing route to functional programming style
Browse files Browse the repository at this point in the history
Breaking changes on Place...() methods
  • Loading branch information
smabuk committed Jun 8, 2024
1 parent 54b2e0f commit 7c4b17f
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 179 deletions.
20 changes: 20 additions & 0 deletions src/Smab.DiceAndTiles/Games/QLess/DiceCollections.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Smab.DiceAndTiles.Games.QLess;

public class DiceCollections
{
public static readonly List<LetterDie> DefaultDiceSet =
[
new([ "M", "M", "L", "L", "B", "Y" ]),
new([ "V", "F", "G", "K", "P", "P" ]),
new([ "H", "H", "N", "N", "R", "R" ]),
new([ "D", "F", "R", "L", "L", "W" ]),
new([ "R", "R", "D", "L", "G", "G" ]),
new([ "X", "K", "B", "S", "Z", "N" ]),
new([ "W", "H", "H", "T", "T", "P" ]),
new([ "C", "C", "B", "T", "J", "D" ]),
new([ "C", "C", "M", "T", "T", "S" ]),
new([ "O", "I", "I", "N", "N", "Y" ]),
new([ "A", "E", "I", "O", "U", "U" ]),
new([ "A", "A", "E", "E", "O", "O" ]),
];
}
157 changes: 5 additions & 152 deletions src/Smab.DiceAndTiles/Games/QLess/QLessDice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,160 +2,13 @@

public record class QLessDice(IDictionaryService? DictionaryService = null, ImmutableList<LetterDie> Dice = null!, bool RollDice = true)
{
private const int ANY_COL = int.MinValue;
internal Dictionary<DieId, PositionedQLessDie> DiceDictionary { get; init; } = ShakeAndFillRack(Dice ?? [.. DefaultDiceSet], RollDice);
internal readonly IDictionaryService dictionaryOfWords = DictionaryService ?? new DictionaryService();

private readonly Dictionary<DieId, PositionedQLessDie> diceDictionary = ShakeAndFillRack(Dice ?? [.. QLessDice.DiceSet], RollDice);
private readonly IDictionaryService dictionaryOfWords = DictionaryService ?? new DictionaryService();
public ImmutableList<LetterDie> Dice { get; } = Dice ?? [.. DefaultDiceSet];

public static readonly List<LetterDie> DiceSet =
[
new([ "M", "M", "L", "L", "B", "Y" ]),
new([ "V", "F", "G", "K", "P", "P" ]),
new([ "H", "H", "N", "N", "R", "R" ]),
new([ "D", "F", "R", "L", "L", "W" ]),
new([ "R", "R", "D", "L", "G", "G" ]),
new([ "X", "K", "B", "S", "Z", "N" ]),
new([ "W", "H", "H", "T", "T", "P" ]),
new([ "C", "C", "B", "T", "J", "D" ]),
new([ "C", "C", "M", "T", "T", "S" ]),
new([ "O", "I", "I", "N", "N", "Y" ]),
new([ "A", "E", "I", "O", "U", "U" ]),
new([ "A", "A", "E", "E", "O", "O" ]),
];

public ImmutableList<LetterDie> Dice { get; init; } = Dice ?? [.. DiceSet];

public IReadOnlyList<PositionedDie> Board => [.. diceDictionary.Values.Where(p => p.Location is Location.Board)];
public IReadOnlyList<PositionedDie> Rack => [.. diceDictionary.Values.Where(p => p.Location is Location.Rack)];
public IReadOnlyList<PositionedDie> Board => [.. DiceDictionary.Values.Where(p => p.Location is Location.Board)];
public IReadOnlyList<PositionedDie> Rack => [.. DiceDictionary.Values.Where(p => p.Location is Location.Rack)];

public bool HasDictionary => dictionaryOfWords.HasWords;

private static Dictionary<DieId, PositionedQLessDie> ShakeAndFillRack(IEnumerable<LetterDie> dice, bool rollDice)
{
Dictionary<DieId, PositionedQLessDie> diceDictionary = [];
LetterDie[] bag = [.. dice];
Random.Shared.Shuffle(bag);

for (int i = 0; i < bag.Length; i++)
{
LetterDie die = bag[i];
if (rollDice)
{
die = (LetterDie)die.Roll();
}
diceDictionary.Add(die.Id, new PositionedQLessDie(die, i));
}
return diceDictionary;
}

public Status GameStatus()
{
HashSet<PositionedDie> errorDice = [];
ErrorReasons errorReasons = ErrorReasons.None;
ScrabbleWordFinder swf = new(Board, dictionaryOfWords);
List<string> words = swf.FindWords();
bool notFinished = false;

if (Board.Count != Dice.Count) {
errorReasons |= ErrorReasons.MissingDice;
notFinished = true;
}

if (swf.ValidWordsAsTiles.Concat(swf.InvalidWordsAsTiles).Any(t => t.Count == 2)) {
errorReasons |= ErrorReasons.TwoLetterWords;
notFinished = true;
errorDice = [.. errorDice,
.. swf
.ValidWordsAsTiles
.Concat(swf.InvalidWordsAsTiles)
.Where(t => t.Count == 2)
.SelectMany(t => t)
.Distinct()
.Select(t => Board.SingleDieAt(t.Col, t.Row))
];
}

if (swf.IsBlockInMoreThanOnePiece()) {
errorReasons |= ErrorReasons.MultipleBlocks;
notFinished = true;
errorDice = [.. errorDice,
.. swf
.Islands
.OrderByDescending(i => i.Count)
.Skip(1)
.Where(i => i.Count != 0)
.SelectMany(t => t)
.Select(t => Board.SingleDieAt(t.Col, t.Row))
];
}

if (dictionaryOfWords is not null && dictionaryOfWords.HasWords) {
if (notFinished is false && swf.InvalidWordsAsTiles.Count == 0) {
return new Win();
}

if (swf.InvalidWordsAsTiles.Count != 0)
{
errorReasons |= ErrorReasons.Misspelt;
foreach (List<PositionedTile> tiles in swf.InvalidWordsAsTiles)
{
tiles.ForEach(t => errorDice.Add(Board.SingleDieAt(t.Col, t.Row)));
}
}
}
else if (notFinished is false && errorDice.Count == 0) {
return new Win();
}

return new Errors(errorDice, errorReasons);
}

public bool PlaceOnBoard(Die die, int col, int row) => PlaceOnBoard(die.Id, col, row);
public bool PlaceOnBoard(DieId name, int col, int row)
{
PositionedQLessDie positionedDie = diceDictionary[name];
if (Board.Any(d => d.Row == row && d.Col == col)) {
return false;
}

positionedDie = positionedDie.PlaceOnBoard(col, row);
diceDictionary[name] = positionedDie;
return true;
}

public bool PlaceOnRack(Die die, int col = ANY_COL) => PlaceOnRack(die.Id, col);
public bool PlaceOnRack(DieId dieId, int col = ANY_COL)
{
PositionedQLessDie positionedDie = diceDictionary[dieId];
if (col != ANY_COL && Rack.Any(p => p.Col == col)) {
return false;
}

if (col == ANY_COL) {
col = Enumerable.Range(0, Dice.Count).Except(Rack.Select(d => d.Col)).Min();
}

if (Rack.Any(p => p.Col == col)) {
return false;
}

positionedDie = positionedDie.PlaceOnRack(col);
diceDictionary[dieId] = positionedDie;
return true;
}


public abstract record class Status();
public record Win() : Status;
public record Errors(IEnumerable<PositionedDie> DiceWithErrors, ErrorReasons ErrorReasons) : Status;

[Flags]
public enum ErrorReasons
{
None = 0,
MultipleBlocks = 1,
TwoLetterWords = 2,
MissingDice = 4,
Misspelt = 8,
}
}
128 changes: 128 additions & 0 deletions src/Smab.DiceAndTiles/Games/QLess/QLessDiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,133 @@
namespace Smab.DiceAndTiles.Games.QLess;
public static class QLessDiceExtensions
{
private const int ANY_COL = int.MinValue;

public static QLessDiceStatus GameStatus(this QLessDice qLessDice)
{
HashSet<PositionedDie> errorDice = [];
ErrorReasons errorReasons = ErrorReasons.None;
ScrabbleWordFinder swf = new(qLessDice.Board, qLessDice.dictionaryOfWords);
List<string> words = swf.FindWords();
bool notFinished = false;

if (qLessDice.Board.Count != qLessDice.Dice.Count)
{
errorReasons |= ErrorReasons.MissingDice;
notFinished = true;
}

if (swf.ValidWordsAsTiles.Concat(swf.InvalidWordsAsTiles).Any(t => t.Count == 2))
{
errorReasons |= ErrorReasons.TwoLetterWords;
notFinished = true;
errorDice = [.. errorDice,
.. swf
.ValidWordsAsTiles
.Concat(swf.InvalidWordsAsTiles)
.Where(t => t.Count == 2)
.SelectMany(t => t)
.Distinct()
.Select(t => qLessDice.Board.SingleDieAt(t.Col, t.Row))
];
}

if (swf.IsBlockInMoreThanOnePiece())
{
errorReasons |= ErrorReasons.MultipleBlocks;
notFinished = true;
errorDice = [.. errorDice,
.. swf
.Islands
.OrderByDescending(i => i.Count)
.Skip(1)
.Where(i => i.Count != 0)
.SelectMany(t => t)
.Select(t => qLessDice.Board.SingleDieAt(t.Col, t.Row))
];
}

if (qLessDice.dictionaryOfWords is not null && qLessDice.dictionaryOfWords.HasWords)
{
if (notFinished is false && swf.InvalidWordsAsTiles.Count == 0)
{
return new Win();
}

if (swf.InvalidWordsAsTiles.Count != 0)
{
errorReasons |= ErrorReasons.Misspelt;
foreach (List<PositionedTile> tiles in swf.InvalidWordsAsTiles)
{
tiles.ForEach(t => errorDice.Add(qLessDice.Board.SingleDieAt(t.Col, t.Row)));
}
}
}
else if (notFinished is false && errorDice.Count == 0)
{
return new Win();
}

return new Errors(errorDice, errorReasons);
}

public static (bool Success, QLessDice QLessDice) PlaceOnBoard(this QLessDice qLessDice, Die die, int col, int row) => qLessDice.PlaceOnBoard(die.Id, col, row);
public static (bool Success, QLessDice QLessDice) PlaceOnBoard(this QLessDice qLessDice, DieId name, int col, int row)
{
if (qLessDice.Board.Any(d => d.Row == row && d.Col == col))
{
return (false, qLessDice);
}

PositionedQLessDie positionedDie = qLessDice.DiceDictionary[name].PlaceOnBoard(col, row);
Dictionary<DieId, PositionedQLessDie> newDiceDictionary = new(qLessDice.DiceDictionary)
{
[name] = positionedDie
};
return (true, qLessDice with { DiceDictionary = newDiceDictionary });
}

public static (bool Success, QLessDice QLessDice) PlaceOnRack(this QLessDice qLessDice, Die die, int col = ANY_COL) => qLessDice.PlaceOnRack(die.Id, col);
public static (bool Success, QLessDice QLessDice) PlaceOnRack(this QLessDice qLessDice, DieId dieId, int col = ANY_COL)
{
if (col != ANY_COL && qLessDice.Rack.Any(p => p.Col == col))
{
return (false, qLessDice);
}

if (col == ANY_COL)
{
col = Enumerable.Range(0, qLessDice.Dice.Count).Except(qLessDice.Rack.Select(d => d.Col)).Min();
}

if (qLessDice.Rack.Any(p => p.Col == col))
{
return (false, qLessDice);
}

PositionedQLessDie positionedDie = qLessDice.DiceDictionary[dieId].PlaceOnRack(col);
Dictionary<DieId, PositionedQLessDie> newDiceDictionary = new(qLessDice.DiceDictionary)
{
[dieId] = positionedDie
};
return (true, qLessDice with { DiceDictionary = newDiceDictionary });
}

internal static Dictionary<DieId, PositionedQLessDie> ShakeAndFillRack(IEnumerable<LetterDie> dice, bool rollDice)
{
Dictionary<DieId, PositionedQLessDie> diceDictionary = [];
LetterDie[] bag = [.. dice];
Random.Shared.Shuffle(bag);

for (int i = 0; i < bag.Length; i++)
{
LetterDie die = bag[i];
if (rollDice)
{
die = (LetterDie)die.Roll();
}
diceDictionary.Add(die.Id, new PositionedQLessDie(die, i));
}
return diceDictionary;
}
}
17 changes: 17 additions & 0 deletions src/Smab.DiceAndTiles/Games/QLess/QLessDiceStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Smab.DiceAndTiles.Games.QLess;

public abstract record class QLessDiceStatus();

public record Win() : QLessDiceStatus;

public record Errors(IEnumerable<PositionedDie> DiceWithErrors, ErrorReasons ErrorReasons) : QLessDiceStatus;

[Flags]
public enum ErrorReasons
{
None = 0,
MultipleBlocks = 1,
TwoLetterWords = 2,
MissingDice = 4,
Misspelt = 8,
}
4 changes: 4 additions & 0 deletions src/Smab.DiceAndTiles/Games/QLess/_GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
global using static Smab.DiceAndTiles.Games.QLess.BoardExtensions;
global using static Smab.DiceAndTiles.Games.QLess.DiceCollections;
global using static Smab.DiceAndTiles.Games.QLess.QLessDiceExtensions;
global using static Smab.DiceAndTiles.Games.QLess.QLessDiceStatus;
Loading

0 comments on commit 7c4b17f

Please sign in to comment.