データ可視化[6]:分布図の作成

csvデータを読み込んで分布図(ヒストグラム)を作成するためのスケッチの例を次に示す.詳細は授業中に説明する.


int M = 11;             // the number of bins 
////////// the sample code to draw histogram /////
int[] bin = new int[M]; // array for the bins
size(500, 500);

background(170);

// initialize the bins
for(int i = 0; i < M; i++){
  bin[i] = 0;
}

// load the data file
Table table = loadTable("adata.csv", "header ");

// get the amount of data
int N = table.getRowCount();

// classify the scores into the M+1 bins
for(int i = 0; i < N; i++){
  TableRow row = table.getRow(i);
  int   student = row.getInt("ID");
  float x = row.getFloat("score");
  ////// classification //////
  int j = floor(x/10); 
  bin[j]++;
  ////////////////////////////
  println(student + ": " + x + " : bin = " + j);
}

// display the histogram (bins)
float w = width / M;  // width of the bar
int   bmax = 30;      // the expected maximum
for(int i = 0; i < M; i++){
  float x = i * w;
  float h = map(bin[i],0,bmax,0,height);
  float y = height - h;
  rect(x,y,w,h);      // draw the bar
}

課題1:任意のデータファイルからヒストグラムを作成するときに考慮すべき点を考え,それらを整理しておきなさい.

課題2:web上などから適当なデータファイル(csvファイル形式)を見つけてきて,ヒストグラムを作成しなさい.

課題3:適当なアルゴリズムによってデータファイル(csvファイル形式)を作成し,ヒストグラムを作成しなさい.

データ可視化[5]:人工データの生成

ここに人工データファイルを生成するためのスケッチを示す.データの生成にはPerlinノイズを利用した.詳細は授業中に説明する.


int N = 40;
PrintWriter output;
float tmax;
//////////////////////////////////////////////////
void setup(){
  size(700,200);
  noiseSeed(0);
  tmax = 40;
  
}
//////////////////////////////////////////////////
void draw(){
  background(170);
  
  for(int i = 0; i < width; i++){
    float x = i;
    float t = map(i,0,width,0,tmax);
    float y = map(noise(t),0,1,height,0);
    stroke(255,0,0);
    ellipse(x,y,2,2);
  }
  
  for(int i = 1; i <= N; i++){
    float x = map(i,0,N,0,width);
    stroke(255,100);
    line(x, 0, x, height);
  }
  
  tmax = mouseX / 10.0;
  fill(0);
  textSize(20);
  text("tmax = " + tmax, 10, height-5);
  text("N = " + N, width/2, height-5);
}
//////////////////////////////////////////////////
//////////////////////////////////////////////////
void mousePressed(){
  output = createWriter("data/adata.csv"); 
  
  println("ID" + "," + "score");
  output.println("ID" + "," + "score");
  for(int i = 1; i <= N; i++){
    float t = map(i,0,N,0,tmax);
    println(i + "," + score(noise(t)));
    output.println(i + "," + score(noise(t)));
  }
  output.flush();
  output.close();
}
//////////////////////////////////////////////////
int score(float r){
  return round(r * 50 + 50);
}

補足:上記コード中のmousePressed()は,mouseClicked()に置き換えた方が良い.

データ可視化[4]:頻度分布の表示

今回は,頻度分布の表示方法を学びます.

ついでに,数学を少々勉強しましょう.キーワードは,ガウス分布(正規分布),中心極限定理です.


//////////////// central limit theorem ///////////////////
//// global variables ////////////////////////////////////
int N = 20;                   // how many elements         
float[] p = new float[N+1];   // the distribution
int count = 1;                // counter for the trials
//////////////////////////////////////////////////////////
void setup(){
  size(500, 500);
  colorMode(HSB);  // set the HSB mode
  init();          // initialize
}
void draw(){
  background(170);
  display();
  p[gen()]++;      // update the distribution
  count++;         // increment the counter
}
//////////////////////////////////////////////////////////
int gen(){  /// generate tha random value
  int m = 0;
  for(int i = 1; i <= N; i++){
    m += floor(random(2));  //  0 or 1
  }
  return m; // from 0 to N
}

