├── .keep ├── 00_intro.html ├── 01_values.html ├── 02_program_structure.html ├── 03_functions.html ├── 04_data.html ├── 05_higher_order.html ├── 06_object.html ├── 07_robot.html ├── 08_error.html ├── 09_regexp.html ├── 10_modules.html ├── 11_async.html ├── 12_language.html ├── 13_browser.html ├── 14_dom.html ├── 15_event.html ├── 16_game.html ├── 17_canvas.html ├── 18_http.html ├── 19_paint.html ├── 20_node.html ├── 21_skillsharing.html ├── CNAME ├── author.html ├── author.json ├── author.txt ├── backers.html ├── backers3.html ├── code ├── animatevillage.js ├── chapter │ ├── .keep │ ├── 04_data.js │ ├── 05_higher_order.js │ ├── 06_object.js │ ├── 07_robot.js │ ├── 08_error.js │ ├── 11_async.js │ ├── 12_language.js │ ├── 16_game.js │ ├── 17_canvas.js │ ├── 19_paint.js │ └── 22_fast.js ├── crow-tech.js ├── draw_layout.js ├── file_server.js ├── hello.js ├── index.html ├── intro.js ├── journal.js ├── levels.js ├── load.js ├── packages_chapter_10.js ├── scripts.js ├── skillsharing.zip ├── skillsharing │ ├── .keep │ ├── package.json │ ├── public │ │ ├── index.html │ │ ├── skillsharing.css │ │ └── skillsharing_client.js │ ├── router.js │ └── skillsharing_server.js ├── solutions │ ├── 02_1_looping_a_triangle.js │ ├── 02_2_fizzbuzz.js │ ├── 02_3_chessboard.js │ ├── 03_1_minimum.js │ ├── 03_2_recursion.js │ ├── 03_3_bean_counting.js │ ├── 04_1_the_sum_of_a_range.js │ ├── 04_2_reversing_an_array.js │ ├── 04_3_a_list.js │ ├── 04_4_deep_comparison.js │ ├── 05_1_flattening.js │ ├── 05_2_your_own_loop.js │ ├── 05_3_everything.js │ ├── 05_4_dominant_writing_direction.js │ ├── 06_1_a_vector_type.js │ ├── 06_2_groups.js │ ├── 06_3_iterable_groups.js │ ├── 06_4_borrowing_a_method.js │ ├── 07_1_measuring_a_robot.js │ ├── 07_2_robot_efficiency.js │ ├── 07_3_persistent_group.js │ ├── 08_1_retry.js │ ├── 08_2_the_locked_box.js │ ├── 09_1_regexp_golf.js │ ├── 09_2_quoting_style.js │ ├── 09_3_numbers_again.js │ ├── 10_2_roads_module.js │ ├── 11_1_tracking_the_scalpel.js │ ├── 11_2_building_promiseall.js │ ├── 12_1_arrays.js │ ├── 12_3_comments.js │ ├── 12_4_fixing_scope.js │ ├── 14_1_build_a_table.html │ ├── 14_2_elements_by_tag_name.html │ ├── 14_3_the_cats_hat.html │ ├── 15_1_balloon.html │ ├── 15_2_mouse_trail.html │ ├── 15_3_tabs.html │ ├── 16_1_game_over.html │ ├── 16_2_pausing_the_game.html │ ├── 16_3_a_monster.html │ ├── 17_1_shapes.html │ ├── 17_2_the_pie_chart.html │ ├── 17_3_a_bouncing_ball.html │ ├── 18_1_content_negotiation.js │ ├── 18_2_a_javascript_workbench.html │ ├── 18_3_conways_game_of_life.html │ ├── 19_1_keyboard_bindings.html │ ├── 19_2_efficient_drawing.html │ ├── 19_3_circles.html │ ├── 19_4_proper_lines.html │ ├── 20_1_search_tool.js │ ├── 20_2_directory_creation.js │ ├── 20_3_a_public_space_on_the_web.zip │ ├── 20_3_a_public_space_on_the_web │ │ ├── index.html │ │ ├── other.html │ │ └── public_space.js │ ├── 21_1_disk_persistence.js │ ├── 21_2_comment_field_resets.js │ ├── 22_1_pathfinding.js │ ├── 22_2_timing.js │ └── 22_3_optimizing.js └── squareworker.js ├── css ├── ejs.css ├── game.css └── paint.css ├── empty.html ├── errata.html ├── example ├── bert.json ├── data.txt ├── fruit.json ├── fruit.xml ├── message.html ├── muriel.json ├── submit.html └── suzie.json ├── favicon.ico ├── font ├── Shabnam-Bold.eot ├── Shabnam-Bold.ttf ├── Shabnam-Bold.woff ├── Shabnam-Bold.woff2 ├── Shabnam-Light.eot ├── Shabnam-Light.ttf ├── Shabnam-Light.woff ├── Shabnam-Light.woff2 ├── Shabnam.eot ├── Shabnam.ttf ├── Shabnam.woff ├── Shabnam.woff2 ├── cinzel_bold.woff └── pt_mono.woff ├── img ├── Hieres-sur-Amby.png ├── blockquote.png ├── boxed-in.png ├── button_disabled.png ├── canvas_beziercurve.png ├── canvas_circle.png ├── canvas_fill.png ├── canvas_game.png ├── canvas_path.png ├── canvas_pie_chart.png ├── canvas_quadraticcurve.png ├── canvas_scale.png ├── canvas_stroke.png ├── canvas_tree.png ├── canvas_triangle.png ├── cat-animation.png ├── cat.png ├── chapter_picture_00.jpg ├── chapter_picture_1.jpg ├── chapter_picture_10.jpg ├── chapter_picture_11.jpg ├── chapter_picture_12.jpg ├── chapter_picture_13.jpg ├── chapter_picture_14.jpg ├── chapter_picture_15.jpg ├── chapter_picture_16.jpg ├── chapter_picture_17.jpg ├── chapter_picture_18.jpg ├── chapter_picture_19.jpg ├── chapter_picture_2.jpg ├── chapter_picture_20.jpg ├── chapter_picture_21.jpg ├── chapter_picture_3.jpg ├── chapter_picture_4.jpg ├── chapter_picture_5.jpg ├── chapter_picture_6.jpg ├── chapter_picture_7.jpg ├── chapter_picture_8.jpg ├── chapter_picture_9.jpg ├── color-field.png ├── colored-links.png ├── control-io.svg ├── controlflow-else.svg ├── controlflow-if.svg ├── controlflow-loop.svg ├── controlflow-nested-if.svg ├── controlflow-straight.svg ├── cos_sin.svg ├── cover.jpg ├── darkblue.png ├── display.png ├── drag-bar.png ├── exercise_shapes.png ├── flood-grid.svg ├── form_fields.png ├── form_select.png ├── game-grid.svg ├── game_simpleLevel.png ├── generated │ └── .keep ├── ghostery.png ├── ghostery_mini.png ├── hack_reactor.png ├── hack_reactor_mini.png ├── hat.png ├── help-field.png ├── highlighted.png ├── holberton.png ├── home-page.png ├── html-boxes.svg ├── html-links.svg ├── html-tree.svg ├── line-grid.svg ├── linked-list.svg ├── middle_east_graph.png ├── middle_east_graph_random.png ├── mirror.svg ├── mozilla.png ├── mozilla_mini.png ├── nextjournal.png ├── object.jpg ├── object_full.jpg ├── ostrich.png ├── parcel2x.png ├── pixel_editor.png ├── pizza-squirrel.svg ├── player.png ├── player_big.png ├── prompt.png ├── rabbits.svg ├── re_number.svg ├── re_pigchickens.svg ├── re_slow.svg ├── robot_idle.png ├── robot_idle2x.png ├── robot_moving.gif ├── robot_moving2x.gif ├── skillsharing.png ├── sprites.png ├── sprites_big.png ├── svg-demo.png ├── syntax_tree.svg ├── tamil.png ├── transform.svg ├── tree_graph.png ├── unicycle.svg ├── village.png ├── village2x.png ├── weresquirrel.png └── weresquirrel.svg ├── index.html └── js ├── .tern-project ├── acorn_codemirror.js ├── chapter_info.js ├── code.js ├── ejs.js ├── node_modules └── codemirror │ └── lib │ ├── codemirror.css │ └── codemirror.js └── sandbox.js /.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/.keep -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | eloquentjs.ir -------------------------------------------------------------------------------- /author.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Marijn Haverbeke, Programmer

6 | 7 |

You can reach me at marijn@haverbeke.nl, or visit my web page, marijnhaverbeke.nl.

10 |
11 | -------------------------------------------------------------------------------- /author.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Marijn Haverbeke", 3 | "email": "marijn@haverbeke.nl", 4 | "website": "https://marijnhaverbeke.nl/" 5 | } 6 | -------------------------------------------------------------------------------- /author.txt: -------------------------------------------------------------------------------- 1 | My name is Marijn Haverbeke. You can email me at marijn@haverbeke.nl, or visit my website, https://marijnhaverbeke.nl/ . 2 | -------------------------------------------------------------------------------- /code/animatevillage.js: -------------------------------------------------------------------------------- 1 | // test: no 2 | 3 | (function() { 4 | "use strict" 5 | 6 | let active = null 7 | 8 | const places = { 9 | "Alice's House": {x: 279, y: 100}, 10 | "Bob's House": {x: 295, y: 203}, 11 | "Cabin": {x: 372, y: 67}, 12 | "Daria's House": {x: 183, y: 285}, 13 | "Ernie's House": {x: 50, y: 283}, 14 | "Farm": {x: 36, y: 118}, 15 | "Grete's House": {x: 35, y: 187}, 16 | "Marketplace": {x: 162, y: 110}, 17 | "Post Office": {x: 205, y: 57}, 18 | "Shop": {x: 137, y: 212}, 19 | "Town Hall": {x: 202, y: 213} 20 | } 21 | const placeKeys = Object.keys(places) 22 | 23 | const speed = 2 24 | 25 | class Animation { 26 | constructor(worldState, robot, robotState) { 27 | this.worldState = worldState 28 | this.robot = robot 29 | this.robotState = robotState 30 | this.turn = 0 31 | 32 | let outer = (window.__sandbox ? window.__sandbox.output.div : document.body), doc = outer.ownerDocument 33 | this.node = outer.appendChild(doc.createElement("div")) 34 | this.node.style.cssText = "position: relative; line-height: 0.1; margin-left: 10px" 35 | this.map = this.node.appendChild(doc.createElement("img")) 36 | this.map.src = "img/village2x.png" 37 | this.map.style.cssText = "vertical-align: -8px" 38 | this.robotElt = this.node.appendChild(doc.createElement("div")) 39 | this.robotElt.style.cssText = `position: absolute; transition: left ${0.8 / speed}s, top ${0.8 / speed}s;` 40 | let robotPic = this.robotElt.appendChild(doc.createElement("img")) 41 | robotPic.src = "img/robot_moving2x.gif" 42 | this.parcels = [] 43 | 44 | this.text = this.node.appendChild(doc.createElement("span")) 45 | this.button = this.node.appendChild(doc.createElement("button")) 46 | this.button.style.cssText = "color: white; background: #28b; border: none; border-radius: 2px; padding: 2px 5px; line-height: 1.1; font-family: sans-serif; font-size: 80%" 47 | this.button.textContent = "Stop" 48 | 49 | this.button.addEventListener("click", () => this.clicked()) 50 | this.schedule() 51 | 52 | this.updateView() 53 | this.updateParcels() 54 | 55 | this.robotElt.addEventListener("transitionend", () => this.updateParcels()) 56 | } 57 | 58 | 59 | updateView() { 60 | let pos = places[this.worldState.place] 61 | this.robotElt.style.top = (pos.y - 38) + "px" 62 | this.robotElt.style.left = (pos.x - 16) + "px" 63 | 64 | this.text.textContent = ` Turn ${this.turn} ` 65 | } 66 | 67 | updateParcels() { 68 | while (this.parcels.length) this.parcels.pop().remove() 69 | let heights = {} 70 | for (let {place, address} of this.worldState.parcels) { 71 | let height = heights[place] || (heights[place] = 0) 72 | heights[place] += 14 73 | let node = document.createElement("div") 74 | let offset = placeKeys.indexOf(address) * 16 75 | node.style.cssText = "position: absolute; height: 16px; width: 16px; background-image: url(img/parcel2x.png); background-position: 0 -" + offset + "px"; 76 | if (place == this.worldState.place) { 77 | node.style.left = "25px" 78 | node.style.bottom = (20 + height) + "px" 79 | this.robotElt.appendChild(node) 80 | } else { 81 | let pos = places[place] 82 | node.style.left = (pos.x - 5) + "px" 83 | node.style.top = (pos.y - 10 - height) + "px" 84 | this.node.appendChild(node) 85 | } 86 | this.parcels.push(node) 87 | } 88 | } 89 | 90 | tick() { 91 | let {direction, memory} = this.robot(this.worldState, this.robotState) 92 | this.worldState = this.worldState.move(direction) 93 | this.robotState = memory 94 | this.turn++ 95 | this.updateView() 96 | if (this.worldState.parcels.length == 0) { 97 | this.button.remove() 98 | this.text.textContent = ` Finished after ${this.turn} turns` 99 | this.robotElt.firstChild.src = "img/robot_idle2x.png" 100 | } else { 101 | this.schedule() 102 | } 103 | } 104 | 105 | schedule() { 106 | this.timeout = setTimeout(() => this.tick(), 1000 / speed) 107 | } 108 | 109 | clicked() { 110 | if (this.timeout == null) { 111 | this.schedule() 112 | this.button.textContent = "Stop" 113 | this.robotElt.firstChild.src = "img/robot_moving2x.gif" 114 | } else { 115 | clearTimeout(this.timeout) 116 | this.timeout = null 117 | this.button.textContent = "Start" 118 | this.robotElt.firstChild.src = "img/robot_idle2x.png" 119 | } 120 | } 121 | } 122 | 123 | window.runRobotAnimation = function(worldState, robot, robotState) { 124 | if (active && active.timeout != null) 125 | clearTimeout(active.timeout) 126 | active = new Animation(worldState, robot, robotState) 127 | } 128 | })() 129 | -------------------------------------------------------------------------------- /code/chapter/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/code/chapter/.keep -------------------------------------------------------------------------------- /code/chapter/04_data.js: -------------------------------------------------------------------------------- 1 | var journal = []; 2 | 3 | function addEntry(events, squirrel) { 4 | journal.push({events, squirrel}); 5 | } 6 | 7 | function phi(table) { 8 | return (table[3] * table[0] - table[2] * table[1]) / 9 | Math.sqrt((table[2] + table[3]) * 10 | (table[0] + table[1]) * 11 | (table[1] + table[3]) * 12 | (table[0] + table[2])); 13 | } 14 | 15 | function tableFor(event, journal) { 16 | let table = [0, 0, 0, 0]; 17 | for (let i = 0; i < journal.length; i++) { 18 | let entry = journal[i], index = 0; 19 | if (entry.events.includes(event)) index += 1; 20 | if (entry.squirrel) index += 2; 21 | table[index] += 1; 22 | } 23 | return table; 24 | } 25 | 26 | function journalEvents(journal) { 27 | let events = []; 28 | for (let entry of journal) { 29 | for (let event of entry.events) { 30 | if (!events.includes(event)) { 31 | events.push(event); 32 | } 33 | } 34 | } 35 | return events; 36 | } 37 | 38 | function max(...numbers) { 39 | let result = -Infinity; 40 | for (let number of numbers) { 41 | if (number > result) result = number; 42 | } 43 | return result; 44 | } 45 | 46 | var list = { 47 | value: 1, 48 | rest: { 49 | value: 2, 50 | rest: { 51 | value: 3, 52 | rest: null 53 | } 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /code/chapter/05_higher_order.js: -------------------------------------------------------------------------------- 1 | function repeat(n, action) { 2 | for (let i = 0; i < n; i++) { 3 | action(i); 4 | } 5 | } 6 | 7 | function characterScript(code) { 8 | for (let script of SCRIPTS) { 9 | if (script.ranges.some(([from, to]) => { 10 | return code >= from && code < to; 11 | })) { 12 | return script; 13 | } 14 | } 15 | return null; 16 | } 17 | 18 | function countBy(items, groupName) { 19 | let counts = []; 20 | for (let item of items) { 21 | let name = groupName(item); 22 | let known = counts.findIndex(c => c.name == name); 23 | if (known == -1) { 24 | counts.push({name, count: 1}); 25 | } else { 26 | counts[known].count++; 27 | } 28 | } 29 | return counts; 30 | } 31 | 32 | function textScripts(text) { 33 | let scripts = countBy(text, char => { 34 | let script = characterScript(char.codePointAt(0)); 35 | return script ? script.name : "none"; 36 | }).filter(({name}) => name != "none"); 37 | 38 | let total = scripts.reduce((n, {count}) => n + count, 0); 39 | if (total == 0) return "No scripts found"; 40 | 41 | return scripts.map(({name, count}) => { 42 | return `${Math.round(count * 100 / total)}% ${name}`; 43 | }).join(", "); 44 | } 45 | -------------------------------------------------------------------------------- /code/chapter/06_object.js: -------------------------------------------------------------------------------- 1 | function speak(line) { 2 | console.log(`The ${this.type} rabbit says '${line}'`); 3 | } 4 | var whiteRabbit = {type: "white", speak}; 5 | var hungryRabbit = {type: "hungry", speak}; 6 | 7 | 8 | var Rabbit = class Rabbit { 9 | constructor(type) { 10 | this.type = type; 11 | } 12 | speak(line) { 13 | console.log(`The ${this.type} rabbit says '${line}'`); 14 | } 15 | } 16 | 17 | var killerRabbit = new Rabbit("killer"); 18 | var blackRabbit = new Rabbit("black"); 19 | 20 | Rabbit.prototype.toString = function() { 21 | return `a ${this.type} rabbit`; 22 | }; 23 | 24 | var toStringSymbol = Symbol("toString"); 25 | 26 | var Matrix = class Matrix { 27 | constructor(width, height, element = (x, y) => undefined) { 28 | this.width = width; 29 | this.height = height; 30 | this.content = []; 31 | 32 | for (let y = 0; y < height; y++) { 33 | for (let x = 0; x < width; x++) { 34 | this.content[y * width + x] = element(x, y); 35 | } 36 | } 37 | } 38 | 39 | get(x, y) { 40 | return this.content[y * this.width + x]; 41 | } 42 | set(x, y, value) { 43 | this.content[y * this.width + x] = value; 44 | } 45 | } 46 | 47 | var MatrixIterator = class MatrixIterator { 48 | constructor(matrix) { 49 | this.x = 0; 50 | this.y = 0; 51 | this.matrix = matrix; 52 | } 53 | 54 | next() { 55 | if (this.y == this.matrix.height) return {done: true}; 56 | 57 | let value = {x: this.x, 58 | y: this.y, 59 | value: this.matrix.get(this.x, this.y)}; 60 | this.x++; 61 | if (this.x == this.matrix.width) { 62 | this.x = 0; 63 | this.y++; 64 | } 65 | return {value, done: false}; 66 | } 67 | } 68 | 69 | Matrix.prototype[Symbol.iterator] = function() { 70 | return new MatrixIterator(this); 71 | }; 72 | 73 | var SymmetricMatrix = class SymmetricMatrix extends Matrix { 74 | constructor(size, element = (x, y) => undefined) { 75 | super(size, size, (x, y) => { 76 | if (x < y) return element(y, x); 77 | else return element(x, y); 78 | }); 79 | } 80 | 81 | set(x, y, value) { 82 | super.set(x, y, value); 83 | if (x != y) { 84 | super.set(y, x, value); 85 | } 86 | } 87 | } 88 | 89 | var matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`); 90 | -------------------------------------------------------------------------------- /code/chapter/07_robot.js: -------------------------------------------------------------------------------- 1 | var roads = [ 2 | "Alice's House-Bob's House", "Alice's House-Cabin", 3 | "Alice's House-Post Office", "Bob's House-Town Hall", 4 | "Daria's House-Ernie's House", "Daria's House-Town Hall", 5 | "Ernie's House-Grete's House", "Grete's House-Farm", 6 | "Grete's House-Shop", "Marketplace-Farm", 7 | "Marketplace-Post Office", "Marketplace-Shop", 8 | "Marketplace-Town Hall", "Shop-Town Hall" 9 | ]; 10 | 11 | function buildGraph(edges) { 12 | let graph = Object.create(null); 13 | function addEdge(from, to) { 14 | if (graph[from] == null) { 15 | graph[from] = [to]; 16 | } else { 17 | graph[from].push(to); 18 | } 19 | } 20 | for (let [from, to] of edges.map(r => r.split("-"))) { 21 | addEdge(from, to); 22 | addEdge(to, from); 23 | } 24 | return graph; 25 | } 26 | 27 | var roadGraph = buildGraph(roads); 28 | 29 | var VillageState = class VillageState { 30 | constructor(place, parcels) { 31 | this.place = place; 32 | this.parcels = parcels; 33 | } 34 | 35 | move(destination) { 36 | if (!roadGraph[this.place].includes(destination)) { 37 | return this; 38 | } else { 39 | let parcels = this.parcels.map(p => { 40 | if (p.place != this.place) return p; 41 | return {place: destination, address: p.address}; 42 | }).filter(p => p.place != p.address); 43 | return new VillageState(destination, parcels); 44 | } 45 | } 46 | } 47 | 48 | function runRobot(state, robot, memory) { 49 | for (let turn = 0;; turn++) { 50 | if (state.parcels.length == 0) { 51 | console.log(`Done in ${turn} turns`); 52 | break; 53 | } 54 | let action = robot(state, memory); 55 | state = state.move(action.direction); 56 | memory = action.memory; 57 | console.log(`Moved to ${action.direction}`); 58 | } 59 | } 60 | 61 | function randomPick(array) { 62 | let choice = Math.floor(Math.random() * array.length); 63 | return array[choice]; 64 | } 65 | 66 | function randomRobot(state) { 67 | return {direction: randomPick(roadGraph[state.place])}; 68 | } 69 | 70 | VillageState.random = function(parcelCount = 5) { 71 | let parcels = []; 72 | for (let i = 0; i < parcelCount; i++) { 73 | let address = randomPick(Object.keys(roadGraph)); 74 | let place; 75 | do { 76 | place = randomPick(Object.keys(roadGraph)); 77 | } while (place == address); 78 | parcels.push({place, address}); 79 | } 80 | return new VillageState("Post Office", parcels); 81 | }; 82 | 83 | var mailRoute = [ 84 | "Alice's House", "Cabin", "Alice's House", "Bob's House", 85 | "Town Hall", "Daria's House", "Ernie's House", 86 | "Grete's House", "Shop", "Grete's House", "Farm", 87 | "Marketplace", "Post Office" 88 | ]; 89 | 90 | function routeRobot(state, memory) { 91 | if (memory.length == 0) { 92 | memory = mailRoute; 93 | } 94 | return {direction: memory[0], memory: memory.slice(1)}; 95 | } 96 | 97 | function findRoute(graph, from, to) { 98 | let work = [{at: from, route: []}]; 99 | for (let i = 0; i < work.length; i++) { 100 | let {at, route} = work[i]; 101 | for (let place of graph[at]) { 102 | if (place == to) return route.concat(place); 103 | if (!work.some(w => w.at == place)) { 104 | work.push({at: place, route: route.concat(place)}); 105 | } 106 | } 107 | } 108 | } 109 | 110 | function goalOrientedRobot({place, parcels}, route) { 111 | if (route.length == 0) { 112 | let parcel = parcels[0]; 113 | if (parcel.place != place) { 114 | route = findRoute(roadGraph, place, parcel.place); 115 | } else { 116 | route = findRoute(roadGraph, place, parcel.address); 117 | } 118 | } 119 | return {direction: route[0], memory: route.slice(1)}; 120 | } 121 | -------------------------------------------------------------------------------- /code/chapter/08_error.js: -------------------------------------------------------------------------------- 1 | var accounts = { 2 | a: 100, 3 | b: 0, 4 | c: 20 5 | }; 6 | 7 | function getAccount() { 8 | let accountName = prompt("Enter an account name"); 9 | if (!accounts.hasOwnProperty(accountName)) { 10 | throw new Error(`No such account: ${accountName}`); 11 | } 12 | return accountName; 13 | } 14 | 15 | function transfer(from, amount) { 16 | if (accounts[from] < amount) return; 17 | accounts[from] -= amount; 18 | accounts[getAccount()] += amount; 19 | } 20 | 21 | function transfer(from, amount) { 22 | if (accounts[from] < amount) return; 23 | let progress = 0; 24 | try { 25 | accounts[from] -= amount; 26 | progress = 1; 27 | accounts[getAccount()] += amount; 28 | progress = 2; 29 | } finally { 30 | if (progress == 1) { 31 | accounts[from] += amount; 32 | } 33 | } 34 | } 35 | 36 | var InputError = class InputError extends Error {} 37 | 38 | function promptDirection(question) { 39 | let result = prompt(question); 40 | if (result.toLowerCase() == "left") return "L"; 41 | if (result.toLowerCase() == "right") return "R"; 42 | throw new InputError("Invalid direction: " + result); 43 | } 44 | -------------------------------------------------------------------------------- /code/chapter/11_async.js: -------------------------------------------------------------------------------- 1 | var bigOak = require("./crow-tech").bigOak; 2 | 3 | var defineRequestType = require("./crow-tech").defineRequestType; 4 | 5 | defineRequestType("note", (nest, content, source, done) => { 6 | console.log(`${nest.name} received note: ${content}`); 7 | done(); 8 | }); 9 | 10 | function storage(nest, name) { 11 | return new Promise(resolve => { 12 | nest.readStorage(name, result => resolve(result)); 13 | }); 14 | } 15 | 16 | var Timeout = class Timeout extends Error {} 17 | 18 | function request(nest, target, type, content) { 19 | return new Promise((resolve, reject) => { 20 | let done = false; 21 | function attempt(n) { 22 | nest.send(target, type, content, (failed, value) => { 23 | done = true; 24 | if (failed) reject(failed); 25 | else resolve(value); 26 | }); 27 | setTimeout(() => { 28 | if (done) return; 29 | else if (n < 3) attempt(n + 1); 30 | else reject(new Timeout("Timed out")); 31 | }, 250); 32 | } 33 | attempt(1); 34 | }); 35 | } 36 | 37 | function requestType(name, handler) { 38 | defineRequestType(name, (nest, content, source, 39 | callback) => { 40 | try { 41 | Promise.resolve(handler(nest, content, source)) 42 | .then(response => callback(null, response), 43 | failure => callback(failure)); 44 | } catch (exception) { 45 | callback(exception); 46 | } 47 | }); 48 | } 49 | 50 | requestType("ping", () => "pong"); 51 | 52 | function availableNeighbors(nest) { 53 | let requests = nest.neighbors.map(neighbor => { 54 | return request(nest, neighbor, "ping") 55 | .then(() => true, () => false); 56 | }); 57 | return Promise.all(requests).then(result => { 58 | return nest.neighbors.filter((_, i) => result[i]); 59 | }); 60 | } 61 | 62 | var everywhere = require("./crow-tech").everywhere; 63 | 64 | everywhere(nest => { 65 | nest.state.gossip = []; 66 | }); 67 | 68 | function sendGossip(nest, message, exceptFor = null) { 69 | nest.state.gossip.push(message); 70 | for (let neighbor of nest.neighbors) { 71 | if (neighbor == exceptFor) continue; 72 | request(nest, neighbor, "gossip", message); 73 | } 74 | } 75 | 76 | requestType("gossip", (nest, message, source) => { 77 | if (nest.state.gossip.includes(message)) return; 78 | console.log(`${nest.name} received gossip '${ 79 | message}' from ${source}`); 80 | sendGossip(nest, message, source); 81 | }); 82 | 83 | requestType("connections", (nest, {name, neighbors}, 84 | source) => { 85 | let connections = nest.state.connections; 86 | if (JSON.stringify(connections.get(name)) == 87 | JSON.stringify(neighbors)) return; 88 | connections.set(name, neighbors); 89 | broadcastConnections(nest, name, source); 90 | }); 91 | 92 | function broadcastConnections(nest, name, exceptFor = null) { 93 | for (let neighbor of nest.neighbors) { 94 | if (neighbor == exceptFor) continue; 95 | request(nest, neighbor, "connections", { 96 | name, 97 | neighbors: nest.state.connections.get(name) 98 | }); 99 | } 100 | } 101 | 102 | everywhere(nest => { 103 | nest.state.connections = new Map; 104 | nest.state.connections.set(nest.name, nest.neighbors); 105 | broadcastConnections(nest, nest.name); 106 | }); 107 | 108 | function findRoute(from, to, connections) { 109 | let work = [{at: from, via: null}]; 110 | for (let i = 0; i < work.length; i++) { 111 | let {at, via} = work[i]; 112 | for (let next of connections.get(at) || []) { 113 | if (next == to) return via; 114 | if (!work.some(w => w.at == next)) { 115 | work.push({at: next, via: via || next}); 116 | } 117 | } 118 | } 119 | return null; 120 | } 121 | 122 | function routeRequest(nest, target, type, content) { 123 | if (nest.neighbors.includes(target)) { 124 | return request(nest, target, type, content); 125 | } else { 126 | let via = findRoute(nest.name, target, 127 | nest.state.connections); 128 | if (!via) throw new Error(`No route to ${target}`); 129 | return request(nest, via, "route", 130 | {target, type, content}); 131 | } 132 | } 133 | 134 | requestType("route", (nest, {target, type, content}) => { 135 | return routeRequest(nest, target, type, content); 136 | }); 137 | 138 | requestType("storage", (nest, name) => storage(nest, name)); 139 | 140 | function findInStorage(nest, name) { 141 | return storage(nest, name).then(found => { 142 | if (found != null) return found; 143 | else return findInRemoteStorage(nest, name); 144 | }); 145 | } 146 | 147 | function network(nest) { 148 | return Array.from(nest.state.connections.keys()); 149 | } 150 | 151 | function findInRemoteStorage(nest, name) { 152 | let sources = network(nest).filter(n => n != nest.name); 153 | function next() { 154 | if (sources.length == 0) { 155 | return Promise.reject(new Error("Not found")); 156 | } else { 157 | let source = sources[Math.floor(Math.random() * 158 | sources.length)]; 159 | sources = sources.filter(n => n != source); 160 | return routeRequest(nest, source, "storage", name) 161 | .then(value => value != null ? value : next(), 162 | next); 163 | } 164 | } 165 | return next(); 166 | } 167 | 168 | var Group = class Group { 169 | constructor() { this.members = []; } 170 | add(m) { this.members.add(m); } 171 | } 172 | 173 | function anyStorage(nest, source, name) { 174 | if (source == nest.name) return storage(nest, name); 175 | else return routeRequest(nest, source, "storage", name); 176 | } 177 | 178 | async function chicks(nest, year) { 179 | let list = ""; 180 | await Promise.all(network(nest).map(async name => { 181 | list += `${name}: ${ 182 | await anyStorage(nest, name, `chicks in ${year}`) 183 | }\n`; 184 | })); 185 | return list; 186 | } 187 | -------------------------------------------------------------------------------- /code/chapter/12_language.js: -------------------------------------------------------------------------------- 1 | function parseExpression(program) { 2 | program = skipSpace(program); 3 | let match, expr; 4 | if (match = /^"([^"]*)"/.exec(program)) { 5 | expr = {type: "value", value: match[1]}; 6 | } else if (match = /^\d+\b/.exec(program)) { 7 | expr = {type: "value", value: Number(match[0])}; 8 | } else if (match = /^[^\s(),#"]+/.exec(program)) { 9 | expr = {type: "word", name: match[0]}; 10 | } else { 11 | throw new SyntaxError("Unexpected syntax: " + program); 12 | } 13 | 14 | return parseApply(expr, program.slice(match[0].length)); 15 | } 16 | 17 | function skipSpace(string) { 18 | let first = string.search(/\S/); 19 | if (first == -1) return ""; 20 | return string.slice(first); 21 | } 22 | 23 | function parseApply(expr, program) { 24 | program = skipSpace(program); 25 | if (program[0] != "(") { 26 | return {expr: expr, rest: program}; 27 | } 28 | 29 | program = skipSpace(program.slice(1)); 30 | expr = {type: "apply", operator: expr, args: []}; 31 | while (program[0] != ")") { 32 | let arg = parseExpression(program); 33 | expr.args.push(arg.expr); 34 | program = skipSpace(arg.rest); 35 | if (program[0] == ",") { 36 | program = skipSpace(program.slice(1)); 37 | } else if (program[0] != ")") { 38 | throw new SyntaxError("Expected ',' or ')'"); 39 | } 40 | } 41 | return parseApply(expr, program.slice(1)); 42 | } 43 | 44 | function parse(program) { 45 | let {expr, rest} = parseExpression(program); 46 | if (skipSpace(rest).length > 0) { 47 | throw new SyntaxError("Unexpected text after program"); 48 | } 49 | return expr; 50 | } 51 | // operator: {type: "word", name: "+"}, 52 | // args: [{type: "word", name: "a"}, 53 | // {type: "value", value: 10}]} 54 | 55 | var specialForms = Object.create(null); 56 | 57 | function evaluate(expr, scope) { 58 | if (expr.type == "value") { 59 | return expr.value; 60 | } else if (expr.type == "word") { 61 | if (expr.name in scope) { 62 | return scope[expr.name]; 63 | } else { 64 | throw new ReferenceError( 65 | `Undefined binding: ${expr.name}`); 66 | } 67 | } else if (expr.type == "apply") { 68 | let {operator, args} = expr; 69 | if (operator.type == "word" && 70 | operator.name in specialForms) { 71 | return specialForms[operator.name](expr.args, scope); 72 | } else { 73 | let op = evaluate(operator, scope); 74 | if (typeof op == "function") { 75 | return op(...args.map(arg => evaluate(arg, scope))); 76 | } else { 77 | throw new TypeError("Applying a non-function."); 78 | } 79 | } 80 | } 81 | } 82 | 83 | specialForms.if = (args, scope) => { 84 | if (args.length != 3) { 85 | throw new SyntaxError("Wrong number of args to if"); 86 | } else if (evaluate(args[0], scope) !== false) { 87 | return evaluate(args[1], scope); 88 | } else { 89 | return evaluate(args[2], scope); 90 | } 91 | }; 92 | 93 | specialForms.while = (args, scope) => { 94 | if (args.length != 2) { 95 | throw new SyntaxError("Wrong number of args to while"); 96 | } 97 | while (evaluate(args[0], scope) !== false) { 98 | evaluate(args[1], scope); 99 | } 100 | 101 | // Since undefined does not exist in Egg, we return false, 102 | // for lack of a meaningful result. 103 | return false; 104 | }; 105 | 106 | specialForms.do = (args, scope) => { 107 | let value = false; 108 | for (let arg of args) { 109 | value = evaluate(arg, scope); 110 | } 111 | return value; 112 | }; 113 | 114 | specialForms.define = (args, scope) => { 115 | if (args.length != 2 || args[0].type != "word") { 116 | throw new SyntaxError("Incorrect use of define"); 117 | } 118 | let value = evaluate(args[1], scope); 119 | scope[args[0].name] = value; 120 | return value; 121 | }; 122 | 123 | var topScope = Object.create(null); 124 | 125 | topScope.true = true; 126 | topScope.false = false; 127 | 128 | for (let op of ["+", "-", "*", "/", "==", "<", ">"]) { 129 | topScope[op] = Function("a, b", `return a ${op} b;`); 130 | } 131 | 132 | topScope.print = value => { 133 | console.log(value); 134 | return value; 135 | }; 136 | 137 | function run(program) { 138 | return evaluate(parse(program), Object.create(topScope)); 139 | } 140 | 141 | specialForms.fun = (args, scope) => { 142 | if (!args.length) { 143 | throw new SyntaxError("Functions need a body"); 144 | } 145 | let body = args[args.length - 1]; 146 | let params = args.slice(0, args.length - 1).map(expr => { 147 | if (expr.type != "word") { 148 | throw new SyntaxError("Parameter names must be words"); 149 | } 150 | return expr.name; 151 | }); 152 | 153 | return function() { 154 | if (arguments.length != params.length) { 155 | throw new TypeError("Wrong number of arguments"); 156 | } 157 | let localScope = Object.create(scope); 158 | for (let i = 0; i < arguments.length; i++) { 159 | localScope[params[i]] = arguments[i]; 160 | } 161 | return evaluate(body, localScope); 162 | }; 163 | }; 164 | -------------------------------------------------------------------------------- /code/chapter/17_canvas.js: -------------------------------------------------------------------------------- 1 | var results = [ 2 | {name: "Satisfied", count: 1043, color: "lightblue"}, 3 | {name: "Neutral", count: 563, color: "lightgreen"}, 4 | {name: "Unsatisfied", count: 510, color: "pink"}, 5 | {name: "No comment", count: 175, color: "silver"} 6 | ]; 7 | 8 | function flipHorizontally(context, around) { 9 | context.translate(around, 0); 10 | context.scale(-1, 1); 11 | context.translate(-around, 0); 12 | } 13 | 14 | var CanvasDisplay = class CanvasDisplay { 15 | constructor(parent, level) { 16 | this.canvas = document.createElement("canvas"); 17 | this.canvas.width = Math.min(600, level.width * scale); 18 | this.canvas.height = Math.min(450, level.height * scale); 19 | parent.appendChild(this.canvas); 20 | this.cx = this.canvas.getContext("2d"); 21 | 22 | this.flipPlayer = false; 23 | 24 | this.viewport = { 25 | left: 0, 26 | top: 0, 27 | width: this.canvas.width / scale, 28 | height: this.canvas.height / scale 29 | }; 30 | } 31 | 32 | clear() { 33 | this.canvas.remove(); 34 | } 35 | } 36 | 37 | CanvasDisplay.prototype.syncState = function(state) { 38 | this.updateViewport(state); 39 | this.clearDisplay(state.status); 40 | this.drawBackground(state.level); 41 | this.drawActors(state.actors); 42 | }; 43 | 44 | CanvasDisplay.prototype.updateViewport = function(state) { 45 | let view = this.viewport, margin = view.width / 3; 46 | let player = state.player; 47 | let center = player.pos.plus(player.size.times(0.5)); 48 | 49 | if (center.x < view.left + margin) { 50 | view.left = Math.max(center.x - margin, 0); 51 | } else if (center.x > view.left + view.width - margin) { 52 | view.left = Math.min(center.x + margin - view.width, 53 | state.level.width - view.width); 54 | } 55 | if (center.y < view.top + margin) { 56 | view.top = Math.max(center.y - margin, 0); 57 | } else if (center.y > view.top + view.height - margin) { 58 | view.top = Math.min(center.y + margin - view.height, 59 | state.level.height - view.height); 60 | } 61 | }; 62 | 63 | CanvasDisplay.prototype.clearDisplay = function(status) { 64 | if (status == "won") { 65 | this.cx.fillStyle = "rgb(68, 191, 255)"; 66 | } else if (status == "lost") { 67 | this.cx.fillStyle = "rgb(44, 136, 214)"; 68 | } else { 69 | this.cx.fillStyle = "rgb(52, 166, 251)"; 70 | } 71 | this.cx.fillRect(0, 0, 72 | this.canvas.width, this.canvas.height); 73 | }; 74 | 75 | var otherSprites = document.createElement("img"); 76 | otherSprites.src = "img/sprites.png"; 77 | 78 | CanvasDisplay.prototype.drawBackground = function(level) { 79 | let {left, top, width, height} = this.viewport; 80 | let xStart = Math.floor(left); 81 | let xEnd = Math.ceil(left + width); 82 | let yStart = Math.floor(top); 83 | let yEnd = Math.ceil(top + height); 84 | 85 | for (let y = yStart; y < yEnd; y++) { 86 | for (let x = xStart; x < xEnd; x++) { 87 | let tile = level.rows[y][x]; 88 | if (tile == "empty") continue; 89 | let screenX = (x - left) * scale; 90 | let screenY = (y - top) * scale; 91 | let tileX = tile == "lava" ? scale : 0; 92 | this.cx.drawImage(otherSprites, 93 | tileX, 0, scale, scale, 94 | screenX, screenY, scale, scale); 95 | } 96 | } 97 | }; 98 | 99 | var playerSprites = document.createElement("img"); 100 | playerSprites.src = "img/player.png"; 101 | var playerXOverlap = 4; 102 | 103 | CanvasDisplay.prototype.drawPlayer = function(player, x, y, 104 | width, height){ 105 | width += playerXOverlap * 2; 106 | x -= playerXOverlap; 107 | if (player.speed.x != 0) { 108 | this.flipPlayer = player.speed.x < 0; 109 | } 110 | 111 | let tile = 8; 112 | if (player.speed.y != 0) { 113 | tile = 9; 114 | } else if (player.speed.x != 0) { 115 | tile = Math.floor(Date.now() / 60) % 8; 116 | } 117 | 118 | this.cx.save(); 119 | if (this.flipPlayer) { 120 | flipHorizontally(this.cx, x + width / 2); 121 | } 122 | let tileX = tile * width; 123 | this.cx.drawImage(playerSprites, tileX, 0, width, height, 124 | x, y, width, height); 125 | this.cx.restore(); 126 | }; 127 | 128 | CanvasDisplay.prototype.drawActors = function(actors) { 129 | for (let actor of actors) { 130 | let width = actor.size.x * scale; 131 | let height = actor.size.y * scale; 132 | let x = (actor.pos.x - this.viewport.left) * scale; 133 | let y = (actor.pos.y - this.viewport.top) * scale; 134 | if (actor.type == "player") { 135 | this.drawPlayer(actor, x, y, width, height); 136 | } else { 137 | let tileX = (actor.type == "coin" ? 2 : 1) * scale; 138 | this.cx.drawImage(otherSprites, 139 | tileX, 0, width, height, 140 | x, y, width, height); 141 | } 142 | } 143 | }; 144 | -------------------------------------------------------------------------------- /code/chapter/22_fast.js: -------------------------------------------------------------------------------- 1 | var GraphNode = class GraphNode { 2 | constructor() { 3 | this.pos = new Vec(Math.random() * 1000, 4 | Math.random() * 1000); 5 | this.edges = []; 6 | } 7 | connect(other) { 8 | this.edges.push(other); 9 | other.edges.push(this); 10 | } 11 | hasEdge(other) { 12 | return this.edges.includes(other); 13 | } 14 | } 15 | 16 | function treeGraph(depth, branches) { 17 | let graph = [new GraphNode()]; 18 | if (depth > 1) { 19 | for (let i = 0; i < branches; i++) { 20 | let subGraph = treeGraph(depth - 1, branches); 21 | graph[0].connect(subGraph[0]); 22 | graph = graph.concat(subGraph); 23 | } 24 | } 25 | return graph; 26 | } 27 | 28 | var springLength = 40; 29 | var springStrength = 0.1; 30 | 31 | var repulsionStrength = 1500; 32 | 33 | function forceDirected_simple(graph) { 34 | for (let node of graph) { 35 | for (let other of graph) { 36 | if (other == node) continue; 37 | let apart = other.pos.minus(node.pos); 38 | let distance = Math.max(1, apart.length); 39 | let forceSize = -repulsionStrength / (distance * distance); 40 | if (node.hasEdge(other)) { 41 | forceSize += (distance - springLength) * springStrength; 42 | } 43 | let normalized = apart.times(1 / distance); 44 | node.pos = node.pos.plus(normalized.times(forceSize)); 45 | } 46 | } 47 | } 48 | 49 | function runLayout(implementation, graph) { 50 | function run(steps, time) { 51 | let startTime = Date.now(); 52 | for (let i = 0; i < 100; i++) { 53 | implementation(graph); 54 | } 55 | time += Date.now() - startTime; 56 | drawGraph(graph); 57 | 58 | if (steps == 0) console.log(time); 59 | else requestAnimationFrame(() => run(steps - 100, time)); 60 | } 61 | run(4000, 0); 62 | } 63 | 64 | function forceDirected_noRepeat(graph) { 65 | for (let i = 0; i < graph.length; i++) { 66 | let node = graph[i]; 67 | for (let j = i + 1; j < graph.length; j++) { 68 | let other = graph[j]; 69 | let apart = other.pos.minus(node.pos); 70 | let distance = Math.max(1, apart.length); 71 | let forceSize = -repulsionStrength / (distance * distance); 72 | if (node.hasEdge(other)) { 73 | forceSize += (distance - springLength) * springStrength; 74 | } 75 | let applied = apart.times(forceSize / distance); 76 | node.pos = node.pos.plus(applied); 77 | other.pos = other.pos.minus(applied); 78 | } 79 | } 80 | } 81 | 82 | var skipDistance = 175; 83 | 84 | function forceDirected_skip(graph) { 85 | for (let i = 0; i < graph.length; i++) { 86 | let node = graph[i]; 87 | for (let j = i + 1; j < graph.length; j++) { 88 | let other = graph[j]; 89 | let apart = other.pos.minus(node.pos); 90 | let distance = Math.max(1, apart.length); 91 | let hasEdge = node.hasEdge(other); 92 | if (!hasEdge && distance > skipDistance) continue; 93 | let forceSize = -repulsionStrength / (distance * distance); 94 | if (hasEdge) { 95 | forceSize += (distance - springLength) * springStrength; 96 | } 97 | let applied = apart.times(forceSize / distance); 98 | node.pos = node.pos.plus(applied); 99 | other.pos = other.pos.minus(applied); 100 | } 101 | } 102 | } 103 | 104 | GraphNode.prototype.hasEdgeFast = function(other) { 105 | for (let i = 0; i < this.edges.length; i++) { 106 | if (this.edges[i] === other) return true; 107 | } 108 | return false; 109 | }; 110 | 111 | function forceDirected_hasEdgeFast(graph) { 112 | for (let i = 0; i < graph.length; i++) { 113 | let node = graph[i]; 114 | for (let j = i + 1; j < graph.length; j++) { 115 | let other = graph[j]; 116 | let apart = other.pos.minus(node.pos); 117 | let distance = Math.max(1, apart.length); 118 | let hasEdge = node.hasEdgeFast(other); 119 | if (!hasEdge && distance > skipDistance) continue; 120 | let forceSize = -repulsionStrength / (distance * distance); 121 | if (hasEdge) { 122 | forceSize += (distance - springLength) * springStrength; 123 | } 124 | let applied = apart.times(forceSize / distance); 125 | node.pos = node.pos.plus(applied); 126 | other.pos = other.pos.minus(applied); 127 | } 128 | } 129 | } 130 | 131 | function forceDirected_noVector(graph) { 132 | for (let i = 0; i < graph.length; i++) { 133 | let node = graph[i]; 134 | for (let j = i + 1; j < graph.length; j++) { 135 | let other = graph[j]; 136 | let apartX = other.pos.x - node.pos.x; 137 | let apartY = other.pos.y - node.pos.y; 138 | let distance = Math.max(1, Math.sqrt(apartX * apartX + apartY * apartY)); 139 | let hasEdge = node.hasEdgeFast(other); 140 | if (!hasEdge && distance > skipDistance) continue; 141 | let forceSize = -repulsionStrength / (distance * distance); 142 | if (hasEdge) { 143 | forceSize += (distance - springLength) * springStrength; 144 | } 145 | let forceX = apartX * forceSize / distance; 146 | let forceY = apartY * forceSize / distance; 147 | node.pos.x += forceX; node.pos.y += forceY; 148 | other.pos.x -= forceX; other.pos.y -= forceY; 149 | } 150 | } 151 | } 152 | 153 | var mangledGraph = treeGraph(4, 4); 154 | for (let node of mangledGraph) { 155 | node[`p${Math.floor(Math.random() * 999)}`] = true; 156 | } 157 | -------------------------------------------------------------------------------- /code/crow-tech.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const connections = [ 3 | "Church Tower-Sportsgrounds", "Church Tower-Big Maple", "Big Maple-Sportsgrounds", 4 | "Big Maple-Woods", "Big Maple-Fabienne's Garden", "Fabienne's Garden-Woods", 5 | "Fabienne's Garden-Cow Pasture", "Cow Pasture-Big Oak", "Big Oak-Butcher Shop", 6 | "Butcher Shop-Tall Poplar", "Tall Poplar-Sportsgrounds", "Tall Poplar-Chateau", 7 | "Chateau-Great Pine", "Great Pine-Jacques' Farm", "Jacques' Farm-Hawthorn", 8 | "Great Pine-Hawthorn", "Hawthorn-Gilles' Garden", "Great Pine-Gilles' Garden", 9 | "Gilles' Garden-Big Oak", "Gilles' Garden-Butcher Shop", "Chateau-Butcher Shop" 10 | ] 11 | 12 | function storageFor(name) { 13 | let storage = Object.create(null) 14 | storage["food caches"] = ["cache in the oak", "cache in the meadow", "cache under the hedge"] 15 | storage["cache in the oak"] = "A hollow above the third big branch from the bottom. Several pieces of bread and a pile of acorns." 16 | storage["cache in the meadow"] = "Buried below the patch of nettles (south side). A dead snake." 17 | storage["cache under the hedge"] = "Middle of the hedge at Gilles' garden. Marked with a forked twig. Two bottles of beer." 18 | storage["enemies"] = ["Farmer Jacques' dog", "The butcher", "That one-legged jackdaw", "The boy with the airgun"] 19 | if (name == "Church Tower" || name == "Hawthorn" || name == "Chateau") 20 | storage["events on 2017-12-21"] = "Deep snow. Butcher's garbage can fell over. We chased off the ravens from Saint-Vulbas." 21 | let hash = 0 22 | for (let i = 0; i < name.length; i++) hash += name.charCodeAt(i) 23 | for (let y = 1985; y <= 2018; y++) { 24 | storage[`chicks in ${y}`] = hash % 6 25 | hash = Math.abs((hash << 2) ^ (hash + y)) 26 | } 27 | if (name == "Big Oak") storage.scalpel = "Gilles' Garden" 28 | else if (name == "Gilles' Garden") storage.scalpel = "Woods" 29 | else if (name == "Woods") storage.scalpel = "Chateau" 30 | else if (name == "Chateau" || name == "Butcher Shop") storage.scalpel = "Butcher Shop" 31 | else storage.scalpel = "Big Oak" 32 | for (let prop of Object.keys(storage)) storage[prop] = JSON.stringify(storage[prop]) 33 | return storage 34 | } 35 | 36 | class Network { 37 | constructor(connections, storageFor) { 38 | let reachable = Object.create(null) 39 | for (let [from, to] of connections.map(conn => conn.split("-"))) { 40 | ;(reachable[from] || (reachable[from] = [])).push(to) 41 | ;(reachable[to] || (reachable[to] = [])).push(from) 42 | } 43 | this.nodes = Object.create(null) 44 | for (let name of Object.keys(reachable)) 45 | this.nodes[name] = new Node(name, reachable[name], this, storageFor(name)) 46 | this.types = Object.create(null) 47 | } 48 | 49 | defineRequestType(name, handler) { 50 | this.types[name] = handler 51 | } 52 | 53 | everywhere(f) { 54 | for (let node of Object.values(this.nodes)) f(node) 55 | } 56 | } 57 | 58 | const $storage = Symbol("storage"), $network = Symbol("network") 59 | 60 | function ser(value) { 61 | return value == null ? null : JSON.parse(JSON.stringify(value)) 62 | } 63 | 64 | class Node { 65 | constructor(name, neighbors, network, storage) { 66 | this.name = name 67 | this.neighbors = neighbors 68 | this[$network] = network 69 | this.state = Object.create(null) 70 | this[$storage] = storage 71 | } 72 | 73 | send(to, type, message, callback) { 74 | let toNode = this[$network].nodes[to] 75 | if (!toNode || !this.neighbors.includes(to)) 76 | return callback(new Error(`${to} is not reachable from ${this.name}`)) 77 | let handler = this[$network].types[type] 78 | if (!handler) 79 | return callback(new Error("Unknown request type " + type)) 80 | if (Math.random() > 0.03) setTimeout(() => { 81 | try { 82 | handler(toNode, ser(message), this.name, (error, response) => { 83 | setTimeout(() => callback(error, ser(response)), 10) 84 | }) 85 | } catch(e) { 86 | callback(e) 87 | } 88 | }, 10 * Math.floor(Math.random() * 10)) 89 | } 90 | 91 | readStorage(name, callback) { 92 | let value = this[$storage][name] 93 | setTimeout(() => callback(value && JSON.parse(value)), 20) 94 | } 95 | 96 | writeStorage(name, value, callback) { 97 | setTimeout(() => { 98 | this[$storage][name] = JSON.stringify(value) 99 | callback() 100 | }, 20) 101 | } 102 | } 103 | 104 | let network = new Network(connections, storageFor) 105 | exports.bigOak = network.nodes["Big Oak"] 106 | exports.everywhere = network.everywhere.bind(network) 107 | exports.defineRequestType = network.defineRequestType.bind(network) 108 | 109 | if (typeof __sandbox != "undefined") { 110 | __sandbox.handleDeps = false 111 | __sandbox.notify.onLoad = () => { 112 | // Kludge to make sure some functions are delayed until the 113 | // nodes have been running for 500ms, to give them a chance to 114 | // propagate network information. 115 | let waitFor = Date.now() + 500 116 | function wrapWaiting(f) { 117 | return function(...args) { 118 | let wait = waitFor - Date.now() 119 | if (wait <= 0) return f(...args) 120 | return new Promise(ok => setTimeout(ok, wait)).then(() => f(...args)) 121 | } 122 | } 123 | for (let n of ["routeRequest", "findInStorage", "chicks"]) 124 | window[n] = wrapWaiting(window[n]) 125 | } 126 | } 127 | 128 | if (typeof window != "undefined") { 129 | window.require = name => { 130 | if (name != "./crow-tech") throw new Error("Crow nests can only require \"./crow-tech\"") 131 | return exports 132 | } 133 | } else if (typeof module != "undefined" && module.exports) { 134 | module.exports = exports 135 | } 136 | })() 137 | -------------------------------------------------------------------------------- /code/draw_layout.js: -------------------------------------------------------------------------------- 1 | // The familiar Vec type. 2 | 3 | class Vec { 4 | constructor(x, y) { 5 | this.x = x; this.y = y; 6 | } 7 | plus(other) { 8 | return new Vec(this.x + other.x, this.y + other.y); 9 | } 10 | minus(other) { 11 | return new Vec(this.x - other.x, this.y - other.y); 12 | } 13 | times(factor) { 14 | return new Vec(this.x * factor, this.y * factor); 15 | } 16 | get length() { 17 | return Math.sqrt(this.x * this.x + this.y * this.y); 18 | } 19 | } 20 | 21 | // Since we will want to inspect the layouts our code produces, let's 22 | // first write code to draw a graph onto a canvas. Since we don't know 23 | // in advance how big the graph is, the `Scale` object computes a 24 | // scale and offset so that all nodes fit onto the given canvas. 25 | 26 | const nodeSize = 8; 27 | 28 | function drawGraph(graph) { 29 | let canvas = document.querySelector("canvas"); 30 | if (!canvas) { 31 | canvas = document.body.appendChild(document.createElement("canvas")); 32 | canvas.width = canvas.height = 400; 33 | } 34 | let cx = canvas.getContext("2d"); 35 | 36 | cx.clearRect(0, 0, canvas.width, canvas.height); 37 | let scale = new Scale(graph, canvas.width, canvas.height); 38 | 39 | // Draw the edges. 40 | cx.strokeStyle = "orange"; 41 | cx.lineWidth = 3; 42 | for (let i = 0; i < graph.length; i++) { 43 | let origin = graph[i]; 44 | for (let target of origin.edges) { 45 | if (graph.indexOf(target) <= i) continue; 46 | cx.beginPath(); 47 | cx.moveTo(scale.x(origin.pos.x), scale.y(origin.pos.y)); 48 | cx.lineTo(scale.x(target.pos.x), scale.y(target.pos.y)); 49 | cx.stroke(); 50 | } 51 | } 52 | 53 | // Draw the nodes. 54 | cx.fillStyle = "purple"; 55 | for (let node of graph) { 56 | cx.beginPath(); 57 | cx.arc(scale.x(node.pos.x), scale.y(node.pos.y), nodeSize, 0, 7); 58 | cx.fill(); 59 | } 60 | } 61 | 62 | // The function starts by drawing the edges, so that they appear 63 | // behind the nodes. Since the nodes on _both_ side of an edge refer 64 | // to each other, and we don't want to draw every edge twice, edges 65 | // are only drawn then the target comes _after_ the current node in 66 | // the `graph` array. 67 | 68 | // When the edges have been drawn, the nodes are drawn on top of them 69 | // as purple discs. Remember that the last argument to `arc` gives the 70 | // rotation, and we have to pass something bigger than 2π to get a 71 | // full circle. 72 | 73 | // Finding a scale at which to draw the graph is done by finding the 74 | // top left and bottom right corners of the area taken up by the 75 | // nodes. The offset at which nodes are drawn is based on the top left 76 | // corner, and the scale is based on the size of the canvas divided by 77 | // the distance between those corners. The function reserves space 78 | // along the sides of the canvas based on the `nodeSize` variable, so 79 | // that the circles drawn around nodes’ center points don't get cut off. 80 | 81 | class Scale { 82 | constructor(graph, width, height) { 83 | let xs = graph.map(node => node.pos.x); 84 | let ys = graph.map(node => node.pos.y); 85 | let minX = Math.min(...xs); 86 | let minY = Math.min(...ys); 87 | let maxX = Math.max(...xs); 88 | let maxY = Math.max(...ys); 89 | 90 | this.offsetX = minX; this.offsetY = minY; 91 | this.scaleX = (width - 2 * nodeSize) / (maxX - minX); 92 | this.scaleY = (height - 2 * nodeSize) / (maxY - minY); 93 | } 94 | 95 | // The `x` and `y` methods convert from graph coordinates into 96 | // canvas coordinates. 97 | x(x) { 98 | return this.scaleX * (x - this.offsetX) + nodeSize; 99 | } 100 | y(y) { 101 | return this.scaleY * (y - this.offsetY) + nodeSize; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /code/file_server.js: -------------------------------------------------------------------------------- 1 | const {createServer} = require("http"); 2 | 3 | const methods = Object.create(null); 4 | 5 | createServer((request, response) => { 6 | let handler = methods[request.method] || notAllowed; 7 | handler(request) 8 | .catch(error => { 9 | if (error.status != null) return error; 10 | return {body: String(error), status: 500}; 11 | }) 12 | .then(({body, status = 200, type = "text/plain"}) => { 13 | response.writeHead(status, {"Content-Type": type}); 14 | if (body && body.pipe) body.pipe(response); 15 | else response.end(body); 16 | }); 17 | }).listen(8000); 18 | 19 | async function notAllowed(request) { 20 | return { 21 | status: 405, 22 | body: `Method ${request.method} not allowed.` 23 | }; 24 | } 25 | 26 | var {parse} = require("url"); 27 | var {resolve, sep} = require("path"); 28 | 29 | var baseDirectory = process.cwd(); 30 | 31 | function urlPath(url) { 32 | let {pathname} = parse(url); 33 | let path = resolve(decodeURIComponent(pathname).slice(1)); 34 | if (path != baseDirectory && 35 | !path.startsWith(baseDirectory + sep)) { 36 | throw {status: 403, body: "Forbidden"}; 37 | } 38 | return path; 39 | } 40 | 41 | const {createReadStream} = require("fs"); 42 | const {stat, readdir} = require("fs").promises; 43 | const mime = require("mime"); 44 | 45 | methods.GET = async function(request) { 46 | let path = urlPath(request.url); 47 | let stats; 48 | try { 49 | stats = await stat(path); 50 | } catch (error) { 51 | if (error.code != "ENOENT") throw error; 52 | else return {status: 404, body: "File not found"}; 53 | } 54 | if (stats.isDirectory()) { 55 | return {body: (await readdir(path)).join("\n")}; 56 | } else { 57 | return {body: createReadStream(path), 58 | type: mime.getType(path)}; 59 | } 60 | }; 61 | 62 | const {rmdir, unlink} = require("fs").promises; 63 | 64 | methods.DELETE = async function(request) { 65 | let path = urlPath(request.url); 66 | let stats; 67 | try { 68 | stats = await stat(path); 69 | } catch (error) { 70 | if (error.code != "ENOENT") throw error; 71 | else return {status: 204}; 72 | } 73 | if (stats.isDirectory()) await rmdir(path); 74 | else await unlink(path); 75 | return {status: 204}; 76 | }; 77 | 78 | const {createWriteStream} = require("fs"); 79 | 80 | function pipeStream(from, to) { 81 | return new Promise((resolve, reject) => { 82 | from.on("error", reject); 83 | to.on("error", reject); 84 | to.on("finish", resolve); 85 | from.pipe(to); 86 | }); 87 | } 88 | 89 | methods.PUT = async function(request) { 90 | let path = urlPath(request.url); 91 | await pipeStream(request, createWriteStream(path)); 92 | return {status: 204}; 93 | }; 94 | 95 | const {mkdir} = require("fs").promises; 96 | 97 | methods.MKCOL = async function(request) { 98 | let path = urlPath(request.url); 99 | let stats; 100 | try { 101 | stats = await stat(path); 102 | } catch (error) { 103 | if (error.code != "ENOENT") throw error; 104 | await mkdir(path); 105 | return {status: 204}; 106 | } 107 | if (stats.isDirectory()) return {status: 204}; 108 | else return {status: 400, body: "Not a directory"}; 109 | }; 110 | -------------------------------------------------------------------------------- /code/hello.js: -------------------------------------------------------------------------------- 1 | alert("hello!"); 2 | -------------------------------------------------------------------------------- /code/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Eloquent JavaScript :: Code Sandbox 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 |
21 | Note: If you are reading the second edition of the book, 22 | you'll want to go 23 | to that 24 | edition's sandbox instead! 25 |
26 | 27 |
28 |

