mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-20 04:05:06 -04:00
wip
This commit is contained in:
parent
78c8e154c6
commit
b0d8827e09
11 changed files with 19670 additions and 797 deletions
16237
dist/levels_editor.ef3c2e1a.js
vendored
Normal file
16237
dist/levels_editor.ef3c2e1a.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/levels_editor.ef3c2e1a.js.map
vendored
Normal file
1
dist/levels_editor.ef3c2e1a.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
10
dist/levels_editor.html
vendored
10
dist/levels_editor.html
vendored
|
@ -1,6 +1,6 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head><script src="/index.c0fd3053.js"></script>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Level editor</title>
|
<title>Level editor</title>
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎨</text></svg>">
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎨</text></svg>">
|
||||||
|
@ -9,10 +9,8 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="levels"></div>
|
<div id="app"></div>
|
||||||
<div id="palette">
|
|
||||||
<button id="new-level">new</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/levels_editor.227fd609.js" defer=""></script>
|
|
||||||
|
<script src="/levels_editor.ef3c2e1a.js" defer=""></script>
|
||||||
</body></html>
|
</body></html>
|
3727
package-lock.json
generated
3727
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
@ -6,19 +6,25 @@
|
||||||
"start": "rm -rf .parcel-cache && run-p dev:*",
|
"start": "rm -rf .parcel-cache && run-p dev:*",
|
||||||
"dev:game-fe": "parcel src/*.html --lazy --no-hmr",
|
"dev:game-fe": "parcel src/*.html --lazy --no-hmr",
|
||||||
"dev:editor-be": "nodemon editserver.js --watch editserver.js",
|
"dev:editor-be": "nodemon editserver.js --watch editserver.js",
|
||||||
|
"dev:watch-tests": "jest watch",
|
||||||
"build": "rm -f dist/* && parcel build src/index.html"
|
"build": "rm -f dist/* && parcel build src/index.html"
|
||||||
},
|
},
|
||||||
"browserslist": "since 2009",
|
"browserslist": "since 2009",
|
||||||
"author": "Renan LE CARO",
|
"author": "Renan LE CARO",
|
||||||
"license": "GNU AGPLv3",
|
"license": "GNU AGPLv3",
|
||||||
"devDependencies": {
|
"dependencies": {
|
||||||
"@parcel/transformer-less": "^2.13.3",
|
"@parcel/transformer-less": "^2.13.3",
|
||||||
|
"@types/react": "^19.0.10",
|
||||||
|
"@types/react-dom": "^19.0.4",
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "^1.20.3",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"nodemon": "^3.1.9",
|
"nodemon": "^3.1.9",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"parcel": "^2.13.3",
|
"parcel": "^2.13.3",
|
||||||
|
"process": "^0.11.10",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
"svgo": "^3.3.2"
|
"svgo": "^3.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
src/level_editor_util.test.ts
Normal file
12
src/level_editor_util.test.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import {resizeLevel} from "./levels_editor_util";
|
||||||
|
|
||||||
|
test('resizeLevel',()=>{
|
||||||
|
|
||||||
|
expect(resizeLevel({
|
||||||
|
name:'',
|
||||||
|
bricks:'AAAA',
|
||||||
|
size:2,
|
||||||
|
svg:null,
|
||||||
|
color:''
|
||||||
|
}, 1)).toBe({bricks:'AA_AA____',size:3});
|
||||||
|
})
|
|
@ -11,10 +11,8 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="levels"></div>
|
<div id="app"></div>
|
||||||
<div id="palette">
|
|
||||||
<button id="new-level">new</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="module" src="./levels_editor.ts"></script>
|
|
||||||
|
<script type="module" src="levels_editor.tsx"></script>
|
||||||
</body>
|
</body>
|
|
@ -1,270 +0,0 @@
|
||||||
import {Palette, RawLevel} from "./types";
|
|
||||||
import _backgrounds from './backgrounds.json'
|
|
||||||
|
|
||||||
const backgrounds = _backgrounds as string[];
|
|
||||||
import _palette from './palette.json'
|
|
||||||
|
|
||||||
const palette = _palette as Palette;
|
|
||||||
import _allLevels from './levels.json'
|
|
||||||
import {getLevelBackground, hashCode} from "./getLevelBackground";
|
|
||||||
|
|
||||||
let allLevels = _allLevels as RawLevel[];
|
|
||||||
|
|
||||||
|
|
||||||
let currentCode = '_'
|
|
||||||
|
|
||||||
const paletteEl = document.getElementById('palette') as HTMLDivElement;
|
|
||||||
|
|
||||||
Object.entries(palette).forEach(([code, color]) => {
|
|
||||||
const btn = document.createElement('button')
|
|
||||||
Object.assign(btn.style, {
|
|
||||||
background: color || 'linear-gradient(45deg,black,white)',
|
|
||||||
display: 'inline-block',
|
|
||||||
width: '40px',
|
|
||||||
height: '40px',
|
|
||||||
border: '1px solid black'
|
|
||||||
})
|
|
||||||
if (code === currentCode) {
|
|
||||||
btn.className = 'active'
|
|
||||||
}
|
|
||||||
paletteEl.appendChild(btn)
|
|
||||||
btn.addEventListener('click', (e) => {
|
|
||||||
currentCode = code
|
|
||||||
e.preventDefault()
|
|
||||||
document.querySelector('#palette button.active')?.classList.remove('active');
|
|
||||||
btn.classList.add('active')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function renderAllLevels() {
|
|
||||||
allLevels.forEach((level, levelIndex) => {
|
|
||||||
addLevelEditorToList(level, levelIndex)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const levelsListEl = document?.getElementById('levels') as HTMLDivElement
|
|
||||||
|
|
||||||
function addLevelEditorToList(level: RawLevel, levelIndex: number) {
|
|
||||||
const {name, bricks, size, svg, color} = level
|
|
||||||
let div = document.createElement('div')
|
|
||||||
|
|
||||||
|
|
||||||
div.innerHTML = `
|
|
||||||
<input type="text" value="${level.name || ''}" data-level="${levelIndex}" data-text-val="name" />
|
|
||||||
<div>
|
|
||||||
<button data-level="${levelIndex}" data-delete="yep">Delete</button>
|
|
||||||
<button data-offset-level-size="-1" data-level="${levelIndex}">-</button>
|
|
||||||
<button data-offset-level-size="1" data-level="${levelIndex}">+</button>
|
|
||||||
<button data-offset-x="-1" data-offset-y="0" data-level="${levelIndex}">L</button>
|
|
||||||
<button data-offset-x="1" data-offset-y="0" data-level="${levelIndex}">R</button>
|
|
||||||
<button data-offset-x="0" data-offset-y="-1" data-level="${levelIndex}">U</button>
|
|
||||||
<button data-offset-x="0" data-offset-y="1" data-level="${levelIndex}">D</button>
|
|
||||||
<input type="color" value="${level.color || ''}" data-level="${levelIndex}" data-text-val="color" />
|
|
||||||
<input type="number" value="${level.svg || (hashCode(level.name) % backgrounds.length)}" data-level="${levelIndex}" data-num-val="svg" />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="level-bricks-preview" id="bricks-of-${levelIndex}" >
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
levelsListEl.appendChild(div)
|
|
||||||
|
|
||||||
renderLevelBricks(levelIndex)
|
|
||||||
updateLevelBackground(levelIndex)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLevelBackground(levelIndex: number) {
|
|
||||||
const div = document.getElementById("bricks-of-" + levelIndex) as HTMLDivElement
|
|
||||||
const level = allLevels[levelIndex]
|
|
||||||
const {svg, color} = level
|
|
||||||
if (color) {
|
|
||||||
Object.assign(div.style, {backgroundImage: 'none', backgroundColor: color})
|
|
||||||
} else {
|
|
||||||
const svgSource = getLevelBackground(level) as string
|
|
||||||
|
|
||||||
div.setAttribute('data-svg', svgSource)
|
|
||||||
Object.assign(div.style, {
|
|
||||||
backgroundImage: `url("data:image/svg+xml;UTF8,${encodeURIComponent(svgSource)}")`,
|
|
||||||
backgroundColor: 'transparent'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderLevelBricks(levelIndex: number) {
|
|
||||||
const {size, bricks} = allLevels[levelIndex]
|
|
||||||
|
|
||||||
const buttons = []
|
|
||||||
for (let x = 0; x < size; x++) {
|
|
||||||
for (let y = 0; y < size; y++) {
|
|
||||||
const index = y * size + x
|
|
||||||
buttons.push(`<button style="background: ${palette[bricks[index]] || 'transparent'}; left:${x * 40}px;top:${y * 40
|
|
||||||
}px;width:40px;height: 40px; position: absolute" data-set-color-of="${index}" data-level="${levelIndex}"></button>`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const div = document.getElementById("bricks-of-" + levelIndex) as HTMLDivElement
|
|
||||||
div.innerHTML = buttons.join('')
|
|
||||||
Object.assign(div.style, {
|
|
||||||
width: size * 40 + 'px',
|
|
||||||
height: size * 40 + 'px'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
levelsListEl.addEventListener('change', e => {
|
|
||||||
const target = e.target as HTMLInputElement
|
|
||||||
|
|
||||||
const levelIndexStr = target.getAttribute('data-level')
|
|
||||||
if (!levelIndexStr) return
|
|
||||||
|
|
||||||
const levelIndex = parseInt(levelIndexStr)
|
|
||||||
const level = allLevels[levelIndex]
|
|
||||||
|
|
||||||
if (target.getAttribute('data-text-val') == 'name') {
|
|
||||||
level.name = target.value
|
|
||||||
}
|
|
||||||
if (target.getAttribute('data-text-val') == 'color') {
|
|
||||||
level.color = target.value
|
|
||||||
level.svg = null
|
|
||||||
}
|
|
||||||
if (target.getAttribute('data-num-val') == 'svg') {
|
|
||||||
level.color = ''
|
|
||||||
level.svg = parseFloat(target.value)
|
|
||||||
}
|
|
||||||
updateLevelBackground(levelIndex)
|
|
||||||
save()
|
|
||||||
})
|
|
||||||
levelsListEl.addEventListener('click', e => {
|
|
||||||
const target = e.target as HTMLButtonElement
|
|
||||||
if (target.tagName !== 'BUTTON') return
|
|
||||||
|
|
||||||
const resize = target.getAttribute('data-offset-level-size')
|
|
||||||
const moveX = target.getAttribute('data-offset-x')
|
|
||||||
const moveY = target.getAttribute('data-offset-y')
|
|
||||||
const levelIndexStr = target.getAttribute('data-level')
|
|
||||||
if (!levelIndexStr) return
|
|
||||||
|
|
||||||
const levelIndex = parseInt(levelIndexStr)
|
|
||||||
const level = allLevels[levelIndex]
|
|
||||||
const {bricks, size} = level;
|
|
||||||
|
|
||||||
if (resize) {
|
|
||||||
const newSize = size + parseInt(resize)
|
|
||||||
const newBricks = new Array(newSize * newSize).fill('_')
|
|
||||||
for (let x = 0; x < Math.min(size, newSize); x++) {
|
|
||||||
for (let y = 0; y < Math.min(size, newSize); y++) {
|
|
||||||
newBricks[y * newSize + x] = bricks.split('')[y * size + x] || '_'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
level.size = newSize;
|
|
||||||
level.bricks = newBricks.map(b => b || '_').join('');
|
|
||||||
} else if (moveX && moveY) {
|
|
||||||
const dx = parseInt(moveX), dy = parseInt(moveY)
|
|
||||||
const newBricks = new Array(size * size).fill('_')
|
|
||||||
for (let x = 0; x < size; x++) {
|
|
||||||
for (let y = 0; y < size; y++) {
|
|
||||||
newBricks[(y + dy) * size + (x + dx)] = bricks.split('')[y * size + x] || '_'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
level.bricks = newBricks.map(b => b || '_').join('');
|
|
||||||
} else if (target.getAttribute('data-rename')) {
|
|
||||||
const newName = prompt('Name ? ', level.name)
|
|
||||||
if (newName) {
|
|
||||||
level.name = newName
|
|
||||||
target.textContent = newName
|
|
||||||
}
|
|
||||||
} else if (target.getAttribute('data-delete')) {
|
|
||||||
if (confirm('Delete level')) {
|
|
||||||
allLevels = allLevels.filter((l, i) => i !== levelIndex)
|
|
||||||
save().then(() => window.location.reload())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
renderLevelBricks(levelIndex)
|
|
||||||
save()
|
|
||||||
|
|
||||||
|
|
||||||
}, true)
|
|
||||||
|
|
||||||
let applying = ''
|
|
||||||
|
|
||||||
function colorPixel(e: Event) {
|
|
||||||
const target = e.target as HTMLButtonElement
|
|
||||||
if (applying === '') return
|
|
||||||
console.log('colorPixel', applying)
|
|
||||||
const index = target.getAttribute('data-set-color-of')
|
|
||||||
const level = target.getAttribute('data-level')
|
|
||||||
if (index && level) {
|
|
||||||
const levelIndex = parseInt(level)
|
|
||||||
target.style.background = palette[applying] || 'transparent'
|
|
||||||
setBrick(levelIndex, parseInt(index), applying)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setBrick(levelIndex: number, index: number, chr: string) {
|
|
||||||
const bricks = allLevels[levelIndex].bricks
|
|
||||||
allLevels[levelIndex].bricks = bricks.substring(0, index) + chr + bricks.substring(index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let changed = 0
|
|
||||||
levelsListEl.addEventListener('mousedown', e => {
|
|
||||||
const target = e.target as HTMLButtonElement
|
|
||||||
const index = target.getAttribute('data-set-color-of')
|
|
||||||
const level = target.getAttribute('data-level')
|
|
||||||
if (index && level) {
|
|
||||||
changed = 0
|
|
||||||
const before = allLevels[parseInt(level)].bricks[parseInt(index)] || ''
|
|
||||||
applying = before === currentCode ? '_' : currentCode
|
|
||||||
console.log({before, applying, currentCode})
|
|
||||||
colorPixel(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
levelsListEl.addEventListener('mouseenter', e => {
|
|
||||||
if (applying !== '') {
|
|
||||||
colorPixel(e)
|
|
||||||
changed++
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
document.addEventListener('mouseup', (e: Event) => {
|
|
||||||
applying = '';
|
|
||||||
if (changed) {
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
(document.getElementById('new-level') as HTMLButtonElement).addEventListener('click', (e: Event) => {
|
|
||||||
|
|
||||||
const name = prompt("Name ? ")
|
|
||||||
if (!name) return;
|
|
||||||
|
|
||||||
allLevels.push({
|
|
||||||
name,
|
|
||||||
size: 8,
|
|
||||||
bricks: '________________________________________________________________',
|
|
||||||
svg: null,
|
|
||||||
color: ''
|
|
||||||
})
|
|
||||||
const levelIndex = allLevels.length - 1
|
|
||||||
addLevelEditorToList(allLevels[levelIndex], levelIndex)
|
|
||||||
save()
|
|
||||||
}, true)
|
|
||||||
|
|
||||||
renderAllLevels()
|
|
||||||
|
|
||||||
function save() {
|
|
||||||
return fetch('http://localhost:4400/src/levels.json', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(allLevels, null, 2)
|
|
||||||
})
|
|
||||||
}
|
|
148
src/levels_editor.tsx
Normal file
148
src/levels_editor.tsx
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import {Palette, RawLevel} from "./types";
|
||||||
|
import _backgrounds from './backgrounds.json'
|
||||||
|
import _palette from './palette.json'
|
||||||
|
import _allLevels from './levels.json'
|
||||||
|
import {getLevelBackground, hashCode} from "./getLevelBackground";
|
||||||
|
import {createRoot} from 'react-dom/client';
|
||||||
|
import {useCallback, useState} from "react";
|
||||||
|
import {moveLevel, resizeLevel, setBrick} from "./levels_editor_util";
|
||||||
|
|
||||||
|
const backgrounds = _backgrounds as string[];
|
||||||
|
|
||||||
|
const palette = _palette as Palette;
|
||||||
|
|
||||||
|
let allLevels = _allLevels as RawLevel[];
|
||||||
|
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
return fetch('http://localhost:4400/src/levels.json', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(allLevels, null, 2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState('W')
|
||||||
|
const [applying, setApplying] = useState('')
|
||||||
|
const [levels, setLevels] = useState(allLevels)
|
||||||
|
const updateLevel = useCallback((index: number, change: Partial<RawLevel>) => {
|
||||||
|
setLevels(list => list.map((l, li) => li === index ? {...l, ...change} : l))
|
||||||
|
}, []);
|
||||||
|
const deleteLevel = useCallback((li: number) => {
|
||||||
|
if (confirm('Delete level')) {
|
||||||
|
allLevels = allLevels.filter((l, i) => i !== li)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div onMouseUp={() => setApplying('')} onMouseLeave={() => setApplying('')}>
|
||||||
|
<div id={"levels"}>
|
||||||
|
{
|
||||||
|
levels.map((level, li) => {
|
||||||
|
const {name, bricks, size, svg, color} = level
|
||||||
|
|
||||||
|
const brickButtons = []
|
||||||
|
for (let x = 0; x < size; x++) {
|
||||||
|
for (let y = 0; y < size; y++) {
|
||||||
|
const index = y * size + x
|
||||||
|
brickButtons.push(<button
|
||||||
|
key={index}
|
||||||
|
onMouseDown={() => {
|
||||||
|
const color = selected === bricks[index] ? '_' : applying
|
||||||
|
setApplying(color)
|
||||||
|
updateLevel(li, setBrick(level, index, color))
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
if (applying) {
|
||||||
|
updateLevel(li, setBrick(level, index, applying))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
background: palette[bricks[index]] || 'transparent',
|
||||||
|
left: x * 40, top: y * 40, width: 40, height: 40, position: 'absolute'
|
||||||
|
}}></button>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const background = color ? {backgroundImage: 'none', backgroundColor: color} : {
|
||||||
|
backgroundImage: `url("data:image/svg+xml;UTF8,${encodeURIComponent(getLevelBackground(level) as string)}")`,
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return <div key={li}>
|
||||||
|
<input type="text" value={name} onChange={e => updateLevel(li, {name: e.target.value})}/>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => deleteLevel(li)}>Delete</button>
|
||||||
|
<button onClick={() => updateLevel(li, resizeLevel(level, -1))}>-</button>
|
||||||
|
<button onClick={() => updateLevel(li, resizeLevel(level, +1))}>+</button>
|
||||||
|
<button onClick={() => updateLevel(li, moveLevel(level, -1, 0))}>L</button>
|
||||||
|
<button onClick={() => updateLevel(li, moveLevel(level, 1, 0))}>R</button>
|
||||||
|
<button onClick={() => updateLevel(li, moveLevel(level, 0, -1))}>U</button>
|
||||||
|
<button onClick={() => updateLevel(li, moveLevel(level, 0, 1))}>D</button>
|
||||||
|
<input type="color" value={level.color || ''}
|
||||||
|
onChange={e => e.target.value && updateLevel(li, {color: e.target.value})}/>
|
||||||
|
<input type="number" value={level.svg || (hashCode(level.name) % backgrounds.length)}
|
||||||
|
onChange={e => !isNaN(parseFloat(e.target.value)) && updateLevel(li, {
|
||||||
|
color: '',
|
||||||
|
svg: parseFloat(e.target.value)
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="level-bricks-preview" style={{
|
||||||
|
width: size * 40,
|
||||||
|
height: size * 40,
|
||||||
|
...background
|
||||||
|
|
||||||
|
}}>
|
||||||
|
{brickButtons}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id={"palette"}>
|
||||||
|
{
|
||||||
|
Object.entries(palette).map(([code, color]) => <button
|
||||||
|
key={code}
|
||||||
|
className={code === selected ? 'active' : ''}
|
||||||
|
style={{
|
||||||
|
background: color || 'linear-gradient(45deg,black,white)',
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '40px',
|
||||||
|
height: '40px',
|
||||||
|
border: '1px solid black'
|
||||||
|
}}
|
||||||
|
onClick={() => setSelected(code)}></button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<button id="new-level" onClick={() => {
|
||||||
|
|
||||||
|
const name = prompt("Name ? ")
|
||||||
|
if (!name) return;
|
||||||
|
|
||||||
|
setLevels(l => [...l, {
|
||||||
|
name,
|
||||||
|
size: 8,
|
||||||
|
bricks: '________________________________________________________________',
|
||||||
|
svg: null,
|
||||||
|
color: ''
|
||||||
|
}])
|
||||||
|
|
||||||
|
}}>new
|
||||||
|
</button>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = createRoot(document.getElementById('app') as HTMLDivElement);
|
||||||
|
root.render(<App/>);
|
40
src/levels_editor_util.ts
Normal file
40
src/levels_editor_util.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import {RawLevel} from "./types";
|
||||||
|
|
||||||
|
export function resizeLevel(level: RawLevel, sizeDelta: number) {
|
||||||
|
const {size, bricks} = level
|
||||||
|
const newSize = Math.max(1, size + sizeDelta)
|
||||||
|
const newBricks = new Array(newSize * newSize).fill('_')
|
||||||
|
for (let x = 0; x < Math.min(size, newSize); x++) {
|
||||||
|
for (let y = 0; y < Math.min(size, newSize); y++) {
|
||||||
|
newBricks[y * newSize + x] = bricks.split('')[y * size + x] || '_'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
size: newSize,
|
||||||
|
bricks: newBricks.join('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function moveLevel(level: RawLevel, dx: number, dy: number) {
|
||||||
|
const {size, bricks} = level
|
||||||
|
const newBricks = new Array(size * size).fill('_')
|
||||||
|
for (let x = 0; x < size; x++) {
|
||||||
|
for (let y = 0; y < size; y++) {
|
||||||
|
newBricks[y * size + x] = bricks.split('')[(y - dy) * size + (x - dx)] || '_'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
bricks: newBricks.join('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setBrick(level: RawLevel, index: number, colorCode: string) {
|
||||||
|
let bricksString = level.bricks.slice(0, level.size * level.size)
|
||||||
|
|
||||||
|
if (bricksString.length < level.size * level.size) {
|
||||||
|
bricksString += '_'.repeat(level.size * level.size - bricksString.length)
|
||||||
|
}
|
||||||
|
const bricks = bricksString.split('')
|
||||||
|
bricks[index] = colorCode
|
||||||
|
return {bricks: bricks.join('')}
|
||||||
|
}
|
|
@ -5,6 +5,8 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true,
|
||||||
|
"jsx": "preserve"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue