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

Multiple Random Numbers Support #5518

Merged
merged 15 commits into from
Sep 22, 2024
Merged
92 changes: 59 additions & 33 deletions src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
*/
package ch.njol.skript.expressions;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

import ch.njol.skript.lang.Literal;
import ch.njol.util.Math2;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;
Expand All @@ -36,62 +38,90 @@
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Kleenean;

@Name("Random Number")
@Name("Random Numbers")
@Description({
"A random number or integer between two given numbers. Use 'number' if you want any number with decimal parts, or use use 'integer' if you only want whole numbers.",
"Please note that the order of the numbers doesn't matter, i.e. <code>random number between 2 and 1</code> will work as well as <code>random number between 1 and 2</code>."
"A given amount of random numbers or integers between two given numbers. Use 'number' if you want any number with decimal parts, or use use 'integer' if you only want whole numbers.",
"Please note that the order of the numbers doesn't matter, i.e. <code>random number between 2 and 1</code> will work as well as <code>random number between 1 and 2</code>."
})
@Examples({
"set the player's health to a random number between 5 and 10",
"send \"You rolled a %random integer from 1 to 6%!\" to the player"
"set the player's health to a random number between 5 and 10",
"send \"You rolled a %random integer from 1 to 6%!\" to the player",
"set {_chances::*} to 5 random integers between 5 and 96",
"set {_decimals::*} to 3 random numbers between 2.7 and -1.5"
})
@Since("1.4")
@Since("1.4, INSERT VERSION (Multiple random numbers)")
public class ExprRandomNumber extends SimpleExpression<Number> {

static {
Skript.registerExpression(ExprRandomNumber.class, Number.class, ExpressionType.COMBINED,
"[a] random (:integer|number) (from|between) %number% (to|and) %number%");
"[a|%-integer%] random (:integer|number)[s] (from|between) %number% (to|and) %number%");
}

private Expression<Number> from, to;
@Nullable
private Expression<Integer> amount;
private Expression<Number> lower, upper;
private boolean isInteger;

@Override
@SuppressWarnings("unchecked")
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
from = (Expression<Number>) exprs[0];
to = (Expression<Number>) exprs[1];
isInteger = parser.hasTag("integer");
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
amount = (Expression<Integer>) exprs[0];
lower = (Expression<Number>) exprs[1];
upper = (Expression<Number>) exprs[2];
isInteger = parseResult.hasTag("integer");
return true;
}

