Zum Hauptinhalt springen

Provably Fair – Spielereignisse

Was sind Spielereignisse?

Zuvor haben wir mit den Funktionen generateFloats und byteGenerator zufällige Floats zwischen 0 und 1 generiert. Spielereignisse übersetzen diese generierten Floats in tatsächliche Spielergebnisse. Dies geschieht je nach Spiel auf unterschiedliche Weise. Bei Plinko helfen Floats dabei, die Richtung des Ballfalls zu bestimmen. Bei Dice helfen Floats dabei, das Ergebnis des Würfelwurfs zu bestimmen.

Nachfolgend finden Sie eine detaillierte Erklärung dazu, wie wir Floats für jedes Shuffle Original auf unserer Plattform in faire Ereignisse oder Ergebnisse umwandeln.


Tower

Bei Tower besteht das Ziel darin, die Spitze des Turms zu erreichen und die Prinzessin zu retten, indem Sie die Schlüssel finden und die vergifteten Äpfel vermeiden. Jede Reihe des Turms enthält je nach ausgewähltem Schwierigkeitsgrad eine vordefinierte Anzahl sicherer Felder (Schlüssel) und vergifteter Äpfel.

Das Spielereignis in Tower bestimmt die Position der sicheren Felder (bzw. Schlüssel) in jeder Reihe.

Der Turm hat insgesamt 9 Reihen. Die Anzahl der Spalten sowie die Verteilung von Schlüsseln und vergifteten Äpfeln variieren je nach gewähltem Schwierigkeitsgrad:

  • Einfach: 4 Spalten – 3 Schlüssel und 1 vergifteter Apfel pro Reihe

  • Mittel: 3 Spalten – 2 Schlüssel und 1 vergifteter Apfel pro Reihe

  • Schwer: 2 Spalten – 1 Schlüssel und 1 vergifteter Apfel pro Reihe

  • Experte: 3 Spalten: – 1 Schlüssel und 2 vergiftete Äpfel pro Reihe

  • Meister: 4 Spalten – 1 Schlüssel und 3 vergiftete Äpfel pro Reihe

Das Ergebnis jeder Reihe wird bestimmt, indem auf Grundlage der Schwierigkeitskonfiguration zufällige Positionen für die Schlüssel (sichere Felder) innerhalb der verfügbaren Spalten generiert werden.
​

const TOWER_DIFFICULTY_TO_CONFIG: Record<TowerDifficulty, DifficultyConfig> = {
[TowerDifficulty.EASY]: {
count: 3,
columns: 4,
},
[TowerDifficulty.MEDIUM]: {
count: 2,
columns: 3,
},
[TowerDifficulty.HARD]: {
count: 1,
columns: 2,
},
[TowerDifficulty.EXPERT]: {
count: 1,
columns: 3,
},
[TowerDifficulty.MASTER]: {
count: 1,
columns: 4,
},
};


// Generate floats to determine the positions of safe tiles (keys) for each row
const floats = generateFloats({
serverSeed,
clientSeed,
nonce,
cursor,
count,
});

// Get the tower configuration based on the selected difficulty
const towerConfig = TOWER_DIFFICULTY_TO_CONFIG[difficulty];
const results: number[][] = [];
const resultPerRow = towerConfig.count;

// Iterate through each row to determine the positions of safe tiles for that row
for (let i = 0; i < TOWER_TOTAL_ROWS.toNumber(); i++) {
const result: number[] = [];
const values = Array.from(Array(towerConfig.columns).keys());

// Get positions for safe tiles in the current row
for (let j = 0; j < resultPerRow; j++) {
const float = floats[i * resultPerRow + j];
if (float === undefined) {
break;
}

const position = Math.floor(float * (towerConfig.columns - j));
result.push(values.splice(position, 1)[0] ?? 0);
}

results.push(result);
}

return results;

