Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

feat: 順位計算の実装とトーナメント生成の仮実装 #20

Merged
merged 5 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 20 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ Matz葉がにロボコン 大会運営支援ツール

### サーバーを動作させる

上記必要なものをインストールしてください.
上記必要なものをインストールしてください.

データ保存用の`data.json`を用意してください.

```json
{
"entry": [],
Expand Down Expand Up @@ -43,7 +44,7 @@ bun dev
### Authors/License

| <img src="https://github.com/laminne.png" width="100px"> | <img src="https://github.com/kiharu3112.png" width="100px"> | <img src="https://github.com/tufusa.png" width="100px"> |
|:--------------------------------------------------------:|:-----------------------------------------------------------:|:-------------------------------------------------------:|
| :------------------------------------------------------: | :---------------------------------------------------------: | :-----------------------------------------------------: |
| **laminne (T. YAMAMOTO)**<br>🔧 🦀 | **kiharu3112**<br>🔧 🦀 | **tufusa**<br>🔧 🦀 |

🔧: KCMS/KCMSFの開発
Expand All @@ -70,12 +71,12 @@ MIT License

body: `application/json`

| 項目名 | 型(TS表記) | 説明 | 備考 |
|-------------|----------------------------------|-------------|-----------------------------|
| teamName | `string` | チーム名 | 重複するとエラー |
| members | `[string, string]` | メンバーの名前 | 小学生部門: 1 or 2人 / オープン部門: 1人 |
| isMultiWalk | `boolean` | ロボットが多足歩行型か | |
| category | `"Elementary" or "Open"` (union) | 出場する部門 | |
| 項目名 | 型(TS表記) | 説明 | 備考 |
| ----------- | -------------------------------- | ---------------------- | ---------------------------------------- |
| teamName | `string` | チーム名 | 重複するとエラー |
| members | `[string, string]` | メンバーの名前 | 小学生部門: 1 or 2人 / オープン部門: 1人 |
| isMultiWalk | `boolean` | ロボットが多足歩行型か | |
| category | `"Elementary" or "Open"` (union) | 出場する部門 | |

#### 出力

Expand All @@ -85,10 +86,7 @@ body: `application/json`
{
"id": "39440930485098",
"teamName": "ニカ.reverse()",
"members": [
"木下竹千代",
"織田幸村"
],
"members": ["木下竹千代", "織田幸村"],
"isMultiWalk": false,
"category": "Elementary"
}
Expand All @@ -113,7 +111,7 @@ body: `application/json`
パスパラメータ

- `id`: `string`
- 取り消すエントリーのID
- 取り消すエントリーのID

body: `application/json`

Expand Down Expand Up @@ -141,10 +139,7 @@ body: `application/json`
{
"id": "39440930485098",
"teamName": "ニカ.reverse()",
"members": [
"木下竹千代",
"織田幸村"
],
"members": ["木下竹千代", "織田幸村"],
"isMultiWalk": false,
"category": "Elementary"
}
Expand All @@ -160,7 +155,7 @@ body: `application/json`
パスパラメータ

- `matchType`: `"primary"|"final"`
- 部門名
- 部門名

#### 出力

Expand Down Expand Up @@ -204,16 +199,20 @@ body: `application/json`
- `UNKNOWN_CATEGORY`: 存在しないカテゴリ
- `UNKNOWN_MATCH_TYPE`: 存在しない対戦種類

### `POST /match/{matchType}`
### `POST /match/{matchType}/{category}`

各部門の本選、予選対戦表を生成します
※ 既に生成済みの場合は上書きされます
※ オープン部門の予選対戦表は生成できません(エラーになります)

#### 入力

パスパラメータ

- `matchType`: `"final"|"primary"`
- 部門名
- 部門名
- `category`: `"elementary"|"open"`
- カテゴリ

```json
{}
Expand Down Expand Up @@ -284,7 +283,7 @@ body: `application/json`
パスパラメータ

- id: `string`
- 試合ID
- 試合ID

body: `application/json`

Expand Down
6 changes: 5 additions & 1 deletion src/match/adaptor/dummyRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MatchRepository } from "../service/repository.js";
import { Option, Result } from "@mikuroxina/mini-fn";

