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 democracy #321

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion TPP.Core/Modes/Runmode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ private async Task CollectRunStatistics(User user, InputSequence input, string r
if (runNumber != null && !user.ParticipationEmblems.Contains(runNumber.Value))
await _userRepo.GiveEmblem(user, runNumber.Value);
long counter = await _runCounterRepo.Increment(runNumber, incrementBy: input.InputSets.Count);
await _overlayConnection.Send(new ButtonPressUpdate(counter), CancellationToken.None);
await _overlayConnection.Send(new ButtonPressesCountUpdate(counter), CancellationToken.None);
}

public async Task Run()
Expand Down
75 changes: 73 additions & 2 deletions TPP.Core/Overlay/Events/RunInputEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.Serialization;
using NodaTime;
using TPP.Inputting;
using TPP.Inputting.Inputs;
using TPP.Model;
Expand Down Expand Up @@ -66,11 +67,81 @@ public sealed class AnarchyInputStop : IOverlayEvent
}

[DataContract]
public sealed class ButtonPressUpdate : IOverlayEvent
public sealed class ButtonPressesCountUpdate : IOverlayEvent
{
public string OverlayEventType => "button_press_update";

[DataMember(Name = "presses")] public long NumTotalButtonPresses { get; set; }
public ButtonPressUpdate(long numTotalButtonPresses) => NumTotalButtonPresses = numTotalButtonPresses;
public ButtonPressesCountUpdate(long numTotalButtonPresses) => NumTotalButtonPresses = numTotalButtonPresses;
}

[DataContract]
public readonly struct NewVote
{
[DataMember(Name = "command")] public string Command { get; init; }
[DataMember(Name = "user")] public User User { get; init; }
// [DataMember(Name = "button_sequence")] public InputSequence InputSequence { get; init; } // unused
// [DataMember(Name = "ts")] public Instant Timestamp { get; init; } // unused
}

[DataContract]
public readonly struct Vote
{
[DataMember(Name = "command")] public string Command { get; init; }
[DataMember(Name = "count")] public int Count { get; init; }
// [DataMember(Name = "button_sequence")] public InputSequence InputSequence { get; init; } // unused
}

[DataContract]
public sealed class DemocracyVotesUpdate : IOverlayEvent
{
public string OverlayEventType => "democracy_new_vote";

[DataMember(Name = "new_vote")] public NewVote NewVote { get; init; }
[DataMember(Name = "votes")] public List<Vote> Votes { get; init; }

public DemocracyVotesUpdate(
User newVoteUser,
InputSequence votedInput,
IReadOnlyDictionary<InputSequence, int> votes)
{
NewVote = new NewVote { User = newVoteUser, Command = votedInput.OriginalText };
Votes = votes
.Select(kvp => new Vote { Command = kvp.Key.OriginalText, Count = kvp.Value })
.OrderBy(vote => vote.Count)
.ToList();
}
}

[DataContract]
public sealed class DemocracyReset : IOverlayEvent
{
public string OverlayEventType => "democracy_reset";

[DataMember(Name = "vote_ends_at")] public Instant Timestamp { get; init; }
public DemocracyReset(Instant timestamp) => Timestamp = timestamp;
}

[DataContract]
public sealed class DemocracyVotingOver : IOverlayEvent
{
public string OverlayEventType => "democracy_voting_over";

[DataMember(Name = "winning_button_sequence")] public string WinningSequence { get; init; }
public DemocracyVotingOver(InputSequence input) => WinningSequence = input.OriginalText;
}

[DataContract]
public sealed class DemocracySequenceStart : IOverlayEvent
{
public string OverlayEventType => "democracy_sequence_start";

[DataMember(Name = "button_sequence")] public List<List<string>> Sequence { get; init; }
public DemocracySequenceStart(InputSequence input)
{
Sequence = input.InputSets
.Select(set => set.Inputs
.Select(i => i.DisplayedText).ToList()).ToList();
Copy link
Member Author

Choose a reason for hiding this comment

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

The old overlay relies on the input text to be regex-parseable to be recognized as a touch input and therefore displayed during execution, so this does not work for touchscreen aliases. Might be worth rethinking the wire format.
Note that in the old overlay the wire format for button sequences is shared across many places, and in particular with the anarchy_input_start event in regards to the touch detection logic (see check_button_set_for_touchscreen in old overlay touchscreen.js).

}
}
}
2 changes: 1 addition & 1 deletion TPP.Inputting/InputHoldTiming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public TimedInputSet TimeInput(InputSet inputSet, float duration)
sleepDuration = _minSleepDuration;
}