// Example: when clientSeed='abc', serverSeed='def', nonce=1, difficulty='easy'
// results: [
// [0, 1, 2],
// [0, 2, 3],
// [0, 2, 3],
// [0, 1, 2],
// [0, 1, 3],
// [0, 1, 2],
// [1, 2, 3],
// [0, 1, 3],
// [0, 1, 3]
// ]
// Each sub-array represents the column indexes of safe tiles for that row
// (e.g., for the first row, safe tiles are in columns 0, 1, and 2)

Chicken

Bei Chicken besteht das Ziel darin, das Huhn über möglichst viele Bahnen zu bewegen, ohne auf der Gefahrenbahn zu landen.

Jeder erfolgreiche Schritt nach vorn erhöht den Auszahlungs-Multiplikator. Sie können jederzeit auszahlen lassen, bevor das Huhn auf die Gefahrenbahn springt. Erreicht das Huhn die Gefahrenzone, bevor Sie auszahlen lassen, ist der Einsatz verloren.

Das zentrale Spielereignis in Chicken ist die Bestimmung der Gefahrenbahn – der konkreten Bahn, auf der das Huhn verliert.

Chicken hat 4 Schwierigkeitsgrade. Die Anzahl der Bahnen, die das Huhn überqueren kann, hängt vom ausgewählten Schwierigkeitsgrad ab:

  • Einfach: 19 Bahnen

  • Mittel: 17 Bahnen

  • Schwer: 15 Bahnen

  • Experte: 10 Bahnen

const TOTAL_LANES = 20

// Number of lanes that the chicken can cross for each difficulty
const DIFFICULTY_TO_VALID_LANE_COUNT: Record<ChickenDifficulty, number> = {
[ChickenDifficulty.EASY]: 19,
[ChickenDifficulty.MEDIUM]: 17,
[ChickenDifficulty.HARD]: 15,
[ChickenDifficulty.EXPERT]: 10,
};

// Number of gameResults generated depends on the difficulty
// Example with Hard difficulty, we generate 20 - 15 = 5 numbers
// and the smallest number in the array is the danger lane
const gameResults = generateFloats({
serverSeed,
clientSeed,
nonce,
cursor: 0,
count: TOTAL_LANES - DIFFICULTY_TO_VALID_LANE_COUNT[difficulty] // difficulty selected for the game
}).map((val, index) => Math.floor(val * (TOTAL_LANES - index)));

// Danger lane is determined by the smallest number in the game results array
const dangerLane = Math.min(...gameResults)

// Example: Hard difficulty - gameResults = [10, 4, 6, 15, 8]
// Danger lane will be on 4 (this is zero based index)
// Lane 0 - 3 are safe and lane 4 will be the danger lane
// The chicken will lose upon reaching lane 4 if not cashed out earlier

Plinko

Bei Plinko können Spieler zwischen einer Pyramide mit einer Höhe von 8 bis 16 wählen. Wenn der Ball oben losgelassen wird, fällt er bei jedem Auftreffen auf einen Stift auf dem Plinko-Brett 8 bis 16 Mal entweder nach links oder rechts. Das Spielereignis in Plinko bestimmt, ob der Ball auf jeder Ebene nach links oder rechts fällt, dargestellt durch 0 und 1.

// Create multiple floats between 0 and 1 
// Example: floats = [0.8320531346835196, 0.8531235849950463, 0.18227359023876488, 0.774284637067467, 0.18627392360940576, 0.6012540922965854, 0.608081541955471, 0.46018488449044526] when clientSeed='abc', serverSeed='def', nonce=1, count=8
// count refers to the number of plinko rows
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor, count });

// Example: [1, 1, 0, 1, 0, 1, 1, 0] Each 1 represents a drop to the right whereas 0 represents a drop to the left
const dropDirections = floats.map(val => Math.floor(val * 2));

// This sums up all the values in the array to get the final position of the ball from the left.

// Example: finalPositionFromLeft = 5 (the ball will land on the 6th slot from the left)
const finalPositionFromLeft = dropDirections.reduce((cur, acc) => cur + acc, 0)

