Kamis, 15 September 2022

Widget Kalender

Github:
https://github.com/forkHub/cemcem/tree/main/calendar


Widget kalender adalah pustaka fungsi untuk membantu membuat tampilan kalender pada website. Ada tiga fungsi utama untuk membuat tampilan kalender, salah satunya adalah fungsi untuk membuat tampilan kalender standard yang terdiri dari bulan, tahun, dan daftar tanggal.

Cara pakainya cukup mudah:

import * as kalender from "./Kalender.js"; 
document.body.appendChild(kalender.widget(8, 2022));

Bila dijalankan akan menampilkan sebuah tampilan standard kalender.
Anda bisa memilih untuk menaruh kalender di body, atau di bagian lain pada halaman.


Fungsi widget menerima dua parameter yaitu bulan dan tahun. Fungsi ini akan menghasilkan sebuah html element yang bisa di tambahkan sebagai child dari element yang sudah ada.

Tampilan ini adalah tampilan dasar, kita bisa mengkustom dengan cara mengedit class css tiap element. Tiap element diberi class yang lumayan banyak dan akan terus ditambahi untuk meningkatkan kustomisasi.

Anda bisa mendowload source code pada link github di atas.








Jumat, 18 Maret 2022

Algorithma Mengubah warna pada gambar menjadi 64 warna

Demo bisa dilihat disini : code (forkhub.github.io)


Pada tulisan ini Saya akan membahas algorithma untuk mengurangi jumlah warna pada gambar sehingga jumlahnya menjadi 64 warna saja. Kita sering melihat efek ini pada aplikasi pengedit gambar seperti Gimp. 

Dengan mengurangi jumlah warna pada gambar, maka kualitas gambar akan turun, seperti di bawah ini.


Setiap gambar dalam komputer terdiri dari kumpulan piksel, Tiap piksel memiliki 3 komponen warna, yaitu: merah, hijau dan biru. Tiap komponent warna memiliki intensitas mulai dari 0-255. Komputer bisa menampilkan sebanyak (256 x 256 x 256) warna atau sekitar 16 juta warna (16,777,216).

Tidak semua warna ditampilkan secara bersamaan, Saya belum menemukan sebuah gambar komputer yang memiliki warna sebanyak itu. 

Untuk mengubah jumlah warna sebuah gambar menjadi 64 warna saja, kita akan mengubah intensitas dari tiap komponen warna, yang awalnya memiliki 256 level menjadi hanya 4 saja.
 
Intensitas baru yang kita pakai akan terdiri dari 0, 85, 170, dan 255 saja. Angka ini adalah angka bebas. Saya memilih angka tersebut supaya persebarannya rata. Anda bisa mengubah-ubah angkanya untuk mendapatkan berbagai macam effect yang berbeda.

Bila setiap warna hanya memiliki level intensitas sebanyak 4 macam saja, maka jumlah warna yang bisa dihasilkan masimum adalah sebanyak 4 x 4 x 4 atau sama dengan 64 warna.

Dengan penyederhanaan ini maka setiap intensitas dari komponen warna yang ada akan dibulatkan ke intensitas baru yang terdekat. Kita bisa memilih metode pembulatannya: apakah pembulatan ke bawah, ke atas, atau pembulatan ke angka terdekat, atau kita bisa buat selang-seling antara pembulatan ke atas atau kebawah sesuai posisi dari pixelnya. 

Pembulatan selang-seling ini akan memberikan efek dithering, dan ini yang akan Saya pakai disini.

Kode sumbernya adalah sebagai berikut:

window.onload = () => {
    let canvas: HTMLCanvasElement = document.createElement('canvas');

    let ctx: CanvasRenderingContext2D = canvas.getContext('2d');

    let img: HTMLImageElement = document.getElementById('gbr') as HTMLImageElement;

    canvas.style.width = img.naturalWidth + 'px';

    canvas.style.height = img.naturalHeight + 'px';

    canvas.width = img.naturalWidth;

    canvas.height = img.naturalHeight;

    document.body.appendChild(canvas);

    ctx.drawImage(img, 0, 0);

    ubahWarnaGambar(canvas, [0, 85, 170, 255]);
}

function ubahWarnaKomponen(warna: number, level: number[], atas: boolean): number {

    for (let i: number = level.length - 2; i >= 0; i--) {

        if (warna > level[i]) {

            if (atas) {

                return level[i + 1];
            }

            else {

                return level[i];

            }

        }

        else if (warna == level[i]) {

            return warna;

        }

    }

    throw Error('warna tidak ada dalam level');

}

function checkBulatAtas(x: number, y: number): boolean {
    let modx: number = x % 2;
    let mody: number = y % 2;

    return modx == mody;
}

function ubahWarnaGambar(canvas: HTMLCanvasElement, level: number[]): void {

    let ctx: CanvasRenderingContext2D;

    let data: ImageData;

    ctx = canvas.getContext('2d');

    for (let i: number = 0; i < canvas.width; i++) {

        for (let j: number = 0; j < canvas.height; j++) {

            data = ctx.getImageData(i, j, 1, 1);

            data.data[0] = ubahWarnaKomponen(data.data[0], level, checkBulatAtas(i, j));

            data.data[1] = ubahWarnaKomponen(data.data[1], level, checkBulatAtas(i, j));

            data.data[2] = ubahWarnaKomponen(data.data[2], level, checkBulatAtas(i, j));

            ctx.putImageData(data, i, j);
        }

    }

}

Bagian awal dari kode diatas berfungsi untuk menggambar sebuah gambar pada kanvas. Manipulasi gambar akan dilakukan pada kanvas.

Fungsi ubahWarnaGambar() adalah fungsi untuk mengubah warna gambar. Fungsi ini menerima parameter berupa kanvas. Gambar yang akan kita ubah warnanya kita gambar dulu ke dalam kanvas. Kemudian kita akan menggunakan perintah kanvas getImageData() untuk mendapatkan informasi warna dari tiap pikselnya. 

getImageData() akan menghasilkan sebuah object. Dari object ini kita bisa mengakses property data yang berisi berisi informasi tentang intensitas warna merah, hijau, biru, dan juga transparansi dari tiap piksel. 

Selanjutnya kita akan mengubah intensitas dari warna merah, biru dan hijau ini dengan memanggil fungsi ubahWarnaKomponen(). Tiap komponen warna akan dicheck intensitasnya dan dibulatkan ke intensitas yang baru. Pembulatan bisa bersifat ke atas ataupun ke bawah, sesuai dengan nilai yang dibelikan oleh parameter  atas.

Parameter atas didapatkan dari Fungsi checkBulatAtas(). Fungsi ini mengecek apakah kita akan melakukan pembulatan ke atas atau ke bawah. Pembulatan ke atas kita lakukan bila posisi x dan y keduanya sama-sama ganjil atau sama-sama genap. Posisi ini adalah posisi yang letaknya selang-seling seperti posisi kotak pada papan catur.

Hasilnya bisa dilihat pada gambar di atas. Kita bisa meningkatkan kualitas gambar dengan memainkan angka-angka pada intensitas sesuai dengan gambarnya. 

Saat ini nilai intensitas yang baru berlaku pada semua komponen warna, kita bisa menerapkan nilai intensitas yang berbeda untuk setiap komponen warna, dan hasilnya mungkin bisa lebih baik lagi.

Kita akan bahas itu di tulisan selanjutnya. Terima kasih sudah membaca tulisan ini. Bila ada pertanyaan, silahkan langsung tulis di komentar. 
 

Kredit:




    















Jumat, 04 Februari 2022

Blijs: JavaScript Library untuk kanvas

BLIJS adalah kumpulan library untuk menangani kanvas dan input. 

Syntax dari library ini menggunakan syntax dari BlitzBasic. BlitzBasic adalah sebuah bahasa engigne yang pernah populer di tahun 2000 dan sekarang sudah menjadi open source (tautan).

Saya menyukai BlitzBasic karena kemudahan dalam struktur bahasanya, dan kemampuannya yang sangat bagus saat itu untuk membuat berbagai macam aplikasi berbasis flash sebelum adanya flash. 

Saat ini sudah ada dokumentasi dalam format google.doc dan playground untuk mengetest beberapa demo.


Minggu, 17 Oktober 2021

[DRAFT] Membuat Aplikasi To Do List Tanpa Menggunakan Framework

tautan demo:
Anda bisa mencoba applikasinya di link berikut:
https://hagarden.netlify.app/tdl/


playground:
https://hagarden.netlify.app/tdl/edit.html

Aplikasi ini tidak menggunakan database. Data disimpan dalam bentuk json dan disimpan di local storage.

wireframe:




Layout HTML:

<div class='cont tdl'>

    <div class='header'>

        <span class='judul'>>> RENCANAKU</span>

        <button class='tambah'>+</button>

    </div>

    <div class='bayangan'>

    </div>

    <div class="daftar-cont">

    </div>

    <template>

        ....

    </template>

</div>

Secara umum layout ini bisa di bagi tiga, di bagian atas berisi judul dan tombol untuk menambah item. Di bagian bawah ada kontainer untuk menampung item-item yang sudah dibuat dan dibawahnya lagi ada template untuk menyimpan template-template yang kita pakai.

penjelasan file tdl.js:
File tdl.js adalah file yang berisi kode sumber utama dari applikasi. Hampir semuanya ditaruh disini.

"use strict";

let tdl = {};

tdl.item = {};

tdl.db = {};

tdl.view = {

    tambahTbl: document.body.querySelector('div.tdl div.header button.tambah'),

    daftarCont: document.body.querySelector('div.tdl div.daftar-cont')

};


    • Layout Utama
      • HTML
      • Membuat tautan
    • Layout Item
      • Posisi terbuka
      • Posisi Tertutup
  • Menambahkan Membuat Item Baru
    • Membuat Event on click
    • Isi data deskripsi
    • Membuat View
  • Event pada view
      • Edit
      • Hapus
      • Update
      • Sudah
      • Belum  Selesasi
  • Database
    • Simpan
    • Load
    • Hapus

Sabtu, 13 Maret 2021

Room generator untuk dungeon


Algorithma untuk membuat ruangan dalam dungeon.





