diff --git a/subjects/tron/ai/hard.js b/subjects/tron/ai/hard.js index f7849257..95f3fa24 100644 --- a/subjects/tron/ai/hard.js +++ b/subjects/tron/ai/hard.js @@ -16,100 +16,100 @@ const isAlley = ({ x, y }) => !isFree({ x, y }) || !isInBounds({ x, y }) // this functions will find the best path, so the path that has more empty spaces // so use `isFree`, -const findBestPath = (state) => { +const findBestPath = ({ ai }) => { let arr = [] - let car = state.player.cardinal + let car = ai.cardinal // if it as a block on the symmetric position it must // simulate the symmetric position and see witch path is the best if ( (car === 3 || car === 0) && - !isFree({ x: state.player.x - 1, y: state.player.y - 1 }) && - isFree({ x: state.player.x, y: state.player.y - 1 }) && - isFree({ x: state.player.x - 1, y: state.player.y }) + !isFree({ x: ai.x - 1, y: ai.y - 1 }) && + isFree({ x: ai.x, y: ai.y - 1 }) && + isFree({ x: ai.x - 1, y: ai.y }) ) { - let xad = state.player.x - 1 - let yad = state.player.y - 1 + let xad = ai.x - 1 + let yad = ai.y - 1 let choose = [ calDistance(xad + 1, yad, 1, 0), - calDistance(state.player.x, state.player.y - 1, car, 0), - calDistance(state.player.x - 1, state.player.y, car, 0), + calDistance(ai.x, ai.y - 1, car, 0), + calDistance(ai.x - 1, ai.y, car, 0), calDistance(xad, yad + 1, 2, 0), ] let index = choose.indexOf(Math.max(...choose)) return index === 0 || index === 1 - ? state.player.coords[0] - : state.player.coords[3] + ? ai.coords[0] + : ai.coords[3] } if ( (car === 1 || car === 0) && - !isFree({ x: state.player.x + 1, y: state.player.y - 1 }) && - isFree({ x: state.player.x, y: state.player.y - 1 }) && - isFree({ x: state.player.x + 1, y: state.player.y }) + !isFree({ x: ai.x + 1, y: ai.y - 1 }) && + isFree({ x: ai.x, y: ai.y - 1 }) && + isFree({ x: ai.x + 1, y: ai.y }) ) { - let xad = state.player.x + 1 - let yad = state.player.y - 1 + let xad = ai.x + 1 + let yad = ai.y - 1 // choose will save the biggest path to be chosen // [ down, line1, line0, left ] let choose = [ calDistance(xad, yad + 1, 2, 0), - calDistance(state.player.x + 1, state.player.y, car, 0), - calDistance(state.player.x, state.player.y - 1, car, 0), + calDistance(ai.x + 1, ai.y, car, 0), + calDistance(ai.x, ai.y - 1, car, 0), calDistance(xad - 1, yad, 3, 0), ] let index = choose.indexOf(Math.max(...choose)) return index === 0 || index === 1 - ? state.player.coords[1] - : state.player.coords[0] + ? ai.coords[1] + : ai.coords[0] } if ( (car === 2 || car === 1) && - !isFree({ x: state.player.x + 1, y: state.player.y + 1 }) && - isFree({ x: state.player.x, y: state.player.y + 1 }) && - isFree({ x: state.player.x + 1, y: state.player.y }) + !isFree({ x: ai.x + 1, y: ai.y + 1 }) && + isFree({ x: ai.x, y: ai.y + 1 }) && + isFree({ x: ai.x + 1, y: ai.y }) ) { - let xad = state.player.x + 1 - let yad = state.player.y + 1 + let xad = ai.x + 1 + let yad = ai.y + 1 // choose will save the biggest path to be chosen // [ left, line2, line1, up ] let choose = [ calDistance(xad - 1, yad, 3, 0), - calDistance(state.player.x, state.player.y + 1, car, 0), - calDistance(state.player.x + 1, state.player.y, car, 0), + calDistance(ai.x, ai.y + 1, car, 0), + calDistance(ai.x + 1, ai.y, car, 0), calDistance(xad, yad - 1, 0, 0), ] let index = choose.indexOf(Math.max(...choose)) return index === 0 || index === 1 - ? state.player.coords[2] - : state.player.coords[1] + ? ai.coords[2] + : ai.coords[1] } if ( (car === 2 || car === 3) && - !isFree({ x: state.player.x - 1, y: state.player.y + 1 }) && - isFree({ x: state.player.x, y: state.player.y + 1 }) && - isFree({ x: state.player.x - 1, y: state.player.y }) + !isFree({ x: ai.x - 1, y: ai.y + 1 }) && + isFree({ x: ai.x, y: ai.y + 1 }) && + isFree({ x: ai.x - 1, y: ai.y }) ) { - let xad = state.player.x - 1 - let yad = state.player.y + 1 + let xad = ai.x - 1 + let yad = ai.y + 1 // choose will save the biggest path to be chosen // [ right, line2, line3, up ] let choose = [ calDistance(xad + 1, yad, 1, 0), - calDistance(state.player.x - 1, state.player.y, car, 0), - calDistance(state.player.x, state.player.y + 1, car, 0), + calDistance(ai.x - 1, ai.y, car, 0), + calDistance(ai.x, ai.y + 1, car, 0), calDistance(xad, yad - 1, 0, 0), ] let index = choose.indexOf(Math.max(...choose)) return index === 0 || index === 1 - ? state.player.coords[3] - : state.player.coords[2] + ? ai.coords[3] + : ai.coords[2] } - for ({ x, y, cardinal } of state.player.coords) { + for ({ x, y, cardinal } of ai.coords) { // if everything is ok it must continue with the best path arr.push(calDistance(x, y, cardinal, 0)) } - return state.player.coords[arr.indexOf(Math.max(...arr))] + return ai.coords[arr.indexOf(Math.max(...arr))] } // recursion @@ -166,6 +166,6 @@ const calDistance = (x, y, car, count) => { const addToMap = ({ x, y }) => MAP[y * SIZE + x] = 1 const update = (state) => { - state.players.forEach(addToMap) + state.ais.forEach(addToMap) findBestPath(state) } diff --git a/subjects/tron/ai/license-to-kill.js b/subjects/tron/ai/license-to-kill.js index b3a8ff91..676316f8 100644 --- a/subjects/tron/ai/license-to-kill.js +++ b/subjects/tron/ai/license-to-kill.js @@ -58,19 +58,19 @@ const hasLateralWalls = (card, x, y) => { } } -const goDirection = (state, card) => - ok(state.coords[card]) && - !isAlley(card, state.x, state.y) && - state.coords[card] +const goDirection = (ai, card) => + ok(ai.coords[card]) && + !isAlley(card, ai.x, ai.y) && + ai.coords[card] const findEnemy = state => - state.players.filter(p => state.player.name !== p.name)[0] + state.ais.filter(p => state.ai.name !== p.name)[0] const seekEnemy = state => { - if (state.players.length === 1) return + if (state.ais.length === 1) return const enemy = findEnemy(state) - const xPla = state.player.x - const yPla = state.player.y + const xPla = state.ai.x + const yPla = state.ai.y const xOpo = enemy.x const yOpo = enemy.y const xDif = xPla - xOpo @@ -78,20 +78,20 @@ const seekEnemy = state => { return ( (Math.abs(xDif) > Math.abs(yDif) && - goDirection(state.player, xPla < xOpo ? 1 : 3)) || - goDirection(yPla < yOpo ? 2 : 0) + goDirection(state.ai, xPla < xOpo ? 1 : 3)) || + goDirection(state.ai, yPla < yOpo ? 2 : 0) ) } const walk = state => seekEnemy(state) || - goDirection(state.player, 0) || - goDirection(state.player, 1) || - goDirection(state.player, 2) || - goDirection(state.player, 3) + goDirection(state.ai, 0) || + goDirection(state.ai, 1) || + goDirection(state.ai, 2) || + goDirection(state.ai, 3) const addToMap = ({ x, y }) => (MAP[y * SIZE + x] = 1) const update = state => { - state.players.forEach(addToMap) + state.ais.forEach(addToMap) return walk(state) } diff --git a/subjects/tron/ai/random.js b/subjects/tron/ai/random.js index 9412034a..42355960 100644 --- a/subjects/tron/ai/random.js +++ b/subjects/tron/ai/random.js @@ -2,6 +2,7 @@ const SIZE = 100 const MAP = new Int8Array(SIZE * SIZE) // State of the Map const isFree = ({ x, y }) => MAP[y * SIZE + x] === 0 // 0 = block free const isOccupied = ({ x, y }) => MAP[y * SIZE + x] === 1 // 1 = block occupied +const isMe = ai => ai.me // is true if this AI is you // `inBounds` check if our coord (n) is an existing index in our MAP const inBounds = n => n < SIZE && n >= 0 @@ -17,37 +18,39 @@ const addToMap = ({ x, y }) => MAP[y * SIZE + x] = 1 // `update` this function is called at each turn const update = state => { - // update is called with a state argument that has 2 properties: - // players: an array of all the players - // player: the player for this AI - - // Each players contains: - // color: A number that represent the color of a player - // name: A string of the player name - // score: A number of the total block collected by this player - // x: The horizontal position of the player - // y: The vertical position of the player + // update is called with a state argument that has 3 properties: + // ais: an array of all the AIs + // ai: the current AI + + // Each AIs contains: + // color: A number that represent the color of a AI + // name: A string of the AI name + // score: A number of the total block collected by this AI + // x: The horizontal position of the AI + // y: The vertical position of the AI + // index: The computed index of the coord (x * 100 + y) // coords: An array of 4 coordinates of the nearest blocks - // [ NORTH, EAST, SOUTH, WEST ] - // N - // W + E - // S // Each coordinate contains: // x: The horizontal position // y: The vertical position + // index: The computed index of the coord (x * 100 + y) // cardinal: A number between 0 and 3 that represent the cardinal // [ 0: NORTH, 1: EAST, 2: SOUTH, 3: WEST ] + // N + // W + E + // S + // // direction: A number between 0 and 3 that represent the direction // [ 0: FORWARD, 1: RIGHT, 2: BACKWARD, 3: LEFT ] // Saving state between each updates: - // I update the MAP with the new position of each players - state.players.forEach(addToMap) + // I update the MAP with the new position of each AIs + state.ais.forEach(addToMap) // Actual AI logic: // I filter my array of coords to keep only those that are in bounds - const coordsInBound = state.player.coords.filter(isInBounds) + const coordsInBound = state.ai.coords.filter(isInBounds) // I filter again to keep coords that are free const available = coordsInBound.filter(isFree) diff --git a/subjects/tron/ai/right.js b/subjects/tron/ai/right.js index a2fc0ac4..a49d2edb 100644 --- a/subjects/tron/ai/right.js +++ b/subjects/tron/ai/right.js @@ -1,48 +1,68 @@ const SIZE = 100 -const [FREE, UNSAFE, FILLED] = Array(3).keys() +const [FREE, FILLED] = Array(3).keys() +const [FORWARD, RIGHT, BACKWARD, LEFT] = Array(4).keys() +const [NORTH, EAST, SOUTH, WEST] = Array(4).keys() const MAP = new Int8Array(SIZE * SIZE) -const isFree = ({ x, y }) => MAP[y * SIZE + x] < FILLED -const isUnsafe = ({ x, y }) => MAP[y * SIZE + x] === UNSAFE -const setUnsafe = ({ x, y }) => MAP[y * SIZE + x] = UNSAFE +const isFree = ({ x, y }) => MAP[y * SIZE + x] === FREE const setFilled = ({ x, y }) => MAP[y * SIZE + x] = FILLED const inBounds = n => n < SIZE && n >= 0 const isInBounds = ({ x, y }) => inBounds(x) && inBounds(y) -const isForward = el => el.direction === 0 -const isRight = el => el.direction === 1 -const isLeft = el => el.direction === 3 +const isNotBackward = el => el.direction !== BACKWARD +const isForward = el => el.direction === FORWARD +const isRight = el => el.direction === RIGHT +const isLeft = el => el.direction === LEFT const goForward = arr => arr.find(isForward) const goRight = arr => arr.find(isRight) const goLeft = arr => arr.find(isLeft) -const isOtherPlayer = player => !player.isOwnPlayer +const isOtherAI = ai => !ai.me +const isAlive = ai => !ai.dead -let wallReached = false -const pickForwardOrRightOrLeft = arr => { - if (arr.length === 3 && !wallReached) { - return goForward(arr) || goRight(arr) || goLeft(arr) - } - - if (arr.length <= 2 && !wallReached) { - wallReached = true - return goRight(arr) || goLeft(arr) || goForward(arr) - } +const pickDirection = arr => goLeft(arr) || goForward(arr) || goRight(arr) - return goLeft(arr) || goForward(arr) || goRight(arr) +const getNearestWallCardinal = ai => { + const n = ai.y + const e = 99 - ai.x + const s = 99 - ai.y + const w = ai.x + const smallest = Math.min(n, e, s, w) + if (smallest === n) return NORTH + if (smallest === e) return EAST + if (smallest === s) return SOUTH + if (smallest === w) return WEST } -const update = ({ player, players }) => { - players.forEach(setFilled) - players - .filter(isOtherPlayer) - .flatMap(({ coords }) => coords) +let reachForWall = true +const update = ({ ai, ais }) => { + ais.forEach(setFilled) + + const enemyCoordsIndices = ais + .filter(isOtherAI) + .filter(isAlive) + .flatMap(ai => ai.coords) + .map(coord => coord.index) + + const possibleCoords = ai.coords .filter(isInBounds) .filter(isFree) - .forEach(setUnsafe) - const coordsInBound = player.coords - .filter(isInBounds) - .filter(isNotBackward) + const safeCoords = possibleCoords.filter(c => !enemyCoordsIndices.includes(c.index)) + + if (reachForWall) { + const cardinal = getNearestWallCardinal(ai) + const coord = ai.coords.find(c => c.cardinal === cardinal) + if (safeCoords.includes(coord)) return coord + } - const available = pickForwardOrRightOrLeft(coordsInBound.filter(isFree)) - const lastResort = pickForwardOrRightOrLeft(coordsInBound.filter(isUnsafe)) - return available || lastResort + reachForWall = false + return pickDirection(safeCoords) || pickDirection(possibleCoords) } + + + + + + + + + +// hej \ No newline at end of file diff --git a/subjects/tron/ai/snail.js b/subjects/tron/ai/snail.js index 89cd1c01..1e746062 100644 --- a/subjects/tron/ai/snail.js +++ b/subjects/tron/ai/snail.js @@ -1,11 +1,8 @@ const SIZE = 100 const FREE = 0 -const UNSAFE = -1 const FILLED = 1 const MAP = new Int8Array(SIZE * SIZE) -const isFree = ({ x, y }) => MAP[y * SIZE + x] < FILLED -const isUnsafe = ({ x, y }) => MAP[y * SIZE + x] === UNSAFE -const setUnsafe = ({ x, y }) => MAP[y * SIZE + x] = UNSAFE +const isFree = ({ x, y }) => MAP[y * SIZE + x] === FREE const setFilled = ({ x, y }) => MAP[y * SIZE + x] = FILLED const inBounds = n => n < SIZE && n >= 0 const isInBounds = ({ x, y }) => inBounds(x) && inBounds(y) @@ -16,25 +13,30 @@ const isLeft = el => el.direction === 3 const goForward = arr => arr.find(isForward) const goRight = arr => arr.find(isRight) const goLeft = arr => arr.find(isLeft) -const isOtherPlayer = player => !player.isOwnPlayer +const isAlive = ai => !ai.dead +const isOtherAI = ai => !ai.me // `snailIt` goes with the form of a snail const snailIt = arr => goRight(arr) || goForward(arr) || goLeft(arr) -const update = ({ player, players }) => { - players.forEach(setFilled) - players - .filter(isOtherPlayer) - .flatMap(({ coords }) => coords) +const update = ({ ai, ais }) => { + ais.forEach(setFilled) + + const enemyCoordsIndices = ais + .filter(isOtherAI) + .filter(isAlive) + .flatMap(ai => ai.coords) + .map(coord => coord.index) + + const possibleCoords = ai.coords .filter(isInBounds) .filter(isFree) - .forEach(setUnsafe) - const coordsInBound = player.coords + const safeCoords = possibleCoords.filter(c => !enemyCoordsIndices.includes(c.index)) + + const coordsInBound = ai.coords .filter(isInBounds) .filter(isNotBackward) - const available = snailIt(coordsInBound.filter(isFree)) - const lastResort = snailIt(coordsInBound.filter(isUnsafe)) - return available || lastResort + return snailIt(safeCoords) || snailIt(possibleCoords) } diff --git a/subjects/tron/index.html b/subjects/tron/index.html index cafdba08..fa32d925 100644 --- a/subjects/tron/index.html +++ b/subjects/tron/index.html @@ -37,12 +37,12 @@ h2 b { } -#player-0 h2:after { +#ai-0 h2:after { background-image: linear-gradient(to left, #0000, currentColor 18px, #0000); margin-right: -10px; } -#player-1 h2:after { +#ai-1 h2:after { margin-left: -10px; background-image: linear-gradient(to right, #0000, currentColor 18px, #0000); } @@ -91,18 +91,18 @@ svg { color: cyan; } -#player-0, #player-1 { +#ai-0, #ai-1 { margin: 12px; padding: 6px; user-select: none; } -#player-0 { +#ai-0 { grid-column: 1; text-align: right; } -#player-1 { grid-column: 4 } +#ai-1 { grid-column: 4 } #controls { @@ -136,8 +136,8 @@ svg { @media (orientation: portrait) { h2 { max-width: 300px } #controls { grid-row: 3 } - #player-0 { grid-column: 2; grid-row: 4 } - #player-1 { grid-column: 3; grid-row: 4 } + #ai-0 { grid-column: 2; grid-row: 4 } + #ai-1 { grid-column: 3; grid-row: 4 } } @@ -147,14 +147,14 @@ svg { -
+

