<commit_before>import { Map as IMap } from 'immutable'
import { BulletRecord } from 'types';
import { asBox, DefaultMap, testCollide } from 'utils/common'
import Collision, { CollisionWithBullet } from 'utils/Collision'
import IndexHelper from 'utils/IndexHelper';
import { BULLET_SIZE, FIELD_SIZE } from 'utils/constants'

export function getMBR(...boxes: Box[]): Box {
  let left = Infinity
  let top = Infinity
  let right = -Infinity
  let bottom = -Infinity
  for (const box of boxes) {
    left = Math.min(left, box.x)
    top = Math.min(top, box.y)
    right = Math.max(right, box.x + box.width)
    bottom = Math.max(bottom, box.y + box.height)
  }

  return {
    x: left,
    y: top,
    width: right - left,
    height: bottom - top,
  }
}

export function lastPos(b: BulletRecord) {
  return b.merge({ x: b.lastX, y: b.lastY })
}

export class BulletCollisionInfo extends DefaultMap<BulletId, Collision[]> {
  bullets: IMap<BulletId, BulletRecord>

  constructor(bullets: IMap<BulletId, BulletRecord>) {
    super(() => [])
    this.bullets = bullets
  }

  // 获取一个Collision对象中 碰撞物体的Box对象
  // 例如输入参数collision记录了一个子弹碰到了一个Brick对象, 那么该函数将返回Brick的Box
  static getCollisionBox(collision: Collision) {
    const borderCollisionBox: Box = {
      x: FIELD_SIZE,
      y: FIELD_SIZE,
      width: -FIELD_SIZE,
      height: -FIELD_SIZE,
    }
    if (collision.type === 'brick' || collision.type === 'steel') {
      return IndexHelper.getBox(collision.type, collision.t)
    } else if (collision.type === 'border') {
      return borderCollisionBox
    } else if (collision.type === 'tank') {
      return asBox(collision.tank)
    } else if (collision.type === 'eagle') {
      return asBox(collision.eagle)
    } else { // c.type === 'bullet'
      return { x: collision.x, y: collision.y, width: 0, height: 0 }
    }
  }

  // 判断一颗子弹是否发生爆炸
  static shouldExplode(collisions: Collision[]) {
    for (const c of collisions) {
      if (c.type === 'border'
        || c.type === 'eagle'
        || c.type === 'brick'
        || c.type === 'steel'
        || c.type === 'tank' && c.shouldExplode) {
        return true
      }
    }
    return false
  }

  // 获取子弹爆炸的位置. 如果该子弹不需要爆炸, 那么该函数返回null
  getExplosionPos(bulletId: BulletId): Point {
    const collisions = this.get(bulletId)
    const bullet = this.bullets.get(bulletId)
    if (!BulletCollisionInfo.shouldExplode(collisions)) {
      return null
    }
    const cboxes = collisions.map(BulletCollisionInfo.getCollisionBox)

    if (bullet.direction === 'right') {
      // 子弹往右边运动, 我们需要找到*最左边*的碰撞对象
      const left = cboxes.reduce((left, b) => Math.min(left, b.x), Infinity)
      return { x: left - BULLET_SIZE, y: bullet.y }
    } else if (bullet.direction === 'left') {
      // 子弹往左边运动, 我们需要找到*最右边*的碰撞对象
      const right = cboxes.reduce((right, b) => Math.max(right, b.x + b.width), -Infinity)
      return { x: right, y: bullet.y }
    } else if (bullet.direction === 'up') {
      // 子弹往上方运动, 我们需要找到*最下边*的碰撞对象
      const bottom = cboxes.reduce((bottom, b) => Math.max(bottom, b.y + b.height), -Infinity)
      return { x: bullet.x, y: bottom }
    } else { // bullet.direction === 'down'
      // 子弹往下方运动, 我们需要找到*最上边*的碰撞对象
      const top = cboxes.reduce((top, b) => Math.min(top, b.y), Infinity)
      return { x: bullet.x, y: top - BULLET_SIZE }
    }
  }

  getExplosionInfo() {
    const expBullets = new Map<BulletId, BulletRecord>()
    const noExpBullets = new Map<BulletId, BulletRecord>()
    for (const bulletId of this.keys()) {
      const bullet = this.bullets.get(bulletId)
      const expPos = this.getExplosionPos(bulletId)
      if (expPos == null) {
        noExpBullets.set(bulletId, bullet)
      } else {
        // 注意expBullets中存放的子弹的坐标是*子弹爆炸*的坐标
        expBullets.set(bulletId, bullet.merge(expPos))
      }
    }
    return {
      expBullets: IMap(expBullets),
      noExpBullets: IMap(noExpBullets),
    }
  }
}

const rotateDirectionMap: { [key: string]: Direction } = {
  up: 'right',
  right: 'down',
  down: 'left',
  left: 'up',
}

/** 返回 坐标系旋转90度之后 子弹的新状态(方向/坐标) */
function rotate(bullet: BulletRecord) {
  return bullet.merge({
    direction: rotateDirectionMap[bullet.direction],
    x: bullet.y,
    y: -bullet.x,
    lastX: bullet.lastY,
    lastY: -bullet.lastX,
  })
}

function calculateHitTime(b1: BulletRecord, b2: BulletRecord): number {
  if (b1.direction === 'up') {
    if (b2.direction === 'down') {
      // 两个子弹相向而行, 一定会发生碰撞
      return (b1.lastY - b2.lastY - BULLET_SIZE) / (b1.speed + b2.speed)
    } else if (b2.direction === 'up') {
      if (b1.lastY < b2.lastY) {
        //  [x]
        //  ↑ ↑
        //  | |   [x] means hitArea
        // b1 |
        //    |
        //   b2
        // b2从下方追赶b1
        return (b2.lastY - b1.lastY - BULLET_SIZE) / (b2.speed - b1.speed)
      } else {
        // 递归 b1从下方追赶b2
        return calculateHitTime(b2, b1)
      }
    } else if (b2.direction === 'left') {
      // [x] <--- b2
      //  ↑
      //  |        [x] means hitArea
      //  b1
      const hitArea = { x: b1.x, y: b2.y, width: BULLET_SIZE, height: BULLET_SIZE }

      const time1 = (b1.lastY - b2.lastY - BULLET_SIZE) / b1.speed<commit_msg>修复子弹连续碰撞检测中的BUG<commit_after>import { Map as IMap } from 'immutable'
import { BulletRecord } from 'types';
import { asBox, DefaultMap, testCollide } from 'utils/common'
import Collision, { CollisionWithBullet } from 'utils/Collision'
import IndexHelper from 'utils/IndexHelper';
import { BULLET_SIZE, FIELD_SIZE } from 'utils/constants'

export function getMBR(...boxes: Box[]): Box {
  let left = Infinity
  let top = Infinity
  let right = -Infinity
  let bottom = -Infinity
  for (const box of boxes) {
    left = Math.min(left, box.x)
    top = Math.min(top, box.y)
    right = Math.max(right, box.x + box.width)
    bottom = Math.max(bottom, box.y + box.height)
  }

  return {
    x: left,
    y: top,
    width: right - left,
    height: bottom - top,
  }
}

export function lastPos(b: BulletRecord) {
  return b.merge({ x: b.lastX, y: b.lastY })
}

export class BulletCollisionInfo extends DefaultMap<BulletId, Collision[]> {
  bullets: IMap<BulletId, BulletRecord>

  constructor(bullets: IMap<BulletId, BulletRecord>) {
    super(() => [])
    this.bullets = bullets
  }

  // 获取一个Collision对象中 碰撞物体的Box对象
  // 例如输入参数collision记录了一个子弹碰到了一个Brick对象, 那么该函数将返回Brick的Box
  static getCollisionBox(collision: Collision) {
    const borderCollisionBox: Box = {
      x: FIELD_SIZE,
      y: FIELD_SIZE,
      width: -FIELD_SIZE,
      height: -FIELD_SIZE,
    }
    if (collision.type === 'brick' || collision.type === 'steel') {
      return IndexHelper.getBox(collision.type, collision.t)
    } else if (collision.type === 'border') {
      return borderCollisionBox
    } else if (collision.type === 'tank') {
      return asBox(collision.tank)
    } else if (collision.type === 'eagle') {
      return asBox(collision.eagle)
    } else { // c.type === 'bullet'
      return { x: collision.x, y: collision.y, width: 0, height: 0 }
    }
  }

  // 判断一颗子弹是否发生爆炸
  static shouldExplode(collisions: Collision[]) {
    for (const c of collisions) {
      if (c.type === 'border'
        || c.type === 'eagle'
        || c.type === 'brick'
        || c.type === 'steel'
        || c.type === 'tank' && c.shouldExplode) {
        return true
      }
    }
    return false
  }

  // 获取子弹爆炸的位置. 如果该子弹不需要爆炸, 那么该函数返回null
  getExplosionPos(bulletId: BulletId): Point {
    const collisions = this.get(bulletId)
    const bullet = this.bullets.get(bulletId)
    if (!BulletCollisionInfo.shouldExplode(collisions)) {
      return null
    }
    const cboxes = collisions.map(BulletCollisionInfo.getCollisionBox)

    if (bullet.direction === 'right') {
      // 子弹往右边运动, 我们需要找到*最左边*的碰撞对象
      const left = cboxes.reduce((left, b) => Math.min(left, b.x), Infinity)
      return { x: left - BULLET_SIZE, y: bullet.y }
    } else if (bullet.direction === 'left') {
      // 子弹往左边运动, 我们需要找到*最右边*的碰撞对象
      const right = cboxes.reduce((right, b) => Math.max(right, b.x + b.width), -Infinity)
      return { x: right, y: bullet.y }
    } else if (bullet.direction === 'up') {
      // 子弹往上方运动, 我们需要找到*最下边*的碰撞对象
      const bottom = cboxes.reduce((bottom, b) => Math.max(bottom, b.y + b.height), -Infinity)
      return { x: bullet.x, y: bottom }
    } else { // bullet.direction === 'down'
      // 子弹往下方运动, 我们需要找到*最上边*的碰撞对象
      const top = cboxes.reduce((top, b) => Math.min(top, b.y), Infinity)
      return { x: bullet.x, y: top - BULLET_SIZE }
    }
  }

  getExplosionInfo() {
    const expBullets = new Map<BulletId, BulletRecord>()
    const noExpBullets = new Map<BulletId, BulletRecord>()
    for (const bulletId of this.keys()) {
      const bullet = this.bullets.get(bulletId)
      const expPos = this.getExplosionPos(bulletId)
      if (expPos == null) {
        noExpBullets.set(bulletId, bullet)
      } else {
        // 注意expBullets中存放的子弹的坐标是*子弹爆炸*的坐标
        expBullets.set(bulletId, bullet.merge(expPos))
      }
    }
    return {
      expBullets: IMap(expBullets),
      noExpBullets: IMap(noExpBullets),
    }
  }
}

const rotateDirectionMap: { [key: string]: Direction } = {
  up: 'right',
  right: 'down',
  down: 'left',
  left: 'up',
}

/** 返回 坐标系旋转90度之后 子弹的新状态(方向/坐标) */
function rotate(bullet: BulletRecord) {
  return bullet.merge({
    direction: rotateDirectionMap[bullet.direction],
    x: -bullet.y,
    y: bullet.x,
    lastX: -bullet.lastY,
    lastY: bullet.lastX,
  })
}

function calculateHitTime(b1: BulletRecord, b2: BulletRecord): number {
  if (b1.direction === 'up') {
    if (b2.direction === 'down') {
      // 两个子弹相向而行, 一定会发生碰撞
      return (b1.lastY - b2.lastY - BULLET_SIZE) / (b1.speed + b2.speed)
    } else if (b2.direction === 'up') {
      if (b1.lastY < b2.lastY) {
        //  [x]
        //  ↑ ↑
        //  | |   [x] means hitArea
        // b1 |
        //    |
        //   b2
        // b2从下方追赶b1
        return (b2.lastY - b1.lastY - BULLET_SIZE) / (b2.speed - b1.speed)
      } else {
        // 递归 b1从下方追赶b2
        return calculateHitTime(b2, b1)
      }
    } else if (b2.direction === 'left') {
      // [x] <--- b2
      //  ↑
      //  |        [x] means hitArea
      //  b1
      const hitArea = { x: b1.x, y: b2.y, width: BULLET_SIZE, height: BULLET_SIZE }

      const time1 = (b1.lastY - b2.lastY - BULLET_SIZE) / b1.speed
{'fraction_non_alphanumeric': 0.14740497781207795, 'fraction_numerical': 0.006945784294809956, 'mean_word_length': 2.527390268798911, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1747792', 'n_tokens_mistral': 4163, 'n_tokens_neox': 4171, 'n_words': 835}