Skip to content

Commit

Permalink
feat(integration): change chess engine service api and integrate the …
Browse files Browse the repository at this point in the history
…chess application with the chess engine service

---
Chess Engine API
The contract of the chess engine service (alias ChessPort) has been updated according to the new design.
In addition:
- a getState functionality has been added to retrieve the state of the game on demand
- each functionality now returns a Future, making all functionalities asynchronous, as it should be in a distributed
  system
The implementation of such contract has been updated and moved to io.github.chess.model.ChessGame.

The ChessBoard now acts as a simple container for the chess pieces, exposing functionalities for updating its
internal data structure.
In addition:
- a ChessBoardBuilder has been added to preconfigure the creation of a chess board
- a ChessBoardBuilder.DSL has been designed to speed up the creation of a chess board and making it easier to
  understand (see io.github.chess.model.{ChessBoardBuilderDSLSpec, ChessBoardBuilderSpec, ChessBoardSpec} as
  examples on how to use it)
A standard configuration of a chess board has been provided with placeholder pieces.

Minor refactoring has been applied to some of the dependencies of the service.
---
Chess Application
The chess game interface has been renamed to ChessApplication. Now, the chess application does not depend
directly on the chess board, but is uses a LocalChessProxy for interacting with the chess engine service.
Such proxy is provided together with the main stage of the application in the ChessApplicationContext,
which is unique for the entire application and it is provided as a given to any ChessApplicationComponent.

Other components of the application has been updated according to the new chess engine api.
---
Other
An OptionExtension has been added in the utilities. This extension provides the following functionalities:
- a given conversion that maps any object to an option containing that object
- a getOrThrow method, which returns the content of an option or throws an exception

A Require object has been added in the utilities. This object provides many methods for throwing exceptions
in a more declarative way.
---
Fix
ScalaFormatter now runs before each build, run or test.

The compiler options of the scala compiler has been changes:
- added -feature: this shows feature warnings when a scala feature that requires to be activated explicitly
  has been used without doing so
- added -language:implicitConversions: this allows to use given conversions without throwing feature warnings
  • Loading branch information
jahrim committed Feb 17, 2023
1 parent af695ec commit 9b3ea0b
Show file tree
Hide file tree
Showing 30 changed files with 905 additions and 201 deletions.
14 changes: 11 additions & 3 deletions chess/build.gradle.kts
Expand Up @@ -34,20 +34,28 @@ application {

// Code Style (aesthetic...)
spotless {
isEnforceCheck = false
scala {
scalafmt(libs.versions.scalafmt.version.get()).configFile(".scalafmt.conf")
licenseHeaderFile(file("../LICENSE-HEADER"), "package ")
}
// always apply formatting when building the project
tasks.spotlessCheck.get().dependsOn(tasks.spotlessApply)
// always apply formatting before building, running or testing the project
tasks.compileScala.get().dependsOn(tasks.spotlessApply)
}

// Code Linting (error prevention...)
val wartRemoverCompileOptions = WartRemover.configFile(file(".wartremover.conf")).toCompilerOptions()

// Scala Compiler Options
tasks.withType(ScalaCompile::class.java) {
scalaCompileOptions.additionalParameters = listOf("-Xtarget:8", "-indent", "-rewrite") + wartRemoverCompileOptions
scalaCompileOptions.additionalParameters =
listOf(
"-Xtarget:8",
"-indent",
"-rewrite",
"-feature",
"-language:implicitConversions"
) + wartRemoverCompileOptions
}

// Scala Test
Expand Down
51 changes: 43 additions & 8 deletions chess/src/main/scala/io/github/chess/Main.scala
Expand Up @@ -6,18 +6,53 @@
*/
package io.github.chess

import io.github.chess.adapters.Adapter
import io.github.chess.model.ChessGame
import io.github.chess.ports.ChessPort
import io.github.chess.services.ChessService
import io.github.chess.util.debug.Logger
import io.github.chess.viewcontroller.ChessGameInterface
import io.vertx.core.Vertx
import io.github.chess.viewcontroller.{ChessApplication, ChessLocalProxy}
import io.vertx.core.{Future, Promise, Vertx}

/** The main application. */
@main def main(): Unit =
Logger.info("Start", "Application started...")
deployServiceLocally()
.map {
deployApplicationLocally(_)
}
.onFailure { error =>
error.printStackTrace()
System.exit(1)
}

/**
* Deploys the chess engine service locally.
* @return a future that completes when the chess engine service has been successfully deployed
*/
def deployServiceLocally(): Future[ChessService] =
Logger.info("Start", "Deploying chess engine service...")
val vertx = Vertx.vertx()
val service = ChessService(ChessPort(vertx))
vertx.deployVerticle(service)
ChessGameInterface.launch(
/* TODO: insert a proxy/adapter for the chess engine service here */ Array.empty
)
val service = ChessService(ChessGame(vertx))
val serviceDeployed: Promise[ChessService] = Promise.promise()
vertx
.deployVerticle(service)
.onSuccess { _ =>
Logger.info("Start", "Chess engine service deployed.")
serviceDeployed.complete(service)
}
.onFailure { error =>
Logger.error("Start", "Chess engine service failed to deploy.")
serviceDeployed.fail(error)
}
serviceDeployed.future()

/**
* Deploys the chess game application locally.
* @param service the chess engine service required by the chess game application
*/
def deployApplicationLocally(service: ChessService): Unit =
service.localAdapter.foreach { localAdapter =>
Logger.info("Start", "Starting application...")
ChessApplication.launch(ChessLocalProxy(localAdapter.port))(Array.empty)
Logger.info("Start", "Application started.")
}
Expand Up @@ -6,13 +6,11 @@
*/
package io.github.chess.adapters

import io.vertx.core.Vertx

/** Represents an adapter to help view communicate with the logic. */
trait AbstractAdapter[Port]:
/**
* Represents an adapter that allows to interact with a specific port of a service,
* exposing the port through a specific technology (i.e. HTTP, MQTT...).
*/
trait Adapter[Port]:

/**
* Returns the port (controller) of the adapter
* @return the port (controller)
*/
def port: Port
/** @return the port exposed by this adapter */
protected def port: Port
Expand Up @@ -9,5 +9,8 @@ package io.github.chess.adapters
import io.github.chess.ports.ChessPort
import io.vertx.core.Vertx

/** Helps the view communicate with the chess game. */
class ChessAdapter(override val port: ChessPort) extends AbstractAdapter[ChessPort]
/**
* Represents an adapter that allows to interact with a specific port
* of a service, exposing the port to local interactions.
*/
class ChessLocalAdapter(override val port: ChessPort) extends Adapter[ChessPort]
119 changes: 68 additions & 51 deletions chess/src/main/scala/io/github/chess/model/ChessBoard.scala
Expand Up @@ -7,79 +7,96 @@
package io.github.chess.model

import io.github.chess.events.EndTurnEvent
import io.github.chess.model.ChessBoardBuilder.DSL.*
import io.github.chess.model.Team.{BLACK, WHITE}
import io.vertx.core.Vertx

/** The trait representing the concept of a Chess Board. */
trait ChessBoard:

/**
* Gives access to all the [[Piece]]s that are present on the board.
* @return the map containing both white and black [[Piece]]s of the board
*/
def pieces: Map[Position, Piece]

/**
* Gives all the available positions for a pieces placed in a specified position.
* @param position the [[Position]] where the piece to be moved is placed
* @return all the available positions that could be performed by the piece
* Updates the chess board assigning the specified piece to the specified position.
* @param position the specified position
* @param piece the specified piece
*/
def findMoves(position: Position): Set[Position]
def setPiece(position: Position, piece: Piece): Unit

/**
* Performs the move by a piece on the board.
* @param move The [[Move]] to be executed
* Updates the chess board removing the piece at the specified position.
* @param position the specified position
*/
def move(move: Move): Unit

/** Factory for [[ChessBoard]] instances. */
object ChessBoard:
def removePiece(position: Position): Unit

/**
* Creates a new Chess Board.
* @return a new [[ChessBoard]]
* Updates the chess board applying the specified updates.
* @param updates a list of updates, each mapping a certain position to a new piece,
* or an empty optional if the piece has to be removed from that position
* @example
* {{{
* chessBoard.update(
* Position(A, _5) -> Pawn(),
* Position(B, _8) -> None,
* Position(E, _3) -> Knight(),
* )
* }}}
*/
def apply(vertx: Vertx): ChessBoard = ChessBoardImpl(vertx)

private case class ChessBoardImpl(private val vertx: Vertx) extends ChessBoard:
import scala.collection.immutable.HashMap

private var whitePieces: Map[Position, Piece] =
Map.empty + ((Position(File.A, Rank._2), Pawn()))
private var blackPieces: Map[Position, Piece] = HashMap()
private var currentlyPlayingTeam = Team.WHITE
def update(updates: (Position, Option[Piece])*): Unit =
updates.foreach {
case (position, Some(piece)) => setPiece(position, piece)
case (position, _) => removePiece(position)
}

override def pieces: Map[Position, Piece] = this.whitePieces ++ this.blackPieces

override def findMoves(position: Position): Set[Position] =
val team = playingTeam
val selectedPiece = team.get(position)
selectedPiece match
case Some(piece) => piece.findMoves(position)
case None => Set.empty
/** Companion object of [[ChessBoard]]. */
object ChessBoard:
export io.github.chess.model.ChessBoardBuilder.*

override def move(move: Move): Unit =
if findPiece(move.from).isDefined then
val newTeam = this.applyMove(move)
this.currentlyPlayingTeam match
case WHITE => this.whitePieces = newTeam
case BLACK => this.blackPieces = newTeam
// TODO: changePlayingTeam()
/** The length of the edges of the chess board. */
val Size: Int = 8

private def playingTeam: Map[Position, Piece] = this.currentlyPlayingTeam match
case WHITE => this.whitePieces
case BLACK => this.blackPieces
/** The total number of positions in the chess board. */
val NumberOfPositions: Int = ChessBoard.Size * ChessBoard.Size

private def changePlayingTeam(): Unit =
this.currentlyPlayingTeam = this.currentlyPlayingTeam.oppositeTeam
val endTurnEvent = EndTurnEvent(this.currentlyPlayingTeam)
vertx.eventBus().publish(endTurnEvent.address, endTurnEvent)
/** All the possible positions in the chess board. */
lazy val Positions: Iterable[Position] =
for
i <- 0 until ChessBoard.Size
j <- 0 until ChessBoard.Size
yield (i, j)

private def findPiece(pos: Position): Option[Piece] = playingTeam.get(pos)
/** Alias for [[ChessBoard.empty]]. */
def apply(): ChessBoard = ChessBoard.empty

private def applyMove(move: Move): Map[Position, Piece] =
val team = playingTeam
val pieceToMove = findPiece(move.from)
pieceToMove match
case Some(piece) => team - move.from + ((move.to, piece))
case None => team
/**
* @param builderConfiguration the context of the specified configuration
* @return a custom chess board initialized using a builder with the specified configuration
*/
def apply(builderConfiguration: ChessBoardBuilder ?=> ChessBoardBuilder): ChessBoard =
ChessBoardBuilder.configure(builderConfiguration).build

/** @return an empty chess board with no pieces on top of it */
def empty: ChessBoard = BasicChessBoard()

/** @return a chess board initialized for a standard game of chess. */
def standard: ChessBoard = ChessBoard {
r | n | b | q | k | b | n | r
p | p | p | p | p | p | p | p
* | * | * | * | * | * | * | *
* | * | * | * | * | * | * | *
* | * | * | * | * | * | * | *
* | * | * | * | * | * | * | *
P | P | P | P | P | P | P | P
R | N | B | Q | K | B | N | R
}

/** Basic implementation of a chess board. */
private case class BasicChessBoard(private var _pieces: Map[Position, Piece] = Map.empty)
extends ChessBoard:
override def pieces: Map[Position, Piece] = this._pieces
override def setPiece(position: Position, piece: Piece): Unit =
this._pieces += position -> piece
override def removePiece(position: Position): Unit = this._pieces -= position

0 comments on commit 9b3ea0b

Please sign in to comment.