Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ console:
clean:
${SBT} clean

.PHONY: build test console clean
bench: clean
${SBT} mogCoreJVM/test:run mogCoreJS/test:run

.PHONY: build test console clean bench

27 changes: 27 additions & 0 deletions js/src/test/scala/com/mogproject/mogami/bench/BenchmarkJS.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.mogproject.mogami.bench

import com.mogproject.mogami.core.BitBoard
import com.mogproject.mogami.core.Ptype.{PROOK, ROOK}
import com.mogproject.mogami.core.PieceConstant._
import com.mogproject.mogami.core.SquareConstant._


/**
* Benchmarks for Scala.js
*/
object BenchmarkJS extends scalajs.js.JSApp with Benchmark with TestData {
def main(): Unit = {
benchAttack(BP, None, BitBoard.empty, BitBoard.empty)

benchAttack(BP, Some(P55), BitBoard.empty, BitBoard.empty)

benchAttack(BPR, Some(P11), BitBoard.empty, BitBoard.empty)
benchAttack(BPR, Some(P11), BitBoard.full, BitBoard.full)

benchAttack(BPB, Some(P55), BitBoard.empty, BitBoard.empty)
benchAttack(BPB, Some(P55), BitBoard.full, BitBoard.full)

benchGameLoading(recordSfen01)
benchGameLoading(recordSfen02)
}
}
19 changes: 15 additions & 4 deletions shared/src/main/scala/com/mogproject/mogami/core/Game.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,25 @@ import scala.util.Try
case class Game(initialState: State = State.HIRATE,
moves: Seq[Move] = Seq.empty,
gameInfo: GameInfo = GameInfo(),
movesOffset: Int = 0
movesOffset: Int = 0,
givenHistory: Option[Seq[State]] = None
) extends CsaLike with SfenLike {

require(history.length == moves.length + 1, "all moves must be valid")

import com.mogproject.mogami.core.Game.GameStatus._

override def equals(obj: scala.Any): Boolean = obj match {
case that: Game =>
// ignore givenHistory
initialState == that.initialState && moves == that.moves && gameInfo == that.gameInfo && movesOffset == that.movesOffset
case _ => false
}

/** history of states */
lazy val history: Seq[State] = moves.scanLeft(Some(initialState): Option[State])((s, m) => s.flatMap(_.makeMove(m))).flatten
lazy val history: Seq[State] = {
givenHistory.getOrElse(moves.scanLeft(Some(initialState): Option[State])((s, m) => s.flatMap(_.makeMove(m))).flatten)
}

lazy val hashCodes: Seq[Int] = history.map(_.hashCode())

Expand Down Expand Up @@ -49,8 +59,9 @@ case class Game(initialState: State = State.HIRATE,

def turn: Player = currentState.turn

def makeMove(move: Move): Option[Game] =
(status == Playing && currentState.isValidMove(move)).option(this.copy(moves = moves :+ move))
def makeMove(move: Move): Option[Game] = {
(status == Playing && currentState.isValidMove(move)).option(this.copy(moves = moves :+ move, givenHistory = currentState.makeMove(move).map(history :+ _)))
}

def makeMove(move: MoveBuilder): Option[Game] = move.toMove(currentState).flatMap(makeMove)

Expand Down
2 changes: 2 additions & 0 deletions shared/src/main/scala/com/mogproject/mogami/core/Move.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ case class Move(player: Player,

def capturedPiece: Option[Piece] = captured.map(Piece(!player, _))

def moveFrom: MoveFrom = from.map(Left.apply).getOrElse(Right(Hand(player, oldPtype)))

override def toCsaString: String =
from.map(fr => MoveBuilderCsaBoard(player, fr, to, newPtype, elapsedTime))
.getOrElse(MoveBuilderCsaHand(player, to, newPtype, elapsedTime)).toCsaString
Expand Down
34 changes: 17 additions & 17 deletions shared/src/main/scala/com/mogproject/mogami/core/State.scala
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,12 @@ case class State(turn: Player = BLACK, board: BoardType = Map.empty, hand: HandT
m.mapValues(_ & ~occupancy(turn)).filter(_._2.nonEmpty)
}

// todo: consider converting to Set
/**
* Get all legal moves.
*
* @note This method can be relatively expensive.
* @return list of legal moves
*/
def legalMoves: Seq[Move] = (
for {
(from, bb) <- legalMovesBB
Expand All @@ -186,14 +191,6 @@ case class State(turn: Player = BLACK, board: BoardType = Map.empty, hand: HandT
mv <- from.fold(MoveBuilderSfenBoard(_, to, promote), p => MoveBuilderSfenHand(p.ptype, to)).toMove(this)
} yield mv).toSeq

/**
* Check if the move is legal.
*
* @param move move to test
* @return true if the move is legal
*/
def isValidMove(move: Move): Boolean = legalMoves.contains(move.copy(elapsedTime = None))

/** *
* Check if the state is mated.
*
Expand Down Expand Up @@ -250,6 +247,17 @@ case class State(turn: Player = BLACK, board: BoardType = Map.empty, hand: HandT
case None => List()
}

/**
* Check if the move is legal.
*
* @param move move to test
* @return true if the move is legal
*/
def isValidMove(move: Move): Boolean = {
val mf = move.moveFrom
canAttack(mf, move.to) && getPromotionList(mf, move.to).contains(move.promote)
}

/**
* Check if the in-hand piece is non-empty.
*/
Expand Down Expand Up @@ -281,14 +289,6 @@ object State extends CsaStateReader with SfenStateReader {
val empty = State(BLACK, Map.empty, EMPTY_HANDS)
lazy val capacity: Map[Ptype, Int] = Map(PAWN -> 18, LANCE -> 4, KNIGHT -> 4, SILVER -> 4, GOLD -> 4, BISHOP -> 2, ROOK -> 2, KING -> 2)

/**
* Get the square where the turn-to-move player's king.
*
* @return None if the king is not on board
*/
def getKingSquare(player: Player, board: BoardType): Option[Square] =
board.view.filter { case (s, p) => p == Piece(player, KING) }.map(_._1).headOption

// constant states
val HIRATE = State(BLACK, Map(
Square(1, 1) -> Piece(WHITE, LANCE),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.mogproject.mogami.util

object BenchmarkUtil {
def withTime[A](label: String)(thunk: => A): A = {
val start = System.currentTimeMillis()
val ret = thunk
val end = System.currentTimeMillis()
println(s"${label}: ${(end - start) / 1000.0}s")
ret
}

}
57 changes: 57 additions & 0 deletions shared/src/test/scala/com/mogproject/mogami/bench/Benchmark.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.mogproject.mogami.bench

import com.mogproject.mogami._

import scala.io.Source

/**
* Shared benchmark utility
*/
case class BenchResult(result: Seq[Double]) {
def average: Double = if (result.isEmpty) 0.0 else result.sum / result.length / 1000

def maxValue: Double = result.max / 1000

def minValue: Double = result.min / 1000

def print(): Unit = {
println(f"\n- avg: ${average}%.3fs, min: ${minValue}s, max: ${maxValue}s\n")
}
}

trait Benchmark {

val benchmarkCount = 3
val attackRepeat = 10000

private[this] def withBenchmark(thunk: => Unit): BenchResult = {
val ret = (1 to benchmarkCount).map { n =>
val start = System.currentTimeMillis()
thunk
(System.currentTimeMillis() - start).toDouble
}
BenchResult(ret)
}

def benchGameLoading(sfen: String): Unit = {
println(s"benchGameLoading: sfen=${sfen}")

withBenchmark {
val g = Game.parseSfenString(sfen)
assert(g.isDefined)
}.print()
}

def benchAttack(piece: Piece, from: Option[Square], allOcc: BitBoard, myPawnOcc: BitBoard): Unit = {
println(s"benchAttack: repeat=${attackRepeat}, piece=${piece}, from=${from}, allOcc=${allOcc.toOctalString}, myPawnOcc=${myPawnOcc.toOctalString}")

withBenchmark {
var i = 0
while (i < attackRepeat) {
Attack.get(piece, from, allOcc, myPawnOcc)
i += 1
}
}.print()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.mogproject.mogami.bench

/**
* Benchmarks for Scala JVM
*/
object BenchmarkJVM extends Benchmark with TestData {

def main(args: Array[String]): Unit = {
benchGameLoading(recordSfen01)
benchGameLoading(recordSfen02)
}

}
13 changes: 13 additions & 0 deletions shared/src/test/scala/com/mogproject/mogami/bench/TestData.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.mogproject.mogami.bench

trait TestData {
val recordSfen01: String = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 0"
val recordSfen02: String = Seq(
"lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 0",
" 7g7f 3c3d 2g2f 4a3b 6i7h 8c8d 2f2e 2b8h+ 7i8h 3a2b",
" 3i3h 2b3c 3g3f 7a7b 5i6h 6c6d 3h3g 8d8e 3g4f 8e8f",
" 8g8f 8b8f 2e2d 2c2d 8i7g 8f8b 3f3e 3d3e 4f3e 7c7d",
" 3e2d 3c2d 2h2d P*2c 2d2f 7d7e P*8c 7b8c B*6c B*7d",
" 6c1h+ 7d4g+ 4i5h 4g1d"
).mkString
}