Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <h1>Puzzle 15</h1>
<!-- UI 参考(by John Garcia) https://codepen.io/johnbgarcia/pen/vgdroe   -->
<p class="lead">by neorobin</p>
<!--<p id="solvability"></p>-->
<div class="game">
    <p class="win hide">YOU ROCK! YOU WON!你胜了</p>
</div>
<canvas id="board"></canvas>

              
            
!

CSS

              
                    :root {
        --map_size: 400px;
    }

    .hide {
        display: none;
    }

    body {
        background: #ccd1e0;
        text-align: center;
        font-family: "Barrio", cursive, STHupo; /* STCaiyun */
        color: #3A3335;
    }

    body h1 {
        font-size: 4em;
        margin-bottom: 0;
    }

    body .lead {
        margin-bottom: 2em;
    }

    body .game {
        position: relative;
    }

    body .game .win {
        position: absolute;
        top: 10px;
        left: 50%;
        width: 300px;
        margin-left: -150px;
        -webkit-text-stroke: 1px black;
        color: #FDF0D5;
        text-shadow: 3px 3px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
        transform: rotate(-15deg);
        font-size: 3.5em;
    }

    .fall {
        display: block;
        animation: fall-down 2s ease-in-out;
    }

    @keyframes fall-down {
        0% {
            top: -50px;
            opacity: 0;
        }
    }

    #board {
        width: var(--map_size);
        height: var(--map_size);
        margin: auto;
        background: #3A3335;
        border: 2px solid #3A3335;
        box-sizing: border-box;
        display: block;
        box-shadow: 0 10px 30px -10px #000000;
        font-family: Oswald;
    }

              
            
!

