Created
July 6, 2018 20:33
-
-
Save beardedtim/12b005c4a525acb31c1d80a619390dbf to your computer and use it in GitHub Desktop.
A stupid worm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const B = require('bambi') | |
| const EARTH_WIDTH = 5 | |
| const EARTH_HEIGHT = 5 | |
| const EMPTY = 0 | |
| const FOOD = 2 | |
| const SEEN = 4 | |
| const SELF = 8 | |
| const getValue = ({ | |
| x, | |
| y | |
| }, board) => board[y][x] | |
| const setValue = ({ | |
| point, | |
| value | |
| }, board) => board[point.y][point.x] = value | |
| const setFood = (location, board) => | |
| setValue({ | |
| point: location, | |
| value: FOOD | |
| }, board) | |
| const setSelf = (location, board) => | |
| setValue({ | |
| point: location, | |
| value: SELF | |
| }, board) | |
| const setSeen = (location, board) => | |
| setValue({ | |
| point: location, | |
| value: SEEN | |
| }, board) | |
| /** | |
| * Goal: Find the food | |
| * Given: You only know what's in front of you and to your sides | |
| */ | |
| const createEarth = () => Array.from({ | |
| length: EARTH_HEIGHT | |
| }, | |
| () => Array.from({ | |
| length: EARTH_WIDTH | |
| }, () => EMPTY) | |
| ) | |
| const createWormie = () => ({ | |
| location: { | |
| x: 1, | |
| y: 1 | |
| }, | |
| symbol: SELF | |
| }) | |
| const currentVision = (location) => { | |
| /** | |
| * Wormie can see 1 block in any direction | |
| * | |
| * TODO: Add some linked-list-esque data features here | |
| * so that when wormie looks at a location it can | |
| * say "Look at the next few as well" | |
| */ | |
| const possible = [] | |
| // We can move left | |
| if (location.x) { | |
| possible.push({ ...location, | |
| x: location.x - 1 | |
| }) | |
| } | |
| // We can move up | |
| if (location.y) { | |
| possible.push({ ...location, | |
| y: location.y - 1 | |
| }) | |
| } | |
| // We can move right | |
| if (location.x < EARTH_WIDTH - 1) { | |
| possible.push({ ...location, | |
| x: location.x + 1 | |
| }) | |
| } | |
| // We can move down | |
| if (location.y < EARTH_HEIGHT - 1) { | |
| possible.push({ ...location, | |
| y: location.y + 1 | |
| }) | |
| } | |
| // We morph each into its own value | |
| return possible.map(loc => ({ | |
| location: loc, | |
| value: getValue(loc, earth) | |
| })) | |
| } | |
| const getNextLocation = locations => { | |
| const empties = [] | |
| const seen = [] | |
| for (let i = 0; i < locations.length; i++) { | |
| const loc = locations[i] | |
| // If it's a FOOD item | |
| if (loc.value === FOOD) { | |
| // Choose it right away! | |
| return loc | |
| } | |
| // else, if it's empty | |
| if (loc.value === EMPTY) { | |
| // Let's save it for later | |
| empties.push(loc) | |
| } | |
| if(loc.value === SEEN) { | |
| seen.push(loc) | |
| } | |
| } | |
| if (empties.length) { | |
| // Select a random index between 0 and its max | |
| const index = B.random(0, empties.length - 1) | |
| // return the random location | |
| return empties[index] | |
| } | |
| if(seen.length) { | |
| const index = B.random(0, seen.length - 1) | |
| return seen[index] | |
| } | |
| throw new Error(`END OF GAME. CANNOT MOVE`) | |
| } | |
| // A way to draw a board row by row | |
| const drawBoard = (board = []) => { | |
| for (let y = 0; y < board.length; y++) { | |
| const row = [] | |
| for (let x = 0; x < board[y].length; x++) { | |
| row[x] = board[y][x] | |
| } | |
| console.log(row) | |
| } | |
| } | |
| let earth, Wormie | |
| const isFood = ({ | |
| value | |
| }) => value === FOOD | |
| const updateEarth = newLocation => { | |
| // Move Wormie | |
| setSelf(newLocation, earth) | |
| // Remove old wormie | |
| setSeen(Wormie.location, earth) | |
| } | |
| const updateState = (visibleBlocks) => { | |
| const nextLocation = getNextLocation(visibleBlocks) | |
| // Update Earth State | |
| updateEarth(nextLocation.location) | |
| Wormie.location = nextLocation.location | |
| return nextLocation | |
| } | |
| /** | |
| * Amount of iterations to run simulation | |
| */ | |
| const EPOCS = 1000; | |
| /** | |
| * Max amount of tries any one simulation can have | |
| */ | |
| const MAX_TRIES = 100 | |
| /** | |
| * STATEFUL | |
| */ | |
| /** | |
| * The total amount of tries | |
| * during the epoc | |
| */ | |
| let total = 0 | |
| /** | |
| * Amount of tries spent on successful attempts | |
| */ | |
| let successfulTries = 0 | |
| /** | |
| * Amount of tries spent on failed attempts | |
| */ | |
| let failureTries = 0 | |
| /** | |
| * Amount of epocs that succeeded | |
| */ | |
| let succeeded = 0 | |
| /** | |
| * Amount of epocs that failed | |
| */ | |
| let failed = 0 | |
| /** | |
| * Flag to flip printings of epoc info | |
| */ | |
| const PRINT_HEADINGS = false | |
| // For the total amount of EPOCS | |
| for (let i = 0; i < EPOCS; i++) { | |
| // we create a new world | |
| earth = createEarth() | |
| // and a new wormie | |
| Wormie = createWormie() | |
| const food = [{ x: 3, y: 3 }] | |
| // and we set some values on the earth | |
| food.forEach(f => setFood(f, earth)) | |
| setSelf(Wormie.location, earth) | |
| if (PRINT_HEADINGS) { | |
| console.log('***********BEGIN EPOC************') | |
| console.log('***********BEGIN EPOC************') | |
| } | |
| // and each simulation needs state as well | |
| // This is this specifics iterations count | |
| let count = 0 | |
| // and the amount of food | |
| let foodCount = food.length | |
| while (true) { | |
| count++ | |
| try { | |
| if (count >= MAX_TRIES) { | |
| throw new Error('MAX_TRIES') | |
| } | |
| // Before this iteration | |
| if (PRINT_HEADINGS) { | |
| console.log('***********PREMOVE************') | |
| drawBoard(earth) | |
| console.log('Wormie: ', Wormie) | |
| console.log('***********PREMOVE************') | |
| } | |
| // The next location based on some heuristic | |
| const nextLocation = updateState(currentVision(Wormie.location)) | |
| // If this next location is food | |
| if (isFood(nextLocation)) { | |
| foodCount-- | |
| } | |
| // After updating state | |
| if (PRINT_HEADINGS) { | |
| console.log('***********POSTMOVE************') | |
| drawBoard(earth) | |
| console.log('Wormie: ', Wormie) | |
| console.log('***********POSTMOVE************') | |
| } | |
| // Check for completed state | |
| if (!foodCount) { | |
| succeeded++ | |
| successfulTries += count | |
| throw new Error('You are done! You caught all of the food!') | |
| } | |
| } catch (e) { | |
| // console.warn(e) | |
| // If we've reached max tries or we still have | |
| // food when we get here | |
| if(e.message.indexOf('MAX_TRIES') === 0 || foodCount) { | |
| // treat it as a failure | |
| failed++ | |
| failureTries += count | |
| } | |
| if (PRINT_HEADINGS) { | |
| console.log('\n', e.message, '\n') | |
| } | |
| // Break out of the while loop | |
| break; | |
| } | |
| } | |
| // add this iteration's count to the total | |
| total += count | |
| if (PRINT_HEADINGS) { | |
| console.log('***********END EPOC************') | |
| console.log(`TOTAL MOVES: ${count}`) | |
| console.log('***********END EPOC************') | |
| } | |
| } | |
| console.log('***********END ALL************') | |
| console.log(`TOTAL MOVES: ${total}`) | |
| console.log(`TOTAL ITERATIONS: ${EPOCS}`) | |
| console.log(`TOTAL SUCCESSES: ${succeeded}`) | |
| console.log(`TOTAL FAILURES: ${failed}`) | |
| console.log(`AVERAGE MOVES: ${total / EPOCS}`) | |
| console.log(`TOTAL MOVES IN SUCCESSFUL TRIALS: ${successfulTries}`) | |
| console.log(`AVG MOVES IN SUCCESSFUL TRIALS: ${successfulTries / succeeded}`) | |
| console.log(`TOTAL MOVES IN FAILURE TRIALS: ${failureTries}`) | |
| // console.log(`AVG MOVES IN FAILURE TRIALS: ${failureTries / failed}`) | |
| console.log('***********END ALL************') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment