Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Dice game #2

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added identifier.sqlite
Empty file.
142 changes: 142 additions & 0 deletions src/Abstractions/CharBoard.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Drawing;
using System.Linq;
using System.Text;
using Newtonsoft.Json;

Expand Down Expand Up @@ -72,4 +76,142 @@ public CharBoard Set(int r, int c, char value)
return new CharBoard(Size, newCells);
}
}

public record DiceBoard
{
private static readonly ConcurrentDictionary<int, DiceBoard> EmptyCache = new();
public static DiceBoard Empty(int size) => EmptyCache.GetOrAdd(size, size1 => new DiceBoard(size1));

public int Size { get; }
public ImmutableDictionary<int, Cell> Cells { get; }
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need a dict if you use 100% index range from 0 to N :) You need an array :)


public struct Cell
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thing definitely requires some serialization-related optimizations. Or maybe DiceBoard.

{
public string Background;
public string[] Colors;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why cell has multiple colors?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 colors for all players (blue, green, red, yellow)

public double[] Opacities;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for opacities?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each cell has 1 background color and 4 "color + opacity" units.


public Cell(string background, string[] colors, double[] opacities)
{
var defaultBackground = GetColor(DiceBoard.Colors.Gold);
var defaultColors = new string[] {GetColor(DiceBoard.Colors.Blue),
GetColor(DiceBoard.Colors.Green),
GetColor(DiceBoard.Colors.Red),
GetColor(DiceBoard.Colors.Yellow), };
var defaultOpacities = new double[] {
GetOpacity(Opacity.Invisible), GetOpacity(Opacity.Invisible), GetOpacity(Opacity.Invisible),
GetOpacity(Opacity.Invisible),
};
background ??= defaultBackground;
colors ??= defaultColors;
opacities ??= defaultOpacities;
Background = background;
Colors = colors;
Opacities = opacities;
}
}

public enum Colors
{
Blue,
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather use CellStates vs Colors. Color is meaningless, but cell state - e.g. "BoostCell" - is meaninful.

Green,
Red,
Yellow,
Gold,
Forward,
Backward,
}

public enum Opacity
{
Current,
Past,
Invisible
}

public Cell this[int r, int c] {
get {
var cellIndex = GetCellIndex(r, c);
if (cellIndex < 0 || cellIndex >= Cells.Count)
return new Cell {};
return Cells[cellIndex];
}
}

public DiceBoard(int size)
{
var opacity = GetOpacity(Opacity.Invisible);
var background = GetColor(Colors.Gold);
var p1 = GetColor(Colors.Blue);
var p2 = GetColor(Colors.Green);
var p3 = GetColor(Colors.Red);
var p4 = GetColor(Colors.Yellow);
var colors = new string[] {p1, p2, p3, p4};
var opacities = new double[] {opacity, opacity, opacity, opacity };
if (size < 1)
throw new ArgumentOutOfRangeException(nameof(size));
Size = size;
var builder = ImmutableDictionary.CreateBuilder<int, Cell>();
for (int i = 0; i < size * size; i++) {
if (i == 10 || i == 27 || i == 44) { // ForwardStep cells
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make this random.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random can make infinite loop.

builder.Add(i, new Cell() {Background = GetColor(Colors.Forward), Colors = colors, Opacities = opacities});
} else if (i == 20 || i == 35 || i == 54) { // BackwardStep cells
builder.Add(i, new Cell() {Background = GetColor(Colors.Backward), Colors = colors, Opacities = opacities});
}
else {
builder.Add(i, new Cell() {Background = GetColor(Colors.Gold), Colors = colors, Opacities = opacities});
}
}
Cells = builder.ToImmutable();
}

[JsonConstructor]
public DiceBoard(int size, ImmutableDictionary<int, Cell> cells)
{
if (size < 1)
throw new ArgumentOutOfRangeException(nameof(size));
if (size * size != cells.Count)
throw new ArgumentOutOfRangeException(nameof(size));
Size = size;
Cells = cells;
}