void display(){  /// display the distribution etc.
  fill(0);
  textSize(20);
  text("trial = " + count, width - 150, 20);
  text("N = " + N, width - 120, height - 10);
  float wy = height / (N + 1.0);  // y-width of the bar
  for(int i = 0; i <= N; i++){
    float x = 0;                     // bar position x
    float y = i * wy;                // bar position y
    float h = map(i, 0, N, 0, 255);  // bar color
    fill(h,255,255);
    // x-width of the bar
    float wx = p[i] * height / count * sqrt(N); 
    rect(x, y, wx, wy);         // draw the bar 
    float ts = 400 / (float)N;  // text size for the bar
    textSize(ts);
    fill(0);
    text((int)p[i], wx + 5, y + ts); 
  }
}

void init(){  /// initialize the distribution etc. 
  count = 1;
  for(int i = 0; i <= N; i++){
    p[i] = 0;
  }
}

void keyPressed(){  /// if key pressed then initialize
  init();
}

課題1:上記ソースコードをよく理解し,これを適切に修正することによって,「縦型」の棒グラフの表示を実現せよ.

データ可視化[3]:Web Mercator

今回は経度と緯度から地図画像上のピクセル座標への変換を考えます.

ここでは Web Mercator に基づく変換法を実装します.

参考サイト:https://en.wikipedia.org/wiki/Web_Mercator_projection

変換式は上記に示されているとおりです.ただし,mapboxから取得した画像については,上記「256」を「512」に変更する必要があります.

参考ソースコードを次に示します.詳細は講義中に説明します.


PImage mapImg;
float clat, clon, tlat, tlon, zoom; 
float a = 0;

void setup() {
  size(1024, 512);
  String url = "https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/" + 
               "130.473731,33.004559,1.5,0,0/1024x512?access_token=(自分のアクセストークン)";

  clon = 130.473731; // longitude of the center of the mapimage
  clat = 33.004559;  // latitude  of the center of the mapimage

  tlon = 360 - 123.120738; // longitude of the target
  tlat = 49.282729;        // latitude  of the target
  
  zoom = 1.5;
  mapImg = loadImage(url, "png");
}

void draw() {
  image(mapImg, 0, 0);
  translate(width / 2, height / 2);
  
  float x = mercX(tlon) - mercX(clon);
  float y = mercY(tlat) - mercY(clat);
  
  fill(255, 0, 0, 100);
  float d = 30 * ( 1 + 0.5 * sin(a));
  ellipse(x, y, d, d);
  fill(0,255,0, 100);
  ellipse(0, 0, d, d);
  
  a += 0.1;
}

float mercX(float lon) {
  float a = (512 / TWO_PI)*pow(2, zoom);
  float b = radians(lon) + PI;
  return a * b;
}
float mercY(float lat) {
  float a = (512 / TWO_PI)*pow(2, zoom);
  float b = tan(PI / 4 + radians(lat) / 2);
  float c = PI - log(b);
  return a * c;
}

//////////////////////////////////////////////
// Shanghai
//float lat = 31.224361;
//float lon = 121.469170;
// vancouver
//float lat = 49.282729;
//float lon = 360 - 123.120738; //// caution: "+ 360" is necessary
// Ariake-Kosen
//float lat = 33.004559;
//float lon = 130.473731;

下図に実行結果を示します.

実行結果のスナップショット.緑の円で有明高専の位置を,赤の円でバンクーバーの位置をハイライトしている.

課題1:自分の好きな場所を地図の中心にして,自分の好きな場所をハイライトせよ.

課題2:地震(震源地)の csvデータ(USGSの)を利用して地図情報と重ねて震源地分布を可視化せよ.USGS(アメリカ地質調査所)のwebサイト:https://www.usgs.gov/

