mirror of
https://github.com/Wessel/nhl-setgame.git
synced 2026-07-22 16:27:56 +02:00
feat: Add initial code
This commit is contained in:
16
backend/backend.sln
Executable file
16
backend/backend.sln
Executable file
@@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "backend", "backend\backend.csproj", "{982660A2-7968-4EE8-88F4-F438910EED70}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{982660A2-7968-4EE8-88F4-F438910EED70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{982660A2-7968-4EE8-88F4-F438910EED70}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{982660A2-7968-4EE8-88F4-F438910EED70}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{982660A2-7968-4EE8-88F4-F438910EED70}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
5
backend/backend/.env
Executable file
5
backend/backend/.env
Executable file
@@ -0,0 +1,5 @@
|
||||
DB_USER=postgres
|
||||
DB_PASS=postgres
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_NAME=SetGame
|
||||
134
backend/backend/Controllers/GamesController.cs
Executable file
134
backend/backend/Controllers/GamesController.cs
Executable file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using backend.Models;
|
||||
|
||||
namespace backend.Controllers
|
||||
{
|
||||
[Route("/api/v1/[controller]")]
|
||||
[ApiController]
|
||||
public class GamesController : ControllerBase {
|
||||
private readonly GameContext _context;
|
||||
|
||||
public GamesController(GameContext context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: api/Games
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<Game>>> GetGames() {
|
||||
return await _context.Games.ToListAsync();
|
||||
}
|
||||
|
||||
// GET: api/Games/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<Game>> GetGame(long id)
|
||||
{
|
||||
var game = await _context.Games.FindAsync(id);
|
||||
|
||||
if (game == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
// PUT: api/Games/5
|
||||
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutGame(long id, Game game)
|
||||
{
|
||||
if (id != game.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(game).State = EntityState.Modified;
|
||||
|
||||
try {
|
||||
await _context.SaveChangesAsync();
|
||||
} catch (DbUpdateConcurrencyException) {
|
||||
if (!GameExists(id)) {
|
||||
return NotFound();
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/Games
|
||||
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Game>> PostGame() {
|
||||
var newGame = new Game {
|
||||
Deck = (
|
||||
from shape in Enum.GetValues(typeof(CardShape)).Cast<CardShape>()
|
||||
from color in Enum.GetValues(typeof(CardColor)).Cast<CardColor>()
|
||||
from count in Enum.GetValues(typeof(CardCount)).Cast<CardCount>()
|
||||
from shade in Enum.GetValues(typeof(CardShade)).Cast<CardShade>()
|
||||
select new Card {
|
||||
Shape = shape,
|
||||
Color = color,
|
||||
Count = count,
|
||||
Shade = shade
|
||||
}.ToUshort()).ToArray()
|
||||
};
|
||||
|
||||
newGame.ShuffleDeck();
|
||||
newGame.DealHand();
|
||||
|
||||
_context.Games.Add(newGame);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction(nameof(GetGame), new { id = newGame.Id }, newGame);
|
||||
}
|
||||
|
||||
// DELETE: api/Games/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteGame(long id) {
|
||||
var game = await _context.Games.FindAsync(id);
|
||||
if (game == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.Games.Remove(game);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private bool GameExists(long id) {
|
||||
return _context.Games.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("[action]/{id}")]
|
||||
public async Task<ActionResult<SetCheckResult>> CheckSet(
|
||||
long id,
|
||||
[FromBody] ushort[] cardIndices
|
||||
) {
|
||||
var game = await _context.Games.FindAsync(id);
|
||||
if (game == null) {
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var res = game.IsSet(cardIndices);
|
||||
|
||||
if (res == null) {
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
backend/backend/Migrations/20250226132916_initial.Designer.cs
generated
Executable file
54
backend/backend/Migrations/20250226132916_initial.Designer.cs
generated
Executable file
@@ -0,0 +1,54 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using backend.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace backend.Migrations
|
||||
{
|
||||
[DbContext(typeof(GameContext))]
|
||||
[Migration("20250226132916_initial")]
|
||||
partial class initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("backend.Models.Game", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.PrimitiveCollection<int[]>("Deck")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.PrimitiveCollection<int[]>("Hand")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.Property<DateTime>("StartedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Games");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
38
backend/backend/Migrations/20250226132916_initial.cs
Executable file
38
backend/backend/Migrations/20250226132916_initial.cs
Executable file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace backend.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Games",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
StartedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
Hand = table.Column<int[]>(type: "integer[]", nullable: true),
|
||||
Deck = table.Column<int[]>(type: "integer[]", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Games", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Games");
|
||||
}
|
||||
}
|
||||
}
|
||||
57
backend/backend/Migrations/20250226135631_FailCount.Designer.cs
generated
Executable file
57
backend/backend/Migrations/20250226135631_FailCount.Designer.cs
generated
Executable file
@@ -0,0 +1,57 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using backend.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace backend.Migrations
|
||||
{
|
||||
[DbContext(typeof(GameContext))]
|
||||
[Migration("20250226135631_FailCount")]
|
||||
partial class FailCount
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("backend.Models.Game", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.PrimitiveCollection<int[]>("Deck")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.Property<int>("Fails")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.PrimitiveCollection<int[]>("Hand")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.Property<DateTime>("StartedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Games");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
29
backend/backend/Migrations/20250226135631_FailCount.cs
Executable file
29
backend/backend/Migrations/20250226135631_FailCount.cs
Executable file
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace backend.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FailCount : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Fails",
|
||||
table: "Games",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Fails",
|
||||
table: "Games");
|
||||
}
|
||||
}
|
||||
}
|
||||
60
backend/backend/Migrations/20250301150105_FinishedAt.Designer.cs
generated
Executable file
60
backend/backend/Migrations/20250301150105_FinishedAt.Designer.cs
generated
Executable file
@@ -0,0 +1,60 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using backend.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace backend.Migrations
|
||||
{
|
||||
[DbContext(typeof(GameContext))]
|
||||
[Migration("20250301150105_FinishedAt")]
|
||||
partial class FinishedAt
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("backend.Models.Game", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.PrimitiveCollection<int[]>("Deck")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.Property<int>("Fails")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime?>("FinishedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.PrimitiveCollection<int[]>("Hand")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.Property<DateTime>("StartedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Games");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
29
backend/backend/Migrations/20250301150105_FinishedAt.cs
Executable file
29
backend/backend/Migrations/20250301150105_FinishedAt.cs
Executable file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace backend.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FinishedAt : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "FinishedAt",
|
||||
table: "Games",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FinishedAt",
|
||||
table: "Games");
|
||||
}
|
||||
}
|
||||
}
|
||||
57
backend/backend/Migrations/GameContextModelSnapshot.cs
Executable file
57
backend/backend/Migrations/GameContextModelSnapshot.cs
Executable file
@@ -0,0 +1,57 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using backend.Models;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace backend.Migrations
|
||||
{
|
||||
[DbContext(typeof(GameContext))]
|
||||
partial class GameContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("backend.Models.Game", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.PrimitiveCollection<int[]>("Deck")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.Property<int>("Fails")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime?>("FinishedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.PrimitiveCollection<int[]>("Hand")
|
||||
.HasColumnType("integer[]");
|
||||
|
||||
b.Property<DateTime>("StartedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Games");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
31
backend/backend/Models/Card.cs
Executable file
31
backend/backend/Models/Card.cs
Executable file
@@ -0,0 +1,31 @@
|
||||
namespace backend.Models;
|
||||
|
||||
// 3, 7 and 8 are chosen due to them in no way,
|
||||
// shape or form adding up or substracting into eachother
|
||||
public enum CardCount { One = 0x0003, Two = 0x0007, Three = 0x0008 }
|
||||
public enum CardColor { Red = 0x0030, Green = 0x0070, Blue = 0x0080 }
|
||||
public enum CardShape { Oval = 0x0300, Diamond = 0x0700, Squiggle = 0x0800 }
|
||||
public enum CardShade { Transparent = 0x3000, Solid = 0x7000, Opaque = 0x8000 }
|
||||
|
||||
public struct Card {
|
||||
public CardCount Count { get; set; }
|
||||
public CardColor Color { get; set; }
|
||||
public CardShape Shape { get; set; }
|
||||
public CardShade Shade { get; set; }
|
||||
|
||||
public readonly ushort ToUshort() {
|
||||
return (ushort)(0x0000 | Convert.ToUInt16(Count)
|
||||
| Convert.ToUInt16(Color)
|
||||
| Convert.ToUInt16(Shape)
|
||||
| Convert.ToUInt16(Shade));
|
||||
}
|
||||
|
||||
public static Card ToCard(int card) {
|
||||
return new Card {
|
||||
Count = (CardCount)(card & 0x000F),
|
||||
Color = (CardColor)(card & 0x00F0),
|
||||
Shape = (CardShape)(card & 0x0F00),
|
||||
Shade = (CardShade)(card & 0xF000)
|
||||
};
|
||||
}
|
||||
}
|
||||
97
backend/backend/Models/Game.cs
Executable file
97
backend/backend/Models/Game.cs
Executable file
@@ -0,0 +1,97 @@
|
||||
namespace backend.Models;
|
||||
|
||||
public struct SetCheckResult {
|
||||
public bool IsSet { get; set; }
|
||||
public Game NewState { get; set; }
|
||||
}
|
||||
|
||||
public class Game {
|
||||
public long Id { get; set; }
|
||||
public DateTime StartedAt { get; set; }
|
||||
public DateTime? FinishedAt { get; set; }
|
||||
public int Fails { get; set; }
|
||||
public ushort[]? Hand { get; set; }
|
||||
public ushort[]? Deck { get; set; }
|
||||
|
||||
public void ShuffleDeck() {
|
||||
if (Deck == null) return;
|
||||
|
||||
Random rand = new Random();
|
||||
Deck = Deck.OrderBy(_ => rand.Next()).ToArray();
|
||||
}
|
||||
|
||||
public void DealHand() {
|
||||
if (Deck == null) return;
|
||||
Hand ??= [];
|
||||
|
||||
List<ushort> handList = [.. Hand];
|
||||
|
||||
while (handList.Count < 12 && Deck.Length > 0) {
|
||||
handList.Add(Deck[0]);
|
||||
Deck = [.. Deck.Skip(1)];
|
||||
}
|
||||
|
||||
Hand = [.. handList];
|
||||
}
|
||||
|
||||
public void ReplaceCardInHand(int index) {
|
||||
if (Hand == null || Deck == null) return;
|
||||
|
||||
var handList = Hand.ToList();
|
||||
|
||||
Console.WriteLine("Deck Length: " + Deck.Length);
|
||||
if (Deck.Length < 1) {
|
||||
handList[index] = 0;
|
||||
Hand = [.. handList];
|
||||
return;
|
||||
}
|
||||
|
||||
handList[index] = Deck[0];
|
||||
Deck = [.. Deck.Skip(1)];
|
||||
Hand = [.. handList];
|
||||
}
|
||||
|
||||
public bool GameIsFinished() {
|
||||
if (Deck == null || Hand == null) return false;
|
||||
|
||||
var finished = Deck.Length == 0 && Hand.All(card => card == 0);
|
||||
|
||||
if (finished) Hand = [];
|
||||
|
||||
return finished;
|
||||
}
|
||||
|
||||
public SetCheckResult? IsSet(ushort[] indices) {
|
||||
if (indices.Length != 3 || Hand == null) return null;
|
||||
|
||||
bool AnyEmpty = indices.Any(index => Hand[index] == 0);
|
||||
if (AnyEmpty) return null;
|
||||
|
||||
var cards = indices
|
||||
.Select(index => Card.ToCard(Hand[index]))
|
||||
.ToList();
|
||||
|
||||
bool matchingShapes = cards.Select(card => card.Shape).Distinct().Count() == 1;
|
||||
bool matchingColors = cards.Select(card => card.Color).Distinct().Count() == 1;
|
||||
bool matchingShades = cards.Select(card => card.Shade).Distinct().Count() == 1;
|
||||
bool matchingCounts = cards.Select(card => card.Count).Distinct().Count() == 1;
|
||||
|
||||
if (!matchingShapes && !matchingColors && !matchingShades && !matchingCounts) {
|
||||
Fails += 1;
|
||||
|
||||
return new SetCheckResult { IsSet = false, NewState = this };
|
||||
}
|
||||
|
||||
// Order by Descending to make sure the correct card is removed
|
||||
// (From the top may cause the indices to be off by one)
|
||||
foreach (var index in indices.OrderByDescending(i => i)) {
|
||||
ReplaceCardInHand(index);
|
||||
}
|
||||
|
||||
if (GameIsFinished()) {
|
||||
FinishedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
return new SetCheckResult { IsSet = true, NewState = this };
|
||||
}
|
||||
}
|
||||
17
backend/backend/Models/GameContext.cs
Executable file
17
backend/backend/Models/GameContext.cs
Executable file
@@ -0,0 +1,17 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace backend.Models;
|
||||
|
||||
public class GameContext : DbContext
|
||||
{
|
||||
public GameContext(DbContextOptions<GameContext> options) : base(options) {
|
||||
}
|
||||
|
||||
public DbSet<Game> Games { get; set; } = null!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder) {
|
||||
modelBuilder.Entity<Game>()
|
||||
.Property(b => b.StartedAt)
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
}
|
||||
}
|
||||
45
backend/backend/Program.cs
Executable file
45
backend/backend/Program.cs
Executable file
@@ -0,0 +1,45 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using backend.Models;
|
||||
using DotNetEnv;
|
||||
|
||||
Env.Load();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var config = builder.Configuration
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddControllers();
|
||||
|
||||
var postgres_connection_string = $@"User ID={config["DB_USER"]};
|
||||
Password={config["DB_PASS"]};
|
||||
Host={config["DB_HOST"]};
|
||||
Port={config["DB_PORT"]};
|
||||
Database={config["DB_NAME"]};
|
||||
Connection Lifetime=0;";
|
||||
|
||||
builder.Services.AddDbContext<GameContext>(opt => opt.UseNpgsql(postgres_connection_string));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Disable CORS
|
||||
app.Logger.LogInformation("Disabling CORS to allow communication with frontend");
|
||||
app.UseCors(builder => {
|
||||
builder.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
|
||||
if (app.Environment.IsDevelopment()) {
|
||||
app.Logger.LogInformation("Enabling Swagger UI for development");
|
||||
app.MapOpenApi();
|
||||
app.UseSwaggerUi(options => options.DocumentPath = "/openapi/v1.json");
|
||||
}
|
||||
|
||||
app.Logger.LogInformation("Creating middleware");
|
||||
// app.UseHttpsRedirection();
|
||||
// app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
23
backend/backend/Properties/launchSettings.json
Executable file
23
backend/backend/Properties/launchSettings.json
Executable file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5224",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7205;http://localhost:5224",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
backend/backend/appsettings.Development.json
Executable file
8
backend/backend/appsettings.Development.json
Executable file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
backend/backend/appsettings.json
Executable file
9
backend/backend/appsettings.json
Executable file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
38
backend/backend/backend.csproj
Executable file
38
backend/backend/backend.csproj
Executable file
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNetEnv" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.3" />
|
||||
<PackageReference Include="NSwag.AspNetCore" Version="14.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Controllers\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update=".env">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
backend/backend/backend.http
Executable file
6
backend/backend/backend.http
Executable file
@@ -0,0 +1,6 @@
|
||||
@backend_HostAddress = http://localhost:5224
|
||||
|
||||
GET {{backend_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
Reference in New Issue
Block a user