The Rock Paper Scissors game forms a magma. An example for object-oriented programmers.

### Rock Paper Scissors #

When my first child was born, my wife and I quickly settled on a first name, but we couldn't agree on the surname, since we don't share the same surname. She wanted our daughter to have her surname, and I wanted her to have mine. We couldn't think of any rational arguments for one or the other, so we decided to settle the matter with a game of Rock Paper Scissors. I lost, so my daughter has my wife's surname.

Despite that outcome, I still find that Rock Paper Scissors is a great way to pick between two equally valid alternatives. You could also flip a coin, or throw a die, but most people have their hands handy, so to speak.

In C#, you can model the three shapes of rock, paper, and scissors like this:

```public abstract class Rps
{
public readonly static Rps Rock = new R();
public readonly static Rps Paper = new P();
public readonly static Rps Scissors = new S();

private Rps() { }

private class R : Rps { }

private class P : Rps { }

private class S : Rps { }

// More members...
}```

I've seen more than one example where people use an enum to model the three shapes, but I believe that this is wrong, because `enum`s have an order to them, including a maximum and a minimum value (by default, `enum` is implemented with a 32-bit integer). That's not how Rock Paper Scissors work, so instead, I chose a different model where `Rock`, `Paper`, and `Scissors` are Singletons.

This design works for the example, although I'm not entirely happy with it. The problem is that Rock, Paper, and Scissors should be a finite set, but by making `Rps` abstract, another developer could, conceivably, create additional derived classes. A finite sum type would have been better, but this isn't easily done in C#. In a language with algebraic data types, you can make a prettier implementation, like this Haskell example. F# would be another good language option.

### Binary operation #

When playing the game of Rock Paper Scissors, each round is a throw that compares two shapes. You can model a throw as a binary operation that returns the winning shape:

```public static Rps Throw(Rps x, Rps y)
{
if (x == Rock && y == Rock)
return Rock;
if (x == Rock && y == Paper)
return Paper;
if (x == Rock && y == Scissors)
return Rock;
if (x == Paper && y == Paper)
return Paper;
if (x == Paper && y == Scissors)
return Scissors;
if (x == Paper && y == Rock)
return Paper;
if (x == Scissors && y == Scissors)
return Scissors;
if (x == Scissors && y == Rock)
return Rock;
return Scissors;
}```

To a C# programmer, perhaps the method name `Throw` is bewildering, because you might expect the method to throw an exception, but I chose to use the domain language of the game.

Because this method takes two `Rps` values as input and returns an `Rps` value as output, it's a binary operation. Thus, you already know it's a magma, but could it, also, be another, stricter binary operations, such as a semigroup or quasigroup?

### Lack of associativity #

In order to be a semigroup, the binary operation must be associative. You can easily demonstrate that `Throw` isn't associative by use of a counter-example:

```[Fact]
public void ThrowIsNotAssociative()
{
// Counter-example
Assert.NotEqual(
Rps.Throw(Rps.Throw(Rps.Paper, Rps.Rock), Rps.Scissors),
Rps.Throw(Rps.Paper, Rps.Throw(Rps.Rock, Rps.Scissors)));
}```

This xUnit.net unit test passes, thereby demonstrating that `Throw` is not associative. The result of paper versus rock is paper, which, pitted against scissors yields scissors. On the other hand, paper versus the result of rock versus scissors is paper, because rock versus scissors is rock, and rock versus paper is paper.

Since `Throw` isn't associative, it's not a semigroup (and, by extension, not a monoid). Could it be a quasigroup?

### Lack of invertibility #

A binary operation must be invertible in order to be a quasigroup. This means that for any two elements `a` and `b`, there must exist two other elements `x` and `y` that turns `a` into `b`.

This property must hold for all values involved in the binary operation - in this case `Rock`, `Paper`, and `Scissors`. A single counter-example is enough to show that `Throw` is not invertible:

```[Fact]
public void ThrowIsNotInvertible()
{
// Counter-example
var a = Rps.Rock;
var b = Rps.Scissors;

Assert.False(Rps.All.Any(x => Rps.Throw(a, x) == b));
Assert.False(Rps.All.Any(y => Rps.Throw(y, a) == b));
}```

This (passing) unit test utilises an `All` property on `Rps`:

```public static IReadOnlyCollection<Rps> All
{
get { return new[] { Rock, Paper, Scissors }; }
}```

For a counter-example, pick `Rock` as `a` and `Scissors` as `b`. There's no value in `All` that satisfies the invertibility property. Therefore, `Throw` is not a quasigroup, either.

### Lack of identity #

Since `Throw` is neither associative nor invertible, it's not really any named algebraic construct, other than a magma. It's neither group, semigroup, quasigroup, monoid, loop, groupoid, etc. Does it have any properties at all, apart from being a binary operation?

It doesn't have identity either, which you can illustrate with another counter-example:

```[Fact]
public void ThrowHasNoIdentity()
{
// Find all identity candidates for Rock
var rockIdentities =
from e in Rps.All
where Rps.Throw(e, Rps.Rock) == Rps.Rock
&& Rps.Throw(Rps.Rock, e) == Rps.Rock
select e;

// Narrow for Paper
var paperIdentities =
from e in rockIdentities
where Rps.Throw(e, Rps.Paper) == Rps.Paper
&& Rps.Throw(Rps.Paper, e) == Rps.Paper
select e;

// None of those candidates are the identity for Scissors
var scissorIdentities =
from e in paperIdentities
where Rps.Throw(e, Rps.Scissors) == Rps.Scissors
&& Rps.Throw(Rps.Scissors, e) == Rps.Scissors
select e;
Assert.Empty(scissorIdentities);
}```

First, you use `Rps.All` to find all the values that behave as an identity element for `Rps.Rock`. Recall that the identity is an element that doesn't change the input. In other words it's a value that when combined with `Rps.Rock` in `Throw` still returns `Rps.Rock`. There are two values that fulfil that property: `Rps.Rock` and `Rps.Scissors`. Those are the two values contained in `rockIdentities`.

In order to be an identity, the value must behave as a neutral element for all possible values, so next, filter `rockIdentities` to find those elements that also behave as identities for `Rps.Paper`. Between `Rps.Rock` and `Rps.Scissors`, only `Rps.Rock` behaves like an identity for `Rps.Paper`, so `paperIdentities` is a collection containing only the single value `Rps.Rock`.

Is `Rps.Rock` an identity for `Rps.Scissors`, then? It's not. `scissorIdentities` is empty. There's no element in `Rps.All` that acts an identity for all values in `Rps.All`. Therefore, by brute force, the test `ThrowHasNoIdentity` demonstrates (as it says on the tin) that throw has no identity.

### Commutativity #

While `Throw` is neither associative, invertible, nor has identity, it does have at least one property: it's commutative. This means that the order of the input values doesn't matter. In other words, for any two `Rps` values `x` and `y`, this assertion always passes:

```Assert.Equal(
Rps.Throw(x, y),
Rps.Throw(y, x));```

Since `Rps.Throw(x, y)` is equal to `Rps.Throw(y, x)`, `Throw` is commutative.

### Summary #

The Rock Paper Scissors `Throw` operation is a commutative magma, but while, for example, we call an associative magma a semigroup, there's no fancy word for a commutative magma.

Next: Colour-mixing magma.

### Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

#### Published

Thursday, 28 December 2017 11:22:00 UTC

#### Tags

"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!