Skip to content

Commit efd0732

Browse files
committed
Induct Project
0 parents  commit efd0732

33 files changed

Lines changed: 2276 additions & 0 deletions

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
# Build Output
3+
.bin/
4+
.obj/
5+
6+
# Local Settings
7+
*.props.user
8+
*.DotSettings.user
9+
10+
# IDE Support
11+
.idea/
12+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net7.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<RootNamespace>BinaryMatrix.Engine</RootNamespace>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="JetBrains.Annotations" Version="2023.2.0" />
12+
<PackageReference Include="Fayti1703.CommonLib" Version="1.0.0" />
13+
</ItemGroup>
14+
15+
<PropertyGroup Condition="$(Configuration) == 'Debug'">
16+
<DefineDebug />
17+
<DefineConstants>DEBUG</DefineConstants>
18+
</PropertyGroup>
19+
20+
</Project>

BinaryMatrixEngine/Card.cs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using System.Collections.Immutable;
2+
using System.Diagnostics;
3+
#pragma warning disable CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
4+
5+
namespace BinaryMatrix.Engine;
6+
7+
/**
8+
* <summary>The "axiom" or "suit" of a card.</summary>
9+
* <remarks>This has no effect on gameplay.</remarks>
10+
*/
11+
public enum Axiom {
12+
DATA = 1,
13+
KIN,
14+
FORM,
15+
16+
VOID,
17+
CHAOS,
18+
CHOICE
19+
}
20+
21+
public enum Value {
22+
#region Numeric Values
23+
TWO = 2,
24+
THREE = 3,
25+
FOUR = 4,
26+
FIVE = 5,
27+
SIX = 6,
28+
SEVEN = 7,
29+
EIGHT = 8,
30+
NINE = 9,
31+
TEN = 10,
32+
#endregion
33+
34+
WILD,
35+
BOUNCE,
36+
BREAK,
37+
TRAP
38+
}
39+
40+
[DebuggerDisplay("{DebugDisplay()}")]
41+
public struct Card : IEquatable<Card> {
42+
public readonly Axiom axiom;
43+
public readonly Value value;
44+
public bool revealed = false;
45+
46+
public Card(Axiom axiom, Value value) {
47+
if(axiom < Axiom.DATA || axiom > Axiom.CHOICE)
48+
throw new ArgumentException("Invalid suit provided.", nameof(axiom));
49+
if(value < Value.TWO || value > Value.TRAP)
50+
throw new ArgumentException("Invalid value provided.", nameof(axiom));
51+
this.axiom = axiom;
52+
this.value = value;
53+
}
54+
55+
public bool IsInvalid => this.value == 0 || this.axiom == 0;
56+
57+
public bool Equals(Card other) => this.axiom == other.axiom && this.value == other.value;
58+
override public bool Equals(object? obj) => obj is Card other && this.Equals(other);
59+
override public int GetHashCode() => HashCode.Combine((int) this.axiom, (int) this.value);
60+
61+
public static bool operator ==(Card left, Card right) => left.Equals(right);
62+
public static bool operator !=(Card left, Card right) => !left.Equals(right);
63+
64+
static Card() {
65+
allCards = Enum.GetValues<Axiom>().SelectMany(_ => Enum.GetValues<Value>(), (axiom, value) => new Card(axiom, value)).ToImmutableList();
66+
}
67+
68+
public static readonly IReadOnlyList<Card> allCards;
69+
70+
public static Axiom? AxiomFromSymbol(char c) {
71+
return c switch {
72+
'+' => Axiom.DATA,
73+
'%' => Axiom.KIN,
74+
'&' => Axiom.FORM,
75+
'!' => Axiom.CHAOS,
76+
'^' => Axiom.VOID,
77+
'#' => Axiom.CHOICE,
78+
_ => null
79+
};
80+
}
81+
82+
public static char AxiomToSymbol(Axiom axiom) {
83+
return axiom switch {
84+
Axiom.DATA => '+',
85+
Axiom.KIN => '%',
86+
Axiom.FORM => '&',
87+
Axiom.VOID => '^',
88+
Axiom.CHAOS => '!',
89+
Axiom.CHOICE => '#',
90+
};
91+
}
92+
93+
public static Value? ValueFromSymbol(char c) {
94+
return c switch {
95+
'2' => Value.TWO,
96+
'3' => Value.THREE,
97+
'4' => Value.FOUR,
98+
'5' => Value.FIVE,
99+
'6' => Value.SIX,
100+
'7' => Value.SEVEN,
101+
'8' => Value.EIGHT,
102+
'9' => Value.NINE,
103+
'a' => Value.TEN,
104+
'*' => Value.WILD,
105+
'?' => Value.BOUNCE,
106+
'>' => Value.BREAK,
107+
'@' => Value.TRAP,
108+
_ => null
109+
};
110+
}
111+
112+
public static char ValueToSymbol(Value value) {
113+
return value switch {
114+
Value.TWO => '2',
115+
Value.THREE => '3',
116+
Value.FOUR => '4',
117+
Value.FIVE => '5',
118+
Value.SIX => '6',
119+
Value.SEVEN => '7',
120+
Value.EIGHT => '8',
121+
Value.NINE => '9',
122+
Value.TEN => 'a',
123+
Value.WILD => '*',
124+
Value.BOUNCE => '?',
125+
Value.BREAK => '>',
126+
Value.TRAP => '@'
127+
};
128+
}
129+
130+
override public string ToString() {
131+
if(this.value == 0 || this.axiom == 0)
132+
return "(( DEFAULT CARD ))";
133+
if(this.IsInvalid)
134+
return "(( INVALID CARD ))";
135+
136+
return new string(new [] { ValueToSymbol(this.value), AxiomToSymbol(this.axiom) });
137+
}
138+
139+
public string DebugDisplay() {
140+
return this.ToString();
141+
}
142+
}

