new RNG for everything; Joise update

This commit is contained in:
minjaesong
2018-10-27 00:03:06 +09:00
parent 22bbc8816c
commit 3d1581d0e4
10 changed files with 408 additions and 64 deletions

Binary file not shown.

View File

@@ -0,0 +1,192 @@
package net.torvald.random;
import java.util.Random;
/**
* Xoroshift128
*
* see https://github.com/SquidPony/SquidLib/blob/master/squidlib-util/src/main/java/squidpony/squidmath/XoRoRNG.java
*/
public class HQRNG extends Random {
private static final long DOUBLE_MASK = (1L << 53) - 1;
private static final double NORM_53 = 1. / (1L << 53);
private static final long FLOAT_MASK = (1L << 24) - 1;
private static final double NORM_24 = 1. / (1L << 24);
private static final long serialVersionUID = 1018744536171610262L;
private long state0, state1;
public long getState0() {
return state0;
}
public long getState1() {
return state1;
}
/**
* Creates a new generator seeded using four calls to Math.random().
*/
public HQRNG() {
this((long) ((Math.random() - 0.5) * 0x10000000000000L)
^ (long) (((Math.random() - 0.5) * 2.0) * 0x8000000000000000L),
(long) ((Math.random() - 0.5) * 0x10000000000000L)
^ (long) (((Math.random() - 0.5) * 2.0) * 0x8000000000000000L));
}
/**
* Constructs this XoRoRNG by dispersing the bits of seed using {@link #setSeed(long)} across the two parts of state
* this has.
* @param seed a long that won't be used exactly, but will affect both components of state
*/
public HQRNG(final long seed) {
setSeed(seed);
}
/**
* Constructs this XoRoRNG by calling {@link #setSeed(long, long)} on the arguments as given; see that method for
* the specific details (stateA and stateB are kept as-is unless they are both 0).
* @param stateA the number to use as the first part of the state; this will be 1 instead if both seeds are 0
* @param stateB the number to use as the second part of the state
*/
public HQRNG(final long stateA, final long stateB) {
setSeed(stateA, stateB);
}
@Override
public final int next(int bits) {
final long s0 = state0;
long s1 = state1;
final int result = (int)(s0 + s1) >>> (32 - bits);
s1 ^= s0;
state0 = (s0 << 55 | s0 >>> 9) ^ s1 ^ (s1 << 14); // a, b
state1 = (s1 << 36 | s1 >>> 28); // c
return result;
}
@Override
public final long nextLong() {
final long s0 = state0;
long s1 = state1;
final long result = s0 + s1;
s1 ^= s0;
state0 = (s0 << 55 | s0 >>> 9) ^ s1 ^ (s1 << 14); // a, b
state1 = (s1 << 36 | s1 >>> 28); // c
/*
state0 = Long.rotateLeft(s0, 55) ^ s1 ^ (s1 << 14); // a, b
state1 = Long.rotateLeft(s1, 36); // c
*/
return result;
}
/**
* Can return any int, positive or negative, of any size permissible in a 32-bit signed integer.
* @return any int, all 32 bits are random
*/
public int nextInt() {
return (int)nextLong();
}
/**
* Exclusive on the outer bound; the inner bound is 0. The bound may be negative, which will produce a non-positive
* result.
* @param bound the outer exclusive bound; may be positive or negative
* @return a random int between 0 (inclusive) and bound (exclusive)
*/
public int nextInt(final int bound) {
return (int) ((bound * (nextLong() >>> 33)) >> 31);
}
/**
* Inclusive lower, exclusive upper.
* @param inner the inner bound, inclusive, can be positive or negative
* @param outer the outer bound, exclusive, should be positive, should usually be greater than inner
* @return a random int that may be equal to inner and will otherwise be between inner and outer
*/
public int nextInt(final int inner, final int outer) {
return inner + nextInt(outer - inner);
}
/**
* Exclusive on the outer bound; the inner bound is 0. The bound may be negative, which will produce a non-positive
* result.
* @param bound the outer exclusive bound; may be positive or negative
* @return a random long between 0 (inclusive) and bound (exclusive)
*/
public long nextLong(long bound) {
long rand = nextLong();
final long randLow = rand & 0xFFFFFFFFL;
final long boundLow = bound & 0xFFFFFFFFL;
rand >>>= 32;
bound >>= 32;
final long z = (randLow * boundLow >> 32);
long t = rand * boundLow + z;
final long tLow = t & 0xFFFFFFFFL;
t >>>= 32;
return rand * bound + t + (tLow + randLow * bound >> 32) - (z >> 63) - (bound >> 63);
}
/**
* Inclusive inner, exclusive outer; both inner and outer can be positive or negative.
* @param inner the inner bound, inclusive, can be positive or negative
* @param outer the outer bound, exclusive, can be positive or negative and may be greater than or less than inner
* @return a random long that may be equal to inner and will otherwise be between inner and outer
*/
public long nextLong(final long inner, final long outer) {
return inner + nextLong(outer - inner);
}
public double nextDouble() {
return (nextLong() & DOUBLE_MASK) * NORM_53;
}
public float nextFloat() {
return (float) ((nextLong() & FLOAT_MASK) * NORM_24);
}
public boolean nextBoolean() {
return nextLong() < 0L;
}
public void nextBytes(final byte[] bytes) {
int i = bytes.length, n = 0;
while (i != 0) {
n = Math.min(i, 8);
for (long bits = nextLong(); n-- != 0; bits >>>= 8) {
bytes[--i] = (byte) bits;
}
}
}
/**
* Sets the seed of this generator using one long, running that through LightRNG's algorithm twice to get the state.
* @param seed the number to use as the seed
*/
public void setSeed(final long seed) {
long state = seed + 0x9E3779B97F4A7C15L,
z = state;
z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L;
z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL;
state0 = z ^ (z >>> 31);
state += 0x9E3779B97F4A7C15L;
z = state;
z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L;
z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL;
state1 = z ^ (z >>> 31);
}
/**
* Sets the seed of this generator using two longs, using them without changes unless both are 0 (then it makes the
* state variable corresponding to stateA 1 instead).
* @param stateA the number to use as the first part of the state; this will be 1 instead if both seeds are 0
* @param stateB the number to use as the second part of the state
*/
public void setSeed(final long stateA, final long stateB) {
state0 = stateA;
state1 = stateB;
if((stateA | stateB) == 0L)
state0 = 1L;
}
}

