classTowerDemo{constructor(){}changeDisk(Disk,colA,colB,colC){if(Disk>0){this.changeDisk(Disk-1,colA,colC,colB)console.log(`Move ${Disk} from ${colA} --> ${colC} `)this.changeDisk(Disk-1,colB,colA,colC)}}}lettower=newTower()tower.changeDisk(2,"colA","colB","colC")
colA: tháp bắt đầu
colB: tháp trung gian,
colC: tháp đích
Disk: số lượng đĩa cần di chuyển
Các giải
Chuyển n - 1 đĩa từ tháp bắt đầu A () tới tháp trung gianB--->(~ tháp đích) chuyển B tháp bắt đầu lúc này C là tháp trung gian
Chuyển đĩa n từ tháp bắt đầu A tới tháp đích C
Chuyển n - 1 đĩa từ tháp B (~ tháp bắt đầu) qua tháp C (~ tháp đích) lúc này A là tháp trung gian
Tiến hành bước 1 và 3, áp dụng lại thuật giải cho n-1.
Soure code: sử dụng ES6 and OOP
Bước 1
Tạo các đối tượng Disk, Tower, GameEngine
classDisk{constructor(name){this.name=name}}classTower{constructor(name){this.name=name}}classGameEngine{constructor(){this.counter// Dem cac buoc thuc hien }move(disk,col1,col2,col3){if(disk>0){move(disk-1,col1,col3,col2)console.log(`move ${disk} from ${col1} to ${col2}`)move(disk-1,col2,col1,col3)}}}
console hien thi
Move 1 from colA --> colB
Move 2 from colA --> colC
Move 1 from colB --> colC
Bước 2: Thêm biến data cho class GameEngine để lưu lại các bước thực hiện bài toán
Lưu dữ liệu từng bước (step) chuyển đĩa vào đối tượng GameEngine để xử lý
Dữ liệu step kiểu đối tượng các 3 thuộc tính:
name: tên đĩa {object Disk}
fromTower: di chuyển từ tháp {}
toTower: di chuyển đến tháp {}
Mỗi step được lưu trữ trong dữ liệu data []
classGameEngine{constructor(){this.counter=0this.step={}this.data=[]}move(n,col1,col2,col3){if(n>0){this.move(n-1,col1,col3,col2)// console.log(`Move Disk ${n} from ${col1.name} to ${col3.name}`)this.step={name: disks[n-1]// Phần tử cuối cùng của disks[]fromTower: col1,// Tháp bắt đầutoTower: col3// Tháp đích}this.data.push(this.step)this.counter++this.move(n-1,col2,col1,col3)}}}
Data lưu tại step với thông tin đối tượng disk đi chuyển, đối tượng tháp bắt đầu, đối tượng tháp đến từ đó ta có thể �lấy ta dữ liệu từng step để tính toán
Bước 3: Draw
Thêm phương thức draw cho đối tượng Disk và Tower (mặc định 3 tháp)
Tạo khung SVG để vẽ tháp, tính toán các khoảng các cho 1 khung chứa 1 tháp (tháp còn lại tượng tự cộng thêm k/c toạ độ X)
// Khai báo khung SVGconstsvgFrame={width: 1200,// Width khung svg để vẽ thápheight: 500,// Height khung svgtowerFrame: 400,// Khung tower có 3 tháp nên chia đều khung svg cho 3 thápdisNear: 400,// Khoảng cách tower liền kề nhaudisFar: 800,// Khoảng cách tower xa nhaudiskHeight: 30,// Chiều dày đĩamarginX: 50,// margin trục X trong khung towermarginY: 50,// margin trục Y trong khung towerthick: 15,// Chiều dày tháp towerbirdImg: './bird.gif'// url image}constsvg=d3.select('body').append('svg')svg.attr('class','main-svg')svg.attr('width',svgFrame.width)svg.attr('height',svgFrame.height)
Dựa vào khung ở trên chia 1 tower chiếm 1200/3 = 400 width tiến hành vẽ tháp
Thêm các thuộc tính từ svgFrame để vẽ tháp
>Tower// Them thuộc tính (disOfTower) để kiểm tra số lượng đĩa trong tháp từ đó tính �toạ độ Y của đĩa trong thápclassTower{constructor(name,diskOfTower){this.name=name// Tên thápthis.diskOfTower=diskOfTower// Số lượng đĩa có trong tháp dạng arraythis.towerWidth=svgFrame.towerFrame// khung towerthis.thick=svgFrame.thick// Chiều dày thápthis.marginX=svgFrame.marginX// Khoảng cách trục Xthis.marginY=svgFrame.marginY// Khoảng cách trục Y}drawTower(n){svg.append('rect').attr('class','rect-svg')// add class rect-svg.attr('rx',5)// border-radius trục X.attr('ry',5)// boder-radius trục Y.attr('width',this.towerWidth-(2*this.marginX))// Chiều dài tháp = width - k/c 2 bên.attr('height',this.thick)// Chiểu rộng tháp theo trục X.attr('x',this.marginX+this.towerWidth*n)//Tọa độ X = marginX + ứng với mỗi tháp mặc định n = 0, distance = 400.attr('y',svgFrame.height-this.marginY)// Tọa độ Y = Height khung - k/c dưới (marginY)svg.append('rect').attr('class','rect-svg')// add class rect-svg.attr('rx',2)// border-radius trục X.attr('ry',2)// boder-radius trục Y.attr('width',this.thick)// Chiểu rộng tháp theo trục Y.attr('height',this.towerWidth-(3*this.marginX))// Chiều dài tháp = width - k/c 2 bên .attr('x',(this.towerWidth/2)-(this.thick/2)+(this.towerWidth*n))// Tọa dộ trục X .attr('y',svgFrame.height-this.marginY-(this.towerWidth-(3*this.marginX)))// Tọa độ trục Y}}>Disk// Trong đối tượng Disk thêm 2 thuộc tính vị trí trong mảng disks (posOfTower) và vị trí trên tháp (posOfTower)// Vị trí trong mảng để tính độ dài của đĩa vị trí càng lớn độ dài càng lớn// Vị trí trong tháp để tính toạn độ theo trục Y vị trí càng cao thì Y càng giảmclassDisk{constructor(nameTower,posOfDisks,posOfTower){this.name=nameTower// tên đĩathis.posOfDisks=posOfDisks// Vị trí trong mảng chứa đĩa Disksthis.posOfTower=posOfTower// Vị trí trên thápthis.height=svgFrame.diskHeight// Chiều dày của đĩathis.x_=0// Tọa độ sau khi vẽ xong ở điểm bắt đầu (0,0)}drawDisk(){svg.append('rect')// Vẽ hình chữ nhật.attr('class','disk-svg'+this.posOfDisks)// Thêm class.attr('rx',5)// border-radius trục X.attr('ry',5)// boder-radius trục Y.attr('width',this.height*(this.posOfDisks+1))// Chiều dài đĩa .attr('height',this.height)// Chiều rộng đĩa.attr('x',(svgFrame.towerFrame/2)-(this.height*(this.posOfDisks+1))/2)// Tọa độ X (i số tự tự đĩa: i tăng 0 ---> length).attr('y',(svgFrame.height-svgFrame.marginY-this.height)-this.height*this.posOfTower)// Tọa độ Y (n so thu tutọa độ trục Y: n giam length ---> 0).style('fill',function(){// Random màu fillreturn"hsl("+Math.random()*360+",80%,60%)";})}}letnumberTower=3// So luong thapletnumberDisk=3// Số lượng đĩalettowers=[]letdisks=[]// Vẽ thápfor(leti=0;i<numberTower;i++){if(i===0){towers.push(newTower('Tower'+(i+1),disks))// Vẽ đĩa tên Tower1}else{towers.push(newTower('Tower'+(i+1),[]))// Các Tower còn lại trống}towers[i].drawTower(i)}//Vẽ đĩafor(leti=0;i<numberDisk;i++){disks.push(newDisk('Disk'+(i+1),i+1,numberDisk-1-i))disks[i].drawDisk()}
Trong đối tượng GameEngine thêm 2 phương thức:
distanceTower(tower1,tower2)==> trả về khoảng cách giữa 2 tháp khi đĩa di chuyển
diskOfTowerUpdate(disk, tower1, tower2) ==> khi đĩa di chuyển số lượng đĩa trong tháp bắt đầu loại bỏ phần tử cuối, và tháp đích thêm đĩa di chuyển tới
Animation
Trong GameEngine thêm phương thức start() để tạo animation cho game
start(){this.data.forEach((disk,i)=>{// Lấy khoảng cách giữa 2 tháp theo từng stepletdistance=this.distanceTower(disk.fromTower.name,disk.toTower.name)// Chiều cao của đĩaletheightDisk=disk.name.height// Vị trí của đĩa trong mảng DisksletposOfDisks=disk.name.posOfDisks//Vị trí của đĩa trong TowerletposOfTower=disk.name.posOfTower// Toa do cua dia ban dau theo truc X (lúc đầu chưa di chuyển toạ độ (0,0))letcooX=disk.name.x_// Toa do cua dia ban dau theo truc YletcooY=svgFrame.towerFrame-(3*svgFrame.marginX)-heightDisk*posOfTower// Kiểm tra số lượng đĩa đang có trong tháp đíchletcounter=disk.toTower.diskOfTower.length// console.log(counter.length)// Toạ độ mới của disk theo trục XletnewCooX=cooX+distance// Toạ độ mới của disk theo theo trục YletnewCooY=heightDisk*posOfTower-counter*heightDisk// Cập nhập lại đĩa có trong thápthis.diskOfTowerUpdate(disk.name,disk.fromTower,disk.toTower)// Disk amimate sử dụng D3JS// d3.selectAll(('.disk-svg' + posOfDisks) + ',' + '.bird')d3.selectAll(('.disk-svg'+posOfDisks)).transition().delay(i*3000+1000).duration(1000).attr('transform','translate('+cooX+','+-cooY+')').transition().attr('transform','translate('+newCooX+', '+-cooY+')').transition().attr('transform','translate('+newCooX+','+newCooY+')')// Bird amimated3.selectAll('.bird').transition().delay(i*3000).duration(1000).attr('transform','translate('+cooX+','+cooY+')').transition().attr('transform','translate('+cooX+','+0+')').transition().attr('transform','translate('+newCooX+', '+0+')').transition().attr('transform','translate('+newCooX+','+cooY+')').transition().attr('transform','translate('+newCooX+','+0+')').transition().attr('transform','translate('+-cooX+','+0+')').transition().attr('transform','translate('+-cooX+','+cooY+')')// Cập nhật lại toạ độ của disk disk.name.x_+=distance})}
Bước 5: Hoàn thiện thêm đối tượng Bird tạo thêm hiệu ứng animation cho đẹp, sử lý thêm logic tuỳ thuộc và độ các của tháp khi vẽ ở trên