return new TimedInputSet(new InputSet(inputsWithoutHold), holdDuration, sleepDuration);
return new TimedInputSet(inputSet with { Inputs = inputsWithoutHold }, holdDuration, sleepDuration);
}
}
}
11 changes: 7 additions & 4 deletions TPP.Inputting/InputSequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ namespace TPP.Inputting
/// An input sequence is a sequence of <see cref="InputSet"/> that are inputted in sequence.
/// This is used e.g. in democracy mode.
/// </summary>
public sealed record InputSequence(IImmutableList<InputSet> InputSets)
public sealed record InputSequence(IImmutableList<InputSet> InputSets, string OriginalText)
{
// Need to manually define these, because lists don't implement a proper Equals and GetHashCode themselves.
public bool Equals(InputSequence? other) => other != null && InputSets.SequenceEqual(other.InputSets);
public override int GetHashCode() => InputSets.Select(i => i.GetHashCode()).Aggregate(HashCode.Combine);
public bool Equals(InputSequence? other) =>
other != null && InputSets.SequenceEqual(other.InputSets) && OriginalText == other.OriginalText;
public override int GetHashCode() =>
InputSets.Select(i => i.GetHashCode()).Aggregate(HashCode.Combine) + OriginalText.GetHashCode();

/// <summary>
/// Determines whether this input sequence is effectively equal to another input sequence,
Expand All @@ -32,6 +34,7 @@ public bool HasSameOutcomeAs(InputSequence other)
return true;
}

public override string ToString() => $"{nameof(InputSequence)}({string.Join(", ", InputSets)})";
public override string ToString() =>
$"{nameof(InputSequence)}([{string.Join(", ", InputSets)}] '{OriginalText}')";
}
}
10 changes: 6 additions & 4 deletions TPP.Inputting/InputSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ namespace TPP.Inputting
/// An input set is a set of inputs being inputted simultaneously.
/// These include buttons, touch screen coordinates, waits etc.
/// </summary>
public sealed record InputSet(ImmutableList<Input> Inputs)
public sealed record InputSet(ImmutableList<Input> Inputs, string OriginalText)
{
// Need to manually define these, because lists don't implement a proper Equals and GetHashCode themselves.
public bool Equals(InputSet? other) => other != null && Inputs.SequenceEqual(other.Inputs);
public override int GetHashCode() => Inputs.Select(i => i.GetHashCode()).Aggregate(HashCode.Combine);
public bool Equals(InputSet? other) =>
other != null && Inputs.SequenceEqual(other.Inputs) && OriginalText == other.OriginalText;
public override int GetHashCode() =>
Inputs.Select(i => i.GetHashCode()).Aggregate(HashCode.Combine) + OriginalText.GetHashCode();

/// <summary>
/// Determines whether this input set is effectively equal to another input set,
Expand All @@ -29,7 +31,7 @@ public bool HasSameOutcomeAs(InputSet other)
return new HashSet<Input>(Inputs, SameOutcomeComparer.Instance).SetEquals(other.Inputs);
}

public override string ToString() => string.Join("+", Inputs);
public override string ToString() => $"{nameof(InputSet)}({string.Join("+", Inputs)} '{OriginalText}')";
}

/// <summary>
Expand Down
51 changes: 51 additions & 0 deletions TPP.Inputting/LinqExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace TPP.Inputting;

public static class LinqExtensions
{
private sealed class Group<T> : IGrouping<T, T>
{
public T Elem { get; }
public List<T> Members { get; }
public Group(T elem, List<T> members)
{
Elem = elem;
Members = members;
}

public T Key => Elem;
public IEnumerator<T> GetEnumerator() => Members.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Members).GetEnumerator();
}

