Lottery.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. <?php
  2. namespace Illuminate\Support;
  3. use RuntimeException;
  4. class Lottery
  5. {
  6. /**
  7. * The number of expected wins.
  8. *
  9. * @var int|float
  10. */
  11. protected $chances;
  12. /**
  13. * The number of potential opportunities to win.
  14. *
  15. * @var int|null
  16. */
  17. protected $outOf;
  18. /**
  19. * The winning callback.
  20. *
  21. * @var null|callable
  22. */
  23. protected $winner;
  24. /**
  25. * The losing callback.
  26. *
  27. * @var null|callable
  28. */
  29. protected $loser;
  30. /**
  31. * The factory that should be used to generate results.
  32. *
  33. * @var callable|null
  34. */
  35. protected static $resultFactory;
  36. /**
  37. * Create a new Lottery instance.
  38. *
  39. * @param int|float $chances
  40. * @param int|null $outOf
  41. * @return void
  42. */
  43. public function __construct($chances, $outOf = null)
  44. {
  45. if ($outOf === null && is_float($chances) && $chances > 1) {
  46. throw new RuntimeException('Float must not be greater than 1.');
  47. }
  48. $this->chances = $chances;
  49. $this->outOf = $outOf;
  50. }
  51. /**
  52. * Create a new Lottery instance.
  53. *
  54. * @param int|float $chances
  55. * @param int|null $outOf
  56. * @return static
  57. */
  58. public static function odds($chances, $outOf = null)
  59. {
  60. return new static($chances, $outOf);
  61. }
  62. /**
  63. * Set the winner callback.
  64. *
  65. * @param callable $callback
  66. * @return $this
  67. */
  68. public function winner($callback)
  69. {
  70. $this->winner = $callback;
  71. return $this;
  72. }
  73. /**
  74. * Set the loser callback.
  75. *
  76. * @param callable $callback
  77. * @return $this
  78. */
  79. public function loser($callback)
  80. {
  81. $this->loser = $callback;
  82. return $this;
  83. }
  84. /**
  85. * Run the lottery.
  86. *
  87. * @param mixed ...$args
  88. * @return mixed
  89. */
  90. public function __invoke(...$args)
  91. {
  92. return $this->runCallback(...$args);
  93. }
  94. /**
  95. * Run the lottery.
  96. *
  97. * @param null|int $times
  98. * @return mixed
  99. */
  100. public function choose($times = null)
  101. {
  102. if ($times === null) {
  103. return $this->runCallback();
  104. }
  105. $results = [];
  106. for ($i = 0; $i < $times; $i++) {
  107. $results[] = $this->runCallback();
  108. }
  109. return $results;
  110. }
  111. /**
  112. * Run the winner or loser callback, randomly.
  113. *
  114. * @param mixed ...$args
  115. * @return callable
  116. */
  117. protected function runCallback(...$args)
  118. {
  119. return $this->wins()
  120. ? ($this->winner ?? fn () => true)(...$args)
  121. : ($this->loser ?? fn () => false)(...$args);
  122. }
  123. /**
  124. * Determine if the lottery "wins" or "loses".
  125. *
  126. * @return bool
  127. */
  128. protected function wins()
  129. {
  130. return static::resultFactory()($this->chances, $this->outOf);
  131. }
  132. /**
  133. * The factory that determines the lottery result.
  134. *
  135. * @return callable
  136. */
  137. protected static function resultFactory()
  138. {
  139. return static::$resultFactory ?? fn ($chances, $outOf) => $outOf === null
  140. ? random_int(0, PHP_INT_MAX) / PHP_INT_MAX <= $chances
  141. : random_int(1, $outOf) <= $chances;
  142. }
  143. /**
  144. * Force the lottery to always result in a win.
  145. *
  146. * @param callable|null $callback
  147. * @return void
  148. */
  149. public static function alwaysWin($callback = null)
  150. {
  151. self::setResultFactory(fn () => true);
  152. if ($callback === null) {
  153. return;
  154. }
  155. $callback();
  156. static::determineResultNormally();
  157. }
  158. /**
  159. * Force the lottery to always result in a lose.
  160. *
  161. * @param callable|null $callback
  162. * @return void
  163. */
  164. public static function alwaysLose($callback = null)
  165. {
  166. self::setResultFactory(fn () => false);
  167. if ($callback === null) {
  168. return;
  169. }
  170. $callback();
  171. static::determineResultNormally();
  172. }
  173. /**
  174. * Set the sequence that will be used to determine lottery results.
  175. *
  176. * @param array $sequence
  177. * @param callable|null $whenMissing
  178. * @return void
  179. */
  180. public static function fix($sequence, $whenMissing = null)
  181. {
  182. return static::forceResultWithSequence($sequence, $whenMissing);
  183. }
  184. /**
  185. * Set the sequence that will be used to determine lottery results.
  186. *
  187. * @param array $sequence
  188. * @param callable|null $whenMissing
  189. * @return void
  190. */
  191. public static function forceResultWithSequence($sequence, $whenMissing = null)
  192. {
  193. $next = 0;
  194. $whenMissing ??= function ($chances, $outOf) use (&$next) {
  195. $factoryCache = static::$resultFactory;
  196. static::$resultFactory = null;
  197. $result = static::resultFactory()($chances, $outOf);
  198. static::$resultFactory = $factoryCache;
  199. $next++;
  200. return $result;
  201. };
  202. static::setResultFactory(function ($chances, $outOf) use (&$next, $sequence, $whenMissing) {
  203. if (array_key_exists($next, $sequence)) {
  204. return $sequence[$next++];
  205. }
  206. return $whenMissing($chances, $outOf);
  207. });
  208. }
  209. /**
  210. * Indicate that the lottery results should be determined normally.
  211. *
  212. * @return void
  213. */
  214. public static function determineResultNormally()
  215. {
  216. static::$resultFactory = null;
  217. }
  218. /**
  219. * Set the factory that should be used to determine the lottery results.
  220. *
  221. * @param callable $factory
  222. * @return void
  223. */
  224. public static function setResultFactory($factory)
  225. {
  226. self::$resultFactory = $factory;
  227. }
  228. }