From e8bb8a9e4d336ba6e0dad384bcadf0f727977588 Mon Sep 17 00:00:00 2001 From: Andreas Troelsen Date: Sat, 22 Aug 2020 15:14:54 +0200 Subject: [PATCH] Introduce ThingPicker. Things work best as hardwired, atomic pieces of "stuff", so fitting RNG into the framework is a little difficult. We could have a RandomThing, but the issue with that is that the entropy has to live inside of it for it to be random every time it is given, taken, or checked. This makes it impossible to make a well-defined, atomic Thing with entropy involved. Enter the pickers. The ThingPicker is an early stage extension to the Things framework that encapsulates the possibilities of entropy and grouping _around_ Things, a type of Factory pattern. This introductory stage consists of a "leaf picker" that can only ever pick a single Thing, a Composite Pattern "group picker" that wraps a list of other pickers and shoves their resulting Thing instances into a ThingGroup (introduced in the previous commit). These simple constructs should now make it possible to introduce entropy without placing the responsibility on the Thing framework itself. Using this extension requires adapting code to using the ThingPicker interface instead of using Thing directly. --- .../MobArena/things/RandomThingPicker.java | 31 +++++++++++++ .../MobArena/things/SingleThingPicker.java | 21 +++++++++ .../MobArena/things/ThingGroupPicker.java | 31 +++++++++++++ .../MobArena/things/ThingPicker.java | 22 +++++++++ .../things/RandomThingPickerTest.java | 45 +++++++++++++++++++ .../MobArena/things/ThingGroupPickerTest.java | 40 +++++++++++++++++ 6 files changed, 190 insertions(+) create mode 100644 src/main/java/com/garbagemule/MobArena/things/RandomThingPicker.java create mode 100644 src/main/java/com/garbagemule/MobArena/things/SingleThingPicker.java create mode 100644 src/main/java/com/garbagemule/MobArena/things/ThingGroupPicker.java create mode 100644 src/main/java/com/garbagemule/MobArena/things/ThingPicker.java create mode 100644 src/test/java/com/garbagemule/MobArena/things/RandomThingPickerTest.java create mode 100644 src/test/java/com/garbagemule/MobArena/things/ThingGroupPickerTest.java diff --git a/src/main/java/com/garbagemule/MobArena/things/RandomThingPicker.java b/src/main/java/com/garbagemule/MobArena/things/RandomThingPicker.java new file mode 100644 index 0000000..0b94287 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/things/RandomThingPicker.java @@ -0,0 +1,31 @@ +package com.garbagemule.MobArena.things; + +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +public class RandomThingPicker implements ThingPicker { + + private final List pickers; + private final Random random; + + public RandomThingPicker(List pickers, Random random) { + this.pickers = pickers; + this.random = random; + } + + @Override + public Thing pick() { + int index = random.nextInt(pickers.size()); + return pickers.get(index).pick(); + } + + @Override + public String toString() { + String list = pickers.stream() + .map(ThingPicker::toString) + .collect(Collectors.joining(" or ")); + return "(" + list + ")"; + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/things/SingleThingPicker.java b/src/main/java/com/garbagemule/MobArena/things/SingleThingPicker.java new file mode 100644 index 0000000..6534a5b --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/things/SingleThingPicker.java @@ -0,0 +1,21 @@ +package com.garbagemule.MobArena.things; + +public class SingleThingPicker implements ThingPicker { + + private final Thing thing; + + public SingleThingPicker(Thing thing) { + this.thing = thing; + } + + @Override + public Thing pick() { + return thing; + } + + @Override + public String toString() { + return thing.toString(); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/things/ThingGroupPicker.java b/src/main/java/com/garbagemule/MobArena/things/ThingGroupPicker.java new file mode 100644 index 0000000..ad548a2 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/things/ThingGroupPicker.java @@ -0,0 +1,31 @@ +package com.garbagemule.MobArena.things; + +import java.util.List; +import java.util.stream.Collectors; + +public class ThingGroupPicker implements ThingPicker { + + private final List pickers; + + public ThingGroupPicker(List pickers) { + this.pickers = pickers; + } + + @Override + public Thing pick() { + List things = pickers.stream() + .map(ThingPicker::pick) + .collect(Collectors.toList()); + + return new ThingGroup(things); + } + + @Override + public String toString() { + String list = pickers.stream() + .map(ThingPicker::toString) + .collect(Collectors.joining(" and ")); + return "(" + list + ")"; + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/things/ThingPicker.java b/src/main/java/com/garbagemule/MobArena/things/ThingPicker.java new file mode 100644 index 0000000..a34a40e --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/things/ThingPicker.java @@ -0,0 +1,22 @@ +package com.garbagemule.MobArena.things; + +/** + * Pickers encapsulate a type of highly specific Factory pattern that revolves + * around choosing from an arbitrary pool of Thing instances without directly + * giving them to or taking them from players. + *

+ * The interface exposes a single method, {@link #pick()}. When invoked, the + * implementation is expected to resolve or create a {@link Thing} instance + * or null. The how and why is entirely up to the implementation, but callers + * must not depend on the return value being stable, even though it may be. + */ +public interface ThingPicker { + + /** + * Pick a thing. + * + * @return a {@link Thing} instance, or null + */ + Thing pick(); + +} diff --git a/src/test/java/com/garbagemule/MobArena/things/RandomThingPickerTest.java b/src/test/java/com/garbagemule/MobArena/things/RandomThingPickerTest.java new file mode 100644 index 0000000..c1fd0b0 --- /dev/null +++ b/src/test/java/com/garbagemule/MobArena/things/RandomThingPickerTest.java @@ -0,0 +1,45 @@ +package com.garbagemule.MobArena.things; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class RandomThingPickerTest { + + private RandomThingPicker subject; + private List pickers; + private Random random; + + @Before + public void setup() { + pickers = new ArrayList<>(); + random = mock(Random.class); + subject = new RandomThingPicker(pickers, random); + } + + @Test + public void invokesOnlyChosenPicker() { + ThingPicker decoy = mock(ThingPicker.class); + ThingPicker chosen = mock(ThingPicker.class); + pickers.add(decoy); + pickers.add(decoy); + pickers.add(chosen); + pickers.add(decoy); + when(random.nextInt(pickers.size())).thenReturn(2); + + subject.pick(); + + verify(decoy, never()).pick(); + verify(chosen, times(1)).pick(); + } + +} diff --git a/src/test/java/com/garbagemule/MobArena/things/ThingGroupPickerTest.java b/src/test/java/com/garbagemule/MobArena/things/ThingGroupPickerTest.java new file mode 100644 index 0000000..ae1affb --- /dev/null +++ b/src/test/java/com/garbagemule/MobArena/things/ThingGroupPickerTest.java @@ -0,0 +1,40 @@ +package com.garbagemule.MobArena.things; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ThingGroupPickerTest { + + private ThingGroupPicker subject; + private List pickers; + + @Before + public void setup() { + pickers = new ArrayList<>(); + subject = new ThingGroupPicker(pickers); + } + + @Test + public void invokesAllPickers() { + ThingPicker first = mock(ThingPicker.class); + ThingPicker second = mock(ThingPicker.class); + ThingPicker third = mock(ThingPicker.class); + pickers.add(first); + pickers.add(second); + pickers.add(third); + + subject.pick(); + + verify(first, times(1)).pick(); + verify(second, times(1)).pick(); + verify(third, times(1)).pick(); + } + +}