Loquicom 11 months ago
commit
8f9190f790
4 changed files with 496 additions and 0 deletions
  1. 86 0
      boulier-standalone.js
  2. 87 0
      boulier.css
  3. 204 0
      boulier.js
  4. 119 0
      index.html

+ 86 - 0
boulier-standalone.js

@@ -0,0 +1,86 @@
+
+const boulier = [[true,true,true,false,false,false], [true,true,true,false,false,false]];
+
+move(0, 1);
+print();
+move(0, 0);
+move(1, 2);
+print();
+move(1, 0);
+print();
+move(0, 4);
+move(1, 5);
+print();
+
+function move(line, col) {
+    if (boulier.length <= line || boulier[line].length <= col || ! boulier[line][col]) {
+        return false;
+    }
+    let data = boulier[line]
+
+    // Détermine si il faut bouger les boules vers la gauche ou la droite
+    let right = true;
+    for (let i = col; i < data.length; i++) {
+        if (!data[i]) {
+            right = false;
+            break;
+        }
+    }
+
+    // Si a droite inverse la ligne
+    if (right) {
+        data = data.reverse();
+        col = data.length - col - 1;
+    }
+
+    data = makemove(data, col);
+
+    // Si a droite remet la ligne
+    if (right) {
+        data = data.reverse();
+    }
+}
+
+function makemove(data, col) {
+    // Compte le nombre de boule à deplacer et l'index pour faire le déplacement
+    let cpt = 0;
+    let gap = false;
+    let index = null;
+    for (let i = col; i < data.length; i++) {
+        if (!gap && data[i]) {
+            cpt++;
+            data[i] = false;
+        } else if (gap && data[i]) {
+            index = i - 1;
+            break;
+        } else {
+            gap = true;
+        }
+    }
+    if (index == null) {
+        index = data.length - 1;
+    }
+
+    // Déplace les boules
+    for (let i = 0; i < cpt; i++) {
+        data[index - i] = true;
+    }
+
+    return data;
+}
+
+function print() {
+    let str = '';
+    for (const line of boulier) {
+        str += '-|';
+        for (const boule of line) {
+            if (boule) {
+                str += 'o';
+            } else {
+                str += '-';
+            }
+        }
+        str += '|-\n';
+    }
+    console.log(str);
+}

+ 87 - 0
boulier.css

@@ -0,0 +1,87 @@
+.grid {
+    column-gap: 0;
+    grid-template-columns: repeat(auto-fit,minmax(0%,1fr));
+}
+
+.cell {
+    position: relative;
+    margin: 0;
+    height: 2.6em;
+}
+
+.line {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    height: .6em;
+    width: 100%;
+    background-color: grey;
+    z-index: -1;
+}
+
+/* Boule et couleur */
+
+.ball {
+    border-radius:50%;
+    width: 2.5em;
+    height: 2.5em;
+    border:2px solid;
+    margin-left: auto;
+    margin-right: auto;
+    cursor: pointer;
+}
+
+.red {
+    background-color: #e53935;
+    border-color: #e53935;
+}
+
+.pink {
+    background-color: #d81b60;
+    border-color: #d81b60;
+}
+
+.purple {
+    background-color: #5e35b1;
+    border-color: #5e35b1;
+}
+
+.indigo {
+    background-color: #3949ab;
+    border-color: #3949ab;
+}
+
+.blue {
+    background-color: #039be5;
+    border-color: #039be5;
+}
+
+.cyan {
+    background-color: #00acc1;
+    border-color: #00acc1;
+}
+
+.green {
+    background-color: #43a047;
+    border-color: #43a047;
+}
+
+.lime {
+    background-color: #c0ca33;
+    border-color: #c0ca33;
+}
+
+.yellow {
+    background-color: #fdd835;
+    border-color: #fdd835;
+}
+
+.amber {
+    background-color: #ffb300;
+    border-color: #ffb300;
+}
+
+.orange {
+    background-color: #fb8c00;
+    border-color: #fb8c00;
+}

+ 204 - 0
boulier.js