@Override
@Nullable
protected Number[] get(Event event) {
Number from = this.from.getSingle(event);
Number to = this.to.getSingle(event);
Number lowerNumber = lower.getSingle(event);
Number upperNumber = upper.getSingle(event);
if (upperNumber == null || lowerNumber == null || !Double.isFinite(lowerNumber.doubleValue()) || !Double.isFinite(upperNumber.doubleValue()))
return new Number[0];

if (to == null || from == null || !Double.isFinite(from.doubleValue()) || !Double.isFinite(to.doubleValue()))
Integer amount = this.amount == null ? Integer.valueOf(1) : this.amount.getSingle(event);
if (amount == null || amount <= 0)
return new Number[0];
sovdeeth marked this conversation as resolved.
Show resolved Hide resolved

double lower = Math.min(lowerNumber.doubleValue(), upperNumber.doubleValue());
double upper = Math.max(lowerNumber.doubleValue(), upperNumber.doubleValue());
Random random = ThreadLocalRandom.current();
double min = Math.min(from.doubleValue(), to.doubleValue());
double max = Math.max(from.doubleValue(), to.doubleValue());

if (isInteger) {
long inf = Math2.ceil(min);
long sup = Math2.floor(max);
if (max - min < 1 && inf - sup <= 1) {
if (sup == inf || min == inf)
return new Long[] {inf};
if (max == sup)
return new Long[] {sup};
Long[] longs = new Long[amount];
long floored_upper = Math2.floor(upper);
long ceiled_lower = Math2.ceil(lower);

// catch issues like `integer between 0.5 and 0.6`
if (upper - lower < 1 && ceiled_lower - floored_upper <= 1) {
if (floored_upper == ceiled_lower || lower == ceiled_lower) {
Arrays.fill(longs, ceiled_lower);
return longs;
}
if (upper == floored_upper) {
Arrays.fill(longs, floored_upper);
return longs;
}
return new Long[0];
}
return new Long[] {inf + Math2.mod(random.nextLong(), sup - inf + 1)};

for (int i = 0; i < amount; i++)
longs[i] = Math2.ceil(lower) + Math2.mod(random.nextLong(), floored_upper - ceiled_lower + 1);
return longs;
// non-integers
} else {
Double[] doubles = new Double[amount];
for (int i = 0; i < amount; i++)
doubles[i] = Math.min(lower + random.nextDouble() * (upper - lower), upper);
return doubles;
}
}

return new Double[] {min + random.nextDouble() * (max - min)};
@Override
public boolean isSingle() {
if (amount instanceof Literal)
return ((Literal<Integer>) amount).getSingle() == 1;
return amount == null;
}

@Override
Expand All @@ -101,12 +131,8 @@ public Class<? extends Number> getReturnType() {

@Override
public String toString(@Nullable Event event, boolean debug) {
return "a random " + (isInteger ? "integer" : "number") + " between " + from.toString(event, debug) + " and " + to.toString(event, debug);
}

@Override
public boolean isSingle() {
return true;
return (amount == null ? "a" : amount.toString(event, debug)) + " random " + (isInteger ? "integer" : "number") +
(amount == null ? "" : "s") + " between " + lower.toString(event, debug) + " and " + upper.toString(event, debug);
}

}
72 changes: 72 additions & 0 deletions src/test/skript/tests/syntaxes/expressions/ExprRandomNumber.sk
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
test "random numbers":
loop 1000 times:
set {_num} to random number between 1 and 2.5
assert {_num} is set with "(1) failed to generate random number: %{_num}%"
assert {_num} is between 1 and 2.5 with "(1) random number outside bounds: %{_num}%"

set {_int} to random integer between 1 and 2.5
assert {_int} is set with "(2) failed to generate random int: %{_int}%"
assert {_int} is between 1 and 2.5 with "(2) random int outside bounds: %{_int}%"
assert {_int} is 1 or 2 with "(2) random int generated an invalid result: %{_int}%"

# negatives
set {_num} to random number between 1 and -2.5
assert {_num} is set with "(3) failed to generate random number: %{_num}%"
assert {_num} is between 1 and -2.5 with "(3) random number outside bounds: %{_num}%"

set {_int} to random integer between 1 and -2.5
assert {_int} is set with "(4) failed to generate random int: %{_int}%"
assert {_int} is between 1 and -2.5 with "(4) random int outside bounds: %{_int}%"
assert {_int} is 1 or 0 or -1 or -2 with "(4) random int generated an invalid result: %{_int}%"

# multiple values
set {_nums::*} to 3 random numbers between 1 and -2.5
assert {_nums::*} is set with "(5) failed to generate random number: %{_nums::*}%"
assert size of {_nums::*} is 3 with "(6) random numbers generated the wrong amount of results: %size of {_nums::*}% != 3"
assert {_nums::*} is between 1 and -2.5 with "(5) random number outside bounds: %{_nums::*}%"

set {_ints::*} to 3 random integers between 1 and -2.5
assert {_ints::*} is set with "(6) failed to generate random int: %{_ints::*}%"
assert size of {_ints::*} is 3 with "(6) random ints generated the wrong amount of results: %size of {_ints::*}% != 3"
assert {_ints::*} are between 1 and -2.5 with "(6) random int outside bounds: %{_ints::*}%"
assert {_ints::*} are 1 or 0 or -1 or -2 with "(6) random int generated an invalid result: %{_ints::*}%"

# null checks
set {_num} to random number between {_null} and 1
assert {_num} is not set with "(7) random number with null bound returned non-null result: %{_num}%"
set {_num} to random number between 0 and {_null}
assert {_num} is not set with "(8) random number with null bound returned non-null result: %{_num}%"
set {_num::*} to {_null} random numbers between 0 and 10
assert {_num::*} is not set with "(9) random number with null amount returned non-null result: %{_num::*}%"

set {_int} to random integer between {_null} and 1
assert {_int} is not set with "(10) random integer with null bound returned non-null result: %{_int}%"
set {_int} to random integer between 0 and {_null}
assert {_int} is not set with "(11) random integer with null bound returned non-null result: %{_int}%"
set {_int::*} to {_null} random integers between 0 and 10
assert {_int::*} is not set with "(12) random integer with null bound returned non-null result: %{_int::*}%"

# edge cases
set {_num} to random number between 0 and 0
assert {_num} is 0 with "(13) random number between 0 and 0 returned non-zero result: %{_num}%"
set {_int} to random integer between 0 and 0
assert {_int} is 0 with "(14) random integer between 0 and 0 returned non-zero result: %{_int}%"

set {_int} to random integer between 0.5 and 0.6
assert {_int} is not set with "(15) random integer between 0.5 and 0.6 returned non-null result: %{_int}%"

set {_int::*} to -10 random integers between 1 and 10
assert {_int::*} is not set with "(16) -10 random integers returned non-null result: %{_int::*}%"

# NaN/infinity
set {_int} to random integer between 10 and NaN value
assert {_int} is not set with "(17) random integer between 10 and NaN value returned non-null result: %{_int}%"

set {_int} to random integer between 10 and infinity value
assert {_int} is not set with "(18) random integer between 10 and infinity value returned non-null result: %{_int}%"

set {_int} to random integer between 10 and -infinity value
assert {_int} is not set with "(19) random integer between 10 and -infinity value returned non-null result: %{_int}%"

set {_int::*} to NaN value random integers between 1 and 10
assert {_int::*} is not set with "(20) NaN value random integers returned non-null result: %{_int}%"