Code Sandbox
Eloquent JavaScript

29 | 30 |

You can use this page to download source code and solutions to 31 | exercises for the book Eloquent JavaScript, and to directly run code 32 | in the context of chapters from that book, either to solve exercises 33 | to simply play around.

34 | 35 |

36 | Chapter: 37 | 38 | 39 |

40 | 41 |
42 | 43 |
44 |
45 | 46 |
47 | To run this chapter's code locally, use these files: 48 | 49 |
50 | 51 |
52 | These files contain this chapter’s project code: 53 | 54 |
55 | 56 |

If you've solved the exercise and want to compare your code with 57 | mine, or you really tried, but can't get your code to work, 58 | you can (or download it).

60 | 61 |

62 | The base environment for this chapter (if any) is available in the 63 | sandbox above, allowing you to run the chapter's examples by 64 | simply pasting them into the editor. 65 |

66 |
67 | -------------------------------------------------------------------------------- /code/intro.js: -------------------------------------------------------------------------------- 1 | function range(start, end, step) { 2 | if (step == null) step = 1; 3 | var array = []; 4 | 5 | if (step > 0) { 6 | for (var i = start; i <= end; i += step) 7 | array.push(i); 8 | } else { 9 | for (var i = start; i >= end; i += step) 10 | array.push(i); 11 | } 12 | return array; 13 | } 14 | 15 | function sum(array) { 16 | var total = 0; 17 | for (var i = 0; i < array.length; i++) 18 | total += array[i]; 19 | return total; 20 | } 21 | 22 | function factorial(n) { 23 | if (n == 0) { 24 | return 1; 25 | } else { 26 | return factorial(n - 1) * n; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/load.js: -------------------------------------------------------------------------------- 1 | // Since the code for most chapter in Eloquent JavaScript isn't 2 | // written with node's module system in mind, this kludge is used to 3 | // load dependency files into the global namespace, so that the 4 | // examples can run on node. 5 | 6 | module.exports = function(...args) { 7 | for (let arg of args) 8 | (1,eval)(require("fs").readFileSync(__dirname + "/../" + arg, "utf8")) 9 | } 10 | -------------------------------------------------------------------------------- /code/skillsharing.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/code/skillsharing.zip -------------------------------------------------------------------------------- /code/skillsharing/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/code/skillsharing/.keep -------------------------------------------------------------------------------- /code/skillsharing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ejs-skillsharing", 3 | "version": "1.0.0", 4 | "main": "skillsharing_server.js", 5 | "description": "Skill-sharing website example from Eloquent JavaScript", 6 | "dependencies": { 7 | "ecstatic": "^3.1.0" 8 | }, 9 | "licenses": [ 10 | { 11 | "type": "MIT", 12 | "url": "https://opensource.org/licenses/MIT" 13 | } 14 | ], 15 | "bugs": "https://github.com/marijnh/Eloquent-JavaScript/issues", 16 | "homepage": "https://eloquentjavascript.net/21_skillsharing.html", 17 | "maintainers": [ 18 | { 19 | "name": "Marijn Haverbeke", 20 | "email": "marijnh@gmail.com", 21 | "web": "https://marijnhaverbeke.nl/" 22 | } 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/marijnh/Eloquent-JavaScript.git" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/skillsharing/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Skill Sharing 4 | 5 | 6 |

Skill Sharing

7 | 8 | 9 | -------------------------------------------------------------------------------- /code/skillsharing/public/skillsharing.css: -------------------------------------------------------------------------------- 1 | .talk { margin: 40px 0; } 2 | 3 | .comment { font-style: italic; margin: 0; } 4 | .comment strong { font-style: normal; } 5 | 6 | .talk h2 { font-size: 130%; margin-bottom: 0; } 7 | .talk h2 button { vertical-align: bottom; } 8 | 9 | h1, h3 { margin-bottom: 0.33em; } 10 | 11 | label input { display: block; width: 30em; } 12 | -------------------------------------------------------------------------------- /code/skillsharing/public/skillsharing_client.js: -------------------------------------------------------------------------------- 1 | function handleAction(state, action) { 2 | if (action.type == "setUser") { 3 | localStorage.setItem("userName", action.user); 4 | return Object.assign({}, state, {user: action.user}); 5 | } else if (action.type == "setTalks") { 6 | return Object.assign({}, state, {talks: action.talks}); 7 | } else if (action.type == "newTalk") { 8 | fetchOK(talkURL(action.title), { 9 | method: "PUT", 10 | headers: {"Content-Type": "application/json"}, 11 | body: JSON.stringify({ 12 | presenter: state.user, 13 | summary: action.summary 14 | }) 15 | }).catch(reportError); 16 | } else if (action.type == "deleteTalk") { 17 | fetchOK(talkURL(action.talk), {method: "DELETE"}) 18 | .catch(reportError); 19 | } else if (action.type == "newComment") { 20 | fetchOK(talkURL(action.talk) + "/comments", { 21 | method: "POST", 22 | headers: {"Content-Type": "application/json"}, 23 | body: JSON.stringify({ 24 | author: state.user, 25 | message: action.message 26 | }) 27 | }).catch(reportError); 28 | } 29 | return state; 30 | } 31 | 32 | function fetchOK(url, options) { 33 | return fetch(url, options).then(response => { 34 | if (response.status < 400) return response; 35 | else throw new Error(response.statusText); 36 | }); 37 | } 38 | 39 | function talkURL(title) { 40 | return "talks/" + encodeURIComponent(title); 41 | } 42 | 43 | function reportError(error) { 44 | alert(String(error)); 45 | } 46 | 47 | function renderUserField(name, dispatch) { 48 | return elt("label", {}, "Your name: ", elt("input", { 49 | type: "text", 50 | value: name, 51 | onchange(event) { 52 | dispatch({type: "setUser", user: event.target.value}); 53 | } 54 | })); 55 | } 56 | 57 | function elt(type, props, ...children) { 58 | let dom = document.createElement(type); 59 | if (props) Object.assign(dom, props); 60 | for (let child of children) { 61 | if (typeof child != "string") dom.appendChild(child); 62 | else dom.appendChild(document.createTextNode(child)); 63 | } 64 | return dom; 65 | } 66 | 67 | function renderTalk(talk, dispatch) { 68 | return elt( 69 | "section", {className: "talk"}, 70 | elt("h2", null, talk.title, " ", elt("button", { 71 | type: "button", 72 | onclick() { 73 | dispatch({type: "deleteTalk", talk: talk.title}); 74 | } 75 | }, "Delete")), 76 | elt("div", null, "by ", 77 | elt("strong", null, talk.presenter)), 78 | elt("p", null, talk.summary), 79 | ...talk.comments.map(renderComment), 80 | elt("form", { 81 | onsubmit(event) { 82 | event.preventDefault(); 83 | let form = event.target; 84 | dispatch({type: "newComment", 85 | talk: talk.title, 86 | message: form.elements.comment.value}); 87 | form.reset(); 88 | } 89 | }, elt("input", {type: "text", name: "comment"}), " ", 90 | elt("button", {type: "submit"}, "Add comment"))); 91 | } 92 | 93 | function renderComment(comment) { 94 | return elt("p", {className: "comment"}, 95 | elt("strong", null, comment.author), 96 | ": ", comment.message); 97 | } 98 | 99 | function renderTalkForm(dispatch) { 100 | let title = elt("input", {type: "text"}); 101 | let summary = elt("input", {type: "text"}); 102 | return elt("form", { 103 | onsubmit(event) { 104 | event.preventDefault(); 105 | dispatch({type: "newTalk", 106 | title: title.value, 107 | summary: summary.value}); 108 | event.target.reset(); 109 | } 110 | }, elt("h3", null, "Submit a Talk"), 111 | elt("label", null, "Title: ", title), 112 | elt("label", null, "Summary: ", summary), 113 | elt("button", {type: "submit"}, "Submit")); 114 | } 115 | 116 | async function pollTalks(update) { 117 | let tag = undefined; 118 | for (;;) { 119 | let response; 120 | try { 121 | response = await fetchOK("/talks", { 122 | headers: tag && {"If-None-Match": tag, 123 | "Prefer": "wait=90"} 124 | }); 125 | } catch (e) { 126 | console.log("Request failed: " + e); 127 | await new Promise(resolve => setTimeout(resolve, 500)); 128 | continue; 129 | } 130 | if (response.status == 304) continue; 131 | tag = response.headers.get("ETag"); 132 | update(await response.json()); 133 | } 134 | } 135 | 136 | var SkillShareApp = class SkillShareApp { 137 | constructor(state, dispatch) { 138 | this.dispatch = dispatch; 139 | this.talkDOM = elt("div", {className: "talks"}); 140 | this.dom = elt("div", null, 141 | renderUserField(state.user, dispatch), 142 | this.talkDOM, 143 | renderTalkForm(dispatch)); 144 | this.syncState(state); 145 | } 146 | 147 | syncState(state) { 148 | if (state.talks != this.talks) { 149 | this.talkDOM.textContent = ""; 150 | for (let talk of state.talks) { 151 | this.talkDOM.appendChild( 152 | renderTalk(talk, this.dispatch)); 153 | } 154 | this.talks = state.talks; 155 | } 156 | } 157 | } 158 | 159 | function runApp() { 160 | let user = localStorage.getItem("userName") || "Anon"; 161 | let state, app; 162 | function dispatch(action) { 163 | state = handleAction(state, action); 164 | app.syncState(state); 165 | } 166 | 167 | pollTalks(talks => { 168 | if (!app) { 169 | state = {user, talks}; 170 | app = new SkillShareApp(state, dispatch); 171 | document.body.appendChild(app.dom); 172 | } else { 173 | dispatch({type: "setTalks", talks}); 174 | } 175 | }).catch(reportError); 176 | } 177 | 178 | runApp(); 179 | -------------------------------------------------------------------------------- /code/skillsharing/router.js: -------------------------------------------------------------------------------- 1 | var {parse} = require("url"); 2 | 3 | module.exports = class Router { 4 | constructor() { 5 | this.routes = []; 6 | } 7 | add(method, url, handler) { 8 | this.routes.push({method, url, handler}); 9 | } 10 | resolve(context, request) { 11 | let path = parse(request.url).pathname; 12 | 13 | for (let {method, url, handler} of this.routes) { 14 | let match = url.exec(path); 15 | if (!match || request.method != method) continue; 16 | let urlParts = match.slice(1).map(decodeURIComponent); 17 | return handler(context, ...urlParts, request); 18 | } 19 | return null; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /code/skillsharing/skillsharing_server.js: -------------------------------------------------------------------------------- 1 | var {createServer} = require("http"); 2 | var Router = require("./router"); 3 | var ecstatic = require("ecstatic"); 4 | 5 | var router = new Router(); 6 | var defaultHeaders = {"Content-Type": "text/plain"}; 7 | 8 | var SkillShareServer = class SkillShareServer { 9 | constructor(talks) { 10 | this.talks = talks; 11 | this.version = 0; 12 | this.waiting = []; 13 | 14 | let fileServer = ecstatic({root: "./public"}); 15 | this.server = createServer((request, response) => { 16 | let resolved = router.resolve(this, request); 17 | if (resolved) { 18 | resolved.catch(error => { 19 | if (error.status != null) return error; 20 | return {body: String(error), status: 500}; 21 | }).then(({body, 22 | status = 200, 23 | headers = defaultHeaders}) => { 24 | response.writeHead(status, headers); 25 | response.end(body); 26 | }); 27 | } else { 28 | fileServer(request, response); 29 | } 30 | }); 31 | } 32 | start(port) { 33 | this.server.listen(port); 34 | } 35 | stop() { 36 | this.server.close(); 37 | } 38 | } 39 | 40 | const talkPath = /^\/talks\/([^\/]+)$/; 41 | 42 | router.add("GET", talkPath, async (server, title) => { 43 | if (title in server.talks) { 44 | return {body: JSON.stringify(server.talks[title]), 45 | headers: {"Content-Type": "application/json"}}; 46 | } else { 47 | return {status: 404, body: `No talk '${title}' found`}; 48 | } 49 | }); 50 | 51 | router.add("DELETE", talkPath, async (server, title) => { 52 | if (title in server.talks) { 53 | delete server.talks[title]; 54 | server.updated(); 55 | } 56 | return {status: 204}; 57 | }); 58 | 59 | function readStream(stream) { 60 | return new Promise((resolve, reject) => { 61 | let data = ""; 62 | stream.on("error", reject); 63 | stream.on("data", chunk => data += chunk.toString()); 64 | stream.on("end", () => resolve(data)); 65 | }); 66 | } 67 | 68 | router.add("PUT", talkPath, 69 | async (server, title, request) => { 70 | let requestBody = await readStream(request); 71 | let talk; 72 | try { talk = JSON.parse(requestBody); } 73 | catch (_) { return {status: 400, body: "Invalid JSON"}; } 74 | 75 | if (!talk || 76 | typeof talk.presenter != "string" || 77 | typeof talk.summary != "string") { 78 | return {status: 400, body: "Bad talk data"}; 79 | } 80 | server.talks[title] = {title, 81 | presenter: talk.presenter, 82 | summary: talk.summary, 83 | comments: []}; 84 | server.updated(); 85 | return {status: 204}; 86 | }); 87 | 88 | router.add("POST", /^\/talks\/([^\/]+)\/comments$/, 89 | async (server, title, request) => { 90 | let requestBody = await readStream(request); 91 | let comment; 92 | try { comment = JSON.parse(requestBody); } 93 | catch (_) { return {status: 400, body: "Invalid JSON"}; } 94 | 95 | if (!comment || 96 | typeof comment.author != "string" || 97 | typeof comment.message != "string") { 98 | return {status: 400, body: "Bad comment data"}; 99 | } else if (title in server.talks) { 100 | server.talks[title].comments.push(comment); 101 | server.updated(); 102 | return {status: 204}; 103 | } else { 104 | return {status: 404, body: `No talk '${title}' found`}; 105 | } 106 | }); 107 | 108 | SkillShareServer.prototype.talkResponse = function() { 109 | let talks = []; 110 | for (let title of Object.keys(this.talks)) { 111 | talks.push(this.talks[title]); 112 | } 113 | return { 114 | body: JSON.stringify(talks), 115 | headers: {"Content-Type": "application/json", 116 | "ETag": `"${this.version}"`} 117 | }; 118 | }; 119 | 120 | router.add("GET", /^\/talks$/, async (server, request) => { 121 | let tag = /"(.*)"/.exec(request.headers["if-none-match"]); 122 | let wait = /\bwait=(\d+)/.exec(request.headers["prefer"]); 123 | if (!tag || tag[1] != server.version) { 124 | return server.talkResponse(); 125 | } else if (!wait) { 126 | return {status: 304}; 127 | } else { 128 | return server.waitForChanges(Number(wait[1])); 129 | } 130 | }); 131 | 132 | SkillShareServer.prototype.waitForChanges = function(time) { 133 | return new Promise(resolve => { 134 | this.waiting.push(resolve); 135 | setTimeout(() => { 136 | if (!this.waiting.includes(resolve)) return; 137 | this.waiting = this.waiting.filter(r => r != resolve); 138 | resolve({status: 304}); 139 | }, time * 1000); 140 | }); 141 | }; 142 | 143 | SkillShareServer.prototype.updated = function() { 144 | this.version++; 145 | let response = this.talkResponse(); 146 | this.waiting.forEach(resolve => resolve(response)); 147 | this.waiting = []; 148 | }; 149 | 150 | new SkillShareServer(Object.create(null)).start(8000); 151 | -------------------------------------------------------------------------------- /code/solutions/02_1_looping_a_triangle.js: -------------------------------------------------------------------------------- 1 | for (let line = "#"; line.length < 8; line += "#") 2 | console.log(line); 3 | -------------------------------------------------------------------------------- /code/solutions/02_2_fizzbuzz.js: -------------------------------------------------------------------------------- 1 | for (let n = 1; n <= 100; n++) { 2 | let output = ""; 3 | if (n % 3 == 0) output += "Fizz"; 4 | if (n % 5 == 0) output += "Buzz"; 5 | console.log(output || n); 6 | } 7 | -------------------------------------------------------------------------------- /code/solutions/02_3_chessboard.js: -------------------------------------------------------------------------------- 1 | let size = 8; 2 | 3 | let board = ""; 4 | 5 | for (let y = 0; y < size; y++) { 6 | for (let x = 0; x < size; x++) { 7 | if ((x + y) % 2 == 0) { 8 | board += " "; 9 | } else { 10 | board += "#"; 11 | } 12 | } 13 | board += "\n"; 14 | } 15 | 16 | console.log(board); 17 | -------------------------------------------------------------------------------- /code/solutions/03_1_minimum.js: -------------------------------------------------------------------------------- 1 | function min(a, b) { 2 | if (a < b) return a; 3 | else return b; 4 | } 5 | 6 | console.log(min(0, 10)); 7 | // → 0 8 | console.log(min(0, -10)); 9 | // → -10 10 | -------------------------------------------------------------------------------- /code/solutions/03_2_recursion.js: -------------------------------------------------------------------------------- 1 | function isEven(n) { 2 | if (n == 0) return true; 3 | else if (n == 1) return false; 4 | else if (n < 0) return isEven(-n); 5 | else return isEven(n - 2); 6 | } 7 | 8 | console.log(isEven(50)); 9 | // → true 10 | console.log(isEven(75)); 11 | // → false 12 | console.log(isEven(-1)); 13 | // → false 14 | -------------------------------------------------------------------------------- /code/solutions/03_3_bean_counting.js: -------------------------------------------------------------------------------- 1 | function countChar(string, ch) { 2 | let counted = 0; 3 | for (let i = 0; i < string.length; i++) { 4 | if (string[i] == ch) { 5 | counted += 1; 6 | } 7 | } 8 | return counted; 9 | } 10 | 11 | function countBs(string) { 12 | return countChar(string, "B"); 13 | } 14 | 15 | console.log(countBs("BBC")); 16 | // → 2 17 | console.log(countChar("kakkerlak", "k")); 18 | // → 4 19 | -------------------------------------------------------------------------------- /code/solutions/04_1_the_sum_of_a_range.js: -------------------------------------------------------------------------------- 1 | function range(start, end, step = start < end ? 1 : -1) { 2 | let array = []; 3 | 4 | if (step > 0) { 5 | for (let i = start; i <= end; i += step) array.push(i); 6 | } else { 7 | for (let i = start; i >= end; i += step) array.push(i); 8 | } 9 | return array; 10 | } 11 | 12 | function sum(array) { 13 | let total = 0; 14 | for (let value of array) { 15 | total += value; 16 | } 17 | return total; 18 | } 19 | 20 | console.log(range(1, 10)) 21 | // → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 22 | console.log(range(5, 2, -1)); 23 | // → [5, 4, 3, 2] 24 | console.log(sum(range(1, 10))); 25 | // → 55 26 | -------------------------------------------------------------------------------- /code/solutions/04_2_reversing_an_array.js: -------------------------------------------------------------------------------- 1 | function reverseArray(array) { 2 | let output = []; 3 | for (let i = array.length - 1; i >= 0; i--) { 4 | output.push(array[i]); 5 | } 6 | return output; 7 | } 8 | 9 | function reverseArrayInPlace(array) { 10 | for (let i = 0; i < Math.floor(array.length / 2); i++) { 11 | let old = array[i]; 12 | array[i] = array[array.length - 1 - i]; 13 | array[array.length - 1 - i] = old; 14 | } 15 | return array; 16 | } 17 | 18 | console.log(reverseArray(["A", "B", "C"])); 19 | // → ["C", "B", "A"]; 20 | let arrayValue = [1, 2, 3, 4, 5]; 21 | reverseArrayInPlace(arrayValue); 22 | console.log(arrayValue); 23 | // → [5, 4, 3, 2, 1] 24 | -------------------------------------------------------------------------------- /code/solutions/04_3_a_list.js: -------------------------------------------------------------------------------- 1 | function arrayToList(array) { 2 | let list = null; 3 | for (let i = array.length - 1; i >= 0; i--) { 4 | list = {value: array[i], rest: list}; 5 | } 6 | return list; 7 | } 8 | 9 | function listToArray(list) { 10 | let array = []; 11 | for (let node = list; node; node = node.rest) { 12 | array.push(node.value); 13 | } 14 | return array; 15 | } 16 | 17 | function prepend(value, list) { 18 | return {value, rest: list}; 19 | } 20 | 21 | function nth(list, n) { 22 | if (!list) return undefined; 23 | else if (n == 0) return list.value; 24 | else return nth(list.rest, n - 1); 25 | } 26 | 27 | console.log(arrayToList([10, 20])); 28 | // → {value: 10, rest: {value: 20, rest: null}} 29 | console.log(listToArray(arrayToList([10, 20, 30]))); 30 | // → [10, 20, 30] 31 | console.log(prepend(10, prepend(20, null))); 32 | // → {value: 10, rest: {value: 20, rest: null}} 33 | console.log(nth(arrayToList([10, 20, 30]), 1)); 34 | // → 20 35 | -------------------------------------------------------------------------------- /code/solutions/04_4_deep_comparison.js: -------------------------------------------------------------------------------- 1 | function deepEqual(a, b) { 2 | if (a === b) return true; 3 | 4 | if (a == null || typeof a != "object" || 5 | b == null || typeof b != "object") return false; 6 | 7 | let keysA = Object.keys(a), keysB = Object.keys(b); 8 | 9 | if (keysA.length != keysB.length) return false; 10 | 11 | for (let key of keysA) { 12 | if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false; 13 | } 14 | 15 | return true; 16 | } 17 | 18 | let obj = {here: {is: "an"}, object: 2}; 19 | console.log(deepEqual(obj, obj)); 20 | // → true 21 | console.log(deepEqual(obj, {here: 1, object: 2})); 22 | // → false 23 | console.log(deepEqual(obj, {here: {is: "an"}, object: 2})); 24 | // → true 25 | -------------------------------------------------------------------------------- /code/solutions/05_1_flattening.js: -------------------------------------------------------------------------------- 1 | let arrays = [[1, 2, 3], [4, 5], [6]]; 2 | 3 | console.log(arrays.reduce((flat, current) => flat.concat(current), [])); 4 | // → [1, 2, 3, 4, 5, 6] 5 | -------------------------------------------------------------------------------- /code/solutions/05_2_your_own_loop.js: -------------------------------------------------------------------------------- 1 | function loop(start, test, update, body) { 2 | for (let value = start; test(value); value = update(value)) { 3 | body(value); 4 | } 5 | } 6 | 7 | loop(3, n => n > 0, n => n - 1, console.log); 8 | // → 3 9 | // → 2 10 | // → 1 11 | -------------------------------------------------------------------------------- /code/solutions/05_3_everything.js: -------------------------------------------------------------------------------- 1 | function every(array, predicate) { 2 | for (let element of array) { 3 | if (!predicate(element)) return false; 4 | } 5 | return true; 6 | } 7 | 8 | function every2(array, predicate) { 9 | return !array.some(element => !predicate(element)); 10 | } 11 | 12 | console.log(every([1, 3, 5], n => n < 10)); 13 | // → true 14 | console.log(every([2, 4, 16], n => n < 10)); 15 | // → false 16 | console.log(every([], n => n < 10)); 17 | // → true 18 | -------------------------------------------------------------------------------- /code/solutions/05_4_dominant_writing_direction.js: -------------------------------------------------------------------------------- 1 | function dominantDirection(text) { 2 | let counted = countBy(text, char => { 3 | let script = characterScript(char.codePointAt(0)); 4 | return script ? script.direction : "none"; 5 | }).filter(({name}) => name != "none"); 6 | 7 | if (counted.length == 0) return "ltr"; 8 | 9 | return counted.reduce((a, b) => a.count > b.count ? a : b).name; 10 | } 11 | 12 | console.log(dominantDirection("Hello!")); 13 | // → ltr 14 | console.log(dominantDirection("Hey, مساء الخير")); 15 | // → rtl 16 | -------------------------------------------------------------------------------- /code/solutions/06_1_a_vector_type.js: -------------------------------------------------------------------------------- 1 | class Vec { 2 | constructor(x, y) { 3 | this.x = x; 4 | this.y = y; 5 | } 6 | 7 | plus(other) { 8 | return new Vec(this.x + other.x, this.y + other.y); 9 | } 10 | 11 | minus(other) { 12 | return new Vec(this.x - other.x, this.y - other.y); 13 | } 14 | 15 | get length() { 16 | return Math.sqrt(this.x * this.x + this.y * this.y); 17 | } 18 | } 19 | 20 | console.log(new Vec(1, 2).plus(new Vec(2, 3))); 21 | // → Vec{x: 3, y: 5} 22 | console.log(new Vec(1, 2).minus(new Vec(2, 3))); 23 | // → Vec{x: -1, y: -1} 24 | console.log(new Vec(3, 4).length); 25 | // → 5 26 | -------------------------------------------------------------------------------- /code/solutions/06_2_groups.js: -------------------------------------------------------------------------------- 1 | class Group { 2 | constructor() { 3 | this.members = []; 4 | } 5 | 6 | add(value) { 7 | if (!this.has(value)) { 8 | this.members.push(value); 9 | } 10 | } 11 | 12 | delete(value) { 13 | this.members = this.members.filter(v => v !== value); 14 | } 15 | 16 | has(value) { 17 | return this.members.includes(value); 18 | } 19 | 20 | static from(collection) { 21 | let group = new Group; 22 | for (let value of collection) { 23 | group.add(value); 24 | } 25 | return group; 26 | } 27 | } 28 | 29 | let group = Group.from([10, 20]); 30 | console.log(group.has(10)); 31 | // → true 32 | console.log(group.has(30)); 33 | // → false 34 | group.add(10); 35 | group.delete(10); 36 | console.log(group.has(10)); 37 | -------------------------------------------------------------------------------- /code/solutions/06_3_iterable_groups.js: -------------------------------------------------------------------------------- 1 | class Group { 2 | constructor() { 3 | this.members = []; 4 | } 5 | 6 | add(value) { 7 | if (!this.has(value)) { 8 | this.members.push(value); 9 | } 10 | } 11 | 12 | delete(value) { 13 | this.members = this.members.filter(v => v !== value); 14 | } 15 | 16 | has(value) { 17 | return this.members.includes(value); 18 | } 19 | 20 | static from(collection) { 21 | let group = new Group; 22 | for (let value of collection) { 23 | group.add(value); 24 | } 25 | return group; 26 | } 27 | 28 | [Symbol.iterator]() { 29 | return new GroupIterator(this); 30 | } 31 | } 32 | 33 | class GroupIterator { 34 | constructor(group) { 35 | this.group = group; 36 | this.position = 0; 37 | } 38 | 39 | next() { 40 | if (this.position >= this.group.members.length) { 41 | return {done: true}; 42 | } else { 43 | let result = {value: this.group.members[this.position], 44 | done: false}; 45 | this.position++; 46 | return result; 47 | } 48 | } 49 | } 50 | 51 | for (let value of Group.from(["a", "b", "c"])) { 52 | console.log(value); 53 | } 54 | // → a 55 | // → b 56 | // → c 57 | -------------------------------------------------------------------------------- /code/solutions/06_4_borrowing_a_method.js: -------------------------------------------------------------------------------- 1 | let map = {one: true, two: true, hasOwnProperty: true}; 2 | 3 | console.log(Object.prototype.hasOwnProperty.call(map, "one")); 4 | // → true 5 | -------------------------------------------------------------------------------- /code/solutions/07_1_measuring_a_robot.js: -------------------------------------------------------------------------------- 1 | function countSteps(state, robot, memory) { 2 | for (let steps = 0;; steps++) { 3 | if (state.parcels.length == 0) return steps; 4 | let action = robot(state, memory); 5 | state = state.move(action.direction); 6 | memory = action.memory; 7 | } 8 | } 9 | 10 | function compareRobots(robot1, memory1, robot2, memory2) { 11 | let total1 = 0, total2 = 0; 12 | for (let i = 0; i < 100; i++) { 13 | let state = VillageState.random(); 14 | total1 += countSteps(state, robot1, memory1); 15 | total2 += countSteps(state, robot2, memory2); 16 | } 17 | console.log(`Robot 1 needed ${total1 / 100} steps per task`) 18 | console.log(`Robot 2 needed ${total2 / 100}`) 19 | } 20 | 21 | compareRobots(routeRobot, [], goalOrientedRobot, []); 22 | -------------------------------------------------------------------------------- /code/solutions/07_2_robot_efficiency.js: -------------------------------------------------------------------------------- 1 | function lazyRobot({place, parcels}, route) { 2 | if (route.length == 0) { 3 | // Describe a route for every parcel 4 | let routes = parcels.map(parcel => { 5 | if (parcel.place != place) { 6 | return {route: findRoute(roadGraph, place, parcel.place), 7 | pickUp: true}; 8 | } else { 9 | return {route: findRoute(roadGraph, place, parcel.address), 10 | pickUp: false}; 11 | } 12 | }); 13 | 14 | // This determines the precedence a route gets when choosing. 15 | // Route length counts negatively, routes that pick up a package 16 | // get a small bonus. 17 | function score({route, pickUp}) { 18 | return (pickUp ? 0.5 : 0) - route.length; 19 | } 20 | route = routes.reduce((a, b) => score(a) > score(b) ? a : b).route; 21 | } 22 | 23 | return {direction: route[0], memory: route.slice(1)}; 24 | } 25 | 26 | runRobotAnimation(VillageState.random(), lazyRobot, []); 27 | -------------------------------------------------------------------------------- /code/solutions/07_3_persistent_group.js: -------------------------------------------------------------------------------- 1 | class PGroup { 2 | constructor(members) { 3 | this.members = members; 4 | } 5 | 6 | add(value) { 7 | if (this.has(value)) return this; 8 | return new PGroup(this.members.concat([value])); 9 | } 10 | 11 | delete(value) { 12 | if (!this.has(value)) return this; 13 | return new PGroup(this.members.filter(m => m !== value)); 14 | } 15 | 16 | has(value) { 17 | return this.members.includes(value); 18 | } 19 | } 20 | 21 | PGroup.empty = new PGroup([]); 22 | 23 | let a = PGroup.empty.add("a"); 24 | let ab = a.add("b"); 25 | let b = ab.delete("a"); 26 | 27 | console.log(b.has("b")); 28 | // → true 29 | console.log(a.has("b")); 30 | // → false 31 | console.log(b.has("a")); 32 | // → false 33 | -------------------------------------------------------------------------------- /code/solutions/08_1_retry.js: -------------------------------------------------------------------------------- 1 | function MultiplicatorUnitFailure() {} 2 | 3 | function primitiveMultiply(a, b) { 4 | if (Math.random() < 0.5) { 5 | return a * b; 6 | } else { 7 | throw new MultiplicatorUnitFailure(); 8 | } 9 | } 10 | 11 | function reliableMultiply(a, b) { 12 | for (;;) { 13 | try { 14 | return primitiveMultiply(a, b); 15 | } catch (e) { 16 | if (!(e instanceof MultiplicatorUnitFailure)) 17 | throw e; 18 | } 19 | } 20 | } 21 | 22 | console.log(reliableMultiply(8, 8)); 23 | // → 64 24 | 25 | -------------------------------------------------------------------------------- /code/solutions/08_2_the_locked_box.js: -------------------------------------------------------------------------------- 1 | const box = { 2 | locked: true, 3 | unlock() { this.locked = false; }, 4 | lock() { this.locked = true; }, 5 | _content: [], 6 | get content() { 7 | if (this.locked) throw new Error("Locked!"); 8 | return this._content; 9 | } 10 | }; 11 | 12 | function withBoxUnlocked(body) { 13 | let locked = box.locked; 14 | if (!locked) { 15 | return body(); 16 | } 17 | 18 | box.unlock(); 19 | try { 20 | return body(); 21 | } finally { 22 | box.lock(); 23 | } 24 | } 25 | 26 | withBoxUnlocked(function() { 27 | box.content.push("gold piece"); 28 | }); 29 | 30 | try { 31 | withBoxUnlocked(function() { 32 | throw new Error("Pirates on the horizon! Abort!"); 33 | }); 34 | } catch (e) { 35 | console.log("Error raised:", e); 36 | } 37 | 38 | console.log(box.locked); 39 | // → true 40 | -------------------------------------------------------------------------------- /code/solutions/09_1_regexp_golf.js: -------------------------------------------------------------------------------- 1 | // Fill in the regular expressions 2 | 3 | verify(/ca[rt]/, 4 | ["my car", "bad cats"], 5 | ["camper", "high art"]); 6 | 7 | verify(/pr?op/, 8 | ["pop culture", "mad props"], 9 | ["plop", "prrrop"]); 10 | 11 | verify(/ferr(et|y|ari)/, 12 | ["ferret", "ferry", "ferrari"], 13 | ["ferrum", "transfer A"]); 14 | 15 | verify(/ious\b/, 16 | ["how delicious", "spacious room"], 17 | ["ruinous", "consciousness"]); 18 | 19 | verify(/\s[.,:;]/, 20 | ["bad punctuation ."], 21 | ["escape the dot"]); 22 | 23 | verify(/\w{7}/, 24 | ["hottentottententen"], 25 | ["no", "hotten totten tenten"]); 26 | 27 | verify(/\b[^\We]+\b/i, 28 | ["red platypus", "wobbling nest"], 29 | ["earth bed", "learning ape", "BEET"]); 30 | 31 | 32 | function verify(regexp, yes, no) { 33 | // Ignore unfinished exercises 34 | if (regexp.source == "...") return; 35 | for (let str of yes) if (!regexp.test(str)) { 36 | console.log(`Failure to match '${str}'`); 37 | } 38 | for (let str of no) if (regexp.test(str)) { 39 | console.log(`Unexpected match for '${str}'`); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /code/solutions/09_2_quoting_style.js: -------------------------------------------------------------------------------- 1 | let text = "'I'm the cook,' he said, 'it's my job.'"; 2 | 3 | console.log(text.replace(/(^|\W)'|'(\W|$)/g, '$1"$2')); 4 | // → "I'm the cook," he said, "it's my job." 5 | 6 | -------------------------------------------------------------------------------- /code/solutions/09_3_numbers_again.js: -------------------------------------------------------------------------------- 1 | // Fill in this regular expression. 2 | let number = /^[+\-]?(\d+(\.\d*)?|\.\d+)([eE][+\-]?\d+)?$/; 3 | 4 | // Tests: 5 | for (let str of ["1", "-1", "+15", "1.55", ".5", "5.", 6 | "1.3e2", "1E-4", "1e+12"]) { 7 | if (!number.test(str)) { 8 | console.log(`Failed to match '${str}'`); 9 | } 10 | } 11 | for (let str of ["1a", "+-1", "1.2.3", "1+1", "1e4.5", 12 | ".5.", "1f5", "."]) { 13 | if (number.test(str)) { 14 | console.log(`Incorrectly accepted '${str}'`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /code/solutions/10_2_roads_module.js: -------------------------------------------------------------------------------- 1 | const {buildGraph} = require("./graph"); 2 | 3 | const roads = [ 4 | "Alice's House-Bob's House", "Alice's House-Cabin", 5 | "Alice's House-Post Office", "Bob's House-Town Hall", 6 | "Daria's House-Ernie's House", "Daria's House-Town Hall", 7 | "Ernie's House-Grete's House", "Grete's House-Farm", 8 | "Grete's House-Shop", "Marketplace-Farm", 9 | "Marketplace-Post Office", "Marketplace-Shop", 10 | "Marketplace-Town Hall", "Shop-Town Hall" 11 | ]; 12 | 13 | exports.roadGraph = buildGraph(roads.map(r => r.split("-"))); 14 | -------------------------------------------------------------------------------- /code/solutions/11_1_tracking_the_scalpel.js: -------------------------------------------------------------------------------- 1 | async function locateScalpel(nest) { 2 | let current = nest.name; 3 | for (;;) { 4 | let next = await anyStorage(nest, current, "scalpel"); 5 | if (next == current) return current; 6 | current = next; 7 | } 8 | } 9 | 10 | function locateScalpel2(nest) { 11 | function loop(current) { 12 | return anyStorage(nest, current, "scalpel").then(next => { 13 | if (next == current) return current; 14 | else return loop(next); 15 | }); 16 | } 17 | return loop(nest.name); 18 | } 19 | 20 | locateScalpel(bigOak).then(console.log); 21 | // → Butcher's Shop 22 | locateScalpel2(bigOak).then(console.log); 23 | // → Butcher's Shop 24 | -------------------------------------------------------------------------------- /code/solutions/11_2_building_promiseall.js: -------------------------------------------------------------------------------- 1 | function Promise_all(promises) { 2 | return new Promise((resolve, reject) => { 3 | let results = []; 4 | let pending = promises.length; 5 | for (let i = 0; i < promises.length; i++) { 6 | promises[i].then(result => { 7 | results[i] = result; 8 | pending--; 9 | if (pending == 0) resolve(results); 10 | }).catch(reject); 11 | } 12 | if (promises.length == 0) resolve(results); 13 | }); 14 | } 15 | 16 | // Test code. 17 | Promise_all([]).then(array => { 18 | console.log("This should be []:", array); 19 | }); 20 | function soon(val) { 21 | return new Promise(resolve => { 22 | setTimeout(() => resolve(val), Math.random() * 500); 23 | }); 24 | } 25 | Promise_all([soon(1), soon(2), soon(3)]).then(array => { 26 | console.log("This should be [1, 2, 3]:", array); 27 | }); 28 | Promise_all([soon(1), Promise.reject("X"), soon(3)]).then(array => { 29 | console.log("We should not get here"); 30 | }).catch(error => { 31 | if (error != "X") { 32 | console.log("Unexpected failure:", error); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /code/solutions/12_1_arrays.js: -------------------------------------------------------------------------------- 1 | topScope.array = (...values) => values; 2 | 3 | topScope.length = array => array.length; 4 | 5 | topScope.element = (array, i) => array[i]; 6 | 7 | run(` 8 | do(define(sum, fun(array, 9 | do(define(i, 0), 10 | define(sum, 0), 11 | while(<(i, length(array)), 12 | do(define(sum, +(sum, element(array, i))), 13 | define(i, +(i, 1)))), 14 | sum))), 15 | print(sum(array(1, 2, 3)))) 16 | `); 17 | // → 6 18 | -------------------------------------------------------------------------------- /code/solutions/12_3_comments.js: -------------------------------------------------------------------------------- 1 | function skipSpace(string) { 2 | let skippable = string.match(/^(\s|#.*)*/); 3 | return string.slice(skippable[0].length); 4 | } 5 | 6 | console.log(parse("# hello\nx")); 7 | // → {type: "word", name: "x"} 8 | 9 | console.log(parse("a # one\n # two\n()")); 10 | // → {type: "apply", 11 | // operator: {type: "word", name: "a"}, 12 | // args: []} 13 | -------------------------------------------------------------------------------- /code/solutions/12_4_fixing_scope.js: -------------------------------------------------------------------------------- 1 | specialForms.set = (args, env) => { 2 | if (args.length != 2 || args[0].type != "word") { 3 | throw new SyntaxError("Bad use of set"); 4 | } 5 | let varName = args[0].name; 6 | let value = evaluate(args[1], env); 7 | 8 | for (let scope = env; scope; scope = Object.getPrototypeOf(scope)) { 9 | if (Object.prototype.hasOwnProperty.call(scope, varName)) { 10 | scope[varName] = value; 11 | return value; 12 | } 13 | } 14 | throw new ReferenceError(`Setting undefined variable ${varName}`); 15 | }; 16 | 17 | run(` 18 | do(define(x, 4), 19 | define(setx, fun(val, set(x, val))), 20 | setx(50), 21 | print(x)) 22 | `); 23 | // → 50 24 | run(`set(quux, true)`); 25 | // → Some kind of ReferenceError 26 | -------------------------------------------------------------------------------- /code/solutions/14_1_build_a_table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Mountains

5 | 6 |
7 | 8 | 50 | -------------------------------------------------------------------------------- /code/solutions/14_2_elements_by_tag_name.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Heading with a span element.

4 |

A paragraph with one, two 5 | spans.

6 | 7 | 34 | -------------------------------------------------------------------------------- /code/solutions/14_3_the_cats_hat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 28 | -------------------------------------------------------------------------------- /code/solutions/15_1_balloon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

🎈

4 | 5 | 30 | -------------------------------------------------------------------------------- /code/solutions/15_2_mouse_trail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 33 | 34 | -------------------------------------------------------------------------------- /code/solutions/15_3_tabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Tab one
5 |
Tab two
6 |
Tab three
7 |
8 | 34 | -------------------------------------------------------------------------------- /code/solutions/16_1_game_over.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 29 | 30 | -------------------------------------------------------------------------------- /code/solutions/16_2_pausing_the_game.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 93 | 94 | -------------------------------------------------------------------------------- /code/solutions/16_3_a_monster.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 62 | 63 | -------------------------------------------------------------------------------- /code/solutions/17_1_shapes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 67 | -------------------------------------------------------------------------------- /code/solutions/17_2_the_pie_chart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 41 | -------------------------------------------------------------------------------- /code/solutions/17_3_a_bouncing_ball.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 37 | -------------------------------------------------------------------------------- /code/solutions/18_1_content_negotiation.js: -------------------------------------------------------------------------------- 1 | const url = "https://eloquentjavascript.net/author"; 2 | const types = ["text/plain", 3 | "text/html", 4 | "application/json", 5 | "application/rainbows+unicorns"]; 6 | 7 | async function showTypes() { 8 | for (let type of types) { 9 | let resp = await fetch(url, {headers: {accept: type}}); 10 | console.log(`${type}: ${await resp.text()}\n`); 11 | } 12 | } 13 | 14 | showTypes(); 15 | -------------------------------------------------------------------------------- /code/solutions/18_2_a_javascript_workbench.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

 6 | 
 7 | 
19 | 


--------------------------------------------------------------------------------
/code/solutions/18_3_conways_game_of_life.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
4 | 5 | 6 | 7 | 90 | -------------------------------------------------------------------------------- /code/solutions/19_1_keyboard_bindings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 53 | -------------------------------------------------------------------------------- /code/solutions/19_2_efficient_drawing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 38 | -------------------------------------------------------------------------------- /code/solutions/19_3_circles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 35 | -------------------------------------------------------------------------------- /code/solutions/19_4_proper_lines.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 50 | -------------------------------------------------------------------------------- /code/solutions/20_1_search_tool.js: -------------------------------------------------------------------------------- 1 | const {statSync, readdirSync, readFileSync} = require("fs"); 2 | 3 | let searchTerm = new RegExp(process.argv[2]); 4 | 5 | for (let arg of process.argv.slice(3)) { 6 | search(arg); 7 | } 8 | 9 | function search(file) { 10 | let stats = statSync(file); 11 | if (stats.isDirectory()) { 12 | for (let f of readdirSync(file)) { 13 | search(file + "/" + f); 14 | } 15 | } else if (searchTerm.test(readFileSync(file, "utf8"))) { 16 | console.log(file); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/solutions/20_2_directory_creation.js: -------------------------------------------------------------------------------- 1 | // This code won't work on its own, but is also included in the 2 | // code/file_server.js file, which defines the whole system. 3 | 4 | const {mkdir} = require("fs").promises; 5 | 6 | methods.MKCOL = async function(request) { 7 | let path = urlPath(request.url); 8 | let stats; 9 | try { 10 | stats = await stat(path); 11 | } catch (error) { 12 | if (error.code != "ENOENT") throw error; 13 | await mkdir(path); 14 | return {status: 204}; 15 | } 16 | if (stats.isDirectory()) return {status: 204}; 17 | else return {status: 400, body: "Not a directory"}; 18 | }; 19 | -------------------------------------------------------------------------------- /code/solutions/20_3_a_public_space_on_the_web.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/code/solutions/20_3_a_public_space_on_the_web.zip -------------------------------------------------------------------------------- /code/solutions/20_3_a_public_space_on_the_web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

A Public Space on the Web

5 | 6 |

This is a self-editing website. Select a file, edit it, and save to 7 | update the website.

8 | 9 |

Files:

10 | 11 |

12 | 13 |
14 | 15 |

16 | 17 | 18 | -------------------------------------------------------------------------------- /code/solutions/20_3_a_public_space_on_the_web/other.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

This is another file

5 | -------------------------------------------------------------------------------- /code/solutions/20_3_a_public_space_on_the_web/public_space.js: -------------------------------------------------------------------------------- 1 | // Get a reference to the DOM nodes we need 2 | let filelist = document.querySelector("#filelist"); 3 | let textarea = document.querySelector("#file"); 4 | 5 | // This loads the initial file list from the server 6 | fetch("/").then(resp => resp.text()).then(files => { 7 | for (let file of files.split("\n")) { 8 | let option = document.createElement("option"); 9 | option.textContent = file; 10 | filelist.appendChild(option); 11 | } 12 | // Now that we have a list of files, make sure the textarea contains 13 | // the currently selected one. 14 | loadCurrentFile(); 15 | }); 16 | 17 | // Fetch a file from the server and put it in the textarea. 18 | function loadCurrentFile() { 19 | fetch(filelist.value).then(resp => resp.text()).then(file => { 20 | textarea.value = file; 21 | }); 22 | } 23 | 24 | filelist.addEventListener("change", loadCurrentFile); 25 | 26 | // Called by the button on the page. Makes a request to save the 27 | // currently selected file. 28 | function saveFile() { 29 | fetch(filelist.value, {method: "PUT", 30 | body: textarea.value}); 31 | } 32 | -------------------------------------------------------------------------------- /code/solutions/21_1_disk_persistence.js: -------------------------------------------------------------------------------- 1 | // This isn't a stand-alone file, only a redefinition of a few 2 | // fragments from skillsharing/skillsharing_server.js 3 | 4 | const {readFileSync, writeFile} = require("fs"); 5 | 6 | const fileName = "./talks.json"; 7 | 8 | function loadTalks() { 9 | let json; 10 | try { 11 | json = JSON.parse(readFileSync(fileName, "utf8")); 12 | } catch (e) { 13 | json = {}; 14 | } 15 | return Object.assign(Object.create(null), json); 16 | } 17 | 18 | SkillShareServer.prototype.updated = function() { 19 | this.version++; 20 | let response = this.talkResponse(); 21 | this.waiting.forEach(resolve => resolve(response)); 22 | this.waiting = []; 23 | 24 | writeFile(fileName, JSON.stringify(this.talks)); 25 | }; 26 | 27 | // The line that starts the server must be changed to 28 | new SkillShareServer(loadTalks()).start(8000); 29 | -------------------------------------------------------------------------------- /code/solutions/21_2_comment_field_resets.js: -------------------------------------------------------------------------------- 1 | // This isn't a stand-alone file, only a redefinition of the main 2 | // component from skillsharing/public/skillsharing_client.js 3 | 4 | class Talk { 5 | constructor(talk, dispatch) { 6 | this.comments = elt("div"); 7 | this.dom = elt( 8 | "section", {className: "talk"}, 9 | elt("h2", null, talk.title, " ", elt("button", { 10 | type: "button", 11 | onclick: () => dispatch({type: "deleteTalk", 12 | talk: talk.title}) 13 | }, "Delete")), 14 | elt("div", null, "by ", 15 | elt("strong", null, talk.presenter)), 16 | elt("p", null, talk.summary), 17 | this.comments, 18 | elt("form", { 19 | onsubmit(event) { 20 | event.preventDefault(); 21 | let form = event.target; 22 | dispatch({type: "newComment", 23 | talk: talk.title, 24 | message: form.elements.comment.value}); 25 | form.reset(); 26 | } 27 | }, elt("input", {type: "text", name: "comment"}), " ", 28 | elt("button", {type: "submit"}, "Add comment"))); 29 | this.syncState(talk); 30 | } 31 | 32 | syncState(talk) { 33 | this.talk = talk; 34 | this.comments.textContent = ""; 35 | for (let comment of talk.comments) { 36 | this.comments.appendChild(renderComment(comment)); 37 | } 38 | } 39 | } 40 | 41 | class SkillShareApp { 42 | constructor(state, dispatch) { 43 | this.dispatch = dispatch; 44 | this.talkDOM = elt("div", {className: "talks"}); 45 | this.talkMap = Object.create(null); 46 | this.dom = elt("div", null, 47 | renderUserField(state.user, dispatch), 48 | this.talkDOM, 49 | renderTalkForm(dispatch)); 50 | this.syncState(state); 51 | } 52 | 53 | syncState(state) { 54 | if (state.talks == this.talks) return; 55 | this.talks = state.talks; 56 | 57 | for (let talk of state.talks) { 58 | let cmp = this.talkMap[talk.title]; 59 | if (cmp && cmp.talk.author == talk.author && 60 | cmp.talk.summary == talk.summary) { 61 | cmp.syncState(talk); 62 | } else { 63 | if (cmp) cmp.dom.remove(); 64 | cmp = new Talk(talk, this.dispatch); 65 | this.talkMap[talk.title] = cmp; 66 | this.talkDOM.appendChild(cmp.dom); 67 | } 68 | } 69 | for (let title of Object.keys(this.talkMap)) { 70 | if (!state.talks.some(talk => talk.title == title)) { 71 | this.talkMap[title].dom.remove(); 72 | delete this.talkMap[title]; 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /code/solutions/22_1_pathfinding.js: -------------------------------------------------------------------------------- 1 | function findPath(a, b) { 2 | let work = [[a]]; 3 | for (let path of work) { 4 | let end = path[path.length - 1]; 5 | if (end == b) return path; 6 | for (let next of end.edges) { 7 | if (!work.some(path => path[path.length - 1] == next)) { 8 | work.push(path.concat([next])); 9 | } 10 | } 11 | } 12 | } 13 | 14 | let graph = treeGraph(4, 4); 15 | let root = graph[0], leaf = graph[graph.length - 1]; 16 | console.log(findPath(root, leaf).length); 17 | // → 4 18 | 19 | leaf.connect(root); 20 | console.log(findPath(root, leaf).length); 21 | // → 2 22 | -------------------------------------------------------------------------------- /code/solutions/22_2_timing.js: -------------------------------------------------------------------------------- 1 | function findPath(a, b) { 2 | let work = [[a]]; 3 | for (let path of work) { 4 | let end = path[path.length - 1]; 5 | if (end == b) return path; 6 | for (let next of end.edges) { 7 | if (!work.some(path => path[path.length - 1] == next)) { 8 | work.push(path.concat([next])); 9 | } 10 | } 11 | } 12 | } 13 | 14 | function time(findPath) { 15 | let graph = treeGraph(6, 6); 16 | let startTime = Date.now(); 17 | let result = findPath(graph[0], graph[graph.length - 1]); 18 | console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`); 19 | } 20 | time(findPath); 21 | -------------------------------------------------------------------------------- /code/solutions/22_3_optimizing.js: -------------------------------------------------------------------------------- 1 | function time(findPath) { 2 | let graph = treeGraph(6, 6); 3 | let startTime = Date.now(); 4 | let result = findPath(graph[0], graph[graph.length - 1]); 5 | console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`); 6 | } 7 | 8 | function findPath_set(a, b) { 9 | let work = [[a]]; 10 | let reached = new Set([a]); 11 | for (let path of work) { 12 | let end = path[path.length - 1]; 13 | if (end == b) return path; 14 | for (let next of end.edges) { 15 | if (!reached.has(next)) { 16 | reached.add(next); 17 | work.push(path.concat([next])); 18 | } 19 | } 20 | } 21 | } 22 | 23 | time(findPath_set); 24 | 25 | function pathToArray(path) { 26 | let result = []; 27 | for (; path; path = path.via) result.unshift(path.at); 28 | return result; 29 | } 30 | 31 | function findPath_list(a, b) { 32 | let work = [{at: a, via: null}]; 33 | let reached = new Set([a]); 34 | for (let path of work) { 35 | if (path.at == b) return pathToArray(path); 36 | for (let next of path.at.edges) { 37 | if (!reached.has(next)) { 38 | reached.add(next); 39 | work.push({at: next, via: path}); 40 | } 41 | } 42 | } 43 | } 44 | 45 | time(findPath_list); 46 | -------------------------------------------------------------------------------- /code/squareworker.js: -------------------------------------------------------------------------------- 1 | addEventListener("message", event => { 2 | postMessage(event.data * event.data); 3 | }); 4 | -------------------------------------------------------------------------------- /css/game.css: -------------------------------------------------------------------------------- 1 | .background { background: rgb(52, 166, 251); 2 | table-layout: fixed; 3 | border-spacing: 0; } 4 | .background td { padding: 0; } 5 | .lava { background: rgb(255, 100, 100); } 6 | .wall { background: white; } 7 | 8 | .actor { position: absolute; } 9 | .coin { background: rgb(241, 229, 89); } 10 | .player { background: rgb(64, 64, 64); } 11 | 12 | .game { 13 | overflow: hidden; 14 | max-width: 600px; 15 | max-height: 450px; 16 | position: relative; 17 | } 18 | 19 | .lost .player { 20 | background: rgb(160, 64, 64); 21 | } 22 | .won .player { 23 | box-shadow: -4px -7px 8px white, 4px -7px 8px white; 24 | } 25 | -------------------------------------------------------------------------------- /css/paint.css: -------------------------------------------------------------------------------- 1 | .picturepanel { 2 | width: -webkit-fit-content; 3 | width: -moz-fit-content; 4 | width: -ms-fit-content; 5 | width: fit-content; 6 | max-width: 500px; 7 | max-height: 300px; 8 | border: 2px solid silver; 9 | overflow: auto; 10 | position: relative; 11 | } 12 | .picturepanel canvas { display: block; } 13 | .toolbar > * { margin-right: 5px; } 14 | -------------------------------------------------------------------------------- /empty.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /errata.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Eloquent JavaScript :: Errata 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 |

Errata
Eloquent JavaScript, 3nd Edition

15 | 16 |

These are the known mistakes in the third edition 17 | of the book. For errata in the first edition, 18 | see this 19 | page. For the second edition, 20 | see this 21 | page. To report a problem that is not listed 22 | here, send me an email.

23 | 24 | 25 |

Chapter 2

26 | 27 |

Page 34 (Updating Bindings Succintly): Where it 28 | says counter- it should be counter--.

29 | 30 |

Chapter 20

31 | 32 |

Page 369 (Directory Creation): `MKCOL` stands for 33 | "make collection", not "make column" as the book claims.

34 | 35 |
36 | -------------------------------------------------------------------------------- /example/bert.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bert", 3 | "spouse": "example/suzie.json" 4 | } 5 | -------------------------------------------------------------------------------- /example/data.txt: -------------------------------------------------------------------------------- 1 | This is the content of data.txt 2 | -------------------------------------------------------------------------------- /example/fruit.json: -------------------------------------------------------------------------------- 1 | {"banana": "yellow", 2 | "lemon": "yellow", 3 | "cherry": "red"} 4 | -------------------------------------------------------------------------------- /example/fruit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/message.html: -------------------------------------------------------------------------------- 1 | 2 | Example form target 3 | 4 | 5 | 6 | 10 | 13 | 14 |

(Note that this page is just an illustration. No actual message was delivered anywhere.)

15 | 16 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /example/muriel.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Muriel" 3 | } 4 | -------------------------------------------------------------------------------- /example/submit.html: -------------------------------------------------------------------------------- 1 | 2 | Example form target 3 | 4 | 5 | 6 |

You submitted...

7 | 8 |

 9 | 
10 | 
21 | 
22 | 
23 | 


--------------------------------------------------------------------------------
/example/suzie.json:
--------------------------------------------------------------------------------
1 | {
2 |   "name": "Suzie",
3 |   "spouse": "example/bert.json",
4 |   "mother": "example/muriel.json"
5 | }
6 | 


--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/favicon.ico


--------------------------------------------------------------------------------
/font/Shabnam-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Bold.eot


--------------------------------------------------------------------------------
/font/Shabnam-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Bold.ttf


--------------------------------------------------------------------------------
/font/Shabnam-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Bold.woff


--------------------------------------------------------------------------------
/font/Shabnam-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Bold.woff2


--------------------------------------------------------------------------------
/font/Shabnam-Light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Light.eot


--------------------------------------------------------------------------------
/font/Shabnam-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Light.ttf


--------------------------------------------------------------------------------
/font/Shabnam-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Light.woff


--------------------------------------------------------------------------------
/font/Shabnam-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Light.woff2


--------------------------------------------------------------------------------
/font/Shabnam.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam.eot


--------------------------------------------------------------------------------
/font/Shabnam.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam.ttf


--------------------------------------------------------------------------------
/font/Shabnam.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam.woff


--------------------------------------------------------------------------------
/font/Shabnam.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam.woff2


--------------------------------------------------------------------------------
/font/cinzel_bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/cinzel_bold.woff


--------------------------------------------------------------------------------
/font/pt_mono.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/pt_mono.woff


--------------------------------------------------------------------------------
/img/Hieres-sur-Amby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/Hieres-sur-Amby.png


--------------------------------------------------------------------------------
/img/blockquote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/blockquote.png


--------------------------------------------------------------------------------
/img/boxed-in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/boxed-in.png


--------------------------------------------------------------------------------
/img/button_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/button_disabled.png


--------------------------------------------------------------------------------
/img/canvas_beziercurve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_beziercurve.png


--------------------------------------------------------------------------------
/img/canvas_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_circle.png


--------------------------------------------------------------------------------
/img/canvas_fill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_fill.png


--------------------------------------------------------------------------------
/img/canvas_game.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_game.png


--------------------------------------------------------------------------------
/img/canvas_path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_path.png


--------------------------------------------------------------------------------
/img/canvas_pie_chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_pie_chart.png


--------------------------------------------------------------------------------
/img/canvas_quadraticcurve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_quadraticcurve.png


--------------------------------------------------------------------------------
/img/canvas_scale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_scale.png


--------------------------------------------------------------------------------
/img/canvas_stroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_stroke.png


--------------------------------------------------------------------------------
/img/canvas_tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_tree.png


--------------------------------------------------------------------------------
/img/canvas_triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_triangle.png


--------------------------------------------------------------------------------
/img/cat-animation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/cat-animation.png


--------------------------------------------------------------------------------
/img/cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/cat.png


--------------------------------------------------------------------------------
/img/chapter_picture_00.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_00.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_1.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_10.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_11.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_12.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_13.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_14.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_15.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_16.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_17.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_18.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_19.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_2.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_20.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_21.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_3.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_4.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_5.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_6.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_7.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_8.jpg


--------------------------------------------------------------------------------
/img/chapter_picture_9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_9.jpg


--------------------------------------------------------------------------------
/img/color-field.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/color-field.png


--------------------------------------------------------------------------------
/img/colored-links.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/colored-links.png


--------------------------------------------------------------------------------
/img/control-io.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
10 | synchronous, single thread of controlsynchronous, two threads of controlasynchronous
11 | 


--------------------------------------------------------------------------------
/img/controlflow-if.svg:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 | 
 18 |   
 20 |     
 27 |       
 33 |     
 34 |     
 41 |       
 47 |     
 48 |     
 55 |       
 61 |     
 62 |     
 69 |       
 75 |     
 76 |   
 77 |   
 99 |   
101 |     
102 |       
104 |         image/svg+xml
105 |         
107 |         
108 |       
109 |     
110 |   
111 |   
116 |     
119 |       
125 |       
131 |       
137 |       
143 |       
153 |     
154 |   
155 | 
156 | 


--------------------------------------------------------------------------------
/img/controlflow-loop.svg:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 | 
 18 |   
 20 |     
 27 |       
 33 |     
 34 |     
 41 |       
 47 |     
 48 |     
 55 |       
 61 |     
 62 |     
 69 |       
 75 |     
 76 |   
 77 |   
101 |   
103 |     
104 |       
106 |         image/svg+xml
107 |         
109 |         
110 |       
111 |     
112 |   
113 |   
118 |     
124 |     
130 |     
136 |     
146 |   
147 | 
148 | 


--------------------------------------------------------------------------------
/img/controlflow-straight.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
18 |   
20 |     
27 |       
33 |     
34 |   
35 |   
57 |   
59 |     
60 |       
62 |         image/svg+xml
63 |         
65 |         
66 |       
67 |     
68 |   
69 |   
74 |     
80 |   
81 | 
82 | 


--------------------------------------------------------------------------------
/img/cos_sin.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
11 | cos(¼π)sin(¼π)cos(-⅔π)sin(-⅔π)
12 | 


--------------------------------------------------------------------------------
/img/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/cover.jpg


--------------------------------------------------------------------------------
/img/darkblue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/darkblue.png


--------------------------------------------------------------------------------
/img/display.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/display.png


--------------------------------------------------------------------------------
/img/drag-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/drag-bar.png


--------------------------------------------------------------------------------
/img/exercise_shapes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/exercise_shapes.png


--------------------------------------------------------------------------------
/img/flood-grid.svg:
--------------------------------------------------------------------------------
1 | 
2 | 


--------------------------------------------------------------------------------
/img/form_fields.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/form_fields.png


--------------------------------------------------------------------------------
/img/form_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/form_select.png


--------------------------------------------------------------------------------
/img/game-grid.svg:
--------------------------------------------------------------------------------
1 | 
2 | 


--------------------------------------------------------------------------------
/img/game_simpleLevel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/game_simpleLevel.png


--------------------------------------------------------------------------------
/img/generated/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/generated/.keep


--------------------------------------------------------------------------------
/img/ghostery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/ghostery.png


--------------------------------------------------------------------------------
/img/ghostery_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/ghostery_mini.png


--------------------------------------------------------------------------------
/img/hack_reactor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/hack_reactor.png


--------------------------------------------------------------------------------
/img/hack_reactor_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/hack_reactor_mini.png


--------------------------------------------------------------------------------
/img/hat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/hat.png


--------------------------------------------------------------------------------
/img/help-field.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/help-field.png


--------------------------------------------------------------------------------
/img/highlighted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/highlighted.png


--------------------------------------------------------------------------------
/img/holberton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/holberton.png


--------------------------------------------------------------------------------
/img/home-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/home-page.png


--------------------------------------------------------------------------------
/img/html-boxes.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
23 | herea.I also wrote a book! Read itpHello, I am Marijn and this is...pMy home pageh1bodyMy home pagetitleheadhtml
24 | 


--------------------------------------------------------------------------------
/img/html-links.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
25 | I also wrote a book! ...pHello, I am Marijn...pMy home pageh1body012childNodesfirstChildlastChildpreviousSiblingnextSiblingparentNode
26 | 


--------------------------------------------------------------------------------
/img/line-grid.svg:
--------------------------------------------------------------------------------
1 | 
2 | 


--------------------------------------------------------------------------------
/img/linked-list.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
13 | value: 1rest:value: 2rest:value: 3rest: null
14 | 


--------------------------------------------------------------------------------
/img/middle_east_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/middle_east_graph.png


--------------------------------------------------------------------------------
/img/middle_east_graph_random.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/middle_east_graph_random.png


--------------------------------------------------------------------------------
/img/mirror.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
11 | mirror1234
12 | 


--------------------------------------------------------------------------------
/img/mozilla.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/mozilla.png


--------------------------------------------------------------------------------
/img/mozilla_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/mozilla_mini.png


--------------------------------------------------------------------------------
/img/nextjournal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/nextjournal.png


--------------------------------------------------------------------------------
/img/object.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/object.jpg


--------------------------------------------------------------------------------
/img/object_full.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/object_full.jpg


--------------------------------------------------------------------------------
/img/ostrich.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/ostrich.png


--------------------------------------------------------------------------------
/img/parcel2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/parcel2x.png


--------------------------------------------------------------------------------
/img/pixel_editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/pixel_editor.png


--------------------------------------------------------------------------------
/img/pizza-squirrel.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     
 7 |   
 8 |   
 9 |     
10 |     
11 |     
12 |     
13 |     
14 |     
15 |     
16 |   
17 | 
18 | No squirrel, no pizza76Squirrel, no pizza4No squirrel, pizza9Squirrel, pizza1


--------------------------------------------------------------------------------
/img/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/player.png


--------------------------------------------------------------------------------
/img/player_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/player_big.png


--------------------------------------------------------------------------------
/img/prompt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/prompt.png


--------------------------------------------------------------------------------
/img/rabbits.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
13 | toString: <function>...teeth: "small"speak: <function>killerRabbitteeth: "long, sharp, ..."type: "killer"RabbitprototypeObjectcreate: <function>prototype...
14 | 


--------------------------------------------------------------------------------
/img/robot_idle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/robot_idle.png


--------------------------------------------------------------------------------
/img/robot_idle2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/robot_idle2x.png


--------------------------------------------------------------------------------
/img/robot_moving.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/robot_moving.gif


--------------------------------------------------------------------------------
/img/robot_moving2x.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/robot_moving2x.gif


--------------------------------------------------------------------------------
/img/skillsharing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/skillsharing.png


--------------------------------------------------------------------------------
/img/sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/sprites.png


--------------------------------------------------------------------------------
/img/sprites_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/sprites_big.png


--------------------------------------------------------------------------------
/img/svg-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/svg-demo.png


--------------------------------------------------------------------------------
/img/syntax_tree.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
11 | dodefinex10if>x5print"large"print"small"


--------------------------------------------------------------------------------
/img/tamil.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/tamil.png


--------------------------------------------------------------------------------
/img/transform.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
11 | translate(50, 50)rotate(0.1*Math.PI)rotate(0.1*Math.PI)translate(50, 50)
12 | 


--------------------------------------------------------------------------------
/img/tree_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/tree_graph.png


--------------------------------------------------------------------------------
/img/village.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/village.png


--------------------------------------------------------------------------------
/img/village2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/village2x.png


--------------------------------------------------------------------------------
/img/weresquirrel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/weresquirrel.png


--------------------------------------------------------------------------------
/js/.tern-project:
--------------------------------------------------------------------------------
1 | {
2 |   "libs": ["browser"]
3 | }


--------------------------------------------------------------------------------
/js/chapter_info.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/js/chapter_info.js


--------------------------------------------------------------------------------