@@ -0,0 +1,204 @@
+class Boulier {
+
+    static instance = null;
+    static colors = {
+        red: "Rouge",
+        pink: "Rose",
+        purple: "Violet",
+        indigo: "Indigo",
+        blue: "Bleu",
+        cyan: "Cyan",
+        green: "Vert",
+        lime: "Lime",
+        yellow: "Jaune",
+        amber: "Ambre",
+        orange: "Orange"
+    };
+
+    static setup(elt, nbLine, nbBoule) {
+        Boulier.instance = new Boulier(elt, nbLine, nbBoule);
+        return Boulier.instance;
+    }
+
+    static get(elt, nbLine, nbBoule) {
+        if (Boulier.instance != null) {
+            return Boulier.instance;
+        } else if (elt != null && nbLine != null && nbBoule != null) {
+            return Boulier.setup(elt, nbLine, nbBoule);
+        } else {
+            return false;
+        }
+    }
+
+    static isSaved() {
+        return localStorage.getItem('boulier-ball') != null;
+    }
+
+    static load(elt) {
+        if (Boulier.instance == null) {
+            Boulier.setup(elt, 1, 1);
+        }
+        const instance = Boulier.get();
+        instance.load();
+        return instance;
+    }
+
+    static clear() {
+        localStorage.clear();
+    }
+
+    constructor(elt, nbLine, nbBall) {
+        const emptySpace = Math.floor(nbBall / 2);
+
+        this._elt = elt;
+        this._callback = null;
+        this._ball = nbBall;
+        this.boulier = [];
+        this.color = [];
+
+        for (let i = 0; i < nbLine; i++) {
+            const line = []
+            for (let j = 0; j < nbBall + emptySpace; j++) {
+                line.push(j < nbBall);
+            }
+            this.boulier.push(line);
+            this.color.push(['red']);
+        }
+    }
+
+    save() {
+        localStorage.setItem('boulier-data', JSON.stringify(this.boulier));
+        localStorage.setItem('boulier-color', JSON.stringify(this.color));
+        localStorage.setItem('boulier-ball', this._ball);
+    }
+
+    load() {
+        this.boulier = JSON.parse(localStorage.getItem('boulier-data'));
+        this.color = JSON.parse(localStorage.getItem('boulier-color'));
+        this._ball = parseInt(localStorage.getItem('boulier-ball'));
+    }
+
+    move(line, col) {
+        // Vérifie les paramètres
+        if (this.boulier.length <= line || this.boulier[line].length <= col || ! this.boulier[line][col]) {
+            return false;
+        }
+        let data = this.boulier[line];
+
+        // Détermine s'il faut bouger les boules vers la gauche ou la droite
+        let right = true;
+        for (let i = col; i < data.length; i++) {
+            if (!data[i]) {
+                right = false;
+                break;
+            }
+        }
+
+        // Si les mouvement est vers la droite, on inverse la ligne
+        if (right) {
+            data = data.reverse();
+            col = data.length - col - 1;
+        }
+
+        // Compte le nombre de boule à deplacer et l'index de l'endroit où faire le déplacement
+        let cpt = 0;
+        let gap = false;
+        let index = null;
+        for (let i = col; i < data.length; i++) {
+            if (!gap && data[i]) {
+                cpt++;
+                data[i] = false;
+            } else if (gap && data[i]) {
+                index = i - 1;
+                break;
+            } else {
+                gap = true;
+            }
+        }
+        if (index == null) {
+            index = data.length - 1;
+        }
+
+        // Déplace les boules
+        for (let i = 0; i < cpt; i++) {
+            data[index - i] = true;
+        }
+
+        // Si le mouvement était vers la droite, on remet la ligne dans le bon sens
+        if (right) {
+            data = data.reverse();
+        }
+
+        // Callback
+        if (this._callback != null && typeof this._callback === 'function') {
+            this._callback(Boulier.instance);
+        }
+    }
+
+    reset() {
+        for(let i = 0; i < this.boulier.length; i++) {
+            this.move(i, this.boulier[i].length - 1);
+        }
+    }
+
+    render() {
+        let html = '';
+        for (let i = 0; i < this.boulier.length; i++) {
+            html += '<div class="grid">';
+            // Boule
+            let cptBall = 0;
+            let changeColor = Math.round(this._ball / this.color[i].length);
+            for(let j = 0; j < this.boulier[i].length; j++) {
+                const elt = this.boulier[i][j];
+                html += '<div class="cell"><div class="line"></div>';
+                if (elt) {
+                    let ballColor = this.color[i][Math.trunc(cptBall/changeColor)];
+                    if (ballColor == null) {
+                        ballColor = this.color[i][this.color[i].length - 1];
+                    }
+                    html += '<div class="ball ' + ballColor + '" data-line="' + i + '" data-col="' + j + '" onclick="bouleClick(this)"></div>';
+                    cptBall++;
+                }
+                html += '</div>';
+            }
+            html += '</div>';
+        }
+        this._elt.innerHTML = html;
+    }
+
+    onChange(callback) {
+        this._callback = callback;
+    }
+
+    tostring() {
+        let str = '';
+        for (const line of this.boulier) {
+            str += '-|';
+            for (const boule of line) {
+                if (boule) {
+                    str += 'o';
+                } else {
+                    str += '-';
+                }
+            }
+            str += '|-\n';
+        }
+        return str;
+    }
+
+    print() {
+        console.log(this.tostring());
+    }
+
+}
+
+function bouleClick(elt) {
+    const boulier = Boulier.instance;
+    if (boulier == null) {
+        return;
+    }
+    const line = parseInt(elt.getAttribute('data-line'));
+    const col = parseInt(elt.getAttribute('data-col'));
+    boulier.move(line, col);
+    boulier.render();
+}

