mirror of
https://github.com/seigler/advent-of-code-2020
synced 2025-07-25 23:36:10 +00:00
Initial commit
This commit is contained in:
commit
f3d555f01c
12 changed files with 370 additions and 0 deletions
27
.github/workflows/pr-check.yml
vendored
Normal file
27
.github/workflows/pr-check.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
name: Check PR
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
test:
|
||||
name: Linting and Testing
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node JS LTS
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Run build steps
|
||||
run: npm build -s
|
||||
|
||||
- name: Run default tests
|
||||
run: npm test -s
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
package-lock.json
|
||||
**/jquery-2.1.1.min.js
|
33
README.md
Normal file
33
README.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Advent of Code Template
|
||||
|
||||
Advent of Code Template using Node JS for Current Year.
|
||||
|
||||
## Setup
|
||||
|
||||
If using the Advent of Code Template repo; click [**`Use this template`**](https://github.com/johnbeech/advent-of-code-nodejs-template/generate) and set a new repository name.
|
||||
|
||||
Clone this repo, then run `npm install` to install dependencies.
|
||||
|
||||
If this a brand new repository, run: `node setup` to configure it for Current Year and check in the changes.
|
||||
|
||||
## Running
|
||||
|
||||
To run a solution by day, use:
|
||||
```
|
||||
npm start day1
|
||||
````
|
||||
|
||||
If a solution exists for that day, then it will run with basic tests. If a solution does not exist, it will copy the template, and then try to download that day's puzzle input using [AOCD](https://github.com/wimglenn/advent-of-code-data).
|
||||
|
||||
If you don't have AOCD configured, populate `input.txt` with your solution input from the AOC website, and then start implementing your solution in the new folder for that day.
|
||||
|
||||
Once you have calculated a solution, you should manually submit your answer through the website.
|
||||
|
||||
## Viewer
|
||||
|
||||
A local webserver has been provided to browse the solutions, and optionally create web based visualisations to go with the code.
|
||||
|
||||
To start the server run: `npm run webserver` - a new hardcoded index.html will be generated each time you browse the index.
|
||||
|
||||
If you enable github pages from your repo settings, and host from the root of the project, you'll be able to access this index and the solutions from the provided hosted URL. Please replace this message with a link to those pages if you do.
|
||||
|
75
copy-template.js
Normal file
75
copy-template.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
const path = require('path')
|
||||
const { make, position, find, read, write, run } = require('promise-path')
|
||||
const fromHere = position(__dirname)
|
||||
const report = (...messages) => console.log(`[${require(fromHere('./package.json')).logName} / ${__filename.split(path.sep).pop().split('.js').shift()}]`, ...messages)
|
||||
|
||||
async function fetchAOCDInput (currentYear, currentDay) {
|
||||
report('Using AOCD to attempt to download your puzzle input, see: https://github.com/wimglenn/advent-of-code-data')
|
||||
try {
|
||||
const { stdout, stderr } = await run(`aocd ${currentDay} ${currentYear}`)
|
||||
if (stderr) {
|
||||
report(`AOCD ${currentYear} / ${currentDay}`, stderr)
|
||||
}
|
||||
if (stdout) {
|
||||
report(`Downloaded ${stderr.bytes} bytes of data using AOCD.`)
|
||||
}
|
||||
return stdout
|
||||
} catch (ex) {
|
||||
report(`Could not fetch input for ${currentYear} / ${currentDay}`, ex)
|
||||
}
|
||||
return 'PASTE YOUR INPUT HERE'
|
||||
}
|
||||
|
||||
async function copyTemplate () {
|
||||
const newFolderName = process.argv[2]
|
||||
const templateFolderPath = 'solutions/template'
|
||||
const targetFolderPath = fromHere(`solutions/${newFolderName}`)
|
||||
|
||||
if (!newFolderName) {
|
||||
return report(
|
||||
'No path specified to copy to.',
|
||||
'Please specify a folder name as an argument to this script.',
|
||||
'e.g. node copy-template day5'
|
||||
)
|
||||
}
|
||||
|
||||
const existingFiles = await find(`${targetFolderPath}/*`)
|
||||
if (existingFiles.length > 0) {
|
||||
report('Existing files found:')
|
||||
console.log(existingFiles.map(n => ' ' + n).join('\n'))
|
||||
return report('Path', newFolderName, 'already exists, doing nothing.')
|
||||
}
|
||||
|
||||
report('Creating:', `solutions/${newFolderName}`, 'from template', templateFolderPath)
|
||||
|
||||
const templateFiles = await find(fromHere(`${templateFolderPath}/*`))
|
||||
await make(fromHere(`solutions/${newFolderName}`))
|
||||
await Promise.all(templateFiles.map(async (filepath) => {
|
||||
const contents = await read(filepath)
|
||||
const filename = path.parse(filepath).base
|
||||
const newFilePath = `solutions/${newFolderName}/${filename}`
|
||||
report('Creating:', newFilePath)
|
||||
return write(fromHere(newFilePath), contents)
|
||||
}))
|
||||
|
||||
report('Attemping to download puzzle input for this date')
|
||||
|
||||
const currentPath = fromHere('/')
|
||||
const currentFolder = currentPath.split('/').reverse()[1]
|
||||
const currentYear = currentFolder.split('-').pop()
|
||||
const currentDay = Number.parseInt(newFolderName.replace('day', ''))
|
||||
|
||||
report(`Based on the path, ${currentFolder} I think its: ${currentYear}, and you're trying to solve: Day ${currentDay}`)
|
||||
|
||||
if (currentYear > 0 && currentDay > 0) {
|
||||
report(`Potentially valid year (${currentYear}) / day (${currentDay})`)
|
||||
const aocInputText = await fetchAOCDInput(currentYear, currentDay)
|
||||
await write(fromHere(`solutions/${newFolderName}/input.txt`), aocInputText, 'utf8')
|
||||
} else {
|
||||
report(`Invalid year (${currentYear}) / day (${currentDay})`)
|
||||
}
|
||||
|
||||
report('Done.')
|
||||
}
|
||||
|
||||
module.exports = copyTemplate()
|
22
package.json
Normal file
22
package.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "advent-of-code-template",
|
||||
"logName": "Advent of Code Template",
|
||||
"version": "1.0.0",
|
||||
"description": "Advent of Code Template using Node JS for Current Year.",
|
||||
"main": "run.js",
|
||||
"scripts": {
|
||||
"start": "npm run test && node run.js",
|
||||
"webserver": "node solutions/viewer-server.js",
|
||||
"test": "npm run lint",
|
||||
"lint": "standard --fix"
|
||||
},
|
||||
"author": "John Beech",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.16.4",
|
||||
"pre-push": "*",
|
||||
"promise-path": "*",
|
||||
"standard": "*",
|
||||
"stats-lite": "*"
|
||||
}
|
||||
}
|
28
run.js
Normal file
28
run.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
const solutionId = process.argv[2]
|
||||
|
||||
async function start () {
|
||||
try {
|
||||
await runSolution()
|
||||
} catch (ex) {
|
||||
if (!solutionId) {
|
||||
console.error('No solution ID provided; please re-run with an argument, e.g.: npm start day1, or: node run day1')
|
||||
} else {
|
||||
await copyTemplate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runSolution () {
|
||||
return require(`./solutions/${solutionId}/solution.js`)
|
||||
}
|
||||
|
||||
async function copyTemplate () {
|
||||
try {
|
||||
await require('./copy-template.js')
|
||||
await runSolution()
|
||||
} catch (ex) {
|
||||
console.error(`Unable to run solution for '${solutionId}': ${ex}`, ex.stack)
|
||||
}
|
||||
}
|
||||
|
||||
start()
|
51
setup.js
Normal file
51
setup.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
const path = require('path')
|
||||
const { read, write, position, run } = require('promise-path')
|
||||
|
||||
const fromHere = position(__dirname)
|
||||
const report = (...messages) => console.log(`[${require(fromHere('./package.json')).logName} / ${__filename.split(path.sep).pop().split('.js').shift()}]`, ...messages)
|
||||
|
||||
async function replaceInFile (filename, search, replace) {
|
||||
const haystack = await read(filename, 'utf8')
|
||||
const ashes = haystack.replace(search, replace)
|
||||
return write(filename, ashes, 'utf8')
|
||||
}
|
||||
|
||||
async function setup () {
|
||||
const currentPath = fromHere('/')
|
||||
const currentFolder = currentPath.split('/').reverse()[1]
|
||||
|
||||
report('Setting up template from:', currentFolder)
|
||||
|
||||
const currentYear = currentFolder.split('-').pop()
|
||||
|
||||
if (currentYear === 'template') {
|
||||
console.error(' No current year provided.')
|
||||
console.error(' Please re-run setup after renaming the repo, e.g.: advent-of-code-2020, advent-of-code-2021, advent-of-code-2022, etc.')
|
||||
console.error('')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
report('Replacing strings in templates')
|
||||
await replaceInFile('README.md', 'If using the Advent of Code Template repo; click [**`Use this template`**](https://github.com/johnbeech/advent-of-code-nodejs-template/generate) and set a new repository name.\n', '')
|
||||
await replaceInFile('README.md', 'If this a brand new repository, run: `node setup` to configure it for Current Year and check in the changes.\n', '')
|
||||
|
||||
await replaceInFile('package.json', /Advent of Code Template/g, `Advent of Code ${currentYear}`)
|
||||
await replaceInFile('README.md', '# Advent of Code Template', `# Advent of Code ${currentYear}`)
|
||||
|
||||
await replaceInFile('package.json', 'Advent of Code Template using Node JS for Current Year.', `My solutions for Advent of Code ${currentYear}.`)
|
||||
await replaceInFile('README.md', 'Advent of Code Template using Node JS for Current Year.', `My solutions for Advent of Code ${currentYear}.`)
|
||||
|
||||
await replaceInFile('package.json', 'advent-of-code-template', currentFolder)
|
||||
|
||||
report('Removing setup script')
|
||||
await run(`rm ${fromHere('setup.js')}`)
|
||||
|
||||
report('Committing changes and pushing to remote')
|
||||
await run('git add .')
|
||||
await run(`git commit -m "Setup template for Current Year (${currentYear})"`)
|
||||
await run('git push')
|
||||
|
||||
report(`All done! ${currentYear} setup and ready to go~`)
|
||||
}
|
||||
|
||||
setup()
|
14
solutions/index.html
Normal file
14
solutions/index.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Advent of Code Template</title>
|
||||
<style> html, body { font-family: sans-serif; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Advent of Code Template</h1>
|
||||
<ul>
|
||||
<li><a href="/solutions/template/viewer.html">solutions/template</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
1
solutions/template/input.txt
Normal file
1
solutions/template/input.txt
Normal file
|
@ -0,0 +1 @@
|
|||
PASTE YOUR INPUT HERE
|
24
solutions/template/solution.js
Normal file
24
solutions/template/solution.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
const path = require('path')
|
||||
const { read, position } = require('promise-path')
|
||||
const fromHere = position(__dirname)
|
||||
const report = (...messages) => console.log(`[${require(fromHere('../../package.json')).logName} / ${__dirname.split(path.sep).pop()}]`, ...messages)
|
||||
|
||||
async function run () {
|
||||
const input = (await read(fromHere('input.txt'), 'utf8')).trim()
|
||||
|
||||
await solveForFirstStar(input)
|
||||
await solveForSecondStar(input)
|
||||
}
|
||||
|
||||
async function solveForFirstStar (input) {
|
||||
const solution = 'UNSOLVED'
|
||||
report('Input:', input)
|
||||
report('Solution 1:', solution)
|
||||
}
|
||||
|
||||
async function solveForSecondStar (input) {
|
||||
const solution = 'UNSOLVED'
|
||||
report('Solution 2:', solution)
|
||||
}
|
||||
|
||||
run()
|
43
solutions/template/viewer.html
Normal file
43
solutions/template/viewer.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Solution Viewer</title>
|
||||
<style>
|
||||
html, body { font-family: sans-serif; }
|
||||
pre { border-radius: 0.5em; padding: 0.5em; background: #eee; }
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="viewer">
|
||||
<h1>Solution Viewer ({{ solutionTitle }})</h1>
|
||||
<p>For interesting problems; this page can be used as a dynamic viewer.</p>
|
||||
<h3><a href="./input.txt">input.txt</a></h3>
|
||||
<pre><code>{{ inputText }}</code></pre>
|
||||
<h3><a href="./solution.js">solution.js</a></h3>
|
||||
<pre><code>{{ solutionText }}</code></pre>
|
||||
</div>
|
||||
<script>
|
||||
const app = new Vue({
|
||||
el: '#viewer',
|
||||
data: () => {
|
||||
return {
|
||||
solutionText: '[Loading]',
|
||||
inputText: '[Loading]'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
solutionTitle() {
|
||||
const parts = (document.location + '').split('/')
|
||||
return parts.reverse()[1]
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
this.solutionText = (await axios.get('./solution.js')).data
|
||||
this.inputText = (await axios.get('./input.txt')).data
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
49
solutions/viewer-server.js
Normal file
49
solutions/viewer-server.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
const path = require('path')
|
||||
const express = require('express')
|
||||
const { position, find, write } = require('promise-path')
|
||||
const fromHere = position(__dirname)
|
||||
const report = (...messages) => console.log(`[${require(fromHere('../package.json')).logName} / ${__filename.split(path.sep).pop().split('.js').shift()}]`, ...messages)
|
||||
|
||||
const app = express()
|
||||
const packageData = require('../package.json')
|
||||
|
||||
async function generateIndexHTML () {
|
||||
const title = packageData.logName
|
||||
const solutions = await find(fromHere('/*'))
|
||||
const links = solutions
|
||||
.filter(n => n.indexOf('.js') === -1 && n.indexOf('.html') === -1)
|
||||
.map(solution => {
|
||||
const folder = solution.substr(fromHere('../').length)
|
||||
return ` <li><a href="/${folder}/viewer.html">${folder}</a></li>`
|
||||
})
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${title}</title>
|
||||
<style> html, body { font-family: sans-serif; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${title}</h1>
|
||||
<ul>
|
||||
${links.join('\n')}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
report('Updated hard coded index:', fromHere('index.html'))
|
||||
await write(fromHere('index.html'), html, 'utf8')
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
app.use('/solutions', express.static(fromHere('')))
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
const html = await generateIndexHTML()
|
||||
res.send(html)
|
||||
})
|
||||
|
||||
const port = 8080
|
||||
app.listen(port, () => report(`Listening on http://localhost:${port}/`))
|
Loading…
Add table
Add a link
Reference in a new issue