|
1 |
| -$(document).ready(function(){ |
2 |
| - $.ajax({ |
3 |
| - url : "hillClimbing.js", |
4 |
| - dataType: "text", |
5 |
| - success : function (data) { |
6 |
| - $("#hillCode").html(data); |
7 |
| - } |
8 |
| - }); |
9 |
| - |
10 |
| - var two; |
11 |
| - var hillClimber; |
12 |
| - var climberHandle; |
13 |
| - var terrain; |
14 |
| - var hillCanvas; |
15 |
| - var w,h; |
16 |
| - |
17 |
| - var positionX; |
18 |
| - var positionY; |
19 |
| - var terrainSize; |
20 |
| - |
21 |
| - var DELAY = 60 * 1; // 60FPS |
22 |
| - var SIZE = 20; // Size of the square |
23 |
| - |
24 |
| - function init(){ |
25 |
| - // Initialize variables |
26 |
| - hillCanvas = document.getElementById("hillCanvas"); |
27 |
| - hillCanvas.addEventListener("click", handleClick, false); |
28 |
| - w = hillCanvas.offsetWidth,h = 300; |
29 |
| - two = new Two({ width: w, height: h }).appendTo(hillCanvas); |
30 |
| - hillClimber = new HillClimber(); |
31 |
| - terrainSize = w/SIZE; |
32 |
| - seaLevel = h/SIZE/2; |
33 |
| - |
34 |
| - // Generate terrain and draw background |
35 |
| - terrain = generateTerrain(seaLevel,terrainSize); |
36 |
| - drawBackground(terrain); |
37 |
| - |
38 |
| - // The robot starts from position 1 |
39 |
| - // The height depends on the terrain |
40 |
| - positionX = 1; |
41 |
| - positionY = terrain[positionX]; |
42 |
| - |
43 |
| - // Handle for the square drawn on the screen |
44 |
| - // Y axis is inverted |
45 |
| - climberHandle = two.makeRectangle(positionX * SIZE, |
46 |
| - h - positionY * SIZE, |
47 |
| - SIZE,SIZE); |
48 |
| - climberHandle.noStroke(); |
49 |
| - climberHandle.fill = 'rgb(213, 0, 11)'; |
50 |
| - |
51 |
| - two.update(); |
52 |
| - } |
53 |
| - |
54 |
| - init(); |
55 |
| - |
56 |
| - var m_frame = DELAY; |
57 |
| - var direction; |
58 |
| - two.bind('update', function(frameCount) { |
59 |
| - if(m_frame == DELAY){ |
60 |
| - direction = hillClimber.decide(terrain[positionX-1], |
61 |
| - terrain[positionX], |
62 |
| - terrain[positionX+1]); |
63 |
| - //console.log("Direction = " + direction); |
64 |
| - //console.log(terrain[positionX-1] + " " + terrain[positionX+1]); |
65 |
| - positionX += direction; |
66 |
| - m_frame = 0; |
67 |
| - } else { |
68 |
| - m_frame++; |
69 |
| - var t = m_frame/DELAY; // Interpolation factor |
70 |
| - animateClimber(t,direction); |
71 |
| - } |
72 |
| - |
73 |
| - }).play(); |
74 |
| - |
75 |
| - function animateClimber(t,direction){ |
76 |
| - // If not moving then return |
77 |
| - if(!direction) |
78 |
| - return; |
79 |
| - |
80 |
| - var x,y; |
81 |
| - var curX = positionX - direction; |
82 |
| - var curY = terrain[curX]; |
83 |
| - var newX = curX + direction; |
84 |
| - var newY = terrain[newX]; |
85 |
| - |
86 |
| - if(t < .5){ |
87 |
| - // Go up |
88 |
| - t1 = t * 2; |
89 |
| - x = curX; |
90 |
| - y = t1 * newY + (1-t1) * curY; |
91 |
| - } else { |
92 |
| - // Go in the direction |
93 |
| - t1 = (t-.5) * 2; |
94 |
| - y = newY; |
95 |
| - x = t1 * newX + (1-t1) * curX; |
96 |
| - } |
97 |
| - |
98 |
| - // Translate the climber |
99 |
| - climberHandle.translation.set(x*SIZE,h - y*SIZE); |
100 |
| - } |
101 |
| - |
102 |
| - function drawBackground(terrain){ |
103 |
| - for(var x = 0; x < terrain.length; x++){ |
104 |
| - y = terrain[x]; |
105 |
| - two.makeRectangle(x * SIZE, |
106 |
| - h - (y-1) * SIZE, |
107 |
| - SIZE,SIZE) |
108 |
| - .noStroke() |
109 |
| - .fill = 'rgb(100, 155, 100)'; |
110 |
| - } |
111 |
| - } |
112 |
| - |
113 |
| - function generateTerrain(seaLevel,terrainSize){ |
114 |
| - /* |
115 |
| - var terrain = [seaLevel]; |
116 |
| - for(var i = 0; i < terrainSize; i++){ |
117 |
| - index = terrain.length-1; |
118 |
| - if(Math.random() >= .5){ |
119 |
| - terrain.push(terrain[index]+1); |
120 |
| - } else { |
121 |
| - terrain.push(terrain[index]-1); |
122 |
| - } |
123 |
| - } |
124 |
| -
|
125 |
| - return terrain;*/ |
126 |
| - |
127 |
| - // Handcrafted terrain |
128 |
| - return [2,3,4,5,4,3,2,3,4,5,6,7,6,5,4,3,2,3,4,5,6,5,4,3,2,3,4,5,6,7,6,5,4,3,2,1]; |
129 |
| - } |
130 |
| - |
131 |
| - var offset = $('#hillCanvas').offset(); |
132 |
| - function handleClick(evt){ |
133 |
| - m_frame = DELAY; |
134 |
| - var x = (evt.pageX - offset.left); |
135 |
| - //var y = (evt.pageY - offset.top); |
136 |
| - |
137 |
| - positionX = parseInt((x+SIZE/2)/SIZE); |
138 |
| - //console.log(parseInt(x) + " " +positionX); |
139 |
| - positionY = terrain[positionX]; |
140 |
| - |
141 |
| - climberHandle.translation.set(positionX*SIZE, |
142 |
| - h - positionY*SIZE); |
143 |
| - } |
144 |
| - |
145 |
| - }); |
| 1 | +$(document).ready(function() { |
| 2 | + |
| 3 | + |
| 4 | + //Class to draw the hills |
| 5 | + class HillDiagram { |
| 6 | + constructor(hill, svg, h, w) { |
| 7 | + this.padding = 20; |
| 8 | + this.hill = hill |
| 9 | + this.h = h; |
| 10 | + this.w = w; |
| 11 | + this.svg = svg; |
| 12 | + this.states = this.hill.getStates(); |
| 13 | + this.hillData = []; |
| 14 | + for (let i = 0; i < this.states.length; i++) { |
| 15 | + this.hillData.push({ |
| 16 | + state: i, |
| 17 | + objective: this.states[i], |
| 18 | + visited: false, |
| 19 | + maxima: false |
| 20 | + }) |
| 21 | + } |
| 22 | + |
| 23 | + let maximas = this.hill.getBestStates(); |
| 24 | + for (let i = 0; i < maximas.length; i++) { |
| 25 | + this.hillData[maximas[i]].maxima = true; |
| 26 | + } |
| 27 | + |
| 28 | + |
| 29 | + this.xScale = d3.scaleLinear() |
| 30 | + .domain([0, this.states.length]) |
| 31 | + .range([this.padding, this.w - this.padding]); |
| 32 | + this.blockWidth = (this.xScale(this.states.length) - this.xScale(0)) / this.states.length; |
| 33 | + this.yScale = d3.scaleLinear() |
| 34 | + .domain([0, d3.max(this.states)]) |
| 35 | + .range([this.padding, this.h - this.padding]); |
| 36 | + this.renderHill(); |
| 37 | + |
| 38 | + } |
| 39 | + renderHill() { |
| 40 | + this.svgHill = this.svg.selectAll('.block') |
| 41 | + .data(this.hillData) |
| 42 | + .enter() |
| 43 | + .append('g') |
| 44 | + .attr('class', 'block'); |
| 45 | + this.svgRects = this.svgHill |
| 46 | + .append('rect') |
| 47 | + .attr('height', d => this.yScale(d.objective)) |
| 48 | + .attr('width', this.blockWidth) |
| 49 | + .attr('x', (d) => this.xScale(d.state)) |
| 50 | + .attr('y', (d) => this.h - this.yScale(d.objective)) |
| 51 | + .style('opacity', (d) => (d.visited) ? 1 : 0) |
| 52 | + .style('fill', 'hsl(217,61.2%,53.5%)') |
| 53 | + .style('border', '1px solid'); |
| 54 | + } |
| 55 | + |
| 56 | + visit(state) { |
| 57 | + this.hillData[state].visited = true; |
| 58 | + this.svgRects.filter((d) => d.state == state) |
| 59 | + .transition() |
| 60 | + .duration(100) |
| 61 | + .style('opacity', 1); |
| 62 | + } |
| 63 | + |
| 64 | + showAll() { |
| 65 | + this.svgRects.transition() |
| 66 | + .duration(100) |
| 67 | + .style('opacity', (d) => { |
| 68 | + return (d.visited) ? 1 : 0.6; |
| 69 | + }) |
| 70 | + .style('fill', (d) => { |
| 71 | + if (d.maxima) { |
| 72 | + if (d.visited) { |
| 73 | + return 'hsl(110, 100%, 38%)'; |
| 74 | + } else { |
| 75 | + return 'hsl(102, 100%, 56%)'; |
| 76 | + } |
| 77 | + } else { |
| 78 | + return 'hsl(217,61.2%,53.5%)'; |
| 79 | + } |
| 80 | + }) |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + //Class to draw the robot |
| 85 | + class HillClimberDiagram { |
| 86 | + constructor(hill, svg, h, w, hillDiagram, hillClimber) { |
| 87 | + this.h = h; |
| 88 | + this.w = w; |
| 89 | + this.hill = hill; |
| 90 | + this.hillDiagram = hillDiagram; |
| 91 | + this.hillClimber = hillClimber; |
| 92 | + this.states = this.hill.getStates(); |
| 93 | + this.svg = svg; |
| 94 | + this.xScale = this.hillDiagram.xScale; |
| 95 | + this.yScale = this.hillDiagram.yScale; |
| 96 | + this.blockWidth = this.hillDiagram.blockWidth; |
| 97 | + this.renderHillClimber(); |
| 98 | + } |
| 99 | + renderHillClimber() { |
| 100 | + let robotLocation = this.hillClimber.getCurrentState(); |
| 101 | + let robotStateValue = this.states[robotLocation]; |
| 102 | + this.robot = this.svg.append('g') |
| 103 | + .attr('class', 'robot') |
| 104 | + .append('rect') |
| 105 | + .attr('height', '10px') |
| 106 | + .attr('width', this.blockWidth) |
| 107 | + .attr('x', this.xScale(robotLocation)) |
| 108 | + .attr('y', this.h - this.yScale(robotStateValue) - 10) |
| 109 | + .style('fill', 'red'); |
| 110 | + this.hillDiagram.visit(robotLocation); |
| 111 | + |
| 112 | + } |
| 113 | + move(state) { |
| 114 | + let robotLocation = state; |
| 115 | + let robotStateValue = this.states[state]; |
| 116 | + this.robot.transition() |
| 117 | + .duration(100) |
| 118 | + .attr('x', this.xScale(robotLocation)) |
| 119 | + .attr('y', this.h - this.yScale(robotStateValue) - 10); |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + //Wrapper class for the entire diagram |
| 124 | + class HillClimbingDiagram { |
| 125 | + constructor(selector, h, w) { |
| 126 | + this.h = h; |
| 127 | + this.w = w; |
| 128 | + this.svg = d3.select(selector).html("") |
| 129 | + .append('svg') |
| 130 | + .attr('height', this.h) |
| 131 | + .attr('width', this.w) |
| 132 | + .attr('border', 2); |
| 133 | + |
| 134 | + this.hill = new Hill(); |
| 135 | + this.hillClimber = new HillClimber(this.hill); |
| 136 | + this.hillDiagram = new HillDiagram(this.hill, this.svg, this.h, this.w); |
| 137 | + this.hillClimberDiagram = new HillClimberDiagram(this.hill, this.svg, this.h, this.w, this.hillDiagram, this.hillClimber); |
| 138 | + this.bindClicks(); |
| 139 | + |
| 140 | + this.borderPath = this.svg.append('rect') |
| 141 | + .attr('x', this.hillDiagram.padding) |
| 142 | + .attr('y', 0) |
| 143 | + .attr('height', this.h) |
| 144 | + .attr('width', this.w - 2 * this.hillDiagram.padding) |
| 145 | + .style('stroke', 'black') |
| 146 | + .style('fill', 'none') |
| 147 | + .style('stroke-width', 1); |
| 148 | + |
| 149 | + this.moves = 0; |
| 150 | + this.maxMoves = 15; |
| 151 | + this.moveAllowed = true; |
| 152 | + this.updateMoves(); |
| 153 | + } |
| 154 | + |
| 155 | + bindClicks() { |
| 156 | + this.clickHandler = () => { |
| 157 | + if (this.moveAllowed) { |
| 158 | + let state = Math.floor(this.hillDiagram.xScale.invert(d3.mouse(this.svg.node())[0])); |
| 159 | + if (state >= 0 && state < 100) { |
| 160 | + this.hillClimber.changeState(state); |
| 161 | + this.hillDiagram.visit(state); |
| 162 | + this.hillClimberDiagram.move(state); |
| 163 | + this.moves++; |
| 164 | + this.updateMoves(); |
| 165 | + if (this.moves >= this.maxMoves) { |
| 166 | + this.moveAllowed = false; |
| 167 | + this.hillDiagram.showAll(); |
| 168 | + } |
| 169 | + } |
| 170 | + } |
| 171 | + }; |
| 172 | + |
| 173 | + this.svg.on('mousedown', this.clickHandler); |
| 174 | + } |
| 175 | + |
| 176 | + updateMoves() { |
| 177 | + let leftMoves = this.maxMoves - this.moves; |
| 178 | + if (leftMoves >= 0) { |
| 179 | + d3.select('#hillMoves').html('Moves Left :' + (this.maxMoves - this.moves)); |
| 180 | + } |
| 181 | + } |
| 182 | + finish() { |
| 183 | + this.hillDiagram.showAll(); |
| 184 | + } |
| 185 | + } |
| 186 | + |
| 187 | + function init() { |
| 188 | + var diagram = new HillClimbingDiagram('#hillCanvas', 500, 1000); |
| 189 | + } |
| 190 | + init(); |
| 191 | + $('#hillClimbRestart').click(init); |
| 192 | +}); |
0 commit comments