npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tafl

v2.5.0

Published

A Typescript library for Hnefatafl (Viking Chess, or Tafl) that is used for move generation/validation, piece placement/movement, and game over detection. You can use it to simulate games between AI agents - written for this purpose, and also serves as th

Downloads

42

Readme

tafl

A Typescript library for Hnefatafl (Viking Chess, or Tafl) that is used for move generation/validation, piece placement/movement, and game over detection.

You can use it to simulate games between AI agents - although you need to write the agents yourself :)

Installation

npm install tafl

Features

This library has support for these:

  • [X] Exit forts: Defenders can build exit forts (edge forts) on the sides for the king to run and win the game
  • [X] Shieldwall: Both sides can capture multiple pieces on the sides with shieldwall captures
  • [X] Repetition checks: Game ends on 3-fold repetition. The repeating board states do not have to be in succession. You can modify this number (3) through rules.
  • [X] If attackers surround all defender pieces, game ends
  • [X] If any side has no moves left, they lose
  • [X] King can escape from edges optionally (in addition to corners, usually used in smaller boards)
  • [X] King can be armed or not. Armed kings can help with capturing pieces.
  • [X] King can return to center or not. In some variations of the game, returning to center base is forbidden.
  • [X] Attackers can capture the king with 2, 3, or 4 pieces. In Copenhagen, king has to be surrounded from 4 sides, but this behavior can be modified.
  • [X] Setting the starting side for the game. In Copenhagen, attackers start the game.
  • [X] Changing the width of corners. Usually corners are one square, but in Alea Evangelii variation, corners are 2x2