Kodenya di desain agar bisa dijalankan secara step by step ataupun langsung.

Disertai dengan editor untuk kode sumbernya sehingga bisa di edit dan di test secara langsung.

Jumat, 19 Februari 2021

Warung WA Warung berbasis WA [draft]

Warung WA adalah warung yang terinspirasi dari group WA jualan di perumahan selama pandemi. Banyak ibu-ibu ataupun bapak-bapak yang bergabung dalam group WA untuk berjualan.

http://warungwa.hagarden.xyz/

Warung ini sederhana. Semua transaksi dilakukan lewat WA dan tampilan warung berfungsi sebagai katalog. 

Warung ini bisa mendukung banyak anggota dimana tiap anggota bisa memiliki lapak-lapak sendiri dan bisa saling membantu mempromosikan barang sesama penjual.


Purwarupa: Silsilah Keluarga

Aplikasi silsilah keluarga sederhana yang dibuat dengan menggunakan javascript.

Bisa di coba disini:
https://hagarden.netlify.app/silsilah/




Applikasi ini masih berupa prototype dan dalam pengembangan.

Rabu, 30 September 2020

Agorithma Path Finding #9: Config [DRAFT]

Ini adalah pembahasan ke 9 dari seri Algorithma Path Finding. Pembahasan pertama bisa Anda baca pada tautan ini.

Pada pembahasan sebelumnya kita membahas tentang refaktoring kode sumber kita untuk persiapan pembahasan selanjutnya yang akan banyak membahas tentang variasi dalam path finding. Kita akan membuat algorithma kita lebih fleksibel dengan konfigurasi yang bisa diubah-ubah sesuai kebutuhan.

Kode sumber bisa di download disini


Anda juga bisa mencoba secara daring disini.

Bila Anda mencoba demo di atas, maka Anda akan melihat di pojok kanan atas ada menu kecil. Saat ini  pilihannya hanya ada dua: yaitu Fast dan A star. Untuk melihat perbedaan keduanya silahkan lihat pada videonya. Perilaku algorithma akan terlihat berubah pada kondisi-kondisi tertentu.

Apa yang berubah?
Perubahan pertama yang terlihat adalah adanya tambahan menu. Pembuatan menu sendiri sebenarnya bukan termasuk pembahasan dari algorithma path finding, namun diperlukan untuk membantu agar aplikasinya bisa berjalan.

Perubahan kedua adalah penambahan konfigurasi pada algorithma yang kita pakai. Dengan adanya konfigurasi ini kita tidak perlu membuat algorithma terpisah untuk masing-masing perilaku. 

Perubahan pada file PathFinder.js
function cellBuat(parent, x, y, tx, ty) {
    let cell = {
        x: x,
        y: y,
        buka: 1,
        jarak: -1,
        induk: parent,
        g: parent ? parent.g + 1 : 0
    };
    if (PF_CEPAT == pfConfig.mode) {
        cell.jarak = Math.abs(tx - x) + Math.abs(ty - y);
    }
    else if (PF_A_STAR == pfConfig.mode) {
        cell.jarak += (cell.g * 1.1);
    }
    return cell;
}

Fungsi cellBuat memiliki sedikit perubahan untuk menghitung jarak.
Pada fungsi sebelumnya jarak kita hitung dengan menghitung jarak horizontal dan vertikal. Sekarang jarak ditentukan tergantung dari modenya. Bila mode-nya adalah PF_CEPAT maka penghitungannya sama dengan sebelumnya, bila tidak maka penghitungannya ditambah dengan menambahkan faktor g.












Senin, 28 September 2020

HAGL - Library WebGL Sederhana

Awalnya Saya hanya ingin mencari library WebGL yang fokus ke rendering, dan sudah nemu juga, namanya PIXI.js. PIXI.js sangat bagus sekali, sangat recomended. Tapi PIXI punya masalah yang Saya kurang sreg, yaitu ukurannya yang terlalu besar, yaitu hampir 1/2 MB. Terlau besar, menurut Saya untuk sebuah library. Padahal target saya, ukurannya gak lebih besar dari 100 kb. 

Setelah mempertimbangkan masak-masak, akhirnya Saya memutuskan untuk membuat library WebGL sendiri. Library ini Saya beri nama HAGL.

HAGL adalah sebuah library sederhana untuk menggambar menggunakan WebGL di browser.

HAGL bukan game enggine, tapi bisa digunakan untuk merender game, khususnya game 2D.

HAGL bisa untuk menggambar image, tileset, ataupun spritesheet.

HAGL bukan 3d enggine. HAGL hanya memfokuskan pada fungsi utama dari WebGL sebagai rasterising engine. Cocok untuk game 2D di browser yang butuh performa tambahan untuk menggambar.


Cara penggunaannya sangat sederhana:

var gl = new Hagl(canvas);
gl.drawImage(image, x, y);

Atau bisa juga pakai pilihan

var gl = new Hagl(canvas);
gl.drawImage(image, x, y, {pilihan})

pilihan yang tersedia antara lain:
skala, rotasi, alpha, offset, dan textureUV.

Contoh penggunaan tersedia dalam demo yang bisa dicoba disini:
https://hagarden.blogspot.com/p/blog-page.html

