chess-board/src/client.ts
2022-01-07 22:25:01 -06:00

172 lines
3.8 KiB
TypeScript

import { Coordinates } from "./boardState";
import { Selected } from "./Board";
import BoardState, { fromWireFormat } from "./boardState";
import { COLOR, PIECE } from "./primitives";
import validateBoardResponse from "./boardResponse.validator";
import validateStartMessage from "./startMessage.validator";
import GAME_STATE from "./gameState";
export type ValidBoardResponse = {
boardState: BoardState;
gameState: GAME_STATE;
};
export type StartGame = (
playerColor: COLOR
) => Promise<[string, BoardState] | null>;
export type MakeMove = (
selected: Selected,
destination: Coordinates,
gameId: string,
kind?: PIECE
) => Promise<void>;
export type Poll = (gameId: string) => Promise<ValidBoardResponse | null>;
export type Api = {
startGame: StartGame;
makeMove: MakeMove;
poll: Poll;
};
export type ApiMessage<T> = {
data: T;
gameId: string;
};
const apiMessage = <T>(gameId: string, data: T): ApiMessage<T> => {
return {
data,
gameId,
};
};
const makeBody = <T>(gameId: string, data: T): string => {
return JSON.stringify(apiMessage(gameId, data));
};
const init = (baseUri: string): Api => {
return {
startGame: startGame(baseUri),
makeMove: makeMove(baseUri),
poll: poll(baseUri),
};
};
const startGame = (baseUri: string): StartGame => {
return async (playerColor: COLOR): Promise<[string, BoardState] | null> => {
const startUri = `${baseUri}/start`;
const payload = {
playerColor,
};
const fetchOptions = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
};
try {
const response = await fetch(startUri, fetchOptions);
if (!response.ok) {
console.error("Failed to start game,", response.status);
return null;
}
const json: unknown = await response.json();
const startMessage = validateStartMessage(json);
return [startMessage.gameId, fromWireFormat(startMessage.board)];
} catch (error) {
console.error("Failed to start game,", error);
return null;
}
};
};
const makeMove = (baseUri: string): MakeMove => {
return async (
selected: Selected,
destination: Coordinates,
gameId: string,
kind?: PIECE
): Promise<void> => {
const moveUri = `${baseUri}/move`;
const body = makeBody(gameId, {
piece: selected.piece,
from: selected.location,
to: destination,
kind,
});
const fetchOptions = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body,
};
try {
await fetch(moveUri, fetchOptions);
} catch (error) {
console.error("Failed to make move,", error);
}
};
};
const poll = (baseUri: string): Poll => {
return async (gameId: string) => {
const pollUri = `${baseUri}/poll/${gameId}`;
const fetchOptions = {
method: "GET",
headers: {
Accept: "application/json",
},
};
try {
const result = await fetch(pollUri, fetchOptions);
if (!result.ok) {
if (result.status >= 400 && result.status < 500) {
throw new Error("Invalid status for long poll");
}
console.error("Failed to long poll,", result.status);
return null;
}
if (result.status === 204) {
return null;
}
const json: unknown = await result.json();
const boardResponse = validateBoardResponse(json);
const boardState = fromWireFormat(boardResponse.boardState);
return {
boardState,
gameState: boardResponse.gameState,
};
} catch (error) {
console.error("Failed to long poll,", error);
throw new Error("Failed to long poll");
}
};
};
export { init };