Dice

Bei Dice kann ein Würfelwurf einen Wert zwischen 0.00 und 100.00 haben. Das Spielereignis in Dice bestimmt diesen Wert. Hierfür verwenden wir (floats * 10,001) / 100). Da wir Werte bis zu 2 Dezimalstellen sowie die Zahl 0 einbeziehen, gibt es insgesamt 10,000 + 1 = 10,001 mögliche Ergebnisse (einschließlich Dezimalstellen).

// Creates an array of random numbers 
// Example: With clientSeed = 'abc', serverSeed = 'def', nonce = 1, floats = [0.43053962965495884]
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: 1 });

// Converts the value in the array to a number between 0.00 to 100.00 and return the value

// Example: resultValue = [43.05]
const resultValue = floats.map(val => Math.floor(floats * 10001) / 100);
return resultValue[0];
// Example: 43.05 is returned as the result of the game

Limbo

Bei Limbo besteht das Ziel darin, einen Multiplikator >1 zu erraten. Ist das Spielergebnis höher als der Multiplikator, gewinnt der Nutzer das Spiel. Das Spielereignis generiert einen Float-Wert, der über 1 liegt und einer hyperbolischen Kurvenverteilung folgt.

// Generate float between 0 and 1 
// Example: floats = [0.43053962965495884], float = 0.43053962965495884,
// when clientSeed='abc', serverSeed='def', nonce=1
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: 1 });
const float = floats[0]
// generate limbo game event results with house edge (1%)
// using a hyperbolic 1/x curve
// multiplying by this and dividing by (MAX_VALUE + 1) prevents division by 0 error

const MAX_VALUE = 2 ** 24;
const rtp = 0.99;

// Example: rawValue = 2.298905
const rawValue = (rtp * MAX_VALUE) / (MAX_VALUE * float + 1)

// resultValue = 2.29
const resultValue = (0.99 * MAX_VALUE / (MAX_VALUE * float +1)).toFixed(2,ROUND_DOWN);
return resultValue;

Keno

Bei Keno erfordert das Spielereignis die Bestimmung von 10 eindeutigen Edelsteinpositionen auf dem Keno-Brett. Dazu generieren wir 10 verschiedene Floats zwischen 1 und 40., die jeweils eine Position auf dem Keno-Brett darstellen. Anschließend wird ein Verfahren verwendet, um doppelte Floats zu behandeln und die endgültigen 10 Edelsteinpositionen zurückzugeben.

// Generate 10 different floats as there are 10 positions that are selected when the game is played 
// Example: floats = [18, 26, 7, 22, 31, 10, 23, 8, 24, 13] when clientSeed='abc', serverSeed='def', nonce=1

const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: 10 }) .map((val, index) => Math.ceil(val * (40 - index)))

// array used to store the position of the gems const gemLocations: number[] = [];

// this loop helps to prevent duplicate gem positions by skipping positions that already contain a gem
for (const val of floats) { let currentGem = val;
let i = 0;
while (i <= currentGem) {
if (gemLocations.includes(i)) {
currentGem++;
}
i++;
}

gemLocations.push(currentGem);
}

// returns the location of the gem on the board
// Example: [18, 27, 7, 24, 35, 11, 28, 9, 31, 16] return gemLocations;

Mines

Bei Mines erfordert das Spielereignis die Bestimmung der Minenpositionen auf dem aus 25 Feldern bestehenden Mines-Brett. Basierend auf der vom Nutzer ausgewählten Anzahl an Minen generieren wir eine Liste von Ganzzahlen von 0 bis 24, um die 25 Felder auf dem Brett darzustellen.

// Generate 10 different floats 
// Example: [20, 20, 4] when clientSeed='abc', serverSeed='def', nonce=1, numberOfBombs=3
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: numberOfBombs })
.map((val, index) => Math.floor(val * (25 - index)));

// array used to store the position of the bombs
const mineLocations: number[] = [];

