bppapsdf
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
.env
|
||||
node_modules
|
||||
dist
|
||||
@@ -0,0 +1,9 @@
|
||||
export const apps = [
|
||||
{
|
||||
name: "poem-bot",
|
||||
script: "dist/index.cjs",
|
||||
interpreter: "node",
|
||||
instances: 1,
|
||||
watch: false,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,30 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
|
||||
export default defineConfig(globalIgnores(["dist/*"]), [
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
||||
plugins: { js },
|
||||
extends: ["js/recommended"],
|
||||
ignores: ["/dist/*"],
|
||||
rules: {
|
||||
"require-await": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
args: "all",
|
||||
argsIgnorePattern: "^_",
|
||||
caughtErrors: "all",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
destructuredArrayIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
languageOptions: { globals: globals.browser },
|
||||
},
|
||||
tseslint.configs.recommended,
|
||||
]);
|
||||
Generated
+6126
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "jolb-poem-bot",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "tsup src/server/index.ts --watch --onSuccess \"node dist/index.cjs\"",
|
||||
"lint": "eslint",
|
||||
"build": "tsup src/server/index.ts --minify --clean",
|
||||
"start": "pm2 start ecosystem.config.js",
|
||||
"check:circles": "npx madge --circular src/"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@grammyjs/conversations": "^2.1.1",
|
||||
"@grammyjs/menu": "^1.3.1",
|
||||
"@prisma/adapter-better-sqlite3": "^7.4.1",
|
||||
"@prisma/client": "^7.4.1",
|
||||
"crypto-hash": "^4.0.1",
|
||||
"dotenv": "^17.3.1",
|
||||
"grammy": "^1.40.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/node": "^25.3.0",
|
||||
"eslint": "^10.0.0",
|
||||
"globals": "^17.3.0",
|
||||
"jiti": "^2.6.1",
|
||||
"pm2": "^6.0.14",
|
||||
"prisma": "^7.4.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsup": "^8.5.1",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.56.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'dotenv/config';
|
||||
import { defineConfig, env } from 'prisma/config';
|
||||
|
||||
export default defineConfig({
|
||||
schema: './prisma/schema.prisma',
|
||||
datasource: {
|
||||
url: env('DATABASE_URL'),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
}
|
||||
|
||||
enum PlayerState {
|
||||
IN_ARCHIVES
|
||||
LOBBY
|
||||
POST_GAME
|
||||
SETTING_UP_GAME
|
||||
SPECTATING
|
||||
START
|
||||
TRYING_LEAVE
|
||||
TYPING
|
||||
VIEWING_ARCHIVED_POEM
|
||||
WAITING_AFTER_WRITING
|
||||
WAITING_TO_WRITE
|
||||
WRITING
|
||||
}
|
||||
|
||||
model Player {
|
||||
playerId String @id
|
||||
createdAt DateTime @default(now())
|
||||
currentGame Game? @relation("playersInGame", fields: [currentGameId], references: [gameId])
|
||||
currentGameId Int?
|
||||
state PlayerState @default(LOBBY)
|
||||
previousState PlayerState @default(LOBBY)
|
||||
userName String
|
||||
games Game[]
|
||||
}
|
||||
|
||||
model Game {
|
||||
gameId Int @id @default(autoincrement())
|
||||
config GameConfig? @relation(fields: [gameConfigId], references: [configId])
|
||||
createdAt DateTime @default(now())
|
||||
joinCode String
|
||||
poem Poem?
|
||||
poemId Int?
|
||||
gameConfigId Int?
|
||||
hostPlayer Player @relation(fields: [hostPlayerId], references: [playerId])
|
||||
hostPlayerId String
|
||||
inProgress Boolean @default(false)
|
||||
players Player[] @relation("playersInGame")
|
||||
}
|
||||
|
||||
model GameConfig {
|
||||
configId Int @id @default(autoincrement())
|
||||
games Game[]
|
||||
}
|
||||
|
||||
model Poem {
|
||||
poemId Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
game Game @relation(fields: [gameId], references: [gameId])
|
||||
gameId Int @unique
|
||||
finished Boolean @default(false)
|
||||
lines Json
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
import { HAS_JOINED, HAS_LEFT, HOST_LEFT, HOST_RESTARTED } from "const";
|
||||
import { getPlayerOrThrow, getPlayers, leaveGame } from "data";
|
||||
import { setPlayerState } from "player-state";
|
||||
import {
|
||||
Context,
|
||||
GameOptions,
|
||||
GameOverrides,
|
||||
GameProps,
|
||||
PlayerState,
|
||||
} from "types";
|
||||
import { generateGameId } from "./generate-game-id";
|
||||
import { Player } from "../player";
|
||||
import { Poem } from "../poem";
|
||||
import { forPlayers, randomStarDecorator } from "utils";
|
||||
import { messagePlayer } from "messaging";
|
||||
import { AllStateFn, OthersStateFn, AllFn, OthersFn } from "./types";
|
||||
|
||||
export const forAllOtherPlayers = async ({
|
||||
callback,
|
||||
fromPlayerId,
|
||||
players,
|
||||
}: {
|
||||
fromPlayerId: number;
|
||||
callback: (player: Player) => Promise<void>;
|
||||
players: Player[];
|
||||
}) => {
|
||||
const otherPlayers = players.filter((player) => player.id !== fromPlayerId);
|
||||
await forPlayers(otherPlayers, callback);
|
||||
};
|
||||
|
||||
export const gameDefaults: Partial<GameProps> = {
|
||||
inProgress: false,
|
||||
};
|
||||
|
||||
// TODO decompose this into smaller bits
|
||||
export class Game {
|
||||
currentWriterId?: number;
|
||||
destroy: () => void;
|
||||
options: GameOptions;
|
||||
hostId?: number;
|
||||
id: string;
|
||||
inProgress: boolean;
|
||||
poem: Poem;
|
||||
playerIds: number[];
|
||||
|
||||
constructor(overrides: GameOverrides) {
|
||||
const {
|
||||
currentWriterId,
|
||||
destroy,
|
||||
hostId,
|
||||
id,
|
||||
inProgress,
|
||||
options,
|
||||
poemProps,
|
||||
playerIds,
|
||||
} = {
|
||||
...gameDefaults,
|
||||
...overrides,
|
||||
};
|
||||
this.currentWriterId = currentWriterId;
|
||||
this.destroy = destroy ? () => destroy(this.id) : () => null;
|
||||
this.hostId = hostId;
|
||||
this.id = id ?? generateGameId();
|
||||
this.inProgress = !!inProgress;
|
||||
this.options = { ...(options || {}) };
|
||||
this.playerIds = playerIds ?? [];
|
||||
this.poem = new Poem({ gameId: this.id, ...(poemProps ?? {}) });
|
||||
/* Object.values(helperFns).forEach(
|
||||
(fn) => (this[fn.name as keyof Helpers] = fn),
|
||||
); */
|
||||
}
|
||||
|
||||
advanceTurnOrder = async (ctx: Context, previousPlayerId?: number) => {
|
||||
const nextPlayer = this.getActivePlayer();
|
||||
const gameDone = !nextPlayer;
|
||||
if (gameDone) {
|
||||
await this.finish(ctx);
|
||||
return;
|
||||
}
|
||||
if (previousPlayerId && this.hasPlayer(previousPlayerId)) {
|
||||
await setPlayerState(
|
||||
previousPlayerId,
|
||||
ctx,
|
||||
PlayerState.WAITING_AFTER_WRITING,
|
||||
);
|
||||
}
|
||||
|
||||
const { id: nextPlayerId } = nextPlayer;
|
||||
// messages to the previous and next player are already handled by state changes
|
||||
await forPlayers(this.getPlayers(), async (player) => {
|
||||
if (player.id === nextPlayerId) {
|
||||
await setPlayerState(nextPlayerId, ctx, PlayerState.WRITING);
|
||||
} else if (![previousPlayerId].includes(player.id)) {
|
||||
player.refreshMessage(ctx);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
removePlayer = async (player: Player, ctx: Context) => {
|
||||
if (player.id === this.hostId) {
|
||||
await this.removeHost(ctx);
|
||||
return;
|
||||
}
|
||||
if (!this.getHost()) {
|
||||
return;
|
||||
}
|
||||
this.removePlayerId(player.id);
|
||||
|
||||
await this.messageAllOtherPlayers({
|
||||
ctx,
|
||||
customMessage: HAS_LEFT(player.userName),
|
||||
fromPlayerId: player.id,
|
||||
players: this.getPlayers(),
|
||||
state: null,
|
||||
});
|
||||
if (this.poem.completed) return;
|
||||
const playerWasActive =
|
||||
this.inProgress &&
|
||||
!this.getPlayers().find((p) => p.state === PlayerState.WRITING);
|
||||
if (playerWasActive) {
|
||||
await this.advanceTurnOrder(ctx, player.id);
|
||||
} else {
|
||||
await this.refreshPlayerMessages({ ctx, players: this.getPlayers() });
|
||||
}
|
||||
};
|
||||
|
||||
removeHost = async (ctx: Context) => {
|
||||
await this.messageAllOtherPlayers({
|
||||
ctx,
|
||||
customMessage: HOST_LEFT,
|
||||
fromPlayerId: this.hostId!,
|
||||
players: this.getPlayers(),
|
||||
state: PlayerState.START,
|
||||
});
|
||||
this.removePlayerId(this.hostId);
|
||||
|
||||
const nonHostPlayers = this.getPlayers().filter(
|
||||
(player) => player.id !== this.hostId,
|
||||
);
|
||||
await forPlayers(
|
||||
nonHostPlayers,
|
||||
async (player) => await leaveGame(ctx, this, player),
|
||||
);
|
||||
this.destroy();
|
||||
};
|
||||
|
||||
startGame = async (ctx: Context) => {
|
||||
await this.messageAllOtherPlayers({
|
||||
ctx,
|
||||
customMessage: "The host has started the game.",
|
||||
fromPlayerId: this.hostId!,
|
||||
players: this.getPlayers(),
|
||||
state: null,
|
||||
});
|
||||
this.inProgress = true;
|
||||
this.shufflePlayers();
|
||||
const firstPlayer = this.getPlayers()[0];
|
||||
await this.transitionAllOtherPlayers({
|
||||
ctx,
|
||||
fromPlayerId: firstPlayer.id,
|
||||
players: this.getPlayers(),
|
||||
state: PlayerState.WAITING_TO_WRITE,
|
||||
});
|
||||
await setPlayerState(firstPlayer.id, ctx, PlayerState.WRITING);
|
||||
};
|
||||
|
||||
addPlayer = async (playerId: number, ctx: Context) => {
|
||||
const player = getPlayerOrThrow(playerId);
|
||||
this.playerIds.push(playerId);
|
||||
if (this.playerIds.length === 1) {
|
||||
this.hostId = playerId;
|
||||
} else {
|
||||
await this.messageAllOtherPlayers({
|
||||
ctx,
|
||||
customMessage: HAS_JOINED(player.userName),
|
||||
fromPlayerId: playerId,
|
||||
players: this.getPlayers(),
|
||||
state: null,
|
||||
});
|
||||
await this.refreshOtherPlayerMessages({
|
||||
ctx,
|
||||
players: this.getPlayers(),
|
||||
fromPlayerId: playerId,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
finish = async (ctx: Context) => {
|
||||
this.poem.complete();
|
||||
await this.transitionPlayers({
|
||||
ctx,
|
||||
players: this.getPlayers(),
|
||||
state: PlayerState.POST_GAME,
|
||||
});
|
||||
|
||||
await forPlayers(this.getPlayers(), (player) =>
|
||||
this.poem.sendToPlayer(player.id, ctx),
|
||||
);
|
||||
};
|
||||
|
||||
previousLine = () => {
|
||||
return this.poem.previousLine();
|
||||
};
|
||||
|
||||
prunePlayerIds = () => {
|
||||
const players = this.getPlayers();
|
||||
for (const player of players) {
|
||||
if (player.gameId !== this.id) {
|
||||
this.removePlayerId(player.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getActivePlayer = (): Player | undefined => {
|
||||
const eligiblePlayers = this.getEligiblePlayers();
|
||||
return eligiblePlayers[0];
|
||||
};
|
||||
|
||||
getEligiblePlayers = () => {
|
||||
return this.getPlayers().filter((player) => player.canWrite());
|
||||
};
|
||||
|
||||
getHost = (): Player | undefined => {
|
||||
return this.getPlayers().find((player) => player.isHost());
|
||||
};
|
||||
|
||||
getPlayers = () => {
|
||||
return getPlayers(this.playerIds);
|
||||
};
|
||||
|
||||
hasPlayer = (playerId?: number) => {
|
||||
return playerId && this.playerIds.includes(playerId);
|
||||
};
|
||||
|
||||
removePlayerId = (idToRemove?: number) => {
|
||||
if (!idToRemove) return;
|
||||
const idIndex = this.playerIds.findIndex((id) => id === idToRemove);
|
||||
this.playerIds.splice(idIndex, 1);
|
||||
};
|
||||
|
||||
setOptions = (newOptions: GameOptions) => {
|
||||
this.options = { ...this.options, ...newOptions };
|
||||
};
|
||||
|
||||
private shufflePlayers = () => {
|
||||
this.playerIds.sort(() => Math.random() - 0.5);
|
||||
};
|
||||
|
||||
addLine = async (ctx: Context, line: string, author: Player) => {
|
||||
this.poem.addLine(line, author.userName);
|
||||
await messagePlayer(
|
||||
author.id,
|
||||
author.state,
|
||||
ctx,
|
||||
`Your line has been added! ${randomStarDecorator()}`,
|
||||
);
|
||||
await this.advanceTurnOrder(ctx, author.id);
|
||||
};
|
||||
|
||||
restart = async (ctx: Context) => {
|
||||
this.poem = new Poem({ gameId: this.id });
|
||||
this.inProgress = false;
|
||||
await this.messageAllOtherPlayers({
|
||||
ctx,
|
||||
customMessage: HOST_RESTARTED,
|
||||
fromPlayerId: this.hostId!,
|
||||
players: this.getPlayers(),
|
||||
state: null,
|
||||
});
|
||||
await this.transitionPlayers({
|
||||
players: this.getPlayers(),
|
||||
state: PlayerState.LOBBY,
|
||||
ctx,
|
||||
});
|
||||
};
|
||||
|
||||
forAllOtherPlayers = async ({
|
||||
callback,
|
||||
fromPlayerId,
|
||||
players,
|
||||
}: {
|
||||
fromPlayerId: number;
|
||||
callback: (player: Player) => Promise<void>;
|
||||
players: Player[];
|
||||
}) => {
|
||||
const otherPlayers = players.filter((player) => player.id !== fromPlayerId);
|
||||
await forPlayers(otherPlayers, callback);
|
||||
};
|
||||
|
||||
messagePlayers: AllStateFn = async ({
|
||||
ctx,
|
||||
customMessage = "",
|
||||
players,
|
||||
state,
|
||||
}) => {
|
||||
await forPlayers(players, (player) =>
|
||||
messagePlayer(player.id, state || player.state, ctx, customMessage),
|
||||
);
|
||||
};
|
||||
|
||||
messageAllOtherPlayers: OthersStateFn = async ({
|
||||
ctx,
|
||||
customMessage = "",
|
||||
fromPlayerId,
|
||||
players,
|
||||
state = null,
|
||||
}) => {
|
||||
await forAllOtherPlayers({
|
||||
players,
|
||||
fromPlayerId,
|
||||
callback: (player) =>
|
||||
messagePlayer(player.id, state || player.state, ctx, customMessage),
|
||||
});
|
||||
};
|
||||
|
||||
transitionPlayers: AllStateFn = async ({ ctx, players, state }) => {
|
||||
await forPlayers(players, (player) =>
|
||||
setPlayerState(player.id, ctx, state ?? player.state),
|
||||
);
|
||||
};
|
||||
|
||||
transitionAllOtherPlayers: OthersStateFn = async ({
|
||||
ctx,
|
||||
fromPlayerId,
|
||||
players,
|
||||
state,
|
||||
}) => {
|
||||
await forAllOtherPlayers({
|
||||
fromPlayerId,
|
||||
players,
|
||||
callback: (player) =>
|
||||
setPlayerState(player.id, ctx, state ?? player.state),
|
||||
});
|
||||
};
|
||||
|
||||
refreshPlayerMessages: AllFn = async ({ ctx, players }) => {
|
||||
await forPlayers(players, (player) => player.refreshMessage(ctx));
|
||||
};
|
||||
|
||||
refreshOtherPlayerMessages: OthersFn = async ({
|
||||
ctx,
|
||||
players,
|
||||
fromPlayerId,
|
||||
}) => {
|
||||
await forAllOtherPlayers({
|
||||
callback: (player) => player.refreshMessage(ctx),
|
||||
fromPlayerId,
|
||||
players,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { getGames } from "data";
|
||||
import { randomString } from "utils";
|
||||
|
||||
export const generateGameId = () => {
|
||||
const games = getGames();
|
||||
const maxAttempts = 10;
|
||||
let gameId = "";
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
gameId = randomString();
|
||||
if (!games[gameId]) break;
|
||||
}
|
||||
|
||||
return gameId;
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import { messagePlayer } from "messaging";
|
||||
import { setPlayerState } from "player-state";
|
||||
import { forPlayers } from "utils";
|
||||
import { Player } from "../player";
|
||||
import { AllFn, AllStateFn, OthersFn, OthersStateFn } from "./types";
|
||||
|
||||
export const forAllOtherPlayers = async ({
|
||||
callback,
|
||||
fromPlayerId,
|
||||
players,
|
||||
}: {
|
||||
fromPlayerId: number;
|
||||
callback: (player: Player) => Promise<void>;
|
||||
players: Player[];
|
||||
}) => {
|
||||
const otherPlayers = players.filter((player) => player.id !== fromPlayerId);
|
||||
await forPlayers(otherPlayers, callback);
|
||||
};
|
||||
|
||||
export const messagePlayers: AllStateFn = async ({
|
||||
ctx,
|
||||
customMessage = "",
|
||||
players,
|
||||
state,
|
||||
}) => {
|
||||
await forPlayers(players, (player) =>
|
||||
messagePlayer(player.id, state || player.state, ctx, customMessage),
|
||||
);
|
||||
};
|
||||
|
||||
export const messageAllOtherPlayers: OthersStateFn = async ({
|
||||
ctx,
|
||||
customMessage = "",
|
||||
fromPlayerId,
|
||||
players,
|
||||
state = null,
|
||||
}) => {
|
||||
await forAllOtherPlayers({
|
||||
players,
|
||||
fromPlayerId,
|
||||
callback: (player) =>
|
||||
messagePlayer(player.id, state || player.state, ctx, customMessage),
|
||||
});
|
||||
};
|
||||
|
||||
export const transitionPlayers: AllStateFn = async ({
|
||||
ctx,
|
||||
players,
|
||||
state,
|
||||
}) => {
|
||||
await forPlayers(players, (player) =>
|
||||
setPlayerState(player.id, ctx, state ?? player.state),
|
||||
);
|
||||
};
|
||||
|
||||
export const transitionAllOtherPlayers: OthersStateFn = async ({
|
||||
ctx,
|
||||
fromPlayerId,
|
||||
players,
|
||||
state,
|
||||
}) => {
|
||||
await forAllOtherPlayers({
|
||||
fromPlayerId,
|
||||
players,
|
||||
callback: (player) => setPlayerState(player.id, ctx, state ?? player.state),
|
||||
});
|
||||
};
|
||||
|
||||
export const refreshPlayerMessages: AllFn = async ({ ctx, players }) => {
|
||||
await forPlayers(players, (player) => player.refreshMessage(ctx));
|
||||
};
|
||||
|
||||
export const refreshOtherPlayerMessages: OthersFn = async ({
|
||||
ctx,
|
||||
players,
|
||||
fromPlayerId,
|
||||
}) => {
|
||||
await forAllOtherPlayers({
|
||||
callback: (player) => player.refreshMessage(ctx),
|
||||
fromPlayerId,
|
||||
players,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Game } from "./game";
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Context, PlayerState } from "types";
|
||||
import { Player } from "../player";
|
||||
|
||||
type BasicArgs = { ctx: Context; players: Player[] };
|
||||
type BasicFromArgs = { ctx: Context; fromPlayerId: number; players: Player[] };
|
||||
|
||||
type StateArgs = BasicArgs & {
|
||||
customMessage?: string;
|
||||
noMessage?: boolean;
|
||||
state: PlayerState | null;
|
||||
};
|
||||
type StateFromArgs = BasicArgs & {
|
||||
customMessage?: string;
|
||||
fromPlayerId: number;
|
||||
noMessage?: boolean;
|
||||
state: PlayerState | null;
|
||||
};
|
||||
|
||||
export type AllFn = (args: BasicArgs) => Promise<void>;
|
||||
export type OthersFn = (args: BasicFromArgs) => Promise<void>;
|
||||
|
||||
export type AllStateFn = (args: StateArgs) => Promise<void>;
|
||||
export type OthersStateFn = (args: StateFromArgs) => Promise<void>;
|
||||
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
ArchivedPoems,
|
||||
Context,
|
||||
GameOverrides,
|
||||
Games,
|
||||
GlobalProps,
|
||||
PlayerOverrides,
|
||||
Players,
|
||||
} from "types";
|
||||
import { Game } from "../game";
|
||||
import { Player } from "../player";
|
||||
import { Poem } from "classes/poem";
|
||||
|
||||
export class Global {
|
||||
activePlayers: Players;
|
||||
games: Games;
|
||||
archivedPoems: ArchivedPoems;
|
||||
|
||||
constructor(overrides?: Partial<GlobalProps>) {
|
||||
const { activePlayers, games, archivedPoems } = overrides ?? {};
|
||||
this.activePlayers = activePlayers ?? {};
|
||||
this.games = games ?? {};
|
||||
this.archivedPoems = archivedPoems ?? {};
|
||||
}
|
||||
|
||||
addGame = (overrides: GameOverrides = {}) => {
|
||||
const newGame = new Game({ ...overrides, destroy: this.deleteGame });
|
||||
this.games[newGame.id] = newGame;
|
||||
return newGame;
|
||||
};
|
||||
|
||||
addPlayer = (overrides: PlayerOverrides) => {
|
||||
const newPlayer = new Player({ ...overrides, destroy: this.deletePlayer });
|
||||
this.activePlayers[newPlayer.id] = newPlayer;
|
||||
return newPlayer;
|
||||
};
|
||||
|
||||
deleteGame = (id: string = "") => {
|
||||
delete this.games[id];
|
||||
};
|
||||
|
||||
deletePlayer = (id: number) => {
|
||||
delete this.activePlayers[id];
|
||||
};
|
||||
|
||||
getGame = (id: string = ""): Game | undefined => {
|
||||
return this.games[id];
|
||||
};
|
||||
|
||||
getGames = () => {
|
||||
return this.games;
|
||||
};
|
||||
|
||||
getPlayer = (id: number): Player | undefined => {
|
||||
return this.activePlayers[id];
|
||||
};
|
||||
|
||||
getPlayers = (ids: number[]) => {
|
||||
return ids.map((id) => this.activePlayers[id]).filter((player) => !!player);
|
||||
};
|
||||
|
||||
joinGame = async (
|
||||
ctx: Context,
|
||||
game: Game,
|
||||
player: Player,
|
||||
newGame?: boolean,
|
||||
) => {
|
||||
if (player.gameId === game.id) {
|
||||
throw new Error("You are already in this game.");
|
||||
}
|
||||
await game.addPlayer(player.id, ctx);
|
||||
await player.joinGame(ctx, game.id, newGame);
|
||||
};
|
||||
|
||||
leaveGame = async (ctx: Context, game: Game, player: Player) => {
|
||||
await player.leaveGame(ctx);
|
||||
await game.removePlayer(player, ctx);
|
||||
};
|
||||
|
||||
addArchivedPoem = (poem: Poem) => {
|
||||
this.archivedPoems[poem.id] = poem;
|
||||
};
|
||||
|
||||
getArchivedPoem = (id: string = ""): Poem | undefined => {
|
||||
return this.archivedPoems[id];
|
||||
};
|
||||
|
||||
getArchivedPoems = () => {
|
||||
return this.archivedPoems;
|
||||
};
|
||||
|
||||
clearMemory = () => {
|
||||
this.activePlayers = [];
|
||||
this.games = {};
|
||||
this.archivedPoems = {};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export { Game } from "./game";
|
||||
export { Global } from "./global";
|
||||
export { Player } from "./player";
|
||||
export { Poem } from "./poem";
|
||||
@@ -0,0 +1 @@
|
||||
export { Player } from "./player";
|
||||
@@ -0,0 +1,80 @@
|
||||
import { getGame, getGameOrThrow } from "data";
|
||||
import { messagePlayer } from "messaging";
|
||||
import { setPlayerState } from "player-state";
|
||||
import { Context, PlayerOverrides, PlayerProps, PlayerState } from "types";
|
||||
|
||||
export const playerDefaults: Partial<PlayerProps> = {
|
||||
gameId: "",
|
||||
state: PlayerState.START,
|
||||
};
|
||||
|
||||
export class Player {
|
||||
destroy: (id: number) => void;
|
||||
gameId?: string;
|
||||
id: number;
|
||||
previousState: PlayerState = PlayerState.START;
|
||||
state: PlayerState;
|
||||
userName: string;
|
||||
|
||||
constructor(overrides: PlayerOverrides) {
|
||||
const { destroy, gameId, id, state, userName } = {
|
||||
...playerDefaults,
|
||||
...overrides,
|
||||
};
|
||||
this.destroy = destroy ?? (() => null);
|
||||
this.id = id;
|
||||
this.userName = userName;
|
||||
this.state = state ?? PlayerState.START;
|
||||
this.gameId = gameId;
|
||||
}
|
||||
|
||||
canWrite = () => {
|
||||
return [PlayerState.WAITING_TO_WRITE].includes(this.state);
|
||||
};
|
||||
|
||||
/* destroy=()=>{
|
||||
TODO: run this on player disconnect, advance turn order if needed etc
|
||||
} */
|
||||
|
||||
getGame = () => {
|
||||
return getGame(this.gameId);
|
||||
};
|
||||
|
||||
isHost = () => {
|
||||
const game = this.getGame();
|
||||
return this.id === game?.hostId;
|
||||
};
|
||||
|
||||
isInGame = () => {
|
||||
return !!this.gameId && this.state !== PlayerState.SETTING_UP_GAME;
|
||||
};
|
||||
|
||||
joinGame = async (ctx: Context, gameId: string, newGame?: boolean) => {
|
||||
const game = getGameOrThrow(gameId);
|
||||
this.gameId = gameId;
|
||||
let newState: PlayerState;
|
||||
if (newGame) {
|
||||
newState = PlayerState.SETTING_UP_GAME;
|
||||
} else {
|
||||
newState = game.inProgress ? PlayerState.SPECTATING : PlayerState.LOBBY;
|
||||
}
|
||||
await setPlayerState(this.id, ctx, newState);
|
||||
};
|
||||
|
||||
leaveGame = async (ctx: Context) => {
|
||||
this.gameId = undefined;
|
||||
await setPlayerState(this.id, ctx, PlayerState.START);
|
||||
};
|
||||
|
||||
refreshMessage = async (ctx: Context) => {
|
||||
await messagePlayer(this.id, this.state, ctx);
|
||||
};
|
||||
|
||||
setGameId = (id: string) => {
|
||||
this.gameId = id;
|
||||
};
|
||||
|
||||
setPreviousState = (state: PlayerState) => {
|
||||
this.previousState = state;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { getArchivedPoems } from "data";
|
||||
|
||||
export const generatePoemId = () => {
|
||||
const archivedPoems = getArchivedPoems();
|
||||
const maxAttempts = 10;
|
||||
let id = 0;
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
// random 4-digit number
|
||||
id = Math.floor(Math.random() * 9000) + 1000;
|
||||
if (!archivedPoems[id]) break;
|
||||
}
|
||||
|
||||
return id;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Poem } from "./poem";
|
||||
@@ -0,0 +1,79 @@
|
||||
import { InlineKeyboard } from "grammy";
|
||||
import { RETRIEVE_FROM_ARCHIVE } from "const";
|
||||
import { addArchivedPoem, getPlayerOrThrow } from "data";
|
||||
import { sanitizeHtml } from "messaging";
|
||||
import { CallbackWithData, Context, Line, PoemOverrides } from "types";
|
||||
import { encodeCallbackData } from "utils";
|
||||
import { generatePoemId } from "./generate-poem-id";
|
||||
export { Player } from "../player";
|
||||
|
||||
export class Poem {
|
||||
completed?: boolean;
|
||||
gameId: string;
|
||||
id: number;
|
||||
lines: Line[];
|
||||
messageId?: number;
|
||||
|
||||
constructor(overrides: PoemOverrides) {
|
||||
const { gameId, id, lines } = {
|
||||
...overrides,
|
||||
};
|
||||
this.gameId = gameId;
|
||||
this.id = id ?? generatePoemId();
|
||||
this.lines = lines ?? [];
|
||||
}
|
||||
|
||||
addLine = (text: string, author: string) => {
|
||||
this.lines.push({
|
||||
author,
|
||||
date: new Date().toLocaleString(),
|
||||
text: text.trim(),
|
||||
});
|
||||
};
|
||||
|
||||
compile = (includeMeta?: boolean) => {
|
||||
let compiledPoem = this.lines
|
||||
.map((line) => {
|
||||
if (includeMeta) {
|
||||
const { author, date } = line;
|
||||
const metaPrefix = `${author} (${date}):`;
|
||||
return `<u>${metaPrefix}</u>\n${line.text}`;
|
||||
}
|
||||
return line.text;
|
||||
})
|
||||
.join("\n");
|
||||
compiledPoem += `\n\n${RETRIEVE_FROM_ARCHIVE(this.id)}`;
|
||||
return sanitizeHtml(compiledPoem);
|
||||
};
|
||||
|
||||
complete = () => {
|
||||
this.completed = true;
|
||||
addArchivedPoem(this);
|
||||
};
|
||||
|
||||
previousLine = () => {
|
||||
const lineCount = this.lines.length;
|
||||
if (!lineCount) return "";
|
||||
return this.lines[lineCount - 1].text;
|
||||
};
|
||||
|
||||
// TODO convert to markdown, but Telegram's markdown is weird
|
||||
sendToPlayer = async (playerId: number, ctx: Context) => {
|
||||
const player = getPlayerOrThrow(playerId);
|
||||
const chatId = ctx.chatId;
|
||||
if (!player || !chatId) return;
|
||||
const compiledPoem = this.compile();
|
||||
const keyboard = new InlineKeyboard().text(
|
||||
"Show Metadata",
|
||||
encodeCallbackData(CallbackWithData.SHOW_POEM_META, { id: this.id }),
|
||||
);
|
||||
await ctx.api.sendMessage(player.id, compiledPoem, {
|
||||
parse_mode: "HTML",
|
||||
reply_markup: keyboard,
|
||||
});
|
||||
};
|
||||
|
||||
setMessageId = (id: number) => {
|
||||
this.messageId = id;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { inspoRepo } from "./inspo-repo";
|
||||
export * from "./maps";
|
||||
export * from "./strings";
|
||||
@@ -0,0 +1,192 @@
|
||||
const makeQuote = (quote: string, author: string, work?: string) => {
|
||||
return `${quote}\n\n- ${author}, "${work}"`;
|
||||
};
|
||||
// todo, move this to db
|
||||
|
||||
/* const quantumPoemLines = [
|
||||
"In the ancient glade\n",
|
||||
"Across old bark\n",
|
||||
"The quiet shade\n",
|
||||
`It's always dark\n'`,
|
||||
];
|
||||
|
||||
const getQuantumPoem = () =>
|
||||
makeQuote(
|
||||
quantumPoemLines[Math.floor(Math.random() * quantumPoemLines.length)],
|
||||
"Gabbo"
|
||||
); */
|
||||
|
||||
const reMonarching = makeQuote(
|
||||
`Butterflies overwinter
|
||||
in the milkweed
|
||||
along storm drains.`,
|
||||
"Christian Gullette",
|
||||
"Re-Monarching",
|
||||
);
|
||||
|
||||
const winterRainDeepens = makeQuote(
|
||||
`Winter rain deepens
|
||||
lichened letters on the grave
|
||||
and my old sadness`,
|
||||
"Roka",
|
||||
);
|
||||
|
||||
const bermanSnow = makeQuote(
|
||||
`When it's snowing, the outdoors seem like a room.\n
|
||||
Today I traded hellos with my neighbor.
|
||||
Our voices hung close in the new acoustics.
|
||||
A room with the walls blasted to shreds and falling.`,
|
||||
"David Berman",
|
||||
"Snow",
|
||||
);
|
||||
|
||||
const stillLife = makeQuote(
|
||||
`He still found breath, and yet
|
||||
It was an obscure knack.`,
|
||||
"Thom Gunn",
|
||||
"Still Life",
|
||||
);
|
||||
|
||||
const landlady = makeQuote(
|
||||
`and when I dream images
|
||||
of daring escapes through the snow
|
||||
I find myself walking
|
||||
always over a vast face
|
||||
which is the land-
|
||||
lady's, and wake up shouting.`,
|
||||
"Margaret Atwood",
|
||||
"The Landlady",
|
||||
);
|
||||
|
||||
const suicideNote = makeQuote(
|
||||
`The calm,
|
||||
Cool face of the river
|
||||
Asked me for a kiss.`,
|
||||
"Langston Hughes",
|
||||
"Suicide Note",
|
||||
);
|
||||
|
||||
const capybaraHotsprings1 = makeQuote(
|
||||
`There exists nothing human that can scratch even the lowest sky`,
|
||||
"Yaxkin Melchy Ramos",
|
||||
"The Capybara Hot Springs",
|
||||
);
|
||||
|
||||
const howl1 = makeQuote(
|
||||
`angelheaded hipsters burning for the ancient heavenly connection to the starry dynamo in the machinery of night`,
|
||||
"Allen Ginsberg",
|
||||
"Howl",
|
||||
);
|
||||
|
||||
const eggTooth = makeQuote(
|
||||
`Ears are the eyes on the sides of your head.
|
||||
Memory lives here, between these apostrophes.
|
||||
As if to predict music, the ear contains a drum.`,
|
||||
"Benjamin Garcia",
|
||||
"Egg Tooth",
|
||||
);
|
||||
|
||||
const keepingThingsWhole = makeQuote(
|
||||
`In a field
|
||||
I am the absence
|
||||
of field.`,
|
||||
"Mark Strand",
|
||||
"Keeping Things Whole",
|
||||
);
|
||||
|
||||
const somberBull = makeQuote(
|
||||
`I welcome terror, that somber bull,
|
||||
I fight for your name held in his jaws.`,
|
||||
"Andrea Cote",
|
||||
"Somber Bull",
|
||||
);
|
||||
|
||||
const theRoom = makeQuote(
|
||||
`The house turns slowly round its one closed room.`,
|
||||
"Kevin Hart",
|
||||
"The Room",
|
||||
);
|
||||
|
||||
const illWin = makeQuote(
|
||||
`I'll win the way
|
||||
I always do
|
||||
by being gone
|
||||
when they come.`,
|
||||
"Robert Creely",
|
||||
`I'll Win`,
|
||||
);
|
||||
|
||||
const kingfishers1 = makeQuote(
|
||||
`As kingfishers catch fire, dragonflies draw flame`,
|
||||
"Gerard Manley Hopkins",
|
||||
"As Kingfishers Catch Fire",
|
||||
);
|
||||
|
||||
const frenchNovel = makeQuote(
|
||||
`With boots we trekked through slush for a bottle of red wine\
|
||||
we weren't allowed to buy, our shirts unbuttoned
|
||||
under our winter coats.`,
|
||||
"Richie Hofmann",
|
||||
"French Novel",
|
||||
);
|
||||
|
||||
const iHaveSlept = makeQuote(
|
||||
`[...]on an amber throne of cockroach casings, on a carpet of needles from a cemetery pine[...]`,
|
||||
"Diane Seuss",
|
||||
"I have slept in many places, for years on mattresses that entered",
|
||||
);
|
||||
|
||||
const toMakeAPrarie = makeQuote(
|
||||
`To make a prairie it takes a clover and one bee,
|
||||
One clover, and a bee.
|
||||
And revery.
|
||||
The revery alone will do,
|
||||
If bees are few.`,
|
||||
"Emily Dickinson",
|
||||
"To make a prairie",
|
||||
);
|
||||
|
||||
const blackbird1 = makeQuote(
|
||||
`I was of three minds,
|
||||
Like a tree
|
||||
In which there are three blackbirds.`,
|
||||
"Wallace Stevens",
|
||||
"Thirteen Ways of Looking at a Blackbird",
|
||||
);
|
||||
|
||||
const emergency1 = makeQuote(
|
||||
`Hasn't the goal all along been
|
||||
to make an unforgettable sound?`,
|
||||
"Dobby Gibson",
|
||||
"This Is a Test of the Federal Emergency Management Agency Wireless Warning System",
|
||||
);
|
||||
|
||||
const stationOfMetro = makeQuote(
|
||||
`The apparition of these faces in the crowd;
|
||||
Petals on a wet, black bough.`,
|
||||
"Ezra Pound",
|
||||
"In a Station of the Metro",
|
||||
);
|
||||
|
||||
export const inspoRepo = [
|
||||
reMonarching,
|
||||
winterRainDeepens,
|
||||
bermanSnow,
|
||||
stillLife,
|
||||
landlady,
|
||||
suicideNote,
|
||||
capybaraHotsprings1,
|
||||
howl1,
|
||||
eggTooth,
|
||||
keepingThingsWhole,
|
||||
somberBull,
|
||||
theRoom,
|
||||
illWin,
|
||||
kingfishers1,
|
||||
frenchNovel,
|
||||
iHaveSlept,
|
||||
toMakeAPrarie,
|
||||
blackbird1,
|
||||
emergency1,
|
||||
stationOfMetro,
|
||||
];
|
||||
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
beginGameHandler,
|
||||
createGameHandler,
|
||||
deleteGameDescriptionHandler,
|
||||
enterGameOptionConversation,
|
||||
getArchivedPoemHandler,
|
||||
leaveGameHandler,
|
||||
restartAppHandler,
|
||||
restartGameHandler,
|
||||
returnToPreviousStateHandler,
|
||||
setUpGameHandler,
|
||||
tryJoinGameHandler,
|
||||
tryLeaveGameHandler,
|
||||
} from "handlers";
|
||||
import {
|
||||
BasicCallback,
|
||||
Callback,
|
||||
Context,
|
||||
GameConfigCallback,
|
||||
GameOption,
|
||||
HandlerMap,
|
||||
} from "types";
|
||||
|
||||
const gameOptionConversationFn =
|
||||
(gameOption: GameOption) => async (ctx: Context) => {
|
||||
await enterGameOptionConversation(ctx, gameOption);
|
||||
};
|
||||
|
||||
export const callbackHandlerMap: HandlerMap<Callback> = {
|
||||
[BasicCallback.BEGIN_GAME]: beginGameHandler,
|
||||
[BasicCallback.CREATE_GAME]: createGameHandler,
|
||||
[BasicCallback.EXIT_ARCHIVE]: restartAppHandler,
|
||||
[BasicCallback.LEAVE_GAME]: leaveGameHandler,
|
||||
[BasicCallback.RESTART_APP]: restartAppHandler,
|
||||
[BasicCallback.RESTART_GAME]: restartGameHandler,
|
||||
[BasicCallback.RETURN_TO_PREVIOUS_STATE]: returnToPreviousStateHandler,
|
||||
[BasicCallback.SET_UP_GAME]: setUpGameHandler,
|
||||
[BasicCallback.TRY_JOIN]: tryJoinGameHandler,
|
||||
[BasicCallback.TRY_LEAVE]: tryLeaveGameHandler,
|
||||
[BasicCallback.VIEW_ARCHIVED_POEMS]: getArchivedPoemHandler,
|
||||
[GameConfigCallback.ADD_DESCRIPTION]: gameOptionConversationFn(
|
||||
GameOption.DESCRIPTION,
|
||||
),
|
||||
[GameConfigCallback.EDIT_DESCRIPTION]: gameOptionConversationFn(
|
||||
GameOption.DESCRIPTION,
|
||||
),
|
||||
[GameConfigCallback.REMOVE_DESCRIPTION]: deleteGameDescriptionHandler,
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { hidePoemMetaHandler, showPoemMetaHandler } from "handlers";
|
||||
import { CallbackWithData, HandlerMap } from "types";
|
||||
|
||||
export const callbackWithDataHandlerMap: HandlerMap<CallbackWithData> = {
|
||||
[CallbackWithData.HIDE_POEM_META]: hidePoemMetaHandler,
|
||||
[CallbackWithData.SHOW_POEM_META]: showPoemMetaHandler,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { inspoHandler, startHandler } from "handlers";
|
||||
import { Command, HandlerMap } from "types";
|
||||
|
||||
export const commandHandlerMap: HandlerMap<Command> = {
|
||||
[Command.START]: startHandler,
|
||||
[Command.RESTART]: startHandler,
|
||||
[Command.INSPO]: inspoHandler,
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
archivedPoemConversation,
|
||||
gameDescriptionConversation,
|
||||
gameIdConversation,
|
||||
} from "handlers";
|
||||
import { ConversationState, ConversationFn } from "types";
|
||||
|
||||
export const conversationHandlerMap: {
|
||||
[key in ConversationState]: ConversationFn;
|
||||
} = {
|
||||
[ConversationState.GET_ARCHIVED_POEM]: archivedPoemConversation,
|
||||
[ConversationState.GET_GAME_DESCRIPTION]: gameDescriptionConversation,
|
||||
[ConversationState.GET_GAME_ID]: gameIdConversation,
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ConversationState, GameOption } from "types";
|
||||
|
||||
export const gameOptionConversationMap = {
|
||||
[GameOption.DESCRIPTION]: ConversationState.GET_GAME_DESCRIPTION,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
export { callbackHandlerMap } from "./callback-handler-map";
|
||||
export { callbackWithDataHandlerMap } from "./callback-with-data-handler-map";
|
||||
export { commandHandlerMap } from "./command-handler-map";
|
||||
export { conversationHandlerMap } from "./conversation-handler-map";
|
||||
export { gameOptionConversationMap } from "./game-option-conversation-map";
|
||||
export { replyHandlerMap } from "./reply-handler-map";
|
||||
export { stateKeyboardButtonMap } from "./state-keyboard-button-map";
|
||||
export { stateMessageMap } from "./state-message-map";
|
||||
@@ -0,0 +1,8 @@
|
||||
import { writingReplyHandler } from "handlers";
|
||||
import { archiveSearchHandler } from "handlers/replies/archive-search";
|
||||
import { PlayerState, ReplyHandlerFn } from "types";
|
||||
|
||||
export const replyHandlerMap: { [key in PlayerState]?: ReplyHandlerFn } = {
|
||||
[PlayerState.IN_ARCHIVES]: archiveSearchHandler,
|
||||
[PlayerState.WRITING]: writingReplyHandler,
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { PlayerState } from "generated/prisma/enums";
|
||||
import { PlayerModel } from "generated/prisma/models";
|
||||
import { Callback, ConversationState, MessagingState } from "types";
|
||||
|
||||
export const stateCommandsMap: {
|
||||
[key in MessagingState]: ((player: PlayerModel) => Callback[]) | null;
|
||||
} = {
|
||||
[PlayerState.IN_ARCHIVES]: (_player) => [],
|
||||
[PlayerState.LOBBY]: (_player) => [],
|
||||
[PlayerState.POST_GAME]: (_player) => [],
|
||||
[PlayerState.WRITING]: (_player) => [],
|
||||
[PlayerState.SETTING_UP_GAME]: (_player) => [],
|
||||
[PlayerState.SPECTATING]: (_player) => [],
|
||||
[PlayerState.START]: (_player) => [],
|
||||
[PlayerState.TRYING_LEAVE]: (_player) => [],
|
||||
[PlayerState.TYPING]: (_player) => [],
|
||||
[PlayerState.VIEWING_ARCHIVED_POEM]: (_player) => [],
|
||||
[PlayerState.WAITING_AFTER_WRITING]: (_player) => [],
|
||||
[PlayerState.WAITING_TO_WRITE]: (_player) => [],
|
||||
[ConversationState.GET_ARCHIVED_POEM]: (_player) => [],
|
||||
[ConversationState.GET_GAME_DESCRIPTION]: (_player) => [],
|
||||
[ConversationState.GET_GAME_ID]: (_player) => [],
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
import { Player } from "classes";
|
||||
import { BOT_NAME, LOBBY_MESSAGE } from "const/strings";
|
||||
import { getGameOrThrow } from "data";
|
||||
import {
|
||||
ConversationState,
|
||||
GameOption,
|
||||
MessagingState,
|
||||
PlayerState,
|
||||
} from "types";
|
||||
import { randomStarDecorator } from "utils";
|
||||
|
||||
export const stateMessageMap: {
|
||||
[key in MessagingState]: ((player: Player) => string) | null;
|
||||
} = {
|
||||
// player state messages
|
||||
[PlayerState.IN_ARCHIVES]: (player) => {
|
||||
// on first search
|
||||
if (player.previousState === PlayerState.START) {
|
||||
// TODO use force reply or refactor conversation to make this easier
|
||||
return `Welcome to the archives! Poems completed in games are stored here with 4-digit ids. Reply to this message (tap and press "Reply") with a poem id to re-read it.`;
|
||||
}
|
||||
// on subsequent actions
|
||||
return `Enter another poem id to search, or click the "Exit Archive" button to return to the start page.`;
|
||||
},
|
||||
[PlayerState.LOBBY]: (player) => {
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
const players = game.getPlayers();
|
||||
const isHost = player.isHost();
|
||||
|
||||
return LOBBY_MESSAGE(players, game, isHost);
|
||||
},
|
||||
[PlayerState.POST_GAME]: (_player) => {
|
||||
return `Poem finished! Here's what you all came up with:`;
|
||||
},
|
||||
[PlayerState.WRITING]: (player) => {
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
const firstPlayer = !game.poem.lines.length;
|
||||
const lastPlayer = !game.getEligiblePlayers().length;
|
||||
const previousLine = game.previousLine();
|
||||
// TODO use force reply or refactor conversation to make this easier
|
||||
let message = `It's your turn to add to the poem! ${randomStarDecorator()}\n\nReply to this message (tap and press "Reply") to add the next line.\n\n`;
|
||||
if (previousLine) {
|
||||
message += `Previous line: ${previousLine}\n\n`;
|
||||
}
|
||||
if (firstPlayer && lastPlayer) {
|
||||
message += "You're writing the only line, I guess!!\n\n";
|
||||
} else {
|
||||
if (firstPlayer) message += `You're writing the first line!\n\n`;
|
||||
if (lastPlayer) message += `You're writing the last line!\n\n`;
|
||||
}
|
||||
return message;
|
||||
},
|
||||
[PlayerState.SETTING_UP_GAME]: (player) => {
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
const description = game?.options[GameOption.DESCRIPTION];
|
||||
return `Use the buttons below to configure your game. (More configuration options TBD). Press "Create Game" when ready.${
|
||||
description ? `\n\nGame Description: ${description}` : ""
|
||||
}`;
|
||||
},
|
||||
[PlayerState.SPECTATING]: (player) => {
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
let message =
|
||||
"This game is in progress, so you are spectating until the current round is over. ";
|
||||
if (game.poem.completed) {
|
||||
message +=
|
||||
"Players are viewing a completed poem and waiting for the host to return to the lobby.";
|
||||
} else {
|
||||
const eligiblePlayersRemaining = game.getEligiblePlayers().length + 1;
|
||||
const oneRemaining = eligiblePlayersRemaining === 1;
|
||||
message += `There ${
|
||||
oneRemaining ? "is" : "are"
|
||||
} ${eligiblePlayersRemaining} more player${oneRemaining ? "" : "s"} to go.`;
|
||||
}
|
||||
return message;
|
||||
},
|
||||
[PlayerState.START]: (_player) =>
|
||||
`Welcome to ${BOT_NAME}!\n\nThe game is simple: work with your friends to write a poem one line at a time. However, each person only gets to see the line written by the person before them, leading to chaos and streams-of-consciousness.\n\nNOTE: This bot is a work-in-progress, with further game modes and quality-of-life features planned for future development. For comments, questions, or to report a bug, please message ${process.env.MY_USERNAME}. (Last updated: 1-18-2026)\n\nHit one of the buttons below to get started.`,
|
||||
[PlayerState.TRYING_LEAVE]: (player) => {
|
||||
return `Are you sure you want to leave the game?${player.isHost() ? " Since you are the host, this will end the game for all players." : ""}`;
|
||||
},
|
||||
[PlayerState.TYPING]: null,
|
||||
[PlayerState.VIEWING_ARCHIVED_POEM]: (_player) => {
|
||||
return "viewing archived poem state";
|
||||
},
|
||||
[PlayerState.WAITING_AFTER_WRITING]: (player) => {
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
const totalEligible = game.getEligiblePlayers().length;
|
||||
const eligiblePlayersRemaining = totalEligible || 1;
|
||||
const oneRemaining = eligiblePlayersRemaining === 1;
|
||||
return `There ${
|
||||
oneRemaining ? "is" : "are"
|
||||
} ${eligiblePlayersRemaining} more player${oneRemaining ? "" : "s"} to go.`;
|
||||
},
|
||||
[PlayerState.WAITING_TO_WRITE]: (player) => {
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
const eligiblePlayers = game.getEligiblePlayers();
|
||||
const oneRemaining = eligiblePlayers.length === 1;
|
||||
const playerPosition =
|
||||
eligiblePlayers.findIndex(
|
||||
(eligiblePlayer) => eligiblePlayer.id === player.id,
|
||||
) + 1;
|
||||
const playerIsNext = playerPosition === 1;
|
||||
const nextUpAlert = playerIsNext ? `Your turn is next!` : "";
|
||||
const queueStatus = `There ${
|
||||
oneRemaining ? "is" : "are"
|
||||
} now ${playerPosition} player${oneRemaining ? "" : "s"} ahead of you.`;
|
||||
return `Waiting to write. ${nextUpAlert || queueStatus}`;
|
||||
},
|
||||
// conversation state messages
|
||||
[ConversationState.GET_GAME_DESCRIPTION]: (player) => {
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
const description = game?.options[GameOption.DESCRIPTION];
|
||||
return `Add a description for your game (a writing prompt, a greeting, or anything else).${
|
||||
description ? `\n\nCurrent Description: ${description}` : ""
|
||||
}`;
|
||||
},
|
||||
[ConversationState.GET_ARCHIVED_POEM]: (_player) =>
|
||||
"Enter the id of the poem to check out.",
|
||||
[ConversationState.GET_GAME_ID]: () => "Enter a 4-character game ID.",
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Game, Player } from "classes";
|
||||
import { GameOption } from "types";
|
||||
|
||||
export const MY_USERNAME = `${process.env.MY_USERNAME}`;
|
||||
export const PLS_REPORT = `Please let ${MY_USERNAME} know.`;
|
||||
export const BOT_NAME = "JOLB";
|
||||
export const BOT_RUNNING = `${BOT_NAME} is now running.`;
|
||||
export const FALLBACK_USERNAME = "User";
|
||||
export const GAME_NOT_FOUND = "Game not found.";
|
||||
export const GAME_NOT_FOUND_WITH_ID = (id: string) =>
|
||||
`Game not found with id ${id}.`;
|
||||
export const HAS_JOINED = (name: string) => `${name} has joined the game.`;
|
||||
export const HAS_LEFT = (name: string) => `${name} has left the game.`;
|
||||
export const HIDE_SHOW_METADATA = (show?: boolean) =>
|
||||
`${show ? "Hide" : "Show"} Metadata`;
|
||||
export const HOST_LEFT =
|
||||
"The host has left the game. Returning to start screen...";
|
||||
export const HOST_RESTARTED = `The host has restarted the game.`;
|
||||
export const MISSING_BUTTON_LABEL = "Missing button label.";
|
||||
export const NO_BOTS_PLS = "No bots pls.";
|
||||
export const ONLY_HOST_CAN_START = "Only the host can start the game.";
|
||||
export const PLAYER_NOT_FOUND = "Player not found.";
|
||||
export const POEM_NOT_FOUND = "Poem not found.";
|
||||
export const POEM_NOT_FOUND_WITH_ID = (id: number) =>
|
||||
`Poem not found with id ${id}.`;
|
||||
export const USER_NOT_FOUND = "User not found.";
|
||||
export const RETRIEVE_FROM_ARCHIVE = (id: number) => `(Poem ID: ${id})`;
|
||||
export const UNKNOWN_ERROR = "An unknown error occurred.";
|
||||
export const VALIDATION_POEM_ID_IS_NUMBER = "Poem id must be a number.";
|
||||
export const WELCOME_TO_ARCHIVES = "Welcome to the archives!";
|
||||
|
||||
// player state messages
|
||||
export const LOBBY_MESSAGE = (
|
||||
players: Player[],
|
||||
game: Game,
|
||||
isHost: boolean
|
||||
) => {
|
||||
const description = game.options[GameOption.DESCRIPTION];
|
||||
let message = `Welcome to the game!\n\n`;
|
||||
if (description) {
|
||||
message += `Description: ${description ? `${description}\n\n` : ""}`;
|
||||
}
|
||||
message += `Players in lobby:\n\n${players
|
||||
.map((player) => {
|
||||
return `- ${player.userName}${
|
||||
player.id === game.hostId ? " (host)" : ""
|
||||
}`;
|
||||
})
|
||||
.join("\n")}\n\n`;
|
||||
if (!isHost) {
|
||||
message += "Waiting for host to start game.\n\n";
|
||||
}
|
||||
message += `Invite others with game code ${game?.id}.`;
|
||||
return message;
|
||||
};
|
||||
export const POST_GAME_MESSAGE = ``;
|
||||
export const READY_TO_WRITE_MESSAGE = ``;
|
||||
export const SETTING_UP_GAME_MESSAGE = ``;
|
||||
export const SPECTATING_MESSAGE = ``;
|
||||
export const START_MESSAGE = ``;
|
||||
export const TRYING_LEAVE_MESSAGE = ``;
|
||||
export const TYPING_MESSAGE = ``;
|
||||
export const VIEWING_ARCHIVED_POEM_MESSAGE = ``;
|
||||
export const WAITING_AFTER_WRITING_MESSAGE = ``;
|
||||
export const WAITING_TO_WRITE_MESSAGE = ``;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { GameModel, PlayerModel, PoemModel } from "generated/prisma/models";
|
||||
import { prisma } from "../../lib/prisma";
|
||||
import { ObjName } from "./types";
|
||||
|
||||
export async function create<InputType, OutputType>(
|
||||
objName: ObjName,
|
||||
data: InputType,
|
||||
) {
|
||||
try {
|
||||
let newObj;
|
||||
switch (objName) {
|
||||
case "game":
|
||||
if (Array.isArray(data)) {
|
||||
newObj = await prisma.game.createMany({
|
||||
data: data as GameModel[],
|
||||
});
|
||||
} else {
|
||||
newObj = await prisma.game.create({
|
||||
data: data as GameModel,
|
||||
include: {
|
||||
players: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "player":
|
||||
if (Array.isArray(data)) {
|
||||
newObj = await prisma.player.createMany({
|
||||
data: data as PlayerModel[],
|
||||
});
|
||||
} else {
|
||||
newObj = await prisma.player.create({
|
||||
data: data as PlayerModel,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "poem":
|
||||
if (Array.isArray(data)) {
|
||||
newObj = await prisma.poem.createMany({
|
||||
data: (data as PoemModel[]).map((d) => ({
|
||||
...d,
|
||||
lines: d.lines!,
|
||||
})),
|
||||
});
|
||||
} else {
|
||||
newObj = await prisma.poem.create({
|
||||
data: { ...(data as PoemModel), lines: (data as PoemModel).lines! },
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
return newObj as OutputType;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { prisma } from "../../lib/prisma";
|
||||
import { IdSearchKey, ObjName } from "./types";
|
||||
|
||||
export async function del(objName: ObjName, id: IdSearchKey) {
|
||||
try {
|
||||
switch (objName) {
|
||||
case "game":
|
||||
if (Array.isArray(id)) {
|
||||
await prisma.game.deleteMany({
|
||||
where: { gameId: { in: id as number[] } },
|
||||
});
|
||||
} else if (id === "all") {
|
||||
await prisma.game.deleteMany({});
|
||||
} else {
|
||||
await prisma.game.delete({
|
||||
where: { gameId: id as number },
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "player":
|
||||
if (Array.isArray(id)) {
|
||||
await prisma.player.deleteMany({
|
||||
where: { playerId: { in: id as string[] } },
|
||||
});
|
||||
} else if (id === "all") {
|
||||
await prisma.player.deleteMany({});
|
||||
} else {
|
||||
await prisma.player.delete({
|
||||
where: { playerId: id as string },
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "poem":
|
||||
if (Array.isArray(id)) {
|
||||
await prisma.poem.deleteMany({
|
||||
where: { poemId: { in: id as number[] } },
|
||||
});
|
||||
} else if (id === "all") {
|
||||
await prisma.poem.deleteMany({});
|
||||
} else {
|
||||
await prisma.poem.delete({
|
||||
where: { poemId: id as number },
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
await prisma.$disconnect();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { prisma } from "lib/prisma";
|
||||
import { IdSearchKey, Model } from "./types";
|
||||
|
||||
export async function get<T = Model | Model[]>(
|
||||
objName: string,
|
||||
id: IdSearchKey,
|
||||
) {
|
||||
try {
|
||||
let obj;
|
||||
switch (objName) {
|
||||
case "game":
|
||||
if (Array.isArray(id)) {
|
||||
obj = await prisma.game.findMany({
|
||||
where: { gameId: { in: id as number[] } },
|
||||
});
|
||||
} else if (id === "all") {
|
||||
obj = await prisma.game.findMany();
|
||||
} else {
|
||||
obj = await prisma.game.findUnique({
|
||||
where: { gameId: id as number },
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "player":
|
||||
if (Array.isArray(id)) {
|
||||
obj = await prisma.player.findMany({
|
||||
where: { playerId: { in: id as string[] } },
|
||||
});
|
||||
} else if (id === "all") {
|
||||
obj = await prisma.player.findMany();
|
||||
} else {
|
||||
obj = await prisma.player.findUnique({
|
||||
where: { playerId: id as string },
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "poem":
|
||||
if (Array.isArray(id)) {
|
||||
obj = await prisma.poem.findMany({
|
||||
where: { poemId: { in: id as number[] } },
|
||||
});
|
||||
} else {
|
||||
obj = await prisma.poem.findUnique({
|
||||
where: { poemId: id as number },
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
return obj as T;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export { create } from "./create";
|
||||
export { del } from "./delete";
|
||||
export { get } from "./get";
|
||||
export { update } from "./update";
|
||||
@@ -0,0 +1,11 @@
|
||||
import { GameModel, PlayerModel, PoemModel } from "generated/prisma/models";
|
||||
|
||||
export type ObjName = "game" | "player" | "poem";
|
||||
export type Model = GameModel | PlayerModel | PoemModel;
|
||||
|
||||
export type PartialGame = Partial<GameModel>;
|
||||
export type PartialPlayer = Partial<PlayerModel>;
|
||||
export type PartialPoem = Partial<PoemModel>;
|
||||
export type PartialModel = PartialGame | PartialPlayer | PartialPoem;
|
||||
|
||||
export type IdSearchKey = number | string | string[] | number[] | "all";
|
||||
@@ -0,0 +1,73 @@
|
||||
import { prisma } from "../../lib/prisma";
|
||||
import {
|
||||
PartialGame,
|
||||
PartialPlayer,
|
||||
PartialPoem,
|
||||
ObjName,
|
||||
IdSearchKey,
|
||||
} from "./types";
|
||||
|
||||
export async function update<InputType, OutputType>(
|
||||
objName: ObjName,
|
||||
data: InputType,
|
||||
id: IdSearchKey,
|
||||
) {
|
||||
try {
|
||||
let updatedObj;
|
||||
switch (objName) {
|
||||
case "game":
|
||||
if (Array.isArray(id)) {
|
||||
updatedObj = await prisma.game.updateManyAndReturn({
|
||||
where: { gameId: { in: id as number[] } },
|
||||
data: data as PartialGame,
|
||||
});
|
||||
} else {
|
||||
updatedObj = await prisma.game.update({
|
||||
where: { gameId: id as number },
|
||||
data: data as PartialGame,
|
||||
include: {
|
||||
players: true,
|
||||
poem: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "player":
|
||||
if (Array.isArray(id)) {
|
||||
updatedObj = await prisma.player.updateManyAndReturn({
|
||||
where: { playerId: { in: id as string[] } },
|
||||
data: data as PartialPlayer,
|
||||
});
|
||||
} else {
|
||||
updatedObj = await prisma.player.update({
|
||||
where: { playerId: id as string },
|
||||
data: data as PartialPlayer,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "poem":
|
||||
if (Array.isArray(id)) {
|
||||
updatedObj = await prisma.poem.updateManyAndReturn({
|
||||
where: { poemId: { in: id as number[] } },
|
||||
data: {
|
||||
...(data as PartialPoem),
|
||||
lines: (data as PartialPoem).lines!,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
updatedObj = await prisma.poem.update({
|
||||
where: { poemId: id as number },
|
||||
data: {
|
||||
...(data as PartialPoem),
|
||||
lines: (data as PartialPoem).lines!,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
return updatedObj as OutputType;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { GameModel } from "generated/prisma/models";
|
||||
import { PartialGame } from "data/crud/types";
|
||||
import { create, del, get, update } from "../crud";
|
||||
|
||||
export const createGame = async (data: PartialGame) => {
|
||||
return await create<PartialGame, GameModel>("game", data);
|
||||
};
|
||||
|
||||
export const deleteGame = async (id: number) => {
|
||||
return await del("game", id);
|
||||
};
|
||||
|
||||
export const getGame = async (id: number) => {
|
||||
return await get<GameModel>("game", id);
|
||||
};
|
||||
|
||||
export const updateGame = async (id: number, data: PartialGame) => {
|
||||
return await update<PartialGame, GameModel>("game", data, id);
|
||||
};
|
||||
|
||||
export const deleteGames = async (ids: number[]) => {
|
||||
return await del("game", ids);
|
||||
};
|
||||
|
||||
export const getGames = async (ids: number[] | "all") => {
|
||||
return await get<GameModel[]>("game", ids);
|
||||
};
|
||||
|
||||
export const updateGames = async (ids: number[], data: PartialGame) => {
|
||||
return await update<PartialGame, GameModel[]>("game", data, ids);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./game";
|
||||
export * from "./player";
|
||||
export * from "./poem";
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Prisma } from "generated/prisma/client";
|
||||
import { PlayerState } from "generated/prisma/enums";
|
||||
import { PlayerModel } from "generated/prisma/models";
|
||||
import { PartialPlayer } from "data/crud/types";
|
||||
import { prisma } from "lib/prisma";
|
||||
import { Context } from "types";
|
||||
import { getUserInfo } from "utils";
|
||||
import { create, del, get, update } from "../crud";
|
||||
|
||||
export const createPlayer = async (ctx: Context) => {
|
||||
const { playerId, userName } = await getUserInfo(ctx);
|
||||
return await create<PartialPlayer, PlayerModel>("player", {
|
||||
userName,
|
||||
playerId,
|
||||
});
|
||||
};
|
||||
|
||||
export const createPlayers = async (data: PartialPlayer[]) => {
|
||||
return await create<PartialPlayer[], PlayerModel[]>("player", data);
|
||||
};
|
||||
|
||||
export const deletePlayer = async (id: number) => {
|
||||
return await del("player", id);
|
||||
};
|
||||
|
||||
export const deletePlayers = async (ids: number[]) => {
|
||||
return await del("player", ids);
|
||||
};
|
||||
|
||||
export const getPlayer = async (
|
||||
findBy: string | Context,
|
||||
ifNotFound?: "throw" | "create",
|
||||
) => {
|
||||
const playerId =
|
||||
typeof findBy === "string" ? findBy : getUserInfo(findBy).playerId;
|
||||
const player = await get<PlayerModel>("player", playerId);
|
||||
if (ifNotFound && !player) {
|
||||
if (ifNotFound === "throw") {
|
||||
throw new Error(`Player with id ${playerId} not found`);
|
||||
}
|
||||
return await createPlayer(ifNotFound);
|
||||
}
|
||||
return player;
|
||||
};
|
||||
|
||||
export const getPlayers = async (ids: string[]) => {
|
||||
return await get<PlayerModel[]>("player", ids);
|
||||
};
|
||||
|
||||
export const updatePlayer = async (id: number, data: Partial<PlayerModel>) => {
|
||||
return await update<Partial<PlayerModel>, PlayerModel>("player", data, id);
|
||||
};
|
||||
|
||||
export const updatePlayers = async (
|
||||
ids: number[],
|
||||
data: Partial<PlayerModel>,
|
||||
) => {
|
||||
return await update<Partial<PlayerModel>, PlayerModel[]>("player", data, ids);
|
||||
};
|
||||
|
||||
export const updatePlayerState = async (id: number, newState: PlayerState) => {
|
||||
await prisma.$executeRaw`
|
||||
UPDATE "Player"
|
||||
SET "previousState" = "state",
|
||||
"state" = ${newState}
|
||||
WHERE "id" IS ${id});
|
||||
`;
|
||||
};
|
||||
|
||||
export const updatePlayerStates = async (
|
||||
ids: number[],
|
||||
newState: PlayerState,
|
||||
) => {
|
||||
await prisma.$executeRaw`
|
||||
UPDATE "Player"
|
||||
SET "previousState" = "state",
|
||||
"state" = ${newState}
|
||||
WHERE "id" IN (${Prisma.join(ids)});
|
||||
`;
|
||||
};
|
||||
|
||||
export const updatePlayerGame = async (id: number, currentGameId: number) => {
|
||||
return await update<PartialPlayer, PlayerModel>(
|
||||
"player",
|
||||
{ currentGameId },
|
||||
id,
|
||||
);
|
||||
};
|
||||
|
||||
export const updatePlayerGames = async (
|
||||
ids: number[],
|
||||
currentGameId: number,
|
||||
) => {
|
||||
return await update<PartialPlayer, PlayerModel[]>(
|
||||
"player",
|
||||
{ currentGameId },
|
||||
ids,
|
||||
);
|
||||
};
|
||||
|
||||
export const updatePlayerUsername = async (id: number, userName: string) => {
|
||||
return await update<PartialPlayer, PlayerModel>("player", { userName }, id);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PoemModel } from "generated/prisma/models";
|
||||
import { create, del, get, update } from "../crud";
|
||||
import { PartialPoem } from "data/crud/types";
|
||||
|
||||
export const createPoem = async (data: PartialPoem) => {
|
||||
return await create<PartialPoem, PoemModel>("poem", data);
|
||||
};
|
||||
|
||||
export const deletePoem = async (id: number) => {
|
||||
return await del("poem", id);
|
||||
};
|
||||
|
||||
export const getPoem = async (id: number) => {
|
||||
return await get<PoemModel>("poem", id);
|
||||
};
|
||||
|
||||
export const updatePoem = async (id: number, data: PartialPoem) => {
|
||||
return await update<PartialPoem, PoemModel>("poem", data, id);
|
||||
};
|
||||
|
||||
export const createPoems = async (data: PartialPoem[]) => {
|
||||
return await create<PartialPoem[], PoemModel[]>("poem", data);
|
||||
};
|
||||
|
||||
export const deletePoems = async (ids: number[]) => {
|
||||
return await del("poem", ids);
|
||||
};
|
||||
|
||||
export const getPoems = async (ids: number[]) => {
|
||||
return await get<PoemModel[]>("poem", ids);
|
||||
};
|
||||
|
||||
export const updatePoems = async (ids: number[], data: PartialPoem) => {
|
||||
return await update<PartialPoem, PoemModel[]>("poem", data, ids);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./crud";
|
||||
export * from "./helpers";
|
||||
export * from "./validation";
|
||||
@@ -0,0 +1,3 @@
|
||||
export const validateGame = () => {
|
||||
return true;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export { validateGame } from "./game";
|
||||
export { validatePlayer } from "./player";
|
||||
export { validatePoem } from "./poem";
|
||||
@@ -0,0 +1,3 @@
|
||||
export const validatePlayer = () => {
|
||||
return true;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export const validatePoem = () => {
|
||||
return true;
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
||||
* Use it to get access to models, enums, and input types.
|
||||
*
|
||||
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
|
||||
* See `client.ts` for the standard, server-side entry point.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
import * as Prisma from './internal/prismaNamespaceBrowser'
|
||||
export { Prisma }
|
||||
export * as $Enums from './enums'
|
||||
export * from './enums';
|
||||
/**
|
||||
* Model Player
|
||||
*
|
||||
*/
|
||||
export type Player = Prisma.PlayerModel
|
||||
/**
|
||||
* Model Game
|
||||
*
|
||||
*/
|
||||
export type Game = Prisma.GameModel
|
||||
/**
|
||||
* Model GameConfig
|
||||
*
|
||||
*/
|
||||
export type GameConfig = Prisma.GameConfigModel
|
||||
/**
|
||||
* Model Poem
|
||||
*
|
||||
*/
|
||||
export type Poem = Prisma.PoemModel
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
|
||||
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
import * as process from 'node:process'
|
||||
import * as path from 'node:path'
|
||||
|
||||
import * as runtime from "@prisma/client/runtime/client"
|
||||
import * as $Enums from "./enums"
|
||||
import * as $Class from "./internal/class"
|
||||
import * as Prisma from "./internal/prismaNamespace"
|
||||
|
||||
export * as $Enums from './enums'
|
||||
export * from "./enums"
|
||||
/**
|
||||
* ## Prisma Client
|
||||
*
|
||||
* Type-safe database client for TypeScript
|
||||
* @example
|
||||
* ```
|
||||
* const prisma = new PrismaClient()
|
||||
* // Fetch zero or more Players
|
||||
* const players = await prisma.player.findMany()
|
||||
* ```
|
||||
*
|
||||
* Read more in our [docs](https://pris.ly/d/client).
|
||||
*/
|
||||
export const PrismaClient = $Class.getPrismaClientClass()
|
||||
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||
export { Prisma }
|
||||
|
||||
/**
|
||||
* Model Player
|
||||
*
|
||||
*/
|
||||
export type Player = Prisma.PlayerModel
|
||||
/**
|
||||
* Model Game
|
||||
*
|
||||
*/
|
||||
export type Game = Prisma.GameModel
|
||||
/**
|
||||
* Model GameConfig
|
||||
*
|
||||
*/
|
||||
export type GameConfig = Prisma.GameConfigModel
|
||||
/**
|
||||
* Model Poem
|
||||
*
|
||||
*/
|
||||
export type Poem = Prisma.PoemModel
|
||||
@@ -0,0 +1,401 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
import type * as runtime from "@prisma/client/runtime/client"
|
||||
import * as $Enums from "./enums"
|
||||
import type * as Prisma from "./internal/prismaNamespace"
|
||||
|
||||
|
||||
export type StringFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
mode?: Prisma.QueryMode
|
||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||
}
|
||||
|
||||
export type DateTimeFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||
}
|
||||
|
||||
export type IntNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||
}
|
||||
|
||||
export type EnumPlayerStateFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.PlayerState | Prisma.EnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.PlayerState[] | Prisma.ListEnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
notIn?: $Enums.PlayerState[] | Prisma.ListEnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedEnumPlayerStateFilter<$PrismaModel> | $Enums.PlayerState
|
||||
}
|
||||
|
||||
export type SortOrderInput = {
|
||||
sort: Prisma.SortOrder
|
||||
nulls?: Prisma.NullsOrder
|
||||
}
|
||||
|
||||
export type StringWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
mode?: Prisma.QueryMode
|
||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type EnumPlayerStateWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.PlayerState | Prisma.EnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.PlayerState[] | Prisma.ListEnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
notIn?: $Enums.PlayerState[] | Prisma.ListEnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedEnumPlayerStateWithAggregatesFilter<$PrismaModel> | $Enums.PlayerState
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedEnumPlayerStateFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedEnumPlayerStateFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type IntFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||
}
|
||||
|
||||
export type BoolFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||
}
|
||||
|
||||
export type IntWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type BoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type JsonFilter<$PrismaModel = never> =
|
||||
| Prisma.PatchUndefined<
|
||||
Prisma.Either<Required<JsonFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonFilterBase<$PrismaModel>>, 'path'>>,
|
||||
Required<JsonFilterBase<$PrismaModel>>
|
||||
>
|
||||
| Prisma.OptionalFlat<Omit<Required<JsonFilterBase<$PrismaModel>>, 'path'>>
|
||||
|
||||
export type JsonFilterBase<$PrismaModel = never> = {
|
||||
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||
path?: string[]
|
||||
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||
}
|
||||
|
||||
export type JsonWithAggregatesFilter<$PrismaModel = never> =
|
||||
| Prisma.PatchUndefined<
|
||||
Prisma.Either<Required<JsonWithAggregatesFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonWithAggregatesFilterBase<$PrismaModel>>, 'path'>>,
|
||||
Required<JsonWithAggregatesFilterBase<$PrismaModel>>
|
||||
>
|
||||
| Prisma.OptionalFlat<Omit<Required<JsonWithAggregatesFilterBase<$PrismaModel>>, 'path'>>
|
||||
|
||||
export type JsonWithAggregatesFilterBase<$PrismaModel = never> = {
|
||||
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||
path?: string[]
|
||||
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedJsonFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedJsonFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedStringFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||
}
|
||||
|
||||
export type NestedDateTimeFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||
}
|
||||
|
||||
export type NestedIntNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||
}
|
||||
|
||||
export type NestedEnumPlayerStateFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.PlayerState | Prisma.EnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.PlayerState[] | Prisma.ListEnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
notIn?: $Enums.PlayerState[] | Prisma.ListEnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedEnumPlayerStateFilter<$PrismaModel> | $Enums.PlayerState
|
||||
}
|
||||
|
||||
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedIntFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||
}
|
||||
|
||||
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedFloatNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
|
||||
}
|
||||
|
||||
export type NestedEnumPlayerStateWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.PlayerState | Prisma.EnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.PlayerState[] | Prisma.ListEnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
notIn?: $Enums.PlayerState[] | Prisma.ListEnumPlayerStateFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedEnumPlayerStateWithAggregatesFilter<$PrismaModel> | $Enums.PlayerState
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedEnumPlayerStateFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedEnumPlayerStateFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedBoolFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||
}
|
||||
|
||||
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedFloatFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
||||
}
|
||||
|
||||
export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedJsonFilter<$PrismaModel = never> =
|
||||
| Prisma.PatchUndefined<
|
||||
Prisma.Either<Required<NestedJsonFilterBase<$PrismaModel>>, Exclude<keyof Required<NestedJsonFilterBase<$PrismaModel>>, 'path'>>,
|
||||
Required<NestedJsonFilterBase<$PrismaModel>>
|
||||
>
|
||||
| Prisma.OptionalFlat<Omit<Required<NestedJsonFilterBase<$PrismaModel>>, 'path'>>
|
||||
|
||||
export type NestedJsonFilterBase<$PrismaModel = never> = {
|
||||
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||
path?: string[]
|
||||
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file exports all enum related types from the schema.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
export const PlayerState = {
|
||||
IN_ARCHIVES: 'IN_ARCHIVES',
|
||||
LOBBY: 'LOBBY',
|
||||
POST_GAME: 'POST_GAME',
|
||||
SETTING_UP_GAME: 'SETTING_UP_GAME',
|
||||
SPECTATING: 'SPECTATING',
|
||||
START: 'START',
|
||||
TRYING_LEAVE: 'TRYING_LEAVE',
|
||||
TYPING: 'TYPING',
|
||||
VIEWING_ARCHIVED_POEM: 'VIEWING_ARCHIVED_POEM',
|
||||
WAITING_AFTER_WRITING: 'WAITING_AFTER_WRITING',
|
||||
WAITING_TO_WRITE: 'WAITING_TO_WRITE',
|
||||
WRITING: 'WRITING'
|
||||
} as const
|
||||
|
||||
export type PlayerState = (typeof PlayerState)[keyof typeof PlayerState]
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,157 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* WARNING: This is an internal file that is subject to change!
|
||||
*
|
||||
* 🛑 Under no circumstances should you import this file directly! 🛑
|
||||
*
|
||||
* All exports from this file are wrapped under a `Prisma` namespace object in the browser.ts file.
|
||||
* While this enables partial backward compatibility, it is not part of the stable public API.
|
||||
*
|
||||
* If you are looking for your Models, Enums, and Input Types, please import them from the respective
|
||||
* model files in the `model` directory!
|
||||
*/
|
||||
|
||||
import * as runtime from "@prisma/client/runtime/index-browser"
|
||||
|
||||
export type * from '../models'
|
||||
export type * from './prismaNamespace'
|
||||
|
||||
export const Decimal = runtime.Decimal
|
||||
|
||||
|
||||
export const NullTypes = {
|
||||
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
|
||||
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
|
||||
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
|
||||
}
|
||||
/**
|
||||
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
|
||||
*
|
||||
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||
*/
|
||||
export const DbNull = runtime.DbNull
|
||||
|
||||
/**
|
||||
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
|
||||
*
|
||||
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||
*/
|
||||
export const JsonNull = runtime.JsonNull
|
||||
|
||||
/**
|
||||
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
|
||||
*
|
||||
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||
*/
|
||||
export const AnyNull = runtime.AnyNull
|
||||
|
||||
|
||||
export const ModelName = {
|
||||
Player: 'Player',
|
||||
Game: 'Game',
|
||||
GameConfig: 'GameConfig',
|
||||
Poem: 'Poem'
|
||||
} as const
|
||||
|
||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||
|
||||
/*
|
||||
* Enums
|
||||
*/
|
||||
|
||||
export const TransactionIsolationLevel = runtime.makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
} as const)
|
||||
|
||||
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
|
||||
|
||||
|
||||
export const PlayerScalarFieldEnum = {
|
||||
playerId: 'playerId',
|
||||
createdAt: 'createdAt',
|
||||
currentGameId: 'currentGameId',
|
||||
state: 'state',
|
||||
previousState: 'previousState',
|
||||
userName: 'userName'
|
||||
} as const
|
||||
|
||||
export type PlayerScalarFieldEnum = (typeof PlayerScalarFieldEnum)[keyof typeof PlayerScalarFieldEnum]
|
||||
|
||||
|
||||
export const GameScalarFieldEnum = {
|
||||
gameId: 'gameId',
|
||||
createdAt: 'createdAt',
|
||||
joinCode: 'joinCode',
|
||||
poemId: 'poemId',
|
||||
gameConfigId: 'gameConfigId',
|
||||
hostPlayerId: 'hostPlayerId',
|
||||
inProgress: 'inProgress'
|
||||
} as const
|
||||
|
||||
export type GameScalarFieldEnum = (typeof GameScalarFieldEnum)[keyof typeof GameScalarFieldEnum]
|
||||
|
||||
|
||||
export const GameConfigScalarFieldEnum = {
|
||||
configId: 'configId'
|
||||
} as const
|
||||
|
||||
export type GameConfigScalarFieldEnum = (typeof GameConfigScalarFieldEnum)[keyof typeof GameConfigScalarFieldEnum]
|
||||
|
||||
|
||||
export const PoemScalarFieldEnum = {
|
||||
poemId: 'poemId',
|
||||
createdAt: 'createdAt',
|
||||
gameId: 'gameId',
|
||||
finished: 'finished',
|
||||
lines: 'lines'
|
||||
} as const
|
||||
|
||||
export type PoemScalarFieldEnum = (typeof PoemScalarFieldEnum)[keyof typeof PoemScalarFieldEnum]
|
||||
|
||||
|
||||
export const SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
} as const
|
||||
|
||||
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
|
||||
|
||||
|
||||
export const JsonNullValueInput = {
|
||||
JsonNull: JsonNull
|
||||
} as const
|
||||
|
||||
export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput]
|
||||
|
||||
|
||||
export const QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
} as const
|
||||
|
||||
export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode]
|
||||
|
||||
|
||||
export const NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
} as const
|
||||
|
||||
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
|
||||
|
||||
|
||||
export const JsonNullValueFilter = {
|
||||
DbNull: DbNull,
|
||||
JsonNull: JsonNull,
|
||||
AnyNull: AnyNull
|
||||
} as const
|
||||
|
||||
export type JsonNullValueFilter = (typeof JsonNullValueFilter)[keyof typeof JsonNullValueFilter]
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This is a barrel export file for all models and their related types.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
export type * from './models/Player'
|
||||
export type * from './models/Game'
|
||||
export type * from './models/GameConfig'
|
||||
export type * from './models/Poem'
|
||||
export type * from './commonInputTypes'
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
import { ONLY_HOST_CAN_START } from "const";
|
||||
import { getPlayer } from "data";
|
||||
import { messagePlayer } from "messaging";
|
||||
import { HandlerFn } from "types";
|
||||
|
||||
export const beginGameHandler: HandlerFn = async (ctx) => {
|
||||
const player = getPlayer(ctx);
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
const isHost = player.isHost();
|
||||
if (!isHost) {
|
||||
await messagePlayer(player.id, player.state, ctx, ONLY_HOST_CAN_START);
|
||||
return;
|
||||
}
|
||||
await game.startGame(ctx);
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { getContextPlayerOrThrow } from "data";
|
||||
import { setPlayerState } from "player-state";
|
||||
import { HandlerFn, PlayerState } from "types";
|
||||
|
||||
export const createGameHandler: HandlerFn = async (ctx) => {
|
||||
const player = getContextPlayerOrThrow(ctx);
|
||||
await setPlayerState(player.id, ctx, PlayerState.LOBBY);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { getContextPlayerOrThrow, getGameOrThrow } from "data";
|
||||
import { setPlayerState } from "player-state";
|
||||
import { HandlerFn, PlayerState } from "types";
|
||||
|
||||
const { SETTING_UP_GAME } = PlayerState;
|
||||
|
||||
export const deleteGameDescriptionHandler: HandlerFn = async (ctx) => {
|
||||
const player = getContextPlayerOrThrow(ctx);
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
game.setOptions({ description: undefined });
|
||||
await setPlayerState(player.id, ctx, SETTING_UP_GAME);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import { HandlerFn } from "types";
|
||||
|
||||
export const exitArchiveHandler: HandlerFn = async (_ctx) => {};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { getContextPlayerOrCreate } from "data";
|
||||
import { setPlayerState } from "player-state";
|
||||
import { HandlerFn, PlayerState } from "types";
|
||||
|
||||
export const getArchivedPoemHandler: HandlerFn = async (ctx) => {
|
||||
const player = getContextPlayerOrCreate(ctx);
|
||||
await setPlayerState(player.id, ctx, PlayerState.IN_ARCHIVES);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
export { beginGameHandler } from "./begin-game";
|
||||
export { createGameHandler } from "./create-game";
|
||||
export { deleteGameDescriptionHandler } from "./delete-description";
|
||||
export { exitArchiveHandler } from "./exit-archive";
|
||||
export { getArchivedPoemHandler } from "./get-archived-poem";
|
||||
export { leaveGameHandler } from "./leave-game";
|
||||
export { restartAppHandler } from "./restart-app";
|
||||
export { restartGameHandler } from "./restart-game";
|
||||
export { returnToPreviousStateHandler } from "./return-to-previous-state";
|
||||
export { setUpGameHandler } from "./set-up-game";
|
||||
export { tryJoinGameHandler } from "./try-join-game";
|
||||
export { tryLeaveGameHandler } from "./try-leave";
|
||||
export * from "./utils";
|
||||
@@ -0,0 +1,8 @@
|
||||
import { getContextPlayerOrThrow, getGameOrThrow, leaveGame } from "data";
|
||||
import { HandlerFn } from "types";
|
||||
|
||||
export const leaveGameHandler: HandlerFn = async (ctx) => {
|
||||
const player = getContextPlayerOrThrow(ctx);
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
await leaveGame(ctx, game, player);
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { getContextPlayerOrCreate } from "data";
|
||||
import { setPlayerState } from "player-state";
|
||||
import { HandlerFn, PlayerState } from "types";
|
||||
|
||||
export const restartAppHandler: HandlerFn = async (ctx) => {
|
||||
const player = getContextPlayerOrCreate(ctx);
|
||||
await setPlayerState(player.id, ctx, PlayerState.START);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { getContextPlayerOrThrow, getGameOrThrow } from "data";
|
||||
import { HandlerFn } from "types";
|
||||
|
||||
export const restartGameHandler: HandlerFn = async (ctx) => {
|
||||
const player = getContextPlayerOrThrow(ctx);
|
||||
const isHost = player.isHost();
|
||||
if (!isHost) return;
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
await game.restart(ctx);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { getContextPlayerOrThrow, getGame, leaveGame } from "data";
|
||||
import { setPlayerState } from "player-state";
|
||||
import { HandlerFn, PlayerState } from "types";
|
||||
|
||||
export const returnToPreviousStateHandler: HandlerFn = async (ctx) => {
|
||||
const player = getContextPlayerOrThrow(ctx);
|
||||
const game = getGame(player.gameId);
|
||||
if (game && player.state === PlayerState.SETTING_UP_GAME) {
|
||||
await leaveGame(ctx, game, player);
|
||||
return;
|
||||
}
|
||||
await setPlayerState(
|
||||
player.id,
|
||||
ctx,
|
||||
player.previousState || PlayerState.START,
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { addGame, getContextPlayerOrThrow, joinGame } from "data";
|
||||
import { HandlerFn } from "types";
|
||||
|
||||
export const setUpGameHandler: HandlerFn = async (ctx) => {
|
||||
const player = getContextPlayerOrThrow(ctx);
|
||||
const game = addGame();
|
||||
await joinGame(ctx, game, player, true);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ConversationState, HandlerFn } from "types";
|
||||
import { enterConversation } from "utils";
|
||||
|
||||
export const tryJoinGameHandler: HandlerFn = async (ctx) => {
|
||||
await enterConversation(ConversationState.GET_GAME_ID, ctx);
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { getContextPlayerOrThrow } from "data";
|
||||
import { setPlayerState } from "player-state/set-player-state";
|
||||
import { HandlerFn, PlayerState } from "types";
|
||||
|
||||
export const tryLeaveGameHandler: HandlerFn = async (ctx) => {
|
||||
const player = getContextPlayerOrThrow(ctx);
|
||||
await setPlayerState(player.id, ctx, PlayerState.TRYING_LEAVE);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { gameOptionConversationMap } from "const";
|
||||
import { Context, GameOption } from "types";
|
||||
import { enterConversation } from "utils";
|
||||
|
||||
export const enterGameOptionConversation = async (
|
||||
ctx: Context,
|
||||
option: GameOption
|
||||
) => {
|
||||
const conversation = gameOptionConversationMap[option];
|
||||
await enterConversation(conversation, ctx);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { enterGameOptionConversation } from "./configure-option-handler";
|
||||
@@ -0,0 +1,2 @@
|
||||
export { inspoHandler } from "./inspo";
|
||||
export { startHandler } from "./start";
|
||||
@@ -0,0 +1,9 @@
|
||||
import { inspoRepo } from "const";
|
||||
import { getContextPlayerOrCreate } from "data";
|
||||
import { HandlerFn } from "types";
|
||||
|
||||
export const inspoHandler: HandlerFn = async (ctx) => {
|
||||
const player = getContextPlayerOrCreate(ctx);
|
||||
const inspo = inspoRepo[Math.floor(Math.random() * inspoRepo.length)];
|
||||
await ctx.api.sendMessage(player.id, inspo, { parse_mode: "MarkdownV2" });
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Poem } from "classes";
|
||||
import {
|
||||
addArchivedPoem,
|
||||
addGame,
|
||||
addPlayer,
|
||||
getContextPlayerOrCreate,
|
||||
getGame,
|
||||
joinGame,
|
||||
leaveGame,
|
||||
} from "data";
|
||||
import { setPlayerState } from "player-state";
|
||||
import { HandlerFn, PlayerState } from "types";
|
||||
|
||||
const testingGame = false;
|
||||
const testingArchive = false;
|
||||
|
||||
export const startHandler: HandlerFn = async (ctx) => {
|
||||
if (testingArchive) {
|
||||
const testPoem = new Poem({ id: 1234, gameId: "TEST" });
|
||||
testPoem.addLine("Line", "Test author");
|
||||
addArchivedPoem(testPoem);
|
||||
}
|
||||
if (testingGame) {
|
||||
const testGame =
|
||||
getGame("test") ||
|
||||
addGame({ hostId: Number(process.env.MY_USERID), id: "TEST" });
|
||||
for (const testuser of [
|
||||
{
|
||||
id: Number(process.env.MY_USERID),
|
||||
name: process.env.MY_USERNAME ?? "You",
|
||||
},
|
||||
{ id: Number(process.env.TEST_USER_1_ID), name: "First Test User" },
|
||||
{ id: Number(process.env.TEST_USER_2_ID), name: "Second Test User" },
|
||||
]) {
|
||||
const newPlayer = addPlayer({ id: testuser.id, userName: testuser.name });
|
||||
await joinGame(ctx, testGame, newPlayer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const player = getContextPlayerOrCreate(ctx);
|
||||
const game = getGame(player.gameId);
|
||||
if (game) {
|
||||
await leaveGame(ctx, game, player);
|
||||
}
|
||||
await setPlayerState(player.id, ctx, PlayerState.START);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { POEM_NOT_FOUND_WITH_ID, VALIDATION_POEM_ID_IS_NUMBER } from "const";
|
||||
import { getArchivedPoem, getContextPlayerOrCreate } from "data";
|
||||
import { messagePlayer } from "messaging";
|
||||
import { setPlayerState } from "player-state";
|
||||
import { BasicCallback, ConversationFn, PlayerState } from "types";
|
||||
import { getConversationValue } from "utils";
|
||||
|
||||
export const archivedPoemConversation: ConversationFn = async (
|
||||
conversation,
|
||||
ctx,
|
||||
) => {
|
||||
const player = getContextPlayerOrCreate(ctx);
|
||||
await getConversationValue(conversation, {
|
||||
exitCallback: BasicCallback.EXIT_ARCHIVE,
|
||||
doEach: async () => {},
|
||||
handleCancel: async () => {
|
||||
await setPlayerState(player.id, ctx, PlayerState.START);
|
||||
return;
|
||||
},
|
||||
handleSuccess: async (poemId: string) => {
|
||||
const poem = getArchivedPoem(poemId);
|
||||
await poem?.sendToPlayer(player.id, ctx);
|
||||
await setPlayerState(player.id, ctx, PlayerState.IN_ARCHIVES);
|
||||
return;
|
||||
},
|
||||
stay: true,
|
||||
validate: async (poemId: string) => {
|
||||
const poemIdNumber = Number(poemId);
|
||||
let errorMessage = "";
|
||||
if (isNaN(poemIdNumber)) {
|
||||
errorMessage = VALIDATION_POEM_ID_IS_NUMBER;
|
||||
} else {
|
||||
const poem = getArchivedPoem(poemId);
|
||||
if (!poem) {
|
||||
errorMessage = POEM_NOT_FOUND_WITH_ID(poemIdNumber);
|
||||
}
|
||||
}
|
||||
if (errorMessage) {
|
||||
await messagePlayer(player.id, player.state, ctx, errorMessage);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { GAME_NOT_FOUND_WITH_ID } from "const";
|
||||
import {
|
||||
getContextPlayerOrThrow,
|
||||
getGame,
|
||||
getGameOrThrow,
|
||||
joinGame,
|
||||
} from "data";
|
||||
import { messagePlayer } from "messaging";
|
||||
import { setPlayerState } from "player-state";
|
||||
import {
|
||||
BasicCallback,
|
||||
ConversationFn,
|
||||
ConversationState,
|
||||
PlayerState,
|
||||
} from "types";
|
||||
import { getConversationValue } from "utils";
|
||||
|
||||
export const gameIdConversation: ConversationFn = async (conversation, ctx) => {
|
||||
const player = getContextPlayerOrThrow(ctx);
|
||||
await getConversationValue(conversation, {
|
||||
exitCallback: BasicCallback.RETURN_TO_PREVIOUS_STATE,
|
||||
doEach: async () => {
|
||||
await messagePlayer(player.id, ConversationState.GET_GAME_ID, ctx);
|
||||
},
|
||||
handleCancel: async () => {
|
||||
await setPlayerState(player.id, ctx, PlayerState.START);
|
||||
return;
|
||||
},
|
||||
handleSuccess: async (gameId: string) => {
|
||||
const game = getGameOrThrow(gameId);
|
||||
await joinGame(ctx, game, player);
|
||||
return;
|
||||
},
|
||||
validate: async (gameId: string) => {
|
||||
const game = getGame(gameId);
|
||||
if (game) {
|
||||
return true;
|
||||
}
|
||||
await messagePlayer(
|
||||
player.id,
|
||||
player.state,
|
||||
ctx,
|
||||
GAME_NOT_FOUND_WITH_ID(gameId),
|
||||
);
|
||||
return false;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { getContextPlayerOrThrow, getGameOrThrow } from "data";
|
||||
import { messagePlayer } from "messaging";
|
||||
import { setPlayerState } from "player-state";
|
||||
import {
|
||||
BasicCallback,
|
||||
ConversationFn,
|
||||
ConversationState,
|
||||
PlayerState,
|
||||
} from "types";
|
||||
import { getConversationValue } from "utils";
|
||||
|
||||
export const gameDescriptionConversation: ConversationFn = async (
|
||||
conversation,
|
||||
ctx,
|
||||
) => {
|
||||
const player = getContextPlayerOrThrow(ctx);
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
|
||||
await getConversationValue(conversation, {
|
||||
exitCallback: BasicCallback.RETURN_TO_PREVIOUS_STATE,
|
||||
doEach: async () =>
|
||||
await messagePlayer(
|
||||
player.id,
|
||||
ConversationState.GET_GAME_DESCRIPTION,
|
||||
ctx,
|
||||
),
|
||||
handleCancel: async () => {
|
||||
await setPlayerState(player.id, ctx, PlayerState.SETTING_UP_GAME);
|
||||
},
|
||||
handleSuccess: async (description: string) => {
|
||||
game.setOptions({ description });
|
||||
await setPlayerState(player.id, ctx, PlayerState.SETTING_UP_GAME);
|
||||
return;
|
||||
},
|
||||
// eslint-disable-next-line require-await
|
||||
validate: async (_description: string) => {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { gameDescriptionConversation } from "./game-description";
|
||||
@@ -0,0 +1,3 @@
|
||||
export { archivedPoemConversation } from "./archived-poem-id";
|
||||
export { gameIdConversation } from "./game-id";
|
||||
export * from "./game-options";
|
||||
@@ -0,0 +1 @@
|
||||
export { hidePoemMetaHandler, showPoemMetaHandler } from "./toggle-poem-meta";
|
||||
@@ -0,0 +1,47 @@
|
||||
import { HIDE_SHOW_METADATA, POEM_NOT_FOUND } from "const";
|
||||
import { getArchivedPoem } from "data";
|
||||
import { InlineKeyboard } from "grammy";
|
||||
import { CallbackData, CallbackWithData, Context, DataHandlerFn } from "types";
|
||||
import { encodeCallbackData } from "utils";
|
||||
|
||||
const togglePoemMeta = async (
|
||||
ctx: Context,
|
||||
data: CallbackData,
|
||||
includeMeta?: boolean,
|
||||
) => {
|
||||
const poem = getArchivedPoem(data.id);
|
||||
const keyboard = new InlineKeyboard().text(
|
||||
HIDE_SHOW_METADATA(includeMeta),
|
||||
encodeCallbackData(
|
||||
includeMeta
|
||||
? CallbackWithData.HIDE_POEM_META
|
||||
: CallbackWithData.SHOW_POEM_META,
|
||||
data,
|
||||
),
|
||||
);
|
||||
try {
|
||||
const compiledPoem = poem?.compile(includeMeta);
|
||||
if (compiledPoem) {
|
||||
await ctx.editMessageText(compiledPoem ?? POEM_NOT_FOUND, {
|
||||
parse_mode: "HTML",
|
||||
reply_markup: keyboard,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const showPoemMetaHandler: DataHandlerFn = async (
|
||||
ctx,
|
||||
data: CallbackData,
|
||||
) => {
|
||||
await togglePoemMeta(ctx, data, true);
|
||||
};
|
||||
|
||||
export const hidePoemMetaHandler: DataHandlerFn = async (
|
||||
ctx,
|
||||
data: CallbackData,
|
||||
) => {
|
||||
await togglePoemMeta(ctx, data);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./callbacks";
|
||||
export * from "./commands";
|
||||
export * from "./conversations";
|
||||
export * from "./data-callbacks";
|
||||
export * from "./replies";
|
||||
@@ -0,0 +1,25 @@
|
||||
import { VALIDATION_POEM_ID_IS_NUMBER, POEM_NOT_FOUND_WITH_ID } from "const";
|
||||
import { getArchivedPoem, getContextPlayerOrCreate } from "data";
|
||||
import { messagePlayer } from "messaging";
|
||||
import { setPlayerState } from "player-state";
|
||||
import { PlayerState, ReplyHandlerFn } from "types";
|
||||
|
||||
export const archiveSearchHandler: ReplyHandlerFn = async (ctx, poemId) => {
|
||||
const player = getContextPlayerOrCreate(ctx);
|
||||
const poemIdNumber = Number(poemId);
|
||||
let errorMessage = "";
|
||||
if (isNaN(poemIdNumber)) {
|
||||
errorMessage = VALIDATION_POEM_ID_IS_NUMBER;
|
||||
} else {
|
||||
const poem = getArchivedPoem(poemId);
|
||||
if (!poem) {
|
||||
errorMessage = POEM_NOT_FOUND_WITH_ID(poemIdNumber);
|
||||
} else {
|
||||
await poem?.sendToPlayer(player.id, ctx);
|
||||
}
|
||||
}
|
||||
if (errorMessage) {
|
||||
await messagePlayer(player.id, player.state, ctx, errorMessage);
|
||||
}
|
||||
await setPlayerState(player.id, ctx, PlayerState.IN_ARCHIVES);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./writing";
|
||||
@@ -0,0 +1,9 @@
|
||||
import { getContextPlayerOrThrow, getGameOrThrow } from "data";
|
||||
import { ReplyHandlerFn } from "types";
|
||||
|
||||
export const writingReplyHandler: ReplyHandlerFn = async (ctx, reply) => {
|
||||
// TODO add validation
|
||||
const player = getContextPlayerOrThrow(ctx);
|
||||
const game = getGameOrThrow(player.gameId);
|
||||
await game.addLine(ctx, reply, player);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3";
|
||||
import { PrismaClient } from "../generated/prisma/client";
|
||||
|
||||
const connectionString = `${process.env.DATABASE_URL}`;
|
||||
|
||||
const adapter = new PrismaBetterSqlite3({ url: connectionString });
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
export { prisma };
|
||||
@@ -0,0 +1,40 @@
|
||||
// https://core.telegram.org/bots/api#formatting-options
|
||||
import { ParseMode } from "grammy/types";
|
||||
|
||||
// TODO figure out how to use telegram's markdown because it doesn't seem to work
|
||||
export const sanitizeMarkdownV2 = (markdown: string) => {
|
||||
const regex = new RegExp(/[_*[\]()~`>#+-=|{}.!]/g);
|
||||
const sanitizedMarkdown = markdown;
|
||||
return sanitizedMarkdown.replace(regex, "\\$&");
|
||||
};
|
||||
|
||||
export const sanitizeHtml = (html: string) => {
|
||||
const sanitizedHtml = html;
|
||||
sanitizedHtml.replaceAll("<", "<");
|
||||
sanitizedHtml.replaceAll(">", ">");
|
||||
sanitizedHtml.replaceAll("&", "&");
|
||||
return sanitizedHtml;
|
||||
};
|
||||
|
||||
export const sanitizeText = (
|
||||
str: string,
|
||||
parseMode: ParseMode = "MarkdownV2",
|
||||
): string => {
|
||||
switch (parseMode) {
|
||||
case "Markdown":
|
||||
return str;
|
||||
case "MarkdownV2":
|
||||
return sanitizeMarkdownV2(str);
|
||||
case "HTML":
|
||||
return sanitizeHtml(str);
|
||||
}
|
||||
};
|
||||
|
||||
export const getSanitizedMessage = (
|
||||
msg: string,
|
||||
parseMode: ParseMode = "MarkdownV2",
|
||||
) => {
|
||||
const sanitizedMessage = sanitizeText(msg, parseMode);
|
||||
const newMessage = `${sanitizedMessage.trim()}`;
|
||||
return newMessage;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Player } from "classes";
|
||||
import { MessagingState } from "types";
|
||||
import { stateCommandsMap } from "const/maps/state-keyboard-button-map";
|
||||
|
||||
export const getCommandsForState = (player: Player, state: MessagingState) => {
|
||||
return stateCommandsMap[player.state];
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Player } from "classes";
|
||||
import { stateMessageMap } from "const";
|
||||
import { MessagingState } from "types";
|
||||
|
||||
export const getMessageForState = (player: Player, state: MessagingState) => {
|
||||
const messageFn = stateMessageMap[state];
|
||||
if (!messageFn) {
|
||||
return "";
|
||||
}
|
||||
return messageFn(player);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./format-message";
|
||||
export { messagePlayer } from "./message-player";
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ParseMode } from "grammy/types";
|
||||
import { getPlayerOrThrow } from "data";
|
||||
import { Context, MessagingState } from "types";
|
||||
import { getSanitizedMessage } from "./format-message";
|
||||
import { getMessageForState } from "./get-message";
|
||||
import { getKeyboardForState } from "./get-commands";
|
||||
import { getReplyMarkup } from "utils";
|
||||
|
||||
export const messagePlayer = async (
|
||||
playerId: number,
|
||||
state: MessagingState,
|
||||
ctx: Context,
|
||||
customString = "",
|
||||
) => {
|
||||
const player = getPlayerOrThrow(playerId);
|
||||
const stateMessage = customString || getMessageForState(player, state);
|
||||
if (!stateMessage) return;
|
||||
const keyboard = getKeyboardForState(player, state);
|
||||
|
||||
const formattedMessage = getSanitizedMessage(stateMessage);
|
||||
|
||||
const replyMarkup = getReplyMarkup({ keyboard, player, state });
|
||||
|
||||
try {
|
||||
await ctx.api.sendMessage(player.id, formattedMessage, {
|
||||
parse_mode: "MarkdownV2" as ParseMode,
|
||||
reply_markup: replyMarkup,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { setPlayerState } from "./set-player-state";
|
||||
@@ -0,0 +1,60 @@
|
||||
import { PlayerState } from "generated/prisma/enums";
|
||||
import {
|
||||
getPlayer,
|
||||
getPlayers,
|
||||
updatePlayerState,
|
||||
updatePlayerStates,
|
||||
} from "data";
|
||||
import { messagePlayer } from "messaging";
|
||||
import { Context, StateConfig } from "types";
|
||||
import { stateRegistry } from "./states/registry";
|
||||
import { PlayerModel } from "generated/prisma/models";
|
||||
|
||||
const executeStateChanges = async (
|
||||
player: PlayerModel,
|
||||
oldState: PlayerState,
|
||||
newState: PlayerState,
|
||||
ctx: Context,
|
||||
noMessage?: boolean,
|
||||
) => {
|
||||
console.log(
|
||||
`transitioning player ${player.userName} from ${oldState} to ${newState}`,
|
||||
);
|
||||
const oldConfig: StateConfig | undefined = stateRegistry[oldState!];
|
||||
const newConfig = stateRegistry[newState];
|
||||
|
||||
await oldConfig?.exit(player);
|
||||
|
||||
if (!noMessage) await messagePlayer(player.playerId, newState, ctx);
|
||||
await newConfig.enter(player);
|
||||
};
|
||||
|
||||
export const setPlayerState = async (
|
||||
playerId: number,
|
||||
ctx: Context,
|
||||
newState: PlayerState,
|
||||
noMessage?: boolean,
|
||||
) => {
|
||||
const player = await getPlayer(playerId);
|
||||
const { state: oldState, userName } = player;
|
||||
console.log(
|
||||
`transitioning player ${userName} from ${oldState} to ${newState}`,
|
||||
);
|
||||
await updatePlayerState(playerId, newState);
|
||||
executeStateChanges(player, oldState, newState, ctx, noMessage);
|
||||
};
|
||||
|
||||
export const setPlayerStates = async (
|
||||
playerIds: number[],
|
||||
ctx: Context,
|
||||
newState: PlayerState,
|
||||
noMessage?: boolean,
|
||||
) => {
|
||||
const players = await getPlayers(playerIds);
|
||||
const oldStates = players.map((p) => p.state);
|
||||
await updatePlayerStates(playerIds, newState);
|
||||
for (const [i, player] of players.entries()) {
|
||||
const oldState = oldStates[i];
|
||||
await executeStateChanges(player, oldState, newState, ctx, noMessage);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { StateConfig, StateManagementFn } from "types";
|
||||
|
||||
const enter: StateManagementFn = async (_player) => {};
|
||||
|
||||
const exit: StateManagementFn = async (_player) => {};
|
||||
|
||||
export const defaultState: StateConfig = { enter, exit };
|
||||
@@ -0,0 +1,7 @@
|
||||
import { StateConfig, StateManagementFn } from "types";
|
||||
|
||||
const enter: StateManagementFn = async (_player) => {};
|
||||
|
||||
const exit: StateManagementFn = async (_player) => {};
|
||||
|
||||
export const inArchivesState: StateConfig = { enter, exit };
|
||||
@@ -0,0 +1,7 @@
|
||||
import { StateConfig, StateManagementFn } from "types";
|
||||
|
||||
const enter: StateManagementFn = async (_player) => {};
|
||||
|
||||
const exit: StateManagementFn = async (_player) => {};
|
||||
|
||||
export const lobbyState: StateConfig = { enter, exit };
|
||||
@@ -0,0 +1,7 @@
|
||||
import { StateConfig, StateManagementFn } from "types";
|
||||
|
||||
const enter: StateManagementFn = async (_player) => {};
|
||||
|
||||
const exit: StateManagementFn = async (_player) => {};
|
||||
|
||||
export const postGameState: StateConfig = { enter, exit };
|
||||
@@ -0,0 +1,29 @@
|
||||
import { PlayerState } from "generated/prisma/enums";
|
||||
import { StateConfig } from "types";
|
||||
import { defaultState } from "./default";
|
||||
import { lobbyState } from "./lobby";
|
||||
import { settingUpGameState } from "./setting-up-game";
|
||||
import { spectatingState } from "./spectating";
|
||||
import { waitingAfterWritingState } from "./waiting-after-writing";
|
||||
import { waitingToWriteState } from "./waiting-to-write";
|
||||
import { typingState } from "./typing";
|
||||
import { tryingLeaveState } from "./trying-leave";
|
||||
import { postGameState } from "./post-game";
|
||||
import { viewingArchivedPoemState } from "./viewing-archived-poem";
|
||||
import { writingState } from "./writing";
|
||||
import { inArchivesState } from "./in-archives";
|
||||
|
||||
export const stateRegistry: { [key in PlayerState]: StateConfig } = {
|
||||
[PlayerState.IN_ARCHIVES]: inArchivesState,
|
||||
[PlayerState.LOBBY]: lobbyState,
|
||||
[PlayerState.SETTING_UP_GAME]: settingUpGameState,
|
||||
[PlayerState.SPECTATING]: spectatingState,
|
||||
[PlayerState.POST_GAME]: postGameState,
|
||||
[PlayerState.WRITING]: writingState,
|
||||
[PlayerState.START]: defaultState,
|
||||
[PlayerState.TRYING_LEAVE]: tryingLeaveState,
|
||||
[PlayerState.TYPING]: typingState,
|
||||
[PlayerState.VIEWING_ARCHIVED_POEM]: viewingArchivedPoemState,
|
||||
[PlayerState.WAITING_AFTER_WRITING]: waitingAfterWritingState,
|
||||
[PlayerState.WAITING_TO_WRITE]: waitingToWriteState,
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user