diff --git a/app/Models/Participant.php b/app/Models/Participant.php index 99e7967..803bd79 100644 --- a/app/Models/Participant.php +++ b/app/Models/Participant.php @@ -23,12 +23,12 @@ class Participant extends Model 'desperate' => 'boolean', ]; - public function giver() + public function receiver() { return $this->belongsTo(Participant::class, 'giving_id'); } - public function receiver() + public function giver() { return $this->hasOne(Participant::class, 'giving_id', 'id'); } diff --git a/app/Services/MatcherService.php b/app/Services/MatcherService.php new file mode 100644 index 0000000..9ebd6f9 --- /dev/null +++ b/app/Services/MatcherService.php @@ -0,0 +1,105 @@ +count() < 2) { + return; + } + + // desperate denotes a user A that is supposed to give to a user B that dropped out + // whilst user A has a user C giving to them, or vice versa + $desperate = $participants->where('desperate', true)->values(); + $nonDesperate = $participants->where('desperate', false)->values(); + + $desperate = $desperate->shuffle(); + $nonDesperate = $nonDesperate->shuffle(); + + $assignments = []; + + /* + |-------------------------------------------------------------------------- + | 1. Match DESPERATE participants + |-------------------------------------------------------------------------- + | A desperate user should match: + | - First to another desperate participant + | - If none left, match to a non-desperate participant + |-------------------------------------------------------------------------- + */ + + foreach ($desperate as $p) { + // Exclude self from possible options + $pool = $desperate->filter(fn($d) => $d->id !== $p->id); + + if ($pool->isEmpty()) { + $pool = $nonDesperate; + } + + if ($pool->isEmpty()) { + continue; + } + + $candidate = $this->pickValidTarget($p, $pool, $assignments); + if ($candidate) { + $assignments[$p->id] = $candidate->id; + } + } + + /* + |-------------------------------------------------------------------------- + | 2. Match NON-DESPERATE participants + |-------------------------------------------------------------------------- + | A non-desperate participant always matches to the non-desperate pool. + |-------------------------------------------------------------------------- + */ + + foreach ($nonDesperate as $p) { + // Exclude self + $pool = $nonDesperate->filter(fn($d) => $d->id !== $p->id); + + if ($pool->isEmpty()) { + continue; + } + + $candidate = $this->pickValidTarget($p, $pool, $assignments); + if ($candidate) { + $assignments[$p->id] = $candidate->id; + } + } + + /* + |-------------------------------------------------------------------------- + | 3. Write all results back to DB + |-------------------------------------------------------------------------- + */ + + foreach ($assignments as $id => $givingId) { + Participant::where('id', $id)->update([ + 'giving_id' => $givingId, + ]); + } + } + + private function pickValidTarget(Participant $giver, Collection $pool, array $assignments): ?Participant + { + $shuffled = $pool->shuffle(); + + // Avoid duplicates + foreach ($shuffled as $candidate) { + if (($assignments[$candidate->id] ?? null) === $giver->id) { + continue; + } + + return $candidate; + } + + return null; + } +} diff --git a/resources/views/livewire/pages/profile.blade.php b/resources/views/livewire/pages/profile.blade.php index 0ca48ba..58308e8 100644 --- a/resources/views/livewire/pages/profile.blade.php +++ b/resources/views/livewire/pages/profile.blade.php @@ -8,6 +8,8 @@ state(['token'])->url(); state([ 'participant' => null, 'userData' => null, + 'receiverPrompt' => null, + 'receiverUserData' => null, 'prompt' => '', 'isEditing' => false, ]); @@ -23,6 +25,10 @@ mount(function () { if ($this->participant) { $this->userData = $this->participant->getUserData(); $this->prompt = $this->participant->prompt ?? ''; + if($this->participant->receiver) { + $this->receiverPrompt = $this->participant->receiver->prompt; + $this->receiverUserData = $this->participant->receiver->getUserData(); + } } } }); @@ -82,6 +88,11 @@ $cancel = function () { @if($participant->giving_id) You are giving to
{{ $participant->giving_id }}
+ @if($receiverUserData) +You're in a bind!
@else diff --git a/resources/views/livewire/pages/table.blade.php b/resources/views/livewire/pages/table.blade.php index 91ff41d..cb2226e 100644 --- a/resources/views/livewire/pages/table.blade.php +++ b/resources/views/livewire/pages/table.blade.php @@ -1,8 +1,9 @@ Collection::class]); @@ -10,14 +11,47 @@ mount(function () { $this->participants = Participant::all(); }); +$runMatch = function () { + $matcher = app(MatcherService::class); + $matcher->match(); + + session()->flash('match-message',"Done"); + + $this->participants = Participant::all(); +}; ?>+ Displaying all records from the `participants` table. +
+ + @if (session()->has('match-message')) +Success!
+{{ session('match-message') }}
+