/// <summary>
/// Inspired by <a href="https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/GroupAdjacent.cs">MoreLINQ's GroupAdjacent</a>.
/// Might want to replace with proper dependency on `MoreLINQ` once more than this is needed.
/// </summary>
public static IEnumerable<IGrouping<T, T>> GroupAdjacent<T>(
this IEnumerable<T> source,
Func<T, T, bool> equalityComparer)
{
Group<T>? group = null;
foreach (T element in source)
{
if (group != null)
{
if (equalityComparer(group.Elem, element))
{
group.Members.Add(element);
continue;
}
else
yield return group;
}
group = new Group<T>(element, new List<T> { element });
}
if (group != null)
yield return group;
}
}
19 changes: 13 additions & 6 deletions TPP.Inputting/Parsing/BareInputParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ public BareInputParser(
}

// Get the indexes that each input set ends at
IEnumerable<int> inputSetEndIndexes = match.Groups["inputset"].Captures
IEnumerable<(int, int)> inputSetRanges = match.Groups["inputset"].Captures
.OrderBy(c => c.Index)
.Select(c => c.Index + c.Length);
.Select(c => (c.Index, c.Index + c.Length));
// Get all captures as queues for easy consumption
Dictionary<IInputDefinition, Queue<Capture>> defsToCaptureQueues = _inputDefinitions
.Select((def, i) =>
Expand All @@ -75,7 +75,7 @@ public BareInputParser(
var capturesRepeat = new Queue<Capture>(match.Groups["repeat"].Captures.OrderBy(c => c.Index));

var inputSets = new List<InputSet>();
foreach (int endIndex in inputSetEndIndexes)
foreach ((int startIndex, int endIndex) in inputSetRanges)
{
var inputs = new List<Input>();
var inputWithIndexes = new List<(int, Input)>();
Expand All @@ -99,12 +99,19 @@ public BareInputParser(
inputs.Add(HoldInput.Instance);
capturesHold.Dequeue();
}
string originalText;
int numRepeat = 1;
if (capturesRepeat.Any() && capturesRepeat.Peek().Index < endIndex)
int capturesRepeatIndex = capturesRepeat.Any() ? capturesRepeat.Peek().Index : endIndex;
if (capturesRepeatIndex < endIndex)
{
numRepeat = int.Parse(capturesRepeat.Dequeue().Value);
originalText = text[startIndex..capturesRepeatIndex];
}
var inputSet = new InputSet(inputs.ToImmutableList());
else
{
originalText = text[startIndex..endIndex];
}
var inputSet = new InputSet(inputs.ToImmutableList(), originalText);
inputSets.AddRange(Enumerable.Repeat(inputSet, numRepeat));
// we need to check the length, because the regex cannot enforce the max length since the sequence may
// have been lengthened with a specified number of repetitions for a button set.
Expand All @@ -113,7 +120,7 @@ public BareInputParser(
return null;
}
}
return new InputSequence(inputSets.ToImmutableList());
return new InputSequence(inputSets.ToImmutableList(), text);
}
}
}
3 changes: 2 additions & 1 deletion TPP.Inputting/Parsing/SidedInputParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public SidedInputParser(IInputParser delegateParser)
bool direct = inputSide != null;
var sideInput = new SideInput(inputSide, direct);
return new InputSequence(inputSequence.InputSets
.Select(set => new InputSet(set.Inputs.Append(sideInput).ToImmutableList())).ToImmutableList());
.Select(set => set with { Inputs = set.Inputs.Append(sideInput).ToImmutableList() }).ToImmutableList(),
text);
}
}
}
24 changes: 24 additions & 0 deletions tests/TPP.Core.Tests/Overlay/OverlayConnectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using NodaTime;
using NUnit.Framework;
using TPP.Core.Overlay;

Expand Down Expand Up @@ -50,5 +51,28 @@ await _connection.Send(
const string json = @"{""type"":""test"",""extra_parameters"":{""enum_value"":""foo_bar""}}";
_broadcastServerMock.Verify(s => s.Send(json, CancellationToken.None), Times.Once);
}

