jcbantuelle/dominion-meteor

View on GitHub
app/game/server/services/turn_ender.js

Summary

Maintainability
D
3 days
Test Coverage
TurnEnder = class TurnEnder {

  constructor(game, player_cards) {
    this.game = game
    this.player_cards = player_cards
  }

  end_turn() {
    this.game.previous_state = false
    if (this.game.turn.phase === 'action') {
      this.start_buy_events()
    }
    this.end_buy_events()
    this.game.turn.phase = 'cleanup'
    this.start_cleanup_events()
    this.discard_hand()
    this.clean_up_cards_in_play()
    this.draw_new_hand()
    this.end_turn_events()
    this.track_turn_cards()
    this.game.log.push(`<strong>${this.game.turn.player.username}</strong> ends their turn`)
    if (!_.isEmpty(this.player_cards.inheritance)) {
      Inheritance.convert_estates(this.game, this.player_cards, false)
    }
    if (this.player_cards.capitalism) {
      Capitalism.convert_cards(this.game, this.player_cards, false)
    }
    if (this.game.game_over) {
      delete this.player_cards.fleet
      PlayerCardsModel.update(this.game._id, this.player_cards)
    }
    if (this.game_over()) {
      this.end_game()
    } else {
      GameModel.update(this.game._id, this.game)
      PlayerCardsModel.update(this.game._id, this.player_cards)
      if (this.game.turn.possessed) {
        delete this.game.turn.possessed
        GameModel.update(this.game._id, this.game)
      }
      this.flip_trash_cards_face_up()
      this.between_turn_events()
      this.set_next_turn()
      this.clear_duration_attacks()
      GameModel.update(this.game._id, this.game)
      if (!_.isEmpty(this.next_player_cards.inheritance)) {
        Inheritance.convert_estates(this.game, this.next_player_cards, true)
      }
      if (this.next_player_cards.capitalism) {
        Capitalism.convert_cards(this.game, this.next_player_cards, true)
      }
      this.start_turn_events()
      this.update_db(false)
    }
  }

  update_db(update_current_player = true) {
    GameModel.update(this.game._id, this.game)
    if (this.player_cards._id !== this.next_player_cards._id && update_current_player) {
      PlayerCardsModel.update(this.game._id, this.player_cards)
    }
    PlayerCardsModel.update(this.game._id, this.next_player_cards)
  }

  start_buy_events() {
    this.game.turn.phase = 'treasure'
    let start_buy_event_processor = new StartBuyEventProcessor(this.game, this.player_cards)
    start_buy_event_processor.process()
    this.game.turn.phase = 'buy'
  }

  end_buy_events() {
    let end_buy_event_processor = new EndBuyEventProcessor(this.game, this.player_cards)
    end_buy_event_processor.process()
  }

  start_cleanup_events() {
    let start_cleanup_event_processor = new StartCleanupEventProcessor(this.game, this.player_cards)
    start_cleanup_event_processor.process()
  }

  end_turn_events() {
    let ordered_player_cards = TurnOrderedPlayerCardsQuery.turn_ordered_player_cards(this.game)
    ordered_player_cards.shift()
    ordered_player_cards.unshift(this.player_cards)
    _.each(ordered_player_cards, (player_cards) => {
      let end_turn_event_processor = new EndTurnEventProcessor(this.game, player_cards)
      end_turn_event_processor.process()
    })
  }

  between_turn_events() {
    let between_turn_event_processor = new BetweenTurnEventProcessor(this.game, this.player_cards)
    between_turn_event_processor.process()
  }

  flip_trash_cards_face_up() {
    _.each(this.game.trash, function(card) {
      delete card.face_down
    })
  }

  clean_up_cards_in_play() {
    let cards_to_discard = _.filter(this.player_cards.in_play, (card) => {
      return !ClassCreator.create(card.name).stay_in_play(this.game, this.player_cards, card)
    })
    let card_discarder = new CardDiscarder(this.game, this.player_cards, 'in_play', cards_to_discard)
    card_discarder.discard(false)
    Prince.unset_prince_tracking(this.game, this.player_cards)

    if (this.game.turn.possessed && !_.isEmpty(this.player_cards.possession_trash)) {
      this.player_cards.discard = this.player_cards.discard.concat(this.player_cards.possession_trash)
      this.game.log.push(`<strong>${this.player_cards.username}</strong> puts ${CardView.render(this.player_cards.possession_trash)} in their discard`)
      this.player_cards.possession_trash = []
    }

    let card_mover = new CardMover(this.game, this.player_cards)
    card_mover.move_all(this.player_cards.boons, this.game.boons_discard)
  }

  discard_hand() {
    let card_discarder = new CardDiscarder(this.game, this.player_cards, 'hand')
    card_discarder.discard(false)
  }

  draw_new_hand() {
    let flag = _.find(this.player_cards.artifacts, (artifact) => {
      return artifact.name === 'Flag'
    })
    let cards_to_draw = this.game.turn.outpost ? 3 : 5
    if (flag) {
      cards_to_draw += 1
    }
    let card_drawer = new CardDrawer(this.game, this.player_cards)
    card_drawer.draw(cards_to_draw, false)
  }

  track_turn_cards() {
    this.player_cards.last_turn_gained_cards = this.game.turn.gained_cards
    this.player_cards.last_turn_trashed_cards = this.game.turn.trashed_cards
  }

  set_next_turn() {
    this.new_turn = GameCreator.new_turn()
    this.new_turn.previous_player = this.game.turn.player

    this.set_up_extra_turns()

    if (!_.isEmpty(this.game.extra_turns)) {
      this.process_extra_turns()
    } else {
      this.next_player_turn()
    }

    this.next_player_cards = this.find_next_player_cards()
    if (!this.new_turn.extra_turn) {
      this.next_player_cards.turns += 1
    }
    this.game.turn = this.new_turn
  }

  clear_duration_attacks() {
    let ordered_player_cards = TurnOrderedPlayerCardsQuery.turn_ordered_player_cards(this.game)
    _.each(ordered_player_cards, (player_cards) => {
      if (player_cards.player_id === this.player_cards.player_id) {
        player_cards = this.player_cards
      } else if (player_cards.player_id === this.next_player_cards.player_id) {
        player_cards = this.next_player_cards
      }
      player_cards.duration_attacks = _.reject(player_cards.duration_attacks, (attack) => {
        return attack.player_source._id === this.next_player_cards.player_id
      })
      PlayerCardsModel.update(this.game._id, player_cards)
    })
  }

  set_up_extra_turns() {
    this.set_up_extra_player_turns()
    this.set_up_possession_turns()
    this.set_player_after_extra_turns(this.game.turn.player)
  }

  set_up_extra_player_turns() {
    let queued_turns = []
    if (this.game.turn.outpost) {
      queued_turns.push(this.game.turn.outpost)
    }
    if (this.game.turn.mission) {
      queued_turns.push(this.game.turn.mission)
    }
    if (this.game.turn.seize_the_day) {
      queued_turns.push(this.game.turn.seize_the_day)
    }
    if (!_.isEmpty(queued_turns)) {
      if (this.is_possessed_next_turn()) {
        queued_turns.push(ClassCreator.create('Possession').to_h())
      }

      if (_.size(queued_turns) === 1) {
        this.game.extra_turns.push({type: queued_turns[0].name, player: this.game.turn.player})
      } else {
        let turn_event_id = TurnEventModel.insert({
          game_id: this.game._id,
          player_id: this.player_cards.player_id,
          username: this.player_cards.username,
          type: 'sort_cards',
          instructions: 'Choose order to resolve extra turns: (leftmost will be first)',
          cards: queued_turns
        })
        let turn_event_processor = new TurnEventProcessor(this.game, this.player_cards, turn_event_id)
        turn_event_processor.process(TurnEnder.process_extra_player_turns_order)
      }
    }
  }

  set_up_possession_turns() {
    _.times(this.game.turn.possessions, () => {
      this.game.extra_turns.push({type: 'Possession', player: this.game.turn.player})
    })
  }

  is_possessed_next_turn() {
    let next_extra_turn = _.head(this.game.extra_turns)
    if (next_extra_turn && next_extra_turn.type === 'Possession') {
      let next_player_query = new NextPlayerQuery(this.game, next_extra_turn.player._id)
      return next_player_query.next_player()._id === this.game.turn.player._id
    } else {
      return false
    }
  }

  static process_extra_player_turns_order(game, player_cards, ordered_extra_turns) {
    ordered_extra_turns = ordered_extra_turns.reverse()
    let possession_index = _.findIndex(ordered_extra_turns, function(turn) {
      return turn.name === 'Possession'
    })
    _.each(ordered_extra_turns, function(turn, turn_index) {
      if (turn.name !== 'Possession') {
        let next_extra_turn = {type: turn.name, player: game.turn.player}
        if (turn_index < possession_index) {
          let possession_turn_index = _.findIndex(game.extra_turns, function(extra_turn) {
            return extra_turn.type.name === 'Possession'
          })
          game.extra_turns.splice(possession_turn_index + 1, 0, next_extra_turn)
        } else {
          game.extra_turns.unshift(next_extra_turn)
        }
      }
    })
  }

  set_player_after_extra_turns(player) {
    if (!_.isEmpty(this.game.extra_turns) && !this.game.player_after_extra_turns) {
      let next_player_query = new NextPlayerQuery(this.game, player._id)
      this.game.player_after_extra_turns = next_player_query.next_player()
    }
  }

  process_extra_turns() {
    let extra_turn = this.game.extra_turns.shift()
    if (extra_turn.type === 'Outpost') {
      this.outpost_turn(extra_turn.player)
    } else if (extra_turn.type === 'Mission') {
      this.mission_turn(extra_turn.player)
    } else if (extra_turn.type === 'Possession') {
      this.possession_turn(extra_turn.player)
    } else if (extra_turn.type === 'Seize The Day') {
      this.seize_the_day_turn(extra_turn.player)
    }
  }

  find_next_player_cards() {
    if (this.new_turn.player._id === this.player_cards.player_id) {
      return this.player_cards
    } else {
      return PlayerCardsModel.findOne(this.game._id, this.new_turn.player._id)
    }
  }

  outpost_turn(player) {
    this.new_turn.player = player
    this.new_turn.extra_turn = true
    this.game.log.push(`<strong>- ${this.new_turn.player.username} gets an extra turn from ${CardView.render(new Outpost())} -</strong>`)
  }

  seize_the_day_turn(player) {
    this.new_turn.player = player
    this.new_turn.extra_turn = true
    this.game.log.push(`<strong>- ${this.new_turn.player.username} gets an extra turn from ${CardView.render(new SeizeTheDay())} -</strong>`)
  }

  mission_turn(player) {
    this.new_turn.player = player
    this.new_turn.extra_turn = true
    this.new_turn.mission_turn = true
    this.game.log.push(`<strong>- ${this.new_turn.player.username} gets an extra turn from ${CardView.render(new Mission())} -</strong>`)
  }

  possession_turn(player) {
    let next_player_query = new NextPlayerQuery(this.game, player._id)
    this.new_turn.player = next_player_query.next_player()
    this.new_turn.possessed = player
    this.new_turn.extra_turn = true
    this.game.log.push(`<strong>- ${this.new_turn.player.username} gets an extra turn possessed by ${this.new_turn.possessed.username} -</strong>`)
  }

  next_player_turn() {
    if (this.game.player_after_extra_turns) {
      this.new_turn.player = this.game.player_after_extra_turns
      delete this.game.player_after_extra_turns
    } else {
      let next_player_query = new NextPlayerQuery(this.game, this.game.turn.player._id)
      this.new_turn.player = next_player_query.next_player()
    }
    this.new_turn.extra_turn = false
    this.game.turn_number += 1
    if (this.game.game_over) {
      this.game.log.push(`<strong>- ${this.new_turn.player.username} takes an extra turn from ${CardView.render(new Fleet())} -</strong>`)
    } else {
      this.game.log.push(`<strong>- ${this.new_turn.player.username}'s turn ${this.player_turn_number()} -</strong>`)
    }
  }

  start_turn_events() {
    let start_turn_event_processor = new StartTurnEventProcessor(this.game, this.next_player_cards)
    start_turn_event_processor.process()
  }

  player_turn_number() {
    return Math.floor((this.game.turn_number-1) / _.size(this.game.players)) + 1
  }

  end_game() {
    let game_ender = new GameEnder(this.game)
    game_ender.end_game()
  }

  game_over() {
    let end_game_trigger = this.game.game_over || this.three_empty_stacks() || this.no_more_provinces_or_colonies()
    let fleet_game = _.find(this.game.projects, (project) => {
      return project.name === 'Fleet'
    })
    if (end_game_trigger && fleet_game) {
      this.game.game_over = true
      let ordered_player_cards = TurnOrderedPlayerCardsQuery.turn_ordered_player_cards(this.game)
      let fleet_turns = _.filter(ordered_player_cards, (player_cards) => {
        return player_cards.fleet
      })
      this.game.fleet = !_.isEmpty(fleet_turns)
    }
    return end_game_trigger && !this.game.fleet
  }

  three_empty_stacks() {
    return _.size(this.empty_stacks()) >= 3
  }

  no_more_provinces_or_colonies() {
    return _.find(this.empty_stacks(), function(game_card) {
      return game_card.name === 'Province' || game_card.name === 'Colony'
    }) !== undefined
  }

  empty_stacks() {
    return _.filter(this.game.cards, function(game_card) {
      return game_card.count === 0 && game_card.supply
    })
  }

}