Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
D
deprecated-tabulator-backend
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
deprecated-tabulator-backend
Commits
80632e78
Commit
80632e78
authored
Jan 15, 2023
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
swiss
parent
485d2019
Pipeline
#19577
passed with stages
in 5 minutes and 21 seconds
Changes
9
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
172 additions
and
13 deletions
+172
-13
src/match/entities/match.entity.ts
src/match/entities/match.entity.ts
+4
-0
src/match/match.service.ts
src/match/match.service.ts
+10
-1
src/participant/participant.service.ts
src/participant/participant.service.ts
+17
-4
src/tournament-rules/base.ts
src/tournament-rules/base.ts
+16
-2
src/tournament-rules/rule-map.ts
src/tournament-rules/rule-map.ts
+2
-0
src/tournament-rules/rules/single-elimination.ts
src/tournament-rules/rules/single-elimination.ts
+9
-6
src/tournament-rules/rules/swiss.ts
src/tournament-rules/rules/swiss.ts
+104
-0
src/tournament/entities/Tournament.entity.ts
src/tournament/entities/Tournament.entity.ts
+4
-0
src/tournament/tournament.service.ts
src/tournament/tournament.service.ts
+6
-0
No files found.
src/match/entities/match.entity.ts
View file @
80632e78
...
...
@@ -124,6 +124,10 @@ export class Match extends IdBase() {
return
this
.
player1Id
===
id
||
this
.
player2Id
===
id
;
}
opponentId
(
id
:
number
)
{
return
this
.
player1Id
===
id
?
this
.
player2Id
:
this
.
player1Id
;
}
clone
()
{
const
match
=
new
Match
();
Object
.
assign
(
match
,
this
);
...
...
src/match/match.service.ts
View file @
80632e78
...
...
@@ -5,9 +5,9 @@ import { InjectRepository } from '@nestjs/typeorm';
import
{
MycardUser
}
from
'
nestjs-mycard
'
;
import
{
Tournament
,
TournamentRule
,
TournamentStatus
,
}
from
'
../tournament/entities/Tournament.entity
'
;
import
{
Participant
}
from
'
../participant/entities/participant.entity
'
;
import
{
TournamentService
}
from
'
../tournament/tournament.service
'
;
import
{
MoreThan
}
from
'
typeorm
'
;
...
...
@@ -75,6 +75,15 @@ export class MatchService extends CrudService(Match, {
'
比赛已结束,无法修改。
'
,
).
toException
();
}
if
(
dto
.
winnerId
===
null
&&
match
.
tournament
.
rule
!==
TournamentRule
.
Swiss
)
{
throw
new
BlankReturnMessageDto
(
400
,
'
非瑞士轮对局胜者不能为空。
'
,
).
toException
();
}
if
(
dto
.
winnerId
!==
undefined
)
{
dto
.
status
=
MatchStatus
.
Finished
;
}
...
...
src/participant/participant.service.ts
View file @
80632e78
...
...
@@ -42,7 +42,7 @@ export class ParticipantService extends CrudService(Participant, {
dto
:
Partial
<
Participant
>
,
user
:
MycardUser
,
)
{
await
this
.
checkPermissionOfParticipant
(
id
,
user
);
await
this
.
checkPermissionOfParticipant
(
id
,
user
,
true
);
return
this
.
update
(
id
,
dto
);
}
...
...
@@ -51,7 +51,11 @@ export class ParticipantService extends CrudService(Participant, {
return
this
.
delete
(
id
);
}
async
checkPermissionOfParticipant
(
id
:
number
,
user
:
MycardUser
)
{
async
checkPermissionOfParticipant
(
id
:
number
,
user
:
MycardUser
,
allowRunning
=
false
,
)
{
const
participant
=
await
this
.
repo
.
findOne
({
where
:
{
id
},
select
:
{
...
...
@@ -68,7 +72,16 @@ export class ParticipantService extends CrudService(Participant, {
if
(
!
participant
?.
tournament
)
{
throw
new
BlankReturnMessageDto
(
404
,
'
未找到该参赛者。
'
).
toException
();
}
if
(
participant
.
tournament
.
status
!==
TournamentStatus
.
Ready
)
{
if
(
participant
.
tournament
.
status
===
TournamentStatus
.
Finished
)
{
throw
new
BlankReturnMessageDto
(
400
,
'
比赛已结束,无法修改参赛者。
'
,
).
toException
();
}
if
(
!
allowRunning
&&
participant
.
tournament
.
status
!==
TournamentStatus
.
Ready
)
{
throw
new
BlankReturnMessageDto
(
400
,
'
比赛已开始,无法修改参赛者。
'
,
...
...
@@ -80,7 +93,7 @@ export class ParticipantService extends CrudService(Participant, {
async
importParticipants
(
participants
:
Participant
[],
user
:
MycardUser
)
{
return
this
.
importEntities
(
participants
,
async
(
p
)
=>
{
try
{
await
this
.
tournamentService
.
c
heckPermissionOfTournament
(
await
this
.
tournamentService
.
c
anModifyParticipants
(
p
.
tournamentId
,
user
,
);
...
...
src/tournament-rules/base.ts
View file @
80632e78
...
...
@@ -7,10 +7,16 @@ import {
}
from
'
../participant/entities/participant.entity
'
;
export
class
TournamentRuleBase
{
protected
participantMap
=
new
Map
<
number
,
Participant
>
();
constructor
(
protected
tournament
:
Tournament
,
protected
repo
?:
Repository
<
Match
>
,
)
{}
)
{
this
.
tournament
.
participants
?.
forEach
((
p
)
=>
this
.
participantMap
.
set
(
p
.
id
,
p
),
);
}
async
saveMatch
(
matches
:
Match
[])
{
matches
.
forEach
((
m
)
=>
(
m
.
tournamentId
=
this
.
tournament
.
id
));
...
...
@@ -20,7 +26,11 @@ export class TournamentRuleBase {
currentRoundCount
()
{
return
Math
.
max
(
...
this
.
tournament
.
matches
.
filter
((
m
)
=>
m
.
status
===
MatchStatus
.
Finished
)
.
filter
(
(
m
)
=>
m
.
status
===
MatchStatus
.
Finished
||
m
.
status
===
MatchStatus
.
Running
,
)
.
map
((
m
)
=>
m
.
round
),
);
}
...
...
@@ -53,4 +63,8 @@ export class TournamentRuleBase {
).
length
,
};
}
participantScoreAfter
(
participant
:
Participant
):
Partial
<
ParticipantScore
>
{
return
{};
}
}
src/tournament-rules/rule-map.ts
View file @
80632e78
import
{
TournamentRule
}
from
'
../tournament/entities/Tournament.entity
'
;
import
{
SingleElimination
}
from
'
./rules/single-elimination
'
;
import
{
TournamentRuleBase
}
from
'
./base
'
;
import
{
Swiss
}
from
'
./rules/swiss
'
;
export
const
TournamentRules
=
new
Map
<
TournamentRule
,
...
...
@@ -8,3 +9,4 @@ export const TournamentRules = new Map<
>
();
TournamentRules
.
set
(
<
TournamentRule
>
'
SingleElimination
'
,
SingleElimination
);
TournamentRules
.
set
(
<
TournamentRule
>
'
Swiss
'
,
Swiss
);
src/tournament-rules/rules/single-elimination.ts
View file @
80632e78
...
...
@@ -4,6 +4,7 @@ import {
Participant
,
ParticipantScore
,
}
from
'
../../participant/entities/participant.entity
'
;
import
_
from
'
lodash
'
;
export
class
SingleElimination
extends
TournamentRuleBase
{
totalRoundCount
()
{
...
...
@@ -24,14 +25,16 @@ export class SingleElimination extends TournamentRuleBase {
]);
if
(
i
===
1
)
{
// add participants to first round
const
{
participants
}
=
this
.
tournament
;
const
participants
=
_
.
sortBy
(
this
.
tournament
.
participants
,
(
p
)
=>
-
p
.
id
,
);
const
neededMatchesCount
=
participants
.
length
-
nextRoundMatches
.
length
*
2
;
matches
=
matches
.
slice
(
0
,
neededMatchesCount
);
const
participantsToAdd
=
[...
participants
].
reverse
();
for
(
const
match
of
matches
)
{
match
.
player1Id
=
participants
ToAdd
.
pop
().
id
;
match
.
player2Id
=
participants
ToAdd
.
pop
().
id
;
match
.
player1Id
=
participants
.
pop
().
id
;
match
.
player2Id
=
participants
.
pop
().
id
;
match
.
status
=
MatchStatus
.
Running
;
}
}
...
...
@@ -49,7 +52,7 @@ export class SingleElimination extends TournamentRuleBase {
const
thirdPlaceMatch
=
new
Match
();
thirdPlaceMatch
.
isThirdPlaceMatch
=
true
;
thirdPlaceMatch
.
round
=
roundCount
;
await
this
.
repo
.
save
(
thirdPlaceMatch
);
await
this
.
saveMatch
([
thirdPlaceMatch
]
);
}
}
...
...
@@ -65,7 +68,7 @@ export class SingleElimination extends TournamentRuleBase {
.
reverse
();
const
nextRoundCount
=
this
.
nextRoundCount
();
const
matches
=
this
.
specificMatches
(
MatchStatus
.
Pending
).
filter
(
(
m
)
=>
m
.
round
===
nextRoundCount
,
(
m
)
=>
m
.
round
===
nextRoundCount
&&
!
m
.
isThirdPlaceMatch
,
);
for
(
const
match
of
matches
)
{
match
.
player1Id
=
survivedParticipants
.
pop
().
id
;
...
...
src/tournament-rules/rules/swiss.ts
0 → 100644
View file @
80632e78
import
{
TournamentRuleBase
}
from
'
../base
'
;
import
{
RuleSettings
}
from
'
../../tournament/entities/Tournament.entity
'
;
import
{
Match
,
MatchStatus
}
from
'
../../match/entities/match.entity
'
;
import
_
from
'
lodash
'
;
import
{
Participant
,
ParticipantScore
,
}
from
'
../../participant/entities/participant.entity
'
;
export
class
Swiss
extends
TournamentRuleBase
{
private
settings
:
Partial
<
RuleSettings
>
=
{
rounds
:
this
.
tournament
.
ruleSettings
?.
rounds
??
Math
.
ceil
(
Math
.
log2
(
this
.
tournament
.
participants
?.
length
||
2
)),
winScore
:
this
.
tournament
.
ruleSettings
?.
winScore
??
3
,
drawScore
:
this
.
tournament
.
ruleSettings
?.
drawScore
??
1
,
byeScore
:
this
.
tournament
.
ruleSettings
?.
byeScore
??
3
,
};
async
initialize
()
{
const
matchCountPerRound
=
Math
.
floor
(
this
.
tournament
.
participants
.
length
/
2
,
);
const
allMatches
:
Match
[]
=
[];
for
(
let
r
=
1
;
r
<
this
.
settings
.
rounds
;
++
r
)
{
const
matches
:
Match
[]
=
[];
for
(
let
i
=
0
;
i
<
matchCountPerRound
;
++
i
)
{
const
match
=
new
Match
();
match
.
round
=
r
;
match
.
status
=
MatchStatus
.
Pending
;
matches
.
push
(
match
);
}
if
(
r
===
1
)
{
const
participants
=
_
.
sortBy
(
this
.
tournament
.
participants
,
(
p
)
=>
-
p
.
id
,
);
for
(
const
match
of
matches
)
{
match
.
status
=
MatchStatus
.
Running
;
match
.
player1Id
=
participants
.
pop
().
id
;
match
.
player2Id
=
participants
.
pop
().
id
;
}
}
allMatches
.
push
(...
matches
);
}
await
this
.
saveMatch
(
allMatches
);
}
nextRound
():
Partial
<
Match
[]
>
{
this
.
tournament
.
calculateScore
();
const
participants
=
this
.
tournament
.
participants
.
filter
((
p
)
=>
!
p
.
quit
)
.
reverse
();
const
nextRoundCount
=
this
.
nextRoundCount
();
const
matches
=
this
.
tournament
.
matches
.
filter
(
(
m
)
=>
m
.
round
===
nextRoundCount
,
);
for
(
const
match
of
matches
)
{
match
.
status
=
MatchStatus
.
Running
;
match
.
player1Id
=
participants
.
pop
()?.
id
;
match
.
player2Id
=
participants
.
pop
()?.
id
;
if
(
!
match
.
player1Id
||
!
match
.
player2Id
)
{
match
.
status
=
MatchStatus
.
Abandoned
;
match
.
player1Id
=
null
;
match
.
player2Id
=
null
;
}
}
return
matches
;
}
participantScore
(
participant
:
Participant
):
Partial
<
ParticipantScore
>
{
const
data
=
super
.
participantScore
(
participant
);
let
bye
=
0
;
for
(
let
i
=
1
;
i
<=
this
.
currentRoundCount
();
++
i
)
{
if
(
!
this
.
tournament
.
matches
.
some
(
(
m
)
=>
m
.
round
===
i
&&
m
.
participated
(
participant
.
id
),
)
)
{
++
bye
;
}
}
return
{
...
data
,
bye
,
score
:
data
.
win
*
this
.
settings
.
winScore
+
data
.
draw
*
this
.
settings
.
drawScore
+
bye
*
this
.
settings
.
byeScore
,
};
}
participantScoreAfter
(
participant
:
Participant
):
Partial
<
ParticipantScore
>
{
const
opponentIds
=
this
.
specificMatches
(
MatchStatus
.
Finished
)
.
filter
((
m
)
=>
m
.
participated
(
participant
.
id
))
.
map
((
m
)
=>
m
.
opponentId
(
participant
.
id
));
const
opponents
=
opponentIds
.
map
((
id
)
=>
this
.
participantMap
.
get
(
id
));
return
{
tieBreaker
:
_
.
sumBy
(
opponents
,
(
p
)
=>
p
.
score
.
score
),
};
}
}
src/tournament/entities/Tournament.entity.ts
View file @
80632e78
...
...
@@ -194,6 +194,10 @@ export class Tournament extends DescBase {
p
.
score
=
new
ParticipantScore
();
Object
.
assign
(
p
.
score
,
rule
.
participantScore
(
p
));
});
this
.
participants
.
forEach
((
p
)
=>
{
const
editScore
=
rule
.
participantScoreAfter
(
p
);
Object
.
assign
(
p
.
score
,
editScore
);
});
this
.
participants
=
_
.
sortBy
(
this
.
participants
,
(
p
)
=>
-
p
.
score
.
score
,
...
...
src/tournament/tournament.service.ts
View file @
80632e78
...
...
@@ -112,6 +112,12 @@ export class TournamentService extends CrudService(Tournament, {
'
比赛已经开始,不能重复开始。
'
,
).
toException
();
}
if
(
tournament
.
participants
.
length
<
2
)
{
throw
new
BlankReturnMessageDto
(
403
,
'
参赛者数量不足,不能开始。
'
,
).
toException
();
}
await
this
.
repo
.
manager
.
transaction
(
async
(
edb
)
=>
{
await
tournament
.
getRuleHandler
(
edb
.
getRepository
(
Match
)).
initialize
();
await
edb
.
update
(
Tournament
,
id
,
{
status
:
TournamentStatus
.
Running
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment