Skip to content

Commit 71f3991

Browse files
committed
Added HillClimbing First Diagram for Chapter 4
1 parent 6542592 commit 71f3991

File tree

4 files changed

+324
-179
lines changed

4 files changed

+324
-179
lines changed
+192-145
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,192 @@
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(svg, hill, 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(h, w, svg, hill, 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.svg, this.hill, this.h, this.w);
137+
this.hillClimberDiagram = new HillClimberDiagram(this.h, this.w, this.svg, this.hill, 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

Comments
 (0)