Webster van Robot help

S

Steve Howell

I've written my first nontrivial JS program, and I have a few general
questions about it.

First of all, I've only been able to get it working on Firefox, and
I'd like advice on getting it to work under IE. I'm most interested
in specific mistakes that I've made that affect IE, but I also want to
learn how to debug IE in general.

The program allows a user to create a little world of walls in a 2d
grid. The user moves a robot around the world and builds walls.
Right now the program is just a toy, but I'm hoping to turn it into
something educational, and it's open source FWIW.

Some questions about the program:

1) I tried to make it OO, but I'm not sure I have all the JS idioms
down.
2) I had to do a strange workaround for Firefox. When I update the
DOM to give a table cell a right border, all the cells below it
inherit that border. Although it's partly a CSS question, it seems to
happen only when I update the DOM via JS.
3) In general, how can I improve this program?

Thanks in advance.

Here is the program, which is all self-contained. Sorry if it's a bit
long.

<html>

<style type="text/css">
table {border-collapse: collapse;}
table.wvr th {background: #EEEEEE}
table.wvr td {background: #FFFF00}
table.wvr td, th {height: 40px; width: 40px; text-align: center}
..heading {background: #FF00FF}
..north {border-top: solid red;}
..east {border-right: solid red;}
..no_north {border-top: solid white;}
..no_east {border-right: solid white;}
..robot {background: #FF8888}
..beeper {background: #00FFFF}
..tiny {font-size: 10px}
</style>


<body onload="create_world()">
<script type="text/javascript">
/*
Webster van Robot.
(c) GNU General Public license
*/


function showMsg(the_message) {
document.the_form.the_text.value += (the_message + "\n");
}

function clearMsg() {
document.the_form.the_text.value = ''
}

function World() {
this.robot_x = 1;
this.robot_y = 1;
this.robot_dir = "E";
this.north_walls = [];
this.east_walls = [];
this.num_aves = 12
this.num_streets = 10

function _coords() {
return this.robot_x + "," + this.robot_y;
}
this._coords = _coords;

function wall_on_north(x, y)
{
if (y == 0 || y == this.num_streets) {
return true
}
coords = x + "," + y;
return (this.north_walls[coords] == 1)
}
this.wall_on_north = wall_on_north

function wall_on_south(x, y)
{
return this.wall_on_north(x, y - 1)
}
this.wall_on_south = wall_on_south

function wall_on_east(x, y)
{
if (x == 0 || x == this.num_aves) {
return true
}
coords = x + "," + y;
return (this.east_walls[coords] == 1)
}
this.wall_on_east = wall_on_east

function wall_on_west(x, y)
{
return this.wall_on_east(x - 1, y)
}
this.wall_on_west = wall_on_west

function is_facing_wall() {
if (this.robot_dir == "N") {
return this.wall_on_north(this.robot_x, this.robot_y)
}
else if (this.robot_dir == "W") {
return this.wall_on_west(this.robot_x, this.robot_y)
}
else if (this.robot_dir == "S") {
return this.wall_on_south(this.robot_x, this.robot_y)
}
else {
return this.wall_on_east(this.robot_x, this.robot_y)
}
}
this.is_facing_wall = is_facing_wall

function move() {
if (this.is_facing_wall()) {
return 'blocked by wall'
}
if (this.robot_dir == "N") { this.robot_y += 1;}
else if (this.robot_dir == "W") { this.robot_x -= 1;}
else if (this.robot_dir == "S") { this.robot_y -= 1;}
else { this.robot_x += 1;}
}
this.move = move;

function turnleft() {
if (this.robot_dir == "N") { this.robot_dir = "W";}
else if (this.robot_dir == "W") { this.robot_dir = "S";}
else if (this.robot_dir == "S") { this.robot_dir = "E";}
else { this.robot_dir = "N";}
}
this.turnleft = turnleft;

function turnright() {
if (this.robot_dir == "N") { this.robot_dir = "E";}
else if (this.robot_dir == "W") { this.robot_dir = "N";}
else if (this.robot_dir == "S") { this.robot_dir = "W";}
else { this.robot_dir = "S";}
}
this.turnright = turnright;

function build_east_wall(x, y) {
if (this.wall_on_east(x, y)) {
return 'There is already a wall there'
}
coords = x + "," + y;
this.east_walls[coords] = 1;
}
this.build_east_wall = build_east_wall

function build_west_wall(x, y) {
return this.build_east_wall(x-1, y)
}
this.build_west_wall = build_west_wall

function build_north_wall(x, y) {
if (this.wall_on_north(x, y)) {
return 'There is already a wall there'
}
coords = x + "," + y;
this.north_walls[coords] = 1;
}
this.build_north_wall = build_north_wall

function build_south_wall(x, y) {
return this.build_north_wall(x, y-1)
}
this.build_south_wall = build_south_wall

function build_wall_on_left() {
x = this.robot_x;
y = this.robot_y;
if (this.robot_dir == "N") {
return this.build_west_wall(x, y)
}
else if (this.robot_dir == "S") {
return this.build_east_wall(x, y)
}
else if (this.robot_dir == "E") {
return this.build_north_wall(x, y)
}
else if (this.robot_dir == "W") {
return this.build_south_wall(x, y)
}
}
this.build_wall_on_left = build_wall_on_left;

function robot() {
if (this.robot_dir == "N") {
return '^'
}
else if (this.robot_dir == "S") {
return 'v'
}
else if (this.robot_dir == "E") {
return '>'
}
else if (this.robot_dir == "W") {
return '<'
}
}
this.robot = robot;

function render(avenue, street)
{
var data = ''
var klass = ''
var coords = avenue + ',' + street

if (this.north_walls[coords] == 1) {
klass += 'north'
}
else {
klass += 'no_north'
}

if (this.east_walls[coords] == 1) {
klass += ' east'
}
else {
klass += ' no_east'
}

if (this.robot_x == avenue && this.robot_y == street) {
data = '<div class="robot">' + this.robot() + '</div>'
}
else {
data = '<div class="tiny">' + coords + '</div>'
}

var text = '<td class="' + klass + '">' + data + '</td>'
return text
}
this.render = render
}

the_world = new World()

function start_over() {
the_world = new World()
redraw_grid()
}

function redraw_street(street)
{
/* showMsg('redraw' + street) */
row = document.getElementById("street"+street)

/* row = document.getElementById("street"+street) */
var text = '<th class="st east">S' + street + '</th>'
for (var ave = 1; ave <= the_world.num_aves; ++ave) {
text += the_world.render(ave, street)
}
row.innerHTML = text;
row.id = 'street' + street
}

function create_world() {
ave_row = document.getElementById("ave_heading")
text = '<th></th>'
for (var ave = 1; ave <= the_world.num_aves; ++ave) {
text += '<th class="ave">A' + ave + '</td>'
}
ave_row.innerHTML = text

table = document.getElementById("world")
for (var street = 1; street <= the_world.num_streets; ++street) {
x = table.insertRow(2)
x.id = "street"+street
}
redraw_grid()
}

function redraw_grid() {
for (var street = 1; street <= the_world.num_streets; ++street) {
redraw_street(street)
}
}

function redraw_to_bottom(street) {
for (var i = street; i >= 1; --i) {
redraw_street(i)
}
}

function move() {
old_street = the_world.robot_y
problem = the_world.move()
if (problem) {
alert(problem)
return problem
}
showMsg('move')
new_street = the_world.robot_y
redraw_street(old_street)
redraw_street(new_street)
}

function turnleft() {
showMsg('turnleft')
the_world.turnleft()
street = the_world.robot_y
redraw_street(street)
}

function turnright() {
showMsg('turnright')
the_world.turnright()
street = the_world.robot_y
redraw_street(street)
}


function build_wall_on_left() {
problem = the_world.build_wall_on_left()
if (problem) {
alert(problem)
return problem
}
showMsg('build_wall_on_left')
var street = the_world.robot_y
redraw_to_bottom(street) /* browsers are stupid */
}

</script>

<table>
<tr>
<td valign="top">
<a href="#" onClick="move(); return false;">move</a>
<a href="#" onClick="turnleft(); return false;">turnleft</a>
<a href="#" onClick="turnright(); return false;">turnright</a>
<a href="#" onClick="build_wall_on_left(); return
false;">build_wall_on_left</a>
<a href="#" onClick="start_over(); return false;">start_over</a>



<table id="world" class="wvr">
<tr>
<th colspan=13 class="heading">Webster van Robot's World</th>
</tr>
<tr id="ave_heading">
</tr>
<tr class="north"></tr>
</table>


</td>
<td>

<form name="the_form">
<textarea name="the_text" rows=30 cols=40>
</textarea>
</form>

</td>
</tr>
</table>

</body>
</html>
 
D

David Mark

I've written my first nontrivial JS program, and I have a few general
questions about it.

First of all, I've only been able to get it working on Firefox, and
I'd like advice on getting it to work under IE.  I'm most interested
in specific mistakes that I've made that affect IE, but I also want to
learn how to debug IE in general.

Install Microsoft Script Debugger or Visual studio.
The program allows a user to create a little world of walls in a 2d
grid.  The user moves a robot around the world and builds walls.
Right now the program is just a toy, but I'm hoping to turn it into
something educational, and it's open source FWIW.

Some questions about the program:

  1) I tried to make it OO, but I'm not sure I have all the JS idioms
down.
  2) I had to do a strange workaround for Firefox.  When I update the
DOM to give a table cell a right border, all the cells below it
inherit that border.  Although it's partly a CSS question, it seems to
happen only when I update the DOM via JS.
  3) In general, how can I improve this program?

Thanks in advance.

Here is the program, which is all self-contained.  Sorry if it's a bit
long.

<html>

First off, add a doctype. Quirks mode may be responsible for your
border issue.
<style type="text/css">
table {border-collapse: collapse;}
table.wvr th {background: #EEEEEE}

background-color:#EEEEEE; color:inherit
table.wvr td {background: #FFFF00}
table.wvr td, th {height: 40px; width: 40px; text-align: center}
.heading {background: #FF00FF}
.north {border-top: solid red;}
.east {border-right: solid red;}
.no_north {border-top: solid white;}
.no_east {border-right: solid white;}
.robot {background: #FF8888}
.beeper {background: #00FFFF}
.tiny {font-size: 10px}

Tiny is relative. Pixels are not.

font-size: 80%
</style>

<body onload="create_world()">
<script type="text/javascript">
/*
Webster van Robot.
(c) GNU General Public license
*/

function showMsg(the_message) {
  document.the_form.the_text.value += (the_message + "\n");

document.forms['the_form'].elements['the_test'].value += ...

And you don't need the parentheses.
}

function clearMsg() {
  document.the_form.the_text.value = ''

See previous comment.
}

function World() {
  this.robot_x = 1;
  this.robot_y = 1;
  this.robot_dir = "E";
  this.north_walls = [];
  this.east_walls = [];
  this.num_aves = 12
  this.num_streets = 10

I would think some of these should be parameters.
  function _coords() {
    return this.robot_x + "," + this.robot_y;
  }
  this._coords = _coords;

Why the leading underscore? What is that supposed to denote?
  function wall_on_north(x, y)
  {
    if (y == 0 || y == this.num_streets) {
      return true
    }
    coords = x + "," + y;
    return (this.north_walls[coords] == 1)
  }
  this.wall_on_north = wall_on_north

Your semi-colon key seems to have broke at this point.
  function wall_on_south(x, y)
  {
    return this.wall_on_north(x, y - 1)
  }
  this.wall_on_south = wall_on_south

  function wall_on_east(x, y)
  {
    if (x == 0 || x == this.num_aves) {
      return true
    }
    coords = x + "," + y;
    return (this.east_walls[coords] == 1)
  }
  this.wall_on_east = wall_on_east

  function wall_on_west(x, y)
  {
    return this.wall_on_east(x - 1, y)
  }
  this.wall_on_west = wall_on_west

  function is_facing_wall() {
    if (this.robot_dir == "N") {
      return this.wall_on_north(this.robot_x, this.robot_y)
    }
    else if (this.robot_dir == "W") {
      return this.wall_on_west(this.robot_x, this.robot_y)
    }
    else if (this.robot_dir == "S") {
      return this.wall_on_south(this.robot_x, this.robot_y)
    }
    else {
      return this.wall_on_east(this.robot_x, this.robot_y)
    }
  }

Use a switch statement.
  this.is_facing_wall = is_facing_wall

  function move() {
    if (this.is_facing_wall()) {
      return 'blocked by wall'
    }
    if (this.robot_dir == "N") { this.robot_y += 1;}
    else if (this.robot_dir == "W") { this.robot_x -= 1;}
    else if (this.robot_dir == "S") { this.robot_y -= 1;}
    else { this.robot_x += 1;}

See previous comment.

[snip]

Why are all of the methods nested in the constructor? If you are
going to instantiate more than one of these (and if not, it doesn't
need to be a constructor), it will be more efficient to define the
methods as properties of the World prototype.
}

the_world = new World()

function start_over() {
  the_world = new World()
  redraw_grid()

}

function redraw_street(street)
{
  /* showMsg('redraw' + street) */
  row = document.getElementById("street"+street)

  /* row = document.getElementById("street"+street) */
  var text = '<th class="st east">S' + street + '</th>'
  for (var ave = 1; ave <= the_world.num_aves; ++ave) {
    text += the_world.render(ave, street)
  }
  row.innerHTML = text;

Here is where you are going to have trouble with IE. Your best bet is
to rewrite the bits that use innerHTML (use DOM methods instead.)

[snip]
    x = table.insertRow(2)

Like that.

[smip]
<table>
<tr>
<td valign="top">
<a href="#" onClick="move(); return false;">move</a>
<a href="#" onClick="turnleft(); return false;">turnleft</a>
<a href="#" onClick="turnright(); return false;">turnright</a>
<a href="#" onClick="build_wall_on_left(); return
false;">build_wall_on_left</a>
<a href="#" onClick="start_over(); return false;">start_over</a>

These links (should really be buttons) should be generated with
script. They aren't going to do anything useful without script.

[snip]
 
S

Steve Howell

Pixel is a relative unit, it is relative to the display device whereas %
is relative to the default font size.

<URL:http://www.w3.org/TR/CSS21/syndata.html#length-units>

Thanks Rob, that's a helpful clarification. I went with the 80%
anyway, as my intent is to make "tiny" relative to the default font
size.

David, thanks for your very thorough review. I'm working through
making the easy fixes, but as you probably expect, I will still have
lots of questions.
 
D

David Mark

Pixel is a relative unit, it is relative to the display device whereas %
is relative to the default font size.

For the rule to make sense, the unit must be relative to the other
font sizes on the page.
 
S

Steve Howell

Install Microsoft Script Debugger or Visual studio.

First, thanks for all your suggestions.

I will try the tools above soon, but right now I'm borrowing somebody
else's computer, so I'm reluctant to install software.

Are there any tricks for debugging IE as it comes shipped? I am
getting cryptic errors like this:

Line: 19
Char: 1
Error: Object expected

background-color:#EEEEEE; color:inherit

Done, although I don't understand what I'm fixing here.
Tiny is relative. Pixels are not.

font-size: 80%

Fixed.
function showMsg(the_message) {
document.the_form.the_text.value += (the_message + "\n");

document.forms['the_form'].elements['the_test'].value += ...

Done.
function World() {
this.robot_x = 1;
this.robot_y = 1;
this.robot_dir = "E";
this.north_walls = [];
this.east_walls = [];
this.num_aves = 12
this.num_streets = 10

I would think some of these should be parameters.

Eventually. As you notice in your later comments, the World() class
for now is just a singleton. The only reason I make it a class for
now is simple encapsulation. I want some of the implementation
details of the World model hidden from the rest of my code.
Why the leading underscore? What is that supposed to denote?

I was just using a naming convention to suggest that _coords was an
internal implementation detail, and that callers would not want to
call it. It turns out it was dead code anyway, so I removed it.
Your semi-colon key seems to have broke at this point.

I've obviously been spoiled by Python, Ruby, Firefox JS, etc. I have
tried to sprinkle in semicolons to fix IE, but IE is still broken.
Use a switch statement.

Thanks, good suggestion. I have since used a switch statement in a
couple places, as well as associative arrays.
Why are all of the methods nested in the constructor? If you are
going to instantiate more than one of these (and if not, it doesn't
need to be a constructor), it will be more efficient to define the
methods as properties of the World prototype.

Efficiency is not a major goal for now. All I want is simple
encapsulation. I'm still getting my head around JS's object model.
Here is where you are going to have trouble with IE. Your best bet is
to rewrite the bits that use innerHTML (use DOM methods instead.)

I would prefer to use the DOM, but when I tried that at first, I was
running into issues. I may have been fighting some red herrings due
to CSS, though, so I will try soon to go back to a more DOM-oriented
style.

These links (should really be buttons) should be generated with
script. They aren't going to do anything useful without script.

I'm not sure what you mean by that. The links work exactly as I
expect. When I click move, the robot does in fact move.

Here's the latest version:

<html>

<style type="text/css">
table {border-collapse: collapse;}
table.wvr th {background-color: #EEEEEE; color: inherit;}
table.wvr td {background: #FFFF00}
table.wvr td, th {height: 40px; width: 40px; text-align: center}
..heading {background: #FF00FF}
..north {border-top: solid red;}
..east {border-right: solid red;}
..no_north {border-top: solid white;}
..no_east {border-right: solid white;}
..robot {background: #FF8888}
..beeper {background: #00FFFF}
..tiny {font-size: 70%}
</style>


<body onload="create_world()">
<script type="text/javascript">
/*
Webster van Robot.
(c) GNU General Public license
*/

function text_box() {
return document.forms['the_form'].elements['the_text'];
}

function showMsg(the_message) {
text_box().value += the_message + "\n";
}

function clearMsg() {
text_box().value = '';
}

function World() {
this.robot_x = 1;
this.robot_y = 1;
this.robot_dir = "E";
this.north_walls = [];
this.east_walls = [];
this.num_aves = 12;
this.num_streets = 10;
this.left = {
'N': 'W',
'W': 'S',
'S': 'E',
'E': 'N'};
this.right = {
'N': 'E',
'E': 'S',
'S': 'W',
'W': 'N'};
this.robot_char = {
'N': '^',
'E': '>',
'S': 'v',
'W': '<',
}

function wall_on_north(x, y)
{
if (y == 0 || y == this.num_streets) {
return true;
}
coords = x + "," + y;
return (this.north_walls[coords] == 1);
}
this.wall_on_north = wall_on_north;

function wall_on_south(x, y)
{
return this.wall_on_north(x, y - 1);
}
this.wall_on_south = wall_on_south;

function wall_on_east(x, y)
{
if (x == 0 || x == this.num_aves) {
return true;
}
coords = x + "," + y;
return (this.east_walls[coords] == 1);
}
this.wall_on_east = wall_on_east;

function wall_on_west(x, y)
{
return this.wall_on_east(x - 1, y);
}
this.wall_on_west = wall_on_west;

function is_facing_wall() {
switch (this.robot_dir) {
case 'N':
return this.wall_on_north(this.robot_x, this.robot_y);
case 'W':
return this.wall_on_west(this.robot_x, this.robot_y);
case 'S':
return this.wall_on_south(this.robot_x, this.robot_y);
case 'E':
return this.wall_on_east(this.robot_x, this.robot_y);
}
}
this.is_facing_wall = is_facing_wall;

function move() {
if (this.is_facing_wall()) {
return 'blocked by wall';
}
switch (this.robot_dir) {
case 'N': this.robot_y += 1; break;
case 'W': this.robot_x -= 1; break;
case 'S': this.robot_y -= 1; break;
case 'E': this.robot_x += 1; break;
}
}
this.move = move;

function turnleft() {
this.robot_dir = this.left[this.robot_dir];
}
this.turnleft = turnleft;

function turnright() {
this.robot_dir = this.right[this.robot_dir];
}
this.turnright = turnright;

function build_east_wall(x, y) {
if (this.wall_on_east(x, y)) {
return 'There is already a wall there';
}
coords = x + "," + y;
this.east_walls[coords] = 1;
}
this.build_east_wall = build_east_wall;

function build_west_wall(x, y) {
return this.build_east_wall(x-1, y);
}
this.build_west_wall = build_west_wall;

function build_north_wall(x, y) {
if (this.wall_on_north(x, y)) {
return 'There is already a wall there';
}
coords = x + "," + y;
this.north_walls[coords] = 1;
}
this.build_north_wall = build_north_wall;

function build_south_wall(x, y) {
return this.build_north_wall(x, y-1);
}
this.build_south_wall = build_south_wall;

function build_wall_on_left() {
x = this.robot_x;
y = this.robot_y;
switch (this.robot_dir) {
case 'N': return this.build_west_wall(x, y);
case 'S': return this.build_east_wall(x, y);
case 'E': return this.build_north_wall(x, y);
case 'W': return this.build_south_wall(x, y);
}
}
this.build_wall_on_left = build_wall_on_left;

function robot() {
return this.robot_char[this.robot_dir];
}
this.robot = robot;

function render(avenue, street)
{
var data = '';
var klass = '';
var coords = avenue + ',' + street;

if (this.north_walls[coords] == 1) {
klass += 'north';
}
else {
klass += 'no_north';
}

if (this.east_walls[coords] == 1) {
klass += ' east';
}
else {
klass += ' no_east';
}

if (this.robot_x == avenue && this.robot_y == street) {
data = '<div class="robot">' + this.robot() + '</div>';
}
else {
data = '<div class="tiny">' + coords + '</div>';
}

var text = '<td class="' + klass + '">' + data + '</td>';
return text;
}
this.render = render;
}

the_world = new World();

function start_over() {
the_world = new World();
redraw_grid();
clearMsg();
}

function redraw_street(street)
{
row = document.getElementById("street"+street);

var text = '<th class="st east">S' + street + '</th>';
for (var ave = 1; ave <= the_world.num_aves; ++ave) {
text += the_world.render(ave, street);
}
row.innerHTML = text;
row.id = 'street' + street;
}

function create_world() {
ave_row = document.getElementById("ave_heading");
text = '<th></th>';
for (var ave = 1; ave <= the_world.num_aves; ++ave) {
text += '<th class="ave">A' + ave + '</td>';
}
ave_row.innerHTML = text;

table = document.getElementById("world");
for (var street = 1; street <= the_world.num_streets; ++street) {
x = table.insertRow(2);
x.id = "street"+street;
}
redraw_grid();
}

function redraw_grid() {
for (var street = 1; street <= the_world.num_streets; ++street) {
redraw_street(street);
}
}

function redraw_to_bottom(street) {
for (var i = street; i >= 1; --i) {
redraw_street(i);
}
}

function move() {
old_street = the_world.robot_y;
problem = the_world.move();
if (problem) {
alert(problem);
return problem;
}
showMsg('move');
new_street = the_world.robot_y;
redraw_street(old_street);
redraw_street(new_street);
}

function turnleft() {
showMsg('turnleft');
the_world.turnleft();
street = the_world.robot_y;
redraw_street(street);
}

function turnright() {
showMsg('turnright');
the_world.turnright();
street = the_world.robot_y;
redraw_street(street);
}


function build_wall_on_left() {
problem = the_world.build_wall_on_left()
if (problem) {
alert(problem);
return problem;
}
showMsg('build_wall_on_left');
var street = the_world.robot_y;
redraw_to_bottom(street); /* browsers are stupid */
}
</script>

<table>
<tr>
<td valign="top">
<a href="#" onClick="move(); return false;">move</a>
<a href="#" onClick="turnleft(); return false;">turnleft</a>
<a href="#" onClick="turnright(); return false;">turnright</a>
<a href="#" onClick="build_wall_on_left(); return
false;">build_wall_on_left</a>
<a href="#" onClick="start_over(); return false;">start_over</a>



<table id="world" class="wvr">
<tr>
<th colspan=13 class="heading">Webster van Robot's World</th>
</tr>
<tr id="ave_heading">
</tr>
<tr class="north"></tr>
</table>


</td>
<td>

<form name="the_form">
<textarea name="the_text" rows=30 cols=40>
</textarea>
</form>

</td>
</tr>
</table>

</body>
</html>
 
D

David Mark

First, thanks for all your suggestions.

I will try the tools above soon, but right now I'm borrowing somebody
else's computer, so I'm reluctant to install software.

Are there any tricks for debugging IE as it comes shipped?  I am
getting cryptic errors like this:

  Line: 19
  Char: 1
  Error: Object expected

Is the line number relative to the <script> statement?

I don't think so. Regardless, IE is known to report the wrong line
number for inline scripts. It will make things easier to put the
script in another file.
Done, although I don't understand what I'm fixing here.

Always specify both colors. You don't know what a user might specify
in their style sheets. What if the color for the element is #EEEEEE?
By the same token, make sure that the element inherits a color defined
in your style sheet.
Tiny is relative.  Pixels are not.
font-size: 80%
Fixed.
document.forms['the_form'].elements['the_test'].value += ...
Done.


function World() {
  this.robot_x = 1;
  this.robot_y = 1;
  this.robot_dir = "E";
  this.north_walls = [];
  this.east_walls = [];
  this.num_aves = 12
  this.num_streets = 10
I would think some of these should be parameters.

Eventually.  As you notice in your later comments, the World() class
for now is just a singleton.  The only reason I make it a class for
now is simple encapsulation.  I want some of the implementation
details of the World model hidden from the rest of my code.

IIRC, you didn't hide anything. You could just as well have used
object literal notation.
I was just using a naming convention to suggest that _coords was an
internal implementation detail, and that callers would not want to
call it.  It turns out it was dead code anyway, so I removed it.

If it was meant to be internal, you should have made it a nested
function, rather than a property of the object. Then you wouldn't
have had to rely on suggestion.
I've obviously been spoiled by Python, Ruby, Firefox JS, etc.  I have
tried to sprinkle in semicolons to fix IE, but IE is still broken.

It isn't an issue specific to IE. It is just bad practice to rely on
automatic semi-colon insertion.
Thanks, good suggestion.  I have since used a switch statement in a
couple places, as well as associative arrays.

There is no such thing as associative arrays in JavaScript.

[snip]
I would prefer to use the DOM, but when I tried that at first, I was
running into issues.  I may have been fighting some red herrings due
to CSS, though, so I will try soon to go back to a more DOM-oriented
style.

If you want to manipulate tables in IE, innerHTML is out.
I'm not sure what you mean by that.  The links work exactly as I
expect.  When I click move, the robot does in fact move.

Turn off scripting and they will do nothing. That is why they should
be generated by script.
Here's the latest version:

<html>

<style type="text/css">
table {border-collapse: collapse;}
table.wvr th {background-color: #EEEEEE; color: inherit;}
table.wvr td {background: #FFFF00}
table.wvr td, th {height: 40px; width: 40px; text-align: center}
.heading {background: #FF00FF}
.north {border-top: solid red;}
.east {border-right: solid red;}
.no_north {border-top: solid white;}
.no_east {border-right: solid white;}
.robot {background: #FF8888}
.beeper {background: #00FFFF}
.tiny {font-size: 70%}
</style>

<body onload="create_world()">
<script type="text/javascript">
/*
Webster van Robot.
(c) GNU General Public license
*/

function text_box() {
  return document.forms['the_form'].elements['the_text'];

}

function showMsg(the_message) {
  text_box().value += the_message + "\n";

}

function clearMsg() {
  text_box().value = '';

}

You should really test the return value of text_box before trying to
set its value property. And why are you using a function to retrieve
this element each time? You should assign it to a variable at the
outset and then test it before calling these message functions.
function World() {
  this.robot_x = 1;
  this.robot_y = 1;
  this.robot_dir = "E";
  this.north_walls = [];
  this.east_walls = [];
  this.num_aves = 12;
  this.num_streets = 10;
  this.left = {
    'N': 'W',
    'W': 'S',
    'S': 'E',
    'E': 'N'};

I take it this is what you meant by "associative arrays." This is an
object literal.

[snip]
 
D

David Mark

[snip]
function showMsg(the_message) {
  document.the_form.the_text.value += (the_message + "\n");
document.forms['the_form'].elements['the_test'].value += ...

The only time bracket notation makes any difference over dot notation is
if the form name or the element name has special characters in it with
meaning to Javascript. Otherwise, it is irrelevant.

Yes, the line could have been written:

document.forms.the_form.elements.the_test.value += ...

But that is not what he had.
Semicolons don't make any difference unless you are going to
minify/obfuscate the code.

Or rely on JSLint to spot other problems in the code. It would have
quit part way through this script as virtually every line would have
raised an exception.
 
S

Steve Howell

function World() {
this.robot_x = 1;
this.robot_y = 1;
this.robot_dir = "E";
this.north_walls = [];
this.east_walls = [];
this.num_aves = 12
this.num_streets = 10
I would think some of these should be parameters.
Eventually. As you notice in your later comments, the World() class
for now is just a singleton. The only reason I make it a class for
now is simple encapsulation. I want some of the implementation
details of the World model hidden from the rest of my code.

IIRC, you didn't hide anything. You could just as well have used
object literal notation.

I'm still confused by what you mean. I know I'm not literally hiding
something in the sense of making it private, but I am making the
elements above be part of the World() object's namespace. How would
you rewrite this code?

function World() {
this.robot_x = 0;
function move_east {
this.robot_x += 1
}
this.move_east = move_east
}
w = new World()
w.move_east()

function World() {
this.robot_x = 1;
this.robot_y = 1;
this.robot_dir = "E";
this.north_walls = [];
this.east_walls = [];
this.num_aves = 12;
this.num_streets = 10;
this.left = {
'N': 'W',
'W': 'S',
'S': 'E',
'E': 'N'};

I take it this is what you meant by "associative arrays." This is an
object literal.

Sorry for the loose terminology. I'm using it like a lookup table,
just like what Perl and Ruby call hashes, and what Python calls
dictionaries.
 
D

David Mark

[snip]
I'm still confused by what you mean.  I know I'm not literally hiding
something in the sense of making it private, but I am making the
elements above be part of the World() object's namespace.  How would
you rewrite this code?

function World() {
  this.robot_x = 0;
  function move_east {
    this.robot_x += 1
  }
  this.move_east = move_east}

w = new World()
w.move_east()

Something like:

function World() {
var robot_x = 0;
this.move_east = function() {
robot_x += 1
};
this.robot_x = function() {
return robot_x;
};
}

[snip]
 
S

Steve Howell

David Mark said the following on 12/26/2007 12:03 AM:



JSLint complaining about an optional feature in the language isn't a
reason to do it though. And, JSLint is not a JS engine. It is a language
syntax checker based on Douglas' personal beliefs about how the language
should be written.

The statement ending semicolon is 100% optional.

I agree with Randy on these points:

1) I'm more concerned that my program actually executes on a
mainstream browser than passing a lint checker.
2) Semicolons are an eyesore to me, but that's only personal
preference shaped by my using other languages that tailor their syntax
to one-line-of-text-per-one-line-of-code as the common case.

I did run my program through jslint, and while I won't act on all its
recommendations, I did find it to be a useful tool. Posting the link
for other newbies like me:

http://www.jslint.com/

Getting away from stylistic issues, can somebody give me some guidance
as to the best way to update table cells' attributes in a cross-
platform manner? I can generalize from a small example.
 
S

Steve Howell

Something like:

function World() {
var robot_x = 0;
this.move_east = function() {
robot_x += 1
};
this.robot_x = function() {
return robot_x;
};
}

Ok, I definitely prefer the syntax you're showing me for declaring the
inner functions.

I'm a little more concerned about robot_x. For this.move_east, I want
to be explicity that robot_x is part of World, and not a local
variable.
 
M

My Pet Programmer

Steve Howell said:
I'm a little more concerned about robot_x. For this.move_east, I want
to be explicity that robot_x is part of World, and not a local
variable.

This bit:

Assigns a function to this.robot_x. the only way you'll get that
property is by calling World.robot_x(); If someone tried to get robot_x
without it, it would not be in scope, ever, anywhere.

In short, it will be a part of World(), or it will be undefined.
 
M

My Pet Programmer

My Pet Programmer said:
Steve Howell said:


This bit:


Assigns a function to this.robot_x. the only way you'll get that
property is by calling World.robot_x(); If someone tried to get robot_x
without it, it would not be in scope, ever, anywhere.
That should read "ever, anywhere outside of World's scope."
 
S

Steve Howell

My Pet Programmer said:




That should read "ever, anywhere outside of World's scope."

But it's the inner functions that I'm concerned with. Suppose I write
a method on World called move_robot_to_other_side_of_wall, and it has
these tokens:

robot_x
robot_y
walls
x
y
i
wall

Some of those tokens would be instance variables; other would be
locals. Is there a way to make the code explicitly call out which is
which?
 
M

My Pet Programmer

Steve Howell said:
But it's the inner functions that I'm concerned with. Suppose I write
a method on World called move_robot_to_other_side_of_wall, and it has
these tokens:

robot_x
robot_y
walls
x
y
i
wall

Some of those tokens would be instance variables; other would be
locals. Is there a way to make the code explicitly call out which is
which?
If you had this function:
World.move_robot_to_other_side_of_wall = function(robot_x) {
}

And then used robot_x in the function, scoping rules say you're going to
get the argument passed to the function, not the value of robot_x as
declared above in the constructor. To get the value of robot_x that is
in the constructor, you would still need to access this.robot_x

Really, these guys know what they're doing. The value of that variable
would be inaccessible if not accessed through the World context.
 
S

Steve Howell

Steve Howell said:



If you had this function:
World.move_robot_to_other_side_of_wall = function(robot_x) {

}

And then used robot_x in the function, scoping rules say you're going to
get the argument passed to the function, not the value of robot_x as
declared above in the constructor. To get the value of robot_x that is
in the constructor, you would still need to access this.robot_x

Really, these guys know what they're doing. The value of that variable
would be inaccessible if not accessed through the World context.

I wasn't referring to passed in parameters. Looking at this working
code below, can you tell me which variables are just local variables,
and which are meant to persist between different calls to
wall_on_north or other methods on World()?

function World {

/* snip */

function wall_on_north(x, y)
{
if (y == 0 || y == this.num_streets) {
return true;
}
coords = x + "," + y;
return (north_walls[coords] == 1);
}
}

I'm ok with JS scoping in that it hasn't bitten me so far, but I just
wonder how to express the code most idiomatically. Ruby and Python,
for example, both have an explicit way of calling out instance
variables.
 
M

My Pet Programmer

Steve Howell said:
[snip]
I wasn't referring to passed in parameters. Looking at this working
code below, can you tell me which variables are just local variables,
and which are meant to persist between different calls to
wall_on_north or other methods on World()?

function World {

/* snip */

function wall_on_north(x, y)
{
if (y == 0 || y == this.num_streets) {
return true;
}
coords = x + "," + y;
return (north_walls[coords] == 1);
}
}
As written, that code does not actually work. function World() { is a
better first line, but accepting that....

First, you wouldn't ever be able to call it. Declare it like this:

this.wall_on_north = function(x,y) {

}

Nothing persists. In order to set a variable that would persist in the
World namespace, you'd have to set it like this:

this.x = x;

Then you could grab it.

With David's example:

function World() {
var robot_x = 0;
this.move_east = function() {
robot_x += 1
};
this.robot_x = function() {
return robot_x;
};
}

var v = new World();
alert (v.robot_x()); // alerts 0
v.move_east();
alert (v.robot_x()); // alerts 1
alert (typeof robot_x); // alerts undefined


Everything in your function has function scope there, and unless you
have them outside the whole construct as globals, nothing will persist.
If you do have them outside the contruct as globals, then everything
persists.

The scope chains is something like
[Global space]->[World]->[World var || World function]->[...]


So could you modify the 'private variable' robot_x from other functions
inside the World object? Yes, that's the expected behavior. Could you do
it from outside the world namespace? If you code a global variable with
the same name as one you assign to the class, scope _should_ apply. But
you can't be sure, and it's not a good coding practice to let things
slip out of your 100% sure realm.

http://javascripttoolbox.com/bestpractices/#namespace

~A!
 
M

My Pet Programmer

Steve Howell said:
Steve Howell said:

If you had this function:
World.move_robot_to_other_side_of_wall = function(robot_x) {

}

And then used robot_x in the function, scoping rules say you're going to
get the argument passed to the function, not the value of robot_x as
declared above in the constructor. To get the value of robot_x that is
in the constructor, you would still need to access this.robot_x

Really, these guys know what they're doing. The value of that variable
would be inaccessible if not accessed through the World context.

I wasn't referring to passed in parameters. Looking at this working
code below, can you tell me which variables are just local variables,
and which are meant to persist between different calls to
wall_on_north or other methods on World()?

function World {

/* snip */

function wall_on_north(x, y)
{
if (y == 0 || y == this.num_streets) {
return true;
}
coords = x + "," + y;
return (north_walls[coords] == 1);
}
}

I'm ok with JS scoping in that it hasn't bitten me so far, but I just
wonder how to express the code most idiomatically. Ruby and Python,
for example, both have an explicit way of calling out instance
variables.
Here's something better than what I can give you on it.

http://www.crockford.com/javascript/private.html

He has a lot of good stuff over there, as well.

And some further reading:
http://peter.michaux.ca/article/5004


~A!
 
S

Steve Howell

Steve Howell said:
function World {
/* snip */
function wall_on_north(x, y)
{
if (y == 0 || y == this.num_streets) {
return true;
}
coords = x + "," + y;
return (north_walls[coords] == 1);
}
}

As written, that code does not actually work.

The method wall_on_north really does work, but I snipped out too much
context.

Giving some more context, but still striving for some brevity here:

function World() {
/* snip */
var north_walls = [];
this.num_streets = 10;


function wall_on_north(x, y)
{
if (y == 0 || y == this.num_streets) {
return true;
}
coords = x + "," + y;
return (north_walls[coords] == 1);
}
this.wall_on_north = wall_on_north;

}
the_world = new World();

First of all, forget about how wall_on_north is called from outside
World. It's created a bunch of red herrings that have nothing to do
with my question.

Next, look at the use of num_streets and north_walls above. With both
variables I have the inner function refer to variables that are to
meant to be within the scope of World(). In one case, num_streets, I
use "this" to make the scope explicit. In the other case,
"north_walls," I let JS figure out the scope.

(As for x and y, they're passed in parameters, and as for coords, it's
just a local variable.)

My question is fairly simple--what is the best practice for referring
to variables so that it's clear they're in the scope of World, i.e.
their scope is more than local to the inner function? My original
code had "this," but then David seemed to be suggesting that was
somehow wrong.

function World() { is a
better first line, but accepting that....

You say "function World() {" is a better first line...a better first
line than what? Isn't that what I showed?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top