can match
This commit is contained in:
parent
8f5aab2c57
commit
a930d0f972
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Participant;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class MatcherService
|
||||
{
|
||||
public function match(): void
|
||||
{
|
||||
$participants = Participant::all();
|
||||
|
||||
if ($participants->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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <p>{{ $participant->giving_id }}</p>
|
||||
@if($receiverUserData)
|
||||
<img src ="{{ $receiverUserData['data']['profile_image_url'] ?? '' }}">
|
||||
<h1> {{ $receiverUserData['data']['username'] ?? 'Unknown' }}</h1>
|
||||
@endif
|
||||
{{ "Their prompt is... {$receiverPrompt}" ?: 'No prompt set yet.' }}
|
||||
@elseif($participant->desperate)
|
||||
<p>You're in a bind!</p>
|
||||
@else
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Participant;
|
||||
use App\Services\MatcherService;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use function Livewire\Volt\{state, mount, rules};
|
||||
use function Livewire\Volt\{state, mount};
|
||||
|
||||
state(['participants' => 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();
|
||||
};
|
||||
?>
|
||||
|
||||
<div class="p-6 bg-gray-100 min-h-screen">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-3xl font-extrabold text-gray-900">All Participants Data</h1>
|
||||
|
||||
<button
|
||||
wire:click="runMatch"
|
||||
wire:loading.attr="disabled"
|
||||
wire:target="runMatch"
|
||||
class="px-4 py-2 bg-indigo-600 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700 transition duration-150 disabled:opacity-50"
|
||||
>
|
||||
<span wire:loading wire:target="runMatch">Processing Match...</span>
|
||||
<span wire:loading.remove wire:target="runMatch">Run Matcher Service</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-gray-600 mb-4">
|
||||
Displaying all records from the `participants` table.
|
||||
</p>
|
||||
|
||||
@if (session()->has('match-message'))
|
||||
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-4 rounded" role="alert">
|
||||
<p class="font-bold">Success!</p>
|
||||
<p>{{ session('match-message') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($participants->isEmpty())
|
||||
<div class="p-4 text-center text-gray-500 bg-white shadow rounded-lg">
|
||||
No record
|
||||
No records
|
||||
</div>
|
||||
@else
|
||||
<div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
|
||||
|
|
@ -53,10 +87,10 @@ mount(function () {
|
|||
</td>
|
||||
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
||||
{{ $participant->desperate ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800' }}">
|
||||
{{ $participant->desperate ? 'YES' : 'No' }}
|
||||
</span>
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
||||
{{ $participant->desperate ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800' }}">
|
||||
{{ $participant->desperate ? 'YES' : 'No' }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="px-6 py-4 text-sm font-mono text-gray-500 truncate" title="{{ $participant->token }}">
|
||||
|
|
@ -69,6 +103,3 @@ mount(function () {
|
|||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue