]> git.localhorst.tv Git - alttp.git/commitdiff
server calculated scoring
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 25 Mar 2022 08:44:20 +0000 (09:44 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 25 Mar 2022 10:29:18 +0000 (11:29 +0100)
19 files changed:
app/Console/Commands/RecalculateRoundPlacements.php [new file with mode: 0644]
app/Console/Commands/RecalculateTournamentPlacements.php [new file with mode: 0644]
app/Events/ParticipantChanged.php [new file with mode: 0644]
app/Events/ResultChanged.php [new file with mode: 0644]
app/Events/ResultReported.php [deleted file]
app/Http/Controllers/ResultController.php
app/Models/Participant.php
app/Models/Result.php
app/Models/Round.php
app/Models/Tournament.php
database/migrations/2022_03_23_164955_result_placement.php [new file with mode: 0644]
database/migrations/2022_03_23_175706_participant_placement.php [new file with mode: 0644]
resources/js/components/pages/Tournament.js
resources/js/components/results/Item.js
resources/js/components/results/List.js
resources/js/components/results/ReportForm.js
resources/js/components/tournament/Scoreboard.js
resources/js/helpers/Participant.js
resources/js/helpers/Tournament.js

diff --git a/app/Console/Commands/RecalculateRoundPlacements.php b/app/Console/Commands/RecalculateRoundPlacements.php
new file mode 100644 (file)
index 0000000..ca4548b
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Models\Round;
+use Illuminate\Console\Command;
+
+class RecalculateRoundPlacements extends Command
+{
+       /**
+        * The name and signature of the console command.
+        *
+        * @var string
+        */
+       protected $signature = 'round:recalc {round}';
+
+       /**
+        * The console command description.
+        *
+        * @var string
+        */
+       protected $description = 'Recalculate round placements';
+
+       /**
+        * Execute the console command.
+        *
+        * @return int
+        */
+       public function handle()
+       {
+               $round = Round::findOrFail($this->argument('round'));
+
+               $round->updatePlacement();
+
+               return 0;
+       }
+}
diff --git a/app/Console/Commands/RecalculateTournamentPlacements.php b/app/Console/Commands/RecalculateTournamentPlacements.php
new file mode 100644 (file)
index 0000000..c1f7974
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Models\Tournament;
+use Illuminate\Console\Command;
+
+class RecalculateTournamentPlacements extends Command
+{
+       /**
+        * The name and signature of the console command.
+        *
+        * @var string
+        */
+       protected $signature = 'tournament:recalc {tournament}';
+
+       /**
+        * The console command description.
+        *
+        * @var string
+        */
+       protected $description = 'Recalculate tournament placements';
+
+       /**
+        * Execute the console command.
+        *
+        * @return int
+        */
+       public function handle()
+       {
+               $tournament = Tournament::findOrFail($this->argument('tournament'));
+
+               foreach ($tournament->rounds as $round) {
+                       $round->updatePlacement();
+               }
+               $tournament->updatePlacement();
+
+               return 0;
+       }
+}
diff --git a/app/Events/ParticipantChanged.php b/app/Events/ParticipantChanged.php
new file mode 100644 (file)
index 0000000..2e43437
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Events;
+
+use App\Models\Participant;
+use Illuminate\Broadcasting\Channel;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Broadcasting\PresenceChannel;
+use Illuminate\Broadcasting\PrivateChannel;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+
+class ParticipantChanged implements ShouldBroadcast
+{
+       use Dispatchable, InteractsWithSockets, SerializesModels;
+
+       /**
+        * Create a new event instance.
+        *
+        * @return void
+        */
+       public function __construct(Participant $participant)
+       {
+               $this->participant = $participant;
+       }
+
+       /**
+        * Get the channels the event should broadcast on.
+        *
+        * @return \Illuminate\Broadcasting\Channel|array
+        */
+       public function broadcastOn()
+       {
+               return new Channel('Tournament.'.$this->participant->tournament_id);
+       }
+
+       public $participant;
+
+}
diff --git a/app/Events/ResultChanged.php b/app/Events/ResultChanged.php
new file mode 100644 (file)
index 0000000..4a62d9d
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Events;
+
+use App\Models\Result;
+use Illuminate\Broadcasting\Channel;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Broadcasting\PresenceChannel;
+use Illuminate\Broadcasting\PrivateChannel;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+
+class ResultChanged implements ShouldBroadcast
+{
+       use Dispatchable, InteractsWithSockets, SerializesModels;
+
+       /**
+        * Create a new event instance.
+        *
+        * @return void
+        */
+       public function __construct(Result $result)
+       {
+               $this->result = $result;
+       }
+
+       /**
+        * Get the channels the event should broadcast on.
+        *
+        * @return \Illuminate\Broadcasting\Channel|array
+        */
+       public function broadcastOn()
+       {
+               return new Channel('Tournament.'.$this->result->round->tournament_id);
+       }
+
+       public $result;
+
+}
diff --git a/app/Events/ResultReported.php b/app/Events/ResultReported.php
deleted file mode 100644 (file)
index 61ec98b..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-namespace App\Events;
-
-use App\Models\Result;
-use Illuminate\Broadcasting\Channel;
-use Illuminate\Broadcasting\InteractsWithSockets;
-use Illuminate\Broadcasting\PresenceChannel;
-use Illuminate\Broadcasting\PrivateChannel;
-use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
-use Illuminate\Foundation\Events\Dispatchable;
-use Illuminate\Queue\SerializesModels;
-
-class ResultReported implements ShouldBroadcast
-{
-       use Dispatchable, InteractsWithSockets, SerializesModels;
-
-       /**
-        * Create a new event instance.
-        *
-        * @return void
-        */
-       public function __construct(Result $result)
-       {
-               $this->result = $result;
-       }
-
-       /**
-        * Get the channels the event should broadcast on.
-        *
-        * @return \Illuminate\Broadcasting\Channel|array
-        */
-       public function broadcastOn()
-       {
-               return new Channel('Tournament.'.$this->result->round->tournament_id);
-       }
-
-       public $result;
-
-}
index d72a10020dbe4b3e4f29cb8a488b808e5b583125..5102c8aa01ebec790a2aa4deea0aacc90d32b574 100644 (file)
@@ -2,7 +2,7 @@
 
 namespace App\Http\Controllers;
 
-use App\Events\ResultReported;
+use App\Events\ResultChanged;
 use App\Models\Participant;
 use App\Models\Protocol;
 use App\Models\Result;
@@ -39,6 +39,12 @@ class ResultController extends Controller
                        'forfeit' => $validatedData['forfeit'],
                        'time' => isset($validatedData['time']) ? $validatedData['time'] : 0,
                ]);
