codec.js

'use strict'

// ******************************** 编解码器 ********************************

const Codec = new class {
  // 文本编解码器
  textEncoder = new TextEncoder()
  textDecoder = new TextDecoder()

  /**
   * 编码图块
   * @param {Uint8Array} tiles 图块数组
   * @returns {string}
   */
  encodeTiles = function (tiles) {
    const {encodeClone} = this
    const TILES = tiles
    const TILES_LENGTH = TILES.length
    const BYTES = GL.arrays[0].uint8
    let Bi = 0
    let Ti = 0
    while (Ti < TILES_LENGTH) {
      if (TILES[Ti] === 0) {
        let blankCount = 1
        Ti += 1
        while (TILES[Ti] === 0) {
          blankCount++
          Ti++
        }
        if (blankCount <= 16) {
          BYTES[Bi++] = blankCount + 109
        } else {
          BYTES[Bi++] = 126
          Bi = encodeClone(BYTES, Bi, blankCount)
        }
      } else if (TILES[Ti] === TILES[Ti - 1]) {
        let cloneCount = 1
        Ti += 1
        while (TILES[Ti] === TILES[Ti - 1]) {
          cloneCount++
          Ti++
        }
        if (cloneCount <= 10) {
          BYTES[Bi++] = cloneCount + 98
        } else {
          BYTES[Bi++] = 109
          Bi = encodeClone(BYTES, Bi, cloneCount)
        }
      } else {
        const TILE = TILES[Ti]
        BYTES[Bi    ] = (TILE >> 26           ) + 35
        BYTES[Bi + 1] = (TILE >> 20 & 0b111111) + 35
        BYTES[Bi + 2] = (TILE >> 14 & 0b111111) + 35
        BYTES[Bi + 3] = (TILE >> 8  & 0b111111) + 35
        BYTES[Bi + 4] = (TILE       & 0b111111) + 35
        Bi += 5
        Ti += 1
      }
    }
    return this.textDecoder.decode(
      new Uint8Array(BYTES.buffer, 0, Bi)
    )
  }

  /**
   * 解码图块
   * @param {string} code 图块数据编码
   * @param {number} width 瓦片地图宽度
   * @param {number} height 瓦片地图高度
   * @returns {Uint32Array} 图块数据列表
   */
  decodeTiles(code, width, height) {
    const {decodeClone} = this
    const BYTES = this.textEncoder.encode(code)
    const BYTES_LENGTH = BYTES.length
    const TILES = new Uint32Array(width * height)
    const TILES_LENGTH = TILES.length
    let Bi = 0
    let Ti = 0
    while (Bi < BYTES_LENGTH) {
      const CODE = BYTES[Bi]
      if (CODE <= 98) {
        TILES[Ti] =
          (BYTES[Bi    ] - 35 << 26)
        + (BYTES[Bi + 1] - 35 << 20)
        + (BYTES[Bi + 2] - 35 << 14)
        + (BYTES[Bi + 3] - 35 << 8)
        + (BYTES[Bi + 4] - 35)
        Ti += 1
        Bi += 5
      } else if (CODE <= 109) {
        if (CODE !== 109) {
          const COPY = TILES[Ti - 1]
          const END = Ti + CODE - 98
          while (Ti < END) {
            TILES[Ti++] = COPY
          }
          Bi += 1
        } else {
          const {index, count} = decodeClone(BYTES, ++Bi)
          const COPY = TILES[Ti - 1]
          const END = Ti + count
          while (Ti < END) {
            TILES[Ti++] = COPY
          }
          Bi = index
        }
      } else {
        if (CODE !== 126) {
          Ti += CODE - 109
          Bi += 1
        } else {
          const {index, count} = decodeClone(BYTES, ++Bi)
          Ti += count
          Bi = index
        }
      }
    }
    if (Bi !== BYTES_LENGTH || Ti !== TILES_LENGTH) {
      throw new RangeError(`
      Failed to decode tiles.
      Processed bytes: ${Bi} / ${BYTES_LENGTH}
      Restored data: ${Ti} / ${TILES_LENGTH}
      `)
    }
    return TILES
  }

  /**
   * 编码地形
   * @param {Uint8Array} terrains 地形数组
   * @returns {string}
   */
  encodeTerrains(terrains) {
    const {encodeClone} = this
    const TERRAINS = terrains
    const LENGTH = TERRAINS.length
    const BYTES = GL.arrays[0].uint8
    let Bi = 0
    let Ti = 0
    while (Ti < LENGTH) {
      if (TERRAINS[Ti] === 0) {
        let blankCount = 1
        Ti += 1
        while (TERRAINS[Ti] === 0) {
          blankCount++
          Ti++
        }
        if (blankCount <= 49) {
          BYTES[Bi++] = blankCount + 76
        } else if (blankCount <= 98) {
          BYTES[Bi++] = 125
          BYTES[Bi++] = blankCount - 49 + 76
        } else {
          BYTES[Bi++] = 126
          Bi = encodeClone(BYTES, Bi, blankCount)
        }
      } else if (
        TERRAINS[Ti] === TERRAINS[Ti - 1] &&
        TERRAINS[Ti] === TERRAINS[Ti + 1]) {
        let cloneCount = 2
        Ti += 2
        while (TERRAINS[Ti] === TERRAINS[Ti - 1]) {
          cloneCount++
          Ti++
        }
        if (cloneCount <= 25) {
          BYTES[Bi++] = cloneCount + 50
        } else if (cloneCount <= 50) {
          BYTES[Bi++] = 75
          BYTES[Bi++] = cloneCount - 25 + 50
        } else {
          BYTES[Bi++] = 76
          Bi = encodeClone(BYTES, Bi, cloneCount)
        }
      } else {
        BYTES[Bi++] = TERRAINS[Ti++] + 35
      }
    }
    return this.textDecoder.decode(
      new Uint8Array(BYTES.buffer, 0, Bi)
    )
  }

  /**
   * 解码地形
   * @param {SceneContext} scene 场景上下文对象
   * @param {string} code 地形数据编码
   * @param {number} width 场景宽度
   * @param {number} height 场景高度
   * @returns {SceneTerrainArray} 场景地形数据列表
   */
  decodeTerrains(scene, code, width, height) {
    const {decodeClone} = this
    const BYTES = this.textEncoder.encode(code)
    const BYTES_LENGTH = BYTES.length
    const TERRAINS = new SceneTerrainArray(scene, width, height)
    const TERRAINS_LENGTH = TERRAINS.length
    let Bi = 0
    let Ti = 0
    while (Bi < BYTES_LENGTH) {
      const CODE = BYTES[Bi]
      if (CODE <= 50) {
        TERRAINS[Ti] = CODE - 35
        Ti += 1
        Bi += 1
      } else if (CODE <= 76) {
        if (CODE !== 76) {
          const COPY = TERRAINS[Ti - 1]
          const END = Ti + CODE - 50
          while (Ti < END) {
            TERRAINS[Ti++] = COPY
          }
          Bi += 1
        } else {
          const {index, count} = decodeClone(BYTES, ++Bi)
          const COPY = TERRAINS[Ti - 1]
          const END = Ti + count
          while (Ti < END) {
            TERRAINS[Ti++] = COPY
          }
          Bi = index
        }
      } else {
        if (CODE !== 126) {
          Ti += CODE - 76
          Bi += 1
        } else {
          const {index, count} = decodeClone(BYTES, ++Bi)
          Ti += count
          Bi = index
        }
      }
    }
    if (Bi !== BYTES_LENGTH || Ti !== TERRAINS_LENGTH) {
      throw new RangeError(`
      Failed to decode terrains.
      Processed bytes: ${Bi} / ${BYTES_LENGTH}
      Restored data: ${Ti} / ${TERRAINS_LENGTH}
      `)
    }
    return TERRAINS
  }

  /**
   * 编码队伍数据
   * @param {Uint8Array} data 队伍数据列表
   * @returns {string} 队伍数据编码
   */
  encodeTeamData(data) {
    const DATA = data
    const LENGTH = DATA.length
    const BYTES = GL.arrays[0].uint8
    let Bi = 0
    let Ri = 0
    while (Ri < LENGTH) {
      BYTES[Bi++] = 35 + (
        DATA[Ri    ]
      | DATA[Ri + 1] << 1
      | DATA[Ri + 2] << 2
      | DATA[Ri + 3] << 3
      | DATA[Ri + 4] << 4
      | DATA[Ri + 5] << 5
      )
      Ri += 6
    }
    return this.textDecoder.decode(
      new Uint8Array(BYTES.buffer, 0, Bi)
    )
  }

  /**
   * 解码队伍数据
   * @param {string} code 队伍数据编码
   * @param {number} length 队伍数量
   * @returns {Uint8Array} 队伍数据
   */
  decodeTeamData(code, length) {
    const BYTES = this.textEncoder.encode(code)
    const BYTES_LENGTH = BYTES.length
    const DATA_LENGTH = (length + 1) / 2 * length
    const DATA = new Uint8Array(DATA_LENGTH)
    let Bi = 0
    let Ri = 0
    while (Bi < BYTES_LENGTH) {
      const CODE = BYTES[Bi] - 35
      DATA[Ri    ] = CODE      & 0b000001
      DATA[Ri + 1] = CODE >> 1 & 0b00001
      DATA[Ri + 2] = CODE >> 2 & 0b0001
      DATA[Ri + 3] = CODE >> 3 & 0b001
      DATA[Ri + 4] = CODE >> 4 & 0b01
      DATA[Ri + 5] = CODE >> 5
      Ri += 6
      Bi += 1
    }
    if (Bi !== BYTES_LENGTH || Ri < DATA_LENGTH) {
      throw new RangeError(`
      Failed to decode data.
      Processed bytes: ${Bi} / ${BYTES_LENGTH}
      Restored data: ${Ri} / ${DATA_LENGTH}
      `)
    }
    return DATA
  }

  // 编码克隆数据
  encodeClone(array, index, count) {
    const bits = Math.ceil(Math.log2(count + 1))
    const bytes = Math.ceil(bits / 5)
    for (let i = 0; i < bytes; i++) {
      const n = bytes - i - 1
      const head = n !== 0 ? 1 : 0
      const code = head << 5 | count >> n * 5 & 0b011111
      array[index++] = code + 35
    }
    return index
  }

  /**
   * 解码克隆数据
   * @param {Uint8Array} array 字节码列表
   * @param {number} index 字节码索引
   * @returns {{index: number, count: number}} {结束位置, 克隆数量}
   */
  decodeClone(array, index) {
    let count = 0
    let code
    do {
      code = array[index++] - 35
      count = count << 5 | code & 0b011111
    }
    while (code & 0b100000)
    return {index, count}
  }
}