2048bot

Lie back and watch the dumb bot play!

// ==UserScript==
// @name         2048bot
// @namespace    http://tampermonkey.net/
// @version      0.9415
// @description  Lie back and watch the dumb bot play!
// @author       boynextdesk
// @match        https://play2048.co/*
// @icon         https://play2048.co/favicon.ico
// @grant        none
// @license      GNU General Public License v3.0
// ==/UserScript==

/*
* 128   64  32  16
* 64    32  16  8
* 32    16  8   4
* 16    8   4   2
*
* */

(function() {
    // todo: inspect whether game is over
    // todo: train strategy after game-over
    // todo: set speed and strategy through page
    // todo: beautify layout
    // todo: everlasting game - auto retry until winning
    // todo: bundle direction-ava functinos into an object to global users
    'use strict';
    const eventUp = new KeyboardEvent('keydown', {
        key: "w",
        keyCode: 87,
        which: 87,
        code: "KeyW",
        location: 0,
        description: "w"
    });
    const eventLeft = new KeyboardEvent('keydown', {
        key: "a",
        keyCode: 65,
        which: 65,
        code: "KeyA",
        location: 0,
        description: "a"
    });
    const eventRight = new KeyboardEvent('keydown', {
        key: "d",
        keyCode: 68,
        which: 68,
        code: "KeyD",
        location: 0,
        description: "d"
    });
    const eventDown = new KeyboardEvent('keydown', {
        key: "s",
        keyCode: 83,
        which: 83,
        code: "KeyS",
        location: 0,
        description: "s"
    });

    // thanks to https://zeit.co/blog/async-and-await
    function sleep (time) {
        return new Promise((resolve) => setTimeout(resolve, time));
    }
    function lose() {
        return document.getElementsByClassName("game-message game-over")[0] != null
    }
    function win() {
        return document.getElementsByClassName("game-message game-win")[0] != null
    }
    function over() {
        return lose() || win()
    }
    function inspect() {
        const board = Array();
        for(let j = 1 ; j <= 4 ; j++) // row
        {
            const unit4 = Array();
            for(let i = 1 ; i <= 4 ; i++) //column
            {
                const className = "tile-position-"+i+"-"+j
                const tiles = document.getElementsByClassName(className)
                const len = tiles.length
                const tile = tiles[len - 1]
                /// console.log(className)
                let val = 0;
                if(tile != null){
                    val = tile.firstChild.lastChild.nodeValue
                    val = Number(val)
                    /// console.log(val)
                }
                unit4[i] = val
            }
            board[j] = unit4
        }
        return board
    }
    const aboveBox = document.getElementsByClassName("above-game")[0];
    const btnTemplate = document.getElementsByClassName("restart-button")[0];
    let speed = 10;
    let speedIndex = 0;
    let switchU = true;
    aboveBox.appendChild(btnTemplate.cloneNode(false));
    aboveBox.appendChild(btnTemplate.cloneNode(false));
    aboveBox.appendChild(btnTemplate.cloneNode(false));
    //
    const btns = document.getElementsByClassName("restart-button");
    //
    const btnStartAuto = btns[1];
    const startText = document.createTextNode("Start Auto");
    btnStartAuto.appendChild(startText)
    //
    const btnStopAuto = btns[2];
    const stopText = document.createTextNode("Stop Auto");
    btnStopAuto.appendChild(stopText)
    //
    const btnSpeedAuto = btns[3];
    const speedText = document.createTextNode("Change Speed");
    btnSpeedAuto.appendChild(speedText)
    //
    let moveOn = false;
    let moveCnt = 0;
    let globalBoard = null;
    document.tagName = "not input"
    const strategy01 = function () {
        const num = Math.random();
        let dispatchee = null;
        moveCnt += 1
        const rate = 1 - Math.min(48 / moveCnt, 0.8)
        const downL = 0.02 * rate
        const rightL = downL + 0.02 * rate;
        if (num < downL) {
            dispatchee = eventDown;
            console.log("down");
        } else if (num < rightL) {
            dispatchee = eventRight;
            console.log("right");
        } else if (switchU) {
            dispatchee = eventLeft
            console.log("left")
            switchU = false
        } else {
            dispatchee = eventUp
            console.log("up")
            switchU = true
        }
        return dispatchee
    };
    const strategy02 = function () {
        const board = globalBoard
        if(board == null)
            return eventLeft
        const directionAvailable = function(offsetI, offsetJ){
            for(let i = 1 ; i <= 4 ; i++){
                for(let j = 1 ; j <= 4 ; j++){
                    const ni = i + offsetI
                    const nj = j + offsetJ
                    if(ni < 1 || ni > 4) continue
                    if(nj < 1 || nj > 4) continue
                    if(board[i][j] === 0) continue;
                    if(board[ni][nj] === board[i][j] || board[ni][nj] === 0)
                        return true
                }
            }
            return false
        }
        const dir = [[0, -1], [-1, 0], [1, 0], [0, 1]]
        const msg = ["left", "up", "down", "right"]
        const funcArr = [eventLeft, eventUp, eventDown, eventRight]
        for(let i = 0 ; i < 4 ; i++){
            const con = directionAvailable(dir[i][0], dir[i][1])
            if(con){
                console.log(msg[i])
                return funcArr[i]
            }
            //console.log(msg[i] + " unavailable")
        }
        return funcArr[0]
    }
    const moveOneKey = function () {
        if (!moveOn) {
            console.log("Cancelled.")
            return
        }
        const dispatchee = strategy02();
        document.dispatchEvent(dispatchee)
    };

    const startListener = async function () {
        console.log("start")
        moveOn = true;
        while (moveOn) {
            await sleep(10 * speed).then(() => {
                if(over()){
                    moveOn = false
                    console.log("Game over.")
                }
                const board = inspect();
                console.log(board)
                globalBoard = board
            })
            await sleep(70 * speed).then(moveOneKey)
        }
    };

    const stopListener = function () {
        moveOn = false;
        moveCnt = 0
    }
    const speedListener = function () {
        const speeds = [10, 5, 1];
        const words = ["switch to low speed", "switch to fast speed", "switch to super fast speed"];
        const len = 3
        speedIndex = (speedIndex + 1) % len
        speed = speeds[speedIndex]
        console.log(words[speedIndex])
    }

    btnStartAuto.addEventListener('click', startListener)
    btnStopAuto.addEventListener('click', stopListener)
    btnSpeedAuto.addEventListener('click', speedListener)

})();