+               if ($result->wasChanged()) {
+                       ResultChanged::dispatch($result);
+               }
+               $round->load('results');
+               $round->updatePlacement();
+               $round->tournament->updatePlacement();
 
                Protocol::resultReported(
                        $round->tournament,
@@ -46,8 +52,6 @@ class ResultController extends Controller
                        $request->user(),
                );
 
-               ResultReported::dispatch($result);
-
                return $result->toJson();
        }
 
index 8aac88ce54a04877ecefe43c6f67950ec0d09c63..91b946d887fe726b2e066faf77d40991b8682485 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace App\Models;
 
+use App\Events\ParticipantChanged;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 
@@ -9,6 +10,79 @@ class Participant extends Model
 {
        use HasFactory;
 
+
+       public static function compareResult(Round $round) {
+               return function (Participant $a, Participant $b) use ($round) {
+                       $a_result = $a->findResult($round);
+                       $b_result = $b->findResult($round);
+                       $a_time = $a_result && !$a_result->forfeit ? $a_result->time : 0;
+                       $b_time = $b_result && !$b_result->forfeit ? $b_result->time : 0;
+                       if ($a_time) {
+                               if ($b_time) {
+                                       if ($a_time < $b_time) return -1;
+                                       if ($b_time < $a_time) return 1;
+                                       return static::compareUsername($a, $b);
+                               }
+                               return -1;
+                       }
+                       if ($b_time) {
+                               return 1;
+                       }
+                       $a_forfeit = $a_result ? $a_result->forfeit : false;
+                       $b_forfeit = $b_result ? $b_result->forfeit : false;
+                       if ($a_forfeit) {
+                               if ($b_forfeit) {
+                                       return static::compareUsername($a, $b);
+                               }
+                               return -1;
+                       }
+                       if ($b_forfeit) {
+                               return 1;
+                       }
+                       return static::compareUsername($a, $b);
+               };
+       }
+
+       public static function compareScore(Participant $a, Participant $b) {
+               $a_score = $a->isRunner() ? ($a->score ? $a->score : 0) : -1;
+               $b_score = $b->isRunner() ? ($b->score ? $b->score : 0) : -1;
+               if ($a_score < $b_score) return -1;
+               if ($b_score < $a_score) return 1;
+               return static::compareUsername($a, $b);
+       }
+
+       public static function compareUsername(Participant $a, Participant $b) {
+               return strcasecmp($a->user->username, $b->user->username);
+       }
+
+
+       public function updatePlacement($score, $placement) {
+               $this->score = $score;
+               $this->placement = $placement;
+               $this->save();
+               if ($this->wasChanged()) {
+                       ParticipantChanged::dispatch($this);
+               }
+       }
+
+       public function findResult(Round $round) {
+               foreach ($round->results as $result) {
+                       if ($this->user_id == $result->user_id) {
+                               return $result;
+                       }
+               }
+               return null;
+       }
+
+       public function isRunner() {
+               return in_array('runner', $this->roles);
+       }
+
+       public function isTournamentAdmin() {
+               return in_array('admin', $this->roles);
+       }
+
+
        public function tournament() {
                return $this->belongsTo(Tournament::class);
        }
@@ -17,6 +91,7 @@ class Participant extends Model
                return $this->belongsTo(User::class);
        }
 