BinaryMatrixEngine/CardList.cs

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
using System.Buffers;
2+
using System.Collections;
3+
using static Fayti1703.CommonLib.Misc;
4+
5+
namespace BinaryMatrix.Engine;
6+
7+
/* TODO?: Do an optimization pass over this?
8+
* We could take advantage of the unique nature of literally *every single list* here.
9+
* Combat Stacks: Filled one at a time, then rapidly emptied. (Defender stack might have some leftovers.)
10+
* Lane Decks/Attacker Deck: Drained one at a time, then rapidly refilled.
11+
* Discard Piles: Filled slowly, then rapidly emptied.
12+
*
13+
* (honestly optimizing this doesn't matter except for rapidly simulating games, so this is more of an academic thing)
14+
*/
15+
/* Note on ordering: For optimization purposes, */
16+
public sealed class CardList : IEnumerable<Card>, IDisposable {
17+
18+
private const bool FAIL_FAST_INVALID = true;
19+
20+
private Card[] cards = Array.Empty<Card>();
21+
public int Count { get; private set; } = 0;
22+
23+
/* TODO?: Profile array pool usage to get a sense of some better parameters here. */
24+
private static readonly ArrayPool<Card> listPool = ArrayPool<Card>.Create(8, 32);
25+
26+
public CardList() {}
27+
28+
public CardList(int initialCapacity) {
29+
this.cards = listPool.Rent(initialCapacity);
30+
}
31+
32+
public CardList(IEnumerable<Card> cards) {
33+
if(cards is CardList list) {
34+
this.cards = listPool.Rent(list.Count);
35+
this.AddAll(list);
36+
return;
37+
}
38+
39+
Card[] theCards = cards.ToArray();
40+
this.cards = listPool.Rent(theCards.Length);
41+
Array.Copy(theCards, this.cards, theCards.Length);
42+
this.Count = theCards.Length;
43+
if(FAIL_FAST_INVALID) {
44+
foreach(ref Card card in this) {
45+
if(card.IsInvalid)
46+
throw new ArgumentException("The provided enumerable contains an invalid card!", nameof(cards));
47+
}
48+
}
49+
}
50+
51+
public CardList(CardList cards) {
52+
this.cards = listPool.Rent(cards.Count);
53+
this.AddAll(cards);
54+
}
55+
56+
public void Dispose() {
57+
if(this.cards.Length > 0)
58+
listPool.Return(this.cards);
59+
}
60+
61+
public ref Card this[int index] {
62+
get {
63+
if(index < 0 || index >= this.Count)
64+
throw new IndexOutOfRangeException();
65+
return ref this.cards[index];
66+
}
67+
}
68+
69+
public ref Card Add(Card card) {
70+
if(FAIL_FAST_INVALID && card.IsInvalid)
71+
throw new ArgumentException("Invalid card inserted!", nameof(card));
72+
this.EnsureFreeCapacity(1);
73+
this.cards[this.Count] = card;
74+
this.Count++;
75+
return ref this.cards[this.Count - 1];
76+
}
77+
78+
public void AddAll(CardList cards) {
79+
this.EnsureFreeCapacity(cards.Count);
80+
81+
Span<Card> sourceRange = cards.cards.AsSpan(..cards.Count);
82+
83+
if(FAIL_FAST_INVALID) {
84+
/* TODO: Can this even happen? */
85+
foreach(ref Card card in sourceRange) {
86+
if(card.IsInvalid)
87+
throw new ArgumentException("The provided list contains an invalid card!", nameof(cards));
88+
}
89+
}
90+
91+
sourceRange.CopyTo(this.cards.AsSpan(this.Count..));
92+
this.Count += cards.Count;
93+
}
94+
95+
public void Clear() {
96+
listPool.Return(Exchange(ref this.cards, Array.Empty<Card>()));
97+
this.Count = 0;
98+
}
99+
100+
public OptionalRef<Card> First() {
101+
return this.Count == 0 ? OptionalRef<Card>.Empty : new OptionalRef<Card>(ref this[0]);
102+
}
103+
104+
public OptionalRef<Card> Last() {
105+
return this.Count == 0 ? OptionalRef<Card>.Empty : new OptionalRef<Card>(ref this[^1]);
106+
}
107+
108+
private void EnsureFreeCapacity(int requested) {
109+
int requiredLength = requested + this.Count;
110+
111+
if(requiredLength < this.cards.Length)
112+
return;
113+
114+
int newLength;
115+
do {
116+
newLength = this.cards.Length * 2;
117+
if(newLength == 0) newLength = 1;
118+
} while(requiredLength > newLength);
119+
120+
Card[] aNewArray = listPool.Rent(newLength);
121+
this.cards.CopyTo(aNewArray, 0);
122+
Card[] theOldArray = Exchange(ref this.cards, aNewArray); /* !! REF INVALIDATION HERE !! */
123+
listPool.Return(theOldArray);
124+
}
125+
126+
public CardListEnumerator GetEnumerator() => new(this);
127+
IEnumerator<Card> IEnumerable<Card>.GetEnumerator() => this.GetEnumerator();
128+
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
129+
130+
131+
public void MoveAllTo(CardList cards) {
132+
if(cards.Count == 0) {
133+
/* target list is empty -> we can simply donate our array to it */
134+
cards.cards = this.cards;
135+
cards.Count = this.Count;
136+
this.cards = Array.Empty<Card>();
137+
this.Count = 0;
138+
} else {
139+
/* otherwise -> slow track, add all of our stuff, then clear ourselves */
140+
cards.AddAll(this);
141+
this.Clear();
142+
}
143+
}
144+
145+
public Card? TakeFirst() {
146+
if(this.Count <= 0)
147+
return null;
148+
149+
Card ret = this.cards[0];
150+
this.Count--;
151+
if(this.Count != 0)
152+
Array.Copy(this.cards, 1, this.cards, 0, this.Count);
153+
return ret;
154+
}
155+
156+
public Card? TakeLast() {
157+
if(this.Count <= 0)
158+
return null;
159+
Card ret = this.cards[this.Count - 1];
160+
this.Count--;
161+
return ret;
162+
}
163+
164+
public Card? Take(int index) {
165+
Card ret = this.cards[index];
166+
this.Count--;
167+
if(index < this.Count)
168+
Array.Copy(this.cards, index + 1, this.cards, index, this.Count - index);
169+
return ret;
170+
}
171+
172+
public class CardListEnumerator : IEnumerator<Card> {
173+
private readonly CardList list;
174+
private int current = -1;
175+
176+
public CardListEnumerator(CardList list) {
177+
this.list = list;
178+
}
179+
180+
public bool MoveNext() {
181+
if(this.current >= this.list.Count)
182+
return false;
183+
this.current++;
184+
return this.current < this.list.Count;
185+
}
186+
187+
public void Reset() {
188+
this.current = -1;
189+
}
190+
191+
public ref Card Current => ref this.list[this.current];
192+
Card IEnumerator<Card>.Current => this.Current;
193+
object IEnumerator.Current => this.Current;
194+
195+
public void Dispose() { /* nothing to dispose */ }
196+
}
197+
}

0 commit comments

Comments
 (0)