// this loop helps to prevent duplicate bomb positions by skipping positions that already contain a mine
for (const val of floats) {
let currentMine = val;
let i = 0;

while (i <= currentMine) {
if (mineLocations.includes(i)) {
currentMine++;
} i++; } mineLocations.push(currentMine); }

// returns the location of the bombs on the board
// Example: [20, 21, 4]
return mineLocations;

Roulette

Unser Roulette basiert auf dem europäischen Einzel-„0“-Rad mit 37 verschiedenen Taschen von 0 bis 36. Das Spielereignis beim Roulette berechnet, in welcher der 37 Taschen der Ball landet.

// Generate a float between 0 and 1 
// Example: floats = [0.43053962965495884]
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor: 0, count: 1 });

// generate an integer from 0 to 37 to represent the pocket to land on
// Example: resultValue = 15
const resultValue = floats.map(val => Math.floor(val * 37))[0];
return resultValue

Wheel

Bei Wheel wählt ein Nutzer die Anzahl der Segmente des Rads aus. Je nachdem, auf welchem Segment der Zeiger stehen bleibt, gewinnt der Nutzer den Multiplikator dieses Segments. Das Spielereignis bestimmt das Segment.

// Generate a float between 0 and 1 
// Example: floats = [0.43053962965495884]
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:1 });

// Get an integer representing the number of segments set in the game
// Example: resultSegment = 8
const resultSegment = floats.map(val => Math.floor(val * numberOfSegments))[0];

return resultSegment

Blackjack

Bei Blackjack wird mit einem unbegrenzten Kartendeck gespielt. Daher hat jede Runde immer dieselbe Wahrscheinlichkeit. Das Spielereignis in Blackjack bestimmt die gezogene Karte, wenn der Spieler oder der Banker eine Karte vom Deck zieht.

// Generate a float between 0 and 1 
// Example: floats = [0.43053962965495884, 0.6645898742135614, 0.17454026662744582, 0.5729992990382016, 0.850033157505095] (52 elements are generated but only 5 are shown here for brevity)
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:52 });

const CARDS = [ ♦2, ♥2, ♠2, ♣2, ♦3, ♥3, ♠3, ♣3, ♦4, ♥4, ♠4, ♣4, ♦5, ♥5, ♠5, ♣5, ♦6, ♥6, ♠6, ♣6, ♦7, ♥7, ♠7, ♣7, ♦8, ♥8, ♠8, ♣8, ♦9, ♥9, ♠9, ♣9, ♦10, ♥10, ♠10, ♣10, ♦J, ♥J, ♠J, ♣J, ♦Q, ♥Q, ♠Q, ♣Q, ♦K, ♥K, ♠K, ♣K, ♦A, ♥A, ♠A, ♣A
];

// Get an integer 0 to 51, representing the index of the cards
// Example: cardFloats = [22, 34, 9, 29, 44]
const cardFloats = floats.map(val => Math.floor(val * 52));

// Example: cards = [♠7, ♠10, ♥4, ♥9, ♦K]
const cards = cardFloats.map(val => CARDS[val])
return cards;

Hilo

Bei Hilo wird mit einem unbegrenzten Kartendeck gespielt. Daher hat jede Runde immer dieselbe Wahrscheinlichkeit. Ziel des Spiels ist es, zu erraten, ob die nächste Karte einen höheren oder niedrigeren Wert als die aktuelle Karte hat. Das Spielereignis in Hilo bestimmt, welche Karte ein Nutzer als Nächstes zieht (falls er zieht).

// Generate a float between 0 and 1 
// Example: floats = [0.43053962965495884, 0.6645898742135614, 0.17454026662744582, 0.5729992990382016, 0.850033157505095] (an unlimited number of cards can be drawn but only 5 are shown here for brevity)
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:52 });

