Skip to content

Commit

Permalink
feat: configの値を使うように
Browse files Browse the repository at this point in the history
  • Loading branch information
laminne committed Sep 8, 2024
1 parent ce7939a commit 4b5d7b7
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 173 deletions.
7 changes: 3 additions & 4 deletions packages/kcms/src/match/model/pre.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface CreatePreMatchArgs {
/** @description 試合番号(1始まり) */
matchIndex: number;
/** @description チーム1のID 左を走るチーム */
teamId1: TeamID;
teamId1?: TeamID;
/** @description チーム2のID 右を走るチーム */
teamId2?: TeamID;
/** @description 走行結果 */
Expand All @@ -29,8 +29,7 @@ export class PreMatch {
private readonly id: PreMatchID;
private readonly courseIndex: number;
private readonly matchIndex: number;
private readonly teamId1: TeamID;
// NOTE: 予選参加者は奇数になる可能性があるので2チーム目はいないことがある
private readonly teamId1?: TeamID;
private readonly teamId2?: TeamID;
private runResults: RunResult[];

Expand Down Expand Up @@ -59,7 +58,7 @@ export class PreMatch {
return this.matchIndex;
}

getTeamId1(): TeamID {
getTeamId1(): TeamID | undefined {
return this.teamId1;
}

Expand Down
63 changes: 43 additions & 20 deletions packages/kcms/src/match/service/generatePre.test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,53 @@
import { describe, expect, it } from 'vitest';
import { GeneratePreMatchService } from './generatePre';
import { DummyRepository } from '../../team/adaptor/dummyRepository';
import { testTeamData } from '../../testData/entry';
import { FetchTeamService } from '../../team/service/get';
import { SnowflakeIDGenerator } from '../../id/main';
import { DummyPreMatchRepository } from '../adaptor/dummy/preMatchRepository';
import { Result } from '@mikuroxina/mini-fn';
import { TeamID } from '../../team/models/team';
import { config } from 'config';

describe('GeneratePreMatchService', () => {
const dummyTeamsName = ['A1', 'A2', 'A3', 'A4', 'B1', 'B2', 'B3', 'C1', 'C2', 'N1', 'N2'];
const generateService = new GeneratePreMatchService();
const teamRepository = new DummyRepository([...testTeamData.values()]);
const fetchService = new FetchTeamService(teamRepository);
const generator = new SnowflakeIDGenerator(1, () =>
BigInt(new Date('2024/01/01 00:00:00 UTC').getTime())
);
const preMatchRepository = new DummyPreMatchRepository();
const generateService = new GeneratePreMatchService(fetchService, generator, preMatchRepository);

it('正しく予選対戦表を生成できる', async () => {
const res = await generateService.handle(dummyTeamsName);

expect(res[0]).toStrictEqual([
const expectedTeamPair = [
[
['A1', 'B3'],
['A4', 'N1'],
['B3', 'A1'],
['N1', 'A4'],
]);
expect(res[1]).toStrictEqual([
['A2', 'C1'],
['A3', 'C2'],
['B1', 'N2'],
['C1', 'A2'],
['B3', 'A1'],
['C2', 'A3'],
['N2', 'B1'],
]);
expect(res[2]).toStrictEqual([
['A3', 'C2'],
['B2', undefined],
[undefined, 'A3'],
['C2', 'B2'],
]);
],
[
['A2', 'B2'],
['A4', 'C1'],
['B2', 'N1'],
['C1', 'A2'],
['N1', 'A4'],
],
];

it('正しく予選対戦表を生成できる', async () => {
const generated = await generateService.handle('elementary');
expect(Result.isOk(generated)).toBe(true);
const res = Result.unwrap(generated);

for (let i = 0; i < config.match.pre.course.elementary; i++) {
const course = res.filter((v) => v.getCourseIndex() === i + 1);
const pair = course.map((v) => [
testTeamData.get(v.getTeamId1() ?? ('' as TeamID))?.getTeamName(),
testTeamData.get(v.getTeamId2() ?? ('' as TeamID))?.getTeamName(),
]);
expect(pair).toStrictEqual(expectedTeamPair[i]);
}
});
});
90 changes: 83 additions & 7 deletions packages/kcms/src/match/service/generatePre.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,92 @@
import { Result } from '@mikuroxina/mini-fn';
import { PreMatch } from '../model/pre';
import { config, DepartmentType } from 'config';
import { FetchTeamService } from '../../team/service/get';
import { Team } from '../../team/models/team';
import { SnowflakeIDGenerator } from '../../id/main';
import { PreMatchRepository } from '../model/repository';

export class GeneratePreMatchService {
private readonly COURSE_COUNT = 3;
constructor(
private readonly fetchTeam: FetchTeamService,
private readonly idGenerator: SnowflakeIDGenerator,
private readonly preMatchRepository: PreMatchRepository
) {}

async handle(departmentType: DepartmentType): Promise<Result.Result<Error, PreMatch[]>> {
if (!config.match.pre.course[departmentType]) {
return Result.err(new Error('DepartmentType is not defined'));
}
const pair = await this.makePairs(departmentType);
return await this.makeMatches(pair);
}

private async makeMatches(
data: (Team | undefined)[][][]
): Promise<Result.Result<Error, PreMatch[]>> {
// 与えられたペアをもとに試合を生成する

// コースごとに生成
const generated = data.map((course, courseIndex) => {
// ペアをもとに試合を生成
return course.map((pair, matchIndex): Result.Result<Error, PreMatch> => {
const id = this.idGenerator.generate<PreMatch>();
if (Result.isErr(id)) {
return id;
}
const match = PreMatch.new({
id: Result.unwrap(id),
// ToDo: 他部門のコースがすでに使用されているときにコース番号をどうするかを考える
courseIndex: courseIndex + 1,
matchIndex: matchIndex + 1,
teamId1: pair[0]?.getId(),
teamId2: pair[1]?.getId(),
runResults: [],
});
return Result.ok(match);
});
});
const flatten = generated.flat();
const match = flatten.filter((v) => Result.isOk(v)).map((v) => Result.unwrap(v));

const res = await this.preMatchRepository.createBulk(match);
if (Result.isErr(res)) {
return res;
}

return Result.ok(match);
}

/**
* チームのペアだけを作る関数
*/
async makePairs(departmentType: DepartmentType): Promise<(Team | undefined)[][][]> {
// 多言語環境でソート可能にするためにcollatorを使う
const collator = new Intl.Collator('ja');
const courseCount = config.match['pre'].course[departmentType];

// エントリー済みのチームを取得
const teamRes = await this.fetchTeam.findAll();
if (Result.isErr(teamRes)) {
return [];
}
const team = Result.unwrap(teamRes).filter(
(v) => v.getIsEntered() && v.getDepartmentType() === departmentType
);

async handle(data: string[]): Promise<(string | undefined)[][][]> {
// チームをクラブ名でソートする
const teams = data.sort();
// チームをクラブ名でソートする (ToDo: クラブ名がない場合にどこの位置に動かすかを決める必要がありそう
const teams = team.sort((a, b) =>
collator.compare(a.getClubName() ?? 'N', b.getClubName() ?? 'N')
);

// コースの数でスライスする
// 初期化時に必要な個数作っておく
const slicedTeams: string[][] = new Array(Math.ceil(teams.length / this.COURSE_COUNT)).fill([]);
for (let i = 0; i < Math.ceil(teams.length / this.COURSE_COUNT); i++) {
const slicedTeams: Team[][] = new Array(
Math.ceil(teams.length / config.match['pre'].course[departmentType])
).fill([]);
for (let i = 0; i < Math.ceil(teams.length / courseCount); i++) {
// コース数
slicedTeams[i] = teams.slice(i * this.COURSE_COUNT, (i + 1) * this.COURSE_COUNT);
slicedTeams[i] = teams.slice(i * courseCount, (i + 1) * courseCount);
}

/* スライスされた配列を転置する
Expand Down
18 changes: 10 additions & 8 deletions packages/kcms/src/team/controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { TeamRepository } from './models/repository.js';
import { CreateTeamService } from './service/createTeam';
import { Result, Option } from '@mikuroxina/mini-fn';
import { FindEntryService } from './service/get.js';
import { FetchTeamService } from './service/get.js';
import { DeleteEntryService } from './service/delete.js';
import { SnowflakeIDGenerator } from '../id/main.js';
import { DepartmentType } from 'config';

interface baseEntry {
id: string;
Expand All @@ -15,15 +16,15 @@ interface baseEntry {

export class Controller {
private readonly createTeam: CreateTeamService;
private readonly findEntry: FindEntryService;
private readonly findEntry: FetchTeamService;
private readonly deleteService: DeleteEntryService;

constructor(repository: TeamRepository) {
this.createTeam = new CreateTeamService(
repository,
new SnowflakeIDGenerator(1, () => BigInt(new Date().getTime()))
);
this.findEntry = new FindEntryService(repository);
this.findEntry = new FetchTeamService(repository);
this.deleteService = new DeleteEntryService(repository);
}

Expand All @@ -32,6 +33,7 @@ export class Controller {
members: string[];
isMultiWalk: boolean;
category: 'Elementary' | 'Open';
departmentType: DepartmentType;
}): Promise<Result.Result<Error, baseEntry>> {
const res = await this.createTeam.create(args);
if (Result.isErr(res)) {
Expand All @@ -57,11 +59,11 @@ export class Controller {
return Result.ok(
res[1].map((v) => {
return {
id: v.id,
teamName: v.teamName,
members: v.members,
isMultiWalk: v.isMultiWalk,
category: v.category,
id: v.getId(),
teamName: v.getTeamName(),
members: v.getMembers(),
isMultiWalk: v.getIsMultiWalk(),
category: v.getCategory(),
};
})
);
Expand Down
2 changes: 2 additions & 0 deletions packages/kcms/src/team/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ entryHandler.post('/', zValidator('json', entryRequestSchema), async (c) => {
members,
isMultiWalk,
category,
departmentType: category === 'Open' ? 'open' : 'elementary',
});
if (Result.isErr(res)) {
return c.json({ error: errorToCode(res[1]) }, 400);
Expand Down Expand Up @@ -48,6 +49,7 @@ entryHandler.post('/bulk', zValidator('json', bulkEntryRequestSchema), async (c)
members: v.members,
isMultiWalk: v.isMultiWalk,
category: v.category,
departmentType: v.category === 'Open' ? 'open' : 'elementary',
});
if (Result.isErr(res)) {
return c.json({ error: errorToCode(res[1]) }, 400);
Expand Down
4 changes: 4 additions & 0 deletions packages/kcms/src/team/models/team.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe('正しくインスタンスを生成できる', () => {
members: ['山田太郎', 'テスト大介'],
isMultiWalk: false,
category: 'Open',
departmentType: 'open',
isEntered: true,
});

Expand All @@ -26,6 +27,7 @@ describe('正しくインスタンスを生成できる', () => {
members: ['山田太郎', 'テスト大介'],
isMultiWalk: false,
category: 'Open',
departmentType: 'open',
isEntered: true,
clubName: 'テストクラブ',
});
Expand All @@ -45,6 +47,7 @@ describe('正しくインスタンスを生成できる', () => {
members: ['山田太郎', 'テスト大介'],
isMultiWalk: false,
category: 'Open',
departmentType: 'open',
});

team.enter();
Expand All @@ -59,6 +62,7 @@ describe('正しくインスタンスを生成できる', () => {
members: ['山田太郎', 'テスト大介'],
isMultiWalk: false,
category: 'Open',
departmentType: 'open',
});

team.enter();
Expand Down
13 changes: 12 additions & 1 deletion packages/kcms/src/team/models/team.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Elementary: 小学生部門 / Open: オープン部門
// ToDo: 部門の定義をファイルから読み込むようにする
import { SnowflakeID } from '../../id/main.js';
import { DepartmentType } from 'config';

/**
* @deprecated ToDo: Configで設定されている値を使う
* @deprecated この型は廃止予定. {@link DepartmentType}を使うこと
*/
export type Department = 'Elementary' | 'Open';
export type TeamID = SnowflakeID<Team>;
Expand All @@ -20,6 +21,7 @@ export interface TeamCreateArgs {
* @deprecated ToDo: Configで設定されている値を使う
*/
category: Department;
departmentType: DepartmentType;
clubName?: string;
/**
* 当日参加するかどうか (エントリーしたかどうか)
Expand All @@ -33,6 +35,7 @@ export class Team {
private readonly members: Array<string>;
private readonly isMultiWalk: boolean;
private readonly category: Department;
private readonly depatmentType: DepartmentType;
private readonly clubName?: string;
private isEntered: boolean;

Expand All @@ -42,6 +45,7 @@ export class Team {
_members: Array<string>,
_isMultiWalk: boolean,
category: Department,
departmentType: DepartmentType,
isEntered: boolean,
clubName?: string
) {
Expand All @@ -52,6 +56,7 @@ export class Team {
this.category = category;
this.clubName = clubName;
this.isEntered = isEntered;
this.depatmentType = departmentType;
}

getId(): TeamID {
Expand All @@ -77,6 +82,10 @@ export class Team {
return this.category;
}

getDepartmentType(): DepartmentType {
return this.depatmentType;
}

getClubName(): string | undefined {
return this.clubName;
}
Expand Down Expand Up @@ -109,6 +118,7 @@ export class Team {
arg.members,
arg.isMultiWalk,
arg.category,
arg.departmentType,
false,
arg.clubName
);
Expand All @@ -121,6 +131,7 @@ export class Team {
arg.members,
arg.isMultiWalk,
arg.category,
arg.departmentType,
arg.isEntered,
arg.clubName
);
Expand Down
Loading

0 comments on commit 4b5d7b7

Please sign in to comment.