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 { -