2
\$\begingroup\$

I've been writing some console-based games in python and got in the habit of having a "mainloop" that looks something like this (if it were JS):

// mainLoop would be
function mainLoop() {
    while ( state === "playing" ) {
        if ( turn === "player" ) {
            playerTurn(table, board);
            turn = "computer";
        } else {
            computerTurn(table, board);
            turn = "player";
        }
    }
}

This is great as I can easily swap out computer for player2 so the basic structure doesn't change much for AI vs 2-player games.

However, in HTML5 things get really confusing. Part of me wants to hold onto the simplicity of my mainloop structure, but since I need the listen for events in JS, it looks like I need to completely shift paradigm.

I did actually get what I want happening using

document.addEventListener( "click", function ( event ) {
    if ( event.target.matches( "#reset" ) ) {
        init( );
    } else if ( event.target.matches( ".cell" ) && turn === "player" ) {
        playerTurn( event, table, board );
        if ( hasFreeSpot( board ) ) { // don't switch player if board full (or win position?)
            computerTurn( table, board );
        }
    }
}, false );

and updating turn to player or computer within playerTurn() and computerTurn()

It's hard to reproduce a working example of what I'm trying to do as the program is quite long. what I'm looking for here if possible is some big-picture advice on how to implement player vs computer games in HTML5 where the player move comprises of clicking on some element.

Is there a basic template I can use for this kind of thing? Is the idea of a mainloop relevant in this context?

Is seems my current approach is a very confusing hybrid of procedural code for the computer turn and even-driven code for the player turn.

I will be happy to edit my question an include a full example if that is what you guys want here.

Thanks in advance.

\$\endgroup\$
1
\$\begingroup\$

In a HTML5 game, your rendering should be enqueued with window.requestAnimationFrame. This ensures that your graphics updates are synchronized with those of the browser's rendering engine. You usually have a construct like this somewhere in your architecture:

function render() {
      // do your rendering stuff
      window.requestAnimationFrame(render);
}
window.requestAnimationFrame(render);

As you can see, this function enques itself recursively. So it gets executed in an infinite loop. That means when you want to work with a variable time step, then you can put your game update code right into this loop:

let lastUpdate = performance.now();

function mainLoop() {
      let now = performance.now();
      let deltaTime = now - lastUpdate;          
      // do your game state updating stuff taking deltaTime into account
      lastUpdate = now;
      // do your rendering stuff
      window.requestAnimationFrame(mainLoop);
}
window.requestAnimationFrame(mainLoop);

On the other hand, if you want to work with a fixed time step, you can use window.setInterval to have the browser execute your main loop in regular intervals:

function mainLoop() {
     //update your game mechanics
}
window.setInterval(mainLoop, 100); // 100 updates per second

function render() {
    // do your rendering stuff
    window.requestAnimationFrame(render);
}
window.requestAnimationFrame(render);

By the way: If your game doesn't have much "rendering" per-se and only does visualization by occasionally manipulating DOM nodes, then a render-loop with window.requestAnimationFrame might not be necessary. In that case, you should just do your main game loop with window.setInterval and let the browser decide when to re-render the DOM nodes you changed during that loop. It will usually do that automatically in a timely manner. But if you update a lot of DOM nodes during every main loop iteration, then moving those DOM manipulations into requestAnimationFrame can have performance benefits.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the detailed response @Philipp. It's definitely the case you discuss in the last paragraph I'm interested in. I don't even need setInterval() I don't think, as the computer turn can be instant after the player clicks their "tile". I'm still not sure about what the mainloop does in this context though - it looks like I can put all my player-swapping and computerTurn() calls in the click handler and not use a mainloop but that feels very unsatisfactory, and as you've pointed out, mainloops are something of a mainstay! \$\endgroup\$ – Robin Andrews Feb 27 '20 at 17:13
  • 2
    \$\begingroup\$ @RobinAndrews If I would make a game where nothing ever happens unless in immediate response to player input, then I would also put the call to the AI code into the click handlers. But that usually doesn't stay that way for long. As soon as you want to add some polish to your game in form of animation, you will end up with a requestAnimationFrame loop. \$\endgroup\$ – Philipp Feb 27 '20 at 17:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.