課題3:自転車シェアサービスcitibikeのwebサイトに置いてある自転車乗降データに関する csvファイルを利用して,利用者の振る舞いに関する何らかの情報を地図情報と重ねて可視化せよ.地図の自転車シェアサービスcitibikeのwebサイト:https://www.citibikenyc.com/

課題2の解答例.

参考までに,下に気象庁のwebページ掲載の関連画像を示します.

気象庁のwebページ「地震発生のしくみ」より:http://www.data.jma.go.jp/svd/eqev/data/jishin/about_eq.html

データ可視化[2]:地図画像の取得

ここでは,mapboxというwebサービスを利用して地図画像を取得することにします.

まずは mapbox にアクセスしてみましょう.「mapbox」と検索すれば当該サイトをすぐに見つけられます.

mapboxサイト

ここでは,Static Images を用います.上図で示されているように,画像を取得するには,指定された特定のところにアクセスすればよいのですが,「access_token」というコードが必要となります.このコードを得るには自分のアカウントを作る必要があります.Static Image5万枚までは無料です.

アカウントの作成.

Sign upで必要事項を入力してアカウントをつくります.アカウントを作り,自分のアカウントに Sign in すると下図のようになります.

自分のアカウントを作ってアクセストークンを取得する.

ここでは,まず Documentation に移ります.次に Maps APIs に移ります.さらに Static Images に移ります.つぎに下の方にスクロールしていって,Example request: Retrieve a static map from a style を見つけます.

Static Image を要求する方法.

上図の最後の図の「https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/-122.4241,37.78,14.25,0,60/600x600?access_token=pk.eyJ1IjoiYXl...」のような文字列を地図画像取得のリクエストとして後で利用します.

次に,例として,有明高専近辺の地図(Static Image)を取得することを考えます.そのために,Googlemap を利用して,有明高専の緯度(latitude)と経度(longitude)を知ります.方法は下図を参照してください.

Googlemapを利用して緯度経度情報を取得する.

以上の情報を利用して,有明高専近辺の地図画像を取得してみましょう.スケッチおよび実行結果はは次の図に示すとおりです.「?access_token=」以下の文字列は自分のアカウントにおいて取得する必要があります.詳細は授業中に説明します.

Processingによる地図画像の取得の例.

リクエスト内のパラメータをいろいろ変えて試してみてください.

視野の方向やズームなどを変えることができます.

詳細は授業中に説明します.

次回は,緯度経度の値と画像上の位置の対応のさせ方について説明する予定です.

データ可視化[1]:csvファイルの利用

今年度の課題研究2のテーマはデータ可視化(Data Visualization)です.

昨年度と同様,Processing(Java ベースの開発環境)です.

データは表形式であり,これはcsvファイルの形になっているものとします.

csvファイル(comma separated value file)とは,数値あるいは文字列がコンマ「, 」で区切られたテキストファイルのことです.ここでは,コンマによってデータ値が区切られた表形式のデータ(tabular data)を取り扱います.

表形式の先頭の行は通常はヘッダーである場合が多いです.ヘッダーは各列のデータ値の意味を表します.表形式のデータ(ヘッダーの次からの)各行はレコードと呼ばれます.レコードはいくつかのフィールドからなります.フィールドとはデータ値の格納場所で,これらは表形式csvファイルにおいては,コンマで区切られています.

例えば,次のような表形式データ(tabular data)があるとします.

表形式データの例.

このデータは,data01.csv というcsvファイルに次のように格納されているでしょう.

テキストファイル data01.csv の中身.

Processingには,TableクラスおよびTableRowクラスというクラスが用意されています.csvファイルから欲しいデータを抽出するためにこれらのクラスを利用します.

次の文で,Tableクラスのオブジェクト table を作ることができます.

Table table = loadTable(“data01.csv”, “header “);

ここで,loadTable( , )の第1引数 “data01.csv” は読み込むファイル名です.第2引数 “header ” は読み込むファイルの先頭行がヘッダーであることを意味します.

データファイル data01.csv は,スケッチが置かれているフォルダー内に置かれているフォルダー data の内部に置かれます.下図を参照してください.

データファイル data01.csv はフォルダ data の中に置かれる.ここで,スケッチ名は table01 である.

次にサンプルスケッチおよび,それを実行した様子を示します.

スケッチの例.

データファイルの内容をもとに図形を描画しています.

Tableクラスのメソッド getRowCount() によってデータレコード総数(データが格納されている全行数)を取得することができます.ここでは

int N = table.getRowCount();

によって,整数型変数 N にデータレコード総数が格納されます.ちなみに「row」の辞書的意味は「行」です.

引き続くforループ処理(N回くりかえす)の内部では,

TableRow row = table.getRow(i);

によって,TableRow クラスのオブジェクト row に指定された行のデータが割り当てられます.ここで,getRow(i) はTableクラスのメソッドで引数 i によって特定の行が指定されます.

引き続き,TableRowクラスのメソッド getFloat(・)によって指定されたフィールド(コラム)のデータ値を取得します.たとえば

float x = row.getFloat(“x”);

によって,浮動小数点型変数 x にコラム x におけるデータ値が格納されます.

あとは取得されたデータに基づいて図形が描画されます.ここではデータとは,描画されるべき図形のx座標,y座標,x方向の幅,y方向の幅,色(R,G,B)です.

講義[7]:ボロノイ図(2)

ボロノイ図(2次元)の描画のために3次元空間を考える方法があります.母点(生成元)たちを頂点とする円錐たち(互いに軸が平行)を考えます.その側面(曲面)たちが交差してできる曲線たちを円錐軸に平行な方向に投影(射影)すると考えてください.円錐軸と垂直な投影面に投影(射影)された曲線たちが,ボロノイ辺たちとなります.ボロノイ辺たちの交点たちがボロノイ点たちとなります.複数のボロノイ辺で囲まれたある一つの領域がボロノイ領域です.この一つの領域はある円錐の頂点に対応します.
 上に述べたことを次の動画で示します.

こんな感じです.

こんかいは,この方法でボロノイ図を作成することを目指して,3次元空間に図形を描画する方法を学びます.いくつかの方法がありますが,今回は,空間内に3つの頂点を指定して三角形を作る考え方を基本とする方法を学びます.

まずは,次に2次元空間内に3つの頂点を指定して三角形をつくる基本的方法を示します:


void setup(){
  size(600,600);
}

void draw(){
  background(170);
  
  beginShape(TRIANGLES);
  vertex(100, 50);
  vertex(300, 100);
  vertex(100, 250);
  vertex(200, 200);
  vertex(400, 200);
  vertex(200, 400);
  vertex(300, 250);
  vertex(500, 300);
  vertex(250, 500);
  endShape();
 
}

実行結果はこれです:

「beginShape(TRIANGLES)」と「endShape()」に囲まれた部分で頂点(vertex)たちの座標を指定します.順に3つずつ組になっていると考えてください.

さて,頂点たちの座標を素朴に書いていくのは大変です.繰り返し処理を利用しましょう.繰り返し処理の利用例を次に示します:


int N = 20;
float d;

void setup(){
  size(500,500);
  d = width/N;
}

void draw(){
  background(170);
  
  beginShape(TRIANGLES);
  for(int i = 0; i < N; i++){
    float x = i * d;
    float y0 = height/2;
    float y1 = 200 * sin(x/width * PI * 3);
    vertex(x,     y0);
    vertex(x + d, y0);
    vertex(x,     y0 + y1);
  }
  endShape();
}

実行結果はこれです:

Processingには三角形を敷き詰めていくための便利な方法がいくつか用意されています.そのひとつが「TRIANGLE_STRIP」です.今度はこのキーワードを「beginShape()」の引数として指定します.
「TRIANGLE_STRIP」の使用例を次に示します.帯状(ベルト状)のものを想像してください.帯の両方の縁上の頂点たちの座標を「ジグザグ」に指定していく様子をイメージするとわかりやすいかもしれません:


int N = 30;
float d;

void setup(){
  size(500,500);
  d = width/N;
}

void draw(){
  background(170);
  
  beginShape(TRIANGLE_STRIP);
  for(int i = 0; i < N; i++){
    float x = i * d;
    float y0 = height/2;
    float y1 = 20 * sin(x/width * PI * 6) + 50;
    vertex(x, y0 - y1);
    vertex(x, y0 + y1);
  }
  endShape();
}

実行結果はこれです:

三角形を敷き詰めていくための便利な方法をもうひとつ示します.「TRIANGLE_FAN」です.今度はこのキーワードを「beginShape()」の引数として指定します.「TRIANGLE_FAN」の使用例を次に示します.扇(おうぎ)を想像してください.扇の要(かなめ)の座標を指定してから周辺の座標を指定していきます:


int n = 20;
float r = 100;

void setup(){
  size(500,500);
}

void draw(){
  background(170);
  
  beginShape(TRIANGLE_FAN);
  vertex(width/2, height/2);
  for(int i = 0; i <= n; i++){
    float p = i/(float)n * TWO_PI;
    float x = r * cos(p) + width/2;
    float y = r * sin(p) + height/2;
    vertex(x, y);
  }
  endShape();
}

実行結果はこれです:

この「扇」の要を3つ目の次元の方向に引き上げて円錐をつくることができます.実は,この円錐をボロノイ図の作成に利用しようというわけです.Processingでは,3次元的なものを容易に実現できます.上記の「扇」のソースコーソを少しだけ改変して円錐の表示を実現したものを次に示します.動きを取り入れて面白みをつけくわえました.


int n = 20;
float r = 150;

float a = 0;

void setup(){
  size(500,500,P3D);
}

void draw(){
  background(170);
  lights();
  translate(width/2, height/2);
  rotateX(a);
  a += 0.01;
  
  beginShape(TRIANGLE_FAN);
  float h = 200 * cos(6*a);
  vertex(0, 0, h);
  for(int i = 0; i <= n; i++){
    float p = i/(float)n * TWO_PI;
    float x = r * cos(p);
    float y = r * sin(p);
    vertex(x, y);
  }
  endShape();
}

実行結果はこれです:

さて,ここで,課題です.ボロノイ図の作成に進む前に,せっかくの機会ですから,頂点を指定して図形を作る方法に慣れておきましょう.
なるべく課題3まで到達してください.余力がある人は課題4に挑戦してみてください.

課題1.上記「TRIANGLES」の例題のソースコードの内容をよく理解して次のようにインタラクティブにしてみてください.

課題2.上記「TRIANGLE_STRIP」の例題のソースコードの内容をよく理解して次のようにインタラクティブにしてみてください.

課題3.上記「TRIANGLE_FAN」(2次元)の例題のソースコードの内容をよく理解して次のようにインタラクティブにしてみてください.

課題4.これは,余力がある人が取り組んでみてください.上記「TRIANGLE_FAN」(3次元)の例題のソースコードの内容をよく理解して次のように,にぎやかにしてみてください.オブジェクト指向の考え方を用いましょう!

講義[6]:ボロノイ図(1)

今回からは,ボロノイ図(Voronoi diagram)を取り扱います.
ここでは2次元ユークリッド空間(簡単のため,以後,空間)を考えましょう.

空間内に特に指定されたいくつかの点(母点)があるとします.
ボロノイ図とは,それぞれの母点が他より自分に近い場所を自分の勢力圏(なわばり)として囲い込むことによってできる図形です.
(「なわばりの数理モデル — ボロノイ図からの数理工学入門」杉原著(共立出版2009)より)

下図にボロノイ図に関する用語を示します.

また,次の図にボロノイ図の性質を示します.

次の動画は母点を動かしながらボロノイ図を描画させたものです.

さて,早速ですが,課題です.
課題1(レポートです!)
 (a)ボロノイ図の理学工学における応用例をなるべくたくさん調べてリストを作ってください.必要に応じて簡単な説明をつけましょう.
 (b)余力があれば応用例を分類整理しましょう.
 (c)さらに余力があれば,ボロノイ図として解釈できそうな,自然界にみられる造形物を探してみてください.
 (d)さらに余力があれば,ボロノイ図を作成するためのアルゴリズムにはどのようなものがあるのか調べましょう.
 以上の「調査結果」をレポートとして提出してください.
締め切りなどの必要事項や詳細は授業時間に説明します.

参考文献:
(1)「なわばりの数理モデル — ボロノイ図からの数理工学入門」杉原著(共立出版2009)
(2)「迷路の中のウシ」イアン・スチュアート(川辺訳)(共立出版2015)

講義[5]:迷路をつくる(3)

今回は,迷路をつくるアルゴリズムを理解しましょう.
迷路をつくるための様々なアルゴリズムは既にいくつも提案されています( Wikipedia:「Maze generation algorithm」参照).
 ここでは「depth-first search by recursive backtracker」を実装することを目標にしましょう.実装の前に,まずアルゴリズムを理解しましょう.
 このアルゴリズムは次のように表されます.

------------------------------------------------------
1. Make the initial cell the current cell and mark it 
   as visited
2. While there are unvisited cells
  1. If the current cell has any neighbors 
     which have not been visited
        1. Choose randomly one of the unvisited 
           neighbors
        2. Push the current cell to the stack
        3. Remove the wall between the current cell 
           and the chosen (next) cell
        4. Make the chosen (next) cell the current 
           cell and mark it as visited
  2. Else if stack is not empty
        1. Pop a cell from the stack
        2. Make it the current cell
------------------------------------------------------

ここで活用されている重要なデータ構造は「スタック(stack)」です.
データを格納する操作をpush,データを取り出す操作をpopといいます.
スタックは,最後に格納(push)したデータが最初に取り出される(pop)という特徴をもちます.
つまり「LIFO (Last In First Out)」あるいは「FILO (First In Last Out)」です.

課題1(レポートです!).スタックの特徴に注意しながら,上記アルゴリズムを解読(理解)してください.紙と鉛筆を用いて上記アルゴリズムをシミュレートしてみるとよいかもしれません.そのシミュレーションをわかりやすく図示してまだ理解していない人に説明してあげましょう.
 例えば図示の方法として紙に迷路の図としての4×4ぐらいのマス目を描き,そこに番号を振っておきます.またマス目の左脇にはスタックを描いておく方法が考えられます.各自なるべく「わかりやすい解説方法」を考えてください.
 この「わかりやすい解説方法」をレポートとして提出してください.締め切りなどの必要事項や詳細は授業時間に説明します.

参考動画:

講義[4]:迷路をつくる(2)

今回も前回に引き続き,オブジェクト指向の考え方を用いて迷路を自動生成するプログラムを組んでいきます.少しづつ進めましょう.
今度のプログラムでは,「マーカー」が中心から出発して動き回ります.そのためにCellクラスのオブジェクト「 marker 」を導入しました.
「訪問された」(Cellクラスの)オブジェクト grid[][]には色をつけるようにしました.そのためにCellクラスに「 visited 」というフィールド変数を追加しています.
さて早速ソースコードを示しますが,これを実行してしばらくするとエラーが出ます.何回か実行させてみて,どのようなときにどのようなエラーがでるか確認してください.
「マーカー」が端っこに移動しようとするとエラーがでることがわかるでしょう.
まずはソースコードの意味を理解してください.前回のソースコードとほぼ同じ内容です.
異なる部分(追加した部分)は「 //// <<----(*) 」で示してあります.


//// Maze generation 

int cols, rows;
float w = 20;
Cell[][] grid;