const CARDS = [
♦2, ♥2, ♠2, ♣2, ♦3, ♥3, ♠3, ♣3, ♦4, ♥4, ♠4, ♣4, ♦5, ♥5, ♠5, ♣5, ♦6, ♥6, ♠6, ♣6, ♦7, ♥7, ♠7, ♣7, ♦8, ♥8, ♠8, ♣8, ♦9, ♥9, ♠9, ♣9, ♦10, ♥10, ♠10, ♣10, ♦J, ♥J, ♠J, ♣J, ♦Q, ♥Q, ♠Q, ♣Q, ♦K, ♥K, ♠K, ♣K, ♦A, ♥A, ♠A, ♣A
];

// Get an integer 0 to 51, representing the index of the cards
// Example: cardFloats = [22, 34, 9, 29, 44]
const cardFloats = floats.map(val => Math.floor(val * 52));


// Example: cards = [♠7, ♠10, ♥4, ♥9, ♦K]
const cards = cardFloats.map(val => CARDS[val])
return cards;

Crash

Die Berechnung des Ergebnisses eines Crash-Spiels besteht aus 2 Teilen

  • Bitcoin-Block-Hash

  • Seed

Bitcoin-Hash

Details finden Sie in diesem Tweet:

Der Hash von Block 779588 ist im BTC-Block-Explorer zu finden (https://btcscan.org/block/000000000000000000033ab71b5fa7edcafe388e5f49719f1de8e1f8d0670460) und stimmt mit dem im Tweet angezeigten Hash überein.

Seed

Der Prozess zur Generierung von Seeds für 10 Millionen Crash-Spiele soll Fairness und Transparenz gewährleisten und jede Manipulationsmöglichkeit durch Shuffle verhindern. So werden Seeds generiert und verifiziert:

  1. Generierung des initialen Seeds: Der Seed für das 10. millionste Spiel wird zufällig von Shuffle ausgewählt.

  2. Sequenzielles Hashing zur Seed-Generierung: Um die Seeds für alle anderen Spiele zu generieren, verwenden wir eine rückwärtsgerichtete Hashing-Methode:

    • Der Seed für jedes Spiel ist das Ergebnis des Hashings des Seeds des nachfolgenden Spiels. Konkret ist der Hash für das 9,999,999. Spiel der SHA256-Hash des Hashes des 10. millionsten Spiels.

    • Diese Verkettung wird fortgesetzt, sodass der Seed jedes Spiels durch Hashing des Hashes des nächsten Spiels abgeleitet wird. Letztlich wird der Seed des ersten Spiels aus dem Hash des zweiten Spiels abgeleitet.

  3. Verifizierung der Seed-Integrität:

    • Durch die Veröffentlichung des gehashten Werts des im ersten Spiel verwendeten Hashes können Spieler die Integrität des Hashes für alle Spiele bis zum 10. millionsten Spiel unabhängig verifizieren.

    • Mit dieser Methode können Sie, wenn Sie den Hash eines bestimmten Spiels kennen, die Seeds aller vorherigen Spiele verifizieren. Wenn Sie beispielsweise den Seed von Spiel 1323185 kennen, können Sie diesen Seed hashen, um zu verifizieren, dass er mit dem Seed von Spiel 1323184 übereinstimmt, und so weiter bis zurück zum ersten Spiel.

  4. Transparenz und Sicherheit:

    • Durch die Verwendung eines zukünftigen BTC-Block-Hashes, der außerhalb der Kontrolle von Shuffle liegt, sowie die Veröffentlichung der gehashten Version des Seeds des ersten Spiels können Nutzer nachweisen, dass die Ergebnisse des Crash-Spiels fair und unverändert sind.

Slide

Slide verwendet denselben provably fair und denselben Seed-Prozess wie Crash. Die Seed-Erstellung wurde öffentlich durchgeführt auf x.com, und der Beitrag ist hier.

Der Hash von Block 952294 ist im BTC-Block-Explorer zu finden (https://btcscan.org/block/00000000000000000000a382760ff19cdb7f9f22147b9123f3e471fb3c6d129a) und stimmt mit dem im Tweet angezeigten Hash überein.

Baccarat

Bei Baccarat wird mit einem unbegrenzten Kartendeck gespielt. Daher hat jede Runde immer dieselbe Wahrscheinlichkeit. Das Spielereignis in Baccarat bestimmt die Karten, die dem Spieler- und Bankerblatt bei der Anfangsausteilung sowie bei allen zusätzlichen Karten gemäß den Standardregeln für Baccarat ausgeteilt werden.

Die Karten werden in der folgenden Reihenfolge aus der generierten Float-Sequenz ausgeteilt:

  1. Erste Karte des Spielers – Float-Index 0

  2. Erste Karte des Bankers – Float-Index 1

  3. Zweite Karte des Spielers – Float-Index 2

  4. Zweite Karte des Bankers – Float-Index 3

  5. Dritte Karte des Spielers (falls gezogen) – Float-Index 4

  6. Dritte Karte des Bankers (falls gezogen)

    1. Float-Index 4 – wenn der Spieler keine dritte Karte zieht

    2. Float-Index 5 – wenn der Spieler eine dritte Karte zieht

// Generate a float between 0 and 1
// Example: floats = [0.40353962965495884, 0.6645898742135614, 0.17454026662744582, 0.5729992990382016, 0.850033157505095] (an unlimited number of cards can be drawn but only 5 are shown here for brevity)
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:52 });