+
        protected $casts = [
                'roles' => 'array',
        ];
index 3ced393cc64b1d13ebe3d2970e399e48550a9c13..cdb86c77798fb2bccb204c740ee0ced94473bf71 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace App\Models;
 
+use App\Events\ResultChanged;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 
@@ -9,6 +10,26 @@ class Result extends Model
 {
        use HasFactory;
 
+
+       public function updateResult($time, $forfeit) {
+               $this->time = $time;
+               $this->forfeit = $forfeit;
+               $this->save();
+               if ($this->wasChanged()) {
+                       ResultChanged::dispatch($this);
+               }
+       }
+
+       public function updatePlacement($score, $placement) {
+               $this->score = $score;
+               $this->placement = $placement;
+               $this->save();
+               if ($this->wasChanged()) {
+                       ResultChanged::dispatch($this);
+               }
+       }
+
+
        public function round() {
                return $this->belongsTo(Round::class);
        }
@@ -21,6 +42,7 @@ class Result extends Model
                return $this->time > 0 || $this->forfeit;
        }
 
+
        protected $appends = [
                'has_finished',
        ];
index 71a4273dc9435b21c609341082fe3c52d99efd33..3777df9e8e96040717fa8ce3e97e0d878188463c 100644 (file)
@@ -9,6 +9,50 @@ class Round extends Model
 {
        use HasFactory;
 
+
+       public function updatePlacement() {
+               $runners = [];
+               foreach ($this->tournament->participants as $p) {
+                       if ($p->isRunner()) {
+                               $runners[] = $p;
+                       } else {
+                               $result = $p->findResult($this);
+                               if ($result) {
+                                       $result->updatePlacement(null, null);
+                               }
+                       }
+               }
+
+               usort($runners, Participant::compareResult($this));
+               $mapped = array_map(function ($p) {
+                       return ['participant' => $p, 'result' => $p->findResult($this)];
+               }, $runners);
+               $filtered = array_filter($mapped, function($r) {
+                       return $r['result'] && ($r['result']->time || $r['result']->forfeit);
+               });
+               $reversed = array_reverse($filtered);
+
+               $running = 0;
+               $bonus = 1;
+               $lastResult = null;
+               foreach ($reversed as $r) {
+                       $betterThanLast = is_null($lastResult) || $r['result']->time < $lastResult;
+                       if (!$r['result']->forfeit && $betterThanLast) {
+                               $running += $bonus;
+                               $lastResult = $r['result']->time;
+                               $bonus = 1;
+                       } else {
+                               ++$bonus;
+                       }
+                       if (!$r['result']->forfeit) {
+                               $r['result']->updatePlacement($running, count($filtered) - $running + 1);
+                       } else {
+                               $r['result']->updatePlacement(0, count($filtered));
+                       }
+               }
+       }
+
+
        public function results() {
                return $this->hasMany(Result::class);
        }
@@ -17,6 +61,7 @@ class Round extends Model
                return $this->belongsTo(Tournament::class);
        }
 