private struct EventWithInstant : IOverlayEvent
{
public string OverlayEventType => "iso_test";
[DataMember(Name = "instant")] public Instant Instant { get; init; }
public EventWithInstant(Instant instant) => Instant = instant;
}

[Test]
public async Task send_instant_as_iso8601()
{
await _connection.Send(new EventWithInstant(Instant.FromUnixTimeSeconds(0)), CancellationToken.None);
await _connection.Send(new EventWithInstant(Instant.FromUnixTimeSeconds(123)), CancellationToken.None);
await _connection.Send(new EventWithInstant(Instant.FromUnixTimeSeconds(123).PlusNanoseconds(1)),
CancellationToken.None);
const string json1 = @"{""type"":""iso_test"",""extra_parameters"":{""instant"":""1970-01-01T00:00:00Z""}}";
const string json2 = @"{""type"":""iso_test"",""extra_parameters"":{""instant"":""1970-01-01T00:02:03Z""}}";
const string json3 =
@"{""type"":""iso_test"",""extra_parameters"":{""instant"":""1970-01-01T00:02:03.000000001Z""}}";
_broadcastServerMock.Verify(s => s.Send(json1, CancellationToken.None), Times.Once);
_broadcastServerMock.Verify(s => s.Send(json2, CancellationToken.None), Times.Once);
_broadcastServerMock.Verify(s => s.Send(json3, CancellationToken.None), Times.Once);
}
}
}
13 changes: 7 additions & 6 deletions tests/TPP.Inputting.Tests/InputEqualityTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ namespace TPP.Inputting.Tests
public class InputEqualityTest
{
private static InputSet Set(params string[] inputs) =>
new(inputs.Select(s => new Input(s, s, s)).ToImmutableList());
new(inputs.Select(s => new Input(s, s, s)).ToImmutableList(), string.Join('+', inputs));

private static InputSequence Seq(params InputSet[] inputSets) => new(inputSets.ToImmutableList());
private static InputSequence Seq(params InputSet[] inputSets) => new(inputSets.ToImmutableList(),
string.Join("", inputSets.Select(i => i.OriginalText)));

[Test]
public void TestSameOutcomeRegularInput()
Expand Down Expand Up @@ -136,10 +137,10 @@ public void TestSameOutcomeInputSet()
var input4A = new Input("Foo", "a", "foo");
var input4B = new Input("Bar", "a", "bar");

var setRef = new InputSet(ImmutableList.Create(inputRefA, inputRefB));
var setDifferentOrder = new InputSet(ImmutableList.Create(input1A, input1B));
var setDifferentLength = new InputSet(ImmutableList.Create(input2));
var setDifferentEffectiveInput = new InputSet(ImmutableList.Create(input4A, input4B));
var setRef = new InputSet(ImmutableList.Create(inputRefA, inputRefB), "foo+bar");
var setDifferentOrder = new InputSet(ImmutableList.Create(input1A, input1B), "baz+quz");
var setDifferentLength = new InputSet(ImmutableList.Create(input2), "foo");
var setDifferentEffectiveInput = new InputSet(ImmutableList.Create(input4A, input4B), "foo+bar");

Assert.AreNotEqual(setRef, setDifferentOrder);
Assert.IsTrue(setRef.HasSameOutcomeAs(setDifferentOrder));
Expand Down
4 changes: 2 additions & 2 deletions tests/TPP.Inputting.Tests/InputHoldTimingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ public class InputHoldTimingTest
private const float Delta = 1 / 600f;

private static readonly InputSet DummyInput =
new(ImmutableList.Create(new Input("A", "A", "A")));
new(ImmutableList.Create(new Input("A", "A", "A")), "A");

private static readonly InputSet DummyInputHeld =
new(ImmutableList.Create(new Input("A", "A", "A"), HoldInput.Instance));
new(ImmutableList.Create(new Input("A", "A", "A"), HoldInput.Instance), "A-");

[Test]
public void regular_with_spare_time_divides_normally()
Expand Down
Loading