Ben Ashton
2 years ago
commit
f08b7da487
41 changed files with 27200 additions and 0 deletions
@ -0,0 +1,20 @@
|
||||
module.exports = { |
||||
"env": { |
||||
"es2021": true, |
||||
"node": true |
||||
}, |
||||
"extends": [ |
||||
"eslint:recommended", |
||||
"plugin:@typescript-eslint/recommended" |
||||
], |
||||
"parser": "@typescript-eslint/parser", |
||||
"parserOptions": { |
||||
"ecmaVersion": "latest", |
||||
"sourceType": "module" |
||||
}, |
||||
"plugins": [ |
||||
"@typescript-eslint" |
||||
], |
||||
"rules": { |
||||
} |
||||
} |
@ -0,0 +1,130 @@
|
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
lerna-debug.log* |
||||
.pnpm-debug.log* |
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html) |
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
||||
|
||||
# Runtime data |
||||
pids |
||||
*.pid |
||||
*.seed |
||||
*.pid.lock |
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
lib-cov |
||||
|
||||
# Coverage directory used by tools like istanbul |
||||
coverage |
||||
*.lcov |
||||
|
||||
# nyc test coverage |
||||
.nyc_output |
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
||||
.grunt |
||||
|
||||
# Bower dependency directory (https://bower.io/) |
||||
bower_components |
||||
|
||||
# node-waf configuration |
||||
.lock-wscript |
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||
build/Release |
||||
|
||||
# Dependency directories |
||||
node_modules/ |
||||
jspm_packages/ |
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/) |
||||
web_modules/ |
||||
|
||||
# TypeScript cache |
||||
*.tsbuildinfo |
||||
|
||||
# Optional npm cache directory |
||||
.npm |
||||
|
||||
# Optional eslint cache |
||||
.eslintcache |
||||
|
||||
# Optional stylelint cache |
||||
.stylelintcache |
||||
|
||||
# Microbundle cache |
||||
.rpt2_cache/ |
||||
.rts2_cache_cjs/ |
||||
.rts2_cache_es/ |
||||
.rts2_cache_umd/ |
||||
|
||||
# Optional REPL history |
||||
.node_repl_history |
||||
|
||||
# Output of 'npm pack' |
||||
*.tgz |
||||
|
||||
# Yarn Integrity file |
||||
.yarn-integrity |
||||
|
||||
# dotenv environment variable files |
||||
.env |
||||
.env.development.local |
||||
.env.test.local |
||||
.env.production.local |
||||
.env.local |
||||
|
||||
# parcel-bundler cache (https://parceljs.org/) |
||||
.cache |
||||
.parcel-cache |
||||
|
||||
# Next.js build output |
||||
.next |
||||
out |
||||
|
||||
# Nuxt.js build / generate output |
||||
.nuxt |
||||
dist |
||||
|
||||
# Gatsby files |
||||
.cache/ |
||||
# Comment in the public line in if your project uses Gatsby and not Next.js |
||||
# https://nextjs.org/blog/next-9-1#public-directory-support |
||||
# public |
||||
|
||||
# vuepress build output |
||||
.vuepress/dist |
||||
|
||||
# vuepress v2.x temp and cache directory |
||||
.temp |
||||
.cache |
||||
|
||||
# Docusaurus cache and generated files |
||||
.docusaurus |
||||
|
||||
# Serverless directories |
||||
.serverless/ |
||||
|
||||
# FuseBox cache |
||||
.fusebox/ |
||||
|
||||
# DynamoDB Local files |
||||
.dynamodb/ |
||||
|
||||
# TernJS port file |
||||
.tern-port |
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions |
||||
.vscode-test |
||||
|
||||
# yarn v2 |
||||
.yarn/cache |
||||
.yarn/unplugged |
||||
.yarn/build-state.yml |
||||
.yarn/install-state.gz |
||||
.pnp.* |
Binary file not shown.
@ -0,0 +1,24 @@
|
||||
{ |
||||
"name": "snakes_and_ladders", |
||||
"version": "1.0.0", |
||||
"description": "", |
||||
"main": "main.js", |
||||
"type": "module", |
||||
"scripts": { |
||||
"test": "echo \"Error: no test specified\" && exit 1", |
||||
"build": "npx tsc", |
||||
"start": "node ./dist/main.js", |
||||
"sample": "node ./dist/main.js \"./sample_data/tournament_board.json\" \"./sample_data/game_logs.csv\"" |
||||
}, |
||||
"author": "Ben Ashton", |
||||
"license": "ISC", |
||||
"devDependencies": { |
||||
"@types/node": "^17.0.35", |
||||
"@typescript-eslint/eslint-plugin": "^5.26.0", |
||||
"@typescript-eslint/parser": "^5.26.0", |
||||
"eslint": "^8.16.0", |
||||
"eslint-config-prettier": "^8.5.0", |
||||
"prettier": "2.6.2", |
||||
"typescript": "^4.7.2" |
||||
} |
||||
} |
@ -0,0 +1,23 @@
|
||||
{ |
||||
"size": 100, |
||||
"ladders": [ |
||||
{ "landing": 4, "destination": 10 }, |
||||
{ "landing": 6, "destination": 14 }, |
||||
{ "landing": 31, "destination": 40 }, |
||||
{ "landing": 32, "destination": 34 }, |
||||
{ "landing": 48, "destination": 55 }, |
||||
{ "landing": 57, "destination": 58 }, |
||||
{ "landing": 77, "destination": 87 }, |
||||
{ "landing": 79, "destination": 80 } |
||||
], |
||||
"snakes": [ |
||||
{ "landing": 13, "destination": 9 }, |
||||
{ "landing": 21, "destination": 14 }, |
||||
{ "landing": 28, "destination": 11 }, |
||||
{ "landing": 39, "destination": 33 }, |
||||
{ "landing": 62, "destination": 43 }, |
||||
{ "landing": 65, "destination": 63 }, |
||||
{ "landing": 66, "destination": 60 }, |
||||
{ "landing": 75, "destination": 71 } |
||||
] |
||||
} |
@ -0,0 +1,28 @@
|
||||
{ |
||||
"folders": |
||||
[ |
||||
{ |
||||
"path": "/home/ben/Programming/Work/AuditSoft/snakes_and_ladders" |
||||
} |
||||
], |
||||
"settings": |
||||
{ |
||||
"LSP": |
||||
{ |
||||
"lsp-typescript": |
||||
{ |
||||
"enabled": false |
||||
}, |
||||
"typescript-language-server": |
||||
{ |
||||
"enabled": true |
||||
} |
||||
}, |
||||
"on_pre_save_language": |
||||
[ |
||||
{ |
||||
"command": "js_prettier" |
||||
} |
||||
] |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
export class BaseError extends Error {} |
@ -0,0 +1,124 @@
|
||||
import { readFile } from "fs/promises"; |
||||
import { BoardLoadError } from "./errors/board_load_error.js"; |
||||
|
||||
interface ISnakeOrLadder { |
||||
landing: number; |
||||
destination: number; |
||||
} |
||||
|
||||
export interface IBoardData { |
||||
size: number; |
||||
ladders: ISnakeOrLadder[]; |
||||
snakes: ISnakeOrLadder[]; |
||||
} |
||||
|
||||
export class Board { |
||||
public size: number; |
||||
private snakeMap: Map<number, number> = new Map(); |
||||
private ladderMap: Map<number, number> = new Map(); |
||||
|
||||
static async loadFromFile( |
||||
filename: string, |
||||
encoding: BufferEncoding = "utf-8" |
||||
) { |
||||
let rawData; |
||||
try { |
||||
rawData = await readFile(filename, { encoding }); |
||||
} catch (err) { |
||||
throw new BoardLoadError(`Unable to read file: ${filename}`); |
||||
} |
||||
|
||||
let jsonData; |
||||
try { |
||||
jsonData = JSON.parse(rawData); |
||||
} catch (err) { |
||||
throw new BoardLoadError("Invalid JSON"); |
||||
} |
||||
|
||||
if (typeof jsonData !== "object") { |
||||
throw new BoardLoadError("JSON does not contain board object"); |
||||
} |
||||
|
||||
if ( |
||||
!("ladders" in jsonData && "snakes" in jsonData && "size" in jsonData) |
||||
) { |
||||
throw new BoardLoadError("Missing properties in board object"); |
||||
} |
||||
|
||||
if (!Number.isInteger(jsonData.size)) { |
||||
throw new BoardLoadError("Invalid board size"); |
||||
} |
||||
|
||||
const boardData: IBoardData = { |
||||
size: jsonData.size, |
||||
snakes: [], |
||||
ladders: [], |
||||
}; |
||||
|
||||
const props: Array<"snakes" | "ladders"> = ["snakes", "ladders"]; |
||||
for (const prop of props) { |
||||
const transportData = jsonData[prop]; |
||||
|
||||
if (!Array.isArray(transportData)) { |
||||
throw new BoardLoadError(`Invalid "${prop}" property in board object`); |
||||
} |
||||
|
||||
for (const transport of transportData) { |
||||
if ( |
||||
typeof transport !== "object" || |
||||
!("landing" in transport) || |
||||
!Number.isInteger(transport.landing) || |
||||
!("destination" in transport) || |
||||
!Number.isInteger(transport.destination) |
||||
) { |
||||
const repr = JSON.stringify(transport); |
||||
// Remove plural
|
||||
const transportType = prop.replace(/s$/, prop); |
||||
throw new BoardLoadError(`Invalid ${transportType} entry: ${repr}`); |
||||
} |
||||
|
||||
boardData[prop].push({ |
||||
landing: transport.landing, |
||||
destination: transport.destination, |
||||
}); |
||||
} |
||||
} |
||||
|
||||
return new Board(boardData); |
||||
} |
||||
|
||||
constructor(boardData: IBoardData) { |
||||
this.size = boardData.size; |
||||
for (const transport of boardData.snakes) { |
||||
if (transport.landing <= transport.destination) { |
||||
throw new BoardLoadError( |
||||
"Invalid snake, landing not greater than destination" |
||||
); |
||||
} |
||||
this.snakeMap.set(transport.landing, transport.destination); |
||||
} |
||||
for (const transport of boardData.ladders) { |
||||
if (transport.landing >= transport.destination) { |
||||
throw new BoardLoadError( |
||||
"Invalid ladder, landing not less than destination" |
||||
); |
||||
} |
||||
this.ladderMap.set(transport.landing, transport.destination); |
||||
} |
||||
} |
||||
|
||||
tileHasSnake(pos: number) { |
||||
return this.snakeMap.has(pos); |
||||
} |
||||
|
||||
tileHasLadder(pos: number) { |
||||
return this.ladderMap.has(pos); |
||||
} |
||||
|
||||
calculateLandingPosition(pos: number) { |
||||
return Math.min( |
||||
this.ladderMap.get(pos) ?? this.snakeMap.get(pos) ?? pos, |
||||
this.size |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
|
||||
import { GameError } from "./game_error.js"; |
||||
|
||||
export class BoardLoadError extends GameError { |
||||
constructor(message: string, options?: ErrorOptions) { |
||||
super(`Unable to load board: ${message}`, options); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
|
||||
import { GameError } from "./game_error.js"; |
||||
|
||||
export class DuplicatePlayerError extends GameError { |
||||
constructor(gameId: number, playerId: number, options?: ErrorOptions) { |
||||
super(`Game: ${gameId} already has player ${playerId}`, options); |
||||
} |
||||
} |
@ -0,0 +1,11 @@
|
||||
import { GameError } from "./game_error.js"; |
||||
|
||||
export class GameCompleteError extends GameError { |
||||
constructor(gameId: number, playerId: number, options?: ErrorOptions) { |
||||
super( |
||||
`Player: ${playerId} attempted to participate in game: ${gameId}, but ` + |
||||
`this game has finished`, |
||||
options |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,2 @@
|
||||
import { BaseError } from "../../common_errors/base_error.js"; |
||||
export class GameError extends BaseError {} |
@ -0,0 +1,16 @@
|
||||
import { GameError } from "./game_error.js"; |
||||
|
||||
export class NotParticipatingError extends GameError { |
||||
constructor( |
||||
gameId: number, |
||||
playerId: number, |
||||
action: string, |
||||
options?: ErrorOptions |
||||
) { |
||||
super( |
||||
`Attempted to perform action: "${action}" with player: ${playerId} ` + |
||||
`despite them not being a participent of game: ${gameId}`, |
||||
options |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,11 @@
|
||||
import { GameError } from "./game_error.js"; |
||||
|
||||
export class OutOfTurnError extends GameError { |
||||
constructor(gameId: number, playerId: number, options?: ErrorOptions) { |
||||
super( |
||||
`Player: ${playerId} attempted to roll dice out of turn in game: ` + |
||||
`${gameId}`, |
||||
options |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,3 @@
|
||||
export class GameEvent { |
||||
constructor(public gameId: number) {} |
||||
} |
@ -0,0 +1,3 @@
|
||||
import { GameEvent } from "./game_event.js"; |
||||
|
||||
export class GameStartedEvent extends GameEvent {} |
@ -0,0 +1,7 @@
|
||||
import { GameEvent } from "./game_event.js"; |
||||
|
||||
export class PlayerJoinedEvent extends GameEvent { |
||||
constructor(gameId: number, public playerId: number) { |
||||
super(gameId); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
|
||||
import { GameEvent } from "./game_event.js"; |
||||
|
||||
export class PlayerRolledEvent extends GameEvent { |
||||
constructor(gameId: number, public playerId: number, public roll: number) { |
||||
super(gameId); |
||||
} |
||||
} |
@ -0,0 +1,110 @@
|
||||
import { Board } from "./board.js"; |
||||
|
||||
import { Player } from "./player.js"; |
||||
|
||||
import { DuplicatePlayerError } from "./errors/duplicate_player_error.js"; |
||||
import { NotParticipatingError } from "./errors/not_participating_error.js"; |
||||
import { OutOfTurnError } from "./errors/out_of_turn_error.js"; |
||||
import { GameCompleteError } from "./errors/game_complete_error.js"; |
||||
|
||||
class PlayerData { |
||||
pos: number = 0; |
||||
snakeCount: number = 0; |
||||
ladderCount: number = 0; |
||||
} |
||||
|
||||
export class Game { |
||||
public players: Map<Player, PlayerData> = new Map(); |
||||
private winningPlayer: Player | undefined; |
||||
private nextPlayer: Player | undefined; |
||||
|
||||
constructor(public gameId: number, public board: Board) {} |
||||
|
||||
get playerCount() { |
||||
return this.players.size; |
||||
} |
||||
|
||||
get winner() { |
||||
return this.winningPlayer; |
||||
} |
||||
|
||||
get isComplete() { |
||||
return this.winningPlayer !== undefined; |
||||
} |
||||
|
||||
addPlayer(player: Player) { |
||||
if (this.hasPlayer(player)) { |
||||
new DuplicatePlayerError(this.gameId, player.playerId); |
||||
} |
||||
this.players.set(player, new PlayerData()); |
||||
} |
||||
|
||||
hasPlayer(player: Player) { |
||||
return this.players.has(player); |
||||
} |
||||
|
||||
rollDice(player: Player, roll: number) { |
||||
const playerData = this.players.get(player); |
||||
if (!playerData) { |
||||
throw new NotParticipatingError( |
||||
this.gameId, |
||||
player.playerId, |
||||
"roll dice" |
||||
); |
||||
} |
||||
|
||||
if (this.nextPlayer && this.nextPlayer !== player) { |
||||
throw new OutOfTurnError(this.gameId, player.playerId); |
||||
} |
||||
|
||||
if (this.winningPlayer) { |
||||
throw new GameCompleteError(this.gameId, player.playerId); |
||||
} |
||||
|
||||
const rollPos = playerData.pos + roll; |
||||
|
||||
if (this.board.tileHasLadder(rollPos)) { |
||||
playerData.ladderCount++; |
||||
} else if (this.board.tileHasSnake(rollPos)) { |
||||
playerData.snakeCount++; |
||||
} |
||||
|
||||
playerData.pos = this.board.calculateLandingPosition(rollPos); |
||||
if (playerData.pos >= this.board.size) { |
||||
this.winningPlayer = player; |
||||
} |
||||
|
||||
this.nextTurn(player); |
||||
} |
||||
|
||||
ladderCountForPlayer(player: Player) { |
||||
const playerData = this.players.get(player); |
||||
if (!playerData) { |
||||
throw new NotParticipatingError( |
||||
this.gameId, |
||||
player.playerId, |
||||
"get ladder count" |
||||
); |
||||
} |
||||
return playerData.ladderCount; |
||||
} |
||||
|
||||
snakeCountForPlayer(player: Player) { |
||||
const playerData = this.players.get(player); |
||||
if (!playerData) { |
||||
throw new NotParticipatingError( |
||||
this.gameId, |
||||
player.playerId, |
||||
"get snake count" |
||||
); |
||||
} |
||||
return playerData.snakeCount; |
||||
} |
||||
|
||||
private nextTurn(currentPlayer: Player) { |
||||
const players = Array.from(this.players.keys()); |
||||
const playerIndex = players.indexOf(currentPlayer); |
||||
const nextIndex = playerIndex === players.length - 1 ? 0 : playerIndex + 1; |
||||
this.nextPlayer = players[nextIndex]; |
||||
} |
||||
} |
@ -0,0 +1,33 @@
|
||||
import { Game } from "./game.js"; |
||||
import { GameError } from "./errors/game_error.js"; |
||||
import { NotParticipatingError } from "./errors/not_participating_error.js"; |
||||
|
||||
export class Player { |
||||
public games: Game[] = []; |
||||
private rollCount = 0; |
||||
|
||||
constructor(public playerId: number) {} |
||||
|
||||
get rolls() { |
||||
return this.rollCount; |
||||
} |
||||
|
||||
join(game: Game) { |
||||
if (this.games.includes(game)) { |
||||
throw new GameError( |
||||
`Player: ${this.playerId} attempted to join the same game multiple ` + |
||||
`times` |
||||
); |
||||
} |
||||
this.games.push(game); |
||||
} |
||||
|
||||
rollDice(game: Game, roll: number) { |
||||
if (!this.games.includes(game)) { |
||||
throw new NotParticipatingError(game.gameId, this.playerId, "roll dice"); |
||||
} |
||||
|
||||
this.rollCount++; |
||||
game.rollDice(this, roll); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
|
||||
import { BaseError } from "../../common_errors/base_error.js"; |
||||
|
||||
export class GamesLogError extends BaseError { |
||||
constructor(message?: string, options?: ErrorOptions | undefined) { |
||||
super(message || "Unable to parse game log", options); |
||||
} |
||||
} |
@ -0,0 +1,68 @@
|
||||
import { GamesLogError } from "./errors/games_log_error.js"; |
||||
import { GamesLogParser, LogData } from "./games_log_parser.js"; |
||||
|
||||
import { GameEvent } from "../game/events/game_event.js"; |
||||
import { GameStartedEvent } from "../game/events/game_started_event.js"; |
||||
import { PlayerJoinedEvent } from "../game/events/player_joined_event.js"; |
||||
import { PlayerRolledEvent } from "../game/events/player_rolled_event.js"; |
||||
|
||||
export class GamesLog { |
||||
private logData: LogData = []; |
||||
private gamesLogParser = new GamesLogParser(); |
||||
|
||||
async loadFile(filename: string, encoding?: BufferEncoding) { |
||||
this.logData = await this.gamesLogParser.parseFile(filename, encoding); |
||||
} |
||||
|
||||
async readEvents() { |
||||
return this.logData.map((row, rowNumber) => { |
||||
const payload = row.eventPayload; |
||||
const requireProperties = this.requirePropertiesFor(payload, rowNumber); |
||||
|
||||
let gameEvent: GameEvent; |
||||
switch (row.eventType) { |
||||
case "player_rolls_dice": |
||||
requireProperties("gameId", "playerId", "roll"); |
||||
gameEvent = new PlayerRolledEvent( |
||||
payload.gameId, |
||||
payload.playerId, |
||||
payload.roll |
||||
); |
||||
break; |
||||
case "player_joins_game": |
||||
requireProperties("gameId", "playerId"); |
||||
gameEvent = new PlayerJoinedEvent(payload.gameId, payload.playerId); |
||||
break; |
||||
case "game_started": |
||||
requireProperties("gameId"); |
||||
gameEvent = new GameStartedEvent(payload.gameId); |
||||
break; |
||||
default: |
||||
throw new Error( |
||||
`Unknown event type: "${payload.eventType}" on row ${rowNumber}` |
||||
); |
||||
} |
||||
|
||||
return gameEvent; |
||||
}); |
||||
} |
||||
|
||||
private requirePropertiesFor( |
||||
payload: { [key: string]: any }, |
||||
rowNumber: number |
||||
) { |
||||
return (...props: string[]) => |
||||
props.forEach((name) => { |
||||
if (!(name in payload)) { |
||||
throw new GamesLogError( |
||||
`Missing property: ${name} for event on row ${rowNumber}` |
||||
); |
||||
} |
||||
if (!Number.isInteger(payload[name])) { |
||||
throw new GamesLogError( |
||||
`Invalid value for: ${name} on row ${rowNumber}` |
||||
); |
||||
} |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,58 @@
|
||||
import { readFile } from "fs/promises"; |
||||
|
||||
import { CSVParser } from "../utils/csv_parser/csv_parser.js"; |
||||
import { GamesLogError } from "./errors/games_log_error.js"; |
||||
|
||||
export type LogData = { |
||||
eventType: string; |
||||
eventPayload: { [key: string]: any }; |
||||
}[]; |
||||
|
||||
export class GamesLogParser { |
||||
async parseFile(filename: string, encoding: BufferEncoding = "utf-8") { |
||||
let data; |
||||
try { |
||||
data = await readFile(filename, { encoding }); |
||||
} catch (err) { |
||||
throw new GamesLogError(`Unable to read log file: ${filename}`, { |
||||
cause: err instanceof Error ? err : undefined, |
||||
}); |
||||
} |
||||
return this.parse(data); |
||||
} |
||||
|
||||
parse(data: string): LogData { |
||||
const parser = new CSVParser({ headers: true }); |
||||
const csvData = parser.parse(data); |
||||
|
||||
return csvData.map((row, rowNumber) => { |
||||
if (!("event_type" in row)) { |
||||
throw new GamesLogError(`Missing event type on row: ${rowNumber}`); |
||||
} |
||||
if (!("event_payload" in row)) { |
||||
throw new GamesLogError(`Missing event payload on row: ${rowNumber}`); |
||||
} |
||||
|
||||
let eventPayload; |
||||
try { |
||||
eventPayload = JSON.parse(row.event_payload); |
||||
} catch (err) { |
||||
throw new GamesLogError( |
||||
"Unable to parse event payload on row: ${rowNumber}", |
||||
{ |
||||
cause: err instanceof Error ? err : undefined, |
||||
} |
||||
); |
||||
} |
||||
|
||||
if (typeof eventPayload !== "object") { |
||||
throw new GamesLogError("Invalid event payload on row: ${rowNumber}"); |
||||
} |
||||
|
||||
return { |
||||
eventType: row.event_type, |
||||
eventPayload, |
||||
}; |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,58 @@
|
||||
import { GamesLog } from "./logging/games_log.js"; |
||||
import { Tournament } from "./tournament/tournament.js"; |
||||
import { Board } from "./game/board.js"; |
||||
import { RankingReport } from "./reports/ranking_report.js"; |
||||
import { BaseError } from "./common_errors/base_error.js"; |
||||
import { ArgParse } from "./utils/arg_parse.js"; |
||||
|
||||
function printUsage() { |
||||
console.log("Usage: npm start -- boardFile gameLogFile [options]"); |
||||
console.log(" options:"); |
||||
console.log(" -m --maxPlayersPerGame Maximum players per game"); |
||||
console.log(" -d --debug Print stack trace on error"); |
||||
} |
||||
|
||||
async function main() { |
||||
// Validate command line arguments
|
||||
const boardFilename = ArgParse.getStringArg(2); |
||||
if (!boardFilename) return printUsage(); |
||||
|
||||
const gameLogFilename = ArgParse.getStringArg(3); |
||||
if (!gameLogFilename) return printUsage(); |
||||
|
||||
const maxPlayersPerGame = |
||||
ArgParse.getIntegerArg("m", "maxPlayersPerGame") ?? 2; |
||||
|
||||
// Set up tournament
|
||||
const board = await Board.loadFromFile(boardFilename); |
||||
const tournament = new Tournament(board, { maxPlayersPerGame }); |
||||
|
||||
// Retrieve game events
|
||||
const gamesLog = new GamesLog(); |
||||
await gamesLog.loadFile(gameLogFilename); |
||||
const gameEvents = await gamesLog.readEvents(); |
||||
|
||||
// Process game events
|
||||
tournament.processEvents(gameEvents); |
||||
tournament.requireCompletion(); |
||||
|
||||
// Generate and print report
|
||||
const rankingReport = new RankingReport(tournament); |
||||
rankingReport.printPlayerSummary(); |
||||
} |
||||
|
||||
try { |
||||
await main(); |
||||
} catch (err) { |
||||
if (err instanceof BaseError) { |
||||
const printTrace = ArgParse.getBooleanArg("d", "debug"); |
||||
if (printTrace) { |
||||
console.error(err); |
||||
} else { |
||||
console.log(`Error: ${err.message}`); |
||||
} |
||||
} else { |
||||
// Non-Application Error, re-throw
|
||||
throw err; |
||||
} |
||||
} |
@ -0,0 +1,40 @@
|
||||
import { Tournament } from "../tournament/tournament"; |
||||
|
||||
export class RankingReport { |
||||
constructor(public tournament: Tournament) {} |
||||
|
||||
printPlayerSummary() { |
||||
const sortedPlayers = this.tournament.players.sort( |
||||
(a, b) => a.playerId - b.playerId |
||||
); |
||||
for (const player of sortedPlayers) { |
||||
const wins = player.games.reduce( |
||||
(wins, game) => wins + Number(game.winner === player), |
||||
0 |
||||
); |
||||
const losses = player.games.length - wins; |
||||
const ratio = (wins / player.games.length).toFixed(3); |
||||
const ladders = player.games.reduce( |
||||
(ladders, game) => ladders + game.ladderCountForPlayer(player), |
||||
0 |
||||
); |
||||
const snakes = player.games.reduce( |
||||
(snakes, game) => snakes + game.snakeCountForPlayer(player), |
||||
0 |
||||
); |
||||
|
||||
console.log( |
||||
`Player: ${player.playerId}: ` + |
||||
[ |
||||
`Win:${wins}`, |
||||
`Lose:${losses}`, |
||||
`Percent:${ratio}`, |
||||
`Rolls:${player.rolls}`, |
||||
`Ladders:${ladders}`, |
||||
`Snakes:${snakes}`, |
||||
].join(", ") + |
||||
" ..." |
||||
); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,7 @@
|
||||
import { TournamentError } from "./tournament_error.js"; |
||||
|
||||
export class DuplicateGameError extends TournamentError { |
||||
constructor(gameId: number, options?: ErrorOptions) { |
||||
super(`Duplicate game is: ${gameId}`, options); |
||||
} |
||||
} |
@ -0,0 +1,11 @@
|
||||
import { TournamentError } from "./tournament_error.js"; |
||||
|
||||
export class GameNotStartedError extends TournamentError { |
||||
constructor(gameId: number, playerId: number, options?: ErrorOptions) { |
||||
super( |
||||
`Player: ${playerId} attempted to participate in game: ${gameId}, but ` + |
||||
`that game hasn't started yet`, |
||||
options |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,17 @@
|
||||
import { TournamentError } from "./tournament_error.js"; |
||||
|
||||
export class MaxPlayersExceededError extends TournamentError { |
||||
constructor( |
||||
gameId: number, |
||||
playerId: number, |
||||
maxPlayers: number, |
||||
options?: ErrorOptions |
||||
) { |
||||
super( |
||||
`Player: ${playerId} attempted to join game: ${gameId} which ` + |
||||
`already has the maximum number (${maxPlayers}) of permissible ` + |
||||
`players.`, |
||||
options |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,3 @@
|
||||
import { BaseError } from "../../common_errors/base_error.js"; |
||||
|
||||
export class TournamentError extends BaseError {} |
@ -0,0 +1,11 @@
|
||||
import { TournamentError } from "./tournament_error.js"; |
||||
|
||||
export class TournamentIncompleteError extends TournamentError { |
||||
constructor(incompleteGameIds: number[], options?: ErrorOptions) { |
||||
const incompleteStr = incompleteGameIds.join(", "); |
||||
super( |
||||
"Tournament has the following incomplete game(s): " + incompleteStr, |
||||
options |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,116 @@
|
||||
import { EventProcessor, IEventProcessor } from "../utils/event_processor.js"; |
||||
|
||||
import { GameStartedEvent } from "../game/events/game_started_event.js"; |
||||
import { PlayerJoinedEvent } from "../game/events/player_joined_event.js"; |
||||
import { PlayerRolledEvent } from "../game/events/player_rolled_event.js"; |
||||
|
||||
import { Game } from "../game/game.js"; |
||||
import { Board } from "../game/board.js"; |
||||
import { Player } from "../game/player.js"; |
||||
|
||||
import { MaxPlayersExceededError } from "./errors/max_players_exceeded_error.js"; |
||||
import { GameNotStartedError } from "./errors/game_not_started_error.js"; |
||||
import { DuplicateGameError } from "./errors/duplicate_game_error.js"; |
||||
import { TournamentIncompleteError } from "./errors/tournament_incomplete_error.js"; |
||||
|
||||
// Provide Tournament class with additional EventProcessor methods
|
||||
export interface Tournament extends IEventProcessor {} |
||||
|
||||
interface ITournamentOptions { |
||||
maxPlayersPerGame: number; |
||||
} |
||||
|
||||
@EventProcessor.watch |
||||
export class Tournament implements ITournamentOptions { |
||||
gamesMap = new Map<number, Game>(); |
||||
playersMap = new Map<number, Player>(); |
||||
|
||||
maxPlayersPerGame = 2; |
||||
|
||||
constructor(public board: Board, options: Partial<ITournamentOptions> = {}) { |
||||
Object.assign(this, options); |
||||
} |
||||
|
||||
get players() { |
||||
return Array.from(this.playersMap.values()); |
||||
} |
||||
|
||||
get games() { |
||||
return Array.from(this.gamesMap.values()); |
||||
} |
||||
|
||||
@EventProcessor.handle(GameStartedEvent) |
||||
onGameStarted(gameStartedEvent: GameStartedEvent) { |
||||
this.newGame(gameStartedEvent.gameId); |
||||
} |
||||
|
||||
@EventProcessor.handle(PlayerJoinedEvent) |
||||
onPlayerJoined(playerJoinedEvent: PlayerJoinedEvent) { |
||||
const game = this.getGame(playerJoinedEvent.gameId); |
||||
if (!game) { |
||||
throw new GameNotStartedError( |
||||
playerJoinedEvent.gameId, |
||||
playerJoinedEvent.playerId |
||||
); |
||||
} |
||||
|
||||
const player = this.getOrCreatePlayer(playerJoinedEvent.playerId); |
||||
|
||||
if (game.playerCount >= this.maxPlayersPerGame) { |
||||
throw new MaxPlayersExceededError( |
||||
game.gameId, |
||||
player.playerId, |
||||
this.maxPlayersPerGame |
||||
); |
||||
} |
||||
|
||||
player.join(game); |
||||
game.addPlayer(player); |
||||
} |
||||
|
||||
@EventProcessor.handle(PlayerRolledEvent) |
||||
onPlayerRolled(playerRolledEvent: PlayerRolledEvent) { |
||||
const game = this.getGame(playerRolledEvent.gameId); |
||||
if (!game) { |
||||
throw new GameNotStartedError( |
||||
playerRolledEvent.gameId, |
||||
playerRolledEvent.playerId |
||||
); |
||||
} |
||||
|
||||
const player = this.getOrCreatePlayer(playerRolledEvent.playerId); |
||||
player.rollDice(game, playerRolledEvent.roll); |
||||
} |
||||
|
||||
getGame(gameId: number) { |
||||
return this.gamesMap.get(gameId); |
||||
} |
||||
|
||||
newGame(gameId: number) { |
||||
if (this.gamesMap.has(gameId)) { |
||||
throw new DuplicateGameError(gameId); |
||||
} |
||||
const game = new Game(gameId, this.board); |
||||
this.gamesMap.set(gameId, game); |
||||
return game; |
||||
} |
||||
|
||||
getOrCreatePlayer(playerId: number) { |
||||
let player = this.playersMap.get(playerId); |
||||
if (!player) { |
||||
player = new Player(playerId); |
||||
this.playersMap.set(playerId, player); |
||||
} |
||||
return player; |
||||
} |
||||
|
||||
requireCompletion() { |
||||
const incompleteGameIds = Array.from(this.gamesMap.values()) |
||||
.filter((game: Game) => !game.isComplete) |
||||
.map((game: Game) => game.gameId); |
||||
|
||||
if (incompleteGameIds.length) { |
||||
throw new TournamentIncompleteError(incompleteGameIds); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,109 @@
|
||||
import assert from "assert"; |
||||
|
||||
const argv = [...process.argv]; |
||||
function next() { |
||||
return argv.shift(); |
||||
} |
||||
|
||||
function peek() { |
||||
return argv[0]; |
||||
} |
||||
|
||||
const positional: Array<string | boolean> = []; |
||||
const named: { [key: string]: string | boolean } = {}; |
||||
|
||||
while (argv.length) { |
||||
const arg = next(); |
||||
if (arg === undefined) break; |
||||
|
||||
if (arg.startsWith("--")) { |
||||
const match = arg.match(/^--([^=]*)(=?)(.*)$/); |
||||
assert(match); |
||||
const name = match[1]; |
||||
if (!name.length) continue; |
||||
let value: string | boolean = true; |
||||
if (match[2] === "=") { |
||||
if (match[3].length) { |
||||
value = match[3]; |
||||
} |
||||
} else { |
||||
const nextArg = peek(); |
||||
if (nextArg && !nextArg.startsWith("-")) { |
||||
next(); |
||||
value = nextArg; |
||||
} |
||||
} |
||||
named[name] = value; |
||||
} else if (arg.startsWith("-")) { |
||||
const match = arg.match(/^-(.*?)(=?)([^=a-zA-Z]*)$/); |
||||
assert(match); |
||||
const names = match[1].split(""); |
||||
if (!names.length) continue; |
||||
while (names.length > 1) { |
||||
const name = names.shift(); |
||||
if (name && name.length) { |
||||
named[name] = true; |
||||
} |
||||
} |
||||
let lastName = names.shift(); |
||||
if (!lastName) continue; |
||||
let lastValue: string | boolean = true; |
||||
if (match[2] === "=") { |
||||
if (match[3].length) { |
||||
lastValue = match[3]; |
||||
} |
||||
} else { |
||||
const nextArg = peek(); |
||||
if (nextArg && !nextArg.startsWith("-")) { |
||||
next(); |
||||
lastValue = nextArg; |
||||
} |
||||
} |
||||
named[lastName] = lastValue; |
||||
} else { |
||||
positional.push(arg); |
||||
} |
||||
} |
||||
|
||||
function getArg(...nameOrPositions: Array<string | number>) { |
||||
let value: string | boolean | undefined; |
||||
for (const nameOrPosition of nameOrPositions) { |
||||
if (typeof nameOrPosition === "string") { |
||||
const name = nameOrPosition; |
||||
if (name in named) { |
||||
value = named[name]; |
||||
break; |
||||
} |
||||
} else if (Number.isInteger(nameOrPosition)) { |
||||
const position = nameOrPosition; |
||||
if (position < positional.length) { |
||||
return positional[position]; |
||||
} |
||||
} |
||||
} |
||||
return value; |
||||
} |
||||
|
||||
export const ArgParse = { |
||||
argv: { |
||||
_: positional, |
||||
...named, |
||||
}, |
||||
getStringArg(...nameOrPositions: Array<string | number>): string | undefined { |
||||
const value = getArg(...nameOrPositions); |
||||
if (typeof value === "string") { |
||||
return value; |
||||
} |
||||
}, |
||||
getBooleanArg(...nameOrPositions: Array<string | number>): boolean { |
||||
return Boolean(getArg(...nameOrPositions)); |
||||
}, |
||||
getIntegerArg( |
||||
...nameOrPositions: Array<string | number> |
||||
): number | undefined { |
||||
const value = Number(getArg(...nameOrPositions)); |
||||
if (Number.isInteger(value)) { |
||||
return value; |
||||
} |
||||
}, |
||||
}; |
@ -0,0 +1,136 @@
|
||||
import { Stream } from "./stream.js"; |
||||
import { readFile } from "fs/promises"; |
||||
|
||||
type Options<T> = { |
||||
quotes: string[]; |
||||
delimeter: string; |
||||
headers: T; |
||||
}; |
||||
|
||||
export class CSVParser<T extends true | false = false> { |
||||
quotes: string[] = ['"', "'"]; |
||||
delimeter: string = ","; |
||||
headers: boolean = false; |
||||
|
||||
constructor(options: Partial<Options<T>> = {}) { |
||||
Object.assign(this, options); |
||||
} |
||||
|
||||
async parseFile(filename: string, encoding: BufferEncoding = "utf-8") { |
||||
const data = await readFile(filename, { |
||||
encoding, |
||||
}); |
||||
return this.parse(data); |
||||
} |
||||
|
||||
parse(str: string) { |
||||
return this.parseStream(new Stream(str)); |
||||
} |
||||
|
||||
private parseStream( |
||||
stream: Stream |
||||
): T extends true ? Array<{ [key: string]: string }> : string[] { |
||||
const lines = []; |
||||
let firstLine: string[] | undefined; |
||||
|
||||
while (!stream.eof) { |
||||
const line = this.parseLine(stream); |
||||
|
||||
if (this.headers) { |
||||
if (firstLine !== undefined) { |
||||
const headers = firstLine; |
||||
lines.push( |
||||
line.reduce((obj: { [key: string]: string }, value, index) => { |
||||
obj[ |
||||
index < headers.length ? headers[index] : `column_${index + 1}` |
||||
] = value; |
||||
return obj; |
||||
}, {}) |
||||
); |
||||
} else { |
||||
firstLine = line; |
||||
} |
||||
} else { |
||||
lines.push(line); |
||||
} |
||||
|
||||
if (stream.eof) break; |
||||
|
||||
const next = stream.peek(); |
||||
if (next === "\r") { |
||||
stream.read(); |
||||
if (stream.peek() === "\n") { |
||||
stream.read(); |
||||
} |
||||
continue; |
||||
} else if (next === "\n") { |
||||
stream.read(); |
||||
continue; |
||||
} |
||||
} |
||||
|
||||
return lines as T extends true |
||||
? Array<{ [key: string]: string }> |
||||
: string[]; |
||||
} |
||||
|
||||
private parseLine(stream: Stream) { |
||||
const values = []; |
||||
|
||||
while (!stream.eof) { |
||||
values.push(this.parseValue(stream)); |
||||
|
||||
if (stream.eof) break; |
||||
|
||||
const next = stream.peek(); |
||||
|
||||
if (next === this.delimeter) { |
||||
stream.read(); |
||||
} else if (["\r", "\n"].includes(next)) { |
||||
break; |
||||
} else { |
||||
stream.panic(`Unexpected character: "${next}"`); |
||||
} |
||||
} |
||||
|
||||
return values; |
||||
} |
||||
|
||||
private parseValue(stream: Stream) { |
||||
let str = ""; |
||||
|
||||
const spaces = stream.readSpaces(); |
||||
|
||||
const next = stream.peek(); |
||||
|
||||
if (this.quotes.includes(next)) { |
||||
str += this.parseQuotedString(stream); |
||||
// Read and discard trailing spaces
|
||||
stream.readSpaces(); |
||||
} else { |
||||
// If a string is not quoted, spaces are assumed to be part of the value
|
||||
str += spaces + stream.readUntil("\r", "\n", this.delimeter); |
||||
} |
||||
|
||||
return str; |
||||
} |
||||
|
||||
private parseQuotedString(stream: Stream) { |
||||
const end = stream.read(); |
||||
let str = ""; |
||||
|
||||
while (!stream.eof) { |
||||
const chunk = stream.readUntil(end); |
||||
str += chunk; |
||||
stream.read(); |
||||
if (stream.peek() === end) { |
||||
stream.read(); |
||||
str += end; |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return str; |
||||
} |
||||
} |
@ -0,0 +1,47 @@
|
||||
export class Stream { |
||||
private pos: number; |
||||
|
||||
constructor(private data: string) { |
||||
this.data = data; |
||||
this.pos = 0; |
||||
} |
||||
|
||||
get eof() { |
||||
return this.pos === this.data.length; |
||||
} |
||||
|
||||
peek() { |
||||
return this.data.slice(this.pos, this.pos + 1); |
||||
} |
||||
read() { |
||||
const value = this.data[this.pos]; |
||||
this.pos++; |
||||
return value; |
||||
} |
||||
panic(message: string) { |
||||
throw new Error(`Stream Error: ${message}, at position: ${this.pos}`); |
||||
} |
||||
readSpaces() { |
||||
return this.readWhile(" ", "\t"); |
||||
} |
||||
readWhile(...chars: string[]) { |
||||
let value = ""; |
||||
|
||||
while (!this.eof && chars.includes(this.peek())) { |
||||
value += this.read(); |
||||
} |
||||
|
||||
return value; |
||||
} |
||||
readUntil(...chars: string[]) { |
||||
const nextIndex = chars.reduce((index, chr) => { |
||||
const i = this.data.indexOf(chr, this.pos); |
||||
return i < index && i != -1 ? i : index; |
||||
}, this.data.length - 1); |
||||
|
||||
const value = this.data.substring(this.pos, nextIndex); |
||||
this.pos = nextIndex; |
||||
|
||||
return value; |
||||
} |
||||
} |
@ -0,0 +1,54 @@
|
||||
interface Type<T> extends Function { |
||||
new (...args: any[]): T; |
||||
} |
||||
|
||||
export interface IEventProcessor { |
||||
processEvents: (events: any[]) => void; |
||||
} |
||||
|
||||
export class EventProcessor { |
||||
private handlers = new Map(); |
||||
|
||||
static watch<T extends Type<{}>>(Base: T) { |
||||
const eventProcessor = new EventProcessor(); |
||||
return class extends Base { |
||||
constructor(...args: any[]) { |
||||
super(...args); |
||||
const methods = Base.prototype["eventProcessorMethods"] || new Map(); |
||||
for (const [eventType, handler] of methods) { |
||||
eventProcessor.setHandler(eventType, handler.bind(this)); |
||||
} |
||||
} |
||||
processEvents(events: any[]) { |
||||
for (const event of events) { |
||||
eventProcessor.emit(event); |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
|
||||
static handle<T>(eventType: Type<T>) { |
||||
return ( |
||||
target: any, |
||||
_propertyKey: string, |
||||
descriptor: TypedPropertyDescriptor<(ev: T) => void> |
||||
) => { |
||||
if (!("eventProcessorMethods" in target)) { |
||||
target["eventProcessorMethods"] = new Map(); |
||||
} |
||||
const methods = target["eventProcessorMethods"]; |
||||
methods.set(eventType, descriptor.value); |
||||
}; |
||||
} |
||||
|
||||
setHandler<T>(GameEvent: Type<T>, handler: (gameEvent: T) => void) { |
||||
this.handlers.set(GameEvent, handler); |
||||
} |
||||
|
||||
emit<T extends Object>(gameEvent: T) { |
||||
const handler = this.handlers.get(gameEvent.constructor); |
||||
if (handler !== undefined) { |
||||
handler(gameEvent); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,104 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"experimentalDecorators": true, |
||||
/* Visit https://aka.ms/tsconfig to read more about this file */ |
||||
|
||||
/* Projects */ |
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ |
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ |
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ |
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ |
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ |
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ |
||||
|
||||
/* Language and Environment */ |
||||
"target": "es2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, |
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ |
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */ |
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ |
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ |
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ |
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ |
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ |
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ |
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ |
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ |
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ |
||||
|
||||
/* Modules */ |
||||
"module": "es2022" /* Specify what module code is generated. */, |
||||
"rootDir": "./src" /* Specify the root folder within your source files. */, |
||||
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, |
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ |
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ |
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ |
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ |
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */ |
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ |
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ |
||||
// "resolveJsonModule": true, /* Enable importing .json files. */ |
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ |
||||
|
||||
/* JavaScript Support */ |
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ |
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ |
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ |
||||
|
||||
/* Emit */ |
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ |
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */ |
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ |
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */ |
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ |
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */, |
||||
// "removeComments": true, /* Disable emitting comments. */ |
||||
// "noEmit": true, /* Disable emitting files from a compilation. */ |
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ |
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ |
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ |
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ |
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ |
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ |
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ |
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ |
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */ |
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ |
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ |
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ |
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ |
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */ |
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ |
||||
|
||||
/* Interop Constraints */ |
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ |
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ |
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, |
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ |
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, |
||||
|
||||
/* Type Checking */ |
||||
"strict": true /* Enable all strict type-checking options. */, |
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ |
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ |
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ |
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ |
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ |
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ |
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ |
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ |
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ |
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ |
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ |
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ |
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ |
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ |
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ |
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ |
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ |
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ |
||||
|
||||
/* Completeness */ |
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ |
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */ |
||||
} |
||||
} |
Loading…
Reference in new issue