Saat ini fitur dan demonya masih sedikit. Saya akan menambahkan sedikit demi sedikit pada update-update selanjutnya.

Jumat, 25 September 2020

Algorithma Path Finding #8: Refaktor

Ini adalah pembahasan ke 8 dari seri Algorithma Path Finding. Pembahasan pertama bisa Anda baca pada tautan ini.

Pada pembahasan sebelumnya kita membahas tentang A * (Star). A Star path finding menghasilkan jalur yang lebih optimal dari sebelumnya.

Pada pembahasan kali ini, Saya tidak membahas hal yang baru. Saya hanya melakukan bersih-bersih rumah. Pembahasan kita sampai saat ini sudah semakin kompleks dan sudah mencapai titik dimana kita perlu mengadakan perapihan sebelum membahas pembahasan yang lebih mendalam lagi.

Kode sumber yang sudah dirapikan bisa di unduh disini.

Apa saja yang berubah?

Perubahan pada file index.html

Saya merapikan css nya, dan menambahkan style baru pada elemen kanvas. Saya suka dengan style piksel dan senang bekerja pada resolusi rendah. Selama ini tampilannya cenderung nge-blur karena hal ini. Untuk itulah Saya menambahkan style pada canvas agar lebih ber-piksel

canvas {
position: absolute;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-crisp-edges;
image-rendering: pixelated;
image-rendering: crisp-edges;
}

Kemudian saya menyederhanakan struktur html dengan menghapus semua atribut pada elemen kanvas.

<canvas></canvas>

Semua atribut pada kanvas akan diatur dari pemrograman.

Penambahan file Data.js

Saya menambahkan file Data.js yang akan menampung semua variabel. Awalnya variabel dismpan jadi satu dengan file Game.js. Kita sekarang memisahkan antara data dengan logika dan alur dari program.

Pada file ini, Saya juga menambahkan variabel baru yaitu gp dan gl. Masing-masing berisi informasi mengenai resolusi game. Sekarang kita akan mengontrol resolusi dari sini.

Dan beberapa perubahan kecil lainnya yang tidak bisa dibahas satu persatu.

Sekarang struktur kita sudah siap, dan kita siap untuk membahas algorithma ini ke arah yang lebih kompleks lagi. 

Terima kasih sudah mampir dan membaca.

.



Jumat, 11 September 2020

Algorithma Path Finding #7: A Star

In adalah pembahasan ke 7 dari seri Algorithma Path Finding. Pembahasan pertama bisa Anda baca pada tautan ini.

Pada pembahasan sebelumnya kita membahas tentang bagaimana membuat karakter berjalan lebih halus dalam penerapan algorithma path-finding

Pada tulisan kali ini kita akan membahas tentang A * (A Star) Path finding. A * Pathfinding adalah penyempurnaan dari algorithma path finding sebelumnya yang biasa dikenal dengan istilah fast-path-finding. 

A * Path finding akan menghasilkan jalur yang lebih optimal.

Perhatikan perbedaan keduanya dalam kedua gambar berikut.

Jalur yang dibuat dari algorithma sebelumnya hasilnya tidak optimal. Karakter menempuh jalur yang lebih panjang. 


Jalur yang dihasilkan dari algorithma A* jauh lebih optimal. Jalurnya lebih pendek.

Demonya bisa dicoba online di tautan ini:

Kode sumber bisa di unduh di tautan ini:

Video Youtube:




Apa yang berubah?

Dalam pembahasan kali ini kita mengenalkan konsep G. G adalah 'jarak' dari posisi awal ke posisi sekarang. G dihitung dengan cara berbeda dengan jarak sebelumnya (H) yang dihitung dari posisi sekarang ke posisi tujuan.

G tidak dihutung secara kira-kira/heuristic seperti H. G adalah jarak sebenarnya. Kita mendapatkan G dengan cara menambahkan nilai G dari cell parent. Cell parent mendapatkan nilai G dari parent sebelumnya, hingga ke cell yang pertama.  Dengan cara ini, maka nilai G tidak dikira-kira.

Sebenarnya istilah 'jarak' kurang cocok untuk G, namun untuk menyamakan dengan H, maka saya pakai istilah jarak. G sebenarnya lebih cocok disebut biaya perjalanan dari titik awal ke titik sekarang.

Pada pembahasan sebelumnya kita menghitung jarak dengan cara mengira-ira dari posisi sekarang keposisi target. 

Jarak(F) = H          ...1)


Dengan adanya G maka perhitungannya jadi:.

Jarak(F) = G + H      ...2)

G biasanya tidak berdiri sendiri tapi diberi pemberat.

Jarak (F) = G * P + H ...3)

P adalah pemberat. Bila nilainya satu maka G * P = G, (persamaan 2).

P bernilai lebih dari atau sama dengan satu. Semakin besar P, maka G akan semakin berat, dan hal ini akan mempengaruhi penghitungan jarak. Semakin besar pengaruh G, maka jarak yang dihasilkan akan semakin optimal, namun waktu yang dibutuhkan untuk menyelesaikan algorithma juga semakin lama. 

Perubahan kode sumber

Kita melakukan perubahan pada kode sumber untuk mengenalkan G.

