You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Mô hình hóa thuật toán Tháp Hà Nội (Hanoi Towers) bằng D3JS - Hướng đối tượng
Phân tích bài toán theo Logic
Sử dụng thuật toán Đệ Quy để giải quyết bài toán:
varhanoi=function(disc,src,aux,dest){if(disc>0){hanoi(disc-1,src,dest,aux);console.log('Move disc '+disc+' from '+src+' to '+dest);hanoi(disc-1,aux,src,dest);}};hanoi(3,'src','aux','dest');
Trong đó:
src,aux,dest là các Tháp tương ứng: Tháp gốc (src),Tháp trung gian(aux) và Tháp Đích.
disk là tham số dạng Number tương ứng số đĩa truyền vào.
Cách giải
Chuyển n-1 đĩa từ cọc A sang B. Chỉ còn đĩa n trên cọc A.
Chuyển đĩa n từ cọc A sang cọc C.
Chuyển n-1 đĩa từ B sang C cho các đĩa có đường kính nhỏ hơn lần lượt nằm trên đĩa n.
Tiến hành bước 1 và 3, áp dụng lại thuật giải cho n-1.
Các Bước xây dựng:
Bước 1: Vẽ svg Tổng và tính toán tọa độ các cọc,đĩa
Sử dụng thư viện d3js để vẽ svg:
letw=$(".container").width()leth=300letsvg=d3.select(".container").append("svg").attr("width",w).attr("height",h+disks_input*40);//với n là số đĩa đưa vào ở sau,40 là chiều cao từng đĩa.
Tính toán tọa độ,khoảng cách giữa các cọc,đĩa:
Đầu tiên,ta chia svg ra làm 4 phần theo chiều dọc,tức là width/4
Rồi chia tiếp mỗi phần đó làm đôi theo chiều dọc,tức là width/8
Ta sẽ thấy từ hình dưới,ta tạo được 3 đĩa có bán kính bằng nhau và cách nhau 1 khoảng bằng w/8 và mỗi đĩa có bán kính bằng w/4,khoảng margin(m) giữa các đĩa bằng w/8
Ta định nghĩa các thuộc tính của đĩa và cọc:
//đĩa ở cuối sẽ có bán kính lớn nhất nên ta đặt nó bằng w/4 (d)letbiggest_disk=w/4;// đây là vị trí tower2 với dấu '*' trên hình -> tower1 có x=0,tower3 có x=2*marginletmargin=biggest_disk+biggest_disk/2;//tower1 = 0//tower2 = margin//tower3 = 2 * margin
Bước 2: Khởi tạo các biến global:
letsvg,unit_disk,disks_input=total_disks='',tower_height,checkpoint,timesound;letdisks_in_tower1=disks_in_tower2=disks_in_tower3='';letbiggest_disk=$(".container").width()/4;//khai báo bán kính đĩa cuối cùng cũng là đĩa lớn nhấtletmargin=biggest_disk+biggest_disk/2;letarr_disk=[],courier=[];//tạo mảng chứa disk và courierleti=1,steps=click=delay_disk=delay_courier=0;
Bước 3: Khai báo class và phương thức:
Class Disk và các phương thức:
classDisk{build_disk(){unit_disk=biggest_disk/disks_input;// Chiều rộng của đĩa nhỏ nhất//Tạo và vẽ đĩafor(vari=1;i<=disks_input;i++){// Vẽ đĩa vào thẻ svg đã tạosvg.append("rect").attr("x",function(){// Tọa độ x của đĩa so với thẻ svg//mỗi lần lặp,ta sẽ rút ngắn bán kính của đĩa tùy theo số lượng đĩa.return(disks_input-i)*(unit_disk/2);}).attr("y",function(){// Tọa độ y của đĩa so với thẻ svgreturni*40+150;}).attr("width",function(){// Chiều rộng của từng đĩareturni*unit_disk;}).attr("height",40)// Chiều cao của từng đĩa.attr("rx",10)// Bo góc của từng đĩa.attr("ry",10)// Bo góc của từng đĩa.attr("stroke-width",1)// Độ dày viền border của đĩa.attr("stroke","rgba(0,0,0,.5)")// Màu viền.classed("disk"+i+" color",true);// Gán class cho từng đĩaarr_disk.push(d3.select(".disk"+i));// Chuyển đĩa vào mảng đã khởi tạo ban đầu}//Tạo và vẽ chimsvg.append("svg:image")// Thêm ảnh vào thẻ svg.attr("xlink:href","giphy-right.gif")// Đường dẫn ảnh.attr("x",(biggest_disk-100)/2)// Tọa độ x của ảnh trong thẻ svg.attr("y",129)// Tọa độ y của ảnh trong thẻ svg.attr("width",100)// Chiều rộng của ảnh trong thẻ svg.attr("height",60);// Chiều cao của ảnh trong thẻ svgcourier.push(d3.select("image"));// Chuyển ảnh vào mảng đã khởi tạo ban đầud3.selectAll(".color").style("fill",function(){// Tạo màu cho từng đĩa Randomreturn"hsl("+Math.random()*360+",100%,50%)";});}}
Khai báo class Tower và phương thức:
classTower{build_tower(){for(varj=0;j<3;j++){// Dựng đế cho towersvg.append("rect").attr("x",function(){//j=0,1,2*margin tương ứng tọa độ bắt đầu của tower1,2,3 như đã giải thích ở trênreturnj*margin;}).attr("y",disks_input*40+190)//h.attr("rx",10).attr("ry",10).attr("width",biggest_disk)//bán kính đĩa lớn nhất.attr("height",10).attr("fill","rgba(105, 105, 105, 0.8)");}}}
Khai báo class GameEngine và phương thức:
classGameEngine{move(n,tower1,tower2,tower3){if(n>0){this.move(n-1,tower1,tower3,tower2);letnewX_disk=tower3+(total_disks-n)*(unit_disk/2);// Tọa độ x mới của đĩaletnewY_disk='',start_ycourier='';// Tõa độ y mới của đĩa và courierletGocourier='',Backcourier='';// Chiều đi và chiều về của courierletGoimg="giphy-right.gif",Backimg="giphy-left.gif";// Ảnh đi và ảnh về//kiểm tra xem đĩa cần chuyển có phải từ tower1 -> tower2 if(tower1==0&&tower3==margin){//gán hướng đi cho chim,Goimg là đi xuôi,Backimg là đi ngượcGocourier=Goimg;Backcourier=Backimg;//gán y ban đầu cho courierstart_ycourier=tower_height-disks_in_tower1*40+190;//gán y đích mới cho đĩanewY_disk=tower_height-disks_in_tower2*40+150;//sau khi chuyển đĩa thì giảm đĩa ở fromTower và toTowerdisks_in_tower1--;disks_in_tower2++;console.log("disks_in_tower1 "+disks_in_tower1+", disks_in_tower2 "+disks_in_tower2);}//Kiểm tra xem đĩa cần chuyển có phải từ tower1 -> tower3if(tower1==0&&tower3==2*margin){Gocourier=Goimg;Backcourier=Backimg;start_ycourier=tower_height-disks_in_tower1*40+190;newY_disk=tower_height-disks_in_tower3*40+150;disks_in_tower1--;disks_in_tower3++;console.log("disks_in_tower1 "+disks_in_tower1+", disks_in_tower3 "+disks_in_tower3);}//kiểm tra xem đĩa cần chuyển có phải từ tower2 -> tower1if(tower1==margin&&tower3==0){Gocourier=Backimg;//checkpoint được gán bằng tọa độ của toTower //kiểm tra xem nếu tọa độ của chim hiện tại(toTower) nhỏ hơn tọa độ tower1,2,3 //thì gán courier di chuyển ngược,còn lại là xuôiif(checkpoint<margin){Backcourier=Goimg;//đi ngược}else{Backcourier=Backimg;//đi xuôi}start_ycourier=tower_height-disks_in_tower2*40+190;newY_disk=tower_height-disks_in_tower1*40+150;disks_in_tower2--;disks_in_tower1++;console.log("disks_in_tower1 "+disks_in_tower1+", disks_in_tower2 "+disks_in_tower2);}//kiểm tra xem đĩa cần chuyển có phải từ tower2 -> tower3if(tower1==margin&&tower3==2*margin){Gocourier=Goimg;if(checkpoint>margin){Backcourier=Backimg;}else{Backcourier=Goimg;}start_ycourier=tower_height-disks_in_tower2*40+190;newY_disk=tower_height-disks_in_tower3*40+150;disks_in_tower2--;disks_in_tower3++;console.log("disks_in_tower2 "+disks_in_tower2+", disks_in_tower3 "+disks_in_tower3);}//kiểm tra xem đĩa cần chuyển có phải từ tower3 -> tower2 if(tower1==2*margin&&tower3==margin){Gocourier=Backimg;Backcourier=Goimg;start_ycourier=tower_height-disks_in_tower3*40+190;newY_disk=tower_height-disks_in_tower2*40+150;disks_in_tower2++;disks_in_tower3--;console.log("disks_in_tower2 "+disks_in_tower2+", disks_in_tower3 "+disks_in_tower3);}//kiểm tra xem đĩa cần chuyển có phải từ tower3 -> tower1if(tower1==2*margin&&tower3==0){Gocourier=Backimg;Backcourier=Goimg;start_ycourier=tower_height-disks_in_tower3*40+190;newY_disk=tower_height-disks_in_tower1*40+150;disks_in_tower1++;disks_in_tower3--;console.log("disks_in_tower1 "+disks_in_tower1+", disks_in_tower3 "+disks_in_tower3);}//kiểm tra delay để cho chim chạy cùng disk khi nhấc và chạy trước disk khi quay lạiif(delay_disk!=0){if(delay_courier==0){courier[0].transition().delay(2250).attr("xlink:href",Backcourier).duration(750).attr("y",0).transition().duration(750).attr("x",tower1+92.5).transition().duration(750).attr("y",start_ycourier-62);delay_courier++;}else{courier[0].transition().delay(delay_courier*4500+2250).attr("xlink:href",Backcourier).duration(750).attr("y",-1).transition().duration(750).attr("x",tower1+92.5).transition().duration(750).attr("y",start_ycourier-62);delay_courier++;}}//d3js để animation couriercourier[0].transition().delay(delay_disk*4500).attr("xlink:href",Gocourier).duration(750).attr("y",-1).transition().duration(750).attr("x",tower3+92.5)// tower3 + 92.5.transition().duration(750).attr("y",newY_disk-62);//d3js để animation diskarr_disk[n-1].transition().delay(delay_disk*4500).duration(750).attr("y",60).transition().duration(750).attr("x",newX_disk).transition().duration(750).attr("y",newY_disk);delay_disk++;//delay sau mỗi lần lặpcheckpoint=tower3;//gán checkpoint của courier cho toTowerconsole.log(checkpoint);console.log("Move disk "+n+" from "+tower1+" to "+tower3);steps++;// tính bước thực hiệnconsole.log("Steps: "+steps);this.move(n-1,tower2,tower1,tower3);}}}
Bước 4: Khai báo instances của Disk,Tower và GameEngine
functioncallbuild(){//button event thực hiện vẽ đĩa,tower,courierd3.selectAll("svg").remove();// Xóa thẻ svg trước đây để vẽ lại disk và tower khi ấn nhiều lần buttonsteps=click=delay_disk=delay_courier=0;// Đặt lại giá trị ban đầu cho biến đếmdisks_input=$('input').val();// Lấy giá trị từ inputtry{// Kiểm tra tính hợp lệ của inputif(isNaN(disks_input)||disks_input<=0||disks_input===''){throw"Please insert a number!";}}catch(e){alert("Please insert a number!");//chỗ này chỉ cần alert(e)return;}arr_disk=[];courier=[];disks_in_tower2=disks_in_tower3='';checkpoint='';i=1;// Đặt lại giá trị ban đầu cho các biếnsvg=d3.select(".container").append("svg").attr("width",$(".container").width()).attr("height",disks_input*40+300);// Tạo thẻ svg mới theo số input nhập vàotower_height=disks_input*40;// Tính chiều cao cho các cộtdisks_in_tower1=total_disks=disks_input;// Gán số đĩa từ input vào cột 1 và biến đếmletbuild_disk=newDisk();// Khởi tạo instance, gọi đến Object(đối tượng) đã khai báoletbuild_tower=newTower();build_tower.build_tower();// Gọi đến phương thức của Object thông qua instance đã khởi tạobuild_disk.build_disk();}
Bước 5: Khai báo instance game của GameEngine để chạy chương trình
functioncallmove(){//chỗ này dùng jquery $('').one('click') là đượcif(click>0){// Kiểm tra điều kiện, chỉ nhận 1 lần click gọi functionreturn;}click++;// Biến đếm kiểm tra điều kiện//disks_input=Number($('input').val());// Lấy giá trị từ inputletgameEngine=newGameEngine(disks_input);lettower1=0,tower2=margin,tower3=2*margin;// Đặt tọa độ cho cột 1,2,3gameEngine.move(disks_input,tower1,tower2,tower3);// Gọi phương thức của Object, đồng thời truyền parameter cho phương thức ấy}