From a930d0f972d95a95df2f4566897396dc6c7340a7 Mon Sep 17 00:00:00 2001 From: red Date: Thu, 4 Dec 2025 19:00:42 +0000 Subject: [PATCH] can match --- app/Models/Participant.php | 4 +- app/Services/MatcherService.php | 105 ++++++++++++++++++ .../views/livewire/pages/profile.blade.php | 11 ++ .../views/livewire/pages/table.blade.php | 49 ++++++-- 4 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 app/Services/MatcherService.php 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) + +

{{ $receiverUserData['data']['username'] ?? 'Unknown' }}

+ @endif + {{ "Their prompt is... {$receiverPrompt}" ?: 'No prompt set yet.' }} @elseif($participant->desperate)

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(); +}; ?>
+
+

All Participants Data

+ + +
+ +

+ Displaying all records from the `participants` table. +

+ + @if (session()->has('match-message')) + + @endif + @if($participants->isEmpty())
- No record + No records
@else
@@ -53,10 +87,10 @@ mount(function () { - - {{ $participant->desperate ? 'YES' : 'No' }} - + + {{ $participant->desperate ? 'YES' : 'No' }} + @@ -69,6 +103,3 @@ mount(function () {
@endif
- - -