Cell marker; //// <<----(*)

int margin = 50;
//////////////////////////
void setup() {
  size(600, 600);
  rectMode(CENTER);
  colorMode(HSB);
  cols = floor((width - margin * 2)/w);
  rows = floor((height - margin * 2)/w);
  grid  = new Cell[cols][rows];
  
  for(int j = 0; j < rows; j++){
    for(int i = 0; i < cols; i++){
      grid[i][j] = new Cell(i,j);
    }
  } 
  
  int startCol = int(cols/2);         //// <<----(*)
  int startRow = int(rows/2);         //// <<----(*)
  marker = grid[startCol][startRow];  //// <<----(*)
  
}
//////////////////////////
void draw() {
  background(120);
  for(int j = 0; j < rows; j++){
    for(int i = 0; i < cols; i++){
      grid[i][j].show();
    }
  }
  
  marker.visited = true;  //// <<----(*)
  marker.highlight();     //// <<----(*)
  marker = marker.move(); //// <<----(*)
  
}
////////////////////////////////////////////////////
class Cell{
  int i, j;
  boolean[] walls = {true, true, true, true};
  boolean visited = false; //// <<----(*)
  
  Cell(int i0, int j0){
    i = i0;
    j = j0;
  }
  
  Cell move(){                     //// <<----(*)
    Cell[] ngb = new Cell[4];      //// <<----(*)
    ngb[0] = grid[i][j-1];         //// <<----(*)
    ngb[1] = grid[i+1][j];         //// <<----(*)
    ngb[2] = grid[i][j+1];         //// <<----(*)
    ngb[3] = grid[i-1][j];         //// <<----(*)
    return ngb[floor(random(4))];  //// <<----(*)
  }
  
  void highlight(){                //// <<----(*)
    float x = i*w + margin + w/2;  //// <<----(*)
    float y = j*w + margin + w/2;  //// <<----(*)
    noFill();                      //// <<----(*)
    strokeWeight(4);               //// <<----(*)
    stroke(50,255,255);            //// <<----(*)
    rect(x,y,w*1.3,w*1.3);         //// <<----(*)
  }                                //// <<----(*)
  
  void show(){
    float x = i*w + margin + w/2;
    float y = j*w + margin + w/2;
    
    if(visited){          //// <<----(*)
      noStroke();         //// <<----(*)
      fill(170,140,255);  //// <<----(*)
      rect(x,y,w,w);      //// <<----(*)
    }                     //// <<----(*)
    
    strokeWeight(2);
    stroke(0);
    if(walls[0]){
      line(x-w/2, y-w/2, x+w/2, y-w/2); // 0:top
    }
    if(walls[1]){
      line(x+w/2, y-w/2, x+w/2, y+w/2); // 1:right
    }
    if(walls[2]){
      line(x+w/2, y+w/2, x-w/2, y+w/2); // 2:bottom
    }
    if(walls[3]){
      line(x-w/2, y+w/2, x-w/2, y-w/2); // 3:left
    }
    strokeWeight(4);
    stroke(255);
    if(!walls[0]){
      line(x, y, x, y-w/2); // 0:top
    }
    if(!walls[1]){
      line(x, y, x+w/2, y); // 1:right
    }
    if(!walls[2]){
      line(x, y, x, y+w/2); // 2:bottom
    }
    if(!walls[3]){
      line(x, y, x-w/2, y); // 3:left
    }
  }
}

課題1.上記ソースコードを実行させてエラーが出る理由を理解してください.次にエラーが出ないように修正してください.
ここでは,「マーカー」の移動方向に制限を加えて「端っこ」から出ないようにしてください.
課題2.上記ソースコードを修正して「マーカー」の移動にともなって移動した方向の「壁」を取り除くようにしてください.
課題3.上記ソースコードを修正して一度「訪問した」セルには2度と訪れないようにしてください.

参考図(今回のプログラム):

参考図(課題1):

参考図(課題2):

参考図(課題3):