const CARDS = [
♠2, ♥2, ♦2, ♣2, ♠3, ♥3, ♦3, ♣3, ♠4, ♥4,
♦4, ♣4, ♠5, ♥5, ♦5, ♣5, ♠6, ♥6, ♦6, ♣6,
♠7, ♥7, ♦7, ♣7, ♠8, ♥8, ♦8, ♣8, ♠9, ♥9,
♦9, ♣9, ♠10, ♥10, ♦10, ♣10, ♠J, ♥J, ♦J,
♣J, ♠Q, ♥Q, ♦Q, ♣Q, ♠K, ♥K, ♦K, ♣K, ♠A,
♥A, ♦A, ♣A
];

// Get an integer 0 to 51, representing the index of the cards
// Example: cardFloats = [22, 34, 9, 29, 44]
const cardFloats = floats.map(val => Math.floor(val * 52));

// Example: cards = [♠7, ♠10, ♥4, ♠9, ♦K]
const cards = cardFloats.map(val => CARDS[val])
return cards;

Blitz

Bei Blitz wird mit einem unbegrenzten Kartendeck gespielt. Daher hat jede Runde immer dieselbe Wahrscheinlichkeit. Das Spielereignis in Blitz bestimmt die Karten, die dem Spieler entsprechend der gewählten Anzahl an Ziehungen ausgeteilt werden, und bewertet, ob alle gezogenen Karten eindeutig sind.

// Generate a float between 0 and 1
// Example: floats = [0.43053962965495884, 0.6645898742135614, 0.17454026662744582, 0.5729992990382016, 0.850033157505095] (an unlimited number of cards can be drawn but only 5 are shown here for brevity)
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:52 });

const CARDS = [
♠2, ♥2, ♣2, ♦2, ♠3, ♥3, ♣3, ♦3, ♠4, ♥4,
♣4, ♦4, ♠5, ♥5, ♣5, ♦5, ♠6, ♥6, ♣6, ♦6,
♠7, ♥7, ♣7, ♦7, ♠8, ♥8, ♣8, ♦8, ♠9, ♥9,
♣9, ♦9, ♠10, ♥10, ♣10, ♦10, ♠J, ♥J, ♣J, ♦J,
♠Q, ♥Q, ♣Q, ♦Q, ♠K, ♥K, ♣K, ♦K, ♠A,
♥A, ♣A, ♦A
];

// Get an integer 0 to 51, representing the index of the cards
// Example: cardFloats = [22, 34, 9, 29, 44]
const cardFloats = floats.map(val => Math.floor(val * 52));

// Example: cards = [♠7, ♠10, ♥4, ♠9, ♦K]
const cards = cardFloats.map(val => CARDS[val])
return cards;