Perubahan pertama ada pada fungsi cellBuat().

function cellBuat(parent, x, y, tx, ty) {
    ...
    let cell = {
        x: x,
        y: y,
        buka: 1,
        jarak: -1,
        induk: parent,
        g: parent ? parent.g + 1 : 0
    };

    cell.jarak = Math.abs(tx - x) + Math.abs(ty - y);
    cell.jarak += (cell.g * 1.1);

    return cell;
}

Kita menambahkan property g. Nilainya dihitung dari parent sebelumnya ditambah satu. Bila parentnya tidak ada, karena ini adalah cell yang pertama, maka g diisi dengan nol.

Selanjutnya pada penghitungan jarak kita tambahkan jarak yang dihasilkan dengan perhitungan sebelumnya dengan g. Kita juga memberi pemerat pemberatnya 1.1 pada g. Anda bisa mengisi pemberat dengan angka berapa saja asal lebih dari satu. Namun harus diperhatikan bahwa semakin besar pengaruh g, maka semakin lama waktu yang dibutuhkan untuk menyelesaikan algorithma.


Terima kasih sudah mampir dan membaca







Senin, 07 September 2020

Membuat kanvas bersifat responsif #2: Layar Penuh

Pada pembahasan kali ini kita akan membahas bagaimana membuat kanvas yang bersifat responsif dengan cara men-skala kanvas agar bisa memenuhi layar, dengan tetap menjaga rasio agar tampilan tidak melar. 

Sebelumnya kita sudah membahas tentang macam-macam skala dan bagaimana men-skala kanvas dengan menjaga agar tidak ada bagian yang terpotong.

Kode sumber bisa diunduh di tautan ini:

Demo bisa dilihat di tautan ini. Silahkan ubah-ubah ukuran browser untuk melihat hasilnya.

Contoh hasilnya adalah seperti gambar di bawah ini:


Tampilan landskape


Tampilan portrait

Dari kedua tampilan di atas terlihat bagaimana kanvas akan selalu terlihat memenuhi layar.

Penjelasan Kode:
Bisa dibilan 99% kode pada pembahasan sekarang sama persih dengan kode pada pembahasan sebelumnya. Yang membedakan hanyalah satu baris saja. Perhatikan fungsi resize berikut:

function resize() {
    let cp = 360;
    let cl = 218;
    let wp = window.innerWidth;
    let wl = window.innerHeight;
    let ratio = Math.max((wp / cp), (wl / cl));
    let cp2 = Math.floor(cp * ratio);
    let cl2 = Math.floor(cl * ratio);
    canvas.style.width = cp2 + 'px';
    canvas.style.height = cl2 + 'px';
    canvas.style.top = ((wl - cl2) / 2) + 'px';
    canvas.style.left = ((wp - cp2) / 2) + 'px';
    gambarCanvas(canvasCtx);
}

Perbedaannya adalah dalam mencari ratio.

    let ratio = Math.max((wp / cp), (wl / cl));

Bila sebelumnya kita menggunakan Math.min(), maka sekarang kita pakai Math.max().
Dengan menggunakan Math.max(), kita mendapatkan ratio yang paling besar. 

Bila panjang horisontal kanvas hasil skala sama dengan tinggi layar, maka bagian vertikal kanvas akan terpotong. Bila tingginya yang sama, maka bagian panjangnya yang akan terpotong.

Penjelasan lengkapnya untuk bagian yang lain bisa dibaca pada tulisan sebelumnya.

Terima kasih telah mampir dan membaca. Pada tulisan berikutnya Saya akan membicarakan metode responsif yang lain, Insya Allah.




Senin, 31 Agustus 2020

Algorithma Path Finding #6: Jalan yang lebih halus

In adalah pembahasan ke 6 dari seri Algorithma Path Finding. Pembahasan pertama bisa Anda baca pada tautan ini.

Pada tulisan sebelumnya, kita telah membahas bagaimana membuat karakter yang berjalan mengikuti jalur path-finding. Karakter berjalan dengan cara 'melompat-lompat' dari satu grid ke grid berikutnya.

Pada pembahasan kali ini, kita akan melakukan penyempurnaan cara berjalan. Karakter tidak lagi berjalan secara melompat-lompat, tapi berjalan perlahan-lahan dari satu grid ke grid berikutnya.

Kode sumber nya bisa diunduh di tautan ini:
https://drive.google.com/file/d/1-uGzY1QBcaCyZbUr4SgJSHZyXSQZMMw_/view?usp=sharing

Demo daring bisa dilihat di tautan ini:
https://hagarden.netlify.app/moon/400_jalan/

Video Youtube juga tersedia sbb:



Perubahan kode dari pembahasan sebelumnya.

Perubahan pertama adalah pada struktur data karakter.

let karakter = {
    jalur: [],
    jalurn: 0,
    pindahJml: 4,
    pindahn: 0,
    pos: {
        x: 32,
        y: 32
    },
    status: st_idle
};

Kita tambahkan dua property baru, yaitu pindahJml dan pindahn.
pindahJml berisi informasi jumlah langkah yang dibutuhkan untuk berpindah dari satu grid ke grid berikutnya. pindahn berisi informasi langkah yang ke-berapa.

