gegelulu / hatetris

Tetris which always gives you the worst piece

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

hatetris

This is the source code for HATETRIS.

Writing a custom AI

A custom AI for HATETRIS should be a JavaScript function expression (or arrow function expression), looking something like this:

// AI function.
// Called every time the game needs to spawn a new piece to provide to the player.
// In this example, we return a constant stream of 4x1s, unless the well is all set
// up for a Tetris, in which case we return an S piece
(currentWellState, currentAiState, getNextWellStates) => {
  const nextWellStates = getNextWellStates(currentWellState, 'I')
  for (const nextWellState of nextWellStates) {
    if (nextWellState.score === currentWellState.score + 4) {
      return 'S'
    }
  }

  return 'I'
}

This function must be deterministic, otherwise replays cease to work. In particular, do not use Math.random or refer to the current date.

currentWellState is the current well state object. This has the form { well, score }, where:

  • well is an array of 20 binary numbers, one representing each row in the well from top to bottom. Each bit in each number represents a cell in that row: 0 if the cell is currently clear, and 1 if it is currently obstructed. (Note: the least significant bit represents the first cell, so if the value is 0b0000000011, the leftmost two cells in the row are occupied.)
  • score is the current score, a non-negative integer.

currentAiState is the current AI state value. When spawning the very first piece, this value is undefined. For later piece spawns, this value is whatever AI state value you returned last time. This value should be considered deeply immutable, as mutations break the behaviour of undo and redo, which in turn breaks replays. If the value is an object or array and you need to make changes to it, such as adding a new property to the object or splicing some content out of the array, you will need to clone the value first.

getNextStates is a helper function (wellState, pieceId) => nextWellStates, where:

  • state can be any well state object. You can pass currentWellState here, but you can also create and pass your own hypotheticals.
  • pieceId can be any string indicating the name of a piece: "I", "J", "L", "O", "S", "T" or "Z".
  • The returned nextWellStates is an array of all of the possible new well state objects which could ensue, taking into account every possible location where the player could land this piece.

The return value from the overall AI function should normally be another piece ID, indicating which piece which the game should spawn now.

Alternatively, the return value can be an array [nextPieceId, nextAiState], where:

  • nextPieceId will be used as the next piece ID.
  • nextAiState can be any value. This value will be passed in as currentAiState next time your custom AI function is called.

If you return a piece ID by itself, then implicitly the returned value of nextAiState is currentAiState - that is, the AI state object is left unchanged for the next time your custom AI function is called.

Examples

A very simple AI might ignore all arguments and return the same thing every time:

() => 'I'

A more advanced AI might analyse the current layout of the well to statically determine the best piece to send next:

currentWellState =>
  currentWellState.well[currentWellState.well.length - 1] === 0
    ? 'I' // when the well is empty, send a 4x1
    : 'S' // otherwise S pieces forever

You can use the AI state value to store state between function calls:

// Ignore well state. Return S and Z pieces, alternating
(_, currentAiState) =>
  currentAiState?.last === 'S'
    ? ['Z', { last: 'Z' }]
    : ['S', { last: 'S' }]

More advanced AIs still will make use of getNextStates. This helper function is provided to make it possible to model future possibilities without laboriously reimplementing all of the game's movement code. For example, the default HATETRIS algorithm uses getNextStates to find all the possible outcomes for all possible pieces, rank those outcomes to find the best for each piece, and then select the piece with the worst best outcome. Other AIs could, for example, plug those possible futures back into getNextStates to explore further into the future, or use different heuristics to rank the wells and decide how to prune the search tree.

Does this use eval internally? Isn't there a security risk from that?

Yes, and yes. You are at mortal risk of attacking yourself. Do not paste code into HATETRIS unless you understand every line of it.

About

Tetris which always gives you the worst piece

License:MIT License


Languages

Language:TypeScript 91.8%Language:JavaScript 5.6%Language:CSS 2.1%Language:HTML 0.5%