Coinflip

Bei Coinflip besteht das Ziel darin, die Seite richtig vorherzusagen, auf der eine geworfene Münze landet. Das Spielereignis in Coinflip bestimmt das Ergebnis (KOPF oder ZAHL) jedes Münzwurfs. Coinflip hat drei Modi – Klassisch (ein einzelner Wurf), Klassisch Progressiv (wiederholt werfen und jederzeit auszahlen lassen) und Ziel (Anzahl der Würfe und die zum Gewinnen erforderliche Mindestanzahl richtiger Vorhersagen wählen) – doch alle drei leiten die Wurfergebnisse auf dieselbe Weise ab.

Jeder Wurf wird bestimmt, indem ein Float generiert und genau wie bei Plinko, wo Floats den Links-/Rechts-Fällen zugeordnet werden, 0 oder 1 zugeordnet wird:

// Generate floats to determine each flip outcome
// Example: floats = [0.43053962965495884, 0.6645898742135614, ...] when clientSeed='abc', serverSeed='def', nonce=1
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor, count });

// Each float maps to 0 or 1, which maps to HEADS or TAILS
// Example: [0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]
const flipValues = floats.map(val => Math.floor(val * 2));
const flipResults = flipValues.map(val => (val === 0 ? 'HEADS' : 'TAILS'));


// Example: [HEADS, TAILS, HEADS, TAILS, TAILS, HEADS, TAILS, HEADS, TAILS, HEADS,
// TAILS, HEADS, TAILS, HEADS, HEADS, TAILS, HEADS, HEADS, HEADS, TAILS]
return flipResults;


Klassisch – ein Wurf wird generiert (Anzahl = 1). Der Spieler gewinnt, wenn seine ausgewählte Seite dem Wurfergebnis entspricht.

Klassisch Progressiv – die Würfe werden einzeln generiert, während der Spieler fortfährt, doch das Ergebnis jedes Wurfs steht seit Beginn der Runde fest: Der N-te Wurf wird immer abgeleitet, indem die Würfe 1 bis N generiert und der letzte verwendet wird. Daher ändert das schrittweise Aufdecken der Würfe während des Fortschritts des Spielers nicht, wie ein bestimmter Wurf ausgefallen wäre. Der Spieler kann nach jedem erfolgreichen Wurf auszahlen lassen; die Runde endet beim ersten Fehlversuch.

Ziel – der Spieler wählt eine Gesamtanzahl an Würfen (bis zu 20) und eine zum Gewinnen erforderliche Mindestanzahl richtiger Vorhersagen. Alle Würfe werden als einzelner Batch generiert (Anzahl = totalFlips), und die Runde wird gewonnen, wenn die Anzahl der Würfe, die der Vorhersage des Spielers entsprechen, mindestens dem gewählten Minimum entspricht.

Floor is Lava

Ziel: Überqueren Sie ein 7×7-Raster (49 Felder) über drei aufeinanderfolgende Level, ohne ein Feld zu betreten, das zu Lava wird.

Jedes Level wird in „Runden“ fester Größe gespielt – in jeder Runde fällt ein Batch von Feldern gleichzeitig in die Lava – und Sie können nach jeder überlebten Runde auszahlen lassen.

Generierung des Spielfelds

Bevor die Runde beginnt, wird das Feldlayout für alle drei Level in einer einzigen nachweislich fairen shuffle gezogen: Eine zufällige Permutation der Ganzzahlen 0..146 (49 Felder × 3 Level) wird ohne Zurücklegen aus dem Seed-Generator gezogen und anschließend nach Level aufgeteilt:

