1) { throw new RuntimeException('Float must not be greater than 1.'); } $this->chances = $chances; $this->outOf = $outOf; } /** * Create a new Lottery instance. * * @param int|float $chances * @param ?int $outOf * @return static */ public static function odds($chances, $outOf = null) { return new static($chances, $outOf); } /** * Set the winner callback. * * @param callable $callback * @return $this */ public function winner($callback) { $this->winner = $callback; return $this; } /** * Set the loser callback. * * @param callable $callback * @return $this */ public function loser($callback) { $this->loser = $callback; return $this; } /** * Run the lottery. * * @param mixed ...$args * @return mixed */ public function __invoke(...$args) { return $this->runCallback(...$args); } /** * Run the lottery. * * @param null|int $times * @return mixed */ public function choose($times = null) { if ($times === null) { return $this->runCallback(); } $results = []; for ($i = 0; $i < $times; $i++) { $results[] = $this->runCallback(); } return $results; } /** * Run the winner or loser callback, randomly. * * @param mixed ...$args * @return callable */ protected function runCallback(...$args) { return $this->wins() ? ($this->winner ?? fn () => true)(...$args) : ($this->loser ?? fn () => false)(...$args); } /** * Determine if the lottery "wins" or "loses". * * @return bool */ protected function wins() { return static::resultFactory()($this->chances, $this->outOf); } /** * The factory that determines the lottery result. * * @return callable */ protected static function resultFactory() { return static::$resultFactory ?? fn ($chances, $outOf) => $outOf === null ? random_int(0, PHP_INT_MAX) / PHP_INT_MAX <= $chances : random_int(1, $outOf) <= $chances; } /** * Force the lottery to always result in a win. * * @param callable|null $callback * @return void */ public static function alwaysWin($callback = null) { self::setResultFactory(fn () => true); if ($callback === null) { return; } $callback(); static::determineResultNormally(); } /** * Force the lottery to always result in a lose. * * @param callable|null $callback * @return void */ public static function alwaysLose($callback = null) { self::setResultFactory(fn () => false); if ($callback === null) { return; } $callback(); static::determineResultNormally(); } /** * Set the sequence that will be used to determine lottery results. * * @param array $sequence * @param callable|null $whenMissing * @return void */ public static function fix($sequence, $whenMissing = null) { return static::forceResultWithSequence($sequence, $whenMissing); } /** * Set the sequence that will be used to determine lottery results. * * @param array $sequence * @param callable|null $whenMissing * @return void */ public static function forceResultWithSequence($sequence, $whenMissing = null) { $next = 0; $whenMissing ??= function ($chances, $outOf) use (&$next) { $factoryCache = static::$resultFactory; static::$resultFactory = null; $result = static::resultFactory()($chances, $outOf); static::$resultFactory = $factoryCache; $next++; return $result; }; static::setResultFactory(function ($chances, $outOf) use (&$next, $sequence, $whenMissing) { if (array_key_exists($next, $sequence)) { return $sequence[$next++]; } return $whenMissing($chances, $outOf); }); } /** * Indicate that the lottery results should be determined normally. * * @return void */ public static function determineResultNormally() { static::$resultFactory = null; } /** * Set the factory that should be used to deterine the lottery results. * * @param callable $factory * @return void */ public static function setResultFactory($factory) { self::$resultFactory = $factory; } }