Posisi karakter sekarang dirubah dari posisi di grid menjadi posisi di layar. Pada tulisan sebelumnya, posisi karakter berada pada posisi 1,1. Posisi ini merujuk pada posisi grid. Sekarang posisi karakter adalah 32, 32 yang merujuk pada posisi di layar. Satu grid adalah 32 x 32 pixel.

Perubahan berikutnya adalah penambahan fungsi-fungsi baru, antara lain:

function krkPosisiGrid(karakter) {
    return {
        x: Math.floor(karakter.pos.x / 32),
        y: Math.floor(karakter.pos.y / 32)
    };
}

Fungsi ini akan menghasilkan informasi posisi grid dimana karakter sekarang berdiri.

Fungsi baru berikutnya adalah fungsi krkCheckPosisiDiGrid().

function krkCheckPosisiDiGrid(karakter) {
    if (karakter.pos.x % 32)
        return false;
    if (karakter.pos.y % 32)
        return false;
    return true;
}

Fungsi ini mengecek apakah karakter sekarang sedang berada di grid atau berada diantara grid. Saat karakter berpindah dari grid satu ke grid berikutnya, maka posisi karakter sedang berada di antara grid sampai karakter tersebut sampai di grid berikutnya.

Fungsi berikutnya adalah fungsi krkPindahGrid()

function krkPindahGrid(karakter) {
    let posAwalX;
    let posAwalY;
    let posSelanjutnyaX;
    let posSelanjutnyaY;
    let jarakX;
    let jarakY;
    let posBaruX;
    let posBaruY;

    karakter.pindahn++;

    //posisi grid sekarang
    posAwalX = karakter.jalur[karakter.jalurn][0] * 32;
    posAwalY = karakter.jalur[karakter.jalurn][1] * 32;

    //posisi grid target
    posSelanjutnyaX = karakter.jalur[karakter.jalurn + 1][0] * 32;
    posSelanjutnyaY = karakter.jalur[karakter.jalurn + 1][1] * 32;

    //jarak dari grid sekarang ke target
    jarakX = posSelanjutnyaX - posAwalX;
    jarakY = posSelanjutnyaY - posAwalY;

    //posisi karakter baru
    posBaruX = posAwalX + (karakter.pindahn / karakter.pindahJml) * jarakX;
    posBaruY = posAwalY + (karakter.pindahn / karakter.pindahJml) * jarakY;

    karakter.pos.x = posBaruX;
    karakter.pos.y = posBaruY;

}

Fungsi ini adalah fungsi yang menangani perpindahan karakter dari grid satu ke grid berikutnya. Proses penghitungannya adalah sebagai berikut.

Pertama kita tambah nilai dari pindahn. Variable ini berisi informasi kita sedang di langkah ke berapa

    karakter.pindahn++;

Selanjutnya kita dapatkan posisi grid awal sebelum karakter berpindah.

    //posisi grid sekarang
    posAwalX = karakter.jalur[karakter.jalurn][0] * 32;
    posAwalY = karakter.jalur[karakter.jalurn][1] * 32;

Nilainya kita kali 32 untuk merubah dari posisi grid ke posisi di layar.

Setelah itu kita dapatkan posisi grid berikutnya, yang merupakan grid tujuan.

    //posisi grid target
    posSelanjutnyaX = karakter.jalur[karakter.jalurn + 1][0] * 32;
    posSelanjutnyaY = karakter.jalur[karakter.jalurn + 1][1] * 32;

Kemudian kita hitung jarak perpindahan dari grid sekarang ke grid berikutnya.

    //jarak dari grid sekarang ke target
    jarakX = posSelanjutnyaX - posAwalX;
    jarakY = posSelanjutnyaY - posAwalY;

Posisi karakter yang baru dihitung dari posisi awal + posisi perpindahan

//posisi karakter baru
    posBaruX = posAwalX + (karakter.pindahn / karakter.pindahJml) * jarakX;
    posBaruY = posAwalY + (karakter.pindahn / karakter.pindahJml) * jarakY;

Hasil dari posisi baru ini disimpan di karakter.

    karakter.pos.x = posBaruX;
    karakter.pos.y = posBaruY;

Berikutnya Kita akan membahas perubahan pada fungsi berikutnya, yaitu fungsi update():

function update() {
    if (karakter.status == st_idle) {
    }
    else if (karakter.status == st_jalan) {
        if (krkCheckPosisiDiGrid(karakter)) {
            karakter.jalurn++;
            if (karakter.jalurn >= karakter.jalur.length - 1) {
                karakter.status = st_idle;
            }
            else {
                karakter.status = st_jalan;
                karakter.pindahn = 0;
                krkPindahGrid(karakter);
            }
        }
        else {
            krkPindahGrid(karakter);
        }
    }
}

Kita merubah cara karakter berjalan. Tiap kali posisi karakter berada tepat di grid, maka kita tambahkan nilai dari jalurn.