// Draw a random permutation of 0..146 without replacement 
// Example: raw = [103, 12, 140, 76, 3, ...]
const boards = [[], [], []];
for (const value of raw) {
boards[Math.floor(value / 49)].push(value % 49); // bucket = level, residue = tile index }
// boards[0], boards[1], boards[2] are each a uniformly random ordering of tiles 0..48

Das Ziehen einer 147-Werte-Permutation anstelle von drei separaten 49-Werte-shuffles ist lediglich ein Effizienztrick – da genau 49 der 147 Werte in den Bereich jedes Levels fallen, erhält jedes Level weiterhin eine unabhängige, gleichmäßig zufällige Reihenfolge seiner eigenen 49 Felder.

Runden: So fällt die Lava

Die 49-Felder-Reihenfolge jedes Levels wird in aufeinanderfolgende, nicht überlappende Abschnitte unterteilt: eine feste Anzahl von Feldern pro Runde (dropsPerRound), für eine feste Anzahl an Runden (roundsPerLevel), wobei die am Ende verbleibenden Felder das gesamte Level überleben:

tilesDroppedInRound(level, dropsPerRound, roundIndex) = levelBoard.slice(roundIndex * dropsPerRound, (roundIndex + 1) * dropsPerRound) 

tilesThatSurviveLevel(level, survivingCount) = levelBoard.slice(-survivingCount)

Da die shuffle die Reihenfolge der Felder bereits im Voraus festgelegt hat, wird die Lava-Menge einer Runde nicht als Reaktion auf Ihre Auswahl bestimmt – sie ist der Abschnitt der vor-shuffled Liste, der in dieser Runde „an der Reihe“ ist.

Bei jeder Auswahl bestimmen Sie ein Feld (selectedTile). Dieses Feld darf nicht bereits zu einem Lava-Batch einer früheren Runde in diesem Level gehören. Die Runde wird dann abgeschlossen, indem Ihr einzelnes Feld mit dem gesamten Lava-Batch der Runde verglichen wird (selectedTile):

isLoss = droppedTiles.includes(selectedTile) 

Wenn Ihr Feld zu den in dieser Runde herabgefallenen Feldern gehört, endet der Durchlauf. Andernfalls haben Sie die Runde überlebt – unabhängig davon, wie viele andere Felder gleichzeitig gefallen sind.

Schwierigkeitsgrad Konfiguration

Vier Schwierigkeitsgrade tauschen Rundengröße gegen Rundenzahl. Jeder umfasst weiterhin das vollständige Spielfeld mit 49 Feldern (dropsPerRound × roundsPerLevel + survivingTiles = 49):

Schwierigkeitsgrad

Felder pro Runde

Runden pro Level

Überlebende Felder

Einfach

7

6

7

Mittel

11

4

5

Schwer

23

2

3

Giftig

48

1

1

Level- und Spielfortschritt

Ein Spiel hat 3 Level, jeweils mit einem eigenen, unabhängig-shuffled 49-Kachel-Spielfeld. Wenn Sie alle Runden eines Levels überstehen, schließen Sie es ab und wechseln in Runde eins zum Spielfeld des nächsten Levels; wenn Sie das dritte Level abschließen, ist das Spiel beendet.

Auszahlung

Sie können nach jeder überlebten Runde auszahlen lassen – bereits Ihre erste erfolgreiche Auswahl schaltet diese Möglichkeit frei.

Multiplikator

multiplier = edgeMultiplier * 49^(levelsCleared+1) / (tilesRemainingInLevel * survivingTiles^levelsCleared) 

levelsCleared ist die Anzahl der bisher vollständig abgeschlossenen Level, tilesRemainingInLevel verringert sich jedes Mal um die Fallanzahl der Runde, wenn Sie eine Runde innerhalb des aktuellen Levels überleben, und survivingTiles ist die Anzahl der Überlebenden pro Level des Schwierigkeitsgrads aus der obigen Tabelle.

Dies ist ein Preis mit fairen Quoten: der Kehrwert der kumulativen Wahrscheinlichkeit, diesen exakten Punkt (abgeschlossene Level × überlebte Runden) bei gleichmäßig zufälliger Platzierung der Felder erreicht zu haben, skaliert um den Hausvorteil.

Hat dies deine Frage beantwortet?