summaryrefslogtreecommitdiff
path: root/src/main/scala/me/robbyzambito/othello/game/Player.scala
blob: c55f6db3f677a5c36eed5a4a6fb479e3516cb7a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package me.robbyzambito.othello.game

import scala.util.Try

/**
 * Represents a party which is partaking in the game.
 * Both the AI and the end user are instances of Player.
 *
 * Written by Robby Zambito
 * Written on 11/20/2019
 * Targeting Scala 2.13.1
 *
 * @param color The color of the player.
 */
abstract class Player(val color: Position) {

  /**
   * The players decision on where to move based on
   * the current board. It is assumed that there is
   * at least one valid move.
   *
   * @param board
   * @return
   */
  def nextMove(board: Board): Move

  def canMove(board: Board): Boolean =
    possibleMoves(board).nonEmpty

  def possibleMoves(board: Board): List[Move] = {
    val enemyPosition = if (color == Position.WHITE) Position.BLACK else Position.WHITE

    def possibleAcross(rowCount: Int, colCount: Int): Option[Move] = {
      val row = board.positions(rowCount)

      /**
       * Return the list of positions which will be flipped if a move is made at row(rowCount, pos)
       *
       * @param pos  Offset in the current row
       * @param step Direction which to check for the next piece.
       * @param isPossible
       * @return List of positions which will change if one is placed. Empty list means move is invalid.
       */
      def checkInDirection(pos: Int, step: Int, isPossible: Boolean = false): List[(Int, Int)] = {
        if (pos == colCount && row(pos) != Position.EMPTY) {
          List.empty
        } else if (isPossible && Try(row(pos + step)).getOrElse(Position.EMPTY) == color) {
          List((rowCount, pos))
        } else if (Try(row(pos + step)).getOrElse(Position.EMPTY) == enemyPosition) {
          val nextDirections = checkInDirection(pos + step, step, isPossible = true)
          if (nextDirections.nonEmpty)
            (rowCount, pos) :: nextDirections
          else List.empty
        } else List.empty
      }

      // Check can move left or right
      val changes = checkInDirection(colCount, 1) ::: checkInDirection(colCount, -1)
      if (changes.nonEmpty)
        Some(Move(rowCount, colCount, changes))
      else None
    }

    def possibleVertical(rowCount: Int, colCount: Int): Option[Move] = {
      val col = board.positions.indices.map(i => board.positions(i)(colCount))


      def checkInDirection(pos: Int, step: Int, isPossible: Boolean = false): List[(Int, Int)] = {
        if (pos == rowCount && col(pos) != Position.EMPTY) {
          List.empty
        } else if (isPossible && Try(col(pos + step)).getOrElse(Position.EMPTY) == color) {
          List((pos, colCount))
        } else if (Try(col(pos + step)).getOrElse(Position.EMPTY) == enemyPosition) {
          val nextDiections = checkInDirection(pos + step, step, isPossible = true)
          if (nextDiections.nonEmpty)
            (pos, colCount) :: nextDiections
          else List.empty
        } else List.empty
      }

      // Check can move down or up
      val changes = checkInDirection(rowCount, 1) ::: checkInDirection(rowCount, -1)
      if (changes.nonEmpty)
        Some(Move(rowCount, colCount, changes))
      else None
    }


    (for (rowCount <- board.positions.indices) yield {
      (for (colCount <- board.positions(rowCount).indices) yield {
        val (acc, vert) = (possibleAcross(rowCount, colCount), possibleVertical(rowCount, colCount))

        if (acc.isDefined || vert.isDefined)
          Some(
            Move(rowCount,
              colCount,
              (acc.map(_.takenPositions).getOrElse(List.empty) :::
                vert.map(_.takenPositions).getOrElse(List.empty)).distinct)
          )
        else None
      }).flatten
    }).flatten
      .toList
  }

  override def toString: String = color match {
    case Position.WHITE => "White"
    case Position.BLACK => "Black"
    case _ => "Default"
  }

}