181 lines
5.8 KiB
Dart
181 lines
5.8 KiB
Dart
import '../models/public_participant.dart';
|
|
import '../models/public_team.dart';
|
|
|
|
class PublicRankingUtils {
|
|
static int capScore(int scoreWithHandicap) {
|
|
if (scoreWithHandicap > 300) return 300;
|
|
if (scoreWithHandicap < 0) return 0;
|
|
return scoreWithHandicap;
|
|
}
|
|
|
|
// 평균(핸디 포함), 소수 1자리. 입력된 게임 수 기준으로 계산
|
|
static double participantAverageWithHc(PublicParticipant p) {
|
|
int total = 0;
|
|
int cnt = 0;
|
|
for (int i = 0; i < p.rawScores.length; i++) {
|
|
final raw = p.rawScores[i];
|
|
int hc = (i < p.handicaps.length) ? p.handicaps[i] : 0;
|
|
if (hc == 0) {
|
|
hc = p.handicap;
|
|
}
|
|
if (raw == null) continue;
|
|
total += capScore(raw + hc);
|
|
cnt += 1;
|
|
}
|
|
if (cnt == 0) return 0.0;
|
|
return total / cnt;
|
|
}
|
|
|
|
// 개인별 총점(핸디캡 포함) 계산
|
|
static int participantTotalWithHc(PublicParticipant p) {
|
|
int total = 0;
|
|
for (int i = 0; i < p.rawScores.length; i++) {
|
|
final raw = p.rawScores[i];
|
|
int hc = (i < p.handicaps.length) ? p.handicaps[i] : 0;
|
|
if (hc == 0) {
|
|
// per-game 핸디캡이 없으면 참가자 기본 핸디캡 사용
|
|
hc = p.handicap;
|
|
}
|
|
if (raw == null) continue;
|
|
total += capScore(raw + hc);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// 동점자 순위 처리(1,1,3 방식)
|
|
static List<int> ranksFor(List<int> valuesDesc) {
|
|
if (valuesDesc.isEmpty) return const [];
|
|
final ranks = List<int>.filled(valuesDesc.length, 0);
|
|
int rank = 1;
|
|
for (int i = 0; i < valuesDesc.length; i++) {
|
|
if (i > 0 && valuesDesc[i] == valuesDesc[i - 1]) {
|
|
ranks[i] = ranks[i - 1];
|
|
} else {
|
|
ranks[i] = rank;
|
|
}
|
|
rank = i + 2; // 다음 베이스 랭크
|
|
}
|
|
return ranks;
|
|
}
|
|
|
|
static List<Map<String, dynamic>> individualRanking(List<PublicParticipant> list) {
|
|
// 내림차순 정렬
|
|
final items = list
|
|
.map((p) => {
|
|
'p': p,
|
|
'total': participantTotalWithHc(p),
|
|
})
|
|
.toList();
|
|
items.sort((a, b) => (b['total'] as int).compareTo(a['total'] as int));
|
|
final totals = items.map<int>((e) => e['total'] as int).toList();
|
|
final rs = ranksFor(totals);
|
|
for (int i = 0; i < items.length; i++) {
|
|
items[i]['rank'] = rs[i];
|
|
}
|
|
return items;
|
|
}
|
|
|
|
// 동점자 순위 처리 for double 리스트 (1,1,3 방식)
|
|
static List<int> ranksForDoubles(List<double> valuesDesc) {
|
|
if (valuesDesc.isEmpty) return const [];
|
|
final ranks = List<int>.filled(valuesDesc.length, 0);
|
|
int rank = 1;
|
|
const eps = 1e-9;
|
|
for (int i = 0; i < valuesDesc.length; i++) {
|
|
if (i > 0 && (valuesDesc[i] - valuesDesc[i - 1]).abs() < eps) {
|
|
ranks[i] = ranks[i - 1];
|
|
} else {
|
|
ranks[i] = rank;
|
|
}
|
|
rank = i + 2;
|
|
}
|
|
return ranks;
|
|
}
|
|
|
|
// 평균(핸디 포함) 기준 개인 랭킹 (내림차순). item: {p, total, avg, rank}
|
|
static List<Map<String, dynamic>> individualRankingByAvg(List<PublicParticipant> list) {
|
|
final items = list
|
|
.map((p) => {
|
|
'p': p,
|
|
'total': participantTotalWithHc(p),
|
|
'avg': participantAverageWithHc(p),
|
|
})
|
|
.toList();
|
|
items.sort((a, b) => (b['avg'] as double).compareTo(a['avg'] as double));
|
|
final avgs = items.map<double>((e) => e['avg'] as double).toList();
|
|
final rs = ranksForDoubles(avgs);
|
|
for (int i = 0; i < items.length; i++) {
|
|
items[i]['rank'] = rs[i];
|
|
}
|
|
return items;
|
|
}
|
|
|
|
static List<Map<String, dynamic>> teamRanking(List<PublicTeam> teams) {
|
|
// 팀 총점 = 구성원 총점 합 + 팀 핸디캡 합(여기서는 팀 handicap만 단일 값으로 가정)
|
|
final items = teams
|
|
.map((t) {
|
|
int membersTotal = 0;
|
|
for (final m in t.members) {
|
|
int mt = 0;
|
|
for (int i = 0; i < m.rawScores.length; i++) {
|
|
final raw = m.rawScores[i];
|
|
final hc = (i < m.handicaps.length) ? m.handicaps[i] : 0;
|
|
if (raw == null) continue;
|
|
mt += capScore(raw + hc);
|
|
}
|
|
membersTotal += mt;
|
|
}
|
|
final total = membersTotal + t.handicap;
|
|
return {'team': t, 'total': total};
|
|
})
|
|
.toList();
|
|
items.sort((a, b) => (b['total'] as int).compareTo(a['total'] as int));
|
|
final totals = items.map<int>((e) => e['total'] as int).toList();
|
|
final rs = ranksFor(totals);
|
|
for (int i = 0; i < items.length; i++) {
|
|
items[i]['rank'] = rs[i];
|
|
}
|
|
return items;
|
|
}
|
|
|
|
// 참가자별 총점과 랭크 맵 생성 (participantId -> {total, rank})
|
|
static Map<String, Map<String, int>> buildParticipantRankMap(List<PublicParticipant> list) {
|
|
final ranked = individualRanking(list);
|
|
final map = <String, Map<String, int>>{};
|
|
for (final item in ranked) {
|
|
final p = item['p'] as PublicParticipant;
|
|
map[p.participantId] = {
|
|
'total': item['total'] as int,
|
|
'rank': item['rank'] as int,
|
|
};
|
|
}
|
|
return map;
|
|
}
|
|
|
|
// 팀별 총점 맵 (teamNumber -> {total, rank})
|
|
static Map<int, Map<String, int>> buildTeamRankMap(List<PublicTeam> teams) {
|
|
final ranked = teamRanking(teams);
|
|
final map = <int, Map<String, int>>{};
|
|
for (final item in ranked) {
|
|
final t = item['team'] as PublicTeam;
|
|
map[t.teamNumber] = {
|
|
'total': item['total'] as int,
|
|
'rank': item['rank'] as int,
|
|
};
|
|
}
|
|
return map;
|
|
}
|
|
|
|
// 미배정 참가자 목록 계산
|
|
static List<PublicParticipant> unassignedParticipants(List<PublicParticipant> participants, List<PublicTeam> teams) {
|
|
final assigned = <String>{};
|
|
for (final t in teams) {
|
|
for (final m in t.members) {
|
|
final pid = m.participantId;
|
|
if (pid != null && pid.isNotEmpty) assigned.add(pid);
|
|
}
|
|
}
|
|
return participants.where((p) => !assigned.contains(p.participantId)).toList();
|
|
}
|
|
}
|