Skip to content

Commit 3640bb5

Browse files
authored
[9.x] Lottery (#44894)
* add lottery helper * code style * refactor * add testing helpers * Support single float value
1 parent 527b288 commit 3640bb5

File tree

2 files changed

+442
-0
lines changed

2 files changed

+442
-0
lines changed

src/Illuminate/Support/Lottery.php

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

0 commit comments

Comments
 (0)