JS

              
                    const map_r_len = 4, map_c_len = 4;
    var VEB = map_r_len * map_c_len;  // value of empty block
    const map_size = 400;
    var block_size = map_size / map_r_len;
    document.addEventListener("DOMContentLoaded", function () {
        var c = document.getElementById("board");
        var ctx = c.getContext("2d");
        c.width = map_size;
        c.height = map_size;

        var map = [
            [1, 2, 3, 4],
            [5, 6, 7, 8],
            [9, 10, 11, 12],
            [13, 14, 15, VEB]
        ];

        var winMap = [
            [1, 2, 3, 4],
            [5, 6, 7, 8],
            [9, 10, 11, 12],
            [13, 14, 15, VEB]
        ];

        var tileMap = [];

        var tile = {
            width: block_size,
            height: block_size
        };

        var pos = {
            x: 0,
            y: 0,
            textx: block_size / 2 - 5,
            texty: block_size / 2 + 5
        };

        var drawTile = function () {
            ctx.fillStyle = "#EB5E55";
            ctx.shadowColor = "#000";
            ctx.shadowBlur = 4;
            ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = 2;
            ctx.fillRect(pos.x + 5, pos.y + 5, tile.width - 10, tile.height - 10);
            ctx.shadowColor = "transparent";
            ctx.fillStyle = "#FFFFFF";
            ctx.font = "20px Arial";
            //adjust center for larger numbers
            if (map[i][j] >= 10) {
                ctx.fillText(map[i][j], pos.textx - 2, pos.texty);
            } else {
                ctx.fillText(map[i][j], pos.textx + 2, pos.texty);
            }
        };

        var buildBoard = function () {
            for (i = 0; i < map_r_len; i++) {
                tileMap[i] = [];

                for (j = 0; j < map_c_len; j++) {
                    var currentTile = {
                        tileName: map[i][j],
                        x: pos.x,
                        y: pos.y,
                        width: block_size,
                        height: block_size,
                        colIndex: j
                    };

                    if (map[i][j] !== VEB) {
                        //create our numbered box
                        drawTile();
                        //push box id and cords to tilemap array
                    } else {
                        //create our zero box
                    }
                    tileMap[i].push(currentTile);
                    pos.textx += block_size;
                    pos.x += block_size;
                }
                pos.x = 0;
                pos.textx = block_size / 2 - 5 - 2;
                pos.texty += block_size;
                pos.y += block_size;
            }
        };

        //get mouse position
        function getPosition(event) {
            var x = event.x;
            var y = event.y;
            x -= c.offsetLeft;
            y -= c.offsetTop;

            //Check to see which box we are in
            for (var i = 0; i < tileMap.length; i++) {
                for (var j = 0; j < tileMap[i].length; j++) {
                    if (
                        y > tileMap[i][j].y &&
                        y < tileMap[i][j].y + tileMap[i][j].height &&
                        x > tileMap[i][j].x &&
                        x < tileMap[i][j].x + tileMap[i][j].width
                    ) {
                        checkMove(tileMap[i][j].tileName, tileMap[i][j].colIndex);
                    }
                }
            }
        }

        // detect if move is possible
        // 移动前 item: 被点击的数字; colIndex: 点击所在的 列号(从0开始)
        var checkMove = function (item, colIndex) {
            //check column for zero and clicked box
            // 列向移动
            var checkColumn = function () {
                // 移动前,空块所在的列号(从0开始)
                var empty_block_col_index = null;
                //check for zero, 搜索 空块 的 列号(从0开始)
                for (var x = 0; x < map_r_len; x++) {
                    t = map[x].indexOf(VEB);
                    if (t > -1) {
                        empty_block_col_index = t;
                        break;
                    }
                }
                if (empty_block_col_index === colIndex) { // 同列检测
                    //create a new array with column values
                    var tempArr = [];
                    for (var i = 0; i < map_r_len; i++) {
                        tempArr.push(map[i][empty_block_col_index]);
                    }
                    //keep track of our clicked item and zero
                    // 移动前,空块所在的行号(从0开始)
                    var empty_block_row_index = tempArr.indexOf(VEB);
                    // 移动前,点击处的行号(从0开始)
                    var clicked_row_index = tempArr.indexOf(item);

                    // 将空块移动到点击位置, 同列非空块相对位置不变
                    tempArr.splice(clicked_row_index, 0, tempArr.splice(empty_block_row_index, 1)[0]);

                    //update our map with the correct values for the column
                    for (var l = 0; l < map_r_len; l++) {
                        map[l][empty_block_col_index] = tempArr[l];
                    }
                }
            };

            //check row for zero and clicked box
            var checkRow = function () {
                for (var i = 0; i < map_r_len; i++) {
                    var clicked_col_index = map[i].indexOf(item);
                    var empty_block_col_index = map[i].indexOf(VEB);
                    //if zero and clicked box are present in same row
                    if (clicked_col_index > -1 && empty_block_col_index > -1) {
                        // 将空块移动到点击位置, 同行非空块相对位置不变
                        map[i].splice(clicked_col_index, 0, map[i].splice(empty_block_col_index, 1)[0]);
                        break;
                    }
                }
            };

            checkColumn();
            checkRow();

            clear();
        };

        var clear = function () {
            ctx.clearRect(0, 0, map_size, map_size);
            pos = {
                x: 0,
                y: 0,
                textx: block_size / 2 - 5,
                texty: block_size / 2 + 5
            };
            buildBoard();
            checkWin();
        };

        var checkWin = function () {
            var allMatch = true;
            for (var i = 0; i < winMap.length; i++) {
                if (!allMatch) {
                    break;
                }
                for (var j = 0; j < winMap[i].length; j++) {
                    if (map[i][j] !== winMap[i][j]) {
                        allMatch = false;
                        break;
                    }
                }
            }
            if (allMatch) {
                var winMessage = document.querySelector(".win");
                winMessage.classList.remove("hide");
                winMessage.classList.add("fall");
            }
        };

        var check_solvability = function () {
            var cnt_inversions = 0;
            old_cnt_inversions = cnt_inversions;
            var taxicab_distance_EB;
            for (var i = 0; i < map_r_len; i++) {
                for (var j = 0; j < map_c_len; j++) {
                    for (var r = i; r < map_r_len; r++) {
                        for (var c = 0; c < map_c_len; c++) {
                            if (r * map_r_len + c > i * map_r_len + j && map[r][c] < map[i][j]) {
                                cnt_inversions++;
                            }
                        }
                    }
                    // console.log(cnt_inversions- old_cnt_inversions);
                    // old_cnt_inversions = cnt_inversions;

                    // 空块离右下角的出租车距离 taxicab_distance_EB
                    if (map[i][j] == VEB) {
                        taxicab_distance_EB = (map_r_len - 1) - i + (map_c_len - 1) - j;
                    }
                }
            }
            return ((cnt_inversions + taxicab_distance_EB) & 1) == 0;
        };

        var random_map = function () {
            var tempArr = [];
            for (var i = 0; i < VEB; i++) {
                tempArr.push(i + 1);
            }
            for (var i = 0; i < map_r_len; i++) {
                for (var j = 0; j < map_c_len; j++) {
                    var rand_i = Math.floor(Math.random() * tempArr.length);
                    map[i][j] = tempArr.splice(rand_i, 1)[0];
                }
            }
        }

        var random_solvable_map = function () {
            random_map();
            // 若不可解, 随意交换两个非空块
            if (!check_solvability()) {
                var r = -1, c = -1;
                var done = false;
                for (var i = 0; i < map_r_len; i++) {
                    for (var j = 0; j < map_c_len; j++) {
                        if (map[i][j] != VEB && !done) {
                            if (r < 0) {
                                r = i;
                                c = j;
                            } else {
                                t = map[i][j];
                                map[i][j] = map[r][c];
                                map[r][c] = t;
                                done = true;
                            }
                        }
                        if (done) break;
                    }
                    if (done) break;
                }
            }
            if (!check_solvability()) {
                alert("生成可解排列失败");
            }
        }

        // random_map();
        random_solvable_map();

        buildBoard();
        c.addEventListener("mousedown", getPosition, false);

        document.getElementById("solvability").innerHTML = check_solvability() ? "可解" : "不可解";
    });
              
            
!
999px

Console