공개페이지
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Intentionally minimal; no platform imports to keep tests stable
|
||||
|
||||
// Minimal web storage wrapper. On web uses window.localStorage; otherwise in-memory.
|
||||
// Avoids flutter_secure_storage_web to keep WebAssembly warnings out.
|
||||
class WebStorage {
|
||||
static final Map<String, String> _memory = <String, String>{};
|
||||
|
||||
static String? getItem(String key) {
|
||||
return _memory[key];
|
||||
}
|
||||
|
||||
static void setItem(String key, String value) {
|
||||
_memory[key] = value;
|
||||
}
|
||||
|
||||
static void removeItem(String key) {
|
||||
_memory.remove(key);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user