+
        protected $casts = [
                'code' => 'array',
                'locked' => 'boolean',
index 217622bb9681c950d6b4113b38464ef6bf4ed167..a38e9bde591a546975a402e361c4f420fa63be17 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace App\Models;
 
+use App\Events\ParticipantChanged;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 
@@ -9,6 +10,57 @@ class Tournament extends Model
 {
        use HasFactory;
 
+
+       public function getRunners() {
+               $runners = [];
+               foreach ($this->participants as $participant) {
+                       if (in_array('runner', $participant->roles)) {
+                               $runners[] = $participant;
+                       }
+               }
+               return $runners;
+       }
+
+       public function updatePlacement() {
+               $runners = [];
+               foreach ($this->participants as $p) {
+                       if ($p->isRunner()) {
+                               $p->score = 0;
+                               $runners[] = $p;
+                       } else {
+                               $p->updatePlacement(null, null);
+                       }
+               }
+               if (empty($runners)) {
+                       return;
+               }
+               foreach ($this->rounds as $round) {
+                       foreach ($runners as $p) {
+                               $result = $p->findResult($round);
+                               if ($result) {
+                                       $p->score += $result->score;
+                               }
+                       }
+               }
+
+               usort($runners, [Participant::class, 'compareScore']);
+               $reversed = array_reverse($runners);
+               $placement = count($runners);
+               $skipped = 0;
+               $lastScore = $runners[0]->score;
+               foreach ($runners as $p) {
+                       if ($p->score > $lastScore) {
+                               $placement -= $skipped;
+                               $skipped = 1;
+                               $lastScore = $p->score;
+                       } else {
+                               ++$skipped;
+                       }
+                       $p->updatePlacement($p->score, $placement);
+               }
+       }
+
+
        public function participants() {
                return $this->hasMany(Participant::class);
        }
diff --git a/database/migrations/2022_03_23_164955_result_placement.php b/database/migrations/2022_03_23_164955_result_placement.php
new file mode 100644 (file)
index 0000000..15dcab7
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+       /**
+        * Run the migrations.
+        *
+        * @return void
+        */
+       public function up()
+       {
+               Schema::table('results', function(Blueprint $table) {
+                       $table->integer('placement')->nullable()->default(null);
+                       $table->integer('score')->nullable()->default(null);
+               });
+       }
+
+       /**
+        * Reverse the migrations.
+        *
+        * @return void
+        */
+       public function down()
+       {
+               Schema::table('results', function(Blueprint $table) {
+                       $table->dropColumn('placement');
+                       $table->dropColumn('score');
+               });
+       }
+};
diff --git a/database/migrations/2022_03_23_175706_participant_placement.php b/database/migrations/2022_03_23_175706_participant_placement.php
new file mode 100644 (file)
index 0000000..ef8aad0
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+       /**
+        * Run the migrations.
+        *
+        * @return void
+        */
+       public function up()
+       {
+               Schema::table('participants', function(Blueprint $table) {
+                       $table->integer('placement')->nullable()->default(null);
+                       $table->integer('score')->nullable()->default(null);
+               });
+       }
+
+       /**
+        * Reverse the migrations.
+        *
+        * @return void
+        */
+       public function down()
+       {
+               Schema::table('participants', function(Blueprint $table) {
+                       $table->dropColumn('placement');
+                       $table->dropColumn('score');
+               });
+       }
+};
index 6e32227ab2d04d330b8025dc16744fdbbee5d606..bc1282e0586fdc351d626eb9ccd1707bdcd1ce8b 100644 (file)
@@ -7,7 +7,13 @@ import ErrorMessage from '../common/ErrorMessage';
 import Loading from '../common/Loading';
 import NotFound from '../pages/NotFound';
 import Detail from '../tournament/Detail';