if (krkCheckPosisiDiGrid(karakter)) {
  karakter.jalurn++;

Kemudian kita check apakah jalurn sudah mencapai maksimal, artinya karakter sudah sampai pada index terakhir. Bila ya, maka kita ubah status karakter menjadi idle.

if (karakter.jalurn >= karakter.jalur.length - 1) {
  karakter.status = st_idle;
}

Bila tidak, maka kita ubah statusnya jadi jalan lagi.


karakter.status = st_jalan;
karakter.pindahn = 0;

krkPindahGrid(karakter);

Untuk selanjutnya, maka kode sumbernya sama dengan sebelumnya,

Terima kasih telah mampir dan membaca.














Minggu, 23 Agustus 2020

Algorithma Path Finding #5: Membuat karakter berjalan mengikuti jalur yang dibuat oleh algorithma path-finding

In adalah pembahasan ke 5 dari seri Algorithma Path Finding. Pembahasan pertama bisa Anda baca pada tautan ini.

Dalam tulisan sebelumnya Kita telah membahas tentang penerapan algoritma path-finding untuk mencari jalur pada daerah yang di klik di layar pada layar yang bersifat responsif.

Dalam tulisan kali ini, Kita akan membahas bagaimana membuat karakter berjalan mengikuti jalur yang telah dibuat oleh algoritma path-finding tersebut, tentu saja pada kanvas yang responsif karena kita akan mencobanya bukan hanya di PC tapi juga di telepon gengam.

Demonya bisa dilihat disini:

Kode sumbernya bisa di unduh disini:

Videonya juga tersedia di youtube:



Penjelasan kode sumber:

Pembahasan sekarang lebih kompleks dari sebelumnya sehingga sekarang file javascript-nya Kita pisah menjadi beberapa bagian antara lain:
  • Pathfinder.js: menangani algorithma path finding
  • Game.js: file utama 
  • Peta.js: menangani peta
  • Window.js: menangani segala yang berhubungan dengan window seperti saat window di- resize.
Untuk selanjutnya Saya akan memakai file js terpisah.

Struktur Object Karakter:

Dalam pembahasan kali ini kita akan memiliki sebuah karakter yang berjalan mengikuti jalur path finding. Untuk itu kita perlu menyusun struktur untuk karakter ini.

let karakter = {
    jalur: [],
    jalurn: 0,
    pos: {
        x: 1,
        y: 1
    },
    status: st_idle
};

Struktur karakter ini adalah struktur minimal untuk karakter yang bisa berjalan. 

jalur: berisi informasi jalur hasil path-finding. Jalur disimpan dalam bentuk Array.
jalurn: informasi index ke berapa dari jalur di atas.
pos: berisi informasi posisi x dan y
status: status karakter saat ini

Karakter memiliki status yang didefinisikan dalam konstanta sbb:

const st_idle = 1;
const st_jalan = 2;

window.onload()

Saat saat event window.onload() Kita membuat interval untuk proses perulangan. Dalam perulangan ini kita memanggil fungsi update() dan render(). Fungsi update() akan memperbaharui informasi karakter sedangkan fungsi render() akan melakukan proses penggambaran.

setInterval(() => {
  update();
  render();
}, 100);


canvas.onclick()

Pada saat kanvas diklik, kita melakukan pengecekan terdahulu apakah saat ini status karakter sedang berjalan atau tidak. Bila karakter sedang berjalan maka kita langsung mengakhiri fungsi ini. Kita harus menunggu karakter selesai berjalan sebelum memberi perintah baru.

  if (karakter.status != st_idle)
    return;

Bila ternyata karakter sedang diam, maka kita lanjutkan prosesnya. Kita mencari posisi klik di layar. Metodenya sama dengan tulisan sebelumnya.

let rect = canvas.getBoundingClientRect();
let poslx = (e.clientX - rect.x) * canvasScaleX;
let posly = ((e.clientY - rect.y) * canvasScaleY);
let posx = Math.floor(poslx / 32);
let posy = Math.floor(posly / 32);

Kemudian kita mencari jalur ke posisi tersebut dari posisi karakter yang terakhir.

let hasil = pfCariJalan(karakter.pos.x, karakter.pos.y, posx, posy);

Kemudian kita mengubah status karakter dengan mengisi informasi yang dibutuhkan agar karakter mulai berjalan.

karakter.status = st_jalan;
karakter.jalur = hasil;
karakter.jalurn = -1;

Kita mengubah status karakter yang awalnya diam ke berjalan. Kita juga memasukkan jalur hasil algoritma path-finding.
Saat karakter belum berjalan, isi dari variable jalurn adalah -1. Saat karakter berjalan, maka jalurn akan mulai berjalan dari angka 0 dst. Hal ini karena Array dimulai dari angka 0.

fungsi Update():

Fungsi update berisi proses untuk memperbaharui informasi karakter.

function update() {
  if (karakter.status == st_idle) {}
  else if (karakter.status == st_jalan) {
    if (karakter.jalurn >= (karakter.jalur.length - 1)) {
      karakter.status = st_idle;
    }
    else {
      karakter.jalurn++;
      karakter.pos.x = karakter.jalur[karakter.jalurn][0];
      karakter.pos.y = karakter.jalur[karakter.jalurn][1];
   }
  }
}

Kita mengecek status dari karakter.
Bila statusnya st_idle, maka tidak ada yang dilakukan.
Bila statusnya st_jalan maka kita memperbaharui proses jalan.
Saat berjalan, kita mengecek apakah proses jalan sudah selesai. Hal ini ditandai dengan jalurn yang sudah melebihi panjang dari jalur path-finding.

Bila karakter sudah selesai berjalan, maka statusnya kita rubah ke st_idle.

if (karakter.jalurn >= (karakter.jalur.length - 1)) {
  karakter.status = st_idle;
}

Bila proses jalan belum selesai, maka kita menambah isi dari jalurn. Kemudian kita mengubah posisi karakter sesuai dengan informasi dari jalur path finding.

karakter.jalurn++;
karakter.pos.x = karakter.jalur[karakter.jalurn][0];
karakter.pos.y = karakter.jalur[karakter.jalurn][1];

Fungsi Render():

Fungsi ini berisi proses pengambaran. Kita menggambar peta, jalan dan jalur sesuai informasi yang ada. Kita tidak membahas terlalu dalam mengenai fungsi ini karena isinya Saya rasa sudah cukup jelas dan sudah sering dibahas di tulisan-tulisan sebelumnya.


Terima kasih sudah mampir dan membaca. Bila ada pertanyaan, jangan sungkan untuk bertanya.







Rabu, 19 Agustus 2020

Algorithma Path Finding #4 Mencari posisi klik pada kanvas yang di skala

In adalah pembahasan ke 4 dari seri Algorithma Path Finding. Pembahasan pertama bisa Anda baca pada tautan ini.

Pada tulisan sebelumnya kita telah membahas bagaimana menggunakan algorithma path-finding untuk mencari jalan pada posisi yang di klik pada kanvas. Dan kita juga membahas bagaimana membuat kanvas bersifat responsif, agar bisa menyesuaikan dengan ukuran layar.

Sekarang kita akan melanjutkan dengan menggabungkan keduanya. Kita akan mencari jalur pada posisi yang diklik pada kanvas yang bersifat responsif.

Hasil nya bisa Anda dicoba di sini.

Silahkan buka linknya, dan ubah ukuran browsernya kemudian klik pada kanvas.  Anda bisa lihat bagaimana algorithma path-finding ini bekerja pada ukuran kanvas yang berbeda-beda.

Kode sumber bisa diunduh di sini

Atau Anda bisa mencoba mengedit kodenya secara langsung disini:



Mendeteksi posisi yang di klik pada kanvas yang bersifat responsif memiliki tantangan tersendiri. Hal ini karena kanvas tidak hanya memiliki ukuran yang berbeda-beda namun juga memiliki posisi yang berbeda-beda pula.

Kode sumber dari pembahasan kali diambil dari dua tulisan sebelumnya dengan sedikit perubahan. Disini saya akan membahas perubahannya saja. Sisanya sama saja dan bisa dilihat pada artikel sebelumnya, atau anda bisa mengunduh kode sumbernya.

canvas.onclick = (e) => {
    let rect = canvas.getBoundingClientRect();
    let poslx = (e.clientX - rect.x) * canvasScaleX;
    let posly = ((e.clientY - rect.y) * canvasScaleY);
    let posx = Math.floor(poslx / 32);
    let posy = Math.floor(posly / 32);
    bersihkanLayar();
    hasil = pfCariJalan(1, 1, posx, posy);
    gambarPeta();
    gambarJalan(hasil);
};

Perubahan yang paling mendasar adalah pada saat canvas.onclick(). Pada tulisan sebelumnya kita mencari posisi klik di kanvas dengan menggunakan e.clientX dan e.clientY saja. Hal ini tidak berlaku untuk kanvas yang responsif.

Proses penghitungannya sbb:

Pertama kita mencari dulu posisi kanvas dilayar. Posisi kanvas sekarang selalu berada di tengah menyesuaikan dengan dimensi layar.

    let rect = canvas.getBoundingClientRect();

getBoundingClientRect() akan menghasilkan posisi, panjang dan lebar kanvas. Kemudian kita menggunakan nilainya untuk melakukan perhitungan selanjutnya.

    let poslx = (e.clientX - rect.x) * canvasScaleX;
    let posly = (e.clientY - rect.y) * canvasScaleY;

e.clientX dan e.clientY adalah posisi diklik di layar, bukan di kanvas.. 

rect.x dan rect.y adalah posisi kanvas.

canvasScaleX dan canvasScaleY adalah skala dari kanvas yang dihitung saat kanvas diskala mengikuti ukuran layar. Anda bisa melihat pada fungsi resize() saat kanvas diskala

let posx = Math.floor(poslx / 32);
let posy = Math.floor(posly / 32);

Karena kita menggunakan grid dengan panjang dan lebar 32 maka nilai posisi akhirnya kita bagi 32.

Selanjutnya seperti biasa, kita melakukan proses pencarian jalur, update kanvas, dst seperti pada tulisan sebelumnya.

Pada tulisan berikutnya, Saya akan membahas bagaimana membuat karakter berjalan mengikuti jalur yang telah dibuat oleh algorithma path-finding.


Terima kasih sudah mampir dan membaca. Bila ada pertanyaan, silahkan bertanya di komentar