Changed the project name goal and scope

master
Devin Major 12 months ago
parent 2e70e043ae
commit 93fa2dc974

@ -0,0 +1,27 @@
{
"env": {
"browser": true,
"es2021": true
},
"settings": {
"react": {
"version": "detect"
}
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"prettier" // --> Add this line
],
"overrides": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint"],
"rules": {
"react/react-in-jsx-scope": "off"
}
}

34
.gitignore vendored

@ -1,14 +1,24 @@
# ---> VisualStudioCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Built Visual Studio Code Extensions
*.vsix
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

@ -0,0 +1,8 @@
{
"bracketSpacing": true,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 4
}

@ -1,6 +1,6 @@
# TenThousandGame
#Advent of Code
My iteration of a popular family-style dice game called Ten Thousand. Written using Typescript, React, Node, and PostgreSQL.
My solutions to the challenges for Advent of Code 2023
To get started, please clone the repository into a new directory, navigate to that directory, open a Terminal, and run the following commands:
@ -11,4 +11,4 @@ npm run dev
```
You should now see a prompt in the terminal that shows the local address that the application is running on (for example: http://127.0.0.1:5173/).
Open a browser window, copy and paste the URL from the terminal into the address bar, hit Enter, and you should see the game running!
Open a browser window, copy and paste the URL from the terminal into the address bar, hit Enter, and you should see the site running!

24
client/.gitignore vendored

@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

File diff suppressed because it is too large Load Diff

@ -1,22 +0,0 @@
{
"name": "client",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^3.0.0",
"typescript": "^4.9.3",
"vite": "^4.0.0"
}
}

@ -1,34 +0,0 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
)
}
export default App

@ -1,10 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ten Thousand (Dice)</title>
<title>Advent of Code</title>
</head>
<body>
<div id="root"></div>

8864
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,53 @@
{
"name": "client",
"private": true,
"version": "0.0.0",
"type": "module",
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
"npx eslint --fix",
"npx prettier --write"
]
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"prepare": "husky install"
},
"dependencies": {
"date-fns": "^2.30.0",
"express": "^4.18.2",
"isomorphic-fetch": "^3.0.0",
"lodash": "^4.17.21",
"lodash.sum": "^4.0.2",
"react": "^18.2.0",
"react-day-picker": "^8.9.1",
"react-dom": "^18.2.0",
"react-switch": "^7.0.0",
"sass": "^1.57.1"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/isomorphic-fetch": "^0.0.39",
"@types/lodash": "^4.14.202",
"@types/lodash.sum": "^4.0.9",
"@types/node": "^18.11.18",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@vitejs/plugin-react": "^3.0.0",
"eslint": "^8.31.0",
"eslint-config-prettier": "^8.6.0",
"eslint-config-standard-with-typescript": "^26.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.6.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.31.11",
"husky": "^8.0.3",
"prettier": "^2.8.2",
"typescript": "^4.9.4",
"vite": "^4.0.0",
"vite-tsconfig-paths": "^4.0.3"
}
}

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -27,7 +27,7 @@
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
a:nth-of-type(1) .logo {
animation: logo-spin infinite 20s linear;
}
}
@ -36,6 +36,18 @@
padding: 2em;
}
.read-the-docs {
.split-card {
display: grid;
grid-template-columns: 1fr 1fr;
}
.instructions {
color: #888;
}
.large-editor-textarea {
margin: 16px;
border-radius:1em;
height:20em;
width:100%;
}

@ -0,0 +1,100 @@
import './App.css';
import reactLogo from './assets/react.svg';
import { ChangeEvent, useCallback, useRef, useState } from 'react';
import { DayPicker } from 'react-day-picker';
import { getPuzzlesByDay } from 'data/puzzles/getPuzzlesByDay';
import { PuzzleResult } from 'models';
import { solvePuzzle } from 'data/solutions/solvePuzzle';
import 'react-day-picker/dist/style.css';
import 'components/notification-box/NotificationBox.css';
import NotificationBox from 'components/notification-box/NotificationBox';
function App() {
let puzzles: string[] = [];
const puzzleInputRef = useRef<HTMLTextAreaElement | null>(null);
const [selectedDate, setSelectedDate] = useState<Date>();
// const [ selectedPuzzle, setSelectedPuzzle ] = useState<Date>();
const [puzzleInput, setPuzzleInput] = useState<string>();
const [answer, setAnswer] = useState<PuzzleResult>();
const handleChangeDate = async (date: Date) => {
puzzles = [];
setSelectedDate(date);
const day = date.getDate();
const thisDaysPuzzles = await getPuzzlesByDay(day);
puzzles.push(...thisDaysPuzzles);
};
const handleChangePuzzleInput = useCallback(
(e: ChangeEvent<HTMLTextAreaElement>) => {
const { value } = e.currentTarget;
setPuzzleInput(value);
},
[setPuzzleInput],
);
const handleSolvePuzzle = async () => {
const day = selectedDate?.getDate();
const answer = !!day && (await solvePuzzle(day, puzzleInput || ''));
answer && setAnswer(answer);
};
const disabled = !(puzzleInput && selectedDate);
const badAnswer = !!(answer && answer.answer === '' && !answer.values.length);
const errorText = `Uh oh, we couldn't find an answer for that puzzle!`;
return (
<div className="App">
<div>
<a href="https://reactjs.org" target="_blank" rel="noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Advent of Code Solutions</h1>
<p className="instructions">
Please select a date and a puzzle, then enter some input, and click
&quot;Solve&quot; to see the output.
</p>
<div className="card split-card">
<div>
<h4>Date</h4>
<DayPicker
required={true}
mode="single"
selected={selectedDate}
onDayClick={handleChangeDate}
/>
</div>
{/* <div>
<h4>Puzzle</h4>
<ul>
{puzzles.map((puzzle, index) =>
<li key={ index }>{puzzle}</li>
)}
</ul>
</div> */}
<div>
<h4>Input</h4>
<textarea
className="large-editor-textarea"
ref={puzzleInputRef}
required={true}
autoFocus={true}
onChange={handleChangePuzzleInput}
value={puzzleInput}
/>
</div>
</div>
<div className="card">
<button disabled={disabled} onClick={handleSolvePuzzle}>
Solve
</button>
</div>
<NotificationBox visible={badAnswer} text={errorText} />
</div>
);
}
export default App;

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

@ -0,0 +1,16 @@
.notification {
/* z-index to show it on top of other elements if you have other modals. */
z-index: 100;
position: fixed;
bottom: 1rem;
left: 1rem;
width: 20%;
height: 8vh;
border-radius: 1em;
background-color: gray;
color: white;
justify-items: center;
display: flex;
align-items: center;
justify-content: center;
}

@ -0,0 +1,16 @@
interface Props {
visible: boolean;
text: string;
}
export const NotificationBox = ({ visible, text = '' }: Props) => {
if (!visible) {
return <></>;
}
return (
<div className="notification">
<span>{text}</span>
</div>
);
};

@ -0,0 +1,15 @@
import Switch from 'react-switch';
interface Props {
checked: boolean;
handleChange: any;
}
export const Toggler = ({ handleChange, checked = false }: Props) => {
return (
<label>
<span>Answer Part 2</span>
<Switch onChange={handleChange} checked={checked} />
</label>
);
};

@ -0,0 +1,24 @@
const domParser = new DOMParser(); // the dom parser is reusable
export async function getPuzzlesByDay(day: number): Promise<string[]> {
const document = await fetchDocument(`https://adventofcode.com/2022/day/${day}`);
const puzzleNames: string[] = [];
const dayDescriptions = document.getElementsByClassName('day-desc');
for (const dayDescription of dayDescriptions) {
const name = dayDescription.children[0].textContent;
name && puzzleNames.push(name);
}
return puzzleNames;
}
async function fetchDocument(url: string) {
const headers = new Headers({});
headers.set('Content-Type', 'text/html');
headers.set('Access-Control-Allow-Origin', '*');
const response = await fetch(url, { headers });
const text = await response.text();
return domParser.parseFromString(text, 'text/html');
}

