如何让这两个事件处理程序协同工作?

How Can I Get These Two Event Handlers to Work Together?

我正在使用 cm-chessboard and chess.js 创建国际象棋开局训练器。这个想法是从 Lichess Opening Explorer API 中提取连续的国际象棋位置,玩家通过移动下一个最佳移动来测试自己 and/or 回答关于开口名称的问题。

不过,我有点儿头疼了。

目前,它只对轮到计算机的用户进行提问。我无法找到一种方法让它等待用户按下 return 键,然后再显示有关玩家移动的答案。

这是我的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Chess Opening Trainer</title>
    <meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0"/>    
    <link rel="stylesheet" href="https://alftheelf.github.io/Chess-Opening-Trainer/cm-chessboard/styles/cm-chessboard.css"/> 
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500&display=swap" rel="stylesheet">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.2/chess.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

    <style type="text/css">

        body{font-family: 'Montserrat', sans-serif;}

        .grid-container {
        display: grid;
        grid-template-columns: 1fr 0.6fr;
        grid-template-rows: 1fr;
        gap: 0px 0px;
        }

        .board {
            grid-area: 1 / 1 / 2 / 3;
            width: 800px;
            max-width: 800px;
        }
        #interface { 
            grid-area: 1 / 2 / 2 / 3; 
            border-style: solid;
            border-width: thin;
            width: 400px;
            max-width: 400px;
        }

        p{
            width: 100%;
            align: center;
        }

    </style>
</head>
<body>


<div class="grid-container">
  <div class="board" id="board"></div>
  <div id="interface">
      <span id="prompt"></span>
  </div>
</div>


<script type="text/javascript">


//SETTINGS

var side = 'black' //select which side to play as
var rating = '1600';
var realistic = true;
var limit = 50;


//Define initial variables

var state = null;
var moveCount = 0;
if (side == 'black') {var notSide = 'white'} else{var notSide = 'black'}
var positionAnswer
var bestMove
var opponentMove
var PGN


//define functions

var addEvent = document.addEventListener ? function(target,type,action){
    if(target){
        target.addEventListener(type,action,false);
    }
} : function(target,type,action){
    if(target){
        target.attachEvent('on' + type,action,false);
    }
}

addEvent(document,'keydown',function(e){
    e = e || window.event;
    var key = e.which || e.keyCode;
    if(key===13){ //Press the return key
        keypress()
    }
});

function keypress(){
    if (state == 'questionPosition') { //Then reveal the answer

        document.getElementById("prompt").innerHTML = positionAnswer;

        setTimeout(() => {
            document.getElementById("prompt").innerHTML = positionAnswer + "<br><br>key) What is " + side + "\'s best move?"
            state = "questionBestMove";
        }, 500)
    }
}


</script>