-import { patchResult, patchRound, patchUser, sortParticipants } from '../../helpers/Tournament';
+import {
+       patchParticipant,
+       patchResult,
+       patchRound,
+       patchUser,
+       sortParticipants,
+} from '../../helpers/Tournament';
 
 const Tournament = () => {
        const params = useParams();
@@ -35,7 +41,12 @@ const Tournament = () => {
 
        useEffect(() => {
                window.Echo.channel(`Tournament.${id}`)
-                       .listen('ResultReported', e => {
+                       .listen('ParticipantChanged', e => {
+                               if (e.participant) {
+                                       setTournament(tournament => patchParticipant(tournament, e.participant));
+                               }
+                       })
+                       .listen('ResultChanged', e => {
                                if (e.result) {
                                        setTournament(tournament => patchResult(tournament, e.result));
                                }
index d99d93f75eea34eca456b0d668127f28038ef85b..84395222407e1d50854e283a282a31002b73a328 100644 (file)
@@ -9,20 +9,20 @@ import { findResult } from '../../helpers/Participant';
 import { maySeeResults } from '../../helpers/permissions';
 import { withUser } from '../../helpers/UserContext';
 
-const getIcon = (result, index, maySee) => {
+const getIcon = (result, maySee) => {
        if (!result || !result.has_finished) {
                return <Icon.PENDING className="text-muted" size="lg" />;
        }
        if (result.forfeit && maySee) {
                return <Icon.FORFEIT className="text-danger" size="lg" />;
        }
-       if (index === 0) {
+       if (result.placement === 1) {
                return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
        }
-       if (index === 1) {
+       if (result.placement === 2) {
                return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
        }
-       if (index === 2) {
+       if (result.placement === 3) {
                return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
        }
        return <Icon.FINISHED className="text-success" size="lg" />;
@@ -42,7 +42,6 @@ const getTime = (result, maySee) => {
 };
 
 const Item = ({
-       index,
        participant,
        round,
        tournament,
@@ -56,13 +55,12 @@ const Item = ({
                        <span className="time">
                                {getTime(result, maySee)}
                        </span>
-                       {getIcon(result, index, maySee)}
+                       {getIcon(result, maySee)}
                </div>
        </div>;
 };
 
 Item.propTypes = {
-       index: PropTypes.number,
        participant: PropTypes.shape({
                user: PropTypes.shape({
                }),
index cdd90e847e91785b587ee8376f30912f67ba91f4..f4d4ff10ee502130f44f65a9e03762347100d692 100644 (file)
@@ -6,9 +6,8 @@ import { sortByResult } from '../../helpers/Participant';
 import { getRunners } from '../../helpers/Tournament';
 
 const List = ({ round, tournament }) => <div className="results d-flex flex-wrap">
-       {sortByResult(getRunners(tournament), round).map((participant, index) =>
+       {sortByResult(getRunners(tournament), round).map(participant =>
                <Item
-                       index={index}
                        key={participant.id}
                        participant={participant}
                        round={round}
index 943aac39ff059c016bef1f49e9fff978fc75d60e..28a757a78ed9326a916aa6f3f24a18b3f7fedda6 100644 (file)
@@ -122,7 +122,6 @@ export default withFormik({
        },
        mapPropsToValues: ({ participant, round }) => {
                const result = findResult(participant, round);
-               console.log(result);
                return {
                        forfeit: result ? !!result.forfeit : false,
                        participant_id: participant.id,
index cbc73a32620db88204b5fa81f669a5db18c79702..40ad82ff300724acda076a5f7038d1546b723c5e 100644 (file)
@@ -5,29 +5,30 @@ import { withTranslation } from 'react-i18next';
 
 import Icon from '../common/Icon';
 import Box from '../users/Box';
-import { calculateScores } from '../../helpers/Tournament';
+import { comparePlacement } from '../../helpers/Participant';
+import { getRunners } from '../../helpers/Tournament';
 import { withUser } from '../../helpers/UserContext';
 import i18n from '../../i18n';
 
-const getRowClassName = (tournament, score, user) => {
+const getRowClassName = (tournament, participant, user) => {
        const classNames = ['score'];
-       if (score && user && score.participant && score.participant.user_id == user.id) {
+       if (participant && user && participant.user_id == user.id) {
                classNames.push('is-self');
        }
        return classNames.join(' ');
 };
 
-const getPlacementDisplay = score => {
-       if (score.placement === 1) {
+const getPlacementDisplay = participant => {
+       if (participant.placement === 1) {
                return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
        }
-       if (score.placement === 2) {
+       if (participant.placement === 2) {
                return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
        }
-       if (score.placement === 3) {
+       if (participant.placement === 3) {
                return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
        }
-       return score.placement;
+       return participant.placement;
 };
 
 const Scoreboard = ({ tournament, user }) =>
@@ -40,17 +41,17 @@ const Scoreboard = ({ tournament, user }) =>
                </tr>
        </thead>
        <tbody>
-       {calculateScores(tournament).map(score =>
-               <tr className={getRowClassName(tournament, score, user)} key={score.participant.id}>
+       {getRunners(tournament).sort(comparePlacement).map(participant =>
+               <tr className={getRowClassName(tournament, participant, user)} key={participant.id}>
                        <td className="text-center">
-                               {getPlacementDisplay(score)}
+                               {getPlacementDisplay(participant)}
                        </td>
                        <td>
                                <div className="d-flex align-items-center justify-content-between">
-                                       <Box user={score.participant.user} />
-                                       {score.participant.user.stream_link ?
+                                       <Box user={participant.user} />
+                                       {participant.user.stream_link ?
                                                <Button
-                                                       href={score.participant.user.stream_link}
+                                                       href={participant.user.stream_link}
                                                        size="sm"
                                                        target="_blank"
                                                        title={i18n.t('users.stream')}
@@ -61,7 +62,7 @@ const Scoreboard = ({ tournament, user }) =>
                                        : null}
                                </div>
                        </td>
-                       <td className="text-end">{score.score}</td>
+                       <td className="text-end">{participant.score}</td>
                </tr>
        )}
        </tbody>
index d79349bca8776a65ae853943df671f8ffb8f114f..8545e0335c2f37c9fb854138e1a930211d18648e 100644 (file)
@@ -1,28 +1,23 @@
+export const comparePlacement = (a, b) => {
+       if (a.placement < b.placement) return -1;
+       if (b.placement < a.placement) return 1;
+       return compareUsername(a, b);
+};
+
 export const compareResult = round => (a, b) => {
        const a_result = findResult(a, round);
        const b_result = findResult(b, round);
-       const a_time = a_result && !a_result.forfeit ? a_result.time : 0;
-       const b_time = b_result && !b_result.forfeit ? b_result.time : 0;
-       if (a_time) {
-               if (b_time) {
-                       if (a_time < b_time) return -1;
-                       if (b_time < a_time) return 1;
-                       return 0;
-               }
-               return -1;
-       }
-       if (b_time) {
-               return 1;
-       }
-       const a_forfeit = a_result && a_result.forfeit;
-       const b_forfeit = b_result && b_result.forfeit;
-       if (a_forfeit) {
-               if (b_forfeit) {
-                       return 0;
+       const a_placement = a_result && a_result.placement ? a_result.placement : 0;
+       const b_placement = b_result && b_result.placement ? b_result.placement : 0;
+       if (a_placement) {
+               if (b_placement) {
+                       if (a_placement < b_placement) return -1;
+                       if (b_placement < a_placement) return 1;
+                       return compareUsername(a, b);
                }
                return -1;
        }
-       if (b_forfeit) {
+       if (b_placement) {
                return 1;
        }
        return compareUsername(a, b);
index 7dffebbd07d96c1c189d6f51bdef61b62a524365..ec141d12829b9758405314e052ddeae2aba9d7d7 100644 (file)
@@ -1,54 +1,6 @@
 import Participant from './Participant';
 import Round from './Round';
 
-export const calculateScores = tournament => {
-       const runners = getRunners(tournament);
-       const scores = runners.map(participant => ({ participant, score: 0 }));
-       if (!scores.length) return scores;
-       if (!tournament.rounds || !tournament.rounds.length) return scores;
-       tournament.rounds.forEach(round => {
-               const filtered = Participant
-                       .sortByResult(runners, round)
-                       .map(p => ({ participant: p, result: Participant.findResult(p, round) }))
-                       .filter(r => r.result && (r.result.time || r.result.forfeit))
-                       .reverse();
-               let running = 0;
-               let bonus = 1;
-               let lastResult = null;
-               for (let i = 0; i < filtered.length; ++i) {
-                       const score = scores.find(s => s.participant.id === filtered[i].participant.id);
-                       if (!score) return;
-                       const result = filtered[i].result;
-                       const betterThanLast = lastResult === null || result.time < lastResult;
-                       if (!result.forfeit && betterThanLast) {
-                               running += bonus;
-                               lastResult = result.time;
-                               bonus = 1;
-                       } else {
-                               ++bonus;
-                       }
-                       if (!result.forfeit) {
-                               score.score += running;
-                       }
-               }
-       });
-       const sorted = scores.sort(compareScore);
-       let placement = scores.length;
-       let skipped = 0;
-       let lastScore = sorted[0].score;
-       for (let i = 0; i < sorted.length; ++i) {
-               if (sorted[i].score > lastScore) {
-                       placement -= skipped;
-                       skipped = 1;
-                       lastScore = sorted[i].score;
-               } else {
-                       ++skipped;
-               }
-               sorted[i].placement = placement;
-       }
-       return sorted.reverse();
-};
-
 export const compareScore = (a, b) => {
        const a_score = a && a.score ? a.score : 0;
        const b_score = b && b.score ? b.score : 0;
@@ -85,6 +37,28 @@ export const hasTournamentAdmins = tournament => {
        return getTournamentAdmins(tournament).length > 0;
 };
 
+export const patchParticipant = (tournament, participant) => {
+       if (!tournament) return tournament;
+       if (!tournament.participants || !tournament.participants.length) {
+               return {
+                       ...tournament,
+                       participants: [participant],
+               };
+       }
+       if (!tournament.participants.find(p => p.id === participant.id)) {
+               return {
+                       ...tournament,
+                       participants: [...tournament.participants, participant],
+               };
+       }
+       return {
+               ...tournament,
+               participants: tournament.participants.map(
+                       p => p.id === participant.id ? participant : p,
+               ),
+       };
+};
+
 export const patchResult = (tournament, result) => {
        if (!tournament || !tournament.rounds) return tournament;
        return {
@@ -125,7 +99,6 @@ export const sortParticipants = tournament => {
 };
 
 export default {
-       calculateScores,
        compareScore,
        findParticipant,
        getRunners,