You can use the library with an arbitrary game state. (set the board/rules as you'd like and start using the library from that game state, useful for setting up specific board states)

Basic example

Uses Copenhagen rules on 11x11 board, plays both sides with random agent

const { Tafl } = require('tafl')

const tafl = new Tafl()
let state = tafl.initialState() // by default uses Copenhagen rules on 11x11 board with the classical starting position, you can use a different state if you want

while (!state.result.finished) {
  const possibleActions = tafl.getPossibleActions(state)
  const randomAction = possibleActions[Math.floor(Math.random() * possibleActions.length)];

  state = tafl.act(state, randomAction)
}

tafl.log({ turn: state.turn, result: state.result, lastAction: state.lastAction, board: state.board })
// -> { turn: 604,
//      result: { finished: true, winner: 'Defender', desc: 'King escaped from corner' },
//      lastAction: { from: { r: 10, c: 2 }, to: { r: 10, c: 0 } },
//      board:
//       [ [ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' ],
//         [ ' ', ' ', ' ', ' ', ' ', 'A', ' ', ' ', ' ', ' ', ' ' ],
//         [ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' ],
//         [ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' ],
//         [ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'A', 'A', 'A' ],
//         [ 'D', 'A', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'A' ],
//         [ ' ', ' ', 'A', ' ', ' ', ' ', 'A', ' ', ' ', ' ', 'A' ],
//         [ ' ', ' ', ' ', ' ', 'A', ' ', ' ', 'A', ' ', 'A', ' ' ],
//         [ ' ', ' ', ' ', ' ', 'D', 'A', ' ', 'A', ' ', ' ', ' ' ],
//         [ ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'A', 'A', 'A', ' ' ],
//         [ 'K', ' ', ' ', ' ', ' ', ' ', 'A', ' ', ' ', ' ', ' ' ] ] }

API

You can import these different objects:

const { Piece, TaflBoard, TaflRule, TaflRuleSet, Tafl } = require('tafl')

Piece

Used as an enum. Has __ key for empty places, PA for attackers, PD for defenders, and PK for king.

console.log(Piece)
// -> { __: ' ', PA: 'A', PD: 'D', PK: 'K' }

TaflBoard

You can pass a custom board to the tafl.initialState. Here are some of the starting positions of different variations for the game, you can use these or pass a custom board when creating initial state. See examples for usage.

  • TaflBoard._7_BRANDUBH
  • TaflBoard._9_TABLUT
  • TaflBoard._11_CLASSIC
  • TaflBoard._11_LINE
  • TaflBoard._11_TAWLBWRDD
  • TaflBoard._11_LEWISS
  • TaflBoard._13_PARLETT
  • TaflBoard._15_DAMIEN_WALKER
  • TaflBoard._19_ALEA_EVANGELII

TaflRule

Used as an enum, and maps to string values. Use these to change the behavior of the game. See examples for usage.

| TaflRule name | Type | Default (In TaflRuleset.COPENHAGEN ) | Description | |---------------|-------|---------------------------------|--------------| | TaflRule.EXIT_FORTS | Boolean | true | Exit forts | | TaflRule.SHIELD_WALLS | Boolean | true | Shieldwall captures | | TaflRule.EDGE_ESCAPE | Boolean | false | King can escape from edges (in addition to corners, usually used in smaller boards) | | TaflRule.KING_IS_ARMED | Boolean | true | King armed or not | | TaflRule.KING_CAN_RETURN_TO_CENTER | Boolean | true | King can return to center or not | | TaflRule.ATTACKER_COUNT_TO_CAPTURE | Number | 4 | Attackers can capture with 2, 3, or 4 pieces | | TaflRule.STARTING_SIDE | TaflSide | TaflSide.ATTACKER | Game starts with attackers or defenders | | TaflRule.CORNER_BASE_WIDTH | Number | 1 | Width of corners, usually corners are one piece, but in Alea Evangelii variation, corners are 2x2 | | TaflRule.REPETITION_TURN_LIMIT | Number | 3 | Repetition turn limit (On copenhagen game draws on Threefold repetition, you can modify this number). You can disable draw on repetititon by setting TaflRule.SAVE_BOARD_HISTORY to false | | TaflRule.SAVE_BOARD_HISTORY | Boolean | true | If set to false, repetition checks are disabled. If you are running many simulations and can live without repetition checks, set to false as it may allow an order of magnitude more simulations in a given time. Otherwise should be true. | | TaflRule.SAVE_ACTIONS | Boolean | true | Saving actions may not be required for simulations. Although this has a tiny effect on performance, you may want to set this to false too. |

TaflRuleSet

Used as an enum, used for grouping rules to access with a single name. Only has COPENHAGEN value for now. See examples for usage.

  • TaflRuleSet.COPENHAGEN

Examples

Start with Copenhagen on 7x7 Brandubh board, allow edge escape and use 2x2 corners:

const { Tafl, TaflBoard, TaflRule, TaflRuleSet } = require('tafl')

const tafl = new Tafl()
let state = tafl.initialState({
  board: TaflBoard._7_BRANDUBH,
  rules: {
    ...TaflRuleSet.COPENHAGEN,
    [TaflRule.EDGE_ESCAPE]: true,
    [TaflRule.CORNER_BASE_WIDTH]: 2,
    [TaflRule.SAVE_BOARD_HISTORY]: false
  }
})

while (!state.result.finished) {
  const possibleActions = tafl.getPossibleActions(state)
  const randomAction = possibleActions[Math.floor(Math.random() * possibleActions.length)];

  state = tafl.act(state, randomAction)
}

tafl.log({ turn: state.turn, result: state.result, lastAction: state.lastAction, board: state.board })
// -> { turn: 12,
//      result: { finished: true, winner: 'Defender', desc: 'King escaped from edge' },
//      lastAction: { from: { r: 4, c: 3 }, to: { r: 4, c: 6 } },
//      board:
//       [ [ ' ', ' ', 'A', 'A', 'D', ' ', ' ' ],
//         [ ' ', ' ', ' ', ' ', ' ', ' ', ' ' ],
//         [ ' ', 'A', ' ', 'D', ' ', 'A', ' ' ],
//         [ 'A', ' ', ' ', ' ', ' ', ' ', 'A' ],
//         [ 'D', ' ', ' ', ' ', ' ', ' ', 'K' ],
//         [ ' ', ' ', 'D', 'A', ' ', ' ', ' ' ],
//         [ ' ', ' ', ' ', 'A', ' ', ' ', ' ' ] ] }

Interfaces & properties & methods

Interfaces

interface Named {
  name: String;
}
interface Coords {
  readonly r: number;
  readonly c: number;
}
interface MoveAction {
  from: Coords;
  to: Coords;
}
interface Game extends Named {
  getPossibleActions(state: GameState): MoveAction[];
  isActionPossible(state: GameState, action: MoveAction): boolean;
  isGameOver(state: GameState): typeof state;
  act(state: GameState, action: MoveAction): typeof state;
}

export declare type Board = Array<Array<Piece>>;
export interface GameState {
  board?: Board;
  actions?: Array<MoveAction>;
  boardHistory?: Record<string, number>;
  turn?: number;
  result?: {
    finished: boolean;
    winner?: TaflSide;
    desc: string;
  };
  captures?: Array<Coords>;
  lastAction?: MoveAction;
  rules?: {
    [key: string]: TaflRule;
  };
}

Properties & Methods

Full list of methods and properties, taken from type definitions file and is subject to change.

name: string;
controlMap: Map<TaflSide, Set<Piece>>;
sideMap: Map<Piece, TaflSide>;
initialState(init?: GameState): GameState;
log(thing: any): void;
isCenter(board: Board, coords: Coords): boolean;
isCorner(state: GameState, coords: Coords): boolean;
isEdge(state: GameState, coords: Coords): boolean;
repr(coords: Coords): String;
coords(repr: String): Coords;
toPathRepr(...args: Array<Coords>): String;
toPathCoords(path: String): Array<Coords>;
insideBounds(board: Board, coords: Coords): boolean;
connectedPieces(board: Board, coords: Coords, side: TaflSide): Set<String>;
connectedDefenders(board: Board, coords: Coords): Set<String>;
connectedAttackers(board: Board, coords: Coords): Set<String>;
possiblySurroundingPieces(board: Board, kingCoords: Coords, side: TaflSide): Array<Coords>;
possiblySurrondingDefenders(board: Board, kingCoords: Coords): Array<Coords>;
possiblySurrondingAttackers(board: Board, kingCoords: Coords): Array<Coords>;
isInsideEye(board: Board, coords: Coords, fullFortStructure: Set<String>): boolean;
getPossibleSmallestFortStructure(board: Board, innerSet: Set<String>, fullStructure: Set<String>): Set<String>;
getEmptyPiecesAndOpponentsInsideSmallestClosedStructure(board: Board, kingCoords: Coords, closedStructure: Set<String>, oppSide?: TaflSide): Array<Set<String>>;
getEmptyPiecesAndAttackersInsideSmallestFort(board: Board, kingCoords: Coords, fullFortStructure: Set<String>): Array<Set<String>>;
insideFort(board: Board, kingCoords: Coords): boolean;
kingEscapedThroughFort(state: GameState): boolean;
isBase(state: GameState, coords: Coords): boolean;
isEmpty(board: Board, coords: Coords): boolean;
isEmptyBase(state: GameState, coords: Coords): boolean;
isKing(board: Board, coords: Coords): boolean;
isAttacker(board: Board, coords: Coords): boolean;
isDefender(board: Board, coords: Coords): boolean;
isDefenderOrKing(board: Board, coords: Coords): boolean;
canHelpCapture(state: GameState, coords: Coords, canHelp: TaflSide): boolean;
turnSide(state: GameState): TaflSide;
opponentSide(state: GameState): TaflSide;
canControl(side: TaflSide, piece: Piece): boolean;
sideOfPiece(piece: Piece): TaflSide | undefined;
pieceAt(board: Board, coords: Coords): Piece;
canMovePieceHere(state: GameState, piece: Piece, coords: Coords): boolean;
getPossibleMovesFrom(state: GameState, coords: Coords): Array<Coords>;
canMakeAMove(state: GameState, side: TaflSide): boolean;
getKingCoords(board: Board): Coords;
didAttackersSurroundDefenders(board: Board): boolean;
canBeCaptured(board: Board, coords: Coords, side: TaflSide): boolean;
checkCaptures(state: GameState): Array<Coords>;
checkShieldWalls(state: GameState): Array<Coords>;
getBoardHash(board: Board): string;
addBoardToHistory(state: GameState, board: Board): typeof state;
getEquivalentBoards(board: Board): Record<string, Board>;
fortSearchFromKing(board: Board, kingCoords: Coords): boolean;
getPossibleActions(state: GameState, side?: TaflSide): Array<MoveAction>;
// Given a state, returns all possible actions that can be chosen
isActionPossible(state: GameState, act: MoveAction): boolean;
// Returns true if action is possible in given state, false otherwise
isGameOver(state: GameState): typeof state;
// Given a state, returns new state with game over information. If game is over, returned state object will have `{result: { finished: true }}` with additional information like `result.winner` and `result.desc`.
act(state: GameState, moveAction: MoveAction): typeof state;
// Given a state and an action, applies that action and returns the new state.

Author

Created by Demircan Celebi in 2020-2022 for use at https://litafl.com/tafl