export class DummyMatchRepository implements MatchRepository {
private data: Match[];
private readonly data: Match[];

constructor() {
this.data = [];
Expand Down Expand Up @@ -35,4 +35,8 @@ export class DummyMatchRepository implements MatchRepository {
this.data[i] = match;
return Result.ok(match);
}

public async findAll(): Promise<Result.Result<Error, Match[]>> {
return Result.ok(this.data);
}
}
4 changes: 4 additions & 0 deletions src/match/adaptor/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export class JSONMatchRepository implements MatchRepository {
return Option.some(match);
}

public async findAll(): Promise<Result.Result<Error, Match[]>> {
return Result.ok(this.data);
}

public async update(match: Match): Promise<Result.Result<Error, Match>> {
const i = this.data.findIndex((m) => m.id === match.id);
this.data[i] = match;
Expand Down
80 changes: 65 additions & 15 deletions src/match/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,50 @@ export class MatchController {
constructor(
matchService: GenerateMatchService,
editService: EditMatchService,
getService: GetMatchService
getService: GetMatchService,
) {
this.matchService = matchService;
this.editService = editService;
this.getService = getService;
}

async generateMatch(type: string) {
switch (type) {
case "primary":
return await this.generatePrimary();
default:
return Result.err(new Error("unknown match type"));
async generateMatch(
type: string,
category: string,
): Promise<Result.Result<Error, matchJSON[][]>> {
if (type === "primary") {
if (category === "open")
return Result.err(new Error("cant generate open primary matches"));

const res = await this.generatePrimary();
if (Result.isErr(res)) {
return Result.err(new Error("failed to generate primary matches"));
}
return Result.ok(res[1]);
} else if (type === "final") {
const res = await this.generateFinal(category);
if (Result.isErr(res)) {
return Result.err(new Error("failed to generate final matches"));
}
return Result.ok([res[1]]);
}
return Result.err(new Error("invalid match type"));
}

private async generatePrimary() {
private async generatePrimary(): Promise<
Result.Result<Error, matchJSON[][]>
> {
const res = await this.matchService.generatePrimaryMatch();
if (Result.isErr(res)) {
return Result.err(res[1]);
}
return Result.ok(res[1].map((i) => i.map(this.toJSON)));
}

async editMatch(id: string, args: matchUpdateJSON) {
async editMatch(
id: string,
args: matchUpdateJSON,
): Promise<Result.Result<Error, matchJSON>> {
const res = await this.editService.handle(id, args);
if (Result.isErr(res)) {
return Result.err(res[1]);
Expand All @@ -54,11 +73,24 @@ export class MatchController {
if (Result.isErr(res)) {
return Result.err(res[1]);
}
return Result.ok(res[1].map(i => this.toJSON(i.toDomain())));
return Result.ok(res[1].map((i) => this.toJSON(i.toDomain())));
}

private toJSON(i: Match) {
const toTeamJSON = (i?: Entry) => {
async generateFinal(
category: string,
): Promise<Result.Result<Error, matchJSON[]>> {
if (!(category === "elementary" || category === "open")) {
return Result.err(new Error("invalid match type"));
}
const res = await this.matchService.generateFinalMatch(category);
if (Result.isErr(res)) {
return Result.err(res[1]);
}
return Result.ok(res[1].map((v) => this.toJSON(v)));
}

private toJSON(i: Match): matchJSON {
const toTeamJSON = (i?: Entry): matchTeamJSON | undefined => {
if (!i) {
return i;
}
Expand All @@ -67,19 +99,19 @@ export class MatchController {
id: i.id,
teamName: i.teamName,
isMultiWalk: i.isMultiWalk,
category: i.category
category: i.category,
};
};

return {
id: i.id,
teams: {
left: toTeamJSON(i.teams.Left),
right: toTeamJSON(i.teams.Right)
right: toTeamJSON(i.teams.Right),
},
matchType: i.matchType,
courseIndex: i.courseIndex,
results: i.results
results: i.results,
};
}
}
Expand All @@ -103,3 +135,21 @@ interface matchResultFinalPairJSON {
interface matchUpdateJSON {
results: matchResultPairJSON | matchResultFinalPairJSON;
}

interface matchTeamJSON {
id: string;
teamName: string;
isMultiWalk: boolean;
category: string;
}

interface matchJSON {
id: string;
teams: {
left: undefined | matchTeamJSON;
right: undefined | matchTeamJSON;
};
matchType: "primary" | "final";
courseIndex: number;
results: matchResultPairJSON | matchResultFinalPairJSON | undefined;
}
39 changes: 25 additions & 14 deletions src/match/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,19 @@ const entryRepository = await JSONEntryRepository.new();
const generateService = new GenerateMatchService(entryRepository, repository);
const editService = new EditMatchService(repository);
const getService = new GetMatchService(repository);
const controller = new MatchController(generateService, editService, getService);

matchHandler.post("/:match", async (c) => {
const { match } = c.req.param();
const res = await controller.generateMatch(match);
if (Result.isErr(res)) {
return c.json([{ error: res[1].message }]);
}

return c.json(res[1]);
});

const controller = new MatchController(
generateService,
editService,
getService,
);
matchHandler.get("/:type", async (c) => {
const {type} = c.req.param();
const { type } = c.req.param();
const res = await controller.getMatchByType(type);
if (Result.isErr(res)) {
return c.json([{ error: res[1].message }], 400);
}
return c.json(res[1]);
})
});

matchHandler.put("/:match", async (c) => {
const { match } = c.req.param();
Expand All @@ -51,3 +44,21 @@ matchHandler.put("/:match", async (c) => {

return c.json(res[1]);
});

matchHandler.post("/:type/:category", async (c) => {
/*
例:
(elementary, primary) -> 小学生部門 予選対戦表を生成
(elementary, final) -> 小学生部門 決勝トーナメントを生成
(open, primary) -> エラー
(open, final) -> オープン部門 決勝トーナメントを生成

*/
const { type, category } = c.req.param();
const res = await controller.generateMatch(type, category);
if (Result.isErr(res)) {
return c.json([{ error: res[1].message }], 400);
}

return c.json(res[1]);
});
5 changes: 5 additions & 0 deletions src/match/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export type MatchResultFinalPair = {
// じゃんけんで決定したとき用
winnerID: string;
};
export const isMatchResultPair = (
arg: MatchResultPair | MatchResultFinalPair | undefined,
): arg is MatchResultPair => {
return (arg as MatchResultPair).Left !== undefined;
};

export interface CreateMatchArgs {
id: string;
Expand Down
Loading