<script type="module">
    
    import {INPUT_EVENT_TYPE, COLOR, Chessboard, MARKER_TYPE} from "https://alftheelf.github.io/Chess-Opening-Trainer/cm-chessboard/src/cm-chessboard/Chessboard.js"
    import {BORDER_TYPE} from "https://alftheelf.github.io/Chess-Opening-Trainer/cm-chessboard/src/cm-chessboard/Chessboard.js"

    const chess = new Chess() //Creates a new Chess() object. Add a FEN string as an argument to start from a FEN.

    function inputHandler(event) {
        console.log("event", event)
        event.chessboard.removeMarkers(undefined, MARKER_TYPE.dot)


        //Before move. Clicking about, and showing dot for possible moves and such.
        if (event.type === INPUT_EVENT_TYPE.moveStart) {
            const moves = chess.moves({square: event.square, verbose: true});
            for (const move of moves) {
                event.chessboard.addMarker(move.to, MARKER_TYPE.dot)
            }
            return moves.length > 0

        //Here is once a move has been attempted    
        } else if (event.type === INPUT_EVENT_TYPE.moveDone) {

            if (state == "questionBestMove") {

                const move = {from: event.squareFrom, to: event.squareTo} //gets which move was attempted from event
                const result = chess.move(move) //gets result of move

                bestMove = PGN[moveCount];

                if (result){
                    if (result.san == bestMove.san) {

                        moveCount += 1;

                        event.chessboard.disableMoveInput()
                        event.chessboard.setPosition(chess.fen())

                        document.getElementById("prompt").innerHTML = "";

                        //Here is where I need it to ask a question if the bestMove.name is not null.

                        opponentMove = PGN[moveCount];
                        positionAnswer = PGN[moveCount].name;
                        moveCount += 1;

                        setTimeout(() => { // smoother with 500ms delay
                            chess.move({from: opponentMove.uci.slice(0,2), to: opponentMove.uci.slice(2,4)})
                            event.chessboard.enableMoveInput(inputHandler, side[0])
                            event.chessboard.setPosition(chess.fen())

                            if (positionAnswer){
                                document.getElementById("prompt").innerHTML = "event) What position is this?";
                                state = 'questionPosition';
                            }else{
                                document.getElementById("prompt").innerHTML = "key) What is " + side + "\'s best move?"
                            }

                        }, 500)

                    }else{
                        console.warn("That move is not the answer")
                        chess.undo();
                        event.chessboard.setPosition(chess.fen());
                    }
                    return result
                

                } else { //If result returns null, then we will loop back to the begining of the function to have another go with new dots.
                    console.warn("invalid move", move)
                }
                return result
            }
        }
    }

    // The PGN is inputted manually here. Normally this would be extracted from the Lichess API.
    if (side == 'black') {
    
        PGN = [
            {uci: "e2e4", san: "e4", name: "King's Pawn"},
            {uci: "e7e5", san: "e5", name: "King's Pawn Game"},
            {uci: "g1f3", san: "Nf3", name: "King's Knight Opening"},
            {uci: "b8c6", san: "Nc6", name: "King's Knight Opening: Normal Variation"},
            {uci: "f1b5", san: "Bb5", name: "Ruy Lopez"},
            {uci: "a7a6", san: "a6", name: "Ruy Lopez: Morphy Defense"},
            {uci: "b5c6", san: "Bxc6", name: "Ruy Lopez: Exchange Variation"},
            {uci: "d7c6", san: "dxc6", name: null},
            {uci: "b1c3", san: "Nc3", name: "Ruy Lopez: Exchange Variation, Keres Variation"},
            {uci: "f7f6", san: "f6", name: null},
            {uci: "e1g1", san: "O-O", name: null}
        ]
    }else{

        PGN = [
            {uci: "e2e4", san: "e4", name: "King's Pawn"},
            {uci: "e7e5", san: "e5", name: "King's Pawn Game"},
            {uci: "g1f3", san: "Nf3", name: "King's Knight Opening"},
            {uci: "b8c6", san: "Nc6", name: "King's Knight Opening: Normal Variation"},
            {uci: "f1b5", san: "Bb5", name: "Ruy Lopez"},
            {uci: "a7a6", san: "a6", name: "Ruy Lopez: Morphy Defense"},
            {uci: "b5a4", san: "Ba4", name: null},
            {uci: "b7b5", san: "b5", name: "Ruy Lopez: Morphy Defense, Caro Variation"},
            {uci: "a4b3", san: "Bb3", name: null},
            {uci: "g8f6", san: "Nf6", name: null},
            {uci: "e1g1", san: "O-O", name: null}
        ]
    }

    console.log('PGN', PGN)

    if (side == 'white'){
        const board = new Chessboard(document.getElementById("board"), {
            position: chess.fen(),
            sprite: {url: "https://alftheelf.github.io/Chess-Opening-Trainer/cm-chessboard/assets/images/chessboard-sprite-staunty.svg"},
            style: {moveMarker: MARKER_TYPE.square, hoverMarker: undefined},
            orientation: COLOR.white
        })
        state = 'questionBestMove';
        document.getElementById("prompt").innerHTML = "What is " + side + "\'s best move?";
        board.enableMoveInput(inputHandler, COLOR.white)
    }
    else{
        const board = new Chessboard(document.getElementById("board"), {
            position: chess.fen(),
            sprite: {url: "https://alftheelf.github.io/Chess-Opening-Trainer/cm-chessboard/assets/images/chessboard-sprite-staunty.svg"},
            style: {moveMarker: MARKER_TYPE.square, hoverMarker: undefined},
            orientation: COLOR.black
        })


        //Get opponent move and name
        var opponentMove = PGN[moveCount];
        positionAnswer = PGN[moveCount].name;

        moveCount += 1;

        setTimeout(() => { // smoother with 500ms delay
            chess.move({from: opponentMove.uci.slice(0,2), to: opponentMove.uci.slice(2,4)})
            board.enableMoveInput(inputHandler, COLOR.black)
            board.setPosition(chess.fen())
            
            if (positionAnswer){
            
                document.getElementById("prompt").innerHTML = "What position is this?";
                state = "questionPosition"
            }

        }, 500)

    }

</script>
</body>
</html>

我为此post 压缩了它。通常,变量 PNG 是使用 Lichess API 创建的,但在本例中,我手动定义了两种游戏,一种用于黑色,另一种用于白色。我在中间选择了一些 'null' 名称的示例,以便我可以测试它是否正常工作。

如您所见,我等待对手位置问题,因为它在 keypress() 函数内。我对玩家位置问题做同样的事情时遇到的问题是,一旦问题被揭示,计算机就需要自动移动。我尝试从 keypress() 函数内部进行移动,但我无法让它工作,因为 event 未定义。我尝试以某种方式尝试将 event 传递给 keypress 但它没有用,而且我确信这不是最好的方法。

我认为正确的方法是妥善组织 inputHandler。我已经重写了很多次,但我从来没有设法让它工作。逻辑让我感到困惑,我认为使用我的 state = "questionPosition" 想法来分离游戏状态可能会导致比它解决的问题更多的问题。如果我在 r/badcode 上看到它,我不会感到惊讶。

如果有任何关于如何解决此问题的建议,我将不胜感激。

我post编辑的这个版本是几乎可以工作的版本,我认为它比我的一些版本更容易看到发生了什么随后。

我还在 https://alftheelf.github.io/Chess-Opening-Trainer/ 上传了一个实时版本。

有谁知道我该如何解决这个问题?我花了一个星期试图修复它,但我不知道了。

我终于成功了。

我最终将 inputHander 中的 event 重命名为 chessEvent,以便它与 keypress() 中的 event 分开。然后我克隆 chessEvent 并在按键功能中使用它。 chess 也必须与 inputHandler 一起设为全局变量,我通过将其设为变量来实现。

inputHandler1 = function inputHandler(chessEvent) {...

现在我可以在 keypress() 内控制电路板,而且效果很好。