public int GetCellIndex(int r, int c) => r * Size + c;

public DiceBoard Set(int r, int c, int playerIndex, double value)
{
if (r < 0 || r >= Size)
throw new ArgumentOutOfRangeException(nameof(r));
if (c < 0 || c >= Size)
throw new ArgumentOutOfRangeException(nameof(c));
var cellIndex = GetCellIndex(r, c);
var cell = Cells[cellIndex];
cell.Opacities[playerIndex] = value;
return new DiceBoard(Size, Cells);
}

public static double GetOpacity(Opacity opacity)
{
var results = new Dictionary<Opacity, double>() {
{Opacity.Current, 1.0},
{Opacity.Past, 0.1},
{Opacity.Invisible, 0.0},
};
return results[opacity];
}

public static string GetColor(Colors color)
{
var results = new Dictionary<Colors, string>() {
{Colors.Blue, "blue"},
{Colors.Green, "green"},
{Colors.Red, "red"},
{Colors.Yellow, "yellow"},
{Colors.Gold, "lightgoldenrodyellow"},
{Colors.Backward, "#DC381F"},
{Colors.Forward, "#52D017"},
};
return results[color];
}
}
}
144 changes: 144 additions & 0 deletions src/Abstractions/Games/Dice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Drawing;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Stl.DependencyInjection;
using Stl.Fusion;
using Stl.Time;
using Stl.Time.Internal;

namespace BoardGames.Abstractions.Games
{
public record DiceState(DiceBoard Board,
Dictionary<int, int> Scores,
Dictionary<int, int> Steps,
int MoveIndex = 0,
int FirstPlayerIndex = 0,
int PlayersCount = 0)

{
public int PlayerIndex => (MoveIndex + FirstPlayerIndex) % PlayersCount;
public DiceState() : this((DiceBoard) null!, (Dictionary<int, int>) null!, (Dictionary<int, int>) null!) { }
}

public record DiceMove(int PlayerIndex, int Value) : GameMove
{
public DiceMove() : this(0, 0) {}
}

[Service, ServiceAlias(typeof(IGameEngine), IsEnumerable = true)]
public class DiceEngine : GameEngine<DiceState, DiceMove>
{
public static int BoardSize { get; } = 8;
public override string Id => "dice";
public override string Title => "Dice";
public override string Icon => "fa-dice-five";
public override int MinPlayerCount => 2;
public override int MaxPlayerCount => 4;
public override bool AutoStart => true;

public override Game Start(Game game)
{
var scores = new Dictionary<int, int>() {
{0, -1},
{1, -1},
{2, -1},
{3, -1},
};
var steps = new Dictionary<int, int>() {
{0, 0},
{1, 0},
{2, 0},
{3, 0},
};
var rnd = new Random();
var playersCount = game.Players.Count;
var firstPlayerIndex = rnd.Next(0, playersCount);
var state = new DiceState(DiceBoard.Empty(BoardSize), scores, steps, 0, firstPlayerIndex, playersCount);
var player = game.Players[state.PlayerIndex];
return game with {
StateJson = SerializeState(state),
StateMessage = StandardMessages.MoveTurn(new AppUser(player.UserId)),
};
}

public override Game Move(Game game, DiceMove move)
{
if (game.Stage == GameStage.Ended)
throw new ApplicationException("Game is ended.");
var state = DeserializeState(game.StateJson);
if (move.PlayerIndex != state.PlayerIndex)
throw new ApplicationException("It's another player's turn.");
var board = state.Board;
var player = game.Players[state.PlayerIndex];
var oldPlayerScore = state.Scores[state.PlayerIndex];
var playerScore = oldPlayerScore += move.Value;
state.Steps[state.PlayerIndex] += 1;
var playerSteps = state.Steps[state.PlayerIndex];
if (playerScore == 10 || playerScore == 27 || playerScore == 44) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kinda weird - i.e. I'd use board to check exactly this thing.

playerScore += 3;
}
if (playerScore == 20 || playerScore == 35 || playerScore == 54) {
playerScore -= 3;
}
state.Scores[state.PlayerIndex] = playerScore;
var scores = state.Scores;

if (playerScore >= (BoardSize * BoardSize) - 1)
playerScore = (BoardSize * BoardSize) - 1;

board = ChangePlayerCells(board, move.PlayerIndex, playerScore, oldPlayerScore);

if (playerScore >= (BoardSize * BoardSize) - 1) {
var newState = state with {Board = board, MoveIndex = state.MoveIndex + 1, Scores = scores};
var newGame = game with {StateJson = SerializeState(newState)};
newGame = IncrementPlayerScore(newGame, move.PlayerIndex, 1) with {
StateMessage = StandardMessages.WinWithScore(new AppUser(player.UserId), game, playerSteps),
Stage = GameStage.Ended,
};
return newGame;
}

var rowAndCol = GetRowAndColValues(playerScore);

var nextBoard = board.Set(rowAndCol.Item1, rowAndCol.Item2, state.PlayerIndex, DiceBoard.GetOpacity(DiceBoard.Opacity.Current));
var nextState = state with {
Board = nextBoard,
MoveIndex = state.MoveIndex + 1,
Scores = scores,
};
var nextPlayer = game.Players[nextState.PlayerIndex];
var nextGame = game with {StateJson = SerializeState(nextState)};
nextGame = nextGame with {
StateMessage = StandardMessages.MoveTurn(new AppUser(nextPlayer.UserId)),
};
return nextGame;
}

private DiceBoard ChangePlayerCells(DiceBoard board, int playerIndex, int newScore, int oldScore)
{
var cells = board.Cells;
for (int i = 0; i < newScore + 1; i++) {
var cell = cells.ElementAt(i).Value;
cell.Opacities[playerIndex] = DiceBoard.GetOpacity(DiceBoard.Opacity.Past);
}

for (int i = newScore + 1; i < BoardSize * BoardSize; i++) {
var cell = cells.ElementAt(i).Value;
cell.Opacities[playerIndex] = DiceBoard.GetOpacity(DiceBoard.Opacity.Invisible);
}

return board;
}

private (int, int) GetRowAndColValues(long value)
{
var row = value / BoardSize;
var col = value % BoardSize;
return ((int)row, (int)col);
}
}
}
2 changes: 0 additions & 2 deletions src/Abstractions/Games/Gomoku.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ public enum GomokuGameEndingKind
public record GomokuState(CharBoard Board, int MoveIndex = 0, int FirstPlayerIndex = 0)
{
public int PlayerIndex => (MoveIndex + FirstPlayerIndex) % 2;
public int NextPlayerIndex => (PlayerIndex + 1) % 2;

public GomokuState() : this((CharBoard) null!) { }
}

Expand Down
1 change: 1 addition & 0 deletions src/Host/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public void ConfigureServices(IServiceCollection services)
HostSettings = tmpServices.GetRequiredService<HostSettings>();

// DbContext & related services

var appTempDir = PathEx.GetApplicationTempDirectory("", true);
var sqliteDbPath = appTempDir & "App_v0_1.db";
services.AddDbContextFactory<AppDbContext>(builder => {
Expand Down
5 changes: 5 additions & 0 deletions src/SharedServices/ClientServicesModule.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using BoardGames.Abstractions;
using BoardGames.Abstractions.Games;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Pluralize.NET;
using Stl.Extensibility;
using Stl.Fusion;
Expand All @@ -16,6 +20,7 @@ public ClientServicesModule(IServiceCollection services, IServiceProvider module

public override void Use()
{

// Other UI-related services
Services.AddSingleton<IPluralize, Pluralizer>();
Services.AddFusion().AddLiveClock();
Expand Down
Loading