@ -0,0 +1,23 @@
import { sum } from 'lodash';
export const day_1 = (input: string) => {
const numberLines = input.split('\n').map((inputLine) => inputLine.replace(/\D/g, ''));
const values = numberLines.map((numberLine) =>
parseInt(numberLine.charAt(0) + numberLine.charAt(numberLine.length - 1)),
);
return {
values,
answer: sum(values),
};
};
export const day_1_part_2 = (input: string) => {
const numberLines = input.split('\n').map((inputLine) => inputLine.replace(/\D/g, ''));
const values = numberLines.map((numberLine) =>
parseInt(numberLine.charAt(0) + numberLine.charAt(numberLine.length - 1)),
);
return {
values,
answer: sum(values),
};
};

@ -0,0 +1,11 @@
import { day_1 } from 'data/solutions/day_1';
import { PuzzleResult } from 'models';
export async function solvePuzzle(day: number, input: string): Promise<PuzzleResult> {
switch (day) {
case 1:
return day_1(input);
default:
return { values: [], answer: '' };
}
}

@ -0,0 +1,43 @@
/* eslint-disable @typescript-eslint/naming-convention */
export const Errors = {
ACCESS_DENIED: 'ACCESS_DENIED',
AWS_ERROR: 'AWS_ERROR',
DUPLICATE_URLSLUG: 'DUPLICATE_URLSLUG',
DUPLICATE_DISPLAYNAME: 'DUPLICATE_DISPLAYNAME',
INVALID_PARAMETER: 'INVALID_PARAMETER',
IMAGE_TOO_LARGE: 'IMAGE_TOO_LARGE',
MISSING_PROPERTY: 'MISSING_PROPERTY',
NOT_FOUND: 'NOT_FOUND',
PIN_NOT_FOUND: 'PIN_NOT_FOUND',
CONFIG_ERROR: 'CONFIG_ERROR', // The service hasn't been configured correctly
REDIS_ERROR: 'REDIS_ERROR',
SERVER_ERROR: 'SERVER_ERROR',
SQL_ERROR: 'SQL_ERROR',
USER_DISABLED: 'USER_DISABLED',
TIMEOUT: 'TIMEOUT',
SESSION_EXPIRED: 'SESSION_EXPIRED',
AUTH_CODE_EXPIRED: 'AUTH_CODE_EXPIRED',
FILE_PARSE_ERROR: 'FILE_PARSE_ERROR',
ACCOUNT_LOCKED: 'ACCOUNT_LOCKED', // Too many failed login attempts within a short period (e.g. 7 in one day)
FORBIDDEN: 'FORBIDDEN', // Should only be used when anonymous user attempts to perform an action that requires an authenticated user
LOGIN_TOO_SHORT: 'LOGIN_TOO_SHORT',
LOGIN_INVALID: 'LOGIN_INVALID',
INVALID_EMAIL: 'INVALID_EMAIL',
PASSWORD_RETIRED: 'PASSWORD_RETIRED',
PASSWORD_TOO_WEAK: 'PASSWORD_TOO_WEAK',
PASSWORD_INCORRECT: 'PASSWORD_INCORRECT',
PASSWORD_ALGORITHM_FAILURE: 'PASSWORD_ALGORITHM_FAILURE',
EMAIL_TOO_LONG: 'EMAIL_TOO_LONG',
DUPLICATE_EMAIL: 'DUPLICATE_EMAIL',
DUPLICATE_LOGIN: 'DUPLICATE_LOGIN',
DUPLICATE_KEY: 'DUPLICATE_KEY',
VALUE_TOO_LONG: 'VALUE_TOO_LONG',
UNKNOWN_PARAMETER: 'UNKNOWN_PARAMETER',
MISSING_PARAMETER: 'MISSING_PARAMETER',
PROVIDER_ERROR: 'PROVIDER_ERROR',
INCORRECT_AUTH_CODE: 'INCORRECT_AUTH_CODE',
MISSING_AUTH_CODE: 'MISSING_AUTH_CODE',
IN_USE: 'IN_USE',
TFA_REQUIRED: 'TFA_REQUIRED',
};

@ -27,7 +27,7 @@ a:hover {
body {
margin: 0;
display: flex;
place-items: center;
place-items: top;
min-width: 320px;
min-height: 100vh;
}

@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);

@ -0,0 +1,4 @@
export type PuzzleResult = {
values: number[] | string[];
answer: number | string;
};

@ -1,5 +1,6 @@
{
"compilerOptions": {
"baseUrl": "src",
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],

@ -0,0 +1,8 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tsconfigPaths()], // --> add here
});
Loading…
Cancel
Save