+ 119 - 0
index.html

@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="utf-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1">
+	<title>Boulier</title>
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
+    <link rel="stylesheet" href="boulier.css">
+</head>
+<body>
+    <style>
+        #reset {
+            text-align: center;
+            margin-top: 1em;
+        }
+
+        #color-picker {
+            text-align: center;
+            margin-top: 2em;
+        }
+
+        .hide {
+            display: none;
+        }
+    </style>
+
+    <main class="container">
+        <div id="setup" class="hide">
+            <article>
+                <form>
+                    <label for="nb-line">
+                        Nombre de ligne
+                        <input type="number" id="nb-line" name="nb-line" required>
+                    </label>
+                    <label for="nb-ball">
+                        Nombre de boules
+                        <input type="number" id="nb-ball" name="nb-ball" required>
+                    </label>           
+                </form>
+                <button onclick="setup()">Valider</button>
+            </article>
+        </div>
+        <div id="boulier"></div>
+        <div id="color-picker"></div>
+        <div id="reset" class="hide"><a href="#" onclick="boulier.reset();boulier.render()">Reset</a>&nbsp;|&nbsp;<a href="#" onclick="Boulier.clear();location.reload()">Clear</a></div>
+    </main>
+    
+
+    <script src="boulier.js"></script>
+    <script>
+        let boulier;
+        if (Boulier.isSaved()) {
+            boulier = Boulier.load(document.getElementById('boulier'));
+            renderAll();
+        } else {
+            document.getElementById('setup').classList.remove('hide');
+        }
+
+        /* --- Setup --- */
+        function setup() {
+            document.getElementById('setup').classList.add('hide');
+            const nbLine = parseInt(document.getElementById('nb-line').value);
+            const nbBall = parseInt(document.getElementById('nb-ball').value);
+            boulier = Boulier.setup(document.getElementById('boulier'), nbLine, nbBall);
+            boulier.save();
+            renderAll();
+        }
+
+        function renderAll() {
+            document.getElementById('reset').classList.remove('hide');
+            boulier.render();
+            boulier.onChange(boulier.save);
+            renderColor();
+        }
+
+        /* --- Color picker --- */
+        function renderColor() {
+            let html = '';
+            for (let i = 0; i < boulier.color.length; i++) {
+                const line = boulier.color[i];
+                html += '<div>Ligne ' + (i+1) + ' :</div><div class="grid"><div><div role="button" data-line="' + i + '" onclick="changeNbColor(this, -1)"' + ((line.length < 2) ? ' disabled' : '') + '>-</div></div>';
+                for(let j = 0; j < line.length; j++) {
+                    html += '<div><select id="color-' + i + '-' + j + '" data-line="' + i + '" data-pos="' + j + '" onchange="changeColor(this)">';
+                    for (const color in Boulier.colors) {
+                        html += '<option value="' + color + '"' + ((color == line[j]) ? ' selected' : '') + '>' + Boulier.colors[color] + '</option>';
+                    }
+                    html += '</select></div>';
+                }
+                html += '<div><div role="button" data-line="' + i + '" onclick="changeNbColor(this, 1)"' + ((line.length >= boulier._ball) ? ' disabled' : '') + '>+</div></div></div>';
+            }
+            document.getElementById('color-picker').innerHTML = html;
+        }
+
+        function changeNbColor(elt, nb) {
+            const line = parseInt(elt.getAttribute('data-line'));
+            if (nb > 0) {
+                for (let i = 0; i < nb; i++) {
+                    boulier.color[line].push('red');
+                }
+            } else if (nb < 0) {
+                for (let i = 0; i < -nb; i++) {
+                    boulier.color[line].pop();
+                }
+            }
+            boulier.save();
+            boulier.render();
+            renderColor();
+        }
+
+        function changeColor(elt) {
+            const line = parseInt(elt.getAttribute('data-line'));
+            const pos = parseInt(elt.getAttribute('data-pos'));
+            boulier.color[line][pos] = elt.value;
+            boulier.save();
+            boulier.render();
+        }
+    </script>
+</body>
+</html>