AI-0

LOADING
0
-
+

AI-1

LOADING
@@ -187,18 +187,18 @@ hide.onclick = () => { localStorage.hide || hide.parentElement.classList.remove('hide') -const buildInfo = (player, i) => { - const elem = document.getElementById(`player-${i}`) +const buildInfo = (ai, i) => { + const elem = document.getElementById(`ai-${i}`) if (!elem) return { score: () => {}, status: () => {} } const [name, status, score] = [...elem.children].map(e => e.firstChild).filter(Boolean) - name.firstChild.data = player.name - elem.style.color = `hsl(${player.hue*360}, 100%, 70%)` - elem.style.textShadow = `0 0 6px hsla(${player.hue*360}, 100%, 70%, 0.4)` + name.firstChild.data = ai.name + elem.style.color = `hsl(${ai.hue*360}, 100%, 70%)` + elem.style.textShadow = `0 0 6px hsla(${ai.hue*360}, 100%, 70%, 0.4)` elem.style.width = 'calc(100% - 36px)' // force width force redraw // this fix a bug on chrome, not re-applying `currentColor` to gradients return { - score: text => player.dead || (score.data = text), + score: text => ai.dead || (score.data = text), status: text => status.data = text, } } @@ -212,8 +212,11 @@ const getAIUrl = (login, sha, ai) => `https://rawcdn.githack.com/${login}/tron/${sha}/ai/${ai || login}.js` const getSha = async login => (await (await fetch(getShaUrl(login))).json()).sha -const toBlob = async r => - new Blob([`${await r.text()}${injectedCode}`], { type : 'text/javascript' }) +const toBlob = async r => { + if (!r.ok) throw Error(`${r.status}: ${r.statusText}`) + const code = await r.text() + return new Blob([`${code}${injectedCode}`], { type : 'text/javascript' }) +} const toUrlObject = b => URL.createObjectURL(b, { type: 'text/javascript' }) const memo = {} const fetchBlob = url => memo[url] @@ -241,10 +244,10 @@ const formatURL = url => { const start = async ({ urls, seed }) => { if (urls.length < 2) throw Error('2 AI urls are required to play') - const players = init({ players: urls, seed }) + const ais = init({ ais: urls, seed }) let turn = 1, maxTurn = 1, t = 1, cap = 10000, down - const done = new Set(players) + const done = new Set(ais) const setPosition = e => { const v = typeof e === 'number' @@ -271,101 +274,101 @@ const start = async ({ urls, seed }) => { let requested, timeout const refresh = () => { requested = update(t) - players[0].score(turn) - players[1].score(turn) + ais[0].score(turn) + ais[1].score(turn) loading.style.transform = `translate(${((turn / cap)*600)-600}px)` position.style.transform = `translate(${((t / cap)*600)-600}px)` } - const next = (player) => { - clearTimeout(player.timeout) - done.add(player) + const next = (ai) => { + clearTimeout(ai.timeout) + done.add(ai) requested || (requested = requestAnimationFrame(refresh)) // check if all AI are done - if (done.size >= players.length) { + if (done.size >= ais.length) { turn++ - const data = `[${players.join(',')}]` + const data = `[${ais.join(',')}]` let allDead = true - for (const p of players) { - if (p.dead) continue + for (const a of ais) { + if (a.dead) continue allDead ? (allDead = false) : cap-- - done.delete(p) - p.worker.postMessage(data) - p.timeout = setTimeout(p.kill, 50, 'TIMEOUT') + done.delete(a) + a.worker.postMessage(data) + a.timeout = setTimeout(a.kill, 50, 'TIMEOUT') } allDead && (cap = turn) t === maxTurn ? (t = maxTurn = turn) : (maxTurn = turn) } } - await Promise.all(players.map(async (player, i) => { - const info = buildInfo(player, i) - player.score = info.score + await Promise.all(ais.map(async (ai, i) => { + const info = buildInfo(ai, i) + ai.score = info.score const kill = (cause) => { - if (player.dead) return - console.log(`${player.name} died because he ${cause} at ${player.x} ${player.y}`) - player.cause = cause - player.dead = true - player.worker && player.worker.terminate() + if (ai.dead) return + console.log(`${ai.name} died because he ${cause} at ${ai.x} ${ai.y}`) + ai.cause = cause + ai.dead = true + ai.worker && ai.worker.terminate() info.status(cause) } // init the worker try { - const url = await fetchBlob(await formatURL(player.name)) - player.worker = new Worker(url, { type: 'module', name: player.name }) + const url = await fetchBlob(await formatURL(ai.name)) + ai.worker = new Worker(url, { type: 'module', name: ai.name }) await new Promise((s, f) => { - player.worker.onmessage = e => e.data === 'loaded' ? s() : f(Error(e.data)) - player.worker.onerror = f - player.worker.postMessage(JSON.stringify({ id: player.name, seed })) + ai.worker.onmessage = e => e.data === 'loaded' ? s() : f(Error(e.data)) + ai.worker.onerror = f + ai.worker.postMessage(JSON.stringify({ id: ai.name, seed })) }) // activate the AI info.status('ACTIVE') - player.kill = cause => { + ai.kill = cause => { kill(cause) - next(player) + next(ai) } } catch (err) { console.error(err) kill('FAILED-TO-LOAD') } - move(player.x, player.y, player.color, turn) + move(ai.x, ai.y, ai.color, turn) // handle each response from the AI - player.worker.onmessage = ({ data }) => { - if (done.has(player)) return player.kill('UNEXPECTED-MESSAGE') - if (!data) return player.kill('STUCK') + ai.worker.onmessage = ({ data }) => { + if (done.has(ai)) return ai.kill('UNEXPECTED-MESSAGE') + if (!data) return ai.kill('STUCK') const { x, y } = JSON.parse(data) - if (typeof x !== 'number' || typeof y !== 'number') return player.kill('INVALID_INPUT') - if (notInBounds(x) || notInBounds(y)) return player.kill('OUT_OF_BOUNDS') + if (typeof x !== 'number' || typeof y !== 'number') return ai.kill('INVALID_INPUT') + if (notInBounds(x) || notInBounds(y)) return ai.kill('OUT_OF_BOUNDS') if ( - !(x === player.x - 1 && y === player.y) && - !(x === player.x + 1 && y === player.y) && - !(x === player.x && player.y === y + 1) && - !(x === player.x && player.y === y - 1) - ) return player.kill('IMPOSSIBLE_MOVE') - - player.x = x - player.y = y - const failure = move(x, y, player.color, turn) + !(x === ai.x - 1 && y === ai.y) && + !(x === ai.x + 1 && y === ai.y) && + !(x === ai.x && ai.y === y + 1) && + !(x === ai.x && ai.y === y - 1) + ) return ai.kill('IMPOSSIBLE_MOVE') + + ai.x = x + ai.y = y + const failure = move(x, y, ai.color, turn) if (failure === turn) { - for (const p of players) { - p.x === player.x && p.y === player.y && p.kill('MULTI-CRASH') + for (const a of ais) { + a.x === ai.x && a.y === ai.y && a.kill('MULTI-CRASH') } colorize(x, y, 0xffffff) - return player.kill('MULTI-CRASH') - } else if (failure) return player.kill('CRASH') - next(player) + return ai.kill('MULTI-CRASH') + } else if (failure) return ai.kill('CRASH') + next(ai) } - player.worker.onerror = () => player.kill('AI-ERROR') + ai.worker.onerror = () => ai.kill('AI-ERROR') })) - next(players[0]) + next(ais[0]) } const params = new URLSearchParams(location.search) diff --git a/subjects/tron/lib/state.js b/subjects/tron/lib/state.js index 5a1b7754..85ca6602 100644 --- a/subjects/tron/lib/state.js +++ b/subjects/tron/lib/state.js @@ -27,67 +27,93 @@ const hslToRgb = (h, s, l) => { return toInt(toRange(r), toRange(g), toRange(b)) } -export const init = ({ players, seed }) => { +export const init = ({ ais, seed }) => { + let w = (123456789 + seed) & 0xffffffff + let z = (987654321 - seed) & 0xffffffff + const rand = () => { - let t = seed += 0x6D2B79F5 - t = Math.imul(t ^ t >>> 15, t | 1) - t ^= t + Math.imul(t ^ t >>> 7, t | 61) - return ((t ^ t >>> 14) >>> 0) / 4294967296 + z = (36969 * (z & 65535) + (z >>> 16)) & 0xffffffff + w = (18000 * (w & 65535) + (w >>> 16)) & 0xffffffff + return (((z << 16) + (w & 65535)) >>> 0) / 4294967296 } - const angle = (Math.PI * 2) / players.length - const rate = (SIZE / players.length / SIZE) + const angle = (Math.PI * 2) / ais.length + const rate = (SIZE / ais.length / SIZE) const shift = angle * rand() // shuffle using seeded random - players.sort((a, b) => a.name - b.name) - let i = players.length, j, tmp + ais.sort((a, b) => a.name - b.name) + let i = ais.length, j, tmp while (--i > 0) { j = Math.floor(rand() * (i + 1)) - tmp = players[j] - players[j] = players[i] - players[i] = tmp + tmp = ais[j] + ais[j] = ais[i] + ais[i] = tmp } - return players.map((name, i) => { + return ais.map((name, i) => { const jsonName = `"name":${JSON.stringify(name)}` const hue = max1(i * rate + 0.25) - const p = { + const ai = { hue, name, x: Math.round(max2PI(Math.cos(angle * i + shift)) * m + h), y: Math.round(max2PI(Math.sin(angle * i + shift)) * m + h), - cardinal: 0, - direction: 0, color: hslToRgb(hue, 1, 0.5), - toString: () => `{${jsonName},"dead":${!!p.dead},"cardinal":${p.cardinal},"direction":${p.direction},"color":${p.color},"x":${p.x},"y":${p.y},"coords":[{"x":${p.x},"y":${p.y - 1},"cardinal":0,"direction":${(4 - p.cardinal) % 4}},{"x":${p.x + 1},"y":${p.y},"cardinal":1,"direction":${(5 - p.cardinal) % 4}},{"x":${p.x},"y":${p.y + 1},"cardinal":2,"direction":${(6 - p.cardinal) % 4}},{"x":${p.x - 1},"y":${p.y},"cardinal":3,"direction":${(7 - p.cardinal) % 4}}]}`, + toString: () => `{${jsonName},"dead":${!!ai.dead},"color":${ai.color},"x":${ai.x},"y":${ai.y}}`, } - return p + return ai }) } export const injectedCode = ` if (typeof update !== 'function') throw Error('Update function not defined') addEventListener('message', self.init = initEvent => { - const { seed, id } = JSON.parse(initEvent.data) - const isOwnPlayer = p => p.name === id + let { seed, id } = JSON.parse(initEvent.data) + const r4 = () => Math.floor(Math.random() * 4) + let w = (123456789 + seed) & 0xffffffff + let z = (987654321 - seed) & 0xffffffff Math.random = () => { - let t = seed += 0x6D2B79F5 - t = Math.imul(t ^ t >>> 15, t | 1) - t ^= t + Math.imul(t ^ t >>> 7, t | 61) - return ((t ^ t >>> 14) >>> 0) / 4294967296 + z = (36969 * (z & 65535) + (z >>> 16)) & 0xffffffff + w = (18000 * (w & 65535) + (w >>> 16)) & 0xffffffff + return (((z << 16) + (w & 65535)) >>> 0) / 4294967296 } + const prev = {} + let me removeEventListener('message', self.init) addEventListener('message', ({ data }) => { - const players = JSON.parse(data) - const player = players.find(isOwnPlayer) - player.isOwnPlayer = true + const ais = JSON.parse(data) + me || (ais + .sort((a, b) => a.name - b.name) + .forEach(a => prev[a.name] = [{...a, cardinal: r4(), direction: r4() }])) + + for (const ai of ais) { + const { x, y, name, dead } = ai + if (dead) { + ai.coords = [] + continue + } + + name === id && (me = ai) + const { cardinal, direction } = prev[name].find(c => c.x === ai.x && c.y === ai.y) + + ai.index = ai.x * 100 + ai.y + ai.direction = direction + ai.cardinal = cardinal + ai.coords = prev[name] = [ + { index: x*100+(y-1), x, y: y - 1, cardinal: 0, direction: (4 - cardinal) % 4 }, + { index: (x+1)*100+y, x: x + 1, y, cardinal: 1, direction: (5 - cardinal) % 4 }, + { index: x*100+(y+1), x, y: y + 1, cardinal: 2, direction: (6 - cardinal) % 4 }, + { index: (x-1)*100+y, x: x - 1, y, cardinal: 3, direction: (7 - cardinal) % 4 }, + ] + } + me.me = true - try { postMessage(JSON.stringify(update({ players, player }))) } + try { postMessage(JSON.stringify(update({ ais, ai: me }))) } catch (err) { - console.error(err) + console.error(id, err) throw err } }) diff --git a/subjects/tron/tron.en.md b/subjects/tron/tron.en.md index dacdc8a3..23f07aa6 100644 --- a/subjects/tron/tron.en.md +++ b/subjects/tron/tron.en.md @@ -41,7 +41,9 @@ You will need to create a public repository with the name `tron`. Next you need - You may use this link [tron](/public/subjects/tron?ai=&seed=1653547275), to test your AI - You need to add your AI as a user in that link - > Example: if your git login is **Frenchris** and you want to test against **LEEDASILVA** the link becomes: `/public/subjects/tron?AI=Frenchris@master+LEEDASILVA@master&seed=1653547275` + > Example: + - if your git login is **Frenchris** and you want to test against **LEEDASILVA** the link becomes: `/public/subjects/tron?AI=Frenchris@master+LEEDASILVA@master` + - if you want to test against the default `/random.js` AI the link becomes: `/public/subjects/tron?AI=Frenchris@master+/random.js` - Open the inspector of the browser used and **disable the cache** - let's change the update function so that your AI only goes forward.