-
Notifications
You must be signed in to change notification settings - Fork 8
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
base: main
Are you sure you want to change the base?
Changes from 17 commits
d00f575
2331f5d
603f7eb
187f9ed
a06af13
b44cd41
a4bc099
3ba171d
0bf13c5
d98c600
ed4ebc3
2adf16b
b2979fa
ba8bdcd
4119a59
20f0f01
67da876
6e8a5a1
d84f276
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
|
||
|
@@ -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; } | ||
|
||
public struct Cell | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why cell has multiple colors? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 4 colors for all players (blue, green, red, yellow) |
||
public double[] Opacities; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same for opacities? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd make this random. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]; | ||
} | ||
} | ||
} |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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 :)