View File

@@ -1,4 +1,4 @@
package net.torvald.random
/*package net.torvald.random
import net.torvald.terrarum.serialise.toLittle
import net.torvald.terrarum.serialise.toLittleLong
@@ -6,41 +6,178 @@ import org.apache.commons.codec.digest.DigestUtils
import java.util.Random
/**
* Xorshift128+
* Xoroshift128
*
* @see https://github.com/SquidPony/SquidLib/blob/master/squidlib-util/src/main/java/squidpony/squidmath/XoRoRNG.java
*/
class HQRNG @JvmOverloads constructor(seed: Long = System.nanoTime()) : Random() {
class HQRNG() : Random() {
var s0: Long; private set
var s1: Long; private set
private val DOUBLE_MASK = (1L shl 53) - 1
private val NORM_53 = 1.0 / (1L shl 53)
private val FLOAT_MASK = (1L shl 24) - 1
private val NORM_24 = 1.0 / (1L shl 24)
constructor(s0: Long, s1: Long) : this() {
this.s0 = s0
this.s1 = s1
var state0: Long = 0L; private set
var state1: Long = 0L; private set
/**
* Creates a new generator seeded using four calls to Math.random().
*/
init {
reseed(((Math.random() - 0.5) * 0x10000000000000L).toLong() xor ((Math.random() - 0.5) * 2.0 * -0x8000000000000000L).toLong(),
((Math.random() - 0.5) * 0x10000000000000L).toLong() xor ((Math.random() - 0.5) * 2.0 * -0x8000000000000000L).toLong())
}
init {
if (seed == 0L)
throw IllegalArgumentException("Invalid seed: cannot be zero")
/**
* Constructs this XoRoRNG by dispersing the bits of seed using [.setSeed] across the two parts of state
* this has.
* @param seed a long that won't be used exactly, but will affect both components of state
*/
constructor(seed: Long): this() {
setSeed(seed)
}
val hash = DigestUtils.sha256(seed.toString())
/**
* Constructs this XoRoRNG by calling [.setSeed] on the arguments as given; see that method for
* the specific details (stateA and stateB are kept as-is unless they are both 0).
* @param stateA the number to use as the first part of the state; this will be 1 instead if both seeds are 0
* @param stateB the number to use as the second part of the state
*/
constructor(stateA: Long, stateB: Long): this() {
reseed(stateA, stateB)
}
s0 = hash.copyOfRange(0, 8).toLittleLong()
s1 = hash.copyOfRange(8, 16).toLittleLong()
public override fun next(bits: Int): Int {
val s0 = state0
var s1 = state1
val result = (s0 + s1).toInt().ushr(32 - bits)
s1 = s1 xor s0
state0 = s0 shl 55 or s0.ushr(9) xor s1 xor (s1 shl 14) // a, b
state1 = s1 shl 36 or s1.ushr(28) // c
return result
}
override fun nextLong(): Long {
var x = s0
val y = s1
s0 = y
x = x xor (x shl 23)
s1 = x xor y xor (x ushr 17) xor (y ushr 26)
return s1 + y
val s0 = state0
var s1 = state1
val result = s0 + s1
s1 = s1 xor s0
state0 = s0 shl 55 or s0.ushr(9) xor s1 xor (s1 shl 14) // a, b
state1 = s1 shl 36 or s1.ushr(28) // c
/*
state0 = Long.rotateLeft(s0, 55) ^ s1 ^ (s1 << 14); // a, b
state1 = Long.rotateLeft(s1, 36); // c
*/
return result
}
fun serialize() = s0.toLittle() + s1.toLittle()
fun reseed(s0: Long, s1: Long) {
this.s0 = s0
this.s1 = s1
/**
* Exclusive on the outer bound; the inner bound is 0. The bound may be negative, which will produce a non-positive
* result.
* @param bound the outer exclusive bound; may be positive or negative
* @return a random int between 0 (inclusive) and bound (exclusive)
*/
override fun nextInt(bound: Int): Int {
return (bound * nextLong().ushr(33) shr 31).toInt()
}
}
/**
* Inclusive lower, exclusive upper.
* @param inner the inner bound, inclusive, can be positive or negative
* @param outer the outer bound, exclusive, should be positive, should usually be greater than inner
* @return a random int that may be equal to inner and will otherwise be between inner and outer
*/
fun nextInt(inner: Int, outer: Int): Int {
return inner + nextInt(outer - inner)
}
/**
* Exclusive on the outer bound; the inner bound is 0. The bound may be negative, which will produce a non-positive
* result.
* @param bound the outer exclusive bound; may be positive or negative
* @return a random long between 0 (inclusive) and bound (exclusive)
*/
fun nextLong(bound: Long): Long {
var bound = bound
var rand = nextLong()
val randLow = rand and 0xFFFFFFFFL
val boundLow = bound and 0xFFFFFFFFL
rand = rand ushr 32
bound = bound shr 32
val z = randLow * boundLow shr 32
var t = rand * boundLow + z
val tLow = t and 0xFFFFFFFFL
t = t ushr 32
return rand * bound + t + (tLow + randLow * bound shr 32) - (z shr 63) - (bound shr 63)
}
/**
* Inclusive inner, exclusive outer; both inner and outer can be positive or negative.
* @param inner the inner bound, inclusive, can be positive or negative
* @param outer the outer bound, exclusive, can be positive or negative and may be greater than or less than inner
* @return a random long that may be equal to inner and will otherwise be between inner and outer
*/
fun nextLong(inner: Long, outer: Long): Long {
return inner + nextLong(outer - inner)
}
override fun nextDouble(): Double {
return (nextLong() and DOUBLE_MASK) * NORM_53
}
override fun nextFloat(): Float {
return ((nextLong() and FLOAT_MASK) * NORM_24).toFloat()
}
override fun nextBoolean(): Boolean {
return nextLong() < 0L
}
override fun nextBytes(bytes: ByteArray) {
var i = bytes.size
var n = 0
while (i != 0) {
n = Math.min(i, 8)
var bits = nextLong()
while (n-- != 0) {
bytes[--i] = bits.toByte()
bits = bits ushr 8
}
}
}
fun serialize() = state0.toLittle() + state1.toLittle()
/**
* Sets the seed of this generator using one long, running that through LightRNG's algorithm twice to get the state.
* @param seed the number to use as the seed
*/
override fun setSeed(seed: Long) {
var state = seed + -0x61c8864680b583ebL
var z = state
z = (z xor z.ushr(30)) * -0x40a7b892e31b1a47L
z = (z xor z.ushr(27)) * -0x6b2fb644ecceee15L
state0 = z xor z.ushr(31)
state += -0x61c8864680b583ebL
z = state
z = (z xor z.ushr(30)) * -0x40a7b892e31b1a47L
z = (z xor z.ushr(27)) * -0x6b2fb644ecceee15L
state1 = z xor z.ushr(31)
}
/**
* Sets the seed of this generator using two longs, using them without changes unless both are 0 (then it makes the
* state variable corresponding to stateA 1 instead).
* @param stateA the number to use as the first part of the state; this will be 1 instead if both seeds are 0
* @param stateB the number to use as the second part of the state
*/
fun reseed(stateA: Long, stateB: Long) {
state0 = stateA
state1 = stateB
if (stateA or stateB == 0L)
state0 = 1L
}
}*/

View File

@@ -8,7 +8,7 @@ internal interface RNGConsumer {
val RNG: HQRNG
fun loadFromSave(s0: Long, s1: Long) {
RNG.reseed(s0, s1)
RNG.setSeed(s0, s1)
}
}

View File

@@ -491,7 +491,7 @@ open class ActorHumanoid(
private var oldJUMPPOWERBUFF = -1.0 // init
private var oldScale = -1.0
private var oldDragCoefficient = -1.0
val jumpAirTime: Double = -1.0
var jumpAirTime: Double = -1.0
get() {
// compare all the affecting variables
if (oldMAX_JUMP_LENGTH == MAX_JUMP_LENGTH &&

View File

@@ -13,9 +13,10 @@ typealias time_t = Long
* https://en.wikipedia.org/wiki/World_Calendar
* http://dwarffortresswiki.org/index.php/DF2014:Calendar
*
* And there is no AM/PM concept, 22-hour clock is forced; no leap years.
* (AM 12 is still 00h in this system, again, to reduce confusion)
* And there is no AM/PM concept, 24-hour clock is forced; no leap years.
* An ingame day should last 22 real-life minutes.
*
* // TODO 4-month year? like Stardew Valley
*
* Calendar
*
@@ -110,13 +111,13 @@ class WorldTime(initTime: Long = 0L) {
"Mala", "Gale", "Lime", "Sand", "Timb", "Moon")
companion object {
/** Each day is 22-hour long */
val DAY_LENGTH = 79200 //must be the multiple of 3600
/** Each day is displayed as 24 hours, but in real-life clock it's 22 mins long */
val DAY_LENGTH = 86400 //must be the multiple of 3600
val HOUR_SEC: Int = 3600
val MINUTE_SEC: Int = 60
val HOUR_MIN: Int = 60
val GAME_MIN_TO_REAL_SEC: Float = 60f
val GAME_MIN_TO_REAL_SEC: Float = 720f/11f
val HOURS_PER_DAY = DAY_LENGTH / HOUR_SEC
val YEAR_DAYS: Int = 365
@@ -164,8 +165,8 @@ class WorldTime(initTime: Long = 0L) {
val dayName: String
get() = DAY_NAMES[dayOfWeek]
inline fun Long.toPositiveInt() = this.and(0x7FFFFFFF).toInt()
inline fun Long.abs() = Math.abs(this)
fun Long.toPositiveInt() = this.and(0x7FFFFFFF).toInt()
fun Long.abs() = Math.abs(this)
/** Format: "%A, %d %B %Y %X" */
fun getFormattedTime() = "${getDayNameShort()}, " +

View File

@@ -22,6 +22,11 @@ object ReadWorldInfo {
throw IllegalArgumentException("File not a Save Meta")
}
val descVersion = fis.read(1) // 0-127
val numberOfHashes = fis.read() // 0-127
var byteRead = fis.read()
while (byteRead != 0) {
if (byteRead == -1)
@@ -38,14 +43,14 @@ object ReadWorldInfo {
fis.read(8).toLittleLong(), // rng s1
fis.read(8).toLittleLong(), // weather s0
fis.read(8).toLittleLong(), // weather s1
fis.read(32),
fis.read(32),
fis.read(32),
fis.read(4).toLittleInt(), // player id
fis.read(8).toLittleLong(), // world TIME_T
fis.read(6).toLittleLong(), // creation time
fis.read(6).toLittleLong(), // last play time
fis.read(4).toLittleInt() // total time wasted
fis.read(4).toLittleInt(), // total time wasted
fis.read(32), // sha256sum worldinfo1
fis.read(32), // sha256sum worldinfo2
fis.read(32) // sha256sum worldinfo3
)
}
@@ -57,13 +62,13 @@ object ReadWorldInfo {
val rngS1: Long,
val weatherS0: Long,
val weatherS1: Long,
val worldinfo1Hash: ByteArray,
val worldInfo2Hash: ByteArray,
val worldInfo3Hash: ByteArray,
val playerID: Int,
val timeNow: Long,
val creationTime: Long,
val lastPlayTime: Long,
val totalPlayTime: Int
val totalPlayTime: Int,
val worldinfo1Hash: ByteArray,
val worldInfo2Hash: ByteArray,
val worldInfo3Hash: ByteArray
)
}

View File

@@ -18,6 +18,9 @@ object WriteWorldInfo {
val META_MAGIC = "TESV".toByteArray(Charsets.UTF_8)
val NULL = 0.toByte()
val VERSION = 1
val HASHED_FILES_COUNT = 3
/**
* TODO currently it'll dump the temporary file (tmp_worldinfo1) onto the disk and will return the temp file.
*
@@ -38,10 +41,11 @@ object WriteWorldInfo {
val outFiles = ArrayList<File>()
outFiles.add(metaFile)
val worldInfoHash = ArrayList<ByteArray>() // hash of worldinfo1-3
// try to write worldinfo1-3
for (filenum in 1..3) {
for (filenum in 1..HASHED_FILES_COUNT) {
val outFile = File(path + filenum.toString())
if (outFile.exists()) outFile.delete()
outFile.createNewFile()
@@ -65,11 +69,13 @@ object WriteWorldInfo {
}
// compose save meta
// compose save meta (actual writing part)
val metaOut = BufferedOutputStream(FileOutputStream(metaFile), 256)
metaOut.write(META_MAGIC)
metaOut.write(VERSION)
metaOut.write(HASHED_FILES_COUNT)
// world name
val worldNameBytes = world.worldName.toByteArray(Charsets.UTF_8)
@@ -80,28 +86,23 @@ object WriteWorldInfo {
metaOut.write(world.generatorSeed.toLittle())
// randomiser seed
metaOut.write(RoguelikeRandomiser.RNG.s0.toLittle())
metaOut.write(RoguelikeRandomiser.RNG.s1.toLittle())
metaOut.write(RoguelikeRandomiser.RNG.state0.toLittle())
metaOut.write(RoguelikeRandomiser.RNG.state1.toLittle())
// weather seed
metaOut.write(WeatherMixer.RNG.s0.toLittle())
metaOut.write(WeatherMixer.RNG.s1.toLittle())
// SHA256SUM of worldinfo1-3
worldInfoHash.forEach {
metaOut.write(it)
}
metaOut.write(WeatherMixer.RNG.state0.toLittle())
metaOut.write(WeatherMixer.RNG.state1.toLittle())
// reference ID of the player
metaOut.write(Terrarum.PLAYER_REF_ID.toLittle())
// time_t
// ingame time_t
metaOut.write((world as GameWorldExtension).time.TIME_T.toLittle())
// creation time (real world time)
metaOut.write(world.creationTime.toLittle48())
// time at save
// time at save (real world time)
val timeNow = System.currentTimeMillis() / 1000L
metaOut.write(timeNow.toLittle48())
@@ -111,8 +112,14 @@ object WriteWorldInfo {
world.lastPlayTime = timeNow
world.totalPlayTime += timeToAdd
// SHA256SUM of worldinfo1-3
worldInfoHash.forEach {
metaOut.write(it)
}
// more data goes here //
metaOut.flush()
metaOut.close()

View File

@@ -9,7 +9,11 @@ Ord Hex Description
02 4D S
03 44 V
04 Name of the world in UTF-8 (arbitrary length, must not contain NULL)
04 01 Descriptor version number
05 03 Number of hashes
06 Name of the world in UTF-8 (arbitrary length, must not contain NULL)
... 00 String terminator
... Terrain seed (8 bytes)
@@ -18,10 +22,6 @@ Ord Hex Description
... Weather s0 (8 bytes)
... Weather s1 (8 bytes)
... SHA-256 hash of worldinfo1 (32 bytes)
... SHA-256 hash of worldinfo2 (32 bytes)
... SHA-256 hash of worldinfo3 (32 bytes)
... ReferenceID of the player (4 bytes, a fixed value of 91A7E2)
... Current world's time_t (the ingame time, 8 bytes)
@@ -29,7 +29,9 @@ Ord Hex Description
... Last play time in time_t (6 bytes)
... Total playtime in time_t (4 bytes) // will record 136.1 years of playtime
... SHA-256 hash of worldinfo1 (32 bytes)
... SHA-256 hash of worldinfo2 (32 bytes)
... SHA-256 hash of worldinfo3 (32 bytes)